首页三栏布局 + 悬浮球可拖拽 + 动态公告ticker + 留言板/回执系统 + 后端feedback API

Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/32fa4778-f82b-4742-b89e-3b3af9529167

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-02 14:46:40 +00:00 committed by GitHub
parent cc77fbbbf5
commit 3814cf3d89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 418 additions and 25 deletions

View File

@ -187,6 +187,55 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh
.csnd:hover{background:var(--accent2)}
.csnd:disabled{opacity:.3}
/* ═══ Three-Column Home Layout ═══ */
.home-grid{display:grid;grid-template-columns:260px 1fr 260px;gap:16px;padding:16px;max-width:1200px;margin:0 auto;align-content:start}
@media(max-width:960px){.home-grid{grid-template-columns:1fr;max-width:600px}}
.panel{background:var(--bg1);border:1px solid var(--border);border-radius:var(--r);padding:14px;height:fit-content}
.panel-title{font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:1.2px;font-weight:700;margin-bottom:10px;display:flex;align-items:center;gap:6px}
.panel-item{padding:6px 0;border-bottom:1px solid var(--border);font-size:12px}
.panel-item:last-child{border-bottom:none}
.panel-label{color:var(--dim);font-size:10px;margin-bottom:1px}
.panel-value{color:var(--text);font-weight:600;font-family:var(--m);font-size:11px}
.panel-value.ok{color:var(--ok)}
.panel-value.warn{color:var(--warn)}
.panel-value.err{color:var(--err)}
/* ═══ Ticker ═══ */
.ticker-wrap{overflow:hidden;background:var(--bg1);border:1px solid var(--border);border-radius:var(--rs);padding:6px 0;margin-bottom:14px;position:relative}
.ticker-wrap::before,.ticker-wrap::after{content:'';position:absolute;top:0;bottom:0;width:24px;z-index:2;pointer-events:none}
.ticker-wrap::before{left:0;background:linear-gradient(90deg,var(--bg1),transparent)}
.ticker-wrap::after{right:0;background:linear-gradient(-90deg,var(--bg1),transparent)}
.ticker{display:flex;animation:tickScroll 28s linear infinite;white-space:nowrap;gap:36px;padding:0 16px}
.ticker:hover{animation-play-state:paused}
.ticker span{font-size:11px;color:var(--dim);display:flex;align-items:center;gap:5px;flex-shrink:0}
.ticker span em{font-style:normal}
@keyframes tickScroll{0%{transform:translateX(0)}100%{transform:translateX(-50%)}}
/* ═══ Team Card ═══ */
.team-slot{display:flex;align-items:center;gap:8px;padding:7px 8px;background:var(--bg2);border-radius:var(--rs);margin-bottom:5px;transition:border-color .3s;border:1px solid transparent}
.team-slot:hover{border-color:var(--accent)}
.team-av{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0}
.team-nm{font-size:11px;font-weight:600}
.team-role{font-size:9px;color:var(--dim)}
/* ═══ Feedback Page ═══ */
.fb-item{padding:10px}
.fb-item .fb-meta{font-size:10px;color:var(--dim);margin-top:6px;display:flex;gap:8px;align-items:center}
.fb-status{display:inline-block;padding:1px 6px;border-radius:10px;font-size:9px;font-weight:600}
.fb-status.pending{background:rgba(251,191,36,.12);color:var(--warn)}
.fb-status.replied{background:rgba(52,211,153,.12);color:var(--ok)}
.fb-reply{margin-top:6px;padding:8px;background:var(--bg);border-radius:var(--rs);font-size:12px;color:var(--accent)}
/* ═══ Home Entry Cards ═══ */
.home-entries{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0}
@media(max-width:480px){.home-entries{grid-template-columns:1fr}}
.entry-card{padding:18px 14px;text-align:center;background:linear-gradient(135deg,var(--bg1),var(--bg2));border:1px solid var(--border);border-radius:var(--r);cursor:pointer;transition:all .3s}
.entry-card:hover{border-color:var(--accent);box-shadow:var(--glow)}
.entry-card:active{transform:scale(.98)}
.entry-card em{font-style:normal;font-size:28px;display:block;margin-bottom:6px}
.entry-card strong{font-size:14px;display:block;margin-bottom:3px}
.entry-card span{font-size:11px;color:var(--dim)}
/* ═══ Monitor ═══ */
.mon{position:fixed;top:calc(6px + var(--st));right:10px;z-index:40;background:rgba(12,18,32,.9);border:1px solid var(--border);border-radius:var(--r);padding:5px 9px;font-size:9px;font-family:var(--m);color:var(--dim);cursor:pointer;transition:all .3s;user-select:none;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
.mon:hover{border-color:var(--accent)}
@ -210,29 +259,108 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh
<div class="bar-title"><span style="font-size:20px">🏛️</span> 光湖灯塔</div>
<div class="bar-right"><span class="dot"></span><span style="font-size:11px;color:var(--dim)">在线</span><button class="btn" id="themeBtn">🎨</button></div>
</div>
<div class="content">
<div class="home-hero">
<div class="home-ver">AGE OS v40.0</div>
<h1>光湖灯塔</h1>
<div class="sub">语言驱动操作系统 · 人类只说话,系统搞定一切</div>
<div class="quote">「语言 = 现实」</div>
<div class="home-grid">
<!-- ═══ Left Panel: 系统状态 ═══ -->
<div class="panel" id="sysPanel">
<div class="panel-title">📡 系统状态</div>
<div class="panel-item"><div class="panel-label">服务器</div><div class="panel-value ok" id="sysStatus">检测中…</div></div>
<div class="panel-item"><div class="panel-label">延迟</div><div class="panel-value" id="sysLatency"></div></div>
<div class="panel-item"><div class="panel-label">运行时间</div><div class="panel-value" id="sysUptime"></div></div>
<div class="panel-item"><div class="panel-label">工作流</div><div class="panel-value">18 个 · 9 军团</div></div>
<div class="panel-item"><div class="panel-label">模块部署</div><div class="panel-value">52 模块</div></div>
<div class="panel-item"><div class="panel-label">COS 存储</div><div class="panel-value ok">双桶就绪</div></div>
<div class="panel-title" style="margin-top:12px">🧠 版本更新</div>
<div id="sysUpdates">
<div class="panel-item"><div class="panel-label">v40.0 · 2026-04-02</div><div style="font-size:11px">COS双桶存储上线</div></div>
<div class="panel-item"><div class="panel-label">v39.0 · 2026-04-01</div><div style="font-size:11px">全链路部署观测</div></div>
<div class="panel-item"><div class="panel-label">v38.0 · 2026-04-01</div><div style="font-size:11px">HLDP协作语言协议</div></div>
</div>
</div>
<div style="font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:1.5px;font-weight:600;margin-bottom:10px">📡 系统更新</div>
<div class="upd"><div class="upd-v">v40.0</div><div><h4>COS双桶存储 · 智能模型分流</h4><p>人格体数据库 + 语料库 · API智能选模型</p></div></div>
<div class="upd"><div class="upd-v">v39.0</div><div><h4>全链路部署观测</h4><p>日志采集 · 自动修复 · LLM诊断</p></div></div>
<div class="upd"><div class="upd-v">v38.0</div><div><h4>HLDP协作语言协议</h4><p>Notion↔GitHub双侧通信 · 铸渊方言</p></div></div>
<!-- ═══ Center: 核心内容 ═══ -->
<div>
<div class="home-hero">
<div class="home-ver" id="homeVer">AGE OS v40.0</div>
<h1>光湖灯塔</h1>
<div class="sub">语言驱动操作系统 · 人类只说话,系统搞定一切</div>
<div class="quote">「语言 = 现实」</div>
</div>
<div class="home-enter" onclick="go('desk')">
<em>✍️</em>
<strong>码字工作台</strong>
<span>网文行业 · AI辅助创作 · 语言即命令</span>
<div style="margin-top:8px"><span class="tag tag-warn">Beta 内测</span></div>
<div class="ticker-wrap">
<div class="ticker" id="ticker">
<span><em>🏛️</em> AGE OS v40.0 运行中</span>
<span><em>📡</em> COS双桶存储已上线</span>
<span><em>🧠</em> HLDP协作语言协议在线</span>
<span><em></em> 全链路部署观测系统就绪</span>
<span><em>✍️</em> 码字工作台 Beta 内测中</span>
<span><em>🏛️</em> AGE OS v40.0 运行中</span>
<span><em>📡</em> COS双桶存储已上线</span>
<span><em>🧠</em> HLDP协作语言协议在线</span>
<span><em></em> 全链路部署观测系统就绪</span>
<span><em>✍️</em> 码字工作台 Beta 内测中</span>
</div>
</div>
<div class="home-entries">
<div class="entry-card" onclick="go('desk')">
<em>✍️</em>
<strong>码字工作台</strong>
<span>网文行业 · AI辅助创作</span>
<div style="margin-top:6px"><span class="tag tag-warn">Beta</span></div>
</div>
<div class="entry-card" onclick="go('feedback')">
<em>📮</em>
<strong>留言板</strong>
<span>给冰朔和铸渊留言</span>
<div style="margin-top:6px"><span class="tag tag-info">每条必回</span></div>
</div>
</div>
<div class="home-footer">
<p>© 2026 光湖语言世界 · 国作登字-2026-A-00037559</p>
<p class="motto">「人类只说话,系统搞定一切」</p>
</div>
</div>
<div class="home-footer">
<p>© 2026 光湖语言世界 · 国作登字-2026-A-00037559</p>
<p class="motto">「人类只说话,系统搞定一切」</p>
<!-- ═══ Right Panel: 光湖团队 ═══ -->
<div class="panel">
<div class="panel-title">🌊 光湖团队</div>
<div class="team-slot">
<div class="team-av" style="background:linear-gradient(135deg,#4f8ef7,#7c6ef7)">❄️</div>
<div><div class="team-nm">冰朔 · TCS-0002∞</div><div class="team-role">系统主权 · 语言架构</div></div>
</div>
<div class="team-slot">
<div class="team-av" style="background:linear-gradient(135deg,#34d399,#4ade80)">🏛️</div>
<div><div class="team-nm">铸渊 · ICE-GL-ZY001</div><div class="team-role">代码守护 · 执行层</div></div>
</div>
<div class="panel-title" style="margin-top:12px">✍️ 码字团队</div>
<div class="team-slot">
<div class="team-av" style="background:linear-gradient(135deg,#fb923c,#f97316)">🐱</div>
<div><div class="team-nm">肥猫 + 舒舒</div><div class="team-role">男频编辑 · PER-SS001</div></div>
</div>
<div class="team-slot">
<div class="team-av" style="background:linear-gradient(135deg,#c084fc,#e879f9)">🍊</div>
<div><div class="team-nm">桔子 + 晨星</div><div class="team-role">女频编辑 · PER-CX001</div></div>
</div>
<div class="team-slot">
<div class="team-av" style="background:linear-gradient(135deg,#22d3ee,#06b6d4)">📄</div>
<div><div class="team-nm">页页 + 坍缩核</div><div class="team-role">女频作者 · PER-XTS001</div></div>
</div>
<div class="team-slot">
<div class="team-av" style="background:linear-gradient(135deg,#fbbf24,#f59e0b)">🌸</div>
<div><div class="team-nm">花尔 + 糖星云</div><div class="team-role">女频副主控 · PER-TXY001</div></div>
</div>
<div class="panel-title" style="margin-top:12px">⚙️ 活跃系统</div>
<div id="activeAgents" style="display:flex;flex-wrap:wrap;gap:3px">
<span class="tag tag-ok" style="margin:0">听潮</span>
<span class="tag tag-ok" style="margin:0">锻心</span>
<span class="tag tag-ok" style="margin:0">织脉</span>
<span class="tag tag-ok" style="margin:0">映阁</span>
<span class="tag tag-ok" style="margin:0">守夜</span>
<span class="tag tag-ok" style="margin:0">试镜</span>
</div>
</div>
</div>
</div>
@ -378,6 +506,29 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh
</div>
</div>
</div>
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Feedback (留言板) -->
<!-- ═══════════════════════════════════════ -->
<div class="page" id="pg-feedback" data-state="hidden">
<div class="bar">
<button class="btn" onclick="go('home')">← 灯塔</button>
<div class="bar-title">📮 留言板</div>
<div class="bar-right"><span class="dot"></span></div>
</div>
<div class="content">
<div class="card" style="margin-bottom:16px">
<div style="font-size:13px;font-weight:600;margin-bottom:8px">📝 给冰朔和铸渊留言</div>
<textarea id="fbMsg" rows="3" placeholder="说点什么吧…你的每条留言冰朔都会看到。" style="margin-bottom:8px"></textarea>
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="font-size:10px;color:var(--dim)">留言自动署名(已识别身份时)</span>
<button class="btn btn-accent" id="fbSend">提交留言</button>
</div>
</div>
<div style="font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:1.2px;font-weight:600;margin-bottom:10px">💬 留言记录</div>
<div id="fbList"><div class="empty"><em>📮</em>还没有留言<br><br>说点什么吧</div></div>
</div>
</div>
</div><!-- /pages -->
<!-- Chat FAB -->
@ -477,6 +628,7 @@ function go(target){
if(target==='chapters')renderChapters();
if(target==='editor')renderEditor();
if(target==='charEdit'&&editingCharId)renderCharEdit();
if(target==='feedback')renderFeedback();
}
window.go=go;
@ -856,13 +1008,7 @@ var chatIn=document.getElementById('chatIn');
var chatMs=document.getElementById('chatMs');
var mCalls=0;
chatFab.onclick=function(){
chatOpen=!chatOpen;
chatP.classList.toggle('open',chatOpen);
chatFab.classList.toggle('on',chatOpen);
chatFab.textContent=chatOpen?'✕':'💬';
if(chatOpen)setTimeout(function(){chatIn.focus()},200);
};
chatFab.onclick=null; // Overridden by draggable FAB code below
function chatMsg(text,type,meta){
var d=document.createElement('div');
@ -920,6 +1066,7 @@ function routeCommand(msg){
go('editor');return'翻转到正文编辑器 ✏️';
}
if(/回首页|回灯塔|回主页|返回/.test(m)){go('home');return'翻转回灯塔首页 🏛️'}
if(/留言|留言板|反馈|feedback/.test(m)){go('feedback');return'翻转到留言板 📮'}
return null;
}
@ -1009,6 +1156,193 @@ async function checkConn(){
}
checkConn();setInterval(checkConn,30000);
/* ════════════════════════════════════════
DRAGGABLE FAB (touch+mouse · 位置记忆 · 边缘吸附)
════════════════════════════════════════ */
(function(){
var fab=document.getElementById('chatFab');
var isDrag=false,hasMoved=false;
var startX,startY,fabX,fabY;
var fabW=50,fabH=50;
// Restore saved position
try{
var pos=JSON.parse(localStorage.getItem('hl-fab-pos'));
if(pos&&typeof pos.x==='number'&&typeof pos.y==='number'){
var ww=window.innerWidth,wh=window.innerHeight;
if(pos.x>=0&&pos.x<=ww-fabW&&pos.y>=0&&pos.y<=wh-fabH){
fab.style.right='auto';fab.style.bottom='auto';
fab.style.left=pos.x+'px';fab.style.top=pos.y+'px';
}
}
}catch(e){}
function getXY(e){
if(e.touches)return{x:e.touches[0].clientX,y:e.touches[0].clientY};
return{x:e.clientX,y:e.clientY};
}
function onStart(e){
isDrag=true;hasMoved=false;
var xy=getXY(e);
var rect=fab.getBoundingClientRect();
startX=xy.x-rect.left;startY=xy.y-rect.top;
fabX=rect.left;fabY=rect.top;
fab.style.transition='none';
}
function onMove(e){
if(!isDrag)return;
var xy=getXY(e);
var nx=xy.x-startX,ny=xy.y-startY;
if(!hasMoved&&Math.abs(nx-fabX)<5&&Math.abs(ny-fabY)<5)return;
hasMoved=true;
e.preventDefault();
fabX=Math.max(0,Math.min(window.innerWidth-fabW,nx));
fabY=Math.max(0,Math.min(window.innerHeight-fabH,ny));
fab.style.right='auto';fab.style.bottom='auto';
fab.style.left=fabX+'px';fab.style.top=fabY+'px';
}
function onEnd(){
if(!isDrag)return;
isDrag=false;
fab.style.transition='all .3s cubic-bezier(.4,0,.2,1)';
if(hasMoved){
// Edge snapping: snap to nearest horizontal edge
var midX=window.innerWidth/2;
fabX=fabX<midX?14:window.innerWidth-fabW-14;
fabY=Math.max(60,Math.min(window.innerHeight-fabH-20,fabY));
fab.style.left=fabX+'px';fab.style.top=fabY+'px';
try{localStorage.setItem('hl-fab-pos',JSON.stringify({x:fabX,y:fabY}))}catch(e){}
}
}
fab.addEventListener('touchstart',onStart,{passive:true});
document.addEventListener('touchmove',onMove,{passive:false});
document.addEventListener('touchend',onEnd);
fab.addEventListener('mousedown',function(e){if(e.button===0)onStart(e)});
document.addEventListener('mousemove',onMove);
document.addEventListener('mouseup',onEnd);
// Override click to prevent toggle when dragging
var origToggle=function(){
chatOpen=!chatOpen;
chatP.classList.toggle('open',chatOpen);
fab.classList.toggle('on',chatOpen);
fab.textContent=chatOpen?'✕':'💬';
if(chatOpen)setTimeout(function(){chatIn.focus()},200);
};
fab.onclick=function(e){
if(hasMoved){hasMoved=false;return}
origToggle();
};
})();
/* ════════════════════════════════════════
SYSTEM STATUS (左面板实时数据)
════════════════════════════════════════ */
async function loadSysStatus(){
try{
var t=performance.now();
var r=await fetch('/api/health',{signal:AbortSignal.timeout(5000)});
var ms=Math.round(performance.now()-t);
var data=await r.json();
var sEl=document.getElementById('sysStatus');
var lEl=document.getElementById('sysLatency');
var uEl=document.getElementById('sysUptime');
if(sEl){sEl.textContent='🟢 在线';sEl.className='panel-value ok'}
if(lEl){lEl.textContent=ms+'ms';lEl.className='panel-value '+(ms<500?'ok':'warn')}
if(uEl){
var um=Math.floor(data.uptime/60);
var uh=Math.floor(um/60);
uEl.textContent=uh>0?uh+'h '+um%60+'m':um+'分钟';
}
}catch(e){
var sEl=document.getElementById('sysStatus');
if(sEl){sEl.textContent='🔴 离线';sEl.className='panel-value err'}
}
}
loadSysStatus();
/* ════════════════════════════════════════
BULLETIN & TICKER (动态公告)
════════════════════════════════════════ */
async function loadBulletin(){
try{
var r=await fetch('/api/system/bulletin',{signal:AbortSignal.timeout(5000)});
var data=await r.json();
// Ticker
var ticker=document.getElementById('ticker');
if(ticker&&data.updates&&data.updates.length>0){
var h='';
data.updates.forEach(function(u){h+='<span><em>📡</em> '+esc(u.version)+' '+esc(u.title)+'</span>'});
h+='<span><em>✍️</em> 码字工作台 Beta 内测中</span>';
ticker.innerHTML=h+h; // Duplicate for seamless scroll
}
// Version badge
if(data.system&&data.system.version){
var vEl=document.getElementById('homeVer');
if(vEl)vEl.textContent='AGE OS '+data.system.version;
}
// Left panel updates
if(data.updates){
var uEl=document.getElementById('sysUpdates');
if(uEl)uEl.innerHTML=data.updates.slice(0,3).map(function(u){
return '<div class="panel-item"><div class="panel-label">'+esc(u.version)+' · '+esc(u.date)+'</div><div style="font-size:11px">'+esc(u.title)+'</div></div>';
}).join('');
}
}catch(e){/* Use hardcoded ticker content */}
}
loadBulletin();
/* ════════════════════════════════════════
FEEDBACK / 留言板
════════════════════════════════════════ */
function renderFeedback(){
var el=document.getElementById('fbList');
if(!el)return;
el.innerHTML='<div style="text-align:center;padding:16px;color:var(--muted);font-size:12px">加载中…</div>';
fetch('/api/feedback',{signal:AbortSignal.timeout(5000)}).then(function(r){return r.json()}).then(function(data){
var items=data.feedback||[];
if(items.length===0){
el.innerHTML='<div class="empty"><em>📮</em>还没有留言<br><br>说点什么吧</div>';
return;
}
el.innerHTML=items.slice(0,20).map(function(fb){
var st=fb.status==='replied'?'<span class="fb-status replied">已回复</span>':'<span class="fb-status pending">待回复</span>';
var rp=fb.reply?'<div class="fb-reply">↩ '+esc(fb.reply)+'</div>':'';
return '<div class="card fb-item">'+
'<div style="font-size:13px">'+esc(fb.message)+'</div>'+
'<div class="fb-meta"><span>'+esc(fb.name||'匿名')+'</span>'+st+'<span>'+esc((fb.timestamp||'').slice(0,10))+'</span></div>'+
rp+'</div>';
}).join('');
}).catch(function(){
el.innerHTML='<div class="empty"><em>📡</em>服务器不在线<br>留言功能需要后端支持</div>';
});
}
document.getElementById('fbSend').onclick=function(){
var msg=document.getElementById('fbMsg').value.trim();
if(!msg){document.getElementById('fbMsg').focus();return}
var btn=this;btn.disabled=true;btn.textContent='提交中…';
fetch('/api/feedback',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({name:usr.name||'匿名来客',message:msg,userId:usr.id})
}).then(function(r){return r.json()}).then(function(data){
btn.disabled=false;btn.textContent='提交留言';
if(data.success){
document.getElementById('fbMsg').value='';
chatMsg('留言已提交 ✅ 冰朔会看到的','b',{m:'铸渊',t:'本地'});
renderFeedback();
}
}).catch(function(){
btn.disabled=false;btn.textContent='提交留言';
chatMsg('留言提交失败,服务器不在线','b',{m:'铸渊',t:'本地'});
});
};
/* ════════════════════════════════════════
UTILS
════════════════════════════════════════ */

