Code Reader
首页
帮助
设计文档
首页
帮助
设计文档
  • SillyTavern 前端架构分析

SillyTavern 前端架构分析

本文档详细分析 SillyTavern 项目的前端(客户端)架构设计。

1. 前端技术栈

1.1 核心技术

技术用途说明
原生 JavaScript核心逻辑无框架依赖,使用 ES6+ 语法
jQueryDOM 操作简化 DOM 操作和事件处理
jQuery UIUI 组件提供拖拽、排序等交互功能
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 Promptmainsystem是核心指令
NSFW Promptnsfwsystem否成人内容控制
Jailbreakjailbreaksystem是解除限制指令
Enhance DefinitionsenhanceDefinitionssystem否增强角色定义
角色描述charDescriptionsystem否角色基础描述
角色性格charPersonalitysystem否性格特征
场景设定scenariosystem否场景背景
用户人格personaDescriptionsystem否用户角色设定
世界信息worldInfoBefore/Aftersystem否动态插入的世界信息

步骤 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 架构特点

  1. 模块化设计: 使用 ES Modules 清晰分离关注点
  2. 事件驱动: 通过 EventEmitter 实现模块间松耦合通信
  3. 无框架依赖: 原生 JavaScript + jQuery,降低学习成本
  4. 类型标注: JSDoc 类型定义提升代码可维护性
  5. 扩展友好: 完善的扩展系统支持第三方功能集成

10.2 数据流

用户输入 -> script.js -> PromptManager -> openai.js -> AI API
                    ↓
            chats.js (UI 更新)
                    ↓
            状态更新 -> 本地存储

10.3 关键设计模式

  • 单例模式: eventSource, power_user 等全局状态
  • 工厂模式: Popup 类创建不同类型的弹窗
  • 观察者模式: 事件系统监听和响应状态变化
  • 策略模式: Token 计算支持多种分词器

文档生成时间: 2026-02-01分析基于 SillyTavern 项目代码