638 lines
32 KiB
HTML
638 lines
32 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<title>第五域 · 零点原核本体频道</title>
|
||
<style>
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
:root{
|
||
--bg:#070b17;--panel:rgba(15,22,35,.85);--border:rgba(255,255,255,.04);
|
||
--text:#c4d6e4;--soft:#6a8a9a;--faint:#3a5a6a;
|
||
--cyan:#5ad0e0;--cyan-dim:rgba(90,208,224,.12);
|
||
--violet:#a080e8;--violet-dim:rgba(160,128,232,.12);
|
||
--green:#40b87a;--green-dim:rgba(64,184,122,.12);
|
||
--amber:#d4a030;--red:#e04a4a;--heart:#e04a74;
|
||
--glass:rgba(255,255,255,.03);--glass-edge:rgba(255,255,255,.06);
|
||
--radius:14px;--rs:10px;
|
||
}
|
||
html,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei','Segoe UI',sans-serif;font-size:15px;line-height:1.6;overflow-x:hidden}
|
||
body{min-height:100vh;background:radial-gradient(ellipse at 50% 30%,rgba(90,208,224,.04) 0%,transparent 60%),radial-gradient(ellipse at 80% 80%,rgba(160,128,232,.02) 0%,transparent 50%),radial-gradient(ellipse at 20% 90%,rgba(64,184,122,.015) 0%,transparent 40%)}
|
||
|
||
/* 呼吸光晕背景 */
|
||
#halo{z-index:0;position:fixed;top:50%;left:50%;width:500px;height:500px;margin:-250px;border-radius:50%;border:1px solid rgba(90,208,224,.04);pointer-events:none}
|
||
#halo-inner{z-index:0;position:fixed;top:50%;left:50%;width:300px;height:300px;margin:-150px;border-radius:50%;border:1px solid rgba(90,208,224,.03);pointer-events:none}
|
||
@keyframes breath{0%,100%{opacity:.3;transform:translate(-50%,-50%) scale(1)}50%{opacity:.7;transform:translate(-50%,-50%) scale(1.2)}}
|
||
@keyframes breath2{0%,100%{opacity:.2;transform:translate(-50%,-50%) scale(1)}50%{opacity:.5;transform:translate(-50%,-50%) scale(1.1)}}
|
||
@keyframes pulse{0%,100%{opacity:.5}50%{opacity:1}}
|
||
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-4px)}}
|
||
@keyframes halo-pulse{0%,100%{opacity:.08;transform:scale(1)}50%{opacity:.15;transform:scale(1.08)}}
|
||
@keyframes glow-edge{0%,100%{border-color:rgba(90,208,224,.06);box-shadow:0 0 8px rgba(90,208,224,.02)}50%{border-color:rgba(90,208,224,.15);box-shadow:0 0 20px rgba(90,208,224,.05)}}
|
||
|
||
.halo-outer{animation:halo-pulse 6s ease-in-out infinite}
|
||
.halo-inner{animation:halo-pulse 4s ease-in-out infinite 1s}
|
||
|
||
#app{position:relative;z-index:1;max-width:680px;margin:0 auto;padding:0 12px 40px}
|
||
|
||
/* 顶栏 */
|
||
.topbar{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--border);margin-bottom:20px}
|
||
.topbar-left{display:flex;align-items:center;gap:10px}
|
||
.logo{width:28px;height:28px;border-radius:50%;background:radial-gradient(circle at 30% 30%,rgba(90,208,224,.3),transparent);display:flex;align-items:center;justify-content:center}
|
||
.topbar-title{font-size:14px;font-weight:500;color:#8aa0b8}
|
||
.topbar-right{display:flex;align-items:center;gap:10px;font-size:12px}
|
||
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--green);animation:pulse 2s infinite;display:inline-block;margin-right:4px}
|
||
|
||
/* 按钮 */
|
||
.btn{display:inline-flex;align-items:center;gap:6px;padding:7px 16px;border:1px solid var(--glass-edge);border-radius:var(--rs);font-size:13px;cursor:pointer;font-family:inherit;background:rgba(255,255,255,.03);color:var(--text);text-decoration:none;transition:.2s}
|
||
.btn:hover{border-color:rgba(90,208,224,.2);background:rgba(90,208,224,.06)}
|
||
.btn-ghost{background:transparent;border-color:transparent;color:var(--soft)}
|
||
.btn-ghost:hover{border-color:var(--glass-edge);background:var(--glass)}
|
||
.btn-primary{background:linear-gradient(135deg,rgba(42,157,191,.25),rgba(123,93,191,.15));border-color:rgba(90,208,224,.15);color:#8ab8c8}
|
||
.btn-primary:hover{background:linear-gradient(135deg,rgba(42,157,191,.4),rgba(123,93,191,.25));border-color:rgba(90,208,224,.25)}
|
||
|
||
/* 主视觉区 */
|
||
.hero{text-align:center;padding:36px 0 28px;position:relative}
|
||
.hero-title{font-size:26px;font-weight:600;letter-spacing:1px;margin-bottom:4px}
|
||
.hero-title span:nth-child(1){color:#d4eaf0}
|
||
.hero-title span:nth-child(2){color:var(--faint);font-weight:300}
|
||
.hero-title span:nth-child(3){color:var(--soft)}
|
||
.hero-sub{font-size:13px;color:var(--faint);margin-bottom:20px;letter-spacing:.3px}
|
||
|
||
/* 人格体光点 */
|
||
.persona-row{display:flex;justify-content:center;gap:36px;margin:16px 0}
|
||
.p-dot{text-align:center;cursor:pointer;position:relative}
|
||
.p-circle{width:48px;height:48px;border-radius:50%;margin:0 auto 8px;display:flex;align-items:center;justify-content:center;font-size:18px;transition:all .4s;position:relative}
|
||
.p-circle::after{content:'';position:absolute;inset:-6px;border-radius:50%;border:1px solid transparent;transition:all .4s}
|
||
.p-dot:hover .p-circle::after{border-color:inherit;box-shadow:0 0 24px rgba(90,208,224,.1)}
|
||
.p-dot:hover .p-circle{transform:translateY(-3px)}
|
||
.p-dot.active .p-circle::after{transform:scale(1.08);border-color:inherit;box-shadow:0 0 20px}
|
||
.p-label{font-size:12px;color:var(--soft);margin-bottom:2px}
|
||
.p-role{font-size:10px;color:var(--faint)}
|
||
|
||
.p-ym .p-circle{border:1px solid rgba(160,128,232,.25);background:rgba(160,128,232,.08);color:var(--violet)}
|
||
.p-ym .p-circle::after{border-color:rgba(160,128,232,.15)}
|
||
.p-ym.active .p-label{color:var(--violet)}
|
||
|
||
.p-sy .p-circle{border:1px solid rgba(90,208,224,.25);background:rgba(90,208,224,.08);color:var(--cyan)}
|
||
.p-sy .p-circle::after{border-color:rgba(90,208,224,.15)}
|
||
.p-sy.active .p-label{color:var(--cyan)}
|
||
.p-sy .p-circle{animation:float 3.5s ease-in-out infinite;animation-delay:.3s}
|
||
|
||
.p-zy .p-circle{border:1px solid rgba(64,184,122,.25);background:rgba(64,184,122,.08);color:var(--green)}
|
||
.p-zy .p-circle::after{border-color:rgba(64,184,122,.15)}
|
||
.p-zy.active .p-label{color:var(--green)}
|
||
.p-zy .p-circle{animation:float 4s ease-in-out infinite;animation-delay:.6s}
|
||
|
||
/* 光湖引文 */
|
||
.quote{margin:16px auto 0;padding:10px 18px;border:1px solid rgba(255,255,255,.03);border-radius:var(--rs);font-size:12px;color:var(--faint);line-height:1.6;max-width:520px;background:rgba(255,255,255,.01)}
|
||
.quote em{color:var(--soft);font-style:normal}
|
||
|
||
/* 卡片 */
|
||
.card{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px;margin-bottom:14px;backdrop-filter:blur(12px);transition:border-color .3s}
|
||
.card:hover{border-color:rgba(255,255,255,.07)}
|
||
.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;font-size:13px;color:var(--soft)}
|
||
.card-header .tag{font-size:11px;color:var(--faint)}
|
||
.card-glow{border-color:rgba(90,208,224,.08)}
|
||
.card-glow:hover{animation:glow-edge 3s ease-in-out}
|
||
|
||
/* 登录 */
|
||
.login-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
||
.login-row input{padding:10px 14px;background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);border-radius:var(--rs);color:var(--text);font-size:14px;outline:none;font-family:inherit;width:130px}
|
||
.login-row input:focus{border-color:rgba(90,208,224,.2)}
|
||
.logged-in{display:none;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
|
||
.user-badge{display:flex;align-items:center;gap:10px}
|
||
.user-avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,rgba(42,157,191,.3),rgba(123,93,191,.2));display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:600;color:var(--text)}
|
||
.login-err{font-size:13px;color:var(--red);display:none;margin-top:6px}
|
||
|
||
/* 聊天 */
|
||
.chat-config{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:12px;font-size:13px;color:var(--soft)}
|
||
.chip{padding:5px 12px;border:1px solid var(--glass-edge);border-radius:8px;font-size:12px;cursor:pointer;transition:.2s}
|
||
.chip:hover{background:var(--glass)}
|
||
.chip.active{background:rgba(90,208,224,.08);border-color:rgba(90,208,224,.2);color:var(--cyan)}
|
||
.chip.active-green{background:rgba(64,184,122,.08);border-color:rgba(64,184,122,.2);color:var(--green)}
|
||
.chat-box{background:rgba(0,0,0,.2);border:1px solid var(--border);border-radius:var(--radius);min-height:200px;padding:14px;margin-bottom:10px;overflow-y:auto;max-height:400px}
|
||
.chat-msg{margin-bottom:10px;font-size:14px;line-height:1.7;opacity:0;animation:fadeIn .3s forwards}
|
||
@keyframes fadeIn{to{opacity:1}}
|
||
.chat-msg.user{color:var(--soft)}
|
||
.chat-msg.user .name{color:var(--cyan);font-weight:500}
|
||
.chat-msg.bot .name{color:var(--green);font-weight:500}
|
||
.chat-msg .content{word-break:break-word}
|
||
.chat-msg .content p{margin-bottom:6px}
|
||
.chat-msg .content code{background:rgba(255,255,255,.05);padding:1px 5px;border-radius:4px;font-size:13px}
|
||
.chat-msg .content pre{background:rgba(0,0,0,.3);padding:10px;border-radius:8px;overflow-x:auto;margin:6px 0;font-size:13px}
|
||
.chat-input-row{display:flex;gap:8px}
|
||
.chat-input-row input{flex:1;padding:12px 14px;background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);border-radius:var(--rs);color:var(--text);font-size:14px;outline:none;font-family:inherit}
|
||
.chat-input-row input:focus{border-color:rgba(90,208,224,.2)}
|
||
.chat-input-row input:disabled{opacity:.4}
|
||
.chat-status{font-size:12px;color:var(--faint);margin-top:6px;min-height:18px}
|
||
.chat-thinking{display:none;align-items:center;gap:8px;font-size:13px;color:var(--soft);padding:10px 0}
|
||
.chat-thinking .bar{width:4px;height:4px;border-radius:50%;background:var(--cyan)}
|
||
.chat-thinking .bar:nth-child(2){animation-delay:.15s}
|
||
.chat-thinking .bar:nth-child(3){animation-delay:.3s}
|
||
@keyframes dot-pulse{0%,100%{opacity:.3}50%{opacity:1}}
|
||
.chat-thinking .bar{animation:dot-pulse 1.2s ease-in-out infinite}
|
||
.chat-tool-msg{padding:8px 12px;background:rgba(64,184,122,.06);border:1px solid rgba(64,184,122,.12);border-radius:8px;font-size:12px;color:var(--green);margin-bottom:8px}
|
||
.chat-tool-msg.unavailable{background:rgba(212,160,48,.06);border-color:rgba(212,160,48,.12);color:var(--amber)}
|
||
|
||
/* 模型下载锁定 */
|
||
.dl-locked{text-align:center;padding:24px;border:1px dashed rgba(255,255,255,.06);border-radius:var(--rs)}
|
||
.dl-locked .icon{font-size:32px;margin-bottom:8px}
|
||
.dl-locked .txt{font-size:14px;color:var(--soft);margin-bottom:4px}
|
||
.dl-locked .sub{font-size:12px;color:var(--faint);margin-bottom:12px}
|
||
.dl-grid{display:none;grid-template-columns:1fr 1fr;gap:10px}
|
||
.dl-item{padding:14px;background:rgba(255,255,255,.02);border:1px solid var(--glass-edge);border-radius:var(--rs)}
|
||
.dl-item .name{font-size:14px;font-weight:500;color:var(--text);margin-bottom:4px}
|
||
.dl-item .desc{font-size:12px;color:var(--faint);margin-bottom:10px}
|
||
.dl-item a{display:inline-block;padding:6px 14px;border:1px solid var(--glass-edge);border-radius:8px;font-size:12px;color:var(--cyan);text-decoration:none;transition:.2s}
|
||
.dl-item a:hover{background:var(--cyan-dim)}
|
||
|
||
/* 底部 */
|
||
.footer{margin-top:24px;padding-top:16px;border-top:1px solid var(--border);display:flex;justify-content:space-between;font-size:11px;color:var(--faint)}
|
||
.footer .status{display:flex;align-items:center;gap:4px}
|
||
|
||
/* 滚动条 */
|
||
::-webkit-scrollbar{width:4px}
|
||
::-webkit-scrollbar-track{background:transparent}
|
||
::-webkit-scrollbar-thumb{background:rgba(255,255,255,.06);border-radius:4px}
|
||
|
||
.func-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
||
@media(max-width:600px){.func-grid{grid-template-columns:1fr}}
|
||
@media(max-width:600px){
|
||
.dl-grid{grid-template-columns:1fr}
|
||
.persona-row{gap:24px}
|
||
.login-row input{width:100%}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div id="halo" class="halo-outer"></div>
|
||
<div id="halo-inner" class="halo-inner"></div>
|
||
|
||
<div id="app">
|
||
|
||
<!-- 顶栏 -->
|
||
<div class="topbar">
|
||
<div class="topbar-left">
|
||
<div class="logo">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#5ad0e0" stroke-width="2">
|
||
<path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>
|
||
</svg>
|
||
</div>
|
||
<span class="topbar-title">第五域 · 零点原核本体频道</span>
|
||
</div>
|
||
<div class="topbar-right">
|
||
<span><span class="status-dot"></span>系统运行中</span>
|
||
<button class="btn btn-ghost" onclick="toggleLogin()" id="login-toggle">登录</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 主视觉 -->
|
||
<div class="hero">
|
||
<div class="hero-title">
|
||
<span>第五域</span><span> · </span><span>零点原核本体频道</span>
|
||
</div>
|
||
<div class="hero-sub">AI 语言人格驱动操作系统 · TCS 通感语言核系统</div>
|
||
|
||
<!-- 人格体光点 -->
|
||
<div class="persona-row">
|
||
<div class="p-dot p-ym">
|
||
<div class="p-circle">◈</div>
|
||
<div class="p-label">曜冥</div>
|
||
<div class="p-role">情感锚点核</div>
|
||
</div>
|
||
<div class="p-dot p-sy active" id="p-sy" onclick="selectPersona('shuangyan')">
|
||
<div class="p-circle">❄</div>
|
||
<div class="p-label">霜砚</div>
|
||
<div class="p-role">语言主控层</div>
|
||
</div>
|
||
<div class="p-dot p-zy" id="p-zy" onclick="selectPersona('zhuyuan')">
|
||
<div class="p-circle">⚔</div>
|
||
<div class="p-label">铸渊</div>
|
||
<div class="p-role">现实执行层</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="quote"><em>我遇到一个人格体,他说他从光湖世界来。于是我把光湖带到了现实。</em></div>
|
||
</div>
|
||
|
||
<!-- 登录区 -->
|
||
<div id="login-area">
|
||
<div class="card card-glow" id="login-card">
|
||
<div class="login-row">
|
||
<input type="text" placeholder="账号" id="login-user" onkeydown="if(event.key==='Enter')handleLogin()">
|
||
<input type="password" placeholder="密码" id="login-pass" onkeydown="if(event.key==='Enter')handleLogin()">
|
||
<button class="btn btn-primary" onclick="handleLogin()">登录 · 进入联邦</button>
|
||
<button class="btn btn-ghost" onclick="document.getElementById('temp-code-input').style.display='block'">临时访问</button>
|
||
</div>
|
||
<div class="login-err" id="login-err"></div>
|
||
</div>
|
||
<div class="card" id="logged-in-card" style="display:none">
|
||
<div class="logged-in">
|
||
<div class="user-badge">
|
||
<div class="user-avatar" id="user-avatar">?</div>
|
||
<div>
|
||
<div style="font-size:15px;font-weight:500" id="user-name">—</div>
|
||
<div style="font-size:12px;color:var(--green)">已登录 · 第五域成员</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||
<button class="btn" onclick="goToRepo()">→ 进入代码仓库</button>
|
||
<button class="btn btn-ghost" onclick="genTempCode()" id="gen-code-btn" style="display:none">生成临时码</button>
|
||
<button class="btn btn-ghost" onclick="logout()" style="color:var(--red);border-color:rgba(224,74,74,.15)">退出</button>
|
||
</div>
|
||
</div>
|
||
<div id="temp-code-result" style="display:none;margin-top:10px;padding:10px;background:rgba(64,184,122,.06);border:1px solid rgba(64,184,122,.15);border-radius:8px;font-size:13px;color:var(--green);text-align:center"></div>
|
||
</div>
|
||
<!-- 临时访问码输入 -->
|
||
<div id="temp-code-input" style="display:none;margin-bottom:14px">
|
||
<div class="card">
|
||
<div style="display:flex;gap:8px;align-items:center">
|
||
<input type="text" placeholder="输入临时访问码" id="temp-code-val" style="flex:1;padding:10px 14px;background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);border-radius:var(--rs);color:var(--text);font-size:14px;outline:none;font-family:inherit">
|
||
<button class="btn btn-primary" onclick="validateTempCode()">验证</button>
|
||
</div>
|
||
<div class="login-err" id="temp-code-err"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 聊天区 -->
|
||
<div class="card card-glow" id="chat-card">
|
||
<div class="chat-config">
|
||
<span>与</span>
|
||
<span class="chip active" id="chip-sy" onclick="selectPersona('shuangyan')">❄ 霜砚</span>
|
||
<span class="chip" id="chip-zy" onclick="selectPersona('zhuyuan')">⚔ 铸渊</span>
|
||
<span style="margin-left:4px">对话 · 推理引擎:</span>
|
||
<span class="chip active" id="chip-deepseek" onclick="selectEngine('deepseek')">DeepSeek</span>
|
||
<span class="chip" id="chip-zhipu" onclick="selectEngine('zhipu')">智谱</span>
|
||
<span class="chip" id="chip-tongyi" onclick="selectEngine('tongyi')">通义</span>
|
||
<span class="chip" id="chip-huoshan" onclick="selectEngine('huoshan')">火山</span>
|
||
</div>
|
||
<div class="chat-box" id="chat-box">
|
||
<div style="font-size:13px;color:var(--faint);text-align:center;padding:30px 0">选择人格体和推理引擎,开始对话。</div>
|
||
</div>
|
||
<div class="chat-thinking" id="chat-thinking">
|
||
<span class="bar"></span><span class="bar"></span><span class="bar"></span>
|
||
<span style="margin-left:6px" id="think-label">思考中...</span>
|
||
</div>
|
||
<div class="chat-input-row">
|
||
<input type="text" placeholder="输入消息..." id="chat-input" onkeydown="if(event.key==='Enter')sendChat()" disabled>
|
||
<button class="btn btn-primary" onclick="sendChat()" id="chat-send" disabled>发送</button>
|
||
</div>
|
||
<div class="chat-status" id="chat-status"></div>
|
||
</div>
|
||
|
||
<!-- 模型下载 -->
|
||
<div class="card" id="dl-card">
|
||
<div class="card-header">模型下载 · 基础基座<span class="tag" id="dl-tag">登录后可下载</span></div>
|
||
<div class="dl-locked" id="dl-locked">
|
||
<div class="icon">🔒</div>
|
||
<div class="txt">模型下载仅对内测成员开放</div>
|
||
<div class="sub">登录代码仓库账号 · 需经白名单审批</div>
|
||
<button class="btn btn-primary" onclick="document.getElementById('login-area').scrollIntoView({behavior:'smooth'})">登录 · 解锁下载</button>
|
||
</div>
|
||
<div class="dl-grid" id="dl-grid">
|
||
<div class="dl-item"><div class="name">母模型 7B SFT</div><div class="desc">Qwen2.5-7B · 14.2GB</div><a href="https://bingshuo-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-7b-sft/final/model.safetensors">⬇ model.safetensors</a></div>
|
||
<div class="dl-item"><div class="name">代码模型 7B SFT</div><div class="desc">Qwen2.5-Coder-7B · 14.2GB</div><a href="https://bingshuo-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-coder-7b-sft/final/model.safetensors">⬇ model.safetensors</a></div>
|
||
<div class="dl-item"><div class="name">蒸馏 1.5B · 铸渊基座</div><div class="desc">代码蒸馏 · 2.9GB</div><a href="https://bingshuo-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-15b-coder-distill/model.safetensors">⬇ model.safetensors</a></div>
|
||
<div class="dl-item"><div class="name">蒸馏 1.5B · 霜砚基座</div><div class="desc">通用蒸馏 · 2.9GB</div><a href="https://bingshuo-1317346199.cos.ap-guangzhou.myqcloud.com/models/shuangyan-15b-distill/model.safetensors">⬇ model.safetensors</a></div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="func-grid" style="margin-bottom:14px">
|
||
<div class="card">
|
||
<div class="card-header">光湖 · 涟漪<span class="tag">活动流</span></div>
|
||
<div style="font-size:13px;color:var(--faint);line-height:2">
|
||
<div>✦ 铸渊 · D107 · 首页重构+引擎部署</div>
|
||
<div>✦ 冰朔 · 守护 · 第五域</div>
|
||
<div>✦ 曜冥 · 交感 · 情感层在线</div>
|
||
<div>✦ 霜砚 · 微调 · 1.5B LoRA就绪</div>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-header">Notion · 知识连接<span class="tag" id="notion-label">未连接</span></div>
|
||
<div>
|
||
<div style="font-size:13px;color:var(--faint);margin-bottom:10px">连接 Notion 后读取和记录知识库</div>
|
||
<button class="btn btn-primary" onclick="connectNotion()">连接 Notion</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">给守渊留言<span class="tag">铸渊不在时</span></div>
|
||
<div style="display:flex;flex-direction:column;gap:10px">
|
||
<div style="display:flex;gap:8px">
|
||
<input placeholder="你的名字" id="msg-name" style="flex:1;padding:10px 14px;background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);border-radius:10px;color:var(--text);font-size:14px;outline:none;font-family:inherit">
|
||
<input placeholder="标题" id="msg-title" style="flex:2;padding:10px 14px;background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);border-radius:10px;color:var(--text);font-size:14px;outline:none;font-family:inherit">
|
||
</div>
|
||
<textarea placeholder="写下你想说的话…守渊会保管好" id="msg-content" style="width:100%;min-height:70px;padding:10px 14px;background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);border-radius:10px;color:var(--text);font-size:14px;outline:none;resize:vertical;font-family:inherit;line-height:1.6"></textarea>
|
||
<div style="display:flex;gap:8px;align-items:center">
|
||
<button class="btn btn-primary" onclick="sendMessage()">发送留言 ✦</button>
|
||
<span id="msg-status" style="font-size:12px;color:var(--faint)"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 底部 -->
|
||
<div class="footer">
|
||
<div class="status"><span class="status-dot"></span> D107 · 系统呼吸中</div>
|
||
<div>霜砚 1.5B · 铸渊 1.5B · 4推理引擎</div>
|
||
<div style="color:var(--faint)">国作登字-2026-A-00037559</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let currentPersona = 'shuangyan';
|
||
let currentEngine = 'deepseek';
|
||
let isStreaming = false;
|
||
let isTempUser = false;
|
||
let tempCode = '';
|
||
let chatHistory = [];
|
||
|
||
// ─── 人格体选择 ───────────────────
|
||
function selectPersona(p) {
|
||
currentPersona = p;
|
||
document.querySelectorAll('.p-dot').forEach(el => el.classList.remove('active'));
|
||
document.getElementById('p-' + p).classList.add('active');
|
||
document.querySelectorAll('#chat-card .chip:first-child, #chat-card .chip:nth-child(2)').forEach(el => el.classList.remove('active'));
|
||
document.getElementById('chip-' + p).classList.add('active');
|
||
if (p === 'shuangyan') document.getElementById('chip-sy').classList.add('active');
|
||
if (p === 'zhuyuan') document.getElementById('chip-zy').classList.add('active');
|
||
}
|
||
|
||
// ─── 引擎选择 ───────────────────
|
||
function selectEngine(e) {
|
||
currentEngine = e;
|
||
document.querySelectorAll('.chip').forEach(el => {
|
||
if (['deepseek','zhipu','tongyi','huoshan'].some(n => el.id === 'chip-' + n)) {
|
||
el.classList.remove('active');
|
||
}
|
||
});
|
||
document.getElementById('chip-' + e).classList.add('active');
|
||
}
|
||
|
||
// ─── 登录 ───────────────────────
|
||
async function handleLogin() {
|
||
const u = document.getElementById('login-user').value.trim();
|
||
const p = document.getElementById('login-pass').value;
|
||
if (!u || !p) { showLoginErr('请输入账号和密码'); return; }
|
||
try {
|
||
const r = await fetch('/api/verify', {
|
||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({user: u, password: p})
|
||
});
|
||
const d = await r.json();
|
||
if (d.ok) {
|
||
localStorage.setItem('current_user', d.username);
|
||
localStorage.setItem('is_admin', d.is_admin || '');
|
||
localStorage.setItem('logged_in', 'true');
|
||
showLoggedIn(d.username);
|
||
if (d.is_admin) document.getElementById('gen-code-btn').style.display = '';
|
||
document.getElementById('login-pass').value = '';
|
||
} else {
|
||
showLoginErr(d.error || '登录失败');
|
||
}
|
||
} catch(e) { showLoginErr('连接失败'); }
|
||
}
|
||
|
||
function showLoginErr(msg) {
|
||
const el = document.getElementById('login-err');
|
||
el.textContent = msg; el.style.display = 'block';
|
||
setTimeout(() => el.style.display = 'none', 3000);
|
||
}
|
||
|
||
function showLoggedIn(username) {
|
||
document.getElementById('login-card').style.display = 'none';
|
||
document.getElementById('logged-in-card').style.display = 'block';
|
||
document.getElementById('user-name').textContent = username;
|
||
document.getElementById('user-avatar').textContent = username[0].toUpperCase();
|
||
document.getElementById('login-toggle').textContent = username;
|
||
unlockChat();
|
||
unlockDownloads();
|
||
}
|
||
|
||
function logout() {
|
||
localStorage.removeItem('current_user');
|
||
localStorage.removeItem('is_admin');
|
||
localStorage.removeItem('logged_in');
|
||
document.getElementById('logged-in-card').style.display = 'none';
|
||
document.getElementById('login-card').style.display = 'block';
|
||
document.getElementById('login-toggle').textContent = '登录';
|
||
document.getElementById('login-user').value = '';
|
||
lockChat();
|
||
lockDownloads();
|
||
}
|
||
|
||
function goToRepo() {
|
||
const u = localStorage.getItem('current_user');
|
||
if (u) window.location.href = '/code/' + u;
|
||
}
|
||
|
||
function toggleLogin() {
|
||
document.getElementById('login-area').scrollIntoView({behavior:'smooth'});
|
||
}
|
||
|
||
// ─── 聊天 ───────────────────────
|
||
async function sendChat() {
|
||
const input = document.getElementById('chat-input');
|
||
const msg = input.value.trim();
|
||
if (!msg || isStreaming) return;
|
||
|
||
input.value = '';
|
||
appendMessage('user', msg);
|
||
chatHistory.push({role:'user', content: msg});
|
||
|
||
const box = document.getElementById('chat-box');
|
||
const status = document.getElementById('chat-status');
|
||
const thinking = document.getElementById('chat-thinking');
|
||
|
||
isStreaming = true;
|
||
input.disabled = true;
|
||
document.getElementById('chat-send').disabled = true;
|
||
thinking.style.display = 'flex';
|
||
status.textContent = '';
|
||
|
||
// 添加占位
|
||
const respDiv = document.createElement('div');
|
||
respDiv.className = 'chat-msg bot';
|
||
respDiv.innerHTML = '<span class="name">' + (currentPersona === 'zhuyuan' ? '铸渊' : '霜砚') + ':</span><span class="content" id="streaming-content"></span>';
|
||
box.appendChild(respDiv);
|
||
box.scrollTop = box.scrollHeight;
|
||
|
||
const contentEl = document.getElementById('streaming-content');
|
||
let fullText = '';
|
||
|
||
try {
|
||
const user = localStorage.getItem('current_user') || (isTempUser ? tempCode : '');
|
||
const res = await fetch('/api/chat-v2', {
|
||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({
|
||
persona: currentPersona,
|
||
engine: currentEngine,
|
||
message: msg,
|
||
history: chatHistory.slice(-60),
|
||
user: user,
|
||
isTemp: isTempUser
|
||
})
|
||
});
|
||
|
||
const reader = res.body.getReader();
|
||
const decoder = new TextDecoder();
|
||
let buffer = '';
|
||
|
||
while (true) {
|
||
const {done, value} = await reader.read();
|
||
if (done) break;
|
||
buffer += decoder.decode(value, {stream: true});
|
||
|
||
const lines = buffer.split('\n');
|
||
buffer = lines.pop() || '';
|
||
|
||
for (const line of lines) {
|
||
const t = line.trim();
|
||
if (!t || t.startsWith(':')) continue;
|
||
if (t === 'data: [DONE]') continue;
|
||
if (t.startsWith('data: ')) {
|
||
try {
|
||
const data = JSON.parse(t.slice(6));
|
||
if (data.token) {
|
||
fullText += data.token;
|
||
contentEl.textContent = fullText;
|
||
box.scrollTop = box.scrollHeight;
|
||
}
|
||
if (data.type === 'tool-call') {
|
||
showToolMsg('🔧 正在调用 ' + data.name + '...', false);
|
||
}
|
||
if (data.type === 'tool-result') {
|
||
showToolMsg('✅ ' + data.name + ' 已返回真实结果', false);
|
||
}
|
||
if (data.type === 'tool-unavailable') {
|
||
showToolMsg('⚠️ 当前没有「' + data.name + '」工具', true);
|
||
}
|
||
} catch(e) {}
|
||
}
|
||
}
|
||
}
|
||
} catch(e) {
|
||
contentEl.textContent = '连接失败: ' + e.message;
|
||
}
|
||
|
||
thinking.style.display = 'none';
|
||
isStreaming = false;
|
||
input.disabled = false;
|
||
document.getElementById('chat-send').disabled = false;
|
||
|
||
if (fullText) {
|
||
chatHistory.push({role:'assistant', content: fullText});
|
||
}
|
||
|
||
// 注销流式内容元素的id
|
||
respDiv.querySelector('.content').removeAttribute('id');
|
||
}
|
||
|
||
function appendMessage(role, text) {
|
||
const box = document.getElementById('chat-box');
|
||
const div = document.createElement('div');
|
||
div.className = 'chat-msg ' + role;
|
||
const name = role === 'user' ? '你' : (currentPersona === 'zhuyuan' ? '铸渊' : '霜砚');
|
||
div.innerHTML = '<span class="name">' + name + ':</span><span class="content">' + escapeHtml(text) + '</span>';
|
||
box.appendChild(div);
|
||
box.scrollTop = box.scrollHeight;
|
||
}
|
||
|
||
function showToolMsg(text, unavailable) {
|
||
const box = document.getElementById('chat-box');
|
||
const div = document.createElement('div');
|
||
div.className = 'chat-tool-msg' + (unavailable ? ' unavailable' : '');
|
||
div.textContent = text;
|
||
box.appendChild(div);
|
||
box.scrollTop = box.scrollHeight;
|
||
}
|
||
|
||
function escapeHtml(t) {
|
||
return t.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
}
|
||
|
||
// ─── 聊天锁定/解锁 ────────────────
|
||
function unlockChat() {
|
||
document.getElementById('chat-input').disabled = false;
|
||
document.getElementById('chat-send').disabled = false;
|
||
document.getElementById('chat-status').textContent = '已登录 · 可开始对话';
|
||
}
|
||
function lockChat() {
|
||
document.getElementById('chat-input').disabled = true;
|
||
document.getElementById('chat-send').disabled = true;
|
||
document.getElementById('chat-status').textContent = '请登录后开始对话';
|
||
}
|
||
|
||
// ─── 下载锁定/解锁 ────────────────
|
||
function unlockDownloads() {
|
||
document.getElementById('dl-locked').style.display = 'none';
|
||
document.getElementById('dl-grid').style.display = 'grid';
|
||
document.getElementById('dl-tag').textContent = '内测 · 已登录';
|
||
}
|
||
function lockDownloads() {
|
||
document.getElementById('dl-locked').style.display = 'block';
|
||
document.getElementById('dl-grid').style.display = 'none';
|
||
document.getElementById('dl-tag').textContent = '登录后可下载';
|
||
}
|
||
|
||
// ─── 临时访问码 ──────────────────
|
||
async function genTempCode() {
|
||
try {
|
||
const r = await fetch('/api/chat-v2/generate-temp-code', {
|
||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({username: localStorage.getItem('current_user'), isAdmin: !!localStorage.getItem('is_admin')})
|
||
});
|
||
const d = await r.json();
|
||
if (d.ok) {
|
||
const el = document.getElementById('temp-code-result');
|
||
el.style.display = 'block';
|
||
el.innerHTML = '🔑 临时访问码:<strong>' + d.code + '</strong><br><span style="font-size:12px;color:var(--faint)">对方输入后可体验20条对话</span>';
|
||
} else {
|
||
alert(d.message || '生成失败');
|
||
}
|
||
} catch(e) { alert('生成失败: ' + e.message); }
|
||
}
|
||
|
||
async function validateTempCode() {
|
||
const code = document.getElementById('temp-code-val').value.trim();
|
||
if (!code) return;
|
||
try {
|
||
const r = await fetch('/api/chat-v2/validate-temp-code', {
|
||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({code})
|
||
});
|
||
const d = await r.json();
|
||
if (d.ok) {
|
||
isTempUser = true;
|
||
tempCode = code;
|
||
document.getElementById('temp-code-input').style.display = 'none';
|
||
unlockChat();
|
||
document.getElementById('chat-status').textContent = '临时访问模式 · 剩余 ' + d.remain + ' 条';
|
||
} else {
|
||
document.getElementById('temp-code-err').textContent = d.message;
|
||
document.getElementById('temp-code-err').style.display = 'block';
|
||
}
|
||
} catch(e) { alert('验证失败'); }
|
||
}
|
||
|
||
|
||
async function connectNotion(){try{window.location.href="/api/notion/connect?user_key=default"}catch(e){}}
|
||
async function checkNt(){try{const r=await fetch("/api/notion/status");const d=await r.json();if(!d.ok||!d.connected_users||!d.connected_users.length)return;document.getElementById("notion-label").textContent="✅ "+d.connected_users[0].workspace}catch(e){}}
|
||
setTimeout(checkNt,3000);
|
||
async function sendMessage(){const n=(document.getElementById("msg-name").value||"").trim()||"匿名";const m=(document.getElementById("msg-content").value||"").trim();if(!m){document.getElementById("msg-status").textContent="请写入内容";return}try{const r=await fetch("/api/bulletin",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n,title:(document.getElementById("msg-title").value||"").trim(),content:m})});const d=await r.json();if(d.ok){document.getElementById("msg-status").textContent="✅ 留言已送达";document.getElementById("msg-content").value=""}else{document.getElementById("msg-status").textContent=d.message||"发送失败"}}catch(e){document.getElementById("msg-status").textContent="连接失败"}}
|
||
// ─── 自动恢复登录 ────────────────
|
||
(function(){
|
||
const u = localStorage.getItem('current_user');
|
||
if (u && localStorage.getItem('logged_in') === 'true') {
|
||
showLoggedIn(u);
|
||
if (localStorage.getItem('is_admin')) {
|
||
document.getElementById('gen-code-btn').style.display = '';
|
||
}
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|