Files
st-react/docs/重构文档.md
2026-03-13 21:51:42 +08:00

459 lines
23 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.

这是一个需要长期演进、但方向已经比较清晰的重构计划。为了让“云酒馆”既能承载多用户公共场景,又能高度兼容 SillyTavern以下简称 **ST**)生态,本方案结合当前代码与 `st/` 源码,对整体架构和落地路径进行整理与校准。
---
## 1. 总体架构与设计原则
### 1.1 技术栈与目录结构
- **前端**React 18 + TypeScript + Tailwind CSS + Zustand
- 代码目录:`web-app/`
- **后端**Go + Gin + GORM + pgvector
- 代码目录:`server/`
- **数据库**PostgreSQL 15+(大量使用 JSONB部分向量字段使用 pgvector
- **文件存储**S3 兼容MinIO / OSS 等),用于 PNG 角色卡 / 头像等
- **ST 源码**`st/` 目录,用作兼容参考与数据样例
### 1.2 云版 vs 本地版的根本差异
- **ST 当前形态**
- 本地应用,配置与数据以文件形式存在 `default/content/**``data/**`
- 单机 / 小规模用户,无严格多租户和权限隔离
- **云酒馆目标形态**
- **数据库优先**:所有业务数据(用户、角色、预设、世界书、对话、正则脚本等)统一落到 PostgreSQL
- **多租户隔离**:所有核心表带 `user_id`,中间件强制 `WHERE user_id = ?`
- **前后端分层清晰**
- 后端 → 唯一“真相来源”SSOT负责 Prompt pipeline、世界书触发、正则处理、变量替换、AI 调用
- 前端 → 纯 UI 壳:负责编辑与展示,不再私自保存“内存版世界书/预设”
---
## 2. 当前系统现状(结合现有代码)
### 2.1 后端(`server/`
`server/model/app/README.md` 以及各模型文件来看,当前状态大致为:
- **用户与会话**(已完成)
- 模型:`AppUser``AppUserSession`
- JWT 登录 / 刷新 / 注销 / 用户资料修改等已具备
- **角色卡管理AICharacter**(已完成)
- 模型:`AICharacter`(使用 JSONB 存 ST V2 数据,如 `CardData` / `Tags` 等)
- 支持 PNG / JSON 角色卡导入、角色列表、详情、编辑、删除、导出
- **预设管理AIPreset**模型已就绪API 待补齐)
- 模型:`AIPreset`,已拆分采样参数字段 + `Extensions JSONB`
- **世界书AIWorldInfo与正则脚本RegexScript**(模型已存在,逻辑未完全落地)
- 模型:`AIWorldInfo`(世界书实体)
- 模型:`RegexScript`(完全对齐 ST 正则脚本字段)
- **对话与消息**(基础模型已存在)
- 模型:`AIChat``AIMessage``AIMessageSwipe`,用于会话与多候选
- **AI 配置与向量记忆**(已具备)
- 模型:`AIProvider``AIModel``AIMemoryVector`
> 小结后端数据模型已经为「ST 兼容 + 云平台」打好了基础,但 Prompt pipeline、世界书触发引擎、正则处理引擎等运行时逻辑仍需集中落地。
### 2.2 前端(`web-app/`
- **状态管理Zustand**`src/store/index.ts`
- 单一 Store明确区分 `Model` / `Actions` / `Selectors`
- 内置变量系统 `variables: { user, char, ... }``substituteVariables()` 工具
- **用户系统**:登录 / 注册 / 用户资料页已接入后端
- **角色卡管理**:角色列表 / 上传 PNG/JSON / 编辑 / 导出已接入 `/app/character`
- **预设管理**:有 UI 骨架,但部分仍依赖前端假数据,需要接入真实 `/app/preset`
- **聊天界面**`ChatPage` + `ChatArea` + `MessageContent`
- 布局与基本会话切换已完成
- 近期已接入:
- `regexEngine.ts`:前端正则脚本执行引擎
- `textRenderer.ts`:文本渲染管线(解析 `<maintext>` / `<Status_block>` / choices 等)
- `ChatArea.tsx` 中从后端加载 `RegexScript` 列表,将用户 / 角色变量与脚本一并传入 `MessageContent`
> 小结:前端已具备 MVU 风格的状态管理与 ST 风格文本渲染能力,下一步是把预设 / 世界书 / 正则选择与后端 pipeline 串联起来。
---
## 3. 目标架构SillyTavern 兼容实现)
### 3.1 核心领域模型(后端视角)
基于当前 `server/model/app`,目标是让以下模型整体表达 ST 的角色卡 / 世界书 / 正则 / 预设 / 对话体系:
- **AICharacter**
- 存 ST 角色卡 V2/V3 标准化版本(结构化字段)+ 原始 JSON 存档(用于无损导出)
- 预留:
- `raw_card_json JSONB`(若尚未添加,可在后续迁移中补上)
- `bound_worldinfo_ids`:角色绑定世界书 ID 列表(可选)
- `bound_regex_ids`:角色绑定正则脚本 ID 列表(可选)
- **AIWorldInfoWorldbook & WorldbookEntry**
- 支持:
- 角色内世界书(挂在某个角色下)
- 用户级 / 全局世界书(多个角色共享)
- 字段需覆盖 ST 世界书的完整配置:
- `keys / secondary_keys / comment / content / constant / disabled / use_regex / case_sensitive / match_whole_words / selective / selective_logic / position / depth / order / probability / sticky / cooldown / delay / group / 额外 JSON`
- **RegexScript**
- 已与 ST 字段对齐:
- `findRegex / replaceWith / trimStrings(JSONB)`
- `placement`(输入 / 输出 / 世界书 / 显示等阶段)
- `disabled / markdownOnly / runOnEdit / promptOnly`
- `substituteRegex / minDepth / maxDepth`
- `scope (global | character | preset)` + `ownerCharId / ownerPresetId`
- `order`(执行顺序)
- **AIPreset + 绑定关系**
- `AIPreset` 存采样参数 + systemPrompt + 其他配置
- 预留 `PresetPrompt` / `PresetRegexBinding` 等概念,用于:
- 描述 prompt 列表(不同位置、深度)
- 绑定正则脚本到特定预设
- **AIChat / AIMessage**
- 对话Conversation与消息Message
-`AICharacter``AIPreset`、AI 配置等建立关联
- `AIChat.Settings JSONB` 存储会话级设置:
- 当前 presetId
- 活动世界书 ID 列表
- 活动正则脚本 ID 列表
### 3.2 运行时 Prompt Pipeline目标形态
所有客户端(目前只有 React Web未来可能有更多统一通过 Go 后端完成以下完整流程:
1. **加载上下文**
- 根据 conversationId 加载:`AIChat``AICharacter``AIPreset`、AIConfig、绑定的 `AIWorldInfo``RegexScript`
2. **输入正则处理placement = input**
- 对用户输入文本先跑一遍 RegexScriptglobal + character + preset
3. **写入 user message** 到数据库
4. **世界书扫描World Info Engine**
- 在最近 N 条消息 + 角色描述 / 场景 等组成的扫描文本上,套用 ST 的 world-info 算法:
- 主 / 副关键词匹配,支持 `use_regex``caseSensitive``matchWholeWords`
- `selectiveLogic` 控制主 / 副键组合逻辑
- `depth``sticky``cooldown``delay``probability` 等控制触发频次与时间窗
5. **Prompt 构建**
- system 部分:角色 system + scenario + 作者注 + preset.systemPrompt + world info
- history 部分:最近若干 user/assistant 消息
- 预设附加 prompt按 depth / position / order 注入其他内容
6. **模型调用**
- 根据 `AIProvider` / `AIModel` / `/app/ai-config` 配置,调用相应厂商 APIOpenAI 兼容 / Anthropic 等)
- 支持 SSE 流式输出,将 token 流推送给前端
7. **输出正则处理placement = output**
- 对完整 AI 输出再次执行 RegexScriptglobal + character + preset
8. **写入 assistant message** 到数据库
9. **返回前端**
- 返回最终完整消息内容;若使用 SSE还需流式推送增量 token
---
## 4. 关键子系统的详细设计
### 4.1 用户与多租户隔离
- 所有“用户私有”数据表必须带 `user_id`
- 角色:`ai_characters.user_id`
- 预设:`ai_presets.user_id`
- 世界书:`ai_world_info.user_id`
- 正则脚本:`regex_scripts.user_id`
- 对话 / 消息:`ai_chats.user_id``ai_messages.user_id`
- **共有资源**
- 官方 Demo 角色 / 预设,可以 `user_id = 0``NULL`
- **中间件**
- 在 Gin 中实现统一鉴权中间件,从 JWT 中解析 `user_id`
- 在 Service 层封装“自动附带 `user_id` 条件”的查询方法,避免重复书写 `WHERE user_id = ?`
### 4.2 预设系统AIPreset与 ST 兼容
- **ST 现状**
- 预设分散在 `st/default/content/presets/**` 的 JSON 文件中,按 API 类型区分(`context` / `instruct` / `openai` / `textgen` 等)
- 前端 `public/scripts/preset-manager.js` 在内存管理这些预设
- **云酒馆设计**
- 使用 `AIPreset` 表存储统一预设:
- 常见采样参数拆为字段(`Temperature/TopP/TopK/...`
- 复杂或厂商特定配置放入 `Extensions jsonb`
- 导入工具:
- 后台脚本扫描 ST `presets` 目录 → 解析 JSON → 映射为 `AIPreset`
- 前端:
- 新建 `src/api/preset.ts` 封装 `/app/preset` 系列接口
- `PresetManagePage` 完全改为“后端驱动”(删除假数据 `useState`
### 4.3 变量与宏系统Variables & Macros
> 这一块需要区分“现状”与“目标”。
- **ST 当前做法(前端)**
- `public/scripts/variables.js`:维护局部 / 全局变量(基于 `chat_metadata``extension_settings`
- `public/scripts/macros.js` + `scripts/macros/**`:处理 `{{user}} / {{char}} / {{random}} / {{pick:...}}` 等宏
- 宏替换发生在前端构造 prompt 的阶段
- **云酒馆现状(前端)**
- `web-app/src/store/index.ts`
- `variables: Record<string, string>``user` / `char`
- `substituteVariables(text, customVars?)` 支持时间宏、随机数、`pick`
- `web-app/src/lib/textRenderer.ts` 中也实现了一套独立的变量替换逻辑
- **目标设计(后端统一化)**
- 中短期可以继续在前端做变量替换(已实现),减少一次性改动量
- 中长期建议:
- 后端增加 `user_variables` 表,存 `(user_id, key, value)`
- 在 Prompt pipeline 最后一步由后端统一执行递归变量 / 宏替换(直到不再出现 `{{...}}`
- 文档中要清楚标注:**“后端统一替换变量”是重构目标,而非当前 ST 现状,也不是当前实现状态。**
### 4.4 世界书引擎World Info Engine
- **ST 实现(真实代码)**`st/public/scripts/world-info.js`
- 非常复杂,包含:
- 多种扫描策略(按 persona / description / scenario / creatorNotes 等)
- 关键字与正则混用(`use_regex`
- 主键 / 副键组合逻辑(`selectiveLogic`
- `depth` / `sticky` / `cooldown` / `delay` / `probability` / `position` / `group` 等参数
- **云酒馆建议做法**
1. 从以下来源收集“激活世界书列表”:
- 用户全局启用:例如用户偏好中的 `activeWorldInfoIds`
- 角色绑定:`AICharacter` 上的世界书绑定字段
- 会话级设置:`AIChat.Settings.activeWorldInfoIds`
2. 遍历所有激活世界书的 entries
- 跳过 `disabled`
-`constant = true`,无视关键词匹配直接候选(仍受 sticky / cooldown / delay / probability 影响)
- 根据 `use_regex` / `caseSensitive` / `matchWholeWords``keys` / `secondary_keys` 做匹配
-`selectiveLogic` 结合主副键匹配结果
- 应用 `depth` / `sticky` / `cooldown` / `delay` / `probability` 等控制触发频次
3. 将激活的 entries 按 `order` / `position` 规则,注入到 Prompt 中对应位置
- **现实建议**
- 为保证兼容性,尽量参考 / 复刻 ST `world-info.js` 的排序和筛选逻辑,而不是“重写一套简化版”。
### 4.5 正则脚本引擎Regex Scripts
- **后端模型**`RegexScript`(已实现,字段与 ST 对齐)
- **前端执行(显示层)**
- `web-app/src/lib/regexEngine.ts`
- `applyRegexScripts(text, placement, scripts, depth?)`
- `processAIOutput` / `processUserInput` 等便捷函数
- 支持 `/pattern/flags`、捕获组 `$1/$2/...``{{match}}``trimStrings`
- `ChatArea.tsx`:加载 `/api/regex` 列表,过滤掉 `disabled`,传给 `MessageContent`
- **后端执行Prompt 层)**
- 输入阶段:用户文本入库 / 调用模型前,对应 ST 的“input placement”
- 输出阶段:模型输出保存 / 返回前,对应 ST 的“output placement”
- 世界书阶段:若需要在构造 world info 前后也跑一次 Regex可以利用 `placement` 细分
---
## 5. 前端重构与集成方案(`web-app/`
### 5.1 MVU 状态管理Zustand
- **现状**
- `src/store/index.ts` 已以 MVU 思路实现:
- `AppState` = Model
- `AppActions` = Update
- React 组件 = View
- 使用 `persist``devtools` 扩展,变量系统内置在 `variables` 字段中
- **建议**
- 将以下信息也纳入 Store而不是零散地用本地 `useState`
- 当前会话使用的 `presetId`
- 当前会话启用的世界书 ID 列表
- 当前会话启用的正则脚本 ID 列表
- 通过一个统一的 `ConversationSettings` 结构,与后端 `AIChat.Settings` 对应
### 5.2 角色卡管理页面CharacterManagePage
目标:前端字段与 ST V2/V3 完全对齐,为后端世界书 / 正则拆分做好数据基础。
- **字段映射建议**
- `firstMes``first_mes`
- `mesExample``mes_example`
- `systemPrompt``system_prompt`
- `postHistoryInstructions``post_history_instructions`
- `characterBook``character_book`
- `extensions``extensions`
- **世界书编辑 UI**
- 在现有 `keys / content / enabled / position` 基础上,逐步引入高级字段(折叠到“高级设置”):
- `secondary_keys``constant``use_regex``case_sensitive``match_whole_words`
- `selective``selective_logic`
- `depth``order``probability``sticky``cooldown``delay``group`
- 保存时将上述字段完整写入 `characterBook.entries` 中,后端再拆分到 `AIWorldInfo` 相关表
- **导入/导出**
- 前端不对 ST JSON 结构做“智能改写”,只做表单 ↔ JSON 的字段映射
- ST 兼容性由后端 `utils/character_card.go` + `AICharacter` 来保证
### 5.3 预设管理页面PresetManagePage
目标:彻底从“前端内存预设”升级为“后端驱动的 ST 预设系统”。
- 实施步骤:
1. 后端按规划实现 `/app/preset` 的 CRUD + 导入/导出
2. 前端新建 `src/api/preset.ts`,统一管理预设相关请求
3.`PresetManagePage` 中的本地假数据与 `useState` 初始值删除,全部改为从 API 加载
4. 预设 JSON 字段对齐 ST
- 采样参数:`temperature/top_p/top_k/frequency_penalty/presence_penalty/...`
- Prompt 配置:`system_prompt/stop_sequences/...`
### 5.4 聊天与设置面板ChatPage / SettingsPanel / CharacterPanel
- **SettingsPanel 增强**
- 增加:
- 当前会话使用的 preset下拉列表来自 `/app/preset`
- 启用的世界书列表(来自 `/app/worldinfo`,初期可只展示角色内世界书)
- 启用的正则脚本列表(来自 `/app/regex`
- 保存时调用 `/app/chat/:id/settings`(或 `PUT /app/conversation/:id`)更新 `AIChat.Settings`
- **ChatArea & MessageContent 集成**
- `ChatArea.tsx`
- 已从 `useAppStore` 取出 `user``variables`
- 已加载 `RegexScript` 列表,并传给 `MessageContent`
- `MessageContent.tsx`
- 利用 `regexEngine` + `textRenderer` 完成:
- 正则处理AI 输出修饰)
- `<maintext>` / `<Status_block>` / choices 解析
- 变量替换 / HTML 转义 / 代码块提取 / XSS 防护
---
## 6. 分阶段落地路线图
### 阶段 1打通 CRUD 与数据流(短期)
- **后端**
- 巩固 `/app/character`(保证 ST V2/V3 导入/导出稳定)
- 实现 `/app/preset` 全套 CRUD + 导入/导出
- 补齐世界书 `/app/worldinfo` 与正则 `/app/regex` 接口(基于现有模型)
- **前端**
- `PresetManagePage` 完全接入 `/app/preset`
- 角色编辑页面补全世界书 entry 字段,保持与 ST 的字段对齐
### 阶段 2实现 Prompt Pipeline 与对话 API中期
- **后端**
- 在 service 层实现统一的 `chatPipeline`串接世界书、正则、变量、预设、AI 调用逻辑
- 统一对话入口,例如:`POST /app/chat/:id/messages``POST /app/conversation/:id/message`
- 支持 SSE 流式输出,将 token 流推送给前端
- **前端**
- Chat 页面改造为消费 SSE 流(追加消息 / 局部更新)
- SettingsPanel / CharacterPanel 配置的 preset / 世界书 / 正则 与后端 `AIChat.Settings` 打通
### 阶段 3插件系统与高级特性长期
- **后端**
- 引入插件表与 Hook 定义(`onUserInput` / `onWorldInfoScan` / `beforePromptBuild` / `onAssistantDone`
- 使用 goja / WASM 等方式实现安全的插件执行沙箱
- 增强审计、统计与限流机制
- **前端**
- 插件管理页:安装 / 启用 / 配置 / 查看日志
- 调试视图:展示某次回复中有哪些 world info / 正则 / 插件生效
---
## 7. 重要澄清与修正(相对早期文档)
- **关于 Zod**
- 当前 `st/` 源码并未使用 Zod而是通过 `TavernCardValidator` 等自写校验器验证角色卡结构
-`web-app` 中引入 Zod 进行前端 schema 校验是一个“改进方向”,不是 ST 现状
- **关于 MVU**
- ST 当前前端仍是 jQuery + 全局脚本,不是典型 MVU 架构
- MVU 已在云酒馆前端的 Zustand Store 中落地,应把 MVU 归于“云酒馆前端架构”,而非 ST 现状
- **关于变量 / 宏所在层级**
- ST 将变量与宏主要放在前端处理
- 云酒馆的目标是:短期兼容这种做法,中长期逐步迁移到后端统一替换
- **关于 PNG 块类型**
- ST 将角色卡 JSON 植入 PNG 文本 chunk`tEXt` / 自定义块),不必限定为 `iTXt`
- Go 侧应兼容多种文本块,保证最大兼容性
## 8. 核心模块开发方案
### 8.1 用户管理与数据隔离
**ST 现状**: 物理文件夹隔离(`public/users/admin/...`)。
**重构方案**:
* **数据库设计**: 所有表(`characters`, `chats`, `presets`, `lorebooks`)必须包含 `user_id` 字段。
* **权限控制**: 后端中间件统一拦截请求,从 JWT 中提取 `user_id`,并在所有 SQL 查询中强制加入 `WHERE user_id = ?` 约束。
* **隔离策略**:
* **共有资源**: 官方提供的默认预设、系统角色卡(`user_id` 为 0 或 NULL
* **私有资源**: 用户上传或创建的内容。
### 8.2 预设系统 (Presets) 兼容方案
**ST 现状**: 大量的 JSON 文件,分为 API、Instruct、Context 等类别。
**重构方案**:
* **存储**: 在 PostgreSQL 中创建 `presets` 表,核心配置字段设为 `jsonb` 类型。
* **导入逻辑**: 编写一个 Go 工具类,读取 ST 的 `presets` 文件夹,将 JSON 解析后存入数据库。
* **注意事项**: ST 的预设经常更新字段Go 的结构体定义要使用 `map[string]interface{}` 或冗余字段来保证在 ST 更新协议时,你的后端不会因为缺失字段而崩溃。
### 8.3 变量管理与宏引擎 (Variables & Macros)
这是兼容性的灵魂。
**实现原理**:
1. **基础宏**: `{{user}}`, `{{char}}`, `{{description}}` 等。
2. **动态变量**: 用户通过 `/setvar` 定义的变量。
**重构方案**:
* **后端处理**: 不要在前端替换变量,而是在后端构建 Prompt 的最后一刻进行正则替换。
* **变量表**: `user_variables` 表,存储 `(user_id, key, value)`
* **递归解析**: ST 支持嵌套宏,你的 Go 后端需要实现一个递归替换函数,直到字符串中不再包含 `{{...}}`
### 8.4 前端渲染与通信兼容 (Iframe/Sandbox)
**ST 现状**: jQuery 操作 DOM扩展直接注入 HTML。
**重构方案**:
* **渲染分离**: 使用 React 开发 UI。消息列表应支持 Markdown 解析(推荐使用 `react-markdown` 并配合插件)。
* **通信协议**:
* 前端 React 通过 **WebSocket****SSE** 与 Go 后端保持长连接,模拟 ST 的流式输出。
* 定义一套标准的 JSON API 响应格式,模仿 ST 的 `/api/...` 接口,以便未来的脚本迁移。
### 8.5 扩展系统 (Extensions) 的安全性重构
**ST 现状**: 动态加载 JS 脚本,安全性极低。
**重构方案 (公共平台推荐)**:
* **后端扩展**: 模仿 ST 的插件钩子,但在 Go 后端实现。例如,在发送 Prompt 前,调用一个“拦截器”逻辑。
* **前端扩展**: 如果要兼容 ST 的 JS 插件,**必须将其运行在沙箱Sandbox中**。
* 使用 `<iframe>` 隔离扩展 UI。
* 通过 `postMessage` 机制进行通信。
* **注意事项**: 绝不允许第三方扩展直接访问浏览器的 `localStorage` 或获取用户的 `user_token`
---
## 9. Zod 与 MVU 的重构实现
### 9.1 Zod 变量的作用与迁移
在 ST 源码中Zod 用于 **Runtime Schema Validation**
* **作用**: 当用户上传一个外部角色卡JSON 或 PNG 嵌入数据系统无法确定数据是否完整。Zod 会在前端校验数据格式,如果缺失关键字段,会提供默认值或报错。
* **重构建议**:
* **前端**: 继续在 React 中使用 `zod`。在处理角色卡导入逻辑时,定义与 ST 完全一致的 `CharacterSchema`
* **后端**: Go 端使用 `go-playground/validator` 或直接解析 `jsonb`。确保你的 Go 结构体Struct打上 `json:",omitempty"` 标签,以处理 ST 角色卡中不稳定的可选字段。
### 9.2 MVU (Model-View-Update) 的应用
* **作用**: ST 引入 MVU 是为了解决 jQuery 状态难以管理的问题。
* **重构建议**:
* 在 React 中MVU 就是 **Zustand/Redux + React Components**
* **Model**: Zustand 中的 `store`
* **Update**: Store 中的 `actions`
* **View**: React 组件。
* 你不需要在代码里写 `mvu` 变量名,只需遵循这种单向数据流即可实现比 ST 稳定得多的状态管理。
---
## 10. 开发注意事项 (踩坑预警)
1. **角色卡解析**: ST 的 PNG 角色卡数据存放在 PNG 的 `iTXt` 块中。Go 语言需要使用 `image/png` 包并手动读取这些 Metadata 块。不要只支持 JSON因为大部分玩家的资源都是 PNG。
2. **Lorebook (世界书) 逻辑**:
* 这是最难兼容的部分。它涉及:关键字扫描、递归深度、插入位置、插入顺序。
* **建议**: 直接复刻 ST 的 `world-info.js` 中的排序算法。如果这部分算法不一致AI 的表现会与原版 ST 大相径庭。
3. **计算 Token**: ST 使用 `Tiktoken` 或特定模型的 Tokenizer。为了节省服务器资源建议在前端进行初次计算后端在调用 API 前再次校准。
4. **跨域与 API 代理**: 公共平台需要处理大量大模型 API 的转发。Go 后端要实现一个高性能的 **Proxy Layer**处理超时、重试以及不同厂商OpenAI, Anthropic, Google的协议转换。