Files
LittleWhiteBox/modules/story-summary/story-summary.html

533 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- story-summary.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=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>剧情总结 · Story Summary</title>
<link rel="stylesheet" href="story-summary.css">
</head>
<body>
<div class="container">
<!-- Header -->
<header>
<div>
<h1>剧情<span>总结</span></h1>
<div class="subtitle">Story Summary · Timeline · Character Arcs</div>
</div>
<div class="stats">
<div class="stat">
<div class="stat-val" id="stat-events">0</div>
<div class="stat-lbl">已记录事件</div>
</div>
<div class="stat">
<div class="stat-val" id="stat-summarized">0</div>
<div class="stat-lbl">已总结楼层</div>
</div>
<div class="stat">
<div class="stat-val"><span class="hl" id="stat-pending">0</span></div>
<div class="stat-lbl">待总结</div>
<div class="stat-warning hidden" id="pending-warning">再删1条将回滚</div>
</div>
</div>
</header>
<!-- Controls -->
<div class="controls">
<label class="chk-label">
<input type="checkbox" id="hide-summarized">
<span>聊天时隐藏已总结 · <strong id="summarized-count">0</strong> 楼(保留<input type="number" id="keep-visible-count" min="0" max="50" value="3">楼)</span>
</label>
<span class="spacer"></span>
<div class="btn-group">
<button class="btn btn-icon" id="btn-settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
<span>设置</span>
</button>
<button class="btn btn-recall" id="btn-recall">涌现</button>
<button class="btn" id="btn-clear">清空</button>
<button class="btn btn-p" id="btn-generate">总结</button>
</div>
</div>
<!-- Main Content -->
<main>
<div class="left">
<!-- Keywords -->
<section class="card">
<div class="sec-head">
<div class="sec-title">核心关键词</div>
<button class="sec-btn" data-section="keywords">编辑</button>
</div>
<div class="keywords" id="keywords-cloud"></div>
</section>
<!-- Timeline -->
<section class="card timeline">
<div class="sec-head">
<div class="sec-title">剧情时间线</div>
<button class="sec-btn" data-section="events">编辑</button>
</div>
<div class="tl-list scroll" id="timeline-list"></div>
</section>
</div>
<div class="right">
<!-- World State -->
<section class="card world-state">
<div class="sec-head">
<div class="sec-title">世界状态</div>
<button class="sec-btn" data-section="world">编辑</button>
</div>
<div class="world-state-list scroll" id="world-state-list"></div>
</section>
<!-- Relations -->
<section class="card relations">
<div class="sec-head">
<div class="sec-title">人物关系</div>
<div class="sec-actions">
<button class="sec-btn sec-icon" id="btn-fullscreen-relations" title="全屏查看">
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2">
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
</svg>
</button>
<button class="sec-btn" data-section="characters">编辑</button>
</div>
</div>
<div id="relation-chart"></div>
</section>
<!-- Profile -->
<section class="card profile">
<div class="sec-head">
<div class="sec-title">人物档案</div>
<div class="sec-actions">
<div class="custom-select" id="char-sel">
<div class="sel-trigger" id="char-sel-trigger">
<span id="sel-char-text">选择角色</span>
</div>
<div class="sel-opts" id="char-sel-opts">
<div class="sel-opt" data-value="">暂无角色</div>
</div>
</div>
<button class="sec-btn" data-section="arcs">编辑</button>
</div>
</div>
<div class="profile-content scroll" id="profile-content"></div>
</section>
</div>
</main>
</div>
<!-- Editor Modal -->
<div class="modal" id="editor-modal">
<div class="modal-bg" id="editor-backdrop"></div>
<div class="modal-box">
<div class="modal-head">
<h2 id="editor-title">编辑</h2>
<button class="modal-close" id="editor-close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body">
<div class="editor-hint" id="editor-hint"></div>
<div id="editor-struct" class="hidden"></div>
<textarea class="editor-ta" id="editor-ta"></textarea>
<div class="editor-err" id="editor-err"></div>
</div>
<div class="modal-foot">
<button class="btn" id="editor-cancel">取消</button>
<button class="btn btn-p" id="editor-save">保存</button>
</div>
</div>
</div>
<!-- Settings Modal -->
<div class="modal" id="settings-modal">
<div class="modal-bg" id="settings-backdrop"></div>
<div class="modal-box">
<div class="modal-head">
<h2>设置</h2>
<button class="modal-close" id="settings-close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body">
<!-- API Config -->
<div class="settings-section">
<div class="settings-section-title">API 配置</div>
<div class="settings-row">
<div class="settings-field">
<label>渠道</label>
<select id="api-provider">
<option value="st">酒馆主 API沿用当前</option>
<option value="openai">OpenAI 兼容</option>
<option value="google">Google (Gemini)</option>
<option value="claude">Claude (Anthropic)</option>
<option value="custom">自定义</option>
</select>
</div>
</div>
<div class="settings-row hidden" id="api-url-row">
<div class="settings-field full">
<label>API URL</label>
<input type="text" id="api-url" placeholder="https://api.openai.com 或代理地址">
<div class="settings-hint">不同渠道默认端点OpenAI 用 /v1Gemini 用 /v1betaClaude 用 /v1</div>
</div>
</div>
<div class="settings-row hidden" id="api-key-row">
<div class="settings-field full">
<label>API KEY</label>
<input type="password" id="api-key" placeholder="仅保存在本地,不会上传">
</div>
</div>
<div class="settings-row hidden" id="api-model-manual-row">
<div class="settings-field full">
<label>模型</label>
<input type="text" id="api-model-text" placeholder="如 gemini-1.5-pro、claude-3-haiku">
</div>
</div>
<div class="settings-row hidden" id="api-model-select-row">
<div class="settings-field full">
<label>可用模型</label>
<select id="api-model-select">
<option value="">请先拉取模型列表</option>
</select>
</div>
</div>
<div class="settings-btn-row hidden" id="api-connect-row">
<button class="btn btn-sm btn-p" id="btn-connect">连接 / 拉取模型列表</button>
</div>
</div>
<!-- Gen Params -->
<div class="settings-section">
<div class="settings-section-title">生成参数</div>
<div class="settings-row">
<div class="settings-field">
<label>Temperature</label>
<input type="number" id="gen-temp" step="0.01" min="0" max="2" placeholder="未设置">
</div>
<div class="settings-field">
<label>Top P</label>
<input type="number" id="gen-top-p" step="0.01" min="0" max="1" placeholder="未设置">
</div>
<div class="settings-field">
<label>Top K</label>
<input type="number" id="gen-top-k" step="1" min="1" placeholder="未设置">
</div>
</div>
<div class="settings-row">
<div class="settings-field">
<label>存在惩罚</label>
<input type="number" id="gen-presence" step="0.01" min="-2" max="2" placeholder="未设置">
</div>
<div class="settings-field">
<label>频率惩罚</label>
<input type="number" id="gen-frequency" step="0.01" min="-2" max="2" placeholder="未设置">
</div>
</div>
</div>
<!-- Trigger Settings -->
<div class="settings-section">
<div class="settings-section-title">总结设置</div>
<div class="settings-row">
<div class="settings-field">
<label>注入角色</label>
<select id="trigger-role">
<option value="system">System</option>
<option value="user">User</option>
<option value="assistant">Assistant</option>
</select>
</div>
</div>
<div class="settings-row">
<div class="settings-field">
<label>自动总结间隔(楼)</label>
<input type="number" id="trigger-interval" min="5" step="5" value="20">
</div>
<div class="settings-field">
<label>触发时机</label>
<select id="trigger-timing">
<option value="after_ai">AI 回复后</option>
<option value="before_user" selected>用户发送前</option>
<option value="manual">仅手动</option>
</select>
</div>
<div class="settings-field">
<label>单次最大总结(楼)</label>
<select id="trigger-max-per-run">
<option value="50">50</option>
<option value="100" selected>100</option>
<option value="150">150</option>
<option value="200">200</option>
</select>
</div>
</div>
<div class="settings-row">
<div class="settings-field full">
<label>头部包裹词应对NoAss配置</label>
<input type="text" id="trigger-wrapper-head" placeholder="添加到开头">
</div>
</div>
<div class="settings-row">
<div class="settings-field full">
<label>尾部包裹词应对NoAss配置</label>
<input type="text" id="trigger-wrapper-tail" placeholder="添加到结尾">
</div>
</div>
<div class="settings-row">
<div class="settings-field-inline">
<input type="checkbox" id="trigger-enabled">
<label for="trigger-enabled">启用自动总结</label>
</div>
<div class="settings-field-inline">
<input type="checkbox" id="trigger-stream" checked>
<label for="trigger-stream">启用流式生成</label>
</div>
<div class="settings-field-inline">
<input type="checkbox" id="trigger-insert-at-end">
<label for="trigger-insert-at-end">强制插入到聊天最后</label>
</div>
</div>
<div class="settings-hint" style="margin-top:8px">若 API 不支持非流式请求,请勾选"启用流式生成"</div>
</div>
<!-- Vector Settings -->
<div class="settings-section">
<div class="settings-section-title">智能记忆(向量检索)</div>
<div class="settings-row">
<div class="settings-field-inline">
<input type="checkbox" id="vector-enabled">
<label for="vector-enabled">启用向量检索</label>
</div>
</div>
<div id="vector-config-area" class="hidden">
<div class="settings-row" style="margin-top:16px">
<div class="settings-field full">
<label>Embedding 引擎</label>
<div class="engine-selector">
<label class="engine-option">
<input type="radio" name="vector-engine" value="local">
<span>本地模型</span>
</label>
<label class="engine-option">
<input type="radio" name="vector-engine" value="online" checked>
<span>在线服务</span>
</label>
</div>
</div>
</div>
<!-- Local Engine -->
<div id="local-engine-area" class="engine-area hidden">
<div class="model-select-row">
<select id="local-model-select">
<option value="bge-small-zh">中文轻量 (51MB)</option>
<option value="bge-base-zh">中文标准 (102MB)</option>
<option value="e5-small">多语言 (118MB)</option>
</select>
</div>
<div class="model-desc" id="local-model-desc">手机/低配适用</div>
<div class="engine-status" id="local-model-status">
<span class="status-dot"></span>
<span class="status-text">检查中...</span>
</div>
<div class="engine-progress hidden" id="local-model-progress">
<div class="progress-bar"><div class="progress-inner"></div></div>
<span class="progress-text">0%</span>
</div>
<div class="engine-actions" id="local-model-actions">
<button class="btn btn-sm btn-p" id="btn-download-model">下载模型</button>
<button class="btn btn-sm" id="btn-cancel-download" style="display:none">取消下载</button>
<button class="btn btn-sm btn-del" id="btn-delete-model" style="display:none">删除缓存</button>
</div>
</div>
<!-- Online Engine -->
<div id="online-engine-area" class="engine-area">
<div class="settings-row">
<div class="settings-field full">
<label>服务渠道</label>
<select id="online-provider">
<option value="siliconflow">硅基流动(推荐)</option>
<option value="cohere">Cohere</option>
<option value="openai">OpenAI 兼容(可自建)</option>
</select>
</div>
</div>
<div class="settings-row" id="online-url-row">
<div class="settings-field full">
<label>API URL</label>
<input type="text" id="vector-api-url" placeholder="https://api.siliconflow.cn">
</div>
</div>
<div class="settings-row">
<div class="settings-field full">
<label>API Key</label>
<input type="password" id="vector-api-key" placeholder="sk-xxx">
</div>
</div>
<div class="settings-row">
<div class="settings-field full">
<label>模型</label>
<div style="display:flex;gap:8px">
<select id="vector-model-select" style="flex:1">
<option value="">请选择模型</option>
</select>
<button class="btn btn-sm" id="btn-fetch-models" style="display:none">拉取</button>
</div>
</div>
</div>
<div class="engine-status" id="online-api-status">
<span class="status-dot"></span>
<span class="status-text">未测试</span>
</div>
<div class="settings-btn-row" style="justify-content:center">
<button class="btn btn-sm" id="btn-test-vector-api">测试连接</button>
</div>
<div class="provider-hint" id="provider-hint">
💡 <a href="https://siliconflow.cn" target="_blank">硅基流动</a> 免费、速度快、质量好,推荐 BAAI/bge-m3
</div>
</div>
<!-- 文本过滤规则 -->
<div class="settings-row" style="margin-top:16px">
<div class="settings-field full">
<label>文本过滤规则</label>
<p class="settings-hint" style="margin-bottom:8px">
遇到「起始」后跳过,直到「结束」。起始或结束可单独留空。用于过滤思考标签等干扰内容。
</p>
<div id="filter-rules-list" style="display:flex;flex-direction:column;gap:6px"></div>
<button class="btn btn-sm" id="btn-add-filter-rule" style="margin-top:8px"> 添加规则</button>
</div>
</div>
<!-- Vector Stats -->
<div class="vector-chat-section">
<div class="settings-row">
<div class="settings-field full">
<label>当前聊天向量</label>
<div class="vector-stats" id="vector-stats">
<span>事件向量: <strong id="vector-event-count">0</strong>/<strong id="vector-event-total">0</strong></span>
<span>·</span>
<span>Chunks: <strong id="vector-chunk-count">0</strong> 个(<span id="vector-chunk-floors">0</span>/<span id="vector-chunk-total">0</span> 层)</span>
<span>·</span>
<span>消息: <strong id="vector-message-count">0</strong></span>
</div>
<div class="vector-mismatch-warning hidden" id="vector-mismatch-warning">
⚠ 引擎/模型已变更,需重新生成向量
</div>
</div>
</div>
<div class="engine-progress hidden" id="vector-gen-progress-l1">
<div style="font-size:.75rem;color:var(--txt3);margin-bottom:4px">L1 片段</div>
<div class="progress-bar"><div class="progress-inner"></div></div>
<span class="progress-text">0/0</span>
</div>
<div class="engine-progress hidden" id="vector-gen-progress-l2">
<div style="font-size:.75rem;color:var(--txt3);margin-bottom:4px">L2 事件</div>
<div class="progress-bar"><div class="progress-inner"></div></div>
<span class="progress-text">0/0</span>
</div>
<div class="settings-hint" id="vector-perf-l1"></div>
<div class="settings-hint" id="vector-perf-l2"></div>
<div class="settings-btn-row" id="vector-action-row">
<button class="btn btn-sm btn-p" id="btn-gen-vectors">生成向量</button>
<button class="btn btn-sm btn-del" id="btn-clear-vectors">清除向量</button>
<button class="btn btn-sm hidden" id="btn-cancel-vectors">取消</button>
</div>
<div class="settings-hint" style="margin-top:8px">首次生成向量可能耗时较久,页面短暂卡顿属正常。若本地模型重进酒馆后需重下。</div>
<!-- 向量导入导出 -->
<div class="vector-io-section" style="border-top:1px solid var(--bdr);padding-top:16px;margin-top:16px">
<div class="settings-row">
<div class="settings-field full">
<label>向量迁移(跨设备 / 防清缓存)</label>
<div class="settings-hint" style="margin-bottom:8px">导出/导入均为 zip 格式,勿解压</div>
<div class="settings-btn-row" id="vector-io-row" style="margin-top:8px">
<button class="btn btn-sm" id="btn-export-vectors">导出向量</button>
<button class="btn btn-sm" id="btn-import-vectors">导入向量</button>
</div>
<div class="settings-hint" id="vector-io-status"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-foot">
<button class="btn" id="settings-cancel">取消</button>
<button class="btn btn-p" id="settings-save">保存</button>
</div>
</div>
</div>
<!-- Fullscreen Relations Modal -->
<div class="modal fullscreen" id="rel-fs-modal">
<div class="modal-bg" id="rel-fs-backdrop"></div>
<div class="modal-box">
<div class="modal-head">
<h2>人物关系图</h2>
<button class="modal-close" id="rel-fs-close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body">
<div id="relation-chart-fullscreen"></div>
</div>
</div>
</div>
<!-- HF Guide Modal -->
<div class="modal" id="hf-guide-modal">
<div class="modal-bg" id="hf-guide-backdrop"></div>
<div class="modal-box" style="max-width:900px">
<div class="modal-head">
<h2>🤗 Hugging Face Space 部署指南</h2>
<button class="modal-close" id="hf-guide-close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body" id="hf-guide-body"></div>
</div>
</div>
<!-- Recall Log Modal -->
<div class="modal" id="recall-log-modal">
<div class="modal-bg" id="recall-log-backdrop"></div>
<div class="modal-box" style="max-width:900px">
<div class="modal-head">
<h2>✨ 涌现 · 记忆召回日志</h2>
<button class="modal-close" id="recall-log-close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body" style="padding:0">
<pre id="recall-log-content"></pre>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<script src="story-summary-ui.js"></script>
</body>
</html>