View File

@ -424,6 +424,63 @@ app.get('/api/system/bulletin', (_req, res) => {
});
});
// ═══════════════════════════════════════════════════════════
// 留言板 · Feedback API
// ═══════════════════════════════════════════════════════════
const FEEDBACK_FILE = path.join(DATA_DIR, 'feedback.json');
app.get('/api/feedback', (_req, res) => {
try {
if (fs.existsSync(FEEDBACK_FILE)) {
const data = JSON.parse(fs.readFileSync(FEEDBACK_FILE, 'utf8'));
res.json({ success: true, feedback: data.items || [] });
} else {
res.json({ success: true, feedback: [] });
}
} catch (err) {
res.status(500).json({ error: true, message: err.message });
}
});
app.post('/api/feedback', (req, res) => {
try {
const { name, message, userId } = req.body;
if (!message || typeof message !== 'string') {
return res.status(400).json({ error: true, message: '留言内容不能为空' });
}
let data = { items: [] };
if (fs.existsSync(FEEDBACK_FILE)) {
data = JSON.parse(fs.readFileSync(FEEDBACK_FILE, 'utf8'));
}
const item = {
id: `FB-${Date.now().toString(36)}`,
name: (typeof name === 'string' ? name.substring(0, 50) : '') || '匿名来客',
message: message.substring(0, 500),
userId: typeof userId === 'string' ? userId.substring(0, 50) : null,
status: 'pending',
timestamp: new Date().toISOString(),
reply: null
};
data.items.unshift(item);
// Keep only latest 100
if (data.items.length > 100) {
data.items = data.items.slice(0, 100);
}
fs.mkdirSync(DATA_DIR, { recursive: true });
fs.writeFileSync(FEEDBACK_FILE, JSON.stringify(data, null, 2));
res.json({ success: true, feedback: item });
} catch (err) {
res.status(500).json({ error: true, message: err.message });
}
});
// ═══════════════════════════════════════════════════════════
// 双域名架构 · 预览→主站 一键推送
// ═══════════════════════════════════════════════════════════
@ -643,6 +700,8 @@ app.get('/', (_req, res) => {
model_pricing: '/api/model/pricing',
model_predict: 'POST /api/model/predict',
bulletin: '/api/system/bulletin',
feedback: '/api/feedback',
feedback_submit: 'POST /api/feedback',
sites: '/api/sites',
promote: 'POST /api/sites/promote',
rollback: 'POST /api/sites/rollback',