优化 service, 进行 mcp 连接复用

This commit is contained in:
锦恢 2025-05-21 01:51:27 +08:00
parent 468ce23b66
commit 86c218ab5e
7 changed files with 118 additions and 45 deletions

View File

@ -69,6 +69,8 @@ function reloadTools(option: { first: boolean }) {
} }
function handleClick(tool: { name: string }) { function handleClick(tool: { name: string }) {
console.log('enter');
tabStorage.currentToolName = tool.name; tabStorage.currentToolName = tool.name;
tabStorage.lastToolCallResponse = undefined; tabStorage.lastToolCallResponse = undefined;
} }

View File

@ -0,0 +1,3 @@
import { ref } from "vue";
export const isConnecting = ref(true);

View File

@ -1,16 +1,9 @@
<template> <template>
<div class="connected-status-container" <div v-if="!isConnecting" class="connected-status-container" id="connected-status-container"
id="connected-status-container" @click.stop="toggleConnectionPanel()" :class="{ 'connected': client.connectionResult.success }">
@click.stop="toggleConnectionPanel()"
:class="{ 'connected': client.connectionResult.success }"
>
<span class="mcp-server-info"> <span class="mcp-server-info">
<el-tooltip <el-tooltip class="extra-connect-container" effect="dark" placement="right"
class="extra-connect-container" :content="fullDisplayServerName">
effect="dark"
placement="right"
:content="fullDisplayServerName"
>
<span class="name">{{ displayServerName }}</span> <span class="name">{{ displayServerName }}</span>
</el-tooltip> </el-tooltip>
</span> </span>
@ -24,7 +17,33 @@
<span class="iconfont icon-cuo"></span> <span class="iconfont icon-cuo"></span>
</span> </span>
</span> </span>
</div>
<div v-else class="connected-status-container">
<span class="mcp-server-info">
<el-tooltip class="extra-connect-container" effect="dark" placement="right"
:content="fullDisplayServerName">
<span class="name">
加载中
</span>
</el-tooltip>
</span>
<span class="connect-status">
<span style="display: flex;">
<span class="iconfont icon-connect"></span>
<div class="custom-loading">
<svg class="circular" viewBox="-10, -10, 50, 50">
<path class="path" d="
M 30 15
L 28 17
M 25.61 25.61
A 15 15, 0, 0, 1, 15 30
A 15 15, 0, 1, 1, 27.99 7.5
L 15 15
" style="stroke-width: 4px; fill: rgba(0, 0, 0, 0); stroke: var(--main-color);" />
</svg>
</div>
</span>
</span>
</div> </div>
</template> </template>
@ -33,6 +52,7 @@ import { defineComponent, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { Connection } from './sidebar'; import { Connection } from './sidebar';
import { mcpClientAdapter } from '@/views/connect/core'; import { mcpClientAdapter } from '@/views/connect/core';
import { isConnecting } from './connected';
defineComponent({ name: 'connected' }); defineComponent({ name: 'connected' });
@ -166,4 +186,19 @@ function toggleConnectionPanel() {
font-weight: 400; font-weight: 400;
} }
.custom-loading .circular {
margin-right: 6px;
width: 18px;
height: 18px;
animation: loading-rotate 2s linear infinite;
}
.custom-loading .circular .path {
animation: loading-dash 1.5s ease-in-out infinite;
stroke-dasharray: 90, 150;
stroke-dashoffset: 0;
stroke-width: 2;
stroke: var(--el-button-text-color);
stroke-linecap: round;
}
</style> </style>

View File

@ -3,7 +3,7 @@ import { pinkLog } from "@/views/setting/util";
import { debugModes, tabs } from "@/components/main-panel/panel"; import { debugModes, tabs } from "@/components/main-panel/panel";
import { markRaw, ref, type Reactive } from "vue"; import { markRaw, ref, type Reactive } from "vue";
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import type { McpClient } from "@/views/connect/core"; import { mcpClientAdapter, type McpClient } from "@/views/connect/core";
interface SaveTabItem { interface SaveTabItem {
name: string; name: string;
@ -104,8 +104,12 @@ export function savePanels(saveHandler?: () => void) {
} }
}, { once: true }); }, { once: true });
const masterNode = mcpClientAdapter.masterNode;
bridge.postMessage({ bridge.postMessage({
command: 'panel/save', command: 'panel/save',
data: saveTabs data: {
clientId: masterNode.clientId,
...saveTabs
}
}); });
} }

