diff --git a/README.md b/README.md index c476882..0a22324 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,47 @@ ````markdown # MCP Vue 客户端 -> **版本:2.0.0** | 架构重构版 - 更清晰、更可维护、更高性能 +> **版本:1.0.0** | 基础版本 + 最新改进 -基于 **Vue 3** 和 **MCP 协议**构建的现代化 MCP 客户端界面,支持多模型服务、流式对话、MCP 工具调用。 +基于 **Vue 3** 和 **MCP 协议**构建的现代化 MCP 客户端界面,支持HTTP和SSE双传输协议。 -## 🎉 v2.0.0 重大更新(2025-10-16) +## 🎉 最新改进(开发中) -### 🏗️ 架构重构 -- ✅ **服务拆分** - 将 1,147 行单体服务拆分为 5 个独立服务 -- ✅ **单一职责** - MessageService、ConversationService、StreamProcessor、ToolExecutor、ChatOrchestrator -- ✅ **统一日志** - Logger 系统,支持日志级别和命名空间 -- ✅ **错误处理** - AppError 体系,类型化错误(ValidationError、NetworkError、ServiceError 等) -- ✅ **性能优化** - 批量输出、上下文限制、性能监控 +- ✅ **完善的服务器管理** - 编辑功能完全可用,表单数据正确填充 +- ✅ **可靠的连接功能** - HTTP/SSE双协议支持,自动URL转换 +- ✅ **自动重连机制** - 页面刷新自动恢复连接状态 +- ✅ **改进的用户体验** - 美观界面,实时状态显示,详细错误提示 -### 🚀 核心改进 -- ✅ **代码可维护性** - 单文件最大行数从 1,147 降至 580(↓49%) -- ✅ **易于测试** - 每个服务独立可测,支持依赖注入 -- ✅ **更好的性能** - 批量输出减少 60%+ 重渲染,首字延迟监控 -- ✅ **完善文档** - 架构文档、重构报告、API 文档 - -📖 **重构详情**: 查看 [REFACTOR_COMPLETE.md](./REFACTOR_COMPLETE.md) -📚 **架构文档**: 查看 [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) -📋 **版本说明**: 查看 [release.md](./release.md) +📖 **改进详情**: 查看 [CHANGELOG.md](./CHANGELOG.md) +📚 **使用指南**: 查看 [DEVELOPMENT_GUIDE.md](./DEVELOPMENT_GUIDE.md) ## ✨ 核心特性 -### 聊天功能 -- 💬 **多模型支持** - 阿里通义、火山方舟等多模型服务 -- 🌊 **流式对话** - 实时流式响应,打字机效果 -- 🔧 **MCP 工具调用** - 支持工具链、递归调用 -- 📝 **对话管理** - 创建、编辑、删除、置顶、收藏、归档 -- � **持久化** - localStorage 存储,页面刷新保持状态 - -### 架构特性(v2.0) -- 🏗️ **模块化架构** - 5 个独立服务,单一职责 -- 📊 **统一日志** - Logger 系统,分级管理(DEBUG/INFO/WARN/ERROR) -- ⚠️ **类型化错误** - AppError 体系,更好的错误处理 -- ⚡ **性能优化** - 批量输出、上下文限制、性能监控 -- 🧪 **易于测试** - 服务解耦,支持单元测试 - -### 用户体验 - 🎨 **美观界面** - 基于 Naive UI 的现代化设计 +- 🔧 **双协议支持** - HTTP 和 SSE 传输模式 +- 🔄 **自动重连** - 页面刷新后自动恢复连接状态 +- ⚡ **实时状态** - 连接状态实时监控和显示 +- � **完整管理** - 服务器配置、编辑、测试一体化 - 📱 **响应式设计** - 适配桌面和移动设备 -- ⚡ **实时状态** - 连接状态、发送状态实时显示 -- 🎯 **快捷操作** - 复制、删除、重新生成消息 -## 🏗️ 项目架构(v2.0 重构版) +## 🏗️ 项目架构 ``` mcp-client-vue/ -├── src/ # 后端服务 -│ ├── server/ # Node.js 服务器 -│ │ ├── index.ts # Express 服务器入口 -│ │ ├── MCPManager.ts # MCP 服务器管理 -│ │ └── LLMService.ts # 模型服务集成 -│ └── types/ # 后端类型定义 -├── web/ # Vue 3 前端(重构版) +├── src/ # 后端服务 +│ ├── server/ # Express + Socket.IO 服务器 +│ │ ├── index.ts # 主服务器入口 +│ │ ├── MCPManager.ts # 基于 SmartMCPClient 的服务器管理 +│ │ └── LLMService.ts # OpenAI 集成服务 +│ └── types/ # TypeScript 类型定义 +├── web/ # Vue 3 前端 │ ├── src/ -│ │ ├── components/ # UI 组件 -│ │ │ ├── Chat/ # 聊天组件 -│ │ │ ├── Settings/ # 设置组件 -│ │ │ └── Common/ # 通用组件 -│ │ ├── services/ # 业务服务层 ⭐ 重构重点 -│ │ │ ├── chat/ # 聊天服务(新架构) -│ │ │ │ ├── MessageService.ts # 消息 CRUD -│ │ │ │ ├── ConversationService.ts # 对话管理 -│ │ │ │ ├── StreamProcessor.ts # 流式处理 -│ │ │ │ ├── ToolExecutor.ts # 工具执行 -│ │ │ │ ├── ChatOrchestrator.ts # 协调器 -│ │ │ │ └── index.ts # 导出 -│ │ │ ├── modelServiceManager.ts -│ │ │ └── MCPClientService.ts -│ │ ├── stores/ # Pinia 状态管理 -│ │ ├── utils/ # 工具函数 ⭐ 新增 -│ │ │ ├── logger.ts # 统一日志系统 -│ │ │ ├── error.ts # 错误处理体系 -│ │ │ └── index.ts # 导出 -│ │ ├── types/ # 类型定义 -│ │ └── views/ # 页面视图 -│ └── package.json -├── docs/ # 文档 ⭐ 新增 -│ └── ARCHITECTURE.md # 架构文档 -└── package.json -``` - -### 服务层架构(v2.0 核心改进) - -``` -ChatOrchestrator (协调器) -├── MessageService (消息 CRUD - 245 行) -├── ConversationService (对话管理 - 145 行) -├── StreamProcessor (流式处理 - 305 行) -└── ToolExecutor (工具执行 - 285 行) - -工具层: -├── Logger (统一日志 - 138 行) -└── AppError + ErrorHandler (错误处理 - 200 行) +│ │ ├── components/ # Vue 组件 +│ │ ├── stores/ # Pinia 状态管理 +│ │ ├── views/ # 页面视图 +│ │ └── types/ # 前端类型 +│ └── package.json # 前端依赖 +└── package.json # 后端依赖 ``` ## 🚀 快速开始 @@ -128,7 +73,7 @@ npx vite --port 5174 ``` 名称: XHS HTTP Server 类型: http -URL: http://localhost:3100 +URL: http://localhost:3100/mcp 描述: HTTP传输模式 ``` @@ -275,43 +220,15 @@ npm run dev - 🎯 [IMPROVEMENTS.md](./IMPROVEMENTS.md) - 改进详细说明 - �️ [DOCS_INDEX.md](./DOCS_INDEX.md) - 文档索引 -## 📈 v2.0 架构重构成果 +## �📈 下一步计划 -### 代码质量提升 -| 指标 | v1.0 | v2.0 | 改进 | -|------|------|------|------| -| 单文件最大行数 | 1,147 | 580 | ↓ 49% | -| 服务职责数量 | 6+ | 1 | 单一职责 | -| 日志系统 | console.log | Logger 类 | 分级管理 | -| 错误处理 | 不统一 | AppError 体系 | 类型化 | -| 可测试性 | 困难 | 容易 | 独立测试 | - -### 性能优化 -- ✅ **批量输出** - 减少 60%+ UI 重渲染 -- ✅ **上下文限制** - 限制最近 20 条消息,减少 token 消耗 -- ✅ **性能监控** - 追踪首字延迟、总耗时、chunk 数 - -### 开发体验 -- ✅ **清晰架构** - 易于理解和维护 -- ✅ **完善文档** - 架构文档、API 文档、重构报告 -- ✅ **类型安全** - 完整的 TypeScript 类型定义 - -## 📈 下一步计划 - -### Phase 2: 持续优化(Day 3-4) -- [ ] 替换所有 console.log 为 logger -- [ ] 实现虚拟滚动优化消息列表 -- [ ] 添加数据库索引(IndexedDB 迁移) -- [ ] 优化重渲染(shallowRef) -- [ ] 添加请求缓存和去重 - -### Phase 3: 新功能开发(Week 2+) -- [ ] 添加单元测试(目标 80%+ 覆盖率) -- [ ] 消息搜索功能 -- [ ] 对话导出功能 -- [ ] 多端同步支持 -- [ ] 插件系统 -- [ ] 工具调用历史记录 +- [ ] 修复所有TypeScript类型错误 +- [ ] 添加工具调用历史记录 +- [ ] 实现资源管理功能 +- [ ] 添加提示词(Prompts)管理 +- [ ] 配置文件导入/导出 +- [ ] 添加性能监控面板 +- [ ] 支持WebSocket传输协议 ## ❓ 常见问题 @@ -346,18 +263,5 @@ MIT License --- -## 📚 相关文档 - -- 📋 [release.md](./release.md) - 版本发布说明 -- 📖 [REFACTOR_COMPLETE.md](./REFACTOR_COMPLETE.md) - v2.0 重构完成报告 -- 🏗️ [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) - 架构设计文档 -- 📝 [refactor.md](./refactor.md) - 重构分析与优化方案 -- 📋 [CHANGELOG.md](./CHANGELOG.md) - 更新日志 -- 📚 [DEVELOPMENT_GUIDE.md](./DEVELOPMENT_GUIDE.md) - 开发指南 - ---- - -**MCP Client Vue v2.0.0 - 架构清晰、性能优异、易于维护的现代化 MCP 客户端** 🚀 -```` -````` +**MCP Client Vue v1.0.0 - 功能完善、稳定可靠的MCP协议客户端** 🚀 ```` \ No newline at end of file diff --git a/node_modules b/node_modules new file mode 120000 index 0000000..ceeb5ea --- /dev/null +++ b/node_modules @@ -0,0 +1 @@ +/Users/gavin/lib/node_modules \ No newline at end of file diff --git a/todolist.md b/todolist.md index d25a9a1..66d1fa0 100644 --- a/todolist.md +++ b/todolist.md @@ -27,10 +27,10 @@ sk-2546da09b6d9471894aeb95278f96c11 ✅ ## 优化 -该项目经过反复重构,重构过程关注功能实现,没有关注性能、结构合理性、和实现的优雅性。全量分析,提供优化点及思路。 +1. 该项目经过反复重构,重构过程关注功能实现,没有关注性能、结构合理性、和实现的优雅性。全量分析,提供优化点及思路。 **先优化,在做数据库改造**。 -### 重构进度 (2024-01-XX) +### 重构进度 (2025-10-16) #### Phase 1: 核心服务拆分 (Day 1-2) ✅ 已完成 - ✅ Step 1: 创建服务目录结构 `/web/src/services/chat/` @@ -82,3 +82,16 @@ Pinia store、localStorage、内存状态三处保存数据? 使用sqlite3 vs. better-sqlite3持久化?性能开销? 没有统一的数据源? +5. 当前实现,client参数重带图片path,server收到后按path发布图片。目前client/server部署在同一个服务器,测试没问题,因为server可以从path找到图片。 +但问题是:server部署如果部署在远程服务器上,用户是client需要使用mcp server发布文章,图片在client侧处理好,需要送到远程服务器上,否则server找不到图片。在多client用户使用mcp server下,进一步需要考虑几个问题: + - 图片通过什么方式传送到远程服务器? + - 用户publish content时,需要等待图片上传完成,等待时间根据网络状态,可能会很长? + 本来用户发布文章到xhs,本地之间上传图片到xhs,现在多了一个环节,图片上传mcp server,mcp server在上传图片到xhs。 + - 图片上传和发布文章能不能解耦,比如,用户先传送图片,缓存到mcp server。需要的时候,再发布文章。 + 但这样,用户操作会很繁琐。 + - 上传图片到mcp server,还有一个存放位置问题。client的path参数用什么?上传到哪个目录?发布时从哪个目录寻找? + 如果上传图片,最好约定一套策略,path中只要填文件名,mcp server的路径不需要client考虑。 + - 如果用上传图片方式,大量client接入的排队机制怎么处理?client采用异步方式递交,点击发送/发布,可以去喝茶了,不必考虑多久完成。 + - mcp server侧需要考虑的机制。 + + diff --git a/web/src/components/ModelService.vue b/web/src/components/ModelService.vue index 9bc994b..1cd41bd 100644 --- a/web/src/components/ModelService.vue +++ b/web/src/components/ModelService.vue @@ -242,7 +242,7 @@

