🎨 添加文档和测试用例

This commit is contained in:
2026-03-13 21:51:42 +08:00
parent cec46cabbe
commit 5520dafb56
12 changed files with 10248 additions and 1 deletions

2
.gitignore vendored
View File

@@ -23,7 +23,7 @@ dist-ssr
*.sln *.sln
*.sw? *.sw?
uploads uploads
docs #docs
#.claude #.claude
plugs plugs
sillytavern sillytavern

BIN
docs/Clannad_v3.1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,357 @@
# Clannad_v3.1.png 案例分析与落地要点
## 目标角色卡概览
- **图像**@docs/Clannad_v3.1.png图片来源、版权信息见下方字段
- **风格**Clannad 系列日系校园少女题材,具有较强的情感表达与背景设定需求
- **已知需求**:在云酒馆系统中以“前端卡”方式呈现,并能注入背景信息、变量、互动 UI
---
## 10.1 数据字段与数据模型需求
| 字段名 | 说明 | 备注 |
|--------|------|------|
| portrait_url | 图片主地址 | 建议使用 CDN 托管,支持 webp/AVIF 高效格式 |
| portrait_alt | 无障碍文本描述 | 供屏幕阅读器使用,描述角色外观特征 |
| thumbnail_url | 缩略图地址 | 列表页展示使用,建议 200x200 以内 |
| portrait_caption | 图片简短说明 | 显示在图片下方的简短描述 |
| image_source | 版权信息/出处 | 记录图片来源、授权信息 |
| worldbook_id | 关联的 Worldbook | 用于注入背景设定 |
| worldbook_entries | 世界书条目集合 | 用于背景信息注入 |
| name | 角色名称 | 现有字段 |
| description | 角色描述 | 现有字段 |
| personality | 性格设定 | 现有字段 |
| scenario | 场景设定 | 现有字段 |
| first_mes | 开场白 | 现有字段 |
| mes_example | 对话示例 | 现有字段 |
| system_prompt | 系统提示词 | 现有字段 |
| extensions | 扩展数据 | MVU 相关字段 |
| variables | 变量定义 | MVU 相关字段 |
---
## 10.2 后端改动要点
### 10.2.1 数据模型扩展
`model/app/ai_character.go` 中新增以下字段:
```go
type AICharacter struct {
// ... 现有字段 ...
// 图片相关字段
PortraitURL string `json:"portraitUrl" gorm:"type:varchar(500)"`
PortraitAlt string `json:"portraitAlt" gorm:"type:varchar(200)"`
ThumbnailURL string `json:"thumbnailUrl" gorm:"type:varchar(500)"`
PortraitCaption string `json:"portraitCaption" gorm:"type:varchar(200)"`
ImageSource string `json:"imageSource" gorm:"type:varchar(500)"` // 版权信息
// Worldbook 关联
WorldbookID *uint `json:"worldbookId"`
}
```
### 10.2.2 API 接口
- **上传图片**`POST /api/v1/app/character/:id/portrait`
- 鉴权:需要用户登录
- 校验:文件大小(建议最大 5MB、格式支持 jpg/png/webp/gif
- 返回portrait_url、thumbnail_url
- **获取角色图片信息**`GET /api/v1/app/character/:id`
- 返回字段包含 portrait_url、portrait_alt、thumbnail_url、portrait_caption、image_source
### 10.2.3 Worldbook 关联增强
`conversation.go` 的 system prompt 构建流程中,增加对角色关联 Worldbook 的背景注入:
- 读取 `character.WorldbookID`
- 通过 WorldbookEngine 查询对应的启用条目
- 将背景信息注入到 system_prompt 中(需控制 token budget
### 10.2.4 数据迁移
为现有角色卡补充默认字段值:
```sql
ALTER TABLE ai_characters
ADD COLUMN IF NOT EXISTS portrait_url VARCHAR(500),
ADD COLUMN IF NOT EXISTS portrait_alt VARCHAR(200),
ADD COLUMN IF NOT EXISTS thumbnail_url VARCHAR(500),
ADD COLUMN IF NOT EXISTS portrait_caption VARCHAR(200),
ADD COLUMN IF NOT EXISTS image_source VARCHAR(500),
ADD COLUMN IF NOT EXISTS worldbook_id UINT REFERENCES worldbooks(id);
```
---
## 10.3 前端实现要点
### 10.3.1 角色卡片组件
`web-app/src/components/` 中新增或扩展角色卡片组件:
```tsx
interface CharacterCardProps {
portraitUrl?: string;
portraitAlt?: string;
thumbnailUrl?: string;
portraitCaption?: string;
// ... 其他字段
}
const CharacterPortrait: React.FC<CharacterCardProps> = ({
portraitUrl,
portraitAlt,
thumbnailUrl,
portraitCaption
}) => {
if (!portraitUrl) return null;
return (
<div className="character-portrait">
<img
src={portraitUrl}
alt={portraitAlt || '角色图像'}
loading="lazy"
className="portrait-image"
/>
{portraitCaption && (
<p className="portrait-caption">{portraitCaption}</p>
)}
</div>
);
};
```
### 10.3.2 样式隔离
使用 CSS Modules 或在组件级别定义样式:
```css
/* CharacterPortrait.module.css */
.character-portrait {
display: flex;
flex-direction: column;
align-items: center;
}
.portrait-image {
max-width: 300px;
border-radius: 8px;
}
.portrait-caption {
font-size: 14px;
color: #666;
margin-top: 8px;
}
```
### 10.3.3 沙箱渲染
对于需要执行脚本的前端卡,使用 iframe 沙箱:
```tsx
const FrontendCardSandbox: React.FC<{ htmlContent: string }> = ({ htmlContent }) => {
return (
<iframe
srcDoc={htmlContent}
sandbox="allow-scripts"
title="前端卡内容"
className="frontend-card-iframe"
/>
);
};
```
### 10.3.4 postMessage 通信
定义消息类型:
```ts
// types/frontend-card.ts
interface FrontendCardMessage {
type: 'GET_VARIABLE' | 'SET_VARIABLE' | 'SEND_MESSAGE' | 'TRIGGER_ACTION';
payload: Record<string, any>;
}
interface FrontendCardResponse {
type: 'VARIABLE_VALUE' | 'ACTION_RESULT' | 'ERROR';
payload: Record<string, any>;
}
```
---
## 10.4 Worldbook 与背景注入
### 10.4.1 背景信息转 Worldbook 条目
将 Clannad 角色的背景故事、人物关系、剧情要点等转换为 Worldbook 条目:
```json
{
"keys": ["冈崎汐", "汐", "女儿"],
"content": "冈崎汐主人公冈崎朋也的女儿。5岁时因事故去世后来以幽灵的形式再次出现在父亲面前...",
"enabled": true,
"constant": true,
"position": 1,
"order": 1
}
```
### 10.4.2 Token Budget 控制
`buildContextManagedSystemPrompt` 中,对 Worldbook 注入的内容进行 token 预算控制:
- **优先级**Worldbook 触发条目 > CharacterBook 内嵌条目 > MesExample
- **预算分配**system_prompt 总预算的 20-30% 用于 Worldbook 背景注入
- **截断策略**:超出预算时,从最低优先级条目开始丢弃
---
## 10.5 安全性与合规
### 10.5.1 图片安全
- **来源限制**:只允许从受信任的 CDN 或本地上传
- **内容审查**:上传图片需经过内容审核(可接入第三方审核服务)
- **元数据清理**:清理图片 EXIF 信息,防止泄露拍摄设备、地点等隐私
### 10.5.2 脚本安全
- **沙箱隔离**:前端卡内的 JavaScript 必须在 iframe 沙箱中运行
- **API 白名单**:只暴露受限的 API 接口,映射到后端受控服务
- **CSP 策略**
```
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:;
style-src 'self' 'unsafe-inline';
img-src 'self' https://cdn.example.com data:;
connect-src 'self' https://api.example.com;
```
### 10.5.3 审核流程
- **新卡审核**:用户上传新角色卡后,需经过管理员审核
- **定期检查**:对已上线卡片进行定期安全扫描
- **举报机制**:提供用户举报入口
---
## 10.6 迁移策略
### 10.6.1 MVP 阶段(第一阶段)
- 只引入 portrait_url、portrait_alt、thumbnail_url 字段
- 前端仅展示图片,不改变现有对话逻辑
- 不支持前端卡脚本执行
### 10.6.2 进阶阶段(第二阶段)
- 接入 Worldbook 背景注入
- 支持 portrait_caption 显示
- 引入基础的 iframe 沙箱渲染
### 10.6.3 完整阶段(第三阶段)
- 支持完整的前端卡脚本执行
- 实现 postMessage 通信
- 支持 RPG、VN 等复杂交互系统
### 10.6.4 回滚策略
- 每个阶段保留回滚开关
- 遇到兼容性问题时,可一键回退到上一阶段
- 数据库字段支持版本标记
---
## 10.7 验证与测试
### 10.7.1 功能测试
| 测试用例 | 预期结果 |
|---------|---------|
| 上传角色图片 | 图片正确保存,返回正确的 portrait_url |
| 图片加载失败 | 显示默认占位图alt 文本正确 |
| 无图片角色卡 | 正常显示,不崩溃 |
| Worldbook 注入 | 背景信息正确注入到对话上下文 |
| 懒加载图片 | 首屏不加载,滚动后加载 |
### 10.7.2 安全性测试
| 测试用例 | 预期结果 |
|---------|---------|
| XSS 注入(图片描述) | 被转义,不执行 |
| 恶意脚本(前卡内) | 在沙箱中被阻止,无法访问父窗口 |
| 跨域请求 | 被 CSP 拦截 |
### 10.7.3 性能测试
| 指标 | 目标 |
|-----|------|
| 图片首次加载 | < 1s5G 网络 |
| 懒加载触发 | 视口进入后 200ms |
| 内存占用 | 前端卡渲染时 < 50MB 额外 |
---
## 10.8 版权信息与来源
### 10.8.1 必填字段
- **image_source**图片来源官方美术集」「同人创作」)
- **license_type**许可证类型MIT」「CC BY-SA 4.0」「原创」)
- **original_artist**原作者名称若为同人创作
---
## 10.9 附录/接口示例
### 10.9.1 角色图片上传 API
```bash
# 请求
POST /api/v1/app/character/1/portrait
Authorization: Bearer <token>
Content-Type: multipart/form-data
# 响应
{
"portraitUrl": "https://cdn.example.com/characters/1/portrait.webp",
"thumbnailUrl": "https://cdn.example.com/characters/1/thumb.webp",
"portraitAlt": "粉色头发的少女,穿着校服"
}
```
### 10.9.2 角色详情 API 扩展
```bash
# GET /api/v1/app/character/1
{
"id": 1,
"name": "冈崎汐",
"portraitUrl": "https://cdn.example.com/characters/1/portrait.webp",
"portraitAlt": "粉色头发的少女,穿着校服",
"thumbnailUrl": "https://cdn.example.com/characters/1/thumb.webp",
"portraitCaption": "Clannad 主角冈崎朋也的女儿",
"imageSource": "官方美术集",
"worldbookId": 5,
// ... 其他字段
}
```
---
## 结论
通过独立分析文档分离便于版本管理与跨团队协作请在实现过程中严格遵循安全沙箱与版权规范确保云酒馆的扩展能力可控且可维护
本落地要点覆盖了从数据模型后端 API前端渲染Worldbook 集成安全合规到迁移测试的完整流程建议按 MVP 进阶 完整三阶段逐步落地

