424 lines
20 KiB
HTML
424 lines
20 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>设置 · Writing Agent</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||
<link
|
||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Noto+Serif+SC:wght@500;600;700&display=swap"
|
||
rel="stylesheet"
|
||
/>
|
||
<script src="/theme-init.js"></script>
|
||
<link rel="stylesheet" href="/settings.css" />
|
||
</head>
|
||
<body class="settings-page">
|
||
<header class="st-top">
|
||
<a class="st-brand" href="/">Writing Agent</a>
|
||
<nav class="st-nav" aria-label="主导航">
|
||
<a href="/">对话</a>
|
||
<a href="/chat.html">直聊</a>
|
||
<a href="/modules.html">技能</a>
|
||
<a href="/libraries.html">库</a>
|
||
<a href="/stats.html">Token</a>
|
||
<a href="/presets.html">预设</a>
|
||
<a href="/settings.html" aria-current="page">设置</a>
|
||
</nav>
|
||
</header>
|
||
|
||
<div class="st-shell">
|
||
<aside class="st-rail" aria-label="设置分类">
|
||
<p class="st-rail-label">设置</p>
|
||
<button type="button" class="st-rail-item active" data-section="api">API</button>
|
||
<button type="button" class="st-rail-item" data-section="persona">用户角色</button>
|
||
<button type="button" class="st-rail-item" data-section="preference">用户偏好</button>
|
||
<button type="button" class="st-rail-item" data-section="preset">预设</button>
|
||
<button type="button" class="st-rail-item" data-section="storage">上下文</button>
|
||
<button type="button" class="st-rail-item" data-section="chat">直聊</button>
|
||
<button type="button" class="st-rail-item" data-section="appearance">外观</button>
|
||
<p class="st-rail-note">保存在本机 <code>~/.writing-agent/</code>,明文、不同步。</p>
|
||
</aside>
|
||
|
||
<main class="st-main">
|
||
<header class="st-main-head">
|
||
<div>
|
||
<h1 id="panel-title">API</h1>
|
||
<p class="st-sub" id="panel-subtitle">管理 LLM 连接</p>
|
||
</div>
|
||
<div class="st-main-actions" id="panel-actions"></div>
|
||
</header>
|
||
|
||
<div class="st-status" id="active-settings-bar"></div>
|
||
<div class="settings-toast" id="settings-toast" hidden></div>
|
||
|
||
<div class="st-body">
|
||
<section class="settings-section" id="section-api">
|
||
<div class="profiles-toolbar">
|
||
<label class="profiles-search">
|
||
<span class="visually-hidden">搜索 API 配置</span>
|
||
<input
|
||
type="search"
|
||
id="profile-search"
|
||
placeholder="搜索组名、模型、URL…"
|
||
autocomplete="off"
|
||
/>
|
||
</label>
|
||
</div>
|
||
<div id="profiles-list" class="card-list"></div>
|
||
</section>
|
||
|
||
<section class="settings-section hidden" id="section-persona">
|
||
<div class="settings-card storage-card">
|
||
<h3>用户角色</h3>
|
||
<p class="settings-help">
|
||
游玩时,对话里 <code>@玩家</code> / <code>{{user}}</code> 会换成当前选中角色的名字;
|
||
<code>@人设</code> 与预设的 persona 洞用其正文。可在输入框旁快速切换。
|
||
创作阶段不用当前选用,而用下方勾选的「创作默认」填入预设用户设定。
|
||
建议把创作默认的名称写成 <code>@玩家</code>:模型会按这个称呼写开场白,替换后仍是占位,进游玩再换成当前角色。
|
||
</p>
|
||
</div>
|
||
<div id="personas-list" class="card-list"></div>
|
||
</section>
|
||
|
||
<section class="settings-section hidden" id="section-preference">
|
||
<div class="settings-card storage-card">
|
||
<h3>用户偏好</h3>
|
||
<p class="settings-help">
|
||
跨局硬约束条目的增删与 LLM 提取已迁到顶栏
|
||
<a href="/libraries.html?tab=preferences">库 · 偏好库</a>。
|
||
创作期由「用户需求」节点从库选用写入本局,不再开玩时全量自动摊入。
|
||
</p>
|
||
<label class="field">
|
||
每隔多少游玩回合采集一次
|
||
<input
|
||
type="number"
|
||
id="preference-collect-every"
|
||
min="0"
|
||
max="100"
|
||
step="1"
|
||
value="10"
|
||
/>
|
||
</label>
|
||
<p class="settings-help muted">0 = 关闭自动采集。默认 10。候选经审核卡收下后写入偏好库。</p>
|
||
<div class="storage-actions">
|
||
<button type="button" class="btn-primary" id="btn-save-pref-every">保存间隔</button>
|
||
<a class="btn-secondary" href="/libraries.html?tab=preferences">打开偏好库</a>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-section hidden" id="section-preset">
|
||
<div class="settings-card storage-card">
|
||
<h3>发出前的消息整形</h3>
|
||
<p class="settings-help">
|
||
预设(如酒馆的 <code>squash_system_messages</code>)会要求把相邻同角色的消息合并。
|
||
这里默认「跟随预设」,也可以强制覆盖。
|
||
</p>
|
||
<div class="settings-form">
|
||
<div class="form-row">
|
||
<span class="form-label" id="preset-squash-label">合并模式</span>
|
||
<div class="form-control">
|
||
<select id="message-squash" aria-labelledby="preset-squash-label">
|
||
<option value="follow-preset">跟随预设</option>
|
||
<option value="none">不合并</option>
|
||
<option value="merge">合并相同角色连续的发言</option>
|
||
<option value="semi">半严格(强制对话角色交替)</option>
|
||
<option value="strict">严格(强制对话角色交替、用户最先)</option>
|
||
<option value="single-user-turn">单一用户消息(无工具)</option>
|
||
</select>
|
||
<p class="form-hint">
|
||
请求里带工具调用时,后三档会退化为「只合并相邻同角色」,避免拆散工具调用。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<span class="form-label" id="preset-tool-label">预设工具协议</span>
|
||
<div class="form-control">
|
||
<select id="preset-tool-policy" aria-labelledby="preset-tool-label">
|
||
<option value="adapt">适配(把预设要求的工具折算到已有工具上)</option>
|
||
<option value="app">只用本应用工具(排除预设的伪工具条目)</option>
|
||
<option value="preset">只发预设要求的工具</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<span class="form-label"><code>{{char}}</code> 取值</span>
|
||
<div class="form-control">
|
||
<p class="form-static">
|
||
优先用直聊上下文壳里的<strong>角色名</strong>;创作期取<strong>当前作品名</strong>
|
||
(对话落盘时给快照起的名字)。都没填时用「作品」。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="storage-actions">
|
||
<button type="button" class="btn-primary" id="btn-save-preset-runtime">保存</button>
|
||
</div>
|
||
</div>
|
||
<div id="presets-list" class="card-list"></div>
|
||
<pre id="import-report" class="import-report" hidden></pre>
|
||
</section>
|
||
|
||
<section class="settings-section hidden" id="section-storage">
|
||
<div class="settings-card storage-card">
|
||
<h3>LLM 请求上下文</h3>
|
||
<p class="settings-help">
|
||
每次模型调用可保存完整 prompt。默认每个对话只保留最新若干条;更早消息仍在,但去掉上下文痕迹。可在对话里查看上下文。
|
||
</p>
|
||
<label class="field">
|
||
每个对话保留最新条数
|
||
<input
|
||
type="number"
|
||
id="context-trace-keep"
|
||
min="0"
|
||
max="50"
|
||
step="1"
|
||
value="5"
|
||
/>
|
||
</label>
|
||
<p class="settings-help muted">0 = 不保存;最大 50。保存后对新回复立即生效。</p>
|
||
<div class="storage-actions">
|
||
<button type="button" class="btn-primary" id="btn-save-context-keep">保存</button>
|
||
<button type="button" class="btn-secondary" id="btn-prune-context">修剪已打开会话</button>
|
||
<button type="button" class="btn-secondary danger-outline" id="btn-clear-context">清除全部痕迹</button>
|
||
</div>
|
||
</div>
|
||
<div class="settings-card storage-card">
|
||
<h3>对话落盘补写</h3>
|
||
<p class="settings-help">
|
||
同一句用户消息里,填写没通过时可以再自动补几枪。每一枪只改失败点:已经通过的产物不动,没通过的用上一份正文当底稿,已经填对的自评维度会带回去。次数用尽后仍标成失败待补,等下一句继续。
|
||
</p>
|
||
<label class="field">
|
||
同轮最多补写次数
|
||
<input
|
||
type="number"
|
||
id="dictate-fill-retries"
|
||
min="0"
|
||
max="12"
|
||
step="1"
|
||
value="0"
|
||
/>
|
||
</label>
|
||
<p class="settings-help muted">0 = 只填一次。最大 12。例如设为 7,就是第一次填写之外最多再试 7 次。</p>
|
||
<div class="storage-actions">
|
||
<button type="button" class="btn-primary" id="btn-save-fill-retries">保存次数</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-section hidden" id="section-chat">
|
||
<div class="settings-card storage-card">
|
||
<h3>直聊卡片拖动</h3>
|
||
<p class="settings-help">
|
||
在直聊里按住回复卡片拖动(左右切卡片、上下滚正文)时,指针移动 1px 实际滚动多少 px。
|
||
</p>
|
||
<label class="field">
|
||
拖动倍率
|
||
<input
|
||
type="number"
|
||
id="chat-drag-gain"
|
||
min="0.5"
|
||
max="3"
|
||
step="0.1"
|
||
value="1.5"
|
||
/>
|
||
</label>
|
||
<p class="settings-help muted">
|
||
默认 1.5(拖 100px 走 150px);1 = 跟手;范围 0.5–3。保存后回到直聊刷新即生效。
|
||
</p>
|
||
<div class="storage-actions">
|
||
<button type="button" class="btn-primary" id="btn-save-drag-gain">保存倍率</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-section hidden" id="section-appearance">
|
||
<div class="settings-card storage-card">
|
||
<h3>主题</h3>
|
||
<p class="settings-help">
|
||
只改这台浏览器看到的配色,跟着本机走,不写进作品。点一张即生效。
|
||
</p>
|
||
<div class="appearance-themes" id="appearance-themes" role="radiogroup" aria-label="主题"></div>
|
||
</div>
|
||
<div class="settings-card storage-card">
|
||
<h3>字号</h3>
|
||
<p class="settings-help">
|
||
整体缩放界面文字,100% 为默认;范围 80–150%,步进 5%。
|
||
</p>
|
||
<div class="appearance-scale" role="group" aria-label="字号">
|
||
<button type="button" class="btn-secondary" id="appearance-scale-minus" aria-label="缩小字号">−</button>
|
||
<output class="appearance-scale-value" id="appearance-scale-value" aria-live="polite">100%</output>
|
||
<button type="button" class="btn-secondary" id="appearance-scale-plus" aria-label="放大字号">+</button>
|
||
<button type="button" class="btn-secondary" id="appearance-scale-reset">恢复默认</button>
|
||
</div>
|
||
<p class="settings-help muted">
|
||
同一浏览器的其他标签会自己跟上;回到别的页面刷新即生效。
|
||
</p>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<dialog id="preset-probe-dialog" class="probe-dialog">
|
||
<header class="probe-head">
|
||
<div>
|
||
<h3>试跑预设</h3>
|
||
<p class="probe-sub" id="probe-preset-name"></p>
|
||
</div>
|
||
<button type="button" class="btn-secondary" id="probe-close">关闭</button>
|
||
</header>
|
||
<div class="probe-body">
|
||
<section class="probe-composer">
|
||
<label>
|
||
本轮输入
|
||
<textarea id="probe-message" rows="4" placeholder="发给模型的这一轮"></textarea>
|
||
</label>
|
||
<details class="probe-extras">
|
||
<summary>试跑用设定 / 历史</summary>
|
||
<label>
|
||
历史前(设定)
|
||
<textarea id="probe-lore-before" rows="3"></textarea>
|
||
</label>
|
||
<label>
|
||
对话历史
|
||
<textarea id="probe-history" rows="4"></textarea>
|
||
</label>
|
||
<label>
|
||
历史后(状态)
|
||
<textarea id="probe-lore-after" rows="3"></textarea>
|
||
</label>
|
||
<label>
|
||
本轮后(可选)
|
||
<textarea id="probe-post-turn" rows="2"></textarea>
|
||
</label>
|
||
</details>
|
||
<div class="probe-composer-actions">
|
||
<button type="button" class="btn-primary" id="probe-send">发送一轮</button>
|
||
<p class="probe-status" id="probe-status" hidden></p>
|
||
</div>
|
||
</section>
|
||
<section class="probe-inspect" aria-live="polite">
|
||
<h4>发出的上下文</h4>
|
||
<div id="probe-messages" class="probe-messages">
|
||
<p class="probe-empty">发送后显示实际拼进请求的消息。</p>
|
||
</div>
|
||
<div id="probe-reply-wrap" class="probe-reply" hidden>
|
||
<h4>模型回复</h4>
|
||
<pre id="probe-reply"></pre>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</dialog>
|
||
|
||
<dialog id="profile-dialog" class="profile-dialog">
|
||
<form id="profile-form" method="dialog">
|
||
<h3 id="profile-dialog-title">新增 API</h3>
|
||
<label>
|
||
名称
|
||
<input name="name" required placeholder="例如 DeepSeek" autocomplete="off" />
|
||
</label>
|
||
<label>
|
||
Base URL
|
||
<input name="baseUrl" required placeholder="https://api.deepseek.com/v1" />
|
||
</label>
|
||
<label>
|
||
API Key
|
||
<input name="apiKey" type="password" placeholder="sk-…" autocomplete="off" />
|
||
</label>
|
||
<div id="profile-model-lock" class="model-lock" hidden>
|
||
<span class="model-field-label">型号</span>
|
||
<p id="profile-model-lock-value" class="model-lock-value"></p>
|
||
<span class="field-hint">型号是这一行的身份,探测跟着它走。要换型号,在组里点「添加型号」。</span>
|
||
</div>
|
||
<div id="profile-model-editor" class="model-field">
|
||
<label>
|
||
型号
|
||
<input name="model" placeholder="deepseek-chat" autocomplete="off" />
|
||
</label>
|
||
<span class="field-hint">保存的是这一格里的完整型号。下面的筛选只用来查找,点一条才会把完整名字填进来。</span>
|
||
<div class="model-field-row">
|
||
<input id="profile-model-filter" type="search" placeholder="筛选嗅探结果,如 gemini" autocomplete="off" aria-label="筛选嗅探结果" />
|
||
<button type="button" class="btn-secondary" id="profile-sniff">嗅探</button>
|
||
</div>
|
||
<p class="field-hint" id="profile-sniff-status" hidden></p>
|
||
<div id="profile-model-pick" class="model-pick">
|
||
<div id="profile-model-list" class="model-pick-list" role="listbox" aria-label="嗅探结果"></div>
|
||
<p class="model-pick-empty" id="profile-model-empty" hidden></p>
|
||
</div>
|
||
</div>
|
||
<label>
|
||
思考强度
|
||
<select name="reasoningEffort">
|
||
<option value="">默认(不发送)</option>
|
||
<option value="none">none · 关闭思考(带 tools 时自动用)</option>
|
||
<option value="low">low · 轻量</option>
|
||
<option value="medium">medium</option>
|
||
<option value="high">high · 增强</option>
|
||
<option value="max">max · 深度</option>
|
||
</select>
|
||
<span class="field-hint">只在 API 配置里设置,预设不再带思考强度。GLM-5.3:仅 low/high/max(none 会映射为 low),并附带 thinking.enabled;旧 GLM 只用 thinking、不发 reasoning_effort。Gemini 3.8:仅 low/medium/high,none 与 minimal 都会映射为 low,并附带 reasoning.effort。探测始终用 low。带 function tools 时:多数型号强制 none;GLM-5.3 与 Gemini 3 保留合法档。</span>
|
||
</label>
|
||
<div class="dialog-actions">
|
||
<button type="button" id="profile-cancel" class="btn-secondary">取消</button>
|
||
<button type="button" id="profile-save" class="btn-secondary">仅保存</button>
|
||
<button type="submit" class="btn-primary">保存并选用</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="add-model-dialog" class="profile-dialog">
|
||
<form id="add-model-form" method="dialog">
|
||
<h3>添加型号</h3>
|
||
<p class="field-hint" id="add-model-group"></p>
|
||
<div class="model-field-row">
|
||
<input id="add-model-filter" type="search" placeholder="筛选嗅探结果,如 gemini" autocomplete="off" aria-label="筛选嗅探结果" />
|
||
<button type="button" class="btn-secondary" id="add-model-sniff">嗅探</button>
|
||
</div>
|
||
<p class="field-hint" id="add-model-status" hidden></p>
|
||
<div id="add-model-pick" class="model-pick">
|
||
<div id="add-model-list" class="model-pick-list" role="listbox" aria-label="嗅探结果"></div>
|
||
<p class="model-pick-empty" id="add-model-empty" hidden></p>
|
||
</div>
|
||
<span class="field-hint">点一条会新建一行并选用。组里已有同名型号时,直接选用那一行,不改写别的型号。</span>
|
||
<div class="dialog-actions">
|
||
<button type="button" id="add-model-cancel" class="btn-secondary">关闭</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="persona-dialog" class="profile-dialog">
|
||
<form id="persona-form" method="dialog">
|
||
<h3 id="persona-dialog-title">新增用户角色</h3>
|
||
<label>
|
||
名称
|
||
<input name="name" required placeholder="例如 林晚,或创作默认用 @玩家" autocomplete="off" />
|
||
</label>
|
||
<label>
|
||
人设
|
||
<textarea name="description" rows="6" placeholder="性格、外貌、背景……(可空)"></textarea>
|
||
</label>
|
||
<label class="check-label">
|
||
<input type="checkbox" name="creationDefault" />
|
||
<span>
|
||
作为创作默认用户设定
|
||
<span class="field-hint">创作阶段填入预设的用户设定洞。建议名称写成 @玩家,这样开场白会自然用该占位,进游玩再换成当前角色。</span>
|
||
</span>
|
||
</label>
|
||
<div class="dialog-actions">
|
||
<button type="button" id="persona-cancel" class="btn-secondary">取消</button>
|
||
<button type="submit" class="btn-primary">保存并选用</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="preference-dialog" class="profile-dialog" hidden>
|
||
<!-- 偏好 CRUD 已迁至 /libraries.html -->
|
||
</dialog>
|
||
|
||
<input type="file" id="preset-file" accept=".json,application/json" hidden />
|
||
<script src="/settings.js" type="module"></script>
|
||
</body>
|
||
</html>
|