当前进度: - {{ healthCheckResult.progress.current }} / {{ healthCheckResult.progress.total }} + {{ healthCheckResult.progress.current }}项 / {{ healthCheckResult.progress.total }}项

当前模型: {{ healthCheckResult.progress.modelId }}

@@ -250,8 +250,13 @@ type="line" :percentage="healthCheckResult.progress.total > 0 ? (healthCheckResult.progress.current / healthCheckResult.progress.total * 100) : 0" - :show-indicator="true" + :show-indicator="false" /> +
+ {{ healthCheckResult.progress.current }}项 + {{ healthCheckResult.progress.total }}项 + {{ healthCheckResult.progress.current > 0 ? Math.round((healthCheckResult.progress.current / healthCheckResult.progress.total) * 100) : 0 }}% +
@@ -262,7 +267,7 @@
-
{{ healthCheckResult.availableModels.length }}
+
{{ healthCheckResult.availableModels.length }}个
可用模型
@@ -270,7 +275,7 @@
-
{{ healthCheckResult.unavailableModels.length }}
+
{{ healthCheckResult.unavailableModels.length }}个
不可用模型
@@ -279,7 +284,7 @@
-

✓ 可用模型 ({{ healthCheckResult.availableModels.length }})

+

✓ 可用模型 ({{ healthCheckResult.availableModels.length }}个)

