SillyTavern 前端架构分析
本文档详细分析 SillyTavern 项目的前端(客户端)架构设计。
1. 前端技术栈
1.1 核心技术
| 技术 | 用途 | 说明 |
|---|---|---|
| 原生 JavaScript | 核心逻辑 | 无框架依赖,使用 ES6+ 语法 |
| jQuery | DOM 操作 | 简化 DOM 操作和事件处理 |
| jQuery UI | UI 组件 | 提供拖拽、排序等交互功能 |
| ES Modules | 模块系统 | 使用原生 ES Module 进行代码组织 |
| Webpack | 构建工具 | 模块打包和资源管理 |
1.2 第三方库
// lib.js 中整合的核心库
import {
showdown, // Markdown 渲染
moment, // 日期时间处理
DOMPurify, // HTML 净化
hljs, // 代码高亮
Handlebars, // 模板引擎
SVGInject, // SVG 注入
Popper, // 浮动元素定位
Fuse, // 模糊搜索
Readability, // 网页内容提取
lodash, // 工具函数库
localforage, // 本地存储
} from './lib.js';
1.3 项目结构
public/
├── index.html # 主入口 HTML
├── script.js # 主脚本入口(核心逻辑)
├── lib.js # 第三方库整合
├── style.css # 主样式文件
├── scripts/
│ ├── chats.js # 聊天功能
│ ├── char-data.js # 角色数据类型定义
│ ├── PromptManager.js # Prompt 管理
│ ├── openai.js # OpenAI API 集成
│ ├── group-chats.js # 群组聊天
│ ├── utils.js # 工具函数
│ ├── power-user.js # 用户设置
│ ├── tokenizers.js # Token 计算
│ ├── macros.js # 宏系统
│ ├── popup.js # 弹窗系统
│ ├── templates.js # 模板渲染
│ ├── dragdrop.js # 拖拽系统
│ └── ... # 其他模块
└── css/ # 样式文件目录
2. 核心模块依赖图
3. 核心模块职责
3.1 script.js - 主入口模块
职责:
- 应用初始化与启动
- 全局状态管理(
characters,chat,this_chid等) - 事件系统中心(
eventSource,event_types) - 模块整合与协调
核心导出:
export {
characters, // 角色列表
chat, // 当前聊天记录
this_chid, // 当前角色ID
eventSource, // 事件发射器
event_types, // 事件类型枚举
main_api, // 当前使用的API类型
getContext, // 获取上下文
substituteParams, // 参数替换
// ... 更多
}
3.2 chats.js - 聊天界面与消息管理
文件路径: /home/sujie/dev/github/SillyTavern/public/scripts/chats.js
职责:
- 消息渲染与显示
- 文件附件管理(PDF、Office 文档、图片等)
- 消息样式处理(CSS 样式标签编码/解码)
- 媒体附件(图片、视频、音频)管理
- 消息隐藏/显示控制
核心功能:
// 消息附件处理
export async function populateFileAttachment(message, inputId)
export async function uploadFileAttachment(fileName, base64Data)
export async function getFileAttachment(url)
// 消息可见性控制
export async function hideChatMessageRange(start, end, unhide, nameFilter)
// 样式处理
export function encodeStyleTags(text)
export function decodeStyleTags(text, options)
export function formatCreatorNotes(text, avatarId)
依赖:
script.js- 核心状态和方法utils.js- 工具函数popup.js- 弹窗交互extensions.js- 扩展系统
3.3 char-data.js - 角色数据类型定义
文件路径: /home/sujie/dev/github/SillyTavern/public/scripts/char-data.js
职责:
- 定义角色数据结构(TypeScript JSDoc 风格)
- v1 和 v2 格式的角色数据类型
- World Info 条目类型定义
- 正则脚本类型定义
核心类型:
/**
* @typedef {object} v2CharData
* @property {string} name - 角色名称
* @property {string} description - 角色描述
* @property {string} personality - 性格特征
* @property {string} scenario - 场景设定
* @property {string} first_mes - 开场消息
* @property {string} mes_example - 对话示例
* @property {v2WorldInfoBook} character_book - 世界信息书
* @property {v2CharDataExtensionInfos} extensions - 扩展数据
*/
/**
* @typedef {object} v1CharData
* @property {string} name
* @property {string} description
* @property {string} personality
* @property {string} avatar - 头像文件名(唯一标识)
* @property {v2CharData} data - v2 数据扩展
*/
3.4 group-chats.js - 群组聊天
文件路径: /home/sujie/dev/github/SillyTavern/public/scripts/group-chats.js
职责:
- 群组创建与管理
- 多角色对话协调
- 群组消息生成逻辑
- 群组成员管理
核心导出:
export {
selected_group, // 当前选中的群组ID
is_group_generating, // 群组生成状态
groups, // 群组列表
generateGroupWrapper, // 群组消息生成
saveGroupChat, // 保存群组聊天
// ...
}
生成模式:
export const group_generation_mode = {
SWAP: 0, // 切换角色回复
APPEND: 1, // 追加角色回复
APPEND_DISABLED: 2 // 禁用追加
};
export const group_activation_strategy = {
NATURAL: 0, // 自然模式
LIST: 1, // 列表模式
MANUAL: 2, // 手动模式
POOLED: 3, // 池化模式
};
3.5 openai.js - OpenAI API 客户端
文件路径: /home/sujie/dev/github/SillyTavern/public/scripts/openai.js
职责:
- Chat Completion API 调用
- 多提供商支持(OpenAI、Claude、OpenRouter 等)
- 消息格式转换
- Token 计算和上下文管理
- 流式响应处理
支持的 API 源:
export const chat_completion_sources = {
OPENAI: 'openai',
CLAUDE: 'claude',
OPENROUTER: 'openrouter',
AI21: 'ai21',
MAKERSUITE: 'makersuite',
VERTEXAI: 'vertexai',
MISTRALAI: 'mistralai',
CUSTOM: 'custom',
COHERE: 'cohere',
// ... 更多
};
核心类:
// 消息类
class Message {
constructor(role, content, name = '')
}
// 消息集合类
class MessageCollection {
constructor()
add(message)
hasItemWithIdentifier(identifier)
getItemByIdentifier(identifier)
}
// Token 处理器
class TokenHandler {
constructor(getTokenCountCallback)
count(text, padding = 0)
getCounts()
}
3.6 PromptManager.js - Prompt 构建管理
文件路径: /home/sujie/dev/github/SillyTavern/public/scripts/PromptManager.js
职责:
- Prompt 集合管理
- Prompt 顺序和优先级控制
- 角色特定的 Prompt 配置
- Prompt 注入位置管理(相对/绝对)
- Token 用量估算
核心类:
// Prompt 类
class Prompt {
constructor({
identifier, // 唯一标识
role, // 角色(system/user/assistant)
content, // 内容
name, // 显示名称
system_prompt, // 是否为系统 Prompt
position, // 位置
injection_depth, // 注入深度
injection_position, // 注入位置(0=相对, 1=绝对)
injection_order, // 注入顺序
injection_trigger, // 触发条件
forbid_overrides, // 禁止覆盖
})
}
// Prompt 集合类
export class PromptCollection {
collection = []
overriddenPrompts = []
add(...prompts)
set(prompt, position)
get(identifier)
override(prompt, position)
}
// Prompt 管理器
class PromptManager {
constructor()
init(moduleConfiguration, serviceSettings)
render(afterTryGenerate = true)
// Prompt 操作
getPromptById(identifier)
addPrompt(prompt, identifier)
updatePromptByIdentifier(identifier, updatePrompt)
// 角色相关
getPromptsForCharacter(character, onlyEnabled = false)
getPromptOrderForCharacter(character)
appendPrompt(prompt, character)
detachPrompt(prompt, character)
}
Prompt 源定义:
get promptSources() {
return {
charDescription: 'Character Description',
charPersonality: 'Character Personality',
scenario: 'Character Scenario',
personaDescription: 'Persona Description',
worldInfoBefore: 'World Info (↑Char)',
worldInfoAfter: 'World Info (↓Char)',
};
}
3.7 utils.js - 通用工具函数
文件路径: /home/sujie/dev/github/SillyTavern/public/scripts/utils.js
职责:
- 通用工具函数集合
- 文件处理(读取、转换、下载)
- 字符串处理(转义、格式化、解析)
- 数组和对象操作
- 性能优化工具(防抖、节流)
核心功能分类:
字符串处理:
export function escapeHtml(str)
export function sanitizeSelector(str, replacement = '_')
export function isValidUrl(value)
export function isUuid(value)
export function convertValueType(value, type)
export function extractAllWords(value)
export function escapeRegex(string)
文件处理:
export function download(content, fileName, contentType)
export function getFileText(file)
export function getFileBuffer(file)
export function getBase64Async(file)
export function parseJsonFile(file)
性能优化:
export function debounce(func, timeout)
export function throttle(func, limit)
export function debouncedThrottle(func, limit)
export function delay(ms)
数据处理:
export function deepMerge(target, source)
export function shuffle(array)
export function splitRecursive(input, length, delimiters)
export function humanFileSize(bytes, si = false, dp = 1)
4. Prompt 构建流程
4.1 流程概览
4.2 详细流程
步骤 1: Prompt 收集
// PromptManager.js 中的处理流程
class PromptManager {
render(afterTryGenerate = true) {
// 1. 等待当前生成完成
waitUntilCondition(() => !is_send_press && !is_group_generating)
.then(async () => {
// 2. 执行干运行(dry-run)以确定上下文组成
this.tryGenerate().finally(async () => {
// 3. 渲染 Prompt 管理器 UI
await this.renderPromptManager();
await this.renderPromptManagerListItems();
});
});
}
}
步骤 2: Prompt 类型与优先级
系统预设的 Prompt 类别:
| 类别 | 标识符 | 角色 | 可覆盖 | 说明 |
|---|---|---|---|---|
| Main Prompt | main | system | 是 | 核心指令 |
| NSFW Prompt | nsfw | system | 否 | 成人内容控制 |
| Jailbreak | jailbreak | system | 是 | 解除限制指令 |
| Enhance Definitions | enhanceDefinitions | system | 否 | 增强角色定义 |
| 角色描述 | charDescription | system | 否 | 角色基础描述 |
| 角色性格 | charPersonality | system | 否 | 性格特征 |
| 场景设定 | scenario | system | 否 | 场景背景 |
| 用户人格 | personaDescription | system | 否 | 用户角色设定 |
| 世界信息 | worldInfoBefore/After | system | 否 | 动态插入的世界信息 |
步骤 3: Prompt 组装逻辑
// openai.js 中的消息准备
function prepareOpenAIMessages({
promptManager, // Prompt 管理器实例
mesExamples, // 对话示例
chatHistory, // 聊天历史
systemPrompt, // 系统 Prompt
userPrompt, // 用户 Prompt
contextSize, // 上下文大小限制
}) {
// 1. 创建消息集合
const messageCollection = new MessageCollection();
// 2. 添加系统级 Prompt
const systemPrompts = promptManager.getPromptsForCharacter(character, true);
for (const prompt of systemPrompts) {
if (prompt.injection_position === INJECTION_POSITION.RELATIVE) {
// 相对位置:添加到消息流中
messageCollection.add(new Message(prompt.role, content, prompt.name));
}
}
// 3. 添加对话示例
if (mesExamples && mesExamples.length > 0) {
for (const example of mesExamples) {
messageCollection.add(example);
}
}
// 4. 添加聊天历史(带 Token 截断)
const tokenHandler = new TokenHandler(getTokenCountCallback);
for (const message of chatHistory) {
// Token 检查与截断逻辑
if (tokenHandler.count(message.content) > contextSize) {
break;
}
messageCollection.add(message);
}
// 5. 处理绝对位置注入
for (const prompt of systemPrompts) {
if (prompt.injection_position === INJECTION_POSITION.ABSOLUTE) {
// 绝对位置:在指定深度插入
messageCollection.insertAtDepth(prompt.injection_depth,
new Message(prompt.role, content, prompt.name));
}
}
return messageCollection;
}
步骤 4: Token 管理与截断
// TokenHandler 类
class TokenHandler {
constructor(getTokenCountCallback) {
this.getTokenCount = getTokenCountCallback;
this.counts = {};
}
count(text, padding = 0) {
const tokens = this.getTokenCount(text) + padding;
return tokens;
}
// 上下文填充策略
fillContext(messages, maxTokens) {
let totalTokens = 0;
const result = [];
// 从最新消息开始反向遍历
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
const tokens = this.count(message.content);
if (totalTokens + tokens <= maxTokens) {
result.unshift(message);
totalTokens += tokens;
} else {
// 超出限制,停止添加
break;
}
}
return result;
}
}
4.3 Prompt 顺序策略
PromptManager 支持两种顺序策略:
// 全局策略(默认)
configuration.promptOrder.strategy = 'global';
// 所有角色共享相同的 Prompt 顺序
// 角色特定策略
configuration.promptOrder.strategy = 'character';
// 每个角色有独立的 Prompt 顺序配置
Prompt 顺序数据结构:
// prompt_order 存储结构
this.serviceSettings.prompt_order = [
{
character_id: 'character-uuid',
order: [
{ identifier: 'main', enabled: true },
{ identifier: 'nsfw', enabled: false },
{ identifier: 'jailbreak', enabled: true },
{ identifier: 'charDescription', enabled: true },
// ...
]
}
];
5. UI 组件系统
5.1 弹窗系统 (popup.js)
核心功能:
export const POPUP_TYPE = {
TEXT: 1, // 文本弹窗
CONFIRM: 2, // 确认弹窗
INPUT: 3, // 输入弹窗
DISPLAY: 4, // 展示弹窗
CROP: 5, // 图片裁剪弹窗
};
export const POPUP_RESULT = {
AFFIRMATIVE: 1, // 确认
NEGATIVE: 0, // 否定
CANCELLED: null,// 取消
CUSTOM1: 1001, // 自定义 1
// ...
};
export class Popup {
constructor(content, type, inputValue, options)
async show()
completeAffirmative()
completeNegative()
completeCancelled()
}
// 便捷函数
export async function callGenericPopup(content, type, inputValue, options)
5.2 模板系统 (templates.js)
基于 Handlebars:
export function renderTemplate(templateId, data)
export async function renderTemplateAsync(templateId, data)
5.3 拖拽系统 (dragdrop.js)
export class DragAndDropHandler {
constructor(options)
init()
handleDrop(event)
}
6. 状态管理
6.1 全局状态 (script.js)
// 核心全局状态
let characters = []; // 所有角色数据
let chat = []; // 当前聊天记录
let this_chid = null; // 当前选中角色ID
let selected_group = null; // 当前选中群组ID
let main_api = 'openai'; // 当前使用的API
let is_send_press = false; // 是否正在发送
6.2 用户设置 (power-user.js)
export const power_user = {
tokenizer: tokenizers.BEST_MATCH, // 分词器选择
token_padding: 64, // Token 填充
collapse_newlines: false, // 折叠换行
pin_examples: false, // 固定示例
chat_truncation: 100, // 聊天截断
streaming_fps: 30, // 流式 FPS
// ... 更多设置
};
6.3 变量系统 (variables.js)
聊天级变量:
// 与特定聊天绑定的变量
chat_metadata.variables = {
key: value
}
6.4 本地存储 (f-localStorage.js)
分层存储策略:
// 1. accountStorage - 用户账户相关
// 2. localStorage - 浏览器本地存储
// 3. localforage - IndexedDB 大容量存储
7. 工具函数详解
7.1 Token 计算 (tokenizers.js)
支持的分词器:
export const tokenizers = {
NONE: 'none',
GPT3: 'gpt3',
GPT4: 'gpt4',
CLAUDE: 'claude',
LLAMA: 'llama',
BEST_MATCH: 'best_match',
// ...
};
核心函数:
export function getTokenCount(str, padding = undefined)
export async function getTokenCountAsync(str, padding = undefined)
export function getFriendlyTokenizerName(forApi)
7.2 宏替换系统 (macros.js)
标准宏:
| 宏 | 说明 |
|---|---|
{{user}} | 用户名 |
{{char}} | 角色名 |
{{charIfNotGroup}} | 非群组时的角色名 |
{{group}} | 群组成员列表 |
{{persona}} | 用户人格描述 |
{{description}} | 角色描述 |
{{personality}} | 角色性格 |
{{scenario}} | 场景设定 |
{{wiBefore}} | 世界信息(前) |
{{wiAfter}} | 世界信息(后) |
{{lastMessage}} | 最后一条消息 |
{{time}} | 当前时间 |
{{date}} | 当前日期 |
8. 扩展系统架构
8.1 扩展加载机制
// extensions.js
export async function initExtensions()
export function loadExtensionSettings()
export function runGenerationInterceptors(promptData)
8.2 扩展 API
globalThis.SillyTavern = {
libs, // 第三方库
getContext, // 获取应用上下文
};
9. 事件系统
9.1 事件类型定义
// events.js
export const event_types = {
// 消息事件
MESSAGE_RECEIVED: 'message_received',
MESSAGE_EDITED: 'message_edited',
MESSAGE_DELETED: 'message_deleted',
MESSAGE_SWIPED: 'message_swiped',
// 角色事件
CHARACTER_SELECTED: 'character_selected',
CHARACTER_EDITED: 'character_edited',
CHARACTER_DELETED: 'character_deleted',
// 聊天事件
CHAT_CHANGED: 'chat_changed',
CHAT_CREATED: 'chat_created',
CHAT_DELETED: 'chat_deleted',
// 设置事件
SETTINGS_LOADED: 'settings_loaded',
OAI_PRESET_CHANGED: 'oai_preset_changed',
// 生成事件
GENERATION_STARTED: 'generation_started',
GENERATION_ENDED: 'generation_ended',
GENERATION_STOPPED: 'generation_stopped',
};
export const eventSource = new EventEmitter();
10. 总结
10.1 架构特点
- 模块化设计: 使用 ES Modules 清晰分离关注点
- 事件驱动: 通过 EventEmitter 实现模块间松耦合通信
- 无框架依赖: 原生 JavaScript + jQuery,降低学习成本
- 类型标注: JSDoc 类型定义提升代码可维护性
- 扩展友好: 完善的扩展系统支持第三方功能集成
10.2 数据流
用户输入 -> script.js -> PromptManager -> openai.js -> AI API
↓
chats.js (UI 更新)
↓
状态更新 -> 本地存储
10.3 关键设计模式
- 单例模式:
eventSource,power_user等全局状态 - 工厂模式:
Popup类创建不同类型的弹窗 - 观察者模式: 事件系统监听和响应状态变化
- 策略模式: Token 计算支持多种分词器
文档生成时间: 2026-02-01分析基于 SillyTavern 项目代码