This commit is contained in:
锦恢 2025-06-19 23:56:08 +08:00
commit 66f9b128f8
89 changed files with 15259 additions and 2332 deletions

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

@ -0,0 +1,42 @@
name: Build
on:
push:
branches:
- main
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: 'npm'
- name: Install dependencies
run: npm ci
- name: Build all
run: npm run 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

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

@ -0,0 +1,41 @@
name: Test
on:
push:
branches:
- main
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: 'npm'
- name: Install dependencies
run: npm ci
- 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 npm run test
- name: Run Test (Windows/macOS)
if: runner.os != 'Linux'
run: npm run test

View File

@ -3,6 +3,7 @@
## [main] 0.1.6 ## [main] 0.1.6
- 针对 0.1.5 无法在 Windows 启动的紧急修复。 - 针对 0.1.5 无法在 Windows 启动的紧急修复。
- 修复环境变量中添加 token 失效的问题。 - 修复环境变量中添加 token 失效的问题。
- 优化工具展示的页面布局。
## [main] 0.1.5 ## [main] 0.1.5
- 修复 gemini 获取模型列表时存在 models 前缀的问题 - 修复 gemini 获取模型列表时存在 models 前缀的问题

169
CLAUDE.md Normal file
View File

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

View File

@ -60,6 +60,7 @@ Supports multiple large models
| `service` | Cloud sync for system configuration | `MVP` | 0% | `P1` | | `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
@ -134,9 +135,8 @@ npm run setup
Start dev server: Start dev server:
```bash ```bash
npm run dev npm run serve
``` ```
Port usage: 8282 (renderer) + 8081 (service)
### Extension Development ### Extension Development
@ -151,4 +151,11 @@ 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)

16
esbuild.config.js Normal file
View File

@ -0,0 +1,16 @@
// 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'], // 只排除 vscode其他依赖全部打包进来
target: ['node18'], // 你可以根据实际 node 版本调整
loader: {
'.json': 'json'
}
}).catch(() => process.exit(1));

2659
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"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.6", "version": "0.1.7",
"publisher": "kirigaya", "publisher": "kirigaya",
"author": { "author": {
"name": "kirigaya", "name": "kirigaya",
@ -19,7 +19,7 @@
"Other" "Other"
], ],
"activationEvents": [], "activationEvents": [],
"main": "./dist/extension.js", "main": "./dist/extension.cjs.js",
"icon": "icons/openmcp.png", "icon": "icons/openmcp.png",
"contributes": { "contributes": {
"configuration": { "configuration": {
@ -222,39 +222,58 @@
"scripts": { "scripts": {
"setup": "npm i && npm run prepare:ocr", "setup": "npm i && npm run prepare:ocr",
"serve": "turbo serve", "serve": "turbo serve",
"build": "turbo build", "build": "turbo build && tsc -p ./ && node esbuild.config.js",
"build:electron": "turbo build --filter=@openmcp/electron", "build:plugin": "npm run build && tsc && vsce package",
"build:all": "turbo build", "vscode:prepublish": "node esbuild.config.js",
"vscode:prepublish": "webpack --mode production",
"compile": "tsc -p ./", "compile": "tsc -p ./",
"watch": "tsc -watch -p ./", "watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint", "pretest": "npm run build",
"lint": "eslint src --ext ts", "lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js", "test": "node ./dist/test/e2e/runTest.js",
"prepare:ocr": "webpack --config webpack/webpack.tesseract.js", "prepare:ocr": "rollup -c rollup.tesseract.js --bundleConfigAsCjs",
"build:task-loop": "npx vite build --config webpack/vite.config.task-loop.mjs && node renderer/scripts/task-loop.build.mjs" "build:task-loop": "npx vite build --config renderer/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/vscode": "^1.72.0", "@types/vscode": "^1.72.0",
"@vscode/test-cli": "^0.0.11",
"@vscode/test-electron": "^2.5.2",
"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",
"ts-loader": "^9.5.1", "ts-loader": "^9.5.1",
"turbo": "^2.5.3", "turbo": "^2.5.3",

4713
renderer/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -33,7 +33,8 @@
"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",
@ -47,6 +48,7 @@
"@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",

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=1749572305505') format('woff2'), src: url('iconfont.woff2?t=1750172161574') format('woff2'),
url('iconfont.woff?t=1749572305505') format('woff'), url('iconfont.woff?t=1750172161574') format('woff'),
url('iconfont.ttf?t=1749572305505') format('truetype'); url('iconfont.ttf?t=1750172161574') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.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

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

View File

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

View File

@ -9,7 +9,7 @@ if (fs.existsSync(targetFile)) {
content = content.replace(/'element-plus'/g, "'./tools.js'"); content = content.replace(/'element-plus'/g, "'./tools.js'");
content = content.replace(/"element-plus"/g, "\"./tools.js\""); content = content.replace(/"element-plus"/g, "\"./tools.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

@ -1,4 +1,4 @@
import type { ToolCallContent, ToolItem } from "@/hook/type"; import type { InputSchema, ToolCallContent, ToolItem } from "@/hook/type";
import { type Ref, ref } from "vue"; import { type Ref, ref } from "vue";
import type { OpenAI } from 'openai'; import type { OpenAI } from 'openai';
@ -16,6 +16,7 @@ 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 {
@ -23,6 +24,7 @@ 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;
} }
@ -52,7 +54,7 @@ export interface EnableToolItem {
name: string; name: string;
description: string; description: string;
enabled: boolean; enabled: boolean;
inputSchema: any; inputSchema: InputSchema;
} }
export interface ChatSetting { export interface ChatSetting {
@ -63,6 +65,7 @@ export interface ChatSetting {
enableWebSearch: boolean enableWebSearch: boolean
contextLength: number contextLength: number
parallelToolCalls: boolean parallelToolCalls: boolean
enableXmlWrapper: boolean
} }
export interface ChatStorage { export interface ChatStorage {
@ -107,7 +110,7 @@ export interface IToolRenderMessage {
export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage; export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage;
export function getToolSchema(enableTools: EnableToolItem[]) { export function getToolSchema(enableTools: EnableToolItem[]): any[] {
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

@ -1,6 +1,6 @@
<template> <template>
<el-tooltip :content="t('context-length')" placement="top"> <el-tooltip :content="t('context-length')" placement="top" effect="light">
<div class="setting-button" @click="showContextLengthDialog = true"> <div class="setting-button width-30" @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="99" :step="1" /> <el-slider v-model="tabStorage.settings.contextLength" :min="1" :max="500" :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,4 +39,8 @@ const showContextLengthDialog = ref(false);
.icon-length { .icon-length {
font-size: 16px; font-size: 16px;
} }
.width-30 {
width: 30px;
}
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="t('choose-model')" placement="top"> <el-tooltip :content="t('choose-model')" placement="top" effect="light">
<div class="setting-button" @click="showModelDialog = true"> <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"> <el-tooltip :content="t('parallel-tool-calls')" placement="top" effect="light">
<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"> <el-tooltip :content="t('prompts')" placement="top" effect="light">
<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"> <el-tooltip :content="t('resources')" placement="top" effect="light">
<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,6 +8,7 @@
<ParallelToolCalls /> <ParallelToolCalls />
<Temperature /> <Temperature />
<ContextLength /> <ContextLength />
<XmlWrapper />
</div> </div>
</template> </template>
@ -25,6 +26,7 @@ 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';
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -58,8 +60,9 @@ if (!tabStorage.settings) {
enableTools: [], enableTools: [],
enableWebSearch: false, enableWebSearch: false,
temperature: 0.6, temperature: 0.6,
contextLength: 20, contextLength: 100,
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"> <el-tooltip :content="t('system-prompt')" placement="top" effect="light">
<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"> <el-tooltip :content="t('temperature-parameter')" placement="top" effect="light">
<div class="setting-button" @click="showTemperatureSlider = true"> <div class="setting-button width-30" @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,9 +34,13 @@ const showTemperatureSlider = ref(false);
</script> </script>
<style> <style scoped>
.icon-temperature { .icon-temperature {
font-size: 18px; font-size: 16px;
}
.width-30 {
width: 30px;
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="t('tool-use')" placement="top"> <el-tooltip :content="t('tool-use')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': availableToolsNum > 0 }" size="small" @click="toggleTools"> <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,7 +10,19 @@
</el-tooltip> </el-tooltip>
<el-dialog v-model="showToolsDialog" title="工具管理" width="800px"> <el-dialog v-model="showToolsDialog" width="800px">
<template #header>
<div>
<span>{{ t('tool-manage') }}</span>
<el-tooltip :content="t('enable-xml-wrapper')" placement="top" effect="light">
<span class="xml-tag" :class="{
'active': tabStorage.settings.enableXmlWrapper
}" @click="tabStorage.settings.enableXmlWrapper = !tabStorage.settings.enableXmlWrapper">xml</span>
</el-tooltip>
</div>
</template>
<div class="tools-dialog-container"> <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">
@ -23,23 +35,27 @@
</el-scrollbar> </el-scrollbar>
<el-scrollbar height="400px" class="schema-viewer"> <el-scrollbar height="400px" class="schema-viewer">
<div v-html="activeToolsSchemaHTML"></div> <!-- 如果激活 xml 指令包裹则展示对应的 prompt -->
<div v-if="tabStorage.settings.enableXmlWrapper" v-html="activeToolsXmlPrompt" />
<!-- 如果是普通模式则展示普通的工具列表 -->
<div v-else v-html="activeToolsSchemaHTML" />
</el-scrollbar> </el-scrollbar>
</div> </div>
<template #footer> <template #footer>
<el-button type="primary" @click="enableAllTools">激活所有工具</el-button> <el-button type="primary" @click="enableAllTools">{{ t('enable-all-tools') }}</el-button>
<el-button type="danger" @click="disableAllTools">禁用所有工具</el-button> <el-button type="danger" @click="disableAllTools">{{ t('disable-all-tools') }}</el-button>
<el-button type="primary" @click="showToolsDialog = false">{{ t("cancel") }}</el-button> <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 } from 'vue'; import { ref, computed, inject, onMounted, watch } 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();
@ -48,26 +64,34 @@ 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 toolsSchema = getToolSchema(tabStorage.settings.enableTools);
const jsonString = JSON.stringify(toolsSchema, null, 2);
return markdownToHtml( const activeToolsSchemaHTML = computed(() => {
"```json\n" + jsonString + "\n```" const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
); const jsonString = JSON.stringify(toolsSchema, null, 2);
return markdownToHtml(
"```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;
}); });
}; };
@ -79,34 +103,64 @@ 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(); const tools = await client.getTools({ cache: false });
for (const tool of tools.values()) { if (tools) {
const enabled = !disableToolNames.has(tool.name); for (const tool of tools.values()) {
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></style> <style scoped>
.xml-tag {
margin-left: 10px;
border-radius: .5em;
padding: 2px 5px;
font-size: 12px;
font-weight: 900;
color: black;
background-color: var(--main-color);
opacity: 0.3;
transition: var(--animation-3s);
cursor: pointer;
}
.xml-tag.active {
opacity: 1;
transition: var(--animation-3s);
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="t('websearch')" placement="top"> <el-tooltip :content="t('websearch')" placement="top" effect="light">
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableWebSearch }" size="small" <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

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

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip :content="props.messages[0].content.text" placement="top"> <el-tooltip :content="props.messages[0].content.text" placement="top" effect="light">
<span class="chat-prompt-item" contenteditable="false"> <span class="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>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-tooltip placement="top"> <el-tooltip placement="top" effect="light">
<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,7 +2,14 @@ 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 { redLog } from "@/views/setting/util"; import type OpenAI from "openai";
export interface TaskLoopChatOption {
id?: string
proxyServer?: string
enableXmlWrapper?: boolean
}
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
export interface ToolCallResult { export interface ToolCallResult {
state: MessageState; state: MessageState;
@ -60,7 +67,7 @@ function deserializeToolCallResponse(toolArgs: string) {
} }
} }
function handleToolResponse(toolResponse: ToolCallResponse) { export function handleToolResponse(toolResponse: ToolCallResponse) {
if (typeof toolResponse === 'string') { if (typeof toolResponse === 'string') {
return { return {
@ -98,36 +105,67 @@ function parseErrorObject(error: any): string {
} }
} }
function grokIndexAdapter(toolCall: ToolCall, callId2Index: Map<string, number>): IToolCallIndex {
/**
* @description ID映射为索引
* @param toolCall
* @param callId2Index ID到索引的映射表
* @returns
*/
export function idAsIndexAdapter(toolCall: ToolCall | string, callId2Index: Map<string, number>): IToolCallIndex {
// grok 采用 id 作为 index需要将 id 映射到 zero-based 的 index // grok 采用 id 作为 index需要将 id 映射到 zero-based 的 index
if (!toolCall.id) { const id = typeof toolCall === 'string' ? toolCall : toolCall.id;
if (!id) {
return 0; return 0;
} }
if (!callId2Index.has(toolCall.id)) { if (!callId2Index.has(id)) {
callId2Index.set(toolCall.id, callId2Index.size); callId2Index.set(id, callId2Index.size);
} }
return callId2Index.get(toolCall.id)!; return callId2Index.get(id)!;
} }
function geminiIndexAdapter(toolCall: ToolCall): IToolCallIndex {
/**
* @description
* @param toolCall
* @returns 0
*/
export function singleCallIndexAdapter(toolCall: ToolCall): IToolCallIndex {
// TODO: 等待后续支持 // 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) { export function getToolCallIndexAdapter(llm: BasicLlmDescription, chatData: ChatCompletionCreateParamsBase) {
// 如果是 xml 模式,那么 index adapter 必须是 idAsIndexAdapter
if (chatData.enableXmlWrapper) {
const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
}
if (llm.userModel.startsWith('gemini')) { if (llm.userModel.startsWith('gemini')) {
return geminiIndexAdapter; return singleCallIndexAdapter;
} }
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) => grokIndexAdapter(toolCall, callId2Index); return (toolCall: ToolCall) => idAsIndexAdapter(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 } from "../chat-box/chat"; import { type ToolCall, type ChatStorage, getToolSchema, MessageState, type ChatMessage, type ChatSetting, type EnableToolItem } from "../chat-box/chat";
import { useMessageBridge, MessageBridge, createMessageBridge } from "@/api/message-bridge"; import { 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,9 +13,15 @@ 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 type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string, proxyServer?: string }; export interface TaskLoopChatOption {
id?: string
proxyServer?: string
enableXmlWrapper?: boolean
}
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
export interface TaskLoopOptions { export interface TaskLoopOptions {
maxEpochs?: number; maxEpochs?: number;
maxJsonParseRetry?: number; maxJsonParseRetry?: number;
@ -85,14 +91,25 @@ export class TaskLoop {
this.bridge = useMessageBridge(); this.bridge = useMessageBridge();
} }
private handleChunkDeltaContent(chunk: ChatCompletionChunk) { /**
* @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) {
@ -148,9 +165,15 @@ export class TaskLoop {
const { chunk } = data.msg as { chunk: ChatCompletionChunk }; const { chunk } = data.msg as { chunk: ChatCompletionChunk };
// 处理增量的 content 和 tool_calls // 处理增量的 content 和 tool_calls
this.handleChunkDeltaContent(chunk); if (chatData.enableXmlWrapper) {
this.handleChunkDeltaToolCalls(chunk, toolcallIndexAdapter); this.handleChunkDeltaContent(chunk, chatData);
this.handleChunkUsage(chunk); // no tool call in enableXmlWrapper
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 });
@ -207,23 +230,35 @@ 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
if (tabStorage.settings.systemPrompt) {
const prompt = getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
userMessages.push({ let prompt = '';
role: 'system',
content: prompt // 如果存在系统提示词,则从数据库中获取对应的数据
}); if (tabStorage.settings.systemPrompt) {
prompt += getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
} }
// 如果是 xml 模式,则在开头注入 xml
if (enableXmlWrapper) {
prompt += getXmlWrapperPrompt(tabStorage.settings.enableTools, tabStorage);
}
userMessages.push({
role: 'system',
content: prompt
});
// 如果超出了 tabStorage.settings.contextLength, 则删除最早的消息 // 如果超出了 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);
@ -240,7 +275,8 @@ export class TaskLoop {
tools, tools,
parallelToolCalls, parallelToolCalls,
messages: userMessages, messages: userMessages,
proxyServer proxyServer,
enableXmlWrapper,
} as ChatCompletionCreateParamsBase; } as ChatCompletionCreateParamsBase;
return chatData; return chatData;
@ -461,6 +497,7 @@ export class TaskLoop {
// 等待连接完成 // 等待连接完成
await this.nodejsStatus.connectionFut; await this.nodejsStatus.connectionFut;
} }
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
// 添加目前的消息 // 添加目前的消息
tabStorage.messages.push({ tabStorage.messages.push({
@ -469,7 +506,8 @@ 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
} }
}); });
@ -498,7 +536,7 @@ export class TaskLoop {
this.currentChatId = chatData.id!; this.currentChatId = chatData.id!;
const llm = this.getLlmConfig(); const llm = this.getLlmConfig();
const toolcallIndexAdapter = getToolCallIndexAdapter(llm); const toolcallIndexAdapter = getToolCallIndexAdapter(llm, chatData);
// 发送请求 // 发送请求
const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter); const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter);
@ -513,7 +551,8 @@ 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
} }
}); });
@ -550,7 +589,8 @@ 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;
@ -565,7 +605,8 @@ 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) {
@ -579,7 +620,8 @@ 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
} }
}); });
} }
@ -593,10 +635,96 @@ 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 {
// 一些提示 // 一些提示
@ -609,4 +737,40 @@ 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
}
}
} }

View File

@ -0,0 +1,297 @@
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
}
}
// 进行调用,根据结果返回不同的值
console.log(toolcall);
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" v-if="renderMessages.length > 0 || isLoading"> <el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll"
v-if="renderMessages.length > 0 || isLoading">
<div class="message-list" :ref="el => messageListRef = el"> <div 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,10 +23,8 @@
<!-- 助手调用的工具部分 --> <!-- 助手调用的工具部分 -->
<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.Toolcall :message="message" :tab-id="props.tabId"
:message="message" :tab-id="props.tabId" @update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value" />
@update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value"
/>
</div> </div>
</div> </div>
@ -47,23 +45,22 @@
</div> </div>
</div> </div>
<ChatBox <ChatBox :ref="el => footerRef = el" :tab-id="props.tabId" />
:ref="el => footerRef = el"
:tab-id="props.tabId"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide } from 'vue'; import { ref, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide, watchEffect } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { ElMessage, type ScrollbarInstance } from 'element-plus'; import { 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' });
@ -85,18 +82,71 @@ if (!tabStorage.messages) {
tabStorage.messages = [] as ChatMessage[]; tabStorage.messages = [] as ChatMessage[];
} }
const renderMessages = computed(() => { function getXmlToolCalls(message: ChatMessage) {
const messages: IRenderMessage[] = []; if (message.role !== 'assistant' && message.role !== 'user') {
return [];
}
const enableXmlTools = message.extraInfo?.enableXmlWrapper ?? false;
if (!enableXmlTools) {
return [];
}
const xmls = getXmlsFromString(message.content);
return xmls || [];
}
const renderMessages = ref<IRenderMessage[]>([]);
watchEffect(async () => {
renderMessages.value = [];
for (const message of tabStorage.messages) { for (const message of tabStorage.messages) {
const indexAdapter = getIdAsIndexAdapter();
const xmls = getXmlToolCalls(message);
if (message.role === 'user') { if (message.role === 'user') {
messages.push({ if (xmls.length > 0 && message.extraInfo.enableXmlWrapper) {
role: 'user', // xml xml xml
content: message.content, // assistant/tool_calls
extraInfo: message.extraInfo const lastAssistantMessage = renderMessages.value[renderMessages.value.length - 1];
}); if (lastAssistantMessage.role === 'assistant/tool_calls') {
const toolCallResultXmls = getXmlsFromString(message.content);
for (const xml of toolCallResultXmls) {
const toolResult = await getToolResultFromXmlString(xml);
if (toolResult) {
const index = indexAdapter(toolResult.callId);
lastAssistantMessage.toolResults[index] = toolResult.toolcallContent;
if (lastAssistantMessage.extraInfo.state === MessageState.Unknown) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
} else if (lastAssistantMessage.extraInfo.state === MessageState.Success
|| message.extraInfo.state !== MessageState.Success
) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
}
lastAssistantMessage.extraInfo.usage = lastAssistantMessage.extraInfo.usage || message.extraInfo.usage;
}
}
}
} else {
renderMessages.value.push({
role: 'user',
content: message.content,
extraInfo: message.extraInfo
});
}
} else if (message.role === 'assistant') { } else if (message.role === 'assistant') {
if (message.tool_calls) { if (message.tool_calls) {
messages.push({ renderMessages.value.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([]),
@ -108,16 +158,46 @@ const renderMessages = computed(() => {
} }
}); });
} else { } else {
messages.push({ if (xmls.length > 0 && message.extraInfo.enableXmlWrapper) {
role: 'assistant/content', // xml xml xml
content: message.content, const toolCalls = [];
extraInfo: message.extraInfo for (const xml of xmls) {
}); const xmlToolCall = await getToolCallFromXmlString(xml);
if (xmlToolCall) {
toolCalls.push(
toNormaliseToolcall(xmlToolCall, indexAdapter)
);
}
}
const renderAssistantMessage = message.content.replace(/```xml[\s\S]*?```/g, '');
console.log(toolCalls);
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 = messages[messages.length - 1]; const lastAssistantMessage = renderMessages.value[renderMessages.value.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;
@ -133,10 +213,9 @@ const renderMessages = computed(() => {
} }
} }
} }
return messages;
}); });
const isLoading = ref(false); const isLoading = ref(false);
const streamingContent = ref(''); const streamingContent = ref('');
@ -232,14 +311,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;
} }
@ -285,7 +364,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;
@ -340,9 +419,12 @@ watch(streamingToolCalls, () => {
@keyframes spin { @keyframes spin {
0% { transform: rotate(0deg); } 0% {
100% { transform: rotate(360deg); } transform: rotate(0deg);
}
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 正在使用工具 Agent {{ t('using-tool') }}
<span class="tool-loading iconfont icon-double-loading"> <span class="tool-loading iconfont icon-double-loading">
</span> </span>
</span> </span>

View File

@ -7,6 +7,12 @@
<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>
@ -17,7 +23,8 @@
<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>
<span>{{ tool.description || '' }}</span> <br>
<span class="tool-description">{{ tool.description || '' }}</span>
</div> </div>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -27,7 +34,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, defineProps, ref, type Reactive } from 'vue'; import { onMounted, defineProps, type Reactive } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import type { ToolStorage } from './tools'; import type { ToolStorage } from './tools';
import { tabs } from '../panel'; import { tabs } from '../panel';
@ -99,13 +106,12 @@ 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);
@ -122,18 +128,35 @@ onMounted(async () => {
} }
.tool-list-container>.item>span:first-child { .tool-list-container>.item>span:first-child {
max-width: 200px; min-width: 120px;
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: .5em;
height: fit-content;
font-size: 10px;
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="dark" placement="right" <el-tooltip class="extra-connect-container" effect="light" 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="dark" placement="right" <el-tooltip class="extra-connect-container" effect="light" 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"> <el-tooltip :content="t(item.ident)" placement="right" effect="light">
<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

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

View File

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

View File

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

View File

@ -159,20 +159,6 @@
"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",
@ -185,5 +171,10 @@
"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"
} }

View File

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

View File

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

View File

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

View File

@ -171,5 +171,10 @@
"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": "正在使用工具"
} }

View File

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

View File

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

View File

@ -29,7 +29,7 @@ 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');
} }
@ -197,7 +197,7 @@ 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>();
} }
@ -227,7 +227,7 @@ export class McpClient {
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>();
} }
@ -252,7 +252,7 @@ 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>();
} }
@ -276,7 +276,7 @@ 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>();
@ -455,19 +455,60 @@ 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
) { } ) {
this.addConnectRefreshListener();
}
/** /**
* @description * @description
@ -511,6 +552,43 @@ 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;
}
public addConnectRefreshListener() {
// 创建对于 connect/refresh 的监听
if (!this.connectrefreshListener) {
const bridge = useMessageBridge();
this.connectrefreshListener = bridge.addCommandListener('connect/refresh', async (message) => {
const { code, msg } = message;
if (code === 200) {
// 查找目标客户端
const clientIndex = this.findClientIndexByUuid(msg.uuid);
if (clientIndex > -1) {
// 刷新该客户端的所有资源
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) {
@ -525,7 +603,7 @@ class McpClientAdapter {
} }
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
}); });

View File

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

View File

@ -237,13 +237,22 @@ 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', {
apiKey,
baseURL,
proxyServer
});
// OpenRouter
let result;
if (llm.isDynamic && llm.id === 'openrouter') {
result = await bridge.commandRequest('llm/models/openrouter', {});
} else {
result = await bridge.commandRequest('llm/models', {
apiKey,
baseURL,
proxyServer
});
}
const { code, msg } = result;
const isGemini = baseURL.includes('googleapis'); const 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
@ -257,9 +266,27 @@ 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,18 +4,42 @@
<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: 160px;"> <div style="width: 240px;">
<el-select v-if="llms[llmManager.currentModelIndex]" <el-select v-if="llms[llmManager.currentModelIndex]"
name="language-setting" name="model-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>
@ -46,7 +70,7 @@
<script setup lang="ts"> <script setup lang="ts">
/* eslint-disable */ /* eslint-disable */
import { defineComponent } from 'vue'; import { defineComponent, computed, ref, watch } 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';
@ -57,6 +81,29 @@ 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({
@ -71,7 +118,140 @@ 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> <style scoped>
/* OpenRouter 模型选项样式 */
.openrouter-model-option {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 4px 0;
}
.model-info {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
}
.model-name {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary, #303133);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.model-provider {
font-size: 12px;
color: var(--el-text-color-regular, #606266);
margin-top: 2px;
}
.model-badge {
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
background-color: #f0f9ff;
color: #0369a1;
font-weight: 600;
margin-left: 8px;
white-space: nowrap;
}
/* 普通模型选项样式 */
.regular-model-option {
font-size: 14px;
color: var(--el-text-color-primary, #303133);
font-weight: 500;
}
/* 搜索为空时的样式 */
.search-empty {
text-align: center;
color: var(--el-text-color-secondary, #909399);
font-size: 13px;
padding: 12px 0;
}
/* 下拉框样式优化 */
:global(.openrouter-select-dropdown) {
max-height: 300px !important;
}
:global(.openrouter-select-dropdown .el-select-dropdown__item) {
height: auto !important;
padding: 8px 12px !important;
line-height: normal !important;
}
/* 搜索框样式 */
:global(.el-select .el-input .el-input__wrapper) {
transition: all 0.3s ease;
}
:global(.el-select .el-input.is-focus .el-input__wrapper) {
box-shadow: 0 0 0 1px var(--el-color-primary, #409eff) inset;
}
/* 暗色主题下的文字优化 */
@media (prefers-color-scheme: dark) {
.model-name {
color: #e5eaf3 !important;
}
.model-provider {
color: #a3a6ad !important;
}
.regular-model-option {
color: #e5eaf3 !important;
}
.search-empty {
color: #8b949e !important;
}
}
</style> </style>

View File

@ -22,6 +22,9 @@ 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

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

View File

@ -1,6 +1,6 @@
{ {
"name": "openmcp-sdk", "name": "openmcp-sdk",
"version": "0.0.7", "version": "0.0.8",
"description": "openmcp-sdk", "description": "openmcp-sdk",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"

View File

@ -4,20 +4,13 @@ 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 };
export interface TaskLoopOptions { interface SchemaProperty {
maxEpochs?: number;
maxJsonParseRetry?: number;
adapter?: any;
verbose?: 0 | 1 | 2 | 3;
}
export interface SchemaProperty {
title: string; title: string;
type: string; type: string;
description?: string; description?: string;
} }
export interface InputSchema { interface InputSchema {
type: string; type: string;
properties: Record<string, SchemaProperty>; properties: Record<string, SchemaProperty>;
required?: string[]; required?: string[];
@ -25,7 +18,7 @@ export interface InputSchema {
$defs?: any; $defs?: any;
} }
export interface ToolItem { interface ToolItem {
name: string; name: string;
description: string; description: string;
inputSchema: InputSchema; inputSchema: InputSchema;
@ -33,6 +26,50 @@ export interface ToolItem {
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;
}; };
@ -80,68 +117,132 @@ 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; /**
private handleChunkUsage; * @description make chat data
private doConversation; * @param tabStorage
*/
makeChatData(tabStorage: any): ChatCompletionCreateParamsBase | undefined; makeChatData(tabStorage: any): ChatCompletionCreateParamsBase | undefined;
/**
* @description stop the task loop
*/
abort(): void; abort(): void;
/** /**
* @description error * @description Register a callback function triggered on 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 chat.completion * @description Register a callback function triggered at the beginning of each epoch
* @param handler * @param handler
*/ */
registerOnDone(handler: () => void): void; registerOnDone(handler: () => void): void;
/** /**
* @description epoch * @description Register a callback function triggered at the beginning of each epoch
* @param handler * @param handler
*/ */
registerOnEpoch(handler: () => void): void; registerOnEpoch(handler: () => void): void;
/** /**
* @description toolcall * @description Registers a callback function that is triggered when a tool call is completed. This method allows you to intercept and modify the output of the tool call.
* @param handler * @param handler
*/ */
registerOnToolCalled(handler: (toolCallResult: ToolCallResult) => ToolCallResult): void; registerOnToolCalled(handler: (toolCallResult: ToolCallResult) => ToolCallResult): void;
/** /**
* @description toolcall * @description Register a callback triggered after tool call finishes. You can intercept and modify the output.
* @param handler * @param handler
*/ */
registerOnToolCall(handler: (toolCall: ToolCall) => ToolCall): void; registerOnToolCall(handler: (toolCall: ToolCall) => ToolCall): void;
/** /**
* @description LLM * @description Get current LLM configuration
*/ */
getLlmConfig(): any; getLlmConfig(): any;
/** /**
* @description LLM nodejs * @description Set the current LLM configuration, for Node.js environment
* @param config * @param config
* @example * @example
* setLlmConfig({ * setLlmConfig({
@ -154,11 +255,19 @@ export class TaskLoop {
setLlmConfig(config: any): void; setLlmConfig(config: any): void;
/** /**
* @description epoch * @description Set proxy server
* @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>;
/** /**
@ -168,14 +277,19 @@ export class TaskLoop {
setProxyServer(proxyServer: string): void; setProxyServer(proxyServer: string): void;
/** /**
* @description * @description Get all available tool list
*/ */
listTools(): Promise<ToolItem[]>; listTools(): Promise<ToolItem[]>;
/** /**
* @description DOM * @description Start the loop and asynchronously update the DOM
*/ */
start(tabStorage: any, userMessage: string): Promise<void>; start(tabStorage: any, userMessage: string): Promise<void>;
/**
* @description Create single conversation context
*/
createStorage(settings?: ChatSetting): Promise<ChatStorage>;
} }
export declare const getToolSchema: any; export declare const getToolSchema: any;

37
rollup.tesseract.js Normal file
View File

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

4633
service/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -130,6 +130,21 @@ 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

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

View File

@ -1,11 +1,9 @@
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 { axiosFetch } from "src/hook/axios-fetch.js"; import { fetchOpenRouterModels, getSimplifiedModels } from "../hook/openrouter.js";
export class LlmController { export class LlmController {
@Controller('llm/chat/completions') @Controller('llm/chat/completions')
@ -57,4 +55,66 @@ 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,9 +35,17 @@ export async function streamingChatCompletion(
}); });
// 构建OpenRouter特定的请求头
const defaultHeaders: Record<string, string> = {};
if (baseURL && baseURL.includes('openrouter.ai')) {
defaultHeaders['HTTP-Referer'] = 'https://github.com/openmcp/openmcp-client';
defaultHeaders['X-Title'] = 'OpenMCP Client';
}
const client = new OpenAI({ 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,12 +1,11 @@
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 } from 'path'; 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 path from 'node:path'; import fs from 'fs/promises'; // 使用 Promise API 替代回调
import * as fs from 'node:fs'; import path from 'path';
import { setRunningCWD } from './hook/setting.js';
import axios from 'axios'; import axios from 'axios';
export interface VSCodeMessage { export interface VSCodeMessage {
@ -15,91 +14,83 @@ export interface VSCodeMessage {
callbackId?: string; callbackId?: string;
} }
// 适配 ESM 的 __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 统一路径变量
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;
function refreshConnectionOption(envPath: string) { async function refreshConnectionOption() {
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
}; };
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4)); try {
await fs.writeFile(envPath, JSON.stringify(defaultOption, null, 4), 'utf-8');
return { items: [ defaultOption ] }; return { items: [defaultOption] };
} catch (error) {
logger.error('刷新连接配置失败:', error);
throw error;
}
} }
function acquireConnectionOption() { async function acquireConnectionOption() {
const envPath = path.join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) {
return refreshConnectionOption(envPath);
}
try { try {
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8')); const data = await fs.readFile(envPath, 'utf-8');
const option = JSON.parse(data);
if (!option.items) { if (!option.items || option.items.length === 0) {
return refreshConnectionOption(envPath); return await refreshConnectionOption();
}
if (option.items && option.items.length === 0) {
return refreshConnectionOption(envPath);
} }
// 按照前端的规范,整理成 commandString 样式 // 按照前端的规范,整理成 commandString 样式
option.items = option.items.map((item: any) => { option.items = option.items.map((item: { connectionType: string; commandString: string; command: any; args: any; url: any; }) => {
if (item.connectionType === 'STDIO') { 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 配置文件'); logger.error('读取 .env 配置文件失败:', error);
return refreshConnectionOption(envPath); return await refreshConnectionOption();
} }
} }
function updateConnectionOption(data: any) { async function updateConnectionOption(data: any) {
const envPath = path.join(__dirname, '..', '.env'); try {
const connection = { items: data }; await fs.writeFile(envPath, JSON.stringify({ items: data }, null, 4), 'utf-8');
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4)); } catch (error) {
logger.error('更新连接配置失败:', error);
throw error;
}
} }
const __dirname = dirname(fileURLToPath(import.meta.url)); // 设置运行时路径
const devHome = path.join(__dirname, '..', '..'); import { setRunningCWD } from './hook/setting.js';
setRunningCWD(devHome); setRunningCWD(devHome);
// 启动 WebSocket 服务器
const wss = new WebSocketServer({ port: 8282 }); const wss = new WebSocketServer({ port: 8282 });
console.log('WebSocket 服务器已启动:', '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: {
@ -108,34 +99,33 @@ wss.on('connection', (ws: any) => {
} }
}); });
const option = acquireConnectionOption(); acquireConnectionOption().then(option => {
webview.onDidReceiveMessage(async (message) => {
logger.info(`收到命令: [${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':
const launchResult = { webview.postMessage({
code: 200, command: 'web/launch-signature',
msg: option.items data: {
}; code: 200,
msg: option.items
}
});
break;
webview.postMessage({ case 'web/update-connection-signature':
command: 'web/launch-signature', await updateConnectionOption(data);
data: launchResult break;
});
break; default:
routeMessage(command, data, webview);
case 'web/update-connection-signature': break;
updateConnectionOption(data); }
break; });
}).catch(error => {
default: logger.error('初始化连接配置失败:', error);
routeMessage(command, data, webview);
break;
}
}); });
}); });

View File

@ -0,0 +1,125 @@
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';
// 保留现有 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) => {
// 使用 info 级别记录文件修改
logger.info({
uuid: this.uuid,
size: curr.size,
mtime: new Date(curr.mtime).toLocaleString()
}, 'File modified');
try {
await onchange(this.uuid, this.Options);
this.sendWebviewMessage('connect/refresh', {
code: 200,
msg: {
message: 'refresh connect success',
uuid: this.uuid,
}
});
logger.info({ uuid: this.uuid }, 'Connection refresh successful');
} 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 });
}
}

View File

@ -3,6 +3,7 @@ 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';
@ -14,7 +15,19 @@ 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('[updateClientMap] tools:', tools);
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(' ');
@ -290,6 +303,7 @@ export async function connectService(
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

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

View File

@ -1,12 +1,17 @@
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 path from 'node:path'; import fs from 'fs';
import * as fs from 'node:fs'; import path from 'path';
import { setRunningCWD } from './hook/setting.js'; import { setRunningCWD } from './hook/setting.js';
import { exit } from 'node:process'; import { exit } from 'process';
// 适配 ESM 的 __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export interface VSCodeMessage { export interface VSCodeMessage {
command: string; command: string;
@ -15,22 +20,13 @@ 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 = path.join(__dirname, '..', '..', 'servers'); const serverPath = join(__dirname, '..', '..', 'servers');
const defaultOption = { const defaultOption = {
connectionType: 'STDIO', connectionType: 'STDIO',
commandString: 'mcp run main.py', commandString: 'mcp run main.py',
@ -38,12 +34,11 @@ 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 = path.join(__dirname, '..', '.env'); const envPath = join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) { if (!fs.existsSync(envPath)) {
return refreshConnectionOption(envPath); return refreshConnectionOption(envPath);
@ -52,11 +47,7 @@ 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) { if (!option.items || option.items.length === 0) {
return refreshConnectionOption(envPath);
}
if (option.items && option.items.length === 0) {
return refreshConnectionOption(envPath); return refreshConnectionOption(envPath);
} }
@ -67,7 +58,6 @@ function acquireConnectionOption() {
} else { } else {
item.url = item.url; item.url = item.url;
} }
return item; return item;
}); });
@ -79,20 +69,22 @@ function acquireConnectionOption() {
} }
} }
if (!fs.existsSync(path.join(__dirname, '..', '.env.website.local'))) { // 验证 .env.website.local 存在性
const localEnvPath = join(__dirname, '..', '.env.website.local');
if (!fs.existsSync(localEnvPath)) {
console.log('.env.website.local 不存在!'); 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 = path.join(__dirname, '..', '.env'); const envPath = join(__dirname, '..', '.env');
const connection = { items: data }; fs.writeFileSync(envPath, JSON.stringify({ items: data }, null, 4));
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
} }
const devHome = path.join(__dirname, '..', '..'); const devHome = join(__dirname, '..', '..');
setRunningCWD(devHome); setRunningCWD(devHome);
function verifyToken(url: string) { function verifyToken(url: string) {
@ -104,30 +96,25 @@ function verifyToken(url: string) {
} }
} }
const wss = new WebSocketServer( const wss = new WebSocketServer({
{ port: 8282,
port: 8282, verifyClient: (info, callback) => {
verifyClient: (info, callback) => { const url = info.req.url || '';
console.log(info.req.url); const ok = verifyToken(url);
const ok = verifyToken(info.req.url || '');
if (!ok) { if (!ok) {
callback(false, 401, 'Unauthorized: Invalid token'); callback(false, 401, 'Unauthorized: Invalid token');
} else { } else {
callback(true); // 允许连接 callback(true);
}
} }
}, }
); });
console.log('listen on ws://localhost:8282'); console.log('listen on ws://localhost:8282');
wss.on('connection', (ws: any) => { wss.on('connection', (ws) => {
// 仿造 webview 进行统一接口访问
const webview = new VSCodeWebViewLike(ws); const webview = new VSCodeWebViewLike(ws);
// 先发送成功建立的消息
webview.postMessage({ webview.postMessage({
command: 'hello', command: 'hello',
data: { data: {
@ -138,23 +125,19 @@ wss.on('connection', (ws: any) => {
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: launchResult data: {
code: 200,
msg: option.items
}
}); });
break; break;
case 'web/update-connection-signature': case 'web/update-connection-signature':

View File

@ -53,9 +53,28 @@ 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);

View File

@ -1,4 +1,4 @@
const path = require('path'); import path from '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 '.'; import { registerCommands, registerTreeDataProviders } from './index.js';
import { HelpProvider } from '../sidebar/help.controller'; import { HelpProvider } from '../sidebar/help.controller.js';
import { McpWorkspaceConnectProvider } from '../sidebar/workspace.controller'; import { McpWorkspaceConnectProvider } from '../sidebar/workspace.controller.js';
import { McpInstalledConnectProvider } from '../sidebar/installed.controller'; import { McpInstalledConnectProvider } from '../sidebar/installed.controller.js';
import { WebviewController } from '../webview/webview.controller'; import { WebviewController } from '../webview/webview.controller.js';
import { HookController } from '../hook/hook.controller'; import { HookController } from '../hook/hook.controller.js';
export const InstallModules = [ export const InstallModules = [
McpWorkspaceConnectProvider, McpWorkspaceConnectProvider,

View File

@ -1,4 +1,4 @@
import { CommandHandlerDescriptor, IRegisterCommandItem, IRegisterTreeDataProviderItem, TreeDataProviderConstructor } from "./index.dto"; import { CommandHandlerDescriptor, IRegisterCommandItem, IRegisterTreeDataProviderItem, TreeDataProviderConstructor } from "./index.dto.js";
export const registerCommands = new Array<[string, IRegisterCommandItem]>(); export const 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'; import { setRunningCWD, setVscodeWorkspace } from '../openmcp-sdk/service/index.js';
import { launch } from './common/entry'; import { launch } from './common/entry.js';
import { initialiseI18n } from './i18n'; import { initialiseI18n } from './i18n/index.js';
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
console.log('activate openmcp'); console.log('activate openmcp');
@ -10,11 +10,9 @@ 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,7 +95,13 @@ 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;
} }
@ -110,6 +116,9 @@ 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);
@ -296,6 +305,7 @@ 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, '/');
} }

View File

@ -1,4 +1,4 @@
import { RegisterCommand } from "../common"; import { RegisterCommand } from "../common/index.js";
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'; import { McpOptions } from '../global.js';
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'; import { SidebarItem } from './common.js';
import { RegisterTreeDataProvider } from '../common'; import { RegisterTreeDataProvider } from '../common/index.js';
import { t } from '../i18n'; import { t } from '../i18n/index.js';
@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'; import { RegisterCommand, RegisterTreeDataProvider } from '../common/index.js';
import { ConnectionViewItem } from './common'; import { ConnectionViewItem } from './common.js';
import { getConnectionConfig, getInstalledConnectionConfigPath, saveConnectionConfig } from '../global'; import { getConnectionConfig, getInstalledConnectionConfigPath, saveConnectionConfig } from '../global.js';
import { acquireInstalledConnection, deleteInstalledConnection } from './installed.service'; import { acquireInstalledConnection, deleteInstalledConnection } from './installed.service.js';
import { revealOpenMcpWebviewPanel } from '../webview/webview.service'; import { revealOpenMcpWebviewPanel } from '../webview/webview.service.js';
@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"; import { getConnectionConfig, panels, saveConnectionConfig, getFirstValidPathFromCommand, McpOptions } from "../global.js";
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"; import { t } from "../i18n/index.js";
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'; import { RegisterCommand, RegisterTreeDataProvider } from '../common/index.js';
import { getWorkspaceConnectionConfig, getWorkspaceConnectionConfigPath, getWorkspacePath, saveWorkspaceConnectionConfig } from '../global'; import { getWorkspaceConnectionConfig, getWorkspaceConnectionConfigPath, getWorkspacePath, saveWorkspaceConnectionConfig } from '../global.js';
import { ConnectionViewItem } from './common'; import { ConnectionViewItem } from './common.js';
import { revealOpenMcpWebviewPanel } from '../webview/webview.service'; import { revealOpenMcpWebviewPanel } from '../webview/webview.service.js';
import { acquireUserCustomConnection, deleteUserConnection } from './workspace.service'; import { acquireUserCustomConnection, deleteUserConnection } from './workspace.service.js';
@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(); this._onDidChangeTreeData.fire(undefined);
} }
@RegisterCommand('addConnection') @RegisterCommand('addConnection')

View File

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

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

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

View File

@ -0,0 +1,33 @@
import * as assert from 'assert';
import * as vscode from 'vscode';
suite('测试基础插件激活和命令注册', () => {
vscode.window.showInformationMessage('Start base tests.');
test('Sample 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'), '命令未注册');
});
test('等待 10 秒以便观察窗口', async function () {
this.timeout(15000);
await new Promise(resolve => setTimeout(resolve, 10000));
});
//
});

View File

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

View File

@ -1,7 +1,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { RegisterCommand } from "../common"; import { RegisterCommand } from "../common/index.js";
import { getDefaultLanunchSignature, getWorkspacePath, revealOpenMcpWebviewPanel } from './webview.service'; import { getDefaultLanunchSignature, getWorkspacePath, revealOpenMcpWebviewPanel } from './webview.service.js';
import { getWorkspaceConnectionConfigItemByPath } from '../global'; import { getWorkspaceConnectionConfigItemByPath } from '../global.js';
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, McpOptions, panels, updateInstalledConnectionConfig, updateWorkspaceConnectionConfig } from '../global'; import { ConnectionType, McpOptions, panels, updateInstalledConnectionConfig, updateWorkspaceConnectionConfig } from '../global.js';
import { routeMessage } from '../../openmcp-sdk/service'; import { routeMessage } from '../../openmcp-sdk/service/index.js';
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 => { option.forEach((item: McpOptions) => {
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;

View File

@ -1,14 +1,19 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020",
"module": "commonjs", "module": "commonjs",
"target": "es6", "moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"outDir": "./dist", "outDir": "./dist",
"rootDir": "./src", "rootDir": "./src",
"strict": true, "strict": true,
"esModuleInterop": true,
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"importHelpers": false,
"noEmitHelpers": false,
"experimentalDecorators": true, "experimentalDecorators": true,
"sourceMap": true,
// 访 openmcp-sdk // 访 openmcp-sdk
"paths": { "paths": {
"@openmcp-sdk/*": [ "@openmcp-sdk/*": [

View File

@ -1,50 +0,0 @@
//@ts-check
'use strict';
const path = require('path');
//@ts-check
/** @typedef {import('webpack').Configuration} WebpackConfig **/
/** @type WebpackConfig */
const extensionConfig = {
target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
output: {
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
path: path.resolve(__dirname, 'dist'),
filename: 'extension.js',
libraryTarget: 'commonjs2'
},
externals: {
vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
// modules added here also need to be added in the .vscodeignore file
},
resolve: {
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
extensions: ['.ts', '.js'],
fallback: {
bufferutil: false,
'utf-8-validate': false
}
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
}
]
},
infrastructureLogging: {
level: "log", // enables logging required for problem matchers
},
}; module.exports = [extensionConfig];

View File

@ -1,80 +0,0 @@
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
mode: 'development', // 设置为 development 模式
devtool: 'source-map', // 生成 source map 以便调试
entry: './renderer/src/components/main-panel/chat/core/task-loop.ts',
output: {
path: path.resolve(__dirname, '../openmcp-sdk'),
filename: 'task-loop.js',
libraryTarget: 'commonjs2'
},
target: 'node',
resolve: {
extensions: ['.ts', '.js'],
alias: {
'@': path.resolve(__dirname, '../renderer/src'), // 修正路径别名
},
},
module: {
rules: [
{
test: /\.ts$/,
use: {
loader: 'ts-loader',
options: {
configFile: path.resolve(__dirname, '../tsconfig.json') // 指定 tsconfig.json 路径
}
},
exclude: /node_modules/,
},
{
test: /\.vue$/,
use: {
loader: 'null-loader'
}
}
],
},
optimization: {
minimize: true, // Enable code compression
minimizer: [
new TerserPlugin({
extractComments: false, // Disable extracting license files
terserOptions: {
compress: {
drop_console: true, // Remove all console.* calls
},
},
}),
],
},
plugins: [
new webpack.DefinePlugin({
window: {
nodejs: true,
navigator: {
userAgent: 2
},
performance: {
now: () => Date.now()
}
}
}),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, '../resources/openmcp-sdk-release'),
to: path.resolve(__dirname, '../openmcp-sdk')
}
]
})
],
externals: {
vue: 'vue', // 不打包 vue 库
'element-plus': './tools.js'
},
};

View File

@ -1,43 +0,0 @@
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
entry: './node_modules/tesseract.js/src/worker-script/node/index.js',
output: {
path: path.resolve(__dirname, '..', 'resources', 'ocr'),
filename: 'worker.js',
libraryTarget: 'commonjs2'
},
resolve: {
fallback: {
bufferutil: false,
'utf-8-validate': false
}
},
mode: 'production',
target: 'node',
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, '..', 'node_modules', 'tesseract.js-core', 'tesseract*'),
to: path.resolve(__dirname, '..', 'resources', 'ocr', '[name][ext]'),
},
],
}),
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
};