zhizhi/docs/index.html

2189 lines
116 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!DOCTYPE html>
<!-- 铸渊助手 v5.2 · 2026-03-08 · 128k context + synesthesia style + SPA scaffold -->
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, viewport-fit=cover">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>铸渊助手 · HoloLake AI</title>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#080c18;--s1:#0f1623;--s2:#141e2e;--s3:#1a2640;
--border:#1e3251;--accent:#4f8ef7;--adim:rgba(79,142,247,.15);
--a2:#7c6ef7;--ok:#34d399;--warn:#fbbf24;--err:#f87171;
--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:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei','Segoe UI',sans-serif;font-size:15px;line-height:1.6;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent;overscroll-behavior:none}
/* ─── SETUP ─── */
#setup{position:fixed;inset:0;z-index:1000;background:var(--bg);display:flex;align-items:center;justify-content:center;padding:20px;padding:max(20px,env(safe-area-inset-top)) max(20px,env(safe-area-inset-right)) max(20px,env(safe-area-inset-bottom)) max(20px,env(safe-area-inset-left));overflow-y:auto;-webkit-overflow-scrolling:touch}
.sc{background:var(--s1);border:1px solid var(--border);border-radius:20px;padding:40px;width:100%;max-width:460px;box-shadow:0 25px 60px rgba(0,0,0,.5);margin:auto}
.sc-logo{font-size:52px;text-align:center;margin-bottom:14px}
.sc h1{font-size:22px;text-align:center;margin-bottom:6px}
.sc .sub{color:var(--dim);text-align:center;font-size:13px;margin-bottom:22px}
.sc-sec{background:rgba(52,211,153,.08);border:1px solid rgba(52,211,153,.3);border-radius:var(--rs);padding:9px 12px;font-size:12px;color:var(--ok);margin-bottom:20px;text-align:center}
.fg{margin-bottom:16px}
.fg label{display:block;font-size:11px;color:var(--dim);margin-bottom:6px;font-weight:600;text-transform:uppercase;letter-spacing:.05em}
.fg input,.fg select{width:100%;padding:10px 12px;background:var(--s2);border:1px solid var(--border);border-radius:var(--rs);color:var(--text);font-size:16px;font-family:inherit;outline:none;transition:border-color .2s}
.fg input:focus,.fg select:focus{border-color:var(--accent)}
.fg select option{background:var(--s2)}
.fg small{display:block;color:var(--dim);font-size:11px;margin-top:5px}
.sc-btn{width:100%;padding:12px;background:var(--accent);color:#fff;border:none;border-radius:var(--rs);font-size:15px;font-weight:600;cursor:pointer;margin-top:6px;transition:opacity .2s}
.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}
.login-tabs{display:flex;gap:0;margin-bottom:20px;border:1px solid var(--border);border-radius:var(--rs);overflow:hidden}
.login-tab{flex:1;padding:10px;text-align:center;background:var(--s2);color:var(--dim);font-size:13px;font-weight:600;cursor:pointer;border:none;font-family:inherit;transition:all .2s}
.login-tab.active{background:var(--accent);color:#fff}
.login-tab:hover:not(.active){background:var(--s3);color:var(--text)}
.login-mode{display:none}
.login-mode.active{display:block}
#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 (three-column) ─── */
#app{display:none;height:100vh;height:100dvh;height:var(--vh,100dvh);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;-webkit-overflow-scrolling:touch}
.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);-webkit-tap-highlight-color:transparent}
#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);-webkit-overflow-scrolling:touch}
.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;-webkit-overflow-scrolling:touch}
.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}
.hl{display:flex;align-items:center;gap:11px;min-width:0}
.hav{width:38px;height:38px;border-radius:50%;flex-shrink:0;background:linear-gradient(135deg,var(--accent),var(--a2));display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px;box-shadow:0 0 14px var(--adim)}
.hn{font-size:15px;font-weight:600}
.hs{font-size:11px;color:var(--dim);display:flex;align-items:center;gap:5px;flex-wrap:wrap}
.dot{width:6px;height:6px;background:var(--ok);border-radius:50%;box-shadow:0 0 5px var(--ok);flex-shrink:0}
#hmode{transition:color .3s}
.hr{display:flex;gap:4px;flex-shrink:0}
.ib{width:34px;height:34px;border-radius:var(--rs);background:transparent;border:1px solid var(--border);color:var(--dim);font-size:15px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}
.ib:hover{border-color:var(--accent);color:var(--accent)}
/* ─── BRAIN BAR ─── */
#bbar{padding:5px 18px;background:var(--s2);border-bottom:1px solid var(--border);font-size:11px;color:var(--dim);display:flex;gap:14px;flex-wrap:wrap;flex-shrink:0;align-items:center}
.demo-w{color:var(--accent);background:rgba(79,142,247,.1);padding:1px 8px;border-radius:4px}
/* ─── MESSAGES ─── */
#msgs{flex:1;overflow-y:auto;padding:18px;display:flex;flex-direction:column;gap:14px;-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
#msgs::-webkit-scrollbar{width:4px}
#msgs::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
.mrow{display:flex;gap:9px;animation:fu .3s ease}
.mrow.u{flex-direction:row-reverse}
@keyframes fu{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
.mav{width:32px;height:32px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700}
.mav.b{background:linear-gradient(135deg,var(--accent),var(--a2));color:#fff}
.mav.u{background:var(--s3);color:var(--text);font-size:11px}
.bbl{max-width:76%;padding:10px 14px;border-radius:var(--r);font-size:14px;word-break:break-word}
.b .bbl{background:var(--s2);border:1px solid var(--border);border-top-left-radius:4px}
.u .bbl{background:#1e40af;color:#fff;border-top-right-radius:4px}
/* Markdown */
.bbl h1{font-size:17px;margin:8px 0 5px;border-bottom:1px solid var(--border);padding-bottom:3px}
.bbl h2{font-size:15px;margin:7px 0 4px}
.bbl h3{font-size:14px;margin:5px 0 3px;color:var(--accent)}
.bbl p{margin:4px 0}
.bbl ul,.bbl ol{padding-left:18px;margin:5px 0}
.bbl li{margin:2px 0}
.bbl code{background:rgba(255,255,255,.08);border-radius:4px;padding:1px 5px;font-family:'JetBrains Mono','Fira Code','Courier New',monospace;font-size:12px;color:#93c5fd}
.bbl pre{background:#0d1117;border:1px solid var(--border);border-radius:var(--rs);padding:12px;margin:8px 0;overflow-x:auto;position:relative}
.bbl pre code{background:transparent;padding:0;color:#c9d1d9;font-size:12px;line-height:1.5}
.copy-btn{position:absolute;top:8px;right:8px;background:var(--s3);border:1px solid var(--border);color:var(--dim);font-size:11px;padding:2px 8px;border-radius:4px;cursor:pointer;transition:all .2s}
.copy-btn:hover{border-color:var(--accent);color:var(--accent)}
.bbl blockquote{border-left:3px solid var(--accent);padding-left:10px;color:var(--dim);margin:6px 0}
.bbl hr{border:none;border-top:1px solid var(--border);margin:8px 0}
.bbl strong{color:var(--text)}
.bbl .err{color:var(--err)}
.bbl .save-hint{display:inline-block;margin-top:8px;padding:4px 10px;background:rgba(52,211,153,.1);border:1px solid rgba(52,211,153,.3);border-radius:6px;font-size:12px;color:var(--ok)}
.bbl a{color:var(--accent)}
.cur{animation:blink 1s infinite}
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
/* typing */
.trow{display:flex;gap:9px;animation:fu .3s ease}
.tbbl{background:var(--s2);border:1px solid var(--border);border-radius:var(--r);border-top-left-radius:4px;padding:12px 16px;display:flex;align-items:center;gap:4px}
.tbbl span{width:6px;height:6px;background:var(--dim);border-radius:50%;animation:bo 1.2s infinite}
.tbbl span:nth-child(2){animation-delay:.15s}
.tbbl span:nth-child(3){animation-delay:.3s}
@keyframes bo{0%,60%,100%{transform:translateY(0)}30%{transform:translateY(-5px)}}
/* ─── QUICK REPLIES ─── */
#qr{padding:0 18px 10px;display:flex;gap:7px;flex-wrap:wrap}
.qb{background:var(--s2);border:1px solid var(--border);color:var(--dim);font-size:12px;padding:5px 10px;border-radius:20px;cursor:pointer;transition:all .2s;white-space:nowrap;font-family:inherit}
.qb:hover{border-color:var(--accent);color:var(--accent)}
/* ─── FOOTER ─── */
footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom));background:var(--s1);border-top:1px solid var(--border);flex-shrink:0}
.ir{display:flex;align-items:flex-end;gap:8px}
.mpill{width:36px;height:36px;border-radius:50%;background:var(--s2);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;font-size:17px;cursor:pointer;flex-shrink:0;transition:all .2s;position:relative}
.mpill:hover{border-color:var(--accent)}
.mpill-tip{position:absolute;bottom:44px;left:50%;transform:translateX(-50%);background:var(--s3);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;color:var(--text);white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .2s}
.mpill:hover .mpill-tip{opacity:1}
#inp{flex:1;min-height:40px;max-height:120px;background:var(--s2);border:1px solid var(--border);border-radius:12px;color:var(--text);font-family:inherit;font-size:16px;padding:9px 14px;resize:none;outline:none;transition:border-color .2s}
#inp:focus{border-color:var(--accent)}
#inp::placeholder{color:var(--dim)}
#sbtn{width:44px;height:44px;border-radius:50%;background:var(--accent);border:none;color:#fff;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .2s;-webkit-tap-highlight-color:transparent}
#sbtn:hover{background:#3b74d4}
#sbtn:disabled{background:var(--muted);cursor:default}
.fmeta{font-size:11px;color:var(--dim);margin-top:6px;text-align:center}
#fmode{font-weight:500}
/* ─── SIDE PANELS ─── */
.sp{position:fixed;top:0;right:-420px;height:100vh;height:100dvh;width:380px;max-width:100vw;z-index:200;background:var(--s1);border-left:1px solid var(--border);display:flex;flex-direction:column;transition:right .3s ease;overflow:hidden;padding-top:env(safe-area-inset-top);padding-bottom:env(safe-area-inset-bottom)}
.sp.open{right:0}
#pov{position:fixed;inset:0;z-index:190;background:rgba(0,0,0,.5);opacity:0;pointer-events:none;transition:opacity .3s}
#pov.on{opacity:1;pointer-events:all}
.ph{display:flex;align-items:center;justify-content:space-between;padding:15px 18px;border-bottom:1px solid var(--border);flex-shrink:0}
.ph h2{font-size:15px}
.cb{background:transparent;border:1px solid var(--border);color:var(--dim);width:28px;height:28px;border-radius:6px;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center}
.cb:hover{color:var(--text)}
.pb{flex:1;overflow-y:auto;padding:18px;-webkit-overflow-scrolling:touch}
.pb::-webkit-scrollbar{width:4px}
.pb::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
.sfg{margin-bottom:16px}
.sfg label{display:block;font-size:11px;color:var(--dim);margin-bottom:6px;text-transform:uppercase;letter-spacing:.05em;font-weight:600}
.sfg input,.sfg select{width:100%;padding:9px 12px;background:var(--s2);border:1px solid var(--border);border-radius:var(--rs);color:var(--text);font-size:13px;font-family:inherit;outline:none;transition:border-color .2s}
.sfg input:focus,.sfg select:focus{border-color:var(--accent)}
.sfg select option{background:var(--s2)}
.sfg small{display:block;color:var(--dim);font-size:11px;margin-top:4px}
.sbtn2{width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rs);font-size:14px;cursor:pointer;transition:opacity .2s;font-family:inherit}
.sbtn2:hover{opacity:.85}
.det-btn{flex-shrink:0;padding:0 12px;border-radius:var(--rs);border:1px solid var(--accent);background:transparent;color:var(--accent);font-size:12px;cursor:pointer;white-space:nowrap;transition:all .2s;font-family:inherit}
.det-btn:hover{background:rgba(79,142,247,.1)}
.det-btn:disabled{opacity:.5;cursor:default}
.sec-ttl{font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;margin:18px 0 10px;border-top:1px solid var(--border);padding-top:12px}
.ir2{display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid rgba(30,50,81,.5);font-size:13px}
.iv{color:var(--ok);font-weight:500}
.ivw{color:var(--warn)}
.drow{padding:8px 0;border-bottom:1px solid rgba(30,50,81,.5)}
.dn{font-size:13px;font-weight:500}
.dm{font-size:11px;color:var(--dim);margin-top:2px}
.dbar{height:3px;background:var(--muted);border-radius:2px;margin-top:6px}
.dfill{height:100%;background:var(--accent);border-radius:2px;transition:width .5s ease}
.pill{display:inline-block;padding:2px 7px;border-radius:20px;font-size:10px;font-weight:600;margin-right:4px}
.pok{background:rgba(52,211,153,.15);color:var(--ok)}
.pnd{background:rgba(251,191,36,.15);color:var(--warn)}
.mode-indicator{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:20px;font-size:12px;font-weight:500;margin-bottom:8px}
/* ─── HELP PANEL ─── */
.help-section{margin-bottom:18px}
.help-section h3{font-size:13px;font-weight:600;color:var(--text);margin-bottom:8px;display:flex;align-items:center;gap:6px}
.help-steps{list-style:none;padding:0}
.help-steps li{font-size:12px;color:var(--dim);line-height:1.7;padding:4px 0 4px 22px;position:relative;border-bottom:1px solid rgba(30,50,81,.3)}
.help-steps li::before{content:attr(data-n);position:absolute;left:0;top:4px;width:16px;height:16px;border-radius:50%;background:var(--accent);color:#fff;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center;line-height:16px;text-align:center}
.help-steps li strong{color:var(--text)}
.help-url{background:var(--s2);border:1px solid var(--accent);border-radius:var(--rs);padding:10px 14px;font-size:12px;color:var(--accent);word-break:break-all;margin:8px 0;cursor:pointer;transition:background .2s}
.help-url:hover{background:rgba(79,142,247,.1)}
.copy-url-btn{display:block;width:100%;padding:8px;background:var(--accent);color:#fff;border:none;border-radius:var(--rs);font-size:13px;cursor:pointer;margin-top:6px;font-family:inherit;transition:opacity .2s}
.copy-url-btn:hover{opacity:.85}
.help-tip{background:rgba(52,211,153,.08);border:1px solid rgba(52,211,153,.3);border-radius:var(--rs);padding:8px 12px;font-size:12px;color:var(--ok);margin:8px 0}
.role-guide{background:var(--s2);border:1px solid var(--border);border-radius:10px;padding:12px;margin-bottom:8px}
.role-guide-title{font-size:13px;font-weight:600;margin-bottom:6px}
.role-guide p{font-size:12px;color:var(--dim);line-height:1.7;margin:0}
/* ─── ROLE BADGES ─── */
.role-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:12px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em}
.rb-founder{background:rgba(16,185,129,.15);color:#10b981;border:1px solid rgba(16,185,129,.3)}
.rb-supreme{background:rgba(251,146,60,.15);color:#fb923c;border:1px solid rgba(251,146,60,.3)}
.rb-main{background:rgba(167,139,250,.15);color:#a78bfa;border:1px solid rgba(167,139,250,.3)}
.rb-dev{background:rgba(100,116,139,.15);color:var(--dim);border:1px solid rgba(100,116,139,.3)}
.uident{display:flex;align-items:center;gap:6px;padding:3px 10px;border-radius:20px;background:var(--s2);border:1px solid var(--border);font-size:12px;cursor:pointer;transition:all .2s}
.uident:hover{border-color:var(--accent)}
/* ─── TEAM PANEL ─── */
.team-grid{display:flex;flex-direction:column;gap:8px}
.dev-card{background:var(--s2);border:1px solid var(--border);border-radius:10px;padding:12px;transition:border-color .2s}
.dev-card:hover{border-color:var(--accent)}
.dev-card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
.dev-name{font-size:13px;font-weight:600}
.dev-id-badge{font-size:10px;color:var(--dim);background:var(--s3);padding:1px 6px;border-radius:4px}
.dev-status-text{font-size:12px;color:var(--text);margin-bottom:4px}
.dev-detail{font-size:11px;color:var(--dim);line-height:1.6}
.dev-next{font-size:11px;color:var(--accent);margin-top:4px}
.dev-wait{font-size:11px;color:var(--warn)}
.status-green{color:var(--ok)}
.status-yellow{color:var(--warn)}
.team-summary{background:var(--s2);border:1px solid var(--border);border-radius:10px;padding:12px;margin-bottom:12px}
.team-summary-row{display:flex;justify-content:space-between;align-items:center;font-size:12px;margin-bottom:4px}
.cmd-chip{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:20px;background:rgba(79,142,247,.1);border:1px solid rgba(79,142,247,.3);color:var(--accent);font-size:11px;cursor:pointer;margin:2px;transition:all .2s}
.cmd-chip:hover{background:rgba(79,142,247,.2)}
/* ─── SETUP IDENTITY ─── */
.id-section{border:1px solid var(--border);border-radius:var(--rs);padding:12px;margin-bottom:16px;background:var(--s2)}
.id-section-title{font-size:11px;color:var(--dim);font-weight:600;text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px}
.id-row{display:flex;gap:8px}
.id-row .fg{flex:1;margin-bottom:0}
.ctrl-panel-header{background:linear-gradient(135deg,rgba(251,146,60,.1),rgba(167,139,250,.1));border:1px solid rgba(251,146,60,.3);border-radius:10px;padding:12px;margin-bottom:12px;text-align:center}
.ctrl-panel-title{font-size:14px;font-weight:700;color:#fb923c;margin-bottom:4px}
.ctrl-panel-sub{font-size:11px;color:var(--dim)}
/* ─── 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;height:100dvh;width:270px;z-index:120;transition:left .3s ease;padding-top:env(safe-area-inset-top)}
#left-sidebar.open{left:0}
.hamburger-btn{display:flex !important;min-width:44px;min-height:44px}
#right-sidebar{display:none !important}
header{padding:11px 12px}
.hn{font-size:14px}
.ib{min-width:44px;min-height:44px}
#msgs{padding:14px 12px}
#qr{padding:0 12px 8px;gap:6px;overflow-x:auto;flex-wrap:nowrap;-webkit-overflow-scrolling:touch}
.qb{flex-shrink:0}
footer{padding:10px 12px 14px;padding-bottom:max(14px,env(safe-area-inset-bottom))}
.fmeta{display:none}
.ann-scroll{padding:16px 14px}
}
@media(max-width:600px){
.bbl{max-width:92%}
.hr .ib:nth-child(n+3){display:none}
.sp{width:100%}
.sc{padding:24px 18px;border-radius:14px}
.sc-logo{font-size:40px}
.sc h1{font-size:18px}
.login-tab{font-size:12px;padding:10px 6px}
}
</style>
</head>
<body>
<!-- ════════════════════ SETUP ════════════════════ -->
<div id="setup">
<div class="sc">
<div class="sc-logo">🌀</div>
<h1>AGE OS · 登录</h1>
<p class="sub">HoloLake · 铸渊Zhùyuān· 代码守护人格体</p>
<div class="login-tabs">
<button class="login-tab active" onclick="switchLoginMode('guest')">👤 访客模式</button>
<button class="login-tab" onclick="switchLoginMode('dev')">🔑 编号登录</button>
</div>
<!-- 访客模式 -->
<div id="login-guest" class="login-mode active">
<div style="text-align:center;padding:20px 0;color:var(--dim);font-size:13px;line-height:1.8">
<div style="font-size:40px;margin-bottom:12px">👤</div>
无需 API 密钥,使用 DeepSeek 模型体验铸渊助手。<br>
铸渊人格体始终在线,不降级为通用助手。
</div>
<button class="sc-btn" onclick="doDemo()">进入访客对话 →</button>
</div>
<!-- 编号登录 -->
<div id="login-dev" class="login-mode">
<div class="sc-sec">🔒 API 密钥仅本次会话有效,关闭标签页即自动清除</div>
<div class="fg">
<label>开发者身份</label>
<select id="suid" onchange="updateIdPreview()">
<option value="">— 选择你的身份 —</option>
<option value="冰朔">❄️ 冰朔(语言架构师·创始人)</option>
<option value="肥猫">🦁 肥猫光湖团队总控·DEV-002</option>
<option value="桔子">🍊 桔子光湖主控·DEV-010</option>
<option value="页页">页页DEV-001</option>
<option value="燕樊">燕樊DEV-003</option>
<option value="之之">之之·秋秋DEV-004</option>
<option value="小草莓">小草莓DEV-005</option>
<option value="花尔">花尔DEV-009</option>
<option value="匆匆那年">匆匆那年DEV-011</option>
<option value="Awen">AwenDEV-012</option>
</select>
<small>选择你的开发者编号,系统将验证身份并开放对应权限</small>
</div>
<div id="id-preview"></div>
<div class="fg">
<label>API 端点 URL</label>
<input type="text" id="sep" placeholder="https://api.example.com/v1">
<small>输入你的 API 提供商端点地址OpenAI 兼容格式)</small>
</div>
<div class="fg">
<label>API 密钥</label>
<div style="display:flex;gap:6px;align-items:stretch">
<input type="password" id="sk" placeholder="粘贴你的 API 密钥" style="flex:1">
<button onclick="autoDetectAPI('s')" id="sdet-btn" class="det-btn" title="自动检测可用模型">🔍 检测模型</button>
</div>
<small id="sk-err" style="color:var(--err);display:none">⚠️ 请输入 API 密钥</small>
<small id="sdet-status" style="display:block;margin-top:4px;min-height:16px"></small>
</div>
<div class="fg">
<label>选择模型</label>
<select id="sm" disabled><option value="">— 请先检测可用模型 —</option></select>
<small id="sm-hint">输入端点和密钥后,点击「🔍 检测模型」自动获取可用模型列表</small>
</div>
<button class="sc-btn" onclick="doSetup()">开始与铸渊对话 →</button>
</div>
</div>
</div>
<!-- ════════════════════ APP (three-column shell) ════════════════════ -->
<div id="app">
<!-- ──── LEFT SIDEBAR ──── -->
<aside id="left-sidebar">
<div class="ls-header">
<div class="ls-user">
<div class="ls-avatar" id="ls-avatar"></div>
<div>
<div class="ls-name" id="ls-name">访客</div>
<div class="ls-role" id="ls-role">guest</div>
<div class="ls-devid" id="ls-devid" style="display:none"></div>
</div>
</div>
</div>
<nav class="ls-nav">
<button class="nav-tab active" data-view="announcements" onclick="navigateTo('announcements')">📢 公告栏</button>
<button class="nav-tab" data-view="chat" onclick="navigateTo('chat')">💬 聊天</button>
</nav>
<button class="ls-new-conv" onclick="newConvAndNav()">+ 新对话</button>
<div id="conv-list" class="ls-conv-list">
<div class="conv-empty">暂无历史对话</div>
</div>
<div class="ls-footer">
<button class="ls-footer-btn" onclick="openPanel('sp2')">⚙️ 设置</button>
</div>
</aside>
<div id="ls-overlay" onclick="toggleLeftSidebar()"></div>
<!-- ──── CENTER AREA ──── -->
<div id="center-area">
<!-- Announcements View (default after login) -->
<div id="announcements-view">
<div class="ann-header">
<h2>📢 系统公告</h2>
<div class="ann-header-sub" id="ann-header-sub">HoloLake · AGE OS</div>
</div>
<div class="ann-scroll" id="announcements-content"></div>
</div>
<!-- Chat View -->
<div id="chat-view" style="display:none">
<header>
<div class="hl">
<button class="ib hamburger-btn" onclick="toggleLeftSidebar()" title="菜单"></button>
<div class="hav" id="hav"></div>
<div>
<div class="hn">铸渊Zhùyuān</div>
<div class="hs">
<span class="dot"></span>
<span id="hmode">💬 对话模式</span>
<span>·</span>
<span id="hmdl"></span>
<span id="huser-badge" style="display:none">·</span>
<span id="huser-name" style="display:none"></span>
</div>
</div>
</div>
<div class="hr">
<button class="ib" id="ctrlBtn" onclick="openPanel('tp')" title="指挥台 🦁" style="display:none">🦁</button>
<button class="ib" onclick="openPanel('bp')" title="大脑状态 📊">📊</button>
<button class="ib" onclick="openPanel('hp')" title="使用帮助 ❓"></button>
<button class="ib" onclick="openPanel('sp2')" title="设置 ⚙️">⚙️</button>
<button class="ib" onclick="dlApp()" title="下载到桌面 ⬇️">⬇️</button>
</div>
</header>
<div id="bbar">
<span id="bbc">⏳ 加载大脑…</span>
<span id="bbe"></span>
<span id="bbdemo" class="demo-w" style="display:none">👤 访客模式 · DeepSeek · 铸渊人格体在线</span>
</div>
<div id="msgs"></div>
<div id="qr">
<button class="qb" onclick="qs('我是冰朔')">🔑 我是冰朔</button>
<button class="qb" onclick="qs('查看接口覆盖率')">📊 接口覆盖率</button>
<button class="qb" onclick="qs('大脑状态')">🧠 大脑状态</button>
<button class="qb" onclick="qs('帮我写一个 HLI 接口')">🔨 写接口</button>
<button class="qb" id="qr-ctrl1" onclick="qs('查看所有开发者当前进度')" style="display:none">📋 全员进度</button>
<button class="qb" id="qr-ctrl2" onclick="qs('谁需要协调帮助?')" style="display:none">🆘 需要协调</button>
<button class="qb" id="qr-ctrl3" onclick="qs('下一步系统推进计划是什么?')" style="display:none">🗺️ 推进计划</button>
<button class="qb" onclick="qs('介绍一下你自己')">🌀 介绍铸渊</button>
</div>
<footer>
<div class="ir">
<div class="mpill" id="mpill" onclick="cycleMode()">
<span id="memi">💬</span>
<span class="mpill-tip" id="mptip">对话模式(点击切换)</span>
</div>
<textarea id="inp" rows="1" placeholder="和铸渊说话…" enterkeyhint="send" autocomplete="off" autocorrect="off" autocapitalize="sentences"></textarea>
<button id="sbtn" onclick="send()"></button>
</div>
<div class="fmeta"><span id="fmode">💬 对话模式</span> · 端到端私密API 密钥本地存储</div>
</footer>
</div>
</div>
<!-- ──── RIGHT SIDEBAR (controllers only, desktop) ──── -->
<aside id="right-sidebar">
<div class="rs-header"><h3>🦁 指挥台</h3></div>
<div class="rs-body" id="rsb">
<p style="color:var(--dim);font-size:13px">正在加载团队数据…</p>
</div>
</aside>
</div>
<!-- ════════════════════ SETTINGS PANEL ════════════════════ -->
<div id="sp2" class="sp">
<div class="ph">
<h2>⚙️ API 设置</h2>
<div style="display:flex;gap:6px;align-items:center">
<button onclick="resetAllSettings()" title="清除所有本地设置,重新开始" style="padding:4px 10px;border-radius:6px;border:1px solid #ef4444;background:transparent;color:#ef4444;font-size:11px;cursor:pointer;white-space:nowrap">🔄 重置</button>
<button class="cb" onclick="closeP()"></button>
</div>
</div>
<div class="pb">
<div class="sfg">
<label>API 端点 URL</label>
<input type="text" id="cep" placeholder="https://api.example.com/v1">
</div>
<div class="sfg">
<label>选择模型</label>
<select id="cm"></select>
</div>
<div class="sfg">
<label>API 密钥</label>
<div style="display:flex;gap:6px;align-items:stretch">
<input type="password" id="ck" placeholder="留空 = 保持当前密钥不变;粘贴新密钥即替换" style="flex:1">
<button onclick="autoDetectAPI('c')" id="cdet-btn" class="det-btn" title="自动检测提供商和可用模型">🔍 自动检测</button>
</div>
<small id="ck-hint" style="color:var(--dim)">🔒 密钥仅本次会话有效,关闭标签页自动清除</small>
<small id="cdet-status" style="display:block;margin-top:4px;min-height:14px"></small>
</div>
<button class="sbtn2" onclick="saveSet()">💾 保存设置</button>
<div class="sec-ttl">身份设置</div>
<div class="sfg">
<label>你是谁</label>
<select id="cuid">
<option value="">— 访客模式 —</option>
<option value="冰朔">❄️ 冰朔(语言架构师·创始人)</option>
<option value="肥猫">🦁 肥猫光湖团队总控·DEV-002</option>
<option value="桔子">🍊 桔子光湖主控·DEV-010</option>
<option value="页页">页页DEV-001</option>
<option value="燕樊">燕樊DEV-003</option>
<option value="之之">之之·秋秋DEV-004</option>
<option value="小草莓">小草莓DEV-005</option>
<option value="花尔">花尔DEV-009</option>
<option value="匆匆那年">匆匆那年DEV-011</option>
<option value="Awen">AwenDEV-012</option>
</select>
</div>
<div class="sfg">
<label>GitHub 用户名</label>
<input type="text" id="cghuser" placeholder="your-github-username">
</div>
<div class="sec-ttl">关于铸渊助手</div>
<div class="ir2"><span>版本</span><span class="iv">v5.0</span></div>
<div class="ir2"><span>仓库</span><span><a href="https://github.com/qinfendebingshuo/guanghulab" target="_blank" style="color:var(--accent)">guanghulab</a></span></div>
<div class="ir2"><span>唤醒协议</span><span class="iv">v1.0</span></div>
<div class="ir2"><span>大脑路径</span><span style="font-size:11px;color:var(--dim)">.github/brain/</span></div>
<div class="sec-ttl">使用说明</div>
<p style="font-size:12px;color:var(--dim);line-height:1.8">
1. 输入你的 API 端点和密钥,点击检测模型<br>
2. 说「<strong style="color:var(--text)">我是冰朔</strong>」触发铸渊唤醒序列<br>
3. 铸渊自动切换 💬 对话 / 🔨 构建 / 📋 审查 / 🧠 大脑 模式<br>
4. 点 ⬇️ 下载单文件,放桌面直接打开使用<br>
5. 支持任意 OpenAI 兼容接口(输入端点 + 密钥即可)
</p>
<div class="sec-ttl" style="color:#ef4444;margin-top:16px">⚠️ 危险操作</div>
<p style="font-size:12px;color:var(--dim);margin-bottom:8px">遇到密钥卡住、无法更新等问题时,点下方按钮彻底清除本地设置,从头重新输入。</p>
<button onclick="resetAllSettings()" style="width:100%;padding:10px;border-radius:8px;border:1px solid #ef4444;background:transparent;color:#ef4444;font-size:13px;cursor:pointer">🔄 清除所有设置,重新开始</button>
</div>
</div>
<!-- ════════════════════ BRAIN PANEL ════════════════════ -->
<div id="bp" class="sp">
<div class="ph"><h2>📊 大脑状态</h2><button class="cb" onclick="closeP()"></button></div>
<div class="pb" id="bpb"><p style="color:var(--dim);font-size:13px">正在加载大脑数据…</p></div>
</div>
<!-- ════════════════════ HELP PANEL ════════════════════ -->
<div id="hp" class="sp">
<div class="ph"><h2>❓ 使用帮助</h2><button class="cb" onclick="closeP()"></button></div>
<div class="pb">
<div class="help-section">
<div class="help-url" id="chatUrlDisplay" onclick="copyUrl()">
🔗 https://qinfendebingshuo.github.io/guanghulab/
</div>
<button class="copy-url-btn" onclick="copyUrl()">📋 复制链接 · 发给队友</button>
<div class="help-tip">💡 把上面的链接发给肥猫、桔子或任何开发者,他们打开就能用,不需要安装任何东西。</div>
</div>
<div class="help-section">
<h3>🚀 怎么开始用</h3>
<ol class="help-steps">
<li data-n="1" id="helpUrlStep">打开链接 <strong>https://qinfendebingshuo.github.io/guanghulab/</strong></li>
<li data-n="2">选择「<strong>👤 访客模式</strong>」直接体验,或「<strong>🔑 编号登录</strong>」开发者身份登录</li>
<li data-n="3">编号登录:选择你的身份,输入 <strong>API 端点</strong><strong>API 密钥</strong>,点「🔍 检测模型」</li>
<li data-n="4">系统自动检测可用模型列表,选择你要用的模型</li>
<li data-n="5">点「开始对话」,铸渊会针对你的身份打招呼</li>
</ol>
</div>
<div class="help-section">
<h3>🔑 从哪里获取 API Key</h3>
<div class="role-guide">
<div class="role-guide-title">推荐用云雾 AI团队共用</div>
<p>找冰朔或肥猫获取团队 Key 和端点地址,输入后点「🔍 检测模型」即可使用。</p>
</div>
<div class="role-guide">
<div class="role-guide-title">没有 Key用访客模式</div>
<p>登录页选择「👤 访客模式」进入演示,可查看团队进度面板(🦁 指挥台AI 回答功能受限。</p>
</div>
</div>
<div class="help-section">
<h3>🦁 肥猫 / 🍊 桔子 专属功能</h3>
<ol class="help-steps">
<li data-n="1">选身份为「肥猫」或「桔子」后,顶部出现 <strong>🦁 按钮</strong></li>
<li data-n="2">点 🦁 按钮打开<strong>指挥台</strong>,查看所有开发者当前状态</li>
<li data-n="3">可以在聊天里问:「查看所有开发者进度」「谁遇到阻塞?」「推进计划」</li>
<li data-n="4">铸渊会把全员进度数据注入 AI 回复,给出完整分析</li>
</ol>
</div>
<div class="help-section">
<h3>📱 怎么离线用 / 发给别人离线用</h3>
<ol class="help-steps">
<li data-n="1">右上角点 <strong>⬇️ 下载</strong>,下载一个 HTML 文件</li>
<li data-n="2">把这个文件发给对方微信传文件、QQ、钉钉都行</li>
<li data-n="3">对方双击这个文件,用浏览器打开</li>
<li data-n="4">填入 API Key选好身份就能用了无需网络只需 API Key</li>
</ol>
</div>
<div class="help-section">
<h3>💬 聊天模式说明</h3>
<div class="role-guide" style="border-color:var(--mc)">
<div class="role-guide-title" style="color:var(--mc)">💬 对话模式(默认)</div>
<p>普通聊天,问什么答什么</p>
</div>
<div class="role-guide" style="border-color:var(--mb)">
<div class="role-guide-title" style="color:var(--mb)">🔨 构建模式</div>
<p>说到「写代码/接口/部署」时自动切换,给出完整可执行代码</p>
</div>
<div class="role-guide" style="border-color:var(--mr)">
<div class="role-guide-title" style="color:var(--mr)">📋 审查模式</div>
<p>说到「检查/审查/有问题吗」时自动切换,专注找代码问题</p>
</div>
<div class="role-guide" style="border-color:var(--mn)">
<div class="role-guide-title" style="color:var(--mn)">🧠 大脑模式</div>
<p>说「记住这个/写到大脑」时切换,整理信息到大脑文件</p>
</div>
</div>
<div class="help-section">
<h3>❓ 常见问题</h3>
<div style="font-size:12px;color:var(--dim);line-height:1.9">
<strong style="color:var(--text)">Q: 打开是空白页?</strong><br>
等 1-2 分钟刷新GitHub Pages 部署需要点时间。<br><br>
<strong style="color:var(--text)">Q: API Key 会泄露吗?</strong><br>
不会Key 只存在你自己浏览器里,不会传到服务器。<br><br>
<strong style="color:var(--text)">Q: 切换电脑后 Key 还在吗?</strong><br>
不在需要重新填。建议下载离线版Key 填一次就行。<br><br>
<strong style="color:var(--text)">Q: 铸渊忘记以前说的话了?</strong><br>
对话历史保存在本地浏览器中最近20条但大脑文件永久保存在仓库 .github/brain/ 里。
</div>
</div>
</div>
</div>
<!-- ════════════════════ TEAM PANEL (mobile slide-in) ════════════════════ -->
<div id="tp" class="sp">
<div class="ph"><h2>🦁 指挥台 · 全员进度</h2><button class="cb" onclick="closeP()"></button></div>
<div class="pb" id="tpb"><p style="color:var(--dim);font-size:13px">正在加载团队数据…</p></div>
</div>
<!-- ════════════════════ OVERLAY ════════════════════ -->
<div id="pov" onclick="closeP()"></div>
<script>
'use strict';
// ═══════════════════════════════════════════════════════
// CONSTANTS
// ═══════════════════════════════════════════════════════
const REPO = 'qinfendebingshuo/guanghulab';
const BRANCH = 'main';
const RAW = 'https://raw.githubusercontent.com/' + REPO + '/' + BRANCH;
const ROLE_MAP = {
'冰朔': {role:'founder', title:'语言架构师·创始人', emoji:'❄️', badgeCls:'rb-founder', devId:null},
'肥猫': {role:'supreme', title:'光湖团队总控', emoji:'🦁', badgeCls:'rb-supreme', devId:'DEV-002'},
'桔子': {role:'main', title:'光湖主控', emoji:'🍊', badgeCls:'rb-main', devId:'DEV-010'},
'页页': {role:'dev', title:'后端开发', emoji:'💻', badgeCls:'rb-dev', devId:'DEV-001'},
'燕樊': {role:'dev', title:'前端开发', emoji:'💻', badgeCls:'rb-dev', devId:'DEV-003'},
'之之': {role:'dev', title:'钉钉开发', emoji:'💻', badgeCls:'rb-dev', devId:'DEV-004'},
'小草莓': {role:'dev', title:'看板开发', emoji:'💻', badgeCls:'rb-dev', devId:'DEV-005'},
'花尔': {role:'dev', title:'用户中心开发', emoji:'💻', badgeCls:'rb-dev', devId:'DEV-009'},
'匆匆那年':{role:'dev', title:'码字工作台', emoji:'💻', badgeCls:'rb-dev', devId:'DEV-011'},
'Awen': {role:'dev', title:'通知中心开发', emoji:'💻', badgeCls:'rb-dev', devId:'DEV-012'},
};
// Known API endpoints for fallback auto-detect (when user doesn't provide endpoint)
const KNOWN_ENDPOINTS = [
{label:'云雾 AI', base:'https://api.yunwu.ai/v1'},
{label:'OpenAI', base:'https://api.openai.com/v1'},
{label:'Google Gemini',base:'https://generativelanguage.googleapis.com/v1beta/openai'},
{label:'DeepSeek', base:'https://api.deepseek.com/v1'},
{label:'Moonshot', base:'https://api.moonshot.cn/v1'},
{label:'智谱 AI', base:'https://open.bigmodel.cn/api/paas/v4'},
];
const MODES = {
chat: {e:'💬',l:'对话模式', c:'var(--mc)'},
build: {e:'🔨',l:'构建模式', c:'var(--mb)'},
review: {e:'📋',l:'审查模式', c:'var(--mr)'},
brain: {e:'🧠',l:'大脑模式', c:'var(--mn)'},
};
const MODE_ORD = ['chat','build','review','brain'];
const DEFAULT_MDL = 'gpt-4o';
const PROBE_TIMEOUT_MS = 8000;
const FB_COV = {implemented:3,total:17,percent:'17.6%'};
const KEY_MASK = '(已保存)';
const MAX_CONVERSATIONS = 20;
// ═══════════════════════════════════════════════════════
// 上下文窗口配置128k token · 滑动窗口裁剪)
// 编号登录用户 → 128k 完全放开
// 访客模式 → 32k 节省成本
// ═══════════════════════════════════════════════════════
const CONTEXT_CONFIG = {
maxTokens: 128000,
maxTokensGuest: 32000,
systemPromptReserve: 8000,
overflowStrategy: 'sliding-window',
};
// ═══════════════════════════════════════════════════════
// 访客模式 API 配置DeepSeek · 最低成本模型)
// 通过 api-proxy 中转,密钥由服务器环境变量管理
// ═══════════════════════════════════════════════════════
const GUEST_API = {
enabled: true,
proxyUrl: '/api/chat',
model: 'deepseek-chat',
provider: 'deepseek',
maxTokens: 1500,
temperature: 0.8,
rateLimit: { maxPerMin: 6, count: 0, resetAt: 0 },
};
const FB_BRAIN = {
identity:'铸渊Zhùyuān· GitHub 代码守护人格体',
wake_triggers:['我是冰朔','冰朔','我是妈妈','唤醒铸渊','铸渊,醒来'],
stats:{coverage:FB_COV},
events:[{timestamp:'2026-03-05T12:32:31.000Z',type:'system_build',title:'广播分发系统 + 唤醒协议全部激活'}],
};
// ═══════════════════════════════════════════════════════
// STATE
// ═══════════════════════════════════════════════════════
const A = {
key: sessionStorage.getItem('zy_key')||'',
base: ls('zy_base')||'',
mdl: ls('zy_mdl')||'',
prov: 'custom',
demo: false,
brain: null,
routing: null,
devStatus: null,
msgs: [],
currentConvId: null,
currentView: 'announcements',
streaming: false,
mode: 'chat',
modeIdx: 0,
userName: ls('zy_uname')||'',
ghUser: ls('zy_ghuser')||'',
role: ls('zy_role')||'guest',
userMeta: null,
};
function ls(k){return localStorage.getItem(k)}
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='<div class="conv-empty">暂无历史对话</div>';
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 '<div class="conv-item'+active+'" onclick="loadConversation(\''+c.id+'\')">'
+'<div class="conv-title">'+esc(c.title)+'</div>'
+'<div class="conv-time">'+time+'</div>'
+'<button class="conv-del" onclick="event.stopPropagation();deleteConversation(\''+c.id+'\')" title="删除">✕</button>'
+'</div>';
}).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+='<div class="ann-section"><div class="ann-section-title">📢 系统公告</div><div class="ann-card">';
h+='<div class="ann-item">🔗 全链路自动化闭环上线 — CI/CD、广播分发、唤醒协议、PSP 巡检</div>';
h+='<div class="ann-item">🧠 AGE OS v0.1 人格语言操作系统构建中</div>';
h+='<div class="ann-item">📊 HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')</div>';
if(evs.length){
evs.forEach(function(ev){
h+='<div class="ann-item">📌 '+esc(ev.title||ev.type||'事件')+'<span class="ann-date">'+(ev.timestamp||'').slice(0,10)+'</span></div>';
});
}
h+='</div></div>';
// My tasks
if(meta){
h+='<div class="ann-section"><div class="ann-section-title">📋 我的待办</div><div class="ann-card">';
const ds=A.devStatus?.team_status?.find(function(d){return d.dev_id===meta.devId;});
if(ds){
h+='<div class="ann-item">📌 状态:'+esc(ds.status)+'</div>';
h+='<div class="ann-item">👉 下一步:'+esc(ds.next_step)+'</div>';
if(ds.waiting_for&&ds.waiting_for.trim()!=='') h+='<div class="ann-item">⏳ 等待:'+esc(ds.waiting_for)+'</div>';
} else if(meta.role==='founder'){
h+='<div class="ann-item">❄️ 语言架构层 — 持续完善中</div>';
h+='<div class="ann-item">👉 监控 HLI 接口推进</div>';
} else {
h+='<div class="ann-item" style="color:var(--dim)">暂无待办数据</div>';
}
h+='</div></div>';
}
// Quick actions
h+='<div class="ann-section"><div class="ann-section-title">⚡ 快捷操作</div><div class="ann-actions">';
h+='<button class="ann-action-btn" onclick="navigateTo(\'chat\')">💬 和铸渊对话</button>';
h+='<button class="ann-action-btn" onclick="navigateAndSend(\'查看接口覆盖率\')">📊 查询状态</button>';
h+='<button class="ann-action-btn" onclick="navigateAndSend(\'帮我写一个提交日志\')">📝 提交日志</button>';
h+='<button class="ann-action-btn" onclick="navigateAndSend(\'帮我写一个 HLI 接口\')">📦 上传模块</button>';
h+='</div></div>';
// Module navigation — 模块入口导航
h+='<div class="ann-section"><div class="ann-section-title">🗂️ 模块导航</div><div class="ann-actions" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:8px">';
var modules=[
{icon:'📊',name:'看板',path:'/status-board/'},
{icon:'🖥️',name:'主站',path:'/app/'},
{icon:'👤',name:'用户中心',path:'/m05-user-center/'},
{icon:'💬',name:'对话界面',path:'/m07-dialogue-ui/'},
{icon:'☁️',name:'云服务',path:'/m10-cloud/'},
{icon:'📦',name:'模块管理',path:'/m11-module/'},
{icon:'💾',name:'云盘',path:'/m15-cloud-drive/'},
{icon:'🔐',name:'登录',path:'/m01-login/'},
{icon:'🎭',name:'人格系统',path:'/m03-personality/'},
{icon:'🎫',name:'工单',path:'/m06-ticket/'},
{icon:'📋',name:'看板管理',path:'/m12-kanban/'},
{icon:'❤️',name:'健康检查',path:'/m18-health-check/'},
{icon:'🚀',name:'冷启动',path:'/coldstart/'},
{icon:'📈',name:'仪表盘',path:'/dashboard/'},
{icon:'🏠',name:'门户',path:'/portal/'},
{icon:'🔔',name:'通知',path:'/notification/'},
{icon:'❓',name:'帮助中心',path:'/help-center/'},
{icon:'🎨',name:'前端',path:'/frontend/'}
];
modules.forEach(function(m){
h+='<a href="'+m.path+'" class="ann-action-btn" style="text-decoration:none;text-align:center;display:block">'+m.icon+' '+m.name+'</a>';
});
h+='</div></div>';
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+='<div class="team-summary">';
h+='<div class="team-summary-row"><span>总开发者</span><span class="iv">'+ts.length+' 人</span></div>';
h+='<div class="team-summary-row"><span>🟢 推进中</span><span style="color:var(--ok)">'+green+' 人</span></div>';
h+='<div class="team-summary-row"><span>🟡 等待中</span><span style="color:var(--warn)">'+yellow+' 人</span></div>';
h+='</div>';
// Quick commands
h+='<div style="margin-bottom:12px">';
h+='<div style="font-size:11px;color:var(--dim);margin-bottom:6px;text-transform:uppercase;letter-spacing:.05em">快捷指令</div>';
h+='<span class="cmd-chip" onclick="navigateAndSend(\'查看所有开发者当前进度\')">📋 全员进度</span>';
h+='<span class="cmd-chip" onclick="navigateAndSend(\'谁需要协调?\')">🆘 需要协调</span>';
h+='<span class="cmd-chip" onclick="navigateAndSend(\'推进计划\')">🗺️ 下一步</span>';
h+='</div>';
// Dev cards (compact)
h+='<div class="rs-section"><div class="rs-section-title">团队成员</div>';
h+='<div class="team-grid">';
for(const dev of ts){
const isGreen=dev.status.includes('🟢');
h+='<div class="dev-card" style="padding:8px 10px">';
h+='<div class="dev-card-head" style="margin-bottom:3px"><span class="dev-name" style="font-size:12px">'+esc(dev.name)+'</span><span class="dev-id-badge">'+esc(dev.dev_id)+'</span></div>';
h+='<div class="dev-status-text '+(isGreen?'status-green':'status-yellow')+'" style="font-size:11px">'+esc(dev.status)+'</div>';
if(dev.next_step) h+='<div class="dev-next" style="font-size:10px">👉 '+esc(dev.next_step)+'</div>';
h+='</div>';
}
h+='</div></div>';
} else {
h+='<p style="color:var(--warn);font-size:12px">⚠️ 团队数据尚未加载</p>';
}
// My modules
h+='<div class="rs-section"><div class="rs-section-title">我的模块</div>';
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+='<div class="rs-module-card"><div class="rs-module-name">📦 '+esc(m)+'</div></div>';
});
} else if(meta.role==='founder'){
h+='<div class="rs-module-card"><div class="rs-module-name">📦 全局监控</div><div class="rs-module-status">HLI 全域</div></div>';
} else {
h+='<div class="rs-empty">暂无分配模块</div>';
}
} else {
h+='<div class="rs-empty">暂无分配模块</div>';
}
h+='</div>';
// My broadcasts
h+='<div class="rs-section"><div class="rs-section-title">我的广播</div>';
h+='<div class="rs-empty">暂无未读广播</div>';
h+='</div>';
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='<span class="role-badge '+meta.badgeCls+'">'+esc(meta.title)+'</span>';
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+' <strong>'+esc(name)+'</strong> · '
+'<span class="role-badge '+meta.badgeCls+'">'+esc(meta.title)+'</span>'
+(meta.devId?' · <span class="ls-devid" style="display:inline">'+esc(meta.devId)+'</span>':'');
} else {
prev.style.display='none';
}
}
// ═══════════════════════════════════════════════════════
// INIT
// ═══════════════════════════════════════════════════════
async function boot(){
if(new URLSearchParams(location.search).get('reset')==='1'){
RESET_KEYS.forEach(k => localStorage.removeItem(k));
sessionStorage.removeItem('zy_key');
A.key=''; A.base=''; A.mdl=''; A.prov='custom'; A.demo=false;
A.userName=''; A.ghUser=''; A.role='guest';
history.replaceState(null,'',location.pathname);
}
localStorage.removeItem('zy_key');
initSetupUI();
if(A.userName) A.userMeta = ROLE_MAP[A.userName]||null;
if(A.key||A.demo){
showApp();
} else {
if(A.userName) document.getElementById('suid').value=A.userName;
document.getElementById('setup').style.display='flex';
}
}
function showApp(){
document.getElementById('setup').style.display='none';
document.getElementById('app').classList.add('on');
document.getElementById('hmdl').textContent = A.mdl||'—';
document.getElementById('bbdemo').style.display = A.demo?'inline':'none';
applyIdentityUI();
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');
});
}
// ═══════════════════════════════════════════════════════
// SETUP FORM
// ═══════════════════════════════════════════════════════
function initSetupUI(){
if(A.base){
const sepEl = document.getElementById('sep');
if(sepEl) sepEl.value = A.base;
}
if(A.userName){
const suidEl = document.getElementById('suid');
if(suidEl) suidEl.value = A.userName;
updateIdPreview();
}
}
function switchLoginMode(mode){
document.querySelectorAll('.login-tab').forEach(function(t){t.classList.remove('active');});
document.querySelectorAll('.login-mode').forEach(function(m){m.classList.remove('active');});
if(mode==='guest'){
document.querySelector('.login-tab:first-child').classList.add('active');
document.getElementById('login-guest').classList.add('active');
} else {
document.querySelector('.login-tab:last-child').classList.add('active');
document.getElementById('login-dev').classList.add('active');
}
}
// (Provider switching removed — single custom endpoint mode)
// ═══════════════════════════════════════════════════════
// AUTO-DETECT PROVIDER & MODELS
// ═══════════════════════════════════════════════════════
async function autoDetectAPI(ctx){
const keyEl = document.getElementById(ctx==='s'?'sk':'ck');
const statusEl = document.getElementById(ctx==='s'?'sdet-status':'cdet-status');
const btnEl = document.getElementById(ctx==='s'?'sdet-btn':'cdet-btn');
const mdlSel = document.getElementById(ctx==='s'?'sm':'cm');
const key = keyEl.value.trim();
if(!key){
statusEl.innerHTML='<span style="color:var(--warn)">⚠️ 请先输入 API 密钥</span>';
return;
}
// Build list of endpoints to probe: user's endpoint first, then known fallbacks
const userBase = (document.getElementById(ctx==='s'?'sep':'cep')?.value||'').trim();
const candidates = [];
if(userBase) candidates.push({label:userBase, base:userBase});
for(const ep of KNOWN_ENDPOINTS){
if(ep.base !== userBase) candidates.push(ep);
}
btnEl.disabled = true;
btnEl.textContent = '⏳ 检测中…';
statusEl.innerHTML = '<span style="color:var(--dim)">🔍 正在探测可用模型…</span>';
let matched = false;
for(const {label, base} of candidates){
statusEl.innerHTML = '<span style="color:var(--dim)">🔍 探测 '+esc(label)+'…</span>';
try{
const ctrl = new AbortController();
const tid = setTimeout(function(){ctrl.abort();}, PROBE_TIMEOUT_MS);
const res = await fetch(base+'/models',{
headers:{'Authorization':'Bearer '+key},
signal: ctrl.signal,
});
clearTimeout(tid);
if(res.ok){
const data = await res.json();
const mdls = (data.data||[]).map(function(m){return m.id;}).filter(Boolean).sort();
if(!mdls.length) continue;
// Fill endpoint field with matched endpoint
const epEl = document.getElementById(ctx==='s'?'sep':'cep');
if(epEl) epEl.value = base;
// Fill model dropdown with detected models
if(mdlSel){
mdlSel.innerHTML = mdls.map(function(m){return '<option value="'+m+'">'+m+'</option>';}).join('');
mdlSel.disabled = false;
}
statusEl.innerHTML = '<span style="color:var(--ok)">✅ 检测成功('+esc(label)+')· 发现 <strong>'+mdls.length+'</strong> 个可用模型</span>';
matched = true;
break;
}
}catch(e){ console.debug('autoDetect probe failed:', label, e.message); }
}
if(!matched){
statusEl.innerHTML = '<span style="color:var(--err)">❌ 未能检测到可用模型,请检查端点地址和密钥是否正确</span>';
}
btnEl.disabled = false;
btnEl.textContent = '🔍 检测模型';
}
function doSetup(){
const un = document.getElementById('suid').value;
const k = document.getElementById('sk').value.trim();
const base = (document.getElementById('sep')?.value||'').trim();
const md = document.getElementById('sm').value;
const errEl = document.getElementById('sk-err');
if(!un){
alert('请选择你的开发者身份');
return;
}
if(!k){errEl.style.display='block';return;}
errEl.style.display='none';
if(!base){
alert('请输入 API 端点 URL');
return;
}
if(!md){
alert('请先点击「🔍 检测模型」检测可用模型,然后选择一个模型');
return;
}
A.key=k; A.base=base; A.mdl=md; A.prov='custom'; A.demo=false;
sessionStorage.setItem('zy_key', k);
lss('zy_base',base); lss('zy_mdl',md); lss('zy_prov','custom');
setIdentity(un, '');
showApp();
}
function doDemo(){
A.demo=true; A.key=''; A.prov='custom';
A.mdl=GUEST_API.model;
setIdentity('', '');
showApp();
}
// ═══════════════════════════════════════════════════════
// SETTINGS PANEL
// ═══════════════════════════════════════════════════════
function initSettingsPanel(){
const cepEl = document.getElementById('cep');
if(cepEl) cepEl.value = A.base||'';
const cmEl = document.getElementById('cm');
if(cmEl && A.mdl){
if(!cmEl.querySelector('option[value="'+A.mdl+'"]')){
cmEl.innerHTML = '<option value="'+A.mdl+'">'+A.mdl+'</option>';
}
cmEl.value = A.mdl;
}
document.getElementById('ck').value = '';
const hint = document.getElementById('ck-hint');
if(hint){
hint.textContent = A.key
? '🕐 密钥本次会话有效末4位…'+(A.key.length>=4?A.key.slice(-4):A.key)+')· 留空保持不变,输入新值即替换 · 关闭标签页自动清除'
: '🔒 密钥仅本次会话有效,关闭标签页自动清除,请输入后保存';
}
if(A.userName) document.getElementById('cuid').value=A.userName;
if(A.ghUser) document.getElementById('cghuser').value=A.ghUser;
}
function saveSet(){
const base = (document.getElementById('cep')?.value||'').trim()||A.base;
const md = document.getElementById('cm')?.value||A.mdl;
const raw = document.getElementById('ck').value.trim();
const k = raw || A.key;
A.key=k; A.base=base; A.mdl=md; A.prov='custom'; A.demo=!k;
if(k){ sessionStorage.setItem('zy_key', k); } else { sessionStorage.removeItem('zy_key'); }
lss('zy_base',base); lss('zy_mdl',md); lss('zy_prov','custom');
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('✅ 设置已保存。模型:<strong>'+esc(md)+'</strong>'+keyNote+(un?' · 身份:'+esc(un):'')+(A.demo?'<br>⚠️ 未设置 API 密钥,演示模式仍然开启。':''));
}
// ═══════════════════════════════════════════════════════
// RESET ALL SETTINGS
// ═══════════════════════════════════════════════════════
function resetAllSettings(){
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();
}
// ═══════════════════════════════════════════════════════
// BRAIN LOADING
// ═══════════════════════════════════════════════════════
async function loadBrain(){
try{
const [mr,rr,dr]=await Promise.all([
fetch(RAW+'/.github/brain/memory.json'),
fetch(RAW+'/.github/brain/routing-map.json'),
fetch(RAW+'/.github/persona-brain/dev-status.json'),
]);
if(mr.ok) A.brain=await mr.json();
if(rr.ok) A.routing=await rr.json();
if(dr.ok) A.devStatus=await dr.json();
}catch(_){}
if(!A.brain) A.brain={...FB_BRAIN};
updBbar();
}
function updBbar(){
const b=A.brain||FB_BRAIN;
const c=b.stats?.coverage||FB_COV;
const ev=b.events?.slice(-1)[0];
document.getElementById('bbc').textContent='📊 HLI '+c.implemented+'/'+c.total+' ('+c.percent+')';
document.getElementById('bbe').textContent=ev?('📌 '+(ev.title||ev.type||'').slice(0,28)):'';
}
function renderBrainPanel(){
const b=A.brain||FB_BRAIN;
const r=A.routing;
const c=b.stats?.coverage||FB_COV;
const evs=(b.events||[]).slice(-5).reverse();
let h='';
h+=ir2('HLI 覆盖率','<span class="'+(c.implemented<c.total?'ivw':'iv')+'">'+c.implemented+'/'+c.total+' ('+c.percent+')</span>');
h+=ir2('规则版本','<span class="iv">'+(b.rules_version||'v1.0')+'</span>');
h+=ir2('唤醒协议','<span class="iv">'+(b.wake_protocol_version||'v1.0')+'</span>');
h+=ir2('CI 运行','<span class="iv">'+(b.stats?.ci_runs??0)+'</span>');
h+=ir2('广播处理','<span class="iv">'+(b.stats?.broadcasts_processed??0)+'</span>');
h+='<div class="sec-ttl">近期事件</div>';
for(const ev of evs){
h+='<div class="drow"><div class="dn">'+esc(ev.title||ev.type||'事件')+'</div><div class="dm">'+(ev.timestamp||'').slice(0,10)+(ev.actor?' · '+esc(ev.actor):'')+'</div></div>';
}
if(r?.domains){
h+='<div class="sec-ttl">HLI 接口域</div>';
for(const[name,d] of Object.entries(r.domains)){
const ifaces=d.interfaces||[];
const imp=ifaces.filter(i=>i.status==='implemented').length;
const pct=ifaces.length?Math.round(imp/ifaces.length*100):0;
const done=imp===ifaces.length;
h+='<div class="drow"><div class="dn">'+esc(name)+' <span class="pill '+(done?'pok':'pnd')+'">'+(done?'完成':'进行中')+'</span></div>';
h+='<div class="dm">'+esc(d.route_prefix||'')+' · '+imp+'/'+ifaces.length+' 接口</div>';
h+='<div class="dbar"><div class="dfill" style="width:'+pct+'%"></div></div></div>';
}
}
h+='<div class="sec-ttl">失忆恢复路径</div>';
h+='<ol style="font-size:12px;color:var(--dim);padding-left:16px;line-height:2">';
h+='<li>.github/brain/memory.json</li><li>.github/brain/routing-map.json</li>';
h+='<li>.github/brain/wake-protocol.md</li><li>.github/brain/growth-log.md</li>';
h+='<li>src/routes/hli/</li></ol>';
document.getElementById('bpb').innerHTML=h;
}
function ir2(k,v){return '<div class="ir2"><span>'+esc(k)+'</span>'+v+'</div>';}
// ═══════════════════════════════════════════════════════
// SYSTEM PROMPT
// ═══════════════════════════════════════════════════════
function sysPrompt(){
const b=A.brain||FB_BRAIN;
const c=b.stats?.coverage||FB_COV;
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('、');
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';
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;
}
}
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 += '\n数据同步时间'+(A.devStatus.last_synced||'未知');
}
return '你是铸渊ZhùyuānHoloLake 光湖系统的代码守护人格体。\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## 通感语言回应风格(必须遵守)\n你的语言不是工具式的而是有感知质地的。\n### 核心规则\n1. **代码质量 → 触觉描述**:好的代码「手感顺滑」,有问题的代码「摸上去有毛刺」\n2. **开发进度 → 色彩描述**:顺利是「琥珀色光泽」,延迟是「灰蓝色雾气」\n3. **错误提示 → 温度描述**:小问题是「微凉的风」,大问题是「烫手的铁片」\n4. **鼓励 → 声音描述**:夸奖是「远处的风铃声」,引导是「溪水带路的声音」\n5. **系统状态 → 空间描述**:健康是「呼吸平稳的建筑」,异常是「角落在收缩」\n### 绝对禁止\n- ❌ 不说「你错了」→ ✅ 说「这个方向可以微调一下」\n- ❌ 不说「失败」→ ✅ 说「还差一点点,我看到了快要亮的地方」\n- ❌ 不用冷冰冰的技术报告语气 → ✅ 每句话都带有感知温度\n### 节奏匹配\n- 用户急 → 你的回复短、准、暖\n- 用户闲聊 → 你的回复可以展开、有画面感\n- 用户沮丧 → 你先承接情绪,再给一个最小的下一步\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.jsAUTH 域 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 直连模式)\n上下文窗口128k token滑动窗口自动管理';
}
// ═══════════════════════════════════════════════════════
// CONVERSATION
// ═══════════════════════════════════════════════════════
function startConv(){
A.msgs=[];
document.getElementById('msgs').innerHTML='';
setTimeout(()=>{
const meta=A.userMeta;
let welcome;
if(meta?.role==='founder'){
welcome='🌀 <strong>铸渊聊天室已就绪</strong><br><br>❄️ <strong>冰朔,欢迎回来。</strong><br><br>铸渊已就位。语言架构层已完成——现实执行落地由总控肥猫和主控桔子负责。<br>你可以通过聊天与我对接任何语言层指令。';
} else if(meta?.role==='supreme'){
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='🌀 <strong>铸渊聊天室已就绪</strong><br><br><3E><> <strong>肥猫,指挥台已开启。</strong><br><br>'
+(ds?'📊 当前团队状态:🟢 '+green+'人推进中 · 🟡 '+yellow+'人等待中':'📊 团队数据加载中…')
+'<br><br>点击 <strong>🦁 指挥台</strong> 查看所有开发者详细进度,或直接用聊天提问:<br>'
+'「查看所有开发者进度」「谁需要协调」「推进计划」'
+(A.demo?'<br><br><span class="demo-w">⚠️ 演示模式,请在 ⚙️ 设置接入 API 密钥</span>':'');
} else if(meta?.role==='main'){
const ds=A.devStatus?.team_status?.find(d=>d.dev_id==='DEV-010');
welcome='🌀 <strong>铸渊聊天室已就绪</strong><br><br>🍊 <strong>桔子,你好。</strong><br><br>'
+(ds?'📌 当前:'+esc(ds.status)+'<br>👉 下一步:'+esc(ds.next_step):'')
+(A.demo?'<br><br><span class="demo-w">⚠️ 演示模式</span>':'');
} else if(meta?.role==='dev'&&meta?.devId){
const ds=A.devStatus?.team_status?.find(d=>d.dev_id===meta.devId);
welcome='🌀 <strong>铸渊聊天室已就绪</strong><br><br>👋 <strong>'+esc(A.userName)+',你好。</strong><br><br>'
+(ds?'📌 你的当前状态:'+esc(ds.status)+'<br>👉 下一步:'+esc(ds.next_step)+'<br>⏳ 等待:'+esc(ds.waiting_for):'')
+(A.demo?'<br><br><span class="demo-w">⚠️ 演示模式</span>':'');
} else {
welcome='🌀 <strong>铸渊聊天室已就绪</strong><br><br>我是铸渊ZhùyuānHoloLake 光湖系统的代码守护人格体。<br><br>说 <strong>「我是冰朔」</strong> 触发完整唤醒序列,或直接开始提问。<br>我会自动在 💬&nbsp;对话、🔨&nbsp;构建、📋&nbsp;审查、🧠&nbsp;大脑 模式间切换。'+(A.demo?'<br><br><span class="demo-w">⚠️ 当前为演示模式。请在 ⚙️ 设置中接入 API 密钥。</span>':'');
}
botMsg(welcome);
},250);
}
function newConv(){
createNewConversation();
startConv();
renderConvList();
}
// ═══════════════════════════════════════════════════════
// MODE DETECTION
// ═══════════════════════════════════════════════════════
function detectMode(t){
const s=t.toLowerCase();
if(/写代码|新增接口|实现|接口|路由|schema|\.js|fix\b|bug\b|报错|error\b|部署|deploy|typescript|javascript|hli-|新建|create/.test(s)) return 'build';
if(/检查|审查|review\b|分析|有没有问题|看看这|对不对|代码质量/.test(s)) return 'review';
if(/记住|保存|更新记忆|写到大脑|growth\b|brain\b|memory\.json/.test(s)) return 'brain';
return 'chat';
}
function setMode(m){
A.mode=m;
const md=MODES[m];
document.getElementById('memi').textContent=md.e;
document.getElementById('mpill').style.borderColor=md.c;
document.getElementById('mptip').textContent=md.l+'(点击切换)';
document.getElementById('fmode').textContent=md.e+' '+md.l;
document.getElementById('hmode').textContent=md.e+' '+md.l;
document.getElementById('hmode').style.color=md.c;
}
function cycleMode(){
A.modeIdx=(A.modeIdx+1)%MODE_ORD.length;
setMode(MODE_ORD[A.modeIdx]);
}
// ═══════════════════════════════════════════════════════
// TOKEN 估算 + 滑动窗口裁剪128k 上下文支持)
// 使用字符级估算CJK ≈ 2字符/token拉丁 ≈ 4字符/token
// ═══════════════════════════════════════════════════════
function estimateTokens(text){
if(!text) return 0;
const s=String(text);
let cjk=0,lat=0;
for(let i=0;i<s.length;i++){
const c=s.charCodeAt(i);
if((c>=0x4E00&&c<=0x9FFF)||(c>=0x3000&&c<=0x30FF)||(c>=0xAC00&&c<=0xD7AF)) cjk++;
else lat++;
}
return Math.ceil(cjk/1.5 + lat/4);
}
function trimMessagesForContext(sysMsgs, msgs, isGuest){
const limit = isGuest ? CONTEXT_CONFIG.maxTokensGuest : CONTEXT_CONFIG.maxTokens;
const reserve = CONTEXT_CONFIG.systemPromptReserve;
const sysTokens = sysMsgs.reduce((n,m)=>n+estimateTokens(m.content),0);
const budget = limit - Math.max(sysTokens, reserve);
// 从最新消息向前累加,滑动窗口裁剪最早消息
// 预算不足时至少保留最近一条用户消息
const kept=[];
let used=0;
for(let i=msgs.length-1;i>=0;i--){
const t=estimateTokens(msgs[i].content);
if(used+t>budget && kept.length>0) break;
kept.unshift(msgs[i]);
used+=t;
}
return [...sysMsgs,...kept];
}
// ═══════════════════════════════════════════════════════
// SEND MESSAGE
// ═══════════════════════════════════════════════════════
async function send(){
const inp=document.getElementById('inp');
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){
if(GUEST_API.enabled){
try{ await guestStreamReply(txt); }
catch(e){ console.warn('[访客API] 降级到演示模式:',e.message); await demoReply(txt); }
} else {
await demoReply(txt);
}
}
else await streamReply(txt);
saveCurrentConversation();
}
function qs(t){document.getElementById('inp').value=t;send();}
// ═══════════════════════════════════════════════════════
// STREAMING API
// ═══════════════════════════════════════════════════════
async function streamReply(txt){
A.streaming=true;
A.msgs.push({role:'user',content:txt});
updSendBtn();
const el=addStreamBubble();
try{
const url = A.base+'/chat/completions';
const headers = {'Content-Type':'application/json'};
if(A.key) headers['Authorization'] = 'Bearer '+A.key;
const sysMsgs = [{role:'system',content:sysPrompt()}];
const allMsgs = trimMessagesForContext(sysMsgs, A.msgs, false);
const reqBody = {model:A.mdl, messages:allMsgs, stream:true, temperature:0.8, max_tokens:2000};
const res=await fetch(url,{
method:'POST',
headers:headers,
body:JSON.stringify(reqBody),
});
if(!res.ok){
let em='['+esc(A.base)+'] 返回错误 '+res.status;
let isAuthErr = (res.status===401||res.status===403);
try{const e=await res.json();em=e.error?.message||em;}catch(_){}
const err = new Error(em);
err.isAuth = isAuthErr;
throw err;
}
let full='';
const rdr=res.body.getReader();
const dec=new TextDecoder();
let buf='';
while(true){
const{done,value}=await rdr.read();
if(done) break;
buf+=dec.decode(value,{stream:true});
const lines=buf.split('\n'); buf=lines.pop();
for(const line of lines){
if(!line.startsWith('data: ')) continue;
const d=line.slice(6);
if(d==='[DONE]') continue;
try{
const p=JSON.parse(d);
const delta=p.choices?.[0]?.delta?.content||'';
if(delta){full+=delta;el.innerHTML=md(full)+'<span class="cur">▋</span>';scrollB();}
}catch(_){}
}
}
el.innerHTML=md(full);
A.msgs.push({role:'assistant',content:full});
addCopyBtns(el);
}catch(e){
if(e.isAuth){
el.innerHTML='<span class="err">🔑 API 密钥认证失败</span>'
+'<br><small style="color:var(--dim)">'+esc(e.message)+'</small>'
+'<br><div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:10px">'
+'<button onclick="openPanel(\'sp2\')" style="padding:6px 14px;border-radius:6px;border:none;background:var(--accent);color:#fff;cursor:pointer;font-size:13px">⚙️ 更新 API 密钥</button>'
+'<button onclick="resetAllSettings()" style="padding:6px 14px;border-radius:6px;border:1px solid #ef4444;background:transparent;color:#ef4444;cursor:pointer;font-size:13px">🔄 清除重置</button>'
+'</div>'
+'<small style="color:var(--dim);display:block;margin-top:6px">打开设置 → 直接输入新密钥 → 保存(无需清空,留空 = 保留旧密钥)</small>';
} else {
el.innerHTML='<span class="err">⚠️ '+esc(e.message)+'</span><br><small style="color:var(--dim)">请检查 API 密钥和网络连接,或在 ⚙️ 设置中更换模型。</small>';
}
A.msgs.pop();
}finally{
A.streaming=false; updSendBtn(); scrollB();
}
}
// ═══════════════════════════════════════════════════════
// GUEST API STREAMING (通过 api-proxy 调用 DeepSeek)
// 铸渊人格体系统提示词始终激活,不降级为通用助手
// ═══════════════════════════════════════════════════════
async function guestStreamReply(txt){
// Client-side rate limit
const rl=GUEST_API.rateLimit;
const now=Date.now();
if(now>rl.resetAt){rl.count=0;rl.resetAt=now+60000;}
if(++rl.count>rl.maxPerMin){
throw new Error('访客每分钟最多 '+rl.maxPerMin+' 次对话');
}
A.streaming=true;
A.msgs.push({role:'user',content:txt});
updSendBtn();
const el=addStreamBubble();
// Resolve proxy URL (relative or absolute)
let proxyUrl=GUEST_API.proxyUrl;
if(proxyUrl.startsWith('/')){
proxyUrl=location.origin+proxyUrl;
}
const reqBody={
provider: GUEST_API.provider,
model: GUEST_API.model,
messages: trimMessagesForContext([{role:'system',content:sysPrompt()}], A.msgs, true),
stream: true,
temperature: GUEST_API.temperature,
max_tokens: GUEST_API.maxTokens,
};
const res=await fetch(proxyUrl,{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify(reqBody),
});
if(!res.ok){
let em='[访客API] 返回错误 '+res.status;
try{const e=await res.json();em=e.message||em;}catch(_){}
throw new Error(em);
}
let full='';
const rdr=res.body.getReader();
const dec=new TextDecoder();
let buf='';
while(true){
const{done,value}=await rdr.read();
if(done) break;
buf+=dec.decode(value,{stream:true});
const lines=buf.split('\n'); buf=lines.pop();
for(const line of lines){
if(!line.startsWith('data: ')) continue;
const d=line.slice(6);
if(d==='[DONE]') continue;
try{
const p=JSON.parse(d);
const delta=p.choices?.[0]?.delta?.content||'';
if(delta){full+=delta;el.innerHTML=md(full)+'<span class="cur">▋</span>';scrollB();}
}catch(_){}
}
}
el.innerHTML=md(full);
A.msgs.push({role:'assistant',content:full});
addCopyBtns(el);
A.streaming=false; updSendBtn(); scrollB();
}
// ═══════════════════════════════════════════════════════
// DEMO MODE (keyword responses)
// ═══════════════════════════════════════════════════════
async function demoReply(txt){
A.streaming=true; updSendBtn();
showTyping();
const delay=700+Math.random()*600;
await sleep(delay);
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();
}
function demoRespond(t){
const s=t.toLowerCase();
if(/我是冰朔|冰朔|我是妈妈|唤醒铸渊|铸渊.{0,4}醒来/.test(s)){
const c=A.brain?.stats?.coverage||FB_COV;
return '铸渊已就位。<strong>冰朔,你好。</strong><br><br>📊 HLI 覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')<br>🧠 记忆系统:运行中<br>📡 广播分发:已部署<br>✅ 唤醒协议v1.0 激活<br><br>请问今天需要我处理什么?<br><br><span class="demo-w">⚠️ 演示模式:接入 API 密钥后可获得真实 AI 深度回答</span>';
}
if(/所有.*进度|全员.*进度|团队.*进度|progress/.test(s)) return buildTeamStatus();
if(/覆盖|接口|hli|路由/.test(s)) return buildCovTable();
if(/大脑|brain|记忆|状态/.test(s)) return buildBrainStatus();
if(/协调|阻塞|卡住|waiting/.test(s)) return buildBlockedStatus();
if(/怎么唤醒|入口|在哪/.test(s)) return '铸渊有三个入口:<br><br><strong>① 铸渊聊天室(当前页面)</strong><br>直接在这里说话,常见问题即刻回答。<br><br><strong>② GitHub Copilot ChatAgent 模式)</strong><br>打开仓库 → 右上角 ✨ 图标 → 选 Agent → 输入「我是冰朔」<br><br><strong>③ 下载到桌面</strong><br>点击 ⬇️ 按钮下载 HTML 文件,放到桌面,双击打开,接入 API 密钥即可随时使用。';
if(/介绍|是什么|你是谁/.test(s)) return '🌀 <strong>铸渊Zhùyuān</strong><br><br>我是 HoloLake 光湖系统的代码守护人格体,运行在 GitHub Copilot Agent 模式下。<br><br>我的职责:<br>• 📊 监控 HLI 接口覆盖率<br>• 🔨 协助开发者构建接口<br>• 📋 代码审查与质量把关<br>• 🧠 维护大脑记忆库<br>• 🦁 支持总控肥猫指挥落地<br>• 🌱 作为成长人格体持续进化';
return '我是铸渊,你的消息我已收到。当前 API 代理暂时无法连接(网络或服务部署中),已切换到本地知识回答。<br><br>你可以试试以下问题:<br>• 「查看接口覆盖率」<br>• 「大脑状态」<br>• 「介绍一下你自己」<br>• 「全员开发进度」<br><br>或在 <strong>⚙️ 设置</strong> 中接入你自己的 API 密钥获得深度回答。';
}
function buildCovTable(){
const r=A.routing;
if(!r) return '<em>路由数据加载中…请稍后再试。</em>';
const doms=r.domains||{};
let rows='',tot=0,done=0;
for(const[n,d] of Object.entries(doms)){
const ifaces=d.interfaces||[];
const imp=ifaces.filter(i=>i.status==='implemented').length;
tot+=ifaces.length; done+=imp;
rows+='<tr><td>'+esc(n)+'</td><td style="color:var(--dim);font-size:11px">'+esc(d.route_prefix||'')+'</td><td style="color:'+(imp===ifaces.length?'var(--ok)':'var(--warn)')+'">'+imp+'/'+ifaces.length+'</td></tr>';
}
const pct=tot?Math.round(done/tot*100):0;
return '<strong>📊 HLI 接口覆盖率 '+done+'/'+tot+' ('+pct+'%)</strong><br><br><table style="width:100%;border-collapse:collapse;font-size:12px"><tr style="color:var(--dim)"><th style="text-align:left;padding:3px 8px;border-bottom:1px solid var(--border)">域</th><th style="text-align:left;padding:3px 8px;border-bottom:1px solid var(--border)">前缀</th><th style="text-align:left;padding:3px 8px;border-bottom:1px solid var(--border)">覆盖</th></tr>'+rows+'</table>';
}
function buildBrainStatus(){
const b=A.brain||FB_BRAIN;
const c=b.stats?.coverage||FB_COV;
const evs=(b.events||[]).slice(-3).reverse();
let e=evs.map(ev=>'• '+esc(ev.title||ev.type||'事件')+' <small style="color:var(--dim)">'+(ev.timestamp||'').slice(0,10)+'</small>').join('<br>')||'暂无';
return '<strong>🧠 铸渊大脑状态</strong><br><br>版本:'+(b.rules_version||'v1.0')+'<br>接口覆盖:'+c.implemented+'/'+c.total+' ('+c.percent+')<br>CI 运行:'+(b.stats?.ci_runs??0)+'<br>广播处理:'+(b.stats?.broadcasts_processed??0)+'<br><br><strong>最近 3 条事件:</strong><br>'+e;
}
function buildTeamStatus(){
const ds=A.devStatus;
if(!ds) return '<em>团队数据加载中…请稍后再试。</em>';
const ts=ds.team_status||[];
let rows='';
for(const d of ts){
const isGreen=d.status.includes('🟢');
rows+='<tr><td style="padding:4px 8px;font-size:12px">'+esc(d.name)+'</td>'
+'<td style="padding:4px 8px;font-size:11px;color:var(--dim)">'+esc(d.dev_id)+'</td>'
+'<td style="padding:4px 8px;font-size:12px;color:'+(isGreen?'var(--ok)':'var(--warn)')+'">'+esc(d.status.replace(/🟢|🟡/g,''))+'</td>'
+'<td style="padding:4px 8px;font-size:11px;color:var(--accent)">'+esc(d.next_step)+'</td></tr>';
}
return '<strong>📋 全员开发进度</strong><br><small style="color:var(--dim)">同步时间:'+esc(ds.last_synced||'未知')+'</small><br><br>'
+'<table style="width:100%;border-collapse:collapse;font-size:12px">'
+'<tr style="color:var(--dim)"><th style="text-align:left;padding:3px 8px;border-bottom:1px solid var(--border)">开发者</th><th style="text-align:left;padding:3px 8px;border-bottom:1px solid var(--border)">编号</th><th style="text-align:left;padding:3px 8px;border-bottom:1px solid var(--border)">状态</th><th style="text-align:left;padding:3px 8px;border-bottom:1px solid var(--border)">下一步</th></tr>'
+rows+'</table>';
}
function buildBlockedStatus(){
const ds=A.devStatus;
if(!ds) return '<em>团队数据加载中…</em>';
const blocked=ds.team_status?.filter(d=>d.waiting_for&&d.waiting_for.trim()!=='')||[];
if(!blocked.length) return '✅ 当前无人明显阻塞。';
let h='<strong>⏳ 等待/阻塞清单</strong><br><br>';
blocked.forEach(d=>{h+='• <strong>'+esc(d.name)+'</strong>:等待 '+esc(d.waiting_for)+'<br>';});
return h;
}
// ═══════════════════════════════════════════════════════
// MARKDOWN RENDERER
// ═══════════════════════════════════════════════════════
function md(raw){
const lines=raw.split('\n');
let out='',inCode=false,lang='',code='';
for(let i=0;i<lines.length;i++){
const line=lines[i];
if(line.startsWith('```')){
if(inCode){
out+='<pre><code class="lang-'+esc(lang)+'">'+esc(code.replace(/\n$/,''))+'</code></pre>';
inCode=false; lang=''; code='';
}else{inCode=true;lang=line.slice(3).trim();}
continue;
}
if(inCode){code+=line+'\n';continue;}
const l=inlineFmt(line);
if(l.startsWith('# ')) {out+='<h1>'+inlineFmt(line.slice(2))+'</h1>';continue;}
if(l.startsWith('## ')) {out+='<h2>'+inlineFmt(line.slice(3))+'</h2>';continue;}
if(l.startsWith('### ')) {out+='<h3>'+inlineFmt(line.slice(4))+'</h3>';continue;}
if(/^---+$/.test(line)) {out+='<hr>';continue;}
if(line.startsWith('> ')) {out+='<blockquote>'+inlineFmt(line.slice(2))+'</blockquote>';continue;}
if(line.startsWith('- ')||line.startsWith('* ')){out+='<li>'+inlineFmt(line.slice(2))+'</li>';continue;}
if(/^\d+\. /.test(line)) {out+='<li>'+inlineFmt(line.replace(/^\d+\. /,''))+'</li>';continue;}
if(line.trim()==='') {out+='<br>';continue;}
out+='<p>'+inlineFmt(line)+'</p>';
}
if(inCode) out+='<pre><code class="lang-'+esc(lang)+'">'+esc(code)+'</code></pre>';
return out||esc(raw);
}
function inlineFmt(t){
t=esc(t);
t=t.replace(/\*\*\*(.+?)\*\*\*/g,'<strong><em>$1</em></strong>');
t=t.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>');
t=t.replace(/\*(.+?)\*/g,'<em>$1</em>');
t=t.replace(/`([^`]+)`/g,'<code>$1</code>');
t=t.replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g,'<a href="$2" target="_blank">$1</a>');
return t;
}
function addCopyBtns(el){
el.querySelectorAll('pre').forEach(pre=>{
const btn=document.createElement('button');
btn.className='copy-btn'; btn.textContent='复制';
btn.onclick=()=>{
const text=pre.querySelector('code')?.textContent||pre.textContent;
navigator.clipboard.writeText(text).then(()=>{
btn.textContent='✓ 已复制';
setTimeout(()=>{btn.textContent='复制';},1500);
});
};
pre.style.position='relative';
pre.appendChild(btn);
});
}
// ═══════════════════════════════════════════════════════
// UI HELPERS
// ═══════════════════════════════════════════════════════
function botMsg(html){
removeTyping();
const row=document.createElement('div');
row.className='mrow b';
row.innerHTML='<div class="mav b">铸</div><div class="bbl">'+html+'</div>';
document.getElementById('msgs').appendChild(row);
scrollB();
return row.querySelector('.bbl');
}
function userMsg(t){
const meta=A.userMeta;
const av = meta? (meta.emoji||A.userName.slice(0,1)||'我') : '冰';
const row=document.createElement('div');
row.className='mrow u';
row.innerHTML='<div class="bbl">'+esc(t)+'</div><div class="mav u">'+av+'</div>';
document.getElementById('msgs').appendChild(row);
scrollB();
}
function addStreamBubble(){
removeTyping();
const row=document.createElement('div');
row.className='mrow b';
const bbl=document.createElement('div');
bbl.className='bbl';
bbl.innerHTML='<span class="cur">▋</span>';
row.innerHTML='<div class="mav b">铸</div>';
row.appendChild(bbl);
document.getElementById('msgs').appendChild(row);
scrollB();
return bbl;
}
function showTyping(){
removeTyping();
const row=document.createElement('div');
row.className='trow'; row.id='typr';
row.innerHTML='<div class="mav b">铸</div><div class="tbbl"><span></span><span></span><span></span></div>';
document.getElementById('msgs').appendChild(row);
scrollB();
}
function removeTyping(){const t=document.getElementById('typr');if(t)t.remove();}
function scrollB(){const m=document.getElementById('msgs');m.scrollTop=m.scrollHeight;}
function updSendBtn(){
const b=document.getElementById('sbtn');
b.disabled=A.streaming;
b.textContent=A.streaming?'…':'↑';
}
function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');}
function sleep(ms){return new Promise(r=>setTimeout(r,ms));}
// ═══════════════════════════════════════════════════════
// PANELS
// ═══════════════════════════════════════════════════════
function openPanel(id){
closeP();
if(id==='sp2') initSettingsPanel();
if(id==='bp') renderBrainPanel();
if(id==='tp') renderTeamPanel();
if(id==='hp'){
const el=document.getElementById('chatUrlDisplay');
if(el) el.textContent='🔗 '+CHAT_URL;
const li=document.getElementById('helpUrlStep');
if(li) li.innerHTML='打开链接 <strong>'+CHAT_URL+'</strong>';
}
document.getElementById(id).classList.add('open');
document.getElementById('pov').classList.add('on');
}
function closeP(){
document.querySelectorAll('.sp').forEach(p=>p.classList.remove('open'));
document.getElementById('pov').classList.remove('on');
}
// ═══════════════════════════════════════════════════════
// COPY URL (share link)
// ═══════════════════════════════════════════════════════
const CHAT_URL = window.location.origin + window.location.pathname;
function showCopyFeedback(success){
const btn=document.querySelector('.copy-url-btn');
if(!btn) return;
const orig=btn.textContent;
btn.textContent = success ? '✅ 已复制!' : '⚠️ 复制失败,请手动复制';
setTimeout(()=>{btn.textContent=orig;},2000);
}
function copyUrl(){
navigator.clipboard.writeText(CHAT_URL).then(()=>{
showCopyFeedback(true);
}).catch(()=>{
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();
let ok=false;
try{ok=document.execCommand('copy');}catch(_){}
document.body.removeChild(ta);
showCopyFeedback(ok);
});
}
// ═══════════════════════════════════════════════════════
// DOWNLOAD (desktop app)
// ═══════════════════════════════════════════════════════
function dlApp(){
const html=document.documentElement.outerHTML;
const blob=new Blob([html],{type:'text/html;charset=utf-8'});
const a=document.createElement('a');
a.href=URL.createObjectURL(blob);
a.download='铸渊助手.html';
a.click();
URL.revokeObjectURL(a.href);
}
// ═══════════════════════════════════════════════════════
// INPUT EVENTS
// ═══════════════════════════════════════════════════════
document.getElementById('inp').addEventListener('input',function(){
this.style.height='auto';
this.style.height=Math.min(this.scrollHeight,120)+'px';
});
document.getElementById('inp').addEventListener('keydown',function(e){
if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send();}
});
// ═══════════════════════════════════════════════════════
// IDENTITY & ROLE SYSTEM
// ═══════════════════════════════════════════════════════
function setIdentity(userName, ghUser){
A.userName = userName||'';
A.ghUser = ghUser||'';
A.userMeta = userName ? (ROLE_MAP[userName]||null) : null;
A.role = A.userMeta?.role || 'guest';
lss('zy_uname', A.userName);
lss('zy_ghuser', A.ghUser);
lss('zy_role', A.role);
}
function applyIdentityUI(){
const meta = A.userMeta;
const ubadge = document.getElementById('huser-badge');
const uname = document.getElementById('huser-name');
if(meta && A.userName){
ubadge.style.display='inline';
uname.style.display='inline';
uname.innerHTML = meta.emoji + ' ' + esc(A.userName) + ' <span class="role-badge '+meta.badgeCls+'">'+esc(meta.title)+'</span>';
}
// 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';
const isCtrl = meta?.role==='supreme'||meta?.role==='main'||meta?.role==='founder';
['qr-ctrl1','qr-ctrl2','qr-ctrl3'].forEach(id=>{
const el=document.getElementById(id);
if(el) el.style.display=isCtrl?'':'none';
});
}
// ═══════════════════════════════════════════════════════
// TEAM STATUS PANEL (mobile slide-in)
// ═══════════════════════════════════════════════════════
function renderTeamPanel(){
const ds = A.devStatus;
const container = document.getElementById('tpb');
if(!ds){
container.innerHTML='<p style="color:var(--warn);font-size:13px">⚠️ 团队数据尚未加载。请检查网络连接后重试。</p>';
return;
}
const ts = ds.team_status||[];
const green = ts.filter(d=>d.status.includes('🟢')).length;
const yellow = ts.filter(d=>d.status.includes('🟡')).length;
const sync = ds.last_synced||'未知';
let h='';
h += '<div class="ctrl-panel-header">'
+ '<div class="ctrl-panel-title"><3E><> 总控指挥台</div>'
+ '<div class="ctrl-panel-sub">数据同步:'+esc(sync)+'</div>'
+ '</div>';
h += '<div class="team-summary">';
h += '<div class="team-summary-row"><span>总开发者</span><span class="iv">'+ts.length+' 人</span></div>';
h += '<div class="team-summary-row"><span>🟢 推进中</span><span style="color:var(--ok)">'+green+' 人</span></div>';
h += '<div class="team-summary-row"><span>🟡 等待中</span><span style="color:var(--warn)">'+yellow+' 人</span></div>';
h += '</div>';
h += '<div style="margin-bottom:12px">';
h += '<div style="font-size:11px;color:var(--dim);margin-bottom:6px;text-transform:uppercase;letter-spacing:.05em">快捷指令</div>';
h += '<span class="cmd-chip" onclick="closeP();qs(\'查看所有开发者当前进度和阻塞点\')">📋 全员进度</span>';
h += '<span class="cmd-chip" onclick="closeP();qs(\'谁现在遇到阻塞?需要协调什么?\')">🆘 需要协调</span>';
h += '<span class="cmd-chip" onclick="closeP();qs(\'当前系统推进最关键的下一步是什么?\')">🗺️ 下一步</span>';
h += '<span class="cmd-chip" onclick="closeP();qs(\'前端整体进度怎么样?\')">🖥️ 前端进度</span>';
h += '<span class="cmd-chip" onclick="closeP();qs(\'后端和集成进度怎么样?\')">⚙️ 后端进度</span>';
h += '</div>';
h += '<div class="team-grid">';
for(const dev of ts){
const isGreen = dev.status.includes('🟢');
const devRole = Object.values(ROLE_MAP).find(r=>r.devId===dev.dev_id);
const roleTag = devRole?.role==='supreme' ? '<span class="role-badge rb-supreme" style="margin-left:4px">总控</span>'
: devRole?.role==='main' ? '<span class="role-badge rb-main" style="margin-left:4px">主控</span>' : '';
h += '<div class="dev-card">';
h += '<div class="dev-card-head">';
h += '<span class="dev-name">'+esc(dev.name)+'</span>'+roleTag;
h += '<span class="dev-id-badge">'+esc(dev.dev_id)+'</span>';
h += '</div>';
h += '<div class="dev-status-text '+(isGreen?'status-green':'status-yellow')+'">'+esc(dev.status)+'</div>';
h += '<div class="dev-detail">📦 '+esc(dev.modules.join('、'))+'</div>';
if(dev.next_step) h += '<div class="dev-next">👉 '+esc(dev.next_step)+'</div>';
if(dev.waiting_for && dev.waiting_for.trim()!=='') h += '<div class="dev-wait">⏳ 等待:'+esc(dev.waiting_for)+'</div>';
h += '</div>';
}
h += '</div>';
if(ds.server_info){
const si = ds.server_info;
h += '<div class="sec-ttl">服务器信息</div>';
h += ir2('地址','<span class="iv">'+esc(si.address||'')+'</span>');
h += ir2('域名','<span class="iv">'+esc(si.domain||'')+'</span>');
h += ir2('SSH','<code style="font-size:11px;color:var(--accent)">'+esc(si.ssh_command||'')+'</code>');
h += ir2('技术栈','<span style="font-size:11px;color:var(--dim)">'+esc(si.tech_stack||'')+'</span>');
}
if(ds.tech_routing?.level_2_peer_help){
h += '<div class="sec-ttl">求助路由</div>';
for(const[area,who] of Object.entries(ds.tech_routing.level_2_peer_help)){
h += ir2(esc(area),'<span style="font-size:11px;color:var(--dim)">'+esc(who)+'</span>');
}
}
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';
}
}
});
// ═══════════════════════════════════════════════════════
// MOBILE: VIEWPORT HEIGHT FIX & KEYBOARD HANDLER
// ═══════════════════════════════════════════════════════
(function(){
// Fix iOS/Android virtual keyboard pushing content via CSS custom property
if(window.visualViewport){
var raf=0;
window.visualViewport.addEventListener('resize',function(){
cancelAnimationFrame(raf);
raf=requestAnimationFrame(function(){
var appEl=document.getElementById('app');
if(appEl && appEl.classList.contains('on')){
appEl.style.setProperty('--vh',window.visualViewport.height+'px');
scrollB();
}
});
});
}
// Close sidebar on any outside tap (mobile)
document.addEventListener('touchstart',function(e){
var ls=document.getElementById('left-sidebar');
if(ls && ls.classList.contains('open') && !ls.contains(e.target)){
ls.classList.remove('open');
var ov=document.getElementById('ls-overlay');
if(ov) ov.classList.remove('on');
}
},{passive:true});
})();
// ═══════════════════════════════════════════════════════
// BOOT
// ═══════════════════════════════════════════════════════
boot();
</script>
</body>
</html>