Files
writing-agent/web/settings.html
T
chenmoranzhiandCursor 39fe2732fb 把思考强度收到接口配置里,并支持分组、嗅探和切换同一接口下的模型。
预设不再保存思考强度。设置页会在首屏套用主题。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 12:00:27 +08:00

302 lines
14 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.
<!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="/modules.html">技能</a>
<a href="/shells.html">呈现壳</a>
<a href="/libraries.html">库</a>
<a href="/stats.html">Token</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>
<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 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>
</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>