重构完成基础设施
This commit is contained in:
parent
86c218ab5e
commit
4017fc3290
@ -70,7 +70,10 @@ export function createTab(type: string, index: number): Tab {
|
||||
id,
|
||||
componentIndex: -1,
|
||||
component: undefined,
|
||||
storage: {},
|
||||
storage: {
|
||||
// 默认打开一个 mcp server 的面板
|
||||
activeNames: [0]
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,22 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3>{{ currentPrompt.name }}</h3>
|
||||
<h3>{{ currentPrompt?.name }}</h3>
|
||||
</div>
|
||||
<div class="prompt-reader-container">
|
||||
<el-form :model="tabStorage.formData" :rules="formRules" ref="formRef" label-position="top">
|
||||
<el-form-item v-for="param in currentPrompt?.params" :key="param.name"
|
||||
<el-form-item v-for="param in currentPrompt?.arguments" :key="param.name"
|
||||
:label="param.name" :prop="param.name">
|
||||
<el-input v-if="param.type === 'string'" v-model="tabStorage.formData[param.name]"
|
||||
:placeholder="param.placeholder || `请输入${param.name}`"
|
||||
<el-input v-model="tabStorage.formData[param.name]"
|
||||
:placeholder="t('enter') +' ' + param.name"
|
||||
@keydown.enter.prevent="handleSubmit"
|
||||
/>
|
||||
|
||||
<el-input-number v-else-if="param.type === 'number'" v-model="tabStorage.formData[param.name]"
|
||||
:placeholder="param.placeholder || `请输入${param.name}`"
|
||||
@keydown.enter.prevent="handleSubmit"
|
||||
/>
|
||||
|
||||
<el-switch v-else-if="param.type === 'boolean'" v-model="tabStorage.formData[param.name]" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
@ -36,10 +29,8 @@ import { defineComponent, defineProps, defineEmits, watch, ref, computed, reacti
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { tabs } from '../panel';
|
||||
import { promptsManager, type PromptStorage } from './prompts';
|
||||
import type { PromptStorage } from './prompts';
|
||||
import type { PromptsGetResponse } from '@/hook/type';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { getDefaultValue, normaliseJavascriptType } from '@/hook/mcp';
|
||||
import { mcpClientAdapter } from '@/views/connect/core';
|
||||
|
||||
defineComponent({ name: 'prompt-reader' });
|
||||
@ -65,6 +56,7 @@ if (props.tabId >= 0) {
|
||||
tabStorage = tabs.content[props.tabId].storage as PromptStorage;
|
||||
} else {
|
||||
tabStorage = reactive({
|
||||
activeNames: [0],
|
||||
currentPromptName: props.currentPromptName || '',
|
||||
formData: {},
|
||||
lastPromptGetResponse: undefined
|
||||
@ -80,26 +72,18 @@ const loading = ref(false);
|
||||
const responseData = ref<PromptsGetResponse>();
|
||||
|
||||
const currentPrompt = computed(() => {
|
||||
const template = promptsManager.templates.find(template => template.name === tabStorage.currentPromptName);
|
||||
const name = template?.name || '';
|
||||
const params = template?.arguments || [];
|
||||
|
||||
const viewParams = params.map(param => ({
|
||||
name: param.name,
|
||||
type: 'string',
|
||||
placeholder: t('enter') +' ' + param.name,
|
||||
required: param.required
|
||||
}));
|
||||
|
||||
return {
|
||||
name,
|
||||
params: viewParams
|
||||
};
|
||||
for (const client of mcpClientAdapter.clients) {
|
||||
const prompt = client.promptTemplates?.get(tabStorage.currentPromptName);
|
||||
if (prompt) {
|
||||
return prompt;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const formRules = computed<FormRules>(() => {
|
||||
const rules: FormRules = {}
|
||||
currentPrompt.value?.params.forEach(param => {
|
||||
currentPrompt.value?.arguments.forEach(param => {
|
||||
rules[param.name] = [
|
||||
{
|
||||
message: `${param.name} 是必填字段`,
|
||||
@ -112,15 +96,12 @@ const formRules = computed<FormRules>(() => {
|
||||
});
|
||||
|
||||
const initFormData = () => {
|
||||
|
||||
if (!currentPrompt.value?.params) return;
|
||||
|
||||
if (!currentPrompt.value?.arguments) return;
|
||||
const newSchemaDataForm: Record<string, number | boolean | string> = {};
|
||||
|
||||
currentPrompt.value.params.forEach(param => {
|
||||
newSchemaDataForm[param.name] = getDefaultValue(param);
|
||||
const originType = normaliseJavascriptType(typeof tabStorage.formData[param.name]);
|
||||
if (tabStorage.formData[param.name]!== undefined && originType === param.type) {
|
||||
currentPrompt.value.arguments.forEach(param => {
|
||||
newSchemaDataForm[param.name] = '';
|
||||
if (tabStorage.formData[param.name]!== undefined) {
|
||||
newSchemaDataForm[param.name] = tabStorage.formData[param.name];
|
||||
}
|
||||
});
|
||||
@ -134,7 +115,7 @@ const resetForm = () => {
|
||||
async function handleSubmit() {
|
||||
|
||||
const res = await mcpClientAdapter.readPromptTemplate(
|
||||
currentPrompt.value.name,
|
||||
currentPrompt.value?.name || '',
|
||||
JSON.parse(JSON.stringify(tabStorage.formData))
|
||||
);
|
||||
|
||||
|
@ -1,40 +1,44 @@
|
||||
<template>
|
||||
<el-collapse :expand-icon-position="'left'" v-model="tabStorage.activeNames">
|
||||
<el-collapse-item v-for="(client, index) in mcpClientAdapter.clients" :name="index" :class="[]">
|
||||
|
||||
<!-- header -->
|
||||
<template #title>
|
||||
<h3 class="resource-template">
|
||||
<code>prompts/list</code>
|
||||
<span
|
||||
@click="reloadPrompts({ first: false })"
|
||||
class="iconfont icon-restart"
|
||||
></span>
|
||||
<span @click.stop="reloadPrompts(client, { first: false })" class="iconfont icon-restart"></span>
|
||||
</h3>
|
||||
|
||||
</template>
|
||||
|
||||
<!-- body -->
|
||||
|
||||
<div class="prompt-template-container-scrollbar">
|
||||
<el-scrollbar height="500px">
|
||||
<div class="prompt-template-container">
|
||||
<div
|
||||
class="item"
|
||||
<div class="item"
|
||||
:class="{ 'active': props.tabId >= 0 && tabStorage.currentPromptName === template.name }"
|
||||
v-for="template of promptsManager.templates"
|
||||
:key="template.name"
|
||||
@click="handleClick(template)"
|
||||
>
|
||||
v-for="template of client.promptTemplates?.values()" :key="template.name"
|
||||
@click="handleClick(template)">
|
||||
<span>{{ template.name }}</span>
|
||||
<span>{{ template.description || '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import type { CasualRestAPI, PromptTemplate, PromptsListResponse } from '@/hook/type';
|
||||
import { onMounted, onUnmounted, defineProps, defineEmits, reactive } from 'vue';
|
||||
import type { PromptTemplate } from '@/hook/type';
|
||||
import { onMounted, defineProps, defineEmits, reactive, ref, type Reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { promptsManager, type PromptStorage } from './prompts';
|
||||
import type { PromptStorage } from './prompts';
|
||||
import { tabs } from '../panel';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { McpClient, mcpClientAdapter } from '@/views/connect/core';
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
@ -53,16 +57,15 @@ if (props.tabId >= 0) {
|
||||
tabStorage = tab.storage as PromptStorage;
|
||||
} else {
|
||||
tabStorage = reactive({
|
||||
activeNames: [0],
|
||||
currentPromptName: '',
|
||||
formData: {},
|
||||
lastPromptGetResponse: undefined
|
||||
});
|
||||
}
|
||||
|
||||
function reloadPrompts(option: { first: boolean }) {
|
||||
bridge.postMessage({
|
||||
command: 'prompts/list'
|
||||
});
|
||||
async function reloadPrompts(client: Reactive<McpClient>, option: { first: boolean }) {
|
||||
await client.getPromptTemplates({ cache: false });
|
||||
|
||||
if (!option.first) {
|
||||
ElMessage({
|
||||
@ -77,32 +80,21 @@ function reloadPrompts(option: { first: boolean }) {
|
||||
function handleClick(prompt: PromptTemplate) {
|
||||
tabStorage.currentPromptName = prompt.name;
|
||||
tabStorage.lastPromptGetResponse = undefined;
|
||||
|
||||
emits('prompt-selected', prompt);
|
||||
}
|
||||
|
||||
let commandCancel: (() => void);
|
||||
|
||||
onMounted(() => {
|
||||
commandCancel = bridge.addCommandListener('prompts/list', (data: CasualRestAPI<PromptsListResponse>) => {
|
||||
promptsManager.templates = data.msg.prompts || [];
|
||||
|
||||
const targetPrompt = promptsManager.templates.find(template => template.name === tabStorage.currentPromptName);
|
||||
|
||||
if (targetPrompt === undefined) {
|
||||
tabStorage.currentPromptName = promptsManager.templates[0].name;
|
||||
tabStorage.lastPromptGetResponse = undefined;
|
||||
onMounted(async () => {
|
||||
for (const client of mcpClientAdapter.clients) {
|
||||
await client.getPromptTemplates();
|
||||
}
|
||||
}, { once: false });
|
||||
|
||||
reloadPrompts({ first: true });
|
||||
if (tabStorage.currentPromptName === undefined) {
|
||||
const masterNode = mcpClientAdapter.masterNode;
|
||||
const prompt = masterNode.promptTemplates?.values().next();
|
||||
tabStorage.currentPromptName = prompt?.value?.name || '';
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (commandCancel){
|
||||
commandCancel();
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -1,15 +1,7 @@
|
||||
import type { PromptsGetResponse, PromptTemplate } from '@/hook/type';
|
||||
import { reactive } from 'vue';
|
||||
|
||||
export const promptsManager = reactive<{
|
||||
current: PromptTemplate | undefined
|
||||
templates: PromptTemplate[]
|
||||
}>({
|
||||
current: undefined,
|
||||
templates: []
|
||||
});
|
||||
import type { PromptsGetResponse } from '@/hook/type';
|
||||
|
||||
export interface PromptStorage {
|
||||
activeNames: any[];
|
||||
currentPromptName: string;
|
||||
lastPromptGetResponse?: PromptsGetResponse;
|
||||
formData: Record<string, any>;
|
||||
|
@ -1,19 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3>{{ currentResource.template?.name }}</h3>
|
||||
<h3>{{ currentResource?.name }}</h3>
|
||||
</div>
|
||||
<div class="resource-reader-container">
|
||||
<el-form :model="tabStorage.formData" :rules="formRules" ref="formRef" label-position="top">
|
||||
<el-form-item v-for="param in currentResource?.params" :key="param.name" :label="param.name"
|
||||
:prop="param.name">
|
||||
<!-- 根据不同类型渲染不同输入组件 -->
|
||||
<el-input v-if="param.type === 'string'" v-model="tabStorage.formData[param.name]"
|
||||
:placeholder="param.placeholder || `请输入${param.name}`" @keydown.enter.prevent="handleSubmit" />
|
||||
|
||||
<el-input-number v-else-if="param.type === 'number'" v-model="tabStorage.formData[param.name]"
|
||||
:placeholder="param.placeholder || `请输入${param.name}`" @keydown.enter.prevent="handleSubmit" />
|
||||
|
||||
<el-switch v-else-if="param.type === 'boolean'" v-model="tabStorage.formData[param.name]" />
|
||||
<el-form-item v-for="param in currentResource?.params" :key="param" :label="param"
|
||||
:prop="param">
|
||||
<el-input v-model="tabStorage.formData[param]"
|
||||
:placeholder="t('enter') + ' ' + param"
|
||||
@keydown.enter.prevent="handleSubmit"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="tabStorage.currentType === 'template'">
|
||||
@ -38,9 +34,8 @@ import { defineComponent, defineProps, watch, ref, computed, reactive, defineEmi
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { tabs } from '../panel';
|
||||
import { parseResourceTemplate, resourcesManager, type ResourceStorage } from './resources';
|
||||
import { parseResourceTemplate, type ResourceStorage } from './resources';
|
||||
import type{ ResourcesReadResponse } from '@/hook/type';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import { getDefaultValue, normaliseJavascriptType } from '@/hook/mcp';
|
||||
import { mcpClientAdapter } from '@/views/connect/core';
|
||||
|
||||
@ -68,6 +63,8 @@ if (props.tabId >= 0) {
|
||||
tabStorage = tab.storage as ResourceStorage;
|
||||
} else {
|
||||
tabStorage = reactive({
|
||||
activeNames: [0],
|
||||
templateActiveNames: [0],
|
||||
currentType: 'resource',
|
||||
currentResourceName: props.currentResourceName || '',
|
||||
formData: {},
|
||||
@ -86,30 +83,39 @@ const responseData = ref<ResourcesReadResponse>();
|
||||
|
||||
// 当前 resource 的模板参数
|
||||
const currentResource = computed(() => {
|
||||
const template = resourcesManager.templates.find(template => template.name === tabStorage.currentResourceName);
|
||||
const { params, fill } = parseResourceTemplate(template?.uriTemplate || '');
|
||||
|
||||
const viewParams = params.map(param => ({
|
||||
name: param,
|
||||
type: 'string',
|
||||
placeholder: t('enter') + ' ' + param,
|
||||
required: true
|
||||
}));
|
||||
|
||||
for (const client of mcpClientAdapter.clients) {
|
||||
const resource = client.resources?.get(tabStorage.currentResourceName);
|
||||
if (resource) {
|
||||
return {
|
||||
template,
|
||||
params: viewParams,
|
||||
name: resource.name,
|
||||
template: resource,
|
||||
params: [],
|
||||
// resources 用不到 fill 函数
|
||||
fill: () => ''
|
||||
};
|
||||
}
|
||||
|
||||
const resourceTemplate = client.resourceTemplates?.get(tabStorage.currentResourceName);
|
||||
if (resourceTemplate) {
|
||||
const { params, fill } = parseResourceTemplate(resourceTemplate.uriTemplate);
|
||||
return {
|
||||
name: resourceTemplate.name,
|
||||
template: resourceTemplate,
|
||||
params,
|
||||
fill
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = computed<FormRules>(() => {
|
||||
const rules: FormRules = {}
|
||||
currentResource.value?.params.forEach(param => {
|
||||
rules[param.name] = [
|
||||
rules[param] = [
|
||||
{
|
||||
message: `${param.name} 是必填字段`,
|
||||
message: `${param} 是必填字段`,
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
@ -121,15 +127,11 @@ const formRules = computed<FormRules>(() => {
|
||||
// 初始化表单数据
|
||||
const initFormData = () => {
|
||||
if (!currentResource.value?.params) return;
|
||||
|
||||
const newSchemaDataForm: Record<string, number | boolean | string> = {};
|
||||
|
||||
currentResource.value.params.forEach(param => {
|
||||
newSchemaDataForm[param.name] = getDefaultValue(param);
|
||||
const originType = normaliseJavascriptType(typeof tabStorage.formData[param.name]);
|
||||
|
||||
if (tabStorage.formData[param.name] !== undefined && originType === param.type) {
|
||||
newSchemaDataForm[param.name] = tabStorage.formData[param.name];
|
||||
newSchemaDataForm[param] = '';
|
||||
if (tabStorage.formData[param] !== undefined) {
|
||||
newSchemaDataForm[param] = tabStorage.formData[param];
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -142,21 +144,23 @@ const resetForm = () => {
|
||||
|
||||
function getUri() {
|
||||
if (tabStorage.currentType === 'template') {
|
||||
const fillFn = currentResource.value.fill;
|
||||
const fillFn = currentResource.value?.fill || ((str: any) => str);
|
||||
const uri = fillFn(tabStorage.formData);
|
||||
return uri;
|
||||
}
|
||||
|
||||
const currentResourceName = props.tabId >= 0 ? tabStorage.currentResourceName : props.currentResourceName;
|
||||
const targetResource = resourcesManager.resources.find(resources => resources.name === currentResourceName);
|
||||
return targetResource?.uri;
|
||||
for (const client of mcpClientAdapter.clients) {
|
||||
const resource = client.resources?.get(tabStorage.currentResourceName);
|
||||
if (resource) {
|
||||
return resource.uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function handleSubmit() {
|
||||
const uri = getUri();
|
||||
const res = await mcpClientAdapter.readResource(uri);
|
||||
|
||||
tabStorage.lastResourceReadResponse = res;
|
||||
emits('resource-get-response', res);
|
||||
}
|
||||
|
@ -1,22 +1,23 @@
|
||||
<template>
|
||||
<el-collapse :expand-icon-position="'left'" v-model="tabStorage.templateActiveNames">
|
||||
<el-collapse-item v-for="(client, index) in mcpClientAdapter.clients" :name="index" :class="[]">
|
||||
|
||||
<!-- header -->
|
||||
<template #title>
|
||||
<h3 class="resource-template">
|
||||
<code>resources/templates/list</code>
|
||||
<span
|
||||
class="iconfont icon-restart"
|
||||
@click="reloadResources({ first: false })"
|
||||
></span>
|
||||
<span class="iconfont icon-restart" @click="reloadResources(client, { first: false })"></span>
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<!-- body -->
|
||||
<div class="resource-template-container-scrollbar">
|
||||
<el-scrollbar height="500px" v-if="resourcesManager.templates.length > 0">
|
||||
<el-scrollbar height="500px" v-if="(client.resourceTemplates?.size || 0) > 0">
|
||||
<div class="resource-template-container">
|
||||
<div
|
||||
class="item"
|
||||
<div class="item"
|
||||
:class="{ 'active': props.tabId >= 0 && tabStorage.currentType === 'template' && tabStorage.currentResourceName === template.name }"
|
||||
v-for="template of resourcesManager.templates"
|
||||
:key="template.name"
|
||||
@click="handleClick(template)"
|
||||
>
|
||||
v-for="template of client.resourceTemplates?.values()" :key="template.name"
|
||||
@click="handleClick(template)">
|
||||
<span>{{ template.name }}</span>
|
||||
<span>{{ template.description || '' }}</span>
|
||||
</div>
|
||||
@ -26,16 +27,20 @@
|
||||
empty
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import type { CasualRestAPI, ResourceTemplate, ResourceTemplatesListResponse } from '@/hook/type';
|
||||
import { onMounted, onUnmounted, defineProps, ref, reactive } from 'vue';
|
||||
import { onMounted, onUnmounted, defineProps, ref, reactive, type Reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { resourcesManager, type ResourceStorage } from './resources';
|
||||
import type { ResourceStorage } from './resources';
|
||||
import { tabs } from '../panel';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { McpClient, mcpClientAdapter } from '@/views/connect/core';
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
const { t } = useI18n();
|
||||
@ -54,6 +59,8 @@ if (props.tabId >= 0) {
|
||||
tabStorage = tab.storage as ResourceStorage;
|
||||
} else {
|
||||
tabStorage = reactive({
|
||||
activeNames: [0],
|
||||
templateActiveNames: [0],
|
||||
currentType: 'template',
|
||||
currentResourceName: '',
|
||||
formData: {},
|
||||
@ -61,10 +68,9 @@ if (props.tabId >= 0) {
|
||||
});
|
||||
}
|
||||
|
||||
function reloadResources(option: { first: boolean }) {
|
||||
bridge.postMessage({
|
||||
command: 'resources/templates/list'
|
||||
});
|
||||
async function reloadResources(client: Reactive<McpClient>, option: { first: boolean }) {
|
||||
|
||||
await client.getResourceTemplates({ cache: false });
|
||||
|
||||
if (!option.first) {
|
||||
ElMessage({
|
||||
@ -82,29 +88,18 @@ function handleClick(template: ResourceTemplate) {
|
||||
tabStorage.lastResourceReadResponse = undefined;
|
||||
}
|
||||
|
||||
let commandCancel: (() => void);
|
||||
|
||||
onMounted(() => {
|
||||
commandCancel = bridge.addCommandListener('resources/templates/list', (data: CasualRestAPI<ResourceTemplatesListResponse>) => {
|
||||
resourcesManager.templates = data.msg.resourceTemplates || [];
|
||||
|
||||
if (tabStorage.currentType === 'template') {
|
||||
const targetResource = resourcesManager.templates.find(template => template.name === tabStorage.currentResourceName);
|
||||
if (targetResource === undefined) {
|
||||
tabStorage.currentResourceName = resourcesManager.templates[0]?.name;
|
||||
tabStorage.lastResourceReadResponse = undefined;
|
||||
onMounted(async () => {
|
||||
for (const client of mcpClientAdapter.clients) {
|
||||
await client.getResourceTemplates({ cache: false });
|
||||
}
|
||||
}
|
||||
}, { once: false });
|
||||
|
||||
reloadResources({ first: true });
|
||||
if (tabStorage.currentResourceName === undefined && tabStorage.currentType === 'template') {
|
||||
const masterNode = mcpClientAdapter.masterNode;
|
||||
const resourceTemplate = masterNode?.resourceTemplates?.values().next();
|
||||
tabStorage.currentResourceName = resourceTemplate?.value?.name || '';
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (commandCancel){
|
||||
commandCancel();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -183,5 +178,4 @@ h3.resource-template .iconfont.icon-restart:hover {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
</style>
|
@ -1,40 +1,42 @@
|
||||
<template>
|
||||
<el-collapse :expand-icon-position="'left'" v-model="tabStorage.activeNames">
|
||||
<el-collapse-item v-for="(client, index) in mcpClientAdapter.clients" :name="index" :class="[]">
|
||||
|
||||
<!-- header -->
|
||||
<template #title>
|
||||
<h3 class="resource-template">
|
||||
<code>resources/list</code>
|
||||
<span
|
||||
class="iconfont icon-restart"
|
||||
@click="reloadResources({ first: false })"
|
||||
></span>
|
||||
<span class="iconfont icon-restart" @click="reloadResources(client, { first: false })"></span>
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<!-- body -->
|
||||
<div class="resource-template-container-scrollbar">
|
||||
<el-scrollbar height="500px">
|
||||
<div class="resource-template-container">
|
||||
<div
|
||||
class="item"
|
||||
<div class="item"
|
||||
:class="{ 'active': props.tabId >= 0 && tabStorage.currentType === 'resource' && tabStorage.currentResourceName === resource.name }"
|
||||
v-for="resource of resourcesManager.resources"
|
||||
:key="resource.uri"
|
||||
@click="handleClick(resource)"
|
||||
>
|
||||
v-for="resource of client.resources?.values()" :key="resource.uri"
|
||||
@click="handleClick(resource)">
|
||||
<span>{{ resource.name }}</span>
|
||||
<span>{{ resource.mimeType }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import type { CasualRestAPI, Resources, ResourcesListResponse } from '@/hook/type';
|
||||
import { onMounted, onUnmounted, defineProps, defineEmits, reactive } from 'vue';
|
||||
import type { Resources } from '@/hook/type';
|
||||
import { onMounted, defineProps, defineEmits, reactive, type Reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { resourcesManager, type ResourceStorage } from './resources';
|
||||
import type { ResourceStorage } from './resources';
|
||||
import { tabs } from '../panel';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { McpClient, mcpClientAdapter } from '@/views/connect/core';
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
@ -53,6 +55,8 @@ if (props.tabId >= 0) {
|
||||
tabStorage = tab.storage as ResourceStorage;
|
||||
} else {
|
||||
tabStorage = reactive({
|
||||
activeNames: [0],
|
||||
templateActiveNames: [0],
|
||||
currentType: 'resource',
|
||||
currentResourceName: '',
|
||||
formData: {},
|
||||
@ -60,10 +64,8 @@ if (props.tabId >= 0) {
|
||||
});
|
||||
}
|
||||
|
||||
function reloadResources(option: { first: boolean }) {
|
||||
bridge.postMessage({
|
||||
command: 'resources/list'
|
||||
});
|
||||
async function reloadResources(client: Reactive<McpClient>, option: { first: boolean }) {
|
||||
await client.getResources({ cache: false });
|
||||
|
||||
if (!option.first) {
|
||||
ElMessage({
|
||||
@ -83,35 +85,23 @@ async function handleClick(resource: Resources) {
|
||||
|
||||
// 更新资源
|
||||
if (props.tabId >= 0) {
|
||||
const bridge = useMessageBridge();
|
||||
const { code, msg } = await bridge.commandRequest('resources/read', { resourceUri: resource.uri });
|
||||
tabStorage.lastResourceReadResponse = msg;
|
||||
const res = await mcpClientAdapter.readResource(resource.uri);
|
||||
tabStorage.lastResourceReadResponse = res;
|
||||
}
|
||||
}
|
||||
|
||||
let commandCancel: (() => void);
|
||||
|
||||
onMounted(() => {
|
||||
commandCancel = bridge.addCommandListener('resources/list', (data: CasualRestAPI<ResourcesListResponse>) => {
|
||||
resourcesManager.resources = data.msg.resources || [];
|
||||
|
||||
if (tabStorage.currentType === 'resource') {
|
||||
const targetResource = resourcesManager.resources.find(resources => resources.name === tabStorage.currentResourceName);
|
||||
if (targetResource === undefined) {
|
||||
tabStorage.currentResourceName = resourcesManager.templates[0]?.name;
|
||||
tabStorage.lastResourceReadResponse = undefined;
|
||||
onMounted(async () => {
|
||||
for (const client of mcpClientAdapter.clients) {
|
||||
await client.getResources();
|
||||
}
|
||||
}
|
||||
}, { once: false });
|
||||
|
||||
reloadResources({ first: true });
|
||||
if (tabStorage.currentResourceName === undefined && tabStorage.currentType === 'resource') {
|
||||
const masterNode = mcpClientAdapter.masterNode;
|
||||
const resource = masterNode.resources?.values().next();
|
||||
tabStorage.currentResourceName = resource?.value?.name || '';
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (commandCancel){
|
||||
commandCancel();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -190,5 +180,4 @@ h3.resource-template .iconfont.icon-restart:hover {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
</style>
|
@ -1,18 +1,8 @@
|
||||
import type { ResourcesReadResponse, ResourceTemplate, Resources } from '@/hook/type';
|
||||
import { reactive } from 'vue';
|
||||
|
||||
|
||||
export const resourcesManager = reactive<{
|
||||
current: ResourceTemplate | undefined
|
||||
templates: ResourceTemplate[],
|
||||
resources: Resources[]
|
||||
}>({
|
||||
current: undefined,
|
||||
templates: [],
|
||||
resources: []
|
||||
});
|
||||
import type { ResourcesReadResponse } from '@/hook/type';
|
||||
|
||||
export interface ResourceStorage {
|
||||
activeNames: any[];
|
||||
templateActiveNames: any[];
|
||||
currentType: 'resource' | 'template';
|
||||
currentResourceName: string;
|
||||
lastResourceReadResponse?: ResourcesReadResponse;
|
||||
|
@ -1,18 +1,16 @@
|
||||
<template>
|
||||
|
||||
<el-collapse :expand-icon-position="'left'" v-model="activeNames">
|
||||
<el-collapse :expand-icon-position="'left'" v-model="tabStorage.activeNames">
|
||||
<el-collapse-item v-for="(client, index) in mcpClientAdapter.clients" :name="index" :class="[]">
|
||||
|
||||
<!-- header -->
|
||||
<template #title>
|
||||
<h3 class="resource-template">
|
||||
<code>tools/list</code>
|
||||
<span class="iconfont icon-restart" @click="reloadTools({ first: false })"></span>
|
||||
<span class="iconfont icon-restart" @click.stop="reloadTools(client, { first: false })"></span>
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<!-- body -->
|
||||
|
||||
<div class="tool-list-container-scrollbar">
|
||||
<el-scrollbar height="500px">
|
||||
<div class="tool-list-container">
|
||||
@ -29,16 +27,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
import type { CasualRestAPI, ToolsListResponse } from '@/hook/type';
|
||||
import { onMounted, onUnmounted, defineProps, ref } from 'vue';
|
||||
import { onMounted, defineProps, ref, type Reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { ToolStorage } from './tools';
|
||||
import { tabs } from '../panel';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { mcpClientAdapter } from '@/views/connect/core';
|
||||
import { McpClient, mcpClientAdapter } from '@/views/connect/core';
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
@ -51,12 +46,8 @@ const props = defineProps({
|
||||
const tab = tabs.content[props.tabId];
|
||||
const tabStorage = tab.storage as ToolStorage;
|
||||
|
||||
const activeNames = ref<any[]>([0]);
|
||||
|
||||
function reloadTools(option: { first: boolean }) {
|
||||
bridge.postMessage({
|
||||
command: 'tools/list'
|
||||
});
|
||||
async function reloadTools(client: Reactive<McpClient>, option: { first: boolean }) {
|
||||
await client.getTools({ cache: false });
|
||||
|
||||
if (!option.first) {
|
||||
ElMessage({
|
||||
@ -69,8 +60,6 @@ function reloadTools(option: { first: boolean }) {
|
||||
}
|
||||
|
||||
function handleClick(tool: { name: string }) {
|
||||
console.log('enter');
|
||||
|
||||
tabStorage.currentToolName = tool.name;
|
||||
tabStorage.lastToolCallResponse = undefined;
|
||||
}
|
||||
@ -79,6 +68,12 @@ onMounted(async () => {
|
||||
for (const client of mcpClientAdapter.clients) {
|
||||
await client.getTools();
|
||||
}
|
||||
|
||||
if (tabStorage.currentToolName === undefined) {
|
||||
const masterNode = mcpClientAdapter.masterNode;
|
||||
const tool = masterNode.tools?.values().next();
|
||||
tabStorage.currentToolName = tool?.value?.name || '';
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@ -4,6 +4,7 @@ import type { ToolsListResponse, ToolCallResponse, CasualRestAPI } from '@/hook/
|
||||
import { mcpClientAdapter } from '@/views/connect/core';
|
||||
|
||||
export interface ToolStorage {
|
||||
activeNames: any[];
|
||||
currentToolName: string;
|
||||
lastToolCallResponse?: ToolCallResponse | string;
|
||||
formData: Record<string, any>;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useMessageBridge } from "@/api/message-bridge";
|
||||
import { reactive, type Reactive } from "vue";
|
||||
import type { IConnectionResult, ConnectionTypeOptionItem, IConnectionArgs, IConnectionEnvironment, McpOptions } from "./type";
|
||||
import type { IConnectionResult, ConnectionTypeOptionItem, IConnectionArgs, IConnectionEnvironment, McpOptions, McpClientGetCommonOption } from "./type";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { loadPanels } from "@/hook/panel";
|
||||
import { getPlatform } from "@/api/platform";
|
||||
@ -111,8 +111,13 @@ export class McpClient {
|
||||
return env;
|
||||
}
|
||||
|
||||
public async getTools() {
|
||||
if (this.tools) {
|
||||
public async getTools(option?: McpClientGetCommonOption) {
|
||||
|
||||
const {
|
||||
cache = true
|
||||
} = option || {};
|
||||
|
||||
if (cache && this.tools) {
|
||||
return this.tools;
|
||||
}
|
||||
|
||||
@ -131,14 +136,20 @@ export class McpClient {
|
||||
return this.tools;
|
||||
}
|
||||
|
||||
public async getPromptTemplates() {
|
||||
if (this.promptTemplates) {
|
||||
public async getPromptTemplates(option?: McpClientGetCommonOption) {
|
||||
|
||||
const {
|
||||
cache = true
|
||||
} = option || {};
|
||||
|
||||
if (cache && this.promptTemplates) {
|
||||
return this.promptTemplates;
|
||||
}
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
|
||||
const { code, msg } = await bridge.commandRequest<PromptsListResponse>('prompts/list', { clientId: this.clientId });
|
||||
|
||||
if (code!== 200) {
|
||||
return new Map<string, PromptTemplate>();
|
||||
}
|
||||
@ -151,8 +162,13 @@ export class McpClient {
|
||||
return this.promptTemplates;
|
||||
}
|
||||
|
||||
public async getResources() {
|
||||
if (this.resources) {
|
||||
public async getResources(option?: McpClientGetCommonOption) {
|
||||
|
||||
const {
|
||||
cache = true
|
||||
} = option || {};
|
||||
|
||||
if (cache && this.resources) {
|
||||
return this.resources;
|
||||
}
|
||||
|
||||
@ -170,8 +186,13 @@ export class McpClient {
|
||||
return this.resources;
|
||||
}
|
||||
|
||||
public async getResourceTemplates() {
|
||||
if (this.resourceTemplates) {
|
||||
public async getResourceTemplates(option?: McpClientGetCommonOption) {
|
||||
|
||||
const {
|
||||
cache = true
|
||||
} = option || {};
|
||||
|
||||
if (cache && this.resourceTemplates) {
|
||||
return this.resourceTemplates;
|
||||
}
|
||||
|
||||
@ -184,6 +205,7 @@ export class McpClient {
|
||||
this.resourceTemplates = new Map<string, ResourceTemplate>();
|
||||
msg.resourceTemplates.forEach(template => {
|
||||
this.resourceTemplates!.set(template.name, template);
|
||||
|
||||
});
|
||||
return this.resourceTemplates;
|
||||
}
|
||||
|
@ -71,3 +71,6 @@ export interface ConnectionResult {
|
||||
version: string
|
||||
}
|
||||
|
||||
export interface McpClientGetCommonOption {
|
||||
cache: boolean;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user