430 lines
11 KiB
Markdown
430 lines
11 KiB
Markdown
# 项目架构设计文档
|
||
|
||
## 📐 整体架构
|
||
|
||
本项目采用**前后端分离 + 共享层**的单体仓库(monorepo)架构,严格遵循 MVC 设计模式。
|
||
|
||
```
|
||
┌─────────────────────────────────────────────┐
|
||
│ SillyTavern Repalice │
|
||
├─────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌──────────┐ ┌──────────┐ │
|
||
│ │ Client │◄──►│ Shared │ │
|
||
│ │ (Vue3) │ │ (Types) │ │
|
||
│ └──────────┘ └──────────┘ │
|
||
│ ▲ ▲ │
|
||
│ │ HTTP/API │ Type Safety │
|
||
│ ▼ ▼ │
|
||
│ ┌──────────┐ ┌──────────┐ │
|
||
│ │ Server │◄──►│ Shared │ │
|
||
│ │(NestJS) │ │(Schemas) │ │
|
||
│ └──────────┘ └──────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌──────────┐ │
|
||
│ │ Data │ │
|
||
│ │ (Local) │ │
|
||
│ └──────────┘ │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
## 🎯 核心设计决策
|
||
|
||
### 1. 双数据格式策略
|
||
|
||
**问题**: 需要兼容 SillyTavern 的导入导出,同时支持自定义扩展功能
|
||
|
||
**解决方案**:
|
||
- **外部格式** (`ST*` 前缀): 严格遵循 SillyTavern 规范
|
||
- **内部格式** (无前缀): 继承并扩展,添加自定义字段
|
||
- **转换器**: 双向转换,保证数据完整性
|
||
|
||
**示例**:
|
||
```typescript
|
||
// SillyTavern 格式
|
||
interface STWorldInfoEntry {
|
||
constant?: boolean; // 永久激活标志
|
||
selective?: boolean; // RAG 标志
|
||
}
|
||
|
||
// 内部格式
|
||
interface WorldInfoEntry extends STWorldInfoEntry {
|
||
activationType: ActivationType; // 4种枚举
|
||
logicExpression?: LogicExpression;
|
||
ragConfig?: RAGConfig;
|
||
}
|
||
```
|
||
|
||
### 2. MVC 分层架构
|
||
|
||
#### Model (模型层)
|
||
```
|
||
server/src/
|
||
├── services/ # 业务逻辑
|
||
├── persistence/ # 数据访问
|
||
│ ├── interfaces/ # 仓储接口
|
||
│ └── implementations/ # 具体实现
|
||
└── dto/ # 数据传输对象
|
||
```
|
||
|
||
**职责**:
|
||
- Service: 业务规则、数据验证、事务管理
|
||
- Repository: CRUD 操作、数据持久化
|
||
- DTO: API 输入输出验证
|
||
|
||
#### View (视图层)
|
||
```
|
||
client/src/
|
||
├── components/ # UI 组件
|
||
│ ├── TopBar/
|
||
│ ├── LeftPanel/
|
||
│ ├── CenterPanel/
|
||
│ ├── RightPanel/
|
||
│ └── common/
|
||
├── layouts/ # 页面布局
|
||
└── stores/ # 状态管理
|
||
```
|
||
|
||
**职责**:
|
||
- Components: UI 渲染、用户交互
|
||
- Layouts: 页面结构、路由视图
|
||
- Stores: 客户端状态管理
|
||
|
||
#### Controller (控制层)
|
||
```
|
||
server/src/controllers/ # API 端点
|
||
client/src/router/ # 前端路由
|
||
```
|
||
|
||
**职责**:
|
||
- 接收请求、参数验证
|
||
- 调用 Service 处理业务
|
||
- 返回响应
|
||
|
||
### 3. 依赖注入 (DI)
|
||
|
||
**后端 DI** (NestJS):
|
||
```typescript
|
||
@Injectable()
|
||
export class CharacterService {
|
||
constructor(
|
||
@Inject(CHARACTER_REPOSITORY)
|
||
private repo: CharacterRepository,
|
||
private llmService: LLMService,
|
||
) {}
|
||
}
|
||
```
|
||
|
||
**优势**:
|
||
- 松耦合:模块间通过接口通信
|
||
- 可测试:轻松 mock 依赖
|
||
- 可替换:不同实现无缝切换
|
||
|
||
### 4. 工具层 (Tools Layer)
|
||
|
||
独立于 MVC 的工具层,提供通用能力:
|
||
|
||
```
|
||
server/src/tools/
|
||
├── context-chunker.ts # 上下文分块
|
||
├── prompt-assembler.ts # 提示词组装
|
||
└── token-counter.ts # Token 计数
|
||
```
|
||
|
||
**特点**:
|
||
- 无状态纯函数
|
||
- 不依赖 DI 容器
|
||
- 可在任何层调用
|
||
|
||
## 🗂️ 目录结构设计原则
|
||
|
||
### 后端 (NestJS)
|
||
|
||
```
|
||
server/src/
|
||
├── modules/ # 按业务领域划分
|
||
│ ├── chat/ # 聊天管理
|
||
│ ├── character/ # 角色卡管理
|
||
│ ├── llm/ # LLM 集成
|
||
│ ├── import-export/# 导入导出
|
||
│ └── workflow/ # 工作流引擎
|
||
│
|
||
├── persistence/ # 持久化抽象
|
||
│ ├── interfaces/ # 仓储接口
|
||
│ ├── implementations/ # 实现
|
||
│ └── file-system/ # 文件系统实现
|
||
│
|
||
├── core/ # 基础设施
|
||
│ ├── config/ # 配置管理
|
||
│ ├── filters/ # 异常过滤
|
||
│ ├── interceptors/ # 拦截器
|
||
│ └── guards/ # 守卫
|
||
│
|
||
├── controllers/ # 路由层 (横向切割)
|
||
├── services/ # 业务层 (横向切割)
|
||
└── tools/ # 工具层 (横向切割)
|
||
```
|
||
|
||
**设计原则**:
|
||
1. **垂直切片**: 每个 module 自包含 controller + service
|
||
2. **水平分层**: persistence/core 跨模块复用
|
||
3. **依赖方向**: controllers → services → persistence
|
||
|
||
### 前端 (Vue3)
|
||
|
||
```
|
||
client/src/
|
||
├── components/ # 按布局区域划分
|
||
│ ├── TopBar/ # 顶部栏
|
||
│ ├── LeftPanel/ # 左侧面板
|
||
│ ├── CenterPanel/ # 中央面板
|
||
│ ├── RightPanel/ # 右侧面板
|
||
│ └── common/ # 通用组件
|
||
│
|
||
├── layouts/ # 布局模板
|
||
├── stores/ # 全局状态
|
||
├── router/ # 路由配置
|
||
├── composables/ # 组合式函数
|
||
└── api/ # API 客户端
|
||
```
|
||
|
||
**设计原则**:
|
||
1. **布局驱动**: 组件按 UI 区域组织
|
||
2. **关注点分离**: 状态、路由、API 独立管理
|
||
3. **组合优于继承**: 使用 composables 复用逻辑
|
||
|
||
### 共享层 (Shared)
|
||
|
||
```
|
||
shared/
|
||
├── types/ # TypeScript 类型
|
||
│ ├── sillytavern.types.ts # ST 兼容格式
|
||
│ ├── internal.types.ts # 内部扩展格式
|
||
│ └── converters.ts # 转换器
|
||
│
|
||
└── schemas/ # Zod 运行时验证
|
||
├── sillytavern.schemas.ts
|
||
└── internal.schemas.ts
|
||
```
|
||
|
||
**设计原则**:
|
||
1. **单一真相源**: 前后端共用类型定义
|
||
2. **编译时 + 运行时**: TypeScript + Zod 双重验证
|
||
3. **向后兼容**: 转换器保证旧数据可用
|
||
|
||
## 🔄 数据流
|
||
|
||
### 典型请求流程
|
||
|
||
```
|
||
User Action (Frontend)
|
||
↓
|
||
Vue Component
|
||
↓
|
||
Pinia Store
|
||
↓
|
||
API Client (axios)
|
||
↓
|
||
HTTP Request
|
||
↓
|
||
NestJS Controller
|
||
↓
|
||
Service (Business Logic)
|
||
↓
|
||
Repository (Data Access)
|
||
↓
|
||
File System
|
||
↓
|
||
Response (JSON)
|
||
↓
|
||
Update UI
|
||
```
|
||
|
||
### 数据转换流程
|
||
|
||
```
|
||
SillyTavern Import File
|
||
↓
|
||
Zod Schema Validation (ST Schema)
|
||
↓
|
||
Parse to ST Types
|
||
↓
|
||
Converter (convertSTToInternal)
|
||
↓
|
||
Internal Types
|
||
↓
|
||
Save to File System
|
||
```
|
||
|
||
## 🧪 测试策略
|
||
|
||
### 单元测试
|
||
- **目标**: Services, Tools, Utils
|
||
- **工具**: Jest
|
||
- **Mock**: 外部依赖 (Repository, LLM API)
|
||
|
||
### 集成测试
|
||
- **目标**: Controller + Service 协作
|
||
- **工具**: Supertest
|
||
- **范围**: 单个模块的完整流程
|
||
|
||
### E2E 测试
|
||
- **目标**: 关键用户流程
|
||
- **工具**: Playwright / Cypress
|
||
- **场景**: 创建角色、发送消息、导入导出
|
||
|
||
## 🚀 部署架构
|
||
|
||
### 开发环境
|
||
```
|
||
Client (Vite Dev Server) :5173
|
||
↓ Proxy /api
|
||
Server (NestJS) :3000
|
||
↓
|
||
Local File System ./data
|
||
```
|
||
|
||
### 生产环境 (Docker)
|
||
```
|
||
Client (Nginx) :80
|
||
↓ Proxy /api
|
||
Server (Node.js) :3000
|
||
↓
|
||
Volume Mount /app/data
|
||
```
|
||
|
||
## 📊 关键技术选型理由
|
||
|
||
| 技术 | 选型理由 |
|
||
|------|---------|
|
||
| **NestJS** | 内置 DI、模块化、TypeScript 优先、企业级 |
|
||
| **Vue 3** | 组合式 API、响应式系统、学习曲线平缓 |
|
||
| **Vite** | 极速开发体验、原生 ESM、热更新快 |
|
||
| **Zod** | TypeScript 优先、运行时验证、 schema 推导 |
|
||
| **Vercel AI SDK** | 统一 LLM 接口、流式支持、工具调用 |
|
||
| **Pinia** | Vue 3 官方推荐、TypeScript 友好、轻量 |
|
||
| **文件系统** | 零依赖、易备份、用户可控、无需数据库 |
|
||
|
||
## 🎨 设计模式应用
|
||
|
||
### 1. Repository Pattern
|
||
```typescript
|
||
interface CharacterRepository {
|
||
findById(id: string): Promise<CharacterCard>;
|
||
save(character: CharacterCard): Promise<void>;
|
||
delete(id: string): Promise<void>;
|
||
}
|
||
|
||
class FileSystemCharacterRepository implements CharacterRepository {
|
||
// 实现细节隐藏
|
||
}
|
||
```
|
||
|
||
### 2. Factory Pattern
|
||
```typescript
|
||
class LLMProviderFactory {
|
||
static create(provider: ProviderType): LLMProvider {
|
||
switch (provider) {
|
||
case 'openai': return new OpenAIProvider();
|
||
case 'anthropic': return new AnthropicProvider();
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. Strategy Pattern
|
||
```typescript
|
||
interface ActivationStrategy {
|
||
shouldActivate(entry: WorldInfoEntry, context: string): boolean;
|
||
}
|
||
|
||
class KeywordActivationStrategy implements ActivationStrategy {
|
||
// 关键词匹配逻辑
|
||
}
|
||
|
||
class RAGActivationStrategy implements ActivationStrategy {
|
||
// 向量检索逻辑
|
||
}
|
||
```
|
||
|
||
### 4. Adapter Pattern
|
||
```typescript
|
||
// 适配器:SillyTavern 格式 → 内部格式
|
||
class SillyTavernAdapter {
|
||
static toInternal(stCard: STCharacterCard): CharacterCard {
|
||
return convertSTCharacterCardToInternal(stCard);
|
||
}
|
||
|
||
static toExternal(card: CharacterCard): STCharacterCard {
|
||
return convertCharacterCardToST(card);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 🔐 安全性考虑
|
||
|
||
1. **输入验证**: Zod schemas 在边界验证所有输入
|
||
2. **文件上传**: 限制类型、大小,扫描恶意内容
|
||
3. **API 密钥**: 环境变量管理,不提交到版本控制
|
||
4. **CORS**: 严格配置允许的来源
|
||
5. **路径遍历**: 验证文件路径,防止 `../` 攻击
|
||
|
||
## 📈 性能优化
|
||
|
||
1. **懒加载**: 前端路由和组件按需加载
|
||
2. **缓存**: LLM 响应缓存、角色卡元数据缓存
|
||
3. **分页**: 聊天记录、角色列表分页加载
|
||
4. **流式响应**: SSE 传输 LLM 生成内容
|
||
5. **索引**: 为搜索字段建立内存索引
|
||
|
||
## 🔄 扩展性设计
|
||
|
||
### 新增 LLM Provider
|
||
1. 实现 `LLMProvider` 接口
|
||
2. 注册到 `LLMProviderFactory`
|
||
3. 更新配置 schema
|
||
|
||
### 新增持久化方式
|
||
1. 实现 Repository 接口
|
||
2. 创建新模块 (如 `persistence/sqlite/`)
|
||
3. 通过 DI 切换实现
|
||
|
||
### 新增激活方式
|
||
1. 扩展 `ActivationType` 枚举
|
||
2. 实现新的 `ActivationStrategy`
|
||
3. 更新世界书 UI
|
||
|
||
## 📝 编码规范
|
||
|
||
### TypeScript
|
||
- 严格模式: `strict: true`
|
||
- 禁止 `any`,使用 `unknown` 代替
|
||
- 接口命名不加 `I` 前缀
|
||
- 枚举使用 PascalCase
|
||
|
||
### Vue 3
|
||
- 优先使用 `<script setup>`
|
||
- Props 用 TypeScript 定义
|
||
- 组合式逻辑提取为 composables
|
||
|
||
### NestJS
|
||
- 每个模块一个文件夹
|
||
- Controller 只处理 HTTP 层
|
||
- Service 不包含 HTTP 相关代码
|
||
- 使用 DTO 验证输入
|
||
|
||
## 🎯 下一步计划
|
||
|
||
1. **实现基础 CRUD**: 角色卡、聊天、世界书
|
||
2. **集成 Vercel AI SDK**: 流式聊天
|
||
3. **构建 UI 组件**: 聊天界面、角色编辑器
|
||
4. **实现转换器**: 完整的导入导出功能
|
||
5. **添加测试**: 覆盖核心业务逻辑
|
||
6. **编写文档**: API 文档、用户指南
|
||
|
||
---
|
||
|
||
**文档版本**: 1.0
|
||
**最后更新**: 2026-04-24
|