update
This commit is contained in:
parent
9ee40df8d2
commit
eed67f6eb5
@ -28,6 +28,7 @@
|
||||
- [ ] 支持同时调试多个 MCP Server
|
||||
- [ ] 支持通过大模型进行在线验证
|
||||
- [ ] 支持 completion/complete 协议字段
|
||||
- [ ] 支持 对用户对应服务器的调试工作内容进行保存
|
||||
|
||||
|
||||
## Dev
|
||||
|
@ -13,7 +13,8 @@ import Sidebar from '@/components/sidebar/index.vue';
|
||||
import MainPanel from '@/components/main-panel/index.vue';
|
||||
import { setDefaultCss } from './hook/css';
|
||||
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();
|
||||
|
||||
@ -41,8 +42,25 @@ onMounted(() => {
|
||||
|
||||
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>
|
||||
|
||||
|
@ -6,9 +6,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, defineProps } from 'vue';
|
||||
|
||||
defineComponent({ name: 'chat' });
|
||||
|
||||
const props = defineProps({
|
||||
tabId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -10,19 +10,22 @@ interface Tab {
|
||||
icon: string;
|
||||
type: string;
|
||||
component: any;
|
||||
storage: Record<string, any>;
|
||||
}
|
||||
|
||||
export const debugModes = [
|
||||
Resource, Prompt, Tool, Chat
|
||||
]
|
||||
|
||||
// TODO: 实现对于 tabs 这个数据的可持久化
|
||||
export const tabs = reactive({
|
||||
content: [
|
||||
{
|
||||
name: '空白测试 1',
|
||||
icon: 'icon-blank',
|
||||
type: 'blank',
|
||||
component: undefined
|
||||
component: undefined,
|
||||
storage: {}
|
||||
}
|
||||
] as Tab[],
|
||||
activeIndex: 0,
|
||||
@ -38,7 +41,8 @@ export function addNewTab() {
|
||||
name: `空白测试 ${++ tabCounter}`,
|
||||
icon: 'icon-blank',
|
||||
type: 'blank',
|
||||
component: undefined
|
||||
component: undefined,
|
||||
storage: {}
|
||||
};
|
||||
tabs.content.push(newTab);
|
||||
tabs.activeIndex = tabs.content.length - 1;
|
||||
|
@ -6,9 +6,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, defineProps } from 'vue';
|
||||
|
||||
defineComponent({ name: 'prompt' });
|
||||
|
||||
const props = defineProps({
|
||||
tabId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -7,16 +7,32 @@
|
||||
</h2>
|
||||
<h3><code>resources/templates/list</code></h3>
|
||||
|
||||
<ResourceTemplates></ResourceTemplates>
|
||||
<ResourceTemplates
|
||||
:tab-id="props.tabId"
|
||||
></ResourceTemplates>
|
||||
|
||||
</div>
|
||||
<div class="right">
|
||||
|
||||
<ResourceReader
|
||||
:tab-id="props.tabId"
|
||||
></ResourceReader>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue';
|
||||
import ResourceTemplates from './resource-templates.vue';
|
||||
import ResourceReader from './resouce-reader.vue';
|
||||
|
||||
const props = defineProps({
|
||||
tabId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
</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="item"
|
||||
:class="{ 'active': tabStorage.currentResourceName === template.name }"
|
||||
v-for="template of resourcesManager.templates"
|
||||
:key="template.name"
|
||||
@click="handleClick(template)"
|
||||
>
|
||||
<span>{{ template.name }}</span>
|
||||
<span>{{ template.description || '' }}</span>
|
||||
@ -17,22 +19,41 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { CasualRestAPI, ResourceTemplatesListResponse } from '@/hook/type';
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { resourcesManager } from './resources';
|
||||
import { CasualRestAPI, ResourceTemplate, ResourceTemplatesListResponse } from '@/hook/type';
|
||||
import { onMounted, onUnmounted, defineProps } from 'vue';
|
||||
import { resourcesManager, ResourceStorage } from './resources';
|
||||
import { tabs } from '../panel';
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
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() {
|
||||
bridge.postMessage({
|
||||
command: 'resources/templates/list'
|
||||
});
|
||||
}
|
||||
|
||||
function handleClick(template: ResourceTemplate) {
|
||||
tabStorage.currentResourceName = template.name;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
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();
|
||||
});
|
||||
@ -75,6 +96,11 @@ onUnmounted(() => {
|
||||
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 {
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
|
@ -9,3 +9,7 @@ export const resourcesManager = reactive<{
|
||||
current: undefined,
|
||||
templates: []
|
||||
});
|
||||
|
||||
export interface ResourceStorage {
|
||||
currentResourceName: string;
|
||||
}
|
@ -6,9 +6,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, defineProps } from 'vue';
|
||||
|
||||
defineComponent({ name: 'tool' });
|
||||
|
||||
const props = defineProps({
|
||||
storage: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -50,7 +50,6 @@ const bridge = useMessageBridge();
|
||||
|
||||
bridge.addCommandListener('connect', data => {
|
||||
const { code, msg } = data;
|
||||
|
||||
connectionResult.success = (code === 200);
|
||||
connectionResult.logString = msg;
|
||||
});
|
||||
|
@ -5,10 +5,11 @@
|
||||
<!-- 如果存在激活标签页,则根据标签页进行渲染 -->
|
||||
<div v-show="tabs.activeTab.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"
|
||||
:is="tab.component"
|
||||
:tab-id="index"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,7 +16,7 @@ const logger = pino({
|
||||
options: {
|
||||
colorize: true, // 开启颜色
|
||||
levelFirst: true, // 先打印日志级别
|
||||
translateTime: 'yyyy-mm-dd HH:MM:ss', // 格式化时间
|
||||
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', // 格式化时间
|
||||
ignore: 'pid,hostname', // 忽略部分字段
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user