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
- 针对 0.1.5 无法在 Windows 启动的紧急修复。
- 修复环境变量中添加 token 失效的问题。
- 优化工具展示的页面布局。
## [main] 0.1.5
- 修复 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` |
| `all` | System prompt management module | `Iteration` | 100% | `Done` |
| `service` | Tool-wise logging system | `MVP` | 0% | `P1` |
| `service` | MCP security checks (prevent prompt injection, etc.) | `MVP` | 0% | `P1` |
| `service` | Built-in OCR for character recognition | `Iteration` | 100% | `Done` |
## Project Concept
@ -134,9 +135,8 @@ npm run setup
Start dev server:
```bash
npm run dev
npm run serve
```
Port usage: 8282 (renderer) + 8081 (service)
### Extension Development
@ -151,4 +151,11 @@ Build for deployment:
```bash
npm run build
```
build vscode extension:
```bash
npm run build:plugin
```
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",
"displayName": "OpenMCP",
"description": "An all in one MCP Client/TestTool",
"version": "0.1.6",
"version": "0.1.7",
"publisher": "kirigaya",
"author": {
"name": "kirigaya",
@ -19,7 +19,7 @@
"Other"
],
"activationEvents": [],
"main": "./dist/extension.js",
"main": "./dist/extension.cjs.js",
"icon": "icons/openmcp.png",
"contributes": {
"configuration": {
@ -222,39 +222,58 @@
"scripts": {
"setup": "npm i && npm run prepare:ocr",
"serve": "turbo serve",
"build": "turbo build",
"build:electron": "turbo build --filter=@openmcp/electron",
"build:all": "turbo build",
"vscode:prepublish": "webpack --mode production",
"build": "turbo build && tsc -p ./ && node esbuild.config.js",
"build:plugin": "npm run build && tsc && vsce package",
"vscode:prepublish": "node esbuild.config.js",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"pretest": "npm run build",
"lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js",
"prepare:ocr": "webpack --config webpack/webpack.tesseract.js",
"build:task-loop": "npx vite build --config webpack/vite.config.task-loop.mjs && node renderer/scripts/task-loop.build.mjs"
"test": "node ./dist/test/e2e/runTest.js",
"prepare:ocr": "rollup -c rollup.tesseract.js --bundleConfigAsCjs",
"build:task-loop": "npx vite build --config renderer/vite.config.task-loop.mjs && node renderer/scripts/task-loop.build.mjs"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.1",
"@seald-io/nedb": "^4.1.1",
"@types/glob": "^7.2.0",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"axios": "^1.9.0",
"bson": "^6.8.0",
"form-data-encoder": "^1.7.2",
"formdata-node": "^4.3.2",
"glob": "^7.2.3",
"https-proxy-agent": "^7.0.6",
"mocha": "^10.8.2",
"openai": "^5.0.1",
"pako": "^2.1.0",
"pkce-challenge": "^5.0.0",
"tesseract.js": "^6.0.1",
"tslib": "^2.8.1",
"uuid": "^11.1.0",
"ws": "^8.18.1"
},
"devDependencies": {
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.5",
"@rollup/plugin-inject": "^5.0.5",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"@types/node": "^22.15.29",
"@types/pako": "^2.0.3",
"@types/showdown": "^2.0.0",
"@types/vscode": "^1.72.0",
"@vscode/test-cli": "^0.0.11",
"@vscode/test-electron": "^2.5.2",
"copy-webpack-plugin": "^13.0.0",
"esbuild": "^0.25.5",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"null-loader": "^4.0.1",
"rollup": "^4.43.0",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-visualizer": "^6.0.1",
"ts-loader": "^9.5.1",
"turbo": "^2.5.3",
@ -268,4 +287,4 @@
"webpack-cli": "^5.1.4"
},
"packageManager": "npm@10.0.0"
}
}

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",
"vue": "^3.5.13",
"vue-i18n": "^11.1.0",
"vue-router": "^4.5.0"
"vue-router": "^4.5.0",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@babel/core": "^7.27.1",
@ -47,6 +48,7 @@
"@types/markdown-it": "^14.1.2",
"@types/node": "^22.14.0",
"@types/prismjs": "^1.26.5",
"@types/xml2js": "^0.4.14",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/babel-plugin-jsx": "^1.4.0",
"@vue/devtools-core": "^7.7.6",

View File

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

Binary file not shown.

View File

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

View File

@ -227,4 +227,5 @@ a {
.ͼo,
.ͼo .cm-gutters {
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(/const chalk = require\("chalk"\);/g, 'const chalk = require("chalk").default;');
// content = content.replace(/const chalk = require\("chalk"\);/g, 'const chalk = require("chalk").default;');
// Replace define_window_default$number.performance with performance
content = content.replace(/define_window_default\$\d+\.performance/g, 'performance');

View File

@ -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 { OpenAI } from 'openai';
@ -16,6 +16,7 @@ export enum MessageState {
Success = 'success',
ParseJsonError = 'parse json error',
NoToolFunction = 'no tool function',
InvalidXml = 'invalid xml',
}
export interface IExtraInfo {
@ -23,6 +24,7 @@ export interface IExtraInfo {
state: MessageState,
serverName: string,
usage?: ChatCompletionChunk['usage'];
enableXmlWrapper: boolean;
[key: string]: any;
}
@ -52,7 +54,7 @@ export interface EnableToolItem {
name: string;
description: string;
enabled: boolean;
inputSchema: any;
inputSchema: InputSchema;
}
export interface ChatSetting {
@ -63,6 +65,7 @@ export interface ChatSetting {
enableWebSearch: boolean
contextLength: number
parallelToolCalls: boolean
enableXmlWrapper: boolean
}
export interface ChatStorage {
@ -107,7 +110,7 @@ export interface IToolRenderMessage {
export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage;
export function getToolSchema(enableTools: EnableToolItem[]) {
export function getToolSchema(enableTools: EnableToolItem[]): any[] {
const toolsSchema = [];
for (let i = 0; i < enableTools.length; i++) {
const enableTool = enableTools[i];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<template>
<el-tooltip :content="props.messages[0].content.text" placement="top">
<el-tooltip :content="props.messages[0].content.text" placement="top" effect="light">
<span class="chat-prompt-item" contenteditable="false">
<span class="iconfont icon-chat"></span>
<span class="real-text">{{ props.messages[0].content.text }}</span>

View File

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

View File

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

View File

@ -1,6 +1,6 @@
/* eslint-disable */
import { ref, type Ref } from "vue";
import { type ToolCall, type ChatStorage, getToolSchema, MessageState } from "../chat-box/chat";
import { type ToolCall, type ChatStorage, getToolSchema, MessageState, type ChatMessage, type ChatSetting, type EnableToolItem } from "../chat-box/chat";
import { useMessageBridge, MessageBridge, createMessageBridge } from "@/api/message-bridge";
import type { OpenAI } from 'openai';
import { llmManager, llms, type BasicLlmDescription } from "@/views/setting/llm";
@ -13,9 +13,15 @@ import { mcpSetting } from "@/hook/mcp";
import { mcpClientAdapter } from "@/views/connect/core";
import type { ToolItem } from "@/hook/type";
import chalk from 'chalk';
import { getXmlWrapperPrompt, getToolCallFromXmlString, getXmlsFromString, handleXmlWrapperToolcall, toNormaliseToolcall, getXmlResultPrompt } from "./xml-wrapper";
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string, proxyServer?: string };
export interface TaskLoopChatOption {
id?: string
proxyServer?: string
enableXmlWrapper?: boolean
}
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
export interface TaskLoopOptions {
maxEpochs?: number;
maxJsonParseRetry?: number;
@ -85,14 +91,25 @@ export class TaskLoop {
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 || '';
if (content) {
this.streamingContent.value += content;
}
}
private handleChunkDeltaToolCalls(chunk: ChatCompletionChunk, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) {
/**
* @description streaming chunk tool_calls
* @param chunk
* @param chatData
* @param toolcallIndexAdapter
*/
private handleChunkDeltaToolCalls(chunk: ChatCompletionChunk, chatData: ChatCompletionCreateParamsBase, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) {
const toolCall = chunk.choices[0]?.delta?.tool_calls?.[0];
if (toolCall) {
@ -148,9 +165,15 @@ export class TaskLoop {
const { chunk } = data.msg as { chunk: ChatCompletionChunk };
// 处理增量的 content 和 tool_calls
this.handleChunkDeltaContent(chunk);
this.handleChunkDeltaToolCalls(chunk, toolcallIndexAdapter);
this.handleChunkUsage(chunk);
if (chatData.enableXmlWrapper) {
this.handleChunkDeltaContent(chunk, chatData);
// no tool call in enableXmlWrapper
this.handleChunkUsage(chunk);
} else {
this.handleChunkDeltaContent(chunk, chatData);
this.handleChunkDeltaToolCalls(chunk, chatData, toolcallIndexAdapter);
this.handleChunkUsage(chunk);
}
this.consumeChunks(chunk);
}, { once: false });
@ -207,23 +230,35 @@ export class TaskLoop {
const model = this.getLlmConfig().userModel;
const temperature = tabStorage.settings.temperature;
const tools = getToolSchema(tabStorage.settings.enableTools);
const parallelToolCalls = tabStorage.settings.parallelToolCalls;
const proxyServer = mcpSetting.proxyServer || '';
// 如果是 xml 模式,则 tools 为空
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
const tools = enableXmlWrapper ? []: getToolSchema(tabStorage.settings.enableTools);
const userMessages = [];
// 尝试获取 system prompt在 api 模式下systemPrompt 就是目标提词
// 但是在 UI 模式下systemPrompt 只是一个 index需要从后端数据库中获取真实 prompt
if (tabStorage.settings.systemPrompt) {
const prompt = getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
userMessages.push({
role: 'system',
content: prompt
});
let 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, 则删除最早的消息
const loadMessages = tabStorage.messages.slice(- tabStorage.settings.contextLength);
userMessages.push(...loadMessages);
@ -240,7 +275,8 @@ export class TaskLoop {
tools,
parallelToolCalls,
messages: userMessages,
proxyServer
proxyServer,
enableXmlWrapper,
} as ChatCompletionCreateParamsBase;
return chatData;
@ -461,6 +497,7 @@ export class TaskLoop {
// 等待连接完成
await this.nodejsStatus.connectionFut;
}
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
// 添加目前的消息
tabStorage.messages.push({
@ -469,7 +506,8 @@ export class TaskLoop {
extraInfo: {
created: Date.now(),
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!;
const llm = this.getLlmConfig();
const toolcallIndexAdapter = getToolCallIndexAdapter(llm);
const toolcallIndexAdapter = getToolCallIndexAdapter(llm, chatData);
// 发送请求
const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter);
@ -513,7 +551,8 @@ export class TaskLoop {
extraInfo: {
created: Date.now(),
state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown'
serverName: this.getLlmConfig().id || 'unknown',
enableXmlWrapper
}
});
@ -550,7 +589,8 @@ export class TaskLoop {
created: Date.now(),
state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown',
usage: undefined
usage: undefined,
enableXmlWrapper
}
});
break;
@ -565,7 +605,8 @@ export class TaskLoop {
created: Date.now(),
state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage
usage: this.completionUsage,
enableXmlWrapper
}
});
} else if (toolCallResult.state === MessageState.ToolCall) {
@ -579,7 +620,8 @@ export class TaskLoop {
created: Date.now(),
state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage
usage: this.completionUsage,
enableXmlWrapper
}
});
}
@ -593,10 +635,96 @@ export class TaskLoop {
created: Date.now(),
state: MessageState.Success,
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 {
// 一些提示
@ -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>
<div class="chat-container" :ref="el => chatContainerRef = el">
<el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll" v-if="renderMessages.length > 0 || isLoading">
<el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll"
v-if="renderMessages.length > 0 || isLoading">
<div class="message-list" :ref="el => messageListRef = el">
<div v-for="(message, index) in renderMessages" :key="index"
:class="['message-item', message.role.split('/')[0], message.role.split('/')[1]]"
>
:class="['message-item', message.role.split('/')[0], message.role.split('/')[1]]">
<div class="message-avatar" v-if="message.role === 'assistant/content'">
<span class="iconfont icon-robot"></span>
</div>
@ -23,10 +23,8 @@
<!-- 助手调用的工具部分 -->
<div class="message-content" v-else-if="message.role === 'assistant/tool_calls'">
<Message.Toolcall
:message="message" :tab-id="props.tabId"
@update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value"
/>
<Message.Toolcall :message="message" :tab-id="props.tabId"
@update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value" />
</div>
</div>
@ -47,23 +45,22 @@
</div>
</div>
<ChatBox
:ref="el => footerRef = el"
:tab-id="props.tabId"
/>
<ChatBox :ref="el => footerRef = el" :tab-id="props.tabId" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide } from 'vue';
import { ref, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide, watchEffect } from 'vue';
import { useI18n } from 'vue-i18n';
import { ElMessage, type ScrollbarInstance } from 'element-plus';
import { type ScrollbarInstance } from 'element-plus';
import { tabs } from '../panel';
import type { ChatMessage, ChatStorage, IRenderMessage, ToolCall } from './chat-box/chat';
import { MessageState } from './chat-box/chat';
import * as Message from './message';
import ChatBox from './chat-box/index.vue';
import { getToolCallFromXmlString, getToolResultFromXmlString, getXmlsFromString, toNormaliseToolcall } from './core/xml-wrapper';
import { getIdAsIndexAdapter } from './core/handle-tool-calls';
defineComponent({ name: 'chat' });
@ -85,18 +82,71 @@ if (!tabStorage.messages) {
tabStorage.messages = [] as ChatMessage[];
}
const renderMessages = computed(() => {
const messages: IRenderMessage[] = [];
function getXmlToolCalls(message: ChatMessage) {
if (message.role !== 'assistant' && message.role !== 'user') {
return [];
}
const enableXmlTools = message.extraInfo?.enableXmlWrapper ?? false;
if (!enableXmlTools) {
return [];
}
const xmls = getXmlsFromString(message.content);
return xmls || [];
}
const renderMessages = ref<IRenderMessage[]>([]);
watchEffect(async () => {
renderMessages.value = [];
for (const message of tabStorage.messages) {
const indexAdapter = getIdAsIndexAdapter();
const xmls = getXmlToolCalls(message);
if (message.role === 'user') {
messages.push({
role: 'user',
content: message.content,
extraInfo: message.extraInfo
});
if (xmls.length > 0 && message.extraInfo.enableXmlWrapper) {
// xml xml xml
// assistant/tool_calls
const lastAssistantMessage = renderMessages.value[renderMessages.value.length - 1];
if (lastAssistantMessage.role === 'assistant/tool_calls') {
const toolCallResultXmls = getXmlsFromString(message.content);
for (const xml of toolCallResultXmls) {
const toolResult = await getToolResultFromXmlString(xml);
if (toolResult) {
const index = indexAdapter(toolResult.callId);
lastAssistantMessage.toolResults[index] = toolResult.toolcallContent;
if (lastAssistantMessage.extraInfo.state === MessageState.Unknown) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
} else if (lastAssistantMessage.extraInfo.state === MessageState.Success
|| message.extraInfo.state !== MessageState.Success
) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
}
lastAssistantMessage.extraInfo.usage = lastAssistantMessage.extraInfo.usage || message.extraInfo.usage;
}
}
}
} else {
renderMessages.value.push({
role: 'user',
content: message.content,
extraInfo: message.extraInfo
});
}
} else if (message.role === 'assistant') {
if (message.tool_calls) {
messages.push({
renderMessages.value.push({
role: 'assistant/tool_calls',
content: message.content,
toolResults: Array(message.tool_calls.length).fill([]),
@ -108,16 +158,46 @@ const renderMessages = computed(() => {
}
});
} else {
messages.push({
role: 'assistant/content',
content: message.content,
extraInfo: message.extraInfo
});
if (xmls.length > 0 && message.extraInfo.enableXmlWrapper) {
// xml xml xml
const toolCalls = [];
for (const xml of xmls) {
const xmlToolCall = await getToolCallFromXmlString(xml);
if (xmlToolCall) {
toolCalls.push(
toNormaliseToolcall(xmlToolCall, indexAdapter)
);
}
}
const renderAssistantMessage = message.content.replace(/```xml[\s\S]*?```/g, '');
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') {
// assistant
const lastAssistantMessage = messages[messages.length - 1];
const lastAssistantMessage = renderMessages.value[renderMessages.value.length - 1];
if (lastAssistantMessage.role === 'assistant/tool_calls') {
lastAssistantMessage.toolResults[message.index] = message.content;
@ -128,15 +208,14 @@ const renderMessages = computed(() => {
) {
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
}
lastAssistantMessage.extraInfo.usage = lastAssistantMessage.extraInfo.usage || message.extraInfo.usage;
}
}
}
return messages;
});
const isLoading = ref(false);
const streamingContent = ref('');
@ -232,14 +311,14 @@ watch(streamingToolCalls, () => {
padding-top: 70px;
}
.chat-openmcp-icon > div {
.chat-openmcp-icon>div {
display: flex;
flex-direction: column;
align-items: left;
font-size: 28px;
}
.chat-openmcp-icon > div > span {
.chat-openmcp-icon>div>span {
margin-bottom: 23px;
}
@ -285,7 +364,7 @@ watch(streamingToolCalls, () => {
width: 100%;
}
.user .message-text > span {
.user .message-text>span {
border-radius: .9em;
background-color: var(--main-light-color);
padding: 10px 15px;
@ -340,9 +419,12 @@ watch(streamingToolCalls, () => {
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -171,5 +171,10 @@
"join-discussion": "加入讨论群",
"comment-for-us": "为我们撰写评价!",
"openmcp-developed-by": "OpenMCP Client {version} 由 {author} 开发",
"error-parse-json": "JSON 解析错误:"
"error-parse-json": "JSON 解析错误:",
"enable-xml-wrapper": "开启 XML 指令包裹",
"tool-manage": "工具管理",
"enable-all-tools": "激活所有工具",
"disable-all-tools": "禁用所有工具",
"using-tool": "正在使用工具"
}

View File

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

View File

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

View File

@ -29,12 +29,12 @@ export const connectionSelectDataViewOption: ConnectionTypeOptionItem[] = [
function prettifyMapKeys(keys: MapIterator<string>) {
const result: string[] = [];
for (const key of keys) {
result.push('+ ' +key);
result.push('+ ' + key);
}
return result.join('\n');
}
function _processSchemaNode(node: any, defs: Record<string, any> = {}): any {
function _processSchemaNode(node: any, defs: Record<string, any> = {}): any {
// Handle $ref references
if ('$ref' in node) {
const refPath = node['$ref'];
@ -195,18 +195,18 @@ export class McpClient {
}
const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<ToolsListResponse>('tools/list', { clientId: this.clientId });
if (code!== 200) {
if (code !== 200) {
return new Map<string, ToolItem>();
}
this.tools = new Map<string, ToolItem>();
msg.tools.forEach(tool => {
const standardSchema = _processSchemaNode(tool.inputSchema, tool.inputSchema.$defs || {});
tool.inputSchema = standardSchema;
this.tools!.set(tool.name, tool);
});
@ -224,13 +224,13 @@ export class McpClient {
}
const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<PromptsListResponse>('prompts/list', { clientId: this.clientId });
if (code!== 200) {
const { code, msg } = await bridge.commandRequest<PromptsListResponse>('prompts/list', { clientId: this.clientId });
if (code !== 200) {
return new Map<string, PromptTemplate>();
}
this.promptTemplates = new Map<string, PromptTemplate>();
msg.prompts.forEach(template => {
this.promptTemplates!.set(template.name, template);
@ -250,9 +250,9 @@ export class McpClient {
}
const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<ResourcesListResponse>('resources/list', { clientId: this.clientId });
if (code!== 200) {
if (code !== 200) {
return new Map<string, Resources>();
}
@ -274,9 +274,9 @@ export class McpClient {
}
const bridge = useMessageBridge();
const { code, msg } = await bridge.commandRequest<ResourceTemplatesListResponse>('resources/templates/list', { clientId: this.clientId });
if (code!== 200) {
if (code !== 200) {
return new Map();
}
this.resourceTemplates = new Map<string, ResourceTemplate>();
@ -367,7 +367,7 @@ export class McpClient {
title: `${this.name}'s tools loaded (${tools.size})`,
message: prettifyMapKeys(tools.keys())
});
const prompts = await this.getPromptTemplates({ cache: false });
this.connectionResult.logString.push({
type: 'info',
@ -381,7 +381,7 @@ export class McpClient {
title: `${this.name}'s resources loaded (${resources.size})`,
message: prettifyMapKeys(resources.keys())
});
const resourceTemplates = await this.getResourceTemplates({ cache: false });
this.connectionResult.logString.push({
type: 'info',
@ -440,7 +440,7 @@ export class McpClient {
});
if (code === 200) {
this.connectionResult.logString.push({
type: 'info',
title: t('preset-env-sync.success')
@ -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 {
public clients: Reactive<McpClient[]> = [];
public currentClientIndex: number = 0;
public refreshSignal = reactive({ value: 0 });
private defaultClient: McpClient = new McpClient();
public connectLogListenerCancel: (() => void) | null = null;
public connectrefreshListener: (() => void) | null = null;
constructor(
public platform: string
) { }
) {
this.addConnectRefreshListener();
}
/**
* @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() {
// 创建对于 log/output 的监听
if (!this.connectLogListenerCancel) {
@ -519,22 +597,22 @@ class McpClientAdapter {
const { code, msg } = message;
const client = this.clients.at(-1);
if (!client) {
return;
}
client.connectionResult.logString.push({
type: code === 200 ? 'info': 'error',
type: code === 200 ? 'info' : 'error',
title: msg.title,
message: msg.message
});
}, { once: false });
}, { once: false });
}
const launchSignature = await this.getLaunchSignature();
let allOk = true;
for (const item of launchSignature) {
@ -641,7 +719,7 @@ class McpClientAdapter {
timeout: mcpSetting.timeout * 1000
}
});
return msg;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -60,4 +60,4 @@ export default defineConfig({
'@': resolve(__dirname, '..', 'renderer/src'),
}
}
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 { RestfulResponse } from '../common/index.dto.js';
import { McpOptions } from './client.dto.js';
import { McpServerConnectMonitor } from './connect-monitor.service.js';
import * as crypto from 'node:crypto';
import path from 'node:path';
import fs from 'node:fs';
@ -14,7 +15,19 @@ export const clientMap: Map<string, RequestClientType> = new Map();
export function getClient(clientId?: string): RequestClientType | undefined {
return clientMap.get(clientId || '');
}
export const clientMonitorMap: Map<string, McpServerConnectMonitor> = new Map();
export async function updateClientMap(uuid: string, options: McpOptions): Promise<{ res: boolean; error?: any }> {
try {
const client = await connect(options);
clientMap.set(uuid, client);
const tools = await client.listTools();
console.log('[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 {
try {
const commandString = command + ' ' + args.join(' ');
@ -290,6 +303,7 @@ export async function connectService(
const client = await connect(option);
clientMap.set(uuid, client);
clientMonitorMap.set(uuid, new McpServerConnectMonitor(uuid, option, updateClientMap, webview));
const versionInfo = client.getServerVersion();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -95,7 +95,13 @@ export function getConnectionConfig() {
*/
export function getWorkspaceConnectionConfigPath() {
const workspace = getWorkspacePath();
if (!workspace) {
throw new Error('No workspace found. Please open a folder in VSCode first.');
}
const configDir = fspath.join(workspace, '.openmcp');
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true }); // 递归创建目录
}
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
return connectionConfig;
}
@ -110,6 +116,9 @@ export function getWorkspaceConnectionConfig() {
}
const workspace = getWorkspacePath();
if (!workspace) {
throw new Error('No workspace found. Please open a folder in VSCode first.');
}
const configDir = fspath.join(workspace, '.openmcp');
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
@ -296,6 +305,7 @@ function normaliseConnectionFilePath(item: McpOptions, workspace: string) {
export function getWorkspacePath() {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
console.log('getWorkspacePath: ', vscode.workspace.workspaceFolders);
return (workspaceFolder?.uri.fsPath || '').replace(/\\/g, '/');
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@ -1,14 +1,19 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"target": "es6",
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": false,
"noEmitHelpers": false,
"experimentalDecorators": true,
"sourceMap": true,
// 访 openmcp-sdk
"paths": {
"@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'],
},
},
},
],
},
};