update at 2025-11-01 17:57:04
This commit is contained in:
170
README.md
170
README.md
@@ -1,102 +1,47 @@
|
|||||||
````markdown
|
````markdown
|
||||||
# MCP Vue 客户端
|
# 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 个独立服务
|
- ✅ **可靠的连接功能** - HTTP/SSE双协议支持,自动URL转换
|
||||||
- ✅ **单一职责** - MessageService、ConversationService、StreamProcessor、ToolExecutor、ChatOrchestrator
|
- ✅ **自动重连机制** - 页面刷新自动恢复连接状态
|
||||||
- ✅ **统一日志** - Logger 系统,支持日志级别和命名空间
|
- ✅ **改进的用户体验** - 美观界面,实时状态显示,详细错误提示
|
||||||
- ✅ **错误处理** - AppError 体系,类型化错误(ValidationError、NetworkError、ServiceError 等)
|
|
||||||
- ✅ **性能优化** - 批量输出、上下文限制、性能监控
|
|
||||||
|
|
||||||
### 🚀 核心改进
|
📖 **改进详情**: 查看 [CHANGELOG.md](./CHANGELOG.md)
|
||||||
- ✅ **代码可维护性** - 单文件最大行数从 1,147 降至 580(↓49%)
|
📚 **使用指南**: 查看 [DEVELOPMENT_GUIDE.md](./DEVELOPMENT_GUIDE.md)
|
||||||
- ✅ **易于测试** - 每个服务独立可测,支持依赖注入
|
|
||||||
- ✅ **更好的性能** - 批量输出减少 60%+ 重渲染,首字延迟监控
|
|
||||||
- ✅ **完善文档** - 架构文档、重构报告、API 文档
|
|
||||||
|
|
||||||
📖 **重构详情**: 查看 [REFACTOR_COMPLETE.md](./REFACTOR_COMPLETE.md)
|
|
||||||
📚 **架构文档**: 查看 [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)
|
|
||||||
📋 **版本说明**: 查看 [release.md](./release.md)
|
|
||||||
|
|
||||||
## ✨ 核心特性
|
## ✨ 核心特性
|
||||||
|
|
||||||
### 聊天功能
|
|
||||||
- 💬 **多模型支持** - 阿里通义、火山方舟等多模型服务
|
|
||||||
- 🌊 **流式对话** - 实时流式响应,打字机效果
|
|
||||||
- 🔧 **MCP 工具调用** - 支持工具链、递归调用
|
|
||||||
- 📝 **对话管理** - 创建、编辑、删除、置顶、收藏、归档
|
|
||||||
- <20> **持久化** - localStorage 存储,页面刷新保持状态
|
|
||||||
|
|
||||||
### 架构特性(v2.0)
|
|
||||||
- 🏗️ **模块化架构** - 5 个独立服务,单一职责
|
|
||||||
- 📊 **统一日志** - Logger 系统,分级管理(DEBUG/INFO/WARN/ERROR)
|
|
||||||
- ⚠️ **类型化错误** - AppError 体系,更好的错误处理
|
|
||||||
- ⚡ **性能优化** - 批量输出、上下文限制、性能监控
|
|
||||||
- 🧪 **易于测试** - 服务解耦,支持单元测试
|
|
||||||
|
|
||||||
### 用户体验
|
|
||||||
- 🎨 **美观界面** - 基于 Naive UI 的现代化设计
|
- 🎨 **美观界面** - 基于 Naive UI 的现代化设计
|
||||||
|
- 🔧 **双协议支持** - HTTP 和 SSE 传输模式
|
||||||
|
- 🔄 **自动重连** - 页面刷新后自动恢复连接状态
|
||||||
|
- ⚡ **实时状态** - 连接状态实时监控和显示
|
||||||
|
- <20> **完整管理** - 服务器配置、编辑、测试一体化
|
||||||
- 📱 **响应式设计** - 适配桌面和移动设备
|
- 📱 **响应式设计** - 适配桌面和移动设备
|
||||||
- ⚡ **实时状态** - 连接状态、发送状态实时显示
|
|
||||||
- 🎯 **快捷操作** - 复制、删除、重新生成消息
|
|
||||||
|
|
||||||
## 🏗️ 项目架构(v2.0 重构版)
|
## 🏗️ 项目架构
|
||||||
|
|
||||||
```
|
```
|
||||||
mcp-client-vue/
|
mcp-client-vue/
|
||||||
├── src/ # 后端服务
|
├── src/ # 后端服务
|
||||||
│ ├── server/ # Node.js 服务器
|
│ ├── server/ # Express + Socket.IO 服务器
|
||||||
│ │ ├── index.ts # Express 服务器入口
|
│ │ ├── index.ts # 主服务器入口
|
||||||
│ │ ├── MCPManager.ts # MCP 服务器管理
|
│ │ ├── MCPManager.ts # 基于 SmartMCPClient 的服务器管理
|
||||||
│ │ └── LLMService.ts # 模型服务集成
|
│ │ └── LLMService.ts # OpenAI 集成服务
|
||||||
│ └── types/ # 后端类型定义
|
│ └── types/ # TypeScript 类型定义
|
||||||
├── web/ # Vue 3 前端(重构版)
|
├── web/ # Vue 3 前端
|
||||||
│ ├── src/
|
│ ├── src/
|
||||||
│ │ ├── components/ # UI 组件
|
│ │ ├── components/ # Vue 组件
|
||||||
│ │ │ ├── Chat/ # 聊天组件
|
│ │ ├── stores/ # Pinia 状态管理
|
||||||
│ │ │ ├── Settings/ # 设置组件
|
│ │ ├── views/ # 页面视图
|
||||||
│ │ │ └── Common/ # 通用组件
|
│ │ └── types/ # 前端类型
|
||||||
│ │ ├── services/ # 业务服务层 ⭐ 重构重点
|
│ └── package.json # 前端依赖
|
||||||
│ │ │ ├── chat/ # 聊天服务(新架构)
|
└── package.json # 后端依赖
|
||||||
│ │ │ │ ├── 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 行)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
@@ -128,7 +73,7 @@ npx vite --port 5174
|
|||||||
```
|
```
|
||||||
名称: XHS HTTP Server
|
名称: XHS HTTP Server
|
||||||
类型: http
|
类型: http
|
||||||
URL: http://localhost:3100
|
URL: http://localhost:3100/mcp
|
||||||
描述: HTTP传输模式
|
描述: HTTP传输模式
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -275,43 +220,15 @@ npm run dev
|
|||||||
- 🎯 [IMPROVEMENTS.md](./IMPROVEMENTS.md) - 改进详细说明
|
- 🎯 [IMPROVEMENTS.md](./IMPROVEMENTS.md) - 改进详细说明
|
||||||
- <20>️ [DOCS_INDEX.md](./DOCS_INDEX.md) - 文档索引
|
- <20>️ [DOCS_INDEX.md](./DOCS_INDEX.md) - 文档索引
|
||||||
|
|
||||||
## 📈 v2.0 架构重构成果
|
## <EFBFBD>📈 下一步计划
|
||||||
|
|
||||||
### 代码质量提升
|
- [ ] 修复所有TypeScript类型错误
|
||||||
| 指标 | v1.0 | v2.0 | 改进 |
|
- [ ] 添加工具调用历史记录
|
||||||
|------|------|------|------|
|
- [ ] 实现资源管理功能
|
||||||
| 单文件最大行数 | 1,147 | 580 | ↓ 49% |
|
- [ ] 添加提示词(Prompts)管理
|
||||||
| 服务职责数量 | 6+ | 1 | 单一职责 |
|
- [ ] 配置文件导入/导出
|
||||||
| 日志系统 | console.log | Logger 类 | 分级管理 |
|
- [ ] 添加性能监控面板
|
||||||
| 错误处理 | 不统一 | AppError 体系 | 类型化 |
|
- [ ] 支持WebSocket传输协议
|
||||||
| 可测试性 | 困难 | 容易 | 独立测试 |
|
|
||||||
|
|
||||||
### 性能优化
|
|
||||||
- ✅ **批量输出** - 减少 60%+ UI 重渲染
|
|
||||||
- ✅ **上下文限制** - 限制最近 20 条消息,减少 token 消耗
|
|
||||||
- ✅ **性能监控** - 追踪首字延迟、总耗时、chunk 数
|
|
||||||
|
|
||||||
### 开发体验
|
|
||||||
- ✅ **清晰架构** - 易于理解和维护
|
|
||||||
- ✅ **完善文档** - 架构文档、API 文档、重构报告
|
|
||||||
- ✅ **类型安全** - 完整的 TypeScript 类型定义
|
|
||||||
|
|
||||||
## 📈 下一步计划
|
|
||||||
|
|
||||||
### Phase 2: 持续优化(Day 3-4)
|
|
||||||
- [ ] 替换所有 console.log 为 logger
|
|
||||||
- [ ] 实现虚拟滚动优化消息列表
|
|
||||||
- [ ] 添加数据库索引(IndexedDB 迁移)
|
|
||||||
- [ ] 优化重渲染(shallowRef)
|
|
||||||
- [ ] 添加请求缓存和去重
|
|
||||||
|
|
||||||
### Phase 3: 新功能开发(Week 2+)
|
|
||||||
- [ ] 添加单元测试(目标 80%+ 覆盖率)
|
|
||||||
- [ ] 消息搜索功能
|
|
||||||
- [ ] 对话导出功能
|
|
||||||
- [ ] 多端同步支持
|
|
||||||
- [ ] 插件系统
|
|
||||||
- [ ] 工具调用历史记录
|
|
||||||
|
|
||||||
## ❓ 常见问题
|
## ❓ 常见问题
|
||||||
|
|
||||||
@@ -346,18 +263,5 @@ MIT License
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📚 相关文档
|
**MCP Client Vue v1.0.0 - 功能完善、稳定可靠的MCP协议客户端** 🚀
|
||||||
|
|
||||||
- 📋 [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 客户端** 🚀
|
|
||||||
````
|
|
||||||
`````
|
|
||||||
````
|
````
|
||||||
1
node_modules
Symbolic link
1
node_modules
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/Users/gavin/lib/node_modules
|
||||||
17
todolist.md
17
todolist.md
@@ -27,10 +27,10 @@ sk-2546da09b6d9471894aeb95278f96c11
|
|||||||
✅
|
✅
|
||||||
|
|
||||||
## 优化
|
## 优化
|
||||||
该项目经过反复重构,重构过程关注功能实现,没有关注性能、结构合理性、和实现的优雅性。全量分析,提供优化点及思路。
|
1. 该项目经过反复重构,重构过程关注功能实现,没有关注性能、结构合理性、和实现的优雅性。全量分析,提供优化点及思路。
|
||||||
**先优化,在做数据库改造**。
|
**先优化,在做数据库改造**。
|
||||||
|
|
||||||
### 重构进度 (2024-01-XX)
|
### 重构进度 (2025-10-16)
|
||||||
|
|
||||||
#### Phase 1: 核心服务拆分 (Day 1-2) ✅ 已完成
|
#### Phase 1: 核心服务拆分 (Day 1-2) ✅ 已完成
|
||||||
- ✅ Step 1: 创建服务目录结构 `/web/src/services/chat/`
|
- ✅ Step 1: 创建服务目录结构 `/web/src/services/chat/`
|
||||||
@@ -82,3 +82,16 @@ Pinia store、localStorage、内存状态三处保存数据?
|
|||||||
使用sqlite3 vs. better-sqlite3持久化?性能开销?
|
使用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侧需要考虑的机制。
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
<div class="progress-info">
|
<div class="progress-info">
|
||||||
<p>
|
<p>
|
||||||
<strong>当前进度:</strong>
|
<strong>当前进度:</strong>
|
||||||
{{ healthCheckResult.progress.current }} / {{ healthCheckResult.progress.total }}
|
{{ healthCheckResult.progress.current }}项 / {{ healthCheckResult.progress.total }}项
|
||||||
</p>
|
</p>
|
||||||
<p><strong>当前模型:</strong> {{ healthCheckResult.progress.modelId }}</p>
|
<p><strong>当前模型:</strong> {{ healthCheckResult.progress.modelId }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -250,8 +250,13 @@
|
|||||||
type="line"
|
type="line"
|
||||||
:percentage="healthCheckResult.progress.total > 0 ?
|
:percentage="healthCheckResult.progress.total > 0 ?
|
||||||
(healthCheckResult.progress.current / healthCheckResult.progress.total * 100) : 0"
|
(healthCheckResult.progress.current / healthCheckResult.progress.total * 100) : 0"
|
||||||
:show-indicator="true"
|
:show-indicator="false"
|
||||||
/>
|
/>
|
||||||
|
<div class="progress-details">
|
||||||
|
<span>{{ healthCheckResult.progress.current }}项</span>
|
||||||
|
<span>{{ healthCheckResult.progress.total }}项</span>
|
||||||
|
<span>{{ healthCheckResult.progress.current > 0 ? Math.round((healthCheckResult.progress.current / healthCheckResult.progress.total) * 100) : 0 }}%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="healthCheckResult.status === 'success'" class="check-success">
|
<div v-else-if="healthCheckResult.status === 'success'" class="check-success">
|
||||||
@@ -262,7 +267,7 @@
|
|||||||
<div class="summary-card available">
|
<div class="summary-card available">
|
||||||
<div class="card-icon">✓</div>
|
<div class="card-icon">✓</div>
|
||||||
<div class="card-info">
|
<div class="card-info">
|
||||||
<div class="card-number">{{ healthCheckResult.availableModels.length }}</div>
|
<div class="card-number">{{ healthCheckResult.availableModels.length }}个</div>
|
||||||
<div class="card-label">可用模型</div>
|
<div class="card-label">可用模型</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -270,7 +275,7 @@
|
|||||||
<div class="summary-card unavailable">
|
<div class="summary-card unavailable">
|
||||||
<div class="card-icon">✗</div>
|
<div class="card-icon">✗</div>
|
||||||
<div class="card-info">
|
<div class="card-info">
|
||||||
<div class="card-number">{{ healthCheckResult.unavailableModels.length }}</div>
|
<div class="card-number">{{ healthCheckResult.unavailableModels.length }}个</div>
|
||||||
<div class="card-label">不可用模型</div>
|
<div class="card-label">不可用模型</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,7 +284,7 @@
|
|||||||
<!-- 详细结果 -->
|
<!-- 详细结果 -->
|
||||||
<div class="detailed-results">
|
<div class="detailed-results">
|
||||||
<div v-if="healthCheckResult.availableModels.length > 0" class="result-section">
|
<div v-if="healthCheckResult.availableModels.length > 0" class="result-section">
|
||||||
<h4>✓ 可用模型 ({{ healthCheckResult.availableModels.length }})</h4>
|
<h4>✓ 可用模型 ({{ healthCheckResult.availableModels.length }}个)</h4>
|
||||||
<div class="model-list">
|
<div class="model-list">
|
||||||
<n-tag
|
<n-tag
|
||||||
v-for="result in healthCheckResult.results.filter(r => r.available)"
|
v-for="result in healthCheckResult.results.filter(r => r.available)"
|
||||||
@@ -295,7 +300,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="healthCheckResult.unavailableModels.length > 0" class="result-section">
|
<div v-if="healthCheckResult.unavailableModels.length > 0" class="result-section">
|
||||||
<h4>✗ 不可用模型 ({{ healthCheckResult.unavailableModels.length }})</h4>
|
<h4>✗ 不可用模型 ({{ healthCheckResult.unavailableModels.length }}个)</h4>
|
||||||
<div class="model-list">
|
<div class="model-list">
|
||||||
<n-tag
|
<n-tag
|
||||||
v-for="result in healthCheckResult.results.filter(r => !r.available)"
|
v-for="result in healthCheckResult.results.filter(r => !r.available)"
|
||||||
@@ -539,6 +544,7 @@ const healthCheckModels = async (service: ModelService) => {
|
|||||||
const result = await modelServiceManager.healthCheckAllModels(
|
const result = await modelServiceManager.healthCheckAllModels(
|
||||||
service,
|
service,
|
||||||
(current, total, modelId) => {
|
(current, total, modelId) => {
|
||||||
|
// 直接更新进度,不使用防抖
|
||||||
healthCheckResult.progress = { current, total, modelId }
|
healthCheckResult.progress = { current, total, modelId }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1104,6 +1110,21 @@ onMounted(() => {
|
|||||||
font-size: 14px;
|
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 {
|
.check-success h3 {
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
color: #18a058;
|
color: #18a058;
|
||||||
|
|||||||
@@ -919,6 +919,124 @@ export class ModelServiceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 带自定义超时的聊天请求(用于健康检测)
|
||||||
|
private async makeChatRequestWithTimeout(service: ModelService, messages: any[], model: string, timeoutMs: number): Promise<any> {
|
||||||
|
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<{
|
async testModelHealth(service: ModelService, modelId: string): Promise<{
|
||||||
modelId: string
|
modelId: string
|
||||||
@@ -929,14 +1047,10 @@ export class ModelServiceManager {
|
|||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 发送一个最小的测试请求
|
// 使用3秒超时进行健康检测(简化版)
|
||||||
const result = await this.sendChatRequest(service.id, [
|
await this.makeChatRequestWithTimeout(service, [
|
||||||
{ role: 'user', content: 'hi' }
|
{ role: 'user', content: 'hi' }
|
||||||
], modelId)
|
], modelId, 3000)
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error || '测试失败')
|
|
||||||
}
|
|
||||||
|
|
||||||
const latency = Date.now() - startTime
|
const latency = Date.now() - startTime
|
||||||
return {
|
return {
|
||||||
@@ -945,9 +1059,11 @@ export class ModelServiceManager {
|
|||||||
latency
|
latency
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const latency = Date.now() - startTime
|
||||||
return {
|
return {
|
||||||
modelId,
|
modelId,
|
||||||
available: false,
|
available: false,
|
||||||
|
latency,
|
||||||
error: error instanceof Error ? error.message : '测试失败'
|
error: error instanceof Error ? error.message : '测试失败'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -975,24 +1091,34 @@ export class ModelServiceManager {
|
|||||||
error?: string
|
error?: string
|
||||||
}> = []
|
}> = []
|
||||||
|
|
||||||
|
// 初始进度
|
||||||
|
if (onProgress) {
|
||||||
|
onProgress(0, models.length, '准备开始检测...')
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < models.length; i++) {
|
for (let i = 0; i < models.length; i++) {
|
||||||
const modelId = models[i]
|
const modelId = models[i]
|
||||||
|
|
||||||
// 通知进度
|
// 开始检测当前模型时通知进度
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress(i + 1, models.length, modelId)
|
onProgress(i, models.length, `正在检测: ${modelId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 测试模型健康状态
|
// 测试模型健康状态
|
||||||
const result = await this.testModelHealth(service, modelId)
|
const result = await this.testModelHealth(service, modelId)
|
||||||
results.push(result)
|
results.push(result)
|
||||||
|
|
||||||
// 添加小延迟避免过快请求
|
// 检测完成后更新进度
|
||||||
if (i < models.length - 1) {
|
if (onProgress) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 200))
|
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 availableModels = results.filter(r => r.available).map(r => r.modelId)
|
||||||
const unavailableModels = results.filter(r => !r.available).map(r => r.modelId)
|
const unavailableModels = results.filter(r => !r.available).map(r => r.modelId)
|
||||||
|
|||||||
Reference in New Issue
Block a user