Files
sillytavern-repalice/ARCHITECTURE.md
2026-04-24 01:45:41 +08:00

11 KiB
Raw Permalink Blame History

项目架构设计文档

📐 整体架构

本项目采用前后端分离 + 共享层的单体仓库(monorepo)架构,严格遵循 MVC 设计模式。

┌─────────────────────────────────────────────┐
│           SillyTavern Repalice              │
├─────────────────────────────────────────────┤
│                                             │
│  ┌──────────┐    ┌──────────┐              │
│  │  Client  │◄──►│  Shared  │              │
│  │  (Vue3)  │    │  (Types) │              │
│  └──────────┘    └──────────┘              │
│       ▲                ▲                    │
│       │ HTTP/API       │ Type Safety        │
│       ▼                ▼                    │
│  ┌──────────┐    ┌──────────┐              │
│  │  Server  │◄──►│  Shared  │              │
│  │(NestJS)  │    │(Schemas) │              │
│  └──────────┘    └──────────┘              │
│       │                                    │
│       ▼                                    │
│  ┌──────────┐                              │
│  │   Data   │                              │
│  │ (Local)  │                              │
│  └──────────┘                              │
└─────────────────────────────────────────────┘

🎯 核心设计决策

1. 双数据格式策略

问题: 需要兼容 SillyTavern 的导入导出,同时支持自定义扩展功能

解决方案:

  • 外部格式 (ST* 前缀): 严格遵循 SillyTavern 规范
  • 内部格式 (无前缀): 继承并扩展,添加自定义字段
  • 转换器: 双向转换,保证数据完整性

示例:

// 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):

@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

interface CharacterRepository {
  findById(id: string): Promise<CharacterCard>;
  save(character: CharacterCard): Promise<void>;
  delete(id: string): Promise<void>;
}

class FileSystemCharacterRepository implements CharacterRepository {
  // 实现细节隐藏
}

2. Factory Pattern

class LLMProviderFactory {
  static create(provider: ProviderType): LLMProvider {
    switch (provider) {
      case 'openai': return new OpenAIProvider();
      case 'anthropic': return new AnthropicProvider();
    }
  }
}

3. Strategy Pattern

interface ActivationStrategy {
  shouldActivate(entry: WorldInfoEntry, context: string): boolean;
}

class KeywordActivationStrategy implements ActivationStrategy {
  // 关键词匹配逻辑
}

class RAGActivationStrategy implements ActivationStrategy {
  // 向量检索逻辑
}

4. Adapter Pattern

// 适配器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