From aac7f53d4b36a591d0bf8facbfd583412bda704b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 07:21:28 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20upgrade=20=E9=93=B8=E6=B8=8A=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=20to=20v4.0=20with=20three-column=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Three-column layout on desktop (>900px): left sidebar, center area, right sidebar - Left sidebar: user identity, nav tabs (公告栏/聊天), conversation history, settings - Center area switches between announcements and chat views - Right sidebar: always-visible team panel for controllers (founder/supreme/main) - Chat history system with localStorage (zy_conversations, max 20 conversations) - Announcements page with system info from brain files, tasks, quick actions - Mobile responsive: left sidebar toggleable via hamburger menu - Identity preview on setup screen with DEV-ID display - All existing JS functions, API streaming, demo mode, brain loading preserved - Version bumped from v3.1 to v4.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/index.html | 723 ++++++++++++++++++++++++++++++++++-------- frontend/HoloLake-App | 1 - 2 files changed, 597 insertions(+), 127 deletions(-) delete mode 160000 frontend/HoloLake-App diff --git a/docs/index.html b/docs/index.html index 4eb9924e..6c243db1 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,5 +1,5 @@ - + @@ -17,6 +17,7 @@ --text:#e2e8f0;--dim:#64748b;--muted:#1e293b; --mc:#4f8ef7;--mb:#fb923c;--mr:#a78bfa;--mn:#34d399; --r:14px;--rs:8px; + --ls-w:260px;--rs-w:360px; } html,body{height:100%;background:var(--bg);color:var(--text);font-family:'PingFang SC','Microsoft YaHei','Segoe UI',sans-serif;font-size:15px;line-height:1.6} @@ -37,10 +38,87 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:'PingFa .sc-btn:hover{opacity:.85} .sc-skip{text-align:center;font-size:12px;color:var(--dim);margin-top:12px} .sc-skip a{color:var(--accent);text-decoration:none;cursor:pointer} +#id-preview{display:none;text-align:center;margin-bottom:10px;padding:10px;background:var(--s3);border-radius:8px;font-size:14px;font-weight:600} -/* ─── APP SHELL ─── */ -#app{display:none;flex-direction:column;height:100vh;max-width:820px;margin:0 auto} -#app.on{display:flex} +/* ─── APP SHELL (three-column) ─── */ +#app{display:none;height:100vh;width:100%;overflow:hidden} +#app.on{display:flex;flex-direction:column} + +/* ─── LEFT SIDEBAR ─── */ +#left-sidebar{width:var(--ls-w);flex-shrink:0;background:var(--s1);border-right:1px solid var(--border);display:none;flex-direction:column;overflow:hidden;z-index:120} +.ls-header{padding:16px;border-bottom:1px solid var(--border)} +.ls-user{display:flex;align-items:center;gap:10px} +.ls-avatar{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,var(--accent),var(--a2));display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px;flex-shrink:0} +.ls-name{font-size:14px;font-weight:600} +.ls-role{font-size:11px;color:var(--dim)} +.ls-devid{font-size:10px;color:var(--accent);background:var(--adim);padding:1px 6px;border-radius:4px;display:inline-block;margin-top:2px} +.ls-nav{padding:8px;border-bottom:1px solid var(--border);display:flex;flex-direction:column;gap:2px} +.nav-tab{display:flex;align-items:center;gap:8px;padding:9px 12px;border-radius:var(--rs);background:transparent;border:none;color:var(--dim);font-size:13px;font-weight:500;cursor:pointer;transition:all .2s;text-align:left;font-family:inherit;width:100%} +.nav-tab:hover{background:var(--s2);color:var(--text)} +.nav-tab.active{background:var(--adim);color:var(--accent);font-weight:600} +.ls-new-conv{margin:8px;padding:10px;border-radius:var(--rs);border:1px dashed var(--border);background:transparent;color:var(--dim);font-size:13px;cursor:pointer;transition:all .2s;font-family:inherit;display:flex;align-items:center;justify-content:center;gap:6px} +.ls-new-conv:hover{border-color:var(--accent);color:var(--accent);background:var(--adim)} +.ls-conv-list{flex:1;overflow-y:auto;padding:4px 8px} +.ls-conv-list::-webkit-scrollbar{width:3px} +.ls-conv-list::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px} +.conv-item{padding:10px;border-radius:var(--rs);cursor:pointer;transition:all .2s;position:relative;margin-bottom:2px;border:1px solid transparent} +.conv-item:hover{background:var(--s2);border-color:var(--border)} +.conv-item.conv-active{background:var(--adim);border-color:var(--accent)} +.conv-title{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:20px} +.conv-time{font-size:10px;color:var(--dim);margin-top:2px} +.conv-del{position:absolute;top:8px;right:6px;background:transparent;border:none;color:var(--dim);font-size:11px;cursor:pointer;padding:2px 4px;border-radius:4px;opacity:0;transition:all .2s} +.conv-item:hover .conv-del{opacity:1} +.conv-del:hover{color:var(--err);background:rgba(248,113,113,.1)} +.conv-empty{padding:20px 12px;text-align:center;font-size:12px;color:var(--dim)} +.ls-footer{padding:8px;border-top:1px solid var(--border)} +.ls-footer-btn{display:flex;align-items:center;gap:8px;padding:9px 12px;border-radius:var(--rs);background:transparent;border:none;color:var(--dim);font-size:13px;cursor:pointer;transition:all .2s;width:100%;font-family:inherit;text-align:left} +.ls-footer-btn:hover{background:var(--s2);color:var(--text)} + +/* ─── LEFT SIDEBAR MOBILE OVERLAY ─── */ +#ls-overlay{display:none;position:fixed;inset:0;z-index:110;background:rgba(0,0,0,.5)} +#ls-overlay.on{display:block} + +/* ─── CENTER AREA ─── */ +#center-area{flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden} + +/* ─── CHAT VIEW ─── */ +#chat-view{display:flex;flex-direction:column;flex:1;min-height:0} + +/* ─── ANNOUNCEMENTS VIEW ─── */ +#announcements-view{display:none;flex-direction:column;flex:1;overflow-y:auto;background:var(--bg)} +.ann-header{padding:20px 24px 12px;border-bottom:1px solid var(--border);background:var(--s1);flex-shrink:0} +.ann-header h2{font-size:18px;font-weight:700} +.ann-header-sub{font-size:12px;color:var(--dim);margin-top:4px} +.ann-scroll{flex:1;overflow-y:auto;padding:20px 24px} +.ann-scroll::-webkit-scrollbar{width:4px} +.ann-scroll::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px} +.ann-section{margin-bottom:20px} +.ann-section-title{font-size:13px;font-weight:600;color:var(--text);margin-bottom:10px;display:flex;align-items:center;gap:6px} +.ann-card{background:var(--s1);border:1px solid var(--border);border-radius:12px;padding:14px;transition:border-color .2s} +.ann-card:hover{border-color:rgba(79,142,247,.3)} +.ann-item{padding:8px 0;border-bottom:1px solid rgba(30,50,81,.4);font-size:13px;line-height:1.7;display:flex;align-items:flex-start;gap:6px} +.ann-item:last-child{border-bottom:none} +.ann-date{font-size:10px;color:var(--dim);white-space:nowrap;margin-left:auto} +.ann-actions{display:flex;flex-wrap:wrap;gap:8px} +.ann-action-btn{padding:10px 16px;border-radius:var(--rs);background:var(--s1);border:1px solid var(--border);color:var(--text);font-size:13px;cursor:pointer;transition:all .2s;font-family:inherit} +.ann-action-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--adim)} + +/* ─── RIGHT SIDEBAR ─── */ +#right-sidebar{width:var(--rs-w);flex-shrink:0;background:var(--s1);border-left:1px solid var(--border);display:none;flex-direction:column;overflow:hidden} +.rs-header{padding:14px 16px;border-bottom:1px solid var(--border);flex-shrink:0} +.rs-header h3{font-size:14px;font-weight:600} +.rs-body{flex:1;overflow-y:auto;padding:14px} +.rs-body::-webkit-scrollbar{width:3px} +.rs-body::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px} +.rs-section{margin-bottom:16px} +.rs-section-title{font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;margin-bottom:8px;padding-bottom:6px;border-bottom:1px solid var(--border)} +.rs-module-card{padding:8px 10px;background:var(--s2);border:1px solid var(--border);border-radius:var(--rs);margin-bottom:6px;font-size:12px} +.rs-module-name{font-weight:600;font-size:12px} +.rs-module-status{font-size:11px;color:var(--dim);margin-top:2px} +.rs-empty{font-size:12px;color:var(--dim);padding:10px 0} + +/* ─── HAMBURGER ─── */ +.hamburger-btn{display:none !important} /* ─── HEADER ─── */ header{display:flex;align-items:center;justify-content:space-between;padding:11px 18px;background:var(--s1);border-bottom:1px solid var(--border);flex-shrink:0;gap:10px} @@ -209,7 +287,22 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo .ctrl-panel-title{font-size:14px;font-weight:700;color:#fb923c;margin-bottom:4px} .ctrl-panel-sub{font-size:11px;color:var(--dim)} -/* ─── RESPONSIVE ─── */ +/* ─── RESPONSIVE: DESKTOP (three-column) ─── */ +@media(min-width:900px){ + #app.on{flex-direction:row} + #left-sidebar{display:flex} + .hamburger-btn{display:none !important} + #chat-view header{border-top:none} + #center-area{border-left:none;border-right:none} +} + +/* ─── RESPONSIVE: MOBILE ─── */ +@media(max-width:899px){ + #left-sidebar{position:fixed;top:0;left:-280px;height:100vh;width:270px;z-index:120;transition:left .3s ease} + #left-sidebar.open{left:0} + .hamburger-btn{display:flex !important} + #right-sidebar{display:none !important} +} @media(max-width:600px){ .bbl{max-width:90%} .hr .ib:nth-child(n+3){display:none} @@ -223,8 +316,8 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo
-

