2026-04-02 20:07:12 +08:00
<!DOCTYPE html>
< 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 name = "description" content = "光湖灯塔 · 语言驱动操作系统 AGE OS" >
< meta name = "theme-color" content = "#080c18" >
< meta name = "apple-mobile-web-app-capable" content = "yes" >
< meta name = "apple-mobile-web-app-status-bar-style" content = "black-translucent" >
< title > 光湖灯塔 · AGE OS< / title >
< link rel = "icon" href = "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2280%22>🏛️</text></svg>" >
< style >
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
/* ═══ Themes ═══ */
:root{
--bg:#060a14;--bg1:#0c1220;--bg2:#111827;--bg3:#1a2332;
--border:#1e2d44;--accent:#4f8ef7;--accent2:#7c6ef7;--accent3:#a78bfa;
--ok:#34d399;--warn:#fbbf24;--err:#f87171;
--text:#e2e8f0;--dim:#64748b;--muted:#334155;
--glow:0 0 24px rgba(79,142,247,.1);
--r:12px;--rs:8px;
--f:-apple-system,BlinkMacSystemFont,'PingFang SC','Helvetica Neue','Microsoft YaHei',sans-serif;
--m:'SF Mono','Fira Code',Consolas,monospace;
--sb:env(safe-area-inset-bottom,0px);--st:env(safe-area-inset-top,0px);
}
.t-twi{--bg:#0a0612;--bg1:#140e22;--bg2:#1a1430;--bg3:#241c3d;--accent:#c084fc;--accent2:#e879f9;--accent3:#f0abfc;--border:#2d1f4e}
.t-sea{--bg:#041318;--bg1:#082029;--bg2:#0c2d3a;--bg3:#103a4c;--accent:#22d3ee;--accent2:#06b6d4;--accent3:#67e8f9;--border:#164e63}
.t-for{--bg:#06120a;--bg1:#0c2014;--bg2:#112d1c;--bg3:#183a26;--accent:#34d399;--accent2:#4ade80;--accent3:#86efac;--border:#1a4a2e}
.t-emb{--bg:#140a06;--bg1:#221208;--bg2:#2d1a0e;--bg3:#3a2414;--accent:#fb923c;--accent2:#f97316;--accent3:#fdba74;--border:#4a2e1a}
html{-webkit-tap-highlight-color:transparent}
body{font-family:var(--f);background:var(--bg);color:var(--text);font-size:15px;line-height:1.6;overflow:hidden;height:100vh;height:100dvh;-webkit-font-smoothing:antialiased}
a{color:var(--accent);text-decoration:none}
::-webkit-scrollbar{width:4px}::-webkit-scrollbar-thumb{background:var(--muted);border-radius:2px}
/* ═══ Background ═══ */
.bg{position:fixed;inset:0;z-index:0;pointer-events:none}
.bg::before{content:'';position:absolute;inset:0;background-image:linear-gradient(rgba(79,142,247,.02) 1px,transparent 1px),linear-gradient(90deg,rgba(79,142,247,.02) 1px,transparent 1px);background-size:50px 50px;animation:gm 30s linear infinite}
@keyframes gm{to{background-position:50px 50px}}
.orb{position:absolute;border-radius:50%;filter:blur(80px);opacity:.07;animation:ob 22s ease-in-out infinite}
.orb:nth-child(1){width:350px;height:350px;background:var(--accent);top:-8%;left:-4%}
.orb:nth-child(2){width:280px;height:280px;background:var(--accent2);bottom:-6%;right:-4%;animation-delay:8s}
@keyframes ob{0%,100%{transform:translate(0,0)}50%{transform:translate(20px,-15px)}}
/* ═══ 3D Page Flip Engine ═══ */
.pages{position:fixed;inset:0;z-index:1;perspective:1400px;overflow:hidden}
.page{
position:absolute;inset:0;
overflow-y:auto;overflow-x:hidden;
-webkit-overflow-scrolling:touch;
padding:0 0 var(--sb) 0;
background:var(--bg);
backface-visibility:hidden;-webkit-backface-visibility:hidden;
transition:transform .55s cubic-bezier(.4,0,.15,1),opacity .55s;
transform-origin:center center;
will-change:transform,opacity;
}
.page[data-state="active"]{transform:rotateY(0deg) scale(1);opacity:1;z-index:2;pointer-events:auto}
.page[data-state="left"]{transform:rotateY(-92deg) scale(.92);opacity:0;z-index:1;pointer-events:none}
.page[data-state="right"]{transform:rotateY(92deg) scale(.92);opacity:0;z-index:1;pointer-events:none}
.page[data-state="hidden"]{transform:rotateY(92deg) scale(.88);opacity:0;z-index:0;pointer-events:none;transition:none}
/* ═══ Common UI ═══ */
.bar{position:sticky;top:0;z-index:10;display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:rgba(6,10,20,.92);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-bottom:1px solid var(--border);min-height:48px;padding-top:calc(10px + var(--st))}
.bar-title{font-size:14px;font-weight:700;display:flex;align-items:center;gap:6px}
.bar-right{display:flex;align-items:center;gap:8px}
.dot{width:6px;height:6px;border-radius:50%;background:var(--ok);box-shadow:0 0 6px var(--ok);animation:pls 2s infinite;flex-shrink:0}
@keyframes pls{50%{opacity:.4}}
.btn{background:none;border:1px solid var(--border);color:var(--dim);padding:5px 12px;border-radius:var(--rs);font-size:12px;cursor:pointer;font-family:var(--f);transition:all .2s;white-space:nowrap}
.btn:hover{color:var(--accent);border-color:var(--accent)}
.btn:active{transform:scale(.96)}
.btn-accent{background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff}
.btn-accent:hover{opacity:.88;color:#fff}
.content{padding:16px;max-width:700px;margin:0 auto}
.card{background:var(--bg1);border:1px solid var(--border);border-radius:var(--r);padding:16px;margin-bottom:12px;transition:border-color .3s}
.card:hover{border-color:var(--accent)}
.tag{display:inline-block;padding:2px 8px;border-radius:20px;font-size:10px;font-weight:600}
.tag-ok{background:rgba(52,211,153,.12);color:var(--ok)}
.tag-warn{background:rgba(251,191,36,.12);color:var(--warn)}
.tag-info{background:rgba(79,142,247,.12);color:var(--accent)}
.empty{text-align:center;padding:40px 16px;color:var(--muted);font-size:14px}
.empty em{font-style:normal;font-size:40px;display:block;margin-bottom:12px}
input[type=text],textarea{background:var(--bg2);border:1px solid var(--border);border-radius:var(--rs);color:var(--text);padding:10px 12px;font-size:14px;font-family:var(--f);outline:none;width:100%;transition:border-color .2s;-webkit-appearance:none}
input[type=text]:focus,textarea:focus{border-color:var(--accent)}
::placeholder{color:var(--muted)}
label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weight:600}
.form-row{margin-bottom:14px}
.form-row-inline{display:flex;gap:10px}
@media(max-width:480px){.form-row-inline{flex-direction:column}}
/* ═══ PAGE: Home ═══ */
.home-hero{text-align:center;padding:32px 16px}
.home-ver{display:inline-flex;align-items:center;gap:6px;padding:4px 12px;border-radius:20px;background:rgba(79,142,247,.08);border:1px solid rgba(79,142,247,.12);font-size:11px;color:var(--accent);font-family:var(--m);margin-bottom:16px}
.home-hero h1{font-size:clamp(26px,5.5vw,40px);font-weight:800;line-height:1.2;margin-bottom:6px}
.home-hero .sub{font-size:clamp(12px,2.5vw,15px);color:var(--dim);margin-bottom:12px}
.home-hero .quote{font-size:13px;color:var(--accent2);font-style:italic}
.upd{display:flex;gap:10px;padding:12px;margin-bottom:8px;background:var(--bg1);border:1px solid var(--border);border-radius:var(--r);transition:border-color .3s}
.upd:hover{border-color:var(--accent)}
.upd-v{min-width:42px;padding:3px 6px;text-align:center;background:rgba(79,142,247,.08);border-radius:var(--rs);color:var(--accent);font-size:10px;font-weight:600;font-family:var(--m);height:fit-content;flex-shrink:0}
.upd h4{font-size:13px;font-weight:600;margin-bottom:2px}
.upd p{font-size:11px;color:var(--dim);line-height:1.5}
.home-enter{padding:24px;text-align:center;background:linear-gradient(135deg,var(--bg1),var(--bg2));border:1px solid var(--border);border-radius:var(--r);cursor:pointer;transition:all .3s;margin:20px 0}
.home-enter:hover{border-color:var(--accent);box-shadow:var(--glow)}
.home-enter:active{transform:scale(.98)}
.home-enter em{font-style:normal;font-size:36px;display:block;margin-bottom:8px}
.home-enter strong{font-size:17px;display:block;margin-bottom:4px}
.home-enter span{font-size:12px;color:var(--dim)}
.home-footer{text-align:center;padding:24px 0;font-size:11px;color:var(--muted);border-top:1px solid var(--border);margin-top:24px}
.home-footer .motto{color:var(--accent2);font-style:italic;margin-top:4px}
/* ═══ PAGE: Desk (作品列表) ═══ */
.work-item{cursor:pointer}
.work-item h3{font-size:15px;font-weight:600;margin-bottom:4px}
.work-item .meta{font-size:11px;color:var(--dim);display:flex;gap:10px;flex-wrap:wrap}
.work-item .meta span{display:flex;align-items:center;gap:3px}
/* ═══ PAGE: Work Hub (作品导航) ═══ */
.hub-title{text-align:center;padding:20px 0 12px}
.hub-title h2{font-size:20px;font-weight:700}
.hub-title p{font-size:12px;color:var(--dim);margin-top:2px}
.hub-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
@media(max-width:360px){.hub-grid{grid-template-columns:1fr}}
.hub-card{text-align:center;padding:20px 12px;background:var(--bg1);border:1px solid var(--border);border-radius:var(--r);cursor:pointer;transition:all .3s}
.hub-card:hover{border-color:var(--accent);box-shadow:var(--glow)}
.hub-card:active{transform:scale(.97)}
.hub-card em{font-style:normal;font-size:28px;display:block;margin-bottom:6px}
.hub-card strong{font-size:14px;display:block;margin-bottom:3px}
.hub-card span{font-size:11px;color:var(--dim)}
.hub-stats{display:flex;justify-content:center;gap:20px;padding:12px 0;flex-wrap:wrap}
.hub-stat{text-align:center}
.hub-stat b{display:block;font-size:18px;color:var(--accent);font-family:var(--m)}
.hub-stat small{font-size:10px;color:var(--dim)}
/* ═══ PAGE: Outline ═══ */
.outline-editor{min-height:250px;resize:vertical;line-height:1.9;font-size:14px}
.vol-item{display:flex;align-items:center;justify-content:space-between;padding:10px;background:var(--bg2);border-radius:var(--rs);margin-bottom:6px}
.vol-item span{font-size:13px;font-weight:600}
.vol-actions{display:flex;gap:4px}
/* ═══ PAGE: Characters ═══ */
.char-card{display:flex;gap:12px;align-items:flex-start}
.char-avatar{width:42px;height:42px;border-radius:50%;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
.char-info h4{font-size:14px;font-weight:600;margin-bottom:2px}
.char-info p{font-size:12px;color:var(--dim);line-height:1.5}
.char-role{font-size:10px;color:var(--accent);font-weight:600}
/* ═══ PAGE: Editor ═══ */
.editor-info{display:flex;justify-content:space-between;align-items:center;padding:8px 0;font-size:12px;color:var(--dim);border-bottom:1px solid var(--border);margin-bottom:12px}
.editor-textarea{min-height:calc(100vh - 260px);min-height:calc(100dvh - 260px);resize:none;line-height:2;font-size:15px;border:none;background:transparent;padding:0}
.editor-textarea:focus{border:none}
.editor-bottom{position:sticky;bottom:0;padding:8px 0;background:var(--bg);display:flex;justify-content:space-between;align-items:center;border-top:1px solid var(--border);font-size:11px;color:var(--dim);font-family:var(--m)}
/* ═══ Chat FAB ═══ */
.chat-fab{position:fixed;bottom:calc(16px + var(--sb));right:14px;z-index:50;width:50px;height:50px;border-radius:50%;background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff;font-size:21px;cursor:pointer;box-shadow:0 4px 16px rgba(79,142,247,.3);transition:all .25s;display:flex;align-items:center;justify-content:center}
.chat-fab:hover{transform:scale(1.06)}
.chat-fab:active{transform:scale(.93)}
.chat-fab.on{border-radius:var(--r);background:rgba(248,113,113,.85)}
/* 编辑器模式下缩小悬浮球,不干扰打字 */
.chat-fab.editor-mode{width:38px;height:38px;font-size:16px;opacity:.6;bottom:calc(8px + var(--sb))}
.chat-fab.editor-mode:hover{opacity:1;transform:scale(1.1)}
/* ═══ Chat Panel ═══ */
.chat-p{position:fixed;bottom:calc(76px + var(--sb));right:14px;z-index:50;width:min(360px,calc(100vw - 28px));height:min(440px,calc(100vh - 120px));height:min(440px,calc(100dvh - 120px));background:var(--bg1);border:1px solid var(--border);border-radius:var(--r);display:flex;flex-direction:column;transform:scale(.9) translateY(14px);opacity:0;pointer-events:none;transition:all .22s cubic-bezier(.4,0,.2,1);box-shadow:0 12px 40px rgba(0,0,0,.5)}
.chat-p.open{transform:scale(1) translateY(0);opacity:1;pointer-events:auto}
@media(max-width:480px){.chat-p{bottom:0;right:0;left:0;width:100%;height:calc(100vh - 48px);height:calc(100dvh - 48px);border-radius:var(--r) var(--r) 0 0;padding-bottom:var(--sb)}}
.chat-hd{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;flex-shrink:0}
.chat-av{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
.chat-nm{font-weight:600;font-size:13px}
.chat-sb{font-size:10px;color:var(--dim)}
.chat-tg{margin-left:auto;font-size:9px;padding:2px 6px;background:var(--bg2);border:1px solid var(--border);border-radius:20px;color:var(--dim);font-family:var(--m)}
.chat-ms{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:8px;-webkit-overflow-scrolling:touch}
.cm{max-width:82%;padding:8px 12px;border-radius:var(--r);font-size:13px;line-height:1.6;word-break:break-word}
.cm.u{align-self:flex-end;background:var(--accent);color:#fff;border-bottom-right-radius:4px}
.cm.b{align-self:flex-start;background:var(--bg2);border:1px solid var(--border);border-bottom-left-radius:4px}
.cm.s{align-self:center;background:none;color:var(--muted);font-size:10px;padding:2px}
.cm-m{display:flex;gap:5px;margin-top:4px;font-size:9px;color:var(--muted)}
.ct{font-size:11px;color:var(--dim);padding:4px 12px}
.ct span{animation:bk 1.4s infinite;font-size:14px}
.ct span:nth-child(2){animation-delay:.2s}
.ct span:nth-child(3){animation-delay:.4s}
@keyframes bk{0%,100%{opacity:.2}50%{opacity:1}}
.chat-in{padding:10px;border-top:1px solid var(--border);display:flex;gap:6px;flex-shrink:0}
.cin{flex:1;padding:8px 10px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);color:var(--text);font-size:14px;outline:none;font-family:var(--f);-webkit-appearance:none}
.cin:focus{border-color:var(--accent)}
.csnd{width:36px;height:36px;border-radius:50%;background:var(--accent);border:none;color:#fff;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .2s}
.csnd:hover{background:var(--accent2)}
.csnd:disabled{opacity:.3}
/* ═══ Monitor ═══ */
.mon{position:fixed;top:calc(6px + var(--st));right:10px;z-index:40;background:rgba(12,18,32,.9);border:1px solid var(--border);border-radius:var(--r);padding:5px 9px;font-size:9px;font-family:var(--m);color:var(--dim);cursor:pointer;transition:all .3s;user-select:none;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
.mon:hover{border-color:var(--accent)}
.mon.x{padding:8px 12px}
.mr{display:flex;justify-content:space-between;gap:8px;padding:1px 0}
.mv{color:var(--accent);font-weight:600}
.mx{display:none;margin-top:4px;padding-top:4px;border-top:1px solid var(--border)}
.mon.x .mx{display:block}
< / style >
< / head >
< body >
< div class = "bg" > < div class = "orb" > < / div > < div class = "orb" > < / div > < / div >
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Home (灯塔首页) -->
<!-- ═══════════════════════════════════════ -->
< div class = "pages" id = "pages" >
< div class = "page" id = "pg-home" data-state = "active" >
< div class = "bar" >
< div class = "bar-title" > < span style = "font-size:20px" > 🏛️< / span > 光湖灯塔< / div >
< div class = "bar-right" > < span class = "dot" > < / span > < span style = "font-size:11px;color:var(--dim)" > 在线< / span > < button class = "btn" id = "themeBtn" > 🎨< / button > < / div >
< / div >
< div class = "content" >
< div class = "home-hero" >
< div class = "home-ver" > AGE OS v40.0< / div >
< h1 > 光湖灯塔< / h1 >
< div class = "sub" > 语言驱动操作系统 · 人类只说话,系统搞定一切< / div >
< div class = "quote" > 「语言 = 现实」< / div >
< / div >
< div style = "font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:1.5px;font-weight:600;margin-bottom:10px" > 📡 系统更新< / div >
< div class = "upd" > < div class = "upd-v" > v40.0< / div > < div > < h4 > COS双桶存储 · 智能模型分流< / h4 > < p > 人格体数据库 + 语料库 · API智能选模型< / p > < / div > < / div >
< div class = "upd" > < div class = "upd-v" > v39.0< / div > < div > < h4 > 全链路部署观测< / h4 > < p > 日志采集 · 自动修复 · LLM诊断< / p > < / div > < / div >
< div class = "upd" > < div class = "upd-v" > v38.0< / div > < div > < h4 > HLDP协作语言协议< / h4 > < p > Notion↔GitHub双侧通信 · 铸渊方言< / p > < / div > < / div >
< div class = "home-enter" onclick = "go('desk')" >
< em > ✍️< / em >
< strong > 码字工作台< / strong >
< span > 网文行业 · AI辅助创作 · 语言即命令< / span >
< div style = "margin-top:8px" > < span class = "tag tag-warn" > Beta 内测< / span > < / div >
< / div >
< div class = "home-footer" >
< p > © 2026 光湖语言世界 · 国作登字-2026-A-00037559< / p >
< p class = "motto" > 「人类只说话,系统搞定一切」< / p >
< / div >
< / div >
< / div >
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Desk (码字工作台 · 作品列表) -->
<!-- ═══════════════════════════════════════ -->
< div class = "page" id = "pg-desk" data-state = "hidden" >
< div class = "bar" >
< button class = "btn" onclick = "go('home')" > ← 灯塔< / button >
< div class = "bar-title" > ✍️ 码字工作台< / div >
< button class = "btn btn-accent" onclick = "go('create')" > + 新作品< / button >
< / div >
< div class = "content" >
< div id = "workList" > < / div >
< / div >
< / div >
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Create (新建作品) -->
<!-- ═══════════════════════════════════════ -->
< div class = "page" id = "pg-create" data-state = "hidden" >
< div class = "bar" >
< button class = "btn" onclick = "go('desk')" > ← 返回< / button >
< div class = "bar-title" > 📖 新建作品< / div >
< button class = "btn btn-accent" id = "createSave" > 创建< / button >
< / div >
< div class = "content" >
< div class = "form-row" > < label > 作品名称< / label > < input type = "text" id = "cTitle" placeholder = "你的作品叫什么名字?" > < / div >
< div class = "form-row" > < label > 类型< / label > < input type = "text" id = "cGenre" placeholder = "玄幻 / 都市 / 言情 / 科幻 ..." > < / div >
< div class = "form-row" > < label > 简介< / label > < textarea id = "cSummary" rows = "4" placeholder = "一句话介绍你的故事..." > < / textarea > < / div >
< / div >
< / div >
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Hub (作品导航中心) -->
<!-- ═══════════════════════════════════════ -->
< div class = "page" id = "pg-hub" data-state = "hidden" >
< div class = "bar" >
< button class = "btn" onclick = "go('desk')" > ← 作品列表< / button >
< div class = "bar-title" id = "hubTitle" > 作品< / div >
< div class = "bar-right" > < span class = "dot" > < / span > < / div >
< / div >
< div class = "content" >
< div class = "hub-title" >
< h2 id = "hubName" > —< / h2 >
< p id = "hubGenre" > —< / p >
< / div >
< div class = "hub-stats" id = "hubStats" > < / div >
< div class = "hub-grid" >
< div class = "hub-card" onclick = "go('outline')" > < em > 📋< / em > < strong > 大纲< / strong > < span > 总纲 · 卷纲< / span > < / div >
< div class = "hub-card" onclick = "go('chars')" > < em > 👥< / em > < strong > 人物卡< / strong > < span > 角色管理< / span > < / div >
< div class = "hub-card" onclick = "go('chapters')" > < em > 📚< / em > < strong > 章节< / strong > < span > 目录管理< / span > < / div >
< div class = "hub-card" onclick = "go('editor')" > < em > ✏️< / em > < strong > 写正文< / strong > < span > 续写章节< / span > < / div >
< / div >
< div style = "margin-top:16px;text-align:center;font-size:11px;color:var(--muted);font-style:italic" > 对铸渊说「进入大纲」「人物卡」「写正文」自动翻转< / div >
< / div >
< / div >
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Outline (大纲) -->
<!-- ═══════════════════════════════════════ -->
< div class = "page" id = "pg-outline" data-state = "hidden" >
< div class = "bar" >
< button class = "btn" onclick = "go('hub')" > ← 返回< / button >
< div class = "bar-title" > 📋 大纲< / div >
< button class = "btn btn-accent" id = "outlineSave" > 保存< / button >
< / div >
< div class = "content" >
< div class = "form-row" > < label > 总大纲< / label > < textarea id = "olMain" class = "outline-editor" placeholder = "在这里写下你的故事总纲... 整体世界观、主线剧情、结局走向..." > < / textarea > < / div >
< div style = "display:flex;justify-content:space-between;align-items:center;margin:16px 0 8px" >
< label style = "margin:0" > 分卷大纲< / label >
< button class = "btn" id = "addVol" > + 新卷< / button >
< / div >
< div id = "volList" > < / div >
< / div >
< / div >
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Characters (人物卡) -->
<!-- ═══════════════════════════════════════ -->
< div class = "page" id = "pg-chars" data-state = "hidden" >
< div class = "bar" >
< button class = "btn" onclick = "go('hub')" > ← 返回< / button >
< div class = "bar-title" > 👥 人物卡< / div >
< button class = "btn btn-accent" id = "addChar" > + 新角色< / button >
< / div >
< div class = "content" >
< div id = "charList" > < / div >
< / div >
< / div >
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Add/Edit Character -->
<!-- ═══════════════════════════════════════ -->
< div class = "page" id = "pg-charEdit" data-state = "hidden" >
< div class = "bar" >
< button class = "btn" onclick = "go('chars')" > ← 返回< / button >
< div class = "bar-title" id = "ceTitle" > 新角色< / div >
< button class = "btn btn-accent" id = "ceSave" > 保存< / button >
< / div >
< div class = "content" >
< div class = "form-row" > < label > 姓名< / label > < input type = "text" id = "ceName" placeholder = "角色名" > < / div >
< div class = "form-row" > < label > 身份/定位< / label > < input type = "text" id = "ceRole" placeholder = "主角 / 女主 / 反派 / 配角 ..." > < / div >
< div class = "form-row" > < label > 外貌特征< / label > < textarea id = "ceAppearance" rows = "3" placeholder = "外貌描写..." > < / textarea > < / div >
< div class = "form-row" > < label > 性格特点< / label > < textarea id = "ceTraits" rows = "3" placeholder = "性格、习惯、口头禅..." > < / textarea > < / div >
< div class = "form-row" > < label > 背景故事< / label > < textarea id = "ceBackground" rows = "4" placeholder = "角色的过去、动机、秘密..." > < / textarea > < / div >
< / div >
< / div >
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Chapters (章节管理) -->
<!-- ═══════════════════════════════════════ -->
< div class = "page" id = "pg-chapters" data-state = "hidden" >
< div class = "bar" >
< button class = "btn" onclick = "go('hub')" > ← 返回< / button >
< div class = "bar-title" > 📚 章节< / div >
< button class = "btn btn-accent" id = "addChapter" > + 新章节< / button >
< / div >
< div class = "content" >
< div id = "chapterList" > < / div >
< / div >
< / div >
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Editor (正文编辑) -->
<!-- ═══════════════════════════════════════ -->
< div class = "page" id = "pg-editor" data-state = "hidden" >
< div class = "bar" >
< button class = "btn" onclick = "go('chapters')" > ← 返回< / button >
< div class = "bar-title" id = "edTitle" > 正文< / div >
< button class = "btn btn-accent" id = "edSave" > 保存< / button >
< / div >
< div class = "content" >
< div class = "editor-info" >
< span id = "edChTitle" > —< / span >
< span id = "edWc" > 0 字< / span >
< / div >
< textarea class = "editor-textarea" id = "edText" placeholder = "开始写作... 让文字流淌。" > < / textarea >
< div class = "editor-bottom" >
< span id = "edWc2" > 字数: 0< / span >
< span style = "font-size:10px;color:var(--muted)" > 自动保存 · 点右下角💬唤醒铸渊< / span >
< / div >
< / div >
< / div >
< / div > <!-- /pages -->
<!-- Chat FAB -->
< button class = "chat-fab" id = "chatFab" > 💬< / button >
<!-- Chat Panel -->
< div class = "chat-p" id = "chatP" >
< div class = "chat-hd" >
< div class = "chat-av" > 🏛️< / div >
< div > < div class = "chat-nm" > 铸渊< / div > < div class = "chat-sb" > 光湖灯塔 · 守护者< / div > < / div >
< div class = "chat-tg" id = "chatTg" > ready< / div >
< / div >
< div class = "chat-ms" id = "chatMs" >
< div class = "cm s" > —— 与铸渊对话 ——< / div >
< div class = "cm b" > 你好!我是铸渊 🏛️< br > < br > 告诉我你是谁,我会记住你。< br > 说「我要码字」进入码字工作台。< br > 进入作品后,说「大纲」「人物卡」「写正文」页面会翻转切换。< div class = "cm-m" > < span > 铸渊< / span > < span > ·< / span > < span > 本地< / span > < / div > < / div >
< / div >
< div class = "chat-in" >
< input type = "text" class = "cin" id = "chatIn" placeholder = "对铸渊说..." autocomplete = "off" enterkeyhint = "send" >
< button class = "csnd" id = "chatSnd" > ↑< / button >
< / div >
< / div >
<!-- Monitor -->
< div class = "mon" id = "mon" >
< div class = "mr" > < span > API< / span > < span class = "mv" id = "mC" > 0< / span > < / div >
< div class = "mx" >
< div class = "mr" > < span > 连接< / span > < span class = "mv" id = "mN" style = "color:var(--ok)" > —< / span > < / div >
< div class = "mr" > < span > 模型< / span > < span class = "mv" id = "mM" > —< / span > < / div >
< / div >
< / div >
< script >
(function(){
'use strict';
/* ════════════════════════════════════════
3D PAGE FLIP ENGINE
════════════════════════════════════════ */
var currentPage='home';
var pageHistory=['home'];
function go(target){
if(target===currentPage)return;
var cur=document.getElementById('pg-'+currentPage);
var nxt=document.getElementById('pg-'+target);
if(!cur||!nxt)return;
// Determine direction
var goingBack=pageHistory.length>1&&pageHistory[pageHistory.length-2]===target;
// Set intermediate states
if(goingBack){
cur.setAttribute('data-state','right');
nxt.setAttribute('data-state','left');
}else{
cur.setAttribute('data-state','left');
nxt.setAttribute('data-state','right');
}
// Force reflow
void nxt.offsetWidth;
// Animate in
requestAnimationFrame(function(){
nxt.setAttribute('data-state','active');
});
// Update history
if(goingBack){
pageHistory.pop();
}else{
pageHistory.push(target);
}
// Hide old page fully after transition
var oldPage=currentPage;
setTimeout(function(){
var el=document.getElementById('pg-'+oldPage);
if(el& & el.getAttribute('data-state')!=='active'){
el.setAttribute('data-state','hidden');
el.scrollTop=0;
}
},600);
currentPage=target;
// Editor mode: shrink chat fab to not disturb writing
var fab=document.getElementById('chatFab');
if(target==='editor'){fab.classList.add('editor-mode')}
else{fab.classList.remove('editor-mode')}
// Trigger page-specific renders
if(target==='desk')renderWorkList();
if(target==='hub')renderHub();
if(target==='outline')renderOutline();
if(target==='chars')renderChars();
if(target==='chapters')renderChapters();
if(target==='editor')renderEditor();
2026-04-02 20:11:11 +08:00
if(target==='charEdit'&&editingCharId)renderCharEdit();
2026-04-02 20:07:12 +08:00
}
window.go=go;
/* ════════════════════════════════════════
DATA LAYER (localStorage)
════════════════════════════════════════ */
var DB_KEY='hl-works';
var activeWorkId=null;
var activeChapterId=null;
var editingCharId=null;
function loadWorks(){
try{var d=localStorage.getItem(DB_KEY);return d?JSON.parse(d):[]}catch(e){return[]}
}
function saveWorks(w){try{localStorage.setItem(DB_KEY,JSON.stringify(w))}catch(e){}; syncToCOS(w)}
function getWork(id){return loadWorks().find(function(w){return w.id===id})||null}
function updateWork(id,fn){
var ws=loadWorks();
var idx=ws.findIndex(function(w){return w.id===id});
if(idx===-1)return;
fn(ws[idx]);
ws[idx].updated=new Date().toISOString().slice(0,10);
saveWorks(ws);
}
2026-04-02 20:14:42 +08:00
function genId(){var a=new Uint8Array(6);crypto.getRandomValues(a);return Date.now().toString(36)+Array.from(a,function(b){return b.toString(36)}).join('').substr(0,4)}
2026-04-02 20:07:12 +08:00
function wordCount(s){return s?s.replace(/\s/g,'').length:0}
function totalWords(w){
var c=0;
if(w.chapters)w.chapters.forEach(function(ch){c+=wordCount(ch.content)});
return c;
}
/* ════════════════════════════════════════
RENDER: Work List
════════════════════════════════════════ */
function renderWorkList(){
var ws=loadWorks();
var el=document.getElementById('workList');
if(ws.length===0){
el.innerHTML='< div class = "empty" > < em > 📖< / em > 还没有作品< br > < br > 点击右上角「+ 新作品」< br > 或对铸渊说「新建作品」< / div > ';
return;
}
el.innerHTML=ws.map(function(w){
var wc=totalWords(w);
var cc=w.chapters?w.chapters.length:0;
var chc=w.characters?w.characters.length:0;
2026-04-02 20:14:42 +08:00
return '< div class = "card work-item" data-wid = "'+esc(w.id)+'" > '+
2026-04-02 20:07:12 +08:00
'< h3 > '+esc(w.title)+'< / h3 > '+
'< div class = "meta" > '+
'< span > '+esc(w.genre||'未分类')+'< / span > '+
'< span > 📝 '+wc+' 字< / span > '+
'< span > 📚 '+cc+' 章< / span > '+
'< span > 👥 '+chc+' 角色< / span > '+
'< / div > < / div > ';
}).join('');
2026-04-02 20:14:42 +08:00
el.querySelectorAll('.work-item').forEach(function(item){
item.onclick=function(){activeWorkId=this.getAttribute('data-wid');go('hub')};
});
2026-04-02 20:07:12 +08:00
}
/* ════════════════════════════════════════
CREATE WORK
════════════════════════════════════════ */
document.getElementById('createSave').onclick=function(){
var t=document.getElementById('cTitle').value.trim();
if(!t){document.getElementById('cTitle').focus();return}
var ws=loadWorks();
var nw={
id:genId(),
title:t,
genre:document.getElementById('cGenre').value.trim()||'',
summary:document.getElementById('cSummary').value.trim()||'',
created:new Date().toISOString().slice(0,10),
updated:new Date().toISOString().slice(0,10),
outline:{main:'',volumes:[]},
characters:[],
chapters:[]
};
ws.push(nw);
saveWorks(ws);
document.getElementById('cTitle').value='';
document.getElementById('cGenre').value='';
document.getElementById('cSummary').value='';
activeWorkId=nw.id;
go('hub');
};
/* ════════════════════════════════════════
RENDER: Hub
════════════════════════════════════════ */
function renderHub(){
var w=getWork(activeWorkId);
if(!w){go('desk');return}
document.getElementById('hubName').textContent=w.title;
document.getElementById('hubGenre').textContent=w.genre||'未分类';
var wc=totalWords(w);
document.getElementById('hubStats').innerHTML=
'< div class = "hub-stat" > < b > '+wc+'< / b > < small > 总字数< / small > < / div > '+
'< div class = "hub-stat" > < b > '+(w.chapters?w.chapters.length:0)+'< / b > < small > 章节< / small > < / div > '+
'< div class = "hub-stat" > < b > '+(w.characters?w.characters.length:0)+'< / b > < small > 角色< / small > < / div > '+
'< div class = "hub-stat" > < b > '+(w.outline&&w.outline.volumes?w.outline.volumes.length:0)+'</b><small>卷</small></div>';
}
/* ════════════════════════════════════════
RENDER: Outline
════════════════════════════════════════ */
function renderOutline(){
var w=getWork(activeWorkId);
if(!w)return;
document.getElementById('olMain').value=w.outline?w.outline.main||'':'';
renderVols();
}
function renderVols(){
var w=getWork(activeWorkId);
if(!w||!w.outline)return;
var vols=w.outline.volumes||[];
var el=document.getElementById('volList');
if(vols.length===0){
el.innerHTML='< div style = "text-align:center;padding:16px;color:var(--muted);font-size:13px" > 暂无分卷 · 点击上方「+ 新卷」< / div > ';
return;
}
el.innerHTML=vols.map(function(v,i){
return '< div class = "card" > < div class = "vol-item" > < span > '+(i+1)+'. '+esc(v.title)+'< / span > '+
'< div class = "vol-actions" > < button class = "btn" onclick = "editVol('+i+')" > 编辑< / button > < button class = "btn" onclick = "delVol('+i+')" > 删除< / button > < / div > < / div > '+
'< textarea rows = "3" style = "margin-top:8px;font-size:13px" onchange = "saveVol('+i+',this.value)" placeholder = "这一卷的剧情走向..." > '+esc(v.outline||'')+'< / textarea > < / div > ';
}).join('');
}
document.getElementById('outlineSave').onclick=function(){
updateWork(activeWorkId,function(w){
if(!w.outline)w.outline={main:'',volumes:[]};
w.outline.main=document.getElementById('olMain').value;
});
chatMsg('大纲已保存 ✅','b');
};
document.getElementById('addVol').onclick=function(){
var name=prompt('卷名(如:第一卷 · 起始)');
if(!name)return;
updateWork(activeWorkId,function(w){
if(!w.outline)w.outline={main:'',volumes:[]};
w.outline.volumes.push({id:genId(),title:name,outline:''});
});
renderVols();
};
window.editVol=function(i){
var name=prompt('修改卷名');
if(!name)return;
updateWork(activeWorkId,function(w){w.outline.volumes[i].title=name});
renderVols();
};
window.delVol=function(i){
if(!confirm('删除这一卷?'))return;
updateWork(activeWorkId,function(w){w.outline.volumes.splice(i,1)});
renderVols();
};
window.saveVol=function(i,val){
updateWork(activeWorkId,function(w){w.outline.volumes[i].outline=val});
};
/* ════════════════════════════════════════
RENDER: Characters
════════════════════════════════════════ */
function renderChars(){
var w=getWork(activeWorkId);
if(!w)return;
var chars=w.characters||[];
var el=document.getElementById('charList');
if(chars.length===0){
el.innerHTML='< div class = "empty" > < em > 👥< / em > 还没有角色< br > < br > 点击「+ 新角色」或对铸渊说「新建角色」< / div > ';
return;
}
el.innerHTML=chars.map(function(c){
var icon=c.role==='主角'?'⭐':c.role==='女主'?'💫':c.role==='反派'?'🔥':'👤';
2026-04-02 20:14:42 +08:00
return '< div class = "card" style = "cursor:pointer" data-cid = "'+esc(c.id)+'" > '+
2026-04-02 20:07:12 +08:00
'< div class = "char-card" > < div class = "char-avatar" > '+icon+'< / div > < div class = "char-info" > '+
'< h4 > '+esc(c.name)+'< / h4 > < div class = "char-role" > '+esc(c.role||'角色')+'< / div > '+
'< p > '+esc((c.traits||'').substring(0,60)+(c.traits& & c.traits.length>60?'...':''))+'< / p > '+
'< / div > < / div > < / div > ';
}).join('');
2026-04-02 20:14:42 +08:00
el.querySelectorAll('[data-cid]').forEach(function(item){
item.onclick=function(){editingCharId=this.getAttribute('data-cid');go('charEdit')};
});
2026-04-02 20:07:12 +08:00
}
document.getElementById('addChar').onclick=function(){
editingCharId=null;
document.getElementById('ceTitle').textContent='新角色';
document.getElementById('ceName').value='';
document.getElementById('ceRole').value='';
document.getElementById('ceAppearance').value='';
document.getElementById('ceTraits').value='';
document.getElementById('ceBackground').value='';
go('charEdit');
};
document.getElementById('ceSave').onclick=function(){
var name=document.getElementById('ceName').value.trim();
if(!name){document.getElementById('ceName').focus();return}
updateWork(activeWorkId,function(w){
if(!w.characters)w.characters=[];
var ch={
id:editingCharId||genId(),
name:name,
role:document.getElementById('ceRole').value.trim(),
appearance:document.getElementById('ceAppearance').value.trim(),
traits:document.getElementById('ceTraits').value.trim(),
background:document.getElementById('ceBackground').value.trim()
};
if(editingCharId){
var idx=w.characters.findIndex(function(c){return c.id===editingCharId});
if(idx!==-1)w.characters[idx]=ch;
}else{
w.characters.push(ch);
}
});
go('chars');
};
/* ════════════════════════════════════════
RENDER: Chapters
════════════════════════════════════════ */
function renderChapters(){
var w=getWork(activeWorkId);
if(!w)return;
var chs=w.chapters||[];
var el=document.getElementById('chapterList');
if(chs.length===0){
el.innerHTML='< div class = "empty" > < em > 📚< / em > 还没有章节< br > < br > 点击「+ 新章节」或对铸渊说「新建章节」< / div > ';
return;
}
el.innerHTML=chs.map(function(ch,i){
var wc=wordCount(ch.content);
2026-04-02 20:14:42 +08:00
return '< div class = "card" style = "cursor:pointer" data-chid = "'+esc(ch.id)+'" > '+
2026-04-02 20:07:12 +08:00
'< div style = "display:flex;justify-content:space-between;align-items:center" > '+
'< div > < div style = "font-size:14px;font-weight:600" > 第'+(i+1)+'章 '+esc(ch.title)+'< / div > '+
'< div style = "font-size:11px;color:var(--dim);margin-top:2px" > '+wc+' 字< / div > < / div > '+
'< span class = "tag '+(wc>0?'tag-ok':'tag-info')+'" > '+(wc>0?'已写':'空白')+'< / span > < / div > < / div > ';
}).join('');
2026-04-02 20:14:42 +08:00
el.querySelectorAll('[data-chid]').forEach(function(item){
item.onclick=function(){activeChapterId=this.getAttribute('data-chid');go('editor')};
});
2026-04-02 20:07:12 +08:00
}
document.getElementById('addChapter').onclick=function(){
var title=prompt('章节标题');
if(!title)return;
updateWork(activeWorkId,function(w){
if(!w.chapters)w.chapters=[];
var ch={id:genId(),title:title,content:'',created:new Date().toISOString().slice(0,10)};
w.chapters.push(ch);
activeChapterId=ch.id;
});
go('editor');
};
/* ════════════════════════════════════════
RENDER: Editor
════════════════════════════════════════ */
var edAutoSave=null;
function renderEditor(){
var w=getWork(activeWorkId);
if(!w){go('desk');return}
var ch=null;
if(activeChapterId& & w.chapters){
ch=w.chapters.find(function(c){return c.id===activeChapterId});
}
if(!ch& & w.chapters& & w.chapters.length>0){
ch=w.chapters[w.chapters.length-1];
activeChapterId=ch.id;
}
if(!ch){
document.getElementById('edChTitle').textContent='暂无章节';
document.getElementById('edText').value='';
document.getElementById('edText').placeholder='请先创建章节(返回→章节→新章节)';
updateEdWc();
return;
}
document.getElementById('edTitle').textContent='✏️ '+w.title;
var idx=w.chapters.indexOf(ch)+1;
document.getElementById('edChTitle').textContent='第'+idx+'章 · '+ch.title;
document.getElementById('edText').value=ch.content||'';
document.getElementById('edText').placeholder='开始写作...\n\n让文字自由流淌。';
updateEdWc();
// Auto-save on input
var ta=document.getElementById('edText');
ta.oninput=function(){
updateEdWc();
clearTimeout(edAutoSave);
edAutoSave=setTimeout(function(){saveEditorContent()},2000);
};
}
function updateEdWc(){
var c=wordCount(document.getElementById('edText').value);
document.getElementById('edWc').textContent=c+' 字';
document.getElementById('edWc2').textContent='字数:'+c;
}
function saveEditorContent(){
var val=document.getElementById('edText').value;
updateWork(activeWorkId,function(w){
if(!w.chapters)return;
var ch=w.chapters.find(function(c){return c.id===activeChapterId});
if(ch)ch.content=val;
});
}
document.getElementById('edSave').onclick=function(){
saveEditorContent();
chatMsg('章节已保存 ✅','b');
};
/* ════════════════════════════════════════
RENDER: CharEdit (on navigate)
════════════════════════════════════════ */
function renderCharEdit(){
if(!editingCharId)return;
var w=getWork(activeWorkId);
if(!w)return;
var c=(w.characters||[]).find(function(ch){return ch.id===editingCharId});
if(!c)return;
document.getElementById('ceTitle').textContent='编辑角色';
document.getElementById('ceName').value=c.name||'';
document.getElementById('ceRole').value=c.role||'';
document.getElementById('ceAppearance').value=c.appearance||'';
document.getElementById('ceTraits').value=c.traits||'';
document.getElementById('ceBackground').value=c.background||'';
}
/* ════════════════════════════════════════
THEME
════════════════════════════════════════ */
var TH=['','t-twi','t-sea','t-for','t-emb'];
2026-04-02 20:14:42 +08:00
var ti=new Uint8Array(1);crypto.getRandomValues(ti);ti=ti[0]%TH.length;
2026-04-02 20:07:12 +08:00
try{var sv=localStorage.getItem('hl-t');if(sv!==null)ti=parseInt(sv)}catch(e){}
function setTh(i){ti=i%TH.length;document.body.className=TH[ti];try{localStorage.setItem('hl-t',ti)}catch(e){}}
setTh(ti);
document.getElementById('themeBtn').onclick=function(){setTh(ti+1)};
/* ════════════════════════════════════════
USER IDENTITY (自报身份)
════════════════════════════════════════ */
var usr={id:null,name:null,code:null};
try{var um=localStorage.getItem('hl-u');if(um)usr=JSON.parse(um)}catch(e){}
function saveUsr(){try{localStorage.setItem('hl-u',JSON.stringify(usr))}catch(e){}}
2026-04-02 20:14:42 +08:00
if(!usr.id){var ra=new Uint8Array(6);crypto.getRandomValues(ra);usr.id='u-'+Array.from(ra,function(b){return b.toString(36)}).join('').substr(0,8);saveUsr()}
2026-04-02 20:07:12 +08:00
function detectId(msg){
var nm=msg.match(/我(?:是|叫)\s*([^\s,,。.!! ?? ]{1,20})/);
var cd=msg.match(/编号[: :\s]*([A-Za-z0-9\-]+)/i)||msg.match(/([A-Z]+-\d+)/);
var ch=false;
if(nm){usr.name=nm[1].trim();ch=true}
if(cd){usr.code=cd[1].trim();ch=true}
if(ch)saveUsr();
return ch;
}
function idReply(){
if(usr.name& & usr.code)return'铸渊已记录 ✅\n\n'+usr.name+',编号 '+usr.code+'。\n下次报编号即可恢复记忆。';
if(usr.name)return'记住了:'+usr.name+' ✅\n有编号也可以告诉我。';
if(usr.code)return'编号已记录:'+usr.code+' ✅\n你叫什么名字? ';
return null;
}
function isReturn(msg){
if(!usr.name& & !usr.code)return false;
2026-04-02 20:14:42 +08:00
if(usr.code& & msg.indexOf(usr.code)!==-1)return true;
2026-04-02 20:07:12 +08:00
if(usr.name& & msg.indexOf(usr.name)!==-1)return true;
return false;
}
function recovReply(){
var p=['铸渊记得你 💫\n'];
if(usr.name)p.push('名字:'+usr.name);
if(usr.code)p.push('编号:'+usr.code);
p.push('\n记忆已恢复。需要什么帮忙? ');
return p.join('\n');
}
/* ════════════════════════════════════════
CHAT (语言驱动核心)
════════════════════════════════════════ */
var chatOpen=false,sending=false;
var chatFab=document.getElementById('chatFab');
var chatP=document.getElementById('chatP');
var chatIn=document.getElementById('chatIn');
var chatMs=document.getElementById('chatMs');
var mCalls=0;
chatFab.onclick=function(){
chatOpen=!chatOpen;
chatP.classList.toggle('open',chatOpen);
chatFab.classList.toggle('on',chatOpen);
chatFab.textContent=chatOpen?'✕':'💬';
if(chatOpen)setTimeout(function(){chatIn.focus()},200);
};
function chatMsg(text,type,meta){
var d=document.createElement('div');
d.className='cm '+type;
2026-04-02 20:14:42 +08:00
var h=esc(text).replace(/\n/g,'< br > ');
if(type==='b'& & meta){h+='< div class = "cm-m" > < span > '+esc(meta.m||'铸渊')+'< / span > < span > ·< / span > < span > '+esc(meta.t||'')+'< / span > < / div > '}
2026-04-02 20:07:12 +08:00
d.innerHTML=h;
chatMs.appendChild(d);
chatMs.scrollTop=chatMs.scrollHeight;
}
function showTyp(){var d=document.createElement('div');d.className='ct';d.id='ctyp';d.innerHTML='铸渊思考中 < span > ·< / span > < span > ·< / span > < span > ·< / span > ';chatMs.appendChild(d);chatMs.scrollTop=chatMs.scrollHeight}
function hideTyp(){var e=document.getElementById('ctyp');if(e)e.remove()}
// ─── 语言指令路由 ───
function routeCommand(msg){
var m=msg.toLowerCase();
// 导航指令
if(/码字|工作台|写作台/.test(m)){go('desk');return'好的,翻转到码字工作台 ✍️'}
if(/新建作品|创建作品/.test(m)){go('create');return'翻转到新建作品页 📖'}
if(/(进入|打开)?大纲/.test(m)){
if(!activeWorkId){return'请先选择一个作品,或说「我要码字」进入工作台'}
go('outline');return'翻转到大纲编辑 📋';
}
if(/人物卡|角色|人物/.test(m)){
if(!activeWorkId){return'请先选择一个作品'}
go('chars');return'翻转到人物卡管理 👥';
}
if(/(新建|添加)角色/.test(m)){
if(!activeWorkId){return'请先选择一个作品'}
editingCharId=null;
document.getElementById('ceName').value='';
document.getElementById('ceRole').value='';
document.getElementById('ceAppearance').value='';
document.getElementById('ceTraits').value='';
document.getElementById('ceBackground').value='';
go('charEdit');return'翻转到新建角色页 👤';
}
if(/章节|目录/.test(m)){
if(!activeWorkId){return'请先选择一个作品'}
go('chapters');return'翻转到章节目录 📚';
}
if(/(新建|添加)章节/.test(m)){
if(!activeWorkId){return'请先选择一个作品'}
var title=msg.replace(/(新建|添加)章节/,'').trim()||'新章节';
updateWork(activeWorkId,function(w){
if(!w.chapters)w.chapters=[];
var ch={id:genId(),title:title,content:'',created:new Date().toISOString().slice(0,10)};
w.chapters.push(ch);activeChapterId=ch.id;
});
go('editor');return'新章节「'+title+'」已创建,翻转到编辑器 ✏️';
}
if(/写正文|写作|续写|编辑/.test(m)){
if(!activeWorkId){return'请先选择一个作品'}
go('editor');return'翻转到正文编辑器 ✏️';
}
if(/回首页|回灯塔|回主页|返回/.test(m)){go('home');return'翻转回灯塔首页 🏛️'}
return null;
}
async function send(){
var msg=chatIn.value.trim();
if(!msg||sending)return;
chatIn.value='';
chatMsg(msg,'u');
// 1. 语言指令路由
var cmdReply=routeCommand(msg);
if(cmdReply){setTimeout(function(){chatMsg(cmdReply,'b',{m:'铸渊',t:'本地'})},300);return}
// 2. 身份识别
if(detectId(msg)){setTimeout(function(){chatMsg(idReply(),'b',{m:'铸渊',t:'本地'})},300);return}
if(isReturn(msg)){setTimeout(function(){chatMsg(recovReply(),'b',{m:'铸渊',t:'本地'})},300);return}
// 3. 调用API
sending=true;document.getElementById('chatSnd').disabled=true;showTyp();
try{
var res=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:msg,userId:usr.id})});
var data=await res.json();hideTyp();
if(data.message){
chatMsg(data.message,'b',{m:data.model||'铸渊',t:data.tier||''});
if(data.tier!=='free')mCalls++;
document.getElementById('mC').textContent=mCalls;
document.getElementById('chatTg').textContent=data.model||'ready';
if(data.model)document.getElementById('mM').textContent=data.model;
}
}catch(e){
hideTyp();chatMsg(offline(msg),'b',{m:'铸渊',t:'本地'});
}
sending=false;document.getElementById('chatSnd').disabled=false;
}
function offline(msg){
if(/你好|hi|hello/.test(msg))return'你好!我是铸渊 🏛️\n\n当前API未连接。你可以浏览公告或进入码字工作台。';
if(/状态|系统/.test(msg))return'📊 AGE OS v40.0 在线\n• COS双桶已配置\n• 码字工作台 Beta';
if(/谁|你是/.test(msg))return'我是铸渊 🏛️ 光湖灯塔守护者。\n编号 ICE-GL-ZY001。\n\n告诉我你是谁, 我会记住你。';
return'💫 铸渊收到。当前使用本地回复。\n试试: 「我要码字」「大纲」「人物卡」「写正文」';
}
document.getElementById('chatSnd').onclick=send;
chatIn.onkeydown=function(e){if(e.key==='Enter'){e.preventDefault();send()}};
/* ════════════════════════════════════════
COS SYNC (团队内测用户 → COS)
════════════════════════════════════════ */
var cosSyncTimer=null;
2026-04-02 20:11:11 +08:00
var cosSyncing=false;
2026-04-02 20:07:12 +08:00
function syncToCOS(works){
clearTimeout(cosSyncTimer);
cosSyncTimer=setTimeout(function(){
2026-04-02 20:11:11 +08:00
if(!usr.id||cosSyncing)return;
cosSyncing=true;
2026-04-02 20:07:12 +08:00
fetch('/api/cos/sync-works',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({userId:usr.id,works:works})
2026-04-02 20:11:11 +08:00
}).then(function(){cosSyncing=false}).catch(function(){cosSyncing=false});
2026-04-02 20:07:12 +08:00
},10000);
}
// 启动时尝试从COS恢复( 团队用户)
async function tryLoadFromCOS(){
try{
var res=await fetch('/api/cos/load-works?userId='+encodeURIComponent(usr.id),{signal:AbortSignal.timeout(5000)});
var data=await res.json();
if(data.works& & data.works.length>0){
var local=loadWorks();
// COS有数据且本地为空时恢复
if(local.length===0){
saveWorks(data.works);
}
}
}catch(e){/* 离线时使用本地数据 */}
}
tryLoadFromCOS();
/* ════════════════════════════════════════
MONITOR
════════════════════════════════════════ */
document.getElementById('mon').onclick=function(){this.classList.toggle('x')};
async function checkConn(){
try{var t=performance.now();var r=await fetch('/api/health',{signal:AbortSignal.timeout(4000)});var ms=Math.round(performance.now()-t);
document.getElementById('mN').textContent=ms+'ms';document.getElementById('mN').style.color=ms< 500 ? ' var ( --ok ) ' : ' var ( --warn ) ' ;
}catch(e){document.getElementById('mN').textContent='离线';document.getElementById('mN').style.color='var(--err)'}
}
checkConn();setInterval(checkConn,30000);
/* ════════════════════════════════════════
UTILS
════════════════════════════════════════ */
function esc(s){if(!s)return'';var d=document.createElement('div');d.textContent=s;return d.innerHTML}
})();
< / script >
< / body >
< / html >