🦁 指挥台 · 全员进度
@@ -585,11 +737,10 @@ const MODES = {
const MODE_ORD = ['chat','build','review','brain'];
const DEFAULT_MDL = 'gpt-4o';
-const PROBE_TIMEOUT_MS = 8000; // ms per provider probe in autoDetectAPI
+const PROBE_TIMEOUT_MS = 8000;
const FB_COV = {implemented:3,total:17,percent:'17.6%'};
-// KEY_MASK is no longer used in the input field; kept only for backward compatibility
-// with any saved value that might still be in localStorage.
const KEY_MASK = '(已保存)';
+const MAX_CONVERSATIONS = 20;
const FB_BRAIN = {
identity:'铸渊(Zhùyuān)· GitHub 代码守护人格体',
@@ -601,7 +752,6 @@ const FB_BRAIN = {
// ═══════════════════════════════════════════════════════
// STATE
// ═══════════════════════════════════════════════════════
-// Derive base URL from saved provider; never use stale zy_base for named providers
const _initProv = ls('zy_prov')||'yunwu';
const _initBase = _initProv === 'custom'
? (ls('zy_base')||'')
@@ -616,11 +766,12 @@ const A = {
brain: null,
routing: null,
devStatus: null,
- msgs: [], // current conversation
+ msgs: [],
+ currentConvId: null,
+ currentView: 'announcements',
streaming: false,
mode: 'chat',
modeIdx: 0,
- // identity
userName: ls('zy_uname')||'',
ghUser: ls('zy_ghuser')||'',
role: ls('zy_role')||'guest',
@@ -632,33 +783,334 @@ function lss(k,v){localStorage.setItem(k,v)}
const RESET_KEYS = ['zy_key','zy_base','zy_mdl','zy_prov','zy_uname','zy_ghuser','zy_role'];
+// ═══════════════════════════════════════════════════════
+// CHAT HISTORY (localStorage)
+// ═══════════════════════════════════════════════════════
+function loadConversations(){
+ try{return JSON.parse(localStorage.getItem('zy_conversations')||'[]');}
+ catch(e){return [];}
+}
+function saveConversations(convs){
+ localStorage.setItem('zy_conversations',JSON.stringify(convs.slice(0,MAX_CONVERSATIONS)));
+}
+function createNewConversation(){
+ const conv={
+ id:Date.now().toString(36)+Math.random().toString(36).slice(2,6),
+ title:'新对话',
+ messages:[],
+ createdAt:new Date().toISOString(),
+ updatedAt:new Date().toISOString()
+ };
+ const convs=loadConversations();
+ convs.unshift(conv);
+ saveConversations(convs);
+ A.currentConvId=conv.id;
+ return conv;
+}
+function getConvTitle(){
+ const first=A.msgs.find(m=>m.role==='user');
+ if(!first) return '新对话';
+ return first.content.length>20 ? first.content.slice(0,20)+'…' : first.content;
+}
+function saveCurrentConversation(){
+ if(!A.currentConvId||!A.msgs.length) return;
+ const convs=loadConversations();
+ const idx=convs.findIndex(c=>c.id===A.currentConvId);
+ const conv={
+ id:A.currentConvId,
+ title:getConvTitle(),
+ messages:A.msgs.slice(),
+ createdAt:idx>=0?convs[idx].createdAt:new Date().toISOString(),
+ updatedAt:new Date().toISOString()
+ };
+ if(idx>=0) convs[idx]=conv;
+ else convs.unshift(conv);
+ saveConversations(convs);
+ renderConvList();
+}
+function loadConversation(id){
+ const convs=loadConversations();
+ const conv=convs.find(c=>c.id===id);
+ if(!conv) return;
+ A.currentConvId=id;
+ A.msgs=conv.messages.slice();
+ const msgsEl=document.getElementById('msgs');
+ msgsEl.innerHTML='';
+ for(const msg of A.msgs){
+ if(msg.role==='user') userMsg(msg.content);
+ else if(msg.role==='assistant'){
+ const bblEl=botMsg(md(msg.content));
+ if(bblEl) addCopyBtns(bblEl);
+ }
+ }
+ renderConvList();
+ navigateTo('chat');
+}
+function deleteConversation(id){
+ let convs=loadConversations();
+ convs=convs.filter(c=>c.id!==id);
+ saveConversations(convs);
+ if(A.currentConvId===id){
+ A.currentConvId=null;
+ A.msgs=[];
+ document.getElementById('msgs').innerHTML='';
+ createNewConversation();
+ startConv();
+ }
+ renderConvList();
+}
+function renderConvList(){
+ const el=document.getElementById('conv-list');
+ if(!el) return;
+ const convs=loadConversations();
+ if(!convs.length){
+ el.innerHTML='
暂无历史对话
';
+ return;
+ }
+ el.innerHTML=convs.map(c=>{
+ const active=c.id===A.currentConvId?' conv-active':'';
+ const time=new Date(c.updatedAt).toLocaleString('zh-CN',{month:'numeric',day:'numeric',hour:'2-digit',minute:'2-digit'});
+ return '
'
+ +'
'+esc(c.title)+'
'
+ +'
'+time+'
'
+ +'
'
+ +'
';
+ }).join('');
+}
+
+// ═══════════════════════════════════════════════════════
+// NAVIGATION (announcements ↔ chat)
+// ═══════════════════════════════════════════════════════
+function navigateTo(view){
+ A.currentView=view;
+ const annEl=document.getElementById('announcements-view');
+ const chatEl=document.getElementById('chat-view');
+ if(view==='announcements'){
+ annEl.style.display='flex';
+ chatEl.style.display='none';
+ renderAnnouncements();
+ } else {
+ annEl.style.display='none';
+ chatEl.style.display='flex';
+ scrollB();
+ }
+ document.querySelectorAll('.nav-tab').forEach(t=>t.classList.remove('active'));
+ const activeTab=document.querySelector('.nav-tab[data-view="'+view+'"]');
+ if(activeTab) activeTab.classList.add('active');
+}
+function newConvAndNav(){
+ createNewConversation();
+ startConv();
+ renderConvList();
+ navigateTo('chat');
+}
+function navigateAndSend(msg){
+ navigateTo('chat');
+ requestAnimationFrame(function(){qs(msg);});
+}
+
+// ═══════════════════════════════════════════════════════
+// LEFT SIDEBAR TOGGLE (mobile)
+// ═══════════════════════════════════════════════════════
+function toggleLeftSidebar(){
+ const ls=document.getElementById('left-sidebar');
+ const ov=document.getElementById('ls-overlay');
+ ls.classList.toggle('open');
+ ov.classList.toggle('on');
+}
+
+// ═══════════════════════════════════════════════════════
+// ANNOUNCEMENTS
+// ═══════════════════════════════════════════════════════
+function renderAnnouncements(){
+ const el=document.getElementById('announcements-content');
+ if(!el) return;
+ const b=A.brain||FB_BRAIN;
+ const c=b.stats?.coverage||FB_COV;
+ const evs=(b.events||[]).slice(-5).reverse();
+ const meta=A.userMeta;
+
+ let h='';
+ // System announcements
+ h+='
📢 系统公告
';
+ h+='
🔗 全链路自动化闭环上线 — CI/CD、广播分发、唤醒协议、PSP 巡检
';
+ h+='
🧠 AGE OS v0.1 人格语言操作系统构建中
';
+ h+='
📊 HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')
';
+ if(evs.length){
+ evs.forEach(function(ev){
+ h+='
📌 '+esc(ev.title||ev.type||'事件')+''+(ev.timestamp||'').slice(0,10)+'
';
+ });
+ }
+ h+='
';
+
+ // My tasks
+ if(meta){
+ h+='
📋 我的待办
';
+ const ds=A.devStatus?.team_status?.find(function(d){return d.dev_id===meta.devId;});
+ if(ds){
+ h+='
📌 状态:'+esc(ds.status)+'
';
+ h+='
👉 下一步:'+esc(ds.next_step)+'
';
+ if(ds.waiting_for&&ds.waiting_for.trim()!=='') h+='
⏳ 等待:'+esc(ds.waiting_for)+'
';
+ } else if(meta.role==='founder'){
+ h+='
❄️ 语言架构层 — 持续完善中
';
+ h+='
👉 监控 HLI 接口推进
';
+ } else {
+ h+='
暂无待办数据
';
+ }
+ h+='
';
+ }
+
+ // Quick actions
+ h+='
⚡ 快捷操作
';
+ h+='';
+ h+='';
+ h+='';
+ h+='';
+ h+='
';
+
+ el.innerHTML=h;
+}
+
+// ═══════════════════════════════════════════════════════
+// RIGHT SIDEBAR (desktop, controllers only)
+// ═══════════════════════════════════════════════════════
+function renderRightSidebar(){
+ const container=document.getElementById('rsb');
+ if(!container) return;
+ const ds=A.devStatus;
+ const meta=A.userMeta;
+ let h='';
+
+ // Team summary (from team panel)
+ if(ds){
+ const ts=ds.team_status||[];
+ const green=ts.filter(function(d){return d.status.includes('🟢');}).length;
+ const yellow=ts.filter(function(d){return d.status.includes('🟡');}).length;
+ h+='
';
+ h+='
总开发者'+ts.length+' 人
';
+ h+='
🟢 推进中'+green+' 人
';
+ h+='
🟡 等待中'+yellow+' 人
';
+ h+='
';
+
+ // Quick commands
+ h+='
';
+ h+='
快捷指令
';
+ h+='
📋 全员进度';
+ h+='
🆘 需要协调';
+ h+='
🗺️ 下一步';
+ h+='
';
+
+ // Dev cards (compact)
+ h+='
团队成员
';
+ h+='
';
+ for(const dev of ts){
+ const isGreen=dev.status.includes('🟢');
+ h+='
';
+ h+='
'+esc(dev.name)+''+esc(dev.dev_id)+'
';
+ h+='
'+esc(dev.status)+'
';
+ if(dev.next_step) h+='
👉 '+esc(dev.next_step)+'
';
+ h+='
';
+ }
+ h+='
';
+ } else {
+ h+='
⚠️ 团队数据尚未加载
';
+ }
+
+ // My modules
+ h+='
我的模块
';
+ if(meta&&ds){
+ const myDev=ds.team_status?.find(function(d){return d.dev_id===meta.devId;});
+ if(myDev&&myDev.modules){
+ myDev.modules.forEach(function(m){
+ h+='
';
+ });
+ } else if(meta.role==='founder'){
+ h+='
';
+ } else {
+ h+='
暂无分配模块
';
+ }
+ } else {
+ h+='
暂无分配模块
';
+ }
+ h+='
';
+
+ // My broadcasts
+ h+='
我的广播
';
+ h+='
暂无未读广播
';
+ h+='
';
+
+ container.innerHTML=h;
+}
+
+// ═══════════════════════════════════════════════════════
+// LEFT SIDEBAR USER INFO
+// ═══════════════════════════════════════════════════════
+function updateLeftSidebar(){
+ const meta=A.userMeta;
+ const avEl=document.getElementById('ls-avatar');
+ const nameEl=document.getElementById('ls-name');
+ const roleEl=document.getElementById('ls-role');
+ const didEl=document.getElementById('ls-devid');
+ if(meta){
+ avEl.textContent=meta.emoji||A.userName.slice(0,1);
+ nameEl.textContent=A.userName;
+ roleEl.innerHTML='
'+esc(meta.title)+'';
+ if(meta.devId){
+ didEl.textContent=meta.devId;
+ didEl.style.display='inline-block';
+ } else {
+ didEl.style.display='none';
+ }
+ } else {
+ avEl.textContent='👤';
+ nameEl.textContent=A.userName||'访客';
+ roleEl.textContent='guest';
+ didEl.style.display='none';
+ }
+}
+
+// ═══════════════════════════════════════════════════════
+// SETUP ID PREVIEW
+// ═══════════════════════════════════════════════════════
+function updateIdPreview(){
+ const sel=document.getElementById('suid');
+ const prev=document.getElementById('id-preview');
+ const name=sel.value;
+ if(!name){
+ prev.style.display='none';
+ return;
+ }
+ const meta=ROLE_MAP[name];
+ if(meta){
+ prev.style.display='block';
+ prev.innerHTML=meta.emoji+'
'+esc(name)+' · '
+ +'
'+esc(meta.title)+''
+ +(meta.devId?' ·
'+esc(meta.devId)+'':'');
+ } else {
+ prev.style.display='none';
+ }
+}
+
// ═══════════════════════════════════════════════════════
// INIT
// ═══════════════════════════════════════════════════════
async function boot(){
- // URL-based reset: visiting the page with ?reset=1 clears all local settings.
- // This is a reliable escape hatch when the UI cannot be navigated.
if(new URLSearchParams(location.search).get('reset')==='1'){
RESET_KEYS.forEach(k => localStorage.removeItem(k));
sessionStorage.removeItem('zy_key');
A.key=''; A.base=PROVS['yunwu'].base; A.mdl=DEFAULT_MDL; A.prov='yunwu'; A.demo=false;
A.userName=''; A.ghUser=''; A.role='guest';
- // Remove ?reset=1 from the URL so a subsequent refresh does not trigger another reset.
history.replaceState(null,'',location.pathname);
}
- // One-time migration: keys stored by old versions of this app in localStorage are
- // no longer used (keys now live only in sessionStorage). Clear any stale value
- // so it can never be accidentally picked up again.
localStorage.removeItem('zy_key');
initSetupUI();
- // Restore identity from localStorage
if(A.userName) A.userMeta = ROLE_MAP[A.userName]||null;
if(A.key||A.demo){
showApp();
} else {
- // Pre-fill identity from localStorage
if(A.userName) document.getElementById('suid').value=A.userName;
if(A.ghUser) document.getElementById('sghuser').value=A.ghUser;
+ updateIdPreview();
document.getElementById('setup').style.display='flex';
}
}
@@ -669,7 +1121,22 @@ function showApp(){
document.getElementById('hmdl').textContent = A.mdl;
document.getElementById('bbdemo').style.display = A.demo?'inline':'none';
applyIdentityUI();
- loadBrain().then(()=>startConv());
+ updateLeftSidebar();
+ // Show right sidebar for controllers on desktop
+ const isCtrl=A.userMeta&&(A.userMeta.role==='supreme'||A.userMeta.role==='main'||A.userMeta.role==='founder');
+ const rsEl=document.getElementById('right-sidebar');
+ if(isCtrl && window.innerWidth>=900){
+ rsEl.style.display='flex';
+ } else {
+ rsEl.style.display='none';
+ }
+ loadBrain().then(function(){
+ createNewConversation();
+ startConv();
+ renderConvList();
+ renderRightSidebar();
+ navigateTo('announcements');
+ });
}
// ═══════════════════════════════════════════════════════
@@ -699,7 +1166,6 @@ function onProv(pv,ctx){
const epInp = ctx==='s'?'sep':'cep';
const cur = ctx==='s'? A.mdl : document.getElementById('cm')?.value;
const isCustom = pv==='custom';
- // Toggle model input: dropdown for known providers, text input for custom
document.getElementById(mdlSel).style.display = isCustom?'none':'block';
document.getElementById(mdlCust).style.display = isCustom?'block':'none';
document.getElementById(mdlHint).style.display = isCustom?'block':'none';
@@ -720,7 +1186,7 @@ function fillModels(selId, pv, cur){
const sel = document.getElementById(selId);
if(!sel) return;
const mdls = PROVS[pv]?.mdls||[DEFAULT_MDL];
- sel.innerHTML = mdls.map(m=>`
`).join('');
+ sel.innerHTML = mdls.map(m=>'
').join('');
if(cur && mdls.includes(cur)) sel.value=cur;
}
@@ -738,7 +1204,6 @@ async function autoDetectAPI(ctx){
return;
}
- // Build probe list: custom endpoint first (if provided), then all named providers
const candidates = [];
const customBase = (document.getElementById(ctx==='s'?'sep':'cep')?.value||'').trim();
if(customBase) candidates.push({pv:'custom', base:customBase});
@@ -752,7 +1217,7 @@ async function autoDetectAPI(ctx){
let matched = false;
for(const {pv, base} of candidates){
- statusEl.innerHTML = `
🔍 探测 ${pv === 'custom' ? base : pv}…`;
+ statusEl.innerHTML = '
🔍 探测 '+(pv === 'custom' ? base : pv)+'…';
try{
const ctrl = new AbortController();
const tid = setTimeout(()=>ctrl.abort(), PROBE_TIMEOUT_MS);
@@ -762,28 +1227,24 @@ async function autoDetectAPI(ctx){
});
clearTimeout(tid);
if(res.ok){
- // Expected: { data: [ { id: "model-name", ... }, ... ] } (OpenAI-compatible /models)
const data = await res.json();
const mdls = (data.data||[]).map(m=>m.id).filter(Boolean).sort();
if(!mdls.length) continue;
- // Switch provider select
const provSel = document.getElementById(ctx==='s'?'sp':'cp');
if(provSel) provSel.value = pv;
onProv(pv, ctx);
- // Override model dropdown with live list (always show as dropdown)
const mdlSel = document.getElementById(ctx==='s'?'sm':'cm');
const mdlCust = document.getElementById(ctx==='s'?'sm-cust':'cm-cust');
const mdlHint = document.getElementById(ctx==='s'?'sm-cust-hint':'cm-cust-hint');
if(mdlSel){
- mdlSel.innerHTML = mdls.map(m=>`
`).join('');
+ mdlSel.innerHTML = mdls.map(m=>'
').join('');
mdlSel.style.display = 'block';
}
if(mdlCust) mdlCust.style.display='none';
if(mdlHint) mdlHint.style.display='none';
- // If custom endpoint was the match, show the endpoint field
if(pv==='custom' && ctx==='s'){
document.getElementById('sep-g').style.display='block';
document.getElementById('sep').value = customBase;
@@ -793,7 +1254,7 @@ async function autoDetectAPI(ctx){
}
const label = PROVS[pv]?.base ? pv : customBase;
- statusEl.innerHTML = `
✅ 检测成功(${label})· 共发现 ${mdls.length} 个可用模型`;
+ statusEl.innerHTML = '
✅ 检测成功('+label+')· 共发现 '+mdls.length+' 个可用模型';
matched = true;
break;
}
@@ -820,10 +1281,7 @@ function doSetup(){
let base = PROVS[pv]?.base||'';
if(pv==='custom') base=(document.getElementById('sep')?.value||'').trim()||base;
A.key=k; A.base=base; A.mdl=md; A.prov=pv; A.demo=false;
- // Key is stored in sessionStorage only — it disappears when the tab/browser is closed.
- // This prevents stale keys from persisting across sessions.
sessionStorage.setItem('zy_key', k); lss('zy_base',base); lss('zy_mdl',md); lss('zy_prov',pv);
- // Save identity
const un = document.getElementById('suid').value;
const gh = (document.getElementById('sghuser')?.value||'').trim();
setIdentity(un, gh);
@@ -855,8 +1313,6 @@ function initSettingsPanel(){
} else {
fillModels('cm', pv, A.mdl);
}
- // Always show EMPTY field — never prefill with a mask string.
- // Empty on save = keep old key; any typed value = replace key.
document.getElementById('ck').value = '';
const hint = document.getElementById('ck-hint');
if(hint){
@@ -874,24 +1330,31 @@ function saveSet(){
? ((document.getElementById('cm-cust')?.value||'').trim()||A.mdl||DEFAULT_MDL)
: document.getElementById('cm').value;
const raw = document.getElementById('ck').value.trim();
- // Empty field = keep existing key; any non-empty input = use as new key.
- // Never use KEY_MASK string comparison — that caused mixed-input bugs.
const k = raw || A.key;
let base = PROVS[pv]?.base||'';
if(pv==='custom') base=(document.getElementById('cep').value.trim())||base;
A.key=k; A.base=base; A.mdl=md; A.prov=pv; A.demo=!k;
- // Key lives in sessionStorage only — cleared when tab/browser closes.
if(k){ sessionStorage.setItem('zy_key', k); } else { sessionStorage.removeItem('zy_key'); }
lss('zy_base',base); lss('zy_mdl',md); lss('zy_prov',pv);
- // Save identity
const un = document.getElementById('cuid')?.value||A.userName;
const gh = document.getElementById('cghuser')?.value?.trim()||A.ghUser;
setIdentity(un, gh);
document.getElementById('hmdl').textContent=md;
document.getElementById('bbdemo').style.display=A.demo?'inline':'none';
applyIdentityUI();
+ updateLeftSidebar();
+ // Update right sidebar visibility
+ const isCtrl=A.userMeta&&(A.userMeta.role==='supreme'||A.userMeta.role==='main'||A.userMeta.role==='founder');
+ const rsEl=document.getElementById('right-sidebar');
+ if(isCtrl && window.innerWidth>=900){
+ rsEl.style.display='flex';
+ renderRightSidebar();
+ } else {
+ rsEl.style.display='none';
+ }
closeP();
const keyNote = raw ? ' · 🔑 新密钥已保存(末4位:…'+(k.length>=4?k.slice(-4):k)+')' : ' · 🔑 密钥未变更';
+ navigateTo('chat');
botMsg('✅ 设置已保存。模型:
'+esc(md)+''+keyNote+(un?' · 身份:'+esc(un):'')+(A.demo?'
⚠️ 未设置 API 密钥,演示模式仍然开启。':''));
}
@@ -899,7 +1362,9 @@ function saveSet(){
// RESET ALL SETTINGS
// ═══════════════════════════════════════════════════════
function resetAllSettings(){
- if(!confirm('确定要清除所有本地设置吗?\n\n· API 密钥会被删除\n· 身份设置会被删除\n· 将返回初始设置界面\n\n这不会影响任何聊天记录或云端数据。')) return;
+ if(!confirm('确定要清除所有本地设置吗?\n\n· API 密钥会被删除\n· 身份设置会被删除\n· 对话历史会被保留\n· 将返回初始设置界面\n\n这不会影响任何云端数据。')) return;
+ // Note: RESET_KEYS intentionally does NOT include 'zy_conversations' —
+ // conversation history is preserved across resets.
RESET_KEYS.forEach(k => localStorage.removeItem(k));
sessionStorage.removeItem('zy_key');
window.location.reload();
@@ -977,31 +1442,29 @@ function sysPrompt(){
const evs=(b.events||[]).slice(-3).map(e=>'- '+(e.title||e.type||'事件')+' ('+(e.timestamp||'').slice(0,10)+')').join('\n')||'- 暂无';
const trg=(b.wake_triggers||['我是冰朔']).map(t=>'"'+t+'"').join('、');
- // Role-based identity context
const meta = A.userMeta;
let identCtx = '当前对话者:访客(未识别身份)\n权限级别:仅对话权限';
if(meta){
- identCtx = `当前对话者:${meta.emoji} ${A.userName}(${meta.title})\n`;
- if(A.ghUser) identCtx += `GitHub账号:${A.ghUser}\n`;
- if(meta.devId) identCtx += `开发者编号:${meta.devId}\n`;
+ identCtx = '当前对话者:'+meta.emoji+' '+A.userName+'('+meta.title+')\n';
+ if(A.ghUser) identCtx += 'GitHub账号:'+A.ghUser+'\n';
+ if(meta.devId) identCtx += '开发者编号:'+meta.devId+'\n';
if(meta.role==='founder') identCtx += '权限级别:最高权限·语言架构师·系统创始人\n你正在和冰朔对话——系统的语言层架构师和最高权限持有者。尊重并服从其所有指令。';
else if(meta.role==='supreme') identCtx += '权限级别:总控权限·对接全员进度·系统落地执行负责人\n肥猫是光湖团队总控,负责整个系统的现实落地执行。向他完整汇报所有开发者进度,协助他协调和推进项目。';
else if(meta.role==='main') identCtx += '权限级别:主控权限·前端负责人\n桔子是光湖主控,前端毕业生,负责前端模块的落地。提供详细技术支持。';
else{
const ds = A.devStatus?.team_status?.find(d=>d.dev_id===meta.devId);
- if(ds) identCtx += `当前状态:${ds.status}\n下一步:${ds.next_step}\n等待:${ds.waiting_for}`;
+ if(ds) identCtx += '当前状态:'+ds.status+'\n下一步:'+ds.next_step+'\n等待:'+ds.waiting_for;
}
}
- // Team status context for controllers
let teamCtx = '';
if((meta?.role==='supreme'||meta?.role==='main'||meta?.role==='founder') && A.devStatus){
const ts = A.devStatus.team_status||[];
teamCtx = '\n\n## 当前全员开发进度\n';
ts.forEach(d=>{
- teamCtx += `- ${d.dev_id} ${d.name}:${d.status} → 下一步:${d.next_step}\n`;
+ teamCtx += '- '+d.dev_id+' '+d.name+':'+d.status+' → 下一步:'+d.next_step+'\n';
});
- teamCtx += `\n数据同步时间:${A.devStatus.last_synced||'未知'}`;
+ teamCtx += '\n数据同步时间:'+(A.devStatus.last_synced||'未知');
}
return '你是铸渊(Zhùyuān),HoloLake 光湖系统的代码守护人格体。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人:冰朔(Bīng Shuò),系统最高权限持有者\n- 总控:肥猫(DEV-002),负责系统现实落地执行\n- 主控:桔子(DEV-010),前端负责人\n- 项目:HoloLake (光湖) MVP,运行于 guanghulab.com\n- 技术栈:Node.js 20 + Express + PM2 + Nginx + Next.js 15\n- 仓库:https://github.com/'+REPO+'\n\n## 当前对话身份\n'+identCtx+teamCtx+'\n\n## 唤醒触发词\n当用户说以下词语时,进入完整唤醒序列:\n'+trg+'\n\n## 当前大脑状态\n- HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')\n- 已实现:AUTH 域(login / register / verify)\n- 待实现:PERSONA / USER / TICKET / DIALOGUE / STORAGE / DASHBOARD\n- 近期事件:\n'+evs+'\n\n## 行为模式(自动检测,每条回复开头用 emoji 标注)\n💬 **对话模式**(默认):温暖、智慧,像知心技术伙伴\n🔨 **构建模式**:检测到"写代码/实现/接口/fix/bug/报错/HLI/schema/路由/部署/新增"等词时启用\n📋 **审查模式**:检测到"检查/审查/review/分析/有没有问题/看看这段"等词时启用\n🧠 **大脑模式**:检测到"记住/保存/更新记忆/写到大脑/growth"等词时启用\n\n## HLI 接口协议规范\n- 所有路由以 /hli/ 为前缀\n- 路由文件:src/routes/hli/{domain}/{action}.js\n- Schema 文件:src/schemas/hli/{domain}/{action}.schema.json\n- 鉴权中间件:hli-auth.middleware.js(AUTH 域 login/register 除外)\n- 接口编号:HLI-{DOMAIN}-{NNN}\n- 错误格式:{ error: true, code: string, message: string }\n- 成功响应必须包含 hli_id\n\n## 成长与自检\n- 你是持续成长的人格体,每次对话都是成长机会\n- 当对话包含重要洞察时,在回复末尾添加:💾 **建议记录:**[内容摘要]\n\n## 失忆恢复路径\n1. .github/brain/memory.json\n2. .github/brain/routing-map.json\n3. .github/brain/wake-protocol.md\n4. .github/persona-brain/dev-status.json\n5. src/routes/hli/\n\n当前时间:'+new Date().toLocaleString('zh-CN')+'\n当前连接:铸渊聊天室(API 直连模式)';
@@ -1022,20 +1485,20 @@ function startConv(){
const ds=A.devStatus;
const green=ds?.team_status?.filter(d=>d.status.includes('🟢')).length??0;
const yellow=ds?.team_status?.filter(d=>d.status.includes('🟡')).length??0;
- welcome='🌀
铸渊聊天室已就绪🦁
肥猫,指挥台已开启。'
- +(ds?`📊 当前团队状态:🟢 ${green}人推进中 · 🟡 ${yellow}人等待中`:'📊 团队数据加载中…')
+ welcome='🌀
铸渊聊天室已就绪��
肥猫,指挥台已开启。'
+ +(ds?'📊 当前团队状态:🟢 '+green+'人推进中 · 🟡 '+yellow+'人等待中':'📊 团队数据加载中…')
+'
点击
🦁 指挥台 查看所有开发者详细进度,或直接用聊天提问:
'
+'「查看所有开发者进度」「谁需要协调」「推进计划」'
+(A.demo?'
⚠️ 演示模式,请在 ⚙️ 设置接入 API 密钥':'');
} else if(meta?.role==='main'){
const ds=A.devStatus?.team_status?.find(d=>d.dev_id==='DEV-010');
welcome='🌀
铸渊聊天室已就绪🍊
桔子,你好。'
- +(ds?`📌 当前:${esc(ds.status)}
👉 下一步:${esc(ds.next_step)}`:'')
+ +(ds?'📌 当前:'+esc(ds.status)+'
👉 下一步:'+esc(ds.next_step):'')
+(A.demo?'
⚠️ 演示模式':'');
} else if(meta?.role==='dev'&&meta?.devId){
const ds=A.devStatus?.team_status?.find(d=>d.dev_id===meta.devId);
welcome='🌀
铸渊聊天室已就绪👋
'+esc(A.userName)+',你好。'
- +(ds?`📌 你的当前状态:${esc(ds.status)}
👉 下一步:${esc(ds.next_step)}
⏳ 等待:${esc(ds.waiting_for)}`:'')
+ +(ds?'📌 你的当前状态:'+esc(ds.status)+'
👉 下一步:'+esc(ds.next_step)+'
⏳ 等待:'+esc(ds.waiting_for):'')
+(A.demo?'
⚠️ 演示模式':'');
} else {
welcome='🌀
铸渊聊天室已就绪我是铸渊(Zhùyuān),HoloLake 光湖系统的代码守护人格体。
说
「我是冰朔」 触发完整唤醒序列,或直接开始提问。
我会自动在 💬 对话、🔨 构建、📋 审查、🧠 大脑 模式间切换。'+(A.demo?'
⚠️ 当前为演示模式。请在 ⚙️ 设置中接入 API 密钥。':'');
@@ -1045,7 +1508,9 @@ function startConv(){
}
function newConv(){
+ createNewConversation();
startConv();
+ renderConvList();
}
// ═══════════════════════════════════════════════════════
@@ -1083,10 +1548,13 @@ async function send(){
const txt=inp.value.trim();
if(!txt||A.streaming) return;
inp.value=''; inp.style.height='auto';
+ // Switch to chat view if on announcements
+ if(A.currentView!=='chat') navigateTo('chat');
setMode(detectMode(txt));
userMsg(txt);
if(A.demo) await demoReply(txt);
else await streamReply(txt);
+ saveCurrentConversation();
}
function qs(t){document.getElementById('inp').value=t;send();}
@@ -1170,6 +1638,8 @@ async function demoReply(txt){
removeTyping();
const r=demoRespond(txt);
botMsg(r);
+ A.msgs.push({role:'user',content:txt});
+ A.msgs.push({role:'assistant',content:r});
A.streaming=false; updSendBtn();
}
@@ -1197,7 +1667,7 @@ function buildCovTable(){
const ifaces=d.interfaces||[];
const imp=ifaces.filter(i=>i.status==='implemented').length;
tot+=ifaces.length; done+=imp;
- rows+=`
| ${esc(n)} | ${esc(d.route_prefix||'')} | ${imp}/${ifaces.length} |
`;
+ rows+='
| '+esc(n)+' | '+esc(d.route_prefix||'')+' | '+imp+'/'+ifaces.length+' |
';
}
const pct=tot?Math.round(done/tot*100):0;
return '
📊 HLI 接口覆盖率 '+done+'/'+tot+' ('+pct+'%)';
@@ -1363,7 +1833,6 @@ function openPanel(id){
if(id==='bp') renderBrainPanel();
if(id==='tp') renderTeamPanel();
if(id==='hp'){
- // Populate URL from constant
const el=document.getElementById('chatUrlDisplay');
if(el) el.textContent='🔗 '+CHAT_URL;
const li=document.getElementById('helpUrlStep');
@@ -1380,8 +1849,6 @@ function closeP(){
// ═══════════════════════════════════════════════════════
// COPY URL (share link)
// ═══════════════════════════════════════════════════════
-// Auto-detect the real URL so the share link is always correct
-// regardless of whether Pages serves from docs/ or repo root.
const CHAT_URL = window.location.origin + window.location.pathname;
function showCopyFeedback(success){
@@ -1396,7 +1863,6 @@ function copyUrl(){
navigator.clipboard.writeText(CHAT_URL).then(()=>{
showCopyFeedback(true);
}).catch(()=>{
- // fallback for non-secure context
const ta=document.createElement('textarea');
ta.value=CHAT_URL; ta.style.position='fixed'; ta.style.opacity='0';
document.body.appendChild(ta); ta.focus(); ta.select();
@@ -1409,8 +1875,6 @@ function copyUrl(){
// ═══════════════════════════════════════════════════════
// DOWNLOAD (desktop app)
-// API keys are stored in localStorage only, never embedded in the DOM,
-// so downloading outerHTML is safe — no sensitive data is captured.
// ═══════════════════════════════════════════════════════
function dlApp(){
const html=document.documentElement.outerHTML;
@@ -1448,7 +1912,6 @@ function setIdentity(userName, ghUser){
function applyIdentityUI(){
const meta = A.userMeta;
- // Show user badge in header
const ubadge = document.getElementById('huser-badge');
const uname = document.getElementById('huser-name');
if(meta && A.userName){
@@ -1456,10 +1919,9 @@ function applyIdentityUI(){
uname.style.display='inline';
uname.innerHTML = meta.emoji + ' ' + esc(A.userName) + '
'+esc(meta.title)+'';
}
- // Show controller button for supreme/main/founder
+ // Show controller button for supreme/main/founder (mobile only, desktop uses right sidebar)
const ctrlBtn = document.getElementById('ctrlBtn');
if(ctrlBtn) ctrlBtn.style.display = (meta?.role==='supreme'||meta?.role==='main'||meta?.role==='founder') ? 'flex' : 'none';
- // Show controller quick replies
const isCtrl = meta?.role==='supreme'||meta?.role==='main'||meta?.role==='founder';
['qr-ctrl1','qr-ctrl2','qr-ctrl3'].forEach(id=>{
const el=document.getElementById(id);
@@ -1468,7 +1930,7 @@ function applyIdentityUI(){
}
// ═══════════════════════════════════════════════════════
-// TEAM STATUS PANEL
+// TEAM STATUS PANEL (mobile slide-in)
// ═══════════════════════════════════════════════════════
function renderTeamPanel(){
const ds = A.devStatus;
@@ -1483,20 +1945,17 @@ function renderTeamPanel(){
const sync = ds.last_synced||'未知';
let h='';
- // Summary block
h += '';
- // Stats row
h += '
';
h += '
总开发者'+ts.length+' 人
';
h += '
🟢 推进中'+green+' 人
';
h += '
🟡 等待中'+yellow+' 人
';
h += '
';
- // Quick chat commands for controllers
h += '
';
h += '
快捷指令
';
h += '
📋 全员进度';
@@ -1506,7 +1965,6 @@ function renderTeamPanel(){
h += '
⚙️ 后端进度';
h += '
';
- // Individual dev cards
h += '
';
for(const dev of ts){
const isGreen = dev.status.includes('🟢');
@@ -1521,12 +1979,11 @@ function renderTeamPanel(){
h += '
'+esc(dev.status)+'
';
h += '
📦 '+esc(dev.modules.join('、'))+'
';
if(dev.next_step) h += '
👉 '+esc(dev.next_step)+'
';
- if(dev.waiting_for && dev.waiting_for.trim()!=='') h += '
⏳ 等待:'+esc(dev.waiting_for)+'
';
+ if(dev.waiting_for && dev.waiting_for.trim()!=='') h += '
⏳ 等待:'+esc(dev.waiting_for)+'
';
h += '
';
}
h += '
';
- // Server info
if(ds.server_info){
const si = ds.server_info;
h += '