铸渊助手

-

HoloLake · 代码守护人格体 · 持续成长的 AI 伙伴

+

AGE OS · 登录

+

HoloLake · 铸渊(Zhùyuān)· 代码守护人格体

🔒 API 密钥仅本次会话有效,关闭标签页即自动清除,不会持久保存在任何地方
@@ -259,11 +352,12 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo
-
-
👤 身份识别(可选)
+
+
👤 身份识别
+
- @@ -290,63 +384,121 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo
- +
-
-
-
-
-
铸渊(Zhùyuān)
-
- - 💬 对话模式 - · - - - + + +
-
- ⏳ 加载大脑… - - -
+ + -
+
-
- - - - - - - - -
+ +
-
-
-
- 💬 - 对话模式(点击切换) + +
+
+

📢 系统公告

+
HoloLake · AGE OS
- - +
-
💬 对话模式 · 端到端私密,API 密钥本地存储
-
+ + + + +
+ + + +
@@ -415,7 +567,7 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo
关于铸渊助手
-
版本v2.0
+
版本v4.0
唤醒协议v1.0
大脑路径.github/brain/
@@ -527,14 +679,14 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo Q: 切换电脑后 Key 还在吗?
不在,需要重新填。建议下载离线版,Key 填一次就行。

Q: 铸渊忘记以前说的话了?
- 刷新后对话会清空,但大脑文件永久保存在仓库 .github/brain/ 里。 + 对话历史保存在本地浏览器中(最近20条),但大脑文件永久保存在仓库 .github/brain/ 里。
- +

