Files
writing-agent/docs/preset-workbench.md
chenmoranzhi f3303038fe 新增预设工作台:编辑、版本树、结构检查、用例试跑放在同一页。
改预设要能一边写一边看装出来的 messages,还要多模型对照,所以把编辑与试跑拆成两个视图塞进一个页面;版本存完整快照、可分叉,用例全局共用一份、运行记录按预设存。顺带让读预设文件先剥 BOM,静态资源改成不缓存,免得改了脚本还要强刷。
2026-10-26 01:09:40 +08:00

183 lines
11 KiB
Markdown
Raw Permalink 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.
# 预设工作台
本仓库自用的预设编辑 / 试跑页面:`/presets.html`。它只编辑本仓库的原生 `PresetPackage`,
不做 SillyTavern 兼容导入(导入仍在设置页);目标是把「改一版 → 看结构 → 看实际 messages →
多模型试跑 → 标记好坏」压在同一页里。
## 1. 页面分工
页面是「一条身份条 + 两个视图 + 两个抽屉」。编辑与试跑是两件不同任务:编辑要连续写,试跑要宽度做对照,
因此不挤在同一个视口里。
```text
身份条 预设选择 / 新建 / 改名 / 脏状态 / 重新载入 / 试跑入口 / 版本树入口 / 保存两档 / 应用到对话页
唯一脏状态、唯一 primary(应用到对话页)都在这里,任何视图都能看到。
保存两档:「保存至当前版本」覆盖当前节点,「保存为新版本」追加子节点。
版本 id 不上条(只做 title),要看序列去版本树。
编辑视图 左轨(240)发送顺序大纲:序号 1..N、启用开关、位置标记、错误红点;
行可直接拖动改顺序(落点画一条强调色横线),标记洞也能拖——左轨顺序就是装配顺序;
未加入顺序的模块折叠在底部;最后两栏是「生成参数」与「假历史」。
中区 当前模块正文(一次只编辑一个;标记洞只显示说明,不能改名/删除,但能移顺序)。
「假历史」中区是逐条编辑:每条一个角色下拉 + 一整句正文 + 加一句 / 删除 / 上下移。
右栏(380)实际发出的 messages:顶部是结构检查状态带 + 用例选择 + 随时重算。
改正文、切模块、切用例都会本地重算预览(不调模型)。
试跑视图 顶部运行栏一行:当前用例名 · 模型(下拉多选:勾选不关菜单,可全选可用 / 清空)
· 已选条(只占一行,超出可左右拖,点一项取消) · 「运行此用例」。
左侧用例列表(一条 = 一个用例,点开进大浮窗编辑),右侧全宽结果网格(同一用例 × 多模型对照)。
低频入口:身份条上的「试跑 · 上次结果摘要」按钮,进入后按钮变成「← 编辑」。
抽屉 版本是右侧抽屉(版本树 / 说明 / 保存 / 载入 / 分叉);
用例表单是**大浮窗**(左列 5 个字段,右列历史条目编辑器),字段多、历史要宽度,
不再挤在窄侧栏里。两者都是低频或临时的,不常驻占内容宽度。
```
编辑视图一屏不滚:大纲、正文、预览各自滚动。预览在 ≤1180px 退成右侧浮层,≤860px 大纲也退成浮层。
## 2. 草稿、版本与应用
```text
草稿 页面内存里的 state.preset;每次编辑只改这里。
重新载入 丢掉草稿,从 presets/<presetId>.json 重读;不动版本树。
保存至当前版本 覆盖当前节点(amend),不新增节点。
保存为新版本 把草稿作为子节点追加到版本树,当前节点移到新节点。
载入选中 把选中节点的完整快照载入草稿,并把当前节点切过去。
从选中分叉 用当前草稿在选中节点下建子节点,原分支不动。
应用到对话页 先保存为新版本,再写 presets/<presetId>.json;对话页从这里读。
```
版本节点保存的是**完整 PresetPackage 快照**,不是 diff;因此任意节点都可独立载入。
版本树文件:`<data>/presets/versions/<presetId>.json`。新建预设会同时落预设文件和根版本。
版本抽屉里是 git 式的树:竖线是分支,圆点是节点(实心 = 当前编辑位置),分叉另开一条线。
点任一节点弹菜单:「读取」把这个快照换进编辑器,「从它分叉」用当前草稿在它下面开新分支,
再往右是「复制 id」。底部四个按钮(保存为新版本 / 保存至当前版本 / 载入选中 / 从选中分叉)保留给键盘流。
两种保存别搞混:
```text
保存为新版本(Ctrl+S) 在当前位置下追加子节点;历史留着,能回滚。
保存至当前版本(Ctrl+Shift+S) 直接覆盖当前节点,不产生新节点。适合“这条还没定,先改它”。
应用到对话页 先保存为新版本,再覆盖 presets/<presetId>.json。对话页读的是这个文件。
```
装配只看 `prompt_order.enabled`;`prompts[].enabled` 是酒馆遗留字段。工作台的模块开关
写的是顺序开关,因此和真实发送一致。
### 2.1 左轨定死顺序
```text
左轨顺序 = 装配顺序 = 真实发送顺序。
装配(src/preset/markers.ts: applyExtendedMarkers)只补缺不重排:
缺哪个洞就按锚点插进来(历史前 → 对话历史 → 历史后 → 本轮对话 → … → 本轮之后),
已经在 prompt_order 里的条目一个不动,作者拖到哪儿就发在哪儿。
右侧预览只是展示:同一条装配路径 + 预设的「消息整形」。整形会合并消息(`strict` 默认),
条数变少时预览元信息会写「整形 strict(装配 N 条 → 发出 M 条)」;
想逐条原样发,把「生成参数 → 消息整形」改成 `none`。
```
## 3. 结构检查
不调用模型,只查会在装配 / 发送时出问题的结构:
- 顺序引用了不存在的模块、同一模块在顺序里重复、顺序号重复;
- 未启用 `chatHistory`、模块为空、全角 `{{`;
- 未闭合的 `{{`(`{{{...}}` 的首个 `{` 是字面量,不算未闭合);
- 未识别宏(会原样外发);
- 标记位置重复启用。
`error` 只阻止「应用到对话页」,不阻止保存版本 / 预览;需要时可以强制写回。
## 4. 用例
一个用例就是一条记录:自己存、自己运行。页面上不出现「组」这个层级。
**用例是全局的,不和预设绑定**:一个用例只描述「要测的输入 + 上下文」,任何预设都能拿它去跑。
盘上是 `<data>/presets/tests/global/*.json`(沿用旧 `TestCaseGroup` 形状,每份用例一个只装它的组文件);
旧的按预设分目录存的(`tests/<presetId>/*.json`)会在第一次访问时并进 global,原文件不删。
删掉最后一份用例时组文件一并收掉。
「这个预设上次打开的是哪条用例」另外存:`<data>/presets/lab-state/<presetId>.json`,
记 `selectedCaseId`(运行视图当前用例)与 `previewCaseId`(右栏预览用例)。
写盘而不是塞浏览器缓存 —— 换浏览器、重开页面都还是同一条;切回别的预设又回到它自己那条。
用例列表的点击分工:**点卡片 = 选中(设为当前用例)**,只有卡片右边的「编辑」才打开用例编辑浮窗。
一个用例只包含变动项:
```text
用例名 / 角色名({{char}}) / 角色描述 / 世界书文本 / 用户输入 / 可选历史
```
历史是**逐条编辑**的:每条一个角色下拉(`user` / `assistant`)加一整句正文,正文里的换行就是真换行,
不再用「每行 `user: 内容`」的文本行和 `\n` 转义(旧写法会把不带头部的续行吞掉)。
落盘仍是 `{role, content}[]`,装配时一条一个 message,接在预设假历史之后。
组装走线上同一条路径:`assemblePlayWorkerMessages` → `shapeFramedMessages`。
## 4.1 假历史与示例对话是两回事
```text
假历史 预设级、真多轮(user / assistant),进「对话.历史」区,排在真实历史之前。
模型会把它当成已经说过的话,会占轮次;编辑器里正文换行原样保留。
存储:PresetPackage.pseudoHistory,装配见 src/preset/pseudo-history.ts。
示例对话 酒馆 mes_example(<START> 分块),发给模型时是 system + example_user /
example_assistant,不占轮次,只做 few-shot;正文仍是整段文本,保持酒馆兼容。
```
两者互不覆盖:要「让模型以为前情已经发生」用假历史,要「示范写法但不冒充轮次」用示例对话。
三者(预设假历史 / 用例历史 / 直聊种子历史)用的是同一个控件:`web/history-editor.js` + `web/history-editor.css`。
每行左侧的 `⠿` 是拖动手柄:按住拖到另一行的上半 / 下半即插到它前面 / 后面,松手就到位;
↑↓ 按钮保留(精确微调 / 不用鼠标拖时)。
## 5. 预览与多模型运行
```text
预览 不调用模型;显示当前草稿 + 当前用例实际发出的 messages。
运行 每个 用例 × 模型 发一次;默认最多 4 路并发、单次 180 秒超时。
运行使用请求里的草稿,所以不必先保存版本。
```
运行记录:`<data>/presets/runs/<presetId>/<runId>.json`,保存 `presetVersionId`、每次运行的
`messages` 快照和每个模型的结果。结果不自动评分,人工标记成功 / 失败;标记会落盘。
历史运行可在结果卡上方切回,并展开「发送内容」核对实际请求。
## 6. HTTP 接口
前缀 `/api/preset-lab/`:
```text
GET presets 预设列表
POST presets/create 新建原生预设 + 根版本
GET preset?presetId= 读预设文件
POST preset/apply 应用到对话页({force:true} 跳过 error)
GET versions?presetId= 版本树
POST versions/commit 追加节点(缺省父节点=当前;指定历史节点=分叉)
POST versions/amend 覆盖当前节点(保存至当前版本)
POST versions/checkout 切换当前节点
POST check 结构检查
POST preview 按用例组装 messages
POST run 多模型运行(可带 preset 草稿;一次跑一个用例)
GET cases 使用例列表(全局,不看 presetId)
POST cases/save 存单份用例(带 groupId 就写回原文件)
POST cases/delete 删单份用例(空文件自动收掉)
GET lab-state?presetId= 这个预设上次打开的用例
POST lab-state 写 { presetId, selectedCaseId?, previewCaseId? }
GET groups 旧接口:测试组列表
POST groups/save 旧接口:整组覆盖
POST groups/delete 旧接口:删组
GET runs?presetId= 运行记录列表
GET run?presetId=&runId= 单次运行
POST verdict 修改人工标记
GET profiles 可选 API 配置
```
## 7. 已知边界
- 版本树不做自动合并;分叉后各分支独立。
- 运行是普通 HTTP 请求,页面会一直等到全部结果返回;没有逐条流式进度。
- 「应用到对话页」只改预设文件;要让已有会话立刻使用,去设置页点「启用」。
- 测试用例不会自动生成,也不会自动评分。