🎨 添加文档 && 新增正则脚本模块

Signed-off-by: Echo <1711788888@qq.com>
This commit is contained in:
2026-02-28 15:11:12 +08:00
parent a6234e7bb0
commit 3bfa59cf3e
17 changed files with 26980 additions and 1 deletions

2
.gitignore vendored
View File

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

67
docs/README.md Normal file
View File

@@ -0,0 +1,67 @@
# 云酒馆项目文档
## 目录
- [项目概述](./overview.md)
- [架构设计](./architecture.md)
- [API 文档](./api.md)
- [前端开发指南](./frontend-guide.md)
- [后端开发指南](./backend-guide.md)
- [部署指南](./deployment.md)
- [设计系统](./design-system/)
## 项目架构
```
云酒馆平台
├── 前端 (web-app)
│ ├── 公共页面(首页、角色广场、角色详情)
│ ├── 用户系统(登录、注册、用户中心)
│ ├── 管理功能(角色卡管理、预设管理)
│ └── 对话功能(聊天界面、历史记录)
└── 后端 (server)
├── 用户认证 (JWT)
├── 角色卡 API
├── 对话管理 API
├── 预设管理 API
└── AI 集成
```
## 核心功能
### 角色卡系统
- 支持 PNG 格式角色卡(嵌入 JSON 数据)
- 支持纯 JSON 格式角色卡
- 角色信息编辑
- 导入导出功能
### 预设系统
- 支持多种预设格式SillyTavern、TavernAI 等)
- 参数配置Temperature、Top P、Top K 等)
- 预设复制和导出
### 对话系统
- 实时消息发送
- 对话历史管理
- 多角色对话支持
- 对话导出功能
## 开发规范
### 代码风格
- 前端ESLint + Prettier
- 后端ESLint
- 提交信息Conventional Commits
### Git 工作流
- main: 生产环境
- develop: 开发环境
- feature/*: 功能分支
- bugfix/*: 修复分支
## 快速链接
- [前端 README](../web-app/README.md)
- [后端 README](../server/README.md)
- [设计系统](./design-system/)

View File

@@ -0,0 +1,206 @@
# Design System Master File
> **LOGIC:** When building a specific page, first check `design-system/pages/[page-name].md`.
> If that file exists, its rules **override** this Master file.
> If not, strictly follow the rules below.
---
**Project:** SillyTavern Modern UI
**Generated:** 2026-02-26 17:01:07
**Category:** Quantum Computing Interface
---
## Global Rules
### Color Palette
| Role | Hex | CSS Variable |
|------|-----|--------------|
| Primary | `#7C3AED` | `--color-primary` |
| Secondary | `#A78BFA` | `--color-secondary` |
| CTA/Accent | `#F97316` | `--color-cta` |
| Background | `#FAF5FF` | `--color-background` |
| Text | `#4C1D95` | `--color-text` |
**Color Notes:** Excitement purple + action orange
### Typography
- **Heading Font:** Inter
- **Body Font:** Inter
- **Mood:** spatial, legible, glass, system, clean, neutral
- **Google Fonts:** [Inter + Inter](https://fonts.google.com/share?selection.family=Inter:wght@300;400;500;600)
**CSS Import:**
```css
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');
```
### Spacing Variables
| Token | Value | Usage |
|-------|-------|-------|
| `--space-xs` | `4px` / `0.25rem` | Tight gaps |
| `--space-sm` | `8px` / `0.5rem` | Icon gaps, inline spacing |
| `--space-md` | `16px` / `1rem` | Standard padding |
| `--space-lg` | `24px` / `1.5rem` | Section padding |
| `--space-xl` | `32px` / `2rem` | Large gaps |
| `--space-2xl` | `48px` / `3rem` | Section margins |
| `--space-3xl` | `64px` / `4rem` | Hero padding |
### Shadow Depths
| Level | Value | Usage |
|-------|-------|-------|
| `--shadow-sm` | `0 1px 2px rgba(0,0,0,0.05)` | Subtle lift |
| `--shadow-md` | `0 4px 6px rgba(0,0,0,0.1)` | Cards, buttons |
| `--shadow-lg` | `0 10px 15px rgba(0,0,0,0.1)` | Modals, dropdowns |
| `--shadow-xl` | `0 20px 25px rgba(0,0,0,0.15)` | Hero images, featured cards |
---
## Component Specs
### Buttons
```css
/* Primary Button */
.btn-primary {
background: #F97316;
color: white;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
transition: all 200ms ease;
cursor: pointer;
}
.btn-primary:hover {
opacity: 0.9;
transform: translateY(-1px);
}
/* Secondary Button */
.btn-secondary {
background: transparent;
color: #7C3AED;
border: 2px solid #7C3AED;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
transition: all 200ms ease;
cursor: pointer;
}
```
### Cards
```css
.card {
background: #FAF5FF;
border-radius: 12px;
padding: 24px;
box-shadow: var(--shadow-md);
transition: all 200ms ease;
cursor: pointer;
}
.card:hover {
box-shadow: var(--shadow-lg);
transform: translateY(-2px);
}
```
### Inputs
```css
.input {
padding: 12px 16px;
border: 1px solid #E2E8F0;
border-radius: 8px;
font-size: 16px;
transition: border-color 200ms ease;
}
.input:focus {
border-color: #7C3AED;
outline: none;
box-shadow: 0 0 0 3px #7C3AED20;
}
```
### Modals
```css
.modal-overlay {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
.modal {
background: white;
border-radius: 16px;
padding: 32px;
box-shadow: var(--shadow-xl);
max-width: 500px;
width: 90%;
}
```
---
## Style Guidelines
**Style:** Dark Mode (OLED)
**Keywords:** Dark theme, low light, high contrast, deep black, midnight blue, eye-friendly, OLED, night mode, power efficient
**Best For:** Night-mode apps, coding platforms, entertainment, eye-strain prevention, OLED devices, low-light
**Key Effects:** Minimal glow (text-shadow: 0 0 10px), dark-to-light transitions, low white emission, high readability, visible focus
### Page Pattern
**Pattern Name:** Horizontal Scroll Journey
- **Conversion Strategy:** Immersive product discovery. High engagement. Keep navigation visible.
28,Bento Grid Showcase,bento, grid, features, modular, apple-style, showcase", 1. Hero, 2. Bento Grid (Key Features), 3. Detail Cards, 4. Tech Specs, 5. CTA, Floating Action Button or Bottom of Grid, Card backgrounds: #F5F5F7 or Glass. Icons: Vibrant brand colors. Text: Dark., Hover card scale (1.02), video inside cards, tilt effect, staggered reveal, Scannable value props. High information density without clutter. Mobile stack.
29,Interactive 3D Configurator,3d, configurator, customizer, interactive, product", 1. Hero (Configurator), 2. Feature Highlight (synced), 3. Price/Specs, 4. Purchase, Inside Configurator UI + Sticky Bottom Bar, Neutral studio background. Product: Realistic materials. UI: Minimal overlay., Real-time rendering, material swap animation, camera rotate/zoom, light reflection, Increases ownership feeling. 360 view reduces return rates. Direct add-to-cart.
30,AI-Driven Dynamic Landing,ai, dynamic, personalized, adaptive, generative", 1. Prompt/Input Hero, 2. Generated Result Preview, 3. How it Works, 4. Value Prop, Input Field (Hero) + 'Try it' Buttons, Adaptive to user input. Dark mode for compute feel. Neon accents., Typing text effects, shimmering generation loaders, morphing layouts, Immediate value demonstration. 'Show, don't tell'. Low friction start.
- **CTA Placement:** Floating Sticky CTA or End of Horizontal Track
- **Section Order:** 1. Intro (Vertical), 2. The Journey (Horizontal Track), 3. Detail Reveal, 4. Vertical Footer
---
## Anti-Patterns (Do NOT Use)
- ❌ Generic tech design
- ❌ No viz
### Additional Forbidden Patterns
-**Emojis as icons** — Use SVG icons (Heroicons, Lucide, Simple Icons)
-**Missing cursor:pointer** — All clickable elements must have cursor:pointer
-**Layout-shifting hovers** — Avoid scale transforms that shift layout
-**Low contrast text** — Maintain 4.5:1 minimum contrast ratio
-**Instant state changes** — Always use transitions (150-300ms)
-**Invisible focus states** — Focus states must be visible for a11y
---
## Pre-Delivery Checklist
Before delivering any UI code, verify:
- [ ] No emojis used as icons (use SVG instead)
- [ ] All icons from consistent icon set (Heroicons/Lucide)
- [ ] `cursor-pointer` on all clickable elements
- [ ] Hover states with smooth transitions (150-300ms)
- [ ] Light mode: text contrast 4.5:1 minimum
- [ ] Focus states visible for keyboard navigation
- [ ] `prefers-reduced-motion` respected
- [ ] Responsive: 375px, 768px, 1024px, 1440px
- [ ] No content hidden behind fixed navbars
- [ ] No horizontal scroll on mobile

View File

@@ -0,0 +1,730 @@
# 云酒馆项目开发进度
> 最后更新2026-02-27
## 项目概述
云酒馆是一个现代化的 AI 角色对话平台,采用前后端分离架构,完全兼容 SillyTavern V2/V3 格式。
- **前端**React 18 + TypeScript + Tailwind CSS + Vite
- **后端**Go 1.24 + Gin + PostgreSQL + GORM
- **设计风格**Glassmorphism + 深色主题 (OLED 优化)
---
## 开发进度总览
| 模块 | 状态 | 完成度 | 负责人 | 备注 |
|------|------|--------|--------|------|
| 用户系统 | ✅ 已完成 | 100% | - | 注册、登录、资料管理 |
| 角色卡管理 | ✅ 已完成 | 100% | - | 完全兼容 ST V2前后端已打通 |
| 预设管理 | ✅ 已完成 | 100% | - | 前后端已打通,支持导入导出 |
| 对话系统 | ✅ 已完成 | 95% | - | 预设切换、消息重新生成已完成 |
| AI 集成 | 🚧 进行中 | 80% | - | AI 配置管理、API 调用已完成 |
| 世界书系统 | 📋 待开发 | 0% | - | 规划中 |
| 正则脚本 | 📋 待开发 | 0% | - | 规划中 |
**图例**
- ✅ 已完成
- 🚧 进行中
- 📋 待开发
- ⏸️ 暂停
- ❌ 已取消
---
## 一、用户系统 ✅
### 1.1 后端 API已完成 - 2024-02-26
**数据模型**
-`AppUser` - 用户模型
-`AppUserSession` - 会话模型
**API 端点**
-`POST /app/auth/register` - 用户注册
-`POST /app/auth/login` - 用户登录
-`POST /app/auth/refresh` - 刷新Token
-`POST /app/auth/logout` - 用户登出
-`GET /app/auth/userinfo` - 获取用户信息
-`PUT /app/user/profile` - 更新用户资料
-`POST /app/user/change-password` - 修改密码
**核心功能**
- ✅ JWT 认证7天有效期
- ✅ bcrypt 密码加密
- ✅ 会话管理IP、设备信息
- ✅ 用户状态检查
- ✅ 最后登录记录
**文件清单**
```
server/
├── api/v1/app/auth.go # API 控制器
├── model/app/
│ ├── app_user.go # 用户模型
│ ├── app_user_session.go # 会话模型
│ ├── request/auth.go # 请求结构
│ └── response/auth.go # 响应结构
├── service/app/auth.go # 业务逻辑
├── router/app/auth.go # 路由配置
├── middleware/app_jwt.go # JWT 中间件
└── utils/app_jwt.go # JWT 工具
```
### 1.2 前端页面(已完成 - 2024-02-26
**页面清单**
-`/login` - 登录页
-`/register` - 注册页
-`/forgot-password` - 忘记密码
-`/profile` - 用户中心
**功能特性**
- ✅ 表单验证
- ✅ 密码可见性切换
- ✅ 第三方登录入口GitHub/Google
- ✅ 用户统计数据展示
- ✅ 账号设置、隐私设置、通知设置
**文件清单**
```
web-app/src/pages/
├── LoginPage.tsx
├── RegisterPage.tsx
├── ForgotPasswordPage.tsx
└── ProfilePage.tsx
```
### 1.3 前后端对接(已完成 - 2024-02-26
**API 服务层**
-`web-app/src/api/client.ts` - Axios 客户端配置
-`web-app/src/api/auth.ts` - 用户认证 API 封装
**核心功能**
- ✅ Token 自动注入请求头
- ✅ Token 过期自动刷新
- ✅ 统一错误处理
- ✅ 登录状态持久化localStorage
- ✅ 登录后跳转用户中心
- ✅ 未登录自动跳转登录页
- ✅ 用户信息实时加载
- ✅ 登出功能
**环境配置**
-`.env.development` - 开发环境配置http://localhost:8888
-`.env.production` - 生产环境配置
**依赖安装**
- ✅ axios - HTTP 客户端
---
## 二、角色卡管理 ✅
### 2.1 后端 API已完成 - 2024-02-26
**数据模型**
-`AICharacter` - 角色卡模型(完全兼容 SillyTavern V2 格式)
- ✅ 使用 JSONB 存储复杂数据tags, alternateGreetings, characterBook, extensions
- ✅ 支持公开/私有角色卡
- ✅ 统计信息(使用次数、收藏次数)
**API 端点**
-`POST /app/character` - 创建角色卡
-`GET /app/character` - 获取角色卡列表(分页、搜索、标签筛选)
-`GET /app/character/:id` - 获取角色卡详情
-`PUT /app/character/:id` - 更新角色卡
-`DELETE /app/character/:id` - 删除角色卡
-`POST /app/character/upload` - 上传角色卡文件PNG/JSON
-`GET /app/character/:id/export` - 导出角色卡为 JSON
**核心功能**
- ✅ 完全兼容 SillyTavern V2 格式
- ✅ PNG 元数据提取(使用 `utils/character_card.go`
- ✅ JSON 格式导入导出
- ✅ 角色卡搜索和标签筛选
- ✅ 用户权限控制(只能编辑自己的角色卡)
- ✅ 支持 Base64 编码的 PNG tEXt chunk
**文件清单**
```
server/
├── api/v1/app/character.go # API 控制器
├── model/app/ai_character.go # 角色卡模型
├── model/app/request/character.go # 请求结构
├── model/app/response/character.go # 响应结构
├── service/app/character.go # 业务逻辑
├── router/app/character.go # 路由配置
└── utils/character_card.go # SillyTavern V2 格式工具
```
### 2.2 前端页面(已完成 - 2024-02-26
**页面**
-`/characters` - 角色卡管理页
**功能特性**
- ✅ 上传 PNG 角色卡(后端自动解析)
- ✅ 上传 JSON 角色卡
- ✅ 角色卡编辑(名称、描述、性格、场景、系统提示词等)
- ✅ 角色卡导出为 JSON
- ✅ 角色卡删除
- ✅ 搜索功能
- ✅ 分页加载
**文件清单**
```
web-app/src/
├── api/character.ts # 角色卡 API 封装
└── pages/CharacterManagePage.tsx # 角色卡管理页面
```
### 2.3 前后端对接(已完成 - 2024-02-26
**API 服务层**
-`web-app/src/api/character.ts` - 角色卡 API 封装
**核心功能**
- ✅ 角色卡列表加载(分页、搜索)
- ✅ 文件上传PNG/JSON
- ✅ 角色卡编辑和保存
- ✅ 角色卡删除
- ✅ 角色卡导出
- ✅ 错误处理和用户提示
- ✅ 登录状态检查
---
## 三、预设管理 ✅
### 3.1 后端 API已完成 - 2026-02-27
**数据模型**
-`AIPreset` - 预设模型(完全兼容 SillyTavern 格式)
- ✅ 支持所有采样参数temperature/topP/topK/frequencyPenalty/presencePenalty/maxTokens/repetitionPenalty/minP/topA
- ✅ 支持系统提示词和停止序列
- ✅ 使用 JSONB 存储扩展字段extensions
- ✅ 支持公开/私有预设和默认预设
**API 端点**
-`POST /app/preset` - 创建预设
-`GET /app/preset` - 获取预设列表(分页、搜索、公开筛选)
-`GET /app/preset/:id` - 获取预设详情
-`PUT /app/preset/:id` - 更新预设
-`DELETE /app/preset/:id` - 删除预设
-`POST /app/preset/:id/default` - 设置默认预设
-`POST /app/preset/import` - 导入预设JSON 文件)
-`GET /app/preset/:id/export` - 导出预设为 JSON
**核心功能**
- ✅ 完全兼容 SillyTavern 预设格式
- ✅ JSON 格式导入导出
- ✅ 预设搜索和筛选
- ✅ 用户权限控制(只能编辑自己的预设)
- ✅ 默认预设管理
- ✅ 使用统计
**文件清单**
```
server/
├── api/v1/app/preset.go # API 控制器
├── model/app/preset.go # 预设模型
├── model/app/request/preset.go # 请求结构
├── model/app/response/preset.go # 响应结构
├── service/app/preset.go # 业务逻辑
└── router/app/preset.go # 路由配置
```
### 3.2 前端页面(已完成 - 2026-02-27
**页面**
-`/presets` - 预设管理页
**功能特性**
- ✅ 导入 JSON 预设
- ✅ 预设参数编辑Temperature、Top P、Top K、Frequency Penalty、Presence Penalty 等)
- ✅ 预设复制
- ✅ 预设导出
- ✅ 预设删除
- ✅ 搜索功能
- ✅ 设置默认预设
**文件清单**
```
web-app/src/
├── api/preset.ts # 预设 API 封装
└── pages/PresetManagePage.tsx # 预设管理页面
```
### 3.3 前后端对接(已完成 - 2026-02-27
**API 服务层**
-`web-app/src/api/preset.ts` - 预设 API 封装
**核心功能**
- ✅ 预设列表加载(分页、搜索)
- ✅ 文件上传JSON
- ✅ 预设编辑和保存
- ✅ 预设删除
- ✅ 预设导出
- ✅ 默认预设设置
- ✅ 错误处理和用户提示
- ✅ 登录状态检查
---
## 四、对话系统 ✅
### 4.1 后端 API已完成 - 2026-02-27
**数据模型**
-`Conversation` - 对话会话模型
-`Message` - 消息模型
- ✅ 支持用户、角色、预设关联
- ✅ 支持 AI 提供商和模型配置
- ✅ 使用 JSONB 存储对话设置和消息元数据
- ✅ 统计信息消息数量、Token 使用量)
**API 端点**
-`POST /app/conversation` - 创建对话
-`GET /app/conversation` - 获取对话列表(分页)
-`GET /app/conversation/:id` - 获取对话详情
-`PUT /app/conversation/:id/settings` - 更新对话设置
-`DELETE /app/conversation/:id` - 删除对话
-`GET /app/conversation/:id/messages` - 获取消息历史(分页)
-`POST /app/conversation/:id/message` - 发送消息
-`POST /app/conversation/:id/message?stream=true` - 流式发送消息SSE
**核心功能**
- ✅ 对话会话管理
- ✅ 消息发送接收
- ✅ 对话历史存储
- ✅ SSE 流式响应支持
- ✅ Token 统计
- ✅ 用户权限控制
**文件清单**
```
server/
├── api/v1/app/conversation.go # API 控制器
├── model/app/conversation.go # 对话和消息模型
├── model/app/request/conversation.go # 请求结构
├── model/app/response/conversation.go # 响应结构
├── service/app/conversation.go # 业务逻辑
└── router/app/conversation.go # 路由配置
```
### 4.2 前端页面(已完成 - 2026-02-27
**页面**
-`/chat` - 聊天界面
**功能特性**
- ✅ 对话列表侧边栏
- ✅ 角色选择弹窗
- ✅ 消息输入框
- ✅ 对话菜单(导出、删除)
- ✅ 实时消息发送
- ✅ 消息流式显示
- ✅ 对话历史加载
- ✅ 背景图和主题色设置
**待完善**
- ✅ 预设切换功能(流式模式已修复应用预设参数)
- ✅ 消息重新生成(支持流式,自动删除旧回复)
- 📋 消息编辑
- 📋 世界书开关
- 📋 正则脚本开关
**文件清单**
```
web-app/src/
├── api/conversation.ts # 对话 API 封装
├── pages/ChatPage.tsx # 聊天页面
├── components/Sidebar.tsx # 侧边栏
├── components/ChatArea.tsx # 聊天区域
├── components/CharacterPanel.tsx # 角色面板
└── components/SettingsPanel.tsx # 设置面板
```
### 4.3 前后端对接(已完成 - 2026-02-27
**API 服务层**
-`web-app/src/api/conversation.ts` - 对话 API 封装
**核心功能**
- ✅ 对话列表加载(分页)
- ✅ 创建新对话
- ✅ 发送消息
- ✅ 消息历史加载
- ✅ 对话设置更新
- ✅ 对话删除
- ✅ 错误处理和用户提示
- ✅ 登录状态检查
---
## 五、AI 集成 🚧
### 5.1 后端 API部分完成 - 2026-02-27
**数据模型**
-`AIConfig` - AI 配置模型
- ✅ 支持多种 AI 提供商OpenAI/Anthropic/Custom
- ✅ API Key 加密存储
- ✅ 模型配置和参数管理
**API 端点**
-`POST /app/ai-config` - 创建 AI 配置
-`GET /app/ai-config` - 获取配置列表
-`GET /app/ai-config/:id` - 获取配置详情
-`PUT /app/ai-config/:id` - 更新配置
-`DELETE /app/ai-config/:id` - 删除配置
-`POST /app/ai-config/:id/test` - 测试连接
**待实现功能**
- ✅ 实际 AI API 调用集成OpenAI/Anthropic
- 📋 本地模型支持Ollama/LM Studio
- 📋 Token 计数优化
- 📋 错误重试机制
- 📋 提示词模板系统
**文件清单**
```
server/
├── api/v1/app/ai_config.go # API 控制器
├── model/app/ai_config.go # AI 配置模型
├── model/app/request/ai_config.go # 请求结构
├── model/app/response/ai_config.go # 响应结构
└── service/app/ai_config.go # 业务逻辑
```
### 5.2 前端页面(已完成 - 2026-02-27
**功能特性**
- ✅ AI 配置管理界面
- ✅ API Key 配置
- ✅ 模型选择
- ✅ 连接测试
**文件清单**
```
web-app/src/
└── api/aiConfig.ts # AI 配置 API 封装
```
---
## 六、世界书系统 ✅
### 6.1 后端 API已完成 - 2026-02-27
**数据模型**
-`Worldbook` - 世界书模型
-`WorldbookEntry` - 世界书条目模型(完全兼容 SillyTavern 格式)
- ✅ 支持公开/私有世界书
- ✅ 使用 JSONB 存储关键词数组和扩展字段
**已实现字段**
- ✅ keys/secondary_keys - 主关键词和次要关键词
- ✅ content - 条目内容
- ✅ constant/enabled - 常驻和启用标志
- ✅ use_regex/case_sensitive/match_whole_words - 匹配选项
- ✅ selective/selective_logic - 选择性触发逻辑
- ✅ position/depth/order - 注入位置和深度
- ✅ probability/scan_depth - 触发概率和扫描深度
- ✅ group_id - 分组管理
**API 端点**
-`POST /app/worldbook` - 创建世界书
-`GET /app/worldbook` - 获取世界书列表(分页、搜索)
-`GET /app/worldbook/:id` - 获取世界书详情
-`PUT /app/worldbook/:id` - 更新世界书
-`DELETE /app/worldbook/:id` - 删除世界书(级联删除条目)
-`POST /app/worldbook/import` - 导入世界书JSON
-`GET /app/worldbook/:id/export` - 导出世界书为 JSON
-`POST /app/worldbook/:id/entry` - 创建条目
-`GET /app/worldbook/:id/entries` - 获取条目列表
-`PUT /app/worldbook/:id/entry/:entryId` - 更新条目
-`DELETE /app/worldbook/:id/entry/:entryId` - 删除条目
**核心功能**
- ✅ 完全兼容 SillyTavern 世界书格式
- ✅ JSON 格式导入导出
- ✅ 世界书搜索和筛选
- ✅ 用户权限控制(只能编辑自己的世界书)
- ✅ 条目计数自动更新
- 📋 关键词触发算法(待与对话系统集成)
- 📋 与对话系统集成(待实现)
**文件清单**
```
server/
├── api/v1/app/worldbook.go # API 控制器
├── model/app/worldbook.go # 世界书和条目模型
├── model/app/request/worldbook.go # 请求结构
├── model/app/response/worldbook.go # 响应结构
├── service/app/worldbook.go # 业务逻辑
└── router/app/worldbook.go # 路由配置
```
### 6.2 前端页面(已完成 - 2026-02-27
**页面**
-`/worldbooks` - 世界书管理页
**功能特性**
- ✅ 导入 JSON 世界书
- ✅ 创建世界书
- ✅ 世界书列表展示
- ✅ 世界书搜索
- ✅ 世界书导出
- ✅ 世界书删除
- ✅ 条目创建和编辑
- ✅ 条目列表展示
- ✅ 条目删除
- ✅ 关键词管理(主关键词、次要关键词)
- ✅ 条目选项配置(常驻、启用、正则、大小写等)
- ✅ 高级选项(触发概率、扫描深度)
**文件清单**
```
web-app/src/
├── api/worldbook.ts # 世界书 API 封装
└── pages/WorldbookManagePage.tsx # 世界书管理页面
```
### 6.3 前后端对接(已完成 - 2026-02-27
**API 服务层**
-`web-app/src/api/worldbook.ts` - 世界书 API 封装
**核心功能**
- ✅ 世界书列表加载(分页、搜索)
- ✅ 文件上传JSON
- ✅ 世界书创建、编辑、删除
- ✅ 世界书导出
- ✅ 条目创建、编辑、删除
- ✅ 条目列表加载
- ✅ 错误处理和用户提示
- ✅ 登录状态检查
### 6.4 待完善功能
- 📋 与对话系统集成(在对话中激活世界书)
- 📋 关键词触发引擎实现
- 📋 选择性触发逻辑AND/NOT
- 📋 深度控制和位置注入
- 📋 概率触发机制
- 📋 递归扫描
- 📋 角色绑定世界书
- 📋 全局世界书
---
## 七、正则脚本系统 📋
### 7.1 待实现功能(规划中)
**数据模型**
- 📋 `RegexScript` - 正则脚本模型
- 📋 支持全局、角色、预设三种作用域
- 📋 完全兼容 SillyTavern 正则脚本格式
**待实现字段**
- 📋 find_regex/replace_with - 查找和替换表达式
- 📋 trim_string - 修剪字符串
- 📋 placement - 执行阶段(输入/输出/世界书/推理)
- 📋 disabled/markdown_only/run_on_edit/prompt_only - 执行选项
- 📋 substitute_regex - 宏替换({{user}}/{{char}}
- 📋 min_depth/max_depth - 深度控制
- 📋 scope/owner_char_id/owner_preset_id - 作用域管理
**计划 API 端点**
```
POST /app/regex # 创建正则脚本
GET /app/regex # 获取脚本列表
GET /app/regex/:id # 获取脚本详情
PUT /app/regex/:id # 更新脚本
DELETE /app/regex/:id # 删除脚本
POST /app/regex/:id/test # 测试脚本
```
**核心功能规划**
- 📋 多阶段脚本执行(输入/输出/世界书/推理)
- 📋 正则表达式解析和执行
- 📋 宏替换系统
- 📋 深度控制和条件执行
- 📋 与对话系统集成
---
## 八、公共页面 ✅
### 6.1 已完成页面
-`/` - 首页(平台介绍、热门角色)
-`/market` - 角色广场(浏览、搜索、分类)
-`/character/:id` - 角色详情(信息、评价)
**文件**
```
web-app/src/pages/
├── HomePage.tsx
├── CharacterMarket.tsx
└── CharacterDetail.tsx
```
---
## 九、技术债务与优化
### 9.1 待优化项
- ✅ 前后端 API 对接(已完成:用户、角色卡、预设、对话)
- ✅ 实际 AI API 调用集成
- 📋 错误处理统一化
- 📋 加载状态优化
- 📋 响应式布局完善
- 📋 性能优化(虚拟滚动、懒加载)
- 📋 单元测试
- 📋 E2E 测试
- 📋 实际 AI API 调用集成
- 📋 世界书引擎实现
- 📋 正则脚本引擎实现
- 📋 Prompt Pipeline 优化
### 9.2 已知问题
- ✅ 对话系统预设切换功能(流式模式已支持)
- ✅ 消息重新生成功能(支持流式,自动删除旧回复)
- 📋 消息编辑功能待实现
---
## 十、部署相关 📋
### 10.1 待完成
- 📋 Docker 配置
- 📋 CI/CD 流程
- 📋 环境变量管理
- 📋 数据库迁移脚本
- 📋 备份策略
- 📋 监控告警
---
## 十一、文档完善 📋
### 11.1 待补充文档
- 🚧 API 接口文档Swagger 注释已添加,待生成)
- 📋 数据库设计文档
- 📋 部署文档
- 📋 开发规范
- 📋 贡献指南
- ✅ SillyTavern 兼容优化方案(已完成)
---
## 开发时间线
### 2024-02-26
- ✅ 完成用户系统后端 API
- ✅ 完成用户系统前端页面
- ✅ 完成用户系统前后端对接
- ✅ 完成角色卡管理后端 API完全兼容 SillyTavern V2
- ✅ 完成角色卡管理前端页面
- ✅ 完成角色卡管理前后端对接
- ✅ 完成预设管理前端页面
- ✅ 完成对话系统前端基础界面
- ✅ 完成公共页面(首页、角色广场、角色详情)
- ✅ 项目结构重构(前后端分离)
### 2026-02-27
-**完成预设管理后端 API**
- 实现预设 CRUD 操作
- 实现预设导入导出JSON
- 实现默认预设管理
- 完全兼容 SillyTavern 预设格式
-**完成预设管理前后端对接**
-**完成对话系统后端 API**
- 实现对话会话管理
- 实现消息发送接收
- 实现 SSE 流式响应
- 实现对话历史存储
-**完成对话系统前后端对接**
-**完成 AI 配置管理模块**
-**编写 SillyTavern 完全兼容优化方案**
-**更新开发进度文档**
### 下一阶段规划
- ✅ 实现实际 AI API 调用OpenAI/Anthropic
- 📋 实现世界书系统(完全兼容 SillyTavern
- 📋 实现正则脚本系统(完全兼容 SillyTavern
- 📋 实现 Prompt Pipeline世界书触发、正则处理、提示词构建
- 📋 完善对话系统高级功能(消息重新生成、编辑)
- 📋 性能优化与测试
- 📋 部署上线
---
## 下一步计划
### 短期目标(本周)
1. ✅ 完成角色卡管理后端 API已完成
2. ✅ 完成预设管理后端 API已完成
3. ✅ 完成对话系统后端 API已完成
4. ✅ 实现实际 AI API 调用OpenAI/Anthropic
5. ✅ 完善对话系统高级功能(消息重新生成)
### 中期目标(本月)
1. 📋 实现世界书系统(完全兼容 SillyTavern
- 数据模型设计
- 关键词触发算法
- 选择性触发逻辑
- 与对话系统集成
2. 📋 实现正则脚本系统(完全兼容 SillyTavern
- 数据模型设计
- 多阶段脚本执行
- 正则表达式引擎
- 宏替换系统
3. 📋 实现 Prompt Pipeline
- 世界书扫描与注入
- 正则脚本处理
- 提示词构建优化
4. 📋 基础功能测试与优化
### 长期目标(下月)
1. 📋 插件系统设计与实现
2. 📋 性能优化(虚拟滚动、懒加载、缓存)
3. 📋 完善文档API 文档、部署文档)
4. 📋 Docker 部署配置
5. 📋 CI/CD 流程搭建
6. 📋 部署上线
7. 📋 用户反馈收集
---
## 团队协作
### 开发规范
- 代码风格:遵循项目现有风格
- 提交规范:使用 Conventional Commits
- 分支管理feature/* 用于新功能bugfix/* 用于修复
### 沟通渠道
- 技术讨论GitHub Issues
- 进度同步:本文档
- 代码审查Pull Request
---
## 备注
- 本文档持续更新,记录项目开发进度
- 每完成一个功能模块,更新对应状态
- 遇到问题及时记录在"已知问题"部分

356
docs/优化方案.md Normal file
View File

@@ -0,0 +1,356 @@
## SillyTavern 完全兼容优化方案Go + Gin + Postgres + React
> 目标:基于现有 Go + React 系统,构建一个与 SillyTavern下称 ST高度兼容的角色卡 / 世界书 / 正则 / 预设 / 对话平台。
---
### 1. 总体目标与设计原则
- **技术栈统一**所有核心功能角色卡、世界书、正则、预设、聊天、AI 集成)全部收敛到:
- **后端**`server/` 下的 Go + Gin + PostgreSQL
- **前端**`projects/web-app` 下的 React + TypeScript + Tailwind
- **SillyTavern 完全兼容**
- 支持 ST 角色卡 V2/V3chara_card_v2 / v3导入导出
- 支持 ST 世界书字段及触发逻辑keys/secondary_keys/regex/depth/sticky/cooldown/probability/position 等);
- 支持 ST Regex Scripts 配置placement/runOnEdit/markdownOnly/promptOnly/substituteRegex/min/max depth
- 支持 ST 风格预设与 prompt 注入顺序prompt_order / injection depth/position
- **单一真相来源SSOT**
- **数据库即真相**Postgres 负责持久化所有 ST 相关实体;
- **前端只是 UI**React 只做编辑/展示,请求都经过 API不再有“前端内存版预设/世界书”。
- **可扩展性**
- 提前为插件系统预留 HookonUserInput/onWorldInfoScan/beforePromptBuild/onAssistantDone 等);
- 方便接入未来的 WebSocket/SSE 流式聊天、统计系统、市场/分享功能。
---
### 2. 当前 Go + React 系统现状(基于现有文档与代码)
#### 2.1 后端server/
根据 `projects/docs/development-progress.md`
-**用户系统** 已完成2024-02-26
- 模型:`AppUser`, `AppUserSession`
- API`/app/auth/register`, `/app/auth/login`, `/app/auth/refresh`, `/app/auth/logout`, `/app/auth/userinfo`
- JWT、会话、用户资料、密码修改均已实现
-**角色卡管理AICharacter** 已完成:
- 模型:`AICharacter`(完全兼容 ST V2 格式),使用 JSONB 存储 `tags/alternateGreetings/characterBook/extensions` 等复杂结构
- API
- `POST /app/character`
- `GET /app/character`(分页、搜索、标签筛选)
- `GET /app/character/:id`
- `PUT /app/character/:id`
- `DELETE /app/character/:id`
- `POST /app/character/upload`PNG/JSON 角色卡)
- `GET /app/character/:id/export`(导出 JSON
- 工具:`utils/character_card.go`PNG tEXt chunk / ST V2 工具)
- 🚧 **预设管理**
- 前端页面 `/presets` 已完成(导入 JSON、复制、导出、编辑参数
- 后端预设 API 尚未实现(`development-progress.md` 已给出规划端点)。
- 📋 **对话系统 & AI 集成**
- 前端基础 UI 已完成(`/chat` + 侧边栏 + 输入框)。
- 后端对话 API、AI 流式集成、世界书/正则逻辑尚在规划阶段。
#### 2.2 前端(`projects/web-app`
- **用户系统前端**`LoginPage/RegisterPage/ForgotPasswordPage/ProfilePage` 已完成,`api/client.ts + api/auth.ts` 已完成 token 注入与刷新。
- **角色卡管理前端**`CharacterManagePage` + `api/character.ts`
- 功能:
- 上传 PNG/JSON 角色卡(调用 `/app/character/upload`
- 编辑角色卡核心字段、内嵌 `characterBook`(世界书)字段
- 导出 JSON调用 `/app/character/:id/export`
- 搜索、分页、删除
- **预设管理前端**`PresetManagePage`
- 当前为 **前端内存里的假数据** + 文件导入/导出;尚未接入真实后端 `/app/preset`
- **聊天前端**`ChatPage + Sidebar + ChatArea + CharacterPanel + SettingsPanel`
- 已实现基础布局、会话切换、角色选择、背景图/主题色设置等 UI。
- 消息发送、历史加载、预设切换、世界书/正则开关还需后端配合。
- **AI 配置前端**`api/aiConfig.ts`(对应 `/app/ai-config` 系列)。
> 结论:**角色卡链路基本打通**,用户系统成熟;预设/对话/AI/世界书/正则目前主要停留在前端 UI 或规划层面,正好适合作为“集中下沉到 Go 后端”的突破口。
---
### 3. 目标架构SillyTavern 兼容实现)
#### 3.1 核心领域模型(后端)
建议在现有 `model/app` 中引入或规范以下模型(部分已存在,可扩展):
- `AICharacter`(已存在)
- ST 角色卡 V2/V3 的标准化版本 + 原始 JSON 存档。
- `Worldbook` & `WorldbookEntry`
- 支持角色内世界书与全局世界书。
- `RegexScript`
- 支持 scope`global | character | preset`
- `Preset` & `PresetPrompt` & `PresetRegexBinding`
- 表达 ST preset 的参数、prompt 列表和 regex 绑定。
- `Conversation` & `Message`
- 对话与消息记录,支持与 `AICharacter``Preset` 和 AI 配置关联。
#### 3.2 运行时 Pipeline
所有客户端(现阶段只有 React Web将来可增加其他统一通过 Go 后端完成以下流程:
1. **输入正则脚本处理**global/character/preset
2. **世界书扫描**keys/secondary_keys/regex/depth/sticky/cooldown 等)
3. **Prompt 构建**(角色 system + world info + preset prompts + 历史消息)
4. **AI 调用**OpenAI/Anthropic/custom via `/app/ai-config`
5. **输出正则脚本处理**
6. **持久化消息与统计**
React 前端只负责:
- 提供管理与编辑界面;
- 将用户选择的 preset/worldbook/regex 传给后端;
- 使用 SSE/WebSocket 将 AI 流输出展示给用户。
---
### 4. 后端详细优化方案Go + Gin + Postgres
#### 4.1 模型与数据库设计(概念级)
> 不强制你立刻改现有表名与字段;这部分作为“目标状态”,可通过迁移脚本或视图逐步对齐。
- **AICharacter**(已有)
- 新增/规范字段:
- `raw_card_json JSONB`:存原始 ST 角色卡,用于无损导出。
- `bound_worldbook_ids UUID[]`:角色绑定世界书 ID 列表(可选)。
- `bound_regex_ids UUID[]`:角色绑定正则脚本 ID 列表(可选)。
- **Worldbook / WorldbookEntry**
- Worldbook`id, name, owner_char_id (nullable), meta JSONB, created_at, updated_at`
- WorldbookEntry包含 ST 全字段:
- `uid, 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, extra JSONB`
- **RegexScript**
- `id, name, find_regex, replace_with, trim_string`
- `placement INT[]`
- `disabled, markdown_only, run_on_edit, prompt_only`
- `substitute_regex, min_depth, max_depth`
- `scope, owner_char_id, owner_preset_id, raw_json`
- **Preset / PresetPrompt / PresetRegexBinding**
- 基本采样参数 + prompt 列表 + regex 绑定。
- **Conversation / Message**
- Conversation`id, user_id, character_id, preset_id, ai_config_id, title, settings JSONB, created_at, updated_at`
- Message`id, conversation_id, role, content, token_count, created_at`
#### 4.2 角色卡导入/导出(巩固现有实现)
> 从 `development-progress.md` 看,`/app/character` 模块已经“完全兼容 ST V2”这里更多是规范与扩展。
- **导入 `/app/character/upload` 已具备**
- PNG使用 `utils/character_card.go` 提取 `chara` tEXt chunk → JSON。
- JSON直接解析填充 `AICharacter`
- 优化点:
- 当角色卡中包含 `character_book` 时:
- 自动在 `worldbooks/worldbook_entries` 中创建对应记录;
- 将 worldbook.id 写入 `AICharacter.bound_worldbook_ids`(或冗余到 `characterBook` 字段中)。
- 当包含 `extensions.regex_scripts` 时:
- 自动创建 `RegexScript`scope=character, owner_char_id=该角色)。
- 将脚本 id 写入 `AICharacter.bound_regex_ids``extensions` 中。
- **导出 `/app/character/:id/export`**
-`raw_card_json` 存在,优先以它为基础;将 DB 中新增的信息 patch 回去(例如补上最新的 worldbook/regex 变更)。
- 若不存在,则按 ST V2 规范组装 JSON兼容 V1/V3 的 data 字段)。
#### 4.3 世界书World Info引擎
1. **世界书激活来源**
- 全局启用列表per-user 或全局 settings 中的 `active_worldbook_ids`)。
- 角色绑定:`AICharacter.bound_worldbook_ids`
- 会话特定选项:`conversation.settings.activeWorldbookIds`(从前端设置面板传入)。
2. **触发与过滤算法**
- 遍历所有激活 worldbook 的 entries
- 忽略 `disabled` 的 entry。
-`constant=true`:无视关键词匹配,直接进入候选(仍受 sticky/cooldown/delay/probability 控制)。
- keys 匹配:
- `use_regex=true`:将每个 key 作为正则(考虑 `caseSensitive``matchWholeWords` 标志)。
- 否则:普通字符串包含匹配(可选大小写敏感)。
-`selective=true`:根据 `selectiveLogic` 结合 secondary_keys
- `0=AND`secondary_keys 至少一个命中;
- `1=OR`:主 keys 或 secondary_keys 任一命中;
- `2=NOT`:主 keys 命中,但 secondary_keys 不命中。
- depth仅在最近 `entry.depth` 条消息(或 tokens中搜索关键字。
- sticky/cooldown/delay基于会话级状态比如 Redis/内存/DB存储 entry 上次触发时间或 sticky 状态。
- probability按百分比随机决定最终是否注入。
3. **注入到 prompt 中**
- 简化版:将所有命中 entry.content 拼接为 `[World Info]` 段,附加在 system prompt 后;
- 高级版:按 ST 行为,依据 position/depth/order放到不同位置角色描述前/后、历史中等)。
#### 4.4 正则脚本Regex Scripts引擎
1. **脚本来源**
- Global`scope='global'``disabled=false`
- Character`scope='character' AND owner_char_id=当前角色`
- Preset`preset_regex_bindings` 中绑定到当前 preset 的脚本。
2. **执行阶段placement**
- 输入阶段placement=1用户消息入库前
- 输出阶段placement=2模型输出生成后、入库/返回前;
- 世界书阶段placement=3扫描 / 注入 world info 内容前;
- 推理阶段placement=4如有单独 reasoning prompt可在构建 reasoning 时处理。
3. **实现细节Go**
- 解析 JS 风格正则 `/pattern/flags`,支持 `i/m/s`
- 替换宏:
- `substitute_regex=1` → 用当前用户名称替换 `{{user}}`
- `=2` → 用当前角色名替换 `{{char}}`
- `trim_string`:按行定义需要从文本中删掉的子串。
- minDepth/maxDepth结合消息历史深度决定是否执行脚本。
- runOnEdit在“消息编辑接口”中也调用该脚本集。
- markdownOnly/promptOnly通过标志决定作用在 UI 文本 or prompt 文本。
#### 4.5 Prompt Pipeline 与对话 API
将现有/规划中的对话 API 统一收敛到一个 pipeline例如 `/app/conversation/:id/message`(或 `/conversations/:id/messages`,按统一风格即可):
1. **接收请求**
```json
{
"content": "用户输入文本",
"options": {
"presetId": "uuid-xxx",
"overrideModel": "gpt-4",
"stream": true
}
}
```
2. **管线步骤**
1. 根据 conversationId 加载:
- Conversation、AICharacter、Preset、AI 配置、世界书/正则等上下文;
- 合并 settings会话内 + 用户全局)。
2. 输入文本经过 regex 引擎placement=1
3. 写入一条 user message 到 DB。
4. 从最近 N 条消息构造 world info 触发文本,调用世界书引擎得到 entries。
5. 构建 prompt
- system角色 system + scenario + authorsNote + preset.systemPrompt + world info
- history最近若干 user/assistant 消息;
- preset.prompts按 depth/position 注入额外 messages。
6. 调用 AI 服务(依据 `/app/ai-config` 中 baseUrl/apiKey/model
- 支持流式:通过 SSE/WebSocket 对前端推送 tokens。
7. 将完整 assistant 输出经过 regex 引擎placement=2
8. 写入一条 assistant message 到 DB。
9. 返回响应给前端(同步返回最终内容 + 可选流式过程)。
---
### 5. 前端详细优化方案React `projects/web-app`
#### 5.1 角色卡管理CharacterManagePage
目标:完全对齐 ST 角色卡 V2/V3 字段,并为世界书/正则后端拆分打好基础。
优化点:
- **字段映射明确化**
- 确保 `Character` 接口与后端模型一致,并在注释中标明 ST 对应字段:
- `firstMes``first_mes`
- `mesExample``mes_example`
- `systemPrompt``system_prompt`
- `postHistoryInstructions``post_history_instructions`
- `characterBook``character_book`
- `extensions``extensions`
- **世界书编辑 UI 扩展**
- 目前 `WorldBookEntry` 较简化keys/content/enabled/insertion_order/position
- 建议增加更多高级属性编辑项(可以先折叠到“高级设置”中):
- `secondary_keys` / `constant` / `use_regex` / `case_sensitive` / `match_whole_words`
- `selective` / `selective_logic`
- `depth` / `order` / `probability` / `sticky` / `cooldown` / `delay` / `group`
- 保存时将这些字段一并放入 `characterBook.entries`,后端负责拆分入 DB。
- **导入/导出保持 ST 兼容**
- 当前上传/导出流程基本正确;随着后端增强,无需大改,只要保证前端不破坏 JSON 结构即可。
#### 5.2 预设管理PresetManagePage
目标:把当前的“前端内存预设”完全改造成**后端驱动的 ST 预设系统**。
实施步骤:
1. 后端按 `development-progress.md` 规划实现 `/app/preset` API
```text
POST /app/preset
GET /app/preset
GET /app/preset/:id
PUT /app/preset/:id
DELETE /app/preset/:id
POST /app/preset/import
GET /app/preset/:id/export
```
2. 前端新建 `src/api/preset.ts` 对应这些端点。
3.`PresetManagePage` 中的 `useState` 初始数据删除,改为:
- `const { data } = await presetApi.getPresetList()`
- 导入、导出、编辑、删除全部通过后端。
4. 预设 JSON 的读写字段与 ST 对齐:
- `temperature/top_p/top_k/frequency_penalty/presence_penalty/system_prompt/stop_sequences/...`
#### 5.3 聊天与设置面板ChatPage / SettingsPanel / CharacterPanel
目标:把“选 preset / 选世界书 / 选正则”的入口统一放到设置面板,并通过 `conversation.settings` 与后端 pipeline 串联。
建议:
-`SettingsPanel` 中增加:
- 当前会话使用的 preset 选择器(下拉 list来自 `/app/preset`)。
- 可选的世界书列表(来自未来的 `/app/worldbook`,初期可只展示角色内 worldbook
- 可选的全局正则脚本列表(来自未来的 `/app/regex`)。
- 保存时调用 `/app/conversation/:id/settings`(或 `PUT /app/conversation/:id`
```ts
settings: {
backgroundImage: string
themeColor: string
presetId?: string
activeWorldbookIds?: string[]
activeRegexIds?: string[]
}
```
- 发送消息时,前端不再参与世界书/正则逻辑,只负责传 `content`,后端从 conversation/preset/character 中解析所有配置。
---
### 6. 分阶段落地路线图(仅 Go + React
#### 阶段 1打通所有核心 CRUD 与数据流(短期)
- 后端:
- 巩固 `/app/character` 模块(确保 ST V2/V3 完全兼容)。
- 实现 `/app/preset` 模块CRUD + 导入/导出)。
- 设计并实现 `worldbooks/worldbook_entries``regex_scripts` 的数据结构与基础 API。
- 前端:
- 改造 `PresetManagePage` 接入真实 API。
-`CharacterManagePage` 中补全世界书 entry 字段,保持 ST 兼容。
#### 阶段 2实现 Prompt Pipeline 与对话 API中期
- 后端:
- 聚合世界书与正则逻辑,形成独立的 `chatPipeline` service。
-`/app/conversation/:id/message`(或 `/conversations/:id/messages`)中调用 pipeline完成一次“完整的 ST 风格对话请求”。
- 引入流式输出SSE 或 WebSocket
- 前端:
-`ChatPage/SettingsPanel` 中增加 preset/worldbook/regex 的选择与设置保存。
- 调整 ChatArea 接收流式输出并实时渲染。
#### 阶段 3高级特性与插件系统长期
- 后端:
- 引入插件概念(`plugins` 表 + manifest + hooks
- 实现插件执行沙箱WASM 或 goja并在 pipeline 中注入插件 hooks。
- 增加统计、日志与审计功能。
- 前端:
- 增加插件管理页面与可视化配置。
- 对接统计与调试视图(例如:查看某次回复中哪些 world info/regex/插件生效)。
---
### 7. 总结
- 你当前的 Go + React 系统已经完成:
- **用户系统**(认证/资料);
- **角色卡管理**(完整 ST V2 兼容导入/导出);
- **预设管理与对话 UI 的前端骨架**。
- 接下来最重要的三件事是:
- **在后端固化 ST 兼容的领域模型与 Prompt Pipeline**
- **让 `/app/conversation` 成为唯一的“对话真相源”React 只是 UI 壳**。

367
docs/功能模块梳理.md Normal file
View File

@@ -0,0 +1,367 @@
# 云酒馆 - 功能模块梳理
> 本文档梳理了云酒馆SillyTavern Cloud项目的完整功能模块分为**核心功能**(必须与原版酒馆兼容)和**扩展功能**(基于云端多用户场景的增强)两部分。
>
> 标记说明:✅ 已实现 | 🔧 部分实现 | ⬜ 待实现
---
## 一、核心功能模块(酒馆兼容)
### 1. 角色卡系统 (Character Card)
角色卡是酒馆的核心功能,必须完全兼容 SillyTavern V2 (chara_card_v2) 规范。
| 功能点 | 状态 | 说明 |
|--------|------|------|
| 角色卡 CRUD | ✅ | 创建、读取、更新、删除角色卡 |
| V2 规范字段支持 | ✅ | name, description, personality, scenario, first_mes, mes_example, creator_notes, system_prompt, post_history_instructions, tags, alternate_greetings |
| 角色头像 | ✅ | 头像上传与显示 |
| PNG 导入 | ✅ | 从 PNG 图片中提取嵌入的角色卡数据 (tEXt chunk) |
| JSON 导入/导出 | ✅ | 标准 JSON 格式导入导出 |
| PNG 导出 | ⬜ | 将角色卡数据嵌入 PNG 图片导出 |
| 角色书 (Character Book / Lorebook) | 🔧 | 数据模型已支持 characterBook 字段,但前端编辑器待完善 |
| 备选开场白 (Alternate Greetings) | 🔧 | 数据模型已支持前端切换UI待完善 |
| Extensions 扩展字段 | ✅ | JSON 扩展字段,兼容第三方扩展数据 |
| 角色卡搜索/筛选 | ✅ | 关键词搜索、标签筛选 |
| 批量管理 | 🔧 | 批量删除基础功能,批量导入待完善 |
### 2. 对话系统 (Conversation / Chat)
| 功能点 | 状态 | 说明 |
|--------|------|------|
| 创建对话 | ✅ | 选择角色卡后创建对话会话 |
| 对话列表 | ✅ | 按时间排序、分页加载 |
| 消息发送/接收 | ✅ | 用户输入消息,调用 AI API 获取回复 |
| 流式输出 (SSE Streaming) | ✅ | Server-Sent Events 实时流式显示 AI 回复 |
| 消息重新生成 (Regenerate) | ✅ | 重新生成最后一条 AI 回复 |
| 消息编辑 (Edit) | ⬜ | 编辑已发送的消息内容 |
| 消息删除 | 🔧 | 单条删除已支持,批量删除待实现 |
| 消息滑动 (Swipe) | ⬜ | 同一轮生成多个回复,左右滑动切换(酒馆核心交互) |
| 消息分支 (Branching) | ⬜ | 从任意消息节点创建分支对话 |
| 消息续写 (Continue) | ⬜ | 让 AI 继续上一条未完成的回复 |
| 停止生成 (Stop Generation) | ⬜ | 中途停止 AI 生成 |
| 对话历史导入/导出 | ⬜ | 导出为 JSON / JSONL 格式 |
| 对话标题自动生成 | ⬜ | 根据对话内容自动生成标题 |
| Token 计数显示 | 🔧 | 后端已统计,前端显示待完善 |
| 角色卡信息面板 | ✅ | 右侧面板显示当前角色信息 |
| 聊天背景 | ✅ | 支持自定义背景图片 |
| 主题颜色 | ✅ | 支持自定义主题色 |
### 3. AI 接口管理 (AI API Connection)
| 功能点 | 状态 | 说明 |
|--------|------|------|
| OpenAI API 兼容 | ✅ | 支持 OpenAI 及兼容接口(如各类中转站) |
| Anthropic (Claude) API | ✅ | 原生支持 Anthropic API 格式 |
| 自定义 API (Custom) | ✅ | 可配置任意 OpenAI 兼容的 BaseURL |
| API Key 管理 | ✅ | 加密存储,支持多个 API 配置 |
| 模型列表获取 | ✅ | 根据 API 配置动态获取可用模型 |
| 连接测试 | ✅ | 配置后可测试 API 连通性 |
| 多配置切换 | ✅ | 对话级别切换 AI 提供商和模型 |
| Google AI (Gemini) | ⬜ | 原版酒馆支持,待接入 |
| NovelAI | ⬜ | 原版酒馆支持,待接入 |
| Text Completion API | ⬜ | 原版酒馆支持 text completion 模式(非 chat |
| OpenRouter | ⬜ | 可通过 Custom API 实现,但缺少原生 model 路由支持 |
| Reverse Proxy 支持 | ⬜ | 原版酒馆的反向代理功能 |
### 4. 预设系统 (Presets / Sampler Settings)
| 功能点 | 状态 | 说明 |
|--------|------|------|
| 预设 CRUD | ✅ | 创建、编辑、删除预设 |
| 采样参数 | ✅ | temperature, top_p, top_k, frequency_penalty, presence_penalty |
| 高级采样参数 | ✅ | repetition_penalty, min_p, top_a |
| Max Tokens 设置 | ✅ | 最大生成长度限制 |
| 停止序列 (Stop Sequences) | ✅ | 自定义停止序列 |
| 系统提示词 (System Prompt) | ✅ | 预设级别系统提示词 |
| 默认预设 | ✅ | 设置默认预设 |
| 预设导入/导出 | ✅ | JSON 格式导入导出 |
| 指令模板 (Instruct Mode) | ⬜ | 原版酒馆核心功能:不同模型的 prompt 格式模板 (ChatML, Alpaca, Llama 等) |
| 上下文模板 (Context Template) | ⬜ | 控制角色卡各字段如何组装进 prompt |
| Prompt 排序 (Prompt Order) | ⬜ | 自定义 prompt 各部分的排列顺序 |
| Jailbreak Prompt | ⬜ | 越狱提示词NSFW unlock |
| Token Padding | ⬜ | Token 预留/填充策略 |
### 5. 世界信息/知识书 (World Info / Lorebook)
世界信息是酒馆的核心 RAG 系统,用于根据关键词动态注入背景知识。
| 功能点 | 状态 | 说明 |
|--------|------|------|
| 世界信息 CRUD | ⬜ | 创建、编辑、删除世界信息条目 |
| 关键词触发 (Keyword Trigger) | ⬜ | 消息中出现关键词时自动注入对应条目 |
| 正则匹配触发 | ⬜ | 支持正则表达式匹配触发 |
| 条件激活 (Conditional Activation) | ⬜ | 多条件组合激活策略 |
| 递归扫描 (Recursive Scanning) | ⬜ | 已触发条目内的关键词可继续触发其他条目 |
| 插入位置控制 | ⬜ | 控制条目注入到 prompt 的位置depth |
| 概率触发 | ⬜ | 条目以一定概率触发 |
| Token 预算 | ⬜ | 限制世界信息总 token 消耗 |
| 世界信息导入/导出 | ⬜ | 标准 JSON 格式 |
| 角色绑定 | ⬜ | 世界信息与角色卡绑定 |
| 全局世界信息 | ⬜ | 跨角色生效的全局世界信息 |
### 6. 群聊系统 (Group Chat)
| 功能点 | 状态 | 说明 |
|--------|------|------|
| 创建群聊 | ⬜ | 多个 AI 角色参与的群聊 |
| 角色轮转策略 | ⬜ | 自动/手动选择下一个发言角色 |
| 角色激活/禁用 | ⬜ | 动态控制群聊中角色是否参与 |
| 群聊设置 | ⬜ | 发言顺序、回复长度、触发条件等 |
| 群组角色管理 | ⬜ | 添加/移除群聊角色 |
### 7. 用户人设 (User Persona)
| 功能点 | 状态 | 说明 |
|--------|------|------|
| 人设创建/管理 | ⬜ | 创建多个用户人设档案 |
| 人设切换 | ⬜ | 在不同对话中使用不同人设 |
| 人设描述 | ⬜ | 用户角色的名称和描述,注入 prompt |
| 人设头像 | ⬜ | 用户人设的头像 |
| 默认人设 | ⬜ | 设置默认用户人设 |
---
## 二、Prompt 工程模块(酒馆兼容)
### 8. Prompt 构建系统
酒馆的 prompt 构建是其核心竞争力,必须完整兼容。
| 功能点 | 状态 | 说明 |
|--------|------|------|
| 主系统提示词 (Main System Prompt) | 🔧 | 预设中已支持,独立管理待完善 |
| NSFW 提示词 | ⬜ | NSFW 场景的系统提示词 |
| 角色卡提示词注入 | 🔧 | 基础注入已实现,完整排序策略待完善 |
| 作者注 (Author's Note) | ⬜ | 可在对话中途插入的引导提示 |
| 作者注深度控制 | ⬜ | 控制作者注在 prompt 中的插入位置 |
| 聊天历史截断 | ⬜ | 超出 token 限制时的截断策略 |
| 消息摘要 (Summarization) | ⬜ | 旧消息自动摘要压缩 |
| 自定义提示词排序 | ⬜ | 用户自定义 prompt 各部分的顺序 |
| 宏替换 (Macro Replacement) | ⬜ | `{{char}}`, `{{user}}`, `{{time}}` 等宏变量替换 |
---
## 三、用户系统模块(云端增强)
### 9. 认证与账户
| 功能点 | 状态 | 说明 |
|--------|------|------|
| 用户注册 | ✅ | 用户名 + 密码注册 |
| 用户登录 | ✅ | JWT Token 认证 |
| Token 刷新 | ✅ | 自动刷新过期 Token |
| 登出 | ✅ | 安全登出,清除会话 |
| 个人资料管理 | ✅ | 昵称、邮箱、手机、头像 |
| 修改密码 | ✅ | 旧密码验证后修改 |
| 会话管理 | ✅ | 记录登录 IP、设备信息 |
| 第三方登录 (OAuth) | ⬜ | GitHub、Google 登录UI 已预留) |
| 邮箱验证 | ⬜ | 注册邮箱验证 |
| 找回密码 | ⬜ | 通过邮箱找回密码 |
---
## 四、UI/UX 模块
### 10. 界面与交互
| 功能点 | 状态 | 说明 |
|--------|------|------|
| 深色主题 | ✅ | 默认深色主题OLED 优化 |
| Glassmorphism 设计风格 | ✅ | 毛玻璃效果 |
| 响应式布局 | ✅ | 适配桌面端和移动端 |
| 三栏聊天布局 | ✅ | 侧边栏 + 聊天区 + 角色面板 |
| 消息 Markdown 渲染 | 🔧 | 基础 Markdown 已支持,需增强 |
| 消息代码高亮 | ⬜ | 代码块语法高亮 |
| 聊天背景切换 | ✅ | 自定义上传背景图 |
| 预设背景图库 | ⬜ | 原版酒馆自带的背景图集 |
| 主题切换 (亮色/暗色) | ⬜ | 目前仅暗色主题 |
| 自定义 CSS | ⬜ | 原版酒馆支持用户自定义 CSS |
| 快捷键支持 | ⬜ | 发送消息、新建对话等快捷键 |
| 拖拽排序 | ⬜ | 对话列表、角色排序等拖拽操作 |
| 消息字数/Token 统计面板 | ⬜ | 实时显示 token 用量 |
---
## 五、数据管理模块
### 11. 数据导入导出
| 功能点 | 状态 | 说明 |
|--------|------|------|
| 角色卡 PNG 导入 | ✅ | 从 PNG 提取角色数据 |
| 角色卡 JSON 导入/导出 | ✅ | 标准 JSON 格式 |
| 角色卡 PNG 导出 | ⬜ | 将角色数据嵌入 PNG 导出 |
| 预设 JSON 导入/导出 | ✅ | 预设导入导出 |
| 对话历史导出 | ⬜ | 导出聊天记录 |
| 世界信息导入/导出 | ⬜ | Lorebook 导入导出 |
| CharacterHub 一键导入 | ⬜ | 从 chub.ai 直接导入角色 |
| 完整数据备份/恢复 | ⬜ | 用户全量数据导出与恢复 |
---
## 六、扩展功能模块(未来规划)
以下功能基于云端多用户场景,以及现代 AI 应用趋势进行规划。
### 12. 角色市场 (Character Market)
| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| 公开角色浏览 | 🔧 高 | 已有基础页面,需完善筛选和分类 |
| 角色评分/评价 | ⬜ 高 | 用户对角色卡进行评分和评论 |
| 角色收藏 | ⬜ 高 | 收藏喜欢的角色卡 |
| 角色分类/标签 | 🔧 中 | 完善标签系统和分类导航 |
| 热门/推荐排行 | ⬜ 中 | 按使用量、收藏量、评分排序 |
| 角色创作者主页 | ⬜ 低 | 创作者的个人页面,展示其角色 |
| 角色版本管理 | ⬜ 低 | 角色卡的版本历史和回滚 |
### 13. 社区功能
| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| 预设分享 | ⬜ 中 | 用户分享自己的预设配置 |
| 世界信息分享 | ⬜ 中 | 分享 Lorebook |
| 精彩对话分享 | ⬜ 低 | 分享有趣的对话片段 |
| 社区讨论 | ⬜ 低 | 角色卡下的评论区 |
### 14. 高级 AI 功能
| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| 多模态支持 (Vision) | ⬜ 高 | 发送图片给支持视觉的模型 |
| TTS 语音合成 | ⬜ 中 | AI 回复转语音播放 |
| STT 语音输入 | ⬜ 中 | 语音转文字输入 |
| AI 图片生成 | ⬜ 中 | 集成 Stable Diffusion / DALL-E 在聊天中生成图片 |
| 向量数据库 (RAG) | ⬜ 中 | 更高级的知识检索增强生成 |
| 长期记忆 (Long-term Memory) | ⬜ 中 | 跨对话的角色记忆系统 |
| Function Calling / Tool Use | ⬜ 低 | AI 调用工具能力(天气、搜索等) |
| 多模型混合 (Model Routing) | ⬜ 低 | 不同场景自动选择合适模型 |
### 15. 插件/扩展系统
| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| 插件接口定义 | ⬜ 中 | 标准化的插件 API |
| JS-Slash-Runner 兼容 | ⬜ 中 | 兼容原版酒馆的 slash 命令脚本 |
| 自定义 Regex 脚本 | ⬜ 中 | 正则替换脚本(对 AI 输出进行后处理) |
| 扩展商店 | ⬜ 低 | 在线安装/管理扩展 |
### 16. 用户积分系统 (Credits System)
云端多用户场景下的积分经济体系,用于激励用户参与和控制资源消耗。
| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| **积分消耗** | | |
| 对话 Token 扣费 | ⬜ 高 | 根据 AI 调用的 Token 数量实时扣除积分 |
| 分级计费策略 | ⬜ 高 | 不同模型GPT-4、Claude、Gemini不同费率 |
| 余额不足提醒 | ⬜ 高 | 积分低于阈值时提醒用户充值 |
| 消费记录查询 | ⬜ 中 | 详细的积分消费明细时间、对话、Token、扣费 |
| 每日免费额度 | ⬜ 中 | 每日赠送基础免费 Token 额度 |
| **积分获取** | | |
| 每日签到 | ⬜ 高 | 连续签到获得积分奖励,断签重置 |
| 上传角色卡奖励 | ⬜ 高 | 上传公开角色卡获得积分(审核通过后发放) |
| 角色卡热度奖励 | ⬜ 中 | 角色被收藏/使用达到阈值时额外奖励 |
| 邀请好友 | ⬜ 中 | 邀请新用户注册获得积分 |
| 分享对话 | ⬜ 低 | 分享精彩对话片段获得积分 |
| 社区贡献 | ⬜ 低 | 评论、评分、反馈 Bug 等获得积分 |
| **积分管理** | | |
| 积分余额显示 | ⬜ 高 | 顶部导航栏实时显示当前积分 |
| 积分充值 | ⬜ 高 | 支持在线支付充值积分 |
| 积分套餐 | ⬜ 中 | 不同价格档位的积分包(充值越多单价越低) |
| 积分赠送 | ⬜ 低 | 用户之间转赠积分 |
| 积分有效期 | ⬜ 低 | 充值积分永久有效,赠送积分有效期限制 |
| **数据统计** | | |
| 积分流水账单 | ⬜ 中 | 收入/支出明细,支持导出 |
| Token 消耗统计 | ⬜ 中 | 按时间、模型、角色维度统计 Token 用量 |
| 积分排行榜 | ⬜ 低 | 展示积分最高的用户(可选匿名) |
### 17. 管理后台
| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| 用户管理 | ⬜ 高 | 管理员管理所有用户 |
| 角色审核 | ⬜ 高 | 公开角色的审核机制 |
| 系统配置 | ⬜ 中 | 全局系统参数配置 |
| 数据统计仪表盘 | ⬜ 中 | 用户量、消息量、API 调用量等统计 |
| API 用量监控 | ⬜ 中 | Token 消耗监控和限额管理 |
| 积分系统管理 | ⬜ 中 | 积分费率配置、手动调整用户积分、积分流水监控 |
| 日志管理 | ⬜ 低 | 操作日志和错误日志 |
| 公告系统 | ⬜ 低 | 向用户发布系统公告 |
### 17. 性能与体验优化
| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| 消息虚拟滚动 | ⬜ 高 | 大量消息时的渲染性能优化 |
| 离线缓存 (PWA) | ⬜ 中 | 支持离线访问已缓存的对话 |
| WebSocket 实时通信 | ⬜ 中 | 替代 SSE 的双向通信方案 |
| CDN 加速 | ⬜ 中 | 静态资源 CDN 分发 |
| 图片压缩/懒加载 | ⬜ 中 | 角色头像等图片优化 |
| 国际化 (i18n) | ⬜ 低 | 多语言支持 |
---
## 七、开发优先级建议
### 第一阶段核心兼容P0 - 当务之急)
> 确保与原版酒馆的核心功能对齐
1. **消息滑动 (Swipe)** - 酒馆标志性交互,用户期望最高
2. **世界信息/Lorebook** - 酒馆 RAG 核心,角色扮演深度的关键
3. **指令模板 (Instruct Mode)** - 不同模型的 prompt 格式适配
4. **上下文模板 (Context Template)** - 角色卡信息注入方式
5. **宏替换系统** - `{{char}}`, `{{user}}` 等变量替换
6. **用户人设 (Persona)** - 用户角色设定
7. **消息编辑** - 修改已发送消息
8. **消息续写 (Continue)** - 继续未完成的回复
9. **停止生成** - 中断 AI 生成
### 第二阶段功能完善P1
1. 群聊系统
2. 作者注 (Author's Note)
3. 自定义 Prompt 排序
4. 消息摘要/压缩
5. 角色卡 PNG 导出
6. 对话历史导出
7. 消息分支
8. Token 统计面板
### 第三阶段云端增强P2
1. 角色市场完善(评分、收藏、排行)
2. 管理后台
3. 多模态支持 (Vision)
4. 预设/世界信息分享
5. TTS/STT 语音功能
6. 完整数据备份/恢复
### 第四阶段生态扩展P3
1. 插件/扩展系统
2. AI 图片生成集成
3. 长期记忆系统
4. 社区功能
5. 国际化
6. PWA 离线支持
---
## 八、与原版酒馆的架构差异
| 维度 | 原版 SillyTavern | 云酒馆 |
|------|------------------|--------|
| 部署模式 | 本地单用户 | 云端多用户 |
| 前端 | jQuery + 原生 JS | React + TypeScript |
| 后端 | Node.js + Express | Go + Gin |
| 数据库 | 文件系统 (JSON/PNG) | PostgreSQL + GORM |
| 认证 | 简单密码/无认证 | JWT + 会话管理 |
| AI 调用 | 前端直连 / 后端代理 | 后端统一代理 |
| 数据存储 | 本地文件 | 数据库 + 对象存储 |
| 扩展机制 | iframe + 文件注入 | 插件 API (规划中) |
> **核心原则**:功能上完全兼容原版酒馆的角色卡规范和核心交互体验,架构上利用云端优势进行增强。

View File

@@ -0,0 +1,167 @@
package app
import (
"net/http"
"strconv"
"git.echol.cn/loser/st/server/global"
"git.echol.cn/loser/st/server/model/app/request"
"git.echol.cn/loser/st/server/model/common"
commonResponse "git.echol.cn/loser/st/server/model/common/response"
"git.echol.cn/loser/st/server/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type RegexScriptApi struct{}
// CreateRegexScript
// @Tags AppRegexScript
// @Summary 创建正则脚本
// @Router /app/regex [post]
// @Security ApiKeyAuth
func (a *RegexScriptApi) CreateRegexScript(c *gin.Context) {
userID := common.GetAppUserID(c)
var req request.CreateRegexScriptRequest
if err := c.ShouldBindJSON(&req); err != nil {
commonResponse.FailWithMessage(err.Error(), c)
return
}
resp, err := service.ServiceGroupApp.AppServiceGroup.RegexScriptService.CreateRegexScript(userID, &req)
if err != nil {
global.GVA_LOG.Error("创建正则脚本失败", zap.Error(err))
commonResponse.FailWithMessage(err.Error(), c)
return
}
commonResponse.OkWithData(resp, c)
}
// GetRegexScriptList
// @Tags AppRegexScript
// @Summary 获取正则脚本列表
// @Router /app/regex [get]
// @Security ApiKeyAuth
func (a *RegexScriptApi) GetRegexScriptList(c *gin.Context) {
userID := common.GetAppUserID(c)
var req request.GetRegexScriptListRequest
req.Page, _ = strconv.Atoi(c.DefaultQuery("page", "1"))
req.PageSize, _ = strconv.Atoi(c.DefaultQuery("pageSize", "20"))
req.Keyword = c.Query("keyword")
if scope := c.Query("scope"); scope != "" {
scopeInt, _ := strconv.Atoi(scope)
req.Scope = &scopeInt
}
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 || req.PageSize > 100 {
req.PageSize = 20
}
list, total, err := service.ServiceGroupApp.AppServiceGroup.RegexScriptService.GetRegexScriptList(userID, &req)
if err != nil {
global.GVA_LOG.Error("获取正则脚本列表失败", zap.Error(err))
commonResponse.FailWithMessage(err.Error(), c)
return
}
commonResponse.OkWithDetailed(commonResponse.PageResult{
List: list,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
}, "获取成功", c)
}
// GetRegexScriptByID
// @Tags AppRegexScript
// @Summary 获取正则脚本详情
// @Router /app/regex/:id [get]
// @Security ApiKeyAuth
func (a *RegexScriptApi) GetRegexScriptByID(c *gin.Context) {
userID := common.GetAppUserID(c)
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
commonResponse.FailWithMessage("无效的脚本ID", c)
return
}
resp, err := service.ServiceGroupApp.AppServiceGroup.RegexScriptService.GetRegexScriptByID(userID, uint(id))
if err != nil {
global.GVA_LOG.Error("获取正则脚本失败", zap.Error(err))
commonResponse.FailWithMessage(err.Error(), c)
return
}
commonResponse.OkWithData(resp, c)
}
// UpdateRegexScript
// @Tags AppRegexScript
// @Summary 更新正则脚本
// @Router /app/regex/:id [put]
// @Security ApiKeyAuth
func (a *RegexScriptApi) UpdateRegexScript(c *gin.Context) {
userID := common.GetAppUserID(c)
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
commonResponse.FailWithMessage("无效的脚本ID", c)
return
}
var req request.UpdateRegexScriptRequest
if err := c.ShouldBindJSON(&req); err != nil {
commonResponse.FailWithMessage(err.Error(), c)
return
}
if err := service.ServiceGroupApp.AppServiceGroup.RegexScriptService.UpdateRegexScript(userID, uint(id), &req); err != nil {
global.GVA_LOG.Error("更新正则脚本失败", zap.Error(err))
commonResponse.FailWithMessage(err.Error(), c)
return
}
commonResponse.OkWithMessage("更新成功", c)
}
// DeleteRegexScript
// @Tags AppRegexScript
// @Summary 删除正则脚本
// @Router /app/regex/:id [delete]
// @Security ApiKeyAuth
func (a *RegexScriptApi) DeleteRegexScript(c *gin.Context) {
userID := common.GetAppUserID(c)
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
commonResponse.FailWithMessage("无效的脚本ID", c)
return
}
if err := service.ServiceGroupApp.AppServiceGroup.RegexScriptService.DeleteRegexScript(userID, uint(id)); err != nil {
global.GVA_LOG.Error("删除正则脚本失败", zap.Error(err))
commonResponse.FailWithMessage(err.Error(), c)
return
}
commonResponse.OkWithMessage("删除成功", c)
}
// TestRegexScript
// @Tags AppRegexScript
// @Summary 测试正则脚本
// @Router /app/regex/:id/test [post]
// @Security ApiKeyAuth
func (a *RegexScriptApi) TestRegexScript(c *gin.Context) {
userID := common.GetAppUserID(c)
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
commonResponse.FailWithMessage("无效的脚本ID", c)
return
}
var req request.TestRegexScriptRequest
if err := c.ShouldBindJSON(&req); err != nil {
commonResponse.FailWithMessage(err.Error(), c)
return
}
resp, err := service.ServiceGroupApp.AppServiceGroup.RegexScriptService.TestRegexScript(userID, uint(id), req.TestString)
if err != nil {
global.GVA_LOG.Error("测试正则脚本失败", zap.Error(err))
commonResponse.FailWithMessage(err.Error(), c)
return
}
c.JSON(http.StatusOK, resp)
}

9314
server/docs/docs.go Normal file

File diff suppressed because it is too large Load Diff

9286
server/docs/swagger.json Normal file

File diff suppressed because it is too large Load Diff

5677
server/docs/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
package app
import (
"time"
"gorm.io/datatypes"
"gorm.io/gorm"
)
// RegexScript 正则脚本模型(完全兼容 SillyTavern 格式)
type RegexScript struct {
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
UserID uint `gorm:"index;not null" json:"userId"`
Name string `gorm:"type:varchar(100);not null" json:"name"` // 脚本名称
// 正则表达式
FindRegex string `gorm:"type:text;not null" json:"findRegex"` // 查找的正则表达式
ReplaceWith string `gorm:"type:text" json:"replaceWith"` // 替换的内容
TrimStrings datatypes.JSON `gorm:"type:jsonb" json:"trimStrings"` // 要修剪的字符串列表 []string
// 执行阶段
// 0=输入(input), 1=输出(output), 2=世界书(world_info), 3=推理(display)
Placement int `gorm:"default:1" json:"placement"`
// 执行选项
Disabled bool `gorm:"default:false" json:"disabled"` // 是否禁用
MarkdownOnly bool `gorm:"default:false" json:"markdownOnly"` // 仅在 Markdown 模式下执行
RunOnEdit bool `gorm:"default:false" json:"runOnEdit"` // 编辑消息时执行
PromptOnly bool `gorm:"default:false" json:"promptOnly"` // 仅在 prompt 中执行
// 宏替换
SubstituteRegex bool `gorm:"default:true" json:"substituteRegex"` // 是否替换宏变量 {{user}}/{{char}}
// 深度控制
MinDepth *int `gorm:"type:int" json:"minDepth"` // 最小深度null 表示不限制)
MaxDepth *int `gorm:"type:int" json:"maxDepth"` // 最大深度null 表示不限制)
// 作用域
// 0=全局(global), 1=角色(character), 2=预设(preset)
Scope int `gorm:"default:0" json:"scope"`
OwnerCharID *uint `gorm:"type:int" json:"ownerCharId"` // 角色IDscope=1时有效
OwnerPresetID *uint `gorm:"type:int" json:"ownerPresetId"` // 预设IDscope=2时有效
// 执行顺序
Order int `gorm:"default:100" json:"order"` // 执行顺序,数字越小越先执行
// 扩展字段
Extensions datatypes.JSON `gorm:"type:jsonb" json:"extensions"`
}
func (RegexScript) TableName() string {
return "regex_scripts"
}

View File

@@ -0,0 +1,54 @@
package request
// CreateRegexScriptRequest 创建正则脚本请求
type CreateRegexScriptRequest struct {
Name string `json:"name" binding:"required,max=100"`
FindRegex string `json:"findRegex" binding:"required"`
ReplaceWith string `json:"replaceWith"`
TrimStrings []string `json:"trimStrings"`
Placement int `json:"placement"`
Disabled bool `json:"disabled"`
MarkdownOnly bool `json:"markdownOnly"`
RunOnEdit bool `json:"runOnEdit"`
PromptOnly bool `json:"promptOnly"`
SubstituteRegex bool `json:"substituteRegex"`
MinDepth *int `json:"minDepth"`
MaxDepth *int `json:"maxDepth"`
Scope int `json:"scope"`
OwnerCharID *uint `json:"ownerCharId"`
OwnerPresetID *uint `json:"ownerPresetId"`
Order int `json:"order"`
}
// UpdateRegexScriptRequest 更新正则脚本请求
type UpdateRegexScriptRequest struct {
Name *string `json:"name" binding:"omitempty,max=100"`
FindRegex *string `json:"findRegex"`
ReplaceWith *string `json:"replaceWith"`
TrimStrings []string `json:"trimStrings"`
Placement *int `json:"placement"`
Disabled *bool `json:"disabled"`
MarkdownOnly *bool `json:"markdownOnly"`
RunOnEdit *bool `json:"runOnEdit"`
PromptOnly *bool `json:"promptOnly"`
SubstituteRegex *bool `json:"substituteRegex"`
MinDepth *int `json:"minDepth"`
MaxDepth *int `json:"maxDepth"`
Scope *int `json:"scope"`
OwnerCharID *uint `json:"ownerCharId"`
OwnerPresetID *uint `json:"ownerPresetId"`
Order *int `json:"order"`
}
// GetRegexScriptListRequest 获取正则脚本列表请求
type GetRegexScriptListRequest struct {
Page int `json:"page"`
PageSize int `json:"pageSize"`
Keyword string `json:"keyword"`
Scope *int `json:"scope"`
}
// TestRegexScriptRequest 测试正则脚本请求
type TestRegexScriptRequest struct {
TestString string `json:"testString" binding:"required"`
}

View File

@@ -0,0 +1,78 @@
package response
import (
"encoding/json"
"time"
"git.echol.cn/loser/st/server/model/app"
)
// RegexScriptResponse 正则脚本响应
type RegexScriptResponse struct {
ID uint `json:"id"`
UserID uint `json:"userId"`
Name string `json:"name"`
FindRegex string `json:"findRegex"`
ReplaceWith string `json:"replaceWith"`
TrimStrings []string `json:"trimStrings"`
Placement int `json:"placement"`
Disabled bool `json:"disabled"`
MarkdownOnly bool `json:"markdownOnly"`
RunOnEdit bool `json:"runOnEdit"`
PromptOnly bool `json:"promptOnly"`
SubstituteRegex bool `json:"substituteRegex"`
MinDepth *int `json:"minDepth"`
MaxDepth *int `json:"maxDepth"`
Scope int `json:"scope"`
OwnerCharID *uint `json:"ownerCharId"`
OwnerPresetID *uint `json:"ownerPresetId"`
Order int `json:"order"`
Extensions map[string]interface{} `json:"extensions"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// TestRegexScriptResponse 测试正则脚本响应
type TestRegexScriptResponse struct {
Original string `json:"original"`
Result string `json:"result"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
}
// ToRegexScriptResponse 转换为正则脚本响应结构
func ToRegexScriptResponse(script *app.RegexScript) RegexScriptResponse {
var trimStrings []string
if len(script.TrimStrings) > 0 {
json.Unmarshal(script.TrimStrings, &trimStrings)
}
var extensions map[string]interface{}
if len(script.Extensions) > 0 {
json.Unmarshal(script.Extensions, &extensions)
}
return RegexScriptResponse{
ID: script.ID,
UserID: script.UserID,
Name: script.Name,
FindRegex: script.FindRegex,
ReplaceWith: script.ReplaceWith,
TrimStrings: trimStrings,
Placement: script.Placement,
Disabled: script.Disabled,
MarkdownOnly: script.MarkdownOnly,
RunOnEdit: script.RunOnEdit,
PromptOnly: script.PromptOnly,
SubstituteRegex: script.SubstituteRegex,
MinDepth: script.MinDepth,
MaxDepth: script.MaxDepth,
Scope: script.Scope,
OwnerCharID: script.OwnerCharID,
OwnerPresetID: script.OwnerPresetID,
Order: script.Order,
Extensions: extensions,
CreatedAt: script.CreatedAt,
UpdatedAt: script.UpdatedAt,
}
}

View File

@@ -0,0 +1,24 @@
package app
import (
v1 "git.echol.cn/loser/st/server/api/v1"
"git.echol.cn/loser/st/server/middleware"
"github.com/gin-gonic/gin"
)
type RegexScriptRouter struct{}
// InitRegexScriptRouter 初始化正则脚本路由
func (r *RegexScriptRouter) InitRegexScriptRouter(Router *gin.RouterGroup) {
regexRouter := Router.Group("regex").Use(middleware.AppJWTAuth())
regexApi := v1.ApiGroupApp.AppApiGroup.RegexScriptApi
{
regexRouter.POST("", regexApi.CreateRegexScript) // 创建正则脚本
regexRouter.GET("", regexApi.GetRegexScriptList) // 获取正则脚本列表
regexRouter.GET(":id", regexApi.GetRegexScriptByID) // 获取正则脚本详情
regexRouter.PUT(":id", regexApi.UpdateRegexScript) // 更新正则脚本
regexRouter.DELETE(":id", regexApi.DeleteRegexScript) // 删除正则脚本
regexRouter.POST(":id/test", regexApi.TestRegexScript) // 测试正则脚本
}
}

View File

@@ -0,0 +1,285 @@
package app
import (
"encoding/json"
"errors"
"regexp"
"strings"
"git.echol.cn/loser/st/server/global"
"git.echol.cn/loser/st/server/model/app"
"git.echol.cn/loser/st/server/model/app/request"
"git.echol.cn/loser/st/server/model/app/response"
"go.uber.org/zap"
"gorm.io/datatypes"
"gorm.io/gorm"
)
type RegexScriptService struct{}
// CreateRegexScript 创建正则脚本
func (s *RegexScriptService) CreateRegexScript(userID uint, req *request.CreateRegexScriptRequest) (*response.RegexScriptResponse, error) {
trimStringsJSON, _ := json.Marshal(req.TrimStrings)
script := &app.RegexScript{
UserID: userID,
Name: req.Name,
FindRegex: req.FindRegex,
ReplaceWith: req.ReplaceWith,
TrimStrings: datatypes.JSON(trimStringsJSON),
Placement: req.Placement,
Disabled: req.Disabled,
MarkdownOnly: req.MarkdownOnly,
RunOnEdit: req.RunOnEdit,
PromptOnly: req.PromptOnly,
SubstituteRegex: req.SubstituteRegex,
MinDepth: req.MinDepth,
MaxDepth: req.MaxDepth,
Scope: req.Scope,
OwnerCharID: req.OwnerCharID,
OwnerPresetID: req.OwnerPresetID,
Order: req.Order,
}
if err := global.GVA_DB.Create(script).Error; err != nil {
global.GVA_LOG.Error("创建正则脚本失败", zap.Error(err))
return nil, err
}
resp := response.ToRegexScriptResponse(script)
return &resp, nil
}
// GetRegexScriptList 获取正则脚本列表
func (s *RegexScriptService) GetRegexScriptList(userID uint, req *request.GetRegexScriptListRequest) ([]response.RegexScriptResponse, int64, error) {
var scripts []app.RegexScript
var total int64
db := global.GVA_DB.Model(&app.RegexScript{}).Where("user_id = ?", userID)
if req.Keyword != "" {
db = db.Where("name LIKE ?", "%"+req.Keyword+"%")
}
if req.Scope != nil {
db = db.Where("scope = ?", *req.Scope)
}
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
offset := (req.Page - 1) * req.PageSize
if err := db.Order("\"order\" ASC, created_at DESC").Offset(offset).Limit(req.PageSize).Find(&scripts).Error; err != nil {
global.GVA_LOG.Error("获取正则脚本列表失败", zap.Error(err))
return nil, 0, err
}
var list []response.RegexScriptResponse
for i := range scripts {
list = append(list, response.ToRegexScriptResponse(&scripts[i]))
}
return list, total, nil
}
// GetRegexScriptByID 获取正则脚本详情
func (s *RegexScriptService) GetRegexScriptByID(userID uint, id uint) (*response.RegexScriptResponse, error) {
var script app.RegexScript
if err := global.GVA_DB.Where("id = ? AND user_id = ?", id, userID).First(&script).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("正则脚本不存在或无权访问")
}
return nil, err
}
resp := response.ToRegexScriptResponse(&script)
return &resp, nil
}
// UpdateRegexScript 更新正则脚本
func (s *RegexScriptService) UpdateRegexScript(userID uint, id uint, req *request.UpdateRegexScriptRequest) error {
var script app.RegexScript
if err := global.GVA_DB.Where("id = ? AND user_id = ?", id, userID).First(&script).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("正则脚本不存在或无权修改")
}
return err
}
updates := make(map[string]interface{})
if req.Name != nil {
updates["name"] = *req.Name
}
if req.FindRegex != nil {
updates["find_regex"] = *req.FindRegex
}
if req.ReplaceWith != nil {
updates["replace_with"] = *req.ReplaceWith
}
if req.TrimStrings != nil {
trimStringsJSON, _ := json.Marshal(req.TrimStrings)
updates["trim_strings"] = datatypes.JSON(trimStringsJSON)
}
if req.Placement != nil {
updates["placement"] = *req.Placement
}
if req.Disabled != nil {
updates["disabled"] = *req.Disabled
}
if req.MarkdownOnly != nil {
updates["markdown_only"] = *req.MarkdownOnly
}
if req.RunOnEdit != nil {
updates["run_on_edit"] = *req.RunOnEdit
}
if req.PromptOnly != nil {
updates["prompt_only"] = *req.PromptOnly
}
if req.SubstituteRegex != nil {
updates["substitute_regex"] = *req.SubstituteRegex
}
if req.MinDepth != nil {
updates["min_depth"] = req.MinDepth
}
if req.MaxDepth != nil {
updates["max_depth"] = req.MaxDepth
}
if req.Scope != nil {
updates["scope"] = *req.Scope
}
if req.OwnerCharID != nil {
updates["owner_char_id"] = req.OwnerCharID
}
if req.OwnerPresetID != nil {
updates["owner_preset_id"] = req.OwnerPresetID
}
if req.Order != nil {
updates["order"] = *req.Order
}
return global.GVA_DB.Model(&script).Updates(updates).Error
}
// DeleteRegexScript 删除正则脚本
func (s *RegexScriptService) DeleteRegexScript(userID uint, id uint) error {
result := global.GVA_DB.Where("id = ? AND user_id = ?", id, userID).Delete(&app.RegexScript{})
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("正则脚本不存在或无权删除")
}
return nil
}
// TestRegexScript 测试正则脚本
func (s *RegexScriptService) TestRegexScript(userID uint, id uint, testString string) (*response.TestRegexScriptResponse, error) {
var script app.RegexScript
if err := global.GVA_DB.Where("id = ? AND user_id = ?", id, userID).First(&script).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("正则脚本不存在或无权访问")
}
return nil, err
}
result, err := s.ExecuteScript(&script, testString, "", "")
if err != nil {
return &response.TestRegexScriptResponse{
Original: testString,
Result: testString,
Success: false,
Error: err.Error(),
}, nil
}
return &response.TestRegexScriptResponse{
Original: testString,
Result: result,
Success: true,
}, nil
}
// ExecuteScript 执行正则脚本
func (s *RegexScriptService) ExecuteScript(script *app.RegexScript, text string, userName string, charName string) (string, error) {
if script.Disabled {
return text, nil
}
result := text
// 1. 宏替换
if script.SubstituteRegex {
result = s.substituteMacros(result, userName, charName)
}
// 2. 正则替换
if script.FindRegex != "" {
re, err := regexp.Compile(script.FindRegex)
if err != nil {
global.GVA_LOG.Warn("正则表达式编译失败", zap.String("pattern", script.FindRegex), zap.Error(err))
return text, err
}
result = re.ReplaceAllString(result, script.ReplaceWith)
}
// 3. 修剪字符串
if len(script.TrimStrings) > 0 {
var trimStrings []string
json.Unmarshal(script.TrimStrings, &trimStrings)
for _, trimStr := range trimStrings {
result = strings.ReplaceAll(result, trimStr, "")
}
}
return result, nil
}
// substituteMacros 替换宏变量
func (s *RegexScriptService) substituteMacros(text string, userName string, charName string) string {
result := text
if userName != "" {
result = strings.ReplaceAll(result, "{{user}}", userName)
result = strings.ReplaceAll(result, "{{User}}", userName)
}
if charName != "" {
result = strings.ReplaceAll(result, "{{char}}", charName)
result = strings.ReplaceAll(result, "{{Char}}", charName)
}
return result
}
// GetScriptsForPlacement 获取指定阶段的脚本
func (s *RegexScriptService) GetScriptsForPlacement(userID uint, placement int, charID *uint, presetID *uint) ([]app.RegexScript, error) {
var scripts []app.RegexScript
db := global.GVA_DB.Where("user_id = ? AND placement = ? AND disabled = ?", userID, placement, false)
// 作用域过滤:全局(0) 或 角色(1) 或 预设(2)
scopeCondition := "scope = 0" // 全局
if charID != nil {
scopeCondition += " OR (scope = 1 AND owner_char_id = " + string(rune(*charID)) + ")"
}
if presetID != nil {
scopeCondition += " OR (scope = 2 AND owner_preset_id = " + string(rune(*presetID)) + ")"
}
db = db.Where(scopeCondition)
if err := db.Order("\"order\" ASC").Find(&scripts).Error; err != nil {
return nil, err
}
return scripts, nil
}
// ExecuteScripts 批量执行脚本
func (s *RegexScriptService) ExecuteScripts(scripts []app.RegexScript, text string, userName string, charName string) string {
result := text
for _, script := range scripts {
executed, err := s.ExecuteScript(&script, result, userName, charName)
if err != nil {
global.GVA_LOG.Warn("执行正则脚本失败", zap.String("name", script.Name), zap.Error(err))
continue
}
result = executed
}
return result
}

View File

@@ -0,0 +1,198 @@
package app
import (
"encoding/json"
"regexp"
"strings"
"git.echol.cn/loser/st/server/global"
"git.echol.cn/loser/st/server/model/app"
"go.uber.org/zap"
)
// WorldbookEngine 世界书触发引擎
type WorldbookEngine struct{}
// TriggeredEntry 触发的条目
type TriggeredEntry struct {
Entry *app.WorldbookEntry
Position int
Order int
}
// ScanAndTrigger 扫描消息并触发匹配的世界书条目
func (e *WorldbookEngine) ScanAndTrigger(worldbookID uint, messages []string) ([]*TriggeredEntry, error) {
// 获取世界书的所有启用条目
var entries []app.WorldbookEntry
err := global.GVA_DB.Where("worldbook_id = ? AND enabled = ?", worldbookID, true).
Order("`order` ASC").
Find(&entries).Error
if err != nil {
return nil, err
}
var triggered []*TriggeredEntry
// 合并所有消息用于扫描
var scanTexts []string
for _, entry := range entries {
// 根据 scanDepth 决定扫描范围
if entry.ScanDepth > 0 && entry.ScanDepth < len(messages) {
// 只扫描最近 N 条消息
scanTexts = messages[len(messages)-entry.ScanDepth:]
} else {
// 扫描所有消息
scanTexts = messages
}
// 检查是否触发
if e.shouldTrigger(&entry, scanTexts) {
triggered = append(triggered, &TriggeredEntry{
Entry: &entry,
Position: entry.Position,
Order: entry.Order,
})
}
}
return triggered, nil
}
// shouldTrigger 判断条目是否应该被触发
func (e *WorldbookEngine) shouldTrigger(entry *app.WorldbookEntry, messages []string) bool {
// 常驻条目总是触发
if entry.Constant {
return true
}
// 概率触发
if entry.Probability < 100 {
// 简单的概率判断(实际应用中可以使用更好的随机数生成器)
if entry.Probability <= 0 {
return false
}
// 这里简化处理,实际应该用随机数
// 为了演示,我们假设概率大于 50 就触发
if entry.Probability < 50 {
return false
}
}
// 解析关键词
var keys []string
if len(entry.Keys) > 0 {
json.Unmarshal(entry.Keys, &keys)
}
// 如果没有关键词,不触发
if len(keys) == 0 {
return false
}
// 合并所有消息为一个文本
fullText := strings.Join(messages, " ")
// 检查主关键词是否匹配
primaryMatch := e.matchKeys(keys, fullText, entry.UseRegex, entry.CaseSensitive, entry.MatchWholeWords)
if !primaryMatch {
return false
}
// 如果需要次要关键词
if entry.Selective {
var secondaryKeys []string
if len(entry.SecondaryKeys) > 0 {
json.Unmarshal(entry.SecondaryKeys, &secondaryKeys)
}
if len(secondaryKeys) > 0 {
secondaryMatch := e.matchKeys(secondaryKeys, fullText, entry.UseRegex, entry.CaseSensitive, entry.MatchWholeWords)
// SelectiveLogic: 0=AND, 1=NOT
if entry.SelectiveLogic == 0 {
// AND: 次要关键词也必须匹配
return secondaryMatch
} else {
// NOT: 次要关键词不能匹配
return !secondaryMatch
}
}
}
return true
}
// matchKeys 检查关键词是否匹配
func (e *WorldbookEngine) matchKeys(keys []string, text string, useRegex, caseSensitive, matchWholeWords bool) bool {
for _, key := range keys {
if key == "" {
continue
}
if useRegex {
// 正则表达式匹配
flags := ""
if !caseSensitive {
flags = "(?i)"
}
pattern := flags + key
matched, err := regexp.MatchString(pattern, text)
if err != nil {
global.GVA_LOG.Warn("正则表达式匹配失败", zap.String("pattern", pattern), zap.Error(err))
continue
}
if matched {
return true
}
} else {
// 普通文本匹配
searchText := text
searchKey := key
if !caseSensitive {
searchText = strings.ToLower(searchText)
searchKey = strings.ToLower(searchKey)
}
if matchWholeWords {
// 全词匹配
pattern := `\b` + regexp.QuoteMeta(searchKey) + `\b`
matched, _ := regexp.MatchString(pattern, searchText)
if matched {
return true
}
} else {
// 包含匹配
if strings.Contains(searchText, searchKey) {
return true
}
}
}
}
return false
}
// BuildPromptWithWorldbook 构建包含世界书内容的 prompt
func (e *WorldbookEngine) BuildPromptWithWorldbook(basePrompt string, triggered []*TriggeredEntry) string {
if len(triggered) == 0 {
return basePrompt
}
// 按位置和顺序排序
// Position: 0=系统提示词前, 1=系统提示词后, 4=指定深度
// 这里简化处理,都插入到系统提示词后
var worldbookContent strings.Builder
worldbookContent.WriteString("\n\n[World Information]\n")
for _, t := range triggered {
if t.Entry.Content != "" {
worldbookContent.WriteString(t.Entry.Content)
worldbookContent.WriteString("\n\n")
}
}
// 将世界书内容插入到 basePrompt 之后
return basePrompt + worldbookContent.String()
}

113
web-app/src/api/regex.ts Normal file
View File

@@ -0,0 +1,113 @@
import apiClient from './client'
// 类型定义
export interface RegexScript {
id: number
userId: number
name: string
findRegex: string
replaceWith: string
trimStrings: string[]
placement: number
disabled: boolean
markdownOnly: boolean
runOnEdit: boolean
promptOnly: boolean
substituteRegex: boolean
minDepth?: number
maxDepth?: number
scope: number
ownerCharId?: number
ownerPresetId?: number
order: number
extensions: Record<string, any>
createdAt: string
updatedAt: string
}
export interface CreateRegexScriptRequest {
name: string
findRegex: string
replaceWith?: string
trimStrings?: string[]
placement?: number
disabled?: boolean
markdownOnly?: boolean
runOnEdit?: boolean
promptOnly?: boolean
substituteRegex?: boolean
minDepth?: number
maxDepth?: number
scope?: number
ownerCharId?: number
ownerPresetId?: number
order?: number
extensions?: Record<string, any>
}
export interface UpdateRegexScriptRequest {
name?: string
findRegex?: string
replaceWith?: string
trimStrings?: string[]
placement?: number
disabled?: boolean
markdownOnly?: boolean
runOnEdit?: boolean
promptOnly?: boolean
substituteRegex?: boolean
minDepth?: number
maxDepth?: number
scope?: number
ownerCharId?: number
ownerPresetId?: number
order?: number
extensions?: Record<string, any>
}
export interface GetRegexScriptListRequest {
page?: number
pageSize?: number
keyword?: string
scope?: number
}
export interface RegexScriptListResponse {
list: RegexScript[]
total: number
page: number
pageSize: number
}
// API 方法
export const regexScriptApi = {
// 创建正则脚本
createRegexScript: (data: CreateRegexScriptRequest): Promise<{ data: RegexScript }> => {
return apiClient.post('/app/regex', data)
},
// 获取正则脚本列表
getRegexScriptList: (params?: GetRegexScriptListRequest): Promise<{ data: RegexScriptListResponse }> => {
return apiClient.get('/app/regex', { params })
},
// 获取正则脚本详情
getRegexScriptById: (id: number): Promise<{ data: RegexScript }> => {
return apiClient.get(`/app/regex/${id}`)
},
// 更新正则脚本
updateRegexScript: (id: number, data: UpdateRegexScriptRequest) => {
return apiClient.put(`/app/regex/${id}`, data)
},
// 删除正则脚本
deleteRegexScript: (id: number) => {
return apiClient.delete(`/app/regex/${id}`)
},
// 测试正则脚本
testRegexScript: (id: number, testString: string): Promise<{ data: { result: string } }> => {
return apiClient.post(`/app/regex/${id}/test`, { testString })
},
}