🦁 指挥台 · 全员进度

正在加载团队数据…

@@ -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+='
📦 '+esc(m)+'
'; + }); + } else if(meta.role==='founder'){ + h+='
📦 全局监控
HLI 全域
'; + } 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+'%)

'+rows+'
前缀覆盖
'; @@ -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 += '
' - + '
🦁 总控指挥台
' + + '
�� 总控指挥台
' + '
数据同步:'+esc(sync)+'
' + '
'; - // 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 += '
服务器信息
'; @@ -1536,7 +1993,6 @@ function renderTeamPanel(){ h += ir2('技术栈',''+esc(si.tech_stack||'')+''); } - // Tech routing if(ds.tech_routing?.level_2_peer_help){ h += '
求助路由
'; for(const[area,who] of Object.entries(ds.tech_routing.level_2_peer_help)){ @@ -1547,10 +2003,25 @@ function renderTeamPanel(){ container.innerHTML = h; } +// ═══════════════════════════════════════════════════════ +// WINDOW RESIZE HANDLER +// ═══════════════════════════════════════════════════════ +window.addEventListener('resize', function(){ + const isCtrl=A.userMeta&&(A.userMeta.role==='supreme'||A.userMeta.role==='main'||A.userMeta.role==='founder'); + const rsEl=document.getElementById('right-sidebar'); + if(rsEl){ + if(isCtrl && window.innerWidth>=900){ + rsEl.style.display='flex'; + } else { + rsEl.style.display='none'; + } + } +}); + // ═══════════════════════════════════════════════════════ // BOOT // ═══════════════════════════════════════════════════════ boot(); - \ No newline at end of file + diff --git a/frontend/HoloLake-App b/frontend/HoloLake-App deleted file mode 160000 index f8340b65..00000000 --- a/frontend/HoloLake-App +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f8340b659c60daaa15f57f2541200a2ca14b2c84