459 lines
23 KiB
Markdown
459 lines
23 KiB
Markdown
这是一个需要长期演进、但方向已经比较清晰的重构计划。为了让“云酒馆”既能承载多用户公共场景,又能高度兼容 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 列表(可选)
|
||
- **AIWorldInfo(Worldbook & 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)**:
|
||
- 对用户输入文本先跑一遍 RegexScript(global + 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` 配置,调用相应厂商 API(OpenAI 兼容 / Anthropic 等)
|
||
- 支持 SSE 流式输出,将 token 流推送给前端
|
||
7. **输出正则处理(placement = output)**:
|
||
- 对完整 AI 输出再次执行 RegexScript(global + 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)的协议转换。
|
||
|
||
|
||
|
||
|
||
|
||
|