View File

@ -2,6 +2,7 @@ import { getTour, loadSetting } from "@/hook/setting";
import { ElLoading } from "element-plus"; import { ElLoading } from "element-plus";
import { pinkLog } from "../setting/util"; import { pinkLog } from "../setting/util";
import { mcpClientAdapter } from "./core"; import { mcpClientAdapter } from "./core";
import { isConnecting } from "@/components/sidebar/connected";
export async function initialise() { export async function initialise() {
@ -27,4 +28,6 @@ export async function initialise() {
// loading panels // loading panels
await mcpClientAdapter.loadPanels(); await mcpClientAdapter.loadPanels();
isConnecting.value = false;
} }

View File

@ -43,7 +43,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent, computed, watch, toRef } from 'vue'; import { defineComponent, computed } from 'vue';
import ConnectionPanel from './connection-panel.vue'; import ConnectionPanel from './connection-panel.vue';
import { McpClient, mcpClientAdapter } from './core'; import { McpClient, mcpClientAdapter } from './core';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
@ -54,11 +54,13 @@ function selectServer(index: number) {
mcpClientAdapter.currentClientIndex = index; mcpClientAdapter.currentClientIndex = index;
} }
function addServer() { function addServer() {
mcpClientAdapter.clients.push(new McpClient()); mcpClientAdapter.clients.push(new McpClient());
mcpClientAdapter.currentClientIndex = mcpClientAdapter.clients.length - 1; mcpClientAdapter.currentClientIndex = mcpClientAdapter.clients.length - 1;
} }
const serverOptions = computed(() => { const serverOptions = computed(() => {
return mcpClientAdapter.clients.map((client, index) => ({ return mcpClientAdapter.clients.map((client, index) => ({
value: index, value: index,

View File

@ -3,7 +3,7 @@ import { RequestClientType } from '../common';
import { connect } from './client.service'; import { connect } from './client.service';
import { RestfulResponse } from '../common/index.dto'; import { RestfulResponse } from '../common/index.dto';
import { McpOptions } from './client.dto'; import { McpOptions } from './client.dto';
import { randomUUID } from 'node:crypto'; import * as crypto from 'node:crypto';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import * as os from 'os'; import * as os from 'os';
@ -181,6 +181,24 @@ async function initNpm(option: McpOptions, cwd: string, webview?: PostMessageble
} }
async function deterministicUUID(input: string) {
// 使用Web Crypto API进行哈希
const msgBuffer = new TextEncoder().encode(input);
const hashBuffer = await crypto.subtle.digest('SHA-1', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
// 格式化为UUID (版本5)
return [
hashHex.substring(0, 8),
hashHex.substring(8, 4),
'5' + hashHex.substring(13, 3), // 设置版本为5
'8' + hashHex.substring(17, 3), // 设置变体
hashHex.substring(20, 12)
].join('-');
}
export async function connectService( export async function connectService(
option: McpOptions, option: McpOptions,
webview?: PostMessageble webview?: PostMessageble
@ -191,9 +209,15 @@ export async function connectService(
await preprocessCommand(option, webview); await preprocessCommand(option, webview);
// 通过 option 字符串进行 hash得到唯一的 uuid
const uuid = await deterministicUUID(JSON.stringify(option));
if (!clientMap.has(uuid)) {
const client = await connect(option); const client = await connect(option);
const uuid = randomUUID();
clientMap.set(uuid, client); clientMap.set(uuid, client);
}
const client = clientMap.get(uuid)!;
const versionInfo = client.getServerVersion(); const versionInfo = client.getServerVersion();