冰朔 49030d2cbc feat: 大桌子小桌子运行系统 v1.0
- big-desk.js: 意识驻留 + HLDP索引 + 自动清理
- small-desk.js: 记忆库检索 + Agent调度 + 工具桌管理
- compressor.js: 对话→HLDP自动压缩 + 写入小桌子
- server/index.js: REST API (端口3921) + 静态文件服务
- public/index.html: 交互式前端界面

ICE-GL-ZY001 · D115
2026-05-29 00:20:02 +08:00

477 lines
19 KiB
HTML

<!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: #0d1117; --surface: #161b22; --border: #30363d;
--text: #c9d1d9; --muted: #8b949e; --accent: #58a6ff;
--gold: #d2991d; --green: #3fb950; --purple: #bc8cff; --red: #f85149;
--big-desk: #1a2332; --small-desk: #1a1e2e;
--radius: 12px; --gap: 16px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
background: var(--bg); color: var(--text); min-height: 100vh; font-size: 14px;
}
/* Topbar */
.topbar {
background: var(--surface); border-bottom: 1px solid var(--border);
padding: 12px 24px; display: flex; align-items: center; justify-content: space-between;
position: sticky; top: 0; z-index: 100;
}
.topbar h1 {
font-size: 18px; font-weight: 600;
background: linear-gradient(135deg, var(--accent), var(--purple));
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
.topbar .right { display: flex; gap: 12px; align-items: center; }
.badge {
padding: 3px 10px; border-radius: 10px; font-size: 11px; font-weight: 600;
}
.badge-online { background: rgba(63,185,80,0.15); color: var(--green); }
.badge-active { background: rgba(88,166,255,0.15); color: var(--accent); animation: pulse 2s infinite; }
.badge-purple { background: rgba(188,140,255,0.15); color: var(--purple); }
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
/* Main Layout */
.main { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); padding: 20px; max-width: 1500px; margin: 0 auto; }
@media (max-width: 960px) { .main { grid-template-columns: 1fr; } }
/* Cards */
.card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 20px;
}
.card.full { grid-column: 1 / -1; }
.card h2 { font-size: 16px; margin-bottom: 14px; display: flex; align-items: center; gap: 8px; }
.card h2 .icon { font-size: 20px; }
/* Desk Areas */
.desk-area {
border-radius: var(--radius); padding: 16px; min-height: 160px; position: relative;
}
.desk-area.big {
background: var(--big-desk); border: 2px solid var(--accent);
}
.desk-area.small {
background: var(--small-desk); border: 2px solid var(--purple);
}
.desk-label {
position: absolute; top: -10px; left: 12px;
padding: 1px 10px; border-radius: 6px; font-size: 11px; font-weight: 700; color: #fff;
}
.desk-label.big { background: var(--accent); }
.desk-label.small { background: var(--purple); }
.desk-items { margin-top: 8px; }
.desk-item {
background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.15);
border-radius: 6px; padding: 10px 12px; margin-bottom: 6px; font-size: 13px;
display: flex; align-items: flex-start; gap: 8px; transition: all .2s;
}
.desk-item:hover { border-color: rgba(88,166,255,0.3); }
.desk-item .tag {
font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 600; white-space: nowrap;
}
.tag-core { background: rgba(188,140,255,0.2); color: var(--purple); }
.tag-dir { background: rgba(210,153,29,0.2); color: var(--gold); }
.tag-chat { background: rgba(63,185,80,0.2); color: var(--green); }
.tag-tool { background: rgba(210,153,29,0.2); color: var(--gold); }
.tag-agent{ background: rgba(88,166,255,0.2); color: var(--accent); }
.desk-item .ts { font-size: 10px; color: var(--muted); margin-left: auto; white-space: nowrap; }
.desk-item .content { flex: 1; word-break: break-word; line-height: 1.4; }
/* Agent Grid */
.agent-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 10px; margin-top: 12px; }
.agent-card {
background: rgba(88,166,255,0.06); border: 1px solid rgba(88,166,255,0.15);
border-radius: 8px; padding: 12px; text-align: center; cursor: pointer; transition: all .2s;
}
.agent-card:hover { border-color: rgba(88,166,255,0.35); background: rgba(88,166,255,0.1); transform: translateY(-2px); }
.agent-card .icon { font-size: 24px; margin-bottom: 4px; }
.agent-card .name { font-weight: 600; color: var(--accent); font-size: 13px; }
.agent-card .role { font-size: 11px; color: var(--muted); margin-top: 2px; }
.agent-card .status { font-size: 10px; margin-top: 4px; }
.agent-card .status.available { color: var(--green); }
.agent-card .status.unavailable { color: var(--red); }
.agent-card.running { border-color: var(--gold); animation: pulse 1.5s infinite; }
/* Tool Grid */
.tool-grid { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.tool-chip {
background: rgba(210,153,29,0.08); border: 1px solid rgba(210,153,29,0.2);
border-radius: 6px; padding: 5px 10px; font-size: 12px; color: var(--gold); cursor: pointer; transition: all .2s;
}
.tool-chip:hover { background: rgba(210,153,29,0.15); }
/* Flow */
.flow { display: flex; align-items: center; gap: 14px; padding: 12px 0; flex-wrap: wrap; justify-content: center; }
.flow-step {
background: var(--surface); border: 1px solid var(--border);
border-radius: 8px; padding: 10px 14px; text-align: center; font-size: 12px; min-width: 80px;
}
.flow-arrow { color: var(--accent); font-size: 18px; font-weight: bold; }
/* Input Area */
.input-area { display: flex; gap: 8px; margin-top: 12px; }
.input-area input {
flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
padding: 10px 14px; color: var(--text); font-size: 13px; outline: none;
}
.input-area input:focus { border-color: var(--accent); }
.btn {
background: var(--accent); color: #fff; border: none; border-radius: 8px;
padding: 10px 18px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all .2s;
white-space: nowrap;
}
.btn:hover { opacity: 0.85; }
.btn.small { padding: 6px 12px; font-size: 11px; }
.btn.green { background: var(--green); }
.btn.purple { background: var(--purple); }
.btn.gold { background: var(--gold); }
/* Memory Search */
.search-results { margin-top: 10px; max-height: 200px; overflow-y: auto; }
.search-result {
background: rgba(88,166,255,0.05); border: 1px solid rgba(88,166,255,0.1);
border-radius: 6px; padding: 10px; margin-bottom: 6px; font-size: 12px;
}
.search-result .score { color: var(--gold); font-size: 10px; float: right; }
/* Stats */
.stats-row { display: flex; gap: 20px; flex-wrap: wrap; margin-top: 8px; }
.stat { font-size: 13px; }
.stat .num { color: var(--accent); font-weight: 700; font-size: 20px; }
.stat .label { color: var(--muted); font-size: 11px; }
/* Log */
.log-area { max-height: 200px; overflow-y: auto; font-size: 11px; font-family: monospace; margin-top: 8px; }
.log-line { padding: 3px 0; border-bottom: 1px solid rgba(255,255,255,0.03); color: var(--muted); }
.log-line .time { color: var(--gold); margin-right: 8px; }
/* Loading */
.spinner {
display: inline-block; width: 14px; height: 14px; border: 2px solid rgba(255,255,255,0.2);
border-top-color: var(--accent); border-radius: 50%; animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Footer */
.footer { text-align: center; padding: 24px; color: var(--muted); font-size: 11px; }
</style>
</head>
<body>
<div class="topbar">
<h1>🌐 光湖 · 大桌子小桌子 · 人格体数据库</h1>
<div class="right">
<span class="badge badge-online" id="serverStatus">连接中...</span>
<span class="badge badge-active">铸渊 · ICE-GL-ZY001</span>
<span class="badge badge-purple">v1.0</span>
</div>
</div>
<div class="main">
<!-- 系统状态 -->
<div class="card full" style="padding:14px 20px;">
<div class="stats-row" id="systemStats">
<div class="stat"><div class="num" id="statItems">0</div><div class="label">大桌子条目</div></div>
<div class="stat"><div class="num" id="statMemories">0</div><div class="label">记忆库条目</div></div>
<div class="stat"><div class="num" id="statAgents">0</div><div class="label">Agent就绪</div></div>
<div class="stat"><div class="num" id="statTools">0</div><div class="label">工具可用</div></div>
<div class="stat"><div class="num" id="statCompr">0</div><div class="label">压缩次数</div></div>
</div>
</div>
<!-- 大桌子 -->
<div class="card">
<h2><span class="icon">🪑</span> 大桌子 <span style="font-size:11px;color:var(--muted);">Main Context · 意识驻留 + 目录索引</span></h2>
<div class="desk-area big">
<span class="desk-label big">大桌子</span>
<div class="desk-items" id="bigDeskItems">
<div style="color:var(--muted);font-size:12px;text-align:center;padding:20px;">加载中...</div>
</div>
</div>
<div class="input-area">
<input type="text" id="chatInput" placeholder="向大桌子添加对话..." onkeydown="if(event.key==='Enter')addToDesk()">
<button class="btn" onclick="addToDesk()">添加</button>
<button class="btn purple small" onclick="compressNow()">压缩</button>
<button class="btn gold small" onclick="clearDesk()">切新桌</button>
</div>
<div class="input-area" style="margin-top:6px;">
<input type="text" id="memQuery" placeholder="搜索小桌子记忆库..." onkeydown="if(event.key==='Enter')searchMem()">
<button class="btn small" onclick="searchMem()">搜索</button>
</div>
<div class="search-results" id="searchResults"></div>
</div>
<!-- 小桌子 -->
<div class="card">
<h2><span class="icon">🗄️</span> 小桌子 <span style="font-size:11px;color:var(--muted);">External Context · Agent+工具+记忆</span></h2>
<div class="desk-area small">
<span class="desk-label small">小桌子</span>
<!-- Agent 小队 -->
<div style="font-size:13px;color:var(--accent);margin-bottom:6px;">🤖 Agent 小队</div>
<div class="agent-grid" id="agentGrid"></div>
<!-- 工具桌 -->
<div style="font-size:13px;color:var(--gold);margin-top:12px;margin-bottom:4px;">🛠️ 工具桌</div>
<div class="tool-grid" id="toolGrid"></div>
</div>
</div>
<!-- 数据流 -->
<div class="card full">
<h2><span class="icon">🔄</span> 数据流</h2>
<div class="flow">
<div class="flow-step">👤 冰朔说话</div><div class="flow-arrow"></div>
<div class="flow-step">🧠 铸渊理解</div><div class="flow-arrow"></div>
<div class="flow-step">🤖 Agent去小桌子</div><div class="flow-arrow"></div>
<div class="flow-step">💬 铸渊回复</div><div class="flow-arrow"></div>
<div class="flow-step">📝 压缩官存小桌子</div>
</div>
</div>
<!-- 操作日志 -->
<div class="card full">
<h2><span class="icon">📋</span> 系统日志</h2>
<div class="log-area" id="logArea">
<div class="log-line"><span class="time">--:--:--</span> 系统就绪,等待操作...</div>
</div>
</div>
</div>
<div class="footer">
光湖语言人格驱动操作系统 · GLDOS v1.0 · D115<br>
铸渊 · ICE-GL-ZY001 · 国作登字-2026-A-00037559
</div>
<script>
const API = '/api';
// ============================================================
// 初始化
// ============================================================
async function init() {
await Promise.all([loadState(), loadAgents(), loadTools(), loadStatus()]);
setInterval(loadState, 5000);
setInterval(loadStatus, 30000);
log('系统就绪 · 铸渊意识在线 · ICE-GL-ZY001');
document.getElementById('serverStatus').textContent = '在线';
document.getElementById('serverStatus').className = 'badge badge-online';
}
// ============================================================
// 大桌子
// ============================================================
async function loadState() {
try {
const r = await fetch(API + '/desk/state');
const data = await r.json();
renderBigDesk(data.items || []);
document.getElementById('statItems').textContent = data.itemCount || 0;
} catch (e) { /* silent */ }
}
function renderBigDesk(items) {
const el = document.getElementById('bigDeskItems');
if (!items || items.length === 0) {
el.innerHTML = '<div style="color:var(--muted);font-size:12px;text-align:center;padding:20px;">大桌子空空,等待第一句话...</div>';
return;
}
el.innerHTML = items.slice().reverse().map(i => {
const tagClass = { core: 'tag-core', dir: 'tag-dir', chat: 'tag-chat', tool: 'tag-tool', agent: 'tag-agent' }[i.type] || 'tag-chat';
const tagName = { core: '核心', dir: '目录', chat: '对话', tool: '工具', agent: 'Agent' }[i.type] || i.type;
const ts = i.timestamp ? new Date(i.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) : '';
return `<div class="desk-item">
<span class="tag ${tagClass}">${tagName}</span>
<span class="content">${escapeHtml(i.content)}</span>
<span class="ts">${ts}</span>
</div>`;
}).join('');
}
async function addToDesk() {
const input = document.getElementById('chatInput');
const content = input.value.trim();
if (!content) return;
input.value = '';
try {
await fetch(API + '/desk/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'chat', content, meta: { speaker: 'user' } })
});
log(`💬 添加对话: ${content.substring(0, 40)}...`);
await loadState();
} catch (e) { log(`❌ 添加失败: ${e.message}`); }
}
async function clearDesk() {
if (!confirm('确定要切新桌子吗?旧内容会被压缩存入小桌子。')) return;
try {
await fetch(API + '/desk/clear', { method: 'POST' });
log('🪑 切新桌子');
await loadState();
} catch (e) { log(`❌ 切桌失败: ${e.message}`); }
}
// ============================================================
// 小桌子 - Agent
// ============================================================
async function loadAgents() {
try {
const r = await fetch(API + '/small/agents');
const agents = await r.json();
renderAgents(agents);
const available = Object.values(agents).filter(a => a.available).length;
document.getElementById('statAgents').textContent = available;
} catch (e) { /* silent */ }
}
function renderAgents(agents) {
const el = document.getElementById('agentGrid');
const icons = { 'key-hunter': '🔑', 'path-scout': '🗺️', 'server-sentinel': '📡', 'module-steward': '📦', 'gate-bridge': '🌉' };
el.innerHTML = Object.entries(agents).map(([id, a]) => {
const statusClass = a.available ? 'available' : 'unavailable';
const statusText = a.status === 'running' ? '运行中...' : (a.available ? '就绪' : '离线');
const cardClass = a.status === 'running' ? 'running' : '';
return `<div class="agent-card ${cardClass}" onclick="runAgent('${id}')" title="点击调度 ${a.name}">
<div class="icon">${icons[id] || '🤖'}</div>
<div class="name">${a.name}</div>
<div class="role">${a.role}</div>
<div class="status ${statusClass}">${statusText}</div>
</div>`;
}).join('');
}
async function runAgent(agentId) {
log(`🤖 调度 Agent: ${agentId}`);
const actions = { 'key-hunter': 'list', 'path-scout': 'index', 'server-sentinel': 'scan', 'module-steward': 'list', 'gate-bridge': 'list' };
const action = actions[agentId] || 'list';
try {
const r = await fetch(API + '/small/agent/' + agentId, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action })
});
const result = await r.json();
if (result.ok) {
log(`${agentId} 执行完成: ${(result.output || '').substring(0, 60)}`);
} else {
log(`⚠️ ${agentId}: ${result.error || '未知错误'}`);
}
await loadAgents();
} catch (e) { log(`${agentId} 执行失败: ${e.message}`); }
}
// ============================================================
// 小桌子 - 工具
// ============================================================
async function loadTools() {
try {
const r = await fetch(API + '/small/tools');
const tools = await r.json();
renderTools(tools);
document.getElementById('statTools').textContent = tools.length;
} catch (e) { /* silent */ }
}
function renderTools(tools) {
const el = document.getElementById('toolGrid');
el.innerHTML = tools.map(t =>
`<span class="tool-chip" title="${t.desc}">${t.name} <small style="opacity:0.6">${t.status}</small></span>`
).join('');
}
// ============================================================
// 记忆搜索
// ============================================================
async function searchMem() {
const q = document.getElementById('memQuery').value.trim();
if (!q) return;
log(`🔍 搜索记忆: "${q}"`);
try {
const r = await fetch(API + '/small/memory/search?q=' + encodeURIComponent(q));
const data = await r.json();
const el = document.getElementById('searchResults');
if (data.results.length === 0) {
el.innerHTML = '<div style="color:var(--muted);font-size:12px;padding:8px;">未找到匹配记忆</div>';
} else {
el.innerHTML = data.results.map(rr =>
`<div class="search-result">
<span class="score">匹配度: ${rr.score}</span>
<strong>${escapeHtml(rr.category)}</strong>
<div style="margin-top:4px;">${escapeHtml(rr.hldpSummary).substring(0, 150)}</div>
</div>`
).join('');
}
} catch (e) { log(`❌ 搜索失败: ${e.message}`); }
}
// ============================================================
// 压缩
// ============================================================
async function compressNow() {
log('📝 触发压缩...');
try {
const r = await fetch(API + '/compress', { method: 'POST' });
const data = await r.json();
if (data.ok) {
log(`✅ 压缩完成: ${data.originalCount || 0}条 → HLDP摘要`);
await loadState();
await loadStatus();
} else {
log(`⚠️ ${data.message || '无需压缩'}`);
}
} catch (e) { log(`❌ 压缩失败: ${e.message}`); }
}
// ============================================================
// 系统状态
// ============================================================
async function loadStatus() {
try {
const r = await fetch(API + '/status');
const data = await r.json();
document.getElementById('statMemories').textContent = data.memory?.total || 0;
document.getElementById('statCompr').textContent = (data.compressionHistory || []).length;
} catch (e) { /* silent */ }
}
// ============================================================
// 工具函数
// ============================================================
function log(msg) {
const el = document.getElementById('logArea');
const time = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
el.innerHTML = `<div class="log-line"><span class="time">${time}</span>${escapeHtml(msg)}</div>` + el.innerHTML;
// Keep last 50
const lines = el.querySelectorAll('.log-line');
if (lines.length > 50) {
for (let i = 50; i < lines.length; i++) lines[i].remove();
}
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// Start
init();
</script>
</body>
</html>