update
This commit is contained in:
parent
9ee40df8d2
commit
eed67f6eb5
@ -28,6 +28,7 @@
|
|||||||
- [ ] 支持同时调试多个 MCP Server
|
- [ ] 支持同时调试多个 MCP Server
|
||||||
- [ ] 支持通过大模型进行在线验证
|
- [ ] 支持通过大模型进行在线验证
|
||||||
- [ ] 支持 completion/complete 协议字段
|
- [ ] 支持 completion/complete 协议字段
|
||||||
|
- [ ] 支持 对用户对应服务器的调试工作内容进行保存
|
||||||
|
|
||||||
|
|
||||||
## Dev
|
## Dev
|
||||||
|
@ -13,7 +13,8 @@ import Sidebar from '@/components/sidebar/index.vue';
|
|||||||
import MainPanel from '@/components/main-panel/index.vue';
|
import MainPanel from '@/components/main-panel/index.vue';
|
||||||
import { setDefaultCss } from './hook/css';
|
import { setDefaultCss } from './hook/css';
|
||||||
import { pinkLog } from './views/setting/util';
|
import { pinkLog } from './views/setting/util';
|
||||||
import { useMessageBridge } from './api/message-bridge';
|
import { acquireVsCodeApi, useMessageBridge } from './api/message-bridge';
|
||||||
|
import { connectionArgs, connectionMethods, connectionResult, doConnect } from './views/connect/connection';
|
||||||
|
|
||||||
const bridge = useMessageBridge();
|
const bridge = useMessageBridge();
|
||||||
|
|
||||||
@ -41,8 +42,25 @@ onMounted(() => {
|
|||||||
|
|
||||||
pinkLog('OpenMCP Client 启动');
|
pinkLog('OpenMCP Client 启动');
|
||||||
|
|
||||||
sendPing();
|
// 如果是 debug 模式,直接连接项目中的服务器
|
||||||
})
|
if (acquireVsCodeApi === undefined) {
|
||||||
|
connectionArgs.commandString = 'uv run mcp run ../servers/main.py';
|
||||||
|
connectionMethods.current = 'STDIO';
|
||||||
|
|
||||||
|
let handler: (() => void);
|
||||||
|
handler = bridge.addCommandListener('connect', data => {
|
||||||
|
const { code, msg } = data;
|
||||||
|
connectionResult.success = (code === 200);
|
||||||
|
connectionResult.logString = msg;
|
||||||
|
|
||||||
|
handler();
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
doConnect();
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -6,9 +6,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent, defineProps } from 'vue';
|
||||||
|
|
||||||
defineComponent({ name: 'chat' });
|
defineComponent({ name: 'chat' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tabId: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -10,19 +10,22 @@ interface Tab {
|
|||||||
icon: string;
|
icon: string;
|
||||||
type: string;
|
type: string;
|
||||||
component: any;
|
component: any;
|
||||||
|
storage: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const debugModes = [
|
export const debugModes = [
|
||||||
Resource, Prompt, Tool, Chat
|
Resource, Prompt, Tool, Chat
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// TODO: 实现对于 tabs 这个数据的可持久化
|
||||||
export const tabs = reactive({
|
export const tabs = reactive({
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
name: '空白测试 1',
|
name: '空白测试 1',
|
||||||
icon: 'icon-blank',
|
icon: 'icon-blank',
|
||||||
type: 'blank',
|
type: 'blank',
|
||||||
component: undefined
|
component: undefined,
|
||||||
|
storage: {}
|
||||||
}
|
}
|
||||||
] as Tab[],
|
] as Tab[],
|
||||||
activeIndex: 0,
|
activeIndex: 0,
|
||||||
@ -38,7 +41,8 @@ export function addNewTab() {
|
|||||||
name: `空白测试 ${++ tabCounter}`,
|
name: `空白测试 ${++ tabCounter}`,
|
||||||
icon: 'icon-blank',
|
icon: 'icon-blank',
|
||||||
type: 'blank',
|
type: 'blank',
|
||||||
component: undefined
|
component: undefined,
|
||||||
|
storage: {}
|
||||||
};
|
};
|
||||||
tabs.content.push(newTab);
|
tabs.content.push(newTab);
|
||||||
tabs.activeIndex = tabs.content.length - 1;
|
tabs.activeIndex = tabs.content.length - 1;
|
||||||
|
@ -6,9 +6,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent, defineProps } from 'vue';
|
||||||
|
|
||||||
defineComponent({ name: 'prompt' });
|
defineComponent({ name: 'prompt' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tabId: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -7,16 +7,32 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<h3><code>resources/templates/list</code></h3>
|
<h3><code>resources/templates/list</code></h3>
|
||||||
|
|
||||||
<ResourceTemplates></ResourceTemplates>
|
<ResourceTemplates
|
||||||
|
:tab-id="props.tabId"
|
||||||
|
></ResourceTemplates>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
|
<ResourceReader
|
||||||
|
:tab-id="props.tabId"
|
||||||
|
></ResourceReader>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { defineProps } from 'vue';
|
||||||
import ResourceTemplates from './resource-templates.vue';
|
import ResourceTemplates from './resource-templates.vue';
|
||||||
|
import ResourceReader from './resouce-reader.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tabId: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
124
app/src/components/main-panel/resource/resouce-reader.vue
Normal file
124
app/src/components/main-panel/resource/resouce-reader.vue
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<div class="resource-reader-container">
|
||||||
|
<el-form :model="formData" :rules="formRules" ref="formRef" label-position="top">
|
||||||
|
<el-form-item v-for="param in currentResource?.params" :key="param.name"
|
||||||
|
:label="`${param.name}${param.required ? '*' : ''}`" :prop="param.name" :rules="getParamRules(param)">
|
||||||
|
<!-- 根据不同类型渲染不同输入组件 -->
|
||||||
|
<el-input v-if="param.type === 'string'" v-model="formData[param.name]"
|
||||||
|
:placeholder="param.description || `请输入${param.name}`" />
|
||||||
|
|
||||||
|
<el-input-number v-else-if="param.type === 'number'" v-model="formData[param.name]"
|
||||||
|
:placeholder="param.description || `请输入${param.name}`" />
|
||||||
|
|
||||||
|
<el-switch v-else-if="param.type === 'boolean'" v-model="formData[param.name]" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :loading="loading" @click="handleSubmit">
|
||||||
|
submit
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetForm">
|
||||||
|
reset
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineComponent, defineProps, watch, ref, computed } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus';
|
||||||
|
import { tabs } from '../panel';
|
||||||
|
import { ResourceStorage } from './resources';
|
||||||
|
import { ResourcesReadResponse } from '@/hook/type';
|
||||||
|
|
||||||
|
defineComponent({ name: 'resource-reader' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tabId: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const tab = tabs.content[props.tabId];
|
||||||
|
const tabStorage = tab.storage as ResourceStorage;
|
||||||
|
|
||||||
|
// 表单相关状态
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const formData = ref<Record<string, any>>({});
|
||||||
|
const loading = ref(false);
|
||||||
|
const responseData = ref<ResourcesReadResponse>();
|
||||||
|
|
||||||
|
// 当前资源协议
|
||||||
|
const currentResource = computed(() => props.resource);
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const formRules = computed<FormRules>(() => {
|
||||||
|
const rules: FormRules = {}
|
||||||
|
currentResource.value?.params.forEach(param => {
|
||||||
|
rules[param.name] = [
|
||||||
|
{
|
||||||
|
required: param.required,
|
||||||
|
message: `${param.name} 是必填字段`,
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
})
|
||||||
|
|
||||||
|
// 根据参数类型获取特定规则
|
||||||
|
const getParamRules = (param: ResourceProtocol['params'][0]) => {
|
||||||
|
const rules: FormRules[number] = []
|
||||||
|
|
||||||
|
if (param.required) {
|
||||||
|
rules.push({
|
||||||
|
required: true,
|
||||||
|
message: `${param.name}是必填字段`,
|
||||||
|
trigger: 'blur'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.type === 'number') {
|
||||||
|
rules.push({
|
||||||
|
type: 'number',
|
||||||
|
message: `${param.name}必须是数字`,
|
||||||
|
trigger: 'blur'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化表单数据
|
||||||
|
const initFormData = () => {
|
||||||
|
formData.value = {}
|
||||||
|
currentResource.value?.params.forEach(param => {
|
||||||
|
formData.value[param.name] = param.type === 'number' ? 0 :
|
||||||
|
param.type === 'boolean' ? false : ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const resetForm = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
responseData.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
console.log('submit');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听资源变化重置表单
|
||||||
|
watch(() => tabStorage.currentResourceName, () => {
|
||||||
|
initFormData();
|
||||||
|
resetForm();
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
@ -4,8 +4,10 @@
|
|||||||
<div class="resource-template-container">
|
<div class="resource-template-container">
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="item"
|
||||||
|
:class="{ 'active': tabStorage.currentResourceName === template.name }"
|
||||||
v-for="template of resourcesManager.templates"
|
v-for="template of resourcesManager.templates"
|
||||||
:key="template.name"
|
:key="template.name"
|
||||||
|
@click="handleClick(template)"
|
||||||
>
|
>
|
||||||
<span>{{ template.name }}</span>
|
<span>{{ template.name }}</span>
|
||||||
<span>{{ template.description || '' }}</span>
|
<span>{{ template.description || '' }}</span>
|
||||||
@ -17,22 +19,41 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMessageBridge } from '@/api/message-bridge';
|
import { useMessageBridge } from '@/api/message-bridge';
|
||||||
import { CasualRestAPI, ResourceTemplatesListResponse } from '@/hook/type';
|
import { CasualRestAPI, ResourceTemplate, ResourceTemplatesListResponse } from '@/hook/type';
|
||||||
import { onMounted, onUnmounted } from 'vue';
|
import { onMounted, onUnmounted, defineProps } from 'vue';
|
||||||
import { resourcesManager } from './resources';
|
import { resourcesManager, ResourceStorage } from './resources';
|
||||||
|
import { tabs } from '../panel';
|
||||||
|
|
||||||
const bridge = useMessageBridge();
|
const bridge = useMessageBridge();
|
||||||
let cancelListener: undefined | (() => void) = undefined;
|
let cancelListener: undefined | (() => void) = undefined;
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tabId: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const tab = tabs.content[props.tabId];
|
||||||
|
const tabStorage = tab.storage as ResourceStorage;
|
||||||
|
|
||||||
function reloadResources() {
|
function reloadResources() {
|
||||||
bridge.postMessage({
|
bridge.postMessage({
|
||||||
command: 'resources/templates/list'
|
command: 'resources/templates/list'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleClick(template: ResourceTemplate) {
|
||||||
|
tabStorage.currentResourceName = template.name;
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
cancelListener = bridge.addCommandListener('resources/templates/list', (data: CasualRestAPI<ResourceTemplatesListResponse>) => {
|
cancelListener = bridge.addCommandListener('resources/templates/list', (data: CasualRestAPI<ResourceTemplatesListResponse>) => {
|
||||||
resourcesManager.templates = data.msg.resourceTemplates;
|
resourcesManager.templates = data.msg.resourceTemplates || [];
|
||||||
|
|
||||||
|
if (resourcesManager.templates.length > 0) {
|
||||||
|
tabStorage.currentResourceName = resourcesManager.templates[0].name;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
reloadResources();
|
reloadResources();
|
||||||
});
|
});
|
||||||
@ -75,6 +96,11 @@ onUnmounted(() => {
|
|||||||
transition: var(--animation-3s);
|
transition: var(--animation-3s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resource-template-container > .item.active {
|
||||||
|
background-color: var(--main-light-color);
|
||||||
|
transition: var(--animation-3s);
|
||||||
|
}
|
||||||
|
|
||||||
.resource-template-container > .item > span:first-child {
|
.resource-template-container > .item > span:first-child {
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -9,3 +9,7 @@ export const resourcesManager = reactive<{
|
|||||||
current: undefined,
|
current: undefined,
|
||||||
templates: []
|
templates: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface ResourceStorage {
|
||||||
|
currentResourceName: string;
|
||||||
|
}
|
@ -6,9 +6,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent, defineProps } from 'vue';
|
||||||
|
|
||||||
defineComponent({ name: 'tool' });
|
defineComponent({ name: 'tool' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
storage: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -50,7 +50,6 @@ const bridge = useMessageBridge();
|
|||||||
|
|
||||||
bridge.addCommandListener('connect', data => {
|
bridge.addCommandListener('connect', data => {
|
||||||
const { code, msg } = data;
|
const { code, msg } = data;
|
||||||
|
|
||||||
connectionResult.success = (code === 200);
|
connectionResult.success = (code === 200);
|
||||||
connectionResult.logString = msg;
|
connectionResult.logString = msg;
|
||||||
});
|
});
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
<!-- 如果存在激活标签页,则根据标签页进行渲染 -->
|
<!-- 如果存在激活标签页,则根据标签页进行渲染 -->
|
||||||
<div v-show="tabs.activeTab.component">
|
<div v-show="tabs.activeTab.component">
|
||||||
<component
|
<component
|
||||||
v-for="(tab, index) of tabs.content"
|
|
||||||
v-show="tab === tabs.activeTab"
|
v-show="tab === tabs.activeTab"
|
||||||
|
v-for="(tab, index) of tabs.content"
|
||||||
:key="index"
|
:key="index"
|
||||||
:is="tab.component"
|
:is="tab.component"
|
||||||
|
:tab-id="index"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,7 +16,7 @@ const logger = pino({
|
|||||||
options: {
|
options: {
|
||||||
colorize: true, // 开启颜色
|
colorize: true, // 开启颜色
|
||||||
levelFirst: true, // 先打印日志级别
|
levelFirst: true, // 先打印日志级别
|
||||||
translateTime: 'yyyy-mm-dd HH:MM:ss', // 格式化时间
|
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', // 格式化时间
|
||||||
ignore: 'pid,hostname', // 忽略部分字段
|
ignore: 'pid,hostname', // 忽略部分字段
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user