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

583 lines
33 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" 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">
<!-- Facts -->
<section class="card facts">
<div class="sec-head">
<div class="sec-title">世界状态</div>
<button class="sec-btn" data-section="facts">编辑</button>
</div>
<div class="facts-list scroll" id="facts-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 settings-modal-box">
<div class="modal-head">
<div class="settings-tabs">
<div class="settings-tab active" data-tab="tab-summary">总结设置</div>
<div class="settings-tab" data-tab="tab-vector">向量设置</div>
<div class="settings-tab" data-tab="tab-debug">调试</div>
</div>
<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">
<!-- Tab 1: Summary Settings -->
<div class="tab-pane active" id="tab-summary">
<!-- API Config & Gen Params Combined -->
<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"
style="display: flex; gap: 12px; align-items: center; justify-content: space-between;">
<button class="btn btn-sm btn-p" id="btn-connect" style="flex: 4;">连接 / 拉取模型列表</button>
<label class="chk-label compact"
style="margin: 0; flex: 1; display: flex; align-items: center; gap: 6px; white-space: nowrap; justify-content: center;">
<input type="checkbox" id="trigger-stream" checked>
<span>流式</span>
</label>
</div>
<!-- Collapsible Gen Params -->
<div class="settings-collapse">
<div class="settings-collapse-header" id="gen-params-toggle">
<span>生成参数</span>
<svg class="collapse-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
<div class="settings-collapse-content hidden" id="gen-params-content">
<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>
</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 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>
<!-- Auto Summary with sub-options -->
<div class="settings-checkbox-group">
<label class="settings-checkbox">
<input type="checkbox" id="trigger-enabled">
<span class="checkbox-mark"></span>
<span class="checkbox-label">启用自动总结</span>
</label>
<div class="settings-sub-options hidden" id="auto-summary-options">
<div class="settings-row">
<div class="settings-field">
<label>自动总结间隔(楼)</label>
<input type="number" id="trigger-interval" min="1" max="30" step="1" 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>
</div>
</div>
<!-- Force Insert with wrapper options -->
<div class="settings-checkbox-group">
<label class="settings-checkbox">
<input type="checkbox" id="trigger-insert-at-end">
<span class="checkbox-mark"></span>
<span class="checkbox-label">强制插入到聊天最后(插件冲突用)</span>
</label>
<div class="settings-sub-options hidden" id="insert-wrapper-options">
<div class="settings-row">
<div class="settings-field full">
<label>头部包裹词</label>
<input type="text" id="trigger-wrapper-head" placeholder="添加到开头应对NoAss配置">
</div>
</div>
<div class="settings-row">
<div class="settings-field full">
<label>尾部包裹词</label>
<input type="text" id="trigger-wrapper-tail" placeholder="添加到结尾应对NoAss配置">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tab 2: Vector Settings -->
<div class="tab-pane" id="tab-vector">
<div class="settings-section">
<div class="settings-section-title">智能记忆(向量检索)</div>
<!-- 启用开关 -->
<div class="settings-checkbox-group">
<label class="settings-checkbox">
<input type="checkbox" id="vector-enabled">
<span class="checkbox-mark"></span>
<span class="checkbox-label">启用向量检索</span>
</label>
</div>
<div id="vector-config-area" class="hidden">
<!-- API Key -->
<div class="settings-row" style="margin-top:16px">
<div class="settings-field full">
<label>硅基流动 API Key</label>
<input type="password" id="vector-api-key" placeholder="sk-xxx">
<div class="settings-hint">
💡 <a href="https://siliconflow.cn" target="_blank">硅基流动</a>
内置使用免费模型bge-m3、Qwen3-8B注册认证拿 Key 即可
</div>
</div>
</div>
<!-- 测试连接 -->
<div class="settings-row">
<div class="settings-field full">
<div class="engine-status-row">
<div class="engine-status" id="online-api-status">
<span class="status-dot"></span>
<span class="status-text">未测试</span>
</div>
<button class="btn btn-sm" id="btn-test-vector-api">测试连接</button>
</div>
</div>
</div>
<!-- 文本过滤规则 -->
<div class="filter-rules-section">
<div class="filter-rules-header">
<label>文本过滤规则</label>
<button class="btn btn-sm btn-add" id="btn-add-filter-rule">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none"
stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
添加
</button>
</div>
<p class="settings-hint">过滤干扰内容(如思考标签)</p>
<div id="filter-rules-list" class="filter-rules-list"></div>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- 记忆锚点L0 文本层)-->
<!-- ═══════════════════════════════════════════════════════════ -->
<div class="anchor-section">
<div class="anchor-header">
<div class="anchor-title">
<span class="anchor-icon">📌</span>
<span>记忆锚点</span>
</div>
<div class="anchor-hint">从对话中提取叙事锚点(情绪、地点、动作、揭示等)</div>
</div>
<div class="anchor-stats" id="anchor-stats">
<div class="anchor-stat-item">
<span class="anchor-stat-label">已提取楼层:</span>
<span class="anchor-stat-value"><strong id="anchor-extracted">0</strong></span>
</div>
<span class="anchor-stat-sep">/</span>
<div class="anchor-stat-item">
<span class="anchor-stat-label">总 AI 楼层:</span>
<span class="anchor-stat-value"><strong id="anchor-total">0</strong></span>
</div>
<div class="anchor-stat-pending" id="anchor-pending-wrap">
<span>(待提取 <strong id="anchor-pending">0</strong> 楼)</span>
</div>
<span class="anchor-stat-sep">·</span>
<div class="anchor-stat-item">
<span class="anchor-stat-label">L0 Atoms:</span>
<span class="anchor-stat-value"><strong id="anchor-atoms-count">0</strong>
</span>
</div>
<span class="anchor-stat-sep" id="anchor-extra-sep" style="display:none">·</span>
<div class="anchor-stat-item" id="anchor-extra-wrap" style="display:none">
<span id="anchor-extra"></span>
</div>
</div>
<!-- 进度条 -->
<div class="anchor-progress hidden" id="anchor-progress">
<div class="progress-bar">
<div class="progress-inner"></div>
</div>
<span class="progress-text">0/0</span>
</div>
<!-- 操作按钮 -->
<div class="anchor-actions" id="anchor-action-row">
<button class="btn btn-sm btn-p" id="btn-anchor-generate">生成</button>
<button class="btn btn-sm btn-del" id="btn-anchor-clear">清空</button>
<button class="btn btn-sm hidden" id="btn-anchor-cancel">取消</button>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- 当前聊天向量 -->
<!-- ═══════════════════════════════════════════════════════════ -->
<div class="vector-chat-section">
<div class="settings-row">
<div class="settings-field full">
<label>当前聊天向量</label>
<div class="vector-stats" id="vector-stats">
<div class="vector-stat-col">
<span class="vector-stat-label">L0 Vectors:</span>
<span class="vector-stat-value"><strong
id="vector-atom-count">0</strong></span>
</div>
<span class="vector-stat-sep">·</span>
<div class="vector-stat-col">
<span class="vector-stat-label">L1 Chunks:</span>
<span class="vector-stat-value"><strong
id="vector-chunk-count">0</strong></span>
</div>
<span class="vector-stat-sep">·</span>
<div class="vector-stat-col">
<span class="vector-stat-label">L2 Events:</span>
<span class="vector-stat-value"><strong
id="vector-event-count">0</strong></span>
</div>
</div>
<div class="vector-mismatch-warning hidden" id="vector-mismatch-warning">
⚠ 需重新生成向量
</div>
<div class="vector-empty-warning hidden" id="vector-empty-l0-warning">
⚠ 记忆锚点为空,建议先生成
</div>
</div>
</div>
<!-- 进度条 -->
<div class="engine-progress hidden" id="vector-gen-progress">
<div class="progress-bar">
<div class="progress-inner"></div>
</div>
<span class="progress-text">0%</span>
</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">
向量化现有 L0/L1/L2 数据(首次可能需要 1-2 分钟)
</div>
<!-- 导入导出 -->
<div class="vector-io-section">
<div class="settings-row">
<div class="settings-field full">
<label>向量迁移</label>
<div class="settings-btn-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>
<!-- Tab 3: Debug -->
<div class="tab-pane" id="tab-debug">
<div class="debug-log-header">
<div class="debug-title">🔧 记忆召回日志</div>
<div class="settings-hint">显示最近一次 AI 生成时的向量检索详情</div>
</div>
<pre id="recall-log-content" class="debug-log-viewer"></pre>
</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>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<script src="story-summary-ui.js"></script>
</body>
</html>