完成兼容
This commit is contained in:
parent
be48449df8
commit
355b25b9b7
@ -18,8 +18,7 @@ import MainPanel from '@/components/main-panel/index.vue';
|
||||
import { setDefaultCss } from './hook/css';
|
||||
import { greenLog, pinkLog } from './views/setting/util';
|
||||
import { useMessageBridge } from './api/message-bridge';
|
||||
import { initialise } from './views/connect/connection';
|
||||
import { getPlatform } from './api/platform';
|
||||
import { initialise } from './views/connect';
|
||||
import Tour from '@/components/guide/tour.vue';
|
||||
import { userHasReadGuide } from './components/guide/tour';
|
||||
|
||||
@ -37,7 +36,9 @@ bridge.addCommandListener('hello', data => {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const useAuth = Boolean(import.meta.env.VITE_USE_AUTH);
|
||||
const useAuth = Boolean(import.meta.env.VITE_USE_AUTH !== "false");
|
||||
console.log(import.meta.env.VITE_USE_AUTH, useAuth);
|
||||
|
||||
privilegeStatus.allow = !Boolean(useAuth);
|
||||
|
||||
onMounted(async () => {
|
||||
|
@ -58,7 +58,7 @@
|
||||
</el-tour-step>
|
||||
|
||||
<el-tour-step
|
||||
:target="connectionSettingRef"
|
||||
:target="client.connectionSettingRef.value"
|
||||
:prev-button-props="{ children: '上一步' }"
|
||||
:next-button-props="{ children: '下一步' }"
|
||||
:show-close="false"
|
||||
@ -78,7 +78,7 @@
|
||||
</el-tour-step>
|
||||
|
||||
<el-tour-step
|
||||
:target="connectionLogRef"
|
||||
:target="client.connectionLogRef.value"
|
||||
:prev-button-props="{ children: '上一步' }"
|
||||
:next-button-props="{ children: '下一步' }"
|
||||
:show-close="false"
|
||||
@ -249,16 +249,17 @@ import TourTitle from './tour-title.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { welcomeRef } from '@/views/debug/welcome';
|
||||
import { connectionLogRef, connectionSettingRef } from '@/views/connect/connection';
|
||||
import { llmSettingRef } from '@/views/setting/api';
|
||||
import { userHasReadGuide } from './tour';
|
||||
import { setTour } from '@/hook/setting';
|
||||
import { mcpClientAdapter } from '@/views/connect/core';
|
||||
|
||||
const openTour = ref(true);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const router = useRouter();
|
||||
const client = mcpClientAdapter.masterNode;
|
||||
|
||||
const baseUrl = import.meta.env.BASE_URL;
|
||||
|
||||
|
@ -44,6 +44,8 @@ import { panelLoaded } from '@/hook/panel';
|
||||
|
||||
defineComponent({ name: 'main-panel' });
|
||||
|
||||
const baseURL = import.meta.env.BASE_URL;
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
@ -52,7 +54,7 @@ function pageAddNewTab() {
|
||||
|
||||
// 如果当前不在 debug 路由,则切换到 debug 路由
|
||||
if (route.name !== 'debug') {
|
||||
router.replace('/debug');
|
||||
router.push(baseURL + 'debug');
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +63,7 @@ function setActiveTab(index: number) {
|
||||
tabs.activeIndex = index;
|
||||
// 如果不在 debug 路由,则进入
|
||||
if (route.name !== 'debug') {
|
||||
router.replace('/debug');
|
||||
router.push(baseURL + 'debug');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import { ref, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { privilegeStatus } from './status';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { initialise } from '@/views/connect/connection';
|
||||
import { initialise } from '@/views/connect';
|
||||
|
||||
const dialogVisible = ref(true);
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="connected-status-container"
|
||||
id="connected-status-container"
|
||||
@click.stop="toggleConnectionPanel()"
|
||||
:class="{ 'connected': connectionResult.success }"
|
||||
:class="{ 'connected': client.connectionResult.success }"
|
||||
>
|
||||
<span class="mcp-server-info">
|
||||
<el-tooltip
|
||||
@ -15,7 +15,7 @@
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span class="connect-status">
|
||||
<span v-if="connectionResult.success">
|
||||
<span v-if="client.connectionResult.success">
|
||||
<span class="iconfont icon-connect"></span>
|
||||
<span class="iconfont icon-dui"></span>
|
||||
</span>
|
||||
@ -32,18 +32,19 @@
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Connection } from './sidebar';
|
||||
import { connectionResult } from '@/views/connect/connection';
|
||||
import { mcpClientAdapter } from '@/views/connect/core';
|
||||
|
||||
defineComponent({ name: 'connected' });
|
||||
|
||||
const { t } = useI18n();
|
||||
const client = mcpClientAdapter.masterNode;
|
||||
|
||||
const fullDisplayServerName = computed(() => {
|
||||
return connectionResult.serverInfo.name + '/' + connectionResult.serverInfo.version;
|
||||
return client.connectionResult.name + '/' + client.connectionResult.version;
|
||||
});
|
||||
|
||||
const displayServerName = computed(() => {
|
||||
const name = connectionResult.serverInfo.name;
|
||||
const name = client.connectionResult.name;
|
||||
if (name.length <= 3) return name;
|
||||
|
||||
// 处理中文混合名称
|
||||
|
@ -6,28 +6,28 @@ const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
name : "default",
|
||||
path : "/",
|
||||
redirect : baseURL + "/debug"
|
||||
redirect : baseURL + "debug"
|
||||
},
|
||||
{
|
||||
path: baseURL + "/debug",
|
||||
path: baseURL + "debug",
|
||||
name: "debug",
|
||||
component: () => import( /* webpackMode: "eager" */ "@/views/debug/index.vue"),
|
||||
meta: { title: "Debug" }
|
||||
},
|
||||
{
|
||||
path: baseURL + "/connect",
|
||||
path: baseURL + "connect",
|
||||
name: "connect",
|
||||
component: () => import( /* webpackMode: "eager" */ "@/views/connect/index.vue"),
|
||||
meta: { title: "Connect" }
|
||||
},
|
||||
{
|
||||
path: baseURL + "/setting",
|
||||
path: baseURL + "setting",
|
||||
name: "setting",
|
||||
component: () => import( /* webpackMode: "eager" */ "@/views/setting/index.vue"),
|
||||
meta: { title: "Setting" }
|
||||
},
|
||||
{
|
||||
path: baseURL + "/about",
|
||||
path: baseURL + "about",
|
||||
name: "about",
|
||||
component: () => import( /* webpackMode: "eager" */ "@/views/about/index.vue"),
|
||||
meta: { title: "Tools" }
|
||||
|
@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<!-- STDIO 模式下的命令输入 -->
|
||||
<div class="connection-option" v-if="connectionMethods.current === 'STDIO'">
|
||||
<div class="connection-option" v-if="client.connectionArgs.type === 'STDIO'">
|
||||
<span>{{ t('connect-sigature') }}</span>
|
||||
<span style="width: 310px;">
|
||||
<el-form :model="connectionArgs" :rules="rules" ref="stdioForm">
|
||||
<el-form :model="client.connectionArgs" :rules="rules" ref="stdioForm">
|
||||
<el-form-item prop="commandString">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">{{ t("command") }}</span>
|
||||
<el-input v-model="connectionArgs.commandString" placeholder="mcp run <your script>"></el-input>
|
||||
<el-input v-model="client.connectionArgs.commandString" placeholder="mcp run <your script>"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="cwd">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">{{ t('cwd') }}</span>
|
||||
<el-input v-model="connectionArgs.cwd" placeholder="cwd, 可为空"></el-input>
|
||||
<el-input v-model="client.connectionArgs.cwd" placeholder="cwd, 可为空"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -24,17 +24,17 @@
|
||||
<div class="connection-option" v-else>
|
||||
<span>{{ t('connect-sigature') }}</span>
|
||||
<span style="width: 310px;">
|
||||
<el-form :model="connectionArgs" :rules="rules" ref="urlForm">
|
||||
<el-form :model="client.connectionArgs" :rules="rules" ref="urlForm">
|
||||
<el-form-item prop="url">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">URL</span>
|
||||
<el-input v-model="connectionArgs.url" placeholder="http://"></el-input>
|
||||
<el-input v-model="client.connectionArgs.url" placeholder="http://"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="oauth">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">OAuth</span>
|
||||
<el-input v-model="connectionArgs.oauth" placeholder="认证签名, 可为空"></el-input>
|
||||
<el-input v-model="client.connectionArgs.oauth" placeholder="认证签名, 可为空"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -43,14 +43,23 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, reactive, ref } from 'vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { connectionArgs, connectionMethods } from './connection';
|
||||
import { mcpClientAdapter } from './core';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const client = mcpClientAdapter.clients[props.index];
|
||||
|
||||
const stdioForm = ref<FormInstance>()
|
||||
const urlForm = ref<FormInstance>()
|
||||
|
||||
@ -73,7 +82,7 @@ const rules = reactive<FormRules>({
|
||||
// 验证当前活动表单
|
||||
const validateForm = async () => {
|
||||
try {
|
||||
if (connectionMethods.current === 'STDIO') {
|
||||
if (client.connectionArgs.type === 'STDIO') {
|
||||
await stdioForm.value?.validate()
|
||||
} else {
|
||||
await urlForm.value?.validate()
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
<el-switch
|
||||
v-model="envEnabled"
|
||||
@change="handleEnvSwitch"
|
||||
@change="(enable: boolean) => client.handleEnvSwitch(enable)"
|
||||
inline-prompt
|
||||
active-text="预设"
|
||||
inactive-text="预设"
|
||||
@ -14,10 +14,10 @@
|
||||
<div class="input-env">
|
||||
<span class="input-env-container">
|
||||
<span>
|
||||
<el-input v-model="connectionEnv.newKey" @keyup.enter="addEnvVar"></el-input>
|
||||
<el-input v-model="client.connectionEnvironment.newKey" @keyup.enter="addEnvVar"></el-input>
|
||||
</span>
|
||||
<span>
|
||||
<el-input v-model="connectionEnv.newValue" @keyup.enter="addEnvVar"></el-input>
|
||||
<el-input v-model="client.connectionEnvironment.newValue" @keyup.enter="addEnvVar"></el-input>
|
||||
</span>
|
||||
<span>
|
||||
<div @click="addEnvVar">
|
||||
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<el-scrollbar height="200px" width="350px" class="display-env-container">
|
||||
<div class="display-env">
|
||||
<div class="input-env-container" v-for="option of connectionEnv.data" :key="option.key">
|
||||
<div class="input-env-container" v-for="option of client.connectionEnvironment.data" :key="option.key">
|
||||
<span> <el-input v-model="option.key"></el-input></span>
|
||||
<span> <el-input v-model="option.value" show-password></el-input></span>
|
||||
<span @click="deleteEnvVar(option)">
|
||||
@ -44,8 +44,18 @@
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { mcpClientAdapter } from './core';
|
||||
import type { EnvItem } from './type';
|
||||
|
||||
defineComponent({ name: 'env-var' });
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const client = mcpClientAdapter.clients[props.index];
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@ -54,24 +64,24 @@ const { t } = useI18n();
|
||||
*/
|
||||
function addEnvVar() {
|
||||
// 检查是否存在一样的 key
|
||||
const currentKey = connectionEnv.newKey;
|
||||
const currentValue = connectionEnv.newValue;
|
||||
const currentKey = client.connectionEnvironment.newKey;
|
||||
const currentValue = client.connectionEnvironment.newValue;
|
||||
|
||||
if (currentKey.length === 0 || currentValue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sameNameItems = connectionEnv.data.filter(item => item.key === currentKey);
|
||||
const sameNameItems = client.connectionEnvironment.data.filter(item => item.key === currentKey);
|
||||
|
||||
if (sameNameItems.length > 0) {
|
||||
const conflictItem = sameNameItems[0];
|
||||
conflictItem.value = currentValue;
|
||||
} else {
|
||||
connectionEnv.data.push({
|
||||
client.connectionEnvironment.data.push({
|
||||
key: currentKey, value: currentValue
|
||||
});
|
||||
connectionEnv.newKey = '';
|
||||
connectionEnv.newValue = '';
|
||||
client.connectionEnvironment.newKey = '';
|
||||
client.connectionEnvironment.newValue = '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,11 +90,10 @@ function addEnvVar() {
|
||||
*/
|
||||
function deleteEnvVar(option: EnvItem) {
|
||||
const currentKey = option.key;
|
||||
const reserveItems = connectionEnv.data.filter(item => item.key !== currentKey);
|
||||
connectionEnv.data = reserveItems;
|
||||
const reserveItems = client.connectionEnvironment.data.filter(item => item.key !== currentKey);
|
||||
client.connectionEnvironment.data = reserveItems;
|
||||
}
|
||||
|
||||
|
||||
const envEnabled = ref(true);
|
||||
|
||||
</script>
|
@ -3,7 +3,7 @@
|
||||
<span>{{ t('log') }}</span>
|
||||
<el-scrollbar height="90%">
|
||||
<div class="output-content">
|
||||
<div v-for="(log, index) in connectionResult.logString" :key="index" :class="log.type">
|
||||
<div v-for="(log, index) in client.connectionResult.logString" :key="index" :class="log.type">
|
||||
<span class="log-message">{{ log.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -14,9 +14,17 @@
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { connectionResult } from './connection';
|
||||
import { mcpClientAdapter } from './core';
|
||||
|
||||
defineComponent({ name: 'connection-log' });
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const client = mcpClientAdapter.clients[props.index];
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
<div class="connection-option">
|
||||
<span>{{ t('connection-method') }}</span>
|
||||
<span style="width: 200px;">
|
||||
<el-select name="language-setting" class="language-setting" v-model="connectionMethods.current">
|
||||
<el-option v-for="option in connectionMethods.data" :value="option.value" :label="option.label"
|
||||
<el-select name="language-setting" class="language-setting" v-model="client.connectionArgs.type">
|
||||
<el-option v-for="option in connectionSelectDataViewOption" :value="option.value" :label="option.label"
|
||||
:key="option.label"></el-option>
|
||||
</el-select>
|
||||
</span>
|
||||
@ -13,9 +13,17 @@
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { connectionMethods } from './connection';
|
||||
import { connectionSelectDataViewOption, mcpClientAdapter } from './core';
|
||||
|
||||
defineComponent({ name: 'connection-method' });
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const client = mcpClientAdapter.clients[props.index];
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
140
renderer/src/views/connect/connection-panel.vue
Normal file
140
renderer/src/views/connect/connection-panel.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<el-scrollbar>
|
||||
<div class="connection-container">
|
||||
<div class="connect-panel-container"
|
||||
:ref="el => client.connectionSettingRef.value = el"
|
||||
>
|
||||
<ConnectionMethod :index="props.index" />
|
||||
<ConnectionArgs :index="props.index" />
|
||||
<ConnectionEnvironment :index="props.index" />
|
||||
|
||||
<div class="connect-action">
|
||||
<el-button type="primary" size="large" :loading="isLoading" :disabled="!client.connectionResult"
|
||||
@click="connect()">
|
||||
<span class="iconfont icon-connect" v-if="!isLoading"></span>
|
||||
{{ t('connect.appearance.connect') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connect-panel-container"
|
||||
:ref="el => client.connectionLogRef.value = el"
|
||||
>
|
||||
<ConnectionLog :index="props.index" />
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import ConnectionMethod from './connection-method.vue';
|
||||
import ConnectionArgs from './connection-args.vue';
|
||||
import ConnectionEnvironment from './connection-environment.vue';
|
||||
import ConnectionLog from './connection-log.vue';
|
||||
|
||||
import { getPlatform } from '@/api/platform';
|
||||
import { mcpClientAdapter } from './core';
|
||||
|
||||
defineComponent({ name: 'connection-panel' });
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const client = mcpClientAdapter.clients[props.index];
|
||||
|
||||
console.log(client);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
async function connect() {
|
||||
isLoading.value = true;
|
||||
|
||||
const plaform = getPlatform();
|
||||
const ok = await client.connect(plaform);
|
||||
if (ok) {
|
||||
mcpClientAdapter.saveLaunchSignature();
|
||||
}
|
||||
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.connection-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
.connect-panel-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 45%;
|
||||
min-width: 300px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.connection-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--background);
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: .5em;
|
||||
border: 1px solid var(--background);
|
||||
}
|
||||
|
||||
.connection-option>span:first-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.input-env-container {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.display-env {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.input-env-container>span {
|
||||
width: 150px;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-env-container .iconfont {
|
||||
font-size: 20px;
|
||||
border-radius: 99em;
|
||||
color: var(--foreground);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: var(--animation-3s);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.input-env-container .iconfont:hover {
|
||||
color: var(--main-color);
|
||||
transition: var(--animation-3s);
|
||||
}
|
||||
|
||||
.connect-action {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
@ -1,11 +1,10 @@
|
||||
import { useMessageBridge } from "@/api/message-bridge";
|
||||
import { reactive, type Reactive } from "vue";
|
||||
import { reactive, ref, type Reactive, type Ref } from "vue";
|
||||
import type { IConnectionResult, ConnectionTypeOptionItem, IConnectionArgs, IConnectionEnvironment, McpOptions } from "./type";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { loadPanels, type SaveTab } from "@/hook/panel";
|
||||
import { loadPanels } from "@/hook/panel";
|
||||
import { getPlatform } from "@/api/platform";
|
||||
|
||||
|
||||
export const connectionSelectDataViewOption: ConnectionTypeOptionItem[] = [
|
||||
{
|
||||
value: 'STDIO',
|
||||
@ -21,34 +20,23 @@ export const connectionSelectDataViewOption: ConnectionTypeOptionItem[] = [
|
||||
}
|
||||
]
|
||||
|
||||
export async function getLaunchSignature(platform: string): Promise<IConnectionArgs[]> {
|
||||
const bridge = useMessageBridge();
|
||||
const { code, msg } = await bridge.commandRequest(platform + '/launch-signature');
|
||||
|
||||
if (code !== 200) {
|
||||
const message = msg.toString();
|
||||
ElMessage.error(message);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 判断一下版本,新版本的 msg 应该是数组,老版本是对象
|
||||
// 返回的数组的第一个为主节点,其余为从节点
|
||||
if (Array.isArray(msg)) {
|
||||
return msg;
|
||||
}
|
||||
return [msg];
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class McpClient {
|
||||
|
||||
// 连接入参
|
||||
public connectionArgs: Reactive<IConnectionArgs>;
|
||||
// 连接出参
|
||||
public connectionResult: Reactive<IConnectionResult>;
|
||||
|
||||
|
||||
// 预设环境变量,初始化的时候会去获取它们
|
||||
public presetsEnvironment: string[] = ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
|
||||
// 环境变量
|
||||
public connectionEnvironment: Reactive<IConnectionEnvironment>;
|
||||
|
||||
// logger 面板的 ref
|
||||
public connectionLogRef = ref<any>(null);
|
||||
// setting 面板的 ref
|
||||
public connectionSettingRef = ref<any>(null);
|
||||
|
||||
constructor(
|
||||
public clientVersion: string = '0.0.1',
|
||||
public clientNamePrefix: string = 'openmcp.connect'
|
||||
@ -169,23 +157,16 @@ export class McpClient {
|
||||
type: 'error',
|
||||
message
|
||||
});
|
||||
|
||||
|
||||
ElMessage.error(message);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.connectionResult.status = msg.status;
|
||||
this.connectionResult.clientId = msg.clientId;
|
||||
this.connectionResult.name = msg.name;
|
||||
this.connectionResult.version = msg.version;
|
||||
|
||||
// 同步成功的连接参数到后端,更新 vscode treeview 中的列表
|
||||
const deserializeOption = JSON.parse(JSON.stringify(this.connectOption));
|
||||
|
||||
bridge.postMessage({
|
||||
command: platform + '/update-connection-sigature',
|
||||
data: deserializeOption
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -198,7 +179,7 @@ export class McpClient {
|
||||
const presetVars = this.presetsEnvironment;
|
||||
if (enabled) {
|
||||
const values = await this.lookupEnvVar(presetVars);
|
||||
|
||||
|
||||
if (values) {
|
||||
// 将 key values 合并进 connectionEnv.data 中
|
||||
// 若已有相同的 key, 则替换 value
|
||||
@ -232,7 +213,7 @@ export class McpClient {
|
||||
public async lookupEnvVar(varNames: string[]) {
|
||||
const bridge = useMessageBridge();
|
||||
const { code, msg } = await bridge.commandRequest('lookup-env-var', { keys: varNames });
|
||||
|
||||
|
||||
if (code === 200) {
|
||||
this.connectionResult.logString.push({
|
||||
type: 'info',
|
||||
@ -252,28 +233,79 @@ export class McpClient {
|
||||
|
||||
class McpClientAdapter {
|
||||
public clients: McpClient[] = [];
|
||||
public currentClientIndex: number = 0;
|
||||
|
||||
private defaultClient: McpClient = new McpClient();
|
||||
|
||||
constructor(
|
||||
public platform: string
|
||||
) {}
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @description 获取连接参数签名
|
||||
* @returns
|
||||
*/
|
||||
public async getLaunchSignature(): Promise<IConnectionArgs[]> {
|
||||
const bridge = useMessageBridge();
|
||||
const { code, msg } = await bridge.commandRequest(this.platform + '/launch-signature');
|
||||
|
||||
if (code !== 200) {
|
||||
const message = msg.toString();
|
||||
ElMessage.error(message);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 判断一下版本,新版本的 msg 应该是数组,老版本是对象
|
||||
// 返回的数组的第一个为主节点,其余为从节点
|
||||
if (Array.isArray(msg)) {
|
||||
return msg;
|
||||
}
|
||||
return [msg];
|
||||
}
|
||||
|
||||
get masterNode() {
|
||||
if (this.clients.length === 0) {
|
||||
return this.defaultClient;
|
||||
}
|
||||
return this.clients[0];
|
||||
}
|
||||
|
||||
public async saveLaunchSignature() {
|
||||
const bridge = useMessageBridge();
|
||||
const options: McpOptions[] = this.clients.map(client => client.connectOption);
|
||||
|
||||
// 同步成功的连接参数到后端,更新 vscode treeview 中的列表
|
||||
const deserializeOption = JSON.parse(JSON.stringify(options));
|
||||
bridge.postMessage({
|
||||
command: platform + '/update-connection-signature',
|
||||
data: deserializeOption
|
||||
});
|
||||
}
|
||||
|
||||
public async launch() {
|
||||
const launchSignature = await getLaunchSignature(this.platform);
|
||||
const launchSignature = await this.getLaunchSignature();
|
||||
let allOk = true;
|
||||
|
||||
for (const item of launchSignature) {
|
||||
const client = new McpClient();
|
||||
|
||||
// 同步连接参数
|
||||
await client.acquireConnectionSignature(item);
|
||||
|
||||
|
||||
// 同步环境变量
|
||||
await client.handleEnvSwitch(true);
|
||||
|
||||
// 连接
|
||||
await client.connect(this.platform);
|
||||
|
||||
const ok = await client.connect(this.platform);
|
||||
allOk &&= ok;
|
||||
|
||||
this.clients.push(client);
|
||||
}
|
||||
|
||||
// 如果全部成功,保存连接参数
|
||||
if (allOk) {
|
||||
this.saveLaunchSignature();
|
||||
}
|
||||
}
|
||||
|
||||
public async loadPanels() {
|
||||
@ -283,4 +315,6 @@ class McpClientAdapter {
|
||||
}
|
||||
|
||||
const platform = getPlatform();
|
||||
export const mcpClientAdapter = new McpClientAdapter(platform);
|
||||
export const mcpClientAdapter = reactive(
|
||||
new McpClientAdapter(platform)
|
||||
);
|
@ -1,128 +1,96 @@
|
||||
<template>
|
||||
<el-scrollbar>
|
||||
<div class="connection-container">
|
||||
<div class="connect-panel-container"
|
||||
:ref="el => connectionSettingRef = el"
|
||||
>
|
||||
<ConnectionMethod></ConnectionMethod>
|
||||
<ConnectionArgs></ConnectionArgs>
|
||||
<EnvVar></EnvVar>
|
||||
|
||||
<div class="connect-action">
|
||||
<el-button type="primary" size="large" :loading="isLoading" :disabled="!connectionResult"
|
||||
@click="suitableConnect()">
|
||||
<span class="iconfont icon-connect" v-if="!isLoading"></span>
|
||||
{{ t('connect.appearance.connect') }}
|
||||
</el-button>
|
||||
<div class="connection-container">
|
||||
<div class="server-list">
|
||||
<div v-for="(client, index) in mcpClientAdapter.clients" :key="index" class="server-item"
|
||||
:class="{ 'active': mcpClientAdapter.currentClientIndex === index }" @click="selectServer(index)">
|
||||
<span class="server-name">Server {{ index + 1 }}</span>
|
||||
<span class="server-status" :class="client.connectionResult.status">
|
||||
{{ client.connectionResult.status }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="add-server" @click="addServer">
|
||||
<span class="iconfont icon-add"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connect-panel-container"
|
||||
:ref="el => connectionLogRef = el"
|
||||
>
|
||||
<ConnectionLog></ConnectionLog>
|
||||
<div class="panel-container">
|
||||
<ConnectionPanel v-if="mcpClientAdapter.clients.length > 0" :index="mcpClientAdapter.currentClientIndex" />
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent } from 'vue';
|
||||
import ConnectionPanel from './connection-panel.vue';
|
||||
import { mcpClientAdapter } from './core';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const { t } = useI18n();
|
||||
defineComponent({ name: 'connection' });
|
||||
|
||||
import { connectionLogRef, connectionResult, connectionSettingRef, doConnect } from './connection';
|
||||
|
||||
import ConnectionMethod from './connection-method.vue';
|
||||
import ConnectionArgs from './connection-args.vue';
|
||||
import EnvVar from './env-var.vue';
|
||||
|
||||
import ConnectionLog from './connection-log.vue';
|
||||
import { getPlatform } from '@/api/platform';
|
||||
|
||||
defineComponent({ name: 'connect' });
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
async function suitableConnect() {
|
||||
isLoading.value = true;
|
||||
|
||||
const plaform = getPlatform();
|
||||
|
||||
await doConnect({ namespace: plaform, updateCommandString: false })
|
||||
|
||||
isLoading.value = false;
|
||||
function selectServer(index: number) {
|
||||
mcpClientAdapter.currentClientIndex = index;
|
||||
}
|
||||
|
||||
function addServer() {
|
||||
ElMessage.info('Add server is not implemented yet');
|
||||
|
||||
// mcpClientAdapter.clients.push(new McpClient());
|
||||
// mcpClientAdapter.currentClientIndex = mcpClientAdapter.clients.length - 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.connection-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.server-list {
|
||||
width: 200px;
|
||||
border-right: 1px solid var(--border-color);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.connect-panel-container {
|
||||
.server-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 5px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 45%;
|
||||
min-width: 300px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.server-item.active {
|
||||
background-color: var(--main-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.server-status {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.server-status.connected {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.server-status.disconnected {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.add-server {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
border: 1px dashed var(--border-color);
|
||||
}
|
||||
|
||||
.add-server:hover {
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
.panel-container {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.connection-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--background);
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: .5em;
|
||||
border: 1px solid var(--background);
|
||||
}
|
||||
|
||||
.connection-option>span:first-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.input-env-container {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.display-env {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.input-env-container>span {
|
||||
width: 150px;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-env-container .iconfont {
|
||||
font-size: 20px;
|
||||
border-radius: 99em;
|
||||
color: var(--foreground);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: var(--animation-3s);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.input-env-container .iconfont:hover {
|
||||
color: var(--main-color);
|
||||
transition: var(--animation-3s);
|
||||
}
|
||||
|
||||
.connect-action {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
@ -7,7 +7,7 @@
|
||||
<!-- TODO: 支持更多的 server -->
|
||||
<span
|
||||
class="debug-option"
|
||||
:class="{ 'disable': !connectionResult.success }"
|
||||
:class="{ 'disable': !client.connectionResult.success }"
|
||||
v-for="(option, index) of debugOptions"
|
||||
:key="index"
|
||||
@click="chooseDebugMode(index)"
|
||||
@ -25,13 +25,14 @@
|
||||
import { debugModes, tabs } from '@/components/main-panel/panel';
|
||||
import { defineComponent, markRaw, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { connectionResult } from '../connect/connection';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { welcomeRef } from './welcome';
|
||||
import { mcpClientAdapter } from '../connect/core';
|
||||
|
||||
defineComponent({ name: 'welcome' });
|
||||
|
||||
const { t } = useI18n();
|
||||
const client = mcpClientAdapter.masterNode;
|
||||
|
||||
const debugOptions = [
|
||||
{
|
||||
@ -59,7 +60,7 @@ const debugOptions = [
|
||||
function chooseDebugMode(index: number) {
|
||||
|
||||
// TODO: 支持更多的 server
|
||||
if (connectionResult.success) {
|
||||
if (client.connectionResult.success) {
|
||||
const activeTab = tabs.activeTab;
|
||||
activeTab.component = markRaw(debugModes[index]);
|
||||
|
||||
|
@ -148,7 +148,7 @@ wss.on('connection', (ws: any) => {
|
||||
|
||||
break;
|
||||
|
||||
case 'web/update-connection-sigature':
|
||||
case 'web/update-connection-signature':
|
||||
updateConnectionOption(data);
|
||||
break;
|
||||
|
||||
|
@ -179,7 +179,7 @@ wss.on('connection', (ws: any) => {
|
||||
|
||||
break;
|
||||
|
||||
case 'web/update-connection-sigature':
|
||||
case 'web/update-connection-signature':
|
||||
updateConnectionOption(data);
|
||||
break;
|
||||
|
||||
|
@ -64,7 +64,7 @@ function createWindow(): void {
|
||||
|
||||
break;
|
||||
|
||||
case 'electron/update-connection-sigature':
|
||||
case 'electron/update-connection-signature':
|
||||
updateConnectionOption(data);
|
||||
break;
|
||||
|
||||
|
@ -99,7 +99,7 @@ export function revealOpenMcpWebviewPanel(
|
||||
|
||||
break;
|
||||
|
||||
case 'vscode/update-connection-sigature':
|
||||
case 'vscode/update-connection-signature':
|
||||
if (type === 'installed') {
|
||||
updateInstalledConnectionConfig(panelKey, data);
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user