Compare commits

..

No commits in common. "e09c8beb7ecc596e31fb91338861ef14769aef96" and "0f32993edb4ed17509c5461ac61b8e0a837908e8" have entirely different histories.

106 changed files with 2965 additions and 18687 deletions

View File

@ -1,45 +0,0 @@
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

View File

@ -1,44 +0,0 @@
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

4
.gitignore vendored
View File

@ -15,6 +15,4 @@ resources/renderer
resources/service resources/service
*.traineddata *.traineddata
.turbo .turbo
stats.html stats.html
.openmcp
test-vsix

View File

@ -23,8 +23,4 @@ software/**
.editorconfig .editorconfig
.gitattributes .gitattributes
*.vsix *.vsix
.turbo
.github
webpack
.openmcp

View File

@ -1,19 +1,5 @@
# Change Log # 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 ## [main] 0.1.5
- 修复 gemini 获取模型列表时存在 models 前缀的问题 - 修复 gemini 获取模型列表时存在 models 前缀的问题
- 增加 web api 功能 - 增加 web api 功能

169
CLAUDE.md
View File

@ -1,169 +0,0 @@
# 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,7 +60,6 @@ Supports multiple large models
| `service` | Cloud sync for system configuration | `MVP` | 0% | `P1` | | `service` | Cloud sync for system configuration | `MVP` | 0% | `P1` |
| `all` | System prompt management module | `Iteration` | 100% | `Done` | | `all` | System prompt management module | `Iteration` | 100% | `Done` |
| `service` | Tool-wise logging system | `MVP` | 0% | `P1` | | `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` | | `service` | Built-in OCR for character recognition | `Iteration` | 100% | `Done` |
## Project Concept ## Project Concept
@ -135,8 +134,9 @@ npm run setup
Start dev server: Start dev server:
```bash ```bash
npm run serve npm run dev
``` ```
Port usage: 8282 (renderer) + 8081 (service)
### Extension Development ### Extension Development
@ -151,21 +151,4 @@ Build for deployment:
```bash ```bash
npm run build npm run build
``` ```
build vscode extension:
```bash
npm run build:plugin
```
Then just press F5, いただきます (Let's begin) 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

View File

@ -1,20 +0,0 @@
// 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));

7455
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="t('tool-use')" placement="top" effect="light"> <el-tooltip :content="t('tool-use')" placement="top">
<div class="setting-button" :class="{ 'active': availableToolsNum > 0 }" size="small" @click="toggleTools"> <div class="setting-button" :class="{ 'active': availableToolsNum > 0 }" size="small" @click="toggleTools">
<span class="iconfont icon-tool badge-outer"> <span class="iconfont icon-tool badge-outer">
<span class="badge-inner"> <span class="badge-inner">
@ -10,19 +10,7 @@
</el-tooltip> </el-tooltip>
<el-dialog v-model="showToolsDialog" width="800px"> <el-dialog v-model="showToolsDialog" title="工具管理" 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"> <div class="tools-dialog-container">
<el-scrollbar height="400px" class="tools-list"> <el-scrollbar height="400px" class="tools-list">
<div v-for="(tool, index) in tabStorage.settings.enableTools" :key="index" class="tool-item"> <div v-for="(tool, index) in tabStorage.settings.enableTools" :key="index" class="tool-item">
@ -35,27 +23,23 @@
</el-scrollbar> </el-scrollbar>
<el-scrollbar height="400px" class="schema-viewer"> <el-scrollbar height="400px" class="schema-viewer">
<!-- 如果激活 xml 指令包裹则展示对应的 prompt --> <div v-html="activeToolsSchemaHTML"></div>
<div v-if="tabStorage.settings.enableXmlWrapper" v-html="activeToolsXmlPrompt" />
<!-- 如果是普通模式则展示普通的工具列表 -->
<div v-else v-html="activeToolsSchemaHTML" />
</el-scrollbar> </el-scrollbar>
</div> </div>
<template #footer> <template #footer>
<el-button type="primary" @click="enableAllTools">{{ t('enable-all-tools') }}</el-button> <el-button type="primary" @click="enableAllTools">激活所有工具</el-button>
<el-button type="danger" @click="disableAllTools">{{ t('disable-all-tools') }}</el-button> <el-button type="danger" @click="disableAllTools">禁用所有工具</el-button>
<el-button type="primary" @click="showToolsDialog = false">{{ t("cancel") }}</el-button> <el-button type="primary" @click="showToolsDialog = false">{{ t("cancel") }}</el-button>
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, inject, onMounted, watch } from 'vue'; import { ref, computed, inject, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { type ChatStorage, type EnableToolItem, getToolSchema } from '../chat'; import { type ChatStorage, type EnableToolItem, getToolSchema } from '../chat';
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown'; import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
import { mcpClientAdapter } from '@/views/connect/core'; import { mcpClientAdapter } from '@/views/connect/core';
import { toolSchemaToPromptDescription } from '../../core/xml-wrapper';
const { t } = useI18n(); const { t } = useI18n();
@ -64,34 +48,26 @@ const tabStorage = inject('tabStorage') as ChatStorage;
const showToolsDialog = ref(false); const showToolsDialog = ref(false);
const availableToolsNum = computed(() => { const availableToolsNum = computed(() => {
return tabStorage.settings.enableTools.filter(tool => tool.enabled).length; return tabStorage.settings.enableTools.filter(tool => tool.enabled).length;
}); });
// toggleTools // toggleTools
const toggleTools = () => { const toggleTools = () => {
showToolsDialog.value = true; showToolsDialog.value = true;
}; };
const activeToolsSchemaHTML = computed(() => { const activeToolsSchemaHTML = computed(() => {
const toolsSchema = getToolSchema(tabStorage.settings.enableTools); const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
const jsonString = JSON.stringify(toolsSchema, null, 2); const jsonString = JSON.stringify(toolsSchema, null, 2);
return markdownToHtml( return markdownToHtml(
"```json\n" + jsonString + "\n```" "```json\n" + jsonString + "\n```"
); );
});
const activeToolsXmlPrompt = computed(() => {
const prompt = toolSchemaToPromptDescription(tabStorage.settings.enableTools);
return markdownToHtml(
"```markdown\n" + prompt + "\n```"
);
}); });
// - // -
const enableAllTools = () => { const enableAllTools = () => {
tabStorage.settings.enableTools.forEach(tool => { tabStorage.settings.enableTools.forEach(tool => {
tool.enabled = true; tool.enabled = true;
}); });
}; };
@ -103,64 +79,34 @@ const disableAllTools = () => {
}); });
}; };
// onMounted(async () => {
const updateToolsList = async () => {
// tool tabStorage.settings.enableTools // tool tabStorage.settings.enableTools
// enable // enable
const disableToolNames = new Set<string>( const disableToolNames = new Set<string>(
tabStorage.settings.enableTools tabStorage.settings.enableTools
.filter(tool => !tool.enabled) .filter(tool => !tool.enabled)
.map(tool => tool.name) .map(tool => tool.name)
); );
const newTools: EnableToolItem[] = []; const newTools: EnableToolItem[] = [];
for (const client of mcpClientAdapter.clients) { for (const client of mcpClientAdapter.clients) {
const tools = await client.getTools({ cache: false }); const tools = await client.getTools();
if (tools) { for (const tool of tools.values()) {
for (const tool of tools.values()) { const enabled = !disableToolNames.has(tool.name);
const enabled = !disableToolNames.has(tool.name);
newTools.push({ newTools.push({
name: tool.name, name: tool.name,
description: tool.description, description: tool.description,
inputSchema: tool.inputSchema, inputSchema: tool.inputSchema,
enabled enabled
}); });
}
} }
} }
tabStorage.settings.enableTools = newTools; tabStorage.settings.enableTools = newTools;
}
onMounted(async () => {
await updateToolsList();
watch(() => mcpClientAdapter.refreshSignal.value, async () => {
await updateToolsList();
});
}); });
</script> </script>
<style scoped> <style></style>
.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> <template>
<el-tooltip :content="t('websearch')" placement="top" effect="light"> <el-tooltip :content="t('websearch')" placement="top">
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableWebSearch }" size="small" <div class="setting-button" :class="{ 'active': tabStorage.settings.enableWebSearch }" size="small"
@click="toggleWebSearch"> @click="toggleWebSearch">
<span class="iconfont icon-web"></span> <span class="iconfont icon-web"></span>

View File

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

View File

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

View File

@ -2,14 +2,7 @@ import type { ToolCallContent, ToolCallResponse } from "@/hook/type";
import { MessageState, type ToolCall } from "../chat-box/chat"; import { MessageState, type ToolCall } from "../chat-box/chat";
import { mcpClientAdapter } from "@/views/connect/core"; import { mcpClientAdapter } from "@/views/connect/core";
import type { BasicLlmDescription } from "@/views/setting/llm"; import type { BasicLlmDescription } from "@/views/setting/llm";
import type OpenAI from "openai"; import { redLog } from "@/views/setting/util";
export interface TaskLoopChatOption {
id?: string
proxyServer?: string
enableXmlWrapper?: boolean
}
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
export interface ToolCallResult { export interface ToolCallResult {
state: MessageState; state: MessageState;
@ -67,7 +60,7 @@ function deserializeToolCallResponse(toolArgs: string) {
} }
} }
export function handleToolResponse(toolResponse: ToolCallResponse) { function handleToolResponse(toolResponse: ToolCallResponse) {
if (typeof toolResponse === 'string') { if (typeof toolResponse === 'string') {
return { return {
@ -105,67 +98,36 @@ 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 // grok 采用 id 作为 index需要将 id 映射到 zero-based 的 index
const id = typeof toolCall === 'string' ? toolCall : toolCall.id; if (!toolCall.id) {
if (!id) {
return 0; return 0;
} }
if (!callId2Index.has(id)) { if (!callId2Index.has(toolCall.id)) {
callId2Index.set(id, callId2Index.size); callId2Index.set(toolCall.id, callId2Index.size);
} }
return callId2Index.get(id)!; return callId2Index.get(toolCall.id)!;
} }
function geminiIndexAdapter(toolCall: ToolCall): IToolCallIndex {
/**
* @description
* @param toolCall
* @returns 0
*/
export function singleCallIndexAdapter(toolCall: ToolCall): IToolCallIndex {
// TODO: 等待后续支持 // TODO: 等待后续支持
return 0; return 0;
} }
/** function defaultIndexAdapter(toolCall: ToolCall): IToolCallIndex {
* @description
* @param toolCall
* @returns
*/
export function defaultIndexAdapter(toolCall: ToolCall): IToolCallIndex {
return toolCall.index || 0; return toolCall.index || 0;
} }
export function getToolCallIndexAdapter(llm: BasicLlmDescription, chatData: ChatCompletionCreateParamsBase) { export function getToolCallIndexAdapter(llm: BasicLlmDescription) {
// 如果是 xml 模式,那么 index adapter 必须是 idAsIndexAdapter
if (chatData.enableXmlWrapper) {
const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
}
if (llm.userModel.startsWith('gemini')) { if (llm.userModel.startsWith('gemini')) {
return singleCallIndexAdapter; return geminiIndexAdapter;
} }
if (llm.userModel.startsWith('grok')) { if (llm.userModel.startsWith('grok')) {
const callId2Index = new Map<string, number>(); const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index); return (toolCall: ToolCall) => grokIndexAdapter(toolCall, callId2Index);
} }
return defaultIndexAdapter; 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 */ /* eslint-disable */
import { ref, type Ref } from "vue"; import { ref, type Ref } from "vue";
import { type ToolCall, type ChatStorage, getToolSchema, MessageState, type ChatMessage, type ChatSetting, type EnableToolItem } from "../chat-box/chat"; import { type ToolCall, type ChatStorage, getToolSchema, MessageState } from "../chat-box/chat";
import { useMessageBridge, MessageBridge, createMessageBridge } from "@/api/message-bridge"; import { useMessageBridge, MessageBridge, createMessageBridge } from "@/api/message-bridge";
import type { OpenAI } from 'openai'; import type { OpenAI } from 'openai';
import { llmManager, llms, type BasicLlmDescription } from "@/views/setting/llm"; import { llmManager, llms, type BasicLlmDescription } from "@/views/setting/llm";
@ -13,15 +13,9 @@ import { mcpSetting } from "@/hook/mcp";
import { mcpClientAdapter } from "@/views/connect/core"; import { mcpClientAdapter } from "@/views/connect/core";
import type { ToolItem } from "@/hook/type"; import type { ToolItem } from "@/hook/type";
import chalk from 'chalk'; import chalk from 'chalk';
import { getXmlWrapperPrompt, getToolCallFromXmlString, getXmlsFromString, handleXmlWrapperToolcall, toNormaliseToolcall, getXmlResultPrompt } from "./xml-wrapper";
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk; export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export interface TaskLoopChatOption { export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string, proxyServer?: string };
id?: string
proxyServer?: string
enableXmlWrapper?: boolean
}
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
export interface TaskLoopOptions { export interface TaskLoopOptions {
maxEpochs?: number; maxEpochs?: number;
maxJsonParseRetry?: number; maxJsonParseRetry?: number;
@ -34,8 +28,6 @@ export interface IErrorMssage {
msg: string msg: string
} }
export { MessageState };
export interface IDoConversationResult { export interface IDoConversationResult {
stop: boolean; stop: boolean;
} }
@ -65,8 +57,8 @@ export class TaskLoop {
}; };
constructor( constructor(
private taskOptions: TaskLoopOptions = { private readonly taskOptions: TaskLoopOptions = {
maxEpochs: 50, maxEpochs: 20,
maxJsonParseRetry: 3, maxJsonParseRetry: 3,
adapter: undefined, adapter: undefined,
verbose: 0 verbose: 0
@ -85,58 +77,22 @@ export class TaskLoop {
throw new Error('adapter is required'); throw new Error('adapter is required');
} }
// 根据 adapter 创建 nodejs 下特殊的、基于 event 的 message bridge (不占用任何端口)
createMessageBridge(adapter.emitter); createMessageBridge(adapter.emitter);
// 用于进行连接同步
this.nodejsStatus.connectionFut = mcpClientAdapter.launch(); this.nodejsStatus.connectionFut = mcpClientAdapter.launch();
} }
// web 环境下 bridge 会自动加载完成 // web 环境下 bridge 会自动加载完成
this.bridge = useMessageBridge(); this.bridge = useMessageBridge();
// 注册 HMR
mcpClientAdapter.addConnectRefreshListener();
} }
public async waitConnection() { private handleChunkDeltaContent(chunk: ChatCompletionChunk) {
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 || ''; const content = chunk.choices[0]?.delta?.content || '';
if (content) { if (content) {
this.streamingContent.value += 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]; const toolCall = chunk.choices[0]?.delta?.tool_calls?.[0];
if (toolCall) { if (toolCall) {
@ -192,15 +148,9 @@ export class TaskLoop {
const { chunk } = data.msg as { chunk: ChatCompletionChunk }; const { chunk } = data.msg as { chunk: ChatCompletionChunk };
// 处理增量的 content 和 tool_calls // 处理增量的 content 和 tool_calls
if (chatData.enableXmlWrapper) { this.handleChunkDeltaContent(chunk);
this.handleChunkDeltaContent(chunk, chatData); this.handleChunkDeltaToolCalls(chunk, toolcallIndexAdapter);
// no tool call in enableXmlWrapper this.handleChunkUsage(chunk);
this.handleChunkUsage(chunk);
} else {
this.handleChunkDeltaContent(chunk, chatData);
this.handleChunkDeltaToolCalls(chunk, chatData, toolcallIndexAdapter);
this.handleChunkUsage(chunk);
}
this.consumeChunks(chunk); this.consumeChunks(chunk);
}, { once: false }); }, { once: false });
@ -257,35 +207,23 @@ export class TaskLoop {
const model = this.getLlmConfig().userModel; const model = this.getLlmConfig().userModel;
const temperature = tabStorage.settings.temperature; const temperature = tabStorage.settings.temperature;
const tools = getToolSchema(tabStorage.settings.enableTools);
const parallelToolCalls = tabStorage.settings.parallelToolCalls; const parallelToolCalls = tabStorage.settings.parallelToolCalls;
const proxyServer = mcpSetting.proxyServer || ''; const proxyServer = mcpSetting.proxyServer || '';
// 如果是 xml 模式,则 tools 为空
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
const tools = enableXmlWrapper ? []: getToolSchema(tabStorage.settings.enableTools);
const userMessages = []; const userMessages = [];
// 尝试获取 system prompt在 api 模式下systemPrompt 就是目标提词 // 尝试获取 system prompt在 api 模式下systemPrompt 就是目标提词
// 但是在 UI 模式下systemPrompt 只是一个 index需要从后端数据库中获取真实 prompt // 但是在 UI 模式下systemPrompt 只是一个 index需要从后端数据库中获取真实 prompt
let prompt = '';
// 如果存在系统提示词,则从数据库中获取对应的数据
if (tabStorage.settings.systemPrompt) { if (tabStorage.settings.systemPrompt) {
prompt += getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt; const prompt = getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
}
// 如果是 xml 模式,则在开头注入 xml userMessages.push({
if (enableXmlWrapper) { role: 'system',
prompt += getXmlWrapperPrompt(tabStorage.settings.enableTools, tabStorage); content: prompt
});
} }
userMessages.push({
role: 'system',
content: prompt
});
// 如果超出了 tabStorage.settings.contextLength, 则删除最早的消息 // 如果超出了 tabStorage.settings.contextLength, 则删除最早的消息
const loadMessages = tabStorage.messages.slice(- tabStorage.settings.contextLength); const loadMessages = tabStorage.messages.slice(- tabStorage.settings.contextLength);
userMessages.push(...loadMessages); userMessages.push(...loadMessages);
@ -302,8 +240,7 @@ export class TaskLoop {
tools, tools,
parallelToolCalls, parallelToolCalls,
messages: userMessages, messages: userMessages,
proxyServer, proxyServer
enableXmlWrapper,
} as ChatCompletionCreateParamsBase; } as ChatCompletionCreateParamsBase;
return chatData; return chatData;
@ -408,7 +345,7 @@ export class TaskLoop {
if (verbose > 0) { if (verbose > 0) {
console.log( console.log(
chalk.gray(`[${new Date().toLocaleString()}]`), chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.blueBright('🔧 using tool'), chalk.blueBright('🔧 calling tool'),
chalk.blueBright(toolCall.function!.name) chalk.blueBright(toolCall.function!.name)
); );
} }
@ -422,13 +359,13 @@ export class TaskLoop {
if (result.state === 'success') { if (result.state === 'success') {
console.log( console.log(
chalk.gray(`[${new Date().toLocaleString()}]`), chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.green('✓ use tools'), chalk.green('✓ call tools okey dockey'),
chalk.green(result.state) chalk.green(result.state)
); );
} else { } else {
console.log( console.log(
chalk.gray(`[${new Date().toLocaleString()}]`), chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.red('× use tools'), chalk.red('× fail to call tools'),
chalk.red(result.content.map(item => item.text).join(', ')) chalk.red(result.content.map(item => item.text).join(', '))
); );
} }
@ -438,7 +375,7 @@ export class TaskLoop {
private consumeEpochs() { private consumeEpochs() {
const { verbose = 0 } = this.taskOptions; const { verbose = 0 } = this.taskOptions;
if (verbose > 1) { if (verbose > 0) {
console.log( console.log(
chalk.gray(`[${new Date().toLocaleString()}]`), chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.blue('task loop enters a new epoch') chalk.blue('task loop enters a new epoch')
@ -449,14 +386,12 @@ export class TaskLoop {
private consumeDones() { private consumeDones() {
const { verbose = 0 } = this.taskOptions; const { verbose = 0 } = this.taskOptions;
if (verbose > 0) {
if (verbose > 1) {
console.log( console.log(
chalk.gray(`[${new Date().toLocaleString()}]`), chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.green('task loop finish a epoch') chalk.green('task loop finish a epoch')
); );
} }
return this.onDone(); return this.onDone();
} }
@ -526,7 +461,6 @@ export class TaskLoop {
// 等待连接完成 // 等待连接完成
await this.nodejsStatus.connectionFut; await this.nodejsStatus.connectionFut;
} }
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
// 添加目前的消息 // 添加目前的消息
tabStorage.messages.push({ tabStorage.messages.push({
@ -535,14 +469,13 @@ export class TaskLoop {
extraInfo: { extraInfo: {
created: Date.now(), created: Date.now(),
state: MessageState.Success, state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown', serverName: this.getLlmConfig().id || 'unknown'
enableXmlWrapper
} }
}); });
let jsonParseErrorRetryCount = 0; let jsonParseErrorRetryCount = 0;
const { const {
maxEpochs = 50, maxEpochs = 20,
verbose = 0 verbose = 0
} = this.taskOptions || {}; } = this.taskOptions || {};
@ -565,7 +498,7 @@ export class TaskLoop {
this.currentChatId = chatData.id!; this.currentChatId = chatData.id!;
const llm = this.getLlmConfig(); const llm = this.getLlmConfig();
const toolcallIndexAdapter = getToolCallIndexAdapter(llm, chatData); const toolcallIndexAdapter = getToolCallIndexAdapter(llm);
// 发送请求 // 发送请求
const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter); const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter);
@ -580,15 +513,14 @@ export class TaskLoop {
extraInfo: { extraInfo: {
created: Date.now(), created: Date.now(),
state: MessageState.Success, state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown', serverName: this.getLlmConfig().id || 'unknown'
enableXmlWrapper
} }
}); });
if (verbose > 0) { if (verbose > 0) {
console.log( console.log(
chalk.gray(`[${new Date().toLocaleString()}]`), chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.yellow('🤖 Agent wants to use these tools'), chalk.yellow('🤖 llm wants to call these tools'),
chalk.yellow(this.streamingToolCalls.value.map(tool => tool.function!.name || '').join(', ')) chalk.yellow(this.streamingToolCalls.value.map(tool => tool.function!.name || '').join(', '))
); );
} }
@ -618,8 +550,7 @@ export class TaskLoop {
created: Date.now(), created: Date.now(),
state: toolCallResult.state, state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown', serverName: this.getLlmConfig().id || 'unknown',
usage: undefined, usage: undefined
enableXmlWrapper
} }
}); });
break; break;
@ -634,8 +565,7 @@ export class TaskLoop {
created: Date.now(), created: Date.now(),
state: toolCallResult.state, state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown', serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage, usage: this.completionUsage
enableXmlWrapper
} }
}); });
} else if (toolCallResult.state === MessageState.ToolCall) { } else if (toolCallResult.state === MessageState.ToolCall) {
@ -649,8 +579,7 @@ export class TaskLoop {
created: Date.now(), created: Date.now(),
state: toolCallResult.state, state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown', serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage, usage: this.completionUsage
enableXmlWrapper
} }
}); });
} }
@ -664,96 +593,10 @@ export class TaskLoop {
created: Date.now(), created: Date.now(),
state: MessageState.Success, state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown', serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage, usage: this.completionUsage
enableXmlWrapper
} }
}); });
break;
// 如果 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 { } else {
// 一些提示 // 一些提示
@ -766,52 +609,4 @@ 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,186 +1,175 @@
{ {
"module": "Module", "module": "Modules",
"signal": "Signal", "signal": "Signals",
"search-signal": "Rechercher un signal", "search-signal": "Search Signal",
"language-setting": "Langue", "language-setting": "Language",
"search-setting": "Recherche", "search-setting": "Search",
"search-case-sensitivity": "Sensibilité à la casse", "search-case-sensitivity": "Case Sensitivity",
"search-mode": "Mode de recherche", "search-mode": "search mode",
"search-scope": "Portée de la recherche", "search-scope": "Search Scope",
"search-display-parent-only": "Afficher uniquement le module parent", "search-display-parent-only": "Display Parent Module Only",
"search-nothing": "Aucun signal trouvé", "search-nothing": "Find Nothing",
"signal-only": "Signal uniquement", "signal-only": "Signal Only",
"module-only": "Module uniquement", "module-only": "Module Only",
"signal-module": "Signal + Module", "signal-module": "Signal + Module",
"general-setting": "Général", "general-setting": "General",
"appearance-setting": "Apparence", "appearance-setting": "Appearance",
"display-wave-height": "Hauteur de la piste d'onde", "display-wave-height": "Height of Wave Track",
"display-signal-info-scope": "Informations affichées dans la barre latérale", "display-signal-info-scope": "Info displayed in sidebar",
"display-signal-info-scope.width": "Largeur de bit", "display-signal-info-scope.width": "width",
"display-signal-info-scope.parent": "Nom du module parent", "display-signal-info-scope.parent": "parent",
"wavecolor": "Couleur d'onde par défaut", "wavecolor": "default color of wave",
"wavecolor.normal-bit": "Onde à largeur unique", "wavecolor.normal-bit": "wave of one width",
"wavecolor.normal-vec": "Onde à largeur multiple", "wavecolor.normal-vec": "wave of more than one width",
"wavecolor.high-impedance": "Onde à haute impédance", "wavecolor.high-impedance": "wave of high impedance",
"wavecolor.unknown": "Onde à état inconnu", "wavecolor.unknown": "wave of unknown",
"operation-setting": "Opérations", "operation-setting": "Operation",
"render-setting": "Rendu", "render-setting": "Render",
"render-animation": "Activer l'animation de rendu", "render-animation": "enable rendering animation",
"usermanual": "Manuel utilisateur", "usermanual": "User Manual",
"usermanual.left-right-scroll.caption": "Déplacer vers le haut et le bas", "usermanual.left-right-scroll.caption": "move up and down",
"usermanual.up-down-scroll.caption": "Déplacer vers la gauche et la droite", "usermanual.up-down-scroll.caption": "move left and right",
"usermanual.xscale.caption": "Zoom horizontal", "usermanual.xscale.caption": "scale along x axis",
"loading": "Chargement", "loading": "loading",
"context-menu.create-group": "Créer un groupe", "context-menu.create-group": "create group",
"context-menu.join-group": "Rejoindre un groupe existant", "context-menu.join-group": "join created group",
"context-menu.change-color": "Changer la couleur", "context-menu.change-color": "change color",
"context-menu.delete": "Supprimer le signal", "context-menu.delete": "delete signal",
"context-menu.delete-all-select": "Supprimer tous les signaux sélectionnés", "context-menu.delete-all-select": "delete all the selected signals",
"context-menu.signal.name": "Nom du signal", "context-menu.signal.name": "signal name",
"context-menu.signal.type": "Type de signal", "context-menu.signal.type": "signal type",
"context-menu.signal.width": "Largeur du signal", "context-menu.signal.width": "signal width",
"context-menu.signal.dep": "Dépendances", "context-menu.signal.dep": "signal dependency",
"context-menu.group.cancel": "Annuler le groupement", "context-menu.group.cancel": "cancel group",
"context-menu.group.delete": "Supprimer le groupe", "context-menu.group.delete": "delete group",
"context-menu.group.empty": "Aucun groupe disponible", "context-menu.group.empty": "No groups are currently available",
"context-menu.group.uname-group": "Groupe sans nom", "context-menu.group.uname-group": "unamed group",
"toolbar.modal.common-digital": "Numérique", "toolbar.modal.common-digital": "Digital",
"toolbar.modal.ladder-analog": "Analogique (échelle)", "toolbar.modal.ladder-analog": "Analog (Ladder)",
"toolbar.modal.line-analog": "Analogique (ligne)", "toolbar.modal.line-analog": "Analog (Line)",
"toolbar.search.name": "Nom", "toolbar.search.name": "Name",
"toolbar.search.value": "Valeur", "toolbar.search.value": "Value",
"toolbar.format.category.base": "Base", "toolbar.format.category.base": "Base",
"toolbar.format.category.dec": "Décimal", "toolbar.format.category.dec": "Decimal",
"toolbar.format.category.float": "Virgule flottante", "toolbar.format.category.float": "Float",
"toolbar.format.signed": "Signé", "toolbar.format.signed": "Signed",
"toolbar.format.unsigned": "Non signé", "toolbar.format.unsigned": "Unsigned",
"toolbar.format.half": "Demi-précision (16 bits)", "toolbar.format.half": "Half (16bit)",
"toolbar.format.float": "Simple précision (32 bits)", "toolbar.format.float": "Float (32bit)",
"toolbar.format.double": "Double précision (64 bits)", "toolbar.format.double": "Double (64bit)",
"toolbar.location.to-begin": "Déplacer au début", "toolbar.location.to-begin": "Move to Beginning",
"toolbar.location.to-end": "Déplacer à la fin", "toolbar.location.to-end": "Move to End",
"toolbar.location.to-next-change": "Aller au prochain changement", "toolbar.location.to-next-change": "Go to Next Change Edge",
"toolbar.location.to-prev-change": "Aller au changement précédent", "toolbar.location.to-prev-change": "Go to Previous Change Edge",
"toolbar.location.make-location": "Créer un nouveau repère", "toolbar.location.make-location": "Create New Pivot",
"toolbar.location.clear-location": "Effacer tous les repères", "toolbar.location.clear-location": "Clear All Pivots",
"toolbar.location.clear-location-dialog": "Êtes-vous sûr de vouloir effacer tous les repères?", "toolbar.location.clear-location-dialog": "Are you sure to clear all the pivots ?",
"context-menu.cannot-join-repeat-group": "Le signal actuel est déjà dans ce groupe", "context-menu.cannot-join-repeat-group": "current signal is already contained in this group",
"toolbar.no-result": "Aucun résultat", "toolbar.no-result": "No Result",
"toolbar.search.value.already-to-head": "Déjà au début", "toolbar.search.value.already-to-head": "already to head",
"toolbar.search.value.already-to-tail": "Déjà à la fin", "toolbar.search.value.already-to-tail": "already to tail",
"toolbar.search.value.searching": "Recherche en cours", "toolbar.search.value.searching": "searching",
"pivot.context.delete": "Supprimer le repère", "pivot.context.delete": "delete pivot",
"pivot.context.display-axis": "Créer un axe relatif", "pivot.context.display-axis": "create relative axis",
"pivot.context.cancel-axis": "Annuler l'axe relatif", "pivot.context.cancel-axis": "cancel relative axis",
"setting.appearance.pivot-color": "Couleur du repère", "setting.appearance.pivot-color": "pivot color",
"setting.appearance.moving-pivot": "Repère mobile", "setting.appearance.moving-pivot": "moving pivot",
"setting.appearance.user-pivot": "Repère utilisateur", "setting.appearance.user-pivot": "user pivot",
"setting.appearance.system-pivot": "Repère système", "setting.appearance.system-pivot": "system pivot",
"confirm": "Confirmer", "confirm": "confirm",
"cancel": "Annuler", "cancel": "cancel",
"tips": "Conseils", "tips": "Tips",
"filemenu.save-view": "Enregistrer le fichier de vue", "filemenu.save-view": "保存视图文件",
"filemenu.save-as-view": "Enregistrer la vue sous", "filemenu.save-as-view": "另存为视图文件",
"filemenu.load-view": "Charger le fichier de vue", "filemenu.load-view": "导入视图文件",
"filemenu.auto-save": "Sauvegarde automatique", "filemenu.auto-save": "自动保存",
"current-version": "Version actuelle", "current-version": "current version",
"setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Vcd Viewer.", "setting.language.change-dialog": "You have changed the language to {0}, we recommend restarting Vcd Viewer.",
"resources": "Ressources", "resources": "Resources",
"tools": "Outils", "tools": "Tools",
"prompts": "Invites", "prompts": "Prompts",
"interaction-test": "Test interactif", "interaction-test": "Interactive Test",
"setting": "Paramètres", "setting": "Settings",
"about": "À propos", "about": "About",
"connected": "Connecté", "connected": "Connected",
"disconnected": "Déconnecté", "disconnected": "Disconnected",
"debug": "Déboguer", "debug": "Debug",
"connect": "Connexion", "connect": "Connect",
"setting.general-color-setting": "Paramètres de couleur généraux", "setting.general-color-setting": "General Color Settings",
"choose-a-project-debug": "Sélectionnez un projet à déboguer", "choose-a-project-debug": "Select a project to debug",
"model": "Modèle", "model": "Model",
"server-provider": "Fournisseur de services", "server-provider": "Service Provider",
"api-root-url": "Chemin racine de l'API", "api-root-url": "Base Url",
"api-token": "Clé API", "api-token": "API key",
"connection-method": "Méthode de connexion", "connection-method": "Connection method",
"command": "Commande", "command": "Command",
"env-var": "Variables d'environnement", "env-var": "Environment variables",
"log": "Journaux", "log": "Logs",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter", "warning.click-to-connect": "Please first click on $1 on the left to connect",
"reset": "Réinitialiser", "reset": "Reset",
"read-resource": "Lire les ressources", "read-resource": "Read resources",
"enter": "Entrée", "enter": "Input",
"blank-test": "Test vide", "blank-test": "Blank test",
"connect.appearance.reconnect": "Reconnexion", "connect.appearance.reconnect": "Reconnect",
"connect.appearance.connect": "Connexion", "connect.appearance.connect": "Connection",
"response": "Réponse", "response": "Response",
"refresh": "Rafraîchir", "refresh": "Refresh",
"read-prompt": "Lire l'invite", "read-prompt": "Read prompt",
"execute-tool": "Exécuter", "execute-tool": "Run",
"save": "Enregistrer", "save": "Save",
"send": "Envoyer", "send": "Send",
"server-not-support-statistic": "Le fournisseur que vous utilisez ne prend pas en charge les statistiques temporairement", "server-not-support-statistic": "The vendor you are using does not support statistics temporarily",
"answer-at": "Répondu le", "answer-at": "Answered on",
"input-token": "Entrée", "input-token": "Input",
"output-token": "Sortie", "output-token": "Output",
"total": "Total", "total": "Total",
"cache-hit-ratio": "Taux de réussite du cache", "cache-hit-ratio": "Cache hit rate",
"success-save": "Enregistré avec succès", "success-save": "Successfully saved",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?", "confirm-delete-model": "Are you sure you want to delete the model provider?",
"reserve-one-last-model": "Conservez au moins un modèle", "reserve-one-last-model": "Keep at least one model",
"edit": "Modifier", "edit": "Edit",
"delete": "Supprimer", "delete": "Delete",
"test": "Test", "test": "Test",
"add-new-server": "Ajouter un service", "add-new-server": "Add service",
"choose-model": "Sélectionner le modèle", "choose-model": "Select model",
"system-prompt": "Invite système", "system-prompt": "System prompt",
"tool-use": "Utilisation d'outils", "tool-use": "Tool usage",
"websearch": "Recherche sur Internet", "websearch": "Web search",
"temperature-parameter": "Paramètre de température", "temperature-parameter": "Temperature parameter",
"context-length": "Longueur du contexte", "context-length": "Context length",
"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)", "system-prompt.placeholder": "Enter the system prompt (e.g.: You are a professional front-end development assistant, answer in English)",
"precise": "Précis", "precise": "Precise",
"moderate": "Équilibre", "moderate": "Balance",
"creative": "Créativité", "creative": "Creativity",
"single-dialog": "Dialogue en un tour", "single-dialog": "Single-round dialogue",
"multi-dialog": "Conversation multi-tours", "multi-dialog": "Multi-turn conversation",
"press-and-run": "Tapez une question pour commencer le test", "press-and-run": "Type a question to start the test",
"connect-sigature": "Signature de connexion", "connect-sigature": "Connection signature",
"finish-refresh": "Actualisation terminée", "finish-refresh": "Refresh completed",
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée", "add-system-prompt.name-placeholder": "Title for custom prompt",
"enter-message-dot": "Entrez un message...", "enter-message-dot": "Enter message...",
"generate-answer": "Génération de la réponse", "generate-answer": "Generating answer",
"choose-presetting": "Sélectionner un préréglage", "choose-presetting": "Select preset",
"cwd": "Répertoire d'exécution", "cwd": "Execution directory",
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP", "mcp-server-timeout": "Maximum call time of MCP tool",
"return": "Retour", "return": "Back",
"error": "Erreur", "error": "Error",
"feedback": "Retour", "feedback": "Feedback",
"waiting-mcp-server": "En attente de la réponse du serveur MCP", "waiting-mcp-server": "Waiting for MCP server response",
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse", "parallel-tool-calls": "Allow the model to call multiple tools in a single reply",
"proxy-server": "Serveur proxy", "proxy-server": "Proxy server",
"update-model-list": "Mettre à jour la liste des modèles", "update-model-list": "Update model list",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées", "preset-env-sync.success": "Preset environment variables synchronized",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies", "preset-env-sync.fail": "Preset environment variables synchronization failed",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion", "drag-to-fill-connect-parameters": "Drag to fill connection parameters",
"connect-success": "Connexion réussie", "connect-success": "Connected successfully",
"connect-fail": "Échec de la connexion", "connect-fail": "Connection failed",
"preset": "Préréglage", "preset": "Preset",
"openmcp-document": "Documentation officielle d'OpenMCP", "openmcp-document": "OpenMCP official documentation",
"star-our-project": "Star notre projet", "star-our-project": "Star our project",
"document": "Documentation officielle", "document": "Official documentation",
"join-discussion": "Rejoindre le groupe de discussion", "join-discussion": "Join the discussion group",
"comment-for-us": "Écrivez un avis pour nous !", "comment-for-us": "Write a review for us!",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}", "openmcp-developed-by": "OpenMCP Client {version} developed by {author}",
"error-parse-json": "Erreur d'analyse JSON :", "error-parse-json": "JSON parsing error:"
"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,6 +159,20 @@
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse", "parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse",
"proxy-server": "Serveur proxy", "proxy-server": "Serveur proxy",
"update-model-list": "Mettre à jour la liste des modèles", "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.success": "Variables d'environnement prédéfinies synchronisées",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies", "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", "drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion",
@ -171,16 +185,5 @@
"join-discussion": "Rejoindre le groupe de discussion", "join-discussion": "Rejoindre le groupe de discussion",
"comment-for-us": "Écrivez un avis pour nous !", "comment-for-us": "Écrivez un avis pour nous !",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}", "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,186 +1,175 @@
{ {
"module": "Module", "module": "モジュール",
"signal": "Signal", "signal": "信号",
"search-signal": "Rechercher un signal", "search-signal": "信号を検索",
"language-setting": "Langue", "language-setting": "言語",
"search-setting": "Recherche", "search-setting": "検索",
"search-case-sensitivity": "Sensibilité à la casse", "search-case-sensitivity": "大文字と小文字を区別",
"search-mode": "Mode de recherche", "search-mode": "検索モード",
"search-scope": "Portée de la recherche", "search-scope": "検索範囲",
"search-display-parent-only": "Afficher uniquement le module parent", "search-display-parent-only": "親モジュールのみ表示",
"search-nothing": "Aucun signal trouvé", "search-nothing": "信号が見つかりませんでした",
"signal-only": "Signal uniquement", "signal-only": "信号のみ",
"module-only": "Module uniquement", "module-only": "モジュールのみ",
"signal-module": "Signal + Module", "signal-module": "信号 + モジュール",
"general-setting": "Général", "general-setting": "一般",
"appearance-setting": "Apparence", "appearance-setting": "外観",
"display-wave-height": "Hauteur de la piste d'onde", "display-wave-height": "波形トラックの高さ",
"display-signal-info-scope": "Informations affichées dans la barre latérale", "display-signal-info-scope": "サイドバーに表示する情報",
"display-signal-info-scope.width": "Largeur de bit", "display-signal-info-scope.width": "ビット幅",
"display-signal-info-scope.parent": "Nom du module parent", "display-signal-info-scope.parent": "所属モジュール名",
"wavecolor": "Couleur d'onde par défaut", "wavecolor": "デフォルトの波形色",
"wavecolor.normal-bit": "Onde à largeur unique", "wavecolor.normal-bit": "単位幅波形",
"wavecolor.normal-vec": "Onde à largeur multiple", "wavecolor.normal-vec": "複数ビット幅波形",
"wavecolor.high-impedance": "Onde à haute impédance", "wavecolor.high-impedance": "ハイインピーダンス波形",
"wavecolor.unknown": "Onde à état inconnu", "wavecolor.unknown": "未知状態波形",
"operation-setting": "Opérations", "operation-setting": "操作",
"render-setting": "Rendu", "render-setting": "レンダリング",
"render-animation": "Activer l'animation de rendu", "render-animation": "レンダリングアニメーションを有効にする",
"usermanual": "Manuel utilisateur", "usermanual": "使用説明",
"usermanual.left-right-scroll.caption": "Déplacer vers le haut et le bas", "usermanual.left-right-scroll.caption": "上下に移動",
"usermanual.up-down-scroll.caption": "Déplacer vers la gauche et la droite", "usermanual.up-down-scroll.caption": "左右に移動",
"usermanual.xscale.caption": "Zoom horizontal", "usermanual.xscale.caption": "横方向に拡大",
"loading": "Chargement", "loading": "読み込み中",
"context-menu.create-group": "Créer un groupe", "context-menu.create-group": "新しいグループを作成",
"context-menu.join-group": "Rejoindre un groupe existant", "context-menu.join-group": "既存のグループに参加",
"context-menu.change-color": "Changer la couleur", "context-menu.change-color": "色を変更",
"context-menu.delete": "Supprimer le signal", "context-menu.delete": "信号を削除",
"context-menu.delete-all-select": "Supprimer tous les signaux sélectionnés", "context-menu.delete-all-select": "選択したすべての信号を削除",
"context-menu.signal.name": "Nom du signal", "context-menu.signal.name": "信号名",
"context-menu.signal.type": "Type de signal", "context-menu.signal.type": "信号タイプ",
"context-menu.signal.width": "Largeur du signal", "context-menu.signal.width": "信号幅",
"context-menu.signal.dep": "Dépendances", "context-menu.signal.dep": "依存関係",
"context-menu.group.cancel": "Annuler le groupement", "context-menu.group.cancel": "グループをキャンセル",
"context-menu.group.delete": "Supprimer le groupe", "context-menu.group.delete": "グループを削除",
"context-menu.group.empty": "Aucun groupe disponible", "context-menu.group.empty": "利用可能なグループがありません",
"context-menu.group.uname-group": "Groupe sans nom", "context-menu.group.uname-group": "名前なしグループ",
"toolbar.modal.common-digital": "Numérique", "toolbar.modal.common-digital": "デジタル",
"toolbar.modal.ladder-analog": "Analogique (échelle)", "toolbar.modal.ladder-analog": "アナログ(階段)",
"toolbar.modal.line-analog": "Analogique (ligne)", "toolbar.modal.line-analog": "アナログ(折れ線)",
"toolbar.search.name": "Nom", "toolbar.search.name": "名前",
"toolbar.search.value": "Valeur", "toolbar.search.value": "値",
"toolbar.format.category.base": "Base", "toolbar.format.category.base": "基本",
"toolbar.format.category.dec": "Décimal", "toolbar.format.category.dec": "10進数",
"toolbar.format.category.float": "Virgule flottante", "toolbar.format.category.float": "浮動小数点数",
"toolbar.format.signed": "Signé", "toolbar.format.signed": "符号付き",
"toolbar.format.unsigned": "Non signé", "toolbar.format.unsigned": "符号なし",
"toolbar.format.half": "Demi-précision (16 bits)", "toolbar.format.half": "半精度16ビット",
"toolbar.format.float": "Simple précision (32 bits)", "toolbar.format.float": "単精度32ビット",
"toolbar.format.double": "Double précision (64 bits)", "toolbar.format.double": "倍精度64ビット",
"toolbar.location.to-begin": "Déplacer au début", "toolbar.location.to-begin": "先頭に移動",
"toolbar.location.to-end": "Déplacer à la fin", "toolbar.location.to-end": "末尾に移動",
"toolbar.location.to-next-change": "Aller au prochain changement", "toolbar.location.to-next-change": "次の変化点に移動",
"toolbar.location.to-prev-change": "Aller au changement précédent", "toolbar.location.to-prev-change": "前の変化点に移動",
"toolbar.location.make-location": "Créer un nouveau repère", "toolbar.location.make-location": "新しいピボットを作成",
"toolbar.location.clear-location": "Effacer tous les repères", "toolbar.location.clear-location": "すべてのピボットをクリア",
"toolbar.location.clear-location-dialog": "Êtes-vous sûr de vouloir effacer tous les repères?", "toolbar.location.clear-location-dialog": "すべてのピボットをクリアしてもよろしいですか?",
"context-menu.cannot-join-repeat-group": "Le signal actuel est déjà dans ce groupe", "context-menu.cannot-join-repeat-group": "現在の信号はすでにこのグループに含まれています",
"toolbar.no-result": "Aucun résultat", "toolbar.no-result": "結果なし",
"toolbar.search.value.already-to-head": "Déjà au début", "toolbar.search.value.already-to-head": "すでに先頭にいます",
"toolbar.search.value.already-to-tail": "Déjà à la fin", "toolbar.search.value.already-to-tail": "すでに末尾にいます",
"toolbar.search.value.searching": "Recherche en cours", "toolbar.search.value.searching": "検索中",
"pivot.context.delete": "Supprimer le repère", "pivot.context.delete": "ピボットを削除",
"pivot.context.display-axis": "Créer un axe relatif", "pivot.context.display-axis": "相対座標軸を作成",
"pivot.context.cancel-axis": "Annuler l'axe relatif", "pivot.context.cancel-axis": "相対座標軸をキャンセル",
"setting.appearance.pivot-color": "Couleur du repère", "setting.appearance.pivot-color": "ピボットの色",
"setting.appearance.moving-pivot": "Repère mobile", "setting.appearance.moving-pivot": "移動ピボット",
"setting.appearance.user-pivot": "Repère utilisateur", "setting.appearance.user-pivot": "ユーザーピボット",
"setting.appearance.system-pivot": "Repère système", "setting.appearance.system-pivot": "システムピボット",
"confirm": "Confirmer", "confirm": "確認",
"cancel": "Annuler", "cancel": "キャンセル",
"tips": "Conseils", "tips": "ヒント",
"filemenu.save-view": "Enregistrer le fichier de vue", "filemenu.save-view": "ビューファイルを保存",
"filemenu.save-as-view": "Enregistrer la vue sous", "filemenu.save-as-view": "ビューファイルとして保存",
"filemenu.load-view": "Charger le fichier de vue", "filemenu.load-view": "ビューファイルをインポート",
"filemenu.auto-save": "Sauvegarde automatique", "filemenu.auto-save": "自動保存",
"current-version": "Version actuelle", "current-version": "現在のバージョン",
"setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Vcd Viewer.", "setting.language.change-dialog": "言語を{0}に変更しました。Vcd Viewerを再起動することをお勧めします。",
"resources": "Ressources", "resources": "リソース",
"tools": "Outils", "tools": "ツール",
"prompts": "Invites", "prompts": "プロンプト",
"interaction-test": "Test interactif", "interaction-test": "インタラクティブテスト",
"setting": "Paramètres", "setting": "設定",
"about": "À propos", "about": "について",
"connected": "Connecté", "connected": "接続済み",
"disconnected": "Déconnecté", "disconnected": "切断されました",
"debug": "Déboguer", "debug": "デバッグ",
"connect": "Connexion", "connect": "接続",
"setting.general-color-setting": "Paramètres de couleur généraux", "setting.general-color-setting": "一般的な色設定",
"choose-a-project-debug": "Sélectionnez un projet à déboguer", "choose-a-project-debug": "デバッグするプロジェクトを選択",
"model": "Modèle", "model": "モデル",
"server-provider": "Fournisseur de services", "server-provider": "サービスプロバイダー",
"api-root-url": "Chemin racine de l'API", "api-root-url": "APIルートパス",
"api-token": "Clé API", "api-token": "APIキー",
"connection-method": "Méthode de connexion", "connection-method": "接続方法",
"command": "Commande", "command": "コマンド",
"env-var": "Variables d'environnement", "env-var": "環境変数",
"log": "Journaux", "log": "ログ",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter", "warning.click-to-connect": "まず左側の$1をクリックして接続してください",
"reset": "Réinitialiser", "reset": "リセット",
"read-resource": "Lire les ressources", "read-resource": "リソースを読み込む",
"enter": "Entrée", "enter": "入力",
"blank-test": "Test vide", "blank-test": "空白テスト",
"connect.appearance.reconnect": "Reconnexion", "connect.appearance.reconnect": "再接続",
"connect.appearance.connect": "Connexion", "connect.appearance.connect": "接続",
"response": "Réponse", "response": "応答",
"refresh": "Rafraîchir", "refresh": "更新",
"read-prompt": "Lire l'invite", "read-prompt": "プロンプトを読み取る",
"execute-tool": "Exécuter", "execute-tool": "実行",
"save": "Enregistrer", "save": "保存",
"send": "Envoyer", "send": "送信",
"server-not-support-statistic": "Le fournisseur que vous utilisez ne prend pas en charge les statistiques temporairement", "server-not-support-statistic": "お使いのベンダーは一時的に統計情報をサポートしていません",
"answer-at": "Répondu le", "answer-at": "解答日",
"input-token": "Entrée", "input-token": "入力",
"output-token": "Sortie", "output-token": "出力",
"total": "Total", "total": "合計",
"cache-hit-ratio": "Taux de réussite du cache", "cache-hit-ratio": "キャッシュヒット率",
"success-save": "Enregistré avec succès", "success-save": "正常に保存されました",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?", "confirm-delete-model": "このモデルプロバイダーを削除しますか?",
"reserve-one-last-model": "Conservez au moins un modèle", "reserve-one-last-model": "少なくとも1つのモデルを保持してください",
"edit": "Modifier", "edit": "編集",
"delete": "Supprimer", "delete": "削除",
"test": "Test", "test": "テスト",
"add-new-server": "Ajouter un service", "add-new-server": "サービスを追加",
"choose-model": "Sélectionner le modèle", "choose-model": "モデルを選択",
"system-prompt": "Invite système", "system-prompt": "システムプロンプト",
"tool-use": "Utilisation d'outils", "tool-use": "ツールの使用",
"websearch": "Recherche sur Internet", "websearch": "ウェブ検索",
"temperature-parameter": "Paramètre de température", "temperature-parameter": "温度パラメータ",
"context-length": "Longueur du contexte", "context-length": "コンテキストの長さ",
"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)", "system-prompt.placeholder": "システムプロンプトを入力してください(例:あなたはプロのフロントエンド開発アシスタントで、日本語で答えます)",
"precise": "Précis", "precise": "精密",
"moderate": "Équilibre", "moderate": "バランス",
"creative": "Créativité", "creative": "創造性",
"single-dialog": "Dialogue en un tour", "single-dialog": "単一ラウンドの対話",
"multi-dialog": "Conversation multi-tours", "multi-dialog": "マルチターン会話",
"press-and-run": "Tapez une question pour commencer le test", "press-and-run": "テストを開始するには質問を入力してください",
"connect-sigature": "Signature de connexion", "connect-sigature": "接続署名",
"finish-refresh": "Actualisation terminée", "finish-refresh": "更新が完了しました",
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée", "add-system-prompt.name-placeholder": "カスタムプロンプトのタイトル",
"enter-message-dot": "Entrez un message...", "enter-message-dot": "メッセージを入力...",
"generate-answer": "Génération de la réponse", "generate-answer": "回答を生成中",
"choose-presetting": "Sélectionner un préréglage", "choose-presetting": "プリセットを選択",
"cwd": "Répertoire d'exécution", "cwd": "実行ディレクトリ",
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP", "mcp-server-timeout": "MCPツールの最大呼び出し時間",
"return": "Retour", "return": "戻る",
"error": "Erreur", "error": "エラー",
"feedback": "Retour", "feedback": "フィードバック",
"waiting-mcp-server": "En attente de la réponse du serveur MCP", "waiting-mcp-server": "MCPサーバーの応答を待機中",
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse", "parallel-tool-calls": "モデルが単一の返信で複数のツールを呼び出すことを許可する",
"proxy-server": "Serveur proxy", "proxy-server": "プロキシサーバー",
"update-model-list": "Mettre à jour la liste des modèles", "update-model-list": "モデルリストを更新",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées", "preset-env-sync.success": "プリセット環境変数の同期が完了しました",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies", "preset-env-sync.fail": "プリセット環境変数の同期に失敗しました",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion", "drag-to-fill-connect-parameters": "接続パラメータを入力するためにドラッグしてください",
"connect-success": "Connexion réussie", "connect-success": "接続に成功しました",
"connect-fail": "Échec de la connexion", "connect-fail": "接続に失敗しました",
"preset": "Préréglage", "preset": "プリセット",
"openmcp-document": "Documentation officielle d'OpenMCP", "openmcp-document": "OpenMCP公式ドキュメント",
"star-our-project": "Star notre projet", "star-our-project": "私たちのプロジェクトをスター",
"document": "Documentation officielle", "document": "公式ドキュメント",
"join-discussion": "Rejoindre le groupe de discussion", "join-discussion": "ディスカッショングループに参加",
"comment-for-us": "Écrivez un avis pour nous !", "comment-for-us": "私たちのためにレビューを書いてください!",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}", "openmcp-developed-by": "OpenMCP Client {version} は {author} によって開発されました",
"error-parse-json": "Erreur d'analyse 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,186 +1,189 @@
{ {
"module": "Module", "module": "모듈",
"signal": "Signal", "signal": "신호",
"search-signal": "Rechercher un signal", "search-signal": "신호 검색",
"language-setting": "Langue", "language-setting": "언어",
"search-setting": "Recherche", "search-setting": "검색",
"search-case-sensitivity": "Sensibilité à la casse", "search-case-sensitivity": "대소문자 구분",
"search-mode": "Mode de recherche", "search-mode": "검색 모드",
"search-scope": "Portée de la recherche", "search-scope": "검색 범위",
"search-display-parent-only": "Afficher uniquement le module parent", "search-display-parent-only": "부모 모듈만 표시",
"search-nothing": "Aucun signal trouvé", "search-nothing": "신호를 찾을 수 없습니다",
"signal-only": "Signal uniquement", "signal-only": "신호만",
"module-only": "Module uniquement", "module-only": "모듈만",
"signal-module": "Signal + Module", "signal-module": "신호 + 모듈",
"general-setting": "Général", "general-setting": "일반",
"appearance-setting": "Apparence", "appearance-setting": "외관",
"display-wave-height": "Hauteur de la piste d'onde", "display-wave-height": "파형 트랙의 높이",
"display-signal-info-scope": "Informations affichées dans la barre latérale", "display-signal-info-scope": "사이드바에 표시할 정보",
"display-signal-info-scope.width": "Largeur de bit", "display-signal-info-scope.width": "비트 너비",
"display-signal-info-scope.parent": "Nom du module parent", "display-signal-info-scope.parent": "소속 모듈 이름",
"wavecolor": "Couleur d'onde par défaut", "wavecolor": "기본 파형 색상",
"wavecolor.normal-bit": "Onde à largeur unique", "wavecolor.normal-bit": "단위 너비 파형",
"wavecolor.normal-vec": "Onde à largeur multiple", "wavecolor.normal-vec": "다중 비트 너비 파형",
"wavecolor.high-impedance": "Onde à haute impédance", "wavecolor.high-impedance": "고임피던스 파형",
"wavecolor.unknown": "Onde à état inconnu", "wavecolor.unknown": "알 수 없는 상태 파형",
"operation-setting": "Opérations", "operation-setting": "작업",
"render-setting": "Rendu", "render-setting": "렌더링",
"render-animation": "Activer l'animation de rendu", "render-animation": "렌더링 애니메이션 활성화",
"usermanual": "Manuel utilisateur", "usermanual": "사용 설명서",
"usermanual.left-right-scroll.caption": "Déplacer vers le haut et le bas", "usermanual.left-right-scroll.caption": "상하 이동",
"usermanual.up-down-scroll.caption": "Déplacer vers la gauche et la droite", "usermanual.up-down-scroll.caption": "좌우 이동",
"usermanual.xscale.caption": "Zoom horizontal", "usermanual.xscale.caption": "가로 확대",
"loading": "Chargement", "loading": "로딩 중",
"context-menu.create-group": "Créer un groupe", "context-menu.create-group": "새 그룹 생성",
"context-menu.join-group": "Rejoindre un groupe existant", "context-menu.join-group": "기존 그룹에 참여",
"context-menu.change-color": "Changer la couleur", "context-menu.change-color": "색상 변경",
"context-menu.delete": "Supprimer le signal", "context-menu.delete": "신호 삭제",
"context-menu.delete-all-select": "Supprimer tous les signaux sélectionnés", "context-menu.delete-all-select": "선택한 모든 신호 삭제",
"context-menu.signal.name": "Nom du signal", "context-menu.signal.name": "신호 이름",
"context-menu.signal.type": "Type de signal", "context-menu.signal.type": "신호 유형",
"context-menu.signal.width": "Largeur du signal", "context-menu.signal.width": "신호 너비",
"context-menu.signal.dep": "Dépendances", "context-menu.signal.dep": "종속성",
"context-menu.group.cancel": "Annuler le groupement", "context-menu.group.cancel": "그룹 취소",
"context-menu.group.delete": "Supprimer le groupe", "context-menu.group.delete": "그룹 삭제",
"context-menu.group.empty": "Aucun groupe disponible", "context-menu.group.empty": "사용 가능한 그룹이 없습니다",
"context-menu.group.uname-group": "Groupe sans nom", "context-menu.group.uname-group": "이름 없는 그룹",
"toolbar.modal.common-digital": "Numérique", "toolbar.modal.common-digital": "디지털",
"toolbar.modal.ladder-analog": "Analogique (échelle)", "toolbar.modal.ladder-analog": "아날로그 (사다리)",
"toolbar.modal.line-analog": "Analogique (ligne)", "toolbar.modal.line-analog": "아날로그 (선)",
"toolbar.search.name": "Nom", "toolbar.search.name": "이름",
"toolbar.search.value": "Valeur", "toolbar.search.value": "값",
"toolbar.format.category.base": "Base", "toolbar.format.category.base": "기본",
"toolbar.format.category.dec": "Décimal", "toolbar.format.category.dec": "십진법",
"toolbar.format.category.float": "Virgule flottante", "toolbar.format.category.float": "부동 소수점",
"toolbar.format.signed": "Signé", "toolbar.format.signed": "부호 있음",
"toolbar.format.unsigned": "Non signé", "toolbar.format.unsigned": "부호 없음",
"toolbar.format.half": "Demi-précision (16 bits)", "toolbar.format.half": "반정밀도 (16비트)",
"toolbar.format.float": "Simple précision (32 bits)", "toolbar.format.float": "단정밀도 (32비트)",
"toolbar.format.double": "Double précision (64 bits)", "toolbar.format.double": "배정밀도 (64비트)",
"toolbar.location.to-begin": "Déplacer au début", "toolbar.location.to-begin": "시작 부분으로 이동",
"toolbar.location.to-end": "Déplacer à la fin", "toolbar.location.to-end": "끝 부분으로 이동",
"toolbar.location.to-next-change": "Aller au prochain changement", "toolbar.location.to-next-change": "다음 변화로 이동",
"toolbar.location.to-prev-change": "Aller au changement précédent", "toolbar.location.to-prev-change": "이전 변화로 이동",
"toolbar.location.make-location": "Créer un nouveau repère", "toolbar.location.make-location": "새 표식 생성",
"toolbar.location.clear-location": "Effacer tous les repères", "toolbar.location.clear-location": "모든 표식 지우기",
"toolbar.location.clear-location-dialog": "Êtes-vous sûr de vouloir effacer tous les repères?", "toolbar.location.clear-location-dialog": "모든 표식을 지우시겠습니까?",
"context-menu.cannot-join-repeat-group": "Le signal actuel est déjà dans ce groupe", "context-menu.cannot-join-repeat-group": "현재 신호가 이미 이 그룹에 있습니다",
"toolbar.no-result": "Aucun résultat", "toolbar.no-result": "결과 없음",
"toolbar.search.value.already-to-head": "Déjà au début", "toolbar.search.value.already-to-head": "이미 시작 부분입니다",
"toolbar.search.value.already-to-tail": "Déjà à la fin", "toolbar.search.value.already-to-tail": "이미 끝 부분입니다",
"toolbar.search.value.searching": "Recherche en cours", "toolbar.search.value.searching": "검색 중",
"pivot.context.delete": "Supprimer le repère", "pivot.context.delete": "표식 삭제",
"pivot.context.display-axis": "Créer un axe relatif", "pivot.context.display-axis": "상대 좌표축 생성",
"pivot.context.cancel-axis": "Annuler l'axe relatif", "pivot.context.cancel-axis": "상대 좌표축 취소",
"setting.appearance.pivot-color": "Couleur du repère", "setting.appearance.pivot-color": "표식 색상",
"setting.appearance.moving-pivot": "Repère mobile", "setting.appearance.moving-pivot": "이동 표식",
"setting.appearance.user-pivot": "Repère utilisateur", "setting.appearance.user-pivot": "사용자 표식",
"setting.appearance.system-pivot": "Repère système", "setting.appearance.system-pivot": "시스템 표식",
"confirm": "Confirmer", "confirm": "확인",
"cancel": "Annuler", "cancel": "취소",
"tips": "Conseils", "tips": "팁",
"filemenu.save-view": "Enregistrer le fichier de vue", "filemenu.save-view": "뷰 파일 저장",
"filemenu.save-as-view": "Enregistrer la vue sous", "filemenu.save-as-view": "뷰 파일로 저장",
"filemenu.load-view": "Charger le fichier de vue", "filemenu.load-view": "뷰 파일 불러오기",
"filemenu.auto-save": "Sauvegarde automatique", "filemenu.auto-save": "자동 저장",
"current-version": "Version actuelle", "current-version": "현재 버전",
"setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Vcd Viewer.", "setting.language.change-dialog": "언어를 {0}로 변경했습니다. Vcd Viewer를 다시 시작하는 것을 권장합니다.",
"resources": "Ressources", "resources": "자원",
"tools": "Outils", "tools": "도구",
"prompts": "Invites", "prompts": "프롬프트",
"interaction-test": "Test interactif", "interaction-test": "인터랙티브 테스트",
"setting": "Paramètres", "setting": "설정",
"about": "À propos", "about": "정보",
"connected": "Connecté", "connected": "연결됨",
"disconnected": "Déconnecté", "disconnected": "연결 해제됨",
"debug": "Déboguer", "debug": "디버그",
"connect": "Connexion", "connect": "연결",
"setting.general-color-setting": "Paramètres de couleur généraux", "setting.general-color-setting": "일반 색상 설정",
"choose-a-project-debug": "Sélectionnez un projet à déboguer", "choose-a-project-debug": "디버깅할 프로젝트 선택",
"model": "Modèle", "model": "모델",
"server-provider": "Fournisseur de services", "server-provider": "서비스 제공자",
"api-root-url": "Chemin racine de l'API", "api-root-url": "API 루트 경로",
"api-token": "Clé API", "api-token": "API 키",
"connection-method": "Méthode de connexion", "connection-method": "연결 방법",
"command": "Commande", "command": "명령",
"env-var": "Variables d'environnement", "env-var": "환경 변수",
"log": "Journaux", "log": "로그",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter", "warning.click-to-connect": "먼저 왼쪽의 $1을 클릭하여 연결하십시오",
"reset": "Réinitialiser", "reset": "재설정",
"read-resource": "Lire les ressources", "read-resource": "리소스 읽기",
"enter": "Entrée", "enter": "입력",
"blank-test": "Test vide", "blank-test": "빈 테스트",
"connect.appearance.reconnect": "Reconnexion", "connect.appearance.reconnect": "재연결",
"connect.appearance.connect": "Connexion", "connect.appearance.connect": "연결",
"response": "Réponse", "response": "응답",
"refresh": "Rafraîchir", "refresh": "새로 고침",
"read-prompt": "Lire l'invite", "read-prompt": "프롬프트 읽기",
"execute-tool": "Exécuter", "execute-tool": "실행",
"save": "Enregistrer", "save": "저장",
"send": "Envoyer", "send": "보내기",
"server-not-support-statistic": "Le fournisseur que vous utilisez ne prend pas en charge les statistiques temporairement", "server-not-support-statistic": "사용 중인 공급업체는 일시적으로 통계를 지원하지 않습니다",
"answer-at": "Répondu le", "answer-at": "답변일",
"input-token": "Entrée", "input-token": "입력",
"output-token": "Sortie", "output-token": "출력",
"total": "Total", "total": "총계",
"cache-hit-ratio": "Taux de réussite du cache", "cache-hit-ratio": "캐시 적중률",
"success-save": "Enregistré avec succès", "success-save": "성공적으로 저장됨",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?", "confirm-delete-model": "이 모델 공급자를 삭제하시겠습니까?",
"reserve-one-last-model": "Conservez au moins un modèle", "reserve-one-last-model": "적어도 하나의 모델을 유지하세요",
"edit": "Modifier", "edit": "편집",
"delete": "Supprimer", "delete": "삭제",
"test": "Test", "test": "테스트",
"add-new-server": "Ajouter un service", "add-new-server": "서비스 추가",
"choose-model": "Sélectionner le modèle", "choose-model": "모델 선택",
"system-prompt": "Invite système", "system-prompt": "시스템 프롬프트",
"tool-use": "Utilisation d'outils", "tool-use": "도구 사용",
"websearch": "Recherche sur Internet", "websearch": "웹 검색",
"temperature-parameter": "Paramètre de température", "temperature-parameter": "온도 매개변수",
"context-length": "Longueur du contexte", "context-length": "컨텍스트 길이",
"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)", "system-prompt.placeholder": "시스템 프롬프트를 입력하세요 (예: 당신은 전문 프론트엔드 개발 어시스턴트이며, 한국어로 답변합니다)",
"precise": "Précis", "precise": "정확한",
"moderate": "Équilibre", "moderate": "균형",
"creative": "Créativité", "creative": "창의성",
"single-dialog": "Dialogue en un tour", "single-dialog": "단일 라운드 대화",
"multi-dialog": "Conversation multi-tours", "multi-dialog": "다중 턴 대화",
"press-and-run": "Tapez une question pour commencer le test", "press-and-run": "테스트를 시작하려면 질문을 입력하세요",
"connect-sigature": "Signature de connexion", "connect-sigature": "연결 서명",
"finish-refresh": "Actualisation terminée", "finish-refresh": "새로 고침 완료",
"add-system-prompt.name-placeholder": "Titre de l'invite personnalisée", "add-system-prompt.name-placeholder": "사용자 지정 프롬프트 제목",
"enter-message-dot": "Entrez un message...", "enter-message-dot": "메시지를 입력하세요...",
"generate-answer": "Génération de la réponse", "generate-answer": "답변 생성 중",
"choose-presetting": "Sélectionner un préréglage", "choose-presetting": "프리셋 선택",
"cwd": "Répertoire d'exécution", "cwd": "실행 디렉터리",
"mcp-server-timeout": "Temps d'appel maximum de l'outil MCP", "mcp-server-timeout": "MCP 도구 최대 호출 시간",
"return": "Retour", "return": "돌아가기",
"error": "Erreur", "error": "오류",
"feedback": "Retour", "feedback": "피드백",
"waiting-mcp-server": "En attente de la réponse du serveur MCP", "waiting-mcp-server": "MCP 서버 응답 대기 중",
"parallel-tool-calls": "Permettre au modèle d'appeler plusieurs outils en une seule réponse", "parallel-tool-calls": "모델이 단일 응답에서 여러 도구를 호출할 수 있도록 허용",
"proxy-server": "Serveur proxy", "proxy-server": "프록시 서버",
"update-model-list": "Mettre à jour la liste des modèles", "update-model-list": "모델 목록 업데이트",
"preset-env-sync.success": "Variables d'environnement prédéfinies synchronisées", "ensure-delete-connection": "연결 $1을(를) 삭제하시겠습니까?",
"preset-env-sync.fail": "Échec de la synchronisation des variables d'environnement prédéfinies", "choose-connection-type": "연결 유형을 선택해 주세요",
"drag-to-fill-connect-parameters": "Faites glisser pour remplir les paramètres de connexion", "please-enter-connection-command": "연결 명령을 입력해 주세요",
"connect-success": "Connexion réussie", "example-mcp-run": "예: mcp run main.py",
"connect-fail": "Échec de la connexion", "please-enter-cwd": "작업 디렉토리 (cwd)를 입력하세요 (선택 사항)",
"preset": "Préréglage", "please-enter-cwd-placeholder": "예: /path/to/project",
"openmcp-document": "Documentation officielle d'OpenMCP", "please-enter-url": "연결 URL을 입력해 주세요",
"star-our-project": "Star notre projet", "example-as": "예를 들어:",
"document": "Documentation officielle", "enter-optional-oauth": "OAuth 토큰을 입력하세요 (선택 사항)",
"join-discussion": "Rejoindre le groupe de discussion", "quick-start": "시작하기",
"comment-for-us": "Écrivez un avis pour nous !", "read-document": "문서 읽기",
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}", "report-issue": "문제 신고",
"error-parse-json": "Erreur d'analyse JSON :", "join-project": "프로젝트 참여",
"enable-xml-wrapper": "XML 명령 래핑 활성화", "comment-plugin": "댓글 플러그인",
"tool-manage": "도구 관리", "preset-env-sync.success": "사전 설정 환경 변수 동기화 완료",
"enable-all-tools": "모든 도구 활성화", "preset-env-sync.fail": "사전 설정된 환경 변수 동기화 실패",
"disable-all-tools": "모든 도구 비활성화", "drag-to-fill-connect-parameters": "연결 매개변수를 채우려면 드래그하세요",
"using-tool": "도구 사용 중", "connect-success": "성공적으로 연결되었습니다",
"copy-success": "성공적으로 복사되었습니다", "connect-fail": "연결 실패",
"copy-fail": "복사 실패", "preset": "프리셋",
"copy": "복사", "openmcp-document": "OpenMCP 공식 문서",
"export": "내보내기", "star-our-project": "우리 프로젝트 스타",
"export-filename": "내보내기 파일 이름", "document": "공식 문서",
"how-to-use": "사용 방법?" "join-discussion": "토론 그룹에 참여",
"comment-for-us": "우리를 위해 리뷰를 작성해 주세요!",
"openmcp-developed-by": "OpenMCP Client {version}은 {author}에 의해 개발되었습니다",
"error-parse-json": "JSON 구문 분석 오류:"
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -29,12 +29,12 @@ export const connectionSelectDataViewOption: ConnectionTypeOptionItem[] = [
function prettifyMapKeys(keys: MapIterator<string>) { function prettifyMapKeys(keys: MapIterator<string>) {
const result: string[] = []; const result: string[] = [];
for (const key of keys) { for (const key of keys) {
result.push('+ ' + key); result.push('+ ' +key);
} }
return result.join('\n'); return result.join('\n');
} }
function _processSchemaNode(node: any, defs: Record<string, any> = {}): any { function _processSchemaNode(node: any, defs: Record<string, any> = {}): any {
// Handle $ref references // Handle $ref references
if ('$ref' in node) { if ('$ref' in node) {
const refPath = node['$ref']; const refPath = node['$ref'];
@ -195,18 +195,18 @@ export class McpClient {
} }
const bridge = useMessageBridge(); const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<ToolsListResponse>('tools/list', { clientId: this.clientId }); const { code, msg } = await bridge.commandRequest<ToolsListResponse>('tools/list', { clientId: this.clientId });
if (code !== 200) { if (code!== 200) {
return new Map<string, ToolItem>(); return new Map<string, ToolItem>();
} }
this.tools = new Map<string, ToolItem>(); this.tools = new Map<string, ToolItem>();
msg.tools.forEach(tool => { msg.tools.forEach(tool => {
const standardSchema = _processSchemaNode(tool.inputSchema, tool.inputSchema.$defs || {}); const standardSchema = _processSchemaNode(tool.inputSchema, tool.inputSchema.$defs || {});
tool.inputSchema = standardSchema; tool.inputSchema = standardSchema;
this.tools!.set(tool.name, tool); this.tools!.set(tool.name, tool);
}); });
@ -224,13 +224,13 @@ export class McpClient {
} }
const bridge = useMessageBridge(); const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<PromptsListResponse>('prompts/list', { clientId: this.clientId });
const { code, msg } = await bridge.commandRequest<PromptsListResponse>('prompts/list', { clientId: this.clientId }); if (code!== 200) {
if (code !== 200) {
return new Map<string, PromptTemplate>(); return new Map<string, PromptTemplate>();
} }
this.promptTemplates = new Map<string, PromptTemplate>(); this.promptTemplates = new Map<string, PromptTemplate>();
msg.prompts.forEach(template => { msg.prompts.forEach(template => {
this.promptTemplates!.set(template.name, template); this.promptTemplates!.set(template.name, template);
@ -250,9 +250,9 @@ export class McpClient {
} }
const bridge = useMessageBridge(); const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<ResourcesListResponse>('resources/list', { clientId: this.clientId }); const { code, msg } = await bridge.commandRequest<ResourcesListResponse>('resources/list', { clientId: this.clientId });
if (code !== 200) { if (code!== 200) {
return new Map<string, Resources>(); return new Map<string, Resources>();
} }
@ -274,9 +274,9 @@ export class McpClient {
} }
const bridge = useMessageBridge(); const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<ResourceTemplatesListResponse>('resources/templates/list', { clientId: this.clientId }); const { code, msg } = await bridge.commandRequest<ResourceTemplatesListResponse>('resources/templates/list', { clientId: this.clientId });
if (code !== 200) { if (code!== 200) {
return new Map(); return new Map();
} }
this.resourceTemplates = new Map<string, ResourceTemplate>(); this.resourceTemplates = new Map<string, ResourceTemplate>();
@ -367,7 +367,7 @@ export class McpClient {
title: `${this.name}'s tools loaded (${tools.size})`, title: `${this.name}'s tools loaded (${tools.size})`,
message: prettifyMapKeys(tools.keys()) message: prettifyMapKeys(tools.keys())
}); });
const prompts = await this.getPromptTemplates({ cache: false }); const prompts = await this.getPromptTemplates({ cache: false });
this.connectionResult.logString.push({ this.connectionResult.logString.push({
type: 'info', type: 'info',
@ -381,7 +381,7 @@ export class McpClient {
title: `${this.name}'s resources loaded (${resources.size})`, title: `${this.name}'s resources loaded (${resources.size})`,
message: prettifyMapKeys(resources.keys()) message: prettifyMapKeys(resources.keys())
}); });
const resourceTemplates = await this.getResourceTemplates({ cache: false }); const resourceTemplates = await this.getResourceTemplates({ cache: false });
this.connectionResult.logString.push({ this.connectionResult.logString.push({
type: 'info', type: 'info',
@ -440,7 +440,7 @@ export class McpClient {
}); });
if (code === 200) { if (code === 200) {
this.connectionResult.logString.push({ this.connectionResult.logString.push({
type: 'info', type: 'info',
title: t('preset-env-sync.success') title: t('preset-env-sync.success')
@ -455,62 +455,19 @@ 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 { class McpClientAdapter {
public clients: Reactive<McpClient[]> = []; public clients: Reactive<McpClient[]> = [];
public currentClientIndex: number = 0; public currentClientIndex: number = 0;
public refreshSignal = reactive({ value: 0 });
private defaultClient: McpClient = new McpClient(); private defaultClient: McpClient = new McpClient();
public connectLogListenerCancel: (() => void) | null = null; public connectLogListenerCancel: (() => void) | null = null;
public connectrefreshListener: (() => void) | null = null;
constructor( constructor(
public platform: string public platform: string
) { ) { }
if (platform !== 'nodejs') {
this.addConnectRefreshListener();
}
}
/** /**
* @description * @description
@ -554,50 +511,6 @@ 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() { public async launch() {
// 创建对于 log/output 的监听 // 创建对于 log/output 的监听
if (!this.connectLogListenerCancel) { if (!this.connectLogListenerCancel) {
@ -606,22 +519,22 @@ class McpClientAdapter {
const { code, msg } = message; const { code, msg } = message;
const client = this.clients.at(-1); const client = this.clients.at(-1);
if (!client) { if (!client) {
return; return;
} }
client.connectionResult.logString.push({ client.connectionResult.logString.push({
type: code === 200 ? 'info' : 'error', type: code === 200 ? 'info': 'error',
title: msg.title, title: msg.title,
message: msg.message message: msg.message
}); });
}, { once: false }); }, { once: false });
} }
const launchSignature = await this.getLaunchSignature();
const launchSignature = await this.getLaunchSignature();
let allOk = true; let allOk = true;
for (const item of launchSignature) { for (const item of launchSignature) {
@ -686,7 +599,7 @@ class McpClientAdapter {
return msg; return msg;
} }
public async readPromptTemplate(promptId: string, args?: Record<string, any>) { public async readPromptTemplate(promptId: string, args: Record<string, any>) {
// TODO: 如果遇到不同服务器的同名 tool请拓展解决方案 // TODO: 如果遇到不同服务器的同名 tool请拓展解决方案
// 目前只找到第一个匹配 toolName 的工具进行调用 // 目前只找到第一个匹配 toolName 的工具进行调用
let clientId = this.clients[0].clientId; let clientId = this.clients[0].clientId;
@ -728,7 +641,7 @@ class McpClientAdapter {
timeout: mcpSetting.timeout * 1000 timeout: mcpSetting.timeout * 1000
} }
}); });
return msg; return msg;
} }

View File

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

View File

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

View File

@ -4,42 +4,18 @@
<span class="iconfont icon-llm"></span> <span class="iconfont icon-llm"></span>
<span class="option-title">{{ t('model') }}</span> <span class="option-title">{{ t('model') }}</span>
</span> </span>
<div style="width: 240px;"> <div style="width: 160px;">
<el-select v-if="llms[llmManager.currentModelIndex]" <el-select v-if="llms[llmManager.currentModelIndex]"
name="model-setting" name="language-setting"
v-model="llms[llmManager.currentModelIndex].userModel" v-model="llms[llmManager.currentModelIndex].userModel"
@change="onmodelchange" @change="onmodelchange"
filterable
:placeholder="getPlaceholderText()"
:reserve-keyword="false"
size="default"
:popper-class="isOpenRouter ? 'openrouter-select-dropdown' : ''"
> >
<el-option <el-option
v-for="option in llms[llmManager.currentModelIndex].models" v-for="option in llms[llmManager.currentModelIndex].models"
:value="option" :value="option"
:label="option" :label="option"
:key="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> </el-select>
</div> </div>
</div> </div>
@ -70,7 +46,7 @@
<script setup lang="ts"> <script setup lang="ts">
/* eslint-disable */ /* eslint-disable */
import { defineComponent, computed, ref, watch } from 'vue'; import { defineComponent } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { llmManager, llms } from './llm'; import { llmManager, llms } from './llm';
import { pinkLog } from './util'; import { pinkLog } from './util';
@ -81,29 +57,6 @@ defineComponent({ name: 'connect-interface-openai' });
const { t } = useI18n(); 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() { function saveLlmSetting() {
saveSetting(() => { saveSetting(() => {
ElMessage({ ElMessage({
@ -118,140 +71,7 @@ function onmodelchange() {
saveLlmSetting(); 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> </script>
<style scoped> <style>
/* 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> </style>

View File

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

View File

@ -2,110 +2,123 @@
<img src="./icons/openmcp-sdk.svg" height="200px"/> <img src="./icons/openmcp-sdk.svg" height="200px"/>
<h3>openmcp-sdk: Deployment Framework for OpenMCP</h3> <h3>openmcp-sdk : 适用于 openmcp 的部署框架</h3>
<h4>Lightning-fast deployment of your agent from lab to production</h4> <h4>闪电般将您的 agent 从实验室部署到生产环境</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 Official Documentation</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 官方文档</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> <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>
</div> </div>
## Installation
## 安装
```bash ```bash
npm install openmcp-sdk npm install openmcp-sdk
``` ```
> Currently, openmcp-sdk only supports ESM-style imports. > 目前 openmcp-sdk 只支持 esm 模式的导入
## Usage ## 使用
Filename: `main.ts` 文件名main.ts
```typescript ```typescript
import { OmAgent } from 'openmcp-sdk/service/sdk'; import { TaskLoop } from 'openmcp-sdk/task-loop';
import { TaskLoopAdapter } from 'openmcp-sdk/service';
async function main() {
// 创建适配器,负责通信和 mcp 连接
const adapter = new TaskLoopAdapter();
// create Agent // 添加 mcp 服务器
const agent = new OmAgent(); adapter.addMcp({
connectionType: 'STDIO',
// Load configuration, which can be automatically generated after debugging with openmcp client commandString: 'node index.js',
agent.loadMcpConfig('./mcpconfig.json'); cwd: '~/projects/openmcp-tutorial/my-browser/dist'
});
// Read the debugged prompt // 创建事件循环驱动器, verbose 数值越高,输出的日志越详细
const prompt = await agent.getPrompt('hacknews', { topn: '5' }); const taskLoop = new TaskLoop({ adapter, verbose: 1 });
// Execute the task // 获取所有工具
const res = await agent.ainvoke({ messages: prompt }); const tools = await taskLoop.getTools();
console.log('⚙️ Agent Response', res); // 配置改次事件循环使用的大模型
``` taskLoop.setLlmConfig({
id: 'deepseek',
baseUrl: 'https://api.deepseek.com/v1',
userToken: process.env['DEEPSEEK_API_TOKEN'],
userModel: 'deepseek-chat'
});
`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: // 创建当前事件循环对应的上下文,并且配置当前上下文的设置
const storage = {
```json messages: [],
{ settings: {
"version": "1.0.0", temperature: 0.7,
"namespace": "openmcp", // 在本次对话使用所有工具
"mcpServers": { enableTools: tools,
"my-browser": { // 系统提示词
"command": "mcp", systemPrompt: 'you are a clever bot',
"args": [ // 对话上下文的轮数
"run", contextLength: 20
"~/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", const message = 'hello world';
"model": "deepseek-chat"
} // 开启事件循环
} 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: 下面是可能的输出:
``` ```
[2025/6/20 20:47:31] 🚀 [crawl4ai-mcp] 1.9.1 connected [6/5/2025, 8:16:15 PM] 🚀 [my-browser] 0.1.0 connected
[2025/6/20 20:47:35] 🤖 Agent wants to use these tools get_web_markdown [6/5/2025, 8:16:15 PM] task loop enters a new epoch
[2025/6/20 20:47:35] 🔧 using tool get_web_markdown [6/5/2025, 8:16:23 PM] task loop finish a epoch
[2025/6/20 20:47:39] ✓ use tools success [6/5/2025, 8:16:23 PM] 🤖 llm wants to call these tools k_navigate
[2025/6/20 20:47:46] 🤖 Agent wants to use these tools get_web_markdown, get_web_markdown, get_web_markdown [6/5/2025, 8:16:23 PM] 🔧 calling tool k_navigate
[2025/6/20 20:47:46] 🔧 using tool get_web_markdown [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/
[2025/6/20 20:47:48] ✓ use tools success [6/5/2025, 8:16:34 PM] task loop enters a new epoch
[2025/6/20 20:47:48] 🔧 using tool get_web_markdown [6/5/2025, 8:16:40 PM] task loop finish a epoch
[2025/6/20 20:47:54] ✓ use tools success [6/5/2025, 8:16:40 PM] 🤖 llm wants to call these tools k_navigate
[2025/6/20 20:47:54] 🔧 using tool get_web_markdown [6/5/2025, 8:16:40 PM] 🔧 calling tool k_navigate
[2025/6/20 20:47:57] ✓ use tools success [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
⚙️ Agent Response [6/5/2025, 8:16:57 PM] task loop finish a epoch
⌨️ Today's Tech Article Roundup [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
📌 How to Detect or Observe Passing Gravitational Waves? [6/5/2025, 8:16:57 PM] ✓ call tools okey dockey success
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. [6/5/2025, 8:16:57 PM] task loop enters a new epoch
Author: ynoxinul [6/5/2025, 8:17:06 PM] task loop finish a epoch
Posted: 2 hours ago [6/5/2025, 8:17:06 PM] 🤖 llm wants to call these tools k_navigate, k_navigate
Link: https://physics.stackexchange.com/questions/338912/how-would-a-passing-gravitational-wave-look-or-feel [6/5/2025, 8:17:06 PM] 🔧 calling tool k_navigate
[6/5/2025, 8:17:09 PM] ✓ call tools okey dockey success
📌 Learn Makefile Tutorial [6/5/2025, 8:17:09 PM] 🔧 calling tool k_navigate
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. [6/5/2025, 8:17:12 PM] ✓ call tools okey dockey success
Author: dsego [6/5/2025, 8:17:12 PM] task loop enters a new epoch
Posted: 4 hours ago [6/5/2025, 8:17:19 PM] task loop finish a epoch
Link: https://makefiletutorial.com/ [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
📌 Hurl: Run and Test HTTP Requests in Plain Text [6/5/2025, 8:17:19 PM] ✓ call tools okey dockey success
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. [6/5/2025, 8:17:19 PM] 🔧 calling tool k_evaluate
Author: flykespice [6/5/2025, 8:17:19 PM] ✓ call tools okey dockey success
Posted: 8 hours ago [6/5/2025, 8:17:19 PM] task loop enters a new epoch
Link: https://github.com/Orange-OpenSource/hurl [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如果需要进一步优化或补充其他内容请随时告诉我"
``` ```
For more details, see the official documentation: [https://kirigaya.cn/openmcp/sdk-tutorial/](https://kirigaya.cn/openmcp/sdk-tutorial/) 更多使用请看官方文档https://kirigaya.cn/openmcp/sdk-tutorial/
Star our project: [https://github.com/LSTM-Kirigaya/openmcp-client](https://github.com/LSTM-Kirigaya/openmcp-client) star 我们的项目https://github.com/LSTM-Kirigaya/openmcp-client

View File

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

View File

@ -1,7 +1,6 @@
{ {
"name": "openmcp-sdk", "name": "openmcp-sdk",
"version": "0.0.8", "version": "0.0.7",
"type": "module",
"description": "openmcp-sdk", "description": "openmcp-sdk",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
@ -38,8 +37,6 @@
"open": "^10.1.2", "open": "^10.1.2",
"ws": "^8.18.1", "ws": "^8.18.1",
"cli-table3": "^0.6.5", "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,72 +4,35 @@ import type { OpenAI } from 'openai';
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk; export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string }; export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
interface SchemaProperty { export interface TaskLoopOptions {
title: string; maxEpochs?: number;
type: string; maxJsonParseRetry?: number;
description?: string; adapter?: any;
verbose?: 0 | 1 | 2 | 3;
} }
interface InputSchema { export interface SchemaProperty {
type: string; title: string;
properties: Record<string, SchemaProperty>; type: string;
required?: string[]; description?: string;
title?: string;
$defs?: any;
} }
interface ToolItem { export interface InputSchema {
name: string; type: string;
description: string; properties: Record<string, SchemaProperty>;
inputSchema: InputSchema; required?: string[];
title?: string;
$defs?: any;
}
export interface ToolItem {
name: string;
description: string;
inputSchema: InputSchema;
enabled: boolean; enabled: boolean;
anyOf?: any; 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> = { export type Ref<T> = {
value: T; value: T;
}; };
@ -87,7 +50,7 @@ export interface ToolCall {
export interface ToolCallContent { export interface ToolCallContent {
type: string; type: string;
text: string; text: string;
[key: string]: any; [key: string]: any;
} }
export interface ToolCallResult { export interface ToolCallResult {
@ -117,143 +80,68 @@ export interface IDoConversationResult {
stop: boolean; 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 * @description
*/ */
export class TaskLoop { 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); constructor(taskOptions?: TaskLoopOptions);
private handleChunkDeltaContent;
/** private handleChunkDeltaToolCalls;
* @description wait for connection private handleChunkUsage;
*/ private doConversation;
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; makeChatData(tabStorage: any): ChatCompletionCreateParamsBase | undefined;
/**
* @description stop the task loop
*/
abort(): void; abort(): void;
/** /**
* @description Register a callback function triggered on error * @description error
* @param handler * @param handler
*/ */
registerOnError(handler: (msg: IErrorMssage) => void): void; registerOnError(handler: (msg: IErrorMssage) => void): void;
/**
* @description Register a callback function triggered on chunk
* @param handler
*/
registerOnChunk(handler: (chunk: ChatCompletionChunk) => void): void; registerOnChunk(handler: (chunk: ChatCompletionChunk) => void): void;
/** /**
* @description Register a callback function triggered at the beginning of each epoch * @description chat.completion
* @param handler * @param handler
*/ */
registerOnDone(handler: () => void): void; registerOnDone(handler: () => void): void;
/** /**
* @description Register a callback function triggered at the beginning of each epoch * @description epoch
* @param handler * @param handler
*/ */
registerOnEpoch(handler: () => void): void; 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. * @description toolcall
* @param handler * @param handler
*/ */
registerOnToolCalled(handler: (toolCallResult: ToolCallResult) => ToolCallResult): void; registerOnToolCalled(handler: (toolCallResult: ToolCallResult) => ToolCallResult): void;
/** /**
* @description Register a callback triggered after tool call finishes. You can intercept and modify the output. * @description toolcall
* @param handler * @param handler
*/ */
registerOnToolCall(handler: (toolCall: ToolCall) => ToolCall): void; registerOnToolCall(handler: (toolCall: ToolCall) => ToolCall): void;
/** /**
* @description Get current LLM configuration * @description LLM
*/ */
getLlmConfig(): any; getLlmConfig(): any;
/** /**
* @description Set the current LLM configuration, for Node.js environment * @description LLM nodejs
* @param config * @param config
* @example * @example
* setLlmConfig({ * setLlmConfig({
@ -266,19 +154,11 @@ export class TaskLoop {
setLlmConfig(config: any): void; setLlmConfig(config: any): void;
/** /**
* @description Set proxy server * @description epoch
* @param maxEpochs * @param maxEpochs
*/ */
setMaxEpochs(maxEpochs: number): void; setMaxEpochs(maxEpochs: number): void;
/**
* @description bind streaming content and tool calls
*/
bindStreaming(content: Ref<string>, toolCalls: Ref<ToolCall[]>): void; bindStreaming(content: Ref<string>, toolCalls: Ref<ToolCall[]>): void;
/**
* @description not finish
*/
connectToService(): Promise<void>; connectToService(): Promise<void>;
/** /**
@ -288,29 +168,14 @@ export class TaskLoop {
setProxyServer(proxyServer: string): void; setProxyServer(proxyServer: string): void;
/** /**
* @description Get all available tool list * @description
*/ */
listTools(): Promise<ToolItem[]>; listTools(): Promise<ToolItem[]>;
/** /**
* @description Start the loop and asynchronously update the DOM * @description DOM
*/ */
start(tabStorage: ChatStorage, userMessage: string): Promise<void>; start(tabStorage: any, 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 getToolSchema: any;

View File

@ -1,37 +0,0 @@
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", "types": "dist/index.d.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"serve": "tsx watch src/main.ts", "serve": "nodemon --watch src --exec tsx src/main.ts",
"build": "tsc", "build": "tsc",
"build:watch": "tsc --watch", "build:watch": "tsc --watch",
"postbuild": "node ./scripts/post-build.mjs", "postbuild": "node ./scripts/post-build.mjs",
"start": "node dist/main.js", "start": "node --experimental-specifier-resolution=node dist/main.js",
"start:prod": "NODE_ENV=production node dist/main.js", "start:prod": "NODE_ENV=production node --experimental-specifier-resolution=node dist/main.js",
"debug": "tsx --inspect src/main.ts", "debug": "node --inspect --no-warnings=ExperimentalWarning -r tsx/esm src/main.ts",
"clean": "rm -rf dist", "clean": "rm -rf dist",
"lint": "eslint src --ext .ts,.tsx", "lint": "eslint src --ext .ts,.tsx",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
@ -50,4 +50,4 @@
"uuid": "^11.1.0", "uuid": "^11.1.0",
"ws": "^8.18.1" "ws": "^8.18.1"
} }
} }

View File

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

View File

@ -1,5 +1,8 @@
import { WebSocket } from 'ws'; import { WebSocket } from 'ws';
import { ConnectionType } from '../mcp/client.dto.js'; 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';
// WebSocket 消息格式 // WebSocket 消息格式
export interface WebSocketMessage { export interface WebSocketMessage {
@ -24,10 +27,6 @@ export interface IConnectionArgs {
cwd?: string; cwd?: string;
url?: string; url?: string;
oauth?: string; oauth?: string;
env?: {
[key: string]: string;
};
[key: string]: any;
} }
// 监听器回调类型 // 监听器回调类型
@ -57,7 +56,6 @@ export class VSCodeWebViewLike {
* @param message - command args * @param message - command args
*/ */
postMessage(message: WebSocketMessage): void { postMessage(message: WebSocketMessage): void {
if (this.ws.readyState === WebSocket.OPEN) { if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message)); this.ws.send(JSON.stringify(message));
} else { } else {
@ -77,3 +75,80 @@ 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,21 +130,6 @@ export const llms = [
website: 'https://kimi.moonshot.cn', website: 'https://kimi.moonshot.cn',
userToken: '', userToken: '',
userModel: 'moonshot-v1-8k' 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

@ -1,100 +0,0 @@
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
}));
}

View File

@ -1,327 +0,0 @@
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 { routeMessage } from './common/router.js';
export { VSCodeWebViewLike } from './hook/adapter.js'; export { VSCodeWebViewLike, TaskLoopAdapter } from './hook/adapter.js';
export { setVscodeWorkspace, setRunningCWD } from './hook/setting.js'; export { setVscodeWorkspace, setRunningCWD } from './hook/setting.js';
export { clientMap } from './mcp/connect.service.js'; export { clientMap } from './mcp/connect.service.js';

View File

@ -1,9 +1,11 @@
import { RequestClientType } from "../common/index.dto.js";
import { Controller } from "../common/index.js"; import { Controller } from "../common/index.js";
import { RequestData } from "../common/index.dto.js"; import { RequestData } from "../common/index.dto.js";
import { PostMessageble } from "../hook/adapter.js"; import { PostMessageble } from "../hook/adapter.js";
import { getClient } from "../mcp/connect.service.js";
import { abortMessageService, streamingChatCompletion } from "./llm.service.js"; import { abortMessageService, streamingChatCompletion } from "./llm.service.js";
import { OpenAI } from "openai"; import { OpenAI } from "openai";
import { fetchOpenRouterModels, getSimplifiedModels } from "../hook/openrouter.js"; import { axiosFetch } from "src/hook/axios-fetch.js";
export class LlmController { export class LlmController {
@Controller('llm/chat/completions') @Controller('llm/chat/completions')
@ -55,66 +57,4 @@ export class LlmController {
msg: models.data 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,17 +35,9 @@ 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({ const client = new OpenAI({
baseURL, baseURL,
apiKey, apiKey,
defaultHeaders: Object.keys(defaultHeaders).length > 0 ? defaultHeaders : undefined
}); });
const seriableTools = (tools.length === 0) ? undefined: tools; const seriableTools = (tools.length === 0) ? undefined: tools;

View File

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

View File

@ -137,7 +137,7 @@ export class McpClient {
} }
// 获取提示 // 获取提示
public async getPrompt(name: string, args: Record<string, any> = {}) { public async getPrompt(name: string, args: Record<string, any> = {}) {
return await this.client.getPrompt({ return await this.client.getPrompt({
name, arguments: args name, arguments: args
}); });

View File

@ -1,132 +0,0 @@
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,34 +3,18 @@ import { RequestClientType } from '../common/index.dto.js';
import { connect } from './client.service.js'; import { connect } from './client.service.js';
import { RestfulResponse } from '../common/index.dto.js'; import { RestfulResponse } from '../common/index.dto.js';
import { McpOptions } from './client.dto.js'; import { McpOptions } from './client.dto.js';
import { McpServerConnectMonitor } from './connect-monitor.service.js';
import * as crypto from 'node:crypto'; import * as crypto from 'node:crypto';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import * as os from 'os'; import * as os from 'os';
import { PostMessageble } from '../hook/adapter.js'; import { PostMessageble } from '../hook/adapter.js';
import chalk from 'chalk'; import Table from 'cli-table3';
export const clientMap: Map<string, RequestClientType> = new Map(); export const clientMap: Map<string, RequestClientType> = new Map();
export function getClient(clientId?: string): RequestClientType | undefined { export function getClient(clientId?: string): RequestClientType | undefined {
return clientMap.get(clientId || ''); 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 { export function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
try { try {
const commandString = command + ' ' + args.join(' '); const commandString = command + ' ' + args.join(' ');
@ -270,6 +254,25 @@ export async function connectService(
webview?: PostMessageble webview?: PostMessageble
): Promise<RestfulResponse> { ): Promise<RestfulResponse> {
try { 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); await preprocessCommand(option, webview);
@ -285,14 +288,8 @@ export async function connectService(
// } // }
// const client = clientMap.get(uuid)!; // const client = clientMap.get(uuid)!;
{
clientMap.get(uuid)?.disconnect();
clientMonitorMap.get(uuid)?.close();
}
const client = await connect(option); const client = await connect(option);
clientMap.set(uuid, client); clientMap.set(uuid, client);
clientMonitorMap.set(uuid, new McpServerConnectMonitor(uuid, option, updateClientMap, webview));
const versionInfo = client.getServerVersion(); const versionInfo = client.getServerVersion();

View File

@ -1,185 +0,0 @@
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 };

View File

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

View File

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

View File

@ -53,28 +53,9 @@ export function loadSetting(): IConfig {
try { try {
const configData = fs.readFileSync(configPath, 'utf-8'); const configData = fs.readFileSync(configPath, 'utf-8');
const config = JSON.parse(configData) as IConfig; const config = JSON.parse(configData) as IConfig;
if (!config.LLM_INFO || (Array.isArray(config.LLM_INFO) && config.LLM_INFO.length === 0)) { if (!config.LLM_INFO || (Array.isArray(config.LLM_INFO) && config.LLM_INFO.length === 0)) {
config.LLM_INFO = llms; 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; return config;
} catch (error) { } catch (error) {
console.error('Error loading config file, creating new one:', error); console.error('Error loading config file, creating new one:', error);

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

@ -1,324 +0,0 @@
/* 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, "forceConsistentCasingInFileNames": true,
"outDir": "./dist", "outDir": "./dist",
"declaration": true, "declaration": true,
"declarationMap": false, "declarationMap": true,
"experimentalDecorators": true, "experimentalDecorators": true,
}, },
"paths": { "paths": {

View File

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

View File

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

View File

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

View File

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

View File

@ -95,13 +95,7 @@ export function getConnectionConfig() {
*/ */
export function getWorkspaceConnectionConfigPath() { export function getWorkspaceConnectionConfigPath() {
const workspace = getWorkspacePath(); 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 configDir = fspath.join(workspace, '.openmcp');
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true }); // 递归创建目录
}
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME); const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
return connectionConfig; return connectionConfig;
} }
@ -116,9 +110,6 @@ export function getWorkspaceConnectionConfig() {
} }
const workspace = getWorkspacePath(); 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 configDir = fspath.join(workspace, '.openmcp');
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME); const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
@ -305,7 +296,6 @@ function normaliseConnectionFilePath(item: McpOptions, workspace: string) {
export function getWorkspacePath() { export function getWorkspacePath() {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
console.log('getWorkspacePath: ', vscode.workspace.workspaceFolders);
return (workspaceFolder?.uri.fsPath || '').replace(/\\/g, '/'); return (workspaceFolder?.uri.fsPath || '').replace(/\\/g, '/');
} }
@ -377,18 +367,3 @@ export async function getFirstValidPathFromCommand(command: string, cwd: string)
return undefined; 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/index.js"; import { RegisterCommand } from "../common";
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import Tesseract from 'tesseract.js'; import Tesseract from 'tesseract.js';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,6 @@
import { getFirstValidPathFromCommand, getWorkspaceConnectionConfig, getWorkspacePath, McpOptions, panels, saveWorkspaceConnectionConfig } from "../global.js"; import { getFirstValidPathFromCommand, getWorkspaceConnectionConfig, getWorkspacePath, McpOptions, panels, saveWorkspaceConnectionConfig } from "../global";
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { t } from "../i18n/index.js"; import { t } from "../i18n";
import { exec } from 'child_process';
import { promisify } from 'util';
export async function deleteUserConnection(item: McpOptions[] | McpOptions) { export async function deleteUserConnection(item: McpOptions[] | McpOptions) {
// 弹出确认对话框 // 弹出确认对话框
@ -26,7 +24,7 @@ export async function deleteUserConnection(item: McpOptions[] | McpOptions) {
// 从配置中移除该连接项 // 从配置中移除该连接项
// TODO: 改成基于 path 进行搜索 // TODO: 改成基于 path 进行搜索
const index = workspaceConnectionConfig.items.indexOf(item); const index = workspaceConnectionConfig.items.indexOf(item);
if (index !== -1) { if (index !== -1) {
workspaceConnectionConfig.items.splice(index, 1); workspaceConnectionConfig.items.splice(index, 1);
@ -37,7 +35,7 @@ export async function deleteUserConnection(item: McpOptions[] | McpOptions) {
// 刷新侧边栏视图 // 刷新侧边栏视图
vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh'); vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh');
// 如果该连接有对应的webview面板则关闭它 // 如果该连接有对应的webview面板则关闭它
const filePath = masterNode.filePath || ''; const filePath = masterNode.filePath || '';
const panel = panels.get(filePath); const panel = panels.get(filePath);
@ -47,7 +45,8 @@ export async function deleteUserConnection(item: McpOptions[] | McpOptions) {
} }
export async function validateAndGetCommandPath(command: string, cwd?: string): Promise<string> { export async function validateAndGetCommandPath(command: string, cwd?: string): Promise<string> {
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec); const execAsync = promisify(exec);
try { try {

View File

@ -1,27 +0,0 @@
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

@ -1,31 +0,0 @@
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

@ -1,44 +0,0 @@
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

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

View File

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

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