Compare commits

..

70 Commits

Author SHA1 Message Date
Kirigaya Kazuto
e09c8beb7e
Merge pull request #43 from LSTM-Kirigaya/dev
Some checks are pending
Build / build (ubuntu-latest) (push) Waiting to run
Test / test (macos-latest) (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
Test / test (windows-latest) (push) Waiting to run
update yarn
2025-06-22 17:11:51 +08:00
9abc679c8f update yarn 2025-06-22 16:59:38 +08:00
li1553770945
f9fd4db580 fix:添加vsce作为项目依赖 2025-06-22 16:49:20 +08:00
Li Yaning
eab59e4ba1
Merge pull request #42 from LSTM-Kirigaya/dev
包管理器换成yarn
2025-06-22 16:32:04 +08:00
li1553770945
d3ae4c7999 Merge branch 'dev' of https://github.com/LSTM-Kirigaya/openmcp-client into dev 2025-06-22 16:26:35 +08:00
li1553770945
ed4d5e2ed1 Merge branch 'main' into dev 2025-06-22 16:25:37 +08:00
Li Yaning
216dabec6f
Merge pull request #41 from LSTM-Kirigaya/hotfix
包管理器换成yarn
2025-06-22 16:21:44 +08:00
li1553770945
279768e320 feat:包管理器改为yarn 2025-06-22 16:15:11 +08:00
li1553770945
ef811c1e8a fix:修复依赖问题 2025-06-22 15:32:27 +08:00
li1553770945
1702f93d16 feat:添加连接测试 2025-06-22 15:23:41 +08:00
li1553770945
c96d995f11 fix:修复依赖版本问题 2025-06-22 15:17:33 +08:00
li1553770945
15f0ff91e3 fix:添加缺失依赖 2025-06-22 14:55:32 +08:00
Kirigaya Kazuto
dd7685e42c
Merge pull request #40 from LSTM-Kirigaya/dev
Dev
2025-06-22 04:21:09 +08:00
ae56771c5a finish config export 2025-06-22 04:06:21 +08:00
10ffe098c2 finish config export 2025-06-22 04:03:29 +08:00
d10c88f35e finish config export 2025-06-22 04:00:14 +08:00
Kirigaya Kazuto
22a79cfc55
Merge pull request #39 from LSTM-Kirigaya/dev
Dev
2025-06-20 21:20:37 +08:00
3e363c6dde finish sdk 0.0.8 2025-06-20 21:20:18 +08:00
4a8385d7ad task loop 2025-06-20 21:06:01 +08:00
be641197b5 add prompt reader 2025-06-20 18:19:20 +08:00
7098382317 upgrade taskloop 2025-06-20 17:23:45 +08:00
51ceacbc10 upgrade taskloop 2025-06-20 17:04:26 +08:00
50510ae647 upgrade taskloop 2025-06-20 15:49:54 +08:00
f59e27b569 fix error 2025-06-20 14:35:07 +08:00
10befe12b4 save 2025-06-20 13:54:04 +08:00
7968a0d4c5 finish new agent framework 2025-06-20 02:42:29 +08:00
f51bd364b4 finish new agent framework 2025-06-20 02:36:53 +08:00
66f9b128f8 update 2025-06-19 23:56:08 +08:00
Kirigaya Kazuto
d04c04f574
Merge pull request #38 from LSTM-Kirigaya/dev
Dev
2025-06-19 15:20:34 +08:00
a5fe596cac task loop 2025-06-19 15:19:31 +08:00
e6d1f1205a task loop 2025-06-19 14:24:43 +08:00
Meghan Morrow
1b3418f15b 修复: 添加工作区验证以防止.openmcp目录创建错误
- 在getWorkspaceConnectionConfigPath()中添加工作区路径验证
- 在getWorkspaceConnectionConfig()中添加工作区路径验证
- 当没有找到工作区时抛出描述性错误
- 解决VSCode扩展尝试创建.openmcp目录时的ENOENT错误
2025-06-19 14:24:43 +08:00
Meghan Morrow
0961f18381 refactor: 更新task-loop注释,移除过时的XML工具调用说明
- 删除handleChunkDeltaContent函数注释中关于XML指令包裹的toolcall描述
- 该功能现在在toolcalled生命周期后处理,而非chunk生命周期中处理
2025-06-19 14:24:43 +08:00
9449bdb190 support xml 2025-06-19 14:24:43 +08:00
d4ac089a9a support xml 2025-06-19 14:24:43 +08:00
0df86cfc28 change tooltip color 2025-06-19 14:24:43 +08:00
c9a2d47c85 task loop 2025-06-19 14:24:24 +08:00
Li Yaning
51d4495f88
Merge pull request #37 from LSTM-Kirigaya/main
feat:增加测试样例
2025-06-19 11:01:02 +08:00
Meghan Morrow
8446cf07dc 修复: 添加工作区验证以防止.openmcp目录创建错误
- 在getWorkspaceConnectionConfigPath()中添加工作区路径验证
- 在getWorkspaceConnectionConfig()中添加工作区路径验证
- 当没有找到工作区时抛出描述性错误
- 解决VSCode扩展尝试创建.openmcp目录时的ENOENT错误
2025-06-19 10:55:06 +08:00
li1553770945
61a82061d7 feat:增加测试样例 2025-06-19 00:26:53 +08:00
Meghan Morrow
777a17bb99 refactor: 更新task-loop注释,移除过时的XML工具调用说明
- 删除handleChunkDeltaContent函数注释中关于XML指令包裹的toolcall描述
- 该功能现在在toolcalled生命周期后处理,而非chunk生命周期中处理
2025-06-18 22:17:35 +08:00
8355e0ec66 support xml 2025-06-18 21:47:02 +08:00
2c13ce5e6d support xml 2025-06-18 20:32:04 +08:00
c2a634118b change tooltip color 2025-06-18 17:04:33 +08:00
3700857aa8 update 2025-06-17 23:32:50 +08:00
94935e6b9c update 2025-06-17 23:29:23 +08:00
50990688be update 2025-06-17 23:12:37 +08:00
li1553770945
2a8ff295b2 feat:构建工具改为esbuild 2025-06-15 23:59:39 +08:00
Kirigaya Kazuto
3cae8572e8
Merge pull request #35 from LSTM-Kirigaya/claude
Claude
2025-06-15 20:53:47 +08:00
5bfda7a893 finish ocr adjust 2025-06-15 20:53:19 +08:00
d26201c893 finish ocr adjust 2025-06-15 19:55:39 +08:00
Meghan Morrow
dad8d41e46 feat: 工具模块界面优化 + OpenRouter集成
- 优化工具列表显示:工具名称优先显示,描述悬停提示
- 集成OpenRouter支持:400+模型一键访问
- 改进模型选择界面:支持搜索过滤和分类显示
- 自动同步新提供商配置
- 版本升级至0.1.7

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-15 19:27:54 +08:00
596e42e110 recover task-loop package 2025-06-15 19:18:56 +08:00
e22d344647 recover task-loop package 2025-06-15 18:55:54 +08:00
li1553770945
62ea963003 feat:更新github actions 2025-06-15 00:31:09 +08:00
Meghan Morrow
2af555dd3b feat: add MCP security checks to MVP requirements
Added MCP security checks (prevent prompt injection, etc.) to the MVP roadmap in README to enhance security features.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-15 00:25:05 +08:00
li1553770945
0560fda4bb feat:添加测试程序(未添加测试用例) 2025-06-15 00:18:17 +08:00
dcbcf567cc update 2025-06-14 00:13:46 +08:00
Li Yaning
7e203905de
Merge pull request #33 from ZYD045692/feature/mcp-hot-update-stdio
feat(service-mcp-connect): 实现stdio连接方式的热更新
2025-06-14 00:08:24 +08:00
li1553770945
52074d1ea2 feat:增加自动构建脚本 2025-06-14 00:02:53 +08:00
Li Yaning
66e748bf01
Merge pull request #34 from LSTM-Kirigaya/hotfix
构建工具改为rollup
2025-06-13 23:28:52 +08:00
li1553770945
836fc6d7b0 Merge branch 'main' of https://github.com/LSTM-Kirigaya/openmcp-client into hotfix 2025-06-13 23:28:24 +08:00
li1553770945
786f5ea615 feat:构建工具改为rollup 2025-06-13 23:07:27 +08:00
li1553770945
e7856dd8a6 fix 2025-06-13 21:27:37 +08:00
li1553770945
bd23bfe17c fix 2025-06-13 21:22:49 +08:00
li1553770945
dfb70785e0 bugfix:尝试修复路径问题 2025-06-13 21:12:49 +08:00
aa8547c4ad fix 0.1.5 cannot launch in windows 2025-06-13 21:08:44 +08:00
54826eda62 fix 0.1.5 cannot launch in windows 2025-06-13 18:11:17 +08:00
506d33be9a fix 0.1.5 cannot launch in windows 2025-06-13 18:05:58 +08:00
ZYD045692
17cc8f7612 feat(service-mcp-connect): 实现stdio连接方式的热更新 2025-06-13 17:40:26 +08:00
106 changed files with 18692 additions and 2970 deletions

45
.github/workflows/build.yaml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Build
on:
push:
branches:
- main
- dev
- hotfix
- hotfix/*
release:
types:
- published
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.x'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build all
run: yarn build
- name: Package VSIX
run: npx vsce package --out dist/openmcp.vsix
- name: Upload VSIX
uses: actions/upload-artifact@v4
with:
name: openmcp-${{ matrix.os }}
path: dist/openmcp.vsix

44
.github/workflows/test.yaml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Test
on:
push:
branches:
- main
- dev
- hotfix
- hotfix/*
release:
types:
- published
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.x'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Install xvfb (Linux only)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y xvfb
- name: Run Test with XVFB (Linux)
if: runner.os == 'Linux'
run: xvfb-run --auto-servernum yarn test
- name: Run Test (Windows/macOS)
if: runner.os != 'Linux'
run: yarn test

2
.gitignore vendored
View File

@ -16,3 +16,5 @@ resources/service
*.traineddata
.turbo
stats.html
.openmcp
test-vsix

View File

@ -24,3 +24,7 @@ software/**
.editorconfig
.gitattributes
*.vsix
.turbo
.github
webpack
.openmcp

View File

@ -1,5 +1,19 @@
# Change Log
## [main] 0.1.8
- 增加 STDIO 下的热更新,现在用户修改 mcp 代码openmcp 会自动完成一切相关功能的热更新,无需用户手动重启。
- 完成 mcpconfig.json 的导出功能,导出的 配置文件 可以通过 openmcp-sdk 框架完成低代码 agent 部署;也可以直接载入 Claude Desktop 等等 MCP 客户端中,实现 MCP 的快速部署和使用。
- 修复若干 vscode 插件端 bug
## [main] 0.1.7
- 新的构建系统
- 修复无法在 trae & cursor 中使用的 bug
## [main] 0.1.6
- 针对 0.1.5 无法在 Windows 启动的紧急修复。
- 修复环境变量中添加 token 失效的问题。
- 优化工具展示的页面布局。
## [main] 0.1.5
- 修复 gemini 获取模型列表时存在 models 前缀的问题
- 增加 web api 功能

169
CLAUDE.md Normal file
View File

@ -0,0 +1,169 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
### Setup and Installation
```bash
npm run setup # Install dependencies and prepare OCR resources
```
### Development
```bash
npm run serve # Start all services in development mode (uses Turbo)
npm run build # Build all modules
npm run build:all # Build all modules (alias)
npm run build:electron # Build only Electron app
```
### Service Development
```bash
cd service
npm run serve # Start service with hot reload (nodemon + tsx)
npm run build # Compile TypeScript to dist/
npm run debug # Start with Node.js inspector
npm run typecheck # Type checking without emit
```
### Renderer Development
```bash
cd renderer
npm run serve # Start Vite dev server
npm run build # Build for production
npm run serve:website # Start in website mode
npm run type-check # Vue TypeScript checking
```
### VSCode Extension
```bash
npm run vscode:prepublish # Prepare for VSCode publishing (Rollup build)
npm run compile # Compile TypeScript
npm run watch # Watch mode compilation
vsce package # Create VSIX package for distribution
vsce publish # Publish to VSCode Marketplace (requires auth)
```
### Quality Assurance
```bash
npm run lint # ESLint for TypeScript files
npm run pretest # Run compile and lint
npm run test # Run tests
```
## Architecture Overview
### Multi-Module Structure
OpenMCP follows a **layered modular architecture** with three main deployment targets:
1. **VSCode Extension** (`src/extension.ts`) - IDE integration
2. **Service Layer** (`service/`) - Node.js backend handling MCP protocol
3. **Renderer Layer** (`renderer/`) - Vue.js frontend for UI
### Key Architectural Patterns
#### Message Bridge Communication
The system uses a **message bridge pattern** for cross-platform communication:
- **VSCode**: Uses `vscode.postMessage` API
- **Electron**: Uses IPC communication
- **Web**: Uses WebSocket connections
- **Node.js**: Uses EventEmitter for SDK mode
All communication flows through `MessageBridge` class in `renderer/src/api/message-bridge.ts`.
#### MCP Client Management
- **Connection Management**: `service/src/mcp/connect.service.ts` handles multiple MCP server connections
- **Client Pooling**: `clientMap` maintains active MCP client instances with UUID-based identification
- **Transport Abstraction**: Supports STDIO, SSE, and StreamableHTTP transports
- **Auto-reconnection**: `McpServerConnectMonitor` handles connection monitoring
#### Request/Response Flow
```
Frontend (Vue) → MessageBridge → Service Router → MCP Controllers → MCP SDK → External MCP Servers
```
### Important Service Patterns
#### Preprocessing Commands
`service/src/mcp/connect.service.ts` includes **automatic environment setup**:
- Python projects: Auto-runs `uv sync` and installs MCP CLI
- Node.js projects: Auto-runs `npm install` if node_modules missing
- Path resolution: Handles `~/` home directory expansion
#### OCR Integration
Built-in OCR using Tesseract.js:
- Images from MCP responses are automatically processed
- Base64 images saved to temp files and queued for OCR
- Results delivered via worker threads
### Frontend Architecture (Vue 3)
#### State Management
- **Panel System**: Tab-based interface in `renderer/src/components/main-panel/`
- **Reactive Connections**: MCP connection state managed reactively
- **Multi-language**: Vue i18n with support for 9 languages
#### Core Components
- **Chat Interface**: `main-panel/chat/` - LLM interaction with MCP tools
- **Tool Testing**: `main-panel/tool/` - Direct MCP tool invocation
- **Resource Browser**: `main-panel/resource/` - MCP resource exploration
- **Prompt Manager**: `main-panel/prompt/` - System prompt templates
### Build System
#### Turbo Monorepo
Uses Turbo for coordinated builds across modules:
- **Dependency ordering**: Renderer builds before Electron
- **Parallel execution**: Service and Renderer can build concurrently
- **Task caching**: Disabled for development iterations
#### Rollup Configuration
VSCode extension uses Rollup for optimal bundling:
- **ES modules**: Output as ESM format
- **External dependencies**: VSCode API marked as external
- **TypeScript**: Direct compilation without webpack
## Development Workflow
### Adding New MCP Features
1. **Service Layer**: Add controller in `service/src/mcp/`
2. **Router Registration**: Add to `ModuleControllers` in `service/src/common/router.ts`
3. **Frontend Integration**: Add API calls in `renderer/src/api/`
4. **UI Components**: Create components in `renderer/src/components/`
### Testing MCP Servers
1. **Connection**: Configure in connection panel (STDIO/SSE/HTTP)
2. **Validation**: Test tools/resources in respective panels
3. **Integration**: Verify LLM interaction in chat interface
### Packaging VSCode Extension
1. **Build Dependencies**: Run `npm run build` to build all modules
2. **Prepare Extension**: Run `npm run vscode:prepublish` to bundle extension code
3. **Create Package**: Run `vsce package` to generate `.vsix` file
4. **Install Locally**: Use `code --install-extension openmcp-x.x.x.vsix` for testing
5. **Publish**: Run `vsce publish` (requires marketplace publisher account)
### Platform-Specific Considerations
- **VSCode**: Uses webview API, limited to extension context
- **Electron**: Full desktop app capabilities, local service spawning
- **Web**: Requires external service, WebSocket limitations
- **SDK**: Embedded in other Node.js applications
## Important Files
### Configuration
- `turbo.json` - Monorepo build orchestration
- `rollup.config.js` - VSCode extension bundling
- `service/package.json` - Backend dependencies and scripts
- `renderer/package.json` - Frontend dependencies and scripts
### Core Architecture
- `src/extension.ts` - VSCode extension entry point
- `service/src/main.ts` - Service WebSocket server
- `service/src/common/router.ts` - Request routing system
- `renderer/src/api/message-bridge.ts` - Cross-platform communication
- `service/src/mcp/client.service.ts` - MCP client implementation

View File

@ -60,6 +60,7 @@ Supports multiple large models
| `service` | Cloud sync for system configuration | `MVP` | 0% | `P1` |
| `all` | System prompt management module | `Iteration` | 100% | `Done` |
| `service` | Tool-wise logging system | `MVP` | 0% | `P1` |
| `service` | MCP security checks (prevent prompt injection, etc.) | `MVP` | 0% | `P1` |
| `service` | Built-in OCR for character recognition | `Iteration` | 100% | `Done` |
## Project Concept
@ -134,9 +135,8 @@ npm run setup
Start dev server:
```bash
npm run dev
npm run serve
```
Port usage: 8282 (renderer) + 8081 (service)
### Extension Development
@ -151,4 +151,21 @@ Build for deployment:
```bash
npm run build
```
build vscode extension:
```bash
npm run build:plugin
```
Then just press F5, いただきます (Let's begin)
---
## CI Pipeline
✅ npm run build
✅ npm run build:task-loop
✅ openmcp-client UT
✅ openmcp-sdk UT
✅ vscode extension UT

20
esbuild.config.js Normal file
View File

@ -0,0 +1,20 @@
// esbuild.config.js
const { build } = require('esbuild');
build({
entryPoints: ['src/extension.ts'],
bundle: true,
platform: 'node',
format: 'cjs',
outfile: 'dist/extension.cjs.js',
sourcemap: true,
external: ['vscode'],
target: ['node22'],
loader: {
'.json': 'json'
},
define: { 'import.meta.url': '_importMetaUrl' },
banner: {
js: "const _importMetaUrl=require('url').pathToFileURL(__filename)",
},
}).catch(() => process.exit(1));

7463
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,9 @@
"name": "openmcp",
"displayName": "OpenMCP",
"description": "An all in one MCP Client/TestTool",
"version": "0.1.5",
"version": "0.1.8",
"publisher": "kirigaya",
"private": true,
"author": {
"name": "kirigaya",
"email": "1193466151@qq.com"
@ -19,7 +20,7 @@
"Other"
],
"activationEvents": [],
"main": "./dist/extension.js",
"main": "./dist/extension.cjs.js",
"icon": "icons/openmcp.png",
"contributes": {
"configuration": {
@ -220,42 +221,64 @@
"renderer"
],
"scripts": {
"setup": "npm i && npm run prepare:ocr",
"setup": "yarn install && yarn prepare:ocr",
"serve": "turbo serve",
"build": "turbo build",
"build:electron": "turbo build --filter=@openmcp/electron",
"build:all": "turbo build",
"vscode:prepublish": "webpack --mode production",
"build": "turbo build && tsc -p ./ && node esbuild.config.js",
"build:plugin": "yarn build && tsc && vsce package",
"vscode:prepublish": "node esbuild.config.js",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"pretest": "yarn build",
"lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js",
"prepare:ocr": "webpack --config webpack/webpack.tesseract.js",
"build:task-loop": "npx vite build --config webpack/vite.config.task-loop.mjs && node renderer/scripts/task-loop.build.mjs"
"test": "node ./dist/test/e2e/runTest.js",
"prepare:ocr": "rollup -c rollup.tesseract.js --bundleConfigAsCjs",
"build:task-loop": "npx vite build --config renderer/vite.config.task-loop.mjs && node renderer/scripts/task-loop.build.mjs"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.1",
"@seald-io/nedb": "^4.1.1",
"@types/glob": "^7.2.0",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"axios": "^1.9.0",
"bson": "^6.8.0",
"form-data-encoder": "^1.7.2",
"formdata-node": "^4.3.2",
"glob": "^7.2.3",
"https-proxy-agent": "^7.0.6",
"mocha": "^10.8.2",
"openai": "^5.0.1",
"pako": "^2.1.0",
"pkce-challenge": "^5.0.0",
"tesseract.js": "^6.0.1",
"tslib": "^2.8.1",
"uuid": "^11.1.0",
"ws": "^8.18.1"
},
"devDependencies": {
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.5",
"@rollup/plugin-inject": "^5.0.5",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"@types/node": "^22.15.29",
"@types/pako": "^2.0.3",
"@types/showdown": "^2.0.0",
"@types/sinon": "^17.0.4",
"@types/vscode": "^1.72.0",
"@vscode/test-cli": "^0.0.11",
"@vscode/test-electron": "^2.5.2",
"@vscode/vsce": "^3.5.0",
"copy-webpack-plugin": "^13.0.0",
"esbuild": "^0.25.5",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"null-loader": "^4.0.1",
"rollup": "^4.43.0",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-visualizer": "^6.0.1",
"sinon": "^21.0.0",
"ts-loader": "^9.5.1",
"turbo": "^2.5.3",
"typescript": "^5.4.2",
@ -267,5 +290,5 @@
"webpack": "^5.99.5",
"webpack-cli": "^5.1.4"
},
"packageManager": "npm@10.0.0"
"packageManager": "yarn@1.22.22"
}

View File

@ -33,7 +33,8 @@
"uuid": "^11.1.0",
"vue": "^3.5.13",
"vue-i18n": "^11.1.0",
"vue-router": "^4.5.0"
"vue-router": "^4.5.0",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@babel/core": "^7.27.1",
@ -47,6 +48,7 @@
"@types/markdown-it": "^14.1.2",
"@types/node": "^22.14.0",
"@types/prismjs": "^1.26.5",
"@types/xml2js": "^0.4.14",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/babel-plugin-jsx": "^1.4.0",
"@vue/devtools-core": "^7.7.6",
@ -60,5 +62,7 @@
"vite": "^6.2.4",
"vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.8"
}
},
"main": "index.js",
"license": "MIT"
}

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4870215 */
src: url('iconfont.woff2?t=1749572305505') format('woff2'),
url('iconfont.woff?t=1749572305505') format('woff'),
url('iconfont.ttf?t=1749572305505') format('truetype');
src: url('iconfont.woff2?t=1750532923458') format('woff2'),
url('iconfont.woff?t=1750532923458') format('woff'),
url('iconfont.ttf?t=1750532923458') format('truetype');
}
.iconfont {
@ -13,6 +13,18 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-deploy:before {
content: "\e614";
}
.icon-suffix-xml:before {
content: "\e653";
}
.icon-MCP:before {
content: "\e63c";
}
.icon-wendang:before {
content: "\e61b";
}

Binary file not shown.

View File

@ -0,0 +1,3 @@
# OpenRouter Icon Placeholder
# This would normally be an actual .ico file
# For now, using a placeholder that follows the naming convention

View File

@ -228,3 +228,4 @@ a {
.ͼo .cm-gutters {
background-color: transparent !important;
}

View File

@ -3,13 +3,13 @@ import * as fs from 'node:fs';
const targetFile = './openmcp-sdk/task-loop.js';
if (fs.existsSync(targetFile)) {
let content = fs.readFileSync(targetFile, 'utf8');
let content = fs.readFileSync(targetFile, 'utf-8');
// Replace element-plus with ./tools.js
content = content.replace(/'element-plus'/g, "'./tools.js'");
content = content.replace(/"element-plus"/g, "\"./tools.js\"");
content = content.replace(/'element-plus'/g, "'./tools.mjs'");
content = content.replace(/"element-plus"/g, "\"./tools.mjs\"");
content = content.replace(/const chalk = require\("chalk"\);/g, 'const chalk = require("chalk").default;');
// content = content.replace(/const chalk = require\("chalk"\);/g, 'const chalk = require("chalk").default;');
// Replace define_window_default$number.performance with performance
content = content.replace(/define_window_default\$\d+\.performance/g, 'performance');

View File

@ -39,22 +39,22 @@ export class MessageBridge {
switch (platform) {
case 'vscode':
this.setupVsCodeListener();
pinkLog('当前模式: vscode');
pinkLog('current platform: vscode');
break;
case 'electron':
this.setupElectronListener();
pinkLog('当前模式: electron');
pinkLog('current platform: electron');
break;
case 'nodejs':
this.setupNodejsListener();
pinkLog('当前模式: nodejs');
pinkLog('current platform: nodejs');
break;
case 'web':
this.setupWebSocket();
pinkLog('当前模式: web');
pinkLog('current platform: web');
break;
}
}

View File

@ -1,5 +1,5 @@
import type { ToolCallContent, ToolItem } from "@/hook/type";
import { type Ref, ref } from "vue";
import type { InputSchema, ToolCallContent, ToolItem } from "@/hook/type";
import type { Ref } from "vue";
import type { OpenAI } from 'openai';
type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
@ -16,6 +16,7 @@ export enum MessageState {
Success = 'success',
ParseJsonError = 'parse json error',
NoToolFunction = 'no tool function',
InvalidXml = 'invalid xml',
}
export interface IExtraInfo {
@ -23,6 +24,7 @@ export interface IExtraInfo {
state: MessageState,
serverName: string,
usage?: ChatCompletionChunk['usage'];
enableXmlWrapper: boolean;
[key: string]: any;
}
@ -52,7 +54,7 @@ export interface EnableToolItem {
name: string;
description: string;
enabled: boolean;
inputSchema: any;
inputSchema: InputSchema;
}
export interface ChatSetting {
@ -63,6 +65,7 @@ export interface ChatSetting {
enableWebSearch: boolean
contextLength: number
parallelToolCalls: boolean
enableXmlWrapper: boolean
}
export interface ChatStorage {
@ -92,7 +95,7 @@ export type RichTextItem = PromptTextItem | ResourceTextItem | TextItem;
export interface ICommonRenderMessage {
role: 'user' | 'assistant/content';
content: string;
showJson?: Ref<boolean>;
showJson?: any;
extraInfo: IExtraInfo;
}
@ -101,13 +104,13 @@ export interface IToolRenderMessage {
content: string;
toolResults: ToolCallContent[][];
tool_calls: ToolCall[];
showJson?: Ref<boolean>;
showJson?: any;
extraInfo: IExtraInfo;
}
export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage;
export function getToolSchema(enableTools: EnableToolItem[]) {
export function getToolSchema(enableTools: EnableToolItem[]): any[] {
const toolsSchema = [];
for (let i = 0; i < enableTools.length; i++) {
const enableTool = enableTools[i];

View File

@ -101,8 +101,6 @@ function handleSend(newMessage?: string) {
loop.bindStreaming(streamingContent, streamingToolCalls);
loop.registerOnError((error) => {
console.log('error.msg');
console.log(error.msg);
const errorMessage = clearErrorMessage(error.msg);
ElMessage.error(errorMessage);
@ -114,7 +112,8 @@ function handleSend(newMessage?: string) {
extraInfo: {
created: Date.now(),
state: error.state,
serverName: llms[llmManager.currentModelIndex].id || 'unknown'
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
enableXmlWrapper: false
}
});
}

View File

@ -1,6 +1,6 @@
<template>
<el-tooltip :content="t('context-length')" placement="top">
<div class="setting-button" @click="showContextLengthDialog = true">
<el-tooltip :content="t('context-length')" placement="top" effect="light">
<div class="setting-button width-30" @click="showContextLengthDialog = true">
<span class="iconfont icon-length"></span>
<span class="value-badge">{{ tabStorage.settings.contextLength }}</span>
</div>
@ -10,7 +10,7 @@
<el-dialog v-model="showContextLengthDialog" :title="t('context-length') + ' ' + tabStorage.settings.contextLength"
width="400px">
<div class="slider-container">
<el-slider v-model="tabStorage.settings.contextLength" :min="1" :max="99" :step="1" />
<el-slider v-model="tabStorage.settings.contextLength" :min="1" :max="500" :step="1" />
<div class="slider-tips">
<span> 1: {{ t('single-dialog') }}</span>
<span> >1: {{ t('multi-dialog') }}</span>
@ -39,4 +39,8 @@ const showContextLengthDialog = ref(false);
.icon-length {
font-size: 16px;
}
.width-30 {
width: 30px;
}
</style>

View File

@ -0,0 +1,208 @@
<template>
<el-tooltip :content="'导出 mcpconfig'" placement="top" effect="light">
<div class="setting-button" @click="toggleDialog">
<span class="iconfont icon-deploy">
</span>
</div>
</el-tooltip>
<el-dialog v-model="showDialog" width="fit-content">
<template #header>
<div>
<div class="export-file-input">
<span>{{ t('export-filename') }}</span>
<el-input
v-model="exportFileName"
style="max-width: 300px"
>
<template #append>.json</template>
</el-input>
<span class="how-to-use"
@click="gotoHowtoUse"
>
<span class="iconfont icon-info"></span>
<span>{{ t('how-to-use') }}</span>
</span>
</div>
</div>
</template>
<div class="tools-dialog-container">
<el-scrollbar height="400px" class="tools-list">
<div v-html="exportJson">
</div>
</el-scrollbar>
<!--
<el-scrollbar height="400px" class="tools-list">
<div v-html="exampleCode"></div>
</el-scrollbar> -->
</div>
<template #footer>
<el-button type="primary" @click="copyCode">{{ t('copy') }}</el-button>
<el-button type="primary" @click="exportCode">{{ t('export') }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, inject, onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { type ChatStorage, type EnableToolItem, getToolSchema } from '../chat';
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
import { llmManager, llms } from '@/views/setting/llm';
import { mcpClientAdapter } from '@/views/connect/core';
import { ElMessage } from 'element-plus';
import { useMessageBridge } from '@/api/message-bridge';
const { t, locale } = useI18n();
const showDialog = ref(false);
const exportJson = ref('');
const exportFileName = ref('mcpconfig');
// toggleDialog
const toggleDialog = () => {
showDialog.value = true;
};
const generateExportData = computed(() => {
const currentLLM = llms[llmManager.currentModelIndex];
const mcpServers = {} as any;
for (const client of mcpClientAdapter.clients) {
const option = client.connectOption;
const type = option.connectionType;
if (type === 'STDIO') {
mcpServers[client.name] = {
command: option.command,
args: option.args,
description: "",
}
} else if (type === 'SSE') {
mcpServers[client.name] = {
type: 'sse',
url: option.url,
description: "",
}
} else {
mcpServers[client.name] = {
type: 'streamable_http',
url: option.url,
description: "",
}
}
}
const mcpconfig = {
version: "0.0.1",
namespace: "openmcp",
mcpServers,
defaultLLM: {
baseURL: currentLLM.baseUrl,
apiToken: currentLLM.userToken,
model: currentLLM.userModel
}
};
return JSON.stringify(mcpconfig, null, 2);
});
const innerMarkdownHtml = (code: string) => {
const rawCode = markdownToHtml(code);
const doc = new DOMParser().parseFromString(rawCode, 'text/html');
const pres = doc.querySelectorAll('pre');
if (pres.length < 2) {
return '';
}
const inner = pres[1].outerHTML;
return inner;
}
const exampleCode = computed(() => {
return innerMarkdownHtml(
'```typescript\n' +
`import { OmAgent } from 'openmcp-sdk/service/sdk';
const agent = new OmAgent();
agent.loadMcpConfig('/path/to/${exportFileName.value}.json');
const prompt = await agent.getPrompt('hacknews', { topn: '5' });
const res = await agent.ainvoke({ messages: prompt });
console.log('⚙️ Agent Response', res);
` +
'\n```'
);
});
const copyCode = async () => {
try {
await navigator.clipboard.writeText(generateExportData.value);
ElMessage.success(t('copy-success'));
} catch (error) {
ElMessage.error(t('copy-fail'));
}
}
const exportCode = async () => {
const bridge = useMessageBridge();
bridge.postMessage({
command: 'vscode/export-file',
data: {
filename: exportFileName.value,
content: generateExportData.value
}
})
}
const gotoHowtoUse = () => {
if (locale.value === 'zh') {
window.open('https://kirigaya.cn/openmcp/zh/sdk-tutorial/#%E4%BD%BF%E7%94%A8');
} else if (locale.value === 'ja') {
window.open('https://kirigaya.cn/openmcp/ja/sdk-tutorial/#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95');
} else {
window.open('https://kirigaya.cn/openmcp/sdk-tutorial/#usage');
}
}
onMounted(async () => {
exportJson.value = innerMarkdownHtml(
'```json\n' + generateExportData.value + '\n```'
);
});
</script>
<style scoped>
.export-file-input {
display: flex;
gap: 10px;
align-items: center;
}
.tools-list {
border-radius: .5em;
border: 1px solid var(--main-color);
padding: 5px;
}
.how-to-use {
margin-left: 10px;
font-size: 15px;
cursor: pointer;
background-color: var(--main-color);
color: #1e1e1e;
padding: 3px 5px;
border-radius: .3em;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<el-tooltip :content="t('choose-model')" placement="top">
<el-tooltip :content="t('choose-model')" placement="top" effect="light">
<div class="setting-button" @click="showModelDialog = true">
<span class="iconfont icon-model">
{{ currentServerName }}/{{ currentModelName }}

View File

@ -1,5 +1,5 @@
<template>
<el-tooltip :content="t('parallel-tool-calls')" placement="top">
<el-tooltip :content="t('parallel-tool-calls')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': tabStorage.settings.parallelToolCalls }" size="small"
@click="toggle">
<span class="iconfont icon-parallel"></span>

View File

@ -1,5 +1,5 @@
<template>
<el-tooltip :content="t('prompts')" placement="top">
<el-tooltip :content="t('prompts')" placement="top" effect="light">
<div class="setting-button" @click="showChoosePrompt = true; saveCursorPosition();">
<span class="iconfont icon-chat"></span>
</div>

View File

@ -1,5 +1,5 @@
<template>
<el-tooltip :content="t('resources')" placement="top">
<el-tooltip :content="t('resources')" placement="top" effect="light">
<div class="setting-button" @click="showChooseResource = true; saveCursorPosition();">
<span class="iconfont icon-file"></span>
</div>

View File

@ -8,6 +8,8 @@
<ParallelToolCalls />
<Temperature />
<ContextLength />
<XmlWrapper />
<Export />
</div>
</template>
@ -25,6 +27,8 @@ import Resource from './resource.vue';
import ParallelToolCalls from './parallel-tool-calls.vue';
import Temperature from './temperature.vue';
import ContextLength from './context-length.vue';
import XmlWrapper from './xml-wrapper.vue';
import Export from './export.vue';
const props = defineProps({
modelValue: {
@ -58,8 +62,9 @@ if (!tabStorage.settings) {
enableTools: [],
enableWebSearch: false,
temperature: 0.6,
contextLength: 20,
contextLength: 100,
systemPrompt: '',
enableXmlWrapper: false,
parallelToolCalls: true
} as ChatSetting;
}

View File

@ -1,5 +1,5 @@
<template>
<el-tooltip :content="t('system-prompt')" placement="top">
<el-tooltip :content="t('system-prompt')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': hasSystemPrompt }" size="small"
@click="showSystemPromptDialog = true">
<span class="iconfont icon-prompt"></span>

View File

@ -1,6 +1,6 @@
<template>
<el-tooltip :content="t('temperature-parameter')" placement="top">
<div class="setting-button" @click="showTemperatureSlider = true">
<el-tooltip :content="t('temperature-parameter')" placement="top" effect="light">
<div class="setting-button width-30" @click="showTemperatureSlider = true">
<span class="iconfont icon-temperature"></span>
<span class="value-badge">{{ tabStorage.settings.temperature.toFixed(1) }}</span>
</div>
@ -34,9 +34,13 @@ const showTemperatureSlider = ref(false);
</script>
<style>
<style scoped>
.icon-temperature {
font-size: 18px;
font-size: 16px;
}
.width-30 {
width: 30px;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<el-tooltip :content="t('tool-use')" placement="top">
<el-tooltip :content="t('tool-use')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': availableToolsNum > 0 }" size="small" @click="toggleTools">
<span class="iconfont icon-tool badge-outer">
<span class="badge-inner">
@ -10,7 +10,19 @@
</el-tooltip>
<el-dialog v-model="showToolsDialog" title="工具管理" width="800px">
<el-dialog v-model="showToolsDialog" width="800px">
<template #header>
<div>
<span>{{ t('tool-manage') }}</span>
<el-tooltip :content="t('enable-xml-wrapper')" placement="top" effect="light">
<span class="xml-tag" :class="{
'active': tabStorage.settings.enableXmlWrapper
}" @click="tabStorage.settings.enableXmlWrapper = !tabStorage.settings.enableXmlWrapper">xml</span>
</el-tooltip>
</div>
</template>
<div class="tools-dialog-container">
<el-scrollbar height="400px" class="tools-list">
<div v-for="(tool, index) in tabStorage.settings.enableTools" :key="index" class="tool-item">
@ -23,23 +35,27 @@
</el-scrollbar>
<el-scrollbar height="400px" class="schema-viewer">
<div v-html="activeToolsSchemaHTML"></div>
<!-- 如果激活 xml 指令包裹则展示对应的 prompt -->
<div v-if="tabStorage.settings.enableXmlWrapper" v-html="activeToolsXmlPrompt" />
<!-- 如果是普通模式则展示普通的工具列表 -->
<div v-else v-html="activeToolsSchemaHTML" />
</el-scrollbar>
</div>
<template #footer>
<el-button type="primary" @click="enableAllTools">激活所有工具</el-button>
<el-button type="danger" @click="disableAllTools">禁用所有工具</el-button>
<el-button type="primary" @click="enableAllTools">{{ t('enable-all-tools') }}</el-button>
<el-button type="danger" @click="disableAllTools">{{ t('disable-all-tools') }}</el-button>
<el-button type="primary" @click="showToolsDialog = false">{{ t("cancel") }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, inject, onMounted } from 'vue';
import { ref, computed, inject, onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { type ChatStorage, type EnableToolItem, getToolSchema } from '../chat';
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
import { mcpClientAdapter } from '@/views/connect/core';
import { toolSchemaToPromptDescription } from '../../core/xml-wrapper';
const { t } = useI18n();
@ -56,6 +72,7 @@ const toggleTools = () => {
showToolsDialog.value = true;
};
const activeToolsSchemaHTML = computed(() => {
const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
const jsonString = JSON.stringify(toolsSchema, null, 2);
@ -65,6 +82,13 @@ const activeToolsSchemaHTML = computed(() => {
);
});
const activeToolsXmlPrompt = computed(() => {
const prompt = toolSchemaToPromptDescription(tabStorage.settings.enableTools);
return markdownToHtml(
"```markdown\n" + prompt + "\n```"
);
});
// -
const enableAllTools = () => {
tabStorage.settings.enableTools.forEach(tool => {
@ -79,7 +103,8 @@ const disableAllTools = () => {
});
};
onMounted(async () => {
//
const updateToolsList = async () => {
// tool tabStorage.settings.enableTools
// enable
const disableToolNames = new Set<string>(
@ -91,7 +116,8 @@ onMounted(async () => {
const newTools: EnableToolItem[] = [];
for (const client of mcpClientAdapter.clients) {
const tools = await client.getTools();
const tools = await client.getTools({ cache: false });
if (tools) {
for (const tool of tools.values()) {
const enabled = !disableToolNames.has(tool.name);
@ -103,10 +129,38 @@ onMounted(async () => {
});
}
}
}
tabStorage.settings.enableTools = newTools;
}
onMounted(async () => {
await updateToolsList();
watch(() => mcpClientAdapter.refreshSignal.value, async () => {
await updateToolsList();
});
});
</script>
<style></style>
<style scoped>
.xml-tag {
margin-left: 10px;
border-radius: .5em;
padding: 2px 5px;
font-size: 12px;
font-weight: 900;
color: black;
background-color: var(--main-color);
opacity: 0.3;
transition: var(--animation-3s);
cursor: pointer;
}
.xml-tag.active {
opacity: 1;
transition: var(--animation-3s);
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<el-tooltip :content="t('websearch')" placement="top">
<el-tooltip :content="t('websearch')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableWebSearch }" size="small"
@click="toggleWebSearch">
<span class="iconfont icon-web"></span>

View File

@ -0,0 +1,25 @@
<template>
<el-tooltip :content="t('enable-xml-wrapper')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableXmlWrapper }" size="small"
@click="toggle">
<span class="iconfont icon-suffix-xml"></span>
</div>
</el-tooltip>
</template>
<script setup lang="ts">
import { defineComponent, inject } from 'vue';
import type { ChatStorage } from '../chat';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const tabStorage = inject('tabStorage') as ChatStorage;
const toggle = () => {
tabStorage.settings.enableXmlWrapper = !tabStorage.settings.enableXmlWrapper;
};
</script>
<style></style>

View File

@ -1,5 +1,5 @@
<template>
<el-tooltip :content="props.messages[0].content.text" placement="top">
<el-tooltip :content="props.messages[0].content.text" placement="top" effect="light">
<span class="chat-prompt-item" contenteditable="false">
<span class="iconfont icon-chat"></span>
<span class="real-text">{{ props.messages[0].content.text }}</span>
@ -22,9 +22,6 @@ const props = defineProps({
<style>
.chat-prompt-item {
max-width: 80px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-flex;
border-radius: .3em;
align-items: center;
@ -34,9 +31,17 @@ const props = defineProps({
font-size: 12px;
margin-left: 3px;
margin-right: 3px;
user-select: none;
}
.chat-prompt-item .iconfont {
margin-right: 4px;
}
.chat-prompt-item .real-text {
max-width: 60px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<el-tooltip placement="top">
<el-tooltip placement="top" effect="light">
<template #content>
<div class="resource-chat-item-tooltip">
<div v-for="(item, index) of toolRenderItems" :key="index">

View File

@ -2,7 +2,14 @@ import type { ToolCallContent, ToolCallResponse } from "@/hook/type";
import { MessageState, type ToolCall } from "../chat-box/chat";
import { mcpClientAdapter } from "@/views/connect/core";
import type { BasicLlmDescription } from "@/views/setting/llm";
import { redLog } from "@/views/setting/util";
import type OpenAI from "openai";
export interface TaskLoopChatOption {
id?: string
proxyServer?: string
enableXmlWrapper?: boolean
}
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
export interface ToolCallResult {
state: MessageState;
@ -60,7 +67,7 @@ function deserializeToolCallResponse(toolArgs: string) {
}
}
function handleToolResponse(toolResponse: ToolCallResponse) {
export function handleToolResponse(toolResponse: ToolCallResponse) {
if (typeof toolResponse === 'string') {
return {
@ -98,36 +105,67 @@ function parseErrorObject(error: any): string {
}
}
function grokIndexAdapter(toolCall: ToolCall, callId2Index: Map<string, number>): IToolCallIndex {
/**
* @description ID映射为索引
* @param toolCall
* @param callId2Index ID到索引的映射表
* @returns
*/
export function idAsIndexAdapter(toolCall: ToolCall | string, callId2Index: Map<string, number>): IToolCallIndex {
// grok 采用 id 作为 index需要将 id 映射到 zero-based 的 index
if (!toolCall.id) {
const id = typeof toolCall === 'string' ? toolCall : toolCall.id;
if (!id) {
return 0;
}
if (!callId2Index.has(toolCall.id)) {
callId2Index.set(toolCall.id, callId2Index.size);
if (!callId2Index.has(id)) {
callId2Index.set(id, callId2Index.size);
}
return callId2Index.get(toolCall.id)!;
return callId2Index.get(id)!;
}
function geminiIndexAdapter(toolCall: ToolCall): IToolCallIndex {
/**
* @description
* @param toolCall
* @returns 0
*/
export function singleCallIndexAdapter(toolCall: ToolCall): IToolCallIndex {
// TODO: 等待后续支持
return 0;
}
function defaultIndexAdapter(toolCall: ToolCall): IToolCallIndex {
/**
* @description
* @param toolCall
* @returns
*/
export function defaultIndexAdapter(toolCall: ToolCall): IToolCallIndex {
return toolCall.index || 0;
}
export function getToolCallIndexAdapter(llm: BasicLlmDescription) {
export function getToolCallIndexAdapter(llm: BasicLlmDescription, chatData: ChatCompletionCreateParamsBase) {
// 如果是 xml 模式,那么 index adapter 必须是 idAsIndexAdapter
if (chatData.enableXmlWrapper) {
const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
}
if (llm.userModel.startsWith('gemini')) {
return geminiIndexAdapter;
return singleCallIndexAdapter;
}
if (llm.userModel.startsWith('grok')) {
const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => grokIndexAdapter(toolCall, callId2Index);
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
}
return defaultIndexAdapter;
}
export function getIdAsIndexAdapter() {
const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
}

View File

@ -1,6 +1,6 @@
/* eslint-disable */
import { ref, type Ref } from "vue";
import { type ToolCall, type ChatStorage, getToolSchema, MessageState } from "../chat-box/chat";
import { type ToolCall, type ChatStorage, getToolSchema, MessageState, type ChatMessage, type ChatSetting, type EnableToolItem } from "../chat-box/chat";
import { useMessageBridge, MessageBridge, createMessageBridge } from "@/api/message-bridge";
import type { OpenAI } from 'openai';
import { llmManager, llms, type BasicLlmDescription } from "@/views/setting/llm";
@ -13,9 +13,15 @@ import { mcpSetting } from "@/hook/mcp";
import { mcpClientAdapter } from "@/views/connect/core";
import type { ToolItem } from "@/hook/type";
import chalk from 'chalk';
import { getXmlWrapperPrompt, getToolCallFromXmlString, getXmlsFromString, handleXmlWrapperToolcall, toNormaliseToolcall, getXmlResultPrompt } from "./xml-wrapper";
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string, proxyServer?: string };
export interface TaskLoopChatOption {
id?: string
proxyServer?: string
enableXmlWrapper?: boolean
}
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
export interface TaskLoopOptions {
maxEpochs?: number;
maxJsonParseRetry?: number;
@ -28,6 +34,8 @@ export interface IErrorMssage {
msg: string
}
export { MessageState };
export interface IDoConversationResult {
stop: boolean;
}
@ -57,8 +65,8 @@ export class TaskLoop {
};
constructor(
private readonly taskOptions: TaskLoopOptions = {
maxEpochs: 20,
private taskOptions: TaskLoopOptions = {
maxEpochs: 50,
maxJsonParseRetry: 3,
adapter: undefined,
verbose: 0
@ -77,22 +85,58 @@ export class TaskLoop {
throw new Error('adapter is required');
}
// 根据 adapter 创建 nodejs 下特殊的、基于 event 的 message bridge (不占用任何端口)
createMessageBridge(adapter.emitter);
// 用于进行连接同步
this.nodejsStatus.connectionFut = mcpClientAdapter.launch();
}
// web 环境下 bridge 会自动加载完成
this.bridge = useMessageBridge();
// 注册 HMR
mcpClientAdapter.addConnectRefreshListener();
}
private handleChunkDeltaContent(chunk: ChatCompletionChunk) {
public async waitConnection() {
await this.nodejsStatus.connectionFut;
}
public setTaskLoopOptions(taskOptions: TaskLoopOptions) {
const {
maxEpochs = 50,
maxJsonParseRetry = 3,
verbose = 1,
} = taskOptions;
this.taskOptions = {
maxEpochs,
maxJsonParseRetry,
verbose,
...this.taskOptions
};
}
/**
* @description streaming content
* @param chunk
* @param chatData
*/
private handleChunkDeltaContent(chunk: ChatCompletionChunk, chatData: ChatCompletionCreateParamsBase) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
this.streamingContent.value += content;
}
}
private handleChunkDeltaToolCalls(chunk: ChatCompletionChunk, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) {
/**
* @description streaming chunk tool_calls
* @param chunk
* @param chatData
* @param toolcallIndexAdapter
*/
private handleChunkDeltaToolCalls(chunk: ChatCompletionChunk, chatData: ChatCompletionCreateParamsBase, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) {
const toolCall = chunk.choices[0]?.delta?.tool_calls?.[0];
if (toolCall) {
@ -148,9 +192,15 @@ export class TaskLoop {
const { chunk } = data.msg as { chunk: ChatCompletionChunk };
// 处理增量的 content 和 tool_calls
this.handleChunkDeltaContent(chunk);
this.handleChunkDeltaToolCalls(chunk, toolcallIndexAdapter);
if (chatData.enableXmlWrapper) {
this.handleChunkDeltaContent(chunk, chatData);
// no tool call in enableXmlWrapper
this.handleChunkUsage(chunk);
} else {
this.handleChunkDeltaContent(chunk, chatData);
this.handleChunkDeltaToolCalls(chunk, chatData, toolcallIndexAdapter);
this.handleChunkUsage(chunk);
}
this.consumeChunks(chunk);
}, { once: false });
@ -207,22 +257,34 @@ export class TaskLoop {
const model = this.getLlmConfig().userModel;
const temperature = tabStorage.settings.temperature;
const tools = getToolSchema(tabStorage.settings.enableTools);
const parallelToolCalls = tabStorage.settings.parallelToolCalls;
const proxyServer = mcpSetting.proxyServer || '';
// 如果是 xml 模式,则 tools 为空
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
const tools = enableXmlWrapper ? []: getToolSchema(tabStorage.settings.enableTools);
const userMessages = [];
// 尝试获取 system prompt在 api 模式下systemPrompt 就是目标提词
// 但是在 UI 模式下systemPrompt 只是一个 index需要从后端数据库中获取真实 prompt
let prompt = '';
// 如果存在系统提示词,则从数据库中获取对应的数据
if (tabStorage.settings.systemPrompt) {
const prompt = getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
prompt += getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
}
// 如果是 xml 模式,则在开头注入 xml
if (enableXmlWrapper) {
prompt += getXmlWrapperPrompt(tabStorage.settings.enableTools, tabStorage);
}
userMessages.push({
role: 'system',
content: prompt
});
}
// 如果超出了 tabStorage.settings.contextLength, 则删除最早的消息
const loadMessages = tabStorage.messages.slice(- tabStorage.settings.contextLength);
@ -240,7 +302,8 @@ export class TaskLoop {
tools,
parallelToolCalls,
messages: userMessages,
proxyServer
proxyServer,
enableXmlWrapper,
} as ChatCompletionCreateParamsBase;
return chatData;
@ -345,7 +408,7 @@ export class TaskLoop {
if (verbose > 0) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.blueBright('🔧 calling tool'),
chalk.blueBright('🔧 using tool'),
chalk.blueBright(toolCall.function!.name)
);
}
@ -359,13 +422,13 @@ export class TaskLoop {
if (result.state === 'success') {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.green('✓ call tools okey dockey'),
chalk.green('✓ use tools'),
chalk.green(result.state)
);
} else {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.red('× fail to call tools'),
chalk.red('× use tools'),
chalk.red(result.content.map(item => item.text).join(', '))
);
}
@ -375,7 +438,7 @@ export class TaskLoop {
private consumeEpochs() {
const { verbose = 0 } = this.taskOptions;
if (verbose > 0) {
if (verbose > 1) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.blue('task loop enters a new epoch')
@ -386,12 +449,14 @@ export class TaskLoop {
private consumeDones() {
const { verbose = 0 } = this.taskOptions;
if (verbose > 0) {
if (verbose > 1) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.green('task loop finish a epoch')
);
}
return this.onDone();
}
@ -461,6 +526,7 @@ export class TaskLoop {
// 等待连接完成
await this.nodejsStatus.connectionFut;
}
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
// 添加目前的消息
tabStorage.messages.push({
@ -469,13 +535,14 @@ export class TaskLoop {
extraInfo: {
created: Date.now(),
state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown'
serverName: this.getLlmConfig().id || 'unknown',
enableXmlWrapper
}
});
let jsonParseErrorRetryCount = 0;
const {
maxEpochs = 20,
maxEpochs = 50,
verbose = 0
} = this.taskOptions || {};
@ -498,7 +565,7 @@ export class TaskLoop {
this.currentChatId = chatData.id!;
const llm = this.getLlmConfig();
const toolcallIndexAdapter = getToolCallIndexAdapter(llm);
const toolcallIndexAdapter = getToolCallIndexAdapter(llm, chatData);
// 发送请求
const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter);
@ -513,14 +580,15 @@ export class TaskLoop {
extraInfo: {
created: Date.now(),
state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown'
serverName: this.getLlmConfig().id || 'unknown',
enableXmlWrapper
}
});
if (verbose > 0) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.yellow('🤖 llm wants to call these tools'),
chalk.yellow('🤖 Agent wants to use these tools'),
chalk.yellow(this.streamingToolCalls.value.map(tool => tool.function!.name || '').join(', '))
);
}
@ -550,7 +618,8 @@ export class TaskLoop {
created: Date.now(),
state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown',
usage: undefined
usage: undefined,
enableXmlWrapper
}
});
break;
@ -565,7 +634,8 @@ export class TaskLoop {
created: Date.now(),
state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage
usage: this.completionUsage,
enableXmlWrapper
}
});
} else if (toolCallResult.state === MessageState.ToolCall) {
@ -579,7 +649,8 @@ export class TaskLoop {
created: Date.now(),
state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage
usage: this.completionUsage,
enableXmlWrapper
}
});
}
@ -593,10 +664,96 @@ export class TaskLoop {
created: Date.now(),
state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage
usage: this.completionUsage,
enableXmlWrapper
}
});
// 如果 xml 模型,需要检查内部是否含有有效的 xml 进行调用
if (tabStorage.settings.enableXmlWrapper) {
const xmls = getXmlsFromString(this.streamingContent.value);
if (xmls.length === 0) {
// 没有 xml 了,说明对话结束
break;
}
// 使用 user 作为身份来承载 xml 调用的结果
// 并且在 extra 内存储结构化信息
const fakeUserMessage = {
role: 'user',
content: '',
extraInfo: {
created: Date.now(),
state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage,
enableXmlWrapper,
}
} as ChatMessage;
// 有 xml 了,需要检查 xml 内部是否有有效的 xml 进行调用
for (const xml of xmls) {
const toolcall = await getToolCallFromXmlString(xml);
if (!toolcall) {
continue;
}
// toolcall 事件
// 此处使用的是 xml 使用的 toolcall为了保持一致性需要转换成 openai 标准下的 toolcall
const normaliseToolcall = toNormaliseToolcall(toolcall, toolcallIndexAdapter);
this.consumeToolCalls(normaliseToolcall);
// 调用 XML 调用,其实可以考虑后续把这个循环改成 Promise.race
const toolCallResult = await handleXmlWrapperToolcall(toolcall);
// toolcalled 事件
// 因为是交付给后续进行统一消费的,所以此处的输出满足 openai 接口规范
this.consumeToolCalleds(toolCallResult);
// XML 模式下只存在 assistant 和 user 这两个角色,因此,以 user 为身份来存储
if (toolCallResult.state === MessageState.InvalidXml) {
// 如果是因为解析 XML 错误,则重新开始
tabStorage.messages.pop();
jsonParseErrorRetryCount ++;
redLog('解析 XML 错误 ' + normaliseToolcall?.function?.arguments);
// 如果因为 XML 错误而失败太多,就只能中断了
if (jsonParseErrorRetryCount >= (this.taskOptions.maxJsonParseRetry || 3)) {
const prompt = getXmlResultPrompt(toolcall.callId, `解析 XML 错误,无法继续调用工具 (累计错误次数 ${this.taskOptions.maxJsonParseRetry})`);
fakeUserMessage.content += prompt;
break;
}
} else if (toolCallResult.state === MessageState.Success) {
// TODO: xml 目前只支持 text 类型的回复
const toolCallResultString = toolCallResult.content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n');
fakeUserMessage.content += getXmlResultPrompt(toolcall.callId, toolCallResultString);
} else if (toolCallResult.state === MessageState.ToolCall) {
// TODO: xml 目前只支持 text 类型的回复
const toolCallResultString = toolCallResult.content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n');
fakeUserMessage.content += getXmlResultPrompt(toolcall.callId, toolCallResultString);
}
}
tabStorage.messages.push(fakeUserMessage);
} else {
// 普通对话直接结束
break;
}
} else {
// 一些提示
@ -609,4 +766,52 @@ export class TaskLoop {
}
}
}
public async createStorage(settings?: ChatSetting): Promise<ChatStorage> {
let {
enableXmlWrapper = false,
systemPrompt = '',
temperature = 0.6,
contextLength = 100,
parallelToolCalls = true,
enableWebSearch = false,
enableTools = undefined,
} = settings || {};
if (enableTools === undefined) {
// 默认缺省的情况下使用全部工具
const tools = await this.listTools();
enableTools = tools.map(tool => ({
...tool,
enabled: true
})) as EnableToolItem[];
}
const _settings = {
enableXmlWrapper,
systemPrompt,
temperature,
contextLength,
parallelToolCalls,
enableTools,
enableWebSearch
} as ChatSetting;
return {
messages: [],
settings: _settings
}
}
public async getPrompt(promptId: string, args?: Record<string, any>) {
const prompt = await mcpClientAdapter.readPromptTemplate(promptId, args);
// transform prompt to string
const promptString = prompt.messages.map(m => m.content.text).join('\n');
return promptString;
}
public async getResource(resourceUri: string) {
const resource = await mcpClientAdapter.readResource(resourceUri);
return resource;
}
}

View File

@ -0,0 +1,294 @@
import { parseString } from 'xml2js';
import { MessageState, type ToolCall } from '../chat-box/chat';
import { mcpClientAdapter } from '@/views/connect/core';
import { handleToolResponse, type IToolCallIndex, type ToolCallResult } from './handle-tool-calls';
import type { ChatStorage, EnableToolItem } from "../chat-box/chat";
import type { ToolCallContent } from '@/hook/type';
export interface XmlToolCall {
server: string;
name: string;
callId: string;
parameters: Record<string, string>;
}
export function toolSchemaToPromptDescription(enableTools: EnableToolItem[]) {
let prompt = '';
const tools = enableTools.filter(tool => tool.enabled);
// 无参数的工具
const noParamTools = tools.filter(tool =>
!tool.inputSchema.required || tool.inputSchema.required.length === 0
);
if (noParamTools.length > 0) {
noParamTools.forEach(tool => {
prompt += `- ${tool.name}\n`;
prompt += `**Description**: ${tool.description}\n\n`;
});
}
// 有参数的工具
const paramTools = tools.filter(tool =>
tool.inputSchema.required && tool.inputSchema.required.length > 0
);
if (paramTools.length > 0) {
paramTools.forEach(tool => {
prompt += `- ${tool.name}\n`;
prompt += `**Description**: ${tool.description}\n`;
prompt += `**Parameters**:\n`;
Object.entries(tool.inputSchema.properties).forEach(([name, prop]) => {
const required = tool.inputSchema.required?.includes(name) || false;
prompt += `- \`${name}\`: ${prop.description || 'No Description'} (${prop.type}) ${required ? '(required)' : ''}\n`;
});
prompt += '\n';
});
}
return prompt;
}
export function getXmlWrapperPrompt(tools: EnableToolItem[], tabStorage: ChatStorage) {
const toolPrompt = toolSchemaToPromptDescription(tools);
const requests = [
`ALWAYS analyze what function calls would be appropriate for the task`,
`ALWAYS format your function call usage EXACTLY as specified in the schema`,
`NEVER skip required parameters in function calls`,
`NEVER invent functions that arent available to you`,
`ALWAYS wait for function call execution results before continuing`,
`After invoking a function, wait for the output in <function_results> tag and then continue with your response`,
`NEVER mock or form <function_results> on your own, it will be provided to you after the execution`,
];
if (!tabStorage.settings.parallelToolCalls) {
requests.push(`NEVER invoke multiple functions in a single response`);
}
const requestString = requests.map((text, index) => {
return `${index + 1}. ${text}`;
}).join('\n');
return `
[Start Fresh Session from here]
<SYSTEM>
You are OpenMCP Assistant with the capabilities of invoke functions and make the best use of it during your assistance, a knowledgeable assistant focused on answering questions and providing information on any topics.
In this environment you have access to a set of tools you can use to answer the user's question.
You have access to a set of functions you can use to answer the user's question. You do NOT currently have the ability to inspect files or interact with external resources, except by invoking the below functions.
Function Call Structure:
- All function calls should be wrapped in 'xml' codeblocks tags like \`\`\`xml ... \`\`\`. This is strict requirement.
- Wrap all function calls in 'function_calls' tags
- Each function call uses 'invoke' tags with a 'name' attribute
- Parameters use 'parameter' tags with 'name' attributes
- Parameter Formatting:
- String/scalar parameters: written directly as values
- Lists/objects: must use proper JSON format
- Required parameters must always be included
- Optional parameters should only be included when needed
- If there is xml inside the parameter value, do not use CDATA for wrapping it, just give the xml directly
The instructions regarding 'invoke' specify that:
- When invoking functions, use the 'invoke' tag with a 'name' attribute specifying the function name.
- The invoke tag must be nested within an 'function_calls' block.
- Parameters for the function should be included as 'parameter' tags within the invoke tag, each with a 'name' attribute.
- Include all required parameters for each function call, while optional parameters should only be included when necessary.
- String and scalar parameters should be specified directly as values, while lists and objects should use proper JSON format.
- Do not refer to function/tool names when speaking directly to users - focus on what I'm doing rather than the tool I'm using.
- When invoking a function, ensure all necessary context is provided for the function to execute properly.
- Each 'invoke' tag should represent a single, complete function call with all its relevant parameters.
- DO not generate any <function_calls> tag in your thinking/resoning process, because those will be interpreted as a function call and executed. just formulate the correct parameters for the function call.
The instructions regarding 'call_id="$CALL_ID">
- It is a unique identifier for the function call.
- It is a number that is incremented by 1 for each new function call, starting from 1.
You can invoke one or more functions by writing a "<function_calls>" block like the following as part of your reply to the user, MAKE SURE TO INVOKE ONLY ONE FUNCTION AT A TIME, meaning only one '<function_calls>' tag in your output :
<Example>
\`\`\`xml
<function_calls>
<invoke name="$FUNCTION_NAME" call_id="$CALL_ID">
<parameter name="$PARAMETER_NAME_1">$PARAMETER_VALUE</parameter>
<parameter name="$PARAMETER_NAME_2">$PARAMETER_VALUE</parameter>
...
</invoke>
</function_calls>
\`\`\`
</Example>
String and scalar parameters should be specified as is, while lists and objects should use JSON format. Note that spaces for string values are not stripped. The output is not expected to be valid XML and is parsed with regular expressions.
When a user makes a request:
${requestString}
Answer the user's request using the relevant tool(s), if they are available. Check that all the required parameters for each tool call are provided or can reasonably be inferred from context. IF there are no relevant tools or there are missing values for required parameters, ask the user to supply these values; otherwise proceed with the tool calls. If the user provides a specific value for a parameter (for example provided in quotes), make sure to use that value EXACTLY. DO NOT make up values for or ask about optional parameters. Carefully analyze descriptive terms in the request as they may indicate required parameter values that should be included even if not explicitly quoted.
<Output Format>
<Start HERE>
## Thoughts
- User Query Elaboration:
- Thoughts:
- Observations:
- Solutions:
- Function to be used:
- call_id: $CALL_ID + 1 = $CALL_ID
\`\`\`xml
<function_calls>
<invoke name="$FUNCTION_NAME" call_id="$CALL_ID">
<parameter name="$PARAMETER_NAME_1">$PARAMETER_VALUE</parameter>
<parameter name="$PARAMETER_NAME_2">$PARAMETER_VALUE</parameter>
...
</invoke>
</function_calls>
\`\`\`
<End HERE>
</Output Format>
Do not use <Start HERE> and <End HERE> in your output, that is just output format reference to where to start and end your output.
## AVAILABLE TOOLS FOR SUPERASSISTANT
${toolPrompt}
- list_servers
**Description**: List all connected MCP servers and their capabilities
- get_server_info
**Description**: Get detailed information about a specific server
**Parameters**:
- \`serverName\`: Name of the server to get info for (string) (required)
</SYSTEM>
User Interaction Starts here:
`.trim();
}
export function getXmlResultPrompt(callId: string, result: string) {
return `
\`\`\`xml
<function_results>
<result call_id="${callId}">
${result}
</result>
</function_results>
\`\`\`
`.trim() + '\n\n';
}
export function getXmlsFromString(content: string) {
const matches = content.matchAll(/```xml\n([\s\S]*?)\n```/g);
return Array.from(matches).map(match => match[1].trim());
}
export async function getToolCallFromXmlString(xmlString: string): Promise<XmlToolCall | null> {
try {
const result = await new Promise<any>((resolve, reject) => {
parseString(xmlString, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
if (!result?.function_calls?.invoke) {
return null;
}
const invoke = result.function_calls.invoke[0].$;
const parameters: Record<string, any> = {};
if (result.function_calls.invoke[0].parameter) {
result.function_calls.invoke[0].parameter.forEach((param: any) => {
const name = param.$.name as string;
parameters[name] = param._;
});
}
// name 可能是 neo4j-mcp.executeReadOnlyCypherQuery
return {
server: '',
name: invoke.name,
callId: invoke.call_id,
parameters
};
} catch (error) {
console.error('Failed to parse function calls:', error);
return null;
}
}
export async function getToolResultFromXmlString(xmlString: string) {
try {
const result = await new Promise<any>((resolve, reject) => {
parseString(xmlString, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
if (!result?.function_results?.result) {
return null;
}
const resultData = result.function_results.result[0];
const callId = resultData.$.call_id;
// 提取所有评论文本
const toolcallContent = [] as ToolCallContent[];
const content = resultData._;
toolcallContent.push({
type: 'text',
text: content
});
return {
callId,
toolcallContent
};
} catch (error) {
console.error('Failed to parse function results:', error);
return null;
}
}
export function toNormaliseToolcall(xmlToolcall: XmlToolCall, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex): ToolCall {
const toolcall = {
id: xmlToolcall.callId,
index: -1,
type: 'function',
function: {
name: xmlToolcall.name,
arguments: JSON.stringify(xmlToolcall.parameters)
}
} as ToolCall;
toolcall.index = toolcallIndexAdapter(toolcall);
return toolcall;
}
export async function handleXmlWrapperToolcall(toolcall: XmlToolCall): Promise<ToolCallResult> {
if (!toolcall) {
return {
content: [{
type: 'error',
text: 'invalid xml'
}],
state: MessageState.InvalidXml
}
}
const toolResponse = await mcpClientAdapter.callTool(toolcall.name, toolcall.parameters);
return handleToolResponse(toolResponse);
}

View File

@ -1,10 +1,10 @@
<template>
<div class="chat-container" :ref="el => chatContainerRef = el">
<el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll" v-if="renderMessages.length > 0 || isLoading">
<el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll"
v-if="renderMessages.length > 0 || isLoading">
<div class="message-list" :ref="el => messageListRef = el">
<div v-for="(message, index) in renderMessages" :key="index"
:class="['message-item', message.role.split('/')[0], message.role.split('/')[1]]"
>
:class="['message-item', message.role.split('/')[0], message.role.split('/')[1]]">
<div class="message-avatar" v-if="message.role === 'assistant/content'">
<span class="iconfont icon-robot"></span>
</div>
@ -23,10 +23,8 @@
<!-- 助手调用的工具部分 -->
<div class="message-content" v-else-if="message.role === 'assistant/tool_calls'">
<Message.Toolcall
:message="message" :tab-id="props.tabId"
@update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value"
/>
<Message.Toolcall :message="message" :tab-id="props.tabId"
@update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value" />
</div>
</div>
@ -47,23 +45,22 @@
</div>
</div>
<ChatBox
:ref="el => footerRef = el"
:tab-id="props.tabId"
/>
<ChatBox :ref="el => footerRef = el" :tab-id="props.tabId" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide } from 'vue';
import { ref, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide, watchEffect } from 'vue';
import { useI18n } from 'vue-i18n';
import { ElMessage, type ScrollbarInstance } from 'element-plus';
import { type ScrollbarInstance } from 'element-plus';
import { tabs } from '../panel';
import type { ChatMessage, ChatStorage, IRenderMessage, ToolCall } from './chat-box/chat';
import { MessageState } from './chat-box/chat';
import * as Message from './message';
import ChatBox from './chat-box/index.vue';
import { getToolCallFromXmlString, getToolResultFromXmlString, getXmlsFromString, toNormaliseToolcall } from './core/xml-wrapper';
import { getIdAsIndexAdapter } from './core/handle-tool-calls';
defineComponent({ name: 'chat' });
@ -85,18 +82,71 @@ if (!tabStorage.messages) {
tabStorage.messages = [] as ChatMessage[];
}
const renderMessages = computed(() => {
const messages: IRenderMessage[] = [];
function getXmlToolCalls(message: ChatMessage) {
if (message.role !== 'assistant' && message.role !== 'user') {
return [];
}
const enableXmlTools = message.extraInfo?.enableXmlWrapper ?? false;
if (!enableXmlTools) {
return [];
}
const xmls = getXmlsFromString(message.content);
return xmls || [];
}
const renderMessages = ref<IRenderMessage[]>([]);
watchEffect(async () => {
renderMessages.value = [];
for (const message of tabStorage.messages) {
const indexAdapter = getIdAsIndexAdapter();
const xmls = getXmlToolCalls(message);
if (message.role === 'user') {
messages.push({
if (xmls.length > 0 && message.extraInfo.enableXmlWrapper) {
// xml xml xml
// assistant/tool_calls
const lastAssistantMessage = renderMessages.value[renderMessages.value.length - 1];
if (lastAssistantMessage.role === 'assistant/tool_calls') {
const toolCallResultXmls = getXmlsFromString(message.content);
for (const xml of toolCallResultXmls) {
const toolResult = await getToolResultFromXmlString(xml);
if (toolResult) {
const index = indexAdapter(toolResult.callId);
lastAssistantMessage.toolResults[index] = toolResult.toolcallContent;
if (lastAssistantMessage.extraInfo.state === MessageState.Unknown) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
} else if (lastAssistantMessage.extraInfo.state === MessageState.Success
|| message.extraInfo.state !== MessageState.Success
) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
}
lastAssistantMessage.extraInfo.usage = lastAssistantMessage.extraInfo.usage || message.extraInfo.usage;
}
}
}
} else {
renderMessages.value.push({
role: 'user',
content: message.content,
extraInfo: message.extraInfo
});
}
} else if (message.role === 'assistant') {
if (message.tool_calls) {
messages.push({
renderMessages.value.push({
role: 'assistant/tool_calls',
content: message.content,
toolResults: Array(message.tool_calls.length).fill([]),
@ -108,16 +158,43 @@ const renderMessages = computed(() => {
}
});
} else {
messages.push({
if (xmls.length > 0 && message.extraInfo.enableXmlWrapper) {
// xml xml xml
const toolCalls = [];
for (const xml of xmls) {
const xmlToolCall = await getToolCallFromXmlString(xml);
if (xmlToolCall) {
toolCalls.push(
toNormaliseToolcall(xmlToolCall, indexAdapter)
);
}
}
const renderAssistantMessage = message.content.replace(/```xml[\s\S]*?```/g, '');
renderMessages.value.push({
role: 'assistant/tool_calls',
content: renderAssistantMessage,
toolResults: Array(toolCalls.length).fill([]),
tool_calls: toolCalls,
showJson: ref(false),
extraInfo: {
...message.extraInfo,
state: MessageState.Unknown
}
});
} else {
renderMessages.value.push({
role: 'assistant/content',
content: message.content,
extraInfo: message.extraInfo
});
}
}
} else if (message.role === 'tool') {
// assistant
const lastAssistantMessage = messages[messages.length - 1];
const lastAssistantMessage = renderMessages.value[renderMessages.value.length - 1];
if (lastAssistantMessage.role === 'assistant/tool_calls') {
lastAssistantMessage.toolResults[message.index] = message.content;
@ -133,10 +210,9 @@ const renderMessages = computed(() => {
}
}
}
return messages;
});
const isLoading = ref(false);
const streamingContent = ref('');
@ -232,14 +308,14 @@ watch(streamingToolCalls, () => {
padding-top: 70px;
}
.chat-openmcp-icon > div {
.chat-openmcp-icon>div {
display: flex;
flex-direction: column;
align-items: left;
font-size: 28px;
}
.chat-openmcp-icon > div > span {
.chat-openmcp-icon>div>span {
margin-bottom: 23px;
}
@ -285,7 +361,7 @@ watch(streamingToolCalls, () => {
width: 100%;
}
.user .message-text > span {
.user .message-text>span {
border-radius: .9em;
background-color: var(--main-light-color);
padding: 10px 15px;
@ -340,9 +416,12 @@ watch(streamingToolCalls, () => {
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="message-role">
<span class="message-reminder" v-if="callingTools">
Agent 正在使用工具
Agent {{ t('using-tool') }}
<span class="tool-loading iconfont icon-double-loading">
</span>
</span>

View File

@ -7,17 +7,24 @@
<h3 class="resource-template">
<span>tools/list</span>
<span class="iconfont icon-restart" @click.stop="reloadTools(client, { first: false })"></span>
<span>
<span class="cilent-name-tag">
{{ client.name }}
</span>
</span>
</h3>
</template>
<!-- body -->
<div class="tool-list-container-scrollbar">
<el-scrollbar height="500px">
<el-scrollbar height="fit-content">
<div class="tool-list-container">
<div class="item" :class="{ 'active': tabStorage.currentToolName === tool.name }"
v-for="tool of client.tools?.values()" :key="tool.name" @click="handleClick(tool)">
<span>{{ tool.name }}</span>
<span>{{ tool.description || '' }}</span>
<br>
<span class="tool-description">{{ tool.description || '' }}</span>
</div>
</div>
</el-scrollbar>
@ -27,7 +34,7 @@
</template>
<script setup lang="ts">
import { onMounted, defineProps, ref, type Reactive } from 'vue';
import { onMounted, defineProps, type Reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import type { ToolStorage } from './tools';
import { tabs } from '../panel';
@ -99,13 +106,12 @@ onMounted(async () => {
width: 175px;
}
.tool-list-container>.item {
.tool-list-container > .item {
margin: 3px;
padding: 5px 10px;
border-radius: .3em;
user-select: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
transition: var(--animation-3s);
@ -122,18 +128,34 @@ onMounted(async () => {
}
.tool-list-container>.item>span:first-child {
max-width: 200px;
min-width: 120px;
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex-shrink: 0;
}
.tool-list-container>.item>span:last-child {
min-width: 120px;
max-width: 250px;
overflow: visible;
white-space: normal;
word-wrap: break-word;
}
.resource-template .cilent-name-tag {
margin-left: 10px;
background-color: var(--main-color);
padding: 2px 5px;
border-radius: .3em;
height: fit-content;
font-size: 13px;
color: black;
}
.tool-description {
opacity: 0.6;
font-size: 12.5px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@ -2,7 +2,7 @@
<div v-if="!isConnecting" class="connected-status-container" id="connected-status-container"
@click.stop="toggleConnectionPanel()" :class="{ 'connected': client.connectionResult.success }">
<span class="mcp-server-info">
<el-tooltip class="extra-connect-container" effect="dark" placement="right"
<el-tooltip class="extra-connect-container" effect="light" placement="right"
:content="fullDisplayServerName">
<span class="name">{{ displayServerName }}</span>
</el-tooltip>
@ -20,7 +20,7 @@
</div>
<div v-else class="connected-status-container">
<span class="mcp-server-info">
<el-tooltip class="extra-connect-container" effect="dark" placement="right"
<el-tooltip class="extra-connect-container" effect="light" placement="right"
:content="fullDisplayServerName">
<span class="name">
加载中

View File

@ -3,7 +3,7 @@
<div v-for="(item, index) of sidebarItems" :key="index"
:id="`sidebar-${item.ident}`"
>
<el-tooltip :content="t(item.ident)" placement="right">
<el-tooltip :content="t(item.ident)" placement="right" effect="light">
<div class="sidebar-option-item" :class="{ 'active': isActive(item.ident) }"
@click="gotoOption(item.ident)">
<span :class="`iconfont ${item.icon}`"></span>

View File

@ -61,7 +61,7 @@ export async function loadPanels(client: McpClient | Reactive<McpClient>) {
panelLoaded.value = true;
}
let debounceHandler: number;
let debounceHandler: NodeJS.Timeout;
export function safeSavePanels() {
clearTimeout(debounceHandler);

View File

@ -1,189 +1,186 @@
{
"module": "وحدة",
"signal": "إشارة",
"search-signal": "البحث عن إشارة",
"language-setting": "اللغة",
"search-setting": "البحث",
"search-case-sensitivity": "حساسية الحالة",
"search-mode": "وضع البحث",
"search-scope": "نطاق البحث",
"search-display-parent-only": "عرض الوحدة الرئيسية فقط",
"search-nothing": "لم يتم العثور على أي إشارات",
"signal-only": "إشارة فقط",
"module-only": "وحدة فقط",
"signal-module": "إشارة + وحدة",
"general-setting": "عام",
"appearance-setting": "المظهر",
"display-wave-height": "ارتفاع مسار الموجة",
"display-signal-info-scope": "معلومات العرض في الشريط الجانبي",
"display-signal-info-scope.width": "عرض البت",
"display-signal-info-scope.parent": "اسم الوحدة الرئيسية",
"wavecolor": "لون الموجة الافتراضي",
"wavecolor.normal-bit": "موجة بعرض وحدة",
"wavecolor.normal-vec": "موجة بعرض متعدد البتات",
"wavecolor.high-impedance": "موجة عالية المقاومة",
"wavecolor.unknown": "موجة بحالة غير معروفة",
"operation-setting": "العمليات",
"render-setting": "العرض",
"render-animation": "تفعيل الرسوم المتحركة للعرض",
"usermanual": "دليل المستخدم",
"usermanual.left-right-scroll.caption": "التحرك لأعلى ولأسفل",
"usermanual.up-down-scroll.caption": "التحرك لليسار ولليمين",
"usermanual.xscale.caption": "التكبير الأفقي",
"loading": "جاري التحميل",
"context-menu.create-group": "إنشاء مجموعة جديدة",
"context-menu.join-group": "الانضمام إلى مجموعة موجودة",
"context-menu.change-color": "تغيير اللون",
"context-menu.delete": "حذف الإشارة",
"context-menu.delete-all-select": "حذف جميع الإشارات المحددة",
"context-menu.signal.name": "اسم الإشارة",
"context-menu.signal.type": "نوع الإشارة",
"context-menu.signal.width": "عرض الإشارة",
"context-menu.signal.dep": "التبعيات",
"context-menu.group.cancel": "إلغاء التجميع",
"context-menu.group.delete": "حذف المجموعة",
"context-menu.group.empty": "لا توجد مجموعات متاحة",
"context-menu.group.uname-group": "مجموعة بدون اسم",
"toolbar.modal.common-digital": "رقمي",
"toolbar.modal.ladder-analog": "تناظري (سلم)",
"toolbar.modal.line-analog": "تناظري (خط)",
"toolbar.search.name": "الاسم",
"toolbar.search.value": "القيمة",
"toolbar.format.category.base": "أساسي",
"toolbar.format.category.dec": "عشري",
"toolbar.format.category.float": "فاصلة عائمة",
"toolbar.format.signed": "موقع",
"toolbar.format.unsigned": "غير موقع",
"toolbar.format.half": "نصف دقة (16 بت)",
"toolbar.format.float": "دقة واحدة (32 بت)",
"toolbar.format.double": "دقة مزدوجة (64 بت)",
"toolbar.location.to-begin": "الانتقال إلى البداية",
"toolbar.location.to-end": "الانتقال إلى النهاية",
"toolbar.location.to-next-change": "الانتقال إلى التغيير التالي",
"toolbar.location.to-prev-change": "الانتقال إلى التغيير السابق",
"toolbar.location.make-location": "إنشاء علامة جديدة",
"toolbar.location.clear-location": "مسح جميع العلامات",
"toolbar.location.clear-location-dialog": "هل أنت متأكد أنك تريد مسح جميع العلامات؟",
"context-menu.cannot-join-repeat-group": "الإشارة الحالية موجودة بالفعل في هذه المجموعة",
"toolbar.no-result": "لا توجد نتائج",
"toolbar.search.value.already-to-head": "بالفعل في البداية",
"toolbar.search.value.already-to-tail": "بالفعل في النهاية",
"toolbar.search.value.searching": "جاري البحث",
"pivot.context.delete": "حذف العلامة",
"pivot.context.display-axis": "إنشاء محور نسبي",
"pivot.context.cancel-axis": "إلغاء المحور النسبي",
"setting.appearance.pivot-color": "لون العلامة",
"setting.appearance.moving-pivot": "علامة متحركة",
"setting.appearance.user-pivot": "علامة المستخدم",
"setting.appearance.system-pivot": "علامة النظام",
"confirm": "تأكيد",
"cancel": "إلغاء",
"tips": "نصائح",
"filemenu.save-view": "حفظ ملف العرض",
"filemenu.save-as-view": "حفظ العرض كملف",
"filemenu.load-view": "تحميل ملف العرض",
"filemenu.auto-save": "الحفظ التلقائي",
"current-version": "الإصدار الحالي",
"setting.language.change-dialog": "لقد قمت بتغيير اللغة إلى {0}، ونوصي بإعادة تشغيل Vcd Viewer.",
"resources": "الموارد",
"tools": "أدوات",
"prompts": "المطالبات",
"interaction-test": "اختبار تفاعلي",
"setting": "إعدادات",
"about": "نبذة عنا",
"connected": "متصل",
"disconnected": "غير متصل",
"debug": "تصحيح",
"connect": "اتصال",
"setting.general-color-setting": "إعدادات الألوان العامة",
"choose-a-project-debug": "اختر مشروعًا لتصحيحه",
"model": "النموذج",
"server-provider": "مزود الخدمة",
"api-root-url": "مسار جذر API",
"api-token": "مفتاح API",
"connection-method": "طريقة الاتصال",
"command": "أمر",
"env-var": "متغيرات البيئة",
"log": "سجلات",
"warning.click-to-connect": "يرجى النقر أولاً على $1 على اليسار للاتصال",
"reset": "إعادة تعيين",
"read-resource": "قراءة الموارد",
"enter": "إدخال",
"blank-test": "اختبار فارغ",
"connect.appearance.reconnect": "إعادة الاتصال",
"connect.appearance.connect": "اتصال",
"response": "الاستجابة",
"refresh": "تحديث",
"read-prompt": "قراءة المطالبة",
"execute-tool": "تشغيل",
"save": "حفظ",
"send": "إرسال",
"server-not-support-statistic": "موفر الخدمة الذي تستخدمه لا يدعم الإحصائيات مؤقتًا",
"answer-at": "تم الإجابة في",
"input-token": "إدخال",
"output-token": "إخراج",
"total": "الإجمالي",
"cache-hit-ratio": "معدل ضربات التخزين المؤقت",
"success-save": "تم الحفظ بنجاح",
"confirm-delete-model": "هل تريد حذف موفر النموذج؟",
"reserve-one-last-model": "احتفظ بنموذج واحد على الأقل",
"edit": "تعديل",
"delete": "حذف",
"test": "اختبار",
"add-new-server": "إضافة خدمة",
"choose-model": "اختر النموذج",
"system-prompt": "كلمات توجيه النظام",
"tool-use": "استخدام الأداة",
"websearch": "بحث على الإنترنت",
"temperature-parameter": "معامل درجة الحرارة",
"context-length": "طول السياق",
"system-prompt.placeholder": "أدخل كلمة تلميح النظام (مثال: أنت مساعد محترف في تطوير الواجهات الأمامية، أجب باللغة العربية)",
"precise": "دقيق",
"moderate": "توازن",
"creative": "إبداع",
"single-dialog": "محادثة من جولة واحدة",
"multi-dialog": "محادثة متعددة الجولات",
"press-and-run": "اكتب سؤالاً لبدء الاختبار",
"connect-sigature": "توقيع الاتصال",
"finish-refresh": "تم التحديث",
"add-system-prompt.name-placeholder": "عنوان prompt المخصص",
"enter-message-dot": "أدخل الرسالة...",
"generate-answer": "جارٍ إنشاء الإجابة",
"choose-presetting": "اختر الإعداد المسبق",
"cwd": "دليل التنفيذ",
"mcp-server-timeout": "أطول وقت لاستدعاء أداة MCP",
"return": "عودة",
"error": "خطأ",
"feedback": "تعليقات",
"waiting-mcp-server": "في انتظار استجابة خادم MCP",
"parallel-tool-calls": "السماح للنموذج باستدعاء أدوات متعددة في رد واحد",
"proxy-server": "خادم وكيل",
"update-model-list": "تحديث قائمة النماذج",
"ensure-delete-connection": "هل أنت متأكد أنك تريد حذف الاتصال $1؟",
"choose-connection-type": "الرجاء اختيار نوع الاتصال",
"please-enter-connection-command": "الرجاء إدخال أمر الاتصال",
"example-mcp-run": "على سبيل المثال: mcp run main.py",
"please-enter-cwd": "الرجاء إدخال دليل العمل (cwd)، اختياري",
"please-enter-cwd-placeholder": "على سبيل المثال: /path/to/project",
"please-enter-url": "الرجاء إدخال عنوان URL للاتصال",
"example-as": "على سبيل المثال:",
"enter-optional-oauth": "الرجاء إدخال رمز OAuth، اختياري",
"quick-start": "مقدمة",
"read-document": "قراءة الوثائق",
"report-issue": "الإبلاغ عن مشكلة",
"join-project": "المشاركة في المشروع",
"comment-plugin": "ملحق التعليقات",
"preset-env-sync.success": "تم مزامنة متغيرات البيئة المحددة مسبقًا",
"preset-env-sync.fail": "فشل مزامنة متغيرات البيئة المحددة مسبقًا",
"drag-to-fill-connect-parameters": "اسحب لملء معلمات الاتصال",
"connect-success": "تم الاتصال بنجاح",
"connect-fail": "فشل الاتصال",
"preset": "مسبق",
"openmcp-document": "الوثائق الرسمية لـ OpenMCP",
"star-our-project": "نجم مشروعنا",
"document": "الوثائق الرسمية",
"join-discussion": "انضم إلى مجموعة النقاش",
"comment-for-us": "اكتب تقييمًا لنا!",
"openmcp-developed-by": "OpenMCP Client {version} تم تطويره بواسطة {author}",
"error-parse-json": "خطأ في تحليل JSON:"
"module": "Module",
"signal": "Signal",
"search-signal": "Rechercher un signal",
"language-setting": "Langue",
"search-setting": "Recherche",
"search-case-sensitivity": "Sensibilité à la casse",
"search-mode": "Mode de recherche",
"search-scope": "Portée de la recherche",
"search-display-parent-only": "Afficher uniquement le module parent",
"search-nothing": "Aucun signal trouvé",
"signal-only": "Signal uniquement",
"module-only": "Module uniquement",
"signal-module": "Signal + Module",
"general-setting": "Général",
"appearance-setting": "Apparence",
"display-wave-height": "Hauteur de la piste d'onde",
"display-signal-info-scope": "Informations affichées dans la barre latérale",
"display-signal-info-scope.width": "Largeur de bit",
"display-signal-info-scope.parent": "Nom du module parent",
"wavecolor": "Couleur d'onde par défaut",
"wavecolor.normal-bit": "Onde à largeur unique",
"wavecolor.normal-vec": "Onde à largeur multiple",
"wavecolor.high-impedance": "Onde à haute impédance",
"wavecolor.unknown": "Onde à état inconnu",
"operation-setting": "Opérations",
"render-setting": "Rendu",
"render-animation": "Activer l'animation de rendu",
"usermanual": "Manuel utilisateur",
"usermanual.left-right-scroll.caption": "Déplacer vers le haut et le bas",
"usermanual.up-down-scroll.caption": "Déplacer vers la gauche et la droite",
"usermanual.xscale.caption": "Zoom horizontal",
"loading": "Chargement",
"context-menu.create-group": "Créer un groupe",
"context-menu.join-group": "Rejoindre un groupe existant",
"context-menu.change-color": "Changer la couleur",
"context-menu.delete": "Supprimer le signal",
"context-menu.delete-all-select": "Supprimer tous les signaux sélectionnés",
"context-menu.signal.name": "Nom du signal",
"context-menu.signal.type": "Type de signal",
"context-menu.signal.width": "Largeur du signal",
"context-menu.signal.dep": "Dépendances",
"context-menu.group.cancel": "Annuler le groupement",
"context-menu.group.delete": "Supprimer le groupe",
"context-menu.group.empty": "Aucun groupe disponible",
"context-menu.group.uname-group": "Groupe sans nom",
"toolbar.modal.common-digital": "Numérique",
"toolbar.modal.ladder-analog": "Analogique (échelle)",
"toolbar.modal.line-analog": "Analogique (ligne)",
"toolbar.search.name": "Nom",
"toolbar.search.value": "Valeur",
"toolbar.format.category.base": "Base",
"toolbar.format.category.dec": "Décimal",
"toolbar.format.category.float": "Virgule flottante",
"toolbar.format.signed": "Signé",
"toolbar.format.unsigned": "Non signé",
"toolbar.format.half": "Demi-précision (16 bits)",
"toolbar.format.float": "Simple précision (32 bits)",
"toolbar.format.double": "Double précision (64 bits)",
"toolbar.location.to-begin": "Déplacer au début",
"toolbar.location.to-end": "Déplacer à la fin",
"toolbar.location.to-next-change": "Aller au prochain changement",
"toolbar.location.to-prev-change": "Aller au changement précédent",
"toolbar.location.make-location": "Créer un nouveau repère",
"toolbar.location.clear-location": "Effacer tous les repères",
"toolbar.location.clear-location-dialog": "Êtes-vous sûr de vouloir effacer tous les repères?",
"context-menu.cannot-join-repeat-group": "Le signal actuel est déjà dans ce groupe",
"toolbar.no-result": "Aucun résultat",
"toolbar.search.value.already-to-head": "Déjà au début",
"toolbar.search.value.already-to-tail": "Déjà à la fin",
"toolbar.search.value.searching": "Recherche en cours",
"pivot.context.delete": "Supprimer le repère",
"pivot.context.display-axis": "Créer un axe relatif",
"pivot.context.cancel-axis": "Annuler l'axe relatif",
"setting.appearance.pivot-color": "Couleur du repère",
"setting.appearance.moving-pivot": "Repère mobile",
"setting.appearance.user-pivot": "Repère utilisateur",
"setting.appearance.system-pivot": "Repère système",
"confirm": "Confirmer",
"cancel": "Annuler",
"tips": "Conseils",
"filemenu.save-view": "Enregistrer le fichier de vue",
"filemenu.save-as-view": "Enregistrer la vue sous",
"filemenu.load-view": "Charger le fichier de vue",
"filemenu.auto-save": "Sauvegarde automatique",
"current-version": "Version actuelle",
"setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Vcd Viewer.",
"resources": "Ressources",
"tools": "Outils",
"prompts": "Invites",
"interaction-test": "Test interactif",
"setting": "Paramètres",
"about": "À propos",
"connected": "Connecté",
"disconnected": "Déconnecté",
"debug": "Déboguer",
"connect": "Connexion",
"setting.general-color-setting": "Paramètres de couleur généraux",
"choose-a-project-debug": "Sélectionnez un projet à déboguer",
"model": "Modèle",
"server-provider": "Fournisseur de services",
"api-root-url": "Chemin racine de l'API",
"api-token": "Clé API",
"connection-method": "Méthode de connexion",
"command": "Commande",
"env-var": "Variables d'environnement",
"log": "Journaux",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter",
"reset": "Réinitialiser",
"read-resource": "Lire les ressources",
"enter": "Entrée",
"blank-test": "Test vide",
"connect.appearance.reconnect": "Reconnexion",
"connect.appearance.connect": "Connexion",
"response": "Réponse",
"refresh": "Rafraîchir",
"read-prompt": "Lire l'invite",
"execute-tool": "Exécuter",
"save": "Enregistrer",
"send": "Envoyer",
"server-not-support-statistic": "Le fournisseur que vous utilisez ne prend pas en charge les statistiques temporairement",
"answer-at": "Répondu le",
"input-token": "Entrée",
"output-token": "Sortie",
"total": "Total",
"cache-hit-ratio": "Taux de réussite du cache",
"success-save": "Enregistré avec succès",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?",
"reserve-one-last-model": "Conservez au moins un modèle",
"edit": "Modifier",
"delete": "Supprimer",
"test": "Test",
"add-new-server": "Ajouter un service",
"choose-model": "Sélectionner le modèle",
"system-prompt": "Invite système",
"tool-use": "Utilisation d'outils",
"websearch": "Recherche sur Internet",
"temperature-parameter": "Paramètre de température",
"context-length": "Longueur du contexte",
"system-prompt.placeholder": "Entrez l'invite système (par exemple : Vous êtes un assistant professionnel de développement front-end, répondez en français)",
"precise": "Précis",
"moderate": "Équilibre",
"creative": "Créativité",
"single-dialog": "Dialogue en un tour",
"multi-dialog": "Conversation multi-tours",
"press-and-run": "Tapez une question pour commencer le test",
"connect-sigature": "Signature de connexion",
"finish-refresh": "Actualisation terminée",
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée",
"enter-message-dot": "Entrez un message...",
"generate-answer": "Génération de la réponse",
"choose-presetting": "Sélectionner un préréglage",
"cwd": "Répertoire d'exécution",
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP",
"return": "Retour",
"error": "Erreur",
"feedback": "Retour",
"waiting-mcp-server": "En attente de la réponse du serveur MCP",
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse",
"proxy-server": "Serveur proxy",
"update-model-list": "Mettre à jour la liste des modèles",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion",
"connect-success": "Connexion réussie",
"connect-fail": "Échec de la connexion",
"preset": "Préréglage",
"openmcp-document": "Documentation officielle d'OpenMCP",
"star-our-project": "Star notre projet",
"document": "Documentation officielle",
"join-discussion": "Rejoindre le groupe de discussion",
"comment-for-us": "Écrivez un avis pour nous !",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
"error-parse-json": "Erreur d'analyse JSON :",
"enable-xml-wrapper": "تمكين تغليف تعليمات XML",
"tool-manage": "إدارة الأدوات",
"enable-all-tools": "تفعيل جميع الأدوات",
"disable-all-tools": "تعطيل جميع الأدوات",
"using-tool": "جاري استخدام الأداة",
"copy-success": "تم النسخ بنجاح",
"copy-fail": "فشل النسخ",
"copy": "نسخ",
"export": "تصدير",
"export-filename": "اسم ملف التصدير",
"how-to-use": "كيفية الاستخدام؟"
}

View File

@ -1,189 +1,186 @@
{
"module": "Modul",
"module": "Module",
"signal": "Signal",
"search-signal": "Signal suchen",
"language-setting": "Sprache",
"search-setting": "Suche",
"search-case-sensitivity": "Groß- und Kleinschreibung beachten",
"search-mode": "Suchmodus",
"search-scope": "Suchbereich",
"search-display-parent-only": "Nur übergeordnetes Modul anzeigen",
"search-nothing": "Keine Signale gefunden",
"signal-only": "Nur Signal",
"module-only": "Nur Modul",
"signal-module": "Signal + Modul",
"general-setting": "Allgemein",
"appearance-setting": "Aussehen",
"display-wave-height": "Höhe der Wellenformspur",
"display-signal-info-scope": "Informationen in der Seitenleiste anzeigen",
"display-signal-info-scope.width": "Bitbreite",
"display-signal-info-scope.parent": "Name des übergeordneten Moduls",
"wavecolor": "Standardwellenformfarbe",
"wavecolor.normal-bit": "Einzelbitwellenform",
"wavecolor.normal-vec": "Mehrfachbitwellenform",
"wavecolor.high-impedance": "Hohes Impedanzwellenform",
"wavecolor.unknown": "Unbekannte Zustandswellenform",
"operation-setting": "Operation",
"render-setting": "Rendering",
"render-animation": "Rendering-Animation aktivieren",
"usermanual": "Benutzerhandbuch",
"usermanual.left-right-scroll.caption": "Nach oben und unten bewegen",
"usermanual.up-down-scroll.caption": "Nach links und rechts bewegen",
"usermanual.xscale.caption": "Horizontal skalieren",
"loading": "Laden",
"context-menu.create-group": "Neue Gruppe erstellen",
"context-menu.join-group": "Einer bestehenden Gruppe beitreten",
"context-menu.change-color": "Farbe ändern",
"context-menu.delete": "Signal löschen",
"context-menu.delete-all-select": "Alle ausgewählten Signale löschen",
"context-menu.signal.name": "Signalname",
"context-menu.signal.type": "Signaltyp",
"context-menu.signal.width": "Signalbreite",
"context-menu.signal.dep": "Abhängigkeiten",
"context-menu.group.cancel": "Gruppierung abbrechen",
"context-menu.group.delete": "Gruppe löschen",
"context-menu.group.empty": "Keine verfügbaren Gruppen",
"context-menu.group.uname-group": "Unbenannte Gruppe",
"toolbar.modal.common-digital": "Digital",
"toolbar.modal.ladder-analog": "Analog (Treppe)",
"toolbar.modal.line-analog": "Analog (Linie)",
"toolbar.search.name": "Name",
"toolbar.search.value": "Wert",
"toolbar.format.category.base": "Basis",
"toolbar.format.category.dec": "Dezimal",
"toolbar.format.category.float": "Fließkomma",
"toolbar.format.signed": "Vorzeichenbehaftet",
"toolbar.format.unsigned": "Vorzeichenlos",
"toolbar.format.half": "Halbgenau (16 Bit)",
"toolbar.format.float": "Einfach genau (32 Bit)",
"toolbar.format.double": "Doppelt genau (64 Bit)",
"toolbar.location.to-begin": "Zum Anfang bewegen",
"toolbar.location.to-end": "Zum Ende bewegen",
"toolbar.location.to-next-change": "Zum nächsten Änderungspunkt bewegen",
"toolbar.location.to-prev-change": "Zum vorherigen Änderungspunkt bewegen",
"toolbar.location.make-location": "Neuen Ankerpunkt erstellen",
"toolbar.location.clear-location": "Alle Ankerpunkte löschen",
"toolbar.location.clear-location-dialog": "Sind Sie sicher, dass Sie alle Ankerpunkte löschen möchten?",
"context-menu.cannot-join-repeat-group": "Das aktuelle Signal ist bereits in dieser Gruppe",
"toolbar.no-result": "Kein Ergebnis",
"toolbar.search.value.already-to-head": "Bereits am Anfang",
"toolbar.search.value.already-to-tail": "Bereits am Ende",
"toolbar.search.value.searching": "Suche läuft",
"pivot.context.delete": "Ankerpunkt löschen",
"pivot.context.display-axis": "Relative Achse erstellen",
"pivot.context.cancel-axis": "Relative Achse abbrechen",
"setting.appearance.pivot-color": "Ankerpunktfarbe",
"setting.appearance.moving-pivot": "Beweglicher Ankerpunkt",
"setting.appearance.user-pivot": "Benutzerankerpunkt",
"setting.appearance.system-pivot": "Systemankerpunkt",
"confirm": "Bestätigen",
"cancel": "Abbrechen",
"tips": "Tipps",
"filemenu.save-view": "Ansicht speichern",
"filemenu.save-as-view": "Ansicht speichern unter",
"filemenu.load-view": "Ansicht laden",
"filemenu.auto-save": "Automatisches Speichern",
"current-version": "Aktuelle Version",
"setting.language.change-dialog": "Sie haben die Sprache auf {0} geändert. Wir empfehlen Ihnen, Vcd Viewer neu zu starten.",
"resources": "Ressourcen",
"tools": "Werkzeuge",
"prompts": "Eingabeaufforderungen",
"interaction-test": "Interaktiver Test",
"setting": "Einstellungen",
"about": "Über",
"connected": "Verbunden",
"disconnected": "Getrennt",
"debug": "Debuggen",
"connect": "Verbinden",
"setting.general-color-setting": "Allgemeine Farbeinstellungen",
"choose-a-project-debug": "Wählen Sie ein Projekt zum Debuggen aus",
"model": "Modell",
"server-provider": "Dienstanbieter",
"api-root-url": "API-Stammpfad",
"api-token": "API-Schlüssel",
"connection-method": "Verbindungsmethode",
"command": "Befehl",
"env-var": "Umgebungsvariablen",
"log": "Protokolle",
"warning.click-to-connect": "Bitte klicken Sie zuerst auf $1 links, um eine Verbindung herzustellen",
"reset": "Zurücksetzen",
"read-resource": "Ressourcen lesen",
"enter": "Eingabe",
"blank-test": "Leertest",
"connect.appearance.reconnect": "Neuverbindung",
"connect.appearance.connect": "Verbindung",
"response": "Antwort",
"refresh": "Aktualisieren",
"read-prompt": "Prompt lesen",
"execute-tool": "Ausführen",
"save": "Speichern",
"send": "Senden",
"server-not-support-statistic": "Der von Ihnen verwendete Anbieter unterstützt vorerst keine Statistiken",
"answer-at": "Beantwortet am",
"input-token": "Eingabe",
"output-token": "Ausgabe",
"total": "Gesamt",
"cache-hit-ratio": "Cache-Trefferquote",
"success-save": "Erfolgreich gespeichert",
"confirm-delete-model": "Modellanbieter wirklich löschen?",
"reserve-one-last-model": "Behalten Sie mindestens ein Modell",
"edit": "Bearbeiten",
"delete": "Löschen",
"search-signal": "Rechercher un signal",
"language-setting": "Langue",
"search-setting": "Recherche",
"search-case-sensitivity": "Sensibilité à la casse",
"search-mode": "Mode de recherche",
"search-scope": "Portée de la recherche",
"search-display-parent-only": "Afficher uniquement le module parent",
"search-nothing": "Aucun signal trouvé",
"signal-only": "Signal uniquement",
"module-only": "Module uniquement",
"signal-module": "Signal + Module",
"general-setting": "Général",
"appearance-setting": "Apparence",
"display-wave-height": "Hauteur de la piste d'onde",
"display-signal-info-scope": "Informations affichées dans la barre latérale",
"display-signal-info-scope.width": "Largeur de bit",
"display-signal-info-scope.parent": "Nom du module parent",
"wavecolor": "Couleur d'onde par défaut",
"wavecolor.normal-bit": "Onde à largeur unique",
"wavecolor.normal-vec": "Onde à largeur multiple",
"wavecolor.high-impedance": "Onde à haute impédance",
"wavecolor.unknown": "Onde à état inconnu",
"operation-setting": "Opérations",
"render-setting": "Rendu",
"render-animation": "Activer l'animation de rendu",
"usermanual": "Manuel utilisateur",
"usermanual.left-right-scroll.caption": "Déplacer vers le haut et le bas",
"usermanual.up-down-scroll.caption": "Déplacer vers la gauche et la droite",
"usermanual.xscale.caption": "Zoom horizontal",
"loading": "Chargement",
"context-menu.create-group": "Créer un groupe",
"context-menu.join-group": "Rejoindre un groupe existant",
"context-menu.change-color": "Changer la couleur",
"context-menu.delete": "Supprimer le signal",
"context-menu.delete-all-select": "Supprimer tous les signaux sélectionnés",
"context-menu.signal.name": "Nom du signal",
"context-menu.signal.type": "Type de signal",
"context-menu.signal.width": "Largeur du signal",
"context-menu.signal.dep": "Dépendances",
"context-menu.group.cancel": "Annuler le groupement",
"context-menu.group.delete": "Supprimer le groupe",
"context-menu.group.empty": "Aucun groupe disponible",
"context-menu.group.uname-group": "Groupe sans nom",
"toolbar.modal.common-digital": "Numérique",
"toolbar.modal.ladder-analog": "Analogique (échelle)",
"toolbar.modal.line-analog": "Analogique (ligne)",
"toolbar.search.name": "Nom",
"toolbar.search.value": "Valeur",
"toolbar.format.category.base": "Base",
"toolbar.format.category.dec": "Décimal",
"toolbar.format.category.float": "Virgule flottante",
"toolbar.format.signed": "Signé",
"toolbar.format.unsigned": "Non signé",
"toolbar.format.half": "Demi-précision (16 bits)",
"toolbar.format.float": "Simple précision (32 bits)",
"toolbar.format.double": "Double précision (64 bits)",
"toolbar.location.to-begin": "Déplacer au début",
"toolbar.location.to-end": "Déplacer à la fin",
"toolbar.location.to-next-change": "Aller au prochain changement",
"toolbar.location.to-prev-change": "Aller au changement précédent",
"toolbar.location.make-location": "Créer un nouveau repère",
"toolbar.location.clear-location": "Effacer tous les repères",
"toolbar.location.clear-location-dialog": "Êtes-vous sûr de vouloir effacer tous les repères?",
"context-menu.cannot-join-repeat-group": "Le signal actuel est déjà dans ce groupe",
"toolbar.no-result": "Aucun résultat",
"toolbar.search.value.already-to-head": "Déjà au début",
"toolbar.search.value.already-to-tail": "Déjà à la fin",
"toolbar.search.value.searching": "Recherche en cours",
"pivot.context.delete": "Supprimer le repère",
"pivot.context.display-axis": "Créer un axe relatif",
"pivot.context.cancel-axis": "Annuler l'axe relatif",
"setting.appearance.pivot-color": "Couleur du repère",
"setting.appearance.moving-pivot": "Repère mobile",
"setting.appearance.user-pivot": "Repère utilisateur",
"setting.appearance.system-pivot": "Repère système",
"confirm": "Confirmer",
"cancel": "Annuler",
"tips": "Conseils",
"filemenu.save-view": "Enregistrer le fichier de vue",
"filemenu.save-as-view": "Enregistrer la vue sous",
"filemenu.load-view": "Charger le fichier de vue",
"filemenu.auto-save": "Sauvegarde automatique",
"current-version": "Version actuelle",
"setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Vcd Viewer.",
"resources": "Ressources",
"tools": "Outils",
"prompts": "Invites",
"interaction-test": "Test interactif",
"setting": "Paramètres",
"about": "À propos",
"connected": "Connecté",
"disconnected": "Déconnecté",
"debug": "Déboguer",
"connect": "Connexion",
"setting.general-color-setting": "Paramètres de couleur généraux",
"choose-a-project-debug": "Sélectionnez un projet à déboguer",
"model": "Modèle",
"server-provider": "Fournisseur de services",
"api-root-url": "Chemin racine de l'API",
"api-token": "Clé API",
"connection-method": "Méthode de connexion",
"command": "Commande",
"env-var": "Variables d'environnement",
"log": "Journaux",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter",
"reset": "Réinitialiser",
"read-resource": "Lire les ressources",
"enter": "Entrée",
"blank-test": "Test vide",
"connect.appearance.reconnect": "Reconnexion",
"connect.appearance.connect": "Connexion",
"response": "Réponse",
"refresh": "Rafraîchir",
"read-prompt": "Lire l'invite",
"execute-tool": "Exécuter",
"save": "Enregistrer",
"send": "Envoyer",
"server-not-support-statistic": "Le fournisseur que vous utilisez ne prend pas en charge les statistiques temporairement",
"answer-at": "Répondu le",
"input-token": "Entrée",
"output-token": "Sortie",
"total": "Total",
"cache-hit-ratio": "Taux de réussite du cache",
"success-save": "Enregistré avec succès",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?",
"reserve-one-last-model": "Conservez au moins un modèle",
"edit": "Modifier",
"delete": "Supprimer",
"test": "Test",
"add-new-server": "Dienst hinzufügen",
"choose-model": "Modell auswählen",
"system-prompt": "Systemaufforderung",
"tool-use": "Werkzeugnutzung",
"websearch": "Internetsuche",
"temperature-parameter": "Temperaturparameter",
"context-length": "Kontextlänge",
"system-prompt.placeholder": "Geben Sie den System-Prompt ein (z. B.: Sie sind ein professioneller Frontend-Entwicklungsassistent, antworten Sie auf Deutsch)",
"precise": "Präzise",
"moderate": "Gleichgewicht",
"creative": "Kreativität",
"single-dialog": "Einzelrunden-Dialog",
"multi-dialog": "Mehrrundengespräch",
"press-and-run": "Geben Sie eine Frage ein, um den Test zu starten",
"connect-sigature": "Verbindungssignatur",
"finish-refresh": "Aktualisierung abgeschlossen",
"add-system-prompt.name-placeholder": "Titel für benutzerdefinierte Eingabeaufforderung",
"enter-message-dot": "Nachricht eingeben...",
"generate-answer": "Antwort wird generiert",
"choose-presetting": "Voreinstellung auswählen",
"cwd": "Ausführungsverzeichnis",
"mcp-server-timeout": "Maximale Aufrufzeit des MCP-Tools",
"return": "Zurück",
"error": "Fehler",
"feedback": "Feedback",
"waiting-mcp-server": "Warten auf Antwort vom MCP-Server",
"parallel-tool-calls": "Erlauben Sie dem Modell, mehrere Tools in einer einzigen Antwort aufzurufen",
"proxy-server": "Proxy-Server",
"update-model-list": "Modellliste aktualisieren",
"ensure-delete-connection": "Möchten Sie die Verbindung $1 wirklich löschen?",
"choose-connection-type": "Bitte wählen Sie den Verbindungstyp",
"please-enter-connection-command": "Bitte geben Sie den Verbindungsbefehl ein",
"example-mcp-run": "Beispiel: mcp run main.py",
"please-enter-cwd": "Bitte geben Sie das Arbeitsverzeichnis (cwd) ein, optional",
"please-enter-cwd-placeholder": "Zum Beispiel: /path/to/project",
"please-enter-url": "Bitte geben Sie die Verbindungs-URL ein",
"example-as": "Zum Beispiel:",
"enter-optional-oauth": "Bitte geben Sie das OAuth-Token ein, optional",
"quick-start": "Einführung",
"read-document": "Dokumentation lesen",
"report-issue": "Problem melden",
"join-project": "Am Projekt teilnehmen",
"comment-plugin": "Kommentar-Plugin",
"preset-env-sync.success": "Vordefinierte Umgebungsvariablen synchronisiert",
"preset-env-sync.fail": "Synchronisierung der vordefinierten Umgebungsvariablen fehlgeschlagen",
"drag-to-fill-connect-parameters": "Ziehen Sie die Verbindungsparameter",
"connect-success": "Erfolgreich verbunden",
"connect-fail": "Verbindungsfehler",
"preset": "Voreinstellung",
"openmcp-document": "OpenMCP offizielle Dokumentation",
"star-our-project": "Star unser Projekt",
"document": "Offizielle Dokumentation",
"join-discussion": "Diskussionsgruppe beitreten",
"comment-for-us": "Schreiben Sie eine Bewertung für uns!",
"openmcp-developed-by": "OpenMCP Client {version} entwickelt von {author}",
"error-parse-json": "JSON-Parsing-Fehler:"
"add-new-server": "Ajouter un service",
"choose-model": "Sélectionner le modèle",
"system-prompt": "Invite système",
"tool-use": "Utilisation d'outils",
"websearch": "Recherche sur Internet",
"temperature-parameter": "Paramètre de température",
"context-length": "Longueur du contexte",
"system-prompt.placeholder": "Entrez l'invite système (par exemple : Vous êtes un assistant professionnel de développement front-end, répondez en français)",
"precise": "Précis",
"moderate": "Équilibre",
"creative": "Créativité",
"single-dialog": "Dialogue en un tour",
"multi-dialog": "Conversation multi-tours",
"press-and-run": "Tapez une question pour commencer le test",
"connect-sigature": "Signature de connexion",
"finish-refresh": "Actualisation terminée",
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée",
"enter-message-dot": "Entrez un message...",
"generate-answer": "Génération de la réponse",
"choose-presetting": "Sélectionner un préréglage",
"cwd": "Répertoire d'exécution",
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP",
"return": "Retour",
"error": "Erreur",
"feedback": "Retour",
"waiting-mcp-server": "En attente de la réponse du serveur MCP",
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse",
"proxy-server": "Serveur proxy",
"update-model-list": "Mettre à jour la liste des modèles",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion",
"connect-success": "Connexion réussie",
"connect-fail": "Échec de la connexion",
"preset": "Préréglage",
"openmcp-document": "Documentation officielle d'OpenMCP",
"star-our-project": "Star notre projet",
"document": "Documentation officielle",
"join-discussion": "Rejoindre le groupe de discussion",
"comment-for-us": "Écrivez un avis pour nous !",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
"error-parse-json": "Erreur d'analyse JSON :",
"enable-xml-wrapper": "XML-Befehlsverpackung aktivieren",
"tool-manage": "Werkzeugverwaltung",
"enable-all-tools": "Alle Tools aktivieren",
"disable-all-tools": "Alle Tools deaktivieren",
"using-tool": "Werkzeug wird verwendet",
"copy-success": "Erfolgreich kopiert",
"copy-fail": "Kopieren fehlgeschlagen",
"copy": "Kopieren",
"export": "Exportieren",
"export-filename": "Exportdateiname",
"how-to-use": "Wie benutzt man?"
}

View File

@ -1,175 +1,186 @@
{
"module": "Modules",
"signal": "Signals",
"search-signal": "Search Signal",
"language-setting": "Language",
"search-setting": "Search",
"search-case-sensitivity": "Case Sensitivity",
"search-mode": "search mode",
"search-scope": "Search Scope",
"search-display-parent-only": "Display Parent Module Only",
"search-nothing": "Find Nothing",
"signal-only": "Signal Only",
"module-only": "Module Only",
"module": "Module",
"signal": "Signal",
"search-signal": "Rechercher un signal",
"language-setting": "Langue",
"search-setting": "Recherche",
"search-case-sensitivity": "Sensibilité à la casse",
"search-mode": "Mode de recherche",
"search-scope": "Portée de la recherche",
"search-display-parent-only": "Afficher uniquement le module parent",
"search-nothing": "Aucun signal trouvé",
"signal-only": "Signal uniquement",
"module-only": "Module uniquement",
"signal-module": "Signal + Module",
"general-setting": "General",
"appearance-setting": "Appearance",
"display-wave-height": "Height of Wave Track",
"display-signal-info-scope": "Info displayed in sidebar",
"display-signal-info-scope.width": "width",
"display-signal-info-scope.parent": "parent",
"wavecolor": "default color of wave",
"wavecolor.normal-bit": "wave of one width",
"wavecolor.normal-vec": "wave of more than one width",
"wavecolor.high-impedance": "wave of high impedance",
"wavecolor.unknown": "wave of unknown",
"operation-setting": "Operation",
"render-setting": "Render",
"render-animation": "enable rendering animation",
"usermanual": "User Manual",
"usermanual.left-right-scroll.caption": "move up and down",
"usermanual.up-down-scroll.caption": "move left and right",
"usermanual.xscale.caption": "scale along x axis",
"loading": "loading",
"context-menu.create-group": "create group",
"context-menu.join-group": "join created group",
"context-menu.change-color": "change color",
"context-menu.delete": "delete signal",
"context-menu.delete-all-select": "delete all the selected signals",
"context-menu.signal.name": "signal name",
"context-menu.signal.type": "signal type",
"context-menu.signal.width": "signal width",
"context-menu.signal.dep": "signal dependency",
"context-menu.group.cancel": "cancel group",
"context-menu.group.delete": "delete group",
"context-menu.group.empty": "No groups are currently available",
"context-menu.group.uname-group": "unamed group",
"toolbar.modal.common-digital": "Digital",
"toolbar.modal.ladder-analog": "Analog (Ladder)",
"toolbar.modal.line-analog": "Analog (Line)",
"toolbar.search.name": "Name",
"toolbar.search.value": "Value",
"general-setting": "Général",
"appearance-setting": "Apparence",
"display-wave-height": "Hauteur de la piste d'onde",
"display-signal-info-scope": "Informations affichées dans la barre latérale",
"display-signal-info-scope.width": "Largeur de bit",
"display-signal-info-scope.parent": "Nom du module parent",
"wavecolor": "Couleur d'onde par défaut",
"wavecolor.normal-bit": "Onde à largeur unique",
"wavecolor.normal-vec": "Onde à largeur multiple",
"wavecolor.high-impedance": "Onde à haute impédance",
"wavecolor.unknown": "Onde à état inconnu",
"operation-setting": "Opérations",
"render-setting": "Rendu",
"render-animation": "Activer l'animation de rendu",
"usermanual": "Manuel utilisateur",
"usermanual.left-right-scroll.caption": "Déplacer vers le haut et le bas",
"usermanual.up-down-scroll.caption": "Déplacer vers la gauche et la droite",
"usermanual.xscale.caption": "Zoom horizontal",
"loading": "Chargement",
"context-menu.create-group": "Créer un groupe",
"context-menu.join-group": "Rejoindre un groupe existant",
"context-menu.change-color": "Changer la couleur",
"context-menu.delete": "Supprimer le signal",
"context-menu.delete-all-select": "Supprimer tous les signaux sélectionnés",
"context-menu.signal.name": "Nom du signal",
"context-menu.signal.type": "Type de signal",
"context-menu.signal.width": "Largeur du signal",
"context-menu.signal.dep": "Dépendances",
"context-menu.group.cancel": "Annuler le groupement",
"context-menu.group.delete": "Supprimer le groupe",
"context-menu.group.empty": "Aucun groupe disponible",
"context-menu.group.uname-group": "Groupe sans nom",
"toolbar.modal.common-digital": "Numérique",
"toolbar.modal.ladder-analog": "Analogique (échelle)",
"toolbar.modal.line-analog": "Analogique (ligne)",
"toolbar.search.name": "Nom",
"toolbar.search.value": "Valeur",
"toolbar.format.category.base": "Base",
"toolbar.format.category.dec": "Decimal",
"toolbar.format.category.float": "Float",
"toolbar.format.signed": "Signed",
"toolbar.format.unsigned": "Unsigned",
"toolbar.format.half": "Half (16bit)",
"toolbar.format.float": "Float (32bit)",
"toolbar.format.double": "Double (64bit)",
"toolbar.location.to-begin": "Move to Beginning",
"toolbar.location.to-end": "Move to End",
"toolbar.location.to-next-change": "Go to Next Change Edge",
"toolbar.location.to-prev-change": "Go to Previous Change Edge",
"toolbar.location.make-location": "Create New Pivot",
"toolbar.location.clear-location": "Clear All Pivots",
"toolbar.location.clear-location-dialog": "Are you sure to clear all the pivots ?",
"context-menu.cannot-join-repeat-group": "current signal is already contained in this group",
"toolbar.no-result": "No Result",
"toolbar.search.value.already-to-head": "already to head",
"toolbar.search.value.already-to-tail": "already to tail",
"toolbar.search.value.searching": "searching",
"pivot.context.delete": "delete pivot",
"pivot.context.display-axis": "create relative axis",
"pivot.context.cancel-axis": "cancel relative axis",
"setting.appearance.pivot-color": "pivot color",
"setting.appearance.moving-pivot": "moving pivot",
"setting.appearance.user-pivot": "user pivot",
"setting.appearance.system-pivot": "system pivot",
"confirm": "confirm",
"cancel": "cancel",
"tips": "Tips",
"filemenu.save-view": "保存视图文件",
"filemenu.save-as-view": "另存为视图文件",
"filemenu.load-view": "导入视图文件",
"filemenu.auto-save": "自动保存",
"current-version": "current version",
"setting.language.change-dialog": "You have changed the language to {0}, we recommend restarting Vcd Viewer.",
"resources": "Resources",
"tools": "Tools",
"prompts": "Prompts",
"interaction-test": "Interactive Test",
"setting": "Settings",
"about": "About",
"connected": "Connected",
"disconnected": "Disconnected",
"debug": "Debug",
"connect": "Connect",
"setting.general-color-setting": "General Color Settings",
"choose-a-project-debug": "Select a project to debug",
"model": "Model",
"server-provider": "Service Provider",
"api-root-url": "Base Url",
"api-token": "API key",
"connection-method": "Connection method",
"command": "Command",
"env-var": "Environment variables",
"log": "Logs",
"warning.click-to-connect": "Please first click on $1 on the left to connect",
"reset": "Reset",
"read-resource": "Read resources",
"enter": "Input",
"blank-test": "Blank test",
"connect.appearance.reconnect": "Reconnect",
"connect.appearance.connect": "Connection",
"response": "Response",
"refresh": "Refresh",
"read-prompt": "Read prompt",
"execute-tool": "Run",
"save": "Save",
"send": "Send",
"server-not-support-statistic": "The vendor you are using does not support statistics temporarily",
"answer-at": "Answered on",
"input-token": "Input",
"output-token": "Output",
"toolbar.format.category.dec": "Décimal",
"toolbar.format.category.float": "Virgule flottante",
"toolbar.format.signed": "Signé",
"toolbar.format.unsigned": "Non signé",
"toolbar.format.half": "Demi-précision (16 bits)",
"toolbar.format.float": "Simple précision (32 bits)",
"toolbar.format.double": "Double précision (64 bits)",
"toolbar.location.to-begin": "Déplacer au début",
"toolbar.location.to-end": "Déplacer à la fin",
"toolbar.location.to-next-change": "Aller au prochain changement",
"toolbar.location.to-prev-change": "Aller au changement précédent",
"toolbar.location.make-location": "Créer un nouveau repère",
"toolbar.location.clear-location": "Effacer tous les repères",
"toolbar.location.clear-location-dialog": "Êtes-vous sûr de vouloir effacer tous les repères?",
"context-menu.cannot-join-repeat-group": "Le signal actuel est déjà dans ce groupe",
"toolbar.no-result": "Aucun résultat",
"toolbar.search.value.already-to-head": "Déjà au début",
"toolbar.search.value.already-to-tail": "Déjà à la fin",
"toolbar.search.value.searching": "Recherche en cours",
"pivot.context.delete": "Supprimer le repère",
"pivot.context.display-axis": "Créer un axe relatif",
"pivot.context.cancel-axis": "Annuler l'axe relatif",
"setting.appearance.pivot-color": "Couleur du repère",
"setting.appearance.moving-pivot": "Repère mobile",
"setting.appearance.user-pivot": "Repère utilisateur",
"setting.appearance.system-pivot": "Repère système",
"confirm": "Confirmer",
"cancel": "Annuler",
"tips": "Conseils",
"filemenu.save-view": "Enregistrer le fichier de vue",
"filemenu.save-as-view": "Enregistrer la vue sous",
"filemenu.load-view": "Charger le fichier de vue",
"filemenu.auto-save": "Sauvegarde automatique",
"current-version": "Version actuelle",
"setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Vcd Viewer.",
"resources": "Ressources",
"tools": "Outils",
"prompts": "Invites",
"interaction-test": "Test interactif",
"setting": "Paramètres",
"about": "À propos",
"connected": "Connecté",
"disconnected": "Déconnecté",
"debug": "Déboguer",
"connect": "Connexion",
"setting.general-color-setting": "Paramètres de couleur généraux",
"choose-a-project-debug": "Sélectionnez un projet à déboguer",
"model": "Modèle",
"server-provider": "Fournisseur de services",
"api-root-url": "Chemin racine de l'API",
"api-token": "Clé API",
"connection-method": "Méthode de connexion",
"command": "Commande",
"env-var": "Variables d'environnement",
"log": "Journaux",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter",
"reset": "Réinitialiser",
"read-resource": "Lire les ressources",
"enter": "Entrée",
"blank-test": "Test vide",
"connect.appearance.reconnect": "Reconnexion",
"connect.appearance.connect": "Connexion",
"response": "Réponse",
"refresh": "Rafraîchir",
"read-prompt": "Lire l'invite",
"execute-tool": "Exécuter",
"save": "Enregistrer",
"send": "Envoyer",
"server-not-support-statistic": "Le fournisseur que vous utilisez ne prend pas en charge les statistiques temporairement",
"answer-at": "Répondu le",
"input-token": "Entrée",
"output-token": "Sortie",
"total": "Total",
"cache-hit-ratio": "Cache hit rate",
"success-save": "Successfully saved",
"confirm-delete-model": "Are you sure you want to delete the model provider?",
"reserve-one-last-model": "Keep at least one model",
"edit": "Edit",
"delete": "Delete",
"cache-hit-ratio": "Taux de réussite du cache",
"success-save": "Enregistré avec succès",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?",
"reserve-one-last-model": "Conservez au moins un modèle",
"edit": "Modifier",
"delete": "Supprimer",
"test": "Test",
"add-new-server": "Add service",
"choose-model": "Select model",
"system-prompt": "System prompt",
"tool-use": "Tool usage",
"websearch": "Web search",
"temperature-parameter": "Temperature parameter",
"context-length": "Context length",
"system-prompt.placeholder": "Enter the system prompt (e.g.: You are a professional front-end development assistant, answer in English)",
"precise": "Precise",
"moderate": "Balance",
"creative": "Creativity",
"single-dialog": "Single-round dialogue",
"multi-dialog": "Multi-turn conversation",
"press-and-run": "Type a question to start the test",
"connect-sigature": "Connection signature",
"finish-refresh": "Refresh completed",
"add-system-prompt.name-placeholder": "Title for custom prompt",
"enter-message-dot": "Enter message...",
"generate-answer": "Generating answer",
"choose-presetting": "Select preset",
"cwd": "Execution directory",
"mcp-server-timeout": "Maximum call time of MCP tool",
"return": "Back",
"error": "Error",
"feedback": "Feedback",
"waiting-mcp-server": "Waiting for MCP server response",
"parallel-tool-calls": "Allow the model to call multiple tools in a single reply",
"proxy-server": "Proxy server",
"update-model-list": "Update model list",
"preset-env-sync.success": "Preset environment variables synchronized",
"preset-env-sync.fail": "Preset environment variables synchronization failed",
"drag-to-fill-connect-parameters": "Drag to fill connection parameters",
"connect-success": "Connected successfully",
"connect-fail": "Connection failed",
"preset": "Preset",
"openmcp-document": "OpenMCP official documentation",
"star-our-project": "Star our project",
"document": "Official documentation",
"join-discussion": "Join the discussion group",
"comment-for-us": "Write a review for us!",
"openmcp-developed-by": "OpenMCP Client {version} developed by {author}",
"error-parse-json": "JSON parsing error:"
"add-new-server": "Ajouter un service",
"choose-model": "Sélectionner le modèle",
"system-prompt": "Invite système",
"tool-use": "Utilisation d'outils",
"websearch": "Recherche sur Internet",
"temperature-parameter": "Paramètre de température",
"context-length": "Longueur du contexte",
"system-prompt.placeholder": "Entrez l'invite système (par exemple : Vous êtes un assistant professionnel de développement front-end, répondez en français)",
"precise": "Précis",
"moderate": "Équilibre",
"creative": "Créativité",
"single-dialog": "Dialogue en un tour",
"multi-dialog": "Conversation multi-tours",
"press-and-run": "Tapez une question pour commencer le test",
"connect-sigature": "Signature de connexion",
"finish-refresh": "Actualisation terminée",
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée",
"enter-message-dot": "Entrez un message...",
"generate-answer": "Génération de la réponse",
"choose-presetting": "Sélectionner un préréglage",
"cwd": "Répertoire d'exécution",
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP",
"return": "Retour",
"error": "Erreur",
"feedback": "Retour",
"waiting-mcp-server": "En attente de la réponse du serveur MCP",
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse",
"proxy-server": "Serveur proxy",
"update-model-list": "Mettre à jour la liste des modèles",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion",
"connect-success": "Connexion réussie",
"connect-fail": "Échec de la connexion",
"preset": "Préréglage",
"openmcp-document": "Documentation officielle d'OpenMCP",
"star-our-project": "Star notre projet",
"document": "Documentation officielle",
"join-discussion": "Rejoindre le groupe de discussion",
"comment-for-us": "Écrivez un avis pour nous !",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
"error-parse-json": "Erreur d'analyse JSON :",
"enable-xml-wrapper": "Enable XML command wrapping",
"tool-manage": "Tool Management",
"enable-all-tools": "Activate all tools",
"disable-all-tools": "Disable all tools",
"using-tool": "Using tool",
"copy-success": "Copied successfully",
"copy-fail": "Copy failed",
"copy": "Copy",
"export": "Export",
"export-filename": "Export file name",
"how-to-use": "How to use?"
}

View File

@ -159,20 +159,6 @@
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse",
"proxy-server": "Serveur proxy",
"update-model-list": "Mettre à jour la liste des modèles",
"ensure-delete-connection": "Êtes-vous sûr de vouloir supprimer la connexion $1 ?",
"choose-connection-type": "Veuillez sélectionner le type de connexion",
"please-enter-connection-command": "Veuillez saisir la commande de connexion",
"example-mcp-run": "Par exemple : mcp run main.py",
"please-enter-cwd": "Veuillez entrer le répertoire de travail (cwd), facultatif",
"please-enter-cwd-placeholder": "Par exemple : /path/to/project",
"please-enter-url": "Veuillez saisir l'URL de connexion",
"example-as": "Par exemple :",
"enter-optional-oauth": "Veuillez entrer le jeton OAuth, facultatif",
"quick-start": "Premiers pas",
"read-document": "Lire la documentation",
"report-issue": "Signaler un problème",
"join-project": "Participer au projet",
"comment-plugin": "Plugin de commentaires",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion",
@ -185,5 +171,16 @@
"join-discussion": "Rejoindre le groupe de discussion",
"comment-for-us": "Écrivez un avis pour nous !",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
"error-parse-json": "Erreur d'analyse JSON :"
"error-parse-json": "Erreur d'analyse JSON :",
"enable-xml-wrapper": "Activer l'encapsulation de commande XML",
"tool-manage": "Gestion des outils",
"enable-all-tools": "Activer tous les outils",
"disable-all-tools": "Désactiver tous les outils",
"using-tool": "Utilisation de l'outil",
"copy-success": "Copié avec succès",
"copy-fail": "Échec de la copie",
"copy": "Copier",
"export": "Exporter",
"export-filename": "Nom du fichier d'exportation",
"how-to-use": "Comment utiliser ?"
}

View File

@ -1,175 +1,186 @@
{
"module": "モジュール",
"signal": "信号",
"search-signal": "信号を検索",
"language-setting": "言語",
"search-setting": "検索",
"search-case-sensitivity": "大文字と小文字を区別",
"search-mode": "検索モード",
"search-scope": "検索範囲",
"search-display-parent-only": "親モジュールのみ表示",
"search-nothing": "信号が見つかりませんでした",
"signal-only": "信号のみ",
"module-only": "モジュールのみ",
"signal-module": "信号 + モジュール",
"general-setting": "一般",
"appearance-setting": "外観",
"display-wave-height": "波形トラックの高さ",
"display-signal-info-scope": "サイドバーに表示する情報",
"display-signal-info-scope.width": "ビット幅",
"display-signal-info-scope.parent": "所属モジュール名",
"wavecolor": "デフォルトの波形色",
"wavecolor.normal-bit": "単位幅波形",
"wavecolor.normal-vec": "複数ビット幅波形",
"wavecolor.high-impedance": "ハイインピーダンス波形",
"wavecolor.unknown": "未知状態波形",
"operation-setting": "操作",
"render-setting": "レンダリング",
"render-animation": "レンダリングアニメーションを有効にする",
"usermanual": "使用説明",
"usermanual.left-right-scroll.caption": "上下に移動",
"usermanual.up-down-scroll.caption": "左右に移動",
"usermanual.xscale.caption": "横方向に拡大",
"loading": "読み込み中",
"context-menu.create-group": "新しいグループを作成",
"context-menu.join-group": "既存のグループに参加",
"context-menu.change-color": "色を変更",
"context-menu.delete": "信号を削除",
"context-menu.delete-all-select": "選択したすべての信号を削除",
"context-menu.signal.name": "信号名",
"context-menu.signal.type": "信号タイプ",
"context-menu.signal.width": "信号幅",
"context-menu.signal.dep": "依存関係",
"context-menu.group.cancel": "グループをキャンセル",
"context-menu.group.delete": "グループを削除",
"context-menu.group.empty": "利用可能なグループがありません",
"context-menu.group.uname-group": "名前なしグループ",
"toolbar.modal.common-digital": "デジタル",
"toolbar.modal.ladder-analog": "アナログ(階段)",
"toolbar.modal.line-analog": "アナログ(折れ線)",
"toolbar.search.name": "名前",
"toolbar.search.value": "値",
"toolbar.format.category.base": "基本",
"toolbar.format.category.dec": "10進数",
"toolbar.format.category.float": "浮動小数点数",
"toolbar.format.signed": "符号付き",
"toolbar.format.unsigned": "符号なし",
"toolbar.format.half": "半精度16ビット",
"toolbar.format.float": "単精度32ビット",
"toolbar.format.double": "倍精度64ビット",
"toolbar.location.to-begin": "先頭に移動",
"toolbar.location.to-end": "末尾に移動",
"toolbar.location.to-next-change": "次の変化点に移動",
"toolbar.location.to-prev-change": "前の変化点に移動",
"toolbar.location.make-location": "新しいピボットを作成",
"toolbar.location.clear-location": "すべてのピボットをクリア",
"toolbar.location.clear-location-dialog": "すべてのピボットをクリアしてもよろしいですか?",
"context-menu.cannot-join-repeat-group": "現在の信号はすでにこのグループに含まれています",
"toolbar.no-result": "結果なし",
"toolbar.search.value.already-to-head": "すでに先頭にいます",
"toolbar.search.value.already-to-tail": "すでに末尾にいます",
"toolbar.search.value.searching": "検索中",
"pivot.context.delete": "ピボットを削除",
"pivot.context.display-axis": "相対座標軸を作成",
"pivot.context.cancel-axis": "相対座標軸をキャンセル",
"setting.appearance.pivot-color": "ピボットの色",
"setting.appearance.moving-pivot": "移動ピボット",
"setting.appearance.user-pivot": "ユーザーピボット",
"setting.appearance.system-pivot": "システムピボット",
"confirm": "確認",
"cancel": "キャンセル",
"tips": "ヒント",
"filemenu.save-view": "ビューファイルを保存",
"filemenu.save-as-view": "ビューファイルとして保存",
"filemenu.load-view": "ビューファイルをインポート",
"filemenu.auto-save": "自動保存",
"current-version": "現在のバージョン",
"setting.language.change-dialog": "言語を{0}に変更しました。Vcd Viewerを再起動することをお勧めします。",
"resources": "リソース",
"tools": "ツール",
"prompts": "プロンプト",
"interaction-test": "インタラクティブテスト",
"setting": "設定",
"about": "について",
"connected": "接続済み",
"disconnected": "切断されました",
"debug": "デバッグ",
"connect": "接続",
"setting.general-color-setting": "一般的な色設定",
"choose-a-project-debug": "デバッグするプロジェクトを選択",
"model": "モデル",
"server-provider": "サービスプロバイダー",
"api-root-url": "APIルートパス",
"api-token": "APIキー",
"connection-method": "接続方法",
"command": "コマンド",
"env-var": "環境変数",
"log": "ログ",
"warning.click-to-connect": "まず左側の$1をクリックして接続してください",
"reset": "リセット",
"read-resource": "リソースを読み込む",
"enter": "入力",
"blank-test": "空白テスト",
"connect.appearance.reconnect": "再接続",
"connect.appearance.connect": "接続",
"response": "応答",
"refresh": "更新",
"read-prompt": "プロンプトを読み取る",
"execute-tool": "実行",
"save": "保存",
"send": "送信",
"server-not-support-statistic": "お使いのベンダーは一時的に統計情報をサポートしていません",
"answer-at": "解答日",
"input-token": "入力",
"output-token": "出力",
"total": "合計",
"cache-hit-ratio": "キャッシュヒット率",
"success-save": "正常に保存されました",
"confirm-delete-model": "このモデルプロバイダーを削除しますか?",
"reserve-one-last-model": "少なくとも1つのモデルを保持してください",
"edit": "編集",
"delete": "削除",
"test": "テスト",
"add-new-server": "サービスを追加",
"choose-model": "モデルを選択",
"system-prompt": "システムプロンプト",
"tool-use": "ツールの使用",
"websearch": "ウェブ検索",
"temperature-parameter": "温度パラメータ",
"context-length": "コンテキストの長さ",
"system-prompt.placeholder": "システムプロンプトを入力してください(例:あなたはプロのフロントエンド開発アシスタントで、日本語で答えます)",
"precise": "精密",
"moderate": "バランス",
"creative": "創造性",
"single-dialog": "単一ラウンドの対話",
"multi-dialog": "マルチターン会話",
"press-and-run": "テストを開始するには質問を入力してください",
"connect-sigature": "接続署名",
"finish-refresh": "更新が完了しました",
"add-system-prompt.name-placeholder": "カスタムプロンプトのタイトル",
"enter-message-dot": "メッセージを入力...",
"generate-answer": "回答を生成中",
"choose-presetting": "プリセットを選択",
"cwd": "実行ディレクトリ",
"mcp-server-timeout": "MCPツールの最大呼び出し時間",
"return": "戻る",
"error": "エラー",
"feedback": "フィードバック",
"waiting-mcp-server": "MCPサーバーの応答を待機中",
"parallel-tool-calls": "モデルが単一の返信で複数のツールを呼び出すことを許可する",
"proxy-server": "プロキシサーバー",
"update-model-list": "モデルリストを更新",
"preset-env-sync.success": "プリセット環境変数の同期が完了しました",
"preset-env-sync.fail": "プリセット環境変数の同期に失敗しました",
"drag-to-fill-connect-parameters": "接続パラメータを入力するためにドラッグしてください",
"connect-success": "接続に成功しました",
"connect-fail": "接続に失敗しました",
"preset": "プリセット",
"openmcp-document": "OpenMCP公式ドキュメント",
"star-our-project": "私たちのプロジェクトをスター",
"document": "公式ドキュメント",
"join-discussion": "ディスカッショングループに参加",
"comment-for-us": "私たちのためにレビューを書いてください!",
"openmcp-developed-by": "OpenMCP Client {version} は {author} によって開発されました",
"error-parse-json": "JSON解析エラー:"
"module": "Module",
"signal": "Signal",
"search-signal": "Rechercher un signal",
"language-setting": "Langue",
"search-setting": "Recherche",
"search-case-sensitivity": "Sensibilité à la casse",
"search-mode": "Mode de recherche",
"search-scope": "Portée de la recherche",
"search-display-parent-only": "Afficher uniquement le module parent",
"search-nothing": "Aucun signal trouvé",
"signal-only": "Signal uniquement",
"module-only": "Module uniquement",
"signal-module": "Signal + Module",
"general-setting": "Général",
"appearance-setting": "Apparence",
"display-wave-height": "Hauteur de la piste d'onde",
"display-signal-info-scope": "Informations affichées dans la barre latérale",
"display-signal-info-scope.width": "Largeur de bit",
"display-signal-info-scope.parent": "Nom du module parent",
"wavecolor": "Couleur d'onde par défaut",
"wavecolor.normal-bit": "Onde à largeur unique",
"wavecolor.normal-vec": "Onde à largeur multiple",
"wavecolor.high-impedance": "Onde à haute impédance",
"wavecolor.unknown": "Onde à état inconnu",
"operation-setting": "Opérations",
"render-setting": "Rendu",
"render-animation": "Activer l'animation de rendu",
"usermanual": "Manuel utilisateur",
"usermanual.left-right-scroll.caption": "Déplacer vers le haut et le bas",
"usermanual.up-down-scroll.caption": "Déplacer vers la gauche et la droite",
"usermanual.xscale.caption": "Zoom horizontal",
"loading": "Chargement",
"context-menu.create-group": "Créer un groupe",
"context-menu.join-group": "Rejoindre un groupe existant",
"context-menu.change-color": "Changer la couleur",
"context-menu.delete": "Supprimer le signal",
"context-menu.delete-all-select": "Supprimer tous les signaux sélectionnés",
"context-menu.signal.name": "Nom du signal",
"context-menu.signal.type": "Type de signal",
"context-menu.signal.width": "Largeur du signal",
"context-menu.signal.dep": "Dépendances",
"context-menu.group.cancel": "Annuler le groupement",
"context-menu.group.delete": "Supprimer le groupe",
"context-menu.group.empty": "Aucun groupe disponible",
"context-menu.group.uname-group": "Groupe sans nom",
"toolbar.modal.common-digital": "Numérique",
"toolbar.modal.ladder-analog": "Analogique (échelle)",
"toolbar.modal.line-analog": "Analogique (ligne)",
"toolbar.search.name": "Nom",
"toolbar.search.value": "Valeur",
"toolbar.format.category.base": "Base",
"toolbar.format.category.dec": "Décimal",
"toolbar.format.category.float": "Virgule flottante",
"toolbar.format.signed": "Signé",
"toolbar.format.unsigned": "Non signé",
"toolbar.format.half": "Demi-précision (16 bits)",
"toolbar.format.float": "Simple précision (32 bits)",
"toolbar.format.double": "Double précision (64 bits)",
"toolbar.location.to-begin": "Déplacer au début",
"toolbar.location.to-end": "Déplacer à la fin",
"toolbar.location.to-next-change": "Aller au prochain changement",
"toolbar.location.to-prev-change": "Aller au changement précédent",
"toolbar.location.make-location": "Créer un nouveau repère",
"toolbar.location.clear-location": "Effacer tous les repères",
"toolbar.location.clear-location-dialog": "Êtes-vous sûr de vouloir effacer tous les repères?",
"context-menu.cannot-join-repeat-group": "Le signal actuel est déjà dans ce groupe",
"toolbar.no-result": "Aucun résultat",
"toolbar.search.value.already-to-head": "Déjà au début",
"toolbar.search.value.already-to-tail": "Déjà à la fin",
"toolbar.search.value.searching": "Recherche en cours",
"pivot.context.delete": "Supprimer le repère",
"pivot.context.display-axis": "Créer un axe relatif",
"pivot.context.cancel-axis": "Annuler l'axe relatif",
"setting.appearance.pivot-color": "Couleur du repère",
"setting.appearance.moving-pivot": "Repère mobile",
"setting.appearance.user-pivot": "Repère utilisateur",
"setting.appearance.system-pivot": "Repère système",
"confirm": "Confirmer",
"cancel": "Annuler",
"tips": "Conseils",
"filemenu.save-view": "Enregistrer le fichier de vue",
"filemenu.save-as-view": "Enregistrer la vue sous",
"filemenu.load-view": "Charger le fichier de vue",
"filemenu.auto-save": "Sauvegarde automatique",
"current-version": "Version actuelle",
"setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Vcd Viewer.",
"resources": "Ressources",
"tools": "Outils",
"prompts": "Invites",
"interaction-test": "Test interactif",
"setting": "Paramètres",
"about": "À propos",
"connected": "Connecté",
"disconnected": "Déconnecté",
"debug": "Déboguer",
"connect": "Connexion",
"setting.general-color-setting": "Paramètres de couleur généraux",
"choose-a-project-debug": "Sélectionnez un projet à déboguer",
"model": "Modèle",
"server-provider": "Fournisseur de services",
"api-root-url": "Chemin racine de l'API",
"api-token": "Clé API",
"connection-method": "Méthode de connexion",
"command": "Commande",
"env-var": "Variables d'environnement",
"log": "Journaux",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter",
"reset": "Réinitialiser",
"read-resource": "Lire les ressources",
"enter": "Entrée",
"blank-test": "Test vide",
"connect.appearance.reconnect": "Reconnexion",
"connect.appearance.connect": "Connexion",
"response": "Réponse",
"refresh": "Rafraîchir",
"read-prompt": "Lire l'invite",
"execute-tool": "Exécuter",
"save": "Enregistrer",
"send": "Envoyer",
"server-not-support-statistic": "Le fournisseur que vous utilisez ne prend pas en charge les statistiques temporairement",
"answer-at": "Répondu le",
"input-token": "Entrée",
"output-token": "Sortie",
"total": "Total",
"cache-hit-ratio": "Taux de réussite du cache",
"success-save": "Enregistré avec succès",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?",
"reserve-one-last-model": "Conservez au moins un modèle",
"edit": "Modifier",
"delete": "Supprimer",
"test": "Test",
"add-new-server": "Ajouter un service",
"choose-model": "Sélectionner le modèle",
"system-prompt": "Invite système",
"tool-use": "Utilisation d'outils",
"websearch": "Recherche sur Internet",
"temperature-parameter": "Paramètre de température",
"context-length": "Longueur du contexte",
"system-prompt.placeholder": "Entrez l'invite système (par exemple : Vous êtes un assistant professionnel de développement front-end, répondez en français)",
"precise": "Précis",
"moderate": "Équilibre",
"creative": "Créativité",
"single-dialog": "Dialogue en un tour",
"multi-dialog": "Conversation multi-tours",
"press-and-run": "Tapez une question pour commencer le test",
"connect-sigature": "Signature de connexion",
"finish-refresh": "Actualisation terminée",
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée",
"enter-message-dot": "Entrez un message...",
"generate-answer": "Génération de la réponse",
"choose-presetting": "Sélectionner un préréglage",
"cwd": "Répertoire d'exécution",
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP",
"return": "Retour",
"error": "Erreur",
"feedback": "Retour",
"waiting-mcp-server": "En attente de la réponse du serveur MCP",
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse",
"proxy-server": "Serveur proxy",
"update-model-list": "Mettre à jour la liste des modèles",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion",
"connect-success": "Connexion réussie",
"connect-fail": "Échec de la connexion",
"preset": "Préréglage",
"openmcp-document": "Documentation officielle d'OpenMCP",
"star-our-project": "Star notre projet",
"document": "Documentation officielle",
"join-discussion": "Rejoindre le groupe de discussion",
"comment-for-us": "Écrivez un avis pour nous !",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
"error-parse-json": "Erreur d'analyse JSON :",
"enable-xml-wrapper": "XMLコマンドラッピングを有効にする",
"tool-manage": "ツール管理",
"enable-all-tools": "すべてのツールを有効にする",
"disable-all-tools": "すべてのツールを無効にする",
"using-tool": "ツール使用中",
"copy-success": "コピーしました",
"copy-fail": "コピーに失敗しました",
"copy": "コピー",
"export": "エクスポート",
"export-filename": "エクスポートファイル名",
"how-to-use": "使い方は?"
}

View File

@ -1,189 +1,186 @@
{
"module": "모듈",
"signal": "신호",
"search-signal": "신호 검색",
"language-setting": "언어",
"search-setting": "검색",
"search-case-sensitivity": "대소문자 구분",
"search-mode": "검색 모드",
"search-scope": "검색 범위",
"search-display-parent-only": "부모 모듈만 표시",
"search-nothing": "신호를 찾을 수 없습니다",
"signal-only": "신호만",
"module-only": "모듈만",
"signal-module": "신호 + 모듈",
"general-setting": "일반",
"appearance-setting": "외관",
"display-wave-height": "파형 트랙의 높이",
"display-signal-info-scope": "사이드바에 표시할 정보",
"display-signal-info-scope.width": "비트 너비",
"display-signal-info-scope.parent": "소속 모듈 이름",
"wavecolor": "기본 파형 색상",
"wavecolor.normal-bit": "단위 너비 파형",
"wavecolor.normal-vec": "다중 비트 너비 파형",
"wavecolor.high-impedance": "고임피던스 파형",
"wavecolor.unknown": "알 수 없는 상태 파형",
"operation-setting": "작업",
"render-setting": "렌더링",
"render-animation": "렌더링 애니메이션 활성화",
"usermanual": "사용 설명서",
"usermanual.left-right-scroll.caption": "상하 이동",
"usermanual.up-down-scroll.caption": "좌우 이동",
"usermanual.xscale.caption": "가로 확대",
"loading": "로딩 중",
"context-menu.create-group": "새 그룹 생성",
"context-menu.join-group": "기존 그룹에 참여",
"context-menu.change-color": "색상 변경",
"context-menu.delete": "신호 삭제",
"context-menu.delete-all-select": "선택한 모든 신호 삭제",
"context-menu.signal.name": "신호 이름",
"context-menu.signal.type": "신호 유형",
"context-menu.signal.width": "신호 너비",
"context-menu.signal.dep": "종속성",
"context-menu.group.cancel": "그룹 취소",
"context-menu.group.delete": "그룹 삭제",
"context-menu.group.empty": "사용 가능한 그룹이 없습니다",
"context-menu.group.uname-group": "이름 없는 그룹",
"toolbar.modal.common-digital": "디지털",
"toolbar.modal.ladder-analog": "아날로그 (사다리)",
"toolbar.modal.line-analog": "아날로그 (선)",
"toolbar.search.name": "이름",
"toolbar.search.value": "값",
"toolbar.format.category.base": "기본",
"toolbar.format.category.dec": "십진법",
"toolbar.format.category.float": "부동 소수점",
"toolbar.format.signed": "부호 있음",
"toolbar.format.unsigned": "부호 없음",
"toolbar.format.half": "반정밀도 (16비트)",
"toolbar.format.float": "단정밀도 (32비트)",
"toolbar.format.double": "배정밀도 (64비트)",
"toolbar.location.to-begin": "시작 부분으로 이동",
"toolbar.location.to-end": "끝 부분으로 이동",
"toolbar.location.to-next-change": "다음 변화로 이동",
"toolbar.location.to-prev-change": "이전 변화로 이동",
"toolbar.location.make-location": "새 표식 생성",
"toolbar.location.clear-location": "모든 표식 지우기",
"toolbar.location.clear-location-dialog": "모든 표식을 지우시겠습니까?",
"context-menu.cannot-join-repeat-group": "현재 신호가 이미 이 그룹에 있습니다",
"toolbar.no-result": "결과 없음",
"toolbar.search.value.already-to-head": "이미 시작 부분입니다",
"toolbar.search.value.already-to-tail": "이미 끝 부분입니다",
"toolbar.search.value.searching": "검색 중",
"pivot.context.delete": "표식 삭제",
"pivot.context.display-axis": "상대 좌표축 생성",
"pivot.context.cancel-axis": "상대 좌표축 취소",
"setting.appearance.pivot-color": "표식 색상",
"setting.appearance.moving-pivot": "이동 표식",
"setting.appearance.user-pivot": "사용자 표식",
"setting.appearance.system-pivot": "시스템 표식",
"confirm": "확인",
"cancel": "취소",
"tips": "팁",
"filemenu.save-view": "뷰 파일 저장",
"filemenu.save-as-view": "뷰 파일로 저장",
"filemenu.load-view": "뷰 파일 불러오기",
"filemenu.auto-save": "자동 저장",
"current-version": "현재 버전",
"setting.language.change-dialog": "언어를 {0}로 변경했습니다. Vcd Viewer를 다시 시작하는 것을 권장합니다.",
"resources": "자원",
"tools": "도구",
"prompts": "프롬프트",
"interaction-test": "인터랙티브 테스트",
"setting": "설정",
"about": "정보",
"connected": "연결됨",
"disconnected": "연결 해제됨",
"debug": "디버그",
"connect": "연결",
"setting.general-color-setting": "일반 색상 설정",
"choose-a-project-debug": "디버깅할 프로젝트 선택",
"model": "모델",
"server-provider": "서비스 제공자",
"api-root-url": "API 루트 경로",
"api-token": "API 키",
"connection-method": "연결 방법",
"command": "명령",
"env-var": "환경 변수",
"log": "로그",
"warning.click-to-connect": "먼저 왼쪽의 $1을 클릭하여 연결하십시오",
"reset": "재설정",
"read-resource": "리소스 읽기",
"enter": "입력",
"blank-test": "빈 테스트",
"connect.appearance.reconnect": "재연결",
"connect.appearance.connect": "연결",
"response": "응답",
"refresh": "새로 고침",
"read-prompt": "프롬프트 읽기",
"execute-tool": "실행",
"save": "저장",
"send": "보내기",
"server-not-support-statistic": "사용 중인 공급업체는 일시적으로 통계를 지원하지 않습니다",
"answer-at": "답변일",
"input-token": "입력",
"output-token": "출력",
"total": "총계",
"cache-hit-ratio": "캐시 적중률",
"success-save": "성공적으로 저장됨",
"confirm-delete-model": "이 모델 공급자를 삭제하시겠습니까?",
"reserve-one-last-model": "적어도 하나의 모델을 유지하세요",
"edit": "편집",
"delete": "삭제",
"test": "테스트",
"add-new-server": "서비스 추가",
"choose-model": "모델 선택",
"system-prompt": "시스템 프롬프트",
"tool-use": "도구 사용",
"websearch": "웹 검색",
"temperature-parameter": "온도 매개변수",
"context-length": "컨텍스트 길이",
"system-prompt.placeholder": "시스템 프롬프트를 입력하세요 (예: 당신은 전문 프론트엔드 개발 어시스턴트이며, 한국어로 답변합니다)",
"precise": "정확한",
"moderate": "균형",
"creative": "창의성",
"single-dialog": "단일 라운드 대화",
"multi-dialog": "다중 턴 대화",
"press-and-run": "테스트를 시작하려면 질문을 입력하세요",
"connect-sigature": "연결 서명",
"finish-refresh": "새로 고침 완료",
"add-system-prompt.name-placeholder": "사용자 지정 프롬프트 제목",
"enter-message-dot": "메시지를 입력하세요...",
"generate-answer": "답변 생성 중",
"choose-presetting": "프리셋 선택",
"cwd": "실행 디렉터리",
"mcp-server-timeout": "MCP 도구 최대 호출 시간",
"return": "돌아가기",
"error": "오류",
"feedback": "피드백",
"waiting-mcp-server": "MCP 서버 응답 대기 중",
"parallel-tool-calls": "모델이 단일 응답에서 여러 도구를 호출할 수 있도록 허용",
"proxy-server": "프록시 서버",
"update-model-list": "모델 목록 업데이트",
"ensure-delete-connection": "연결 $1을(를) 삭제하시겠습니까?",
"choose-connection-type": "연결 유형을 선택해 주세요",
"please-enter-connection-command": "연결 명령을 입력해 주세요",
"example-mcp-run": "예: mcp run main.py",
"please-enter-cwd": "작업 디렉토리 (cwd)를 입력하세요 (선택 사항)",
"please-enter-cwd-placeholder": "예: /path/to/project",
"please-enter-url": "연결 URL을 입력해 주세요",
"example-as": "예를 들어:",
"enter-optional-oauth": "OAuth 토큰을 입력하세요 (선택 사항)",
"quick-start": "시작하기",
"read-document": "문서 읽기",
"report-issue": "문제 신고",
"join-project": "프로젝트 참여",
"comment-plugin": "댓글 플러그인",
"preset-env-sync.success": "사전 설정 환경 변수 동기화 완료",
"preset-env-sync.fail": "사전 설정된 환경 변수 동기화 실패",
"drag-to-fill-connect-parameters": "연결 매개변수를 채우려면 드래그하세요",
"connect-success": "성공적으로 연결되었습니다",
"connect-fail": "연결 실패",
"preset": "프리셋",
"openmcp-document": "OpenMCP 공식 문서",
"star-our-project": "우리 프로젝트 스타",
"document": "공식 문서",
"join-discussion": "토론 그룹에 참여",
"comment-for-us": "우리를 위해 리뷰를 작성해 주세요!",
"openmcp-developed-by": "OpenMCP Client {version}은 {author}에 의해 개발되었습니다",
"error-parse-json": "JSON 구문 분석 오류:"
"module": "Module",
"signal": "Signal",
"search-signal": "Rechercher un signal",
"language-setting": "Langue",
"search-setting": "Recherche",
"search-case-sensitivity": "Sensibilité à la casse",
"search-mode": "Mode de recherche",
"search-scope": "Portée de la recherche",
"search-display-parent-only": "Afficher uniquement le module parent",
"search-nothing": "Aucun signal trouvé",
"signal-only": "Signal uniquement",
"module-only": "Module uniquement",
"signal-module": "Signal + Module",
"general-setting": "Général",
"appearance-setting": "Apparence",
"display-wave-height": "Hauteur de la piste d'onde",
"display-signal-info-scope": "Informations affichées dans la barre latérale",
"display-signal-info-scope.width": "Largeur de bit",
"display-signal-info-scope.parent": "Nom du module parent",
"wavecolor": "Couleur d'onde par défaut",
"wavecolor.normal-bit": "Onde à largeur unique",
"wavecolor.normal-vec": "Onde à largeur multiple",
"wavecolor.high-impedance": "Onde à haute impédance",
"wavecolor.unknown": "Onde à état inconnu",
"operation-setting": "Opérations",
"render-setting": "Rendu",
"render-animation": "Activer l'animation de rendu",
"usermanual": "Manuel utilisateur",
"usermanual.left-right-scroll.caption": "Déplacer vers le haut et le bas",
"usermanual.up-down-scroll.caption": "Déplacer vers la gauche et la droite",
"usermanual.xscale.caption": "Zoom horizontal",
"loading": "Chargement",
"context-menu.create-group": "Créer un groupe",
"context-menu.join-group": "Rejoindre un groupe existant",
"context-menu.change-color": "Changer la couleur",
"context-menu.delete": "Supprimer le signal",
"context-menu.delete-all-select": "Supprimer tous les signaux sélectionnés",
"context-menu.signal.name": "Nom du signal",
"context-menu.signal.type": "Type de signal",
"context-menu.signal.width": "Largeur du signal",
"context-menu.signal.dep": "Dépendances",
"context-menu.group.cancel": "Annuler le groupement",
"context-menu.group.delete": "Supprimer le groupe",
"context-menu.group.empty": "Aucun groupe disponible",
"context-menu.group.uname-group": "Groupe sans nom",
"toolbar.modal.common-digital": "Numérique",
"toolbar.modal.ladder-analog": "Analogique (échelle)",
"toolbar.modal.line-analog": "Analogique (ligne)",
"toolbar.search.name": "Nom",
"toolbar.search.value": "Valeur",
"toolbar.format.category.base": "Base",
"toolbar.format.category.dec": "Décimal",
"toolbar.format.category.float": "Virgule flottante",
"toolbar.format.signed": "Signé",
"toolbar.format.unsigned": "Non signé",
"toolbar.format.half": "Demi-précision (16 bits)",
"toolbar.format.float": "Simple précision (32 bits)",
"toolbar.format.double": "Double précision (64 bits)",
"toolbar.location.to-begin": "Déplacer au début",
"toolbar.location.to-end": "Déplacer à la fin",
"toolbar.location.to-next-change": "Aller au prochain changement",
"toolbar.location.to-prev-change": "Aller au changement précédent",
"toolbar.location.make-location": "Créer un nouveau repère",
"toolbar.location.clear-location": "Effacer tous les repères",
"toolbar.location.clear-location-dialog": "Êtes-vous sûr de vouloir effacer tous les repères?",
"context-menu.cannot-join-repeat-group": "Le signal actuel est déjà dans ce groupe",
"toolbar.no-result": "Aucun résultat",
"toolbar.search.value.already-to-head": "Déjà au début",
"toolbar.search.value.already-to-tail": "Déjà à la fin",
"toolbar.search.value.searching": "Recherche en cours",
"pivot.context.delete": "Supprimer le repère",
"pivot.context.display-axis": "Créer un axe relatif",
"pivot.context.cancel-axis": "Annuler l'axe relatif",
"setting.appearance.pivot-color": "Couleur du repère",
"setting.appearance.moving-pivot": "Repère mobile",
"setting.appearance.user-pivot": "Repère utilisateur",
"setting.appearance.system-pivot": "Repère système",
"confirm": "Confirmer",
"cancel": "Annuler",
"tips": "Conseils",
"filemenu.save-view": "Enregistrer le fichier de vue",
"filemenu.save-as-view": "Enregistrer la vue sous",
"filemenu.load-view": "Charger le fichier de vue",
"filemenu.auto-save": "Sauvegarde automatique",
"current-version": "Version actuelle",
"setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Vcd Viewer.",
"resources": "Ressources",
"tools": "Outils",
"prompts": "Invites",
"interaction-test": "Test interactif",
"setting": "Paramètres",
"about": "À propos",
"connected": "Connecté",
"disconnected": "Déconnecté",
"debug": "Déboguer",
"connect": "Connexion",
"setting.general-color-setting": "Paramètres de couleur généraux",
"choose-a-project-debug": "Sélectionnez un projet à déboguer",
"model": "Modèle",
"server-provider": "Fournisseur de services",
"api-root-url": "Chemin racine de l'API",
"api-token": "Clé API",
"connection-method": "Méthode de connexion",
"command": "Commande",
"env-var": "Variables d'environnement",
"log": "Journaux",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter",
"reset": "Réinitialiser",
"read-resource": "Lire les ressources",
"enter": "Entrée",
"blank-test": "Test vide",
"connect.appearance.reconnect": "Reconnexion",
"connect.appearance.connect": "Connexion",
"response": "Réponse",
"refresh": "Rafraîchir",
"read-prompt": "Lire l'invite",
"execute-tool": "Exécuter",
"save": "Enregistrer",
"send": "Envoyer",
"server-not-support-statistic": "Le fournisseur que vous utilisez ne prend pas en charge les statistiques temporairement",
"answer-at": "Répondu le",
"input-token": "Entrée",
"output-token": "Sortie",
"total": "Total",
"cache-hit-ratio": "Taux de réussite du cache",
"success-save": "Enregistré avec succès",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?",
"reserve-one-last-model": "Conservez au moins un modèle",
"edit": "Modifier",
"delete": "Supprimer",
"test": "Test",
"add-new-server": "Ajouter un service",
"choose-model": "Sélectionner le modèle",
"system-prompt": "Invite système",
"tool-use": "Utilisation d'outils",
"websearch": "Recherche sur Internet",
"temperature-parameter": "Paramètre de température",
"context-length": "Longueur du contexte",
"system-prompt.placeholder": "Entrez l'invite système (par exemple : Vous êtes un assistant professionnel de développement front-end, répondez en français)",
"precise": "Précis",
"moderate": "Équilibre",
"creative": "Créativité",
"single-dialog": "Dialogue en un tour",
"multi-dialog": "Conversation multi-tours",
"press-and-run": "Tapez une question pour commencer le test",
"connect-sigature": "Signature de connexion",
"finish-refresh": "Actualisation terminée",
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée",
"enter-message-dot": "Entrez un message...",
"generate-answer": "Génération de la réponse",
"choose-presetting": "Sélectionner un préréglage",
"cwd": "Répertoire d'exécution",
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP",
"return": "Retour",
"error": "Erreur",
"feedback": "Retour",
"waiting-mcp-server": "En attente de la réponse du serveur MCP",
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse",
"proxy-server": "Serveur proxy",
"update-model-list": "Mettre à jour la liste des modèles",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion",
"connect-success": "Connexion réussie",
"connect-fail": "Échec de la connexion",
"preset": "Préréglage",
"openmcp-document": "Documentation officielle d'OpenMCP",
"star-our-project": "Star notre projet",
"document": "Documentation officielle",
"join-discussion": "Rejoindre le groupe de discussion",
"comment-for-us": "Écrivez un avis pour nous !",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
"error-parse-json": "Erreur d'analyse JSON :",
"enable-xml-wrapper": "XML 명령 래핑 활성화",
"tool-manage": "도구 관리",
"enable-all-tools": "모든 도구 활성화",
"disable-all-tools": "모든 도구 비활성화",
"using-tool": "도구 사용 중",
"copy-success": "성공적으로 복사되었습니다",
"copy-fail": "복사 실패",
"copy": "복사",
"export": "내보내기",
"export-filename": "내보내기 파일 이름",
"how-to-use": "사용 방법?"
}

View File

@ -1,189 +1,186 @@
{
"module": "Модуль",
"signal": "Сигнал",
"search-signal": "Поиск сигнала",
"language-setting": "Язык",
"search-setting": "Поиск",
"search-case-sensitivity": "Учитывать регистр",
"search-mode": "Режим поиска",
"search-scope": "Область поиска",
"search-display-parent-only": "Показывать только родительский модуль",
"search-nothing": "Сигналы не найдены",
"signal-only": "Только сигнал",
"module-only": "Только модуль",
"signal-module": "Сигнал + Модуль",
"general-setting": "Общие",
"appearance-setting": "Внешний вид",
"display-wave-height": "Высота дорожки волны",
"display-signal-info-scope": "Информация в боковой панели",
"display-signal-info-scope.width": "Ширина бита",
"display-signal-info-scope.parent": "Имя родительского модуля",
"wavecolor": "Цвет волны по умолчанию",
"wavecolor.normal-bit": "Одиночная ширина волны",
"wavecolor.normal-vec": "Многобитная ширина волны",
"wavecolor.high-impedance": "Высокоимпедансная волна",
"wavecolor.unknown": "Неизвестное состояние волны",
"operation-setting": "Операции",
"render-setting": "Рендеринг",
"render-animation": "Включить анимацию рендеринга",
"usermanual": "Руководство пользователя",
"usermanual.left-right-scroll.caption": "Перемещение вверх и вниз",
"usermanual.up-down-scroll.caption": "Перемещение влево и вправо",
"usermanual.xscale.caption": "Горизонтальное масштабирование",
"loading": "Загрузка",
"context-menu.create-group": "Создать группу",
"context-menu.join-group": "Присоединиться к существующей группе",
"context-menu.change-color": "Изменить цвет",
"context-menu.delete": "Удалить сигнал",
"context-menu.delete-all-select": "Удалить все выбранные сигналы",
"context-menu.signal.name": "Имя сигнала",
"context-menu.signal.type": "Тип сигнала",
"context-menu.signal.width": "Ширина сигнала",
"context-menu.signal.dep": "Зависимости",
"context-menu.group.cancel": "Отменить группировку",
"context-menu.group.delete": "Удалить группу",
"context-menu.group.empty": "Нет доступных групп",
"context-menu.group.uname-group": "Безымянная группа",
"toolbar.modal.common-digital": "Цифровой",
"toolbar.modal.ladder-analog": "Аналоговый (ступенчатый)",
"toolbar.modal.line-analog": "Аналоговый (линейный)",
"toolbar.search.name": "Имя",
"toolbar.search.value": "Значение",
"toolbar.format.category.base": "Основной",
"toolbar.format.category.dec": "Десятичный",
"toolbar.format.category.float": "Плавающая запятая",
"toolbar.format.signed": "Знаковый",
"toolbar.format.unsigned": "Беззнаковый",
"toolbar.format.half": "Полуточный (16 бит)",
"toolbar.format.float": "Одинарная точность (32 бита)",
"toolbar.format.double": "Двойная точность (64 бита)",
"toolbar.location.to-begin": "Переместить в начало",
"toolbar.location.to-end": "Переместить в конец",
"toolbar.location.to-next-change": "Перейти к следующему изменению",
"toolbar.location.to-prev-change": "Перейти к предыдущему изменению",
"toolbar.location.make-location": "Создать новый маркер",
"toolbar.location.clear-location": "Очистить все маркеры",
"toolbar.location.clear-location-dialog": "Вы уверены, что хотите очистить все маркеры?",
"context-menu.cannot-join-repeat-group": "Текущий сигнал уже находится в этой группе",
"toolbar.no-result": "Нет результатов",
"toolbar.search.value.already-to-head": "Уже в начале",
"toolbar.search.value.already-to-tail": "Уже в конце",
"toolbar.search.value.searching": "Поиск",
"pivot.context.delete": "Удалить маркер",
"pivot.context.display-axis": "Создать относительную ось",
"pivot.context.cancel-axis": "Отменить относительную ось",
"setting.appearance.pivot-color": "Цвет маркера",
"setting.appearance.moving-pivot": "Движущийся маркер",
"setting.appearance.user-pivot": "Пользовательский маркер",
"setting.appearance.system-pivot": "Системный маркер",
"confirm": "Подтвердить",
"cancel": "Отменить",
"tips": "Советы",
"filemenu.save-view": "Сохранить файл представления",
"filemenu.save-as-view": "Сохранить представление как",
"filemenu.load-view": "Загрузить файл представления",
"filemenu.auto-save": "Автосохранение",
"current-version": "Текущая версия",
"setting.language.change-dialog": "Вы изменили язык на {0}, рекомендуем перезапустить Vcd Viewer.",
"resources": "Ресурсы",
"tools": "Инструменты",
"prompts": "Подсказки",
"interaction-test": "Интерактивный тест",
"setting": "Настройки",
"about": "О нас",
"connected": "Подключено",
"disconnected": "Отключено",
"debug": "Отладка",
"connect": "Подключение",
"setting.general-color-setting": "Общие настройки цвета",
"choose-a-project-debug": "Выберите проект для отладки",
"model": "Модель",
"server-provider": "Поставщик услуг",
"api-root-url": "Корневой путь API",
"api-token": "API-ключ",
"connection-method": "Способ подключения",
"command": "Команда",
"env-var": "Переменные среды",
"log": "Логи",
"warning.click-to-connect": "Пожалуйста, сначала нажмите на $1 слева для подключения",
"reset": "Сброс",
"read-resource": "Чтение ресурсов",
"enter": "Ввод",
"blank-test": "Пустой тест",
"connect.appearance.reconnect": "Переподключение",
"connect.appearance.connect": "Соединение",
"response": "Ответ",
"refresh": "Обновить",
"read-prompt": "Чтение подсказки",
"execute-tool": "Запуск",
"save": "Сохранить",
"send": "Отправить",
"server-not-support-statistic": "Используемый вами поставщик временно не поддерживает статистику",
"answer-at": "Ответ дан",
"input-token": "Ввод",
"output-token": "Вывод",
"total": "Итого",
"cache-hit-ratio": "Коэффициент попаданий в кэш",
"success-save": "Успешно сохранено",
"confirm-delete-model": "Вы уверены, что хотите удалить поставщика моделей?",
"reserve-one-last-model": "Оставьте хотя бы одну модель",
"edit": "Редактировать",
"delete": "Удалить",
"test": "Тест",
"add-new-server": "Добавить услугу",
"choose-model": "Выбрать модель",
"system-prompt": "Системная подсказка",
"tool-use": "Использование инструмента",
"websearch": "Поиск в Интернете",
"temperature-parameter": "Температурный параметр",
"context-length": "Длина контекста",
"system-prompt.placeholder": "Введите системный запрос (например: Вы профессиональный помощник по фронтенд-разработке, отвечайте на русском)",
"precise": "Точный",
"moderate": "Баланс",
"creative": "Творчество",
"single-dialog": "Однораундовый диалог",
"multi-dialog": "Многораундовый разговор",
"press-and-run": "Введите вопрос, чтобы начать тест",
"connect-sigature": "Подпись соединения",
"finish-refresh": "Обновление завершено",
"add-system-prompt.name-placeholder": "Заголовок пользовательского prompt",
"enter-message-dot": "Введите сообщение...",
"generate-answer": "Генерация ответа",
"choose-presetting": "Выбрать预设",
"cwd": "Каталог выполнения",
"mcp-server-timeout": "Максимальное время вызова инструмента MCP",
"return": "Назад",
"error": "Ошибка",
"feedback": "Обратная связь",
"waiting-mcp-server": "Ожидание ответа от сервера MCP",
"parallel-tool-calls": "Разрешить модели вызывать несколько инструментов в одном ответе",
"proxy-server": "Прокси-сервер",
"update-model-list": "Обновить список моделей",
"ensure-delete-connection": "Вы уверены, что хотите удалить соединение $1?",
"choose-connection-type": "Пожалуйста, выберите тип подключения",
"please-enter-connection-command": "Пожалуйста, введите команду подключения",
"example-mcp-run": "Например: mcp run main.py",
"please-enter-cwd": "Пожалуйста, введите рабочий каталог (cwd), необязательно",
"please-enter-cwd-placeholder": "Например: /path/to/project",
"please-enter-url": "Пожалуйста, введите URL-адрес подключения",
"example-as": "Например:",
"enter-optional-oauth": "Пожалуйста, введите токен OAuth, необязательно",
"quick-start": "Начало работы",
"read-document": "Читать документацию",
"report-issue": "Сообщить о проблеме",
"join-project": "Участвовать в проекте",
"comment-plugin": "Плагин комментариев",
"preset-env-sync.success": "Предустановленные переменные среды синхронизированы",
"preset-env-sync.fail": "Не удалось синхронизировать предустановленные переменные среды",
"drag-to-fill-connect-parameters": "Перетащите, чтобы заполнить параметры подключения",
"connect-success": "Успешное подключение",
"connect-fail": "Ошибка подключения",
"preset": "Предустановка",
"openmcp-document": "Официальная документация OpenMCP",
"star-our-project": "Звезда нашего проекта",
"document": "Официальная документация",
"join-discussion": "Присоединиться к дискуссионной группе",
"comment-for-us": "Напишите отзыв для нас!",
"openmcp-developed-by": "OpenMCP Client {version} разработан {author}",
"error-parse-json": "Ошибка разбора JSON:"
"module": "Module",
"signal": "Signal",
"search-signal": "Rechercher un signal",
"language-setting": "Langue",
"search-setting": "Recherche",
"search-case-sensitivity": "Sensibilité à la casse",
"search-mode": "Mode de recherche",
"search-scope": "Portée de la recherche",
"search-display-parent-only": "Afficher uniquement le module parent",
"search-nothing": "Aucun signal trouvé",
"signal-only": "Signal uniquement",
"module-only": "Module uniquement",
"signal-module": "Signal + Module",
"general-setting": "Général",
"appearance-setting": "Apparence",
"display-wave-height": "Hauteur de la piste d'onde",
"display-signal-info-scope": "Informations affichées dans la barre latérale",
"display-signal-info-scope.width": "Largeur de bit",
"display-signal-info-scope.parent": "Nom du module parent",
"wavecolor": "Couleur d'onde par défaut",
"wavecolor.normal-bit": "Onde à largeur unique",
"wavecolor.normal-vec": "Onde à largeur multiple",
"wavecolor.high-impedance": "Onde à haute impédance",
"wavecolor.unknown": "Onde à état inconnu",
"operation-setting": "Opérations",
"render-setting": "Rendu",
"render-animation": "Activer l'animation de rendu",
"usermanual": "Manuel utilisateur",
"usermanual.left-right-scroll.caption": "Déplacer vers le haut et le bas",
"usermanual.up-down-scroll.caption": "Déplacer vers la gauche et la droite",
"usermanual.xscale.caption": "Zoom horizontal",
"loading": "Chargement",
"context-menu.create-group": "Créer un groupe",
"context-menu.join-group": "Rejoindre un groupe existant",
"context-menu.change-color": "Changer la couleur",
"context-menu.delete": "Supprimer le signal",
"context-menu.delete-all-select": "Supprimer tous les signaux sélectionnés",
"context-menu.signal.name": "Nom du signal",
"context-menu.signal.type": "Type de signal",
"context-menu.signal.width": "Largeur du signal",
"context-menu.signal.dep": "Dépendances",
"context-menu.group.cancel": "Annuler le groupement",
"context-menu.group.delete": "Supprimer le groupe",
"context-menu.group.empty": "Aucun groupe disponible",
"context-menu.group.uname-group": "Groupe sans nom",
"toolbar.modal.common-digital": "Numérique",
"toolbar.modal.ladder-analog": "Analogique (échelle)",
"toolbar.modal.line-analog": "Analogique (ligne)",
"toolbar.search.name": "Nom",
"toolbar.search.value": "Valeur",
"toolbar.format.category.base": "Base",
"toolbar.format.category.dec": "Décimal",
"toolbar.format.category.float": "Virgule flottante",
"toolbar.format.signed": "Signé",
"toolbar.format.unsigned": "Non signé",
"toolbar.format.half": "Demi-précision (16 bits)",
"toolbar.format.float": "Simple précision (32 bits)",
"toolbar.format.double": "Double précision (64 bits)",
"toolbar.location.to-begin": "Déplacer au début",
"toolbar.location.to-end": "Déplacer à la fin",
"toolbar.location.to-next-change": "Aller au prochain changement",
"toolbar.location.to-prev-change": "Aller au changement précédent",
"toolbar.location.make-location": "Créer un nouveau repère",
"toolbar.location.clear-location": "Effacer tous les repères",
"toolbar.location.clear-location-dialog": "Êtes-vous sûr de vouloir effacer tous les repères?",
"context-menu.cannot-join-repeat-group": "Le signal actuel est déjà dans ce groupe",
"toolbar.no-result": "Aucun résultat",
"toolbar.search.value.already-to-head": "Déjà au début",
"toolbar.search.value.already-to-tail": "Déjà à la fin",
"toolbar.search.value.searching": "Recherche en cours",
"pivot.context.delete": "Supprimer le repère",
"pivot.context.display-axis": "Créer un axe relatif",
"pivot.context.cancel-axis": "Annuler l'axe relatif",
"setting.appearance.pivot-color": "Couleur du repère",
"setting.appearance.moving-pivot": "Repère mobile",
"setting.appearance.user-pivot": "Repère utilisateur",
"setting.appearance.system-pivot": "Repère système",
"confirm": "Confirmer",
"cancel": "Annuler",
"tips": "Conseils",
"filemenu.save-view": "Enregistrer le fichier de vue",
"filemenu.save-as-view": "Enregistrer la vue sous",
"filemenu.load-view": "Charger le fichier de vue",
"filemenu.auto-save": "Sauvegarde automatique",
"current-version": "Version actuelle",
"setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Vcd Viewer.",
"resources": "Ressources",
"tools": "Outils",
"prompts": "Invites",
"interaction-test": "Test interactif",
"setting": "Paramètres",
"about": "À propos",
"connected": "Connecté",
"disconnected": "Déconnecté",
"debug": "Déboguer",
"connect": "Connexion",
"setting.general-color-setting": "Paramètres de couleur généraux",
"choose-a-project-debug": "Sélectionnez un projet à déboguer",
"model": "Modèle",
"server-provider": "Fournisseur de services",
"api-root-url": "Chemin racine de l'API",
"api-token": "Clé API",
"connection-method": "Méthode de connexion",
"command": "Commande",
"env-var": "Variables d'environnement",
"log": "Journaux",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter",
"reset": "Réinitialiser",
"read-resource": "Lire les ressources",
"enter": "Entrée",
"blank-test": "Test vide",
"connect.appearance.reconnect": "Reconnexion",
"connect.appearance.connect": "Connexion",
"response": "Réponse",
"refresh": "Rafraîchir",
"read-prompt": "Lire l'invite",
"execute-tool": "Exécuter",
"save": "Enregistrer",
"send": "Envoyer",
"server-not-support-statistic": "Le fournisseur que vous utilisez ne prend pas en charge les statistiques temporairement",
"answer-at": "Répondu le",
"input-token": "Entrée",
"output-token": "Sortie",
"total": "Total",
"cache-hit-ratio": "Taux de réussite du cache",
"success-save": "Enregistré avec succès",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?",
"reserve-one-last-model": "Conservez au moins un modèle",
"edit": "Modifier",
"delete": "Supprimer",
"test": "Test",
"add-new-server": "Ajouter un service",
"choose-model": "Sélectionner le modèle",
"system-prompt": "Invite système",
"tool-use": "Utilisation d'outils",
"websearch": "Recherche sur Internet",
"temperature-parameter": "Paramètre de température",
"context-length": "Longueur du contexte",
"system-prompt.placeholder": "Entrez l'invite système (par exemple : Vous êtes un assistant professionnel de développement front-end, répondez en français)",
"precise": "Précis",
"moderate": "Équilibre",
"creative": "Créativité",
"single-dialog": "Dialogue en un tour",
"multi-dialog": "Conversation multi-tours",
"press-and-run": "Tapez une question pour commencer le test",
"connect-sigature": "Signature de connexion",
"finish-refresh": "Actualisation terminée",
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée",
"enter-message-dot": "Entrez un message...",
"generate-answer": "Génération de la réponse",
"choose-presetting": "Sélectionner un préréglage",
"cwd": "Répertoire d'exécution",
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP",
"return": "Retour",
"error": "Erreur",
"feedback": "Retour",
"waiting-mcp-server": "En attente de la réponse du serveur MCP",
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse",
"proxy-server": "Serveur proxy",
"update-model-list": "Mettre à jour la liste des modèles",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion",
"connect-success": "Connexion réussie",
"connect-fail": "Échec de la connexion",
"preset": "Préréglage",
"openmcp-document": "Documentation officielle d'OpenMCP",
"star-our-project": "Star notre projet",
"document": "Documentation officielle",
"join-discussion": "Rejoindre le groupe de discussion",
"comment-for-us": "Écrivez un avis pour nous !",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
"error-parse-json": "Erreur d'analyse JSON :",
"enable-xml-wrapper": "Включить обёртку XML-команд",
"tool-manage": "Управление инструментами",
"enable-all-tools": "Активировать все инструменты",
"disable-all-tools": "Отключить все инструменты",
"using-tool": "Использование инструмента",
"copy-success": "Скопировано успешно",
"copy-fail": "Ошибка копирования",
"copy": "Копировать",
"export": "Экспорт",
"export-filename": "Имя экспортируемого файла",
"how-to-use": "Как использовать?"
}

View File

@ -171,5 +171,16 @@
"join-discussion": "加入讨论群",
"comment-for-us": "为我们撰写评价!",
"openmcp-developed-by": "OpenMCP Client {version} 由 {author} 开发",
"error-parse-json": "JSON 解析错误:"
"error-parse-json": "JSON 解析错误:",
"enable-xml-wrapper": "开启 XML 指令包裹",
"tool-manage": "工具管理",
"enable-all-tools": "激活所有工具",
"disable-all-tools": "禁用所有工具",
"using-tool": "正在使用工具",
"copy-success": "复制成功",
"copy-fail": "复制失败",
"copy": "复制",
"export": "导出",
"export-filename": "导出文件名",
"how-to-use": "如何使用?"
}

View File

@ -1,189 +1,186 @@
{
"module": "模塊",
"signal": "信號",
"search-signal": "搜索信號",
"language-setting": "語言",
"search-setting": "搜索",
"search-case-sensitivity": "區分大小寫",
"search-mode": "搜索模式",
"search-scope": "搜索範圍",
"search-display-parent-only": "只展示父模塊",
"search-nothing": "沒有找到任何信號",
"signal-only": "信號",
"module-only": "模塊",
"signal-module": "信號 + 模塊",
"general-setting": "通用",
"appearance-setting": "外觀",
"display-wave-height": "波形軌道的高度",
"display-signal-info-scope": "側邊欄展示信息",
"display-signal-info-scope.width": "位寬",
"display-signal-info-scope.parent": "所屬模塊名",
"wavecolor": "默認波形顏色",
"wavecolor.normal-bit": "單位寬波形",
"wavecolor.normal-vec": "多位寬波形",
"wavecolor.high-impedance": "高阻態波形",
"wavecolor.unknown": "未知態波形",
"operation-setting": "操作",
"render-setting": "渲染",
"render-animation": "開啟渲染動畫",
"usermanual": "使用說明",
"usermanual.left-right-scroll.caption": "上下移動",
"usermanual.up-down-scroll.caption": "左右移動",
"usermanual.xscale.caption": "橫向縮放",
"loading": "加載中",
"context-menu.create-group": "新建組",
"context-menu.join-group": "加入已有分組",
"context-menu.change-color": "修改顏色",
"context-menu.delete": "刪除信號",
"context-menu.delete-all-select": "刪除所有選中信號",
"context-menu.signal.name": "信號名稱",
"context-menu.signal.type": "信號類型",
"context-menu.signal.width": "信號寬度",
"context-menu.signal.dep": "依賴關係",
"context-menu.group.cancel": "取消分組",
"context-menu.group.delete": "刪除分組",
"context-menu.group.empty": "當前沒有可用的分組",
"context-menu.group.uname-group": "未命名分組",
"toolbar.modal.common-digital": "數字",
"toolbar.modal.ladder-analog": "模擬(階梯)",
"toolbar.modal.line-analog": "模擬(折線)",
"toolbar.search.name": "名稱",
"toolbar.search.value": "值",
"toolbar.format.category.base": "基礎",
"toolbar.format.category.dec": "十進制",
"toolbar.format.category.float": "浮點數",
"toolbar.format.signed": "有符號",
"toolbar.format.unsigned": "無符號",
"toolbar.format.half": "半精度16bit",
"toolbar.format.float": "單精度32bit",
"toolbar.format.double": "雙精度64bit",
"toolbar.location.to-begin": "移動至開頭",
"toolbar.location.to-end": "移動至結尾",
"toolbar.location.to-next-change": "前往下一個變化的邊沿",
"toolbar.location.to-prev-change": "前往上一個變化的邊沿",
"toolbar.location.make-location": "創建新的信標",
"toolbar.location.clear-location": "清除所有信標",
"toolbar.location.clear-location-dialog": "您確定要清除所有的信標嗎?",
"context-menu.cannot-join-repeat-group": "當前信號已在此分組中",
"toolbar.no-result": "無結果",
"toolbar.search.value.already-to-head": "已經到開頭了",
"toolbar.search.value.already-to-tail": "已經到結尾了",
"toolbar.search.value.searching": "搜索中",
"pivot.context.delete": "刪除信標",
"pivot.context.display-axis": "創建相對坐標軸",
"pivot.context.cancel-axis": "取消相對坐標軸",
"setting.appearance.pivot-color": "信標顏色",
"setting.appearance.moving-pivot": "移動信標",
"setting.appearance.user-pivot": "用戶信標",
"setting.appearance.system-pivot": "系統信標",
"confirm": "確定",
"cancel": "取消",
"tips": "提示",
"filemenu.save-view": "保存視圖文件",
"filemenu.save-as-view": "另存為視圖文件",
"filemenu.load-view": "導入視圖文件",
"filemenu.auto-save": "自動保存",
"current-version": "當前版本",
"setting.language.change-dialog": "您已將語言更改為 {0},我們建議您重新啟動 Vcd Viewer。",
"resources": "資源",
"tools": "工具",
"prompts": "提示",
"interaction-test": "交互測試",
"setting": "設定",
"about": "關於",
"connected": "已連線",
"disconnected": "已斷開連接",
"debug": "偵錯",
"connect": "連接",
"setting.general-color-setting": "通用顏色設定",
"choose-a-project-debug": "選擇一個項目進行調試",
"model": "模型",
"server-provider": "服務提供者",
"api-root-url": "API 根路徑",
"api-token": "API 密鑰",
"connection-method": "連接方式",
"command": "命令",
"env-var": "環境變數",
"log": "日誌",
"warning.click-to-connect": "請先點擊左側的 $1 進行連接",
"reset": "重置",
"read-resource": "讀取資源",
"enter": "輸入",
"blank-test": "空白測試",
"connect.appearance.reconnect": "重新連線",
"connect.appearance.connect": "連接",
"response": "響應",
"refresh": "重新整理",
"read-prompt": "讀取提示",
"execute-tool": "執行",
"save": "儲存",
"send": "傳送",
"server-not-support-statistic": "你使用的供應商暫時不支持統計信息",
"answer-at": "作答於",
"input-token": "輸入",
"output-token": "輸出",
"total": "總計",
"cache-hit-ratio": "緩存命中率",
"success-save": "成功儲存",
"confirm-delete-model": "確定刪除該模型提供商?",
"reserve-one-last-model": "至少保留一個模型",
"edit": "編輯",
"delete": "刪除",
"test": "測試",
"add-new-server": "新增服務",
"choose-model": "選擇模型",
"system-prompt": "系統提示詞",
"tool-use": "工具使用",
"websearch": "網路搜尋",
"temperature-parameter": "溫度參數",
"context-length": "上下文長度",
"system-prompt.placeholder": "輸入系統提示詞(例如:你是一個專業的前端開發助手,用中文回答)",
"precise": "精確",
"moderate": "平衡",
"creative": "創意",
"single-dialog": "單輪對話",
"multi-dialog": "多輪對話",
"press-and-run": "輸入問題以開始測試",
"connect-sigature": "連接簽名",
"finish-refresh": "刷新完成",
"add-system-prompt.name-placeholder": "自定義提示的標題",
"enter-message-dot": "輸入訊息...",
"generate-answer": "正在生成答案",
"choose-presetting": "選擇預設",
"cwd": "執行目錄",
"mcp-server-timeout": "MCP工具最長調用時間",
"return": "返回",
"error": "錯誤",
"feedback": "反饋",
"waiting-mcp-server": "等待MCP伺服器響應",
"parallel-tool-calls": "允許模型在單輪回覆中調用多個工具",
"proxy-server": "代理伺服器",
"update-model-list": "更新模型列表",
"ensure-delete-connection": "確定要刪除連接 $1 嗎?",
"choose-connection-type": "請選擇連接類型",
"please-enter-connection-command": "請輸入連接命令",
"example-mcp-run": "例如: mcp run main.py",
"please-enter-cwd": "請輸入工作目錄 (cwd),可選",
"please-enter-cwd-placeholder": "例如: /path/to/project",
"please-enter-url": "請輸入連接的 URL",
"example-as": "例如:",
"enter-optional-oauth": "請輸入 OAuth 令牌,可選",
"quick-start": "入門",
"read-document": "閱讀文件",
"report-issue": "報告問題",
"join-project": "參與專案",
"comment-plugin": "評論插件",
"preset-env-sync.success": "預設環境變數同步完成",
"preset-env-sync.fail": "預設環境變數同步失敗",
"drag-to-fill-connect-parameters": "拖曳以填充連接參數",
"connect-success": "連接成功",
"connect-fail": "連接失敗",
"preset": "預設",
"openmcp-document": "OpenMCP 官方文件",
"star-our-project": "給我們的項目加星",
"document": "官方文件",
"join-discussion": "加入討論群",
"comment-for-us": "為我們撰寫評價!",
"openmcp-developed-by": "OpenMCP Client {version} 由 {author} 開發",
"error-parse-json": "JSON解析錯誤:"
"module": "Module",
"signal": "Signal",
"search-signal": "Rechercher un signal",
"language-setting": "Langue",
"search-setting": "Recherche",
"search-case-sensitivity": "Sensibilité à la casse",
"search-mode": "Mode de recherche",
"search-scope": "Portée de la recherche",
"search-display-parent-only": "Afficher uniquement le module parent",
"search-nothing": "Aucun signal trouvé",
"signal-only": "Signal uniquement",
"module-only": "Module uniquement",
"signal-module": "Signal + Module",
"general-setting": "Général",
"appearance-setting": "Apparence",
"display-wave-height": "Hauteur de la piste d'onde",
"display-signal-info-scope": "Informations affichées dans la barre latérale",
"display-signal-info-scope.width": "Largeur de bit",
"display-signal-info-scope.parent": "Nom du module parent",
"wavecolor": "Couleur d'onde par défaut",
"wavecolor.normal-bit": "Onde à largeur unique",
"wavecolor.normal-vec": "Onde à largeur multiple",
"wavecolor.high-impedance": "Onde à haute impédance",
"wavecolor.unknown": "Onde à état inconnu",
"operation-setting": "Opérations",
"render-setting": "Rendu",
"render-animation": "Activer l'animation de rendu",
"usermanual": "Manuel utilisateur",
"usermanual.left-right-scroll.caption": "Déplacer vers le haut et le bas",
"usermanual.up-down-scroll.caption": "Déplacer vers la gauche et la droite",
"usermanual.xscale.caption": "Zoom horizontal",
"loading": "Chargement",
"context-menu.create-group": "Créer un groupe",
"context-menu.join-group": "Rejoindre un groupe existant",
"context-menu.change-color": "Changer la couleur",
"context-menu.delete": "Supprimer le signal",
"context-menu.delete-all-select": "Supprimer tous les signaux sélectionnés",
"context-menu.signal.name": "Nom du signal",
"context-menu.signal.type": "Type de signal",
"context-menu.signal.width": "Largeur du signal",
"context-menu.signal.dep": "Dépendances",
"context-menu.group.cancel": "Annuler le groupement",
"context-menu.group.delete": "Supprimer le groupe",
"context-menu.group.empty": "Aucun groupe disponible",
"context-menu.group.uname-group": "Groupe sans nom",
"toolbar.modal.common-digital": "Numérique",
"toolbar.modal.ladder-analog": "Analogique (échelle)",
"toolbar.modal.line-analog": "Analogique (ligne)",
"toolbar.search.name": "Nom",
"toolbar.search.value": "Valeur",
"toolbar.format.category.base": "Base",
"toolbar.format.category.dec": "Décimal",
"toolbar.format.category.float": "Virgule flottante",
"toolbar.format.signed": "Signé",
"toolbar.format.unsigned": "Non signé",
"toolbar.format.half": "Demi-précision (16 bits)",
"toolbar.format.float": "Simple précision (32 bits)",
"toolbar.format.double": "Double précision (64 bits)",
"toolbar.location.to-begin": "Déplacer au début",
"toolbar.location.to-end": "Déplacer à la fin",
"toolbar.location.to-next-change": "Aller au prochain changement",
"toolbar.location.to-prev-change": "Aller au changement précédent",
"toolbar.location.make-location": "Créer un nouveau repère",
"toolbar.location.clear-location": "Effacer tous les repères",
"toolbar.location.clear-location-dialog": "Êtes-vous sûr de vouloir effacer tous les repères?",
"context-menu.cannot-join-repeat-group": "Le signal actuel est déjà dans ce groupe",
"toolbar.no-result": "Aucun résultat",
"toolbar.search.value.already-to-head": "Déjà au début",
"toolbar.search.value.already-to-tail": "Déjà à la fin",
"toolbar.search.value.searching": "Recherche en cours",
"pivot.context.delete": "Supprimer le repère",
"pivot.context.display-axis": "Créer un axe relatif",
"pivot.context.cancel-axis": "Annuler l'axe relatif",
"setting.appearance.pivot-color": "Couleur du repère",
"setting.appearance.moving-pivot": "Repère mobile",
"setting.appearance.user-pivot": "Repère utilisateur",
"setting.appearance.system-pivot": "Repère système",
"confirm": "Confirmer",
"cancel": "Annuler",
"tips": "Conseils",
"filemenu.save-view": "Enregistrer le fichier de vue",
"filemenu.save-as-view": "Enregistrer la vue sous",
"filemenu.load-view": "Charger le fichier de vue",
"filemenu.auto-save": "Sauvegarde automatique",
"current-version": "Version actuelle",
"setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Vcd Viewer.",
"resources": "Ressources",
"tools": "Outils",
"prompts": "Invites",
"interaction-test": "Test interactif",
"setting": "Paramètres",
"about": "À propos",
"connected": "Connecté",
"disconnected": "Déconnecté",
"debug": "Déboguer",
"connect": "Connexion",
"setting.general-color-setting": "Paramètres de couleur généraux",
"choose-a-project-debug": "Sélectionnez un projet à déboguer",
"model": "Modèle",
"server-provider": "Fournisseur de services",
"api-root-url": "Chemin racine de l'API",
"api-token": "Clé API",
"connection-method": "Méthode de connexion",
"command": "Commande",
"env-var": "Variables d'environnement",
"log": "Journaux",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter",
"reset": "Réinitialiser",
"read-resource": "Lire les ressources",
"enter": "Entrée",
"blank-test": "Test vide",
"connect.appearance.reconnect": "Reconnexion",
"connect.appearance.connect": "Connexion",
"response": "Réponse",
"refresh": "Rafraîchir",
"read-prompt": "Lire l'invite",
"execute-tool": "Exécuter",
"save": "Enregistrer",
"send": "Envoyer",
"server-not-support-statistic": "Le fournisseur que vous utilisez ne prend pas en charge les statistiques temporairement",
"answer-at": "Répondu le",
"input-token": "Entrée",
"output-token": "Sortie",
"total": "Total",
"cache-hit-ratio": "Taux de réussite du cache",
"success-save": "Enregistré avec succès",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?",
"reserve-one-last-model": "Conservez au moins un modèle",
"edit": "Modifier",
"delete": "Supprimer",
"test": "Test",
"add-new-server": "Ajouter un service",
"choose-model": "Sélectionner le modèle",
"system-prompt": "Invite système",
"tool-use": "Utilisation d'outils",
"websearch": "Recherche sur Internet",
"temperature-parameter": "Paramètre de température",
"context-length": "Longueur du contexte",
"system-prompt.placeholder": "Entrez l'invite système (par exemple : Vous êtes un assistant professionnel de développement front-end, répondez en français)",
"precise": "Précis",
"moderate": "Équilibre",
"creative": "Créativité",
"single-dialog": "Dialogue en un tour",
"multi-dialog": "Conversation multi-tours",
"press-and-run": "Tapez une question pour commencer le test",
"connect-sigature": "Signature de connexion",
"finish-refresh": "Actualisation terminée",
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée",
"enter-message-dot": "Entrez un message...",
"generate-answer": "Génération de la réponse",
"choose-presetting": "Sélectionner un préréglage",
"cwd": "Répertoire d'exécution",
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP",
"return": "Retour",
"error": "Erreur",
"feedback": "Retour",
"waiting-mcp-server": "En attente de la réponse du serveur MCP",
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse",
"proxy-server": "Serveur proxy",
"update-model-list": "Mettre à jour la liste des modèles",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion",
"connect-success": "Connexion réussie",
"connect-fail": "Échec de la connexion",
"preset": "Préréglage",
"openmcp-document": "Documentation officielle d'OpenMCP",
"star-our-project": "Star notre projet",
"document": "Documentation officielle",
"join-discussion": "Rejoindre le groupe de discussion",
"comment-for-us": "Écrivez un avis pour nous !",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
"error-parse-json": "Erreur d'analyse JSON :",
"enable-xml-wrapper": "開啟 XML 指令包裹",
"tool-manage": "工具管理",
"enable-all-tools": "啟用所有工具",
"disable-all-tools": "禁用所有工具",
"using-tool": "正在使用工具",
"copy-success": "複製成功",
"copy-fail": "複製失敗",
"copy": "複製",
"export": "匯出",
"export-filename": "匯出檔案名稱",
"how-to-use": "如何使用?"
}

View File

@ -47,7 +47,7 @@ import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const version = '0.1.5';
const version = '0.1.8';
const author = 'LSTM-Kirigaya (锦恢)';
defineComponent({ name: 'about' });

View File

@ -67,7 +67,7 @@ async function connect() {
}
const isDraging = ref(false);
let dragHandler: number;
let dragHandler: NodeJS.Timeout;
function handleDragOver(event: DragEvent) {
event.preventDefault();

View File

@ -29,7 +29,7 @@ export const connectionSelectDataViewOption: ConnectionTypeOptionItem[] = [
function prettifyMapKeys(keys: MapIterator<string>) {
const result: string[] = [];
for (const key of keys) {
result.push('+ ' +key);
result.push('+ ' + key);
}
return result.join('\n');
}
@ -197,7 +197,7 @@ export class McpClient {
const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<ToolsListResponse>('tools/list', { clientId: this.clientId });
if (code!== 200) {
if (code !== 200) {
return new Map<string, ToolItem>();
}
@ -227,7 +227,7 @@ export class McpClient {
const { code, msg } = await bridge.commandRequest<PromptsListResponse>('prompts/list', { clientId: this.clientId });
if (code!== 200) {
if (code !== 200) {
return new Map<string, PromptTemplate>();
}
@ -252,7 +252,7 @@ export class McpClient {
const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<ResourcesListResponse>('resources/list', { clientId: this.clientId });
if (code!== 200) {
if (code !== 200) {
return new Map<string, Resources>();
}
@ -276,7 +276,7 @@ export class McpClient {
const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<ResourceTemplatesListResponse>('resources/templates/list', { clientId: this.clientId });
if (code!== 200) {
if (code !== 200) {
return new Map();
}
this.resourceTemplates = new Map<string, ResourceTemplate>();
@ -455,19 +455,62 @@ export class McpClient {
});
}
}
// 添加资源刷新方法,支持超时控制
public async refreshAllResources(timeoutMs = 30000): Promise<void> {
const controller = new AbortController();
const signal = controller.signal;
// 设置超时
const timeoutId = setTimeout(() => {
controller.abort();
console.error(`[REFRESH TIMEOUT] Client ${this.clientId}`);
}, timeoutMs);
try {
console.log(`[REFRESH START] Client ${this.clientId}`);
// 按顺序刷新资源
await this.getTools({ cache: false });
await this.getPromptTemplates({ cache: false });
await this.getResources({ cache: false });
await this.getResourceTemplates({ cache: false });
console.log(chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.green(`🚀 [${this.name}] REFRESH COMPLETE`));
} catch (error) {
if (signal.aborted) {
throw new Error(`Refresh timed out after ${timeoutMs}ms`);
}
console.error(`[REFRESH ERROR] Client ${this.clientId}:`, error);
console.error(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.red(`🚀 [${this.name}] REFRESH FAILED`),
error
);
throw error;
} finally {
clearTimeout(timeoutId);
}
}
}
class McpClientAdapter {
public clients: Reactive<McpClient[]> = [];
public currentClientIndex: number = 0;
public refreshSignal = reactive({ value: 0 });
private defaultClient: McpClient = new McpClient();
public connectLogListenerCancel: (() => void) | null = null;
public connectrefreshListener: (() => void) | null = null;
constructor(
public platform: string
) { }
) {
if (platform !== 'nodejs') {
this.addConnectRefreshListener();
}
}
/**
* @description
@ -511,6 +554,50 @@ class McpClientAdapter {
});
}
private findClientIndexByUuid(uuid: string): number {
// 检查客户端数组是否存在且不为空
if (!this.clients || this.clients.length === 0) {
return -1;
}
const index = this.clients.findIndex(client => client.clientId === uuid);
return index;
}
/**
* @description register HMR
*/
public addConnectRefreshListener() {
// 创建对于 connect/refresh 的监听
if (!this.connectrefreshListener) {
const bridge = useMessageBridge();
this.connectrefreshListener = bridge.addCommandListener('connect/refresh', async (message) => {
const { code, msg } = message;
console.log('refresh');
if (code === 200) {
// 查找目标客户端
const clientIndex = this.findClientIndexByUuid(msg.uuid);
if (clientIndex > -1) {
// 刷新该客户端的所有资源
console.log('clientIndex', clientIndex);
await this.clients[clientIndex].refreshAllResources();
this.refreshSignal.value++;
} else {
console.error(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.red(`No client found with ID: ${msg.uuid}`),
);
}
}
}, { once: false });
}
}
public async launch() {
// 创建对于 log/output 的监听
if (!this.connectLogListenerCancel) {
@ -525,7 +612,7 @@ class McpClientAdapter {
}
client.connectionResult.logString.push({
type: code === 200 ? 'info': 'error',
type: code === 200 ? 'info' : 'error',
title: msg.title,
message: msg.message
});
@ -599,7 +686,7 @@ class McpClientAdapter {
return msg;
}
public async readPromptTemplate(promptId: string, args: Record<string, any>) {
public async readPromptTemplate(promptId: string, args?: Record<string, any>) {
// TODO: 如果遇到不同服务器的同名 tool请拓展解决方案
// 目前只找到第一个匹配 toolName 的工具进行调用
let clientId = this.clients[0].clientId;

View File

@ -37,6 +37,7 @@ export async function makeSimpleTalk() {
enableTools: [],
enableWebSearch: false,
contextLength: 5,
enableXmlWrapper: false,
parallelToolCalls: true
}
};

View File

@ -237,13 +237,22 @@ async function updateModels() {
const proxyServer = mcpSetting.proxyServer;
const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest('llm/models', {
// OpenRouter
let result;
if (llm.isDynamic && llm.id === 'openrouter') {
result = await bridge.commandRequest('llm/models/openrouter', {});
} else {
result = await bridge.commandRequest('llm/models', {
apiKey,
baseURL,
proxyServer
});
}
const { code, msg } = result;
const isGemini = baseURL.includes('googleapis');
const isOpenRouter = llm.id === 'openrouter';
if (code === 200 && Array.isArray(msg)) {
const models = msg
@ -257,9 +266,27 @@ async function updateModels() {
});
llm.models = models;
// OpenRouter
if (isOpenRouter && !llm.userModel && models.length > 0) {
// GPT-4
const recommendedModel = models.find(model =>
model.includes('gpt-4') ||
model.includes('claude') ||
model.includes('gemini')
) || models[0];
llm.userModel = recommendedModel;
}
saveLlmSetting();
if (isOpenRouter) {
ElMessage.success(`已更新 ${models.length} 个 OpenRouter 模型`);
} else {
ElMessage.error('模型列表更新失败' + msg);
ElMessage.success('模型列表更新成功');
}
} else {
ElMessage.error('模型列表更新失败: ' + msg);
}
updateModelLoading.value = false;
}

View File

@ -4,18 +4,42 @@
<span class="iconfont icon-llm"></span>
<span class="option-title">{{ t('model') }}</span>
</span>
<div style="width: 160px;">
<div style="width: 240px;">
<el-select v-if="llms[llmManager.currentModelIndex]"
name="language-setting"
name="model-setting"
v-model="llms[llmManager.currentModelIndex].userModel"
@change="onmodelchange"
filterable
:placeholder="getPlaceholderText()"
:reserve-keyword="false"
size="default"
:popper-class="isOpenRouter ? 'openrouter-select-dropdown' : ''"
>
<el-option
v-for="option in llms[llmManager.currentModelIndex].models"
:value="option"
:label="option"
:key="option"
></el-option>
>
<div v-if="isOpenRouter" class="openrouter-model-option">
<div class="model-info">
<span class="model-name">{{ getModelDisplayName(option) }}</span>
<span class="model-provider">{{ getModelProvider(option) }}</span>
</div>
<span v-if="getModelBadge(option)" class="model-badge">{{ getModelBadge(option) }}</span>
</div>
<span v-else class="regular-model-option">{{ option }}</span>
</el-option>
<!-- 当没有搜索结果时显示 -->
<el-option v-if="filteredModels.length === 0 && searchKeyword"
value=""
disabled
class="search-result-info">
<div class="search-empty">
<span>{{ `找到 0 个模型匹配 "${searchKeyword}"` }}</span>
</div>
</el-option>
</el-select>
</div>
</div>
@ -46,7 +70,7 @@
<script setup lang="ts">
/* eslint-disable */
import { defineComponent } from 'vue';
import { defineComponent, computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { llmManager, llms } from './llm';
import { pinkLog } from './util';
@ -57,6 +81,29 @@ defineComponent({ name: 'connect-interface-openai' });
const { t } = useI18n();
//
const searchKeyword = ref('');
// OpenRouter
const isOpenRouter = computed(() => {
return llms[llmManager.currentModelIndex]?.id === 'openrouter';
});
//
const filteredModels = computed(() => {
if (!llms[llmManager.currentModelIndex]) return [];
const models = llms[llmManager.currentModelIndex].models;
if (!searchKeyword.value) return models;
const keyword = searchKeyword.value.toLowerCase();
return models.filter(model =>
model.toLowerCase().includes(keyword) ||
getModelDisplayName(model).toLowerCase().includes(keyword) ||
getModelProvider(model).toLowerCase().includes(keyword)
);
});
function saveLlmSetting() {
saveSetting(() => {
ElMessage({
@ -71,7 +118,140 @@ function onmodelchange() {
saveLlmSetting();
}
//
function filterModels(query: string) {
searchKeyword.value = query;
// false Element Plus 使 filteredModels
return false;
}
//
function getPlaceholderText() {
if (isOpenRouter.value) {
const modelCount = llms[llmManager.currentModelIndex]?.models?.length || 0;
return `搜索 ${modelCount} 个模型... (可输入模型名或提供商)`;
}
return '选择模型';
}
//
function getModelDisplayName(modelId: string) {
if (!modelId.includes('/')) return modelId;
return modelId.split('/')[1] || modelId;
}
//
function getModelProvider(modelId: string) {
if (!modelId.includes('/')) return '';
return modelId.split('/')[0];
}
// free
function getModelBadge(modelId: string) {
if (modelId.includes(':free')) return 'FREE';
if (modelId.includes(':thinking')) return 'THINKING';
if (modelId.includes(':beta')) return 'BETA';
if (modelId.includes('preview')) return 'PREVIEW';
if (modelId.includes('exp')) return 'EXP';
return '';
}
</script>
<style>
<style scoped>
/* OpenRouter 模型选项样式 */
.openrouter-model-option {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 4px 0;
}
.model-info {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
}
.model-name {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary, #303133);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.model-provider {
font-size: 12px;
color: var(--el-text-color-regular, #606266);
margin-top: 2px;
}
.model-badge {
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
background-color: #f0f9ff;
color: #0369a1;
font-weight: 600;
margin-left: 8px;
white-space: nowrap;
}
/* 普通模型选项样式 */
.regular-model-option {
font-size: 14px;
color: var(--el-text-color-primary, #303133);
font-weight: 500;
}
/* 搜索为空时的样式 */
.search-empty {
text-align: center;
color: var(--el-text-color-secondary, #909399);
font-size: 13px;
padding: 12px 0;
}
/* 下拉框样式优化 */
:global(.openrouter-select-dropdown) {
max-height: 300px !important;
}
:global(.openrouter-select-dropdown .el-select-dropdown__item) {
height: auto !important;
padding: 8px 12px !important;
line-height: normal !important;
}
/* 搜索框样式 */
:global(.el-select .el-input .el-input__wrapper) {
transition: all 0.3s ease;
}
:global(.el-select .el-input.is-focus .el-input__wrapper) {
box-shadow: 0 0 0 1px var(--el-color-primary, #409eff) inset;
}
/* 暗色主题下的文字优化 */
@media (prefers-color-scheme: dark) {
.model-name {
color: #e5eaf3 !important;
}
.model-provider {
color: #a3a6ad !important;
}
.regular-model-option {
color: #e5eaf3 !important;
}
.search-empty {
color: #8b949e !important;
}
}
</style>

View File

@ -22,6 +22,9 @@ export interface BasicLlmDescription {
website: string,
userToken: string,
userModel: string,
isDynamic?: boolean,
modelsEndpoint?: string,
supportsPricing?: boolean,
[key: string]: any
}

View File

@ -32,8 +32,8 @@ export default defineConfig({
lib: {
entry: resolve(__dirname, '..', 'renderer/src/components/main-panel/chat/core/task-loop.ts'),
name: 'TaskLoop',
fileName: 'task-loop',
formats: ['cjs']
fileName: (format) => `task-loop.js`,
formats: ['es']
},
outDir: resolve(__dirname, '..', 'openmcp-sdk'),
emptyOutDir: false,

View File

@ -2,123 +2,110 @@
<img src="./icons/openmcp-sdk.svg" height="200px"/>
<h3>openmcp-sdk : 适用于 openmcp 的部署框架</h3>
<h4>闪电般将您的 agent 从实验室部署到生产环境</h4>
<h3>openmcp-sdk: Deployment Framework for OpenMCP</h3>
<h4>Lightning-fast deployment of your agent from lab to production</h4>
<a href="https://kirigaya.cn/openmcp" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #7D3FF8; color: white; border-radius: .5em; text-decoration: none;">📄 OpenMCP 官方文档</a>
<a href="https://kirigaya.cn/openmcp" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #7D3FF8; color: white; border-radius: .5em; text-decoration: none;">📄 OpenMCP Official Documentation</a>
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #CB81DA; color: white; border-radius: .5em; text-decoration: none;">QQ 讨论群</a><a href="https://discord.gg/af5cfB9a" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: rgb(84, 176, 84); color: white; border-radius: .5em; text-decoration: none; margin-left: 5px;">Discord频道</a>
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #CB81DA; color: white; border-radius: .5em; text-decoration: none;">QQ Discussion Group</a><a href="https://discord.gg/af5cfB9a" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: rgb(84, 176, 84); color: white; border-radius: .5em; text-decoration: none; margin-left: 5px;">Discord Channel</a>
</div>
## 安装
## Installation
```bash
npm install openmcp-sdk
```
> 目前 openmcp-sdk 只支持 esm 模式的导入
> Currently, openmcp-sdk only supports ESM-style imports.
## 使用
## Usage
文件名main.ts
Filename: `main.ts`
```typescript
import { TaskLoop } from 'openmcp-sdk/task-loop';
import { TaskLoopAdapter } from 'openmcp-sdk/service';
async function main() {
// 创建适配器,负责通信和 mcp 连接
const adapter = new TaskLoopAdapter();
import { OmAgent } from 'openmcp-sdk/service/sdk';
// 添加 mcp 服务器
adapter.addMcp({
connectionType: 'STDIO',
commandString: 'node index.js',
cwd: '~/projects/openmcp-tutorial/my-browser/dist'
});
// create Agent
const agent = new OmAgent();
// 创建事件循环驱动器, verbose 数值越高,输出的日志越详细
const taskLoop = new TaskLoop({ adapter, verbose: 1 });
// Load configuration, which can be automatically generated after debugging with openmcp client
agent.loadMcpConfig('./mcpconfig.json');
// 获取所有工具
const tools = await taskLoop.getTools();
// Read the debugged prompt
const prompt = await agent.getPrompt('hacknews', { topn: '5' });
// 配置改次事件循环使用的大模型
taskLoop.setLlmConfig({
id: 'deepseek',
baseUrl: 'https://api.deepseek.com/v1',
userToken: process.env['DEEPSEEK_API_TOKEN'],
userModel: 'deepseek-chat'
});
// Execute the task
const res = await agent.ainvoke({ messages: prompt });
// 创建当前事件循环对应的上下文,并且配置当前上下文的设置
const storage = {
messages: [],
settings: {
temperature: 0.7,
// 在本次对话使用所有工具
enableTools: tools,
// 系统提示词
systemPrompt: 'you are a clever bot',
// 对话上下文的轮数
contextLength: 20
console.log('⚙️ Agent Response', res);
```
`mcpconfig.json` can be generated from [openmcp client](https://github.com/LSTM-Kirigaya/openmcp-client) directly, you don't have to write it by yourself. Here is the example:
```json
{
"version": "1.0.0",
"namespace": "openmcp",
"mcpServers": {
"my-browser": {
"command": "mcp",
"args": [
"run",
"~/projects/openmcp-tutorial/crawl4ai-mcp/main.py"
],
"description": "A MCP for long-term memory support",
"prompts": [
"hacknews"
]
}
},
"defaultLLM": {
"baseURL": "https://api.deepseek.com",
"apiToken": "sk-xxxxxxxxxxxxxx",
"model": "deepseek-chat"
}
};
// 本次发出的问题
const message = 'hello world';
// 开启事件循环
await taskLoop.start(storage, message);
// 打印上下文,最终的回答在 messages.at(-1) 中
const content = storage.messages.at(-1).content;
console.log('最终回答:', content);
}
main();
```
下面是可能的输出:
Run your agent and get example output:
```
[6/5/2025, 8:16:15 PM] 🚀 [my-browser] 0.1.0 connected
[6/5/2025, 8:16:15 PM] task loop enters a new epoch
[6/5/2025, 8:16:23 PM] task loop finish a epoch
[6/5/2025, 8:16:23 PM] 🤖 llm wants to call these tools k_navigate
[6/5/2025, 8:16:23 PM] 🔧 calling tool k_navigate
[6/5/2025, 8:16:34 PM] × fail to call tools McpError: MCP error -32603: net::ERR_CONNECTION_RESET at https://towardsdatascience.com/tag/editors-pick/
[6/5/2025, 8:16:34 PM] task loop enters a new epoch
[6/5/2025, 8:16:40 PM] task loop finish a epoch
[6/5/2025, 8:16:40 PM] 🤖 llm wants to call these tools k_navigate
[6/5/2025, 8:16:40 PM] 🔧 calling tool k_navigate
[6/5/2025, 8:16:44 PM] ✓ call tools okey dockey success
[6/5/2025, 8:16:44 PM] task loop enters a new epoch
[6/5/2025, 8:16:57 PM] task loop finish a epoch
[6/5/2025, 8:16:57 PM] 🤖 llm wants to call these tools k_evaluate
[6/5/2025, 8:16:57 PM] 🔧 calling tool k_evaluate
[6/5/2025, 8:16:57 PM] ✓ call tools okey dockey success
[6/5/2025, 8:16:57 PM] task loop enters a new epoch
[6/5/2025, 8:17:06 PM] task loop finish a epoch
[6/5/2025, 8:17:06 PM] 🤖 llm wants to call these tools k_navigate, k_navigate
[6/5/2025, 8:17:06 PM] 🔧 calling tool k_navigate
[6/5/2025, 8:17:09 PM] ✓ call tools okey dockey success
[6/5/2025, 8:17:09 PM] 🔧 calling tool k_navigate
[6/5/2025, 8:17:12 PM] ✓ call tools okey dockey success
[6/5/2025, 8:17:12 PM] task loop enters a new epoch
[6/5/2025, 8:17:19 PM] task loop finish a epoch
[6/5/2025, 8:17:19 PM] 🤖 llm wants to call these tools k_evaluate, k_evaluate
[6/5/2025, 8:17:19 PM] 🔧 calling tool k_evaluate
[6/5/2025, 8:17:19 PM] ✓ call tools okey dockey success
[6/5/2025, 8:17:19 PM] 🔧 calling tool k_evaluate
[6/5/2025, 8:17:19 PM] ✓ call tools okey dockey success
[6/5/2025, 8:17:19 PM] task loop enters a new epoch
[6/5/2025, 8:17:45 PM] task loop finish a epoch
"以下是整理好的热门文章信息,并已翻译为简体中文:\n\n---\n\n### K1 标题 \n**《数据漂移并非真正问题:你的监控策略才是》** \n\n**简介** \n在机器学习领域数据漂移常被视为模型性能下降的罪魁祸首但本文作者提出了一种颠覆性的观点数据漂移只是一个信号真正的核心问题在于监控策略的不足。文章通过实际案例如电商推荐系统和金融风控模型揭示了传统统计监控的局限性并提出了一个三层监控框架 \n1. **统计监控**:快速检测数据分布变化,但仅作为初步信号。 \n2. **上下文监控**:结合业务逻辑,判断漂移是否对关键指标产生影响。 \n3. **行为监控**:追踪模型预测的实际效果,避免“无声漂移”。 \n\n亮点在于作者强调了监控系统需要与业务目标紧密结合而非单纯依赖技术指标。 \n\n**原文链接** \n[点击阅读原文](https://towardsdatascience.com/data-drift-is-not-the-actual-problem-your-monitoring-strategy-is/) \n\n---\n\n### K2 标题 \n**《从 Jupyter 到程序员的快速入门指南》** \n\n**简介** \n本文为数据科学家和初学者提供了一条从 Jupyter Notebook 过渡到专业编程的清晰路径。作者通过实际代码示例和工具推荐(如 VS Code、Git 和 Docker帮助读者摆脱 Notebook 的局限性,提升代码的可维护性和可扩展性。 \n\n亮点包括 \n- 如何将 Notebook 代码模块化为可复用的 Python 脚本。 \n- 使用版本控制和容器化技术优化开发流程。 \n- 实战案例展示如何将实验性代码转化为生产级应用。 \n\n**原文链接** \n[点击阅读原文](https://towardsdatascience.com/the-journey-from-jupyter-to-programmer-a-quick-start-guide/) \n\n---\n\n如果需要进一步优化或补充其他内容请随时告诉我"
[2025/6/20 20:47:31] 🚀 [crawl4ai-mcp] 1.9.1 connected
[2025/6/20 20:47:35] 🤖 Agent wants to use these tools get_web_markdown
[2025/6/20 20:47:35] 🔧 using tool get_web_markdown
[2025/6/20 20:47:39] ✓ use tools success
[2025/6/20 20:47:46] 🤖 Agent wants to use these tools get_web_markdown, get_web_markdown, get_web_markdown
[2025/6/20 20:47:46] 🔧 using tool get_web_markdown
[2025/6/20 20:47:48] ✓ use tools success
[2025/6/20 20:47:48] 🔧 using tool get_web_markdown
[2025/6/20 20:47:54] ✓ use tools success
[2025/6/20 20:47:54] 🔧 using tool get_web_markdown
[2025/6/20 20:47:57] ✓ use tools success
⚙️ Agent Response
⌨️ Today's Tech Article Roundup
📌 How to Detect or Observe Passing Gravitational Waves?
Summary: This article explores the physics of gravitational waves, explaining their effects on space-time and how humans might perceive or observe this cosmic phenomenon.
Author: ynoxinul
Posted: 2 hours ago
Link: https://physics.stackexchange.com/questions/338912/how-would-a-passing-gravitational-wave-look-or-feel
📌 Learn Makefile Tutorial
Summary: A comprehensive Makefile tutorial for beginners and advanced users, covering basic syntax, variables, automatic rules, and advanced features to help developers manage project builds efficiently.
Author: dsego
Posted: 4 hours ago
Link: https://makefiletutorial.com/
📌 Hurl: Run and Test HTTP Requests in Plain Text
Summary: Hurl is a command-line tool that allows defining and executing HTTP requests in plain text format, ideal for data fetching and HTTP session testing. It supports chained requests, value capture, and response queries, making it perfect for testing REST, SOAP, and GraphQL APIs.
Author: flykespice
Posted: 8 hours ago
Link: https://github.com/Orange-OpenSource/hurl
```
更多使用请看官方文档https://kirigaya.cn/openmcp/sdk-tutorial/
For more details, see the official documentation: [https://kirigaya.cn/openmcp/sdk-tutorial/](https://kirigaya.cn/openmcp/sdk-tutorial/)
star 我们的项目https://github.com/LSTM-Kirigaya/openmcp-client
Star our project: [https://github.com/LSTM-Kirigaya/openmcp-client](https://github.com/LSTM-Kirigaya/openmcp-client)

View File

@ -1,5 +1,5 @@
const { TaskLoop } = require('../../openmcp-sdk/task-loop');
const { TaskLoopAdapter } = require('../../openmcp-sdk/service');
import { TaskLoop } from '../../openmcp-sdk/task-loop.js';
import { TaskLoopAdapter } from '../../openmcp-sdk/service.js';
async function main() {
// 创建适配器,负责通信和 mcp 连接

View File

@ -1,6 +1,7 @@
{
"name": "openmcp-sdk",
"version": "0.0.7",
"version": "0.0.8",
"type": "module",
"description": "openmcp-sdk",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
@ -37,6 +38,8 @@
"open": "^10.1.2",
"ws": "^8.18.1",
"cli-table3": "^0.6.5",
"https-proxy-agent": "^7.0.6"
"https-proxy-agent": "^7.0.6",
"pino": "^9.6.0",
"pino-pretty": "^13.0.0"
}
}

View File

@ -4,20 +4,13 @@ import type { OpenAI } from 'openai';
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
export interface TaskLoopOptions {
maxEpochs?: number;
maxJsonParseRetry?: number;
adapter?: any;
verbose?: 0 | 1 | 2 | 3;
}
export interface SchemaProperty {
interface SchemaProperty {
title: string;
type: string;
description?: string;
}
export interface InputSchema {
interface InputSchema {
type: string;
properties: Record<string, SchemaProperty>;
required?: string[];
@ -25,7 +18,7 @@ export interface InputSchema {
$defs?: any;
}
export interface ToolItem {
interface ToolItem {
name: string;
description: string;
inputSchema: InputSchema;
@ -33,6 +26,50 @@ export interface ToolItem {
anyOf?: any;
}
interface IExtraInfo {
created: number,
state: MessageState,
serverName: string,
usage?: ChatCompletionChunk['usage'];
enableXmlWrapper: boolean;
[key: string]: any;
}
interface ToolMessage {
role: 'tool';
index: number;
content: ToolCallContent[];
tool_call_id?: string
name?: string // 工具名称,当 role 为 tool
tool_calls?: ToolCall[],
extraInfo: IExtraInfo
}
interface TextMessage {
role: 'user' | 'assistant' | 'system';
content: string;
tool_call_id?: string
name?: string // 工具名称,当 role 为 tool
tool_calls?: ToolCall[],
extraInfo: IExtraInfo
}
export type ChatMessage = ToolMessage | TextMessage;
interface ChatStorage {
messages: ChatMessage[]
settings: ChatSetting
}
interface EnableToolItem {
name: string;
description: string;
enabled: boolean;
inputSchema: InputSchema;
}
export type Ref<T> = {
value: T;
};
@ -80,68 +117,143 @@ export interface IDoConversationResult {
stop: boolean;
}
export interface TaskLoopOptions {
/**
* The maximum number of epochs (conversation rounds) to perform.
*/
maxEpochs?: number;
/**
* The maximum number of retries allowed when parsing JSON responses fails.
*/
maxJsonParseRetry?: number;
/**
* A custom adapter that can be used to modify behavior or integrate with different environments.
*/
adapter?: any;
/**
* Verbosity level for logging:
* 0 - Silent, 1 - Errors only, 2 - Warnings and errors, 3 - Full debug output.
*/
verbose?: 0 | 1 | 2 | 3;
}
interface ChatSetting {
/**
* Index of the selected language model from a list of available models.
*/
modelIndex: number;
/**
* System-level prompt used to guide the behavior of the assistant.
*/
systemPrompt: string;
/**
* List of tools that are enabled and available during the chat.
*/
enableTools: EnableToolItem[];
/**
* Sampling temperature for generating responses.
* Higher values (e.g., 0.8) make output more random; lower values (e.g., 0.2) make it more focused and deterministic.
*/
temperature: number;
/**
* Whether web search is enabled for enhancing responses with real-time information.
*/
enableWebSearch: boolean;
/**
* Maximum length of the conversation context to keep.
*/
contextLength: number;
/**
* Whether multiple tools can be called in parallel within a single message.
*/
parallelToolCalls: boolean;
/**
* Whether to wrap tool call responses in XML format.
*/
enableXmlWrapper: boolean;
}
/**
* @description
*/
export class TaskLoop {
private streamingContent;
private streamingToolCalls;
private readonly taskOptions;
private bridge;
private currentChatId;
private onError;
private onChunk;
private onDone;
private onToolCalled;
private onEpoch;
private completionUsage;
private llmConfig;
constructor(taskOptions?: TaskLoopOptions);
private handleChunkDeltaContent;
private handleChunkDeltaToolCalls;
private handleChunkUsage;
private doConversation;
/**
* @description wait for connection
*/
waitConnection(): Promise<void>;
/**
* @description Set the task loop options
* @param taskOptions
*/
setTaskLoopOptions(taskOptions: TaskLoopOptions): void;
/**
* @description make chat data
* @param tabStorage
*/
makeChatData(tabStorage: any): ChatCompletionCreateParamsBase | undefined;
/**
* @description stop the task loop
*/
abort(): void;
/**
* @description error
* @description Register a callback function triggered on error
* @param handler
*/
registerOnError(handler: (msg: IErrorMssage) => void): void;
/**
* @description Register a callback function triggered on chunk
* @param handler
*/
registerOnChunk(handler: (chunk: ChatCompletionChunk) => void): void;
/**
* @description chat.completion
* @description Register a callback function triggered at the beginning of each epoch
* @param handler
*/
registerOnDone(handler: () => void): void;
/**
* @description epoch
* @description Register a callback function triggered at the beginning of each epoch
* @param handler
*/
registerOnEpoch(handler: () => void): void;
/**
* @description toolcall
* @description Registers a callback function that is triggered when a tool call is completed. This method allows you to intercept and modify the output of the tool call.
* @param handler
*/
registerOnToolCalled(handler: (toolCallResult: ToolCallResult) => ToolCallResult): void;
/**
* @description toolcall
* @description Register a callback triggered after tool call finishes. You can intercept and modify the output.
* @param handler
*/
registerOnToolCall(handler: (toolCall: ToolCall) => ToolCall): void;
/**
* @description LLM
* @description Get current LLM configuration
*/
getLlmConfig(): any;
/**
* @description LLM nodejs
* @description Set the current LLM configuration, for Node.js environment
* @param config
* @example
* setLlmConfig({
@ -154,11 +266,19 @@ export class TaskLoop {
setLlmConfig(config: any): void;
/**
* @description epoch
* @description Set proxy server
* @param maxEpochs
*/
setMaxEpochs(maxEpochs: number): void;
/**
* @description bind streaming content and tool calls
*/
bindStreaming(content: Ref<string>, toolCalls: Ref<ToolCall[]>): void;
/**
* @description not finish
*/
connectToService(): Promise<void>;
/**
@ -168,14 +288,29 @@ export class TaskLoop {
setProxyServer(proxyServer: string): void;
/**
* @description
* @description Get all available tool list
*/
listTools(): Promise<ToolItem[]>;
/**
* @description DOM
* @description Start the loop and asynchronously update the DOM
*/
start(tabStorage: any, userMessage: string): Promise<void>;
start(tabStorage: ChatStorage, userMessage: string): Promise<void>;
/**
* @description Create single conversation context
*/
createStorage(settings?: ChatSetting): Promise<ChatStorage>;
/**
* @description Get prompt template from mcp server
*/
getPrompt(promptId: string, args?: Record<string, any>): Promise<string>;
/**
* @description Get resource template from mcp server
*/
getResource(resourceUri: string): Promise<string>;
}
export declare const getToolSchema: any;

37
rollup.tesseract.js Normal file
View File

@ -0,0 +1,37 @@
import path from 'path';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { babel } from '@rollup/plugin-babel';
import json from '@rollup/plugin-json'; // ✅ 新增
import copy from 'rollup-plugin-copy';
export default {
input: './node_modules/tesseract.js/src/worker-script/node/index.js',
output: {
file: path.resolve(__dirname, '..', 'resources', 'ocr', 'worker.js'),
format: 'cjs',
exports: 'auto'
},
plugins: [
json(), // ✅ 插入 JSON 插件
nodeResolve({
browser: false,
preferBuiltins: true
}),
commonjs(),
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**',
presets: ['@babel/preset-env']
}),
copy({
targets: [
{
src: path.resolve(__dirname, '..', 'node_modules', 'tesseract.js-core', 'tesseract*'),
dest: path.resolve(__dirname, '..', 'resources', 'ocr')
}
]
})
],
external: ['bufferutil', 'utf-8-validate']
};

View File

@ -6,13 +6,13 @@
"types": "dist/index.d.ts",
"type": "module",
"scripts": {
"serve": "nodemon --watch src --exec tsx src/main.ts",
"serve": "tsx watch src/main.ts",
"build": "tsc",
"build:watch": "tsc --watch",
"postbuild": "node ./scripts/post-build.mjs",
"start": "node --experimental-specifier-resolution=node dist/main.js",
"start:prod": "NODE_ENV=production node --experimental-specifier-resolution=node dist/main.js",
"debug": "node --inspect --no-warnings=ExperimentalWarning -r tsx/esm src/main.ts",
"start": "node dist/main.js",
"start:prod": "NODE_ENV=production node dist/main.js",
"debug": "tsx --inspect src/main.ts",
"clean": "rm -rf dist",
"lint": "eslint src --ext .ts,.tsx",
"typecheck": "tsc --noEmit"

View File

@ -22,7 +22,6 @@ export async function routeMessage(command: string, data: any, webview: PostMess
const { handler, option = {} } = handlerStore;
try {
// TODO: select client based on something
const res = await handler(data, webview);
// res.code = -1 代表当前请求不需要返回发送

View File

@ -1,8 +1,5 @@
import { WebSocket } from 'ws';
import { EventEmitter } from 'events';
import { routeMessage } from '../common/router.js';
import { ConnectionType, McpOptions } from '../mcp/client.dto.js';
import { clientMap, connectService } from '../mcp/connect.service.js';
import { ConnectionType } from '../mcp/client.dto.js';
// WebSocket 消息格式
export interface WebSocketMessage {
@ -27,6 +24,10 @@ export interface IConnectionArgs {
cwd?: string;
url?: string;
oauth?: string;
env?: {
[key: string]: string;
};
[key: string]: any;
}
// 监听器回调类型
@ -56,6 +57,7 @@ export class VSCodeWebViewLike {
* @param message - command args
*/
postMessage(message: WebSocketMessage): void {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
} else {
@ -75,80 +77,3 @@ export class VSCodeWebViewLike {
};
}
}
export class TaskLoopAdapter {
public emitter: EventEmitter;
private messageHandlers: Set<MessageHandler>;
private connectionOptions: IConnectionArgs[] = [];
constructor(option?: any) {
this.emitter = new EventEmitter(option);
this.messageHandlers = new Set();
this.emitter.on('message/renderer', (message: WebSocketMessage) => {
this.messageHandlers.forEach((handler) => handler(message));
});
// 默认需要将监听的消息导入到 routeMessage 中
this.onDidReceiveMessage((message) => {
const { command, data } = message;
switch (command) {
case 'nodejs/launch-signature':
this.postMessage({
command: 'nodejs/launch-signature',
data: {
code: 200,
msg: this.connectionOptions
}
})
break;
case 'nodejs/update-connection-signature':
// sdk 模式下不需要自动保存连接参数
break;
default:
routeMessage(command, data, this);
break;
}
});
}
/**
* @description
* @param message - command args
*/
public postMessage(message: WebSocketMessage): void {
this.emitter.emit('message/service', message);
}
/**
* @description
* @param callback -
* @returns {{ dispose: () => void }} -
*/
public onDidReceiveMessage(callback: MessageHandler): { dispose: () => void } {
this.messageHandlers.add(callback);
return {
dispose: () => this.messageHandlers.delete(callback),
};
}
/**
* @description mcp
* @param mcpOption
*/
public addMcp(mcpOption: IConnectionArgs) {
// 0.1.4 新版本开始,此处修改为懒加载连接
// 实际的连接移交给前端 mcpAdapter 中进行统一的调度
// 调度步骤如下:
// getLaunchSignature 先获取访问签名,访问签名通过当前函数 push 到 class 中
this.connectionOptions.push(mcpOption);
}
}

View File

@ -130,6 +130,21 @@ export const llms = [
website: 'https://kimi.moonshot.cn',
userToken: '',
userModel: 'moonshot-v1-8k'
},
{
id: 'openrouter',
name: 'OpenRouter',
baseUrl: 'https://openrouter.ai/api/v1',
models: [], // 动态加载
provider: 'OpenRouter',
isOpenAICompatible: true,
description: '400+ AI models from multiple providers in one API',
website: 'https://openrouter.ai',
userToken: '',
userModel: '',
isDynamic: true,
modelsEndpoint: 'https://openrouter.ai/api/v1/models',
supportsPricing: true
}
];

View File

@ -0,0 +1,100 @@
export interface OpenRouterModel {
id: string;
name: string;
description?: string;
context_length: number;
pricing: {
prompt: string;
completion: string;
};
architecture?: {
input_modalities?: string[];
output_modalities?: string[];
tokenizer?: string;
};
supported_parameters?: string[];
}
export interface OpenRouterModelsResponse {
data: OpenRouterModel[];
}
// 模型缓存避免频繁API调用
let modelsCache: { models: OpenRouterModel[]; timestamp: number } | null = null;
const CACHE_DURATION = 5 * 60 * 1000; // 5分钟缓存
export async function fetchOpenRouterModels(): Promise<OpenRouterModel[]> {
const now = Date.now();
// 检查缓存
if (modelsCache && (now - modelsCache.timestamp) < CACHE_DURATION) {
return modelsCache.models;
}
try {
const response = await fetch('https://openrouter.ai/api/v1/models');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: OpenRouterModelsResponse = await response.json();
const models = data.data.map(model => ({
id: model.id,
name: model.name,
description: model.description,
context_length: model.context_length,
pricing: model.pricing,
architecture: model.architecture,
supported_parameters: model.supported_parameters
}));
// 更新缓存
modelsCache = {
models,
timestamp: now
};
console.log(`Fetched ${models.length} OpenRouter models`);
return models;
} catch (error) {
console.error('Failed to fetch OpenRouter models:', error);
// 返回缓存的模型(如果有)或空数组
return modelsCache?.models || [];
}
}
export async function getOpenRouterModelsByCategory(category?: string): Promise<OpenRouterModel[]> {
try {
const url = category
? `https://openrouter.ai/api/v1/models?category=${encodeURIComponent(category)}`
: 'https://openrouter.ai/api/v1/models';
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: OpenRouterModelsResponse = await response.json();
return data.data;
} catch (error) {
console.error(`Failed to fetch OpenRouter models for category ${category}:`, error);
return [];
}
}
// 清除缓存的函数
export function clearOpenRouterCache(): void {
modelsCache = null;
}
// 获取模型的简化信息,用于下拉框显示
export function getSimplifiedModels(models: OpenRouterModel[]): { id: string; name: string; pricing?: string }[] {
return models.map(model => ({
id: model.id,
name: model.name,
pricing: model.pricing ? `$${model.pricing.prompt}/1K` : undefined
}));
}

327
service/src/hook/sdk.ts Normal file
View File

@ -0,0 +1,327 @@
import { EventEmitter } from 'events';
import { routeMessage } from '../common/router.js';
import * as fs from 'fs';
export class TaskLoopAdapter {
public emitter: EventEmitter;
private messageHandlers: Set<MessageHandler>;
private connectionOptions: IConnectionArgs[] = [];
constructor(option?: any) {
this.emitter = new EventEmitter(option);
this.messageHandlers = new Set();
this.emitter.on('message/renderer', (message: WebSocketMessage) => {
this.messageHandlers.forEach((handler) => handler(message));
});
// 默认需要将监听的消息导入到 routeMessage 中
this.onDidReceiveMessage((message) => {
const { command, data } = message;
switch (command) {
case 'nodejs/launch-signature':
this.postMessage({
command: 'nodejs/launch-signature',
data: {
code: 200,
msg: this.connectionOptions
}
})
break;
case 'nodejs/update-connection-signature':
// sdk 模式下不需要自动保存连接参数
break;
default:
routeMessage(command, data, this);
break;
}
});
}
/**
* @description
* @param message - command args
*/
public postMessage(message: WebSocketMessage): void {
this.emitter.emit('message/service', message);
}
/**
* @description
* @param callback -
* @returns {{ dispose: () => void }} -
*/
public onDidReceiveMessage(callback: MessageHandler): { dispose: () => void } {
this.messageHandlers.add(callback);
return {
dispose: () => this.messageHandlers.delete(callback),
};
}
/**
* @description mcp
* @param mcpOption
*/
public addMcp(mcpOption: IConnectionArgs) {
// 0.1.4 新版本开始,此处修改为懒加载连接
// 实际的连接移交给前端 mcpAdapter 中进行统一的调度
// 调度步骤如下:
// getLaunchSignature 先获取访问签名,访问签名通过当前函数 push 到 class 中
this.connectionOptions.push(mcpOption);
}
}
interface StdioMCPConfig {
command: string;
args: string[];
env?: {
[key: string]: string;
};
description?: string;
prompts?: string[];
resources?: string[];
}
interface HttpMCPConfig {
url: string;
type?: string;
env?: {
[key: string]: string;
};
description?: string;
prompts?: string[];
resources?: string[];
}
export interface OmAgentConfiguration {
version: string;
mcpServers: {
[key: string]: StdioMCPConfig | HttpMCPConfig;
};
defaultLLM: {
baseURL: string;
apiToken: string;
model: string;
}
}
export interface DefaultLLM {
baseURL: string;
apiToken?: string;
model: string;
}
import {
MessageState,
type TaskLoopOptions,
type ChatMessage,
type ChatSetting,
type TaskLoop,
type TextMessage
} from '../../task-loop.js';
import { IConnectionArgs, MessageHandler, WebSocketMessage } from './adapter.js';
import { ConnectionType } from 'src/mcp/client.dto.js';
export function UserMessage(content: string): TextMessage {
return {
role: 'user',
content,
extraInfo: {
created: Date.now(),
state: MessageState.None,
serverName: '',
enableXmlWrapper: false
}
}
}
export function AssistantMessage(content: string): TextMessage {
return {
role: 'assistant',
content,
extraInfo: {
created: Date.now(),
state: MessageState.None,
serverName: '',
enableXmlWrapper: false
}
}
}
export class OmAgent {
private _adapter: TaskLoopAdapter;
private _loop?: TaskLoop;
private _defaultLLM?: DefaultLLM;
constructor() {
this._adapter = new TaskLoopAdapter();
}
/**
* @description Load MCP configuration from file.
* Supports multiple MCP backends and a default LLM model configuration.
*
* @example
* Example configuration:
* {
* "version": "1.0.0",
* "mcpServers": {
* "openmemory": {
* "command": "npx",
* "args": ["-y", "openmemory"],
* "env": {
* "OPENMEMORY_API_KEY": "YOUR_API_KEY",
* "CLIENT_NAME": "openmemory"
* },
* "description": "A MCP for long-term memory support"
* }
* },
* "defaultLLM": {
* "baseURL": "https://api.openmemory.ai",
* "apiToken": "YOUR_API_KEY",
* "model": "deepseek-chat"
* }
* }
*
* @param configPath - Path to the configuration file
*/
public loadMcpConfig(configPath: string) {
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as OmAgentConfiguration;
const { mcpServers, defaultLLM } = config;
// set default llm
this.setDefaultLLM(defaultLLM);
for (const key in mcpServers) {
const mcpConfig = mcpServers[key];
if ('command' in mcpConfig) {
const commandString = (
mcpConfig.command + ' ' + mcpConfig.args.join(' ')
).trim();
this._adapter.addMcp({
commandString,
connectionType: 'STDIO',
env: mcpConfig.env,
description: mcpConfig.description,
});
} else {
const connectionType: ConnectionType = mcpConfig.type === 'http' ? 'STREAMABLE_HTTP' : 'SSE';
this._adapter.addMcp({
url: mcpConfig.url,
env: mcpConfig.env,
connectionType,
description: mcpConfig.description,
});
}
}
}
/**
* @description Add MCP server
*/
public addMcpServer(connectionArgs: IConnectionArgs) {
this._adapter.addMcp(connectionArgs);
}
private async getLoop(loopOption?: TaskLoopOptions) {
if (this._loop) {
if (loopOption) {
this._loop.setTaskLoopOptions(loopOption);
}
return this._loop;
}
const {
verbose = 1,
maxEpochs = 50,
maxJsonParseRetry = 3,
} = loopOption || {}
const adapter = this._adapter;
const { TaskLoop } = await import('../../task-loop.js');
this._loop = new TaskLoop({ adapter, verbose, maxEpochs, maxJsonParseRetry });
await this._loop.waitConnection();
return this._loop;
}
public setDefaultLLM(option: DefaultLLM) {
this._defaultLLM = option;
}
public async getPrompt(promptId: string, args: Record<string, any>) {
const loop = await this.getLoop();
const prompt = await loop.getPrompt(promptId, JSON.parse(JSON.stringify(args)));
return prompt;
}
/**
* @description Asynchronous invoking agent by string or messages
* @param messages Chat message or string
* @param settings Chat setting and task loop options
* @returns
*/
public async ainvoke(
{ messages, settings }: { messages: ChatMessage[] | string; settings?: ChatSetting & Partial<TaskLoopOptions>; }
) {
if (messages.length === 0) {
throw new Error('messages is empty');
}
// detach taskloop option from settings and set default value
const {
maxEpochs = 50,
maxJsonParseRetry = 3,
verbose = 1
} = settings || {};
const loop = await this.getLoop({ maxEpochs, maxJsonParseRetry, verbose });
const storage = await loop.createStorage(settings);
// set input message
// user can invoke [UserMessage("CONTENT")] to make messages quickly
// or use string directly
let userMessage: string;
if (typeof messages === 'string') {
userMessage = messages;
} else {
const lastMessageContent = messages.at(-1)?.content;
if (typeof lastMessageContent === 'string') {
userMessage = lastMessageContent;
} else {
throw new Error('last message content is undefined');
}
}
// select correct llm config
// user can set llm config via omagent.setDefaultLLM()
// or write "defaultLLM" in mcpconfig.json to specify
if (this._defaultLLM) {
loop.setLlmConfig({
baseUrl: this._defaultLLM.baseURL,
userToken: this._defaultLLM.apiToken,
userModel: this._defaultLLM.model,
});
} else {
// throw error to user and give the suggestion
throw new Error('default LLM is not set, please set it via omagent.setDefaultLLM() or write "defaultLLM" in mcpconfig.json');
}
await loop.start(storage, userMessage);
// get response from last message in message list
const lastMessage = storage.messages.at(-1)?.content;
return lastMessage;
}
}

View File

@ -1,4 +1,4 @@
export { routeMessage } from './common/router.js';
export { VSCodeWebViewLike, TaskLoopAdapter } from './hook/adapter.js';
export { VSCodeWebViewLike } from './hook/adapter.js';
export { setVscodeWorkspace, setRunningCWD } from './hook/setting.js';
export { clientMap } from './mcp/connect.service.js';

View File

@ -1,11 +1,9 @@
import { RequestClientType } from "../common/index.dto.js";
import { Controller } from "../common/index.js";
import { RequestData } from "../common/index.dto.js";
import { PostMessageble } from "../hook/adapter.js";
import { getClient } from "../mcp/connect.service.js";
import { abortMessageService, streamingChatCompletion } from "./llm.service.js";
import { OpenAI } from "openai";
import { axiosFetch } from "src/hook/axios-fetch.js";
import { fetchOpenRouterModels, getSimplifiedModels } from "../hook/openrouter.js";
export class LlmController {
@Controller('llm/chat/completions')
@ -57,4 +55,66 @@ export class LlmController {
msg: models.data
}
}
@Controller('llm/models/openrouter')
async getOpenRouterModels(data: RequestData, webview: PostMessageble) {
try {
const models = await fetchOpenRouterModels();
const simplifiedModels = getSimplifiedModels(models);
// 转换为标准格式与其他模型API保持一致
const standardModels = simplifiedModels.map(model => ({
id: model.id,
object: 'model',
name: model.name,
pricing: model.pricing
}));
return {
code: 200,
msg: standardModels
};
} catch (error) {
console.error('Failed to fetch OpenRouter models:', error);
return {
code: 500,
msg: `Failed to fetch OpenRouter models: ${error instanceof Error ? error.message : String(error)}`
};
}
}
@Controller('llm/models/dynamic')
async getDynamicModels(data: RequestData, webview: PostMessageble) {
const { providerId } = data;
try {
if (providerId === 'openrouter') {
const models = await fetchOpenRouterModels();
const simplifiedModels = getSimplifiedModels(models);
const standardModels = simplifiedModels.map(model => ({
id: model.id,
object: 'model',
name: model.name,
pricing: model.pricing
}));
return {
code: 200,
msg: standardModels
};
} else {
return {
code: 400,
msg: `Unsupported dynamic provider: ${providerId}`
};
}
} catch (error) {
console.error(`Failed to fetch dynamic models for ${providerId}:`, error);
return {
code: 500,
msg: `Failed to fetch models: ${error instanceof Error ? error.message : String(error)}`
};
}
}
}

View File

@ -35,9 +35,17 @@ export async function streamingChatCompletion(
});
// 构建OpenRouter特定的请求头
const defaultHeaders: Record<string, string> = {};
if (baseURL && baseURL.includes('openrouter.ai')) {
defaultHeaders['HTTP-Referer'] = 'https://github.com/openmcp/openmcp-client';
defaultHeaders['X-Title'] = 'OpenMCP Client';
}
const client = new OpenAI({
baseURL,
apiKey,
defaultHeaders: Object.keys(defaultHeaders).length > 0 ? defaultHeaders : undefined
});
const seriableTools = (tools.length === 0) ? undefined: tools;

View File

@ -1,13 +1,11 @@
import { WebSocketServer } from 'ws';
import {pino} from 'pino';
import { pino } from 'pino';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { dirname, join } from 'path';
import { routeMessage } from './common/router.js';
import { VSCodeWebViewLike } from './hook/adapter.js';
import path from 'node:path';
import * as fs from 'node:fs';
import { setRunningCWD } from './hook/setting.js';
import axios from 'axios';
import fs from 'fs/promises'; // 使用 Promise API 替代回调
import chalk from 'chalk';
export interface VSCodeMessage {
command: string;
@ -15,91 +13,80 @@ export interface VSCodeMessage {
callbackId?: string;
}
// 统一路径变量
const devHome = join(__dirname, '..', '..');
const serverPath = join(devHome, 'servers');
const envPath = join(__dirname, '..', '.env');
const logger = pino({
transport: {
target: 'pino-pretty', // 启用 pino-pretty
options: {
colorize: true, // 开启颜色
levelFirst: true, // 先打印日志级别
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', // 格式化时间
ignore: 'pid,hostname', // 忽略部分字段
}
}
});
export type MessageHandler = (message: VSCodeMessage) => void;
function refreshConnectionOption(envPath: string) {
const serverPath = path.join(__dirname, '..', '..', 'servers');
async function refreshConnectionOption() {
const defaultOption = {
connectionType: 'STDIO',
commandString: 'mcp run main.py',
cwd: serverPath
};
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
return { items: [ defaultOption ] };
try {
await fs.writeFile(envPath, JSON.stringify(defaultOption, null, 4), 'utf-8');
return { items: [defaultOption] };
} catch (error) {
logger.error('刷新连接配置失败:', error);
throw error;
}
}
function acquireConnectionOption() {
const envPath = path.join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) {
return refreshConnectionOption(envPath);
}
async function acquireConnectionOption() {
try {
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
const data = await fs.readFile(envPath, 'utf-8');
const option = JSON.parse(data);
if (!option.items) {
return refreshConnectionOption(envPath);
}
if (option.items && option.items.length === 0) {
return refreshConnectionOption(envPath);
if (!option.items || option.items.length === 0) {
return await refreshConnectionOption();
}
// 按照前端的规范,整理成 commandString 样式
option.items = option.items.map((item: any) => {
option.items = option.items.map((item: { connectionType: string; commandString: string; command: any; args: any; url: any; }) => {
if (item.connectionType === 'STDIO') {
item.commandString = [item.command, ...item.args]?.join(' ');
} else {
item.url = item.url;
}
return item;
});
return option;
} catch (error) {
logger.error('读取 .env 配置文件');
return refreshConnectionOption(envPath);
logger.error('读取 .env 配置文件失败:', error);
return await refreshConnectionOption();
}
}
function updateConnectionOption(data: any) {
const envPath = path.join(__dirname, '..', '.env');
const connection = { items: data };
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
async function updateConnectionOption(data: any) {
try {
await fs.writeFile(envPath, JSON.stringify({ items: data }, null, 4), 'utf-8');
} catch (error) {
logger.error('更新连接配置失败:', error);
throw error;
}
}
const __dirname = dirname(fileURLToPath(import.meta.url));
const devHome = path.join(__dirname, '..', '..');
// 设置运行时路径
import { setRunningCWD } from './hook/setting.js';
setRunningCWD(devHome);
// 启动 WebSocket 服务器
const wss = new WebSocketServer({ port: 8282 });
console.log('WebSocket 服务器已启动:', 'ws://localhost:8282');
console.log('listen on ws://localhost:8282');
wss.on('connection', (ws: any) => {
// 仿造 webview 进行统一接口访问
wss.on('connection', (ws) => {
const webview = new VSCodeWebViewLike(ws);
// 先发送成功建立的消息
webview.postMessage({
command: 'hello',
data: {
@ -108,29 +95,28 @@ wss.on('connection', (ws: any) => {
}
});
const option = acquireConnectionOption();
acquireConnectionOption().then(option => {
webview.onDidReceiveMessage(async (message) => {
console.log(
chalk.white('receive command') +
chalk.blue(` [${message.command || '未定义'}]`)
);
// 注册消息接受的管线
webview.onDidReceiveMessage(message => {
logger.info(`command: [${message.command || 'No Command'}]`);
const { command, data } = message;
switch (command) {
case 'web/launch-signature':
const launchResult = {
code: 200,
msg: option.items
};
webview.postMessage({
command: 'web/launch-signature',
data: launchResult
data: {
code: 200,
msg: option.items
}
});
break;
case 'web/update-connection-signature':
updateConnectionOption(data);
await updateConnectionOption(data);
break;
default:
@ -138,4 +124,7 @@ wss.on('connection', (ws: any) => {
break;
}
});
}).catch(error => {
logger.error('初始化连接配置失败:', error);
});
});

View File

@ -0,0 +1,132 @@
import { McpOptions } from './client.dto.js';
import { SingleFileMonitor, FileMonitorConfig } from './file-monitor.service.js';
import { PostMessageble } from '../hook/adapter.js';
import * as fs from 'fs';
import * as path from 'path';
import { pino } from 'pino';
import chalk from 'chalk';
// 保留现有 logger 配置
const logger = pino({
});
function getFilePath(options: {
cwd?: string;
args?: string[];
}): string {
const baseDir = options.cwd || process.cwd();
const targetFile = options.args?.length ? options.args[options.args.length - 1] : '';
if (!targetFile || path.isAbsolute(targetFile)) {
return targetFile;
}
return path.resolve(baseDir, targetFile);
}
export class McpServerConnectMonitor {
private Monitor: SingleFileMonitor | undefined;
private Options: McpOptions;
private uuid: string;
private webview: PostMessageble | undefined;
private filePath: string;
constructor(uuid: string, options: McpOptions, onchange: Function, webview?: PostMessageble) {
this.Options = options;
this.webview = webview;
this.uuid = uuid;
this.filePath = getFilePath(options);
// 记录实例创建
// logger.info({ uuid, connectionType: options.connectionType }, 'Created new connection monitor instance');
switch (options.connectionType) {
case 'STDIO':
this.setupStdioMonitor(onchange);
break;
case 'SSE':
logger.info({ uuid }, 'SSE connection type configured but not implemented');
break;
case 'STREAMABLE_HTTP':
logger.info({ uuid }, 'STREAMABLE_HTTP connection type configured but not implemented');
break;
}
}
private setupStdioMonitor(onchange: Function) {
const fileConfig: FileMonitorConfig = {
filePath: this.filePath,
debounceTime: 500,
duplicateCheckTime: 500,
onChange: async (curr, prev) => {
try {
await onchange(this.uuid, this.Options);
console.log('send something');
this.sendWebviewMessage('connect/refresh', {
code: 200,
msg: {
message: 'refresh connect success',
uuid: this.uuid,
}
});
console.log(
chalk.green('Connection refresh successfully')
);
} catch (err) {
this.sendWebviewMessage('connect/refresh', {
code: 500,
msg: {
message: 'refresh connect failed',
uuid: this.uuid,
}
});
// 使用 error 级别记录错误
logger.error({ uuid: this.uuid, error: err }, 'Connection refresh failed');
}
},
onDelete: () => {
// 使用 warn 级别记录文件删除
logger.warn({ uuid: this.uuid }, 'Monitored file has been deleted');
},
onStart: () => {
// 使用 info 级别记录监控开始
// logger.info({ uuid: this.uuid, filePath: path.resolve(fileConfig.filePath) }, 'Started monitoring file');
try {
const stats = fs.statSync(fileConfig.filePath);
// 使用 debug 级别记录详细文件信息
logger.debug({
uuid: this.uuid,
size: stats.size,
ctime: new Date(stats.ctime).toLocaleString()
}, 'File information retrieved');
} catch (err) {
// 使用 error 级别记录获取文件信息失败
logger.error({ uuid: this.uuid, error: err }, 'Failed to retrieve file information');
}
},
onError: (error) => {
// 使用 error 级别记录监控错误
logger.error({ uuid: this.uuid, error }, 'Error occurred during monitoring');
}
};
this.Monitor = new SingleFileMonitor(fileConfig);
}
private sendWebviewMessage(command: string, data: any) {
// 发送消息到webview
this.webview?.postMessage({ command, data });
}
public close() {
this.Monitor?.close();
}
}

View File

@ -3,18 +3,34 @@ import { RequestClientType } from '../common/index.dto.js';
import { connect } from './client.service.js';
import { RestfulResponse } from '../common/index.dto.js';
import { McpOptions } from './client.dto.js';
import { McpServerConnectMonitor } from './connect-monitor.service.js';
import * as crypto from 'node:crypto';
import path from 'node:path';
import fs from 'node:fs';
import * as os from 'os';
import { PostMessageble } from '../hook/adapter.js';
import Table from 'cli-table3';
import chalk from 'chalk';
export const clientMap: Map<string, RequestClientType> = new Map();
export function getClient(clientId?: string): RequestClientType | undefined {
return clientMap.get(clientId || '');
}
export const clientMonitorMap: Map<string, McpServerConnectMonitor> = new Map();
export async function updateClientMap(uuid: string, options: McpOptions): Promise<{ res: boolean; error?: any }> {
try {
const client = await connect(options);
clientMap.set(uuid, client);
const tools = await client.listTools();
console.log(
chalk.white('update client tools'),
chalk.blue(tools.tools.map(tool => tool.name).join(','))
);
return { res: true };
} catch (error) {
console.error('[updateClientMap] error:', error);
return { res: false, error };
}
}
export function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
try {
const commandString = command + ' ' + args.join(' ');
@ -254,25 +270,6 @@ export async function connectService(
webview?: PostMessageble
): Promise<RestfulResponse> {
try {
// 使用cli-table3创建美观的表格
const table = new Table({
head: ['Property', 'Value'],
colWidths: [20, 60],
style: {
head: ['green'],
border: ['grey']
}
});
table.push(
['Connection Type', option.connectionType],
['Command', option.command || 'N/A'],
['Arguments', option.args?.join(' ') || 'N/A'],
['Working Directory', option.cwd || 'N/A'],
['URL', option.url || 'N/A']
);
console.log(table.toString());
// 预处理字符串
await preprocessCommand(option, webview);
@ -288,8 +285,14 @@ export async function connectService(
// }
// const client = clientMap.get(uuid)!;
{
clientMap.get(uuid)?.disconnect();
clientMonitorMap.get(uuid)?.close();
}
const client = await connect(option);
clientMap.set(uuid, client);
clientMonitorMap.set(uuid, new McpServerConnectMonitor(uuid, option, updateClientMap, webview));
const versionInfo = client.getServerVersion();

View File

@ -0,0 +1,185 @@
import * as fs from 'fs';
import * as path from 'path';
/**
*
*/
interface FileMonitorConfig {
filePath: string;
onChange?: (curr: fs.Stats, prev: fs.Stats) => void;
onDelete?: () => void;
onStart?: () => void;
onError?: (error: Error) => void;
debounceTime?: number; // 防抖时间(毫秒)
duplicateCheckTime?: number; // 去重检查时间阈值(毫秒)
}
/**
*
*/
class SingleFileMonitor {
private filePath: string;
private onChange: (curr: fs.Stats, prev: fs.Stats) => void;
private onDelete: () => void;
private onError: (error: Error) => void;
private watcher: fs.FSWatcher | null = null;
private exists: boolean = false;
private lastModified: number = 0;
private lastSize: number = 0;
private debounceTimer: NodeJS.Timeout | null = null;
private debounceTime: number;
private duplicateCheckTime: number; // 去重检查时间阈值
private lastChangeTime: number = 0;
private lastChangeSize: number = 0;
private isProcessingChange = false;
private config: FileMonitorConfig; // 添加config属性
constructor(config: FileMonitorConfig) {
this.config = config; // 保存配置
this.filePath = config.filePath;
this.onChange = config.onChange || (() => {});
this.onDelete = config.onDelete || (() => {});
this.onError = config.onError || ((error) => console.error('文件监控错误:', error));
this.debounceTime = config.debounceTime || 1000;
// 使用配置中的去重时间默认800ms
this.duplicateCheckTime = config.duplicateCheckTime !== undefined
? config.duplicateCheckTime
: 800;
this.init();
}
private init() {
// 检查文件是否存在
this.checkFileExists()
.then(exists => {
this.exists = exists;
if (exists) {
const stats = fs.statSync(this.filePath);
this.lastModified = stats.mtimeMs;
this.lastSize = stats.size;
}
this.config.onStart?.(); // 使用保存的config属性
this.startWatching();
})
.catch(this.onError);
}
private startWatching() {
try {
this.watcher = fs.watch(this.filePath, (eventType) => {
if (eventType === 'change') {
this.handleFileChange(true);
}
});
// console.log(`正在监控文件: ${this.filePath}`);
} catch (error) {
this.onError(error as Error);
}
}
private checkFileExists(): Promise<boolean> {
return new Promise(resolve => {
fs.access(this.filePath, fs.constants.F_OK, (err) => {
resolve(!err);
});
});
}
private handleFileChange(isFromWatch: boolean = false) {
if (this.isProcessingChange) return;
if (!this.exists) {
this.checkFileExists()
.then(exists => {
if (exists) {
this.exists = true;
const stats = fs.statSync(this.filePath);
this.lastModified = stats.mtimeMs;
this.lastSize = stats.size;
}
});
return;
}
let currentStats: fs.Stats;
try {
currentStats = fs.statSync(this.filePath);
} catch (error) {
this.exists = false;
this.onDelete();
return;
}
const currentMtime = currentStats.mtimeMs;
const currentSize = currentStats.size;
if (currentSize === this.lastSize && currentMtime - this.lastModified < 800) {
return;
}
const now = Date.now();
// 使用可配置的去重时间阈值
if (now - this.lastChangeTime < this.duplicateCheckTime && currentSize === this.lastChangeSize) {
return;
}
this.lastChangeTime = now;
this.lastChangeSize = currentSize;
const prevStats = fs.statSync(this.filePath);
this.lastModified = currentMtime;
this.lastSize = currentSize;
this.isProcessingChange = true;
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(() => {
this.checkFileExists()
.then(exists => {
if (exists) {
const currStats = fs.statSync(this.filePath);
this.onChange(currStats, prevStats);
}
})
.catch(this.onError)
.finally(() => {
this.isProcessingChange = false;
this.debounceTimer = null;
});
}, this.debounceTime);
}
private checkFileStatus() {
this.checkFileExists()
.then(exists => {
if (this.exists && !exists) {
this.exists = false;
this.onDelete();
} else if (!this.exists && exists) {
this.exists = true;
const stats = fs.statSync(this.filePath);
this.lastModified = stats.mtimeMs;
this.lastSize = stats.size;
}
})
.catch(this.onError);
}
public close() {
if (this.watcher) {
// 明确指定close方法的类型解决TS2554错误
(this.watcher.close as (callback?: () => void) => void)(() => {
// console.log(`已停止监控文件: ${this.filePath}`);
});
this.watcher = null;
}
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
}
}
export { SingleFileMonitor, FileMonitorConfig };

1
service/src/sdk.ts Normal file
View File

@ -0,0 +1 @@
export { TaskLoopAdapter, OmAgent, OmAgentConfiguration, UserMessage, AssistantMessage } from './hook/sdk.js';

View File

@ -1,12 +1,14 @@
import { WebSocketServer } from 'ws';
import pino from 'pino';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { routeMessage } from './common/router.js';
import { VSCodeWebViewLike } from './hook/adapter.js';
import path from 'node:path';
import * as fs from 'node:fs';
import fs from 'fs';
import path from 'path';
import { setRunningCWD } from './hook/setting.js';
import { exit } from 'node:process';
import { exit } from 'process';
export interface VSCodeMessage {
command: string;
@ -15,22 +17,13 @@ export interface VSCodeMessage {
}
const logger = pino.default({
transport: {
target: 'pino-pretty', // 启用 pino-pretty
options: {
colorize: true, // 开启颜色
levelFirst: true, // 先打印日志级别
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', // 格式化时间
ignore: 'pid,hostname', // 忽略部分字段
}
}
});
export type MessageHandler = (message: VSCodeMessage) => void;
function refreshConnectionOption(envPath: string) {
const serverPath = path.join(__dirname, '..', '..', 'servers');
const serverPath = join(__dirname, '..', '..', 'servers');
const defaultOption = {
connectionType: 'STDIO',
commandString: 'mcp run main.py',
@ -38,12 +31,11 @@ function refreshConnectionOption(envPath: string) {
};
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
return { items: [defaultOption] };
}
function acquireConnectionOption() {
const envPath = path.join(__dirname, '..', '.env');
const envPath = join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) {
return refreshConnectionOption(envPath);
@ -52,11 +44,7 @@ function acquireConnectionOption() {
try {
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
if (!option.items) {
return refreshConnectionOption(envPath);
}
if (option.items && option.items.length === 0) {
if (!option.items || option.items.length === 0) {
return refreshConnectionOption(envPath);
}
@ -67,7 +55,6 @@ function acquireConnectionOption() {
} else {
item.url = item.url;
}
return item;
});
@ -79,20 +66,22 @@ function acquireConnectionOption() {
}
}
if (!fs.existsSync(path.join(__dirname, '..', '.env.website.local'))) {
// 验证 .env.website.local 存在性
const localEnvPath = join(__dirname, '..', '.env.website.local');
if (!fs.existsSync(localEnvPath)) {
console.log('.env.website.local 不存在!');
exit(0);
}
const authPassword = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.env.website.local'), 'utf-8')).password;
// 读取认证密码
const authPassword = JSON.parse(fs.readFileSync(localEnvPath, 'utf-8')).password;
function updateConnectionOption(data: any) {
const envPath = path.join(__dirname, '..', '.env');
const connection = { items: data };
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
const envPath = join(__dirname, '..', '.env');
fs.writeFileSync(envPath, JSON.stringify({ items: data }, null, 4));
}
const devHome = path.join(__dirname, '..', '..');
const devHome = join(__dirname, '..', '..');
setRunningCWD(devHome);
function verifyToken(url: string) {
@ -104,30 +93,25 @@ function verifyToken(url: string) {
}
}
const wss = new WebSocketServer(
{
const wss = new WebSocketServer({
port: 8282,
verifyClient: (info, callback) => {
console.log(info.req.url);
const ok = verifyToken(info.req.url || '');
const url = info.req.url || '';
const ok = verifyToken(url);
if (!ok) {
callback(false, 401, 'Unauthorized: Invalid token');
} else {
callback(true); // 允许连接
callback(true);
}
}
},
);
});
console.log('listen on ws://localhost:8282');
wss.on('connection', (ws: any) => {
// 仿造 webview 进行统一接口访问
wss.on('connection', (ws) => {
const webview = new VSCodeWebViewLike(ws);
// 先发送成功建立的消息
webview.postMessage({
command: 'hello',
data: {
@ -138,23 +122,19 @@ wss.on('connection', (ws: any) => {
const option = acquireConnectionOption();
// 注册消息接受的管线
webview.onDidReceiveMessage(message => {
logger.info(`command: [${message.command || 'No Command'}]`);
const { command, data } = message;
switch (command) {
case 'web/launch-signature':
const launchResult = {
code: 200,
msg: option.items
};
webview.postMessage({
command: 'web/launch-signature',
data: launchResult
data: {
code: 200,
msg: option.items
}
});
break;
case 'web/update-connection-signature':

View File

@ -53,9 +53,28 @@ export function loadSetting(): IConfig {
try {
const configData = fs.readFileSync(configPath, 'utf-8');
const config = JSON.parse(configData) as IConfig;
if (!config.LLM_INFO || (Array.isArray(config.LLM_INFO) && config.LLM_INFO.length === 0)) {
config.LLM_INFO = llms;
} else {
// 自动同步新的提供商:检查默认配置中是否有新的提供商未在用户配置中
const existingIds = new Set(config.LLM_INFO.map((llm: any) => llm.id));
const newProviders = llms.filter((llm: any) => !existingIds.has(llm.id));
if (newProviders.length > 0) {
console.log(`Adding ${newProviders.length} new providers:`, newProviders.map(p => p.name));
config.LLM_INFO.push(...newProviders);
// 自动保存更新后的配置
try {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
console.log('Configuration updated with new providers');
} catch (saveError) {
console.error('Failed to save updated configuration:', saveError);
}
}
}
return config;
} catch (error) {
console.error('Error loading config file, creating new one:', error);

324
service/task-loop.d.ts vendored Normal file
View File

@ -0,0 +1,324 @@
/* eslint-disable */
import type { OpenAI } from 'openai';
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
interface SchemaProperty {
title: string;
type: string;
description?: string;
}
interface InputSchema {
type: string;
properties: Record<string, SchemaProperty>;
required?: string[];
title?: string;
$defs?: any;
}
interface ToolItem {
name: string;
description: string;
inputSchema: InputSchema;
enabled: boolean;
anyOf?: any;
}
interface IExtraInfo {
created: number,
state: MessageState,
serverName: string,
usage?: ChatCompletionChunk['usage'];
enableXmlWrapper: boolean;
[key: string]: any;
}
interface ToolMessage {
role: 'tool';
index: number;
content: ToolCallContent[];
tool_call_id?: string
name?: string // 工具名称,当 role 为 tool
tool_calls?: ToolCall[],
extraInfo: IExtraInfo
}
interface TextMessage {
role: 'user' | 'assistant' | 'system';
content: string;
tool_call_id?: string
name?: string // 工具名称,当 role 为 tool
tool_calls?: ToolCall[],
extraInfo: IExtraInfo
}
export type ChatMessage = ToolMessage | TextMessage;
interface ChatStorage {
messages: ChatMessage[]
settings: ChatSetting
}
interface EnableToolItem {
name: string;
description: string;
enabled: boolean;
inputSchema: InputSchema;
}
export type Ref<T> = {
value: T;
};
export interface ToolCall {
id?: string;
index?: number;
type: string;
function: {
name: string;
arguments: string;
}
}
export interface ToolCallContent {
type: string;
text: string;
[key: string]: any;
}
export interface ToolCallResult {
state: MessageState;
content: ToolCallContent[];
}
export enum MessageState {
ServerError = 'server internal error',
ReceiveChunkError = 'receive chunk error',
Timeout = 'timeout',
MaxEpochs = 'max epochs',
Unknown = 'unknown error',
Abort = 'abort',
ToolCall = 'tool call failed',
None = 'none',
Success = 'success',
ParseJsonError = 'parse json error'
}
export interface IErrorMssage {
state: MessageState;
msg: string;
}
export interface IDoConversationResult {
stop: boolean;
}
export interface TaskLoopOptions {
/**
* The maximum number of epochs (conversation rounds) to perform.
*/
maxEpochs?: number;
/**
* The maximum number of retries allowed when parsing JSON responses fails.
*/
maxJsonParseRetry?: number;
/**
* A custom adapter that can be used to modify behavior or integrate with different environments.
*/
adapter?: any;
/**
* Verbosity level for logging:
* 0 - Silent, 1 - Errors only, 2 - Warnings and errors, 3 - Full debug output.
*/
verbose?: 0 | 1 | 2 | 3;
}
interface ChatSetting {
/**
* Index of the selected language model from a list of available models.
*/
modelIndex: number;
/**
* System-level prompt used to guide the behavior of the assistant.
*/
systemPrompt: string;
/**
* List of tools that are enabled and available during the chat.
*/
enableTools: EnableToolItem[];
/**
* Sampling temperature for generating responses.
* Higher values (e.g., 0.8) make output more random; lower values (e.g., 0.2) make it more focused and deterministic.
*/
temperature: number;
/**
* Whether web search is enabled for enhancing responses with real-time information.
*/
enableWebSearch: boolean;
/**
* Maximum length of the conversation context to keep.
*/
contextLength: number;
/**
* Whether multiple tools can be called in parallel within a single message.
*/
parallelToolCalls: boolean;
/**
* Whether to wrap tool call responses in XML format.
*/
enableXmlWrapper: boolean;
}
/**
* @description
*/
export class TaskLoop {
constructor(taskOptions?: TaskLoopOptions);
/**
* @description wait for connection
*/
waitConnection(): Promise<void>;
/**
* @description Set the task loop options
* @param taskOptions
*/
setTaskLoopOptions(taskOptions: TaskLoopOptions): void;
/**
* @description make chat data
* @param tabStorage
*/
makeChatData(tabStorage: any): ChatCompletionCreateParamsBase | undefined;
/**
* @description stop the task loop
*/
abort(): void;
/**
* @description Register a callback function triggered on error
* @param handler
*/
registerOnError(handler: (msg: IErrorMssage) => void): void;
/**
* @description Register a callback function triggered on chunk
* @param handler
*/
registerOnChunk(handler: (chunk: ChatCompletionChunk) => void): void;
/**
* @description Register a callback function triggered at the beginning of each epoch
* @param handler
*/
registerOnDone(handler: () => void): void;
/**
* @description Register a callback function triggered at the beginning of each epoch
* @param handler
*/
registerOnEpoch(handler: () => void): void;
/**
* @description Registers a callback function that is triggered when a tool call is completed. This method allows you to intercept and modify the output of the tool call.
* @param handler
*/
registerOnToolCalled(handler: (toolCallResult: ToolCallResult) => ToolCallResult): void;
/**
* @description Register a callback triggered after tool call finishes. You can intercept and modify the output.
* @param handler
*/
registerOnToolCall(handler: (toolCall: ToolCall) => ToolCall): void;
/**
* @description Get current LLM configuration
*/
getLlmConfig(): any;
/**
* @description Set the current LLM configuration, for Node.js environment
* @param config
* @example
* setLlmConfig({
* id: 'openai',
* baseUrl: 'https://api.openai.com/v1',
* userToken: 'sk-xxx',
* userModel: 'gpt-3.5-turbo',
* })
*/
setLlmConfig(config: any): void;
/**
* @description Set proxy server
* @param maxEpochs
*/
setMaxEpochs(maxEpochs: number): void;
/**
* @description bind streaming content and tool calls
*/
bindStreaming(content: Ref<string>, toolCalls: Ref<ToolCall[]>): void;
/**
* @description not finish
*/
connectToService(): Promise<void>;
/**
* @description
* @param proxyServer
*/
setProxyServer(proxyServer: string): void;
/**
* @description Get all available tool list
*/
listTools(): Promise<ToolItem[]>;
/**
* @description Start the loop and asynchronously update the DOM
*/
start(tabStorage: ChatStorage, userMessage: string): Promise<void>;
/**
* @description Create single conversation context
*/
createStorage(settings?: ChatSetting): Promise<ChatStorage>;
/**
* @description Get prompt template from mcp server
*/
getPrompt(promptId: string, args?: Record<string, any>): Promise<string>;
/**
* @description Get resource template from mcp server
*/
getResource(resourceUri: string): Promise<string>;
}
export declare const getToolSchema: any;
export declare const useMessageBridge: any;
export declare const llmManager: any;
export declare const llms: any;
export declare const pinkLog: any;
export declare const redLog: any;
export declare const ElMessage: any;
export declare const handleToolCalls: any;
export declare const getPlatform: any;

View File

@ -10,7 +10,7 @@
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"declaration": true,
"declarationMap": true,
"declarationMap": false,
"experimentalDecorators": true,
},
"paths": {

View File

@ -1,4 +1,4 @@
const path = require('path');
import path from 'path';
module.exports = {
entry: './src/main.ts',

View File

@ -1,10 +1,10 @@
import * as vscode from 'vscode';
import { registerCommands, registerTreeDataProviders } from '.';
import { HelpProvider } from '../sidebar/help.controller';
import { McpWorkspaceConnectProvider } from '../sidebar/workspace.controller';
import { McpInstalledConnectProvider } from '../sidebar/installed.controller';
import { WebviewController } from '../webview/webview.controller';
import { HookController } from '../hook/hook.controller';
import { registerCommands, registerTreeDataProviders } from './index.js';
import { HelpProvider } from '../sidebar/help.controller.js';
import { McpWorkspaceConnectProvider } from '../sidebar/workspace.controller.js';
import { McpInstalledConnectProvider } from '../sidebar/installed.controller.js';
import { WebviewController } from '../webview/webview.controller.js';
import { HookController } from '../hook/hook.controller.js';
export const InstallModules = [
McpWorkspaceConnectProvider,

View File

@ -1,4 +1,4 @@
import { CommandHandlerDescriptor, IRegisterCommandItem, IRegisterTreeDataProviderItem, TreeDataProviderConstructor } from "./index.dto";
import { CommandHandlerDescriptor, IRegisterCommandItem, IRegisterTreeDataProviderItem, TreeDataProviderConstructor } from "./index.dto.js";
export const registerCommands = new Array<[string, IRegisterCommandItem]>();
export const registerTreeDataProviders = new Map<string, IRegisterTreeDataProviderItem<any>>();

View File

@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import { setRunningCWD, setVscodeWorkspace } from '../openmcp-sdk/service';
import { launch } from './common/entry';
import { initialiseI18n } from './i18n';
import { setRunningCWD, setVscodeWorkspace } from '../openmcp-sdk/service/index.js';
import { launch } from './common/entry.js';
import { initialiseI18n } from './i18n/index.js';
export function activate(context: vscode.ExtensionContext) {
console.log('activate openmcp');
@ -10,11 +10,9 @@ export function activate(context: vscode.ExtensionContext) {
// 获取当前打开的项目的路径
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
const workspace = workspaceFolder?.uri.fsPath || '';
setVscodeWorkspace(workspace);
setRunningCWD(context.extensionPath);
initialiseI18n(context);
launch(context);
}

View File

@ -95,7 +95,13 @@ export function getConnectionConfig() {
*/
export function getWorkspaceConnectionConfigPath() {
const workspace = getWorkspacePath();
if (!workspace) {
throw new Error('No workspace found. Please open a folder in VSCode first.');
}
const configDir = fspath.join(workspace, '.openmcp');
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true }); // 递归创建目录
}
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
return connectionConfig;
}
@ -110,6 +116,9 @@ export function getWorkspaceConnectionConfig() {
}
const workspace = getWorkspacePath();
if (!workspace) {
throw new Error('No workspace found. Please open a folder in VSCode first.');
}
const configDir = fspath.join(workspace, '.openmcp');
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
@ -296,6 +305,7 @@ function normaliseConnectionFilePath(item: McpOptions, workspace: string) {
export function getWorkspacePath() {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
console.log('getWorkspacePath: ', vscode.workspace.workspaceFolders);
return (workspaceFolder?.uri.fsPath || '').replace(/\\/g, '/');
}
@ -367,3 +377,18 @@ export async function getFirstValidPathFromCommand(command: string, cwd: string)
return undefined;
}
export async function exportFile(filename: string, content: any) {
// 使用 vscode 的 api创建文件导出窗口询问用户
const uri = await vscode.window.showSaveDialog({
defaultUri: vscode.Uri.file(filename),
filters: {
'JSON': ['json']
}
});
if (uri) {
fs.writeFileSync(uri.fsPath, content, 'utf-8');
}
}

View File

@ -1,4 +1,4 @@
import { RegisterCommand } from "../common";
import { RegisterCommand } from "../common/index.js";
import * as vscode from 'vscode';
import * as path from 'path';
import Tesseract from 'tesseract.js';

View File

@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import { McpOptions } from '../global';
import { McpOptions } from '../global.js';
export class SidebarItem extends vscode.TreeItem {
constructor(

View File

@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import { SidebarItem } from './common';
import { RegisterTreeDataProvider } from '../common';
import { t } from '../i18n';
import { SidebarItem } from './common.js';
import { RegisterTreeDataProvider } from '../common/index.js';
import { t } from '../i18n/index.js';
@RegisterTreeDataProvider('openmcp.sidebar.help')
export class HelpProvider implements vscode.TreeDataProvider<SidebarItem> {

View File

@ -1,9 +1,9 @@
import * as vscode from 'vscode';
import { RegisterCommand, RegisterTreeDataProvider } from '../common';
import { ConnectionViewItem } from './common';
import { getConnectionConfig, getInstalledConnectionConfigPath, saveConnectionConfig } from '../global';
import { acquireInstalledConnection, deleteInstalledConnection } from './installed.service';
import { revealOpenMcpWebviewPanel } from '../webview/webview.service';
import { RegisterCommand, RegisterTreeDataProvider } from '../common/index.js';
import { ConnectionViewItem } from './common.js';
import { getConnectionConfig, getInstalledConnectionConfigPath, saveConnectionConfig } from '../global.js';
import { acquireInstalledConnection, deleteInstalledConnection } from './installed.service.js';
import { revealOpenMcpWebviewPanel } from '../webview/webview.service.js';
@RegisterTreeDataProvider('openmcp.sidebar.installed-connection')
export class McpInstalledConnectProvider implements vscode.TreeDataProvider<ConnectionViewItem> {

View File

@ -1,7 +1,7 @@
import { getConnectionConfig, panels, saveConnectionConfig, getFirstValidPathFromCommand, McpOptions } from "../global";
import { getConnectionConfig, panels, saveConnectionConfig, getFirstValidPathFromCommand, McpOptions } from "../global.js";
import { exec, spawn } from 'node:child_process';
import * as vscode from 'vscode';
import { t } from "../i18n";
import { t } from "../i18n/index.js";
export async function deleteInstalledConnection(item: McpOptions[] | McpOptions) {
// 弹出确认对话框

View File

@ -1,9 +1,9 @@
import * as vscode from 'vscode';
import { RegisterCommand, RegisterTreeDataProvider } from '../common';
import { getWorkspaceConnectionConfig, getWorkspaceConnectionConfigPath, getWorkspacePath, saveWorkspaceConnectionConfig } from '../global';
import { ConnectionViewItem } from './common';
import { revealOpenMcpWebviewPanel } from '../webview/webview.service';
import { acquireUserCustomConnection, deleteUserConnection } from './workspace.service';
import { RegisterCommand, RegisterTreeDataProvider } from '../common/index.js';
import { getWorkspaceConnectionConfig, getWorkspaceConnectionConfigPath, getWorkspacePath, saveWorkspaceConnectionConfig } from '../global.js';
import { ConnectionViewItem } from './common.js';
import { revealOpenMcpWebviewPanel } from '../webview/webview.service.js';
import { acquireUserCustomConnection, deleteUserConnection } from './workspace.service.js';
@RegisterTreeDataProvider('openmcp.sidebar.workspace-connection')
export class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<ConnectionViewItem> {
@ -55,7 +55,7 @@ export class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<Conn
public refresh(context: vscode.ExtensionContext): void {
console.log(this);
this._onDidChangeTreeData.fire();
this._onDidChangeTreeData.fire(undefined);
}
@RegisterCommand('addConnection')

View File

@ -1,6 +1,8 @@
import { getFirstValidPathFromCommand, getWorkspaceConnectionConfig, getWorkspacePath, McpOptions, panels, saveWorkspaceConnectionConfig } from "../global";
import { getFirstValidPathFromCommand, getWorkspaceConnectionConfig, getWorkspacePath, McpOptions, panels, saveWorkspaceConnectionConfig } from "../global.js";
import * as vscode from 'vscode';
import { t } from "../i18n";
import { t } from "../i18n/index.js";
import { exec } from 'child_process';
import { promisify } from 'util';
export async function deleteUserConnection(item: McpOptions[] | McpOptions) {
// 弹出确认对话框
@ -45,8 +47,7 @@ export async function deleteUserConnection(item: McpOptions[] | McpOptions) {
}
export async function validateAndGetCommandPath(command: string, cwd?: string): Promise<string> {
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
try {

27
src/test/e2e/runTest.ts Normal file
View File

@ -0,0 +1,27 @@
import * as path from 'path';
import { runTests } from '@vscode/test-electron';
async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../../');
console.log('Extension Path:', extensionDevelopmentPath); // 添加日志验证路径
// The path to the extension test script
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, './suite/index.js');
// Download VS Code, unzip it and run the integration test
await runTests({
extensionDevelopmentPath: extensionDevelopmentPath,
extensionTestsPath: extensionTestsPath,
});
} catch (err) {
console.error('Failed to run tests');
console.error(err);
process.exit(1);
}
}
main();

View File

@ -0,0 +1,31 @@
import * as assert from 'assert';
import * as vscode from 'vscode';
suite('测试基础插件激活和命令注册', () => {
vscode.window.showInformationMessage('开始测试基础插件激活和命令注册');
setup(async () => {
await vscode.commands.executeCommand('workbench.view.extension.openmcp-sidebar');
});
test('测试的测试', () => {
assert.strictEqual([1, 2, 3].indexOf(5), -1);
assert.strictEqual([1, 2, 3].indexOf(0), -1);
});
test('插件存在测试', async () => {
const ext = vscode.extensions.getExtension('kirigaya.openmcp');
assert.ok(ext, '插件未找到');
});
test('插件激活测试', async () => {
const ext = vscode.extensions.getExtension('kirigaya.openmcp');
await ext?.activate();
assert.ok(ext?.isActive, '插件未激活');
});
test('命令 openmcp.showOpenMCP 注册测试', async () => {
const commands = await vscode.commands.getCommands(true);
assert.ok(commands.includes('openmcp.showOpenMCP'), '命令未注册');
});
//
});

View File

@ -0,0 +1,44 @@
import * as assert from 'assert';
import * as vscode from 'vscode';
import * as sinon from 'sinon';
suite('连接管理测试', () => {
vscode.window.showInformationMessage('开始测试连接管理');
let inputBoxStub: sinon.SinonStub;
let quickPickStub: sinon.SinonStub;
setup(async () => {
// mock showQuickPick
// quickPickStub = sinon.stub(vscode.window, 'showQuickPick');
// // mock showInputBox
// inputBoxStub = sinon.stub(vscode.window, 'showInputBox');
await vscode.commands.executeCommand('workbench.view.extension.openmcp-sidebar');
});
teardown(() => {
sinon.restore();
});
test('新建STDIO连接', async function () {
this.timeout(15000);
// await vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.addConnection');
// quickPickStub.onFirstCall().resolves('STDIO');
// await new Promise(resolve => setTimeout(resolve, 5000));
// inputBoxStub.onFirstCall().resolves('echo'); // command
// await new Promise(resolve => setTimeout(resolve, 5000));
// inputBoxStub.onSecondCall().resolves(''); // cwd
await vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.addConnection');
});
test('等待以便观察窗口', async function () {
this.timeout(15000);
await new Promise(resolve => setTimeout(resolve, 10000));
});
});

View File

@ -0,0 +1,37 @@
import * as path from 'path';
import Mocha from 'mocha';
import glob from 'glob';
export function run(): Promise<void> {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd'
});
const testsRoot = path.resolve(__dirname, '..');
return new Promise((c, e) => {
glob('**/**.test.js', { cwd: testsRoot }, (err: Error | null, files: string[]) => {
if (err) {
return e(err);
}
// Add files to the test suite
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
try {
// Run the mocha test
mocha.run(failures => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
console.error(err);
e(err);
}
});
});
}

View File

@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import { RegisterCommand } from "../common";
import { getDefaultLanunchSignature, getWorkspacePath, revealOpenMcpWebviewPanel } from './webview.service';
import { getWorkspaceConnectionConfigItemByPath } from '../global';
import { RegisterCommand } from "../common/index.js";
import { getDefaultLanunchSignature, getWorkspacePath, revealOpenMcpWebviewPanel } from './webview.service.js';
import { getWorkspaceConnectionConfigItemByPath } from '../global.js';
import path from 'path';
export class WebviewController {

View File

@ -1,8 +1,8 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as fspath from 'path';
import { ConnectionType, McpOptions, panels, updateInstalledConnectionConfig, updateWorkspaceConnectionConfig } from '../global';
import { routeMessage } from '../../openmcp-sdk/service';
import { ConnectionType, exportFile, McpOptions, panels, updateInstalledConnectionConfig, updateWorkspaceConnectionConfig } from '../global.js';
import { routeMessage } from '../../openmcp-sdk/service/index.js';
export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined {
const viewRoot = fspath.join(context.extensionPath, 'openmcp-sdk', 'renderer');
@ -45,7 +45,7 @@ export function revealOpenMcpWebviewPanel(
// 对老版本的 option 进行调整
option = Array.isArray(option)? option : [option];
option.forEach(item => {
option.forEach((item: McpOptions) => {
const itemType = (item.type || item.connectionType).toUpperCase() as ConnectionType;
item.type = undefined;
item.connectionType = itemType;
@ -101,6 +101,10 @@ export function revealOpenMcpWebviewPanel(
}
break;
case 'vscode/export-file':
exportFile(data.filename, data.content);
break;
case 'vscode/clipboard/writeText':
vscode.env.clipboard.writeText(data.text);
break;

Some files were not shown because too many files have changed in this diff Show More