1475 lines
66 KiB
HTML
1475 lines
66 KiB
HTML
<!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="光湖智库 · 智能书库 · 团队成员阅读系统">
|
||
<meta name="theme-color" content="#050810">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<title>光湖智库 · 智能书库</title>
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'><stop offset='0%25' stop-color='%23a78bfa'/><stop offset='100%25' stop-color='%2322d3ee'/></linearGradient></defs><rect rx='20' width='100' height='100' fill='%23050810'/><text x='50' y='68' text-anchor='middle' font-size='48' font-weight='900' font-family='serif' fill='url(%23g)'>库</text></svg>">
|
||
<style>
|
||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||
:root{
|
||
--bg:#050810;--panel:rgba(12,18,40,0.94);
|
||
--card:rgba(14,20,48,0.6);--card-border:rgba(167,139,250,0.12);
|
||
--card-hover:rgba(167,139,250,0.22);
|
||
--purple:#a78bfa;--violet:#c4b5fd;--cyan:#22d3ee;--blue:#60a5fa;
|
||
--ok:#34d399;--warn:#fbbf24;--err:#f87171;
|
||
--text:#eaf0ff;--dim:#94a7d0;--muted:rgba(120,150,200,0.4);
|
||
--border:rgba(167,139,250,0.1);
|
||
--font:-apple-system,BlinkMacSystemFont,'PingFang SC','Helvetica Neue','Microsoft YaHei',sans-serif;
|
||
--mono:'SF Mono','JetBrains Mono','Fira Code',monospace
|
||
}
|
||
html{font-size:16px;scroll-behavior:smooth}
|
||
body{
|
||
font-family:var(--font);background:var(--bg);color:var(--text);
|
||
min-height:100vh;overflow-x:hidden;line-height:1.7;
|
||
-webkit-font-smoothing:antialiased
|
||
}
|
||
a{color:var(--cyan);text-decoration:none;transition:color .3s}
|
||
a:hover{color:var(--purple)}
|
||
::-webkit-scrollbar{width:5px}
|
||
::-webkit-scrollbar-thumb{background:rgba(167,139,250,0.15);border-radius:3px}
|
||
|
||
/* ═══ Background ═══ */
|
||
.cosmos{position:fixed;inset:0;z-index:0;pointer-events:none}
|
||
#starCanvas{position:absolute;inset:0;width:100%;height:100%}
|
||
.nebula{position:fixed;border-radius:50%;filter:blur(120px);opacity:.06;pointer-events:none;z-index:0}
|
||
.neb-1{width:55vw;height:50vh;background:radial-gradient(circle,var(--purple),transparent);top:-10%;right:-5%;animation:nebDrift 22s ease-in-out infinite alternate}
|
||
.neb-2{width:45vw;height:40vh;background:radial-gradient(circle,var(--cyan),transparent);bottom:5%;left:-8%;animation:nebDrift 28s ease-in-out infinite alternate-reverse}
|
||
.neb-3{width:35vw;height:30vh;background:radial-gradient(circle,var(--blue),transparent);top:40%;left:30%;animation:nebDrift 32s ease-in-out infinite alternate;opacity:.04}
|
||
@keyframes nebDrift{0%{transform:translate(0,0) scale(1)}100%{transform:translate(15px,-10px) scale(1.05)}}
|
||
|
||
.lake{position:fixed;bottom:0;left:0;right:0;height:18vh;z-index:0;pointer-events:none;
|
||
background:linear-gradient(to top,rgba(167,139,250,0.02),transparent);
|
||
animation:lakeB 6s ease-in-out infinite alternate}
|
||
.lake::before{content:'';position:absolute;bottom:0;left:8%;right:8%;height:1px;
|
||
background:linear-gradient(90deg,transparent,rgba(167,139,250,0.12),rgba(34,211,238,0.1),transparent)}
|
||
@keyframes lakeB{0%{opacity:.5}100%{opacity:1}}
|
||
|
||
/* ═══ Particles ═══ */
|
||
.particles{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
|
||
.particle{position:absolute;width:3px;height:3px;border-radius:50%;opacity:0;
|
||
animation:particleFloat linear infinite}
|
||
@keyframes particleFloat{
|
||
0%{opacity:0;transform:translateY(100vh) scale(0)}
|
||
10%{opacity:.6}90%{opacity:.6}
|
||
100%{opacity:0;transform:translateY(-20vh) scale(1)}
|
||
}
|
||
|
||
/* ═══ Layout ═══ */
|
||
.page{position:relative;z-index:1;min-height:100vh;display:flex;flex-direction:column}
|
||
|
||
/* ═══ Header ═══ */
|
||
.header{
|
||
padding:16px 28px;display:flex;align-items:center;justify-content:space-between;
|
||
border-bottom:1px solid var(--border);backdrop-filter:blur(16px);
|
||
background:rgba(5,8,16,0.85);position:sticky;top:0;z-index:50;transition:box-shadow .3s
|
||
}
|
||
.header.scrolled{box-shadow:0 4px 30px rgba(0,0,0,0.3)}
|
||
.header-left{display:flex;align-items:center;gap:12px}
|
||
.header-glyph{
|
||
width:38px;height:38px;border-radius:10px;
|
||
background:linear-gradient(135deg,rgba(167,139,250,0.12),rgba(34,211,238,0.08));
|
||
border:1px solid rgba(167,139,250,0.2);
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-size:17px;font-weight:900;font-family:serif;color:var(--purple);
|
||
animation:glyphB 5s ease-in-out infinite;transition:transform .3s
|
||
}
|
||
.header-glyph:hover{transform:rotate(10deg) scale(1.1)}
|
||
@keyframes glyphB{0%,100%{box-shadow:0 0 10px rgba(167,139,250,0.1)}50%{box-shadow:0 0 28px rgba(167,139,250,0.25)}}
|
||
.header-title{font-size:16px;font-weight:700;letter-spacing:1px}
|
||
.header-sub{font-size:11px;color:var(--dim);display:block;margin-top:2px}
|
||
.header-right{display:flex;align-items:center;gap:12px}
|
||
.back-btn{
|
||
font-size:12px;color:var(--dim);display:flex;align-items:center;gap:5px;
|
||
padding:7px 14px;border-radius:8px;border:1px solid var(--border);
|
||
background:transparent;cursor:pointer;transition:all .25s;font-family:var(--font)
|
||
}
|
||
.back-btn:hover{color:var(--cyan);border-color:rgba(34,211,238,0.25);transform:translateY(-1px)}
|
||
|
||
/* ═══ Content ═══ */
|
||
.content{flex:1;padding:32px 28px 48px;max-width:1100px;margin:0 auto;width:100%}
|
||
|
||
/* ═══ Hero ═══ */
|
||
.hero{text-align:center;margin-bottom:40px;animation:heroIn 1s ease-out}
|
||
@keyframes heroIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
|
||
.hero-icon{
|
||
width:72px;height:72px;margin:0 auto 20px;border-radius:18px;
|
||
background:linear-gradient(135deg,rgba(167,139,250,0.15),rgba(34,211,238,0.08));
|
||
border:1px solid rgba(167,139,250,0.2);
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-size:32px;font-weight:900;font-family:serif;color:var(--purple);
|
||
animation:glyphB 5s ease-in-out infinite;position:relative
|
||
}
|
||
.hero-icon::after{content:'';position:absolute;inset:-4px;border-radius:22px;
|
||
background:linear-gradient(135deg,rgba(167,139,250,0.1),rgba(34,211,238,0.05));
|
||
animation:ringPulse 4s ease-in-out infinite;z-index:-1}
|
||
@keyframes ringPulse{0%,100%{transform:scale(1);opacity:.5}50%{transform:scale(1.1);opacity:0}}
|
||
.hero h1{font-size:26px;font-weight:700;letter-spacing:2px;margin-bottom:8px;
|
||
background:linear-gradient(135deg,var(--text),var(--purple));
|
||
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
||
.hero p{font-size:14px;color:var(--dim);line-height:1.8}
|
||
|
||
/* ═══ Status Bar ═══ */
|
||
.status-bar{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:32px;animation:fadeUp 1.2s ease-out}
|
||
@keyframes fadeUp{from{opacity:0;transform:translateY(15px)}to{opacity:1;transform:translateY(0)}}
|
||
.stat-card{
|
||
flex:1;min-width:140px;padding:18px 20px;border-radius:14px;
|
||
background:var(--card);border:1px solid var(--card-border);
|
||
backdrop-filter:blur(8px);transition:all .3s;position:relative;overflow:hidden
|
||
}
|
||
.stat-card:hover{border-color:var(--card-hover);transform:translateY(-2px)}
|
||
.stat-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;
|
||
background:linear-gradient(90deg,var(--purple),var(--cyan));opacity:0;transition:opacity .3s}
|
||
.stat-card:hover::before{opacity:.5}
|
||
.stat-num{font-size:28px;font-weight:700;font-family:var(--mono);color:var(--purple);margin-bottom:2px;transition:transform .3s}
|
||
.stat-card:hover .stat-num{transform:scale(1.05)}
|
||
.stat-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:1px}
|
||
|
||
/* ═══ Section ═══ */
|
||
.section-title{
|
||
font-size:12px;font-weight:700;color:var(--dim);text-transform:uppercase;
|
||
letter-spacing:1.5px;font-family:var(--mono);margin-bottom:16px;
|
||
display:flex;align-items:center;gap:8px
|
||
}
|
||
.section-title::after{content:'';flex:1;height:1px;background:var(--border)}
|
||
|
||
/* ═══ Tabs ═══ */
|
||
.tab-bar{display:flex;gap:4px;margin-bottom:20px;padding:4px;border-radius:12px;
|
||
background:rgba(14,20,48,0.4);border:1px solid var(--border);width:fit-content}
|
||
.tab-btn{padding:8px 18px;border-radius:8px;border:none;background:transparent;
|
||
color:var(--dim);font-size:13px;font-family:var(--font);cursor:pointer;
|
||
transition:all .25s;white-space:nowrap}
|
||
.tab-btn:hover{color:var(--text)}
|
||
.tab-btn.active{background:rgba(167,139,250,0.12);color:var(--purple);box-shadow:0 2px 8px rgba(167,139,250,0.1)}
|
||
|
||
/* ═══ Search Bar ═══ */
|
||
.search-wrap{margin-bottom:28px;animation:fadeUp 1.4s ease-out}
|
||
.search-bar{
|
||
display:flex;gap:10px;padding:6px;border-radius:14px;
|
||
background:var(--card);border:1px solid var(--card-border);backdrop-filter:blur(8px);
|
||
transition:border-color .3s,box-shadow .3s
|
||
}
|
||
.search-bar:focus-within{border-color:rgba(167,139,250,0.3);box-shadow:0 0 20px rgba(167,139,250,0.06)}
|
||
.search-input{
|
||
flex:1;background:transparent;border:none;padding:12px 16px;
|
||
color:var(--text);font-size:14px;font-family:var(--font);outline:none
|
||
}
|
||
.search-input::placeholder{color:var(--muted)}
|
||
.search-btn{
|
||
padding:10px 20px;border:1px solid rgba(167,139,250,0.2);border-radius:10px;
|
||
background:rgba(167,139,250,0.08);color:var(--purple);font-size:13px;font-weight:600;
|
||
cursor:pointer;transition:all .25s;font-family:var(--font);white-space:nowrap
|
||
}
|
||
.search-btn:hover{background:rgba(167,139,250,0.15);border-color:rgba(167,139,250,0.35);
|
||
transform:translateY(-1px);box-shadow:0 4px 12px rgba(167,139,250,0.1)}
|
||
.search-btn:active{transform:translateY(0)}
|
||
|
||
/* ═══ Search Results ═══ */
|
||
.search-results{margin-top:12px;display:none}
|
||
.search-result-card{
|
||
display:flex;align-items:center;justify-content:space-between;gap:12px;
|
||
padding:14px 18px;border-radius:12px;margin-bottom:8px;
|
||
background:var(--card);border:1px solid var(--card-border);
|
||
transition:all .3s;animation:slideIn .3s ease-out
|
||
}
|
||
.search-result-card:hover{border-color:var(--card-hover);transform:translateX(4px)}
|
||
@keyframes slideIn{from{opacity:0;transform:translateX(-10px)}to{opacity:1;transform:translateX(0)}}
|
||
.result-info{flex:1;min-width:0}
|
||
.result-title{font-size:14px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.result-meta{font-size:11px;color:var(--dim);margin-top:2px}
|
||
.result-actions{display:flex;gap:6px;flex-shrink:0}
|
||
.action-btn{
|
||
padding:6px 12px;border-radius:8px;border:1px solid var(--border);
|
||
background:transparent;color:var(--dim);font-size:11px;cursor:pointer;
|
||
font-family:var(--font);transition:all .25s;white-space:nowrap
|
||
}
|
||
.action-btn:hover{transform:translateY(-1px)}
|
||
.action-btn.read{color:var(--cyan);border-color:rgba(34,211,238,0.25)}
|
||
.action-btn.read:hover{background:rgba(34,211,238,0.08)}
|
||
.action-btn.download{color:var(--ok);border-color:rgba(52,211,153,0.25)}
|
||
.action-btn.download:hover{background:rgba(52,211,153,0.08)}
|
||
|
||
/* ═══ Download Panel ═══ */
|
||
.dl-panel{
|
||
margin-top:16px;padding:16px 18px;border-radius:12px;
|
||
background:rgba(167,139,250,0.03);border:1px solid var(--card-border);
|
||
display:none;animation:fadeUp .4s ease-out
|
||
}
|
||
.dl-fields{display:flex;gap:10px;flex-wrap:wrap}
|
||
.dl-input{
|
||
flex:2;min-width:120px;background:rgba(5,8,16,0.4);border:1px solid var(--border);
|
||
border-radius:8px;padding:10px 14px;color:var(--text);font-size:13px;
|
||
font-family:var(--font);outline:none;transition:border-color .25s
|
||
}
|
||
.dl-input:focus{border-color:rgba(167,139,250,0.3)}
|
||
.dl-input::placeholder{color:var(--muted)}
|
||
.dl-select{
|
||
flex:1;min-width:110px;background:rgba(5,8,16,0.4);border:1px solid var(--border);
|
||
border-radius:8px;padding:10px;color:var(--text);font-size:13px;
|
||
font-family:var(--font);outline:none;cursor:pointer
|
||
}
|
||
.dl-select option{background:#0c1228}
|
||
|
||
/* ═══ Upload Zone ═══ */
|
||
.upload-zone{
|
||
margin-bottom:28px;padding:32px 24px;border-radius:14px;
|
||
background:var(--card);border:2px dashed var(--card-border);
|
||
text-align:center;cursor:pointer;transition:all .35s;position:relative;overflow:hidden
|
||
}
|
||
.upload-zone:hover,.upload-zone.dragover{
|
||
border-color:rgba(167,139,250,0.35);background:rgba(167,139,250,0.03);
|
||
transform:translateY(-2px);box-shadow:0 8px 30px rgba(167,139,250,0.06)
|
||
}
|
||
.upload-zone.dragover{border-color:rgba(34,211,238,0.4)}
|
||
.upload-icon{font-size:36px;margin-bottom:12px;opacity:.6;transition:transform .3s}
|
||
.upload-zone:hover .upload-icon{transform:translateY(-4px) scale(1.1)}
|
||
.upload-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:6px}
|
||
.upload-desc{font-size:12px;color:var(--dim);line-height:1.8}
|
||
.upload-formats{font-size:10px;color:var(--muted);margin-top:8px;font-family:var(--mono)}
|
||
.upload-progress{display:none;margin-top:12px}
|
||
.upload-bar{width:100%;height:4px;border-radius:2px;background:var(--border);overflow:hidden}
|
||
.upload-bar-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,var(--purple),var(--cyan));
|
||
transition:width .3s ease-out;width:0}
|
||
.upload-status{font-size:11px;color:var(--dim);margin-top:6px}
|
||
input[type="file"]{display:none}
|
||
|
||
/* ═══ Book Grid ═══ */
|
||
.book-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px;margin-bottom:32px}
|
||
.book-card{
|
||
background:var(--card);border:1px solid var(--card-border);border-radius:14px;
|
||
padding:20px 22px;backdrop-filter:blur(8px);transition:all .35s;cursor:pointer;
|
||
position:relative;overflow:hidden
|
||
}
|
||
.book-card:hover{border-color:var(--card-hover);box-shadow:0 8px 32px rgba(167,139,250,0.06);transform:translateY(-2px)}
|
||
.book-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;
|
||
background:linear-gradient(90deg,var(--purple),var(--cyan));opacity:0;transition:opacity .3s}
|
||
.book-card:hover::before{opacity:.4}
|
||
.book-title{font-size:15px;font-weight:700;color:var(--text);margin-bottom:4px;
|
||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.book-author{font-size:12px;color:var(--dim);margin-bottom:8px}
|
||
.book-meta{display:flex;gap:6px;flex-wrap:wrap}
|
||
.book-tag{font-size:10px;padding:2px 8px;border-radius:6px;font-family:var(--mono);
|
||
background:rgba(167,139,250,0.08);color:var(--purple);border:1px solid rgba(167,139,250,0.15)}
|
||
.book-actions-hover{display:none;position:absolute;bottom:0;left:0;right:0;padding:12px 16px;
|
||
background:linear-gradient(to top,rgba(5,8,16,0.95),transparent);gap:8px;justify-content:center}
|
||
.book-card:hover .book-actions-hover{display:flex}
|
||
|
||
/* ═══ Module Cards ═══ */
|
||
.module-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;margin-bottom:32px}
|
||
.mod-card{
|
||
background:var(--card);border:1px solid var(--card-border);border-radius:14px;
|
||
padding:22px 24px;backdrop-filter:blur(8px);transition:all .35s;
|
||
position:relative;overflow:hidden;cursor:default
|
||
}
|
||
.mod-card:hover{border-color:var(--card-hover);box-shadow:0 8px 40px rgba(167,139,250,0.06);transform:translateY(-3px)}
|
||
.mod-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;
|
||
background:linear-gradient(90deg,var(--purple),var(--cyan));opacity:.2;transition:opacity .3s}
|
||
.mod-card:hover::before{opacity:.5}
|
||
.mod-icon{font-size:22px;margin-bottom:10px}
|
||
.mod-name{font-size:15px;font-weight:700;color:var(--text);margin-bottom:6px}
|
||
.mod-desc{font-size:13px;color:var(--dim);line-height:1.7;margin-bottom:12px}
|
||
.mod-status{font-size:11px;font-family:var(--mono);display:flex;align-items:center;gap:6px}
|
||
.mod-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
|
||
.mod-dot.ok{background:var(--ok);box-shadow:0 0 6px rgba(52,211,153,0.4)}
|
||
.mod-dot.err{background:var(--err);box-shadow:0 0 6px rgba(248,113,113,0.4)}
|
||
.mod-dot.building{background:var(--warn);box-shadow:0 0 6px rgba(251,191,36,0.4);animation:pulse 1.5s infinite}
|
||
.mod-dot.plan{background:var(--muted)}
|
||
@keyframes pulse{0%,100%{opacity:.6}50%{opacity:1}}
|
||
|
||
/* ═══ Agent Chat ═══ */
|
||
.agent-chat{
|
||
background:var(--card);border:1px solid var(--card-border);border-radius:14px;
|
||
padding:0;backdrop-filter:blur(8px);overflow:hidden;transition:all .3s;margin-bottom:28px
|
||
}
|
||
.agent-header{
|
||
padding:14px 20px;border-bottom:1px solid var(--border);
|
||
display:flex;align-items:center;justify-content:space-between;background:rgba(167,139,250,0.03)
|
||
}
|
||
.agent-header-left{display:flex;align-items:center;gap:10px}
|
||
.agent-avatar{width:32px;height:32px;border-radius:10px;
|
||
background:linear-gradient(135deg,rgba(167,139,250,0.2),rgba(34,211,238,0.1));
|
||
display:flex;align-items:center;justify-content:center;font-size:16px;
|
||
animation:agentPulse 3s ease-in-out infinite}
|
||
@keyframes agentPulse{0%,100%{box-shadow:0 0 8px rgba(167,139,250,0.15)}50%{box-shadow:0 0 16px rgba(167,139,250,0.3)}}
|
||
.agent-name{font-size:13px;font-weight:700;color:var(--purple)}
|
||
.agent-status{font-size:10px;color:var(--ok);display:flex;align-items:center;gap:4px}
|
||
.agent-status-dot{width:5px;height:5px;border-radius:50%;background:var(--ok);animation:pulse 2s infinite}
|
||
.agent-model-badge{font-size:10px;padding:2px 8px;border-radius:6px;
|
||
background:rgba(34,211,238,0.08);color:var(--cyan);border:1px solid rgba(34,211,238,0.15);font-family:var(--mono)}
|
||
.chat-body{height:300px;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth}
|
||
.chat-msg{margin-bottom:12px;animation:msgIn .3s ease-out}
|
||
@keyframes msgIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
||
.chat-msg-user{display:flex;justify-content:flex-end}
|
||
.chat-msg-agent{display:flex;justify-content:flex-start}
|
||
.chat-bubble{max-width:80%;padding:10px 14px;border-radius:12px;font-size:13px;line-height:1.8;
|
||
white-space:pre-wrap;word-break:break-word}
|
||
.chat-bubble-user{background:rgba(167,139,250,0.12);border:1px solid rgba(167,139,250,0.15);
|
||
color:var(--text);border-bottom-right-radius:4px}
|
||
.chat-bubble-agent{background:rgba(34,211,238,0.06);border:1px solid rgba(34,211,238,0.1);
|
||
color:var(--text);border-bottom-left-radius:4px}
|
||
.chat-bubble-agent .model-tag{font-size:9px;color:var(--muted);font-family:var(--mono);display:block;margin-top:4px}
|
||
.chat-input-wrap{padding:12px 16px;border-top:1px solid var(--border);display:flex;gap:8px;align-items:center}
|
||
.chat-input{
|
||
flex:1;background:rgba(5,8,16,0.4);border:1px solid var(--border);
|
||
border-radius:10px;padding:10px 14px;color:var(--text);font-size:13px;
|
||
font-family:var(--font);outline:none;resize:none;min-height:40px;max-height:100px;transition:border-color .25s
|
||
}
|
||
.chat-input:focus{border-color:rgba(167,139,250,0.3)}
|
||
.chat-input::placeholder{color:var(--muted)}
|
||
.chat-send-btn{
|
||
width:40px;height:40px;border-radius:10px;border:none;
|
||
background:linear-gradient(135deg,rgba(167,139,250,0.15),rgba(34,211,238,0.1));
|
||
color:var(--purple);cursor:pointer;display:flex;align-items:center;justify-content:center;
|
||
transition:all .25s;flex-shrink:0
|
||
}
|
||
.chat-send-btn:hover{background:linear-gradient(135deg,rgba(167,139,250,0.25),rgba(34,211,238,0.15));transform:scale(1.05)}
|
||
.chat-send-btn:disabled{opacity:.3;cursor:not-allowed;transform:none}
|
||
.chat-quick-actions{padding:8px 16px 12px;display:flex;gap:6px;flex-wrap:wrap}
|
||
.quick-btn{
|
||
font-size:10px;padding:5px 10px;border-radius:6px;border:1px solid var(--border);
|
||
background:transparent;color:var(--dim);cursor:pointer;font-family:var(--font);transition:all .2s;white-space:nowrap
|
||
}
|
||
.quick-btn:hover{color:var(--cyan);border-color:rgba(34,211,238,0.25);background:rgba(34,211,238,0.03)}
|
||
|
||
/* ═══ Typing Indicator ═══ */
|
||
.typing-indicator{display:flex;gap:4px;padding:4px 0}
|
||
.typing-dot{width:6px;height:6px;border-radius:50%;background:var(--dim);animation:typingBounce 1.4s infinite both}
|
||
.typing-dot:nth-child(2){animation-delay:.2s}
|
||
.typing-dot:nth-child(3){animation-delay:.4s}
|
||
@keyframes typingBounce{0%,80%,100%{transform:scale(0);opacity:.3}40%{transform:scale(1);opacity:1}}
|
||
|
||
/* ═══ Empty State ═══ */
|
||
.empty-state{text-align:center;padding:60px 24px;animation:fadeUp 1.6s ease-out}
|
||
.empty-icon{font-size:48px;margin-bottom:16px;opacity:.5}
|
||
.empty-title{font-size:18px;font-weight:600;color:var(--dim);margin-bottom:8px}
|
||
.empty-desc{font-size:13px;color:var(--muted);line-height:1.8;max-width:400px;margin:0 auto}
|
||
|
||
/* ═══ Reader Modal ═══ */
|
||
.reader-modal{display:none;position:fixed;inset:0;z-index:100;background:var(--bg);flex-direction:column;animation:readerIn .3s ease-out}
|
||
@keyframes readerIn{from{opacity:0}to{opacity:1}}
|
||
.reader-header{
|
||
display:flex;align-items:center;justify-content:space-between;padding:14px 20px;
|
||
border-bottom:1px solid var(--border);background:rgba(5,8,16,0.95);backdrop-filter:blur(12px);flex-shrink:0
|
||
}
|
||
.reader-body{flex:1;overflow-y:auto;padding:24px 20px 80px;max-width:720px;margin:0 auto;width:100%;font-size:16px;line-height:1.9;color:var(--text)}
|
||
.reader-nav{
|
||
position:fixed;bottom:0;left:0;right:0;display:flex;justify-content:space-between;
|
||
padding:12px 20px;background:rgba(5,8,16,0.95);border-top:1px solid var(--border);backdrop-filter:blur(12px)
|
||
}
|
||
|
||
/* ═══ Footer ═══ */
|
||
.footer{
|
||
text-align:center;padding:32px 24px;font-size:11px;color:var(--muted);
|
||
border-top:1px solid var(--border);letter-spacing:.5px
|
||
}
|
||
.footer-line{width:100px;height:1px;margin:0 auto 12px;
|
||
background:linear-gradient(90deg,transparent,rgba(167,139,250,0.12),transparent)}
|
||
|
||
/* ═══ Responsive ═══ */
|
||
@media(max-width:600px){
|
||
.content{padding:20px 16px 36px}
|
||
.hero h1{font-size:22px}
|
||
.module-grid,.book-grid{grid-template-columns:1fr}
|
||
.status-bar{flex-direction:column}
|
||
.header{padding:12px 16px}
|
||
.tab-bar{width:100%;overflow-x:auto}
|
||
.search-result-card{flex-direction:column;align-items:flex-start;gap:8px}
|
||
.result-actions{width:100%;justify-content:flex-end}
|
||
.chat-body{height:240px}
|
||
.dl-fields{flex-direction:column}
|
||
}
|
||
|
||
/* ═══ Toast ═══ */
|
||
.toast{
|
||
position:fixed;bottom:32px;left:50%;transform:translateX(-50%) translateY(100px);
|
||
padding:14px 24px;border-radius:12px;background:rgba(14,20,48,0.95);
|
||
border:1px solid rgba(167,139,250,0.2);color:var(--text);font-size:13px;
|
||
z-index:9999;backdrop-filter:blur(12px);max-width:90%;text-align:center;
|
||
opacity:0;transition:all .4s cubic-bezier(.175,.885,.32,1.275)
|
||
}
|
||
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
|
||
|
||
@media(prefers-reduced-motion:reduce){
|
||
*,*::before,*::after{animation-duration:0.01ms!important;transition-duration:0.01ms!important}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="cosmos">
|
||
<canvas id="starCanvas"></canvas>
|
||
<div class="nebula neb-1"></div>
|
||
<div class="nebula neb-2"></div>
|
||
<div class="nebula neb-3"></div>
|
||
</div>
|
||
<div class="lake"></div>
|
||
<div class="particles" id="particlesContainer"></div>
|
||
|
||
<div class="page">
|
||
|
||
<!-- Header -->
|
||
<header class="header" id="mainHeader">
|
||
<div class="header-left">
|
||
<div class="header-glyph">库</div>
|
||
<div>
|
||
<span class="header-title">光湖智库</span>
|
||
<span class="header-sub">智能书库 · 内部成员系统</span>
|
||
</div>
|
||
</div>
|
||
<div class="header-right">
|
||
<button class="back-btn" onclick="window.location.href='/'">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
||
返回首页
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="content">
|
||
|
||
<!-- Hero -->
|
||
<div class="hero">
|
||
<div class="hero-icon">库</div>
|
||
<h1>光湖智库 · 智能书库</h1>
|
||
<p>
|
||
光湖语言世界 · 团队内部智能阅读系统<br>
|
||
书库搜索 · 智能分章 · 在线阅读 · AI辅助 · 文稿上传
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Status -->
|
||
<div class="status-bar" id="statusBar">
|
||
<div class="stat-card">
|
||
<div class="stat-num" id="statBooks">—</div>
|
||
<div class="stat-label">书库总量</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-num" id="statMembers">—</div>
|
||
<div class="stat-label">团队成员</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-num" id="statToday">—</div>
|
||
<div class="stat-label">今日活跃</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-num" id="statChapters">—</div>
|
||
<div class="stat-label">已分章书籍</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ 搜索书 (统一入口) ═══ -->
|
||
<div class="section-title">🔍 搜索书</div>
|
||
<div class="search-wrap">
|
||
<div class="search-bar" id="mainSearchBar">
|
||
<input class="search-input" id="searchInput" type="text" placeholder="输入书名、作者搜索书库...">
|
||
<button class="search-btn" onclick="searchBooks()">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:middle;margin-right:4px"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||
搜索
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Search Results -->
|
||
<div class="search-results" id="searchResults"></div>
|
||
|
||
<!-- Download toggle -->
|
||
<div style="margin-top:12px;text-align:center">
|
||
<button class="quick-btn" onclick="toggleDownloadPanel()" style="font-size:11px">📥 从平台下载新书</button>
|
||
</div>
|
||
<div class="dl-panel" id="downloadPanel">
|
||
<div style="font-size:11px;color:var(--dim);margin-bottom:8px;line-height:1.6">
|
||
💡 系统会先匹配 <span id="dlSourceCount" style="color:var(--cyan)">--</span> 个开源书源数据库,找到则直接使用;未找到才从选定平台下载。
|
||
</div>
|
||
<div class="dl-fields">
|
||
<input class="dl-input" id="dlBookName" type="text" placeholder="书名" style="flex:2;min-width:120px">
|
||
<input class="dl-input" id="dlAuthor" type="text" placeholder="作者(选填)" style="flex:1;min-width:100px">
|
||
<select class="dl-select" id="dlPlatform">
|
||
<option value="qimao">七猫</option>
|
||
<option value="fanqie">番茄</option>
|
||
<option value="yuewen">阅文</option>
|
||
<option value="jjwxc">晋江</option>
|
||
</select>
|
||
<button class="search-btn" onclick="downloadBook()">搜索并下载</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ 上传文稿 ═══ -->
|
||
<div class="section-title">📤 上传文稿</div>
|
||
<div class="upload-zone" id="uploadZone" onclick="document.getElementById('fileInput').click()">
|
||
<input type="file" id="fileInput" accept=".txt,.epub,.html,.htm,.md,.docx">
|
||
<div class="upload-icon">📄</div>
|
||
<div class="upload-title">点击选择文件 或 拖拽文件到这里</div>
|
||
<div class="upload-desc">支持上传小说文稿,系统自动检测格式并转换<br>可在线阅读、自动分章整理</div>
|
||
<div class="upload-formats">支持格式: TXT · EPUB · HTML · Markdown · DOCX | 最大 50MB</div>
|
||
<div class="upload-progress" id="uploadProgress">
|
||
<div class="upload-bar"><div class="upload-bar-fill" id="uploadBarFill"></div></div>
|
||
<div class="upload-status" id="uploadStatus">准备上传...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ 书库 ═══ -->
|
||
<div class="section-title">📚 书库</div>
|
||
<div class="tab-bar">
|
||
<button class="tab-btn active" onclick="switchBookTab('all',this)">全部</button>
|
||
<button class="tab-btn" onclick="switchBookTab('downloaded',this)">已下载</button>
|
||
<button class="tab-btn" onclick="switchBookTab('uploaded',this)">已上传</button>
|
||
</div>
|
||
<div id="bookArea">
|
||
<div class="empty-state" id="emptyState">
|
||
<div class="empty-icon">📚</div>
|
||
<div class="empty-title">书库为空</div>
|
||
<div class="empty-desc">
|
||
搜索并下载书籍,或上传你的小说文稿。<br>
|
||
系统会自动分章,支持在线阅读。
|
||
</div>
|
||
</div>
|
||
<div class="book-grid" id="bookGrid" style="display:none"></div>
|
||
</div>
|
||
|
||
<!-- ═══ Reader Modal ═══ -->
|
||
<div class="reader-modal" id="readerModal">
|
||
<div class="reader-header">
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<button class="back-btn" onclick="closeReader()">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
||
返回
|
||
</button>
|
||
<span id="readerTitle" style="font-size:14px;font-weight:600;color:var(--text)"></span>
|
||
</div>
|
||
<div style="display:flex;gap:8px;align-items:center">
|
||
<button class="back-btn" onclick="changeFontSize(-1)" title="减小字号">A-</button>
|
||
<button class="back-btn" onclick="changeFontSize(1)" title="增大字号">A+</button>
|
||
<button class="back-btn" id="themeToggle" onclick="toggleReaderTheme()" title="切换主题">☀️</button>
|
||
</div>
|
||
</div>
|
||
<div class="reader-body" id="readerBody"></div>
|
||
<div class="reader-nav">
|
||
<button class="search-btn" id="prevChBtn" onclick="prevChapter()">上一章</button>
|
||
<span id="chapterInfo" style="font-size:12px;color:var(--dim);align-self:center;font-family:var(--mono)"></span>
|
||
<button class="search-btn" id="nextChBtn" onclick="nextChapter()">下一章</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ 团队书库 ═══ -->
|
||
<div class="section-title" style="margin-top:20px">📦 团队书库</div>
|
||
<div id="librarySection" style="margin-bottom:28px">
|
||
<div class="tab-bar" style="margin-bottom:12px">
|
||
<button class="tab-btn active" onclick="loadLibrary(null,this)">全部</button>
|
||
<button class="tab-btn" onclick="loadLibrary('男频',this)">男频</button>
|
||
<button class="tab-btn" onclick="loadLibrary('女频',this)">女频</button>
|
||
<button class="tab-btn" onclick="loadLibrary('无CP',this)">无CP</button>
|
||
<button class="tab-btn" onclick="loadLibrary('百合',this)">百合</button>
|
||
<button class="tab-btn" onclick="loadLibrary('耽美',this)">耽美</button>
|
||
<button class="tab-btn" onclick="loadLibrary('其他',this)">其他</button>
|
||
</div>
|
||
<div class="book-grid" id="libraryGrid"></div>
|
||
<div id="libraryEmpty" class="empty-state" style="padding:30px">
|
||
<div class="empty-icon">📦</div>
|
||
<div class="empty-title">团队书库为空</div>
|
||
<div class="empty-desc">下载的书籍会自动进入团队书库,也可以手动添加。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ AI Agent 对话 ═══ -->
|
||
<div class="section-title">🤖 AI 对话</div>
|
||
<div class="agent-chat" id="agentSection">
|
||
<div class="agent-header">
|
||
<div class="agent-header-left">
|
||
<div class="agent-avatar">🤖</div>
|
||
<div>
|
||
<div class="agent-name">智库 Agent</div>
|
||
<div class="agent-status" id="agentStatusWrap"><span class="agent-status-dot" id="agentStatusDot" style="background:var(--warn);animation:none"></span> <span id="agentStatusText">连接中...</span></div>
|
||
</div>
|
||
</div>
|
||
<div id="agentModelBadge" class="agent-model-badge" style="display:none">AI</div>
|
||
</div>
|
||
<div class="chat-body" id="agentChatBox"></div>
|
||
<div class="chat-input-wrap">
|
||
<textarea class="chat-input" id="agentInput" placeholder="和AI对话...(支持 DeepSeek / Kimi / 通义千问)" rows="1"></textarea>
|
||
<button class="chat-send-btn" id="agentSendBtn" onclick="sendAgentChat()">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m22 2-7 20-4-9-9-4z"/><path d="m22 2-10 10"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="chat-quick-actions">
|
||
<button class="quick-btn" onclick="sendAgentChat('帮助')">💡 帮助</button>
|
||
<button class="quick-btn" onclick="sendAgentChat('记忆')">🧠 我的记忆</button>
|
||
<button class="quick-btn" onclick="sendAgentChat('帮我分析一下最近在读的书')">📖 阅读分析</button>
|
||
<button class="quick-btn" onclick="sendAgentChat('给我推荐一些好书')">📚 推荐书籍</button>
|
||
<button class="quick-btn" onclick="window.open('/novel.html','_blank')">✍️ 打开创作台</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ 系统模块 ═══ -->
|
||
<div class="section-title" style="margin-top:20px">🛠️ 系统模块</div>
|
||
<div class="module-grid">
|
||
<div class="mod-card">
|
||
<div class="mod-icon">🛡️</div>
|
||
<div class="mod-name">语言保护罩</div>
|
||
<div class="mod-desc">Nginx蜜罐(L2) + fail2ban自动封禁(L3)。服务器永远不暴露真实IP。</div>
|
||
<div class="mod-status" id="modShield"><span class="mod-dot" id="modShieldDot"></span><span id="modShieldText" style="color:var(--dim)">检测中...</span></div>
|
||
</div>
|
||
<div class="mod-card">
|
||
<div class="mod-icon">📥</div>
|
||
<div class="mod-name">下载引擎</div>
|
||
<div class="mod-desc">整合开源数据库 + 七猫/番茄/阅文/晋江。优先匹配已有资源。</div>
|
||
<div class="mod-status" id="modDownload"><span class="mod-dot" id="modDownloadDot"></span><span id="modDownloadText" style="color:var(--dim)">检测中...</span></div>
|
||
</div>
|
||
<div class="mod-card">
|
||
<div class="mod-icon">✂️</div>
|
||
<div class="mod-name">智能分章 Agent</div>
|
||
<div class="mod-desc">AI识别章节规律,无章节标识时按内容结构分段。</div>
|
||
<div class="mod-status" id="modChapter"><span class="mod-dot" id="modChapterDot"></span><span id="modChapterText" style="color:var(--dim)">检测中...</span></div>
|
||
</div>
|
||
<div class="mod-card">
|
||
<div class="mod-icon">📖</div>
|
||
<div class="mod-name">在线阅读器</div>
|
||
<div class="mod-desc">自动格式检测。翻页、字体、深/亮色。进度自动存档。</div>
|
||
<div class="mod-status" id="modReader"><span class="mod-dot" id="modReaderDot"></span><span id="modReaderText" style="color:var(--dim)">检测中...</span></div>
|
||
</div>
|
||
<div class="mod-card">
|
||
<div class="mod-icon">📤</div>
|
||
<div class="mod-name">文稿上传</div>
|
||
<div class="mod-desc">支持 TXT/EPUB/HTML/MD/DOCX,自动格式检测转换,拖拽上传。</div>
|
||
<div class="mod-status" id="modUpload"><span class="mod-dot" id="modUploadDot"></span><span id="modUploadText" style="color:var(--dim)">检测中...</span></div>
|
||
</div>
|
||
<div class="mod-card">
|
||
<div class="mod-icon">🤖</div>
|
||
<div class="mod-name">AI 对话引擎</div>
|
||
<div class="mod-desc">接入 DeepSeek/Kimi/通义千问 + 第三方 API。自动降级,多模型备用。</div>
|
||
<div class="mod-status" id="modAI"><span class="mod-dot" id="modAIDot"></span><span id="modAIText" style="color:var(--dim)">检测中...</span></div>
|
||
</div>
|
||
<div class="mod-card">
|
||
<div class="mod-icon">🔗</div>
|
||
<div class="mod-name">开源书源</div>
|
||
<div class="mod-desc">已注册外部开源小说下载器数据库,优先匹配已有书源。</div>
|
||
<div class="mod-status" id="modSources"><span class="mod-dot" id="modSourcesDot"></span><span id="modSourcesText" style="color:var(--dim)">检测中...</span></div>
|
||
</div>
|
||
<div class="mod-card">
|
||
<div class="mod-icon">✍️</div>
|
||
<div class="mod-name">智能小说系统</div>
|
||
<div class="mod-desc">小说创作工作台。大纲/人物卡/章节管理。AI续写/改写。</div>
|
||
<div class="mod-status" id="modNovel"><span class="mod-dot" id="modNovelDot"></span><span id="modNovelText" style="color:var(--dim)">检测中...</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<!-- Footer -->
|
||
<footer class="footer">
|
||
<div class="footer-line"></div>
|
||
光湖智库 · 光湖语言世界 · 内部成员系统<br>
|
||
版权所有 © 冰朔 · TCS-0002∞ · 国作登字-2026-A-00037559
|
||
</footer>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
/* ═══ API Base ═══ */
|
||
var API = (location.hostname === 'guanghuyaoming.com')
|
||
? ''
|
||
: (location.hostname === 'novel.guanghuyaoming.com')
|
||
? ''
|
||
: 'http://127.0.0.1:4000';
|
||
var MEMBER_ID = 'member-default';
|
||
var currentBookTab = 'all';
|
||
var systemStatus = { api: false, ai: false, agent: false, sources: 0 };
|
||
|
||
/* ═══ Star Field ═══ */
|
||
(function(){
|
||
var c=document.getElementById('starCanvas');
|
||
if(!c)return;
|
||
var ctx=c.getContext('2d'),stars=[],w,h;
|
||
function resize(){w=c.width=window.innerWidth;h=c.height=window.innerHeight;initS();}
|
||
function initS(){
|
||
stars=[];
|
||
var n=Math.min(Math.floor(w*h/4000),200);
|
||
for(var i=0;i<n;i++)stars.push({x:Math.random()*w,y:Math.random()*h,r:Math.random()*1.1+0.2,a:Math.random()*0.5+0.2,speed:Math.random()*0.0008+0.0002,phase:Math.random()*Math.PI*2});
|
||
}
|
||
function draw(t){
|
||
ctx.clearRect(0,0,w,h);
|
||
for(var i=0;i<stars.length;i++){var s=stars[i];var alpha=s.a*(0.5+0.5*Math.sin(t*s.speed+s.phase));ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fillStyle='rgba(200,210,240,'+alpha+')';ctx.fill();}
|
||
requestAnimationFrame(draw);
|
||
}
|
||
window.addEventListener('resize',resize);resize();requestAnimationFrame(draw);
|
||
})();
|
||
|
||
/* ═══ Floating Particles ═══ */
|
||
(function(){
|
||
var container=document.getElementById('particlesContainer');
|
||
if(!container)return;
|
||
var colors=['rgba(167,139,250,0.4)','rgba(34,211,238,0.3)','rgba(96,165,250,0.3)'];
|
||
for(var i=0;i<15;i++){
|
||
var p=document.createElement('div');
|
||
p.className='particle';
|
||
p.style.left=Math.random()*100+'%';
|
||
p.style.width=p.style.height=(Math.random()*3+1)+'px';
|
||
p.style.background=colors[Math.floor(Math.random()*colors.length)];
|
||
p.style.animationDuration=(Math.random()*12+8)+'s';
|
||
p.style.animationDelay=(Math.random()*10)+'s';
|
||
container.appendChild(p);
|
||
}
|
||
})();
|
||
|
||
/* ═══ Header scroll effect ═══ */
|
||
window.addEventListener('scroll',function(){
|
||
var h=document.getElementById('mainHeader');
|
||
if(h)h.classList.toggle('scrolled',window.scrollY>20);
|
||
});
|
||
|
||
/* ═══ Toast ═══ */
|
||
function showToast(msg,type){
|
||
var existing=document.querySelector('.toast');
|
||
if(existing)existing.remove();
|
||
var t=document.createElement('div');
|
||
t.className='toast';
|
||
var borderColor=type==='error'?'rgba(248,113,113,0.3)':type==='ok'?'rgba(52,211,153,0.3)':'rgba(167,139,250,0.2)';
|
||
t.style.borderColor=borderColor;
|
||
t.textContent=msg;
|
||
document.body.appendChild(t);
|
||
requestAnimationFrame(function(){requestAnimationFrame(function(){t.classList.add('show');});});
|
||
setTimeout(function(){t.classList.remove('show');setTimeout(function(){t.remove()},400)},3000);
|
||
}
|
||
|
||
/* ═══ Fetch Helper ═══ */
|
||
function api(method,path,body){
|
||
var opts={method:method,headers:{'Content-Type':'application/json'}};
|
||
if(body)opts.body=JSON.stringify(body);
|
||
return fetch(API+path,opts).then(function(r){
|
||
if(!r.ok&&r.status===502)return{error:true,message:'智库节点服务未响应(502),请确认 novel-db 进程已启动'};
|
||
if(!r.ok&&r.status===504)return{error:true,message:'智库节点连接超时(504),服务器间网络可能不通'};
|
||
return r.json();
|
||
}).catch(function(e){return{error:true,message:'网络错误: '+(e.message||'无法连接到服务器')}});
|
||
}
|
||
|
||
function escHtml(s){return s?s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/'/g,''').replace(/"/g,'"'):'';}
|
||
|
||
/* ═══ Load Stats ═══ */
|
||
function loadStats(){
|
||
api('GET','/api/zhiku/stats').then(function(d){
|
||
if(d.error){
|
||
// API不可达
|
||
updateSystemHealth(false, null);
|
||
return;
|
||
}
|
||
document.getElementById('statBooks').textContent=(d.download?d.download.total_books:0)+(d.upload?d.upload.total_uploads:0);
|
||
document.getElementById('statMembers').textContent=d.reader?d.reader.total_members:0;
|
||
document.getElementById('statToday').textContent=d.download?d.download.completed_tasks:0;
|
||
document.getElementById('statChapters').textContent=d.chapter?d.chapter.total_chaptered_books:0;
|
||
if(d.ai&&d.ai.available_models&&d.ai.available_models.length>0){
|
||
var badge=document.getElementById('agentModelBadge');
|
||
badge.textContent=d.ai.available_models[0].name;
|
||
badge.style.display='';
|
||
}
|
||
updateSystemHealth(true, d);
|
||
});
|
||
}
|
||
|
||
/* ═══ System Health Check ═══ */
|
||
function setModStatus(id, ok, text) {
|
||
var dot = document.getElementById(id + 'Dot');
|
||
var txt = document.getElementById(id + 'Text');
|
||
if (dot) {
|
||
dot.className = 'mod-dot ' + (ok === true ? 'ok' : ok === false ? 'err' : '');
|
||
dot.style.background = ok === true ? 'var(--ok)' : ok === false ? 'var(--err)' : 'var(--warn)';
|
||
}
|
||
if (txt) {
|
||
txt.style.color = ok === true ? 'var(--ok)' : ok === false ? 'var(--err)' : 'var(--warn)';
|
||
txt.textContent = text;
|
||
}
|
||
}
|
||
|
||
function updateSystemHealth(apiOk, statsData) {
|
||
systemStatus.api = apiOk;
|
||
|
||
if (!apiOk) {
|
||
// API不可达 - 全部标记为离线
|
||
setModStatus('modShield', false, '无法连接');
|
||
setModStatus('modDownload', false, '无法连接');
|
||
setModStatus('modChapter', false, '无法连接');
|
||
setModStatus('modReader', false, '无法连接');
|
||
setModStatus('modUpload', false, '无法连接');
|
||
setModStatus('modAI', false, '无法连接');
|
||
setModStatus('modSources', false, '无法连接');
|
||
setModStatus('modNovel', false, '无法连接');
|
||
updateAgentStatus(false, 'API离线');
|
||
return;
|
||
}
|
||
|
||
// API可达 — 根据各模块统计更新状态
|
||
setModStatus('modShield', true, '已激活');
|
||
|
||
// 下载引擎
|
||
if (statsData && statsData.download) {
|
||
var dl = statsData.download;
|
||
var srcCount = dl.registered_sources || 0;
|
||
setModStatus('modDownload', true, '在线 · ' + dl.total_books + ' 本');
|
||
setModStatus('modSources', true, srcCount + ' 个数据源 · 索引 ' + (dl.local_index_count || 0));
|
||
systemStatus.sources = srcCount;
|
||
// 更新下载面板的数据源数量显示
|
||
var dlSrcEl = document.getElementById('dlSourceCount');
|
||
if (dlSrcEl) dlSrcEl.textContent = srcCount;
|
||
} else {
|
||
setModStatus('modDownload', null, '未知');
|
||
setModStatus('modSources', null, '未知');
|
||
}
|
||
|
||
// 分章引擎
|
||
if (statsData && statsData.chapter) {
|
||
setModStatus('modChapter', true, '在线 · ' + statsData.chapter.total_chaptered_books + ' 已分章');
|
||
} else {
|
||
setModStatus('modChapter', null, '未知');
|
||
}
|
||
|
||
// 阅读器
|
||
if (statsData && statsData.reader) {
|
||
setModStatus('modReader', true, '在线 · ' + statsData.reader.total_members + ' 成员');
|
||
} else {
|
||
setModStatus('modReader', null, '未知');
|
||
}
|
||
|
||
// 上传
|
||
if (statsData && statsData.upload) {
|
||
setModStatus('modUpload', true, '在线 · ' + statsData.upload.total_uploads + ' 已上传');
|
||
} else {
|
||
setModStatus('modUpload', null, '未知');
|
||
}
|
||
|
||
// AI引擎
|
||
if (statsData && statsData.ai) {
|
||
var aiModels = statsData.ai.available_models || [];
|
||
systemStatus.ai = aiModels.length > 0;
|
||
if (aiModels.length > 0) {
|
||
setModStatus('modAI', true, '在线 · ' + aiModels.map(function(m){return m.name}).join(', '));
|
||
} else {
|
||
setModStatus('modAI', false, '无可用模型 · 需配置 API Key');
|
||
}
|
||
} else {
|
||
setModStatus('modAI', null, '未知');
|
||
systemStatus.ai = false;
|
||
}
|
||
|
||
// 小说系统
|
||
if (statsData && statsData.novel) {
|
||
setModStatus('modNovel', true, '在线 · ' + statsData.novel.total_novels + ' 部作品');
|
||
} else {
|
||
setModStatus('modNovel', null, '未知');
|
||
}
|
||
|
||
// Agent状态
|
||
if (systemStatus.api) {
|
||
if (systemStatus.ai) {
|
||
updateAgentStatus(true, 'AI在线');
|
||
} else {
|
||
updateAgentStatus('partial', '本地模式(无AI)');
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateAgentStatus(ok, text) {
|
||
var dot = document.getElementById('agentStatusDot');
|
||
var txt = document.getElementById('agentStatusText');
|
||
systemStatus.agent = ok;
|
||
if (dot) {
|
||
if (ok === true) {
|
||
dot.style.background = 'var(--ok)';
|
||
dot.style.animation = 'pulse 2s infinite';
|
||
} else if (ok === 'partial') {
|
||
dot.style.background = 'var(--warn)';
|
||
dot.style.animation = 'pulse 3s infinite';
|
||
} else {
|
||
dot.style.background = 'var(--err)';
|
||
dot.style.animation = 'none';
|
||
}
|
||
}
|
||
if (txt) txt.textContent = text || (ok ? '在线' : '离线');
|
||
}
|
||
|
||
/* ═══ Load Books ═══ */
|
||
var allDownloadedBooks=[];
|
||
var allUploadedBooks=[];
|
||
|
||
function loadBooks(){
|
||
api('GET','/api/zhiku/download/books').then(function(d){
|
||
allDownloadedBooks=(d.data||[]);
|
||
renderBookGrid();
|
||
});
|
||
api('GET','/api/zhiku/upload/list').then(function(d){
|
||
allUploadedBooks=(d.data||[]);
|
||
renderBookGrid();
|
||
});
|
||
}
|
||
|
||
function renderBookGrid(){
|
||
var books=[];
|
||
if(currentBookTab==='all'||currentBookTab==='downloaded'){
|
||
allDownloadedBooks.forEach(function(b){
|
||
books.push({type:'downloaded',book_name:b.book_name,author:b.author,platform:b.platform,
|
||
size:b.size,filename:b.filename,tags:[]});
|
||
});
|
||
}
|
||
if(currentBookTab==='all'||currentBookTab==='uploaded'){
|
||
allUploadedBooks.forEach(function(b){
|
||
books.push({type:'uploaded',book_name:b.title,author:b.author,platform:'upload',
|
||
size:0,filename:b.converted_file,tags:[b.original_format],word_count:b.word_count});
|
||
});
|
||
}
|
||
|
||
var grid=document.getElementById('bookGrid');
|
||
var empty=document.getElementById('emptyState');
|
||
|
||
if(books.length===0){
|
||
empty.style.display='';grid.style.display='none';return;
|
||
}
|
||
empty.style.display='none';grid.style.display='';
|
||
grid.innerHTML='';
|
||
|
||
var platformNames={qimao:'七猫',fanqie:'番茄',yuewen:'阅文',jjwxc:'晋江',upload:'上传'};
|
||
|
||
books.forEach(function(book,idx){
|
||
var card=document.createElement('div');
|
||
card.className='book-card';
|
||
card.style.animationDelay=(idx*0.05)+'s';
|
||
card.onclick=function(){openBook(book.filename)};
|
||
|
||
var sizeStr=book.size?(book.size/1024).toFixed(1)+' KB':'';
|
||
var wordStr=book.word_count?(book.word_count/10000).toFixed(1)+'万字':'';
|
||
var metaStr=sizeStr||wordStr;
|
||
|
||
var tagsHtml='<span class="book-tag">'+(platformNames[book.platform]||book.platform)+'</span>';
|
||
if(metaStr)tagsHtml+='<span class="book-tag">'+escHtml(metaStr)+'</span>';
|
||
if(book.type==='uploaded')tagsHtml+='<span class="book-tag" style="color:var(--cyan);border-color:rgba(34,211,238,0.15)">已上传</span>';
|
||
(book.tags||[]).forEach(function(t){tagsHtml+='<span class="book-tag">'+escHtml(t)+'</span>';});
|
||
|
||
card.innerHTML='<div class="book-title">'+escHtml(book.book_name)+'</div>'
|
||
+'<div class="book-author">'+escHtml(book.author)+'</div>'
|
||
+'<div class="book-meta">'+tagsHtml+'</div>'
|
||
+'<div class="book-actions-hover">'
|
||
+'<button class="action-btn read" data-filename="'+escHtml(book.filename)+'">📖 阅读</button>'
|
||
+'</div>';
|
||
card.querySelector('.action-btn.read').addEventListener('click',function(e){e.stopPropagation();openBook(book.filename);});
|
||
grid.appendChild(card);
|
||
});
|
||
}
|
||
|
||
function switchBookTab(tab,btn){
|
||
currentBookTab=tab;
|
||
var btns=btn.parentElement.querySelectorAll('.tab-btn');
|
||
btns.forEach(function(b){b.classList.remove('active')});
|
||
btn.classList.add('active');
|
||
renderBookGrid();
|
||
}
|
||
|
||
/* ═══ Search (统一搜索) ═══ */
|
||
function searchBooks(){
|
||
var q=document.getElementById('searchInput').value.trim().toLowerCase();
|
||
var resultsDiv=document.getElementById('searchResults');
|
||
|
||
if(!q){
|
||
resultsDiv.style.display='none';
|
||
loadBooks();
|
||
return;
|
||
}
|
||
|
||
var results=[];
|
||
allDownloadedBooks.forEach(function(b){
|
||
if(b.book_name.toLowerCase().includes(q)||b.author.toLowerCase().includes(q)){
|
||
results.push({type:'downloaded',book_name:b.book_name,author:b.author,platform:b.platform,filename:b.filename,size:b.size});
|
||
}
|
||
});
|
||
|
||
allUploadedBooks.forEach(function(b){
|
||
if((b.title||'').toLowerCase().includes(q)||(b.author||'').toLowerCase().includes(q)){
|
||
results.push({type:'uploaded',book_name:b.title,author:b.author,platform:'upload',filename:b.converted_file,word_count:b.word_count});
|
||
}
|
||
});
|
||
|
||
api('GET','/api/zhiku/library').then(function(d){
|
||
if(!d.error&&d.data){
|
||
d.data.forEach(function(b){
|
||
if(b.book_name.toLowerCase().includes(q)||b.author.toLowerCase().includes(q)){
|
||
var exists=results.find(function(r){return r.book_name===b.book_name&&r.author===b.author});
|
||
if(!exists)results.push({type:'library',book_name:b.book_name,author:b.author,platform:'library',category:b.category});
|
||
}
|
||
});
|
||
}
|
||
renderSearchResults(results,q);
|
||
});
|
||
}
|
||
|
||
function renderSearchResults(results,query){
|
||
var div=document.getElementById('searchResults');
|
||
if(results.length===0){
|
||
div.innerHTML='<div style="text-align:center;padding:20px;color:var(--dim);font-size:13px">未找到匹配 "'+escHtml(query)+'" 的书籍</div>';
|
||
div.style.display='block';
|
||
return;
|
||
}
|
||
|
||
div.style.display='block';
|
||
div.innerHTML='<div style="font-size:11px;color:var(--muted);margin-bottom:8px;font-family:var(--mono)">找到 '+results.length+' 个结果</div>';
|
||
|
||
var platformNames={qimao:'七猫',fanqie:'番茄',yuewen:'阅文',jjwxc:'晋江',upload:'上传',library:'团队书库'};
|
||
|
||
results.forEach(function(book,idx){
|
||
var card=document.createElement('div');
|
||
card.className='search-result-card';
|
||
card.style.animationDelay=(idx*0.05)+'s';
|
||
|
||
var metaStr=platformNames[book.platform]||book.platform;
|
||
if(book.size)metaStr+=' · '+(book.size/1024).toFixed(1)+' KB';
|
||
if(book.word_count)metaStr+=' · '+(book.word_count/10000).toFixed(1)+'万字';
|
||
if(book.category)metaStr+=' · '+book.category;
|
||
|
||
var actionsHtml='';
|
||
if(book.filename){
|
||
actionsHtml+='<button class="action-btn read" data-filename="'+escHtml(book.filename)+'">📖 阅读</button>';
|
||
}
|
||
|
||
card.innerHTML='<div class="result-info">'
|
||
+'<div class="result-title">'+escHtml(book.book_name)+'</div>'
|
||
+'<div class="result-meta">'+escHtml(book.author)+' · '+escHtml(metaStr)+'</div>'
|
||
+'</div>'
|
||
+'<div class="result-actions">'+actionsHtml+'</div>';
|
||
if(book.filename){
|
||
card.querySelector('.action-btn.read').addEventListener('click',function(){openBook(book.filename);});
|
||
}
|
||
div.appendChild(card);
|
||
});
|
||
}
|
||
|
||
/* ═══ Download Panel ═══ */
|
||
function toggleDownloadPanel(){
|
||
var panel=document.getElementById('downloadPanel');
|
||
panel.style.display=panel.style.display==='block'?'none':'block';
|
||
}
|
||
|
||
function downloadBook(){
|
||
var bookName=document.getElementById('dlBookName').value.trim();
|
||
var author=document.getElementById('dlAuthor').value.trim();
|
||
var platform=document.getElementById('dlPlatform').value;
|
||
if(!bookName){showToast('请输入书名','error');return;}
|
||
|
||
// Step 1: 先搜索开源数据库匹配
|
||
showToast('正在匹配开源书源数据库...');
|
||
api('GET','/api/zhiku/download/search?book_name='+encodeURIComponent(bookName)+'&author='+encodeURIComponent(author)).then(function(d){
|
||
if(!d.error && d.data && d.data.local_match_count > 0){
|
||
// 本地已有匹配
|
||
var match = d.data.local_matches[0];
|
||
showToast('✅ 书源匹配成功! 「'+match.book_name+'」已在 '+match.source_name+' 中找到','ok');
|
||
document.getElementById('dlBookName').value='';
|
||
document.getElementById('dlAuthor').value='';
|
||
if(match.file_path){
|
||
// 直接打开已有的书
|
||
setTimeout(function(){ openBook(match.file_path); }, 800);
|
||
}
|
||
loadBooks();
|
||
return;
|
||
}
|
||
|
||
// 本地无匹配,显示数据源信息后尝试平台下载
|
||
var srcCount = (d.data && d.data.registered_source_count) || 0;
|
||
showToast('📥 已搜索 '+srcCount+' 个书源数据库,未找到匹配。正在从 '+platform+' 下载...');
|
||
|
||
// Step 2: 无本地匹配,从平台下载
|
||
api('POST','/api/zhiku/download',{book_name:bookName,author:author||'未知',platform:platform,requested_by:MEMBER_ID}).then(function(dd){
|
||
if(dd.error){showToast(dd.message||'下载失败','error');return;}
|
||
showToast('下载任务已创建','ok');
|
||
document.getElementById('dlBookName').value='';
|
||
document.getElementById('dlAuthor').value='';
|
||
setTimeout(function(){
|
||
var filename=bookName.replace(/[<>:"/\\|?*]/g,'_')+'_'+(author||'未知').replace(/[<>:"/\\|?*]/g,'_')+'_'+platform+'.txt';
|
||
api('POST','/api/zhiku/chapter/split',{filename:filename}).then(function(r){
|
||
if(!r.error)showToast('自动分章完成: '+r.data.total_chapters+' 章','ok');
|
||
loadBooks();loadStats();
|
||
});
|
||
},1500);
|
||
});
|
||
});
|
||
}
|
||
|
||
/* ═══ File Upload ═══ */
|
||
(function(){
|
||
var zone=document.getElementById('uploadZone');
|
||
var input=document.getElementById('fileInput');
|
||
if(!zone||!input)return;
|
||
|
||
['dragenter','dragover'].forEach(function(evt){
|
||
zone.addEventListener(evt,function(e){e.preventDefault();e.stopPropagation();zone.classList.add('dragover');});
|
||
});
|
||
['dragleave','drop'].forEach(function(evt){
|
||
zone.addEventListener(evt,function(e){e.preventDefault();e.stopPropagation();zone.classList.remove('dragover');});
|
||
});
|
||
|
||
zone.addEventListener('drop',function(e){
|
||
var files=e.dataTransfer.files;
|
||
if(files.length>0)handleFileUpload(files[0]);
|
||
});
|
||
|
||
input.addEventListener('change',function(){
|
||
if(input.files.length>0)handleFileUpload(input.files[0]);
|
||
input.value='';
|
||
});
|
||
})();
|
||
|
||
function handleFileUpload(file){
|
||
var progress=document.getElementById('uploadProgress');
|
||
var barFill=document.getElementById('uploadBarFill');
|
||
var status=document.getElementById('uploadStatus');
|
||
|
||
progress.style.display='block';
|
||
barFill.style.width='10%';
|
||
status.textContent='正在上传: '+file.name+' ('+(file.size/1024).toFixed(1)+' KB)...';
|
||
|
||
var formData=new FormData();
|
||
formData.append('file',file);
|
||
formData.append('title',file.name.replace(/\.[^.]+$/,''));
|
||
formData.append('uploader',MEMBER_ID);
|
||
|
||
barFill.style.width='40%';
|
||
|
||
fetch(API+'/api/zhiku/upload',{method:'POST',body:formData})
|
||
.then(function(r){return r.json()})
|
||
.then(function(d){
|
||
barFill.style.width='80%';
|
||
if(d.error){
|
||
status.textContent='❌ '+d.message;
|
||
showToast(d.message,'error');
|
||
setTimeout(function(){progress.style.display='none';barFill.style.width='0'},3000);
|
||
return;
|
||
}
|
||
|
||
barFill.style.width='100%';
|
||
status.textContent='✅ 上传成功 · '+d.data.original_format+' → TXT · '+d.data.word_count+'字';
|
||
showToast('上传成功: '+d.data.title+' ('+d.data.word_count+'字)','ok');
|
||
|
||
if(d.data.converted_file){
|
||
setTimeout(function(){
|
||
api('POST','/api/zhiku/chapter/split',{filename:d.data.converted_file}).then(function(r){
|
||
if(!r.error)showToast('自动分章完成: '+r.data.total_chapters+' 章','ok');
|
||
loadBooks();loadStats();
|
||
});
|
||
},500);
|
||
}else{
|
||
loadBooks();loadStats();
|
||
}
|
||
|
||
setTimeout(function(){progress.style.display='none';barFill.style.width='0'},4000);
|
||
})
|
||
.catch(function(err){
|
||
status.textContent='❌ 上传失败: '+err.message;
|
||
showToast('上传失败','error');
|
||
setTimeout(function(){progress.style.display='none';barFill.style.width='0'},3000);
|
||
});
|
||
}
|
||
|
||
/* ═══ Open Book ═══ */
|
||
function openBook(filename){
|
||
var bookId=filename.replace('.txt','');
|
||
api('GET','/api/zhiku/chapter/'+encodeURIComponent(bookId)).then(function(d){
|
||
if(d.error){
|
||
showToast('正在分章...');
|
||
api('POST','/api/zhiku/chapter/split',{filename:filename}).then(function(r){
|
||
if(r.error){showToast('分章失败: '+r.message,'error');return;}
|
||
showToast('分章完成: '+r.data.total_chapters+' 章','ok');
|
||
loadStats();
|
||
openReader(bookId,0);
|
||
});
|
||
return;
|
||
}
|
||
api('GET','/api/zhiku/reader/progress/'+MEMBER_ID+'/'+encodeURIComponent(bookId)).then(function(p){
|
||
var startCh=(p&&p.data&&p.data.chapter_index)?p.data.chapter_index:0;
|
||
openReader(bookId,startCh);
|
||
});
|
||
});
|
||
}
|
||
|
||
/* ═══ Reader State ═══ */
|
||
var readerState={bookId:'',currentCh:0,totalCh:0,fontSize:16,theme:'dark'};
|
||
|
||
function openReader(bookId,chapterIndex){
|
||
readerState.bookId=bookId;
|
||
readerState.currentCh=chapterIndex;
|
||
document.getElementById('readerModal').style.display='flex';
|
||
loadChapter(bookId,chapterIndex);
|
||
}
|
||
|
||
function closeReader(){
|
||
document.getElementById('readerModal').style.display='none';
|
||
if(readerState.bookId){
|
||
api('POST','/api/zhiku/reader/progress/'+MEMBER_ID+'/'+encodeURIComponent(readerState.bookId),{
|
||
chapter_index:readerState.currentCh,
|
||
scroll_position:document.getElementById('readerBody').scrollTop,
|
||
percent:readerState.totalCh>0?Math.round((readerState.currentCh+1)/readerState.totalCh*100):0
|
||
});
|
||
}
|
||
}
|
||
|
||
function loadChapter(bookId,idx){
|
||
api('GET','/api/zhiku/chapter/'+encodeURIComponent(bookId)+'/'+idx).then(function(d){
|
||
if(d.error){showToast('加载章节失败','error');return;}
|
||
var ch=d.data;
|
||
readerState.totalCh=ch.total_chapters;
|
||
readerState.currentCh=idx;
|
||
|
||
var rendered=autoRenderContent(ch.content);
|
||
|
||
document.getElementById('readerTitle').textContent=ch.title;
|
||
document.getElementById('readerBody').innerHTML='<h2 style="text-align:center;margin-bottom:20px;font-size:1.1em;color:var(--purple)">'+escHtml(ch.title)+'</h2>'
|
||
+'<div style="word-break:break-word">'+rendered+'</div>';
|
||
document.getElementById('readerBody').scrollTop=0;
|
||
document.getElementById('chapterInfo').textContent=(idx+1)+' / '+ch.total_chapters;
|
||
document.getElementById('prevChBtn').disabled=!ch.has_prev;
|
||
document.getElementById('nextChBtn').disabled=!ch.has_next;
|
||
document.getElementById('prevChBtn').style.opacity=ch.has_prev?'1':'0.3';
|
||
document.getElementById('nextChBtn').style.opacity=ch.has_next?'1':'0.3';
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 自动检测内容格式并渲染
|
||
* 安全: 所有内容先 escape,再应用安全格式化
|
||
* 支持: 纯文本、Markdown-like
|
||
*/
|
||
function autoRenderContent(content){
|
||
if(!content)return '';
|
||
|
||
// SECURITY: Always escape HTML first, then apply safe formatting
|
||
var escaped=escHtml(content);
|
||
|
||
// Check if original content looks like Markdown (headers, bold, etc.)
|
||
if(/^#{1,3}\s/m.test(content)||/\*\*[^*]+\*\*/m.test(content)){
|
||
var html=escaped
|
||
.replace(/^### (.+)$/gm,'<h3 style="font-size:1.1em;margin:16px 0 8px;color:var(--purple)">$1</h3>')
|
||
.replace(/^## (.+)$/gm,'<h2 style="font-size:1.2em;margin:20px 0 10px;color:var(--purple)">$1</h2>')
|
||
.replace(/^# (.+)$/gm,'<h1 style="font-size:1.3em;margin:24px 0 12px;color:var(--purple)">$1</h1>')
|
||
.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>')
|
||
.replace(/\*(.+?)\*/g,'<em>$1</em>')
|
||
.replace(/\n\n/g,'</p><p style="text-indent:2em;margin:0.5em 0">')
|
||
.replace(/\n/g,'<br>');
|
||
return '<p style="text-indent:2em;margin:0.5em 0">'+html+'</p>';
|
||
}
|
||
|
||
// Plain text: format with indentation
|
||
var paragraphs=escaped.split(/\n\s*\n/).filter(function(p){return p.trim()});
|
||
if(paragraphs.length>1){
|
||
return paragraphs.map(function(p){
|
||
return '<p style="text-indent:2em;margin:0.8em 0;line-height:1.9">'+p.trim().replace(/\n/g,'<br>')+'</p>';
|
||
}).join('');
|
||
}
|
||
|
||
return '<div style="white-space:pre-wrap;text-indent:2em;line-height:1.9">'+escaped+'</div>';
|
||
}
|
||
|
||
function prevChapter(){if(readerState.currentCh>0)loadChapter(readerState.bookId,readerState.currentCh-1);}
|
||
function nextChapter(){if(readerState.currentCh<readerState.totalCh-1)loadChapter(readerState.bookId,readerState.currentCh+1);}
|
||
|
||
function changeFontSize(delta){
|
||
readerState.fontSize=Math.min(28,Math.max(12,readerState.fontSize+delta*2));
|
||
document.getElementById('readerBody').style.fontSize=readerState.fontSize+'px';
|
||
}
|
||
|
||
function toggleReaderTheme(){
|
||
var body=document.getElementById('readerBody');
|
||
var modal=document.getElementById('readerModal');
|
||
var btn=document.getElementById('themeToggle');
|
||
if(readerState.theme==='dark'){
|
||
readerState.theme='light';
|
||
modal.style.background='#f5f5f0';
|
||
body.style.color='#1a1a1a';
|
||
btn.textContent='🌙';
|
||
}else{
|
||
readerState.theme='dark';
|
||
modal.style.background='var(--bg)';
|
||
body.style.color='var(--text)';
|
||
btn.textContent='☀️';
|
||
}
|
||
}
|
||
|
||
/* ═══ Team Library ═══ */
|
||
function loadLibrary(category,btn){
|
||
if(btn){
|
||
var btns=btn.parentElement.querySelectorAll('.tab-btn');
|
||
btns.forEach(function(b){b.classList.remove('active')});
|
||
btn.classList.add('active');
|
||
}
|
||
var url='/api/zhiku/library';
|
||
if(category)url+='?category='+encodeURIComponent(category);
|
||
api('GET',url).then(function(d){
|
||
var grid=document.getElementById('libraryGrid');
|
||
var empty=document.getElementById('libraryEmpty');
|
||
if(d.error||!d.data||d.data.length===0){
|
||
grid.style.display='none';empty.style.display='';return;
|
||
}
|
||
grid.style.display='';empty.style.display='none';
|
||
grid.innerHTML='';
|
||
d.data.forEach(function(book,idx){
|
||
var card=document.createElement('div');card.className='book-card';
|
||
card.style.animationDelay=(idx*0.05)+'s';
|
||
var stars='';for(var i=0;i<5;i++)stars+=i<Math.round(book.rating)?'★':'☆';
|
||
card.innerHTML='<div class="book-title">'+escHtml(book.book_name)+'</div>'
|
||
+'<div class="book-author">'+escHtml(book.author)+'</div>'
|
||
+'<div class="book-meta">'
|
||
+'<span class="book-tag">'+escHtml(book.category)+'</span>'
|
||
+(book.tags||[]).map(function(t){return '<span class="book-tag">'+escHtml(t)+'</span>'}).join('')
|
||
+'<span class="book-tag" style="color:var(--warn)">'+stars+' '+book.rating+'</span>'
|
||
+'<span class="book-tag">引用 '+book.ref_count+'</span>'
|
||
+'</div>';
|
||
grid.appendChild(card);
|
||
});
|
||
});
|
||
}
|
||
|
||
/* ═══ Agent Chat (AI Powered) ═══ */
|
||
var agentReady=false;
|
||
var agentSending=false;
|
||
|
||
function initAgent(){
|
||
// 先尝试注册Agent,如果失败说明API不可达
|
||
api('POST','/api/zhiku/agent/'+MEMBER_ID,{nickname:'成员'}).then(function(d){
|
||
if(d.error){
|
||
agentReady=false;
|
||
updateAgentStatus(false, 'API连接失败');
|
||
appendChat('agent','⚠️ 无法连接到后端服务。请检查网络连接或联系管理员。\n\n错误: '+(d.message||'网络错误'));
|
||
return;
|
||
}
|
||
agentReady=true;
|
||
// 检查AI是否真正可用
|
||
api('GET','/api/zhiku/ai/health').then(function(aiData){
|
||
if(!aiData.error && aiData.data && aiData.data.available_models && aiData.data.available_models.length > 0){
|
||
updateAgentStatus(true, 'AI在线 · '+aiData.data.available_models[0].name);
|
||
appendChat('agent','🤖 AI Agent已就绪!当前使用: '+aiData.data.available_models.map(function(m){return m.name}).join(' / ')+'\n\n📖 分析小说内容\n✂️ 拆文整理\n📝 生成摘要\n💡 创作建议\n\n直接聊天即可,有什么我可以帮你的?');
|
||
} else {
|
||
updateAgentStatus('partial', '本地模式(无AI API)');
|
||
appendChat('agent','🤖 Agent已连接(本地模式)。AI大模型尚未配置 API Key。\n\n当前可用功能:\n💬 基础对话\n📝 记忆/笔记\n💡 帮助指令\n\n⚠️ 如需AI辅助(摘要/改写/续写),请配置 DeepSeek/Kimi/通义千问 API Key。');
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function appendChat(role,text,model){
|
||
var box=document.getElementById('agentChatBox');
|
||
var div=document.createElement('div');
|
||
div.className='chat-msg '+(role==='user'?'chat-msg-user':'chat-msg-agent');
|
||
|
||
var bubble=document.createElement('div');
|
||
bubble.className='chat-bubble '+(role==='user'?'chat-bubble-user':'chat-bubble-agent');
|
||
|
||
var rendered=escHtml(text).replace(/\n/g,'<br>');
|
||
bubble.innerHTML=rendered;
|
||
|
||
if(role==='agent'&&model&&model!=='local'&&model!=='none'){
|
||
var tag=document.createElement('span');
|
||
tag.className='model-tag';
|
||
tag.textContent='via '+model;
|
||
bubble.appendChild(tag);
|
||
}
|
||
|
||
div.appendChild(bubble);
|
||
box.appendChild(div);
|
||
box.scrollTop=box.scrollHeight;
|
||
}
|
||
|
||
function showTypingIndicator(){
|
||
var box=document.getElementById('agentChatBox');
|
||
var div=document.createElement('div');
|
||
div.className='chat-msg chat-msg-agent';
|
||
div.id='typingIndicator';
|
||
div.innerHTML='<div class="chat-bubble chat-bubble-agent"><div class="typing-indicator"><div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div></div></div>';
|
||
box.appendChild(div);
|
||
box.scrollTop=box.scrollHeight;
|
||
}
|
||
|
||
function removeTypingIndicator(){
|
||
var el=document.getElementById('typingIndicator');
|
||
if(el)el.remove();
|
||
}
|
||
|
||
function sendAgentChat(preset){
|
||
if(agentSending)return;
|
||
var msg=preset||document.getElementById('agentInput').value.trim();
|
||
if(!msg)return;
|
||
if(!preset)document.getElementById('agentInput').value='';
|
||
|
||
if(!agentReady && !systemStatus.api){
|
||
appendChat('user',msg);
|
||
appendChat('agent','⚠️ 无法发送消息:后端服务未连接。\n\n请检查:\n1. 服务器是否运行中\n2. API地址是否正确\n3. 网络连接是否正常');
|
||
return;
|
||
}
|
||
|
||
appendChat('user',msg);
|
||
agentSending=true;
|
||
document.getElementById('agentSendBtn').disabled=true;
|
||
showTypingIndicator();
|
||
|
||
api('POST','/api/zhiku/agent/'+MEMBER_ID+'/chat',{message:msg}).then(function(d){
|
||
removeTypingIndicator();
|
||
agentSending=false;
|
||
document.getElementById('agentSendBtn').disabled=false;
|
||
|
||
if(d.error){
|
||
appendChat('agent','⚠️ '+(d.message||'网络错误 · 无法连接到服务器'));
|
||
updateAgentStatus(false, '连接中断');
|
||
return;
|
||
}
|
||
var model=d.data.model||'';
|
||
appendChat('agent',d.data.reply,model);
|
||
|
||
if(model&&model!=='local'&&model!=='none'){
|
||
var badge=document.getElementById('agentModelBadge');
|
||
badge.textContent=model;
|
||
badge.style.display='';
|
||
updateAgentStatus(true, 'AI在线 · '+model);
|
||
}
|
||
});
|
||
}
|
||
|
||
/* ═══ Auto-resize chat input ═══ */
|
||
(function(){
|
||
var input=document.getElementById('agentInput');
|
||
if(!input)return;
|
||
input.addEventListener('input',function(){
|
||
this.style.height='auto';
|
||
this.style.height=Math.min(this.scrollHeight,100)+'px';
|
||
});
|
||
})();
|
||
|
||
/* ═══ Keyboard shortcuts ═══ */
|
||
document.getElementById('searchInput').addEventListener('keydown',function(e){if(e.key==='Enter')searchBooks();});
|
||
document.getElementById('agentInput').addEventListener('keydown',function(e){
|
||
if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendAgentChat();}
|
||
});
|
||
|
||
/* ═══ Init with retry ═══ */
|
||
var statsRetryCount=0;
|
||
var statsRetryMax=3;
|
||
var statsLoading=false;
|
||
function loadStatsWithRetry(){
|
||
if(statsLoading)return;
|
||
statsLoading=true;
|
||
var origCb=systemStatus.api;
|
||
loadStats();
|
||
// loadStats是异步的,通过轮询检测结果
|
||
setTimeout(function checkResult(){
|
||
statsLoading=false;
|
||
if(!systemStatus.api && statsRetryCount<statsRetryMax){
|
||
statsRetryCount++;
|
||
console.log('[智库] API重试 '+statsRetryCount+'/'+statsRetryMax);
|
||
setTimeout(loadStatsWithRetry,(statsRetryCount)*5000);
|
||
} else if(systemStatus.api){
|
||
statsRetryCount=0;
|
||
}
|
||
},3000);
|
||
}
|
||
loadStatsWithRetry();
|
||
loadBooks();
|
||
loadLibrary();
|
||
initAgent();
|
||
// 定期刷新状态(每60秒)
|
||
(function scheduleRefresh(){setTimeout(function(){loadStats();scheduleRefresh();},60000)})();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|