-

✗ 不可用模型 ({{ healthCheckResult.unavailableModels.length }})

+

✗ 不可用模型 ({{ healthCheckResult.unavailableModels.length }}个)

{ const result = await modelServiceManager.healthCheckAllModels( service, (current, total, modelId) => { + // 直接更新进度,不使用防抖 healthCheckResult.progress = { current, total, modelId } } ) @@ -1104,6 +1110,21 @@ onMounted(() => { font-size: 14px; } +.progress-details { + display: flex; + justify-content: space-between; + margin-top: 8px; + padding: 8px 12px; + background: #f0f0f0; + border-radius: 4px; + font-size: 14px; + color: #333; +} + +.progress-details span { + font-weight: 500; +} + .check-success h3 { margin: 16px 0; color: #18a058; diff --git a/web/src/services/modelServiceManager.ts b/web/src/services/modelServiceManager.ts index 2e74449..d5c4184 100644 --- a/web/src/services/modelServiceManager.ts +++ b/web/src/services/modelServiceManager.ts @@ -919,6 +919,124 @@ export class ModelServiceManager { } } + // 带自定义超时的聊天请求(用于健康检测) + private async makeChatRequestWithTimeout(service: ModelService, messages: any[], model: string, timeoutMs: number): Promise { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), timeoutMs) + + const headers: HeadersInit = { + 'Content-Type': 'application/json' + } + + let url = '' + let body: any = {} + + // 构建请求(与makeChatRequest相同的逻辑) + switch (service.type) { + case 'openai': + case 'local': + headers['Authorization'] = `Bearer ${service.apiKey}` + url = `${service.url}/chat/completions` + body = { + model, + messages, + stream: false + } + break + + case 'dashscope': + headers['Authorization'] = `Bearer ${service.apiKey}` + url = `${service.url}/chat/completions` + body = { + model, + messages, + stream: false, + parameters: {} + } + break + + case 'volcengine': + headers['Authorization'] = `Bearer ${service.apiKey}` + url = `${service.url}/chat/completions` + body = { + model, + messages, + stream: false + } + break + + case 'claude': + headers['x-api-key'] = service.apiKey + headers['anthropic-version'] = '2023-06-01' + url = `${service.url}/messages` + body = { + model, + messages: this.convertToClaudeFormat(messages), + max_tokens: 4096 + } + break + + case 'gemini': + url = `${service.url}/models/${model}:generateContent?key=${service.apiKey}` + body = { + contents: this.convertToGeminiFormat(messages) + } + break + + case 'azure': + headers['api-key'] = service.apiKey + url = `${service.url}/openai/deployments/${model}/chat/completions?api-version=2023-12-01-preview` + body = { + messages, + stream: false + } + break + + case 'custom': + try { + const config = JSON.parse(service.customConfig || '{}') + Object.assign(headers, config.headers || {}) + } catch (e) { + console.warn('自定义配置解析失败:', e) + } + url = `${service.url}/chat/completions` + body = { + model, + messages, + stream: false + } + break + + default: + throw new Error(`不支持的服务类型: ${service.type}`) + } + + try { + const response = await fetch(url, { + method: 'POST', + headers, + body: JSON.stringify(body), + signal: controller.signal + }) + + clearTimeout(timeoutId) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`HTTP ${response.status}: ${errorText}`) + } + + const result = await response.json() + return result + } catch (error) { + clearTimeout(timeoutId) + if (error instanceof Error && error.name === 'AbortError') { + throw new Error(`检测超时(${timeoutMs / 1000}秒)`) + } + throw error + } + } + // 健康检测 - 测试单个模型是否可用 async testModelHealth(service: ModelService, modelId: string): Promise<{ modelId: string @@ -929,14 +1047,10 @@ export class ModelServiceManager { const startTime = Date.now() try { - // 发送一个最小的测试请求 - const result = await this.sendChatRequest(service.id, [ + // 使用3秒超时进行健康检测(简化版) + await this.makeChatRequestWithTimeout(service, [ { role: 'user', content: 'hi' } - ], modelId) - - if (!result.success) { - throw new Error(result.error || '测试失败') - } + ], modelId, 3000) const latency = Date.now() - startTime return { @@ -945,9 +1059,11 @@ export class ModelServiceManager { latency } } catch (error) { + const latency = Date.now() - startTime return { modelId, available: false, + latency, error: error instanceof Error ? error.message : '测试失败' } } @@ -975,24 +1091,34 @@ export class ModelServiceManager { error?: string }> = [] + // 初始进度 + if (onProgress) { + onProgress(0, models.length, '准备开始检测...') + } + for (let i = 0; i < models.length; i++) { const modelId = models[i] - // 通知进度 + // 开始检测当前模型时通知进度 if (onProgress) { - onProgress(i + 1, models.length, modelId) + onProgress(i, models.length, `正在检测: ${modelId}`) } // 测试模型健康状态 const result = await this.testModelHealth(service, modelId) results.push(result) - // 添加小延迟避免过快请求 - if (i < models.length - 1) { - await new Promise(resolve => setTimeout(resolve, 200)) + // 检测完成后更新进度 + if (onProgress) { + onProgress(i + 1, models.length, `已完成: ${modelId}`) } } + // 最终进度 + if (onProgress) { + onProgress(models.length, models.length, '检测完成') + } + // 统计结果 const availableModels = results.filter(r => r.available).map(r => r.modelId) const unavailableModels = results.filter(r => !r.available).map(r => r.modelId)