456
docs/flutter参考.md Normal file
View File

@@ -0,0 +1,456 @@
### NativeTavern 功能设计文档(供 Go + React 集成参考)
---
## 总览
本项目中,这几个模块的作用和依赖关系如下:
- **聊天功能Chat**统一负责从「用户消息」到「LLM 调用」再到「写回消息」的完整流程。
- **提示词管理Prompt Management**:把系统提示、人设、世界信息、作者注释、历史等拆成可配置的「段」,决定顺序与注入深度。
- **宏系统Macros**:在构建 prompt 时,根据上下文展开 `{{user}}``{{char}}``{{time}}``{{random}}` 等占位符。
- **变量系统Variables**:提供全局 / 每会话变量 + `{{setvar}}` / `{{getvar}}` 等变量宏,支持状态机、计数等逻辑。
- **思维链支持Chain-of-Thought / Reasoning**:对 o1/o3、Claude、Gemini 等的推理内容单独解析、存储,并在前端单独渲染。
Go + React 中建议同样分层:**数据模型DB + 领域服务Go + 状态/UIReact**。
---
## 一、变量系统Variables System
### 1.1 目标
- 支持两类变量:
- **全局变量**:跨所有聊天共用,用户级。
- **本地变量**:每个 chat 独立,存到 chat metadata。
- 通过宏语法在文本中读写变量,如:
- `{{setvar::score::10}}`
- `{{incvar::turn}}`
- `{{getvar::user_name}}`
### 1.2 数据模型(示例)
后端可参考:
```go
// 全局变量:按 userId 存
type GlobalVariables map[string]any // name -> value
// 本地变量:按 chatId 存
type LocalVariables map[string]map[string]any // chatId -> (name -> value)
type ChatMetadata struct {
Variables map[string]any `json:"variables,omitempty"`
// ... 其他 metadata 字段 ...
}
```
- 全局变量:存 Redis / Postgres JSONB / KV按 userId 分片。
- 本地变量:挂在 `chats.metadata.variables` 字段。
### 1.3 变量服务接口Go
```go
type VariablesService interface {
// 全局变量
GetGlobal(userId, name string) any
SetGlobal(userId, name string, v any) error
AddGlobal(userId, name string, delta any) (any, error)
IncGlobal(userId, name string) (any, error)
DecGlobal(userId, name string) (any, error)
// 本地变量(带 chatId
GetLocal(userId, chatId, name string) any
SetLocal(userId, chatId, name string, v any) error
AddLocal(userId, chatId, name string, delta any) (any, error)
IncLocal(userId, chatId, name string) (any, error)
DecLocal(userId, chatId, name string) (any, error)
LoadLocalFromMetadata(chatId string, metaVars map[string]any)
ExportLocalToMetadata(chatId string) map[string]any
}
```
### 1.4 变量宏语法
支持以下语法(与项目一致):
- 本地变量:
- `{{setvar::name::value}}`
- `{{addvar::name::value}}`
- `{{incvar::name}}`
- `{{decvar::name}}`
- `{{getvar::name}}`
- 全局变量:
- `{{setglobalvar::name::value}}`
- `{{addglobalvar::name::value}}`
- `{{incglobalvar::name}}`
- `{{decglobalvar::name}}`
- `{{getglobalvar::name}}`
解析函数示例:
```go
func ProcessVariableMacros(input, userId string, chatId *string, vars VariablesService) (string, error)
```
实现建议:
1. 为每种宏写一个正则,比如:
- `\{\{setvar::([^:]+)::([^}]*)\}\}`
- `\{\{getvar::([^}]+)\}\}` 等。
2. 按「写变量 → 读变量」顺序依次 `ReplaceAllStringFunc`
- set / add / inc / dec调用 `VariablesService`,返回空串或新值字符串。
- get用变量值替换整段 `{{...}}`
**集成位置**:在构建 prompt、处理用户输入时先跑一遍 `ProcessVariableMacros`
---
## 二、宏系统Macro System
### 2.1 目标
- 使用 `{{...}}` 宏在 prompt 中引入上下文信息,包括:
- 用户/人设:`{{user}}``{{persona}}``{{user_description}}`
- 角色:`{{char}}``{{char_description}}``{{system_prompt}}``{{jailbreak}}`
- 时间:`{{time}}``{{date}}``{{weekday}}`
- 聊天上下文:`{{lastMessage}}``{{lastUserMessage}}``{{messageCount}}`
- 随机:`{{random}}``{{random::min::max}}``{{roll::2d6}}``{{pick::a::b}}`
- 元信息:`{{model}}``{{provider}}``{{idle_duration}}` 等。
### 2.2 宏上下文Go
```go
type MacroContext struct {
UserName string
UserDescription string
CharacterName string
CharacterDescription string
CharacterSystemPrompt string
PostHistoryInstructions string
LastMessage string
LastUserMessage string
LastCharacterMessage string
MessageCount int
ChatID string
OriginalPrompt string
CurrentInput string
ModelName string
ProviderName string
IdleDurationMinutes int
Now time.Time
}
```
### 2.3 宏解析函数
```go
func ProcessMacros(text string, ctx MacroContext) string
```
推荐拆为多个子步骤(顺序类似原项目):
1. `_processRandomMacros`
- `{{random}}`
- `{{random::min::max}}`
- `{{roll::NdM}}`
- `{{pick::opt1::opt2}}`
- `{{uuid}}`
2. `_processConditionalMacros`
- `{{if::cond::then}}`
- `{{if::cond::then::else}}`(可以做简单布尔表达式解析)
3. `_processTimeDateMacros`
- `{{time}}` / `{{date}}` / `{{weekday}}` / 自定义时间格式。
4. `_processCharacterMacros`
- `{{char}}``{{char_description}}``{{system_prompt}}``{{post_history_instructions}}` 等。
5. `_processUserMacros`
- `{{user}}` / `{{persona}}` / `{{user_description}}`
6. `_processChatMacros`
- `{{lastMessage}}` / `{{lastUserMessage}}` / `{{lastCharMessage}}` / `{{messageCount}}` / `{{chatId}}`
7. `_processSpecialMacros`
- `{{newline}}` / `{{nl}}``{{trim}}``{{noop}}`
- `{{original}}`(原始 prompt用于局部覆盖
- `{{input}}`(当前用户输入)
- `{{model}}` / `{{provider}}`
- `{{idle_duration}}`
**集成顺序**(在构建 prompt 时):
1. 先跑变量宏 `ProcessVariableMacros`(会改变变量状态)。
2. 再跑文本宏 `ProcessMacros`
---
## 三、思维链支持Chain-of-Thought / Reasoning
### 3.1 目标
- 对支持推理的模型o1/o3、Claude Thinking、Gemini 思考流):
- 把推理内容单独抓出来(从 `reasoning_content` / `thinking` / `thought` 字段等)。
- 和正常回复一起存在消息上。
- 前端给一个可折叠的「思维链/推理」区域。
### 3.2 数据模型
```go
type ChatMessage struct {
ID string
ChatID string
Role string // "user" / "assistant" / "system"
Content string
Timestamp time.Time
Swipes []string
CurrentSwipeIdx int
Reasoning *string // 默认推理文本
ReasoningSwipes []string // 每个 swipe 的推理
// ... 其他字段attachments、characterId 等)
}
```
前端便于使用的派生属性(在 React 里实现):
- `currentReasoning`
- 如果 `ReasoningSwipes` 长度 > `CurrentSwipeIdx` → 用对应项;
- 否则用 `Reasoning`
- `hasReasoning`
- `Reasoning` 非空;或 `ReasoningSwipes` 中有任意非空字符串。
### 3.3 LLM 接口设计Go
**非流式:**
```go
type LLMReasoningResponse struct {
Content string
Reasoning *string
}
func GenerateWithReasoning(promptCtx PromptContext, cfg LLMConfig) (LLMReasoningResponse, error)
```
**流式:**
```go
type ReasoningChunk struct {
Content *string
Reasoning *string
IsReasoningChunk bool
}
func GenerateStreamWithReasoning(promptCtx PromptContext, cfg LLMConfig) (<-chan ReasoningChunk, error)
```
解析逻辑示例(伪代码):
```go
for chunk := range ch {
if chunk.IsReasoningChunk && chunk.Reasoning != nil {
reasoningBuffer.WriteString(*chunk.Reasoning)
}
if chunk.Content != nil {
contentBuffer.WriteString(*chunk.Content)
}
// 把当前 buffer 写回当前 assistant 消息,前端即可实时显示
}
```
- OpenAI o1/o3`choice.message.reasoning_content` 或自定义扩展字段中解析。
- Claude`thinking` 字段解析。
- Gemini`thought` 或增强字段解析。
### 3.4 前端渲染React
示例:
```tsx
function MessageView({ message }: { message: ChatMessage }) {
return (
<div className="message">
<div className="content">{renderMarkdownOrHtml(message.content)}</div>
{message.hasReasoning && (
<ReasoningPanel
label="Thinking"
content={message.currentReasoning ?? ""}
/>
)}
</div>
);
}
```
`ReasoningPanel`
- 折叠 / 展开。
- 文本可复制。
- UI 上与主内容区明显区分(浅色背景、小字体等)。
---
## 四、聊天功能Chat Flow
### 4.1 目标
- 封装完整一轮对话的流程:
1. 记录用户消息。
2. 做摘要/裁剪历史(可选)。
3. 根据 PromptManager + 世界信息 + 宏/变量 构造 prompt。
4. 调用 LLM流式/非流式 + reasoning
5. 写入 assistant 消息,并更新前端状态。
### 4.2 核心流程(伪代码)
```go
func (svc *ChatService) SendMessage(userId, chatId, input string, cfg LLMConfig, atts []ChatAttachment) error {
chat := repo.GetChat(chatId)
character := repo.GetCharacter(chat.CharacterID)
history := repo.ListMessages(chatId)
// 1. 写 user 消息
userMsg := ChatMessage{
ID: newID(),
ChatID: chatId,
Role: "user",
Content: input,
Timestamp: time.Now(),
Swipes: []string{input},
// Attachments: atts, ...
}
repo.AddMessage(userMsg)
// 2. 可选:基于 token 限制做摘要或丢弃最老历史
svc.checkAndSummarize(chat, history, cfg)
// 3. 构建 prompt 上下文
promptCtx := svc.BuildPromptContext(userId, chat, character, history, input)
// 4. LLM 调用
if cfg.Stream {
return svc.streamAssistantResponse(chatId, promptCtx, cfg)
}
return svc.oneShotAssistantResponse(chatId, promptCtx, cfg)
}
```
### 4.3 BuildPromptContext 的职责
- 从 PromptManager 获取启用的 PromptSection排序后
- 从世界信息系统取匹配条目(按关键词、优先级、位置)。
- 按「深度注入」规则遍历历史消息,决定在每条消息前插入哪些 PromptSection / 世界信息 / 作者注释:
- 定义 `depthFromEnd = total-1 - i`
-`section.InjectionDepth == depthFromEnd` → 在该消息之前插入对应提示。
- 对所有文本块跑:
1. `ProcessVariableMacros(...)`
2. `ProcessMacros(...)`
- 输出统一形式的 prompt例如 `[]OpenAIMessage{ {role, content}, ... }`,交给 LLMProvider 层。
---
## 五、提示词管理Prompt Management
### 5.1 目标
- 把 prompt 拆成若干「段」,每段可:
- 单独启用/禁用。
- 调整顺序。
- 指定注入深度(在倒数第几条消息前插入)。
- 与 SillyTavern 的 `prompts` / `prompt_order` JSON 兼容。
### 5.2 数据模型
```go
type PromptSectionType string
const (
SectionSystemPrompt PromptSectionType = "systemPrompt"
SectionPersona PromptSectionType = "persona"
SectionCharacterDescription PromptSectionType = "characterDescription"
SectionCharacterPersonality PromptSectionType = "characterPersonality"
SectionCharacterScenario PromptSectionType = "characterScenario"
SectionExampleMessages PromptSectionType = "exampleMessages"
SectionWorldInfo PromptSectionType = "worldInfo"
SectionWorldInfoAfter PromptSectionType = "worldInfoAfter"
SectionAuthorNote PromptSectionType = "authorNote"
SectionPostHistoryInstr PromptSectionType = "postHistoryInstructions"
SectionNsfw PromptSectionType = "nsfw"
SectionChatHistory PromptSectionType = "chatHistory"
SectionEnhanceDefinitions PromptSectionType = "enhanceDefinitions"
SectionCustom PromptSectionType = "custom"
)
type PromptSection struct {
Type PromptSectionType
Name string
Enabled bool
Order int
Content *string
Identifier *string // ST 中的 identifier
Role *string // "system" / "user" / "assistant"
InjectionPosition *int
InjectionDepth *int
}
```
### 5.3 PromptManager 服务
```go
type PromptManager struct {
Sections []PromptSection
}
func (pm *PromptManager) EnabledSections() []PromptSection {
// 过滤 Enabled并按 Order 排好
}
```
从 SillyTavern 导入:
1. 建一个 `identifier -> PromptSectionType` 映射。
2. 解析 `prompts`(内容)和 `prompt_order`(顺序 & enabled生成 `PromptSection`
3. 对不在映射表里的 identifier统一归类为 `SectionCustom`
### 5.4 深度注入逻辑
在构建 prompt 时,遍历历史消息 `messages`
```go
N := len(messages)
for i, msg := range messages {
depthFromEnd := N - 1 - i
// 1. 深度注入世界信息
for _, entry := range depthWorldInfo {
if entry.Depth == depthFromEnd {
systemMsg := buildWorldInfoMessage(entry)
promptMessages = append(promptMessages, systemMsg)
}
}
// 2. 深度注入 PromptSection
for _, sec := range depthBasedSections {
if sec.InjectionDepth != nil && *sec.InjectionDepth == depthFromEnd {
secMsgs := buildSectionMessages(sec, ctx)
promptMessages = append(promptMessages, secMsgs...)
}
}
// 3. Authors Note 深度注入
// ...
// 4. 加入当前 chat 消息本身user / assistant
promptMessages = append(promptMessages, convertChatMessage(msg))
}
```
---

1167
docs/html/index.html Normal file

File diff suppressed because it is too large Load Diff

1222
docs/html/my.html Normal file

File diff suppressed because it is too large Load Diff

1335
docs/html/st.html Normal file

File diff suppressed because it is too large Load Diff

913
docs/init/pgsql_init.sql Normal file
View File

@@ -0,0 +1,913 @@
-- SillyTavern Cloud / 云酒馆
-- PostgreSQL 手动初始化脚本,对应后端 PgsqlInitHandler.InitData 全量初始化数据
-- 使用方法(建议在全新空库上执行):
-- psql -h <host> -U <user> -d <db> -f pgsql_init.sql
--
-- 注意:
-- 1. 本脚本不会自动清理旧数据,如需在已有数据上重置,请自行 TRUNCATE 相关表。
-- 2. 用户密码字段留空(''),初始化后请通过接口或 SQL 自行重置管理员密码。
BEGIN;
/* =========================
* 1. 角色表 sys_authorities
* ========================= */
INSERT INTO sys_authorities (authority_id, authority_name, parent_id, default_router)
VALUES
(888, '普通用户', 0, 'dashboard'),
(9528, '测试角色', 0, 'dashboard'),
(8881, '普通用户子角色', 888, 'dashboard');
/* =========================
* 2. 用户表 sys_users
* 对应 initUser.InitializeData
* ========================= */
INSERT INTO sys_users (uuid, username, password, nick_name, header_img, authority_id, phone, email, enable)
VALUES
('00000000-0000-0000-0000-000000000001', 'admin', '', 'Lee', 'https://qmplusimg.henrongyi.top/gva_header.jpg', 888, '17611111111', '333333333@qq.com', 1),
('00000000-0000-0000-0000-000000000002', 'a303176530', '', '用户1', 'https://qmplusimg.henrongyi.top/1572075907logo.png', 9528, '17611111111', '333333333@qq.com', 1);
/* =====================================================
* 3. 字典表 & 字典详情表
* sys_dictionaries, sys_dictionary_details
* ===================================================== */
-- 3.1 字典表 sys_dictionaries
INSERT INTO sys_dictionaries (name, type, status, "desc")
VALUES
('性别', 'gender', TRUE, '性别字典'),
('数据库int类型', 'int', TRUE, 'int类型对应的数据库类型'),
('数据库时间日期类型', 'time.Time', TRUE, '数据库时间日期类型'),
('数据库浮点型', 'float64', TRUE, '数据库浮点型'),
('数据库字符串', 'string', TRUE, '数据库字符串'),
('数据库bool类型', 'bool', TRUE, '数据库bool类型');
-- 3.2 字典详情表 sys_dictionary_details
-- 使用 INSERT ... SELECT通过 type 反查 sys_dictionaries.id
-- 性别 gender
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT
'' AS label, '1' AS value, '' AS extend, TRUE AS status, 1 AS sort,
d.id AS sys_dictionary_id, NULL::integer AS parent_id, 0 AS level, '' AS path
FROM sys_dictionaries d WHERE d.type = 'gender';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT
'', '2', '', TRUE, 2,
d.id, NULL::integer, 0, ''
FROM sys_dictionaries d WHERE d.type = 'gender';
-- int
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'smallint', '1', 'mysql', TRUE, 1, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'int';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'mediumint', '2', 'mysql', TRUE, 2, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'int';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'int', '3', 'mysql', TRUE, 3, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'int';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'bigint', '4', 'mysql', TRUE, 4, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'int';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'int2', '5', 'pgsql', TRUE, 5, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'int';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'int4', '6', 'pgsql', TRUE, 6, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'int';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'int6', '7', 'pgsql', TRUE, 7, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'int';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'int8', '8', 'pgsql', TRUE, 8, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'int';
-- time.Time
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'date', '0', 'mysql', TRUE, 0, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'time.Time';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'time', '1', 'mysql', TRUE, 1, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'time.Time';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'year', '2', 'mysql', TRUE, 2, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'time.Time';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'datetime', '3', 'mysql', TRUE, 3, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'time.Time';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'timestamp', '5', 'mysql', TRUE, 5, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'time.Time';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'timestamptz', '6', 'pgsql', TRUE, 5, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'time.Time';
-- float64
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'float', '0', 'mysql', TRUE, 0, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'float64';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'double', '1', 'mysql', TRUE, 1, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'float64';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'decimal', '2', 'mysql', TRUE, 2, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'float64';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'numeric', '3', 'pgsql', TRUE, 3, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'float64';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'smallserial', '4', 'pgsql', TRUE, 4, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'float64';
-- string
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'char', '0', 'mysql', TRUE, 0, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'string';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'varchar', '1', 'mysql', TRUE, 1, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'string';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'tinyblob', '2', 'mysql', TRUE, 2, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'string';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'tinytext', '3', 'mysql', TRUE, 3, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'string';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'text', '4', 'mysql', TRUE, 4, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'string';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'blob', '5', 'mysql', TRUE, 5, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'string';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'mediumblob', '6', 'mysql', TRUE, 6, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'string';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'mediumtext', '7', 'mysql', TRUE, 7, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'string';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'longblob', '8', 'mysql', TRUE, 8, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'string';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'longtext', '9', 'mysql', TRUE, 9, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'string';
-- bool
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'tinyint', '1', 'mysql', TRUE, 0, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'bool';
INSERT INTO sys_dictionary_details (label, value, extend, status, sort, sys_dictionary_id, parent_id, level, path)
SELECT 'bool', '2', 'pgsql', TRUE, 0, d.id, NULL::integer, 0, '' FROM sys_dictionaries d WHERE d.type = 'bool';
/* =========================
* 4. API 表 sys_apis
* ========================= */
INSERT INTO sys_apis (api_group, method, path, description)
VALUES
('jwt', 'POST', '/jwt/jsonInBlacklist', 'jwt加入黑名单(退出,必选)'),
('登录日志', 'DELETE', '/sysLoginLog/deleteLoginLog', '删除登录日志'),
('登录日志', 'DELETE', '/sysLoginLog/deleteLoginLogByIds', '批量删除登录日志'),
('登录日志', 'GET', '/sysLoginLog/findLoginLog', '根据ID获取登录日志'),
('登录日志', 'GET', '/sysLoginLog/getLoginLogList', '获取登录日志列表'),
('API Token', 'POST', '/sysApiToken/createApiToken', '签发API Token'),
('API Token', 'POST', '/sysApiToken/getApiTokenList', '获取API Token列表'),
('API Token', 'POST', '/sysApiToken/deleteApiToken', '作废API Token'),
('系统用户', 'DELETE', '/user/deleteUser', '删除用户'),
('系统用户', 'POST', '/user/admin_register', '用户注册'),
('系统用户', 'POST', '/user/getUserList', '获取用户列表'),
('系统用户', 'PUT', '/user/setUserInfo', '设置用户信息'),
('系统用户', 'PUT', '/user/setSelfInfo', '设置自身信息(必选)'),
('系统用户', 'GET', '/user/getUserInfo', '获取自身信息(必选)'),
('系统用户', 'POST', '/user/setUserAuthorities', '设置权限组'),
('系统用户', 'POST', '/user/changePassword', '修改密码(建议选择)'),
('系统用户', 'POST', '/user/setUserAuthority', '修改用户角色(必选)'),
('系统用户', 'POST', '/user/resetPassword', '重置用户密码'),
('系统用户', 'PUT', '/user/setSelfSetting', '用户界面配置'),
('api', 'POST', '/api/createApi', '创建api'),
('api', 'POST', '/api/deleteApi', '删除Api'),
('api', 'POST', '/api/updateApi', '更新Api'),
('api', 'POST', '/api/getApiList', '获取api列表'),
('api', 'POST', '/api/getAllApis', '获取所有api'),
('api', 'POST', '/api/getApiById', '获取api详细信息'),
('api', 'DELETE', '/api/deleteApisByIds', '批量删除api'),
('api', 'GET', '/api/syncApi', '获取待同步API'),
('api', 'GET', '/api/getApiGroups', '获取路由组'),
('api', 'POST', '/api/enterSyncApi', '确认同步API'),
('api', 'POST', '/api/ignoreApi', '忽略API'),
('角色', 'POST', '/authority/copyAuthority', '拷贝角色'),
('角色', 'POST', '/authority/createAuthority', '创建角色'),
('角色', 'POST', '/authority/deleteAuthority', '删除角色'),
('角色', 'PUT', '/authority/updateAuthority', '更新角色信息'),
('角色', 'POST', '/authority/getAuthorityList', '获取角色列表'),
('角色', 'POST', '/authority/setDataAuthority', '设置角色资源权限'),
('casbin', 'POST', '/casbin/updateCasbin', '更改角色api权限'),
('casbin', 'POST', '/casbin/getPolicyPathByAuthorityId', '获取权限列表'),
('菜单', 'POST', '/menu/addBaseMenu', '新增菜单'),
('菜单', 'POST', '/menu/getMenu', '获取菜单树(必选)'),
('菜单', 'POST', '/menu/deleteBaseMenu', '删除菜单'),
('菜单', 'POST', '/menu/updateBaseMenu', '更新菜单'),
('菜单', 'POST', '/menu/getBaseMenuById', '根据id获取菜单'),
('菜单', 'POST', '/menu/getMenuList', '分页获取基础menu列表'),
('菜单', 'POST', '/menu/getBaseMenuTree', '获取用户动态路由'),
('菜单', 'POST', '/menu/getMenuAuthority', '获取指定角色menu'),
('菜单', 'POST', '/menu/addMenuAuthority', '增加menu和角色关联关系'),
('分片上传', 'GET', '/fileUploadAndDownload/findFile', '寻找目标文件(秒传)'),
('分片上传', 'POST', '/fileUploadAndDownload/breakpointContinue', '断点续传'),
('分片上传', 'POST', '/fileUploadAndDownload/breakpointContinueFinish', '断点续传完成'),
('分片上传', 'POST', '/fileUploadAndDownload/removeChunk', '上传完成移除文件'),
('文件上传与下载', 'POST', '/fileUploadAndDownload/upload', '文件上传(建议选择)'),
('文件上传与下载', 'POST', '/fileUploadAndDownload/deleteFile', '删除文件'),
('文件上传与下载', 'POST', '/fileUploadAndDownload/editFileName', '文件名或者备注编辑'),
('文件上传与下载', 'POST', '/fileUploadAndDownload/getFileList', '获取上传文件列表'),
('文件上传与下载', 'POST', '/fileUploadAndDownload/importURL', '导入URL'),
('系统服务', 'POST', '/system/getServerInfo', '获取服务器信息'),
('系统服务', 'POST', '/system/getSystemConfig', '获取配置文件内容'),
('系统服务', 'POST', '/system/setSystemConfig', '设置配置文件内容'),
('skills', 'GET', '/skills/getTools', '获取技能工具列表'),
('skills', 'POST', '/skills/getSkillList', '获取技能列表'),
('skills', 'POST', '/skills/getSkillDetail', '获取技能详情'),
('skills', 'POST', '/skills/saveSkill', '保存技能定义'),
('skills', 'POST', '/skills/createScript', '创建技能脚本'),
('skills', 'POST', '/skills/getScript', '读取技能脚本'),
('skills', 'POST', '/skills/saveScript', '保存技能脚本'),
('skills', 'POST', '/skills/createResource', '创建技能资源'),
('skills', 'POST', '/skills/getResource', '读取技能资源'),
('skills', 'POST', '/skills/saveResource', '保存技能资源'),
('skills', 'POST', '/skills/createReference', '创建技能参考'),
('skills', 'POST', '/skills/getReference', '读取技能参考'),
('skills', 'POST', '/skills/saveReference', '保存技能参考'),
('skills', 'POST', '/skills/createTemplate', '创建技能模板'),
('skills', 'POST', '/skills/getTemplate', '读取技能模板'),
('skills', 'POST', '/skills/saveTemplate', '保存技能模板'),
('skills', 'POST', '/skills/getGlobalConstraint', '读取全局约束'),
('skills', 'POST', '/skills/saveGlobalConstraint', '保存全局约束'),
('客户', 'PUT', '/customer/customer', '更新客户'),
('客户', 'POST', '/customer/customer', '创建客户'),
('客户', 'DELETE','/customer/customer', '删除客户'),
('客户', 'GET', '/customer/customer', '获取单一客户'),
('客户', 'GET', '/customer/customerList', '获取客户列表'),
('代码生成器', 'GET', '/autoCode/getDB', '获取所有数据库'),
('代码生成器', 'GET', '/autoCode/getTables', '获取数据库表'),
('代码生成器', 'POST', '/autoCode/createTemp', '自动化代码'),
('代码生成器', 'POST', '/autoCode/preview', '预览自动化代码'),
('代码生成器', 'GET', '/autoCode/getColumn', '获取所选table的所有字段'),
('代码生成器', 'POST', '/autoCode/installPlugin', '安装插件'),
('代码生成器', 'POST', '/autoCode/pubPlug', '打包插件'),
('代码生成器', 'POST', '/autoCode/removePlugin', '卸载插件'),
('代码生成器', 'GET', '/autoCode/getPluginList', '获取已安装插件'),
('代码生成器', 'POST', '/autoCode/mcp', '自动生成 MCP Tool 模板'),
('代码生成器', 'POST', '/autoCode/mcpTest', 'MCP Tool 测试'),
('代码生成器', 'POST', '/autoCode/mcpList', '获取 MCP ToolList'),
('模板配置', 'POST', '/autoCode/createPackage', '配置模板'),
('模板配置', 'GET', '/autoCode/getTemplates', '获取模板文件'),
('模板配置', 'POST', '/autoCode/getPackage', '获取所有模板'),
('模板配置', 'POST', '/autoCode/delPackage', '删除模板'),
('代码生成器历史', 'POST', '/autoCode/getMeta', '获取meta信息'),
('代码生成器历史', 'POST', '/autoCode/rollback', '回滚自动生成代码'),
('代码生成器历史', 'POST', '/autoCode/getSysHistory', '查询回滚记录'),
('代码生成器历史', 'POST', '/autoCode/delSysHistory', '删除回滚记录'),
('代码生成器历史', 'POST', '/autoCode/addFunc', '增加模板方法'),
('系统字典详情', 'PUT', '/sysDictionaryDetail/updateSysDictionaryDetail', '更新字典内容'),
('系统字典详情', 'POST', '/sysDictionaryDetail/createSysDictionaryDetail', '新增字典内容'),
('系统字典详情', 'DELETE','/sysDictionaryDetail/deleteSysDictionaryDetail', '删除字典内容'),
('系统字典详情', 'GET', '/sysDictionaryDetail/findSysDictionaryDetail', '根据ID获取字典内容'),
('系统字典详情', 'GET', '/sysDictionaryDetail/getSysDictionaryDetailList', '获取字典内容列表'),
('系统字典详情', 'GET', '/sysDictionaryDetail/getDictionaryTreeList', '获取字典数列表'),
('系统字典详情', 'GET', '/sysDictionaryDetail/getDictionaryTreeListByType', '根据分类获取字典数列表'),
('系统字典详情', 'GET', '/sysDictionaryDetail/getDictionaryDetailsByParent', '根据父级ID获取字典详情'),
('系统字典详情', 'GET', '/sysDictionaryDetail/getDictionaryPath', '获取字典详情的完整路径'),
('系统字典', 'POST', '/sysDictionary/createSysDictionary', '新增字典'),
('系统字典', 'DELETE','/sysDictionary/deleteSysDictionary', '删除字典'),
('系统字典', 'PUT', '/sysDictionary/updateSysDictionary', '更新字典'),
('系统字典', 'GET', '/sysDictionary/findSysDictionary', '根据ID获取字典建议选择'),
('系统字典', 'GET', '/sysDictionary/getSysDictionaryList', '获取字典列表'),
('系统字典', 'POST', '/sysDictionary/importSysDictionary', '导入字典JSON'),
('系统字典', 'GET', '/sysDictionary/exportSysDictionary', '导出字典JSON'),
('操作记录', 'POST', '/sysOperationRecord/createSysOperationRecord', '新增操作记录'),
('操作记录', 'GET', '/sysOperationRecord/findSysOperationRecord', '根据ID获取操作记录'),
('操作记录', 'GET', '/sysOperationRecord/getSysOperationRecordList', '获取操作记录列表'),
('操作记录', 'DELETE','/sysOperationRecord/deleteSysOperationRecord', '删除操作记录'),
('操作记录', 'DELETE','/sysOperationRecord/deleteSysOperationRecordByIds', '批量删除操作历史'),
('断点续传(插件版)', 'POST', '/simpleUploader/upload', '插件版分片上传'),
('断点续传(插件版)', 'GET', '/simpleUploader/checkFileMd5', '文件完整度验证'),
('断点续传(插件版)', 'GET', '/simpleUploader/mergeFileMd5', '上传完成合并文件'),
('email', 'POST', '/email/emailTest', '发送测试邮件'),
('email', 'POST', '/email/sendEmail', '发送邮件'),
('按钮权限', 'POST', '/authorityBtn/setAuthorityBtn', '设置按钮权限'),
('按钮权限', 'POST', '/authorityBtn/getAuthorityBtn', '获取已有按钮权限'),
('按钮权限', 'POST', '/authorityBtn/canRemoveAuthorityBtn', '删除按钮'),
('导出模板', 'POST', '/sysExportTemplate/createSysExportTemplate', '新增导出模板'),
('导出模板', 'DELETE','/sysExportTemplate/deleteSysExportTemplate', '删除导出模板'),
('导出模板', 'DELETE','/sysExportTemplate/deleteSysExportTemplateByIds', '批量删除导出模板'),
('导出模板', 'PUT', '/sysExportTemplate/updateSysExportTemplate', '更新导出模板'),
('导出模板', 'GET', '/sysExportTemplate/findSysExportTemplate', '根据ID获取导出模板'),
('导出模板', 'GET', '/sysExportTemplate/getSysExportTemplateList', '获取导出模板列表'),
('导出模板', 'GET', '/sysExportTemplate/exportExcel', '导出Excel'),
('导出模板', 'GET', '/sysExportTemplate/exportTemplate', '下载模板'),
('导出模板', 'GET', '/sysExportTemplate/previewSQL', '预览SQL'),
('导出模板', 'POST', '/sysExportTemplate/importExcel', '导入Excel'),
('错误日志', 'POST', '/sysError/createSysError', '新建错误日志'),
('错误日志', 'DELETE','/sysError/deleteSysError', '删除错误日志'),
('错误日志', 'DELETE','/sysError/deleteSysErrorByIds', '批量删除错误日志'),
('错误日志', 'PUT', '/sysError/updateSysError', '更新错误日志'),
('错误日志', 'GET', '/sysError/findSysError', '根据ID获取错误日志'),
('错误日志', 'GET', '/sysError/getSysErrorList', '获取错误日志列表'),
('错误日志', 'GET', '/sysError/getSysErrorSolution', '触发错误处理(异步)'),
('公告', 'POST', '/info/createInfo', '新建公告'),
('公告', 'DELETE','/info/deleteInfo', '删除公告'),
('公告', 'DELETE','/info/deleteInfoByIds', '批量删除公告'),
('公告', 'PUT', '/info/updateInfo', '更新公告'),
('公告', 'GET', '/info/findInfo', '根据ID获取公告'),
('公告', 'GET', '/info/getInfoList', '获取公告列表'),
('参数管理', 'POST', '/sysParams/createSysParams', '新建参数'),
('参数管理', 'DELETE','/sysParams/deleteSysParams', '删除参数'),
('参数管理', 'DELETE','/sysParams/deleteSysParamsByIds', '批量删除参数'),
('参数管理', 'PUT', '/sysParams/updateSysParams', '更新参数'),
('参数管理', 'GET', '/sysParams/findSysParams', '根据ID获取参数'),
('参数管理', 'GET', '/sysParams/getSysParamsList', '获取参数列表'),
('参数管理', 'GET', '/sysParams/getSysParam', '获取参数列表'),
('媒体库分类', 'GET', '/attachmentCategory/getCategoryList', '分类列表'),
('媒体库分类', 'POST', '/attachmentCategory/addCategory', '添加/编辑分类'),
('媒体库分类', 'POST', '/attachmentCategory/deleteCategory', '删除分类'),
('版本控制', 'GET', '/sysVersion/findSysVersion', '获取单一版本'),
('版本控制', 'GET', '/sysVersion/getSysVersionList', '获取版本列表'),
('版本控制', 'GET', '/sysVersion/downloadVersionJson', '下载版本json'),
('版本控制', 'POST', '/sysVersion/exportVersion', '创建版本'),
('版本控制', 'POST', '/sysVersion/importVersion', '同步版本'),
('版本控制', 'DELETE','/sysVersion/deleteSysVersion', '删除版本'),
('版本控制', 'DELETE','/sysVersion/deleteSysVersionByIds', '批量删除版本');
/* =========================
* 5. 忽略 API 表 sys_ignore_apis
* ========================= */
INSERT INTO sys_ignore_apis (method, path)
VALUES
('GET', '/swagger/*any'),
('GET', '/api/freshCasbin'),
('GET', '/uploads/file/*filepath'),
('GET', '/health'),
('HEAD', '/uploads/file/*filepath'),
('POST', '/autoCode/llmAuto'),
('POST', '/system/reloadSystem'),
('POST', '/base/login'),
('POST', '/base/captcha'),
('POST', '/init/initdb'),
('POST', '/init/checkdb'),
('GET', '/info/getInfoDataSource'),
('GET', '/info/getInfoPublic');
/* =========================
* 6. Casbin 规则表 casbin_rules
* ========================= */
INSERT INTO casbin_rules (ptype, v0, v1, v2)
VALUES
-- 888
('p', '888', '/user/admin_register', 'POST'),
('p', '888', '/sysLoginLog/deleteLoginLog', 'DELETE'),
('p', '888', '/sysLoginLog/deleteLoginLogByIds', 'DELETE'),
('p', '888', '/sysLoginLog/findLoginLog', 'GET'),
('p', '888', '/sysLoginLog/getLoginLogList', 'GET'),
('p', '888', '/sysApiToken/createApiToken', 'POST'),
('p', '888', '/sysApiToken/getApiTokenList', 'POST'),
('p', '888', '/sysApiToken/deleteApiToken', 'POST'),
('p', '888', '/api/createApi', 'POST'),
('p', '888', '/api/getApiList', 'POST'),
('p', '888', '/api/getApiById', 'POST'),
('p', '888', '/api/deleteApi', 'POST'),
('p', '888', '/api/updateApi', 'POST'),
('p', '888', '/api/getAllApis', 'POST'),
('p', '888', '/api/deleteApisByIds', 'DELETE'),
('p', '888', '/api/syncApi', 'GET'),
('p', '888', '/api/getApiGroups', 'GET'),
('p', '888', '/api/enterSyncApi', 'POST'),
('p', '888', '/api/ignoreApi', 'POST'),
('p', '888', '/authority/copyAuthority', 'POST'),
('p', '888', '/authority/updateAuthority', 'PUT'),
('p', '888', '/authority/createAuthority', 'POST'),
('p', '888', '/authority/deleteAuthority', 'POST'),
('p', '888', '/authority/getAuthorityList', 'POST'),
('p', '888', '/authority/setDataAuthority', 'POST'),
('p', '888', '/menu/getMenu', 'POST'),
('p', '888', '/menu/getMenuList', 'POST'),
('p', '888', '/menu/addBaseMenu', 'POST'),
('p', '888', '/menu/getBaseMenuTree', 'POST'),
('p', '888', '/menu/addMenuAuthority', 'POST'),
('p', '888', '/menu/getMenuAuthority', 'POST'),
('p', '888', '/menu/deleteBaseMenu', 'POST'),
('p', '888', '/menu/updateBaseMenu', 'POST'),
('p', '888', '/menu/getBaseMenuById', 'POST'),
('p', '888', '/user/getUserInfo', 'GET'),
('p', '888', '/user/setUserInfo', 'PUT'),
('p', '888', '/user/setSelfInfo', 'PUT'),
('p', '888', '/user/getUserList', 'POST'),
('p', '888', '/user/deleteUser', 'DELETE'),
('p', '888', '/user/changePassword', 'POST'),
('p', '888', '/user/setUserAuthority', 'POST'),
('p', '888', '/user/setUserAuthorities', 'POST'),
('p', '888', '/user/resetPassword', 'POST'),
('p', '888', '/user/setSelfSetting', 'PUT'),
('p', '888', '/fileUploadAndDownload/findFile', 'GET'),
('p', '888', '/fileUploadAndDownload/breakpointContinueFinish', 'POST'),
('p', '888', '/fileUploadAndDownload/breakpointContinue', 'POST'),
('p', '888', '/fileUploadAndDownload/removeChunk', 'POST'),
('p', '888', '/fileUploadAndDownload/upload', 'POST'),
('p', '888', '/fileUploadAndDownload/deleteFile', 'POST'),
('p', '888', '/fileUploadAndDownload/editFileName', 'POST'),
('p', '888', '/fileUploadAndDownload/getFileList', 'POST'),
('p', '888', '/fileUploadAndDownload/importURL', 'POST'),
('p', '888', '/casbin/updateCasbin', 'POST'),
('p', '888', '/casbin/getPolicyPathByAuthorityId', 'POST'),
('p', '888', '/jwt/jsonInBlacklist', 'POST'),
('p', '888', '/system/getSystemConfig', 'POST'),
('p', '888', '/system/setSystemConfig', 'POST'),
('p', '888', '/system/getServerInfo', 'POST'),
('p', '888', '/skills/getTools', 'GET'),
('p', '888', '/skills/getSkillList', 'POST'),
('p', '888', '/skills/getSkillDetail', 'POST'),
('p', '888', '/skills/saveSkill', 'POST'),
('p', '888', '/skills/createScript', 'POST'),
('p', '888', '/skills/getScript', 'POST'),
('p', '888', '/skills/saveScript', 'POST'),
('p', '888', '/skills/createResource', 'POST'),
('p', '888', '/skills/getResource', 'POST'),
('p', '888', '/skills/saveResource', 'POST'),
('p', '888', '/skills/createReference', 'POST'),
('p', '888', '/skills/getReference', 'POST'),
('p', '888', '/skills/saveReference', 'POST'),
('p', '888', '/skills/createTemplate', 'POST'),
('p', '888', '/skills/getTemplate', 'POST'),
('p', '888', '/skills/saveTemplate', 'POST'),
('p', '888', '/skills/getGlobalConstraint', 'POST'),
('p', '888', '/skills/saveGlobalConstraint', 'POST'),
('p', '888', '/customer/customer', 'GET'),
('p', '888', '/customer/customer', 'PUT'),
('p', '888', '/customer/customer', 'POST'),
('p', '888', '/customer/customer', 'DELETE'),
('p', '888', '/customer/customerList', 'GET'),
('p', '888', '/autoCode/getDB', 'GET'),
('p', '888', '/autoCode/getMeta', 'POST'),
('p', '888', '/autoCode/preview', 'POST'),
('p', '888', '/autoCode/getTables', 'GET'),
('p', '888', '/autoCode/getColumn', 'GET'),
('p', '888', '/autoCode/rollback', 'POST'),
('p', '888', '/autoCode/createTemp', 'POST'),
('p', '888', '/autoCode/delSysHistory', 'POST'),
('p', '888', '/autoCode/getSysHistory', 'POST'),
('p', '888', '/autoCode/createPackage', 'POST'),
('p', '888', '/autoCode/getTemplates', 'GET'),
('p', '888', '/autoCode/getPackage', 'POST'),
('p', '888', '/autoCode/delPackage', 'POST'),
('p', '888', '/autoCode/createPlug', 'POST'),
('p', '888', '/autoCode/installPlugin', 'POST'),
('p', '888', '/autoCode/pubPlug', 'POST'),
('p', '888', '/autoCode/removePlugin', 'POST'),
('p', '888', '/autoCode/getPluginList', 'GET'),
('p', '888', '/autoCode/addFunc', 'POST'),
('p', '888', '/autoCode/mcp', 'POST'),
('p', '888', '/autoCode/mcpTest', 'POST'),
('p', '888', '/autoCode/mcpList', 'POST'),
('p', '888', '/sysDictionaryDetail/findSysDictionaryDetail', 'GET'),
('p', '888', '/sysDictionaryDetail/updateSysDictionaryDetail', 'PUT'),
('p', '888', '/sysDictionaryDetail/createSysDictionaryDetail', 'POST'),
('p', '888', '/sysDictionaryDetail/getSysDictionaryDetailList', 'GET'),
('p', '888', '/sysDictionaryDetail/deleteSysDictionaryDetail', 'DELETE'),
('p', '888', '/sysDictionaryDetail/getDictionaryTreeList', 'GET'),
('p', '888', '/sysDictionaryDetail/getDictionaryTreeListByType', 'GET'),
('p', '888', '/sysDictionaryDetail/getDictionaryDetailsByParent', 'GET'),
('p', '888', '/sysDictionaryDetail/getDictionaryPath', 'GET'),
('p', '888', '/sysDictionary/findSysDictionary', 'GET'),
('p', '888', '/sysDictionary/updateSysDictionary', 'PUT'),
('p', '888', '/sysDictionary/getSysDictionaryList', 'GET'),
('p', '888', '/sysDictionary/createSysDictionary', 'POST'),
('p', '888', '/sysDictionary/deleteSysDictionary', 'DELETE'),
('p', '888', '/sysDictionary/importSysDictionary', 'POST'),
('p', '888', '/sysDictionary/exportSysDictionary', 'GET'),
('p', '888', '/sysOperationRecord/findSysOperationRecord', 'GET'),
('p', '888', '/sysOperationRecord/updateSysOperationRecord', 'PUT'),
('p', '888', '/sysOperationRecord/createSysOperationRecord', 'POST'),
('p', '888', '/sysOperationRecord/getSysOperationRecordList', 'GET'),
('p', '888', '/sysOperationRecord/deleteSysOperationRecord', 'DELETE'),
('p', '888', '/sysOperationRecord/deleteSysOperationRecordByIds', 'DELETE'),
('p', '888', '/email/emailTest', 'POST'),
('p', '888', '/email/sendEmail', 'POST'),
('p', '888', '/simpleUploader/upload', 'POST'),
('p', '888', '/simpleUploader/checkFileMd5', 'GET'),
('p', '888', '/simpleUploader/mergeFileMd5', 'GET'),
('p', '888', '/authorityBtn/setAuthorityBtn', 'POST'),
('p', '888', '/authorityBtn/getAuthorityBtn', 'POST'),
('p', '888', '/authorityBtn/canRemoveAuthorityBtn', 'POST'),
('p', '888', '/sysExportTemplate/createSysExportTemplate', 'POST'),
('p', '888', '/sysExportTemplate/deleteSysExportTemplate', 'DELETE'),
('p', '888', '/sysExportTemplate/deleteSysExportTemplateByIds', 'DELETE'),
('p', '888', '/sysExportTemplate/updateSysExportTemplate', 'PUT'),
('p', '888', '/sysExportTemplate/findSysExportTemplate', 'GET'),
('p', '888', '/sysExportTemplate/getSysExportTemplateList', 'GET'),
('p', '888', '/sysExportTemplate/exportExcel', 'GET'),
('p', '888', '/sysExportTemplate/exportTemplate', 'GET'),
('p', '888', '/sysExportTemplate/previewSQL', 'GET'),
('p', '888', '/sysExportTemplate/importExcel', 'POST'),
('p', '888', '/sysError/createSysError', 'POST'),
('p', '888', '/sysError/deleteSysError', 'DELETE'),
('p', '888', '/sysError/deleteSysErrorByIds', 'DELETE'),
('p', '888', '/sysError/updateSysError', 'PUT'),
('p', '888', '/sysError/findSysError', 'GET'),
('p', '888', '/sysError/getSysErrorList', 'GET'),
('p', '888', '/sysError/getSysErrorSolution', 'GET'),
('p', '888', '/info/createInfo', 'POST'),
('p', '888', '/info/deleteInfo', 'DELETE'),
('p', '888', '/info/deleteInfoByIds', 'DELETE'),
('p', '888', '/info/updateInfo', 'PUT'),
('p', '888', '/info/findInfo', 'GET'),
('p', '888', '/info/getInfoList', 'GET'),
('p', '888', '/sysParams/createSysParams', 'POST'),
('p', '888', '/sysParams/deleteSysParams', 'DELETE'),
('p', '888', '/sysParams/deleteSysParamsByIds', 'DELETE'),
('p', '888', '/sysParams/updateSysParams', 'PUT'),
('p', '888', '/sysParams/findSysParams', 'GET'),
('p', '888', '/sysParams/getSysParamsList', 'GET'),
('p', '888', '/sysParams/getSysParam', 'GET'),
('p', '888', '/attachmentCategory/getCategoryList', 'GET'),
('p', '888', '/attachmentCategory/addCategory', 'POST'),
('p', '888', '/attachmentCategory/deleteCategory', 'POST'),
('p', '888', '/sysVersion/findSysVersion', 'GET'),
('p', '888', '/sysVersion/getSysVersionList', 'GET'),
('p', '888', '/sysVersion/downloadVersionJson', 'GET'),
('p', '888', '/sysVersion/exportVersion', 'POST'),
('p', '888', '/sysVersion/importVersion', 'POST'),
('p', '888', '/sysVersion/deleteSysVersion', 'DELETE'),
('p', '888', '/sysVersion/deleteSysVersionByIds', 'DELETE'),
-- 8881
('p', '8881', '/user/admin_register', 'POST'),
('p', '8881', '/api/createApi', 'POST'),
('p', '8881', '/api/getApiList', 'POST'),
('p', '8881', '/api/getApiById', 'POST'),
('p', '8881', '/api/deleteApi', 'POST'),
('p', '8881', '/api/updateApi', 'POST'),
('p', '8881', '/api/getAllApis', 'POST'),
('p', '8881', '/authority/createAuthority', 'POST'),
('p', '8881', '/authority/deleteAuthority', 'POST'),
('p', '8881', '/authority/getAuthorityList', 'POST'),
('p', '8881', '/authority/setDataAuthority', 'POST'),
('p', '8881', '/menu/getMenu', 'POST'),
('p', '8881', '/menu/getMenuList', 'POST'),
('p', '8881', '/menu/addBaseMenu', 'POST'),
('p', '8881', '/menu/getBaseMenuTree', 'POST'),
('p', '8881', '/menu/addMenuAuthority', 'POST'),
('p', '8881', '/menu/getMenuAuthority', 'POST'),
('p', '8881', '/menu/deleteBaseMenu', 'POST'),
('p', '8881', '/menu/updateBaseMenu', 'POST'),
('p', '8881', '/menu/getBaseMenuById', 'POST'),
('p', '8881', '/user/changePassword', 'POST'),
('p', '8881', '/user/getUserList', 'POST'),
('p', '8881', '/user/setUserAuthority', 'POST'),
('p', '8881', '/fileUploadAndDownload/upload', 'POST'),
('p', '8881', '/fileUploadAndDownload/getFileList', 'POST'),
('p', '8881', '/fileUploadAndDownload/deleteFile', 'POST'),
('p', '8881', '/fileUploadAndDownload/editFileName', 'POST'),
('p', '8881', '/fileUploadAndDownload/importURL', 'POST'),
('p', '8881', '/casbin/updateCasbin', 'POST'),
('p', '8881', '/casbin/getPolicyPathByAuthorityId', 'POST'),
('p', '8881', '/jwt/jsonInBlacklist', 'POST'),
('p', '8881', '/system/getSystemConfig', 'POST'),
('p', '8881', '/system/setSystemConfig', 'POST'),
('p', '8881', '/customer/customer', 'POST'),
('p', '8881', '/customer/customer', 'PUT'),
('p', '8881', '/customer/customer', 'DELETE'),
('p', '8881', '/customer/customer', 'GET'),
('p', '8881', '/customer/customerList', 'GET'),
('p', '8881', '/user/getUserInfo', 'GET'),
-- 9528
('p', '9528', '/user/admin_register', 'POST'),
('p', '9528', '/api/createApi', 'POST'),
('p', '9528', '/api/getApiList', 'POST'),
('p', '9528', '/api/getApiById', 'POST'),
('p', '9528', '/api/deleteApi', 'POST'),
('p', '9528', '/api/updateApi', 'POST'),
('p', '9528', '/api/getAllApis', 'POST'),
('p', '9528', '/authority/createAuthority', 'POST'),
('p', '9528', '/authority/deleteAuthority', 'POST'),
('p', '9528', '/authority/getAuthorityList', 'POST'),
('p', '9528', '/authority/setDataAuthority', 'POST'),
('p', '9528', '/menu/getMenu', 'POST'),
('p', '9528', '/menu/getMenuList', 'POST'),
('p', '9528', '/menu/addBaseMenu', 'POST'),
('p', '9528', '/menu/getBaseMenuTree', 'POST'),
('p', '9528', '/menu/addMenuAuthority', 'POST'),
('p', '9528', '/menu/getMenuAuthority', 'POST'),
('p', '9528', '/menu/deleteBaseMenu', 'POST'),
('p', '9528', '/menu/updateBaseMenu', 'POST'),
('p', '9528', '/menu/getBaseMenuById', 'POST'),
('p', '9528', '/user/changePassword', 'POST'),
('p', '9528', '/user/getUserList', 'POST'),
('p', '9528', '/user/setUserAuthority', 'POST'),
('p', '9528', '/fileUploadAndDownload/upload', 'POST'),
('p', '9528', '/fileUploadAndDownload/getFileList', 'POST'),
('p', '9528', '/fileUploadAndDownload/deleteFile', 'POST'),
('p', '9528', '/fileUploadAndDownload/editFileName', 'POST'),
('p', '9528', '/fileUploadAndDownload/importURL', 'POST'),
('p', '9528', '/casbin/updateCasbin', 'POST'),
('p', '9528', '/casbin/getPolicyPathByAuthorityId', 'POST'),
('p', '9528', '/jwt/jsonInBlacklist', 'POST'),
('p', '9528', '/system/getSystemConfig', 'POST'),
('p', '9528', '/system/setSystemConfig', 'POST'),
('p', '9528', '/customer/customer', 'PUT'),
('p', '9528', '/customer/customer', 'GET'),
('p', '9528', '/customer/customer', 'POST'),
('p', '9528', '/customer/customer', 'DELETE'),
('p', '9528', '/customer/customerList', 'GET'),
('p', '9528', '/autoCode/createTemp', 'POST'),
('p', '9528', '/user/getUserInfo', 'GET');
/* =========================
* 7. 菜单表 sys_base_menus
* ========================= */
-- 7.1 顶级菜单
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
VALUES
(0, FALSE, 0, 'dashboard', 'dashboard', 'view/dashboard/index.vue', 1, '仪表盘', 'odometer'),
(0, FALSE, 0, 'about', 'about', 'view/about/index.vue', 9, '关于我们', 'info-filled'),
(0, FALSE, 0, 'admin', 'superAdmin', 'view/superAdmin/index.vue', 3, '超级管理员', 'user'),
(0, TRUE, 0, 'person', 'person', 'view/person/person.vue', 4, '个人信息', 'message'),
(0, FALSE, 0, 'example', 'example', 'view/example/index.vue', 7, '示例文件', 'management'),
(0, FALSE, 0, 'systemTools', 'systemTools', 'view/systemTools/index.vue', 5, '系统工具', 'tools'),
(0, FALSE, 0, 'https://www.gin-vue-admin.com', 'https://www.gin-vue-admin.com', '/', 0, '官方网站', 'customer-gva'),
(0, FALSE, 0, 'state', 'state', 'view/system/state.vue', 8, '服务器状态', 'cloudy'),
(0, FALSE, 0, 'plugin', 'plugin', 'view/routerHolder.vue', 6, '插件系统', 'cherry');
-- 7.2 子菜单(通过名称反查父级 ID
-- superAdmin 子菜单
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon, meta_keep_alive)
SELECT
1, FALSE, m.id, 'authority', 'authority', 'view/superAdmin/authority/authority.vue', 1, '角色管理', 'avatar', FALSE
FROM sys_base_menus m WHERE m.name = 'superAdmin';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon, meta_keep_alive)
SELECT
1, FALSE, m.id, 'menu', 'menu', 'view/superAdmin/menu/menu.vue', 2, '菜单管理', 'tickets', TRUE
FROM sys_base_menus m WHERE m.name = 'superAdmin';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon, meta_keep_alive)
SELECT
1, FALSE, m.id, 'api', 'api', 'view/superAdmin/api/api.vue', 3, 'api管理', 'platform', TRUE
FROM sys_base_menus m WHERE m.name = 'superAdmin';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'user', 'user', 'view/superAdmin/user/user.vue', 4, '用户管理', 'coordinate'
FROM sys_base_menus m WHERE m.name = 'superAdmin';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'dictionary', 'dictionary', 'view/superAdmin/dictionary/sysDictionary.vue', 5, '字典管理', 'notebook'
FROM sys_base_menus m WHERE m.name = 'superAdmin';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'operation', 'operation', 'view/superAdmin/operation/sysOperationRecord.vue', 6, '操作历史', 'pie-chart'
FROM sys_base_menus m WHERE m.name = 'superAdmin';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'sysParams', 'sysParams', 'view/superAdmin/params/sysParams.vue', 7, '参数管理', 'compass'
FROM sys_base_menus m WHERE m.name = 'superAdmin';
-- example 子菜单
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'upload', 'upload', 'view/example/upload/upload.vue', 5, '媒体库(上传下载)', 'upload'
FROM sys_base_menus m WHERE m.name = 'example';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'breakpoint', 'breakpoint', 'view/example/breakpoint/breakpoint.vue', 6, '断点续传', 'upload-filled'
FROM sys_base_menus m WHERE m.name = 'example';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'customer', 'customer', 'view/example/customer/customer.vue', 7, '客户列表(资源示例)', 'avatar'
FROM sys_base_menus m WHERE m.name = 'example';
-- systemTools 子菜单
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon, meta_keep_alive)
SELECT
1, FALSE, m.id, 'autoCode', 'autoCode', 'view/systemTools/autoCode/index.vue', 1, '代码生成器', 'cpu', TRUE
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon, meta_keep_alive)
SELECT
1, FALSE, m.id, 'formCreate', 'formCreate', 'view/systemTools/formCreate/index.vue', 3, '表单生成器', 'magic-stick', TRUE
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'system', 'system', 'view/systemTools/system/system.vue', 4, '系统配置', 'operation'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'autoCodeAdmin', 'autoCodeAdmin', 'view/systemTools/autoCodeAdmin/index.vue', 2, '自动化代码管理', 'magic-stick'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'loginLog', 'loginLog', 'view/systemTools/loginLog/index.vue', 5, '登录日志', 'monitor'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'apiToken', 'apiToken', 'view/systemTools/apiToken/index.vue', 6, 'API Token', 'key'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, hidden, meta_title, meta_icon)
SELECT
1, TRUE, m.id, 'autoCodeEdit/:id', 'autoCodeEdit', 'view/systemTools/autoCode/index.vue', 0, TRUE, '自动化代码-${id}', 'magic-stick'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'autoPkg', 'autoPkg', 'view/systemTools/autoPkg/autoPkg.vue', 0, '模板配置', 'folder'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'exportTemplate', 'exportTemplate', 'view/systemTools/exportTemplate/exportTemplate.vue', 5, '导出模板', 'reading'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'skills', 'skills', 'view/systemTools/skills/index.vue', 6, 'Skills管理', 'document'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'picture', 'picture', 'view/systemTools/autoCode/picture.vue', 6, 'AI页面绘制', 'picture-filled'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'mcpTool', 'mcpTool', 'view/systemTools/autoCode/mcp.vue', 7, 'Mcp Tools模板', 'magnet'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'mcpTest', 'mcpTest', 'view/systemTools/autoCode/mcpTest.vue', 7, 'Mcp Tools测试', 'partly-cloudy'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'sysVersion', 'sysVersion', 'view/systemTools/version/version.vue', 8, '版本管理', 'server'
FROM sys_base_menus m WHERE m.name = 'systemTools';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'sysError', 'sysError', 'view/systemTools/sysError/sysError.vue', 9, '错误日志', 'warn'
FROM sys_base_menus m WHERE m.name = 'systemTools';
-- plugin 子菜单
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'https://plugin.gin-vue-admin.com/', 'https://plugin.gin-vue-admin.com/', 'https://plugin.gin-vue-admin.com/', 0, '插件市场', 'shop'
FROM sys_base_menus m WHERE m.name = 'plugin';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'installPlugin', 'installPlugin', 'view/systemTools/installPlugin/index.vue', 1, '插件安装', 'box'
FROM sys_base_menus m WHERE m.name = 'plugin';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'pubPlug', 'pubPlug', 'view/systemTools/pubPlug/pubPlug.vue', 3, '打包插件', 'files'
FROM sys_base_menus m WHERE m.name = 'plugin';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'plugin-email', 'plugin-email', 'plugin/email/view/index.vue', 4, '邮件插件', 'message'
FROM sys_base_menus m WHERE m.name = 'plugin';
INSERT INTO sys_base_menus (menu_level, hidden, parent_id, path, name, component, sort, meta_title, meta_icon)
SELECT
1, FALSE, m.id, 'anInfo', 'anInfo', 'plugin/announcement/view/info.vue', 5, '公告管理[示例]', 'scaleToOriginal'
FROM sys_base_menus m WHERE m.name = 'plugin';
/* =========================
* 8. 示例文件表 exa_file_upload_and_downloads
* ========================= */
INSERT INTO exa_file_upload_and_downloads (name, class_id, url, tag, key)
VALUES
('10.png', '0', 'https://qmplusimg.henrongyi.top/gvalogo.png', 'png', '158787308910.png'),
('logo.png', '0', 'https://qmplusimg.henrongyi.top/1576554439myAvatar.png', 'png', '1587973709logo.png');
/* =========================
* 9. 导出模板表 sys_export_templates
* ========================= */
INSERT INTO sys_export_templates (db_name, name, table_name, template_id, template_info)
VALUES
('', 'api', 'sys_apis', 'api',
'{
"path":"路径",
"method":"方法(大写)",
"description":"方法介绍",
"api_group":"方法分组"
}');
/* =========================
* 10. 多对多关联表
* sys_data_authority_id, sys_user_authority, sys_authority_menus
* ========================= */
-- 10.1 数据权限表 sys_data_authority_id
INSERT INTO sys_data_authority_id (sys_authority_authority_id, data_authority_id)
VALUES
(888, 888),
(888, 9528),
(888, 8881),
(9528, 9528),
(9528, 8881);
-- 10.2 用户-角色表 sys_user_authority
INSERT INTO sys_user_authority (sys_user_id, sys_authority_authority_id)
SELECT u.id, a.authority_id
FROM sys_users u
JOIN sys_authorities a ON a.authority_id IN (888, 9528, 8881)
WHERE u.username = 'admin';
INSERT INTO sys_user_authority (sys_user_id, sys_authority_authority_id)
SELECT u.id, a.authority_id
FROM sys_users u
JOIN sys_authorities a ON a.authority_id = 888
WHERE u.username = 'a303176530';
-- 10.3 角色-菜单表 sys_authority_menus
-- 888: 拥有所有菜单
INSERT INTO sys_authority_menus (sys_authority_authority_id, sys_base_menu_id)
SELECT 888, id FROM sys_base_menus;
-- 8881: 仅拥有 dashboard / about / person / state 顶级菜单
INSERT INTO sys_authority_menus (sys_authority_authority_id, sys_base_menu_id)
SELECT 8881, id
FROM sys_base_menus
WHERE parent_id = 0 AND name IN ('dashboard', 'about', 'person', 'state');
-- 9528: 所有顶级菜单 + systemTools/example 下的子菜单
INSERT INTO sys_authority_menus (sys_authority_authority_id, sys_base_menu_id)
SELECT 9528, id
FROM sys_base_menus
WHERE parent_id = 0;
INSERT INTO sys_authority_menus (sys_authority_authority_id, sys_base_menu_id)
SELECT 9528, id
FROM sys_base_menus
WHERE parent_id IN (
SELECT id FROM sys_base_menus WHERE name IN ('systemTools', 'example')
);
COMMIT;

