Files
st-react/docs/预设模块优化设计.md
2026-03-13 21:51:42 +08:00

268 lines
11 KiB
Markdown
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.

## 预设模块Preset导入与启用逻辑优化设计
### 一、背景与问题
当前系统支持从 SillyTavern / TavernAI 风格的 JSON 文件导入预设并将其中的采样参数和提示词配置prompts / prompt_order存入后端 `AIPreset.Extensions` 字段中,前端在 `PresetManagePage` / `EditPresetModal` 中提供基本的编辑能力。
在实际使用中暴露出一个明显问题:
- **问题导入预设后提示词块prompts的启用 / 禁用状态与原始 JSON 文件不一致。**
具体表现:
- SillyTavern 预设中的「默认启用 / 禁用」信息主要体现在:
- `prompts[*].enabled`(整体默认开关,可能存在)
- `prompt_order[*].order[*].enabled`(更精细的 per-character 启用信息,是真正生效的一层)
- 我们当前前端 UI 在编辑时:
- 仅从 `extensions.prompts` 中取出 prompts
-`marker` 字段反向推导「启用」状态(`checked={!prompt.marker}`
- 完全没有解析和尊重 `prompt_order` 里的 `enabled` 状态
因此,对于像 `Sudachi.Next1.21.json` 这种复杂预设:
- 文件中已经通过 `prompt_order` 精确控制了某些块默认 `enabled: false`
- 导入后数据虽然保存在 `extensions.prompt_order` 中,但前端不读取,导致 UI 展示的勾选状态与原始预设不一致
### 二、设计目标
1. **忠实还原 ST 预设的启用 / 禁用行为**
- 导入后,在不改动 ST 原始结构的前提下,让 UI 所见的启用状态尽可能与 ST 一致。
2. **保持数据的完整与可扩展性**
- 保留 `prompts``prompt_order` 的原始结构,未来可以继续扩展更高级行为(多角色、多上下文)。
3. **简化前端编辑模型**
- 前端在编辑时只需要关注:
- 哪些 prompt 当前“启用”
- 顺序如何
- 不强制实现 ST 全量复杂逻辑,但要保证基础行为正确。
4. **向后兼容现有数据**
- 对于已存在的 `AIPreset`,在没有 `prompt_order` / `enabled` 信息时,仍然能正常工作。
### 三、数据结构与语义梳理
#### 3.1 ST 预设结构(简化视图)
- 顶层采样 / 行为字段(已正确映射到 `AIPreset`
- `temperature`, `top_p`, `top_k`, `min_p`, `top_a`
- `frequency_penalty`, `presence_penalty`, `repetition_penalty`
- `openai_max_tokens`, `openai_max_context`, `stream_openai`, 等
- `prompts: Prompt[]`
- `name: string`
- `identifier: string`(如 `"main"`, `"nsfw"`, `"jailbreak"` 或 UUID
- `role: "system" | "user" | "assistant"`
- `content: string`
- `system_prompt: boolean`
- `marker: boolean`(占位块;本身不一定等于“禁用”)
- `enabled?: boolean`(整体缺省启用的开关)
- `injection_position: number`
- `injection_depth: number`
- `injection_order: number`
- `injection_trigger: any[]`
- `forbid_overrides: boolean`
- `prompt_order: Array<{ character_id: number; order: Array<{ identifier: string; enabled: boolean }> }>`
- 按「角色 / 场景」维度,指定各 identifier 的启用状态与顺序
**关键点:**
真正决定「是否启用」的是 `prompt_order[*].order[*].enabled``prompts[*].enabled` 更多是全局默认值。
#### 3.2 当前系统存储结构
后端 `PresetService.ImportPresetFromJSON` 当前的行为:
-`prompts` / `prompt_order` 原样塞入:
```go
extensions := map[string]interface{}{
"prompts": stPreset.Prompts,
"prompt_order": stPreset.PromptOrder,
}
```
前端 `EditPresetModal` 当前的行为:
- 通过 `preset.extensions.prompts` 取得 `prompts` 列表
- 「启用」复选框用的是:
- `checked={!prompt.marker}`
- 也就是:只要不是 marker就视为启用
- 没有考虑:
- `prompt.enabled`
- `prompt_order[*].order[*].enabled`
### 四、优化方案总览
整体策略:
- **后端保持“原样存储”原则**,继续把 ST 的 `prompts``prompt_order` 保存在 `extensions` 里,方便以后做高级兼容。
- **新增一层“系统视角配置”**(推荐写在 `extensions.stMapping` 内),用于:
- 提取出我们当前真正要用到的“默认启用 / 禁用信息”
- 为前端提供简单直观的数据结构
- **前端 UI 只依赖这层映射**来决定默认勾选状态、顺序等,避免直接耦合 ST 的复杂行为。
### 五、后端导入逻辑优化设计
#### 5.1 导入时的启用状态归一化
`PresetService.ImportPresetFromJSON` 中,对 `stPreset.Prompts``stPreset.PromptOrder` 做一次归一化处理,得到:
- `map[string]bool``identifier``enabled`(系统视角下的默认启用状态)
处理规则建议如下:
1.`prompt_order` 中选出一个「主视角 character_id」
- 优先策略:取 `prompt_order`**第一个条目**`character_id`(例如 Sudachi 文件中的 `100000`)。
- 这样可理解为“该预设的主角 / 默认角色对应的一组配置”。
2. 针对该 `character_id``order` 数组:
- 遍历每一项 `{ identifier, enabled }`
- 记入 `enabledMap[identifier] = enabled`
3. 对于 `prompts` 中存在但在 `order` 中未出现的 identifier
-`prompt.enabled` 明确存在,则用该字段
- 否则默认 `true`(保持向后兼容,除非 ST 未来规范有特别说明)
归一化后的结果示意:
```json
{
"stMapping": {
"mainCharacterId": 100000,
"enabledByIdentifier": {
"main": true,
"worldInfoBefore": true,
"enhanceDefinitions": false,
"nsfw": true,
...
}
}
}
```
注意:
- 原始的 `prompts` / `prompt_order` 仍然原封不动地存放在 `extensions.prompts` / `extensions.prompt_order` 中。
- `stMapping` 只是我们系统内部使用的“视图”,不影响原始数据。
#### 5.2 可选:为 prompts 注入统一的 `enabled` 字段
为了前端更好使用,也可以在导入时(或者在后端读取时)对 `prompts` 做一次增强:
- 对每个 `prompt`
- 查询 `enabled := stMapping.enabledByIdentifier[prompt.identifier]`
- 如果找到了,就在该 `prompt` 上写入 / 覆盖 `prompt["enabled"] = enabled`
这样,前端只要拿到 `extensions.prompts`,就能直接看到每条的 `enabled` 字段,不必额外关联 `prompt_order`
> 兼容建议:
> - 不删除原有的 `enabled`,只是统一成一个规范值;
> - 如果未来 ST 升级行为,我们仍保留原始 JSON可以再次调整映射逻辑。
### 六、前端编辑行为优化设计
#### 6.1 EditPresetModal 中的启用逻辑修改
当前逻辑:
- 通过 `getPrompts()``extensions.prompts` 取出数组
- 复选框逻辑:
```tsx
<input
type="checkbox"
checked={!prompt.marker}
onChange={(e) => handlePromptChange(index, 'marker', !e.target.checked)}
/>
```
问题:
- 把 “是否 marker” 当成了 “是否启用”,与 ST 语义不符。
优化方案:
1. 新增对 `enabled` 字段的支持
- 解析时,优先使用:
- `prompt.enabled`(如果存在)
- 否则回退为 `!prompt.marker`(为了兼容老数据)
2. UI 含义调整:
- 勾选框表示「此 prompt 是否启用」,而非「是否 marker」
- `marker` 仍然保留,用于区分“占位块”(如 `chatHistory``dialogueExamples`)和“实际有内容的块”,但不再被直接当作启用条件。
示例(伪代码):
```tsx
const isEnabled = prompt.enabled !== undefined
? prompt.enabled
: !prompt.marker
<input
type="checkbox"
checked={isEnabled}
onChange={(e) => handlePromptChange(index, 'enabled', e.target.checked)}
/>
```
3. 保存时,将 `enabled` 写回 `extensions.prompts` 中对应项。
> 提醒:
> - 短期内可以不在前端去写 `prompt_order`,只要我们的对话生成逻辑是“按 prompts + enabled 过滤”就足够;
> - 如果未来要做到 ST 完整语义(如多角色不同顺序),再在前端提供更高级的顺序管理 UI。
#### 6.2 默认显示顺序
当前 UI 在 EditPresetModal 里对 prompts 没有太多排序逻辑,直接用数组顺序:
- 初期优化阶段,可以继续沿用 ST 原始 `prompts` 顺序
- 如果希望更贴近 ST 行为,可以:
- 根据 `injection_position` / `injection_depth` / `injection_order` 做一个简易排序
- 或者读取 `stMapping.mainCharacterId` 对应 `prompt_order` 的顺序,按其中 identifier 顺序排列
建议分阶段:
1. **第一阶段(快速修复启用逻辑)**
- 先只修复 `enabled` 使用,仍按原数组顺序展示
2. **第二阶段(顺序优化)**
- 再根据 `prompt_order` / injection 字段优化展示顺序
### 七、对话生成逻辑的使用建议(可选)
目前后端对话生成Convesation Service是如何使用预设参数需要在代码中再具体确认。但从设计角度建议
1. 生成最终 prompt 时:
-`extensions.prompts` 中筛选:`enabled != false` 的 prompt
- 按既定顺序(可先用原始数组顺序,或更细致地用 `injection_*` / `prompt_order`)拼接成系统提示。
2. 如需高度还原 ST 行为:
- 在后端增加一个专门的「ST Prompt 编译器」:
- 输入:`prompts` + `prompt_order` + 当前角色 / 会话上下文
- 输出:一串系统消息数组(包含角色、内容、插入位置信息)
- 这一步可以在后续迭代中实现,本次优化先以「启用状态正确」为目标。
### 八、兼容性与迁移策略
1. **已有预设数据**
- 已有的 `AIPreset.Extensions` 中可能只有 `prompts`,没有 `prompt_order` / `stMapping` / `enabled`
- `EditPresetModal` 在解析时:
- 若找不到 `prompt.enabled`,则默认 `enabled = !marker`
- 新导入的预设,将会有完整的 `enabled` / `stMapping` 信息。
2. **导出为 ST JSON**
- 目前导出的逻辑是从 `AIPreset` 再组装为 ST 样式的 JSON
- 建议导出时仍以 `extensions.prompts` / `extensions.prompt_order` 为主
- `enabled` 字段如果已更新,也可以同步写回到导出 JSON 中对应位置,保持一致性
3. **前后端升级顺序**
- 先在后端实现 `stMapping` 和 prompts 的 `enabled` 归一化逻辑
- 再在前端切换到从 `prompt.enabled` 读取启用状态
- 测试导入 Sudachi 预设后,界面上默认启用 / 禁用是否与原文件一致
### 九、后续扩展方向(可选)
1. **多角色 / 多场景支持**
- 完整解析 `prompt_order` 中所有 `character_id` 条目,根据当前对话绑定的角色 ID 切换不同启用方案。
2. **可视化 prompt 顺序编辑**
- 在 Preset 管理页面增加「提示词顺序拖拽调整」、分组展示(角色定义 / 文风 / COT / 变量初始化等)。
3. **预设版本管理与对比**
- 由于 ST 预设经常迭代,可考虑为 `AIPreset` 增加版本号 / 变更日志,以便回滚与对比。
---
以上设计以「最小代价修复启用/禁用逻辑不一致」为优先目标,同时为后续更深入兼容 ST 行为(如多角色、多 prompt_order 视图)预留空间。后续具体实现时,可以按本文件拆分为:
- 后端改动:`PresetService.ImportPresetFromJSON` + 可选的 Prompt 编译辅助函数
- 前端改动:`PresetManagePage` / `EditPresetModal` 中的 prompts 解析与复选框逻辑