zhizhi/docs/index.html

2043 lines
111 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
<!-- 铸渊助手 v4.0 · 2026-03-06 · three-column layout + chat history + announcements -->
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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:'PingFang SC','Microsoft YaHei','Segoe UI',sans-serif;font-size:15px;line-height:1.6}
/* ─── SETUP ─── */
#setup{position:fixed;inset:0;z-index:1000;background:var(--bg);display:flex;align-items:center;justify-content:center;padding:20px}
.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)}
.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:14px;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}
#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;width:100%;overflow:hidden}
#app.on{display:flex;flex-direction:column}
/* ─── LEFT SIDEBAR ─── */
#left-sidebar{width:var(--ls-w);flex-shrink:0;background:var(--s1);border-right:1px solid var(--border);display:none;flex-direction:column;overflow:hidden;z-index:120}
.ls-header{padding:16px;border-bottom:1px solid var(--border)}
.ls-user{display:flex;align-items:center;gap:10px}
.ls-avatar{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,var(--accent),var(--a2));display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px;flex-shrink:0}
.ls-name{font-size:14px;font-weight:600}
.ls-role{font-size:11px;color:var(--dim)}
.ls-devid{font-size:10px;color:var(--accent);background:var(--adim);padding:1px 6px;border-radius:4px;display:inline-block;margin-top:2px}
.ls-nav{padding:8px;border-bottom:1px solid var(--border);display:flex;flex-direction:column;gap:2px}
.nav-tab{display:flex;align-items:center;gap:8px;padding:9px 12px;border-radius:var(--rs);background:transparent;border:none;color:var(--dim);font-size:13px;font-weight:500;cursor:pointer;transition:all .2s;text-align:left;font-family:inherit;width:100%}
.nav-tab:hover{background:var(--s2);color:var(--text)}
.nav-tab.active{background:var(--adim);color:var(--accent);font-weight:600}
.ls-new-conv{margin:8px;padding:10px;border-radius:var(--rs);border:1px dashed var(--border);background:transparent;color:var(--dim);font-size:13px;cursor:pointer;transition:all .2s;font-family:inherit;display:flex;align-items:center;justify-content:center;gap:6px}
.ls-new-conv:hover{border-color:var(--accent);color:var(--accent);background:var(--adim)}
.ls-conv-list{flex:1;overflow-y:auto;padding:4px 8px}
.ls-conv-list::-webkit-scrollbar{width:3px}
.ls-conv-list::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
.conv-item{padding:10px;border-radius:var(--rs);cursor:pointer;transition:all .2s;position:relative;margin-bottom:2px;border:1px solid transparent}
.conv-item:hover{background:var(--s2);border-color:var(--border)}
.conv-item.conv-active{background:var(--adim);border-color:var(--accent)}
.conv-title{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:20px}
.conv-time{font-size:10px;color:var(--dim);margin-top:2px}
.conv-del{position:absolute;top:8px;right:6px;background:transparent;border:none;color:var(--dim);font-size:11px;cursor:pointer;padding:2px 4px;border-radius:4px;opacity:0;transition:all .2s}
.conv-item:hover .conv-del{opacity:1}
.conv-del:hover{color:var(--err);background:rgba(248,113,113,.1)}
.conv-empty{padding:20px 12px;text-align:center;font-size:12px;color:var(--dim)}
.ls-footer{padding:8px;border-top:1px solid var(--border)}
.ls-footer-btn{display:flex;align-items:center;gap:8px;padding:9px 12px;border-radius:var(--rs);background:transparent;border:none;color:var(--dim);font-size:13px;cursor:pointer;transition:all .2s;width:100%;font-family:inherit;text-align:left}
.ls-footer-btn:hover{background:var(--s2);color:var(--text)}
/* ─── LEFT SIDEBAR MOBILE OVERLAY ─── */
#ls-overlay{display:none;position:fixed;inset:0;z-index:110;background:rgba(0,0,0,.5)}
#ls-overlay.on{display:block}
/* ─── CENTER AREA ─── */
#center-area{flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden}
/* ─── CHAT VIEW ─── */
#chat-view{display:flex;flex-direction:column;flex:1;min-height:0}
/* ─── ANNOUNCEMENTS VIEW ─── */
#announcements-view{display:none;flex-direction:column;flex:1;overflow-y:auto;background:var(--bg)}
.ann-header{padding:20px 24px 12px;border-bottom:1px solid var(--border);background:var(--s1);flex-shrink:0}
.ann-header h2{font-size:18px;font-weight:700}
.ann-header-sub{font-size:12px;color:var(--dim);margin-top:4px}
.ann-scroll{flex:1;overflow-y:auto;padding:20px 24px}
.ann-scroll::-webkit-scrollbar{width:4px}
.ann-scroll::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
.ann-section{margin-bottom:20px}
.ann-section-title{font-size:13px;font-weight:600;color:var(--text);margin-bottom:10px;display:flex;align-items:center;gap:6px}
.ann-card{background:var(--s1);border:1px solid var(--border);border-radius:12px;padding:14px;transition:border-color .2s}
.ann-card:hover{border-color:rgba(79,142,247,.3)}
.ann-item{padding:8px 0;border-bottom:1px solid rgba(30,50,81,.4);font-size:13px;line-height:1.7;display:flex;align-items:flex-start;gap:6px}
.ann-item:last-child{border-bottom:none}
.ann-date{font-size:10px;color:var(--dim);white-space:nowrap;margin-left:auto}
.ann-actions{display:flex;flex-wrap:wrap;gap:8px}
.ann-action-btn{padding:10px 16px;border-radius:var(--rs);background:var(--s1);border:1px solid var(--border);color:var(--text);font-size:13px;cursor:pointer;transition:all .2s;font-family:inherit}
.ann-action-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--adim)}
/* ─── RIGHT SIDEBAR ─── */
#right-sidebar{width:var(--rs-w);flex-shrink:0;background:var(--s1);border-left:1px solid var(--border);display:none;flex-direction:column;overflow:hidden}
.rs-header{padding:14px 16px;border-bottom:1px solid var(--border);flex-shrink:0}
.rs-header h3{font-size:14px;font-weight:600}
.rs-body{flex:1;overflow-y:auto;padding:14px}
.rs-body::-webkit-scrollbar{width:3px}
.rs-body::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
.rs-section{margin-bottom:16px}
.rs-section-title{font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;margin-bottom:8px;padding-bottom:6px;border-bottom:1px solid var(--border)}
.rs-module-card{padding:8px 10px;background:var(--s2);border:1px solid var(--border);border-radius:var(--rs);margin-bottom:6px;font-size:12px}
.rs-module-name{font-weight:600;font-size:12px}
.rs-module-status{font-size:11px;color:var(--dim);margin-top:2px}
.rs-empty{font-size:12px;color:var(--dim);padding:10px 0}
/* ─── HAMBURGER ─── */
.hamburger-btn{display:none !important}
/* ─── HEADER ─── */
header{display:flex;align-items:center;justify-content:space-between;padding:11px 18px;background:var(--s1);border-bottom:1px solid var(--border);flex-shrink:0;gap:10px}
.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(--warn);background:rgba(251,191,36,.1);padding:1px 8px;border-radius:4px}
/* ─── MESSAGES ─── */
#msgs{flex:1;overflow-y:auto;padding:18px;display:flex;flex-direction:column;gap:14px}
#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;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:14px;padding:9px 14px;resize:none;outline:none;transition:border-color .2s}
#inp:focus{border-color:var(--accent)}
#inp::placeholder{color:var(--dim)}
#sbtn{width:40px;height:40px;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}
#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;width:380px;z-index:200;background:var(--s1);border-left:1px solid var(--border);display:flex;flex-direction:column;transition:right .3s ease;overflow:hidden}
.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}
.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;width:270px;z-index:120;transition:left .3s ease}
#left-sidebar.open{left:0}
.hamburger-btn{display:flex !important}
#right-sidebar{display:none !important}
}
@media(max-width:600px){
.bbl{max-width:90%}
.hr .ib:nth-child(n+3){display:none}
.sp{width:100%}
}
</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="sc-sec">🔒 API 密钥仅本次会话有效,关闭标签页即自动清除,不会持久保存在任何地方</div>
<div class="fg">
<label>选择 AI 提供商</label>
<select id="sp" onchange="onProv(this.value,'s')">
<option value="proxy">🔌 后端代理(国内直连·推荐)</option>
<option value="yunwu">☁️ 云雾 AI团队推荐</option>
<option value="openai">OpenAI (ChatGPT)</option>
<option value="gemini">🔮 Google Gemini</option>
<option value="deepseek">DeepSeek深度求索·国内直连</option>
<option value="moonshot">MoonshotKimi·国内直连</option>
<option value="zhipu">智谱 AIGLM·国内直连</option>
<option value="custom">自定义 / 其他兼容接口</option>
</select>
</div>
<div class="fg" id="sep-g" style="display:none">
<label>API 端点 URL</label>
<input type="text" id="sep" placeholder="https://api.example.com/v1">
</div>
<div class="fg">
<label>模型</label>
<select id="sm"></select>
<input type="text" id="sm-cust" placeholder="输入模型名称,如 gpt-4o、claude-3-5-sonnet-20241022、deepseek-chat" style="display:none;margin-top:6px">
<small id="sm-cust-hint" style="display:none;color:var(--dim);font-size:11px;margin-top:4px">自定义模式:直接输入你的服务商支持的任意模型名称</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="id-section" style="border-color:var(--accent);background:rgba(79,142,247,.05)">
<div class="id-section-title" style="color:var(--accent);font-size:13px">👤 身份识别</div>
<div id="id-preview"></div>
<div class="fg" style="margin-bottom:10px">
<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 class="fg" style="margin-bottom:0">
<label>GitHub 用户名(可选)</label>
<input type="text" id="sghuser" placeholder="your-github-username">
<small>用于人格体记忆绑定,可留空</small>
</div>
</div>
<button class="sc-btn" onclick="doSetup()">开始与铸渊对话 →</button>
<p class="sc-skip">没有 API 密钥?<a onclick="doDemo()">使用演示模式(功能有限)</a></p>
</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">⚠️ 演示模式 · 在 ⚙️ 设置中接入 API 密钥获得真实 AI 回答</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="和铸渊说话… (Enter 发送Shift+Enter 换行)"></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>AI 提供商</label>
<select id="cp" onchange="onProv(this.value,'c')">
<option value="proxy">🔌 后端代理(国内直连·推荐)</option>
<option value="yunwu">☁️ 云雾 AI团队推荐</option>
<option value="openai">OpenAI (ChatGPT)</option>
<option value="gemini">🔮 Google Gemini</option>
<option value="deepseek">DeepSeek深度求索·国内直连</option>
<option value="moonshot">MoonshotKimi·国内直连</option>
<option value="zhipu">智谱 AIGLM·国内直连</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="sfg" id="cep-g" style="display:none">
<label>API 端点</label>
<input type="text" id="cep" placeholder="https://api.example.com/v1">
</div>
<div class="sfg">
<label>模型</label>
<select id="cm"></select>
<input type="text" id="cm-cust" placeholder="输入模型名称,如 gpt-4o、claude-3-5-sonnet-20241022、deepseek-chat" style="display:none;margin-top:6px">
<small id="cm-cust-hint" style="display:none;color:var(--dim);font-size:11px;margin-top:4px">自定义模式:直接输入你的服务商支持的任意模型名称</small>
</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">v4.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. 输入你的 AI 模型 API 密钥并保存<br>
2. 说「<strong style="color:var(--text)">我是冰朔</strong>」触发铸渊唤醒序列<br>
3. 铸渊自动切换 💬 对话 / 🔨 构建 / 📋 审查 / 🧠 大脑 模式<br>
4. 点 ⬇️ 下载单文件,放桌面直接打开使用<br>
5. 支持 OpenAI · DeepSeek · Kimi · 智谱 · 任意兼容接口
</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>(冰朔 / 肥猫 / 桔子 / 开发者名字)</li>
<li data-n="3">填入你的 <strong>API Key</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。填入后选「☁ 云雾 AI团队推荐」提供商即可直接使用。</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'},
};
const PROVS = {
proxy: {base:'/api', mdls:['deepseek-chat','gpt-4o','moonshot-v1-8k','glm-4'],isProxy:true},
yunwu: {base:'https://api.yunwu.ai/v1', mdls:['gpt-4o','gpt-4o-mini','claude-3-5-sonnet-20241022','deepseek-chat','gemini-1.5-pro']},
openai: {base:'https://api.openai.com/v1', mdls:['gpt-4o','gpt-4o-mini','gpt-4-turbo','gpt-3.5-turbo']},
gemini: {base:'https://generativelanguage.googleapis.com/v1beta/openai', mdls:['gemini-2.0-flash','gemini-2.0-flash-lite','gemini-1.5-pro','gemini-1.5-flash']},
deepseek: {base:'https://api.deepseek.com/v1', mdls:['deepseek-chat','deepseek-reasoner']},
moonshot: {base:'https://api.moonshot.cn/v1', mdls:['moonshot-v1-8k','moonshot-v1-32k','moonshot-v1-128k']},
zhipu: {base:'https://open.bigmodel.cn/api/paas/v4', mdls:['glm-4','glm-4-flash','glm-4-air']},
custom: {base:'', mdls:['gpt-4o']},
};
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;
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 _initProv = ls('zy_prov')||'yunwu';
const _initBase = _initProv === 'custom'
? (ls('zy_base')||'')
: (PROVS[_initProv]?.base||'https://api.yunwu.ai/v1');
const A = {
key: sessionStorage.getItem('zy_key')||'',
base: _initBase,
mdl: ls('zy_mdl')||DEFAULT_MDL,
prov: _initProv,
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>';
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=PROVS['yunwu'].base; A.mdl=DEFAULT_MDL; A.prov='yunwu'; 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;
const isProxy = PROVS[A.prov]?.isProxy;
if(A.key||A.demo||isProxy){
showApp();
} else {
if(A.userName) document.getElementById('suid').value=A.userName;
if(A.ghUser) document.getElementById('sghuser').value=A.ghUser;
updateIdPreview();
document.getElementById('setup').style.display='flex';
}
}
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(){
const pv = A.prov||'yunwu';
document.getElementById('sp').value = pv;
const isCustom = pv==='custom';
document.getElementById('sm').style.display = isCustom?'none':'block';
document.getElementById('sm-cust').style.display = isCustom?'block':'none';
document.getElementById('sm-cust-hint').style.display= isCustom?'block':'none';
if(isCustom){
document.getElementById('sm-cust').value = A.mdl||DEFAULT_MDL;
document.getElementById('sep-g').style.display='block';
document.getElementById('sep').value = A.base;
} else {
fillModels('sm', pv, A.mdl);
}
}
function onProv(pv,ctx){
const mdlSel = ctx==='s'?'sm':'cm';
const mdlCust = ctx==='s'?'sm-cust':'cm-cust';
const mdlHint = ctx==='s'?'sm-cust-hint':'cm-cust-hint';
const epGrp = ctx==='s'?'sep-g':'cep-g';
const epInp = ctx==='s'?'sep':'cep';
const keyInp = ctx==='s'?'sk':'ck';
const cur = ctx==='s'? A.mdl : document.getElementById('cm')?.value;
const isCustom = pv==='custom';
const isProxy = PROVS[pv]?.isProxy;
document.getElementById(mdlSel).style.display = isCustom?'none':'block';
document.getElementById(mdlCust).style.display = isCustom?'block':'none';
document.getElementById(mdlHint).style.display = isCustom?'block':'none';
if(isCustom){
const custInp = document.getElementById(mdlCust);
if(custInp && !custInp.value) custInp.value = cur||DEFAULT_MDL;
} else {
fillModels(mdlSel, pv, cur);
}
document.getElementById(epGrp).style.display = isCustom?'block':'none';
if(!isCustom){
const ep = document.getElementById(epInp);
if(ep) ep.value = PROVS[pv]?.base||'';
}
// Proxy mode: API key is optional (managed server-side)
const ki = document.getElementById(keyInp);
if(ki) ki.placeholder = isProxy ? '后端代理模式 — 无需填写 API 密钥(服务器端统一管理)' : (ctx==='s'?'粘贴任意 API 密钥,点击右侧按钮自动识别提供商和模型':'留空 = 保持当前密钥不变;粘贴新密钥即替换');
}
function fillModels(selId, pv, cur){
const sel = document.getElementById(selId);
if(!sel) return;
const mdls = PROVS[pv]?.mdls||[DEFAULT_MDL];
sel.innerHTML = mdls.map(m=>'<option value="'+m+'">'+m+'</option>').join('');
if(cur && mdls.includes(cur)) sel.value=cur;
}
// ═══════════════════════════════════════════════════════
// 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 key = keyEl.value.trim();
if(!key){
statusEl.innerHTML='<span style="color:var(--warn)">⚠️ 请先输入 API 密钥</span>';
return;
}
const candidates = [];
const customBase = (document.getElementById(ctx==='s'?'sep':'cep')?.value||'').trim();
if(customBase) candidates.push({pv:'custom', base:customBase});
for(const [pv, cfg] of Object.entries(PROVS)){
if(pv!=='custom' && cfg.base) candidates.push({pv, base:cfg.base});
}
btnEl.disabled = true;
btnEl.textContent = '⏳ 检测中…';
statusEl.innerHTML = '<span style="color:var(--dim)">🔍 正在自动探测兼容提供商…</span>';
let matched = false;
for(const {pv, base} of candidates){
statusEl.innerHTML = '<span style="color:var(--dim)">🔍 探测 '+(pv === 'custom' ? base : pv)+'…</span>';
try{
const ctrl = new AbortController();
const tid = setTimeout(()=>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(m=>m.id).filter(Boolean).sort();
if(!mdls.length) continue;
const provSel = document.getElementById(ctx==='s'?'sp':'cp');
if(provSel) provSel.value = pv;
onProv(pv, ctx);
const mdlSel = document.getElementById(ctx==='s'?'sm':'cm');
const mdlCust = document.getElementById(ctx==='s'?'sm-cust':'cm-cust');
const mdlHint = document.getElementById(ctx==='s'?'sm-cust-hint':'cm-cust-hint');
if(mdlSel){
mdlSel.innerHTML = mdls.map(m=>'<option value="'+m+'">'+m+'</option>').join('');
mdlSel.style.display = 'block';
}
if(mdlCust) mdlCust.style.display='none';
if(mdlHint) mdlHint.style.display='none';
if(pv==='custom' && ctx==='s'){
document.getElementById('sep-g').style.display='block';
document.getElementById('sep').value = customBase;
} else if(pv==='custom' && ctx==='c'){
document.getElementById('cep-g').style.display='block';
document.getElementById('cep').value = customBase;
}
const label = PROVS[pv]?.base ? pv : customBase;
statusEl.innerHTML = '<span style="color:var(--ok)">✅ 检测成功('+label+')· 共发现 <strong>'+mdls.length+'</strong> 个可用模型</span>';
matched = true;
break;
}
}catch(e){ console.debug('autoDetect probe failed:', pv, e.message); }
}
if(!matched){
statusEl.innerHTML = '<span style="color:var(--err)">❌ 未能自动匹配,请手动选择提供商和模型后保存</span>';
}
btnEl.disabled = false;
btnEl.textContent = '🔍 自动检测';
}
function doSetup(){
const pv = document.getElementById('sp').value;
const isProxy = PROVS[pv]?.isProxy;
const k = document.getElementById('sk').value.trim();
const errEl = document.getElementById('sk-err');
// Proxy mode doesn't require an API key
if(!k && !isProxy){errEl.style.display='block';return;}
errEl.style.display='none';
const md = pv==='custom'
? ((document.getElementById('sm-cust')?.value||'').trim()||DEFAULT_MDL)
: document.getElementById('sm').value;
let base = PROVS[pv]?.base||'';
if(pv==='custom') base=(document.getElementById('sep')?.value||'').trim()||base;
A.key=k; A.base=base; A.mdl=md; A.prov=pv; A.demo=false;
if(k) sessionStorage.setItem('zy_key', k);
lss('zy_base',base); lss('zy_mdl',md); lss('zy_prov',pv);
const un = document.getElementById('suid').value;
const gh = (document.getElementById('sghuser')?.value||'').trim();
setIdentity(un, gh);
showApp();
}
function doDemo(){
A.demo=true; A.key='';
const un = document.getElementById('suid').value;
const gh = (document.getElementById('sghuser')?.value||'').trim();
setIdentity(un, gh);
showApp();
}
// ═══════════════════════════════════════════════════════
// SETTINGS PANEL
// ═══════════════════════════════════════════════════════
function initSettingsPanel(){
const pv = A.prov||'yunwu';
document.getElementById('cp').value = pv;
const isCustom = pv==='custom';
document.getElementById('cm').style.display = isCustom?'none':'block';
document.getElementById('cm-cust').style.display = isCustom?'block':'none';
document.getElementById('cm-cust-hint').style.display= isCustom?'block':'none';
if(isCustom){
document.getElementById('cm-cust').value = A.mdl||DEFAULT_MDL;
document.getElementById('cep-g').style.display='block';
document.getElementById('cep').value = A.base;
} else {
fillModels('cm', pv, 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 pv = document.getElementById('cp').value;
const md = pv==='custom'
? ((document.getElementById('cm-cust')?.value||'').trim()||A.mdl||DEFAULT_MDL)
: document.getElementById('cm').value;
const raw = document.getElementById('ck').value.trim();
const k = raw || A.key;
let base = PROVS[pv]?.base||'';
if(pv==='custom') base=(document.getElementById('cep').value.trim())||base;
A.key=k; A.base=base; A.mdl=md; A.prov=pv; A.demo=!k;
if(k){ sessionStorage.setItem('zy_key', k); } else { sessionStorage.removeItem('zy_key'); }
lss('zy_base',base); lss('zy_mdl',md); lss('zy_prov',pv);
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## 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 直连模式)';
}
// ═══════════════════════════════════════════════════════
// 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]);
}
// ═══════════════════════════════════════════════════════
// 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) 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 isProxy = PROVS[A.prov]?.isProxy;
const url = isProxy ? (A.base+'/chat') : (A.base+'/chat/completions');
const headers = {'Content-Type':'application/json'};
if(!isProxy && A.key) headers['Authorization'] = 'Bearer '+A.key;
const reqBody = isProxy
? {provider:A.prov==='proxy'?undefined:A.prov, model:A.mdl, messages:[{role:'system',content:sysPrompt()},...A.msgs], stream:true, temperature:0.8, max_tokens:2000, system:sysPrompt()}
: {model:A.mdl, messages:[{role:'system',content:sysPrompt()},...A.msgs], stream:true, temperature:0.8, max_tokens:2000};
const res=await fetch(url,{
method:'POST',
headers:headers,
body:JSON.stringify(reqBody),
});
if(!res.ok){
const provLabel = A.prov==='custom' ? A.base : (A.prov+'('+A.base+')');
let em='['+provLabel+'] 返回错误 '+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();
}
}
// ═══════════════════════════════════════════════════════
// 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 '我收到了你的消息。由于当前是演示模式,无法给出真实 AI 回答。<br><br>请在 <strong>⚙️ 设置</strong> 中输入你的 API 密钥(支持 OpenAI / DeepSeek / Kimi / 智谱),然后你就可以得到铸渊真正的回答了。';
}
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';
}
}
});
// ═══════════════════════════════════════════════════════
// BOOT
// ═══════════════════════════════════════════════════════
boot();
</script>
</body>
</html>