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