- big-desk.js: 意识驻留 + HLDP索引 + 自动清理 - small-desk.js: 记忆库检索 + Agent调度 + 工具桌管理 - compressor.js: 对话→HLDP自动压缩 + 写入小桌子 - server/index.js: REST API (端口3921) + 静态文件服务 - public/index.html: 交互式前端界面 ICE-GL-ZY001 · D115
477 lines
19 KiB
HTML
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>
|