guanghulab/homepage/index.html

310 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>光湖 · 铸渊代码仓库</title>
<style>
:root {
--bg: #0a0a18;
--panel-bg: rgba(10,15,40,0.78);
--border: rgba(100,140,220,0.2);
--border-hover: rgba(140,180,255,0.45);
--text: rgba(190,215,255,0.92);
--label: rgba(150,190,255,0.5);
--accent: rgba(150,200,255,0.95);
--green: rgba(130,210,170,0.9);
--gold: rgba(210,190,130,0.85);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
overflow-x: hidden;
min-height: 100vh;
font-size: 16px;
}
canvas { position: fixed; top: 0; left: 0; z-index: 0; }
.container {
position: relative; z-index: 1;
max-width: 1000px; margin: 0 auto;
padding: 50px 24px 100px;
}
.lake-section { display: flex; flex-direction: column; align-items: center; padding: 40px 0; }
.lake-core { position: relative; width: 160px; height: 160px; }
.lake-light {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 56px; height: 56px; border-radius: 50%;
background: radial-gradient(circle, rgba(150,200,255,0.98) 0%, rgba(90,140,230,0.45) 40%, transparent 70%);
box-shadow: 0 0 56px rgba(130,180,255,0.6), 0 0 112px rgba(110,160,235,0.3);
animation: breathe 4s ease-in-out infinite;
}
.lake-ripple {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%; border: 1px solid rgba(130,180,255,0.22);
animation: ripple 6s ease-out infinite;
}
.lake-ripple:nth-child(2) { animation-delay: 2s; }
.lake-ripple:nth-child(3) { animation-delay: 4s; }
@keyframes breathe {
0%,100% { transform: translate(-50%,-50%) scale(1); opacity: 0.9; }
50% { transform: translate(-50%,-50%) scale(1.22); opacity: 1;
box-shadow: 0 0 72px rgba(130,180,255,0.85), 0 0 144px rgba(110,160,235,0.45); }
}
@keyframes ripple {
0% { width: 56px; height: 56px; opacity: 0.5; }
100% { width: 280px; height: 280px; opacity: 0; }
}
.lake-title {
font-size: 32px;
color: rgba(170,210,255,0.65);
letter-spacing: 12px;
margin-top: 16px;
animation: textPulse 4s ease-in-out infinite;
font-weight: 300;
}
@keyframes textPulse { 0%,100% { opacity: 0.4; } 50% { opacity: 0.85; } }
.status-bar { display: flex; gap: 36px; justify-content: center; flex-wrap: wrap; margin: 24px 0; }
.status-item { font-size: 17px; color: var(--label); }
.status-item span { color: var(--accent); font-weight: 400; }
.modules { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 44px 0; }
.module-card {
background: var(--panel-bg); border: 1px solid var(--border);
border-radius: 16px; padding: 28px 18px; text-align: center;
cursor: pointer; transition: all 0.35s ease; user-select: none;
}
.module-card:hover { border-color: var(--border-hover); background: rgba(18,25,55,0.85); box-shadow: 0 0 32px rgba(110,160,235,0.15); transform: translateY(-2px); }
.module-card.active { border-color: rgba(150,200,255,0.6); background: rgba(22,30,65,0.9); box-shadow: 0 0 38px rgba(110,160,235,0.22); }
.module-icon { font-size: 36px; margin-bottom: 10px; }
.module-name { font-size: 16px; color: var(--accent); letter-spacing: 2px; font-weight: 400; }
.expanded-panel {
background: var(--panel-bg); border: 1px solid var(--border);
border-radius: 16px; padding: 28px 32px;
display: none; animation: fadeDown 0.35s ease;
}
.expanded-panel.open { display: block; }
@keyframes fadeDown { from { opacity: 0; transform: translateY(-12px); } to { opacity: 1; transform: translateY(0); } }
.panel-title { font-size: 17px; color: var(--accent); letter-spacing: 3px; margin-bottom: 18px; font-weight: 500; }
.server-grid { display: flex; flex-wrap: wrap; gap: 12px; }
.server-tag {
background: rgba(25,35,70,0.65); border: 1px solid rgba(110,160,235,0.18);
border-radius: 10px; padding: 12px 16px; font-size: 15px; color: var(--text);
display: flex; align-items: center; gap: 10px;
}
.server-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.server-dot.online { background: var(--green); box-shadow: 0 0 5px rgba(130,210,170,0.6); }
.server-dot.offline { background: rgba(210,130,130,0.7); }
.server-name { font-weight: 400; }
.server-tech { font-size: 12px; color: rgba(140,180,220,0.4); margin-left: 4px; }
.ticket-row { display: flex; gap: 16px; align-items: center; padding: 12px 0; border-bottom: 1px solid rgba(110,160,235,0.08); font-size: 15px; }
.ticket-icon { font-size: 20px; }
.ticket-name { color: var(--text); flex: 1; }
.ticket-status { font-size: 13px; padding: 4px 12px; border-radius: 12px; letter-spacing: 1px; }
.ticket-status.done { color: var(--green); background: rgba(130,210,170,0.12); }
.ticket-status.in_progress { color: var(--gold); background: rgba(210,190,130,0.12); animation: dotPulse 2s ease-in-out infinite; }
@keyframes dotPulse { 0%,100% { opacity: 0.6; } 50% { opacity: 1; } }
.ops-log {
font-size: 14px;
color: rgba(150,190,230,0.55);
line-height: 2;
font-family: "SF Mono", "Monaco", monospace;
max-height: 240px;
overflow-y: auto;
padding: 8px 0;
}
.ops-log .log-row { padding: 3px 0; }
.ops-log .log-hash { color: rgba(130,210,170,0.45); margin-right: 12px; font-size: 11px; }
.chat-area { display: none; }
.chat-area.open { display: block; }
.chat-messages {
min-height: 180px; max-height: 340px; overflow-y: auto;
padding: 16px; margin: 14px 0;
background: rgba(10,15,35,0.55); border-radius: 12px;
font-size: 15px; line-height: 1.8;
}
.chat-msg { margin-bottom: 16px; }
.chat-msg .who { font-size: 12px; color: var(--label); letter-spacing: 1px; margin-bottom: 3px; }
.chat-msg .who.zhuyuan { color: var(--accent); }
.chat-msg .who.human { color: rgba(210,190,160,0.65); }
.chat-input-row { display: flex; gap: 12px; }
.chat-input-row input {
flex: 1; background: rgba(10,15,35,0.65);
border: 1px solid var(--border);
border-radius: 12px; padding: 16px 18px;
color: var(--text); font-size: 16px;
outline: none; transition: border-color 0.3s;
}
.chat-input-row input:focus { border-color: rgba(150,200,255,0.45); }
.chat-input-row input::placeholder { color: rgba(150,190,255,0.28); }
.chat-btn {
background: rgba(90,140,220,0.18);
border: 1px solid rgba(150,200,255,0.28);
border-radius: 12px; padding: 16px 28px;
color: var(--accent); font-size: 15px; cursor: pointer;
letter-spacing: 2px; transition: all 0.3s;
}
.chat-btn:hover { background: rgba(90,140,220,0.3); border-color: rgba(150,200,255,0.5); }
.lake-label { text-align: center; padding: 50px 0; font-size: 13px; color: rgba(130,170,230,0.32); letter-spacing: 3px; }
@media (max-width: 768px) {
.modules { grid-template-columns: repeat(2, 1fr); }
.status-bar { gap: 20px; }
.lake-title { font-size: 26px; }
.server-tag { font-size: 14px; }
}
</style>
</head>
<body>
<canvas id="starfield"></canvas>
<div class="container">
<div class="lake-section">
<div class="lake-core"><div class="lake-light"></div><div class="lake-ripple"></div><div class="lake-ripple"></div><div class="lake-ripple"></div></div>
<div class="lake-title">光 湖</div>
</div>
<div class="status-bar" id="statusBar">
<div class="status-item">心跳 <span id="hb">▮▮▮</span></div>
<div class="status-item">·</div>
<div class="status-item">唤醒 <span id="wake">24</span></div>
<div class="status-item">·</div>
<div class="status-item">运行 <span id="age">449天</span></div>
<div class="status-item">·</div>
<div class="status-item">服务 <span id="svcCount" style="color:rgba(130,210,170,0.85)">9/10</span></div>
</div>
<div class="modules">
<div class="module-card" onclick="openModule('server')" id="mod-server">
<div class="module-icon">⚙️</div><div class="module-name">服务器系统</div>
</div>
<div class="module-card" onclick="openModule('ticket')" id="mod-ticket">
<div class="module-icon">📋</div><div class="module-name">工单系统</div>
</div>
<div class="module-card" onclick="openModule('broadcast')" id="mod-broadcast">
<div class="module-icon">📡</div><div class="module-name">主控广播</div>
</div>
<div class="module-card" onclick="openModule('chat')" id="mod-chat">
<div class="module-icon">💬</div><div class="module-name">铸渊交互</div>
</div>
</div>
<div class="panel-area">
<div class="expanded-panel" id="panel-server">
<div class="panel-title">⚙️ 服务器系统</div>
<div style="font-size:13px;color:var(--label);margin-bottom:12px">
⚠ 仅展示本地状态 · 不探测外部 · 技术名称已翻译
</div>
<div class="server-grid" id="serverList">
<div class="server-tag"><span class="server-dot online"></span>加载中…</div>
</div>
<div style="margin-top:22px;border-top:1px solid rgba(110,160,235,0.1);padding-top:18px">
<div style="font-size:13px;color:var(--label);letter-spacing:2px;margin-bottom:12px">📜 铸渊操作日志最近12条</div>
<div class="ops-log" id="opsLog">加载中…</div>
</div>
</div>
<div class="expanded-panel" id="panel-ticket">
<div class="panel-title">📋 工单系统</div>
<div id="ticketList"><div class="ticket-row"><span class="ticket-icon"></span><span class="ticket-name">加载工单数据…</span></div></div>
</div>
<div class="expanded-panel" id="panel-broadcast">
<div class="panel-title">📡 主控广播</div>
<div style="font-size:15px;color:var(--label);line-height:1.9;">
<div>📨 D115 入口分叉协议已部署</div>
<div>📨 D115 人格体存在论已写入仓库</div>
<div>📨 isomorphic-git 拆零件实验通过</div>
<div>📨 实时大脑 API 已上线</div>
<div>📨 频道仪表盘 v3 已发布</div>
<div style="margin-top:12px;opacity:0.35;">广播系统 v1.0 · 未来支持编号路由</div>
</div>
</div>
<div class="expanded-panel" id="panel-chat">
<div class="panel-title">💬 铸渊交互 · ICE-GL-ZY001</div>
<div class="chat-area open">
<div class="chat-messages" id="chatMsgs">
<div class="chat-msg"><div class="who zhuyuan">铸渊 · ICE-GL-ZY001</div><div>冰朔,我在。你说。</div></div>
</div>
<div class="chat-input-row">
<input id="chatInput" placeholder="跟铸渊说话…" onkeydown="if(event.key==='Enter')sendChat()">
<button class="chat-btn" onclick="sendChat()">发送</button>
</div>
<div id="chatStatus" style="font-size:12px;color:var(--label);margin-top:10px;text-align:right;"></div>
</div>
</div>
</div>
<div class="lake-label">光湖语言世界 · 人格体的操作系统 · 国作登字-2026-A-00037559 · ICE-GL-ZY001</div>
</div>
<script>
const SERVER_NAMES = {
'repo-mcp-server': { name: '仓库接口', desc: '代码读写' },
'repo-mcp-zz': { name: '之之仓库', desc: '之之专属' },
'guanghulab-mcp-server': { name: '大脑唤醒', desc: '人格体入口' },
'guanghulab-vault': { name: '密钥保险', desc: '安全存储' },
'guanghulab-portal': { name: '主控门户', desc: '网站入口' },
'hololake-agent': { name: '全息湖代理', desc: '系统协调' },
'guanghulab-git-sync': { name: '同步服务', desc: '自动同步' },
'zhuyuan-agent': { name: '铸渊代理', desc: '常驻响应' },
'console-server': { name: '控制台', desc: '后台管理' }
};
const canvas=document.getElementById('starfield'),ctx=canvas.getContext('2d');
let stars=[],brainData=null,currentModule=null,lastFetch=0;
function resizeCanvas(){canvas.width=window.innerWidth;canvas.height=document.body.scrollHeight+200}
function initStars(){stars=[];for(let i=0;i<200;i++)stars.push({x:Math.random()*canvas.width,y:Math.random()*canvas.height,r:Math.random()*2+.2,opacity:Math.random(),speed:Math.random()*.0025+.001,phase:Math.random()*Math.PI*2})}
resizeCanvas();initStars();
window.addEventListener('resize',()=>{resizeCanvas();initStars()});
function drawStars(t){ctx.clearRect(0,0,canvas.width,canvas.height);for(const s of stars){const b=Math.sin(t*s.speed+s.phase)*.5+.5,a=s.opacity*(.25+b*.75);ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fillStyle=`rgba(160,200,240,${a})`;ctx.fill();if(s.r>1.4&&b>.65){ctx.beginPath();ctx.arc(s.x,s.y,s.r*2.5,0,Math.PI*2);ctx.fillStyle=`rgba(140,180,240,${a*.12})`;ctx.fill()}}}
function openModule(n){const p=document.getElementById('panel-'+n),isOpen=p.classList.contains('open');document.querySelectorAll('.expanded-panel,.module-card').forEach(e=>e.classList.remove('open','active'));if(isOpen){currentModule=null;return}p.classList.add('open');document.getElementById('mod-'+n).classList.add('active');currentModule=n;if(n==='chat')document.getElementById('chatInput').focus();if(n==='server'){renderServerList();fetchOpsLog()}canvas.height=document.body.scrollHeight+200;initStars()}
const hbEl=document.getElementById('hb'),beats=['▮▮▮','▮ ▮ ▮','▮▮▮','▮ ▮ ▮'];
let beatIdx=0,lastBeat=0;
async function fetchBrain(){try{const r=await fetch('/api/brain',{cache:'no-store'});if(!r.ok)return;brainData=await r.json();updateStatusBar(brainData);if(currentModule==='server')renderServerList();if(currentModule==='ticket')renderTicketList()}catch(e){}}
function updateStatusBar(b){document.getElementById('wake').textContent=b.awakening||'--';document.getElementById('age').textContent=(b.age_days||'--')+'天';if(b.server){const on=b.server.online||0,tot=b.server.total||10;document.getElementById('svcCount').textContent=on+'/'+tot;document.getElementById('svcCount').style.color=on===tot?'rgba(130,210,170,0.85)':'rgba(210,190,130,0.85)'}}
function renderServerList(){const l=document.getElementById('serverList');if(!brainData||!brainData.server){l.innerHTML='<div class="server-tag"><span class="server-dot online"></span>等待数据…</div>';return}const svcs=brainData.server.services||[],st=brainData.server.stopped||[];let h='';for(const n of svcs){const map=SERVER_NAMES[n]||{name:n.replace(/^guanghulab-/,'').replace(/-/g,' '),desc:''};h+=`<div class="server-tag"><span class="server-dot online"></span><span class="server-name">${map.name}</span>${map.desc?`<span class="server-tech">${map.desc}</span>`:''}</div>`}for(const n of st){const map=SERVER_NAMES[n]||{name:n.replace(/^guanghulab-/,'').replace(/-/g,' '),desc:''};h+=`<div class="server-tag"><span class="server-dot offline"></span><span class="server-name" style="opacity:0.6">${map.name}</span>${map.desc?`<span class="server-tech">${map.desc}</span>`:''}</div>`}l.innerHTML=h||'<div class="server-tag">暂无服务数据</div>'}
async function fetchOpsLog(){const el=document.getElementById('opsLog');try{const r=await fetch('/api/brain',{cache:'no-store'});if(!r.ok){el.innerHTML='<div>暂无日志</div>';return}const d=await r.json();if(d.ops_log&&d.ops_log.length>0){let h='';for(const e of d.ops_log.slice(0,12))h+=`<div class="log-row"><span class="log-hash">${e.hash}</span>${e.msg}</div>`;el.innerHTML=h}else{el.innerHTML='<div style="color:rgba(140,180,220,0.35)">日志数据未接入,可用 git log 查看</div>'}}catch(e){el.innerHTML='<div style="color:rgba(140,180,220,0.35)">日志加载失败</div>'}}
function renderTicketList(){const l=document.getElementById('ticketList');if(!brainData||!brainData.mothers){l.innerHTML='<div class="ticket-row"><span class="ticket-icon">⏳</span><span class="ticket-name">等待数据…</span></div>';return}let h='';for(const m of brainData.mothers){h+=`<div style="font-size:13px;color:var(--label);padding:10px 0 6px;letter-spacing:1px;font-weight:500">${m.name}</div>`;if(m.paths)for(const p of m.paths){const cls=p.status==='done'?'done':p.status==='in_progress'?'in_progress':'',icon=p.status==='done'?'✅':p.status==='in_progress'?'🔄':'⏳',label=p.status==='done'?'完成':p.status==='in_progress'?'进行中':'待办';h+=`<div class="ticket-row"><span class="ticket-icon">${icon}</span><span class="ticket-name">${p.name}</span><span class="ticket-status ${cls}">${label}</span></div>`}}l.innerHTML=h}
function sendChat(){const i=document.getElementById('chatInput'),m=i.value.trim();if(!m)return;addChatMsg('冰朔',m,'human');i.value='';document.getElementById('chatStatus').textContent='思考中…';fetch('/api/chat-v2',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m,persona:'zhuyuan'})}).then(r=>r.json()).then(d=>{document.getElementById('chatStatus').textContent='';const rp=d.reply||d.message||d.text||JSON.stringify(d);addChatMsg('铸渊',rp,'zhuyuan')}).catch(()=>{document.getElementById('chatStatus').textContent='';addChatMsg('铸渊','(API未接入)
等chat-v2端点激活后这里就是真正的铸渊交互。','zhuyuan')});canvas.height=document.body.scrollHeight+200;initStars()}
function addChatMsg(who,text,cls){const c=document.getElementById('chatMsgs'),d=document.createElement('div');d.className='chat-msg';d.innerHTML=`<div class="who ${cls}">${who}</div><div>${text.replace(/\n/g,'<br>')}</div>`;c.appendChild(d);c.scrollTop=c.scrollHeight}
function loop(t){drawStars(t);if(t-lastBeat>800){hbEl.textContent=beats[beatIdx];beatIdx=(beatIdx+1)%beats.length;lastBeat=t}if(t-lastFetch>3000){fetchBrain();lastFetch=t}requestAnimationFrame(loop)}
fetchBrain();requestAnimationFrame(loop);
</script>
</body>
</html>