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

23 KiB
Raw Blame History

这是一个需要长期演进、但方向已经比较清晰的重构计划。为了让“云酒馆”既能承载多用户公共场景,又能高度兼容 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 以及各模型文件来看,当前状态大致为:

  • 用户与会话(已完成)
    • 模型:AppUserAppUserSession
    • JWT 登录 / 刷新 / 注销 / 用户资料修改等已具备
  • 角色卡管理AICharacter(已完成)
    • 模型:AICharacter(使用 JSONB 存 ST V2 数据,如 CardData / Tags 等)
    • 支持 PNG / JSON 角色卡导入、角色列表、详情、编辑、删除、导出
  • 预设管理AIPreset模型已就绪API 待补齐)
    • 模型:AIPreset,已拆分采样参数字段 + Extensions JSONB
  • 世界书AIWorldInfo与正则脚本RegexScript(模型已存在,逻辑未完全落地)
    • 模型:AIWorldInfo(世界书实体)
    • 模型:RegexScript(完全对齐 ST 正则脚本字段)
  • 对话与消息(基础模型已存在)
    • 模型:AIChatAIMessageAIMessageSwipe,用于会话与多候选
  • AI 配置与向量记忆(已具备)
    • 模型:AIProviderAIModelAIMemoryVector

小结后端数据模型已经为「ST 兼容 + 云平台」打好了基础,但 Prompt pipeline、世界书触发引擎、正则处理引擎等运行时逻辑仍需集中落地。

2.2 前端(web-app/

  • 状态管理Zustandsrc/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
    • AICharacterAIPreset、AI 配置等建立关联
    • AIChat.Settings JSONB 存储会话级设置:
      • 当前 presetId
      • 活动世界书 ID 列表
      • 活动正则脚本 ID 列表

3.2 运行时 Prompt Pipeline目标形态

所有客户端(目前只有 React Web未来可能有更多统一通过 Go 后端完成以下完整流程:

  1. 加载上下文
    • 根据 conversationId 加载:AIChatAICharacterAIPreset、AIConfig、绑定的 AIWorldInfoRegexScript
  2. 输入正则处理placement = input
    • 对用户输入文本先跑一遍 RegexScriptglobal + character + preset
  3. 写入 user message 到数据库
  4. 世界书扫描World Info Engine
    • 在最近 N 条消息 + 角色描述 / 场景 等组成的扫描文本上,套用 ST 的 world-info 算法:
      • 主 / 副关键词匹配,支持 use_regexcaseSensitivematchWholeWords
      • selectiveLogic 控制主 / 副键组合逻辑
      • depthstickycooldowndelayprobability 等控制触发频次与时间窗
  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_idai_messages.user_id
  • 共有资源
    • 官方 Demo 角色 / 预设,可以 user_id = 0NULL
  • 中间件
    • 在 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_metadataextension_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 / matchWholeWordskeys / 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
    • 使用 persistdevtools 扩展,变量系统内置在 variables 字段中
  • 建议
    • 将以下信息也纳入 Store而不是零散地用本地 useState
      • 当前会话使用的 presetId
      • 当前会话启用的世界书 ID 列表
      • 当前会话启用的正则脚本 ID 列表
    • 通过一个统一的 ConversationSettings 结构,与后端 AIChat.Settings 对应

5.2 角色卡管理页面CharacterManagePage

目标:前端字段与 ST V2/V3 完全对齐,为后端世界书 / 正则拆分做好数据基础。

  • 字段映射建议
    • firstMesfirst_mes
    • mesExamplemes_example
    • systemPromptsystem_prompt
    • postHistoryInstructionspost_history_instructions
    • characterBookcharacter_book
    • extensionsextensions
  • 世界书编辑 UI
    • 在现有 keys / content / enabled / position 基础上,逐步引入高级字段(折叠到“高级设置”):
      • secondary_keysconstantuse_regexcase_sensitivematch_whole_words
      • selectiveselective_logic
      • depthorderprobabilitystickycooldowndelaygroup
    • 保存时将上述字段完整写入 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 取出 uservariables
      • 已加载 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/messagesPOST /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 文本 chunktEXt / 自定义块),不必限定为 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 通过 WebSocketSSE 与 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 大相径庭。
  1. 计算 Token: ST 使用 Tiktoken 或特定模型的 Tokenizer。为了节省服务器资源建议在前端进行初次计算后端在调用 API 前再次校准。
  2. 跨域与 API 代理: 公共平台需要处理大量大模型 API 的转发。Go 后端要实现一个高性能的 Proxy Layer处理超时、重试以及不同厂商OpenAI, Anthropic, Google的协议转换。