diff --git a/docs/EXPORT_IMPLEMENTATION.md b/docs/EXPORT_IMPLEMENTATION.md deleted file mode 100644 index 8586a7143..000000000 --- a/docs/EXPORT_IMPLEMENTATION.md +++ /dev/null @@ -1,113 +0,0 @@ -# 导出功能实现完成 - -## 功能概述 - -我已经成功实现了一个完整的、可扩展的会话导出功能,支持多种格式(Markdown、HTML、纯文本)。 - -## 实现的组件 - -### 1. **ThreadPresenter 导出接口** (`src/main/presenter/threadPresenter/index.ts`) -- `exportConversation()` - 主导出方法 -- `exportToMarkdown()` - Markdown格式导出 -- `exportToHtml()` - HTML格式导出 -- `exportToText()` - 纯文本格式导出 -- `escapeHtml()` - HTML转义辅助函数 - -### 2. **Worker 导出处理** (`src/renderer/workers/exportWorker.ts`) -- 独立的Worker文件用于处理大型会话导出,防止UI卡顿 -- 支持进度报告和错误处理 -- 完整的格式化逻辑实现 - -### 3. **Chat Store 集成** (`src/renderer/src/stores/chat.ts`) -- `exportThread()` - 调用导出并触发下载 -- `getContentType()` - 获取正确的MIME类型 -- 自动文件下载处理 - -### 4. **UI 组件更新** (`src/renderer/src/components/ThreadItem.vue`) -- 添加导出子菜单到会话右键菜单 -- 支持三种格式的导出选项 -- `handleExport()` - 导出处理函数 - -### 5. **国际化支持** -- 更新了英文和中文的翻译文件 -- 添加了 "export" 和 "exportText" 翻译键 - -## 功能特性 - -### ✅ **完整数据导出** -- 用户消息(包括文本、文件附件、链接) -- 助手响应(包括内容、工具调用、搜索结果、思考过程) -- 消息元数据(时间戳、Token使用情况、生成时间) -- 会话配置信息(模型、提供商等) - -### ✅ **多种导出格式** -- **Markdown (.md)** - 结构化文档格式,支持代码块和表格 -- **HTML (.html)** - 美观的网页格式,包含CSS样式 -- **纯文本 (.txt)** - 简洁的文本格式 - -### ✅ **可扩展架构** -- 模块化设计,易于添加新的导出格式 -- 统一的接口设计 -- 类型安全的TypeScript实现 - -### ✅ **用户友好** -- 直观的右键菜单界面 -- 自动文件下载 -- 错误处理和用户反馈 - -### ✅ **性能优化** -- Worker支持(虽然当前在主进程中实现以简化架构) -- 内存友好的流式处理 -- 大会话的高效处理 - -## 使用方法 - -1. 在会话列表中,右键点击任何会话 -2. 选择 "导出" 子菜单 -3. 选择所需的导出格式: - - Markdown (.md) - - HTML (.html) - - 纯文本 (.txt) -4. 文件将自动下载到默认下载文件夹 - -## 导出内容示例 - -### Markdown 格式特性 -- 标题和元信息 -- 用户消息时间戳 -- 工具调用的参数和响应(JSON格式化) -- 思考过程(代码块) -- 搜索结果统计 -- Token使用情况统计 - -### HTML 格式特性 -- 响应式设计 -- 美观的CSS样式 -- 颜色编码的消息类型 -- 结构化的内容块 -- 可打印的格式 - -### 纯文本格式特性 -- 简洁的文本表示 -- 清晰的章节分隔 -- 易于处理和搜索 - -## 代码质量 - -- ✅ 通过 OxLint 检查(0 warnings, 0 errors) -- ✅ TypeScript 类型安全 -- ✅ 符合项目编码规范 -- ✅ 完整的错误处理 -- ✅ 国际化支持 - -## 后续扩展 - -这个实现为未来的扩展提供了坚实的基础: - -1. **新导出格式** - 可以轻松添加PDF、Word、JSON等格式 -2. **高级过滤** - 按日期范围、消息类型等过滤导出内容 -3. **批量导出** - 一次导出多个会话 -4. **云同步** - 导出到云存储服务 -5. **自定义模板** - 用户自定义导出格式 - -导出功能已经完全实现并可以立即使用! \ No newline at end of file diff --git a/docs/builtin-knowledge-architecture.md b/docs/builtin-knowledge-architecture.md deleted file mode 100644 index 973db1f8a..000000000 --- a/docs/builtin-knowledge-architecture.md +++ /dev/null @@ -1,369 +0,0 @@ -# Knowledge Presenter 架构文档 - -## 模块概述 - -Knowledge Presenter 是 DeepChat 中负责管理本地知识库的核心模块,主要功能包括: - -1. **知识库生命周期管理**: 创建、更新、删除知识库实例,配置管理和持久化存储。 -2. **文件管理**: 文件添加、删除、重新处理,状态跟踪和进度反馈。 -3. **向量化与检索**: 文件分片、嵌入生成、向量存储和相似度检索。 -4. **任务调度**: 全局串行任务队列,并发控制和异常处理。 - -## 核心组件 - -```mermaid -classDiagram - class IKnowledgePresenter { - <> - +create(config) - +update(config) - +delete(id) - +addFile(id, filePath) - +deleteFile(id, fileId) - +reAddFile(id, fileId) - +queryFile(id, fileId) - +listFiles(id) - +similarityQuery(id, key) - +getTaskQueueStatus() - +pauseAllRunningTasks(id) - +resumeAllPausedTasks(id) - +closeAll() - +destroy() - +beforeDestroy() - } - - class KnowledgePresenter { - -storageDir: string - -configP: IConfigPresenter - -taskP: KnowledgeTaskPresenter - -storePresenterCache: Map - +create(config) - +update(config) - +delete(id) - +addFile(id, filePath) - +deleteFile(id, fileId) - +reAddFile(id, fileId) - +queryFile(id, fileId) - +listFiles(id) - +similarityQuery(id, key) - +getTaskQueueStatus() - +pauseAllRunningTasks(id) - +resumeAllPausedTasks(id) - +closeAll() - +destroy() - +beforeDestroy() - -createStorePresenter(config) - -getStorePresenter(id) - -getOrCreateStorePresenter(id) - -closeStorePresenterIfExists(id) - -getVectorDatabasePresenter(id, dimensions, normalized) - -initStorageDir() - -setupEventBus() - } - - class KnowledgeStorePresenter { - -vectorP: IVectorDatabasePresenter - -config: BuiltinKnowledgeConfig - -taskP: IKnowledgeTaskPresenter - -fileProgressMap: Map - +addFile(filePath, fileId) - +deleteFile(fileId) - +reAddFile(fileId) - +queryFile(fileId) - +listFiles() - +similarityQuery(key) - +close() - +destroy() - +updateConfig(config) - +getVectorPresenter() - +pauseAllRunningTasks() - +resumeAllPausedTasks() - -processFileAsync(fileMessage) - -processChunkTask(chunkMsg, signal) - -handleChunkCompletion() - -onFileFinish() - } - - class KnowledgeTaskPresenter { - -queue: KnowledgeChunkTask[] - -controllers: Map - -isProcessing: boolean - -currentTask: KnowledgeChunkTask | null - +addTask(task) - +removeTasks(filter) - +cancelTasksByKnowledgeBase(knowledgeBaseId) - +cancelTasksByFile(fileId) - +cancelTasksByChunk(chunkId) - +getTaskStatus() - +getStatus() - +hasActiveTasks() - +hasActiveTasksForKnowledgeBase(knowledgeBaseId) - +hasActiveTasksForFile(fileId) - +terminateTask(taskId) - -processQueue() - } - - class IVectorDatabasePresenter { - <> - +initialize(dimensions, opts) - +open() - +close() - +destroy() - +insertFile(file) - +updateFile(file) - +deleteFile(id) - +queryFiles(filter) - +insertChunks(chunks) - +updateChunk(chunk) - +updateChunkStatus(chunkId, status) - +deleteChunks(fileId) - +insertVector(opts) - +similarityQuery(vector, options) - +executeInTransaction(operation) - } - - class DuckDBPresenter { - -dbInstance: DuckDBInstance - -connection: DuckDBConnection - -dbPath: string - -vectorTable: string - -fileTable: string - -chunkTable: string - -metadataTable: string - +initialize(dimensions, opts) - +create() - +open() - +close() - +destroy() - +insertFile(file) - +updateFile(file) - +deleteFile(id) - +queryFiles(filter) - +insertChunks(chunks) - +updateChunk(chunk) - +updateChunkStatus(chunkId, status) - +deleteChunks(fileId) - +insertVector(opts) - +similarityQuery(vector, options) - +executeInTransaction(operation) - +safeRun(sql, params) - +getDbVersion() - +setDbVersion(version) - +checkAndRunMigrations() - -installAndLoadExtension(name, postLoad) - -initMetadataTable() - -initFileTable() - -initChunkTable() - -initVectorTable(dimensions, opts) - } - - class IConfigPresenter { - <> - +getKnowledgeConfigs() - +setKnowledgeConfigs(configs) - +diffKnowledgeConfigs(newConfigs) - // ... other config methods - } - - KnowledgePresenter ..|> IKnowledgePresenter - KnowledgePresenter o-- IConfigPresenter - KnowledgePresenter o-- KnowledgeTaskPresenter : 全局共享 - KnowledgePresenter "1" *-- "0..*" KnowledgeStorePresenter : 管理实例 - - KnowledgeStorePresenter o-- IVectorDatabasePresenter - KnowledgeStorePresenter o-- KnowledgeTaskPresenter : 全局共享 - - DuckDBPresenter ..|> IVectorDatabasePresenter -``` - -## 数据流 - -### 1. 初始化与配置管理 - -```mermaid -sequenceDiagram - participant AppStartup - participant KnowledgePresenter - participant IConfigPresenter - participant EventBus - - AppStartup->>KnowledgePresenter: constructor(configPresenter, dbDir) - KnowledgePresenter->>KnowledgePresenter: initStorageDir() - KnowledgePresenter->>KnowledgePresenter: setupEventBus() - KnowledgePresenter->>EventBus: on(MCP_EVENTS.CONFIG_CHANGED) - EventBus->>KnowledgePresenter: CONFIG_CHANGED event - KnowledgePresenter->>IConfigPresenter: diffKnowledgeConfigs(newConfigs) - IConfigPresenter-->>KnowledgePresenter: { added, updated, deleted } - KnowledgePresenter->>IConfigPresenter: setKnowledgeConfigs(configs) - loop For each deleted config - KnowledgePresenter->>KnowledgePresenter: delete(configId) - end - loop For each added config - KnowledgePresenter->>KnowledgePresenter: create(config) - end - loop For each updated config - KnowledgePresenter->>KnowledgePresenter: update(config) - end -``` - -### 2. 文件添加与处理流程 - -```mermaid -sequenceDiagram - participant UI - participant KnowledgePresenter - participant KnowledgeStorePresenter - participant KnowledgeTaskPresenter - participant DuckDBPresenter - participant LLMProviderPresenter - participant EventBus - - UI->>KnowledgePresenter: addFile(id, filePath) - KnowledgePresenter->>KnowledgePresenter: getOrCreateStorePresenter(id) - KnowledgePresenter->>KnowledgeStorePresenter: addFile(filePath) - - KnowledgeStorePresenter->>DuckDBPresenter: queryFiles({path: filePath}) - DuckDBPresenter-->>KnowledgeStorePresenter: existingFile[] - alt File already exists - KnowledgeStorePresenter-->>KnowledgePresenter: {data: existingFile} - else New file - KnowledgeStorePresenter->>DuckDBPresenter: insertFile(fileMessage) (status: 'processing') - KnowledgeStorePresenter->>EventBus: sendToRenderer(FILE_UPDATED) - - Note right of KnowledgeStorePresenter: 异步处理开始 - KnowledgeStorePresenter->>KnowledgeStorePresenter: processFileAsync(fileMessage) - KnowledgeStorePresenter->>KnowledgeStorePresenter: 读取文件、分片 - KnowledgeStorePresenter->>DuckDBPresenter: updateFile(totalChunks) - KnowledgeStorePresenter->>EventBus: sendToRenderer(FILE_UPDATED) - KnowledgeStorePresenter->>DuckDBPresenter: insertChunks(chunkMessages) - - loop 为每个分片创建任务 - KnowledgeStorePresenter->>KnowledgeTaskPresenter: addTask(chunkTask) - end - - Note over KnowledgeTaskPresenter: 任务队列串行执行 - KnowledgeTaskPresenter->>KnowledgeTaskPresenter: processQueue() - KnowledgeTaskPresenter->>KnowledgeStorePresenter: task.run() -> processChunkTask() - KnowledgeStorePresenter->>LLMProviderPresenter: getEmbeddings(content) - LLMProviderPresenter-->>KnowledgeStorePresenter: vectors - KnowledgeStorePresenter->>DuckDBPresenter: executeInTransaction(updateChunk, insertVector) - DuckDBPresenter-->>KnowledgeStorePresenter: Success - KnowledgeStorePresenter->>KnowledgeStorePresenter: handleChunkCompletion() - KnowledgeStorePresenter->>EventBus: sendToRenderer(FILE_PROGRESS) - - alt 所有分片处理完毕 - KnowledgeStorePresenter->>KnowledgeStorePresenter: onFileFinish() - KnowledgeStorePresenter->>DuckDBPresenter: updateFile(status: 'completed') - KnowledgeStorePresenter->>EventBus: sendToRenderer(FILE_UPDATED) - end - end -``` - -### 3. 相似度检索流程 - -```mermaid -sequenceDiagram - participant User - participant ThreadPresenter - participant KnowledgePresenter - participant KnowledgeStorePresenter - participant DuckDBPresenter - participant LLMProviderPresenter - - User->>ThreadPresenter: 提问 - ThreadPresenter->>KnowledgePresenter: similarityQuery(knowledgeBaseId, query) - KnowledgePresenter->>KnowledgePresenter: getOrCreateStorePresenter(id) - KnowledgePresenter->>KnowledgeStorePresenter: similarityQuery(query) - KnowledgeStorePresenter->>LLMProviderPresenter: getEmbeddings(query) - LLMProviderPresenter-->>KnowledgeStorePresenter: queryVector - KnowledgeStorePresenter->>DuckDBPresenter: similarityQuery(queryVector, options) - DuckDBPresenter-->>KnowledgeStorePresenter: QueryResult[] - KnowledgeStorePresenter-->>KnowledgePresenter: QueryResult[] - KnowledgePresenter-->>ThreadPresenter: QueryResult[] - ThreadPresenter->>ThreadPresenter: 将结果注入 Prompt - ThreadPresenter->>LLMProviderPresenter: generateAnswer(promptWithContext) - LLMProviderPresenter-->>ThreadPresenter: 回答 - ThreadPresenter->>User: 显示回答 -``` - -## 关键设计 - -作为知识库功能的统一入口和协调器,负责: - -- **生命周期管理**: 监听全局配置 (`IConfigPresenter`) 的变化,动态地创建、更新和销毁 `KnowledgeStorePresenter` 实例。 -- **实例缓存**: 缓存活跃的 `KnowledgeStorePresenter` 实例,避免重复创建,提高响应速度。 -- **API 路由**: 将来自上层(如UI)的请求(如 `addFile`, `similarityQuery`)路由到正确的 `KnowledgeStorePresenter` 实例。 -- **数据库管理**: 管理 `DuckDBPresenter` 实例的创建和初始化。 - -### 2. KnowledgeStorePresenter (存储管理层) - -负责单个知识库实例的具体管理,是文件处理和检索的核心。职责包括: - -- **文件处理流程**: 实现文件的添加、删除、重新处理的完整逻辑。 -- **分片与任务创建**: 读取文件内容,使用 `RecursiveCharacterTextSplitter` 进行分片,并为每个分片创建异步处理任务。 -- **数据库交互**: 通过 `IVectorDatabasePresenter` 接口,将文件元数据、分片和向量持久化到数据库。 -- **进度跟踪**: 维护一个 `fileProgressMap` 来实时跟踪每个文件所有分片的处理进度,并通过 `eventBus` 通知前端。 -- **相似度检索**: 调用 `IVectorDatabasePresenter` 执行向量检索,并对结果进行格式化。 - -### 3. KnowledgeTaskPresenter (任务调度层) - -一个全局的、单例的任务调度器,用于处理所有知识库的后台任务。职责包括: - -- **任务队列**: 维护一个先进先出(FIFO)的 `KnowledgeChunkTask` 队列。 -- **串行执行**: 确保所有数据库写操作相关的任务(如向量生成和存储)串行执行,从根本上避免了数据库的并发写入问题。 -- **任务控制**: 提供基于知识库ID、文件ID或分片ID取消任务的能力,以支持删除、禁用等操作。 -- **状态报告**: 提供查询当前任务队列状态的接口。 - -### 4. DuckDBPresenter (数据库访问层) - -封装了与 DuckDB 向量数据库的所有交互细节。职责包括: - -- **数据库生命周期**: 管理数据库的初始化 (`initialize`)、连接 (`open`)、关闭 (`close`) 和销毁 (`destroy`)。 -- **模式与迁移**: 定义数据库表结构(`file`, `chunk`, `vector`, `metadata`),并包含一个简单的迁移系统来处理未来的模式变更。 -- **CRUD 操作**: 封装对所有表的增删改查操作。 -- **事务管理**: 实现了一个健壮的 `executeInTransaction` 机制,将多个操作打包到单个事务中,确保数据一致性,并在失败时自动回滚。 -- **异常恢复**: 在启动时检查 WAL (Write-Ahead Logging) 文件,能够自动修复因异常关闭导致的索引损坏问题,并清理脏数据。 - -## 关键设计 - -1. **分层架构**: - * 接口层 (`IKnowledgePresenter`): 定义公共 API。 - * 协调层 (`KnowledgePresenter`): 生命周期管理,配置同步和实例缓存。 - * 存储层 (`KnowledgeStorePresenter`): 文件管理,向量化处理和检索逻辑。 - * 调度层 (`KnowledgeTaskPresenter`): 全局任务队列,串行执行和并发控制。 - * 数据层 (`DuckDBPresenter`): 向量数据库操作,事务管理和持久化存储。 - -2. **全局串行任务队列**: - * **问题**: 并发处理多个文件的分片时,会产生大量的数据库写入请求,容易导致数据库锁死、写入失败或数据不一致。 - * **决策**: 引入一个全局的、单例的 `KnowledgeTaskPresenter`。所有知识库实例共享这一个任务队列。 - * **优势**: - * **简化并发控制**: 所有与数据库写入相关的耗时操作(向量生成、数据入库)都被放入队列中串行执行,从根本上避免了并发写入问题。 - * **资源可控**: 可以控制任务的并发数(当前为1),避免因大量并行任务消耗过多 CPU 和内存。 - * **易于管理**: 提供了统一的任务管理入口,可以方便地取消、暂停和查询任务状态。 - -3. **事务性数据库操作**: - * **问题**: 数据库操作(如更新分片状态、插入向量)可能因为各种原因失败。如果这些操作不是原子性的,会导致数据状态不一致(例如,向量已插入但分片状态未更新)。 - * **决策**: 在 `DuckDBPresenter` 中实现 `executeInTransaction` 方法。所有关联的数据库写操作都被包裹在这个方法中,作为一个事务来执行。 - * **优势**: - * **数据一致性**: 保证了操作的原子性。事务内的任何一步失败,整个事务都会回滚,数据库恢复到操作前的状态。 - * **性能提升**: 将多个离散的写操作合并为一次提交,减少了 I/O 次数,提高了性能。 - -4. **异步处理与实时反馈**: - * 文件添加立即返回,后台异步处理文件分片和向量化。 - * 通过 `eventBus` 实时推送文件状态更新和处理进度。 - * 支持任务取消、暂停和恢复操作。 - -5. **配置驱动与持久化**: - * 行为由 `IConfigPresenter` 管理的配置驱动。 - * 支持知识库配置的热更新,动态创建、更新和删除知识库实例。 - * 使用 `electron-store` 进行配置持久化。 - -6. **错误处理与恢复机制**: - * **异常关闭恢复**: 应用崩溃时自动处理 DuckDB WAL 文件,防止索引损坏。 - * **数据库迁移**: 支持数据库版本管理和自动迁移。 - * **文件重新处理**: 支持文件状态重置和重新向量化。 - -7. **性能优化**: - * 实例缓存管理,避免重复创建数据库连接。 - * 向量检索使用 HNSW 索引,支持高效相似度搜索。 - * 分片大小和重叠度可配置,支持不同类型文档的优化。 diff --git a/docs/builtin-knowledge-design.md b/docs/builtin-knowledge-design.md deleted file mode 100644 index 2e2f42980..000000000 --- a/docs/builtin-knowledge-design.md +++ /dev/null @@ -1,228 +0,0 @@ -# Knowledge Presenter 设计文档 - -## 1. 核心类设计 - -### 1.1 KnowledgePresenter - -`KnowledgePresenter` (`src/main/presenter/knowledgePresenter/index.ts`) 是模块的主入口,实现了 `IKnowledgePresenter` 接口,主要职责: - -- 依赖 `IConfigPresenter` 获取知识库配置。 -- 初始化并管理 `KnowledgeStorePresenter` 实例和 `KnowledgeTaskPresenter`。 -- **初始化流程**: - - 创建知识库存储目录。 - - 监听配置变更事件,动态管理知识库实例。 -- 提供知识库生命周期管理 (创建/更新/删除)、文件管理和检索的接口。 -- 管理 `KnowledgeStorePresenter` 实例的缓存,避免重复创建数据库连接。 -- 通过 `eventBus` 监听配置变更并触发相关事件。 - -**关键方法**: - -- `create()`, `update()`, `delete()`: 知识库生命周期管理。 -- `addFile()`, `deleteFile()`, `reAddFile()`, `queryFile()`, `listFiles()`: 文件管理。 -- `similarityQuery()`: 相似度检索。 -- `getTaskQueueStatus()`, `pauseAllRunningTasks()`, `resumeAllPausedTasks()`: 任务管理。 -- `closeAll()`, `destroy()`, `beforeDestroy()`: 资源清理。 -- `createStorePresenter()`, `getOrCreateStorePresenter()`, `closeStorePresenterIfExists()`: 实例管理。 -- `getVectorDatabasePresenter()`: 数据库实例创建。 - -### 1.2 KnowledgeStorePresenter - -`KnowledgeStorePresenter` (`src/main/presenter/knowledgePresenter/knowledgeStorePresenter.ts`) 负责单个知识库实例的管理: - -- **文件处理管理**: - - 维护文件的完整生命周期 (添加、处理、删除、重新处理)。 - - 异步文件处理,立即返回结果,后台执行分片和向量化。 - - 实时进度跟踪和状态更新。 -- **分片与向量化**: - - 使用 `RecursiveCharacterTextSplitter` 进行文件分片。 - - 与 `LLMProviderPresenter` 协作生成向量嵌入。 - - 通过 `KnowledgeTaskPresenter` 创建并管理向量化任务。 -- **数据库交互**: 通过 `IVectorDatabasePresenter` 接口进行所有数据库操作。 -- **事件通知**: 通过 `eventBus` 发送文件状态更新和处理进度事件。 - -### 1.3 KnowledgeTaskPresenter - -`KnowledgeTaskPresenter` (`src/main/presenter/knowledgePresenter/knowledgeTaskPresenter.ts`) 负责全局任务调度: - -- **任务队列管理**: - - 维护全局的 `KnowledgeChunkTask` 队列。 - - 串行执行所有任务,避免数据库并发写入问题。 - - 支持任务取消、过滤和状态查询。 -- **并发控制**: - - 使用 `AbortController` 管理任务的生命周期。 - - 提供基于知识库、文件或分片ID的批量任务操作。 -- **状态监控**: 提供详细的任务执行状态和统计信息。 - -### 1.4 DuckDBPresenter - -`DuckDBPresenter` (`src/main/presenter/knowledgePresenter/database/duckdbPresenter.ts`) 封装向量数据库操作: - -- **数据库生命周期管理**: 初始化、连接、关闭和销毁。 -- **数据表管理**: `file`, `chunk`, `vector`, `metadata` 四个核心表的操作。 -- **事务管理**: 实现 `executeInTransaction` 确保数据一致性。 -- **向量检索**: 使用 HNSW 索引进行高效相似度搜索。 -- **迁移系统**: 支持数据库版本管理和自动迁移。 -- **异常恢复**: 处理 WAL 文件和索引损坏问题。 - -## 2. 文件处理流程 - -```mermaid -sequenceDiagram - participant UI - participant KnowledgePresenter - participant KnowledgeStorePresenter - participant KnowledgeTaskPresenter - participant DuckDBPresenter - participant LLMProviderPresenter - participant EventBus - - Note over UI, EventBus: 1. 用户添加文件 - UI->>KnowledgePresenter: addFile(id, filePath) - KnowledgePresenter->>KnowledgePresenter: getOrCreateStorePresenter(id) - KnowledgePresenter->>KnowledgeStorePresenter: addFile(filePath) - - Note over KnowledgeStorePresenter, DuckDBPresenter: 2. 检查文件是否已存在 - KnowledgeStorePresenter->>DuckDBPresenter: queryFiles({path: filePath}) - DuckDBPresenter-->>KnowledgeStorePresenter: existingFile[] - alt File already exists - KnowledgeStorePresenter-->>KnowledgePresenter: {data: existingFile} - KnowledgePresenter-->>UI: 文件已存在 - else New file - Note over KnowledgeStorePresenter, EventBus: 3. 创建文件记录并立即返回 - KnowledgeStorePresenter->>DuckDBPresenter: insertFile(fileMessage) (status: 'processing') - KnowledgeStorePresenter->>EventBus: sendToRenderer(FILE_UPDATED) - KnowledgeStorePresenter-->>KnowledgePresenter: {data: fileMessage} - KnowledgePresenter-->>UI: 文件添加成功 - - Note over KnowledgeStorePresenter, EventBus: 4. 后台异步处理开始 - KnowledgeStorePresenter->>KnowledgeStorePresenter: processFileAsync(fileMessage) - KnowledgeStorePresenter->>KnowledgeStorePresenter: 读取文件基本信息 - KnowledgeStorePresenter->>KnowledgeStorePresenter: 使用 RecursiveCharacterTextSplitter 分片 - KnowledgeStorePresenter->>DuckDBPresenter: updateFile(totalChunks, size, mimeType) - KnowledgeStorePresenter->>EventBus: sendToRenderer(FILE_UPDATED) - - Note over KnowledgeStorePresenter, DuckDBPresenter: 5. 批量插入分片记录 - KnowledgeStorePresenter->>DuckDBPresenter: insertChunks(chunkMessages) - - Note over KnowledgeStorePresenter, KnowledgeTaskPresenter: 6. 创建向量化任务 - loop 为每个分片创建任务 - KnowledgeStorePresenter->>KnowledgeTaskPresenter: addTask(chunkTask) - end - - Note over KnowledgeTaskPresenter, EventBus: 7. 任务队列串行执行 - KnowledgeTaskPresenter->>KnowledgeTaskPresenter: processQueue() - loop 处理每个任务 - KnowledgeTaskPresenter->>KnowledgeStorePresenter: task.run() -> processChunkTask() - KnowledgeStorePresenter->>LLMProviderPresenter: getEmbeddings(content) - LLMProviderPresenter-->>KnowledgeStorePresenter: vectors - KnowledgeStorePresenter->>DuckDBPresenter: executeInTransaction(updateChunk, insertVector) - DuckDBPresenter-->>KnowledgeStorePresenter: Success - KnowledgeStorePresenter->>KnowledgeStorePresenter: handleChunkCompletion() - KnowledgeStorePresenter->>EventBus: sendToRenderer(FILE_PROGRESS) - end - - Note over KnowledgeStorePresenter, EventBus: 8. 文件处理完成 - alt 所有分片处理完毕 - KnowledgeStorePresenter->>KnowledgeStorePresenter: onFileFinish() - KnowledgeStorePresenter->>DuckDBPresenter: updateFile(status: 'completed') - KnowledgeStorePresenter->>EventBus: sendToRenderer(FILE_UPDATED) - end - end -``` - -**流程说明**: - -1. **添加文件**: UI 调用 `KnowledgePresenter.addFile()`,传入知识库ID和文件路径。 -2. **获取实例**: `KnowledgePresenter` 获取或创建对应的 `KnowledgeStorePresenter` 实例。 -3. **重复检查**: 检查文件是否已经存在于数据库中,避免重复处理。 -4. **立即返回**: 创建文件记录并立即返回给用户,提供快速响应。 -5. **异步处理**: 在后台异步执行文件读取、分片等预处理操作。 -6. **任务创建**: 为每个分片创建向量化任务,加入全局队列。 -7. **串行执行**: 任务队列确保所有向量化操作串行执行,避免数据库并发问题。 -8. **进度反馈**: 通过事件总线实时推送处理进度和状态更新。 -9. **完成标记**: 所有分片处理完成后,更新文件状态并通知前端。 -- **决策**: 在 `DuckDBPresenter.open()` 方法中增加了恢复逻辑。 -- **实现**: - 1. **检测 WAL**: 启动时检查是否存在 `.wal` 文件。 - 2. **内存中修复**: 如果存在,则先在内存中创建一个 DuckDB 实例,加载 `vss` 扩展,然后 `ATTACH` 到磁盘上的数据库文件。 - 3. **手动 Checkpoint**: 在内存实例中断开连接会自动触发 Checkpoint,将 WAL 文件合并回主数据库文件,从而完成修复。 - 4. **清理脏数据**: 修复后,清理那些在异常关闭时可能产生的孤立数据(如没有对应文件的向量或分片)。 - 5. **重置任务状态**: 将所有处于 `processing` 状态的文件和分片更新为 `paused`,等待用户决定是继续还是放弃。 - -### 4. 数据库模式设计 - -- **`file` 表**: 存储文件的核心元数据。`status` 字段用于跟踪文件的宏观状态。 -- **`chunk` 表**: 存储分片信息。同样包含 `status` 字段,用于跟踪每个分片的微观处理状态。 -- **`vector` 表**: 存储生成的向量。通过 `file_id` 和 `chunk_id` 与其他表关联。 -- **`metadata` 表**: 用于数据库版本控制和迁移。这是一个可扩展的设计,便于未来对数据库模式进行升级。 -- **索引**: 为关键字段(如 `id`, `file_id`, `status`)创建了索引,以加速查询和关联操作。向量列上创建了 `HNSW` 索引以支持高效的相似度检索。 - -## 3. 事件系统 - -Knowledge Presenter 通过 `eventBus` 发出以下事件: - -| 事件名称 | 触发时机 | 触发源 | 参数 | -| --------------------------- | -------------------------- | ------------------------- | ----------------------------------------- | -| `RAG_EVENTS.FILE_UPDATED` | 文件状态变更 | KnowledgeStorePresenter | KnowledgeFileMessage | -| `RAG_EVENTS.FILE_PROGRESS` | 文件处理进度更新 | KnowledgeStorePresenter | { fileId, progress, completed, error } | -| `RAG_EVENTS.CHUNK_UPDATED` | 分片状态变更 | KnowledgeStorePresenter | KnowledgeChunkMessage | -| `MCP_EVENTS.CONFIG_CHANGED` | 知识库配置变更 | ConfigPresenter | { mcpServers: { builtinKnowledge: ... } } | - -## 4. 配置管理 - -Knowledge 相关配置通过 `KnowledgeConfHelper` (`src/main/presenter/configPresenter/knowledgeConfHelper.ts`) 管理,并存储在配置系统中。 - -**核心配置项**: - -- `builtinKnowledge.configs`: `BuiltinKnowledgeConfig[]` - 存储所有已配置的知识库及其配置。 - -**`BuiltinKnowledgeConfig` 接口**: - -```typescript -interface BuiltinKnowledgeConfig { - id: string // 知识库唯一标识 - description: string // 知识库描述 - embedding: ModelProvider // 嵌入模型配置 - rerank?: ModelProvider // 重排序模型配置(可选) - dimensions: number // 向量维度 - normalized: boolean // 是否使用归一化向量 - chunkSize?: number // 分片大小(可选) - chunkOverlap?: number // 分片重叠度(可选) - fragmentsNumber: number // 检索返回的片段数量 - enabled: boolean // 是否启用该知识库 -} - -interface ModelProvider { - modelId: string // 模型ID - providerId: string // 提供商ID -} -``` - -配置管理还提供了知识库配置的比较 (`diffKnowledgeConfigs`) 和热更新能力,支持动态添加、删除和修改知识库。 - -## 5. 扩展指南 - -### 5.1 添加新的向量数据库支持 - -1. 实现 `IVectorDatabasePresenter` 接口。 -2. 在 `KnowledgePresenter.getVectorDatabasePresenter()` 中添加新数据库类型的创建逻辑。 -3. 更新配置项以支持新数据库的特定参数。 - -### 5.2 添加新的文件类型支持 - -1. 在 `FilePresenter` 中添加新文件类型的解析逻辑。 -2. 更新 `KnowledgeStorePresenter.processFileAsync()` 以处理新文件类型的特殊需求。 -3. 确保新文件类型能够正确提取文本内容进行分片。 - -### 5.3 自定义分片策略 - -1. 实现新的 TextSplitter 类,继承现有的分片接口。 -2. 在 `KnowledgeStorePresenter` 中根据文件类型或配置选择合适的分片策略。 -3. 更新配置接口以支持分片策略的选择和参数配置。 - -### 5.4 优化检索算法 - -1. 在 `KnowledgeStorePresenter.similarityQuery()` 中实现新的检索逻辑。 -2. 支持多种相似度计算方法(余弦相似度、欧几里得距离等)。 -3. 实现重排序(rerank)功能以提高检索精度。 -4. 添加查询扩展和上下文增强功能。 diff --git a/docs/builtin-knowledge.md b/docs/builtin-knowledge.md deleted file mode 100644 index acf0c989d..000000000 --- a/docs/builtin-knowledge.md +++ /dev/null @@ -1,188 +0,0 @@ -# Knowledge Presenter 功能文档 - -## 模块概述 - -Knowledge Presenter 是 DeepChat 中负责管理本地知识库的核心模块,它允许用户将本地文件无缝集成到与大语言模型的对话中。通过利用检索增强生成(RAG)技术,DeepChat 能够从用户提供的文档中提取相关信息,作为上下文(Context)来生成更准确、更具个性化的回答。该功能完全在本地运行,确保了用户数据的隐私和安全。 - -## 核心功能 - -1. **知识库管理**: 创建、配置、启用、禁用和删除多个独立的知识库实例。 -2. **文件管理**: 支持添加、删除和重新处理多种格式的文件(如 `.txt`, `.md`, `.pdf` 等)。 -3. **智能分片与向量化**: 文件被自动分割成优化的文本块,并通过嵌入模型转换为向量表示。 -4. **高效相似度检索**: 在对话中自动将用户问题转换为向量,在知识库中检索最相关的内容。 -5. **异步处理与实时反馈**: 文件处理在后台进行,提供实时状态更新和进度反馈。 - -## 设计目标 - -1. **无缝集成**: 将本地文件作为上下文源,自然地融入对话流程。 -2. **用户友好**: 提供清晰的文件管理界面,用户可以轻松添加、删除和查看文件状态。 -3. **高性能**: 文件处理(分片、向量化)在后台异步执行,不阻塞 UI,并提供实时进度反馈。 -4. **高准确性**: 通过优化的分片和检索策略,确保检索到的上下文与用户问题高度相关。 -5. **隐私安全**: 所有文件处理和数据存储均在本地完成,用户数据不会离开本地设备。 -6. **可扩展性**: 架构设计支持未来轻松扩展更多文件类型、检索策略和数据源。 - -## 用户交互流程 - -```mermaid -graph TD - A[用户配置知识库] --> B{Knowledge Presenter}; - B --> |1. 创建知识库实例| C[DuckDB 向量数据库]; - - D[用户选择文件] --> B; - B --> |2. 添加文件| E[文件处理与向量化]; - E --> |3. 存入本地数据库| C; - - F[用户提问] --> G{Thread Presenter}; - G --> |4. 查询知识库| B; - B --> |5. 相似度检索| C; - C --> |6. 返回相关文本块| B; - B --> |7. 返回检索结果| G; - G --> |8. 注入上下文| H[LLM 生成回答]; - H --> I[向用户展示回答]; -``` - -## 技术特性 - -### 并发控制与任务管理 - -- **全局串行任务队列**: 所有向量化任务通过单一队列串行执行,避免数据库并发写入问题。 -- **任务生命周期管理**: 支持任务的创建、取消、暂停和恢复操作。 -- **进度跟踪**: 实时监控文件处理进度,提供详细的状态反馈。 - -### 数据持久化与事务保证 - -- **DuckDB 向量数据库**: 使用高性能的 DuckDB 作为本地向量存储引擎。 -- **HNSW 向量索引**: 支持高效的近似最近邻搜索。 -- **事务性操作**: 所有数据库写操作都在事务中执行,确保数据一致性。 -- **异常恢复机制**: 自动处理应用异常关闭导致的数据库状态问题。 - -### 智能文本处理 - -- **递归字符分片**: 使用 `RecursiveCharacterTextSplitter` 进行智能文档分割。 -- **可配置分片参数**: 支持自定义分片大小、重叠度等参数。 -- **多模型支持**: 可配置不同的嵌入模型和重排序模型。 -- **文本预处理**: 自动处理文本清理和格式化。 - -## 配置说明 - -### 知识库配置 (`BuiltinKnowledgeConfig`) - -```typescript -interface BuiltinKnowledgeConfig { - id: string // 知识库唯一标识符 - description: string // 知识库描述信息 - embedding: { // 嵌入模型配置 - modelId: string // 模型ID (如: "text-embedding-3-small") - providerId: string // 提供商ID (如: "openai") - } - rerank?: { // 重排序模型配置 (可选) - modelId: string - providerId: string - } - dimensions: number // 向量维度 (如: 1536) - normalized: boolean // 是否使用归一化向量 - chunkSize?: number // 分片大小 (默认: 1000) - chunkOverlap?: number // 分片重叠度 (默认: 200) - fragmentsNumber: number // 检索返回的片段数量 (如: 5) - enabled: boolean // 是否启用该知识库 -} -``` - -### 支持的文件类型 - -- **纯文本** - - [x] `.txt` - - [x] `.md` - - [x] `.markdown` -- **文档格式** - - [x] `.pdf` (文本型) - - [x] `.docx` - - [x] `.pptx` -- **代码文件** - - [ ] `.js` - - [ ] `.ts` - - [ ] `.py` - - [ ] `.java` - - [ ] `.c` - - [ ] `.cpp` - - [ ] `.cc` - - [ ] `.cxx` - - [ ] `.h` - - [ ] `.hpp` - - [ ] `.hxx` - - [ ] `.hh` - - [ ] `.json` - - [ ] ... - -## 使用示例 - -### 1. 创建知识库 - -```javascript -// 配置知识库 -const config = { - id: "my-docs", - description: "我的文档知识库", - embedding: { - modelId: "text-embedding-3-small", - providerId: "openai" - }, - dimensions: 1536, - normalized: true, - fragmentsNumber: 5, - enabled: true -} - -// 通过配置系统创建知识库 -await configPresenter.setKnowledgeConfigs([config]) -``` - -### 2. 添加文件 - -```javascript -// 添加文件到知识库 -const result = await knowledgePresenter.addFile("my-docs", "/path/to/document.pdf") - -if (result.error) { - console.error("文件添加失败:", result.error) -} else { - console.log("文件添加成功:", result.data) -} -``` - -### 3. 检索相关内容 - -```javascript -// 在知识库中搜索相关内容 -const results = await knowledgePresenter.similarityQuery("my-docs", "用户查询文本") - -// 结果包含最相关的文本片段 -results.forEach(result => { - console.log("相关度:", result.score) - console.log("内容:", result.content) - console.log("来源文件:", result.source) -}) -``` - -## 性能优化建议 - -### 文件处理优化 - -- **批量添加**: 一次性添加多个文件比逐个添加更高效。 -- **文件大小**: 建议单个文件不超过 50MB,避免内存占用过高。 -- **分片配置**: 根据文档类型调整 `chunkSize` 和 `chunkOverlap` 参数。 - -### 检索优化 - -- **查询长度**: 查询文本建议在 50-200 字符之间,既有足够的语义信息又不会过于复杂。并根据文档长度合理考虑切分方式。 -- **片段数量**: `fragmentsNumber` 建议设置为 3-10,平衡检索质量和响应速度。 -- **重排序**: 对于关键应用,启用 `rerank` 模型可以显著提高检索精度。 - -## 未来计划 - -- [ ] 文件批量上传逻辑优化 -- [ ] 支持更多文件类型 -- [ ] 实现更多的文档切分方式 -- [ ] 集成全文检索 -- [ ] 实现混合检索召回 -- [ ] 实现召回后调用Rerank模型排序 \ No newline at end of file diff --git a/docs/conversation-search-mcp.md b/docs/conversation-search-mcp.md deleted file mode 100644 index ace09e5bf..000000000 --- a/docs/conversation-search-mcp.md +++ /dev/null @@ -1,175 +0,0 @@ -# Conversation History Search MCP Server - -## Overview - -`deepchat-inmemory/conversation-search-server` is a built-in MCP server for searching and analyzing DeepChat's historical conversation records. It provides powerful search functionality that can find keywords in conversation titles and message contents, and also provides conversation statistics. - -## Features - -### 1. Conversation Search (`search_conversations`) -- Search conversation titles and message contents -- Support pagination queries -- Return matched conversation lists with search snippets -- Automatic merging and deduplication of results - -### 2. Message Search (`search_messages`) -- Search keywords in message contents -- Support filtering by conversation ID -- Support filtering by message role (user/assistant/system/function) -- Return matched messages with their context information - -### 3. Conversation History Retrieval (`get_conversation_history`) -- Get complete history of a specific conversation -- Optional inclusion of system messages -- Return conversation information and all message lists - -### 4. Statistics Information (`get_conversation_stats`) -- Get conversation statistics for a specified time period -- Count total conversations and messages -- Statistics by message role distribution -- Display most active conversation lists - -## Tool Details - -### search_conversations -Search historical conversation records, supports title and content search - -**Parameters:** -- `query` (string): Search keyword to search in conversation titles and message contents -- `limit` (number, optional): Result limit (1-50, default 10) -- `offset` (number, optional): Pagination offset (default 0) - -**Return Example:** -```json -{ - "conversations": [ - { - "id": "conv_123", - "title": "Python Programming Questions", - "createdAt": 1698765432000, - "updatedAt": 1698765500000, - "messageCount": 15, - "snippet": "Title match: Python Programming Questions" - } - ], - "total": 1 -} -``` - -### search_messages -Search historical message records, supports filtering by conversation ID, role and other conditions - -**Parameters:** -- `query` (string): Search keyword to search in message contents -- `conversationId` (string, optional): Optional conversation ID to limit search within specific conversation -- `role` (string, optional): Optional message role filter (user/assistant/system/function) -- `limit` (number, optional): Result limit (1-100, default 20) -- `offset` (number, optional): Pagination offset (default 0) - -**Return Example:** -```json -{ - "messages": [ - { - "id": "msg_456", - "conversationId": "conv_123", - "conversationTitle": "Python Programming Questions", - "role": "user", - "content": "How to use Python to process JSON data?", - "createdAt": 1698765450000, - "snippet": "How to use **Python** to process JSON data?" - } - ], - "total": 1 -} -``` - -### get_conversation_history -Get complete history of a specific conversation - -**Parameters:** -- `conversationId` (string): Conversation ID -- `includeSystem` (boolean, optional): Whether to include system messages (default false) - -**Return Example:** -```json -{ - "conversation": { - "id": "conv_123", - "title": "Python Programming Questions", - "createdAt": 1698765432000, - "updatedAt": 1698765500000, - "settings": { ... } - }, - "messages": [ - { - "id": "msg_456", - "role": "user", - "content": "How to use Python to process JSON data?", - "createdAt": 1698765450000, - "tokenCount": 12, - "status": "sent" - } - ] -} -``` - -### get_conversation_stats -Get conversation statistics including totals, recent activity and more - -**Parameters:** -- `days` (number, optional): Statistics period in days (default 30 days) - -**Return Example:** -```json -{ - "period": "30 days", - "total": { - "conversations": 150, - "messages": 2500 - }, - "recent": { - "conversations": 25, - "messages": 380 - }, - "messagesByRole": { - "user": 190, - "assistant": 185, - "system": 5 - }, - "activeConversations": [ - { - "id": "conv_123", - "title": "Python Programming Questions", - "messageCount": 15, - "lastActivity": "2023-10-31T10:30:00.000Z" - } - ] -} -``` - -## Use Cases - -1. **Quick Find Historical Conversations**: Search for relevant conversation records through keywords -2. **Content Review**: Find discussion content for specific topics or technical issues -3. **Data Analysis**: Understand conversation activity statistics and usage patterns -4. **Conversation Management**: Get complete conversation history for backup or analysis - -## Search Features - -- **Smart Snippet Generation**: Automatically generate search snippets containing keywords, with keywords highlighted using `**` surrounding -- **Result Deduplication**: Automatically merge title matches and content matches to avoid duplicates -- **Pagination Support**: Support pagination queries for large result sets -- **Role Filtering**: Can search only specific role messages (e.g., only user input or assistant replies) -- **Time Range**: Statistics function supports custom time ranges - -## Permission Configuration - -This server is configured to auto-approve all tool calls (`autoApprove: ['all']`) because it only performs read-only operations and does not modify any data. - -## Technical Implementation - -- Uses SQLite database for efficient full-text search -- Fuzzy matching based on LIKE operators -- Supports JOIN queries to associate conversation and message tables -- Automatic handling of database connections and error recovery \ No newline at end of file diff --git a/docs/data-sync-feature.md b/docs/data-sync-feature.md deleted file mode 100644 index c8b2fe9fb..000000000 --- a/docs/data-sync-feature.md +++ /dev/null @@ -1,92 +0,0 @@ -# Deepchat 数据同步功能说明文档 - -## 功能概述 - -数据同步功能允许用户将应用程序的数据(包括聊天记录和配置信息)备份到指定的同步文件夹中,并可以在其他设备上导入这些数据。这个功能主要用于以下场景: - -1. 数据备份:防止数据丢失 -2. 跨设备同步:在多台设备之间共享相同的聊天记录和配置 -3. 迁移数据:更换设备或重装系统时迁移数据 - -## 技术实现 - -### 核心组件 - -1. **SyncPresenter**:负责处理同步逻辑的主要类,包括备份和导入功能 -2. **ConfigPresenter扩展**:添加了同步相关的配置项 -3. **DataSettings.vue**:提供用户界面,允许用户控制同步设置 - -### 数据流程 - -#### 备份流程 - -1. 用户启用同步功能并设置同步文件夹 -2. 当数据发生变化时(如添加/修改消息、创建会话等),系统会在一段时间无变更后(默认60秒)自动触发备份 -3. 备份过程中会创建临时文件,完成后再替换最终文件,避免导入过程中的文件冲突 -4. 备份的数据包括: - - 数据库文件(chat.db) - - 配置文件(xxx.json,provider-models/xxx.json) - -#### 导入流程 - -1. 用户在设置界面中点击"导入数据" -2. 系统会检查同步文件夹中是否存在有效的备份文件 -3. 导入前会先备份当前数据,以便导入失败时恢复 -4. 导入完成后需要重启应用以应用更改 - -### 安全措施 - -1. 备份过程使用临时文件,避免导入过程中的文件冲突 -2. 导入前会备份当前数据,确保可以恢复 -3. 同步文件夹路径不会被同步,避免在不同设备上路径冲突 -4. 备份和导入操作有完整的错误处理和状态反馈 - -## 用户界面 - -数据同步功能的用户界面位于设置页面的"数据"选项卡中,包括以下功能: - -1. 同步功能开关:启用/禁用同步功能 -2. 同步文件夹设置:选择数据备份的目标文件夹 -3. 打开同步文件夹:直接打开文件浏览器查看同步文件夹 -4. 上次同步时间:显示最近一次成功备份的时间 -5. 手动备份:手动触发数据备份 -6. 导入数据:从同步文件夹导入数据(默认增量导入) - - 增量导入:聊天数据增量导入,不覆盖当前聊天数据,对同一聊天数据不会重复导入。如:导入1月1日备份的数据后再次导入1月2日的备份,不会导致1月1日的对话出现两次 - - 覆盖导入:覆盖当前聊天数据,导入新数据 - -## 事件系统 - -系统使用事件机制来通知备份和导入的状态变化: - -1. `sync:backup-started`:备份开始 -2. `sync:backup-completed`:备份完成 -3. `sync:backup-error`:备份出错 -4. `sync:import-started`:导入开始 -5. `sync:import-completed`:导入完成 -6. `sync:import-error`:导入出错 - -这些事件在主进程和渲染进程之间传递,确保用户界面能够实时反映同步状态。 - -## 配置项 - -同步功能相关的配置项包括: - -1. `syncEnabled`:是否启用同步功能(布尔值) -2. `syncFolderPath`:同步文件夹路径(字符串) -3. `lastSyncTime`:上次同步时间(时间戳) - -这些配置项存储在应用程序的配置文件中,但在备份时会过滤掉`syncFolderPath`,避免在不同设备上路径冲突。 - -## 使用建议 - -1. 选择稳定的存储位置作为同步文件夹,如云盘同步目录 -2. 定期检查备份状态,确保数据正常备份 -3. 导入数据前先确保应用程序没有重要的未保存数据 -4. 导入完成后重启应用程序以确保所有更改生效 - -## 限制和注意事项 - -1. 同步功能不会实时同步数据,而是在数据变更后的一段时间(默认60秒)进行备份(暂时禁用,会导致比较多的io,目前性价比较低) -2. 导入操作会替换当前的所有数据,包括聊天记录和配置 -3. 同步文件夹路径不会被同步,需要在每台设备上单独设置 -4. 导入数据后需要重启应用程序才能完全应用更改 diff --git a/docs/deepchat_tool_use_document.md b/docs/deepchat_tool_use_document.md deleted file mode 100644 index e56a5a20e..000000000 --- a/docs/deepchat_tool_use_document.md +++ /dev/null @@ -1,166 +0,0 @@ - -# DeepChat Tool Use 技术文档 - -## 背景与目标 - -随着 LLM (大模型) 支持 Function Calling 能力的增强,DeepChat 通过**提示词工程(prompt engineering)**,即使在不依赖原生 function calling 的情况下,也能**模拟 Tool Use 行为**。 - -设计目标: -- 通过标准化 prompt 包装,引导模型以规范格式调用 Tool -- 适配各类 LLM,包括不支持原生 Function Calling 的模型 -- 支持扩展到多 Tool 使用和复杂调度场景 - ---- - -## 结构概览 - -| 组件 | 说明 | -| :---------------------------- | :-------------------------------------------------------------- | -| `baseProvider.ts` | 定义基础 prompt 封装和 Tool Use 逻辑 | -| `openAICompatibleProvider.ts` | 实现与 OpenAI 等接口兼容的交互和 function call 解析 | -| 核心函数 | `getFunctionCallWrapPrompt`、`coreStream`、`parseFunctionCalls` | - ---- - -## 总流程概览 - -1. **Prompt 封装**:用 `getFunctionCallWrapPrompt` 包装用户输入和 Tools -2. **流式处理**:用 `coreStream` 接收模型返回的 delta 流 -3. **函数调用解析**:用 `parseFunctionCalls` 从自然语言中提取 Tool Call JSON - ---- - -## 核心模块详解 - -### 1. getFunctionCallWrapPrompt(prompt, functions) - -**功能**: -> 将原始用户 prompt 和可用 Tools 打包,引导 LLM 按指定 JSON 格式返回 Tool Call。 - -**主要逻辑**: -- 列出全部函数(包括名称和参数格式) -- 定义规范格式,比如: -```json -{ "tool_name": "xxx", "parameters": { "key": "value" } } -``` -- 插入原始用户输入,保持连贯自然 - -**核心思想**: -让不支持原生 Function Calling 的模型也能理解“可以调用工具”。 - ---- - -### 2. coreStream(config) - -**功能**: -> 负责流式向 LLM 发送请求,同时流式接收 delta 数据并实时处理。 - -**处理细节**: -- 每次接收 delta: - - 检测是否包含 `content` - - 将每个字符段重新组合,保证符合 JSON 格式 - - 封装成新的 reply 字符串,防止丢片或乱序 -- 重组处理: - - 遇到 Tool Call JSON 特征(如 `{ "tool_name"` 开头) - - 将可能被切断的文本段进行合并 -- 检测到完整 Tool Call JSON,立刻调用 `parseFunctionCalls` - -**状态机**: -流式数据处理过程采用状态机模型来逐步处理返回的 delta 数据。 - -1. **接收 delta**:进入接收状态,分析是否含有 `content`。 -2. **抽取与拼接**:若 delta 数据不完整,暂时保存并与后续数据拼接。 -3. **判断工具调用**:检查 delta 是否符合工具调用的 JSON 格式,如果是,则进入工具调用解析状态。 -4. **工具调用解析**:调用 `parseFunctionCalls` 解析工具调用 JSON 格式,提取参数并进行处理。 -5. **完成调用**:返回解析后的结果,或继续处理剩余内容。 - -```mermaid -stateDiagram-v2 - [*] --> 接收Delta - 接收Delta --> 抽取拼接 - 抽取拼接 --> 判断工具调用 - 判断工具调用 --> 工具调用解析 : 是 - 判断工具调用 --> 继续累加 : 否 - 工具调用解析 --> 完成调用 - 继续累加 --> [*] -``` - ---- - -### 3. parseFunctionCalls(text) - -**功能**: -> 从自然语言输出中,提取符合格式的 Tool Call JSON,并解析成标准 JS Object。 - -**主要逻辑**: -- 正则匹配 `{...}` 结构 -- 支持多 Tool Call 同时存在 -- 对异常 JSON(超出字符、缺引号等)进行容错修正 - ---- - -## 总体流程图 (Mermaid) - -```mermaid -flowchart TD - A[用户输入 prompt] --> B[getFunctionCallWrapPrompt] - B --> C[打包后的 prompt] - C --> D[coreStream 流式发送] - D --> E[LLM 返回 delta] - E --> F{delta 有内容吗} - F -- 是 --> G[抽取字符、重组] - G --> H{包含 Tool Call 吗} - H -- 是 --> I[parseFunctionCalls] - H -- 否 --> J{是否有可能组成 Tool Call} - J -- 是 --> K[继续累加内容] - J -- 否 --> L[发送累加内容并清空] - I --> M[返回 Tool Call result] - K --> G - L --> G -``` ---- - -## 时序图 (Mermaid) - -```mermaid -sequenceDiagram - participant User - participant Frontend - participant BaseProvider - participant LLM - participant Parser - - User->>Frontend: 提交 prompt - Frontend->>BaseProvider: getFunctionCallWrapPrompt(prompt, functions) - BaseProvider-->>Frontend: 返回打包后 prompt - - Frontend->>LLM: 发送请求 - LLM-->>Frontend: 流式返回 delta - - loop 流式处理 - Frontend->>Frontend: 抽取字符、重组 - Frontend->>Frontend: 检测是否有 Tool Call JSON - alt 检测到 Tool Call - Frontend->>Parser: parseFunctionCalls(text) - Parser-->>Frontend: 返回函数调用结果 - end - end - - Frontend->>User: 展示最终结果 -``` - ---- - -## 设计亮点 - -- **Prompt 智能封装**:适配各类模型,不依赖原生技术 -- **流式处理精细**:字符级重组与校验 -- **Tool Call 高容错解析**:支持不规则、复杂、多重并发 Tool Use - ---- - -## 后续可增强方向 - -- 自适应 Prompt 调整,根据模型类型高效引导 -- 支持嵌套式 Tool Call(工具内部再调工具) -- 多轮对话中的 Tool Use 继承与独立管理 diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 85da8d172..d3de806db 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -128,7 +128,7 @@ This guide should provide a good starting point for developers. For specific que - Project Structure: Based on `ls()` output and `CONTRIBUTING.md`. - Architecture Overview: Explaining Electron's main/renderer architecture, the tech stack (Vue.js, TypeScript), and linking to relevant documents in `docs/` that I identified earlier. - API Documentation: Pointing to `shared/presenter.d.ts` and `src/preload/index.d.ts`. -- Model Context Protocol (MCP): Explaining its purpose based on `README.md` and linking to `docs/function-call-and-mcp.md` and other MCP-specific architectural documents. +- Model Context Protocol (MCP): Explaining its purpose based on `README.md` and linking to `docs/tool-calling-system.md` and other MCP-specific architectural documents. - Development Setup: Linking to the relevant sections in `README.md` and `CONTRIBUTING.md`. - Building the Application: Linking to the relevant section in `README.md`. - Contribution Guidelines: Linking to `CONTRIBUTING.md`. diff --git a/docs/dialog-presenter.md b/docs/dialog-presenter.md deleted file mode 100644 index 0bf510267..000000000 --- a/docs/dialog-presenter.md +++ /dev/null @@ -1,60 +0,0 @@ -# Dialog 模块文档 - -## 概述 -Dialog 模块用于在 Electron 应用中通过渲染进程展示消息对话框。该模块支持多按钮、超时自动选择、国际化等特性,确保在多标签页/窗口环境下的唯一性和交互一致性。 - -## 主要组成 - -### 1. 主进程 Presenter (`src/main/presenter/dialogPresenter/index.ts`) -- **DialogPresenter**:实现 `IDialogPresenter` 接口,负责: - - 生成唯一对话框请求(`DialogRequest`),通过 `eventBus` 发送到渲染进程。 - - 维护 pendingDialogs Map,确保同一窗口同一时刻只有一个对话框。 - - 处理渲染进程的响应(`DialogResponse`),或异常(如取消/超时)。 -- **核心方法**: - - `showDialog(request: DialogRequestParams): Promise`:发起对话框请求,返回 Promise,resolve 为按钮 key。 - - `handleDialogResponse(response: DialogResponse)`:收到响应后 resolve Promise。 - - `handleDialogError(id: string)`:异常时 reject Promise。 - -### 2. 渲染进程 Store (`src/renderer/src/stores/dialog.ts`) -- **Pinia Store**: - - 监听主进程的对话框请求事件(`DIALOG_EVENTS.REQUEST`)。 - - 管理对话框的显示、倒计时、响应与异常处理。 - - 支持超时自动选择默认按钮。 -- **核心属性/方法**: - - `dialogRequest`:当前对话框请求数据。 - - `showDialog`:对话框显示状态。 - - `timeoutMilliseconds`:倒计时剩余时间。 - - `handleResponse`:用户点击按钮或超时后响应主进程。 - - `handleError`:对话框异常处理。 - -### 3. 渲染进程 UI 组件 (`src/renderer/src/components/ui/MessageDialog.vue`) -- **功能**: - - 根据 `dialogRequest` 渲染对话框内容、按钮、图标。 - - 支持国际化(i18n)、倒计时显示、按钮自定义。 - - 用户点击按钮后调用 `handleResponse` 响应主进程。 -- **细节**: - - 支持多种按钮类型(默认/取消),超时自动触发默认按钮。 - - 支持图标自定义与国际化标题/描述。 - -## 典型流程 -1. 主进程调用 `showDialog`,通过 eventBus 发送请求到渲染进程。 -2. 渲染进程 Store 监听到请求,更新 `dialogRequest` 并显示对话框。 -3. 用户点击按钮或超时,Store 调用 `handleResponse`,通过 presenter 通知主进程。 -4. 主进程 resolve Promise,返回结果。 - -## 设计要点 -- **唯一性**:同一窗口同一时刻只允许一个对话框,重复请求会自动取消前一个。 -- **超时处理**:支持倒计时自动选择默认按钮。 -- **国际化**:支持 i18n 标题、描述、按钮。 -- **解耦**:主进程与渲染进程通过事件总线通信,UI 与逻辑分离。 - -## 相关文件 -- 主进程 Presenter:`src/main/presenter/dialogPresenter/index.ts` -- 渲染进程 Store:`src/renderer/src/stores/dialog.ts` -- 渲染进程 UI:`src/renderer/src/components/ui/MessageDialog.vue` - -## 参考 -- 事件定义:`@/events`、`@shared/presenter` -- 组件库:`@/components/ui/alert-dialog` -- 状态管理:`pinia` -- 国际化:`vue-i18n` diff --git a/docs/event-system-design.md b/docs/event-system-design.md deleted file mode 100644 index 1332951ed..000000000 --- a/docs/event-system-design.md +++ /dev/null @@ -1,100 +0,0 @@ -# 事件系统设计文档 - -## 问题背景 - -当前项目中的`provider-models-updated`事件存在混乱的情况,这个事件同时由两个不同的来源触发: - -1. **BaseLLMProvider**: 在处理模型时触发(如`addCustomModel`, `removeCustomModel`等方法) -2. **ConfigPresenter**: 在配置更改时触发(如`addCustomModel`, `removeCustomModel`等方法) - -这种设计导致了多种问题: -- 事件循环触发(导致死循环问题) -- 事件语义不清晰(同一事件表示不同的业务含义) -- 代码耦合度高且难以维护 - -## 事件分类与命名规范 - -将事件按功能领域分类,并采用统一的命名规范: - -1. **配置相关事件**: - - `config:provider-changed`:提供者配置变更 - - `config:system-changed`:系统配置变更 - - `config:model-list-changed`:配置中的模型列表变更 - -2. **模型相关事件**: - 全部去掉,模型状态和名称事件都有config来发起,和上层settings保持语义一致 - -3. **会话相关事件**: - - `conversation:created` - - `conversation:activated` - - `conversation:cleared` - -4. **通信相关事件**: - - `stream:response` - - `stream:end` - - `stream:error` - -5. **应用更新相关事件**: - - `update:status-changed` - - `update:progress` - - `update:error` - - `update:will-restart` - -6. **同步相关事件**: - - `sync:backup-started`:备份开始 - - `sync:backup-completed`:备份完成 - - `sync:backup-error`:备份出错 - - `sync:import-started`:导入开始 - - `sync:import-completed`:导入完成 - - `sync:import-error`:导入出错 - -## 责任分离 - -明确每个组件的事件触发责任: - -- **ConfigPresenter**:仅负责配置相关事件 -- **BaseLLMProvider**:仅负责模型操作,不发起事件 -- **ThreadPresenter**:仅负责会话相关事件 -- **UpgradePresenter**:仅负责应用更新相关事件 - -## 事件流时序图 - -### 当前事件流 - -``` -BaseLLMProvider ConfigPresenter Presenter(Main) Settings(Renderer) - | | | | - |--- provider-models-updated-->| | | - | |--- provider-models-updated----->| | - | | |--- provider-models-updated---->| - | | | |--- refreshProviderModels() - | | | | - |--- model-status-changed----->| | | - | |--- model-status-changed-------->| | - | | |--- model-status-changed------->| - | | | |--- updateLocalModelStatus() - | | | | - | |--- provider-setting-changed---->| | - | | |--- provider-setting-changed--->| - | | | |--- refreshAllModels() -``` - -### 重构后的事件流 - -``` -ConfigPresenter Presenter(Main) Settings(Renderer) - | | | - | | - |--- config:model-list-changed--->| | - | |--- config:model-list-changed-->| - | | |--- refreshProviderModels() - | | | - | | | - |--- model:status-changed-------->| | - | |--- model:status-changed------->| - | | |--- updateLocalModelStatus() - | | | - |--- config:provider-changed----->| | - | |--- config:provider-changed---->| - | | |--- refreshAllModels() -``` diff --git a/docs/filesystem-enhancements.md b/docs/filesystem-enhancements.md deleted file mode 100644 index 7fef3698e..000000000 --- a/docs/filesystem-enhancements.md +++ /dev/null @@ -1,296 +0,0 @@ -# Filesystem Service Enhancements - -This document describes the enhanced text file functionality added to the filesystem MCP server. - -## New Features - -### 1. Text Content Search (grep_search) - -Search for text patterns within file contents using regular expressions, similar to the Unix `grep` command. - -**Features:** -- Regular expression pattern matching -- Recursive directory traversal -- File name filtering with glob patterns -- Case-sensitive/insensitive search -- Context lines before and after matches -- Line number display -- Result limiting to prevent overwhelming output - -**Usage Example:** -```json -{ - "tool": "grep_search", - "arguments": { - "path": "/path/to/search", - "pattern": "function\\s+\\w+\\(", - "filePattern": "*.ts", - "recursive": true, - "caseSensitive": false, - "includeLineNumbers": true, - "contextLines": 2, - "maxResults": 50 - } -} -``` - -**Use Cases:** -- Finding function definitions across a codebase -- Searching for specific error messages or log patterns -- Locating configuration values -- Code analysis and refactoring preparation - -### 2. Text Pattern Replacement (text_replace) - -Replace text patterns in files using regular expressions with support for dry-run mode and diff preview. - -**Features:** -- Regular expression find and replace -- Global or first-match replacement -- Case-sensitive/insensitive matching -- Dry-run mode with git-style diff preview -- Automatic backup through diff display -- Error handling for invalid patterns - -**Usage Example:** -```json -{ - "tool": "text_replace", - "arguments": { - "path": "/path/to/file.ts", - "pattern": "oldFunctionName", - "replacement": "newFunctionName", - "global": true, - "caseSensitive": true, - "dryRun": true - } -} -``` - -**Use Cases:** -- Bulk text replacements across files -- Code refactoring (renaming variables, functions) -- Configuration updates -- Documentation updates -- Version number updates - -### 3. Enhanced Glob Search (glob_search) - -Advanced file pattern matching using glob patterns, inspired by Google Gemini CLI tools. - -**Features:** -- Powerful glob pattern matching (e.g., `**/*.ts`, `src/**/*.js`) -- Intelligent sorting by modification time (newest first) -- Git-aware filtering with .gitignore support -- Performance optimized for large codebases -- Result limiting and validation -- Security-conscious path validation - -**Usage Example:** -```json -{ - "tool": "glob_search", - "arguments": { - "pattern": "**/*.{ts,js}", - "path": "./src", - "caseSensitive": false, - "respectGitIgnore": true, - "sortByModified": true, - "maxResults": 100 - } -} -``` - -**Use Cases:** -- Finding files by complex patterns -- Locating recently modified files -- Bulk file operations preparation -- Project structure analysis - -### 4. Enhanced Directory Listing (list_directory) - -Comprehensive directory listing with detailed information, sorting, and filtering options. - -**Features:** -- Detailed file information (size, modification time, permissions) -- Multiple sorting options (name, size, modification time) -- Ignore patterns with glob support -- Git-aware filtering -- Directory-first sorting -- Human-readable file sizes - -**Usage Example:** -```json -{ - "tool": "list_directory", - "arguments": { - "path": "/path/to/directory", - "showDetails": true, - "sortBy": "modified", - "ignorePatterns": ["*.tmp", "node_modules"], - "respectGitIgnore": true - } -} -``` - -**Use Cases:** -- Detailed directory analysis -- Finding large files -- Identifying recently modified files -- Project cleanup and organization - -## Enhanced Existing Features - -### Improved File Editing -- Better diff display formatting -- Enhanced error messages -- More robust pattern matching - -### Better Error Handling -- Detailed error messages for invalid regex patterns -- Graceful handling of binary files -- Better permission error reporting - -## Security Features - -All new functionality maintains the existing security model: -- Path validation within allowed directories -- Symlink protection -- Permission checking -- Input sanitization - -## Performance Considerations - -- **grep_search**: Results are limited by `maxResults` parameter to prevent memory issues -- **text_replace**: Processes files in memory, suitable for text files up to several MB -- **glob_search**: Uses native glob library for optimal performance -- **list_directory**: Efficient stat operations with error handling -- **Context lines**: Limited context prevents excessive memory usage -- **Recursive search**: Respects directory permissions and skips unreadable directories - -## Error Handling - -All tools provide comprehensive error handling: -- Invalid regex pattern detection -- File permission issues -- Path validation failures -- Memory constraints -- Network/disk I/O errors - -## Integration with Existing Tools - -The new tools complement existing functionality: -- Use `search_files` to find files by name, then `grep_search` to search content -- Use `glob_search` for advanced pattern matching, then operate on results -- Use `grep_search` to find patterns, then `text_replace` to modify them -- Use `list_directory` with details to analyze directory contents -- Use dry-run mode in `text_replace` before making actual changes - -## Examples - -### Finding and Replacing Function Names -```bash -# 1. Find all TypeScript files with a specific function using glob -glob_search: - pattern: "**/*.ts" - path: "./src" - -# 2. Search for the function in those files -grep_search: - path: "./src" - pattern: "function oldName\\(" - filePattern: "*.ts" - -# 3. Preview the replacement -text_replace: - path: "./src/utils.ts" - pattern: "function oldName\\(" - replacement: "function newName(" - dryRun: true - -# 4. Apply the replacement -text_replace: - path: "./src/utils.ts" - pattern: "function oldName\\(" - replacement: "function newName(" - dryRun: false -``` - -### Project Analysis Workflow -```bash -# 1. Get detailed directory listing -list_directory: - path: "./src" - showDetails: true - sortBy: "size" - -# 2. Find recently modified files -glob_search: - pattern: "**/*" - path: "./src" - sortByModified: true - maxResults: 20 - -# 3. Search for TODO items -grep_search: - path: "./src" - pattern: "TODO|FIXME|HACK" - caseSensitive: false - includeLineNumbers: true -``` - -### Configuration Management -```bash -# 1. Find all config files -glob_search: - pattern: "**/*.{json,yml,yaml,env}" - respectGitIgnore: true - -# 2. Search for API keys -grep_search: - path: "./config" - pattern: "API_KEY.*=" - contextLines: 1 - -# 3. Update configuration values -text_replace: - path: "./config/app.json" - pattern: '"version":\\s*"[^"]*"' - replacement: '"version": "2.0.0"' - dryRun: true -``` - -## Best Practices - -1. **Always use dry-run first** when using `text_replace` on important files -2. **Limit search scope** with appropriate patterns to improve performance -3. **Test regex patterns** with small datasets first -4. **Use context lines** in `grep_search` to understand match context -5. **Set reasonable `maxResults`** to prevent overwhelming output -6. **Leverage glob patterns** for efficient file discovery -7. **Use detailed listing** for directory analysis and cleanup -8. **Combine tools** for powerful workflows -9. **Validate patterns** before running on production code - -## Migration from Basic Tools - -If you were previously using basic file operations, here's how to upgrade: - -- **File discovery**: Use `glob_search` instead of manual directory traversal -- **File content search**: Replace manual `read_file` + string search with `grep_search` -- **Text replacement**: Replace `read_file` + manual editing + `write_file` with `text_replace` -- **Directory analysis**: Use enhanced `list_directory` with details and sorting -- **Code analysis**: Combine `glob_search` and `grep_search` for comprehensive analysis - -## Tool Comparison - -| Feature | Basic Tools | Enhanced Tools | -|---------|-------------|----------------| -| File Search | `search_files` (simple patterns) | `glob_search` (advanced patterns, sorting) | -| Directory Listing | `list_directory` (basic) | `list_directory` (enhanced with details, sorting, filtering) | -| Content Search | Manual `read_file` + search | `grep_search` (regex, context, performance) | -| Text Editing | `edit_file` (line-based) | `text_replace` (pattern-based, dry-run) | -| Performance | Good for small projects | Optimized for large codebases | -| Features | Basic functionality | Advanced filtering, sorting, git-awareness | - -These enhancements provide powerful text processing and file discovery capabilities while maintaining the security and reliability of the original filesystem service. diff --git a/docs/function-call-and-mcp.md b/docs/function-call-and-mcp.md deleted file mode 100644 index 9a74bda7e..000000000 --- a/docs/function-call-and-mcp.md +++ /dev/null @@ -1,535 +0,0 @@ -# 从 MCP 到 Tool Use - -- [从 MCP 到 Tool Use](#从-mcp-到-tool-use) - - [MCP Tools 映射和定义](#mcp-tools-映射和定义) - - [Anthropic tool API和上下文组织格式](#anthropic-tool-api和上下文组织格式) - - [格式转换](#格式转换) - - [上下文组织](#上下文组织) - - [流式处理](#流式处理) - - [范例 Anthropic的Tool Use实现](#范例-anthropic的tool-use实现) - - [工具定义](#工具定义) - - [用户请求示例](#用户请求示例) - - [大模型响应](#大模型响应) - - [MCP 模块执行命令](#mcp-模块执行命令) - - [最终大模型结合上下文给出答案](#最终大模型结合上下文给出答案) - - [Gemini tool API 和上下文组织格式](#gemini-tool-api-和上下文组织格式) - - [格式转换](#格式转换-1) - - [上下文组织](#上下文组织-1) - - [流式处理](#流式处理-1) - - [范例:Gemini 的 Tool Use 实现](#范例gemini-的-tool-use-实现) - - [工具定义](#工具定义-1) - - [用户请求示例](#用户请求示例-1) - - [大模型响应(调用工具)](#大模型响应调用工具) - - [MCP 模块执行命令](#mcp-模块执行命令-1) - - [最终大模型结合上下文给出答案](#最终大模型结合上下文给出答案-1) - - [OpenAI tool API 和上下文组织格式](#openai-tool-api-和上下文组织格式) - - [格式转换](#格式转换-2) - - [上下文组织](#上下文组织-2) - - [流式处理](#流式处理-2) - - [范例:OpenAI 的 Tool Use 实现](#范例openai-的-tool-use-实现) - - [工具定义](#工具定义-2) - - [用户请求示例](#用户请求示例-2) - - [大模型响应(调用工具)](#大模型响应调用工具-1) - - [MCP 模块执行命令](#mcp-模块执行命令-2) - - [最终大模型结合上下文给出答案](#最终大模型结合上下文给出答案-2) - - [不支持 Tool Use 的模型如何用提示词工程来实现,流式内容如何正确解析函数信息](#不支持-tool-use-的模型如何用提示词工程来实现流式内容如何正确解析函数信息) - - [提示词包装](#提示词包装) - - [流式内容解析](#流式内容解析) - - [使用同样的getTime示例,提示词工程方案的流程如下:](#使用同样的gettime示例提示词工程方案的流程如下) - -## MCP Tools 映射和定义 - -MCP (Model Context Protocol) 是一种用于标准化与各种模型交互的协议。在本项目中,MCP工具定义通过`McpClient`类统一管理,为不同LLM提供商提供一致的工具调用接口。 - -MCP 工具的基本结构定义如下(官方额外还有一些注释字段,目前没用到): -```typescript -{ - name: string; // Unique identifier for the tool - description?: string; // Human-readable description - inputSchema: { // JSON Schema for the tool's parameters - type: "object", - properties: { ... } // Tool-specific parameters - } -} -``` - -通过`mcpClient.ts`的`callTool`方法,可以实现跨提供商的工具调用: -```typescript -async callTool(toolName: string, args: Record): Promise -``` - -工具调用结果遵循统一格式: -```typescript -interface ToolCallResult { - isError?: boolean; - content: Array<{ - type: string; - text: string; - }>; -} -``` - -当需要将MCP工具映射到不同提供商时,会通过以下流程: -1. 使用`presenter.mcpPresenter.mcpToolsToOpenAITools`、`presenter.mcpPresenter.mcpToolsToAnthropicTools`或`presenter.mcpPresenter.mcpToolsToGeminiTools`等方法进行转换 -2. 这些方法会将MCP工具的`inputSchema`转换为各提供商期望的参数格式 -3. 确保工具名称和描述在转换过程中保持一致 - -## Anthropic tool API和上下文组织格式 - -Anthropic的Tool API是通过`AnthropicProvider`类实现的,支持Claude 3系列中具备tool use能力的模型。 - -### 格式转换 -Anthropic要求工具定义通过`tools`参数传递,格式遵循以下结构: -```typescript -{ - tools: [ - { - name: string; - description: string; - input_schema: object; // JSON Schema格式 - } - ] -} -``` - -### 上下文组织 -Anthropic对消息格式有特殊要求,特别是工具调用相关的消息结构: -1. 系统消息(system):独立于对话消息,通过`system`参数传递 -2. 用户消息(user):包含`content`数组,可以包含文本和图像 -3. 助手消息(assistant):可以包含工具调用,使用`tool_use`类型的内容块 -4. 工具响应:作为用户消息的一部分,使用`tool_result`类型的内容块 - -`formatMessages`方法负责将标准聊天消息转换为Anthropic格式: -```typescript -private formatMessages(messages: ChatMessage[]): { - system?: string; - messages: Anthropic.MessageParam[]; -} -``` - -### 流式处理 -Claude API返回的工具调用事件包括: -- `content_block_start`(类型为`tool_use`):工具调用开始 -- `content_block_delta`(带有`input_json_delta`):工具参数流式更新 -- `content_block_stop`:工具调用结束 -- `message_delta`(带有`stop_reason: 'tool_use'`):因工具调用而停止生成 - -这些事件被转换为标准化的`LLMCoreStreamEvent`事件: -```typescript -{ - type: 'tool_call_start' | 'tool_call_chunk' | 'tool_call_end'; - tool_call_id?: string; - tool_call_name?: string; - tool_call_arguments_chunk?: string; - tool_call_arguments_complete?: string; -} -``` - -## 范例 Anthropic的Tool Use实现 -### 工具定义 -首先,定义一个getTime工具: -``` json -{ - "name": "getTime", - "description": "获取特定时间偏移量的时间戳(毫秒)。可用于获取过去或未来的时间。正数表示未来时间,负数表示过去时间。例如,要获取昨天的时间戳,使用-86400000作为偏移量(一天的毫秒数)。", - "input_schema": { - "type": "object", - "properties": { - "offset_ms": { - "type": "number", - "description": "相对于当前时间的毫秒数偏移量。负值表示过去时间,正值表示未来时间。" - } - }, - "required": ["offset_ms"] - } -} -``` -### 用户请求示例 -```json -{ - "role": "user", - "content": [ - { - "type": "text", - "text": "请告诉我昨天的日期是什么时候?" - } - ] -} -``` -### 大模型响应 -```json -{ - "role": "assistant", - "content": [ - { - "type": "text", - "text": "为了告诉您昨天的日期,我需要获取昨天的时间戳。" - }, - { - "type": "tool_use", - "id": "toolu_01ABCDEFGHIJKLMNOPQRST", - "name": "getTime", - "input": {"offset_ms": -86400000} - } - ] -} -``` -### MCP 模块执行命令 -```json -{ - "role": "user", - "content": [ - { - "type": "tool_result", - "tool_use_id": "toolu_01ABCDEFGHIJKLMNOPQRST", - "result": "1684713600000" - } - ] -} -``` -### 最终大模型结合上下文给出答案 -```json -{ - "role": "assistant", - "content": [ - { - "type": "text", - "text": "根据获取的时间戳1684713600000,昨天的日期是2023年5月22日。这个时间戳表示从1970年1月1日至昨天的毫秒数。" - } - ] -} -``` -## Gemini tool API 和上下文组织格式 - -Gemini通过`GeminiProvider`类实现工具调用功能,支持Gemini Pro及更新版本的模型。 - -### 格式转换 -Gemini要求工具定义传递为以下格式: -```typescript -{ - tools: [ - { - functionDeclarations: [ - { - name: string, - description: string, - parameters: object // OpenAPI格式的JSON Schema - } - ] - } - ] -} -``` - -### 上下文组织 -Gemini的消息结构相对简单,但有一些特殊处理: -1. 系统指令(systemInstruction):作为独立参数传递 -2. 内容数组(contents):包含用户和模型消息 -3. 工具调用:通过`functionCall`对象表示 -4. 工具响应:通过`functionResponse`对象表示 - -### 流式处理 -Gemini的流式响应需要处理以下特殊情况: -- `functionCall`对象表示工具调用开始 -- 函数参数通过`functionCall.args`对象传递 -- `functionCallResult`事件表示工具响应 - -这些事件同样被转换为标准的`LLMCoreStreamEvent`格式,方便统一处理。 - -## 范例:Gemini 的 Tool Use 实现 - -### 工具定义 -```json -{ - "tools": [ - { - "functionDeclarations": [ - { - "name": "getTime", - "description": "获取特定时间偏移量的时间戳(毫秒)。", - "parameters": { - "type": "object", - "properties": { - "offset_ms": { - "type": "number", - "description": "相对于当前时间的毫秒偏移量,负数表示过去,正数表示未来。" - } - }, - "required": ["offset_ms"] - } - } - ] - } - ] -} -``` - -### 用户请求示例 -```json -{ - "role": "user", - "parts": [ - { - "text": "请告诉我昨天的日期是什么时候?" - } - ] -} -``` - -### 大模型响应(调用工具) -```json -{ - "role": "model", - "parts": [ - { - "functionCall": { - "name": "getTime", - "args": { - "offset_ms": -86400000 - } - } - } - ] -} -``` - -### MCP 模块执行命令 -```json -{ - "role": "user", - "parts": [ - { - "functionResponse": { - "name": "getTime", - "response": 1684713600000 - } - } - ] -} -``` - -### 最终大模型结合上下文给出答案 -```json -{ - "role": "model", - "parts": [ - { - "text": "根据获取的时间戳1684713600000,昨天的日期是2023年5月22日。" - } - ] -} -``` - -## OpenAI tool API 和上下文组织格式 - -OpenAI的工具调用实现在`OpenAICompatibleProvider`类中,支持GPT-3.5-Turbo和GPT-4系列模型。 - -### 格式转换 -OpenAI的函数调用格式最为广泛使用: -```typescript -{ - tools: [ - { - type: "function", - function: { - name: string, - description: string, - parameters: object // JSON Schema格式 - } - } - ] -} -``` - -### 上下文组织 -OpenAI的消息格式比较标准化: -1. 消息数组(messages):包含role和content -2. 工具调用:记录在assistant消息中的`tool_calls`数组 -3. 工具响应:作为单独的`tool`角色消息,包含`tool_call_id`引用 - -### 流式处理 -OpenAI的流式事件包括: -- `tool_calls`数组表示工具调用 -- 流式API返回`delta.tool_calls`表示工具调用的增量更新 -- 流式工具参数通过`tool_calls[i].function.arguments`传递 - -这些事件同样被标准化为通用的`LLMCoreStreamEvent`格式。 - -## 范例:OpenAI 的 Tool Use 实现 - -### 工具定义 -```json -{ - "tools": [ - { - "type": "function", - "function": { - "name": "getTime", - "description": "获取特定时间偏移量的时间戳(毫秒)。", - "parameters": { - "type": "object", - "properties": { - "offset_ms": { - "type": "number", - "description": "相对于当前时间的毫秒偏移量,负数表示过去,正数表示未来。" - } - }, - "required": ["offset_ms"] - } - } - } - ] -} -``` - -### 用户请求示例 -```json -[ - { - "role": "user", - "content": "请告诉我昨天的日期是什么时候?" - } -] -``` - -### 大模型响应(调用工具) -```json -[ - { - "role": "assistant", - "content": null, - "tool_calls": [ - { - "id": "call_abc123", - "type": "function", - "function": { - "name": "getTime", - "arguments": "{ \"offset_ms\": -86400000 }" - } - } - ] - } -] -``` - -### MCP 模块执行命令 -```json -[ - { - "role": "tool", - "tool_call_id": "call_abc123", - "content": "1684713600000" - } -] -``` - -### 最终大模型结合上下文给出答案 -```json -[ - { - "role": "assistant", - "content": "根据获取的时间戳1684713600000,昨天的日期是2023年5月22日。" - } -] -``` - -## 不支持 Tool Use 的模型如何用提示词工程来实现,流式内容如何正确解析函数信息 - -对于不支持原生工具调用的模型,项目实现了基于提示词工程的替代方案: - -### 提示词包装 -在`OpenAICompatibleProvider`中的`prepareFunctionCallPrompt`方法实现了这一功能: -```typescript -private prepareFunctionCallPrompt( - messages: ChatCompletionMessageParam[], - mcpTools: MCPToolDefinition[] -): ChatCompletionMessageParam[] -``` - -该方法将工具定义作为指令添加到系统消息中,包括: -1. 工具调用格式说明(通常使用XML风格标签如``) -2. 工具定义的JSON Schema -3. 使用示例和格式要求 - -### 流式内容解析 -从流式文本中解析函数调用通过正则表达式和状态机实现: -```typescript -protected parseFunctionCalls( - response: string, - fallbackIdPrefix: string = 'tool-call' -): Array<{ id: string; type: string; function: { name: string; arguments: string } }> -``` - -解析过程处理以下挑战: -1. 检测函数调用的开始和结束标记 -2. 处理嵌套的JSON结构 -3. 处理不完整或格式错误的函数调用 -4. 为函数调用分配唯一ID - -流式解析通过状态机(`TagState`)跟踪标签状态: -```typescript -type TagState = 'none' | 'start' | 'inside' | 'end' -``` - -这使得即使在复杂的流式生成中,也能准确识别和提取函数调用信息。 - -### 使用同样的getTime示例,提示词工程方案的流程如下: - -1. 添加函数描述到系统提示中: -``` -你是一个有用的AI助手。当需要时,你可以使用以下工具帮助回答问题: - -function getTime(offset_ms: number): number -描述: 获取当前时间偏移后的毫秒数时间戳 -参数: - - offset_ms: 时间偏移量(毫秒) - -使用工具时,请使用以下格式: - -{ - "name": "getTime", - "arguments": { - "offset_ms": -86400000 - } -} - -``` - -2. 模型生成带有函数调用标记的回复: -``` -我需要获取昨天的日期。我将调用getTime函数获取昨天的时间戳。 - - -{ - "name": "getTime", - "arguments": { - "offset_ms": -86400000 - } -} - -``` - -3. 通过正则表达式解析函数调用: -```typescript -// 使用状态机和正则匹配提取标签内容 -const functionCallMatch = response.match(/([\s\S]*?)<\/function_call>/); -if (functionCallMatch) { - try { - const parsedCall = JSON.parse(functionCallMatch[1]); - // 调用函数并获取结果 - } catch (error) { - // 处理解析错误 - } -} -``` - -4. 将函数结果添加到上下文中: -``` -函数结果: 1684713600000 - -根据获取的时间戳,昨天是5月22日。 -``` - -这种方法通过精心设计的提示词和文本解析技术,使不支持原生工具调用的模型也能模拟工具调用功能。 - - diff --git a/docs/ipc/eventbus-refactor-summary.md b/docs/ipc/eventbus-refactor-summary.md deleted file mode 100644 index 695ea0a8e..000000000 --- a/docs/ipc/eventbus-refactor-summary.md +++ /dev/null @@ -1,286 +0,0 @@ -# EventBus 重构总结 - -## 🎯 重构目标 - -构建一个简洁、明确的事件通信机制,支持主进程和渲染进程之间的精确事件传递。通过继承 EventEmitter 保持基础功能,专注于提供显式的事件发送方法,避免复杂的自动转发机制。 - -## 🚀 主要功能特性 - -### 1. EventBus 核心架构 - -- **继承 EventEmitter**:保持原生事件系统的基础功能 -- **精确的发送方法**: - - `sendToMain(eventName, ...args)`:仅发送到主进程 - - `sendToWindow(eventName, windowId, ...args)`:发送到特定窗口 - - `sendToRenderer(eventName, target, ...args)`:发送到渲染进程 - - `send(eventName, target, ...args)`:同时发送到主进程和渲染进程 -- **显式通信**:所有跨进程通信都需要明确指定 -- **WindowPresenter 集成**:通过标准接口管理渲染进程通信 - -### 2. SendTarget 枚举定义 - -```typescript -enum SendTarget { - ALL_WINDOWS = 'all_windows', // 广播到所有窗口(默认推荐) - DEFAULT_TAB = 'default_tab' // 发送到默认标签页(特殊场景) -} -``` - -## 📊 事件通信模式 - -### 主进程内部通信 -适用于窗口管理、系统级操作等场景: -```typescript -// 窗口生命周期管理 -eventBus.sendToMain('window:created', windowId) -eventBus.sendToMain('window:focused', windowId) -eventBus.sendToMain('window:blurred', windowId) - -// 快捷键触发的主进程操作 -eventBus.sendToMain('shortcut:create-new-window') -eventBus.sendToMain('shortcut:create-new-tab', windowId) -eventBus.sendToMain('shortcut:close-current-tab', windowId) -``` - -### 渲染进程通信 -适用于 UI 更新、用户界面响应等场景: -```typescript -// 配置变更通知 -eventBus.sendToRenderer('config:language-changed', SendTarget.ALL_WINDOWS, language) -eventBus.sendToRenderer('config:theme-changed', SendTarget.ALL_WINDOWS, theme) - -// 特定窗口操作 -eventBus.sendToWindow('window:specific-update', targetWindowId, data) - -// 默认标签页操作 -eventBus.sendToRenderer('deeplink:mcp-install', SendTarget.DEFAULT_TAB, installData) -``` - -### 双向通信(推荐) -适用于需要主进程和渲染进程同时响应的场景: -```typescript -// 配置系统事件 -eventBus.send('config:provider-changed', SendTarget.ALL_WINDOWS, providerConfig) -eventBus.send('config:model-list-updated', SendTarget.ALL_WINDOWS, modelList) - -// 同步系统事件 -eventBus.send('sync:backup-started', SendTarget.ALL_WINDOWS, backupInfo) -eventBus.send('sync:backup-completed', SendTarget.ALL_WINDOWS, result) - -// 用户界面缩放 -eventBus.send('shortcut:zoom-in', SendTarget.ALL_WINDOWS) -eventBus.send('shortcut:zoom-out', SendTarget.ALL_WINDOWS) -``` - -### 流事件和业务事件处理 -需要明确指定每个事件的发送目标: -```typescript -// 流事件处理 -class StreamEventHandler { - handleError(error: Error) { - // 主进程记录错误 - eventBus.sendToMain('stream:error-logged', error) - // 渲染进程显示错误 - eventBus.sendToRenderer('stream:error-display', SendTarget.ALL_WINDOWS, error) - } -} - -// 会话事件处理 -class ConversationHandler { - activateConversation(conversationId: string) { - // 通知所有窗口更新UI - eventBus.send('conversation:activated', SendTarget.ALL_WINDOWS, conversationId) - } - - editMessage(messageData: any) { - // 通知所有窗口消息已编辑 - eventBus.send('conversation:message-edited', SendTarget.ALL_WINDOWS, messageData) - } -} - -// MCP 服务器事件 -class MCPHandler { - startServer(serverInfo: any) { - // 主进程和渲染进程都需要知道服务器启动 - eventBus.send('mcp:server-started', SendTarget.ALL_WINDOWS, serverInfo) - } - - updateConfig(newConfig: any) { - // 配置变更通知所有窗口 - eventBus.send('mcp:config-changed', SendTarget.ALL_WINDOWS, newConfig) - } -} -``` - -## 🔧 架构优势 - -### 简化的初始化 -```typescript -// 构造函数无需复杂参数 -export const eventBus = new EventBus() - -// 运行时设置 WindowPresenter -eventBus.setWindowPresenter(windowPresenter) -``` - -### 显式通信保障 -- 所有跨进程通信都需要明确调用相应方法 -- 避免意外的事件泄漏或遗漏 -- 代码逻辑更加清晰和可预测 -- 便于调试和维护 - -### 类型安全保障 -- 完全移除 `any` 类型使用 -- 参数类型明确定义:`...args: unknown[]` -- 枚举类型提供编译时检查 -- TypeScript 智能提示支持 - -### 错误处理机制 -```typescript -// 内置的错误检查和警告 -sendToRenderer(eventName: string, target: SendTarget = SendTarget.ALL_WINDOWS, ...args: unknown[]) { - if (!this.windowPresenter) { - console.warn('WindowPresenter not available, cannot send to renderer') - return - } - // ... 发送逻辑 -} -``` - -## 🎨 实际应用场景 - -### 配置管理系统 -```typescript -class ConfigManager { - updateLanguage(language: string) { - this.saveConfig('language', language) - // 明确通知所有界面更新语言 - eventBus.send('config:language-changed', SendTarget.ALL_WINDOWS, language) - } - - updateProvider(provider: ProviderConfig) { - this.saveConfig('provider', provider) - // 通知主进程和所有界面 - eventBus.send('config:provider-changed', SendTarget.ALL_WINDOWS, provider) - } -} -``` - -### 窗口管理系统 -```typescript -class WindowManager { - createWindow() { - const windowId = this.doCreateWindow() - // 仅通知主进程 - eventBus.sendToMain('window:created', windowId) - } - - focusWindow(windowId: number) { - this.doFocusWindow(windowId) - // 仅通知主进程 - eventBus.sendToMain('window:focused', windowId) - } - - notifySpecificWindow(windowId: number, data: any) { - // 向特定窗口发送消息 - eventBus.sendToWindow('window:notification', windowId, data) - } -} -``` - -### 通知系统 -```typescript -class NotificationManager { - showError(message: string) { - // 明确指定仅向渲染进程发送通知 - eventBus.sendToRenderer('notification:show-error', SendTarget.ALL_WINDOWS, message) - } - - handleSystemNotificationClick() { - // 系统通知点击需要通知所有窗口 - eventBus.send('notification:sys-notify-clicked', SendTarget.ALL_WINDOWS) - } -} -``` - -### 快捷键处理系统 -```typescript -class ShortcutManager { - handleGoSettings() { - // 明确通知渲染进程跳转设置 - eventBus.sendToRenderer('shortcut:go-settings', SendTarget.ALL_WINDOWS) - } - - handleCleanHistory() { - // 主进程清理历史 - this.cleanHistoryInMain() - // 明确通知渲染进程更新UI - eventBus.sendToRenderer('shortcut:clean-chat-history', SendTarget.ALL_WINDOWS) - } - - handleZoom(direction: 'in' | 'out' | 'reset') { - // 缩放操作需要主进程和渲染进程同时响应 - eventBus.send(`shortcut:zoom-${direction}`, SendTarget.ALL_WINDOWS) - } -} -``` - -## 🎯 性能优化 - -### 精确的目标控制 -- 支持发送到特定窗口而非广播 -- 可选择发送到默认标签页 -- 避免无效的事件传播 -- 减少不必要的进程间通信 - -### 显式控制的优势 -- 开发者必须明确指定事件的发送目标 -- 避免意外的性能开销 -- 更好的代码可读性和维护性 -- 便于性能分析和优化 - -### 错误预防机制 -- WindowPresenter 状态检查 -- 控制台警告提示 -- 优雅的错误降级处理 - -## 🔄 兼容性和迁移 - -### 向后兼容 -- 完全保持 EventEmitter 的所有原生功能 -- 主进程内部的事件监听不受影响 -- 现有的事件监听器无需修改 - -### 迁移指导 -原有依赖自动转发的代码需要调整: - -```typescript -// ❌ 之前的自动转发方式 -eventBus.emit('stream:error', error) // 自动转发到渲染进程 - -// ✅ 现在需要明确指定 -eventBus.sendToMain('stream:error-logged', error) // 主进程记录 -eventBus.sendToRenderer('stream:error-display', SendTarget.ALL_WINDOWS, error) // 渲染进程显示 - -// 或者使用双向发送 -eventBus.send('stream:error', SendTarget.ALL_WINDOWS, error) -``` - -## 🎉 重构成果总结 - -这次 EventBus 简化重构成功实现了: - -1. **架构简化**:移除复杂的自动转发机制,专注于显式通信 -2. **逻辑清晰**:每个事件的发送目标都需要明确指定 -3. **性能优化**:避免不必要的事件转发和处理 -4. **维护性提升**:代码逻辑更加直观和可预测 -5. **兼容性保障**:保持 EventEmitter 基础功能不变 - -特别重要的改进: -- **显式通信**:所有跨进程通信都需要明确指定,避免隐藏的依赖 -- **精确控制**:可以选择发送到所有窗口、特定窗口或默认标签页 -- **简洁架构**:移除了复杂的事件定义和自动转发逻辑 -- **更好的可维护性**:事件流向清晰,便于调试和维护 -- **性能提升**:避免了不必要的事件处理开销 - -现在的 EventBus 更加简洁明了,虽然需要开发者显式指定事件目标,但这带来了更好的代码可读性、可维护性和性能表现。每个事件的处理逻辑都是明确和可预测的,为应用的稳定运行提供了更好的基础。 diff --git a/docs/ipc/eventbus-usage.md b/docs/ipc/eventbus-usage.md index c3174cd03..3ce1796a8 100644 --- a/docs/ipc/eventbus-usage.md +++ b/docs/ipc/eventbus-usage.md @@ -1,81 +1,166 @@ -# EventBus 使用指南 +# EventBus 使用指南与最佳实践 ## 概述 -EventBus 类提供了主进程和渲染进程之间精确的事件通信机制。它继承自 EventEmitter,专注于提供明确的事件发送控制,支持向主进程、渲染进程或特定窗口发送事件。 +EventBus 提供了主进程和渲染进程之间精确的事件通信机制,支持从基础广播到精确Tab路由的完整功能。本指南将帮助你充分利用已有的强大IPC架构。 ## 核心理念 -- **精确控制**:使用具体的发送方法,明确事件的目标 -- **显式发送**:所有跨进程通信都需要明确指定 +- **精确路由优先**:尽可能使用精确路由而非广播 +- **明确事件作用域**:根据影响范围选择合适的发送方法 - **类型安全**:完整的 TypeScript 支持 -- **简洁架构**:无复杂的自动转发机制 +- **性能优化**:减少不必要的事件传播 -## 主要方法 +## 可用方法详解 + +### 🎯 精确路由方法(推荐优先使用) + +#### `sendToTab(tabId, eventName, ...args)` ✨ +**用途**: 向指定Tab发送事件 +**场景**: Tab特定的操作结果、状态更新 -### 1. 仅发送到主进程 ```typescript -import { eventBus } from '@/main/eventbus' +// 示例:消息编辑完成,只通知相关Tab +async editMessage(tabId: number, messageId: string, newContent: string) { + await this.updateMessageInDatabase(messageId, newContent) + + // 只通知当前Tab更新UI + eventBus.sendToTab(tabId, 'conversation:message-edited', { + messageId, + content: newContent, + timestamp: Date.now() + }) +} +``` + +#### `sendToActiveTab(windowId, eventName, ...args)` ✨ +**用途**: 向窗口的活跃Tab发送事件 +**场景**: 快捷键操作、窗口级操作 + +```typescript +// 示例:快捷键创建新对话 +handleCreateNewConversation(windowId: number) { + const conversationId = this.createNewConversation() + + // 只通知当前活跃的Tab + eventBus.sendToActiveTab(windowId, 'conversation:new-created', { + conversationId, + switchTo: true + }) +} +``` +#### `broadcastToTabs(tabIds, eventName, ...args)` ✨ +**用途**: 向多个指定Tab广播事件 +**场景**: 批量操作、相关Tab的协调更新 + +```typescript +// 示例:删除线程,通知所有相关Tab +async deleteThread(threadId: string) { + await this.deleteThreadFromDatabase(threadId) + + // 获取所有显示此线程的Tab + const relatedTabIds = this.getTabsByThreadId(threadId) + + // 只通知相关Tab更新 + eventBus.broadcastToTabs(relatedTabIds, 'thread:deleted', { + threadId, + redirectTo: 'home' + }) +} +``` + +### 📡 基础通信方法 + +#### `sendToMain(eventName, ...args)` +**用途**: 仅发送到主进程 +**场景**: 窗口管理、内部状态记录 + +```typescript // 窗口管理、标签页操作等主进程内部事件 eventBus.sendToMain('window:created', windowId) eventBus.sendToMain('shortcut:create-new-tab', windowId) ``` -### 2. 发送到特定窗口 -```typescript -import { eventBus } from '@/main/eventbus' +#### `sendToWindow(eventName, windowId, ...args)` +**用途**: 发送到特定窗口 +**场景**: 窗口特定操作 -// 发送到指定窗口ID的渲染进程 -eventBus.sendToWindow('custom-event', windowId, data) +```typescript +// 发送到指定窗口的渲染进程 +eventBus.sendToWindow('window:focus-changed', windowId, isFocused) ``` -### 3. 发送到渲染进程 -```typescript -import { eventBus, SendTarget } from '@/main/eventbus' +#### `sendToRenderer(eventName, target, ...args)` +**用途**: 发送到渲染进程 +**场景**: 真正的全局UI更新 -// 发送到所有窗口(默认) +```typescript +// ✅ 适合广播的场景:全局配置变更 +eventBus.sendToRenderer('config:theme-changed', SendTarget.ALL_WINDOWS, theme) eventBus.sendToRenderer('config:language-changed', SendTarget.ALL_WINDOWS, language) -// 发送到默认标签页 -eventBus.sendToRenderer('deeplink:mcp-install', SendTarget.DEFAULT_TAB, data) +// ❌ 避免:Tab特定操作使用广播 +// eventBus.sendToRenderer('notification:show', SendTarget.ALL_WINDOWS, message) ``` -### 4. 同时发送到主进程和渲染进程(推荐) +#### `send(eventName, target, ...args)` +**用途**: 同时发送到主进程和渲染进程 +**场景**: 需要主进程和渲染进程同时响应的事件 + ```typescript -// 最常用的方法:确保主进程和渲染进程都能收到事件 +// 配置变更需要主进程和渲染进程都知道 eventBus.send('config:provider-changed', SendTarget.ALL_WINDOWS, providers) eventBus.send('sync:backup-completed', SendTarget.ALL_WINDOWS, timestamp) ``` -## 事件分类指南 +## 🎯 选择合适的方法 -### 仅主进程内部 -适用于窗口管理、标签页操作等不需要渲染进程知道的事件: -```typescript -eventBus.sendToMain('window:created', windowId) -eventBus.sendToMain('window:focused', windowId) -eventBus.sendToMain('shortcut:create-new-window') +### 决策流程图 +``` +事件需要发送给谁? +├── 特定Tab ────────────→ sendToTab(tabId, ...) +├── 当前活跃Tab ────────→ sendToActiveTab(windowId, ...) +├── 多个相关Tab ───────→ broadcastToTabs(tabIds, ...) +├── 特定窗口 ─────────→ sendToWindow(windowId, ...) +├── 仅主进程 ─────────→ sendToMain(...) +├── 全局配置/状态 ────→ send(..., ALL_WINDOWS, ...) +└── 纯UI更新 ────────→ sendToRenderer(..., ALL_WINDOWS, ...) ``` -### 仅渲染进程 -适用于纯 UI 更新,主进程不需要处理的事件: +### 事件作用域分类 + +#### 🎯 Tab级别事件(优先使用精确路由) ```typescript -eventBus.sendToRenderer('notification:show-error', SendTarget.ALL_WINDOWS, error) -eventBus.sendToRenderer('ui:theme-changed', SendTarget.ALL_WINDOWS, theme) +// ✅ 推荐:精确路由 +eventBus.sendToTab(tabId, 'conversation:message-updated', messageData) +eventBus.sendToTab(tabId, 'stream:completed', streamData) +eventBus.sendToTab(tabId, 'error:display', errorInfo) + +// ❌ 避免:不必要的广播 +// eventBus.sendToRenderer('notification:show', SendTarget.ALL_WINDOWS, message) ``` -### 主进程 + 渲染进程 -适用于配置变更、状态同步等需要两端都知道的事件: +#### 🪟 窗口级别事件 ```typescript -eventBus.send('config:language-changed', SendTarget.ALL_WINDOWS, language) -eventBus.send('sync:backup-started', SendTarget.ALL_WINDOWS) +// 快捷键操作:影响当前活跃Tab +eventBus.sendToActiveTab(windowId, 'shortcut:new-conversation') +eventBus.sendToActiveTab(windowId, 'shortcut:go-settings') + +// 窗口状态:影响整个窗口 +eventBus.sendToWindow('window:focus-changed', windowId, isFocused) ``` -### 特定窗口通信 -适用于需要与特定窗口通信的场景: +#### 🌍 全局事件(合理使用广播) ```typescript -eventBus.sendToWindow('window:specific-action', targetWindowId, actionData) +// ✅ 适合广播:真正的全局配置 +eventBus.send('config:theme-changed', SendTarget.ALL_WINDOWS, theme) +eventBus.send('config:language-changed', SendTarget.ALL_WINDOWS, language) +eventBus.send('system:update-available', SendTarget.ALL_WINDOWS, updateInfo) + +// 主进程内部事件 +eventBus.sendToMain('window:created', windowId) +eventBus.sendToMain('app:will-quit') ``` ## SendTarget 选项 @@ -138,24 +223,79 @@ onStreamError(error: Error) { } ``` -### 5. 流事件处理 +## 🚀 实际代码优化示例 + +### 场景1: 流事件处理优化 + ```typescript -// 处理各种流事件,明确指定发送目标 -handleConversationEvents() { - // 会话激活 - 通知所有窗口更新UI - eventBus.send('conversation:activated', SendTarget.ALL_WINDOWS, conversationId) +// ❌ 当前可能的实现(过度广播) +class StreamEventHandler { + handleStreamComplete(data: StreamData) { + // 广播到所有窗口,但只有1个Tab需要 + eventBus.sendToRenderer('stream:completed', SendTarget.ALL_WINDOWS, data) + } +} - // 消息编辑 - 通知所有窗口 - eventBus.send('conversation:message-edited', SendTarget.ALL_WINDOWS, messageData) +// ✅ 优化后的实现 +class StreamEventHandler { + handleStreamComplete(tabId: number, data: StreamData) { + // 只通知发起流的Tab + eventBus.sendToTab(tabId, 'stream:completed', data) + + // 如果需要,可以通知主进程记录 + eventBus.sendToMain('stream:completed-logged', { tabId, ...data }) + } } +``` -// MCP 服务器事件 -handleMCPEvents() { - // MCP 服务器启动 - 通知主进程和所有窗口 - eventBus.send('mcp:server-started', SendTarget.ALL_WINDOWS, serverInfo) +### 场景2: 配置更新优化 - // 配置变更 - 通知所有窗口 - eventBus.send('mcp:config-changed', SendTarget.ALL_WINDOWS, newConfig) +```typescript +// ❌ 过度广播 +updateProviderConfig(providerId: string, config: ProviderConfig) { + this.saveConfig(providerId, config) + + // 不必要的广播 + eventBus.send('config:provider-changed', SendTarget.ALL_WINDOWS, { providerId, config }) +} + +// ✅ 精确通知 +updateProviderConfig(tabId: number, providerId: string, config: ProviderConfig) { + this.saveConfig(providerId, config) + + // 通知主进程更新内存中的配置 + eventBus.sendToMain('config:provider-updated', { providerId, config }) + + // 只通知操作的Tab配置已更新 + eventBus.sendToTab(tabId, 'config:provider-update-success', { providerId }) + + // 如果其他Tab也在使用此provider,才通知它们 + const affectedTabs = this.getTabsUsingProvider(providerId) + if (affectedTabs.length > 0) { + eventBus.broadcastToTabs(affectedTabs, 'config:provider-config-changed', { providerId, config }) + } +} +``` + +### 场景3: 错误处理优化 + +```typescript +// ❌ 广播错误到所有Tab +handleError(error: Error) { + eventBus.sendToRenderer('error:occurred', SendTarget.ALL_WINDOWS, error) +} + +// ✅ 精确错误通知 +handleError(tabId: number, error: Error, context: ErrorContext) { + // 主进程记录错误 + eventBus.sendToMain('error:logged', { tabId, error, context }) + + // 只向出错的Tab显示错误 + eventBus.sendToTab(tabId, 'error:display', { + message: error.message, + type: context.type, + recoverable: context.canRetry + }) } ``` @@ -222,16 +362,82 @@ class ShortcutManager { } ``` -## 调试技巧 +## 🛠️ 性能优化技巧 + +### 1. 减少不必要的事件传播 ```typescript -// 监听主进程事件进行调试 -eventBus.on('*', (eventName, ...args) => { - console.log(`Main process event: ${eventName}`, args) -}) +// ❌ 性能浪费:100个Tab都收到事件,但只有1个需要 +function notifyAllTabs(data: any) { + eventBus.sendToRenderer('data:updated', SendTarget.ALL_WINDOWS, data) +} + +// ✅ 精确通知:只通知相关Tab +function notifyRelevantTabs(dataId: string, data: any) { + const relevantTabs = this.getTabsDisplayingData(dataId) + eventBus.broadcastToTabs(relevantTabs, 'data:updated', data) +} +``` + +### 2. 批量操作优化 + +```typescript +// ❌ 多次调用 +function updateMultipleTabs(updates: Array<{tabId: number, data: any}>) { + updates.forEach(update => { + eventBus.sendToTab(update.tabId, 'data:updated', update.data) + }) +} + +// ✅ 批量通知 +function updateMultipleTabs(updates: Array<{tabId: number, data: any}>) { + const tabIds = updates.map(u => u.tabId) + const batchData = updates.reduce((acc, u) => { + acc[u.tabId] = u.data + return acc + }, {} as Record) + + eventBus.broadcastToTabs(tabIds, 'data:batch-updated', batchData) +} +``` + +## 🔧 调试技巧 + +### 1. 启用IPC日志 + +```bash +# 在开发环境中启用详细的IPC调用日志 +VITE_LOG_IPC_CALL=1 pnpm run dev +``` + +这将显示: +- `[IPC Call] Tab:123 Window:456 -> presenterName.methodName` +- `[Renderer IPC] WebContents:789 -> presenterName.methodName` + +### 2. 检查事件路由 + +```typescript +// 在EventBus中添加调试日志 +sendToTab(tabId: number, eventName: string, ...args: unknown[]) { + if (import.meta.env.DEV) { + console.log(`[EventBus] Sending ${eventName} to Tab:${tabId}`) + } + // 实际发送逻辑... +} // 检查 WindowPresenter 状态 if (!eventBus.windowPresenter) { console.warn('WindowPresenter not set, renderer events will not work') } ``` + +### 3. 迁移现有代码 + +```bash +# 搜索可能过度广播的地方 +grep -r "SendTarget.ALL_WINDOWS" src/ +grep -r "sendToRenderer" src/ + +# 分析:这个事件真的需要所有窗口都知道吗? +# 替换:从广播改为精确路由 +``` diff --git a/docs/ipc/ipc-architecture-complete.md b/docs/ipc/ipc-architecture-complete.md new file mode 100644 index 000000000..564b41d59 --- /dev/null +++ b/docs/ipc/ipc-architecture-complete.md @@ -0,0 +1,474 @@ +# DeepChat IPC 架构完整指南 + +## 概述 + +DeepChat 已经实现了完整的多Tab IPC通信架构,支持精确的Tab上下文识别、事件路由和进程间通信。本文档提供架构现状、使用指南和最佳实践的完整描述。 + +## 🏗️ 架构现状 + +### 核心成就 + +EventBus 已成功实现了完整的多Tab精确通信机制,支持主进程和渲染进程之间的精确事件传递。基于 EventEmitter 构建,提供了强大的显式事件发送方法和Tab级别的精确路由功能。 + +### 已实现的核心功能 ✅ + +#### 1. Tab上下文识别机制 + +**WebContents ID 映射** +- **位置**: `src/main/presenter/tabPresenter.ts:32` +- **实现**: `webContentsToTabId: Map` +- **功能**: 自动建立WebContents ID到Tab ID的映射关系 + +```typescript +// TabPresenter.ts:159 - Tab创建时自动建立映射 +this.webContentsToTabId.set(view.webContents.id, tabId) + +// TabPresenter.ts:465 - 根据WebContents ID获取Tab ID +getTabIdByWebContentsId(webContentsId: number): number | undefined { + return this.webContentsToTabId.get(webContentsId) +} + +// TabPresenter.ts:474 - 根据WebContents ID获取Window ID +getWindowIdByWebContentsId(webContentsId: number): number | undefined { + const tabId = this.getTabIdByWebContentsId(webContentsId) + return tabId ? this.tabWindowMap.get(tabId) : undefined +} +``` + +#### 2. IPC调用处理 + +**主进程处理器增强** +- **位置**: `src/main/presenter/index.ts:197` +- **功能**: 自动识别IPC调用的来源Tab和Window + +```typescript +ipcMain.handle('presenter:call', (event: IpcMainInvokeEvent, name: string, method: string, ...payloads: unknown[]) => { + // 构建调用上下文 - 已实现 + const webContentsId = event.sender.id + const tabId = presenter.tabPresenter.getTabIdByWebContentsId(webContentsId) + const windowId = presenter.tabPresenter.getWindowIdByWebContentsId(webContentsId) + + const context: IPCCallContext = { + tabId, + windowId, + webContentsId, + presenterName: name, + methodName: method, + timestamp: Date.now() + } + + // 详细的日志记录 - 已实现 + if (import.meta.env.VITE_LOG_IPC_CALL === '1') { + console.log(`[IPC Call] Tab:${context.tabId || 'unknown'} Window:${context.windowId || 'unknown'} -> ${context.presenterName}.${context.methodName}`) + } +}) +``` + +#### 3. EventBus精确路由 + +**完整的路由方法集** +- **位置**: `src/main/eventbus.ts` +- **功能**: 支持从基础通信到精确Tab路由的完整功能 + +```typescript +export class EventBus extends EventEmitter { + // 基础通信方法 + sendToMain(eventName: string, ...args: unknown[]) // 仅主进程 + sendToWindow(eventName: string, windowId: number, ...args: unknown[]) // 特定窗口 + sendToRenderer(eventName: string, target: SendTarget, ...args: unknown[]) // 渲染进程 + send(eventName: string, target: SendTarget, ...args: unknown[]) // 双向通信 + + // 精确路由方法 ✨ 已实现 + sendToTab(tabId: number, eventName: string, ...args: unknown[]) // 指定Tab + sendToActiveTab(windowId: number, eventName: string, ...args: unknown[]) // 活跃Tab + broadcastToTabs(tabIds: number[], eventName: string, ...args: unknown[]) // 多Tab广播 +} +``` + +#### 4. 渲染进程集成 + +**WebContents ID获取** +- **位置**: `src/preload/index.ts:27` +- **实现**: 通过preload API暴露webContentsId + +```typescript +// preload/index.ts - 已实现 +getWebContentsId: () => { + if (cachedWebContentsId !== undefined) { + return cachedWebContentsId + } + cachedWebContentsId = ipcRenderer.sendSync('get-web-contents-id') + return cachedWebContentsId +} +``` + +**渲染进程调用** +- **位置**: `src/renderer/src/composables/usePresenter.ts:61` +- **实现**: 自动注入WebContents ID到IPC调用 + +```typescript +// 自动注入WebContents ID +const webContentsId = getWebContentsId() + +// IPC调用日志包含WebContents ID +if (import.meta.env.VITE_LOG_IPC_CALL === '1') { + console.log(`[Renderer IPC] WebContents:${webContentsId || 'unknown'} -> ${presenterName}.${functionName as string}`) +} +``` + +## 🎯 使用指南与最佳实践 + +### 精确路由方法(推荐优先使用) + +#### `sendToTab(tabId, eventName, ...args)` ✨ +**用途**: 向指定Tab发送事件 +**场景**: Tab特定的操作结果、状态更新 + +```typescript +// 示例:消息编辑完成,只通知相关Tab +async editMessage(tabId: number, messageId: string, newContent: string) { + await this.updateMessageInDatabase(messageId, newContent) + + // 只通知当前Tab更新UI + eventBus.sendToTab(tabId, 'conversation:message-edited', { + messageId, + content: newContent, + timestamp: Date.now() + }) +} +``` + +#### `sendToActiveTab(windowId, eventName, ...args)` ✨ +**用途**: 向窗口的活跃Tab发送事件 +**场景**: 快捷键操作、窗口级操作 + +```typescript +// 示例:快捷键创建新对话 +handleCreateNewConversation(windowId: number) { + const conversationId = this.createNewConversation() + + // 只通知当前活跃的Tab + eventBus.sendToActiveTab(windowId, 'conversation:new-created', { + conversationId, + switchTo: true + }) +} +``` + +#### `broadcastToTabs(tabIds, eventName, ...args)` ✨ +**用途**: 向多个指定Tab广播事件 +**场景**: 批量操作、相关Tab的协调更新 + +```typescript +// 示例:删除线程,通知所有相关Tab +async deleteThread(threadId: string) { + await this.deleteThreadFromDatabase(threadId) + + // 获取所有显示此线程的Tab + const relatedTabIds = this.getTabsByThreadId(threadId) + + // 只通知相关Tab更新 + eventBus.broadcastToTabs(relatedTabIds, 'thread:deleted', { + threadId, + redirectTo: 'home' + }) +} +``` + +### 选择合适的方法 + +#### 决策流程图 +``` +事件需要发送给谁? +├── 特定Tab ────────────→ sendToTab(tabId, ...) +├── 当前活跃Tab ────────→ sendToActiveTab(windowId, ...) +├── 多个相关Tab ───────→ broadcastToTabs(tabIds, ...) +├── 特定窗口 ─────────→ sendToWindow(windowId, ...) +├── 仅主进程 ─────────→ sendToMain(...) +├── 全局配置/状态 ────→ send(..., ALL_WINDOWS, ...) +└── 纯UI更新 ────────→ sendToRenderer(..., ALL_WINDOWS, ...) +``` + +#### 事件作用域分类 + +**🎯 Tab级别事件(优先使用精确路由)** +```typescript +// ✅ 推荐:精确路由 +eventBus.sendToTab(tabId, 'conversation:message-updated', messageData) +eventBus.sendToTab(tabId, 'stream:completed', streamData) +eventBus.sendToTab(tabId, 'error:display', errorInfo) + +// ❌ 避免:不必要的广播 +// eventBus.sendToRenderer('notification:show', SendTarget.ALL_WINDOWS, message) +``` + +**🪟 窗口级别事件** +```typescript +// 快捷键操作:影响当前活跃Tab +eventBus.sendToActiveTab(windowId, 'shortcut:new-conversation') +eventBus.sendToActiveTab(windowId, 'shortcut:go-settings') + +// 窗口状态:影响整个窗口 +eventBus.sendToWindow('window:focus-changed', windowId, isFocused) +``` + +**🌍 全局事件(合理使用广播)** +```typescript +// ✅ 适合广播:真正的全局配置 +eventBus.send('config:theme-changed', SendTarget.ALL_WINDOWS, theme) +eventBus.send('config:language-changed', SendTarget.ALL_WINDOWS, language) +eventBus.send('system:update-available', SendTarget.ALL_WINDOWS, updateInfo) + +// 主进程内部事件 +eventBus.sendToMain('window:created', windowId) +eventBus.sendToMain('app:will-quit') +``` + +## 📝 事件命名规范 + +### 分类与命名约定 + +采用 `领域:动作` 的命名格式: + +#### 1. 配置相关事件 +```typescript +'config:provider-changed' // 提供者配置变更 +'config:system-changed' // 系统配置变更 +'config:language-changed' // 语言配置变更 +'config:theme-changed' // 主题配置变更 +``` + +#### 2. 会话相关事件 +```typescript +'conversation:created' // 会话创建 +'conversation:activated' // 会话激活 +'conversation:message-edited'// 消息编辑 +'conversation:cleared' // 会话清理 +``` + +#### 3. 流处理相关事件 +```typescript +'stream:data' // 流数据 +'stream:completed' // 流完成 +'stream:error' // 流错误 +``` + +#### 4. 系统相关事件 +```typescript +'system:theme-updated' // 系统主题更新 +'system:update-available' // 系统更新可用 +'window:created' // 窗口创建 +'window:focused' // 窗口聚焦 +``` + +#### 5. MCP相关事件 +```typescript +'mcp:server-started' // MCP服务器启动 +'mcp:server-stopped' // MCP服务器停止 +'mcp:tool-result' // MCP工具执行结果 +'mcp:config-changed' // MCP配置变更 +``` + +#### 6. 同步相关事件 +```typescript +'sync:backup-started' // 备份开始 +'sync:backup-completed' // 备份完成 +'sync:import-completed' // 导入完成 +'sync:error' // 同步错误 +``` + +## 🚀 代码优化示例 + +### 场景1: 流事件处理优化 + +```typescript +// ❌ 当前可能的实现(过度广播) +class StreamEventHandler { + handleStreamComplete(data: StreamData) { + // 广播到所有窗口,但只有1个Tab需要 + eventBus.sendToRenderer('stream:completed', SendTarget.ALL_WINDOWS, data) + } +} + +// ✅ 优化后的实现 +class StreamEventHandler { + handleStreamComplete(tabId: number, data: StreamData) { + // 只通知发起流的Tab + eventBus.sendToTab(tabId, 'stream:completed', data) + + // 如果需要,可以通知主进程记录 + eventBus.sendToMain('stream:completed-logged', { tabId, ...data }) + } +} +``` + +### 场景2: 配置更新优化 + +```typescript +// ❌ 过度广播 +updateProviderConfig(providerId: string, config: ProviderConfig) { + this.saveConfig(providerId, config) + + // 不必要的广播 + eventBus.send('config:provider-changed', SendTarget.ALL_WINDOWS, { providerId, config }) +} + +// ✅ 精确通知 +updateProviderConfig(tabId: number, providerId: string, config: ProviderConfig) { + this.saveConfig(providerId, config) + + // 通知主进程更新内存中的配置 + eventBus.sendToMain('config:provider-updated', { providerId, config }) + + // 只通知操作的Tab配置已更新 + eventBus.sendToTab(tabId, 'config:provider-update-success', { providerId }) + + // 如果其他Tab也在使用此provider,才通知它们 + const affectedTabs = this.getTabsUsingProvider(providerId) + if (affectedTabs.length > 0) { + eventBus.broadcastToTabs(affectedTabs, 'config:provider-config-changed', { providerId, config }) + } +} +``` + +### 场景3: 错误处理优化 + +```typescript +// ❌ 广播错误到所有Tab +handleError(error: Error) { + eventBus.sendToRenderer('error:occurred', SendTarget.ALL_WINDOWS, error) +} + +// ✅ 精确错误通知 +handleError(tabId: number, error: Error, context: ErrorContext) { + // 主进程记录错误 + eventBus.sendToMain('error:logged', { tabId, error, context }) + + // 只向出错的Tab显示错误 + eventBus.sendToTab(tabId, 'error:display', { + message: error.message, + type: context.type, + recoverable: context.canRetry + }) +} +``` + +## 🛠️ 性能优化技巧 + +### 1. 减少不必要的事件传播 + +```typescript +// ❌ 性能浪费:100个Tab都收到事件,但只有1个需要 +function notifyAllTabs(data: any) { + eventBus.sendToRenderer('data:updated', SendTarget.ALL_WINDOWS, data) +} + +// ✅ 精确通知:只通知相关Tab +function notifyRelevantTabs(dataId: string, data: any) { + const relevantTabs = this.getTabsDisplayingData(dataId) + eventBus.broadcastToTabs(relevantTabs, 'data:updated', data) +} +``` + +### 2. 批量操作优化 + +```typescript +// ❌ 多次调用 +function updateMultipleTabs(updates: Array<{tabId: number, data: any}>) { + updates.forEach(update => { + eventBus.sendToTab(update.tabId, 'data:updated', update.data) + }) +} + +// ✅ 批量通知 +function updateMultipleTabs(updates: Array<{tabId: number, data: any}>) { + const tabIds = updates.map(u => u.tabId) + const batchData = updates.reduce((acc, u) => { + acc[u.tabId] = u.data + return acc + }, {} as Record) + + eventBus.broadcastToTabs(tabIds, 'data:batch-updated', batchData) +} +``` + +## 🔧 调试与监控 + +### 启用IPC日志 + +```bash +# 在开发环境中启用详细的IPC调用日志 +VITE_LOG_IPC_CALL=1 pnpm run dev +``` + +这将显示: +- `[IPC Call] Tab:123 Window:456 -> presenterName.methodName` +- `[Renderer IPC] WebContents:789 -> presenterName.methodName` +- `[EventBus] Sending eventName to Tab:123` + +### 检查事件路由 + +```typescript +// 在EventBus中添加调试日志 +sendToTab(tabId: number, eventName: string, ...args: unknown[]) { + if (import.meta.env.DEV) { + console.log(`[EventBus] Sending ${eventName} to Tab:${tabId}`) + } + // 实际发送逻辑... +} + +// 检查 WindowPresenter 状态 +if (!eventBus.windowPresenter) { + console.warn('WindowPresenter not set, renderer events will not work') +} +``` + +### 迁移现有代码 + +```bash +# 搜索可能过度广播的地方 +grep -r "SendTarget.ALL_WINDOWS" src/ +grep -r "sendToRenderer" src/ + +# 分析:这个事件真的需要所有窗口都知道吗? +# 替换:从广播改为精确路由 +``` + +## 📊 当前状态总结 + +### ✅ 已完全实现的功能 + +1. **Tab上下文识别**: 100%完成,主进程能准确识别每个IPC调用的来源Tab +2. **精确事件路由**: 100%完成,EventBus支持向指定Tab发送事件 +3. **增强错误处理**: 100%完成,所有错误日志包含Tab上下文信息 +4. **性能优化**: 使用Map结构,O(1)时间复杂度的映射查找 +5. **向后兼容**: 100%兼容,现有API接口完全保持不变 + +### ⚠️ 待优化的方面 + +1. **使用率低**: 很多代码仍使用`SendTarget.ALL_WINDOWS`广播,未充分利用精确路由 +2. **监控不足**: 缺乏IPC性能监控和异常告警 +3. **文档滞后**: 之前的文档没有准确反映当前架构状态 + +### 🎯 实际需要的改进 + +1. **推广使用**: 在代码中更多使用精确路由方法 +2. **性能监控**: 添加IPC调用统计和性能监控 +3. **开发体验**: 提供更好的调试工具 + +## 🎉 结论 + +DeepChat的IPC架构已经非常成熟和完善,核心的多Tab通信问题已经完全解决。架构具有以下特点: + +- **功能完整**: 从基础通信到精确路由的完整功能集 +- **性能优秀**: 使用高效的数据结构和算法 +- **开发友好**: 提供详细的调试信息和错误处理 +- **扩展性强**: 支持复杂的多Tab、多窗口场景 + +**主要需要的是使用优化而非架构改进**: + +1. **文档更新**: 准确反映当前架构状态 ✅ 已完成 +2. **使用优化**: 推广精确路由方法的使用 +3. **监控完善**: 添加性能监控和调试工具 + +**不需要大规模重构**,当前架构已经足够支撑复杂的多Tab场景。重点应该放在充分利用现有功能上。 \ No newline at end of file diff --git a/docs/ipc/ipc-optimization-phase1-summary.md b/docs/ipc/ipc-optimization-phase1-summary.md deleted file mode 100644 index ea11697a5..000000000 --- a/docs/ipc/ipc-optimization-phase1-summary.md +++ /dev/null @@ -1,168 +0,0 @@ -# IPC优化第一阶段实施总结 - -## 已完成的功能 - -### 1. Tab上下文识别机制 ✅ - -- **TabPresenter增强**: 添加了WebContents ID到Tab ID的映射机制 -- **映射管理**: 在tab创建时建立映射,销毁时清理映射 -- **API扩展**: 新增了`getTabIdByWebContentsId()`和`getWindowIdByWebContentsId()`方法 - -### 2. IPC处理器优化 ✅ - -- **主进程增强**: 在`presenter:call`处理器中自动识别调用来源tab -- **上下文构建**: 每次IPC调用都构建完整的调用上下文(tabId, windowId, webContentsId) -- **日志改进**: 所有IPC日志现在都包含tab上下文信息 - -### 3. EventBus功能扩展 ✅ - -- **精确路由**: 新增`sendToTab()`方法,支持向指定tab发送事件 -- **活跃tab路由**: 新增`sendToActiveTab()`方法,向窗口的活跃tab发送事件 -- **批量广播**: 新增`broadcastToTabs()`方法,向多个tab广播事件 -- **TabPresenter集成**: EventBus与TabPresenter深度集成,支持精确的tab路由 - -### 4. 渲染进程优化 ✅ - -- **WebContentsId获取**: 简化为直接使用preload层的`window.api.getWebContentsId()` -- **日志统一**: 渲染进程日志现在包含WebContentsId标识 -- **向后兼容**: 保持原有API接口不变,内部实现透明升级 - -## 技术实现亮点 - -### 架构设计 - -- **渐进式升级**: 完全保持向后兼容,现有代码无需修改 -- **清晰分层**: 渲染进程负责WebContentsId获取,主进程负责映射和路由 -- **资源管理**: 完善的映射表生命周期管理,防止内存泄漏 - -### 错误处理 - -- **增强日志**: 所有错误日志现在都包含具体的tab上下文 -- **调用追踪**: 可以精确追踪每个IPC调用的来源和去向 -- **优雅降级**: 当映射失败时,系统仍能正常工作,只是日志中显示'unknown' - -### 性能优化 - -- **缓存机制**: 渲染进程缓存WebContentsId,减少重复获取 -- **直接映射**: 主进程使用Map结构,O(1)时间复杂度的映射查找 -- **最小开销**: 新增的上下文识别逻辑开销极小 - -## 解决的问题 - -### 1. Tab间事件错乱 ✅ - -- **问题**: 事件可能发送到错误的tab -- **解决**: 通过精确的tab路由,确保事件只发送到目标tab - -### 2. 调用来源不明 ✅ - -- **问题**: 无法知道IPC调用来自哪个tab -- **解决**: 每个IPC调用都包含完整的来源上下文信息 - -### 3. 错误调试困难 ✅ - -- **问题**: 错误日志缺乏tab上下文,难以定位问题 -- **解决**: 所有日志都包含tab ID,错误追踪更精确 - -### 4. 事件路由不精确 ✅ - -- **问题**: 只能广播事件,无法精确路由 -- **解决**: 支持基于tab ID的精确事件路由 - -## 代码变更统计 - -### 新增文件 - -- `docs/multi-tab-ipc-analysis.md` - 架构分析文档 -- `docs/multi-tab-ipc-optimization-plan.md` - 实施方案文档 -- `docs/ipc-tab-context-test.md` - 测试指南 -- `docs/ipc-optimization-phase1-summary.md` - 本总结文档 - -### 修改文件 - -- `src/main/presenter/tabPresenter.ts` - 添加WebContents映射机制 -- `src/main/presenter/index.ts` - 增强IPC处理器和EventBus集成 -- `src/main/eventbus.ts` - 扩展事件路由功能 -- `src/shared/presenter.d.ts` - 更新ITabPresenter接口定义 -- `src/renderer/src/composables/usePresenter.ts` - 简化上下文获取逻辑 - -### 核心改动 - -- **新增代码**: 约200行 -- **修改代码**: 约100行 -- **删除代码**: 约50行 -- **净增长**: 约150行代码 - -## 向后兼容性 - -### API兼容性 ✅ - -- 所有现有的presenter调用接口保持不变 -- EventBus的原有方法继续可用 -- 渲染进程的usePresenter用法完全相同 - -### 行为兼容性 ✅ - -- 单tab环境下行为完全一致 -- 现有的广播事件机制继续工作 -- 所有现有功能的语义保持不变 - -## 测试验证 - -### 类型检查 ✅ - -- TypeScript编译通过 -- 无类型错误和警告 - -### 功能测试待验证 ⏳ - -- 多tab环境下的IPC调用正确性 -- 事件路由的精确性 -- 错误处理的完整性 - -## 下一步计划 - -### 短期 (本周) - -1. 在开发环境中进行功能测试 -2. 验证多tab场景下的表现 -3. 确认性能影响在可接受范围内 - -### 中期 (下周) - -1. 如果第一阶段验证通过,开始第二阶段实施 -2. 实现更高级的IPC通道管理 -3. 优化性能和资源使用 - -### 长期 (下月) - -1. 完成第三阶段的架构现代化 -2. 建立完整的监控和调试体系 -3. 成为团队的技术最佳实践 - -## 风险评估 - -### 已控制风险 ✅ - -- **兼容性风险**: 通过渐进式升级完全避免 -- **复杂性风险**: 保持实现简洁,避免过度设计 -- **性能风险**: 新增开销最小化,影响可忽略 - -### 剩余风险 ⚠️ - -- **测试覆盖**: 需要更全面的实际环境测试 -- **边界情况**: 某些极端情况可能需要额外处理 -- **用户体验**: 需要验证是否真正解决了用户痛点 - -## 结论 - -第一阶段的IPC优化已经成功完成,实现了以下核心目标: - -1. **完全向后兼容**的tab上下文识别机制 -2. **精确的事件路由**功能 -3. **增强的错误处理**和调试能力 -4. **清晰的架构设计**为后续阶段奠定基础 - -这次优化解决了多tab环境下90%的IPC通信问题,为用户提供了更稳定可靠的多tab体验,同时为开发团队提供了更好的调试和维护工具。 - -下一步将进行实际环境测试,验证效果后决定是否继续第二阶段的优化工作。 diff --git a/docs/ipc/ipc-tab-context-test.md b/docs/ipc/ipc-tab-context-test.md deleted file mode 100644 index 346f31249..000000000 --- a/docs/ipc/ipc-tab-context-test.md +++ /dev/null @@ -1,74 +0,0 @@ -# IPC Tab上下文功能测试 - -## 测试目标 - -验证多tab环境下IPC调用能够正确识别调用来源并提供精确的事件路由。 - -## 测试场景 - -### 1. 基础Tab上下文识别 - -- **目标**: 验证主进程能正确通过webContentsId识别tab -- **步骤**: - 1. 创建多个tab - 2. 在不同tab中调用presenter方法 - 3. 检查日志中的tab ID标识是否正确 - -### 2. 事件精确路由 - -- **目标**: 验证EventBus能将事件发送到正确的tab -- **步骤**: - 1. 在tab A中触发需要回调的操作 - 2. 验证回调事件只发送到tab A,不影响其他tab - -### 3. 错误处理增强 - -- **目标**: 验证错误日志包含正确的tab上下文 -- **步骤**: - 1. 在不同tab中触发错误情况 - 2. 检查错误日志是否包含正确的tab ID信息 - -## 验证点 - -### 主进程日志格式 - -``` -[IPC Call] Tab:123 Window:456 -> presenterName.methodName -[IPC Warning] Tab:123 calling wrong presenter: invalidName -[IPC Error] Tab:123 presenterName.methodName: Error message -``` - -### 渲染进程日志格式 - -``` -[Renderer IPC] WebContents:789 -> presenterName.methodName -[Renderer IPC Error] WebContents:789 presenterName.methodName: Error message -``` - -### EventBus新功能验证 - -- `eventBus.sendToTab(tabId, eventName, ...args)` - 发送到指定tab -- `eventBus.sendToActiveTab(windowId, eventName, ...args)` - 发送到活跃tab -- `eventBus.broadcastToTabs(tabIds, eventName, ...args)` - 广播到多个tab - -## 预期结果 - -1. 所有IPC调用日志都包含正确的tab标识 -2. 事件能够精确路由到目标tab -3. 不再出现tab间事件错乱问题 -4. 错误追踪更加精准 - -## 回归测试 - -确保以下现有功能不受影响: - -- 单tab环境下的正常运行 -- 现有的广播事件机制 -- WindowPresenter的功能 -- 所有presenter的现有API接口 - -## 性能验证 - -- IPC调用延迟不应显著增加 -- 内存使用应保持稳定 -- Tab创建/销毁的性能不受影响 diff --git a/docs/ipc/multi-tab-ipc-analysis.md b/docs/ipc/multi-tab-ipc-analysis.md deleted file mode 100644 index bd7f56c3b..000000000 --- a/docs/ipc/multi-tab-ipc-analysis.md +++ /dev/null @@ -1,153 +0,0 @@ -# 多Tab IPC通信架构分析 - -## 当前架构概述 - -### 核心组件关系 - -``` -多个 WebContentsView (Tabs) - ↓ (presenter:call) -单一 IPC Handler (main/presenter/index.ts) - ↓ -多个 Presenter 实例 - ↓ (eventBus) -回调到 渲染进程 (可能发错tab) -``` - -### 现有实现分析 - -#### 1. 渲染进程端 (`usePresenter.ts`) - -- **优点**: 提供了统一的代理接口,支持安全序列化 -- **问题**: - - 所有tab共享同一个 `presenter:call` 通道 - - 无法标识调用来源的tab ID - - 无tab上下文信息传递 - -#### 2. 主进程端 (`main/presenter/index.ts`) - -- **优点**: 统一的方法调用分发机制 -- **问题**: - - `ipcMain.handle` 只能获取到 `_event` 但无法确定具体来源tab - - 错误处理缺乏tab上下文 - - 无法将响应精确发送到调用方tab - -#### 3. 事件总线 (`eventbus.ts`) - -- **优点**: 提供了灵活的事件分发机制 -- **问题**: - - `sendToRenderer` 使用广播模式,可能导致事件发送到错误tab - - 缺乏基于tab ID的精确事件路由 - - `SendTarget.DEFAULT_TAB` 概念在多tab环境下不明确 - -#### 4. Tab管理 (`tabPresenter.ts`) - -- **优点**: 完善的tab生命周期管理,维护了tab状态映射 -- **问题**: - - tab创建时没有建立IPC通信标识关联 - - 缺乏与presenter调用的关联机制 - -## 关键问题识别 - -### 1. 调用来源识别问题 - -```typescript -// 当前实现无法区分调用来自哪个tab -ipcMain.handle('presenter:call', (_event, name, method, ...payloads) => { - // _event 包含 webContents,但如何关联到 tabId? -}) -``` - -### 2. 响应路由问题 - -```typescript -// EventBus 的响应可能发送到错误的tab -sendToRenderer(eventName: string, target: SendTarget = SendTarget.ALL_WINDOWS, ...args) -// 缺乏 sendToSpecificTab(tabId, eventName, ...args) 机制 -``` - -### 3. 状态同步问题 - -- tab切换时,状态可能不同步 -- 多个tab可能接收到不属于自己的事件 -- presenter调用结果可能被错误tab接收 - -### 4. 错误处理问题 - -- 错误日志缺乏tab上下文信息 -- 无法追踪特定tab的调用链路 -- 调试困难 - -## 影响范围评估 - -### 高影响区域 - -1. **用户交互混乱**: 用户在tab A操作,结果可能显示在tab B -2. **状态污染**: 多个tab间状态可能相互干扰 -3. **事件丢失**: 事件可能发送到非活跃tab而被忽略 - -### 中等影响区域 - -1. **性能问题**: 广播事件导致不必要的处理 -2. **调试困难**: 无法精确定位问题tab -3. **代码维护**: 缺乏清晰的通信边界 - -## 技术债务识别 - -### 1. 架构债务 - -- 单一IPC通道设计不符合多tab架构 -- 事件系统缺乏精确路由能力 - -### 2. 可维护性债务 - -- 调用链路不清晰 -- 错误处理不完整 -- 缺乏统一的tab上下文管理 - -### 3. 扩展性债务 - -- 难以支持tab特定功能 -- 难以实现tab间隔离 -- 难以支持复杂的多窗口场景 - -## 建议优化方向 - -### 1. 短期优化 (向后兼容) - -- 在现有 `presenter:call` 中添加 tabId 参数 -- 扩展 EventBus 支持基于 tabId 的精确路由 -- 改进错误处理添加tab上下文 - -### 2. 中期优化 (架构调整) - -- 设计基于tab的IPC通道管理 -- 实现tab级别的presenter实例隔离 -- 建立完整的tab上下文传递机制 - -### 3. 长期优化 (重构) - -- 考虑采用更现代的IPC架构 (如基于Service Worker模式) -- 实现完全的tab间隔离 -- 支持更复杂的多窗口多tab场景 - -## 风险评估 - -### 优化风险 - -- **兼容性风险**: 现有代码依赖当前IPC机制 -- **复杂性风险**: 过度设计可能增加维护成本 -- **性能风险**: 新机制可能带来额外开销 - -### 不优化风险 - -- **用户体验风险**: 持续的tab间干扰问题 -- **开发效率风险**: 调试和维护困难 -- **扩展性风险**: 无法支持新的多tab功能需求 - -## 下一步行动 - -1. 制定详细的实施方案 -2. 设计新的IPC通信协议 -3. 实现向后兼容的渐进式升级方案 -4. 建立完善的测试覆盖 diff --git a/docs/ipc/multi-tab-ipc-optimization-plan.md b/docs/ipc/multi-tab-ipc-optimization-plan.md deleted file mode 100644 index 5561c0084..000000000 --- a/docs/ipc/multi-tab-ipc-optimization-plan.md +++ /dev/null @@ -1,1541 +0,0 @@ -# 多Tab IPC通信优化实施方案 - -## 方案概述 - -本方案采用**渐进式优化**策略,确保向后兼容的同时逐步解决多tab环境下的IPC通信问题。 - -## 实施阶段 - -### 阶段一: 增强型Tab上下文识别 (短期 - 1-2周) - -#### 目标 - -- 在现有架构基础上添加tab上下文识别 -- 实现精确的tab级事件路由 -- 保持完全向后兼容 - -#### 具体实施 - -##### 1. 扩展 `presenter:call` 协议 - -```typescript -// 新的调用协议结构 -interface PresenterCallRequest { - tabId?: number // 调用来源tab ID - windowId?: number // 调用来源窗口 ID - name: string // presenter名称 - method: string // 方法名 - payloads: unknown[] // 参数 -} -``` - -##### 2. 改进 `usePresenter.ts` - -- 自动注入当前tab上下文信息 -- 支持新旧协议的兼容处理 -- 增强错误处理和日志记录 - -##### 3. 升级主进程IPC处理器 - -- 解析tab上下文信息 -- 建立调用来源映射 -- 改进错误响应机制 - -##### 4. 扩展 EventBus 功能 - -- 添加 `sendToTab(tabId, eventName, ...args)` 方法 -- 添加 `sendToWindow(windowId, eventName, ...args)` 方法 -- 保持现有广播机制的兼容性 - -#### 预期效果 - -- 解决90%的tab间事件错乱问题 -- 提供清晰的调用链路追踪 -- 为后续优化奠定基础 - -### 阶段二: IPC通道管理优化 (中期 - 2-3周) - -#### 目标 - -- 实现基于tab的IPC通道隔离 -- 优化性能和资源使用 -- 支持更复杂的多tab场景 - -#### 具体实施 - -##### 1. 设计Tab级IPC管理器 - -```typescript -class TabIPCManager { - private tabChannels: Map - - createTabChannel(tabId: number): IPCChannel - destroyTabChannel(tabId: number): void - getTabChannel(tabId: number): IPCChannel | null -} -``` - -##### 2. 实现Presenter上下文隔离 - -- 支持tab级别的presenter状态 -- 实现tab间数据隔离 -- 提供共享状态管理机制 - -##### 3. 优化事件订阅机制 - -- 支持tab级事件订阅 -- 实现智能事件路由 -- 减少不必要的事件传播 - -#### 预期效果 - -- 完全消除tab间干扰 -- 提升系统性能和稳定性 -- 支持复杂的多tab业务逻辑 - -### 阶段三: 架构现代化升级 (长期 - 3-4周) - -#### 目标 - -- 采用现代化IPC架构设计 -- 支持未来扩展需求 -- 提供最佳的开发体验 - -#### 具体实施 - -##### 1. 设计新一代IPC协议 - -- 支持异步流式通信 -- 内置错误处理和重试机制 -- 支持类型安全的接口定义 - -##### 2. 实现智能路由系统 - -- 基于规则的事件路由 -- 动态负载均衡 -- 支持插件化扩展 - -##### 3. 建立完整的监控体系 - -- IPC调用性能监控 -- 错误统计和分析 -- 开发调试工具集成 - -## 现代化IPC架构设计 (基于utilityProcess) - -### 架构概述 - -采用utilityProcess作为IPC中介层,实现多进程协调和状态管理的现代化架构。相比Service Worker方案,utilityProcess提供更好的资源隔离、独立的进程空间,以及更强的容错能力。 - -### 整体架构图 - -```mermaid -graph TB - subgraph "Main Process" - MP[Main Process] - EM[EventBus Manager] - PM[Presenter Manager] - WM[Window Manager] - end - - subgraph "Utility Process" - UP[IPC Coordination Service] - SM[State Manager] - RM[Route Manager] - MM[Message Queue] - LM[Lifecycle Manager] - end - - subgraph "Renderer Processes" - R1[Renderer 1 - Tab A] - R2[Renderer 1 - Tab B] - R3[Renderer 2 - Tab C] - R4[Renderer 2 - Tab D] - end - - subgraph "IPC Channels" - MC1[Main ↔ Utility] - MC2[Utility ↔ Renderer] - MC3[Direct Emergency Channel] - end - - %% Main Process connections - MP --> MC1 - EM --> MC1 - PM --> MC1 - WM --> MC1 - - %% Utility Process connections - MC1 --> UP - UP --> SM - UP --> RM - UP --> MM - UP --> LM - - %% Renderer connections - UP --> MC2 - MC2 --> R1 - MC2 --> R2 - MC2 --> R3 - MC2 --> R4 - - %% Emergency direct channels - MP -.-> MC3 - MC3 -.-> R1 - MC3 -.-> R2 - MC3 -.-> R3 - MC3 -.-> R4 - - style UP fill:#e1f5fe - style SM fill:#f3e5f5 - style RM fill:#e8f5e8 - style MM fill:#fff3e0 - style LM fill:#fce4ec -``` - -### 核心组件设计 - -#### 1. IPC协调服务 (utilityProcess) - -```typescript -// src/utility/ipc-coordinator.ts -interface IPCCoordinatorConfig { - maxConcurrentCalls: number - heartbeatInterval: number - stateSnapshotInterval: number - errorRetryLimit: number -} - -class IPCCoordinator { - private stateManager: StateManager - private routeManager: RouteManager - private messageQueue: MessageQueue - private lifecycleManager: LifecycleManager - - constructor(config: IPCCoordinatorConfig) { - this.stateManager = new StateManager() - this.routeManager = new RouteManager() - this.messageQueue = new MessageQueue(config.maxConcurrentCalls) - this.lifecycleManager = new LifecycleManager() - } - - // 处理来自渲染进程的调用 - async handleRendererCall(request: IPCRequest): Promise { - const route = this.routeManager.resolveRoute(request) - const queuedCall = await this.messageQueue.enqueue(request, route) - - try { - const result = await this.executeCall(queuedCall) - await this.stateManager.updateState(request.context, result) - return { success: true, data: result } - } catch (error) { - return this.handleError(error, request) - } - } - - // 处理状态同步 - async syncStateToRenderers(stateUpdate: StateUpdate) { - const affectedTabs = this.routeManager.getAffectedTabs(stateUpdate) - await Promise.all( - affectedTabs.map((tabId) => this.sendToRenderer(tabId, 'state:update', stateUpdate)) - ) - } -} -``` - -#### 2. 智能路由管理器 - -```typescript -// src/utility/route-manager.ts -interface RouteRule { - pattern: string | RegExp - target: 'main' | 'utility' | 'renderer' - scope: 'global' | 'window' | 'tab' - priority: number -} - -class RouteManager { - private routes: Map = new Map() - private tabRegistry: Map = new Map() - - resolveRoute(request: IPCRequest): ResolvedRoute { - const rules = this.routes.get(request.method) || [] - const applicableRules = rules - .filter((rule) => this.matchesPattern(rule.pattern, request)) - .sort((a, b) => b.priority - a.priority) - - const rule = applicableRules[0] - if (!rule) { - throw new Error(`No route found for ${request.method}`) - } - - return { - target: rule.target, - scope: rule.scope, - tabContext: this.getTabContext(request.context.tabId) - } - } - - // 获取受状态更新影响的tab - getAffectedTabs(stateUpdate: StateUpdate): number[] { - switch (stateUpdate.scope) { - case 'global': - return Array.from(this.tabRegistry.keys()) - case 'window': - return this.getTabsByWindowId(stateUpdate.windowId) - case 'tab': - return [stateUpdate.tabId] - default: - return [] - } - } -} -``` - -#### 3. 分布式状态管理器 - -```typescript -// src/utility/state-manager.ts -interface StateSlice { - id: string - scope: 'global' | 'window' | 'tab' - data: unknown - version: number - lastUpdated: number - dependencies: string[] -} - -class StateManager { - private globalState: Map = new Map() - private windowStates: Map> = new Map() - private tabStates: Map> = new Map() - - async updateState(context: IPCContext, update: StateUpdate): Promise { - const slice: StateSlice = { - id: update.key, - scope: update.scope, - data: update.data, - version: this.getNextVersion(update.key), - lastUpdated: Date.now(), - dependencies: update.dependencies || [] - } - - switch (update.scope) { - case 'global': - this.globalState.set(update.key, slice) - break - case 'window': - this.ensureWindowState(context.windowId) - this.windowStates.get(context.windowId)!.set(update.key, slice) - break - case 'tab': - this.ensureTabState(context.tabId) - this.tabStates.get(context.tabId)!.set(update.key, slice) - break - } - - // 触发依赖更新 - await this.updateDependentStates(slice) - } - - getState(context: IPCContext, key: string): StateSlice | null { - // 优先级: tab -> window -> global - return ( - this.tabStates.get(context.tabId)?.get(key) || - this.windowStates.get(context.windowId)?.get(key) || - this.globalState.get(key) || - null - ) - } - - // 创建状态快照用于恢复 - createSnapshot(): StateSnapshot { - return { - global: new Map(this.globalState), - windows: new Map(this.windowStates), - tabs: new Map(this.tabStates), - timestamp: Date.now() - } - } -} -``` - -#### 4. 消息队列与并发控制 - -```typescript -// src/utility/message-queue.ts -interface QueuedMessage { - id: string - request: IPCRequest - route: ResolvedRoute - priority: number - timestamp: number - retryCount: number -} - -class MessageQueue { - private queue: PriorityQueue - private processing: Map> = new Map() - private maxConcurrency: number - - constructor(maxConcurrency: number = 10) { - this.maxConcurrency = maxConcurrency - this.queue = new PriorityQueue((a, b) => b.priority - a.priority) - } - - async enqueue(request: IPCRequest, route: ResolvedRoute): Promise { - const messageId = this.generateMessageId() - const queuedMessage: QueuedMessage = { - id: messageId, - request, - route, - priority: this.calculatePriority(request), - timestamp: Date.now(), - retryCount: 0 - } - - this.queue.enqueue(queuedMessage) - - // 返回Promise,等待处理完成 - return new Promise((resolve, reject) => { - this.processQueue() - // 注册回调... - }) - } - - private async processQueue(): Promise { - while (this.processing.size < this.maxConcurrency && !this.queue.isEmpty()) { - const message = this.queue.dequeue() - if (message) { - const promise = this.processMessage(message) - this.processing.set(message.id, promise) - - promise.finally(() => { - this.processing.delete(message.id) - this.processQueue() // 递归处理队列 - }) - } - } - } -} -``` - -### 通信协议设计 - -#### 1. 统一消息格式 - -```typescript -// src/shared/ipc-protocol.ts -interface IPCMessage { - id: string - type: 'request' | 'response' | 'event' | 'heartbeat' - source: ProcessInfo - target: ProcessInfo - timestamp: number - payload: unknown -} - -interface IPCRequest extends IPCMessage { - type: 'request' - method: string - args: unknown[] - context: IPCContext - options: CallOptions -} - -interface IPCResponse extends IPCMessage { - type: 'response' - requestId: string - success: boolean - data?: unknown - error?: IPCError -} - -interface IPCEvent extends IPCMessage { - type: 'event' - eventName: string - data: unknown - scope: EventScope -} -``` - -#### 2. 类型安全的API定义 - -```typescript -// src/shared/ipc-contracts.ts -interface IPCContract { - // 定义所有可用的方法签名 - 'config.getConfig': () => Promise - 'config.updateConfig': (updates: Partial) => Promise - 'thread.createThread': (params: CreateThreadParams) => Promise - 'llm.sendMessage': (params: SendMessageParams) => Promise -} - -// 生成类型安全的客户端 -type IPCClient = { - [K in keyof IPCContract]: IPCContract[K] -} - -// 在渲染进程中使用 -const ipcClient: IPCClient = createIPCClient() -const config = await ipcClient['config.getConfig']() -``` - -### 性能优化策略 - -#### 1. 智能缓存机制 - -```typescript -// src/utility/cache-manager.ts -class CacheManager { - private cache: Map = new Map() - - async get(key: string, fetcher: () => Promise): Promise { - const entry = this.cache.get(key) - - if (entry && !this.isExpired(entry)) { - return entry.data - } - - const data = await fetcher() - this.cache.set(key, { - data, - timestamp: Date.now(), - ttl: this.getTTL(key) - }) - - return data - } - - invalidate(pattern: string | RegExp): void { - for (const [key] of this.cache) { - if (this.matchesPattern(pattern, key)) { - this.cache.delete(key) - } - } - } -} -``` - -#### 2. 批量操作优化 - -```typescript -// src/utility/batch-processor.ts -class BatchProcessor { - private batches: Map = new Map() - private timers: Map = new Map() - - addToBatch(operation: BatchOperation): Promise { - const batchKey = this.getBatchKey(operation) - - if (!this.batches.has(batchKey)) { - this.batches.set(batchKey, []) - } - - this.batches.get(batchKey)!.push(operation) - - // 设置批量处理定时器 - this.scheduleBatchProcess(batchKey) - - return operation.promise - } - - private scheduleBatchProcess(batchKey: string): void { - if (this.timers.has(batchKey)) { - return - } - - const timer = setTimeout(() => { - this.processBatch(batchKey) - this.timers.delete(batchKey) - }, 50) // 50ms批量窗口 - - this.timers.set(batchKey, timer) - } -} -``` - -### 错误处理和恢复机制 - -#### 1. 容错处理 - -```typescript -// src/utility/error-handler.ts -class ErrorHandler { - private errorStrategies: Map = new Map() - - async handleError(error: IPCError, context: IPCContext): Promise { - const strategy = this.getErrorStrategy(error) - - switch (strategy.type) { - case 'retry': - return this.retryWithBackoff(error, context, strategy) - case 'fallback': - return this.executeFallback(error, context, strategy) - case 'circuit-breaker': - return this.handleCircuitBreaker(error, context, strategy) - default: - return this.createErrorResponse(error) - } - } - - private async retryWithBackoff( - error: IPCError, - context: IPCContext, - strategy: RetryStrategy - ): Promise { - for (let attempt = 1; attempt <= strategy.maxRetries; attempt++) { - await this.delay(strategy.backoffMs * attempt) - - try { - // 重试原始调用 - return await this.retryOriginalCall(context) - } catch (retryError) { - if (attempt === strategy.maxRetries) { - return this.createErrorResponse(retryError) - } - } - } - } -} -``` - -#### 2. 进程恢复机制 - -```typescript -// src/main/process-manager.ts -class ProcessManager { - private utilityProcess: Electron.UtilityProcess | null = null - private isRecovering = false - - async ensureUtilityProcess(): Promise { - if (!this.utilityProcess || this.utilityProcess.killed) { - await this.createUtilityProcess() - } - } - - private async createUtilityProcess(): Promise { - this.utilityProcess = utilityProcess.fork( - path.join(__dirname, '../utility/ipc-coordinator.js'), - { - serviceName: 'ipc-coordinator', - env: process.env - } - ) - - // 监听进程崩溃 - this.utilityProcess.on('exit', (code) => { - console.error(`Utility process exited with code ${code}`) - if (!this.isRecovering) { - this.recoverUtilityProcess() - } - }) - - // 设置通信通道 - await this.setupIPCChannels() - } - - private async recoverUtilityProcess(): Promise { - this.isRecovering = true - - try { - // 保存当前状态快照 - const stateSnapshot = await this.captureStateSnapshot() - - // 重新创建进程 - await this.createUtilityProcess() - - // 恢复状态 - await this.restoreState(stateSnapshot) - - // 通知所有渲染进程重新连接 - await this.notifyRenderersToReconnect() - } finally { - this.isRecovering = false - } - } -} -``` - -### 监控和调试工具 - -#### 1. 性能监控 - -```typescript -// src/utility/performance-monitor.ts -class PerformanceMonitor { - private metrics: Map = new Map() - - startTimer(operation: string): PerformanceTimer { - return { - operation, - startTime: performance.now(), - end: (metadata?: Record) => { - const duration = performance.now() - this.startTime - this.recordMetric(operation, duration, metadata) - } - } - } - - recordMetric(operation: string, duration: number, metadata?: Record): void { - if (!this.metrics.has(operation)) { - this.metrics.set(operation, []) - } - - this.metrics.get(operation)!.push({ - operation, - duration, - timestamp: Date.now(), - metadata - }) - - // 检查性能阈值 - this.checkPerformanceThresholds(operation, duration) - } - - getPerformanceReport(): PerformanceReport { - const report: PerformanceReport = {} - - for (const [operation, metrics] of this.metrics) { - report[operation] = { - count: metrics.length, - avgDuration: metrics.reduce((sum, m) => sum + m.duration, 0) / metrics.length, - minDuration: Math.min(...metrics.map((m) => m.duration)), - maxDuration: Math.max(...metrics.map((m) => m.duration)), - p95Duration: this.calculatePercentile(metrics, 0.95) - } - } - - return report - } -} -``` - -#### 2. 调试工具集成 - -```typescript -// src/dev-tools/ipc-debugger.ts -class IPCDebugger { - private isEnabled = import.meta.env.DEV - private callTrace: IPCCallTrace[] = [] - - traceCall(request: IPCRequest, response: IPCResponse, duration: number): void { - if (!this.isEnabled) return - - this.callTrace.push({ - id: request.id, - method: request.method, - tabId: request.context.tabId, - windowId: request.context.windowId, - duration, - success: response.success, - timestamp: Date.now() - }) - - // 限制追踪历史大小 - if (this.callTrace.length > 1000) { - this.callTrace.splice(0, 100) - } - } - - exportTrace(): string { - return JSON.stringify(this.callTrace, null, 2) - } - - // 与Electron DevTools集成 - setupDevToolsIntegration(): void { - if (this.isEnabled) { - // 注册DevTools扩展 - // 提供实时IPC调用监控界面 - } - } -} -``` - -## 技术实现细节 - -### 1. Tab上下文获取策略 - -#### WebContents ID映射方案 - -```typescript -// 在 TabPresenter 中维护映射关系 -private webContentsToTabId: Map = new Map(); - -// 在创建tab时建立映射 -async createTab(windowId: number, url: string, options: TabCreateOptions) { - const view = new WebContentsView(/* ... */); - const tabId = view.webContents.id; - this.webContentsToTabId.set(view.webContents.id, tabId); - // ... -} - -// 在IPC处理器中获取tabId -ipcMain.handle('presenter:call', (event, ...args) => { - const webContentsId = event.sender.id; - const tabId = presenter.tabPresenter.getTabIdByWebContentsId(webContentsId); - // ... -}); - -// 在渲染进程中通过preload API获取webContentsId (已有实现) -const webContentsId = window.api.getWebContentsId(); -// 主进程自动通过webContentsId映射到tabId和windowId -``` - -### 2. 事件路由实现 - -#### EventBus扩展设计 - -```typescript -export class EventBus extends EventEmitter { - // 新增方法 - sendToTab(tabId: number, eventName: string, ...args: unknown[]) { - const tabView = this.getTabView(tabId) - if (tabView && !tabView.webContents.isDestroyed()) { - tabView.webContents.send(eventName, ...args) - } - } - - sendToActiveTab(windowId: number, eventName: string, ...args: unknown[]) { - const activeTabId = this.getActiveTabId(windowId) - if (activeTabId) { - this.sendToTab(activeTabId, eventName, ...args) - } - } - - broadcastToTabs(tabIds: number[], eventName: string, ...args: unknown[]) { - tabIds.forEach((tabId) => this.sendToTab(tabId, eventName, ...args)) - } -} -``` - -### 3. 错误处理和日志增强 - -#### 错误上下文结构 - -```typescript -interface IPCError { - tabId?: number - windowId?: number - presenterName: string - methodName: string - timestamp: number - error: Error - callStack?: string -} -``` - -#### 日志记录策略 - -```typescript -class IPCLogger { - logCall(context: IPCCallContext) { - console.log(`[IPC Call] Tab:${context.tabId} -> ${context.presenterName}.${context.methodName}`) - } - - logError(error: IPCError) { - console.error( - `[IPC Error] Tab:${error.tabId} ${error.presenterName}.${error.methodName}:`, - error.error - ) - } -} -``` - -## 兼容性保证 - -### 1. 渐进式升级路径 - -- 阶段一完全兼容现有代码 -- 阶段二提供迁移工具和指南 -- 阶段三支持新旧协议并存 - -### 2. API兼容性保证 - -```typescript -// 保持现有API不变 -export function usePresenter(name: T): IPresenter[T] { - // 内部升级,外部接口不变 - return createEnhancedProxy(name) -} -``` - -### 3. 配置化升级 - -```typescript -// 通过配置控制新功能的启用 -interface IPCConfig { - enableTabContext: boolean - enableAdvancedRouting: boolean - enablePerformanceMonitoring: boolean -} -``` - -## 测试策略 - -### 1. 单元测试覆盖 - -- IPC调用路由正确性 -- 事件分发准确性 -- 错误处理完整性 - -### 2. 集成测试场景 - -- 多tab并发操作 -- tab切换状态一致性 -- 窗口关闭资源清理 - -### 3. 性能测试指标 - -- IPC调用延迟 -- 内存使用优化 -- 事件处理吞吐量 - -## 风险控制 - -### 1. 回滚机制 - -- 支持配置开关控制新功能 -- 保留原有代码路径 -- 提供快速回滚方案 - -### 2. 监控和告警 - -- 实时监控IPC调用异常 -- 性能指标自动告警 -- 用户体验数据收集 - -### 3. 灰度发布 - -- 按功能模块逐步启用 -- 小范围用户测试 -- 根据反馈调整策略 - -## 预期收益 - -### 短期收益 (阶段一完成) - -- 消除tab间事件错乱问题 -- 提升调试和维护效率 -- 改善用户体验 - -### 中期收益 (阶段二完成) - -- 系统稳定性显著提升 -- 支持更复杂的多tab功能 -- 开发效率大幅提升 - -### 长期收益 (阶段三完成) - -- 具备现代化的IPC架构 -- 支持未来业务扩展 -- 成为技术标杆实践 - -## 实施路线图与技术选型 - -### 第一里程碑: utilityProcess 基础架构 (Week 1-2) - -#### 技术选型说明 - -##### utilityProcess vs Service Worker 对比 - -| 特性 | utilityProcess | Service Worker | -| ------------ | ------------------------------ | --------------------------- | -| 进程隔离 | ✅ 完全独立的进程 | ❌ 仍在渲染进程上下文 | -| 资源管理 | ✅ 独立内存空间,易于监控 | ⚠️ 与渲染进程共享资源 | -| 容错能力 | ✅ 进程崩溃不影响主功能 | ❌ 崩溃可能影响整个tab | -| 调试便利性 | ✅ 独立的DevTools调试 | ⚠️ 需要特殊的调试工具 | -| 性能开销 | ⚠️ 进程间通信开销 | ✅ 较低的通信开销 | -| Electron支持 | ✅ 原生支持,API稳定 | ❌ 需要额外配置和兼容性处理 | -| 扩展性 | ✅ 易于水平扩展多个utility进程 | ⚠️ 扩展性受限 | - -**结论**: utilityProcess 在我们的多tab场景下提供更好的隔离性和容错能力,虽然有一定的进程间通信开销,但带来的架构优势远超性能成本。 - -#### 核心接口设计 - -```typescript -// src/shared/ipc-types.ts -export interface IPCContext { - tabId: number - windowId: number - webContentsId: number - userId?: string - sessionId: string - timestamp: number -} - -export interface IPCRequest { - id: string - method: string - args: unknown[] - context: IPCContext - options: CallOptions - metadata?: Record -} - -export interface CallOptions { - timeout?: number - priority?: 'low' | 'normal' | 'high' | 'critical' - retry?: RetryOptions - cache?: CacheOptions - batch?: boolean -} - -export interface RetryOptions { - maxAttempts: number - backoffMs: number - retryOn?: (error: Error) => boolean -} - -export interface CacheOptions { - key?: string - ttl?: number - invalidateOn?: string[] -} -``` - -#### utilityProcess 启动配置 - -```typescript -// src/main/utility-process-manager.ts -export class UtilityProcessManager { - private processes: Map = new Map() - - async startIPCCoordinator(): Promise { - const coordinatorProcess = utilityProcess.fork( - path.join(__dirname, '../utility/ipc-coordinator.js'), - { - serviceName: 'ipc-coordinator', - allowLoadingUnsignedLibraries: false, - env: { - NODE_ENV: process.env.NODE_ENV, - LOG_LEVEL: process.env.LOG_LEVEL || 'info', - MAX_CONCURRENT_CALLS: '50', - HEARTBEAT_INTERVAL: '30000', - STATE_SNAPSHOT_INTERVAL: '300000' - } - } - ) - - coordinatorProcess.on('spawn', () => { - console.log('IPC Coordinator process started') - this.setupIPCChannels(coordinatorProcess) - }) - - coordinatorProcess.on('exit', (code) => { - console.error(`IPC Coordinator exited with code: ${code}`) - this.handleProcessCrash('ipc-coordinator') - }) - - this.processes.set('ipc-coordinator', coordinatorProcess) - } - - private async handleProcessCrash(processName: string): Promise { - console.warn(`Utility process ${processName} crashed, attempting recovery...`) - - // 等待一定时间后重启 - await this.delay(1000) - - if (processName === 'ipc-coordinator') { - await this.startIPCCoordinator() - } - } -} -``` - -### 第二里程碑: 渐进式迁移策略 (Week 3-4) - -#### 兼容性适配器设计 - -```typescript -// src/main/presenter/compatibility-adapter.ts -export class CompatibilityAdapter { - private useUtilityProcess: boolean = false - - constructor() { - // 通过配置控制是否启用新架构 - this.useUtilityProcess = this.getConfig('experimental.useUtilityProcess', false) - } - - async handlePresenterCall( - event: IpcMainInvokeEvent, - name: string, - method: string, - ...payloads: unknown[] - ): Promise { - if (this.useUtilityProcess) { - return this.handleViaUtilityProcess(event, name, method, ...payloads) - } else { - return this.handleViaLegacyPath(event, name, method, ...payloads) - } - } - - private async handleViaUtilityProcess( - event: IpcMainInvokeEvent, - name: string, - method: string, - ...payloads: unknown[] - ): Promise { - const context = this.buildIPCContext(event) - const request: IPCRequest = { - id: this.generateRequestId(), - method: `${name}.${method}`, - args: payloads, - context, - options: this.getDefaultCallOptions() - } - - return this.forwardToUtilityProcess(request) - } - - private async handleViaLegacyPath( - event: IpcMainInvokeEvent, - name: string, - method: string, - ...payloads: unknown[] - ): Promise { - // 保持原有的直接调用逻辑 - const calledPresenter = presenter[name as keyof Presenter] - if (calledPresenter && isFunction(calledPresenter, method)) { - return calledPresenter[method](...payloads) - } - throw new Error(`Method not found: ${name}.${method}`) - } -} -``` - -#### 配置驱动的功能开关 - -```typescript -// src/shared/config-schema.ts -export interface IPCConfiguration { - experimental: { - useUtilityProcess: boolean - enableAdvancedRouting: boolean - enablePerformanceMonitoring: boolean - enableBatchProcessing: boolean - } - performance: { - maxConcurrentCalls: number - defaultTimeout: number - cacheDefaultTTL: number - batchWindowMs: number - } - reliability: { - maxRetryAttempts: number - circuitBreakerThreshold: number - heartbeatInterval: number - processRecoveryDelay: number - } - monitoring: { - enableMetrics: boolean - enableTracing: boolean - metricsReportInterval: number - maxTraceHistorySize: number - } -} - -// 默认配置 -export const DEFAULT_IPC_CONFIG: IPCConfiguration = { - experimental: { - useUtilityProcess: false, - enableAdvancedRouting: false, - enablePerformanceMonitoring: true, - enableBatchProcessing: false - }, - performance: { - maxConcurrentCalls: 50, - defaultTimeout: 30000, - cacheDefaultTTL: 300000, - batchWindowMs: 50 - }, - reliability: { - maxRetryAttempts: 3, - circuitBreakerThreshold: 5, - heartbeatInterval: 30000, - processRecoveryDelay: 1000 - }, - monitoring: { - enableMetrics: true, - enableTracing: false, - metricsReportInterval: 60000, - maxTraceHistorySize: 1000 - } -} -``` - -### 第三里程碑: 性能优化与监控 (Week 5-6) - -#### 智能缓存策略 - -```typescript -// src/utility/cache-strategies.ts -export enum CacheStrategy { - LRU = 'lru', - TTL = 'ttl', - WRITE_THROUGH = 'write-through', - WRITE_BACK = 'write-back' -} - -export class IntelligentCacheManager { - private caches: Map = new Map() - private hitRates: Map = new Map() - - constructor() { - this.caches.set(CacheStrategy.LRU, new LRUCache(1000)) - this.caches.set(CacheStrategy.TTL, new TTLCache()) - } - - async get(key: string, fetcher: () => Promise): Promise { - const strategy = this.selectOptimalStrategy(key) - const cache = this.caches.get(strategy)! - - let result = cache.get(key) - if (result !== undefined) { - this.recordHit(key) - return result - } - - // Cache miss - fetch and store - result = await fetcher() - cache.set(key, result) - this.recordMiss(key) - - return result - } - - private selectOptimalStrategy(key: string): CacheStrategy { - const hitRate = this.hitRates.get(key) || 0 - - // 根据历史命中率选择最优缓存策略 - if (hitRate > 0.8) { - return CacheStrategy.LRU // 高频访问用LRU - } else if (key.includes('config') || key.includes('model')) { - return CacheStrategy.TTL // 配置类数据用TTL - } else { - return CacheStrategy.LRU // 默认LRU - } - } -} -``` - -#### 实时性能监控 - -```typescript -// src/utility/real-time-monitor.ts -export class RealTimeMonitor { - private metricsBuffer: PerformanceMetric[] = [] - private alertRules: AlertRule[] = [] - - recordMetric(metric: PerformanceMetric): void { - this.metricsBuffer.push(metric) - - // 实时告警检查 - this.checkAlertRules(metric) - - // 定期清理缓冲区 - if (this.metricsBuffer.length > 10000) { - this.flushMetrics() - } - } - - private checkAlertRules(metric: PerformanceMetric): void { - for (const rule of this.alertRules) { - if (rule.condition(metric)) { - this.triggerAlert(rule, metric) - } - } - } - - private triggerAlert(rule: AlertRule, metric: PerformanceMetric): void { - const alert: Alert = { - id: this.generateAlertId(), - rule: rule.name, - metric, - severity: rule.severity, - timestamp: Date.now(), - message: rule.message(metric) - } - - // 发送告警到主进程 - this.sendAlertToMain(alert) - } - - addAlertRule(rule: AlertRule): void { - this.alertRules.push(rule) - } - - // 预定义告警规则 - setupDefaultAlertRules(): void { - this.addAlertRule({ - name: 'high-latency', - condition: (metric) => metric.duration > 5000, - severity: 'warning', - message: (metric) => `High latency detected: ${metric.operation} took ${metric.duration}ms` - }) - - this.addAlertRule({ - name: 'high-error-rate', - condition: (metric) => { - const recentErrors = this.getRecentErrorRate(metric.operation, 60000) // 1分钟内 - return recentErrors > 0.1 // 错误率超过10% - }, - severity: 'critical', - message: (metric) => `High error rate for ${metric.operation}` - }) - } -} -``` - -### 第四里程碑: 生产环境部署 (Week 7-8) - -#### 部署检查清单 - -```typescript -// src/main/deployment-checker.ts -export class DeploymentChecker { - async performPreDeploymentChecks(): Promise { - const checks: DeploymentCheck[] = [ - this.checkUtilityProcessSupport(), - this.checkPerformanceBaseline(), - this.checkMemoryUsage(), - this.checkErrorHandling(), - this.checkBackwardCompatibility() - ] - - const results = await Promise.all(checks) - - return { - passed: results.every((r) => r.success), - checks: results, - recommendations: this.generateRecommendations(results) - } - } - - private async checkUtilityProcessSupport(): Promise { - try { - // 测试创建utility process - const testProcess = utilityProcess.fork(path.join(__dirname, '../utility/test-process.js')) - await this.waitForProcessReady(testProcess, 5000) - testProcess.kill() - - return { name: 'utilityProcess-support', success: true } - } catch (error) { - return { - name: 'utilityProcess-support', - success: false, - error: error.message - } - } - } - - private async checkPerformanceBaseline(): Promise { - const metrics = await this.runPerformanceTest() - - const baselineThresholds = { - avgLatency: 100, // ms - p95Latency: 500, // ms - errorRate: 0.01 // 1% - } - - const passed = - metrics.avgLatency <= baselineThresholds.avgLatency && - metrics.p95Latency <= baselineThresholds.p95Latency && - metrics.errorRate <= baselineThresholds.errorRate - - return { - name: 'performance-baseline', - success: passed, - metrics - } - } -} -``` - -#### 监控指标定义 - -```typescript -// src/shared/monitoring-metrics.ts -export interface SystemMetrics { - // IPC性能指标 - ipc: { - totalCalls: number - avgLatency: number - p95Latency: number - p99Latency: number - errorRate: number - timeoutRate: number - } - - // 进程健康指标 - processes: { - main: ProcessHealth - utility: ProcessHealth - renderers: ProcessHealth[] - } - - // 资源使用指标 - resources: { - memoryUsage: MemoryMetrics - cpuUsage: number - handleCount: number - } - - // 业务指标 - business: { - activeTabCount: number - concurrentOperations: number - cacheHitRate: number - stateSize: number - } -} - -export interface ProcessHealth { - pid: number - status: 'healthy' | 'warning' | 'critical' - uptime: number - memoryUsage: number - cpuUsage: number - lastHeartbeat: number -} -``` - -## 技术债务和迁移注意事项 - -### 现有代码影响分析 - -#### 需要修改的文件清单 - -```typescript -// 高优先级 - 核心IPC逻辑 -const HIGH_PRIORITY_FILES = [ - 'src/main/presenter/index.ts', // 主要的IPC处理器 - 'src/renderer/src/composables/usePresenter.ts', // 渲染进程IPC客户端 - 'src/main/eventbus.ts', // 事件总线 - 'src/shared/presenter.d.ts' // 类型定义 -] - -// 中优先级 - Presenter实现 -const MEDIUM_PRIORITY_FILES = [ - 'src/main/presenter/configPresenter.ts', - 'src/main/presenter/threadPresenter.ts', - 'src/main/presenter/tabPresenter.ts' - // ... 其他presenter文件 -] - -// 低优先级 - 测试和配置 -const LOW_PRIORITY_FILES = [ - 'tests/**/*.spec.ts', // 测试文件需要更新 - 'src/main/index.ts', // 主进程入口 - 'electron.vite.config.ts' // 构建配置 -] -``` - -#### 迁移风险评估 - -| 风险类别 | 风险等级 | 影响范围 | 缓解措施 | -| ---------- | -------- | ----------- | ------------------------ | -| 性能回退 | 中 | 所有IPC调用 | 性能基准测试,渐进式启用 | -| 兼容性问题 | 低 | 现有API | 兼容性适配器,双路径支持 | -| 进程稳定性 | 中 | utility进程 | 自动恢复机制,监控告警 | -| 调试复杂度 | 中 | 开发体验 | 专用调试工具,详细文档 | -| 内存占用 | 低 | 系统资源 | 内存监控,优化配置 | - -### 回滚策略 - -#### 快速回滚机制 - -```typescript -// src/main/rollback-manager.ts -export class RollbackManager { - private currentConfig: IPCConfiguration - private backupConfig: IPCConfiguration - - async performEmergencyRollback(reason: string): Promise { - console.warn(`Performing emergency rollback: ${reason}`) - - // 1. 立即禁用新特性 - await this.disableExperimentalFeatures() - - // 2. 恢复到legacy IPC路径 - await this.switchToLegacyIPC() - - // 3. 清理utility process - await this.cleanupUtilityProcesses() - - // 4. 记录回滚事件 - this.logRollbackEvent(reason) - - // 5. 通知相关系统 - this.notifyRollbackComplete() - } - - private async disableExperimentalFeatures(): Promise { - this.currentConfig.experimental = { - useUtilityProcess: false, - enableAdvancedRouting: false, - enablePerformanceMonitoring: true, // 保持监控 - enableBatchProcessing: false - } - - await this.saveConfig(this.currentConfig) - } -} -``` - -## 下一步行动计划 - -### 立即行动项 (本周内) - -1. **环境准备** - - - [ ] 创建 `src/utility/` 目录结构 - - [ ] 设置TypeScript配置支持utility process - - [ ] 安装必要的开发依赖 - -2. **原型开发** - - - [ ] 实现基础的 `IPCCoordinator` 类 - - [ ] 创建简单的 `RouteManager` 原型 - - [ ] 测试utility process的创建和通信 - -3. **兼容性设计** - - [ ] 设计 `CompatibilityAdapter` 接口 - - [ ] 创建配置文件模板 - - [ ] 准备功能开关机制 - -### 短期目标 (2周内) - -1. **核心功能实现** - - - [ ] 完成 `StateManager` 的设计和实现 - - [ ] 实现基础的 `MessageQueue` - - [ ] 建立错误处理框架 - -2. **集成测试** - - - [ ] 单个presenter方法的调用测试 - - [ ] 多tab并发场景测试 - - [ ] 错误恢复机制测试 - -3. **性能基准** - - [ ] 建立当前系统的性能基准 - - [ ] 设计性能测试用例 - - [ ] 准备监控指标收集 - -### 中期目标 (1个月内) - -1. **生产就绪** - - - [ ] 完整的监控和告警系统 - - [ ] 部署检查和验证流程 - - [ ] 回滚机制的完善 - -2. **文档和培训** - - [ ] API文档更新 - - [ ] 开发者指南编写 - - [ ] 团队培训计划 - ---- - -**总结**: 这个基于utilityProcess的现代化IPC架构设计提供了完整的技术方案,从渐进式迁移到最终的生产部署。通过详细的实施路线图和风险控制措施,确保了升级过程的安全性和可控性。接下来的关键是按照计划逐步实施,并在每个里程碑节点进行充分的测试验证。 diff --git a/docs/ipc/optimization-recommendations.md b/docs/ipc/optimization-recommendations.md new file mode 100644 index 000000000..3a18eabff --- /dev/null +++ b/docs/ipc/optimization-recommendations.md @@ -0,0 +1,127 @@ +# IPC 使用优化建议 + +## 发现的问题 + +通过审查代码,发现28个文件中存在`SendTarget.ALL_WINDOWS`的使用,其中很多可以优化为精确路由。 + +## 优化机会分析 + +### 高优先级优化目标 + +以下presenter中的广播使用可能存在优化空间: + +1. **ThreadPresenter** (`src/main/presenter/threadPresenter/index.ts`) + - 流事件处理可能只需通知发起请求的Tab + - 消息更新可能只影响特定Thread的Tab + +2. **LLMProviderPresenter** (`src/main/presenter/llmProviderPresenter/index.ts`) + - 模型响应应该只发送给发起请求的Tab + - Provider状态变化可能只需通知使用该Provider的Tab + +3. **McpPresenter** (`src/main/presenter/mcpPresenter/index.ts`) + - Tool执行结果应该只通知调用方Tab + - MCP服务器状态可以精确通知相关Tab + +### 合理的广播使用 + +以下场景的`ALL_WINDOWS`使用是合理的: + +1. **ConfigPresenter** - 全局配置变更 +2. **DevicePresenter** - 系统级设备状态变化 +3. **UpgradePresenter** - 应用更新通知 +4. **NotificationPresenter** - 系统通知 +5. **ShortcutPresenter** - 全局快捷键操作 + +## 具体优化建议 + +### 1. ThreadPresenter优化示例 + +```typescript +// 当前可能的实现 +class ThreadPresenter { + handleStreamResponse(data: StreamData) { + eventBus.sendToRenderer('stream:data', SendTarget.ALL_WINDOWS, data) + } +} + +// 建议优化为 +class ThreadPresenter { + handleStreamResponse(tabId: number, data: StreamData) { + // 只通知发起Stream的Tab + eventBus.sendToTab(tabId, 'stream:data', data) + } +} +``` + +### 2. LLMProviderPresenter优化示例 + +```typescript +// 优化Provider响应处理 +class LLMProviderPresenter { + async processResponse(tabId: number, response: LLMResponse) { + // 处理响应逻辑... + + // 只通知发起请求的Tab + eventBus.sendToTab(tabId, 'llm:response', response) + + // 如果需要记录,只通知主进程 + eventBus.sendToMain('llm:response-logged', { tabId, response }) + } +} +``` + +### 3. McpPresenter优化示例 + +```typescript +// 优化Tool执行结果通知 +class McpPresenter { + async executeTool(tabId: number, toolName: string, args: any) { + try { + const result = await this.callTool(toolName, args) + + // 只通知调用方Tab + eventBus.sendToTab(tabId, 'mcp:tool-result', { toolName, result }) + + } catch (error) { + // 错误也只通知调用方 + eventBus.sendToTab(tabId, 'mcp:tool-error', { toolName, error }) + } + } +} +``` + +## 实施计划 + +### 阶段1: 分析和准备 +- [ ] 详细分析每个使用`ALL_WINDOWS`的场景 +- [ ] 确定哪些确实需要Tab上下文信息 +- [ ] 准备测试用例验证优化效果 + +### 阶段2: 逐步优化 +- [ ] 从ThreadPresenter开始,优化流事件处理 +- [ ] 优化LLMProviderPresenter的响应处理 +- [ ] 优化McpPresenter的Tool执行通知 +- [ ] 验证每个优化的正确性 + +### 阶段3: 性能监控 +- [ ] 添加IPC调用统计 +- [ ] 对比优化前后的性能差异 +- [ ] 建立持续监控机制 + +## 预期收益 + +1. **性能提升**: 减少不必要的事件处理,降低CPU使用 +2. **用户体验**: 避免Tab间的状态干扰 +3. **开发体验**: 更清晰的事件流向,便于调试 +4. **代码质量**: 更符合最佳实践的事件处理 + +## 风险控制 + +1. **渐进式优化**: 一次只优化一个presenter +2. **充分测试**: 确保优化不影响现有功能 +3. **可回滚**: 保留原始实现,便于快速回滚 +4. **文档更新**: 及时更新相关文档和示例 + +## 结论 + +DeepChat的IPC架构已经非常完善,主要的优化空间在于更好地利用已有的精确路由功能。通过系统性的代码审查和优化,可以进一步提升应用的性能和用户体验。 \ No newline at end of file diff --git a/docs/knowledge-presenter-complete.md b/docs/knowledge-presenter-complete.md new file mode 100644 index 000000000..557cdcbd0 --- /dev/null +++ b/docs/knowledge-presenter-complete.md @@ -0,0 +1,229 @@ +# Knowledge Presenter 完整文档 + +## 模块概述 + +Knowledge Presenter 是 DeepChat 中负责管理本地知识库的核心模块,它允许用户将本地文件无缝集成到与大语言模型的对话中。通过利用检索增强生成(RAG)技术,DeepChat 能够从用户提供的文档中提取相关信息,作为上下文(Context)来生成更准确、更具个性化的回答。该功能完全在本地运行,确保了用户数据的隐私和安全。 + +## 核心功能 + +1. **知识库生命周期管理**: 创建、更新、删除知识库实例,配置管理和持久化存储。 +2. **文件管理**: 文件添加、删除、重新处理,状态跟踪和进度反馈。 +3. **向量化与检索**: 文件分片、嵌入生成、向量存储和相似度检索。 +4. **任务调度**: 全局串行任务队列,并发控制和异常处理。 + +## 设计目标 + +1. **无缝集成**: 将本地文件作为上下文源,自然地融入对话流程。 +2. **用户友好**: 提供清晰的文件管理界面,用户可以轻松添加、删除和查看文件状态。 +3. **高性能**: 文件处理(分片、向量化)在后台异步执行,不阻塞 UI,并提供实时进度反馈。 +4. **高准确性**: 通过优化的分片和检索策略,确保检索到的上下文与用户问题高度相关。 +5. **隐私安全**: 所有文件处理和数据存储均在本地完成,用户数据不会离开本地设备。 +6. **可扩展性**: 架构设计支持未来轻松扩展更多文件类型、检索策略和数据源。 + +## 架构设计 + +### 核心组件 + +```mermaid +classDiagram + class IKnowledgePresenter { + <> + +create(config) + +update(config) + +delete(id) + +addFile(id, filePath) + +deleteFile(id, fileId) + +reAddFile(id, fileId) + +queryFile(id, fileId) + +listFiles(id) + +similarityQuery(id, key) + +getTaskQueueStatus() + +pauseAllRunningTasks(id) + +resumeAllPausedTasks(id) + +closeAll() + } + + class KnowledgePresenter { + -configPresenter: IConfigPresenter + -knowledgeStoreInstances: Map + -knowledgeTaskPresenter: KnowledgeTaskPresenter + -knowledgeDir: string + +initialize() + +create(config) + +update(config) + +delete(id) + +addFile(id, filePath) + +deleteFile(id, fileId) + +reAddFile(id, fileId) + +queryFile(id, fileId) + +listFiles(id) + +similarityQuery(id, key) + +getTaskQueueStatus() + +pauseAllRunningTasks(id) + +resumeAllPausedTasks(id) + +createStorePresenter(id, config) + +getOrCreateStorePresenter(id, config) + +closeStorePresenterIfExists(id) + +getVectorDatabasePresenter(config) + } + + class KnowledgeStorePresenter { + -config: KnowledgeStoreConfig + -vectorDbPresenter: IVectorDatabasePresenter + -taskPresenter: KnowledgeTaskPresenter + -filePresenter: FilePresenter + +addFile(filePath) + +deleteFile(fileId) + +reAddFile(fileId) + +queryFile(fileId) + +listFiles() + +similarityQuery(key) + +close() + +pauseAllTasks() + +resumeAllTasks() + } + + class KnowledgeTaskPresenter { + -taskQueue: KnowledgeTask[] + -runningTasks: Map + -taskIdCounter: number + -eventBus: EventBus + +addTask(task) + +pauseTask(taskId) + +resumeTask(taskId) + +cancelTask(taskId) + +getQueueStatus() + +pauseAllRunningTasks(storeId) + +resumeAllPausedTasks(storeId) + +processQueue() + +executeTask(task) + } + + IKnowledgePresenter <|.. KnowledgePresenter + KnowledgePresenter --> KnowledgeStorePresenter : manages + KnowledgePresenter --> KnowledgeTaskPresenter : uses + KnowledgeStorePresenter --> KnowledgeTaskPresenter : uses +``` + +### 类详细设计 + +#### 1. KnowledgePresenter + +`KnowledgePresenter` (`src/main/presenter/knowledgePresenter/index.ts`) 是模块的主入口,实现了 `IKnowledgePresenter` 接口,主要职责: + +- 依赖 `IConfigPresenter` 获取知识库配置。 +- 初始化并管理 `KnowledgeStorePresenter` 实例和 `KnowledgeTaskPresenter`。 +- **初始化流程**: + - 创建知识库存储目录。 + - 监听配置变更事件,动态管理知识库实例。 +- 提供知识库生命周期管理 (创建/更新/删除)、文件管理和检索的接口。 +- 管理 `KnowledgeStorePresenter` 实例的缓存,避免重复创建数据库连接。 +- 通过 `eventBus` 监听配置变更并触发相关事件。 + +**关键方法**: + +- `create()`, `update()`, `delete()`: 知识库生命周期管理。 +- `addFile()`, `deleteFile()`, `reAddFile()`, `queryFile()`, `listFiles()`: 文件管理。 +- `similarityQuery()`: 相似度检索。 +- `getTaskQueueStatus()`, `pauseAllRunningTasks()`, `resumeAllPausedTasks()`: 任务管理。 +- `closeAll()`, `destroy()`, `beforeDestroy()`: 资源清理。 +- `createStorePresenter()`, `getOrCreateStorePresenter()`, `closeStorePresenterIfExists()`: 实例管理。 +- `getVectorDatabasePresenter()`: 数据库实例创建。 + +#### 2. KnowledgeStorePresenter + +`KnowledgeStorePresenter` (`src/main/presenter/knowledgePresenter/knowledgeStorePresenter.ts`) 负责单个知识库实例的管理: + +- 管理向量数据库连接和文件处理逻辑。 +- 处理文件的添加、删除、更新操作。 +- 执行相似度检索查询。 +- 管理知识库的生命周期和资源清理。 + +#### 3. KnowledgeTaskPresenter + +`KnowledgeTaskPresenter` (`src/main/presenter/knowledgePresenter/knowledgeTaskPresenter.ts`) 负责任务队列管理: + +- 维护全局的任务队列,确保文件处理的有序执行。 +- 支持任务的暂停、恢复和取消操作。 +- 提供任务状态监控和进度反馈。 +- 处理任务执行过程中的异常情况。 + +## 用户交互流程 + +```mermaid +graph TD + A[用户配置知识库] --> B{Knowledge Presenter}; + B --> |1. 创建知识库实例| C[DuckDB 向量数据库]; + A --> |2. 添加文件| D[文件处理队列]; + D --> |3. 文件解析| E[文本提取]; + E --> |4. 文本分片| F[智能分片]; + F --> |5. 向量化| G[嵌入模型]; + G --> |6. 存储| C; + + H[用户发起对话] --> I[相似度检索]; + I --> C; + C --> |7. 返回相关文档| J[上下文增强]; + J --> |8. 生成回答| K[LLM 响应]; + + style B fill:#e1f5fe + style C fill:#f3e5f5 + style D fill:#e8f5e8 + style I fill:#fff3e0 +``` + +## 技术特性 + +### ✅ **支持的文件格式** +- 文本文件: `.txt`, `.md`, `.csv` +- 文档文件: `.pdf`, `.doc`, `.docx` +- 代码文件: `.js`, `.ts`, `.py`, `.java`, `.cpp` 等 +- 其他格式可通过扩展支持 + +### ✅ **智能分片策略** +- 基于内容语义的分片算法 +- 可配置的分片大小和重叠度 +- 保持文档结构完整性 + +### ✅ **高效向量检索** +- 基于 DuckDB 的向量数据库 +- 余弦相似度检索 +- 可配置的检索结果数量 + +### ✅ **异步任务处理** +- 后台文件处理,不阻塞UI +- 实时进度反馈 +- 任务队列管理和优先级控制 + +### ✅ **用户友好界面** +- 直观的文件管理界面 +- 实时状态更新 +- 错误处理和用户反馈 + +## 使用方法 + +1. **创建知识库**: 在设置中配置新的知识库实例 +2. **添加文件**: 通过文件选择器添加本地文件到知识库 +3. **文件处理**: 系统自动在后台处理文件(分片、向量化) +4. **开始对话**: 在对话中,系统会自动从知识库检索相关内容 +5. **管理文件**: 可以删除、重新处理文件或查看文件状态 + +## 配置选项 + +- **嵌入模型**: 选择用于向量化的模型 +- **分片大小**: 配置文本分片的大小 +- **检索数量**: 设置每次检索返回的结果数量 +- **相似度阈值**: 设置相似度检索的最小阈值 + +## 性能优化 + +- **增量处理**: 只处理新增或修改的文件 +- **缓存机制**: 缓存常用的检索结果 +- **异步处理**: 所有耗时操作都在后台执行 +- **资源管理**: 自动清理不再使用的资源 + +## 未来扩展 + +- 支持更多文件格式 +- 增强的检索算法 +- 多语言支持 +- 云存储集成选项 \ No newline at end of file diff --git a/docs/mcp-architecture.md b/docs/mcp-architecture.md new file mode 100644 index 000000000..3012cb89e --- /dev/null +++ b/docs/mcp-architecture.md @@ -0,0 +1,474 @@ +# MCP (Model Context Protocol) 完整架构文档 + +## 模块概述 + +MCP (Model Context Protocol) Presenter 是 DeepChat 中负责管理 MCP 服务器和工具的核心模块,主要功能包括: + +1. **MCP 服务器管理**: 启动、停止、配置管理、默认服务器设置、npm registry 速度测试 +2. **MCP 工具管理**: 定义获取、名称冲突处理、缓存、权限检查和调用 +3. **LLM 适配**: 在 MCP 工具格式与不同 LLM 提供商 (OpenAI, Anthropic, Gemini) 的工具格式之间进行转换 +4. **状态与事件**: 监控服务器状态并通过 `eventBus` 发布相关事件 +5. **权限系统**: 提供用户友好的权限请求和管理机制 + +## 核心组件架构 + +```mermaid +classDiagram + class IMCPPresenter { + <> + +getMcpServers() + +getMcpClients() + +startServer() + +stopServer() + +callTool() + +getAllToolDefinitions() + +mcpToolsToOpenAITools() + +openAIToolsToMcpTool() + +mcpToolsToAnthropicTools() + +anthropicToolUseToMcpTool() + +mcpToolsToGeminiTools() + +geminiFunctionCallToMcpTool() + +addMcpServer() + +removeMcpServer() + +updateMcpServer() + +getMcpDefaultServers() + +addMcpDefaultServer() + +removeMcpDefaultServer() + +toggleMcpDefaultServer() + +getMcpEnabled() + +setMcpEnabled() + +resetToDefaultServers() + } + + class McpPresenter { + -serverManager: ServerManager + -toolManager: ToolManager + -configPresenter: IConfigPresenter + +initialize() + +getMcpServers() + +getMcpClients() + +startServer() + +stopServer() + +callTool() + +getAllToolDefinitions() + +mcpToolsToOpenAITools() + +openAIToolsToMcpTool() + +mcpToolsToAnthropicTools() + +anthropicToolUseToMcpTool() + +mcpToolsToGeminiTools() + +geminiFunctionCallToMcpTool() + +addMcpServer() + +removeMcpServer() + +updateMcpServer() + +getMcpDefaultServers() + +addMcpDefaultServer() + +removeMcpDefaultServer() + +toggleMcpDefaultServer() + +getMcpEnabled() + +setMcpEnabled() + +resetToDefaultServers() + } + + class ServerManager { + -clients: Map + -configPresenter: IConfigPresenter + -npmRegistry: string | null + +testNpmRegistrySpeed() + +getNpmRegistry() + +startServer() + +stopServer() + +getRunningClients() + +getDefaultServerNames() + +getDefaultClients() + +getClient() + +isServerRunning() + } + + class ToolManager { + -configPresenter: IConfigPresenter + -serverManager: ServerManager + -cachedToolDefinitions: MCPToolDefinition[] | null + -toolNameToTargetMap: Map | null + +getAllToolDefinitions() + +callTool() + +checkToolPermission() + +handleServerListUpdate() + +getRunningClients() + } + + class McpClient { + +serverName: string + +serverConfig: Record + -client: Client | null + -transport: Transport | null + -isConnected: boolean + -npmRegistry: string | null + +connect() + +disconnect() + +callTool() + +listTools() + +readResource() + +isServerRunning() + } + + class McpConfHelper { + -mcpStore: ElectronStore + +getMcpServers() + +setMcpServers() + +getMcpDefaultServers() + +addMcpDefaultServer() + +removeMcpDefaultServer() + +toggleMcpDefaultServer() + +setMcpEnabled() + +getMcpEnabled() + +addMcpServer() + +removeMcpServer() + +updateMcpServer() + +resetToDefaultServers() + +onUpgrade() + } + + McpPresenter ..|> IMCPPresenter + McpPresenter o-- ServerManager + McpPresenter o-- ToolManager + McpPresenter o-- IConfigPresenter + + ServerManager o-- IConfigPresenter + ServerManager "1" *-- "0..*" McpClient : manages + + ToolManager o-- IConfigPresenter + ToolManager o-- ServerManager : uses + + McpConfHelper ..> IConfigPresenter : (typically implements relevant parts) +``` + +## 数据流与工具调用流程 + +### 初始化与默认服务器启动 + +```mermaid +sequenceDiagram + participant AppStartup + participant McpPresenter + participant ServerManager + participant IConfigPresenter + participant McpClient + + AppStartup->>McpPresenter: constructor(configPresenter) + McpPresenter->>ServerManager: constructor(configPresenter) + McpPresenter->>ToolManager: constructor(configPresenter, serverManager) + AppStartup->>McpPresenter: initialize() + McpPresenter->>IConfigPresenter: getMcpServers() + McpPresenter->>IConfigPresenter: getMcpDefaultServers() + McpPresenter->>ServerManager: testNpmRegistrySpeed() + ServerManager-->>McpPresenter: (registry selected) + loop For each defaultServerName + McpPresenter->>ServerManager: startServer(defaultServerName) + ServerManager->>IConfigPresenter: getMcpServers() (to get config) + ServerManager->>McpClient: new McpClient(name, config, npmRegistry) + ServerManager->>McpClient: connect() + McpClient->>McpClient: Establish Transport (stdio/sse/http/inmemory) + McpClient->>MCP Server: Connect Request + MCP Server-->>McpClient: Connected + McpClient-->>ServerManager: Connected (triggers status event) + ServerManager-->>McpPresenter: Success / Error + end +``` + +### LLM 工具调用流程 (以 OpenAI 为例) + +```mermaid +sequenceDiagram + participant LLM Provider + participant McpPresenter + participant ToolManager + participant McpClient + participant MCP Server + + Note over LLM Provider, McpPresenter: 1. 获取和转换工具定义 (按需) + LLM Provider->>McpPresenter: getAllToolDefinitions() + McpPresenter->>ToolManager: getAllToolDefinitions() + ToolManager->>ServerManager: getRunningClients() + ServerManager-->>ToolManager: List + loop For each client + ToolManager->>McpClient: listTools() + McpClient-->>ToolManager: Raw Tool List + end + ToolManager->>ToolManager: 处理冲突, 缓存定义和映射 + ToolManager-->>McpPresenter: Processed MCPToolDefinition[] + McpPresenter->>McpPresenter: mcpToolsToOpenAITools(definitions) + McpPresenter-->>LLM Provider: OpenAI Tool Format + + Note over LLM Provider, McpPresenter: 2. LLM 生成工具调用请求 + LLM Provider->>LLM Provider: LLM decides to call tool(s) + LLM Provider->>LLM Provider: Generates tool_calls (OpenAI Format) + + Note over LLM Provider, McpPresenter: 3. 转换并执行工具调用 + loop For each tool_call from LLM + LLM Provider->>McpPresenter: openAIToolsToMcpTool(tool_call) + McpPresenter-->>LLM Provider: Standard MCPToolCall + LLM Provider->>McpPresenter: callTool(mcpToolCall) + McpPresenter->>ToolManager: callTool(mcpToolCall) + ToolManager->>ToolManager: Lookup target client & original name + ToolManager->>ToolManager: checkToolPermission() + alt Permission Denied + ToolManager-->>McpPresenter: Error Response + else Permission Granted + ToolManager->>McpClient: callTool(originalToolName, args) + McpClient->>MCP Server: Execute Tool + MCP Server-->>McpClient: Raw Result + McpClient-->>ToolManager: ToolCallResult + ToolManager-->>McpPresenter: MCPToolResponse (triggers event) + end + McpPresenter-->>LLM Provider: Formatted Response (Success or Error) + end + + Note over LLM Provider: 4. 处理结果并继续 + LLM Provider->>LLM Provider: Add tool response(s) to context + LLM Provider->>LLM Provider: Generate next response or further calls +``` + +## 权限系统设计 + +### 权限请求流程 + +DeepChat 的 MCP 权限系统提供用户友好的权限请求和管理机制: + +``` +User Input → LLM → Tool Call → Permission Check → [Permission Required] + ↓ + Create Permission Block + ↓ + Display Permission UI + ↓ + User Decision (Allow/Deny) + ↓ + [If Allowed] → Continue Tool Execution → Agent Loop Continues +``` + +### 权限消息块类型 + +```typescript +type PermissionRequestBlock = { + type: 'tool_call_permission' + content: string // Description of permission needed + status: 'pending' | 'granted' | 'denied' + timestamp: number + tool_call: { + id: string + name: string + params: string + server_name: string + server_icons: string + server_description: string + } + extra: { + permissionType: 'read' | 'write' | 'all' + serverName: string + toolName: string + needsUserAction: boolean // Whether user action is still needed + grantedPermissions?: string[] // What permissions were granted + } +} +``` + +### 权限检查流程 + +```mermaid +sequenceDiagram + participant LLM as LLMProviderPresenter + participant TM as ToolManager + participant TP as ThreadPresenter + participant UI as Frontend + + LLM->>TM: callTool(toolCall) + TM->>TM: checkPermission() + alt Permission Required + TM-->>LLM: PermissionRequiredResponse + LLM->>TP: yield permission block + TP->>UI: render permission UI + UI->>UI: user clicks Allow/Deny + UI->>TP: permissionResponse + TP->>LLM: continueWithPermission() + LLM->>TM: callTool(toolCall) // retry + TM-->>LLM: success response + else Permission Granted + TM-->>LLM: normal response + end +``` + +### 权限类型 + +#### Read Permissions +用于只读数据的工具: +- `list_directory` +- `read_file` +- `get_information` + +#### Write Permissions +用于修改数据的工具: +- `write_file` +- `create_directory` +- `delete_file` +- `execute_command` + +#### All Permissions +授予读写访问权限给服务器。 + +## 核心设计原则 + +1. **分层架构**: + - 接口层 (`IMCPPresenter`): 定义公共 API + - 展示层 (`McpPresenter`): 协调者,处理 LLM 适配和委托 + - 管理层 (`ServerManager`, `ToolManager`): 处理服务器生命周期和工具管理/调用逻辑 + - 配置层 (`IConfigPresenter`, `McpConfHelper`): 提供和持久化配置 + - 客户端层 (`McpClient`): 封装与单个 MCP 服务器的通信 + +2. **多协议支持**: + - `McpClient` 通过不同的 `Transport` 实现支持 stdio, SSE, HTTP, InMemory + +3. **工具管理与适配**: + - `ToolManager` 集中处理工具定义获取、**名称冲突解决**和缓存 + - `McpPresenter` 负责在 MCP 格式与各 LLM 特定格式间转换 + - `ToolManager` 使用映射表 (`toolNameToTargetMap`) 将(可能重命名的)工具调用路由到正确的 `McpClient` 和原始工具名称 + +4. **配置驱动与持久化**: + - 行为由 `McpConfHelper` 管理的配置驱动 + - 使用 `electron-store` 进行持久化 + +5. **错误处理与事件通知**: + - 在服务器启动 (`ServerManager`)、工具调用 (`ToolManager`) 等环节包含错误处理 + - 通过 `eventBus` 发布状态变更和结果事件 + +6. **性能与环境优化**: + - `ServerManager` 自动测试并选择最快的 npm registry + - `McpClient` 精细化处理 `stdio` 进程的环境变量 (PATH, 代理, npm registry) + +## 事件系统 + +MCP Presenter 通过 `eventBus` 发出以下事件: + +| 事件名称 | 触发时机 | 触发源 | 参数 | +| ---------------------------------- | -------------------------------- | ------------- | ------------------------------------------------ | +| `MCP_EVENTS.SERVER_STARTED` | 服务器成功启动 | McpPresenter | serverName (string) | +| `MCP_EVENTS.SERVER_STOPPED` | 服务器停止 | McpPresenter | serverName (string) | +| `MCP_EVENTS.TOOL_CALL_RESULT` | 工具调用完成 | ToolManager | MCPToolResponse | +| `MCP_EVENTS.CONFIG_CHANGED` | MCP 配置 (服务器/默认/启用) 变更 | McpConfHelper | { mcpServers, defaultServers, mcpEnabled } | +| `MCP_EVENTS.SERVER_STATUS_CHANGED` | MCP 客户端连接状态变化 | McpClient | { name: string, status: 'running' \| 'stopped' } | +| `MCP_EVENTS.CLIENT_LIST_UPDATED` | 运行中的 MCP 客户端列表更新 | ServerManager | (无) | + +## 配置管理 + +MCP 相关配置通过 `McpConfHelper` 管理,并存储在 ElectronStore (`mcp-settings`) 中。 + +**核心配置项**: + +- `mcpServers`: `Record` - 存储所有已配置的 MCP 服务器及其配置 +- `defaultServers`: `string[]` - 默认启动的服务器名称列表 +- `mcpEnabled`: `boolean` - 全局启用/禁用 MCP 功能的开关 + +**`MCPServerConfig` 接口**: + +```typescript +interface MCPServerConfig { + command?: string // 可执行命令 (stdio 类型) + args?: string[] // 命令行参数 + env?: Record // 环境变量 + type?: 'stdio' | 'sse' | 'http' | 'inmemory' // 服务器类型 + baseUrl?: string // 用于 SSE/HTTP 类型的服务器 URL + autoApprove?: string[] // 自动批准的权限列表 ('all', 'read', 'write', 或具体工具名) + icons?: string // 服务器图标 (emoji 或 URL) + descriptions?: string // 服务器描述 + disable?: boolean // 是否禁用该服务器 (UI 层面) + customHeaders?: Record // 用于 SSE/HTTP 的自定义请求头 (包含 Authorization) +} +``` + +## 安全考虑 + +1. **默认拒绝**: 所有工具调用默认需要明确权限 +2. **服务器隔离**: 权限按服务器分别授予,而非全局 +3. **权限提升**: 用户必须明确授予更高级别权限 +4. **审计跟踪**: 所有权限决策记录在消息历史中 +5. **会话范围**: 临时权限仅在当前会话有效 + +## Session 自动恢复机制 + +### 问题背景 + +在 MCP Streamable HTTP 传输协议中,当服务器重启或 session 过期时,客户端会收到错误。DeepChat 实现了简单高效的 session 错误处理机制。 + +### Session 错误检测 + +```typescript +function isSessionError(error: unknown): error is SessionError { + if (error instanceof Error) { + const message = error.message.toLowerCase() + + // 检查特定的MCP Streamable HTTP session错误模式 + const sessionErrorPatterns = [ + 'no valid session', + 'session expired', + 'session not found', + 'invalid session', + 'session id', + 'mcp-session-id' + ] + + const httpErrorPatterns = [ + 'http 400', + 'http 404', + 'bad request', + 'not found' + ] + + // 优先检查session相关错误(高置信度) + const hasSessionPattern = sessionErrorPatterns.some(pattern => message.includes(pattern)) + if (hasSessionPattern) { + return true + } + + // 检查可能与session相关的HTTP错误(低置信度) + const hasHttpPattern = httpErrorPatterns.some(pattern => message.includes(pattern)) + if (hasHttpPattern && (message.includes('posting') || message.includes('endpoint'))) { + return true + } + } + return false +} +``` + +### 恢复处理流程 + +1. **正常操作**: 客户端执行 MCP 操作 +2. **错误检测**: 如果收到 session 相关错误,`isSessionError` 函数检测到 +3. **首次重启**: 如果是第一次遇到 session 错误,立即清理当前连接和缓存,重置服务状态 +4. **抛出错误**: 向上层抛出原始错误,让调用者知道需要重试 +5. **重新调用**: 上层调用者重新发起请求,此时会建立新的连接和 session +6. **持续错误检测**: 如果重启后再次出现 session 错误,**彻底停止服务** +7. **服务停止**: 清理所有资源,通知系统服务已停止,避免无限重试 + +## 扩展指南 + +### 添加新服务器类型 + +1. 在 `McpClient` 中添加新的传输类型处理逻辑 (继承或实现 `Transport` 接口) +2. 更新 `MCPServerConfig` 类型定义 (如果需要新的配置项) +3. 在 `McpClient` 的 `connect` 方法中添加根据 `type` 创建新 Transport 的分支 +4. 更新 `ServerManager` (如果需要特定的管理逻辑) + +### 添加新工具格式转换 + +1. 在 `McpPresenter` 中添加新的转换方法 (如 `mcpToolsToNewFormat()`) +2. 实现对应的反向转换方法 (如 `newFormatToMcpTool()`) +3. 更新相关 LLM Provider 代码以使用新的转换方法 + +### 自定义权限控制 + +1. 修改 `ToolManager.checkToolPermission()` 方法以实现新的权限逻辑 +2. 可能需要更新 `MCPServerConfig` 接口以支持新的权限配置 +3. 更新相关文档和用户界面以反映新的权限模型 + +--- + +此文档整合了 MCP 系统的架构设计、权限管理、session 恢复等全面内容,为开发者提供了完整的 MCP 系统理解和扩展指南。 \ No newline at end of file diff --git a/docs/mcp-permission-implementation-guide.md b/docs/mcp-permission-implementation-guide.md deleted file mode 100644 index 981a89759..000000000 --- a/docs/mcp-permission-implementation-guide.md +++ /dev/null @@ -1,549 +0,0 @@ -# MCP Permission System Implementation Guide - -## Overview - -This document provides step-by-step implementation instructions for the MCP Tool Permission Request System in DeepChat. - -## Prerequisites - -- Understanding of DeepChat's architecture (LLMProviderPresenter, ThreadPresenter, ToolManager) -- Familiarity with Vue 3 Composition API -- Knowledge of TypeScript and event-driven programming - -## Implementation Steps - -### Step 1: Update Type Definitions - -#### 1.1 Add Permission Block Type - -In `src/shared/chat.d.ts`, add the new permission block type: - -```typescript -export type AssistantMessageBlock = { - type: - | 'content' - | 'search' - | 'reasoning_content' - | 'error' - | 'tool_call' - | 'action' - | 'tool_call_permission' // NEW: Permission request block - | 'image' - | 'artifact-thinking' - // ... existing fields -} -``` - -#### 1.2 Update Tool Response Types - -In `src/shared/presenter.d.ts`, add permission-related types: - -```typescript -export interface MCPToolResponse { - // ... existing fields - requiresPermission?: boolean - permissionRequest?: { - toolName: string - serverName: string - permissionType: 'read' | 'write' | 'all' - description: string - } -} -``` - -### Step 2: Modify ToolManager - -#### 2.1 Update Permission Check Logic - -In `src/main/presenter/mcpPresenter/toolManager.ts`: - -```typescript -async callTool(toolCall: MCPToolCall): Promise { - // ... existing code for tool resolution and argument parsing - - // Check permissions - const hasPermission = this.checkToolPermission(originalName, toolServerName, autoApprove) - - if (!hasPermission) { - const permissionType = this.determinePermissionType(originalName) - - return { - toolCallId: toolCall.id, - content: `Permission required: The '${originalName}' operation requires ${permissionType} permissions.`, - isError: false, - requiresPermission: true, - permissionRequest: { - toolName: originalName, - serverName: toolServerName, - permissionType, - description: `Allow ${originalName} to perform ${permissionType} operations on ${toolServerName}?` - } - } - } - - // ... existing tool execution code -} -``` - -#### 2.2 Add Permission Management Methods - -```typescript -export class ToolManager { - // ... existing code - - async grantPermission(serverName: string, permissionType: 'read' | 'write' | 'all', remember: boolean = false): Promise { - if (remember) { - await this.updateServerPermissions(serverName, permissionType) - } - } - - private async updateServerPermissions(serverName: string, permissionType: 'read' | 'write' | 'all'): Promise { - const servers = await this.configPresenter.getMcpServers() - const serverConfig = servers[serverName] - - if (serverConfig) { - const autoApprove = [...(serverConfig.autoApprove || [])] - - if (permissionType === 'all') { - autoApprove.length = 0 // Clear existing permissions - autoApprove.push('all') - } else if (!autoApprove.includes(permissionType) && !autoApprove.includes('all')) { - autoApprove.push(permissionType) - } - - await this.configPresenter.updateMcpServer(serverName, { - ...serverConfig, - autoApprove - }) - } - } -} -``` - -### Step 3: Update LLMProviderPresenter Agent Loop - -#### 3.1 Handle Permission Requests - -In `src/main/presenter/llmProviderPresenter/index.ts`, modify the tool execution section: - -```typescript -// Inside the agent loop, in tool execution section -try { - const toolResponse = await presenter.mcpPresenter.callTool(mcpToolInput) - - if (abortController.signal.aborted) break - - // Check if permission is required - if (toolResponse.requiresPermission) { - // Create permission request block - yield { - type: 'response', - data: { - eventId, - tool_call: 'permission-required', - tool_call_id: toolCall.id, - tool_call_name: toolCall.name, - tool_call_params: toolCall.arguments, - tool_call_server_name: toolResponse.permissionRequest?.serverName, - tool_call_server_icons: toolDef.server.icons, - tool_call_server_description: toolDef.server.description, - tool_call_response: toolResponse.content, - permission_request: toolResponse.permissionRequest - } - } - - // Pause the agent loop - needContinueConversation = false - break - } - - // ... continue with normal tool response handling -} catch (toolError) { - // ... existing error handling -} -``` - -### Step 4: Update ThreadPresenter - -#### 4.1 Handle Permission Blocks - -In `src/main/presenter/threadPresenter/index.ts`, add permission block handling: - -```typescript -// In handleLLMAgentResponse method -if (tool_call === 'permission-required') { - finalizeLastBlock() - - const { permission_request } = msg - - state.message.content.push({ - type: 'tool_call_permission', - content: tool_call_response || 'Permission required for this operation', - status: 'pending', - timestamp: currentTime, - tool_call: { - id: tool_call_id, - name: tool_call_name, - params: tool_call_params || '', - server_name: tool_call_server_name, - server_icons: tool_call_server_icons, - server_description: tool_call_server_description - }, - extra: { - permissionType: permission_request?.permissionType || 'write', - serverName: permission_request?.serverName || tool_call_server_name, - toolName: permission_request?.toolName || tool_call_name, - needsUserAction: true - } - }) -} -``` - -#### 4.2 Add Permission Response Handler - -```typescript -export class ThreadPresenter implements IThreadPresenter { - // ... existing code - - async handlePermissionResponse( - messageId: string, - toolCallId: string, - granted: boolean, - permissionType: 'read' | 'write' | 'all', - remember: boolean = false - ): Promise { - const state = this.generatingMessages.get(messageId) - if (!state) { - throw new Error('Message not found or not in generating state') - } - - // Update the permission block - const permissionBlock = state.message.content.find( - block => block.type === 'tool_call_permission' && - block.tool_call?.id === toolCallId - ) - - if (permissionBlock) { - permissionBlock.status = granted ? 'granted' : 'denied' - if (permissionBlock.extra) { - permissionBlock.extra.needsUserAction = false - if (granted) { - permissionBlock.extra.grantedPermissions = [permissionType] - } - } - - await this.messageManager.editMessage(messageId, JSON.stringify(state.message.content)) - } - - if (granted) { - // Grant permission in ToolManager - const serverName = permissionBlock?.extra?.serverName as string - if (serverName) { - await presenter.mcpPresenter.grantPermission(serverName, permissionType, remember) - } - - // Continue the agent loop - await this.continueWithPermission(messageId, toolCallId) - } - } - - private async continueWithPermission(messageId: string, toolCallId: string): Promise { - // Resume the agent loop for this specific tool call - // This involves re-executing the tool and continuing the conversation - const state = this.generatingMessages.get(messageId) - if (!state) return - - // Find the tool call that needs to be re-executed - const permissionBlock = state.message.content.find( - block => block.type === 'tool_call_permission' && - block.tool_call?.id === toolCallId - ) - - if (!permissionBlock?.tool_call) return - - // Re-execute the tool call - try { - const mcpToolInput: MCPToolCall = { - id: permissionBlock.tool_call.id!, - type: 'function', - function: { - name: permissionBlock.tool_call.name!, - arguments: permissionBlock.tool_call.params! - }, - server: { - name: permissionBlock.tool_call.server_name!, - icons: permissionBlock.tool_call.server_icons!, - description: permissionBlock.tool_call.server_description! - } - } - - const toolResponse = await presenter.mcpPresenter.callTool(mcpToolInput) - - // Send tool result and continue conversation - eventBus.sendToRenderer(STREAM_EVENTS.RESPONSE, SendTarget.ALL_WINDOWS, { - eventId: messageId, - tool_call: 'end', - tool_call_id: toolCallId, - tool_call_name: permissionBlock.tool_call.name, - tool_call_response: typeof toolResponse.content === 'string' - ? toolResponse.content - : JSON.stringify(toolResponse.content), - tool_call_response_raw: toolResponse - }) - - // Resume agent loop by restarting stream completion - await this.resumeStreamCompletion(state.conversationId, messageId) - - } catch (error) { - console.error('Failed to continue with permission:', error) - } - } -} -``` - -### Step 5: Create Frontend Permission Component - -#### 5.1 Create Permission Request Component - -Create `src/renderer/src/components/message/MessageBlockPermissionRequest.vue`: - -```vue - - - - - -``` - -#### 5.2 Register Component in MessageItemAssistant - -In `src/renderer/src/components/message/MessageItemAssistant.vue`: - -```vue - - - -``` - -### Step 6: Add Presenter Interface - -#### 6.1 Update ThreadPresenter Interface - -In `src/shared/presenter.d.ts`: - -```typescript -export interface IThreadPresenter { - // ... existing methods - - handlePermissionResponse( - messageId: string, - toolCallId: string, - granted: boolean, - permissionType: 'read' | 'write' | 'all', - remember?: boolean - ): Promise -} -``` - -#### 6.2 Update MCPPresenter Interface - -```typescript -export interface IMcpPresenter { - // ... existing methods - - grantPermission( - serverName: string, - permissionType: 'read' | 'write' | 'all', - remember?: boolean - ): Promise -} -``` - -## Testing the Implementation - -### Test Cases - -1. **Permission Request Flow** - - Trigger a tool call that requires permissions - - Verify permission block is created - - Test grant and deny actions - - Verify conversation continues after grant - -2. **Permission Persistence** - - Grant permission with "remember" checked - - Verify subsequent calls don't require permission - - Test permission management in settings - -3. **Error Handling** - - Test permission denial - - Test network errors during permission check - - Test malformed permission requests - -### Manual Testing - -1. Set up an MCP server without auto-approve permissions -2. Start a conversation that requires tool usage -3. Verify permission request UI appears -4. Test granting and denying permissions -5. Verify conversation flow continues correctly - -## Rollout Plan - -1. **Phase 1**: Implement core backend logic (Steps 1-4) -2. **Phase 2**: Implement frontend UI (Step 5) -3. **Phase 3**: Add presenter interfaces (Step 6) -4. **Phase 4**: Testing and refinement -5. **Phase 5**: Documentation and deployment - -This implementation provides a robust, user-friendly permission system that integrates seamlessly with DeepChat's existing architecture while maintaining security and usability. \ No newline at end of file diff --git a/docs/mcp-permission-system-design.md b/docs/mcp-permission-system-design.md deleted file mode 100644 index 2f3e06cff..000000000 --- a/docs/mcp-permission-system-design.md +++ /dev/null @@ -1,215 +0,0 @@ -# MCP Tool Permission Request System Design - -## Overview - -This document describes the redesigned permission request system for MCP (Model Context Protocol) tool calls in DeepChat. The system provides a user-friendly way to request and manage permissions for tool executions while maintaining the conversational flow. - -## Goals - -1. **Non-intrusive Flow**: Permission requests should pause the agent loop naturally, similar to maximum tool calls behavior -2. **Clear User Interface**: Users should clearly understand what permissions are being requested and why -3. **Persistent Choices**: Users can choose to remember their permission decisions -4. **Seamless Continuation**: After permission is granted, the agent loop continues smoothly -5. **Flexible Permission Types**: Support for read, write, and all permission levels - -## Architecture Overview - -``` -User Input → LLM → Tool Call → Permission Check → [Permission Required] - ↓ - Create Permission Block - ↓ - Display Permission UI - ↓ - User Decision (Allow/Deny) - ↓ - [If Allowed] → Continue Tool Execution → Agent Loop Continues -``` - -## Component Design - -### 1. Message Block System - -#### New Permission Block Type -```typescript -type PermissionRequestBlock = { - type: 'tool_call_permission' - content: string // Description of permission needed - status: 'pending' | 'granted' | 'denied' - timestamp: number - tool_call: { - id: string - name: string - params: string - server_name: string - server_icons: string - server_description: string - } - extra: { - permissionType: 'read' | 'write' | 'all' - serverName: string - toolName: string - needsUserAction: boolean // Whether user action is still needed - grantedPermissions?: string[] // What permissions were granted - } -} -``` - -### 2. Tool Manager Changes - -The `ToolManager.callTool()` method will return a special response when permissions are required: - -```typescript -interface PermissionRequiredResponse extends MCPToolResponse { - toolCallId: string - content: string - isError: false - requiresPermission: true - permissionRequest: { - toolName: string - serverName: string - permissionType: 'read' | 'write' | 'all' - description: string - } -} -``` - -### 3. Agent Loop Modifications - -The LLMProviderPresenter agent loop will: - -1. Detect permission-required responses from tool calls -2. Create a permission block and yield it to ThreadPresenter -3. Pause the agent loop (set `needContinueConversation = false`) -4. Wait for user permission decision via a continuation mechanism - -### 4. Permission Management - -#### ThreadPresenter Extensions -- Add `continueWithPermission(messageId: string, toolCallId: string, granted: boolean)` method -- Handle permission responses and resume agent loops - -#### ConfigPresenter Integration -- Update server configurations when users choose to remember permissions -- Manage persistent permission settings - -### 5. Frontend Components - -#### Permission Request UI Component -```vue - -``` - -Features: -- Clear permission type display (read/write/all) -- Tool and server information -- "Remember this choice" checkbox -- Allow/Deny buttons -- Loading states during processing - -## Implementation Flow - -### 1. Permission Check Flow - -```mermaid -sequenceDiagram - participant LLM as LLMProviderPresenter - participant TM as ToolManager - participant TP as ThreadPresenter - participant UI as Frontend - - LLM->>TM: callTool(toolCall) - TM->>TM: checkPermission() - alt Permission Required - TM-->>LLM: PermissionRequiredResponse - LLM->>TP: yield permission block - TP->>UI: render permission UI - UI->>UI: user clicks Allow/Deny - UI->>TP: permissionResponse - TP->>LLM: continueWithPermission() - LLM->>TM: callTool(toolCall) // retry - TM-->>LLM: success response - else Permission Granted - TM-->>LLM: normal response - end -``` - -### 2. State Management - -#### Message State -Each permission request creates a persistent message block that tracks: -- Current permission status -- User's decision -- Whether the request is still active - -#### Conversation State -The ThreadPresenter maintains: -- Active permission requests per conversation -- Continuation callbacks for resumed agent loops - -### 3. Permission Types - -#### Read Permissions -For tools that only read data: -- `list_directory` -- `read_file` -- `get_information` - -#### Write Permissions -For tools that modify data: -- `write_file` -- `create_directory` -- `delete_file` -- `execute_command` - -#### All Permissions -Grant both read and write access to the server. - -## Security Considerations - -1. **Default Deny**: All tool calls require explicit permission by default -2. **Server Isolation**: Permissions are granted per-server, not globally -3. **Permission Escalation**: Users must explicitly grant higher-level permissions -4. **Audit Trail**: All permission decisions are logged in message history -5. **Session Scope**: Temporary permissions last only for the current session - -## User Experience - -### Permission Request Display -- Clear visual distinction from regular tool calls -- Prominent permission type indicator -- Detailed explanation of what the tool will do -- Server trust indicators (icons, descriptions) - -### Permission Management -- Settings page to review and modify saved permissions -- Bulk permission operations for trusted servers -- Permission history and audit log - -### Error Handling -- Clear error messages when permissions are denied -- Guidance on how to grant permissions if needed -- Fallback options when tools are unavailable - -## Future Enhancements - -1. **Granular Permissions**: File-path or operation-specific permissions -2. **Temporary Permissions**: Time-limited or usage-limited grants -3. **Permission Templates**: Pre-configured permission sets for common workflows -4. **Server Reputation**: Community-driven trust scores for MCP servers -5. **Permission Analytics**: Usage statistics and security insights - -## Migration Plan - -1. **Phase 1**: Implement core permission blocking system -2. **Phase 2**: Add UI components and user interaction -3. **Phase 3**: Implement permission persistence and management -4. **Phase 4**: Add advanced features and optimizations - -This design ensures a secure, user-friendly permission system that maintains the conversational flow while giving users full control over tool execution permissions. \ No newline at end of file diff --git a/docs/mcp-presenter-architecture.md b/docs/mcp-presenter-architecture.md deleted file mode 100644 index 419bacfd6..000000000 --- a/docs/mcp-presenter-architecture.md +++ /dev/null @@ -1,277 +0,0 @@ -# MCP Presenter 架构文档 - -## 模块概述 - -MCP (Model Context Protocol) Presenter 是 DeepChat 中负责管理 MCP 服务器和工具的核心模块,主要功能包括: - -1. **MCP 服务器管理**: 启动、停止、配置管理、默认服务器设置、npm registry 速度测试。 -2. **MCP 工具管理**: 定义获取、名称冲突处理、缓存、权限检查和调用。 -3. **LLM 适配**: 在 MCP 工具格式与不同 LLM 提供商 (OpenAI, Anthropic, Gemini) 的工具格式之间进行转换。 -4. **状态与事件**: 监控服务器状态并通过 `eventBus` 发布相关事件。 - -## 核心组件 - -```mermaid -classDiagram - class IMCPPresenter { - <> - +getMcpServers() - +getMcpClients() - +startServer() - +stopServer() - +callTool() - +getAllToolDefinitions() - +mcpToolsToOpenAITools() - +openAIToolsToMcpTool() - +mcpToolsToAnthropicTools() - +anthropicToolUseToMcpTool() - +mcpToolsToGeminiTools() - +geminiFunctionCallToMcpTool() - +addMcpServer() - +removeMcpServer() - +updateMcpServer() - +getMcpDefaultServers() - +addMcpDefaultServer() - +removeMcpDefaultServer() - +toggleMcpDefaultServer() - +getMcpEnabled() - +setMcpEnabled() - +resetToDefaultServers() - } - - class McpPresenter { - -serverManager: ServerManager - -toolManager: ToolManager - -configPresenter: IConfigPresenter - +initialize() - +getMcpServers() - +getMcpClients() - +startServer() - +stopServer() - +callTool() - +getAllToolDefinitions() - +mcpToolsToOpenAITools() - +openAIToolsToMcpTool() - +mcpToolsToAnthropicTools() - +anthropicToolUseToMcpTool() - +mcpToolsToGeminiTools() - +geminiFunctionCallToMcpTool() - +addMcpServer() - +removeMcpServer() - +updateMcpServer() - +getMcpDefaultServers() - +addMcpDefaultServer() - +removeMcpDefaultServer() - +toggleMcpDefaultServer() - +getMcpEnabled() - +setMcpEnabled() - +resetToDefaultServers() - } - - class ServerManager { - -clients: Map - -configPresenter: IConfigPresenter - -npmRegistry: string | null - +testNpmRegistrySpeed() - +getNpmRegistry() - +startServer() - +stopServer() - +getRunningClients() - +getDefaultServerNames() - +getDefaultClients() - +getClient() - +isServerRunning() - } - - class ToolManager { - -configPresenter: IConfigPresenter - -serverManager: ServerManager - -cachedToolDefinitions: MCPToolDefinition[] | null - -toolNameToTargetMap: Map | null - +getAllToolDefinitions() - +callTool() - +checkToolPermission() - +handleServerListUpdate() - +getRunningClients() - } - - class McpClient { - +serverName: string - +serverConfig: Record - -client: Client | null - -transport: Transport | null - -isConnected: boolean - -npmRegistry: string | null - +connect() - +disconnect() - +callTool() - +listTools() - +readResource() - +isServerRunning() - } - - class McpConfHelper { - -mcpStore: ElectronStore - +getMcpServers() - +setMcpServers() - +getMcpDefaultServers() - +addMcpDefaultServer() - +removeMcpDefaultServer() - +toggleMcpDefaultServer() - +setMcpEnabled() - +getMcpEnabled() - +addMcpServer() - +removeMcpServer() - +updateMcpServer() - +resetToDefaultServers() - +onUpgrade() - } - - class IConfigPresenter { - <> - +getMcpServers() - +setMcpServers() - +getMcpDefaultServers() - +addMcpDefaultServer() - +removeMcpDefaultServer() - +toggleMcpDefaultServer() - +setMcpEnabled() - +getMcpEnabled() - +addMcpServer() - +removeMcpServer() - +updateMcpServer() - +resetToDefaultServers() - +getLanguage() - // ... other config methods - } - - - McpPresenter ..|> IMCPPresenter - McpPresenter o-- ServerManager - McpPresenter o-- ToolManager - McpPresenter o-- IConfigPresenter - - ServerManager o-- IConfigPresenter - ServerManager "1" *-- "0..*" McpClient : manages - - ToolManager o-- IConfigPresenter - ToolManager o-- ServerManager : uses - - McpConfHelper ..> IConfigPresenter : (typically implements relevant parts) -``` - -## 数据流 - -### 1. 初始化与默认服务器启动 - -```mermaid -sequenceDiagram - participant AppStartup - participant McpPresenter - participant ServerManager - participant IConfigPresenter - participant McpClient - - AppStartup->>McpPresenter: constructor(configPresenter) - McpPresenter->>ServerManager: constructor(configPresenter) - McpPresenter->>ToolManager: constructor(configPresenter, serverManager) - AppStartup->>McpPresenter: initialize() - McpPresenter->>IConfigPresenter: getMcpServers() - McpPresenter->>IConfigPresenter: getMcpDefaultServers() - McpPresenter->>ServerManager: testNpmRegistrySpeed() - ServerManager-->>McpPresenter: (registry selected) - loop For each defaultServerName - McpPresenter->>ServerManager: startServer(defaultServerName) - ServerManager->>IConfigPresenter: getMcpServers() (to get config) - ServerManager->>McpClient: new McpClient(name, config, npmRegistry) - ServerManager->>McpClient: connect() - McpClient->>McpClient: Establish Transport (stdio/sse/http/inmemory) - McpClient->>MCP Server: Connect Request - MCP Server-->>McpClient: Connected - McpClient-->>ServerManager: Connected (triggers status event) - ServerManager-->>McpPresenter: Success / Error - end -``` - -### 2. LLM 工具调用流程 (以 OpenAI 为例) - -```mermaid -sequenceDiagram - participant LLM Provider - participant McpPresenter - participant ToolManager - participant McpClient - participant MCP Server - - Note over LLM Provider, McpPresenter: 1. 获取和转换工具定义 (按需) - LLM Provider->>McpPresenter: getAllToolDefinitions() - McpPresenter->>ToolManager: getAllToolDefinitions() - ToolManager->>ServerManager: getRunningClients() - ServerManager-->>ToolManager: List - loop For each client - ToolManager->>McpClient: listTools() - McpClient-->>ToolManager: Raw Tool List - end - ToolManager->>ToolManager: 处理冲突, 缓存定义和映射 - ToolManager-->>McpPresenter: Processed MCPToolDefinition[] - McpPresenter->>McpPresenter: mcpToolsToOpenAITools(definitions) - McpPresenter-->>LLM Provider: OpenAI Tool Format - - Note over LLM Provider, McpPresenter: 2. LLM 生成工具调用请求 - LLM Provider->>LLM Provider: LLM decides to call tool(s) - LLM Provider->>LLM Provider: Generates tool_calls (OpenAI Format) - - Note over LLM Provider, McpPresenter: 3. 转换并执行工具调用 - loop For each tool_call from LLM - LLM Provider->>McpPresenter: openAIToolsToMcpTool(tool_call) - McpPresenter-->>LLM Provider: Standard MCPToolCall - LLM Provider->>McpPresenter: callTool(mcpToolCall) - McpPresenter->>ToolManager: callTool(mcpToolCall) - ToolManager->>ToolManager: Lookup target client & original name - ToolManager->>ToolManager: checkToolPermission() - alt Permission Denied - ToolManager-->>McpPresenter: Error Response - else Permission Granted - ToolManager->>McpClient: callTool(originalToolName, args) - McpClient->>MCP Server: Execute Tool - MCP Server-->>McpClient: Raw Result - McpClient-->>ToolManager: ToolCallResult - ToolManager-->>McpPresenter: MCPToolResponse (triggers event) - end - McpPresenter-->>LLM Provider: Formatted Response (Success or Error) - end - - Note over LLM Provider: 4. 处理结果并继续 - LLM Provider->>LLM Provider: Add tool response(s) to context - LLM Provider->>LLM Provider: Generate next response or further calls - -``` - -## 关键设计 - -1. **分层架构**: - * 接口层 (`IMCPPresenter`): 定义公共 API。 - * 展示层 (`McpPresenter`): 协调者,处理 LLM 适配和委托。 - * 管理层 (`ServerManager`, `ToolManager`): 处理服务器生命周期和工具管理/调用逻辑。 - * 配置层 (`IConfigPresenter`, `McpConfHelper`): 提供和持久化配置。 - * 客户端层 (`McpClient`): 封装与单个 MCP 服务器的通信。 - -2. **多协议支持**: - * `McpClient` 通过不同的 `Transport` 实现支持 stdio, SSE, HTTP, InMemory。 - -3. **工具管理与适配**: - * `ToolManager` 集中处理工具定义获取、**名称冲突解决**和缓存。 - * `McpPresenter` 负责在 MCP 格式与各 LLM 特定格式间转换。 - * `ToolManager` 使用映射表 (`toolNameToTargetMap`) 将(可能重命名的)工具调用路由到正确的 `McpClient` 和原始工具名称。 - -4. **配置驱动与持久化**: - * 行为由 `McpConfHelper` 管理的配置驱动。 - * 使用 `electron-store` 进行持久化。 - -5. **错误处理与事件通知**: - * 在服务器启动 (`ServerManager`)、工具调用 (`ToolManager`) 等环节包含错误处理。 - * 通过 `eventBus` 发布状态变更和结果事件。 - -6. **性能与环境优化**: - * `ServerManager` 自动测试并选择最快的 npm registry。 - * `McpClient` 精细化处理 `stdio` 进程的环境变量 (PATH, 代理, npm registry)。 diff --git a/docs/mcp-presenter-design.md b/docs/mcp-presenter-design.md deleted file mode 100644 index 507aafb6e..000000000 --- a/docs/mcp-presenter-design.md +++ /dev/null @@ -1,195 +0,0 @@ -# MCP Presenter 设计文档 - -## 1. 核心类设计 - -### 1.1 McpPresenter - -`McpPresenter` (`src/main/presenter/mcpPresenter/index.ts`) 是模块的主入口,实现了 `IMCPPresenter` 接口,主要职责: - -- 依赖 `IConfigPresenter` 获取配置。 -- 初始化并管理 `ServerManager` 和 `ToolManager`。 -- **初始化流程**: - - 测试 npm registry 速度 (通过 `ServerManager`)。 - - 根据配置启动默认的 MCP 服务器。 -- 提供管理服务器生命周期 (启动/停止)、配置和默认设置的接口。 -- 获取所有可用工具定义 (通过 `ToolManager`),处理名称冲突。 -- 提供在 MCP 工具格式与不同 LLM 提供商 (OpenAI, Anthropic, Gemini) 格式之间相互转换的方法。 -- 接收来自 LLM Provider 的工具调用请求,转换为标准格式后分发给 `ToolManager`。 -- 通过 `eventBus` 触发 MCP 相关事件。 - -**关键方法**: - -- `initialize()`: 执行初始化逻辑。 -- `getMcpServers()`, `startServer()`, `stopServer()`, `addMcpServer()`, `removeMcpServer()`, `updateMcpServer()`: 服务器管理。 -- `getMcpDefaultServers()`, `addMcpDefaultServer()`, `removeMcpDefaultServer()`, `toggleMcpDefaultServer()`: 默认服务器管理。 -- `getAllToolDefinitions()`: 获取工具定义。 -- `callTool()`: 接收标准化工具调用请求并委托给 `ToolManager`。 -- `mcpToolsToOpenAITools()`, `openAIToolsToMcpTool()`, `mcpToolsToAnthropicTools()`, `anthropicToolUseToMcpTool()`, `mcpToolsToGeminiTools()`, `geminiFunctionCallToMcpTool()`: 格式转换。 -- `getMcpEnabled()`, `setMcpEnabled()`: 全局开关管理。 -- `resetToDefaultServers()`: 恢复默认配置。 - -### 1.2 ServerManager - -`ServerManager` (`src/main/presenter/mcpPresenter/serverManager.ts`) 负责 MCP 服务器实例 (`McpClient`) 的生命周期和管理: - -- **NPM Registry 管理**: - - 自动测试多个 npm registry (`NPM_REGISTRY_LIST`) 并选择最快的。 - - 将选择的 registry 传递给 `McpClient` 实例。 -- **客户端管理**: - - 维护运行中的 `McpClient` 实例 (`clients` Map)。 - - 提供启动 (`startServer`) 和停止 (`stopServer`) 服务器的方法。 - - 获取默认或所有运行中的客户端实例。 -- 处理服务器启动失败时的错误通知。 -- 触发 `MCP_EVENTS.CLIENT_LIST_UPDATED` 事件。 - -### 1.3 ToolManager - -`ToolManager` (`src/main/presenter/mcpPresenter/toolManager.ts`) 负责 MCP 工具的管理和调用: - -- **工具定义获取与缓存**: - - 从所有运行中的 `McpClient` 获取工具定义 (`listTools`)。 - - **冲突处理**: 检测并自动重命名来自不同服务器的同名工具 (格式: `serverName_toolName`)。 - - 缓存处理后的工具定义列表 (`cachedToolDefinitions`) 和工具名称到目标的映射 (`toolNameToTargetMap`)。 - - 监听 `MCP_EVENTS.CLIENT_LIST_UPDATED` 事件以清除缓存。 -- **工具调用处理**: - - `callTool()`: 接收标准化的 `MCPToolCall` 请求。 - - **查找目标**: 使用 `toolNameToTargetMap` 找到处理该工具的 `McpClient` 和原始工具名称。 - - **权限控制**: 调用 `checkToolPermission()` 检查权限,基于服务器配置中的 `autoApprove` 列表 (支持 `all`, `read`, `write` 等)。 - - **执行调用**: 使用 *原始* 工具名称调用目标 `McpClient` 的 `callTool` 方法。 - - 格式化工具调用结果并触发 `MCP_EVENTS.TOOL_CALL_RESULT` 事件。 - -### 1.4 McpClient - -`McpClient` (`src/main/presenter/mcpPresenter/mcpClient.ts`) 是与单个 MCP 服务器通信的客户端实现: - -- **通信与传输**: - - 处理与 MCP 服务器的连接建立 (`connect`) 和断开 (`disconnect`)。 - - 支持多种传输层 (`stdio`, `sse`, `http`, `inmemory`)。 - - 执行工具调用 (`callTool`)、列出工具 (`listTools`)、读取资源 (`readResource`) 等。 -- **环境与配置**: - - 处理 `stdio` 类型的环境变量,特别是 `PATH` 的合并(系统、默认、自定义、运行时)和代理设置 (`http_proxy`, `https_proxy`)。 - - 使用 `ServerManager` 提供的 `npmRegistry`。 - - 处理认证 (`AuthProvider` for Bearer Token) 和自定义头 (`customHeaders`)。 -- **连接管理**: - - 5分钟连接超时。 - - 触发 `MCP_EVENTS.SERVER_STATUS_CHANGED` 事件。 - -## 2. 工具调用流程 (以 OpenAI Provider 为例) - -```mermaid -sequenceDiagram - participant LLM as "LLM Provider (e.g., OpenAICompatibleProvider)" - participant McpPresenter - participant ToolManager - participant McpClient - participant MCP Server - - Note over LLM, McpPresenter: 初始化/获取工具定义 - LLM->>McpPresenter: getAllToolDefinitions() - McpPresenter->>ToolManager: getAllToolDefinitions() - ToolManager->>McpClient: listTools() (对所有运行中的Client) - McpClient-->>ToolManager: 原始工具列表 - ToolManager->>ToolManager: 处理冲突, 缓存定义和映射 - ToolManager-->>McpPresenter: 处理后的工具定义列表 - McpPresenter->>LLM: mcpToolsToOpenAITools(definitions) - LLM-->>McpPresenter: OpenAI 格式工具 - - Note over LLM, McpPresenter: 用户请求,LLM决定调用工具 - LLM->>LLM: LLM 生成工具调用请求 (OpenAI 格式) - LLM->>McpPresenter: openAIToolsToMcpTool(tool_call) - McpPresenter-->>LLM: 标准化 MCPToolCall - - Note over LLM, McpPresenter: 执行工具调用 - LLM->>McpPresenter: callTool(mcpToolCall) - McpPresenter->>ToolManager: callTool(mcpToolCall) - ToolManager->>ToolManager: 查找映射 (toolNameToTargetMap) - ToolManager->>ToolManager: 检查权限 (checkToolPermission) - alt 权限不足 - ToolManager-->>McpPresenter: 错误响应 - McpPresenter-->>LLM: 错误响应 (包装后) - else 权限通过 - ToolManager->>McpClient: callTool(originalToolName, args) - McpClient->>MCP Server: 执行工具调用 - MCP Server-->>McpClient: 原始结果 - McpClient-->>ToolManager: 工具调用结果 (ToolCallResult) - ToolManager->>ToolManager: 格式化结果 - ToolManager-->>McpPresenter: 格式化响应 (MCPToolResponse) - McpPresenter-->>LLM: 最终响应 (包装后) - end - LLM->>LLM: 处理响应, 可能继续对话或返回用户 -``` - -**流程说明**: - -1. **获取工具**: LLM Provider (如 `OpenAICompatibleProvider`) 在需要时向 `McpPresenter` 请求工具定义。`McpPresenter` 委托 `ToolManager`,后者从所有运行的 `McpClient` 获取原始工具列表,处理命名冲突,缓存结果,并将处理后的定义返回给 `McpPresenter`。`McpPresenter` 将其转换为 LLM Provider 所需的格式。 -2. **生成调用**: LLM 根据用户输入和可用工具,生成一个工具调用请求 (LLM 特定格式)。 -3. **转换调用**: LLM Provider 将此请求传递给 `McpPresenter`,后者将其转换为标准的 `MCPToolCall` 格式。 -4. **执行调用**: LLM Provider 调用 `McpPresenter.callTool()` 并传入标准化的 `MCPToolCall`。 -5. **查找与检查**: `McpPresenter` 委托给 `ToolManager`。`ToolManager` 使用内部映射找到负责该工具的 `McpClient` 实例和该工具在服务器上的原始名称,并检查调用权限。 -6. **实际执行**: 如果权限允许,`ToolManager` 调用目标 `McpClient` 的 `callTool` 方法,并使用 *原始* 工具名称和参数。 -7. **结果返回**: `McpClient` 与 MCP 服务器通信,获取结果并返回给 `ToolManager`。 -8. **格式化与响应**: `ToolManager` 格式化结果为 `MCPToolResponse`,触发事件,并将响应返回给 `McpPresenter`。`McpPresenter` 可能进一步包装响应,最终返回给 LLM Provider。 -9. **后续处理**: LLM Provider 处理工具调用的结果,可能将其添加到对话历史中,并让 LLM 基于结果生成下一步的响应或进行下一轮工具调用。 - -## 3. 事件系统 - -MCP Presenter 通过 `eventBus` 发出以下事件: - -| 事件名称 | 触发时机 | 触发源 | 参数 | -| ---------------------------------- | -------------------------------- | ------------- | ------------------------------------------------ | -| `MCP_EVENTS.SERVER_STARTED` | 服务器成功启动 | McpPresenter | serverName (string) | -| `MCP_EVENTS.SERVER_STOPPED` | 服务器停止 | McpPresenter | serverName (string) | -| `MCP_EVENTS.TOOL_CALL_RESULT` | 工具调用完成 | ToolManager | MCPToolResponse | -| `MCP_EVENTS.CONFIG_CHANGED` | MCP 配置 (服务器/默认/启用) 变更 | McpConfHelper | { mcpServers, defaultServers, mcpEnabled } | -| `MCP_EVENTS.SERVER_STATUS_CHANGED` | MCP 客户端连接状态变化 | McpClient | { name: string, status: 'running' \| 'stopped' } | -| `MCP_EVENTS.CLIENT_LIST_UPDATED` | 运行中的 MCP 客户端列表更新 | ServerManager | (无) | - -## 4. 配置管理 - -MCP 相关配置通过 `McpConfHelper` (`src/main/presenter/configPresenter/mcpConfHelper.ts`) 管理,并存储在 ElectronStore (`mcp-settings`) 中。 - -**核心配置项**: - -- `mcpServers`: `Record` - 存储所有已配置的 MCP 服务器及其配置。 -- `defaultServers`: `string[]` - 默认启动的服务器名称列表。 -- `mcpEnabled`: `boolean` - 全局启用/禁用 MCP 功能的开关。 - -**`MCPServerConfig` 接口**: - -```typescript -interface MCPServerConfig { - command?: string // 可执行命令 (stdio 类型) - args?: string[] // 命令行参数 - env?: Record // 环境变量 - type?: 'stdio' | 'sse' | 'http' | 'inmemory' // 服务器类型 - baseUrl?: string // 用于 SSE/HTTP 类型的服务器 URL - autoApprove?: string[] // 自动批准的权限列表 ('all', 'read', 'write', 或具体工具名) - icons?: string // 服务器图标 (emoji 或 URL) - descriptions?: string // 服务器描述 - disable?: boolean // 是否禁用该服务器 (UI 层面) - customHeaders?: Record // 用于 SSE/HTTP 的自定义请求头 (包含 Authorization) -} -``` - -`McpConfHelper` 还提供了恢复默认配置、添加内置服务器 (如 `buildInFileSystem`, `Artifacts`) 以及处理版本升级迁移的逻辑。 - -## 5. 扩展指南 - -### 5.1 添加新服务器类型 - -1. 在 `McpClient` 中添加新的传输类型处理逻辑 (继承或实现 `Transport` 接口)。 -2. 更新 `MCPServerConfig` 类型定义 (如果需要新的配置项)。 -3. 在 `McpClient` 的 `connect` 方法中添加根据 `type` 创建新 Transport 的分支。 -4. 更新 `ServerManager` (如果需要特定的管理逻辑)。 - -### 5.2 添加新工具格式转换 - -1. 在 `McpPresenter` 中添加新的转换方法 (如 `mcpToolsToNewFormat()`)。 -2. 实现对应的反向转换方法 (如 `newFormatToMcpTool()`)。 -3. 更新相关 LLM Provider 代码以使用新的转换方法。 - -### 5.3 自定义权限控制 - -1. 修改 `ToolManager.checkToolPermission()` 方法以实现新的权限逻辑。 -2. 可能需要更新 `MCPServerConfig` 接口以支持新的权限配置。 -3. 更新相关文档和用户界面以反映新的权限模型。 diff --git a/docs/mcp-session-recovery.md b/docs/mcp-session-recovery.md deleted file mode 100644 index b475cd0a5..000000000 --- a/docs/mcp-session-recovery.md +++ /dev/null @@ -1,240 +0,0 @@ -# MCP Streamable HTTP Session 自动恢复机制 - -## 问题背景 - -在MCP (Model Context Protocol) Streamable HTTP传输协议中,当服务器重启或session过期时,客户端会收到以下错误: - -``` -Error POSTing to endpoint (HTTP 400): Bad Request: No valid session ID provided -``` - -根据[MCP Streamable HTTP规范](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http): - -- 服务器可以在初始化时分配session ID,通过`Mcp-Session-Id`头返回 -- 客户端必须在后续所有HTTP请求中包含这个session ID -- 服务器可以随时终止session,之后必须对包含该session ID的请求返回HTTP 404 -- 当客户端收到HTTP 404时,必须通过发送新的`InitializeRequest`来启动新的session -- 对于没有session ID的请求(除了初始化),服务器应该返回HTTP 400 - -## 解决方案 - -我们在`McpClient`类中实现了简单且高效的session错误处理机制:**当检测到session错误时,立即重启服务并清理缓存,让上层调用者重新发起请求**。 - -### 1. Session错误检测 - -```typescript -function isSessionError(error: unknown): error is SessionError { - if (error instanceof Error) { - const message = error.message.toLowerCase() - - // 检查特定的MCP Streamable HTTP session错误模式 - const sessionErrorPatterns = [ - 'no valid session', - 'session expired', - 'session not found', - 'invalid session', - 'session id', - 'mcp-session-id' - ] - - const httpErrorPatterns = [ - 'http 400', - 'http 404', - 'bad request', - 'not found' - ] - - // 优先检查session相关错误(高置信度) - const hasSessionPattern = sessionErrorPatterns.some(pattern => message.includes(pattern)) - if (hasSessionPattern) { - return true - } - - // 检查可能与session相关的HTTP错误(低置信度) - // 仅当是HTTP传输时才视为session错误 - const hasHttpPattern = httpErrorPatterns.some(pattern => message.includes(pattern)) - if (hasHttpPattern && (message.includes('posting') || message.includes('endpoint'))) { - return true - } - } - return false -} -``` - -### 2. 简单的服务重启处理 - -```typescript -private async checkAndHandleSessionError(error: unknown): Promise { - if (isSessionError(error) && !this.isRecovering) { - // 如果已经重启过一次且仍然出现session错误,停止服务 - if (this.hasRestarted) { - console.error(`Session error persists after restart for server ${this.serverName}, stopping service...`, error) - await this.stopService() - throw new Error(`MCP服务 ${this.serverName} 重启后仍然出现session错误,已停止服务`) - } - - console.warn(`Session error detected for server ${this.serverName}, restarting service...`, error) - - this.isRecovering = true - - try { - // 清理当前连接 - this.cleanupResources() - - // 清除所有缓存以确保下次获取新数据 - this.cachedTools = null - this.cachedPrompts = null - this.cachedResources = null - - // 标记为已重启 - this.hasRestarted = true - - console.info(`Service ${this.serverName} restarted due to session error`) - } catch (restartError) { - console.error(`Failed to restart service ${this.serverName}:`, restartError) - } finally { - this.isRecovering = false - } - } -} - -// 完全停止服务(由于持续的session错误) -private async stopService(): Promise { - try { - // 使用内部断开方法,提供特定的错误原因 - await this.internalDisconnect('persistent session errors') - } catch (error) { - console.error(`Failed to stop service ${this.serverName}:`, error) - } -} - -// 内部断开方法,支持自定义原因 -private async internalDisconnect(reason?: string): Promise { - // 清理所有资源 - this.cleanupResources() - - const logMessage = reason - ? `MCP service ${this.serverName} has been stopped due to ${reason}` - : `Disconnected from MCP server: ${this.serverName}` - - console.log(logMessage) - - // 触发服务器状态变更事件通知系统 - eventBus.send(MCP_EVENTS.SERVER_STATUS_CHANGED, SendTarget.ALL_WINDOWS, { - name: this.serverName, - status: 'stopped' - }) -} -``` - -## 使用方法 - -所有MCP客户端操作现在都自动包含session错误处理,当session过期时会自动重启服务: - -```typescript -try { - // 调用工具 - 如果session过期,会自动重启服务,然后抛出错误 - const result = await mcpClient.callTool('tool_name', { param: 'value' }) -} catch (error) { - // 服务已重启,重新调用即可 - const result = await mcpClient.callTool('tool_name', { param: 'value' }) -} - -try { - // 列出工具 - 如果session过期,会自动重启服务,然后抛出错误 - const tools = await mcpClient.listTools() -} catch (error) { - // 服务已重启,重新调用即可 - const tools = await mcpClient.listTools() -} -``` - -## 工作流程 - -1. **正常操作**: 客户端执行MCP操作 -2. **错误检测**: 如果收到session相关错误,`isSessionError`函数检测到 -3. **首次重启**: 如果是第一次遇到session错误,立即清理当前连接和缓存,重置服务状态 -4. **抛出错误**: 向上层抛出原始错误,让调用者知道需要重试 -5. **重新调用**: 上层调用者重新发起请求,此时会建立新的连接和session -6. **持续错误检测**: 如果重启后再次出现session错误,**彻底停止服务** -7. **服务停止**: 清理所有资源,通知系统服务已停止,避免无限重试 - -## 错误处理策略 - -- **首次Session错误**: 自动重启服务,抛出错误让上层重试 -- **重启后再次Session错误**: 彻底停止服务,避免无限重试循环 -- **非Session错误**: 直接抛出,不进行任何特殊处理 -- **防止重复重启**: 使用`isRecovering`标志防止同时多个重启操作 -- **成功重置**: 成功操作后重置`hasRestarted`标志,允许将来再次重启 - -## 日志输出 - -系统会输出简洁的日志信息: - -**首次session错误(重启):** -``` -Session error detected for server doris_server, restarting service... -Service doris_server restarted due to session error -``` - -**重启后仍有session错误(停止服务):** -``` -Session error persists after restart for server doris_server, stopping service... -MCP service doris_server has been stopped due to persistent session errors -``` - -## 优势 - -1. **简单高效**: 不需要复杂的重试逻辑,直接重启服务 -2. **状态清理**: 确保重启后状态完全干净 -3. **上层控制**: 让上层调用者决定是否重试和如何重试 -4. **避免复杂性**: 不需要管理重试次数、超时等复杂逻辑 -5. **符合规范**: 完全遵循MCP规范的session管理要求 -6. **防止无限重试**: 重启后如果仍然失败,自动停止服务避免无限循环 - -## `disconnect()` vs `stopService()` 的区别 - -| 方法 | 访问性 | 使用场景 | 检查连接状态 | 日志信息 | -|------|--------|----------|--------------|----------| -| `disconnect()` | 公共方法 | 正常断开连接 | ✅ 检查是否已连接 | "Disconnected from MCP server" | -| `stopService()` | 私有方法 | Session错误后强制停止 | ❌ 直接清理 | "stopped due to persistent session errors" | - -两个方法现在都使用相同的内部方法 `internalDisconnect(reason?)` 来避免代码重复,只是传入不同的原因参数。 - -## 注意事项 - -1. **缓存清理**: 重启后会清空所有缓存,确保获取最新数据 -2. **错误传播**: 错误会正常传播到上层,不会被吞掉 -3. **防止并发**: 使用标志位防止并发重启 -4. **简单重试**: 上层可以简单地重新调用相同的方法 -5. **服务停止**: 如果重启后仍然出现session错误,服务会被完全停止 -6. **状态通知**: 服务停止时会通过事件总线通知整个系统 -7. **代码复用**: `disconnect()` 和 `stopService()` 都使用统一的内部断开逻辑 - -## 使用建议 - -当您的代码遇到MCP操作失败时,可以这样处理: - -```typescript -try { - const result = await mcpClient.callTool('tool_name', { param: 'value' }) - // 成功处理 -} catch (error) { - if (error.message.includes('已停止服务')) { - // 服务已被停止,不要再重试 - console.error('MCP service has been stopped due to persistent issues') - return - } - - // 其他错误,可以重试一次 - try { - const result = await mcpClient.callTool('tool_name', { param: 'value' }) - // 重试成功 - } catch (retryError) { - // 重试失败,放弃 - console.error('MCP operation failed after retry:', retryError) - } -} -``` - -这个设计确保了系统的稳定性,避免了无限重试循环,同时保持了简单易用的特性。 diff --git a/docs/multi-window-architecture.md b/docs/multi-window-architecture.md deleted file mode 100644 index a99d91020..000000000 --- a/docs/multi-window-architecture.md +++ /dev/null @@ -1,117 +0,0 @@ -# 多窗口与多标签页架构设计与改造计划 - -## 目标 - -将 DeepChat 的 Electron 应用从当前的单窗口模式重构为支持多个等价窗口,并且每个窗口内支持多个标签页(使用 `WebContentsView`)。用户应该能够: - -1. 打开多个独立的 DeepChat 窗口,每个窗口功能一致。 -2. 在每个窗口内打开、关闭、切换多个标签页。 -3. 将标签页从一个窗口拖拽到另一个窗口中。 - -## 当前架构的挑战 (原始) - -原始架构强耦合于一个"主窗口" (`MAIN_WIN`) 的概念,不支持多窗口,更不用说多标签页。 - -## 新架构设计 - -核心思路是分离窗口管理和标签页管理,并实现它们之间的协作。 - -1. **核心组件:** - - - **`WindowPresenter`**: 管理 `BrowserWindow` 实例的生命周期和集合。 - - **`TabPresenter` (新增)**: 全局管理所有 `WebContentsView` (标签页) 实例的生命周期、状态、窗口归属以及跨窗口移动。 - - **`BrowserWindow`**: 作为顶级容器,包含一个用于渲染标签栏 UI 的轻量级页面,并管理一组由 `TabPresenter` 控制的 `WebContentsView`。 - -2. **`WindowPresenter` 改造:** - - - **职责:** 创建、管理 `BrowserWindow` 实例(窗口本身的最小化、最大化、关闭等)。 - - **窗口集合:** 使用 `Map` 存储窗口实例,`key` 为 `BrowserWindow` 的 `id`。 - - **窗口创建:** `createWindow` 负责创建配置好标签栏 UI 和 `WebContentsView` 容器的 `BrowserWindow`。 - - **移除单窗口引用:** 废弃 `MAIN_WIN` 常量和 `mainWindow` getter。 - - **窗口操作:** `minimize`, `maximize`, `close` 等方法修改为接受 `windowId` 或操作当前聚焦窗口。关闭窗口时需通知 `TabPresenter` 清理关联的标签页。 - - **事件广播:** 应用级事件(如主题更改)需要广播到所有窗口;窗口级事件需要正确处理。 - -3. **`TabPresenter` (新增):** - - - **职责:** 核心的标签页管理器。 - - **数据结构:** - - `tabs: Map`: 全局标签页实例及其状态存储。 Key 为 `tabId` (`webContents.id`), Value 为一个包含 `WebContentsView` 实例、状态对象 (`TabState`: { URL, title, favicon, isActive, etc. }) 以及所属窗口 ID (`windowId`) 的对象。 - - `windowTabs: Map`: 窗口ID (`windowId`) 到其包含的标签页ID列表 (`tabId[]`) 的映射,维护标签页在窗口内的顺序。 - - **核心方法:** - - `createTab(windowId, url, options)`: 创建 `WebContentsView`,生成 `TabInfo` 对象存入 `tabs` Map,并将 `tabId` 添加到对应 `windowId` 的 `windowTabs` 数组中。将其添加到 `windowId` 对应的 `BrowserWindow` 的视图层级中 (e.g., `window.contentView.addChildView()`)。 - - `destroyTab(tabId)`: 从 `tabs` Map 中获取 `TabInfo`,找到 `windowId` 和 `view`。从窗口视图层级移除 `view`,销毁 `WebContentsView`,从 `tabs` Map 中删除条目,并从 `windowTabs` 中移除 `tabId`。 - - `activateTab(tabId)`: 在 `tabs` 中找到对应的 `TabInfo`,更新其 `state.isActive`,并在其所属窗口内将 `view` 提升到最前。可能还需要将同一窗口内其他标签的 `isActive` 设为 `false`。 - - `detachTab(tabId)`: 从 `tabs` 中获取 `TabInfo`,从其当前窗口的视图层级移除 `view`。更新 `TabInfo` 中的 `windowId` (可能设为 `null` 或特殊值表示已分离),并从旧窗口的 `windowTabs` 中移除 `tabId`。**注意:此时 `WebContentsView` 实例本身不销毁。** - - `attachTab(tabId, targetWindowId, index?)`: 找到 `tabs` 中的 `TabInfo`。将其 `view` 添加到 `targetWindowId` 对应窗口的视图层级。更新 `TabInfo` 中的 `windowId` 为 `targetWindowId`。将 `tabId` 插入到 `targetWindowId` 对应的 `windowTabs` 数组的指定 `index` (或末尾)。 - - `moveTab(tabId, targetWindowId, index?)`: 协调 `detachTab` 和 `attachTab` 完成标签移动。 - - **事件/IPC 处理:** 监听 `WebContentsView` 事件更新 `tabs` 中对应 `TabInfo` 的 `state`,处理来自渲染进程的标签操作请求。 - -4. **IPC 通信:** - - - **Renderer -> Main:** - - `requestNewTab(windowId, url)` -> `TabPresenter.createTab` - - `requestCloseTab(windowId, tabId)` -> `TabPresenter.destroyTab` - - `requestSwitchTab(windowId, tabId)` -> `TabPresenter.activateTab` - - `notifyTabDragStart(windowId, tabId)`: 通知拖拽开始。 - - `notifyTabDrop(sourceWindowId, tabId, targetWindowId, targetIndex)`: 通知拖拽结束及放置目标 -> `TabPresenter.moveTab`。 - - **Main -> Renderer:** - - `updateWindowTabs(windowId, tabListData)`: 发送标签列表 `{ id, title, faviconUrl, isActive }[]` 给对应窗口渲染进程用于更新UI。 - - `setActiveTab(windowId, tabId)`: 指示渲染进程高亮活动标签。 - -5. **`TrayPresenter` & `ContextMenuHelper`:** - - - 需要能访问窗口和标签列表 (`WindowPresenter`, `TabPresenter`)。 - - 明确托盘图标点击行为(如显示窗口列表、激活最近使用的标签页等)。 - - 上下文菜单能根据当前的 `WebContentsView` 提供相关操作。 - -6. **渲染进程 (Renderer): 多入口架构** - - 为了清晰地分离窗口外壳 (Window Shell) 和标签页内容 (Tab Content) 的职责与构建产物,渲染层将采用多入口构建策略。这将需要修改 `electron.vite.config.ts` 以支持多个 `renderer` 输入。 - - - **入口 1: Window Shell (窗口外壳)** - - - **代码目录:** `src/renderer/shell/` - - **入口文件:** `src/renderer/shell/index.html` (及其关联的 `main.ts`) - - **职责:** - - 渲染 `BrowserWindow` 的主界面框架,主要是顶部的**标签栏 UI** (`TabBar.vue`)。 - - 运行一个独立的、轻量级的 Vue 应用实例。 - - **获取 `windowId`:** 必须能识别自身所属的 `BrowserWindow` ID (通过 preload 脚本注入或 IPC 获取)。 - - **处理标签栏交互:** 响应用户对标签的点击(切换、关闭)、新建标签按钮的点击,并**触发相应的 IPC 消息** (`requestSwitchTab`, `requestCloseTab`, `requestNewTab`) 到主进程。 - - **实现标签拖拽:** 在标签栏内实现标签的拖拽排序和跨窗口拖拽的启动,通过 IPC **通知主进程** (`notifyTabDragStart`, `notifyTabDrop`)。 - - **接收状态更新:** 监听主进程发送的针对该窗口的标签列表更新 (`updateWindowTabs`) 和活动标签变更 (`setActiveTab`) 消息,并更新 UI。 - - **加载方式:** 主进程的 `WindowPresenter` 在创建 `BrowserWindow` 时,应加载此入口的 `index.html` (例如 `dist/renderer/shell/index.html`)。 - - - **入口 2: Tab Content (标签页内容)** - - - **代码目录:** `src/renderer/content/` - - **入口文件:** `src/renderer/content/index.html` (及其关联的 `main.ts`) - - **职责:** - - 渲染**指定标签页内的实际应用视图** (例如聊天界面、设置页面等)。 - - 运行另一个独立的 Vue 应用实例,包含应用本身的状态管理 (Pinia) 和路由 (Vue Router)。 - - **路由:** 使用 Vue Router 根据加载到 `WebContentsView` 中的 URL (由 `TabPresenter` 控制,例如 `dist/renderer/content/index.html#/chat/123` 或 `dist/renderer/content/index.html#/settings`) 来决定显示哪个具体的视图组件 (`ChatView.vue`, `SettingsView.vue` 等)。 - - **与主进程通信:** 处理视图内部的业务逻辑,并通过 IPC 与主进程交互(例如发送消息、保存设置)。 - - **更新标签状态:** 如果内容需要改变标签的外观(如标题、图标),需通过 IPC 通知主进程 (`TabPresenter`) 更新状态。 - - **加载方式:** 主进程的 `TabPresenter` 在创建 `WebContentsView` 实例时,应加载此入口的 `index.html` 并附加相应的 URL hash/path 以进行内容路由。 - - - **共享代码:** - - - 通用的类型定义、工具函数、常量等应放置在 `@shared` 目录中,供主进程和两个渲染进程入口共享。 - - 如果有跨 `shell` 和 `content` 的共享 Vue 组件或 UI 库,需要规划好共享方式 (例如通过 `@shared` 或独立的 UI 包)。 - - - **构建配置 (`electron.vite.config.ts`)** - - 需要将 `renderer` 配置修改为支持多入口的形式,明确指定 `shell` 和 `content` 的 `input` HTML 文件,并可能需要配置不同的 `resolve.alias` 指向各自的源代码目录 (`src/renderer/shell`, `src/renderer/content`)。 - -## 改造计划 - -1. **设计并实现 `TabPresenter`**: 核心数据结构、标签页生命周期管理、状态管理、窗口关联及移动逻辑 (`detachTab`, `attachTab`, `moveTab`)。 -2. **重构 `WindowPresenter`**: 调整窗口创建逻辑以包含标签容器,与 `TabPresenter` 协作处理窗口事件和关闭逻辑。 -3. **实现 `WebContentsView` 容器**: 在 `BrowserWindow` 中设置合适的视图来容纳和管理 `WebContentsView` 实例 (e.g., using `contentView` API)。 -4. **实现 Main Process IPC**: 建立 `Renderer <-> Main (TabPresenter/WindowPresenter)` 的通信通道和消息处理器。 -5. **实现 Renderer 标签栏 UI**: 使用前端框架(如 Vue)创建组件显示标签,处理用户交互并触发 IPC 调用。 -6. **实现 Renderer 标签拖拽逻辑**: 处理拖拽事件、计算放置目标、通过 IPC 通知主进程 (`notifyTabDragStart`, `notifyTabDrop`)。 -7. **实现 Main Process 标签移动处理**: `TabPresenter` 响应 `notifyTabDrop`,调用 `moveTab` 来实际操作 `WebContentsView`。 -8. **实现 State 同步**: 确保标签状态 (title, URL, favicon, active status) 在 Main (`TabPresenter`) 和 Renderer (UI) 之间正确同步。 -9. **调整 `TrayPresenter` & `ContextMenuHelper`**: 使其适应多窗口多标签环境。 -10. **审阅生命周期管理**: 确保标签关闭、窗口关闭、应用退出逻辑的健壮性。 -11. **更新文档**: 将此详细设计写入 `docs/multi-window-architecture.md`。(本步骤) -12. **测试**: 全面测试多窗口创建/关闭、多标签创建/关闭/切换、跨窗口拖拽、状态同步、IPC 通信等功能。 diff --git a/docs/performance_analysis_report.md b/docs/performance_analysis_report.md deleted file mode 100644 index f67eafcc6..000000000 --- a/docs/performance_analysis_report.md +++ /dev/null @@ -1,284 +0,0 @@ -# DeepChat 事件通讯与Store刷新性能分析报告 - -## 概述 - -本报告分析了 DeepChat 项目中 main 到 renderer 进程的事件通讯机制和 Pinia store 的刷新逻辑,识别了性能瓶颈并提出了相应的优化建议。 - -## 架构概览 - -### 通讯架构 -``` -Main Process Renderer Process - ↓ ↑ -EventBus ←→ Presenter → IPC ←→ Preload → usePresenter - ↓ ↑ - └─────→ sendToRenderer ────→ ipcRenderer.on -``` - -### 核心组件 -1. **EventBus (`src/main/eventbus.ts`)**: 主进程事件协调中心 -2. **IPC Bridge (`src/preload/index.ts`)**: 安全的进程间通讯桥梁 -3. **usePresenter (`src/renderer/src/composables/usePresenter.ts`)**: 渲染进程调用主进程的代理 -4. **Pinia Stores**: 渲染进程状态管理 - -## 性能问题分析 - -### 1. 频繁的全量刷新 - -#### 问题描述 -在多个 store 中发现频繁调用全量刷新方法: - -- **settings.ts**: `refreshAllModels()` 被频繁调用 -- **chat.ts**: 每次消息更新都会触发完整的消息列表刷新 -- **mcp.ts**: 服务器状态变化时重新加载所有工具 - -#### 具体表现 -```typescript -// settings.ts:612 - 过于频繁的全量刷新 -window.electron.ipcRenderer.on(CONFIG_EVENTS.PROVIDER_CHANGED, async () => { - providers.value = await configP.getProviders() - await refreshAllModels() // 全量刷新所有模型 -}) - -// settings.ts:567 - 未优化的节流配置 -const refreshAllModels = useThrottleFn(_refreshAllModelsInternal, 1000, true, false) -``` - -#### 性能影响 -- 网络请求密集:每次刷新可能触发多个 Provider API 调用 -- UI 渲染阻塞:大量数据更新导致界面卡顿 -- 内存占用增加:频繁的数据克隆和序列化 - -### 2. 低效的事件监听器管理 - -#### 问题描述 -事件监听器注册缺乏生命周期管理: - -```typescript -// chat.ts:1076 - 缺少清理逻辑 -const setupEventListeners = () => { - window.electron.ipcRenderer.on(CONVERSATION_EVENTS.LIST_UPDATED, (_, data) => { - // 处理逻辑 - }) - // 没有对应的 removeListener 逻辑 -} -``` - -#### 性能影响 -- 内存泄漏:监听器累积导致内存使用持续增长 -- 重复执行:同一事件可能被多个监听器处理 -- 调试困难:事件流追踪复杂 - -### 3. 同步阻塞操作 - -#### 问题描述 -preload 脚本中使用同步 IPC 调用: - -```typescript -// preload/index.ts:24,31 - 同步调用阻塞渲染进程 -cachedWindowId = ipcRenderer.sendSync('get-window-id') -cachedWebContentsId = ipcRenderer.sendSync('get-web-contents-id') -``` - -#### 性能影响 -- 渲染进程阻塞:同步调用会暂停 UI 响应 -- 启动延迟:应用初始化过程变慢 - -### 4. 过度的序列化开销 - -#### 问题描述 -usePresenter 中过度保护性的序列化: - -```typescript -// usePresenter.ts:64 - 每次调用都进行深度序列化 -const rawPayloads = payloads.map((e) => safeSerialize(toRaw(e))) -``` - -#### 性能影响 -- CPU 占用:复杂对象的深度序列化消耗大量计算资源 -- 内存开销:创建大量临时对象 -- 调用延迟:每次 IPC 调用都有额外开销 - -### 5. 状态管理冗余 - -#### 问题描述 -多个 store 之间存在状态冗余和重复计算: - -```typescript -// chat.ts 中的复杂状态管理 -const threadsWorkingStatusMap = ref>>(new Map()) -const generatingMessagesCacheMap = ref>>(new Map()) -``` - -#### 性能影响 -- 内存占用:重复存储相似数据 -- 同步复杂:多个状态源需要保持一致性 -- 更新延迟:状态变更需要在多处同步 - -## 优化建议 - -### 1. 增量更新策略 - -#### 实现方案 -```typescript -// 替换全量刷新为增量更新 -const updateModelStatus = (providerId: string, modelId: string, enabled: boolean) => { - // 只更新特定模型状态,而非重新加载所有模型 - const providerIndex = enabledModels.value.findIndex(p => p.providerId === providerId) - if (providerIndex !== -1) { - const modelIndex = enabledModels.value[providerIndex].models.findIndex(m => m.id === modelId) - if (modelIndex !== -1) { - enabledModels.value[providerIndex].models[modelIndex].enabled = enabled - } - } -} -``` - -#### 预期收益 -- 减少 50% 的 API 调用 -- 降低 70% 的 UI 重渲染 -- 提升响应速度 3-5 倍 - -### 2. 事件监听器生命周期管理 - -#### 实现方案 -```typescript -// 添加监听器清理机制 -const useEventCleanup = () => { - const listeners = new Set<() => void>() - - const addListener = (event: string, handler: Function) => { - window.electron.ipcRenderer.on(event, handler) - listeners.add(() => window.electron.ipcRenderer.off(event, handler)) - } - - const cleanup = () => { - listeners.forEach(remove => remove()) - listeners.clear() - } - - onUnmounted(cleanup) - - return { addListener, cleanup } -} -``` - -#### 预期收益 -- 消除内存泄漏 -- 减少事件处理冲突 -- 提升应用稳定性 - -### 3. 异步化改造 - -#### 实现方案 -```typescript -// 将同步调用改为异步 -const getWebContentsId = async (): Promise => { - if (cachedWebContentsId !== null) { - return cachedWebContentsId - } - - try { - cachedWebContentsId = await window.electron.ipcRenderer.invoke('get-web-contents-id') - return cachedWebContentsId - } catch (error) { - console.warn('Failed to get webContentsId:', error) - return -1 - } -} -``` - -#### 预期收益 -- 消除 UI 阻塞 -- 改善用户体验 -- 提升应用启动速度 - -### 4. 智能序列化优化 - -#### 实现方案 -```typescript -// 实现智能序列化策略 -const smartSerialize = (obj: unknown): unknown => { - // 对于简单类型直接返回 - if (obj === null || typeof obj !== 'object') { - return obj - } - - // 检查对象大小,小对象直接序列化 - if (getObjectSize(obj) < 1024) { - return safeSerialize(obj) - } - - // 大对象进行引用传递 - return createObjectReference(obj) -} -``` - -#### 预期收益 -- 减少 60% 的序列化开销 -- 降低内存使用 -- 提升 IPC 调用性能 - -### 5. 状态管理优化 - -#### 实现方案 -```typescript -// 统一状态管理 -const useGlobalState = () => { - const centralStore = useCentralStore() - - // 使用计算属性避免重复状态 - const threadStatus = computed(() => - centralStore.getThreadStatus(activeThreadId.value) - ) - - // 使用 reactive 替代多层嵌套的 ref - const statusMap = reactive(new Map()) - - return { threadStatus, statusMap } -} -``` - -#### 预期收益 -- 减少 40% 的内存使用 -- 简化状态同步逻辑 -- 提升维护性 - -## 性能监控建议 - -### 1. 关键指标监控 -- IPC 调用频率和延迟 -- 事件监听器数量 -- Store 状态更新频率 -- 内存使用趋势 - -### 2. 性能测试用例 -- 大量消息场景下的渲染性能 -- 多个 Provider 同时更新的响应时间 -- 长时间运行的内存稳定性 - -### 3. 开发工具集成 -- 添加 IPC 调用日志(已有 VITE_LOG_IPC_CALL) -- 实现事件流可视化 -- 集成性能分析器 - -## 实施优先级 - -### 高优先级(立即实施) -1. 异步化 preload 中的同步调用 -2. 实现事件监听器清理机制 -3. 优化 settings store 的全量刷新逻辑 - -### 中优先级(短期实施) -1. 实现增量更新策略 -2. 优化序列化机制 -3. 添加性能监控 - -### 低优先级(长期优化) -1. 重构状态管理架构 -2. 实现更细粒度的事件系统 -3. 添加自动化性能测试 - -## 总结 - -DeepChat 的事件通讯和状态管理系统总体架构合理,但在性能优化方面还有很大提升空间。通过实施上述优化建议,预计可以显著提升应用的响应速度、降低内存使用并改善用户体验。建议按照优先级逐步实施,并在每个阶段进行性能测试验证优化效果。 \ No newline at end of file diff --git a/docs/start.md b/docs/start.md deleted file mode 100644 index 436a16e4c..000000000 --- a/docs/start.md +++ /dev/null @@ -1,21 +0,0 @@ -# DeepChat Documentation - -This directory contains design documents and usage guides for DeepChat. - -## User Documentation - -- [User Guide](./user-guide.md) - How to install, configure, and use DeepChat. - -## Developer Documentation - -- [Developer Guide](./developer-guide.md) - Information for developers contributing to or building with DeepChat. - -## Design Documents - -- [Event System Design](./event-system-design.md) - Design and implementation of the application event system -- [Data Sync Feature](./data-sync-feature.md) - Design and implementation of the data sync feature -- [LLM Provider Interface Design](./llm-provider-interface.md) - Design of the LLM Provider interface - -## Build Guides - -- [Linux Build Guide](./linux-build-guide.md) - Steps to build DeepChat on Linux systems diff --git a/docs/tool-calling-system.md b/docs/tool-calling-system.md new file mode 100644 index 000000000..6ba5b7057 --- /dev/null +++ b/docs/tool-calling-system.md @@ -0,0 +1,608 @@ +# DeepChat 工具调用系统完整文档 + +## 概述 + +DeepChat 的工具调用系统支持两种实现方式: +1. **原生 Tool Calling**: 使用 LLM 原生的 function calling 能力 +2. **提示词工程**: 通过标准化 prompt 包装,模拟工具调用行为 + +系统通过 MCP (Model Context Protocol) 提供统一的工具定义和调用接口,支持多种 LLM 提供商的工具格式转换。 + +## 技术架构 + +### 系统组件关系 + +```mermaid +graph TB + subgraph "LLM Provider Layer" + OpenAI[OpenAI Compatible] + Anthropic[Anthropic Claude] + Gemini[Google Gemini] + Other[其他提供商] + end + + subgraph "Tool Calling Engine" + AgentLoop[Agent Loop 代理循环] + FormatConverter[格式转换器] + PromptEngine[提示词引擎] + end + + subgraph "MCP Integration" + McpPresenter[MCP Presenter] + ToolManager[Tool Manager] + ServerManager[Server Manager] + end + + subgraph "MCP Tools & Services" + BuiltinTools[内置工具] + ExternalServers[外部 MCP 服务器] + CustomTools[自定义工具] + end + + OpenAI --> AgentLoop + Anthropic --> AgentLoop + Gemini --> AgentLoop + Other --> AgentLoop + + AgentLoop --> FormatConverter + AgentLoop --> PromptEngine + + FormatConverter --> McpPresenter + PromptEngine --> McpPresenter + + McpPresenter --> ToolManager + McpPresenter --> ServerManager + + ToolManager --> BuiltinTools + ServerManager --> ExternalServers + ToolManager --> CustomTools +``` + +### MCP 工具映射和定义 + +MCP 工具的基本结构定义: + +```typescript +{ + name: string; // 工具的唯一标识符 + description?: string; // 人类可读的描述 + inputSchema: { // 工具参数的 JSON Schema + type: "object", + properties: { ... } // 工具特定参数 + } +} +``` + +通过 `mcpClient.ts` 的 `callTool` 方法实现跨提供商的工具调用: + +```typescript +async callTool(toolName: string, args: Record): Promise +``` + +工具调用结果遵循统一格式: + +```typescript +interface ToolCallResult { + isError?: boolean; + content: Array<{ + type: string; + text: string; + }>; +} +``` + +## 原生 Tool Calling 实现 + +### Anthropic Claude API + +#### 格式转换 +Anthropic 要求工具定义通过 `tools` 参数传递: + +```typescript +{ + tools: [ + { + name: string; + description: string; + input_schema: object; // JSON Schema格式 + } + ] +} +``` + +#### 上下文组织 +1. 系统消息(system):独立于对话消息,通过 `system` 参数传递 +2. 用户消息(user):包含 `content` 数组,可以包含文本和图像 +3. 助手消息(assistant):可以包含工具调用,使用 `tool_use` 类型的内容块 +4. 工具响应:作为用户消息的一部分,使用 `tool_result` 类型的内容块 + +#### 流式处理 +Claude API 返回的工具调用事件: +- `content_block_start`(类型为 `tool_use`):工具调用开始 +- `content_block_delta`(带有 `input_json_delta`):工具参数流式更新 +- `content_block_stop`:工具调用结束 +- `message_delta`(带有 `stop_reason: 'tool_use'`):因工具调用而停止生成 + +#### 示例:获取时间 + +**工具定义:** +```json +{ + "name": "getTime", + "description": "获取特定时间偏移量的时间戳(毫秒)", + "input_schema": { + "type": "object", + "properties": { + "offset_ms": { + "type": "number", + "description": "相对于当前时间的毫秒数偏移量" + } + }, + "required": ["offset_ms"] + } +} +``` + +**用户请求:** +```json +{ + "role": "user", + "content": [{"type": "text", "text": "请告诉我昨天的日期是什么时候?"}] +} +``` + +**模型响应:** +```json +{ + "role": "assistant", + "content": [ + {"type": "text", "text": "为了告诉您昨天的日期,我需要获取昨天的时间戳。"}, + { + "type": "tool_use", + "id": "toolu_01ABCDEFGHIJKLMNOPQRST", + "name": "getTime", + "input": {"offset_ms": -86400000} + } + ] +} +``` + +**工具执行结果:** +```json +{ + "role": "user", + "content": [ + { + "type": "tool_result", + "tool_use_id": "toolu_01ABCDEFGHIJKLMNOPQRST", + "result": "1684713600000" + } + ] +} +``` + +**最终响应:** +```json +{ + "role": "assistant", + "content": [ + { + "type": "text", + "text": "根据获取的时间戳1684713600000,昨天的日期是2023年5月22日。" + } + ] +} +``` + +### Google Gemini API + +#### 格式转换 +Gemini 要求工具定义为: + +```typescript +{ + tools: [ + { + functionDeclarations: [ + { + name: string, + description: string, + parameters: object // OpenAPI格式的JSON Schema + } + ] + } + ] +} +``` + +#### 消息结构 +1. 系统指令(systemInstruction):作为独立参数传递 +2. 内容数组(contents):包含用户和模型消息 +3. 工具调用:通过 `functionCall` 对象表示 +4. 工具响应:通过 `functionResponse` 对象表示 + +#### 示例流程 + +**工具定义:** +```json +{ + "tools": [ + { + "functionDeclarations": [ + { + "name": "getTime", + "description": "获取特定时间偏移量的时间戳(毫秒)", + "parameters": { + "type": "object", + "properties": { + "offset_ms": { + "type": "number", + "description": "相对于当前时间的毫秒偏移量" + } + }, + "required": ["offset_ms"] + } + } + ] + } + ] +} +``` + +**模型响应(调用工具):** +```json +{ + "role": "model", + "parts": [ + { + "functionCall": { + "name": "getTime", + "args": {"offset_ms": -86400000} + } + } + ] +} +``` + +**工具响应:** +```json +{ + "role": "user", + "parts": [ + { + "functionResponse": { + "name": "getTime", + "response": 1684713600000 + } + } + ] +} +``` + +### OpenAI API + +#### 格式转换 +OpenAI 的函数调用格式: + +```typescript +{ + tools: [ + { + type: "function", + function: { + name: string, + description: string, + parameters: object // JSON Schema格式 + } + } + ] +} +``` + +#### 消息结构 +1. 消息数组(messages):包含 role 和 content +2. 工具调用:记录在 assistant 消息中的 `tool_calls` 数组 +3. 工具响应:作为单独的 `tool` 角色消息,包含 `tool_call_id` 引用 + +#### 流式处理 +- `tool_calls` 数组表示工具调用 +- 流式 API 返回 `delta.tool_calls` 表示工具调用的增量更新 +- 流式工具参数通过 `tool_calls[i].function.arguments` 传递 + +#### 示例流程 + +**模型响应(调用工具):** +```json +[ + { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_abc123", + "type": "function", + "function": { + "name": "getTime", + "arguments": "{ \"offset_ms\": -86400000 }" + } + } + ] + } +] +``` + +**工具响应:** +```json +[ + { + "role": "tool", + "tool_call_id": "call_abc123", + "content": "1684713600000" + } +] +``` + +## 提示词工程实现 + +### 设计目标 + +对于不支持原生 function calling 的模型,通过**提示词工程(prompt engineering)**,即使在不依赖原生 function calling 的情况下,也能**模拟 Tool Use 行为**。 + +设计目标: +- 通过标准化 prompt 包装,引导模型以规范格式调用 Tool +- 适配各类 LLM,包括不支持原生 Function Calling 的模型 +- 支持扩展到多 Tool 使用和复杂调度场景 + +### 核心实现流程 + +```mermaid +flowchart TD + A[用户输入 prompt] --> B[getFunctionCallWrapPrompt] + B --> C[打包后的 prompt] + C --> D[coreStream 流式发送] + D --> E[LLM 返回 delta] + E --> F{delta 有内容吗} + F -- 是 --> G[抽取字符、重组] + G --> H{包含 Tool Call 吗} + H -- 是 --> I[parseFunctionCalls] + H -- 否 --> J{是否有可能组成 Tool Call} + J -- 是 --> K[继续累加内容] + J -- 否 --> L[发送累加内容并清空] + I --> M[返回 Tool Call result] + K --> G + L --> G +``` + +### 1. getFunctionCallWrapPrompt(prompt, functions) + +**功能**:将原始用户 prompt 和可用 Tools 打包,引导 LLM 按指定 JSON 格式返回 Tool Call。 + +**主要逻辑**: +- 列出全部函数(包括名称和参数格式) +- 定义规范格式: +```json +{ "tool_name": "xxx", "parameters": { "key": "value" } } +``` +- 插入原始用户输入,保持连贯自然 + +**核心思想**:让不支持原生 Function Calling 的模型也能理解"可以调用工具"。 + +### 2. coreStream(config) + +**功能**:负责流式向 LLM 发送请求,同时流式接收 delta 数据并实时处理。 + +**处理细节**: +- 每次接收 delta: + - 检测是否包含 `content` + - 将每个字符段重新组合,保证符合 JSON 格式 + - 封装成新的 reply 字符串,防止丢片或乱序 +- 重组处理: + - 遇到 Tool Call JSON 特征(如 `{ "tool_name"` 开头) + - 将可能被切断的文本段进行合并 +- 检测到完整 Tool Call JSON,立刻调用 `parseFunctionCalls` + +**状态机**:流式数据处理过程采用状态机模型来逐步处理返回的 delta 数据。 + +```mermaid +stateDiagram-v2 + [*] --> 接收Delta + 接收Delta --> 抽取拼接 + 抽取拼接 --> 判断工具调用 + 判断工具调用 --> 工具调用解析 : 是 + 判断工具调用 --> 继续累加 : 否 + 工具调用解析 --> 完成调用 + 继续累加 --> [*] +``` + +### 3. parseFunctionCalls(text) + +**功能**:从自然语言输出中,提取符合格式的 Tool Call JSON,并解析成标准 JS Object。 + +**主要逻辑**: +- 正则匹配 `{...}` 结构 +- 支持多 Tool Call 同时存在 +- 对异常 JSON(超出字符、缺引号等)进行容错修正 + +### 提示词工程示例 + +#### 1. 添加函数描述到系统提示中: + +``` +你是一个有用的AI助手。当需要时,你可以使用以下工具帮助回答问题: + +function getTime(offset_ms: number): number +描述: 获取当前时间偏移后的毫秒数时间戳 +参数: + - offset_ms: 时间偏移量(毫秒) + +使用工具时,请使用以下格式: + +{ + "name": "getTime", + "arguments": { + "offset_ms": -86400000 + } +} + +``` + +#### 2. 模型生成带有函数调用标记的回复: + +``` +我需要获取昨天的日期。我将调用getTime函数获取昨天的时间戳。 + + +{ + "name": "getTime", + "arguments": { + "offset_ms": -86400000 + } +} + +``` + +#### 3. 通过正则表达式解析函数调用: + +```typescript +// 使用状态机和正则匹配提取标签内容 +const functionCallMatch = response.match(/([\s\S]*?)<\/function_call>/); +if (functionCallMatch) { + try { + const parsedCall = JSON.parse(functionCallMatch[1]); + // 调用函数并获取结果 + } catch (error) { + // 处理解析错误 + } +} +``` + +#### 4. 将函数结果添加到上下文中: + +``` +函数结果: 1684713600000 + +根据获取的时间戳,昨天是5月22日。 +``` + +## Agent Loop 架构 + +### 两层架构设计 + +LLM 系统遵循两层架构: + +1. **Agent Loop Layer** (`llmProviderPresenter/index.ts`): + - 管理对话流程和多轮工具调用 + - 处理通过 McpPresenter 的工具执行 + - 向前端发送标准化事件 + +2. **Provider Layer** (`llmProviderPresenter/providers/*.ts`): + - 每个提供商处理特定的 LLM API 交互 + - 将 MCP 工具转换为提供商特定格式 + - 将流式响应标准化为标准事件 + - 支持原生和提示词包装的工具调用 + +### 标准化事件接口 + +所有 LLM 提供商都转换为统一的 `LLMCoreStreamEvent` 事件: + +```typescript +{ + type: 'tool_call_start' | 'tool_call_chunk' | 'tool_call_end' | 'content_chunk'; + tool_call_id?: string; + tool_call_name?: string; + tool_call_arguments_chunk?: string; + tool_call_arguments_complete?: string; + content?: string; +} +``` + +### 工具调用流程时序图 + +```mermaid +sequenceDiagram + participant User + participant Frontend + participant BaseProvider + participant LLM + participant Parser + + User->>Frontend: 提交 prompt + Frontend->>BaseProvider: getFunctionCallWrapPrompt(prompt, functions) + BaseProvider-->>Frontend: 返回打包后 prompt + + Frontend->>LLM: 发送请求 + LLM-->>Frontend: 流式返回 delta + + loop 流式处理 + Frontend->>Frontend: 抽取字符、重组 + Frontend->>Frontend: 检测是否有 Tool Call JSON + alt 检测到 Tool Call + Frontend->>Parser: parseFunctionCalls(text) + Parser-->>Frontend: 返回函数调用结果 + end + end + + Frontend->>User: 展示最终结果 +``` + +## 设计亮点 + +### 1. 统一接口 +- MCP 提供统一的工具定义和调用接口 +- 支持多种传输协议(stdio, SSE, HTTP, InMemory) +- 标准化的工具调用结果格式 + +### 2. 多提供商支持 +- 原生支持 OpenAI、Anthropic、Google Gemini 等主流 LLM +- 自动格式转换,无需修改工具定义 +- 提示词工程兼容不支持原生工具调用的模型 + +### 3. 智能流式处理 +- 实时处理流式响应 +- 字符级重组与校验 +- 容错解析不规则 JSON + +### 4. 高容错性 +- 支持不规则、复杂、多重并发 Tool Use +- 自动处理工具名称冲突 +- 错误恢复和重试机制 + +### 5. 权限管理 +- 细粒度权限控制 +- 用户友好的权限请求界面 +- 持久化权限设置 + +## 使用建议 + +### 开发者指南 + +1. **工具开发**:使用标准 MCP 接口定义工具 +2. **错误处理**:实现完善的错误处理和用户反馈 +3. **性能优化**:合理使用缓存和批处理 +4. **安全考虑**:实施适当的权限检查和输入验证 + +### 最佳实践 + +1. **工具设计**:保持工具功能单一、参数简洁 +2. **错误消息**:提供清晰、可操作的错误信息 +3. **文档完善**:为工具提供详细的描述和示例 +4. **测试覆盖**:包括正常流程和异常情况的测试 + +### 性能考虑 + +1. **缓存策略**:合理缓存工具定义和结果 +2. **并发控制**:避免同时执行过多工具调用 +3. **资源管理**:及时清理不需要的资源 +4. **监控告警**:实施必要的监控和告警机制 + +## 未来扩展方向 + +1. **自适应 Prompt 调整**:根据模型类型高效引导 +2. **嵌套式 Tool Call**:支持工具内部再调工具 +3. **多轮对话中的 Tool Use 继承与独立管理** +4. **更多传输协议支持**:WebSocket、gRPC 等 +5. **工具组合和工作流**:支持复杂的工具编排 + +--- + +DeepChat 的工具调用系统通过 MCP 统一接口、多提供商支持和智能流式处理,为用户提供了强大而灵活的 AI 工具调用能力。无论是支持原生工具调用的先进模型,还是需要提示词工程的传统模型,都能获得一致的用户体验和开发体验。 \ No newline at end of file diff --git a/docs/usePageCapture.example.md b/docs/usePageCapture.example.md deleted file mode 100644 index 1bd25c2e2..000000000 --- a/docs/usePageCapture.example.md +++ /dev/null @@ -1,217 +0,0 @@ -# usePageCapture 使用示例 - -## 基本用法 - -```typescript -import { usePageCapture } from '@/composables/usePageCapture' -import { useI18n } from 'vue-i18n' - -const { t } = useI18n() -const { isCapturing, captureArea, captureAndCopy } = usePageCapture() - -// 获取应用版本 -const appVersion = await devicePresenter.getAppVersion() - -// 定义截图目标区域 -const getTargetRect = () => { - const element = document.querySelector('.target-element') - if (!element) return null - - const rect = element.getBoundingClientRect() - return { - x: Math.round(rect.x), - y: Math.round(rect.y), - width: Math.round(rect.width), - height: Math.round(rect.height) - } -} - -// 执行截图并复制到剪贴板 -const handleCapture = async () => { - const success = await captureAndCopy({ - container: '.scroll-container', - getTargetRect, - watermark: { - isDark: true, - version: appVersion, - texts: { - brand: 'DeepChat', - tip: t('common.watermarkTip') - } - } - }) - - if (success) { - console.log('截图成功') - } else { - console.error('截图失败') - } -} -``` - -## 使用预设配置 - -```typescript -import { usePageCapture, createCapturePresets } from '@/composables/usePageCapture' - -const { captureAndCopy } = usePageCapture() -const { captureFullConversation, captureMessageRange, captureCustomElement } = createCapturePresets() - -// 截取整个会话 -const captureConversation = async () => { - const config = captureFullConversation({ - isDark: true, - version: appVersion, - texts: { - brand: 'DeepChat', - tip: t('common.watermarkTip') - } - }) - - await captureAndCopy(config) -} - -// 截取指定消息范围 -const captureMessages = async (startId: string, endId: string) => { - const config = captureMessageRange(startId, endId, { - isDark: true, - version: appVersion, - texts: { - brand: 'DeepChat', - tip: t('common.watermarkTip') - } - }) - - await captureAndCopy(config) -} - -// 截取自定义元素 -const captureCustomArea = async () => { - const config = captureCustomElement('.chat-sidebar', '.main-container', { - isDark: true, - version: appVersion, - texts: { - brand: 'DeepChat', - tip: t('common.watermarkTip') - } - }) - - await captureAndCopy(config) -} -``` - -## 高级用法 - -```typescript -// 自定义配置参数 -const advancedCapture = async () => { - const result = await captureArea({ - container: '.custom-scroll-container', - getTargetRect: () => { - // 自定义目标区域计算逻辑 - const elements = document.querySelectorAll('.message-item') - if (elements.length === 0) return null - - const firstRect = elements[0].getBoundingClientRect() - const lastRect = elements[elements.length - 1].getBoundingClientRect() - - return { - x: Math.round(firstRect.x), - y: Math.round(firstRect.y), - width: Math.round(firstRect.width), - height: Math.round(lastRect.bottom - firstRect.top) - } - }, - watermark: { - isDark: false, - version: '1.0.0', - texts: { - brand: 'MyApp', - tip: '自定义水印提示' - } - }, - scrollBehavior: 'smooth', // 平滑滚动 - captureDelay: 500, // 增加延迟时间 - maxIterations: 50, // 增加最大迭代次数 - scrollbarOffset: 15, // 自定义滚动条偏移 - containerHeaderOffset: 60 // 自定义容器头部偏移 - }) - - if (result.success && result.imageData) { - // 处理截图数据 - console.log('截图成功,数据长度:', result.imageData.length) - - // 可以保存到文件或执行其他操作 - // await saveImageToFile(result.imageData) - } else { - console.error('截图失败:', result.error) - } -} -``` - -## 在组件中使用 - -```vue - - - -``` - -## 配置参数说明 - -| 参数 | 类型 | 默认值 | 说明 | -|------|------|--------|------| -| `container` | `string \| HTMLElement` | - | 滚动容器,CSS选择器或DOM元素 | -| `getTargetRect` | `() => CaptureRect \| null` | - | 获取目标截图区域的函数 | -| `watermark` | `WatermarkConfig` | `undefined` | 水印配置 | -| `scrollBehavior` | `'auto' \| 'smooth'` | `'auto'` | 滚动行为 | -| `captureDelay` | `number` | `350` | 每次截图后的延迟时间(毫秒) | -| `maxIterations` | `number` | `30` | 最大迭代次数 | -| `scrollbarOffset` | `number` | `20` | 滚动条偏移量 | -| `containerHeaderOffset` | `number` | `44` | 容器顶部预留空间 | - -## 返回值说明 - -### CaptureResult - -```typescript -interface CaptureResult { - success: boolean // 是否成功 - imageData?: string // base64格式的图片数据 - error?: string // 错误信息 -} -``` - -### 方法说明 - -- `captureArea(config)`: 执行截图并返回结果对象 -- `captureAndCopy(config)`: 执行截图并直接复制到剪贴板,返回是否成功 -- `isCapturing`: 响应式的截图状态,用于显示加载状态