diff --git a/docs/index.html b/docs/index.html index 3e4566cc..5edabb93 100644 --- a/docs/index.html +++ b/docs/index.html @@ -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
🏛️ 光湖灯塔
在线
-
-
-
AGE OS v40.0
-

光湖灯塔

-
语言驱动操作系统 · 人类只说话,系统搞定一切
-
「语言 = 现实」
+
+ +
+
📡 系统状态
+
服务器
检测中…
+
延迟
+
运行时间
+
工作流
18 个 · 9 军团
+
模块部署
52 模块
+
COS 存储
双桶就绪
+
🧠 版本更新
+
+
v40.0 · 2026-04-02
COS双桶存储上线
+
v39.0 · 2026-04-01
全链路部署观测
+
v38.0 · 2026-04-01
HLDP协作语言协议
+
-
📡 系统更新
-
v40.0

COS双桶存储 · 智能模型分流

人格体数据库 + 语料库 · API智能选模型

-
v39.0

全链路部署观测

日志采集 · 自动修复 · LLM诊断

-
v38.0

HLDP协作语言协议

Notion↔GitHub双侧通信 · 铸渊方言

+ +
+
+
AGE OS v40.0
+

光湖灯塔

+
语言驱动操作系统 · 人类只说话,系统搞定一切
+
「语言 = 现实」
+
-
- ✍️ - 码字工作台 - 网文行业 · AI辅助创作 · 语言即命令 -
Beta 内测
+
+
+ 🏛️ AGE OS v40.0 运行中 + 📡 COS双桶存储已上线 + 🧠 HLDP协作语言协议在线 + 全链路部署观测系统就绪 + ✍️ 码字工作台 Beta 内测中 + 🏛️ AGE OS v40.0 运行中 + 📡 COS双桶存储已上线 + 🧠 HLDP协作语言协议在线 + 全链路部署观测系统就绪 + ✍️ 码字工作台 Beta 内测中 +
+
+ +
+
+ ✍️ + 码字工作台 + 网文行业 · AI辅助创作 +
Beta
+
+
+ 📮 + 留言板 + 给冰朔和铸渊留言 +
每条必回
+
+
+ +
-
@@ -378,6 +506,29 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh
+ + + +
+
+ +
📮 留言板
+
+
+
+
+
📝 给冰朔和铸渊留言
+ +
+ 留言自动署名(已识别身份时) + +
+
+
💬 留言记录
+
📮还没有留言

说点什么吧
+
+
+ @@ -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=fabX0?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+='📡 '+esc(u.version)+' '+esc(u.title)+''}); + h+='✍️ 码字工作台 Beta 内测中'; + 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 '
'+esc(u.version)+' · '+esc(u.date)+'
'+esc(u.title)+'
'; + }).join(''); + } + }catch(e){/* Use hardcoded ticker content */} +} +loadBulletin(); + +/* ════════════════════════════════════════ + FEEDBACK / 留言板 + ════════════════════════════════════════ */ +function renderFeedback(){ + var el=document.getElementById('fbList'); + if(!el)return; + el.innerHTML='
加载中…
'; + 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='
📮还没有留言

说点什么吧
'; + return; + } + el.innerHTML=items.slice(0,20).map(function(fb){ + var st=fb.status==='replied'?'已回复':'待回复'; + var rp=fb.reply?'
↩ '+esc(fb.reply)+'
':''; + return '
'+ + '
'+esc(fb.message)+'
'+ + '
'+esc(fb.name||'匿名')+''+st+''+esc((fb.timestamp||'').slice(0,10))+'
'+ + rp+'
'; + }).join(''); + }).catch(function(){ + el.innerHTML='
📡服务器不在线
留言功能需要后端支持
'; + }); +} + +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 ════════════════════════════════════════ */ diff --git a/server/app/server.js b/server/app/server.js index dce23025..c73129d6 100644 --- a/server/app/server.js +++ b/server/app/server.js @@ -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',