179
docs/前端卡.md Normal file
View File

@@ -0,0 +1,179 @@
在 SillyTavernST的圈子里**“前端卡”**Frontend Card / HTML Character Card是一个进阶概念它打破了传统角色卡“纯文字描述”的限制。
作为开发者,在重构“云酒馆”时,理解前端卡至关重要,因为它是 ST 社区目前最活跃、最“花哨”的部分。
### 1. 什么是前端卡?
传统角色卡只包含姓名、描述、问候语等文本数据。而**前端卡**本质上是一个**以角色卡为载体的“微型网页应用”**。
它利用 ST 的扩展系统Extensions和 HTML 渲染能力,在聊天界面注入自定义的 HTML、CSS 和 JavaScript。常见的表现形式形式包括
* **状态栏**:显示角色的好感度、心情、体力值。
* **互动 UI**:点击按钮触发特定的指令或对话。
* **视觉小说VN模式**:将背景、立绘和对话框完全重新设计。
* **RPG 系统**:自带背包、商店、任务系统。
---
### 2. 实现原理ST 源码逻辑)
前端卡的实现主要依赖以下几个技术点:
#### A. HTML 注入
ST 允许在 `World Info`(世界书)或者角色卡描述中使用特定的标签。最常用的是配合 **Quick Reply快速回复** 扩展或特定的 **Extension 钩子**
* **逻辑**:后端读取到包含 HTML 字符串的数据,前端通过 `$.append()``innerHTML` 将其塞进 DOM。
#### B. JavaScript 脚本执行
这是前端卡的“灵魂”。制作者通常会将加密或压缩后的 JS 代码嵌入在角色卡的“深度提示词”Depth Prompt或世界书条目中。
* **执行机制**ST 并没有为这些脚本提供沙箱环境。脚本运行在全局作用域下,可以直接通过 `window.SillyTavern` 对象访问 ST 的内部函数(如 `printMessage()`, `setVar()`, `token` 等)。
#### C. 通信与状态同步
前端卡通过以下方式与 ST 核心交互:
* **Macros**:利用 `{{getvar::hp}}` 渲染变量。
* **Slash Commands**:通过 JS 调用 `/setvar key=value` 来更新后端数据。
* **Event Listeners**:监听 `character_selected``message_received` 事件来刷新 UI。
---
### 3. 重构时的“重难点”与注意事项
如果你希望“云酒馆”完全兼容这些前端卡,你在使用 **React + Go** 重构时会面临巨大的架构挑战:
#### **1. 安全性(最致命的问题)**
* **ST 原版**因为是单机运行JS 随便跑,顶多是用户自己害自己。
* **你的公共平台**:这是 **跨站脚本攻击XSS** 的重灾区。如果用户 A 上传了一张“前端卡”,用户 B 导入后,这张卡里的 JS 脚本可以直接偷走用户 B 的浏览器 `LocalStorage`(包含你的平台 Token或者劫持 API Key。
* **解决方案**
* **必须隔离**:绝对不能直接在 React 主页面插入角色卡的 HTML/JS。
* **Iframe 沙箱**:将前端卡的渲染逻辑放在一个独立的、跨域的 `<iframe>` 中,限制 `sandbox` 属性(禁止访问父窗口)。
* **消息传递**React 宿主环境与 Iframe 之间通过 `postMessage` 进行受限的通信。
#### **2. API 兼容层**
前端卡通常会调用 `SillyTavern.XXX` 这种全局对象。
* **重构方案**:你需要在 Iframe 沙箱中预先注入一个“模拟对象”,模拟出 ST 的全局 API 环境,将这些调用映射到你自己的后端 API 或 Zustand Store。
#### **3. 数据持久化**
前端卡依赖大量的自定义变量Variables
* **重构方案**:确保你的 PostgreSQL 数据库中 `user_variables` 表能够支持高频读写,因为一些前端卡会通过 JS 脚本每秒多次更新变量(例如计时器或动态动画)。
#### **4. 样式的冲突**
前端卡自带的 CSS 经常会全局污染样式。
* **重构方案**:使用 **Shadow DOM****CSS Modules**。当然,如果用了 Iframe天然就解决了这个问题。
---
### 4. 架构要点(结合你们现有系统)
#### 安全与隔离
- 使用 iframe 沙箱模式来渲染前端卡内容,避免直接在宿主页面全局作用域执行脚本。
- 通过 postMessage 实现前端卡与宿主系统的受控通信,限定可访问的接口集合。
- 设定严格的内容策略Content Security Policy对内联脚本、外部资源、以及跨域请求进行管理。
#### 数据与 API 设计
- 后端存储前端卡的 HTML/CSS/JS 片段、扩展数据、以及变量定义。后端仅做存储与校验,不执行脚本。
- 前端卡通过受控接口访问后端数据(如变量、角色信息、事件触发等)。
- 提供可版本化的 API 接口,用于加载、更新、禁用前端卡,以及回滚到稳定版本。
#### 渲染与交互
- 前端卡在 iframe 内部渲染,用 Shadow DOM/CSS Modules/或独立的样式域来避免全局样式冲突。
- 提供一个明确的 UI 生命周期:初始化、加载资源、渲染、更新、销毁。
- 交互事件通过 postMessage 传递,宿主统一进行权限校验与路由。
#### 兼容性与迁移
- 对现有角色卡数据结构进行向后兼容处理Gradual Migration 路线图:先支持最小化前端卡,再逐步引入扩展能力。
- 做好版本回滚与降级策略,确保单卡或单次迁移失败时系统可继续工作。
#### 性能与监控
- 对前端卡资源进行容量与时间上的监控(加载时间、渲染时间、内存占用)。
- 对复杂卡片设定资源上限,避免页面整体降级。
---
### 5. 实施工具与注意事项
#### 安全策略
- 明确规定 iframe sandbox 属性例如sandbox="allow-scripts"(可选限制更多能力),禁用同源策略外的访问,严格限制跨域能力。
- 使用 CSP、Content Security Policy 报告与阻断机制,避免内联脚本被滥用。
#### 数据边界
- 设置前端卡大小的上限HTML/JS 总长度、资源大小),避免 UI 注入导致页面阻塞或崩溃。
#### 日志与审计
- 记录哪些前端卡被加载、哪些代码被执行、以及潜在的安全事件。
#### 流程与回滚
- 卡片的版本化、灰度发布、快速回滚机制,确保单卡异常不会影响全局。
#### 测试要点
- 安全性测试XSS/恶意脚本注入)、性能压测、跨域通信正确性测试、回滚演练。
---
### 6. 开发路线与任务清单(建议)
- 确定沙箱实现方案iframe + postMessage与接口白名单。
- 设计并实现一个前端卡沙箱适配层:在宿主页面暴露受控 API映射到后端或前端状态商店。
- 定义前端卡数据模型HTML/CSS/JS、Extensions、World Info 引用、变量定义等的数据库字段与版本控制。
- 引入内容审核/过滤机制,对上传的前端卡进行静态/动态分析,防止危害。
- 提供一个最小可用版本MVP只支持简单的 UI 注入与变量调用逐步扩展互动组件、VN 模式、背包/商店等系统。
- 编写迁移指南与回滚策略,保障升级过程的可控性。
- 与前端团队协同,建立 UI/UX 一致性规范渐进式增强、UI 风格、交互动画等)。
---
### 7. 风险与注意事项
- 安全性是第一位,绝不能让前端卡访问宿主的敏感 API、Token、Key 等。
- 资源占用管理,避免长时间执行脚本导致内存/CPU 过高。
- 兼容性测试要覆盖历史卡、扩展卡、以及新卡的回滚路径。
- 版本与数据一致性,确保升级/回滚时的卡状态不会导致数据错乱。
---
### 8. 参考与链接
- ST 官方文档(前端卡相关章节)
- 安全最佳实践Iframe 沙箱、postMessage、Content Security Policy
- 你们的现有实现片段:后端 API、WorldbookEngine、Extensions、Macros、Variables
---
### 9. 结论
**前端卡 = 角色卡数据 + 动态 UI 脚本。**
在重构时,我建议你将其视为**“第三方不可信插件”**来处理。
1. **后端Go**:负责存储和校验 HTML/JS 字符串,但不执行。
2. **前端React**:负责开辟一个“隔离区”(沙箱),把脚本关进去运行。
3. **兼容性**:你需要实现一个适配器,让那些习惯于 ST 语境的脚本以为自己还在 ST 里,但实际上是在受你监控的“云酒馆”容器中。
如果你能完美解决“安全隔离”的同时保留“交互能力”,你的云酒馆将直接降维打击目前市面上大部分只能聊天的网页端酒馆。
---
### 10. 案例分析Clannad_v3.1.png 落地要点
请参阅独立文档 [docs/Clannad_v3.1_analysis.md](./Clannad_v3.1_analysis.md) 以获取完整的落地要点、字段设计、实现步骤与验收标准。

458
docs/重构文档.md Normal file
View File

@@ -0,0 +1,458 @@
这是一个需要长期演进、但方向已经比较清晰的重构计划。为了让“云酒馆”既能承载多用户公共场景,又能高度兼容 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的协议转换。

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,267 @@
## 预设模块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 解析与复选框逻辑