## 预设模块（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 解析与复选框逻辑

