update
This commit is contained in:
parent
eed67f6eb5
commit
37d0194fab
@ -22,7 +22,7 @@ const bridge = useMessageBridge();
|
||||
bridge.addCommandListener('hello', data => {
|
||||
pinkLog(`${data.name} 上线`);
|
||||
pinkLog(`version: ${data.version}`);
|
||||
});
|
||||
}, { once: true });
|
||||
|
||||
|
||||
// 发送消息
|
||||
@ -47,14 +47,11 @@ onMounted(() => {
|
||||
connectionArgs.commandString = 'uv run mcp run ../servers/main.py';
|
||||
connectionMethods.current = 'STDIO';
|
||||
|
||||
let handler: (() => void);
|
||||
handler = bridge.addCommandListener('connect', data => {
|
||||
bridge.addCommandListener('connect', data => {
|
||||
const { code, msg } = data;
|
||||
connectionResult.success = (code === 200);
|
||||
connectionResult.logString = msg;
|
||||
|
||||
handler();
|
||||
});
|
||||
}, { once: true });
|
||||
|
||||
setTimeout(() => {
|
||||
doConnect();
|
||||
|
@ -12,6 +12,10 @@ export type CommandHandler = (data: any) => void;
|
||||
|
||||
export const acquireVsCodeApi = (window as any)['acquireVsCodeApi'];
|
||||
|
||||
interface AddCommandListenerOption {
|
||||
once: boolean // 只调用一次就销毁
|
||||
}
|
||||
|
||||
class MessageBridge {
|
||||
private ws: WebSocket | null = null;
|
||||
private handlers = new Map<string, Set<CommandHandler>>();
|
||||
@ -94,17 +98,21 @@ class MessageBridge {
|
||||
|
||||
/**
|
||||
* @description 注册一个命令的执行器
|
||||
* @param handler
|
||||
* @returns
|
||||
*/
|
||||
public addCommandListener(command: string, commandHandler: CommandHandler) {
|
||||
public addCommandListener(command: string, commandHandler: CommandHandler, option: AddCommandListenerOption) {
|
||||
if (!this.handlers.has(command)) {
|
||||
this.handlers.set(command, new Set<CommandHandler>());
|
||||
}
|
||||
const commandHandlers = this.handlers.get(command)!;
|
||||
commandHandlers.add(commandHandler);
|
||||
|
||||
return () => commandHandlers.delete(commandHandler);
|
||||
const wrapperCommandHandler = option.once ? (data: any) => {
|
||||
commandHandler(data);
|
||||
commandHandlers.delete(wrapperCommandHandler);
|
||||
} : commandHandler;
|
||||
|
||||
commandHandlers.add(wrapperCommandHandler);
|
||||
return () => commandHandlers.delete(wrapperCommandHandler);
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
|
@ -16,6 +16,10 @@
|
||||
<ResourceReader
|
||||
:tab-id="props.tabId"
|
||||
></ResourceReader>
|
||||
|
||||
<ResourceLogger
|
||||
:tab-id="props.tabId"
|
||||
></ResourceLogger>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -24,6 +28,7 @@
|
||||
import { defineProps } from 'vue';
|
||||
import ResourceTemplates from './resource-templates.vue';
|
||||
import ResourceReader from './resouce-reader.vue';
|
||||
import ResourceLogger from './resource-logger.vue';
|
||||
|
||||
const props = defineProps({
|
||||
tabId: {
|
||||
@ -40,6 +45,8 @@ const props = defineProps({
|
||||
.resource-module {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.resource-module .left {
|
||||
|
@ -1,24 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3>{{ currentResource.template?.name }}</h3>
|
||||
</div>
|
||||
<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)">
|
||||
:label="param.name" :prop="param.name">
|
||||
<!-- 根据不同类型渲染不同输入组件 -->
|
||||
<el-input v-if="param.type === 'string'" v-model="formData[param.name]"
|
||||
:placeholder="param.description || `请输入${param.name}`" />
|
||||
:placeholder="param.placeholder || `请输入${param.name}`" />
|
||||
|
||||
<el-input-number v-else-if="param.type === 'number'" v-model="formData[param.name]"
|
||||
:placeholder="param.description || `请输入${param.name}`" />
|
||||
:placeholder="param.placeholder || `请输入${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
|
||||
{{ t('read-resource') }}
|
||||
</el-button>
|
||||
<el-button @click="resetForm">
|
||||
reset
|
||||
{{ t('reset') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -27,14 +30,17 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, defineProps, watch, ref, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { tabs } from '../panel';
|
||||
import { ResourceStorage } from './resources';
|
||||
import { ResourcesReadResponse } from '@/hook/type';
|
||||
import { parseResourceTemplate, resourcesManager, ResourceStorage } from './resources';
|
||||
import { CasualRestAPI, ResourcesReadResponse } from '@/hook/type';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
|
||||
defineComponent({ name: 'resource-reader' });
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
tabId: {
|
||||
type: Number,
|
||||
@ -51,8 +57,24 @@ const formData = ref<Record<string, any>>({});
|
||||
const loading = ref(false);
|
||||
const responseData = ref<ResourcesReadResponse>();
|
||||
|
||||
// 当前资源协议
|
||||
const currentResource = computed(() => props.resource);
|
||||
// 当前 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
|
||||
}));
|
||||
|
||||
return {
|
||||
template,
|
||||
params: viewParams,
|
||||
fill
|
||||
};
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = computed<FormRules>(() => {
|
||||
@ -60,7 +82,6 @@ const formRules = computed<FormRules>(() => {
|
||||
currentResource.value?.params.forEach(param => {
|
||||
rules[param.name] = [
|
||||
{
|
||||
required: param.required,
|
||||
message: `${param.name} 是必填字段`,
|
||||
trigger: 'blur'
|
||||
}
|
||||
@ -68,30 +89,8 @@ const formRules = computed<FormRules>(() => {
|
||||
});
|
||||
|
||||
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 = () => {
|
||||
@ -109,8 +108,20 @@ const resetForm = () => {
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
console.log('submit');
|
||||
function handleSubmit() {
|
||||
const fillFn = currentResource.value.fill;
|
||||
const uri = fillFn(formData.value);
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
|
||||
bridge.addCommandListener('resources/read', (data: CasualRestAPI<ResourcesReadResponse>) => {
|
||||
tabStorage.lastResourceReadResponse = data.msg;
|
||||
}, { once: true });
|
||||
|
||||
bridge.postMessage({
|
||||
command: 'resources/read',
|
||||
data: { resourceUri: uri }
|
||||
});
|
||||
}
|
||||
|
||||
// 监听资源变化重置表单
|
||||
@ -121,4 +132,11 @@ watch(() => tabStorage.currentResourceName, () => {
|
||||
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style>
|
||||
.resource-reader-container {
|
||||
background-color: var(--background);
|
||||
padding: 10px 12px;
|
||||
border-radius: .5em;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
61
app/src/components/main-panel/resource/resource-logger.vue
Normal file
61
app/src/components/main-panel/resource/resource-logger.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="resource-logger">
|
||||
<span>{{ "Response" }}</span>
|
||||
<el-scrollbar height="300px">
|
||||
<div
|
||||
class="output-content"
|
||||
contenteditable="false"
|
||||
>
|
||||
<span v-for="(content, index) of tabStorage.lastResourceReadResponse?.contents || []" :key="index">
|
||||
{{ content.text }}
|
||||
</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, defineProps } from 'vue';
|
||||
import { tabs } from '../panel';
|
||||
import { ResourceStorage } from './resources';
|
||||
|
||||
defineComponent({ name: 'resource-logger' });
|
||||
|
||||
const props = defineProps({
|
||||
tabId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const tab = tabs.content[props.tabId];
|
||||
const tabStorage = tab.storage as ResourceStorage;
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.resource-logger {
|
||||
border-radius: .5em;
|
||||
background-color: var(--background);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.resource-logger .output-content {
|
||||
border-radius: .5em;
|
||||
padding: 15px;
|
||||
min-height: 300px;
|
||||
height: fit-content;
|
||||
font-family: var(--code-font-family);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
user-select: text;
|
||||
cursor: text;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
background-color: var(--sidebar);
|
||||
}
|
||||
|
||||
</style>
|
@ -25,7 +25,6 @@ import { resourcesManager, ResourceStorage } from './resources';
|
||||
import { tabs } from '../panel';
|
||||
|
||||
const bridge = useMessageBridge();
|
||||
let cancelListener: undefined | (() => void) = undefined;
|
||||
|
||||
const props = defineProps({
|
||||
tabId: {
|
||||
@ -45,24 +44,24 @@ function reloadResources() {
|
||||
|
||||
function handleClick(template: ResourceTemplate) {
|
||||
tabStorage.currentResourceName = template.name;
|
||||
// TODO: 恢复这部分响应?
|
||||
tabStorage.lastResourceReadResponse = undefined;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
cancelListener = bridge.addCommandListener('resources/templates/list', (data: CasualRestAPI<ResourceTemplatesListResponse>) => {
|
||||
bridge.addCommandListener('resources/templates/list', (data: CasualRestAPI<ResourceTemplatesListResponse>) => {
|
||||
resourcesManager.templates = data.msg.resourceTemplates || [];
|
||||
|
||||
if (resourcesManager.templates.length > 0) {
|
||||
tabStorage.currentResourceName = resourcesManager.templates[0].name;
|
||||
// TODO: 恢复这部分响应?
|
||||
tabStorage.lastResourceReadResponse = undefined;
|
||||
}
|
||||
});
|
||||
}, { once: true });
|
||||
|
||||
reloadResources();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (cancelListener) {
|
||||
cancelListener();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ResourceTemplate, ResourceTemplatesListResponse } from '@/hook/type';
|
||||
import { ResourcesReadResponse, ResourceTemplate, ResourceTemplatesListResponse } from '@/hook/type';
|
||||
import { reactive } from 'vue';
|
||||
|
||||
|
||||
@ -12,4 +12,50 @@ export const resourcesManager = reactive<{
|
||||
|
||||
export interface ResourceStorage {
|
||||
currentResourceName: string;
|
||||
lastResourceReadResponse?: ResourcesReadResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 解析资源模板字符串
|
||||
* @param template 资源模板字符串,如 "greeting://{name}"
|
||||
* @returns { params: 参数名数组, fill: 填充函数 }
|
||||
*/
|
||||
export function parseResourceTemplate(template: string): {
|
||||
params: string[],
|
||||
fill: (params: Record<string, string>) => string
|
||||
} {
|
||||
// 1. 提取所有参数名
|
||||
const paramRegex = /\{([^}]+)\}/g;
|
||||
const params = new Set<string>();
|
||||
let match;
|
||||
|
||||
while ((match = paramRegex.exec(template)) !== null) {
|
||||
params.add(match[1]);
|
||||
}
|
||||
|
||||
const paramList = Array.from(params);
|
||||
|
||||
// 2. 创建填充函数
|
||||
const fill = (values: Record<string, string>): string => {
|
||||
let result = template;
|
||||
|
||||
// 验证所有必填参数
|
||||
for (const param of paramList) {
|
||||
if (values[param] === undefined) {
|
||||
throw new Error(`缺少必要参数: ${param}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 替换所有参数
|
||||
for (const param of paramList) {
|
||||
result = result.replace(new RegExp(`\\{${param}\\}`, 'g'), values[param]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return {
|
||||
params: paramList,
|
||||
fill
|
||||
};
|
||||
}
|
@ -105,5 +105,8 @@
|
||||
"command": "أمر",
|
||||
"env-var": "متغيرات البيئة",
|
||||
"log": "سجلات",
|
||||
"warning.click-to-connect": "يرجى النقر أولاً على $1 على اليسار للاتصال"
|
||||
"warning.click-to-connect": "يرجى النقر أولاً على $1 على اليسار للاتصال",
|
||||
"reset": "إعادة تعيين",
|
||||
"read-resource": "قراءة الموارد",
|
||||
"enter": "إدخال"
|
||||
}
|
@ -105,5 +105,8 @@
|
||||
"command": "Befehl",
|
||||
"env-var": "Umgebungsvariablen",
|
||||
"log": "Protokolle",
|
||||
"warning.click-to-connect": "Bitte klicken Sie zuerst auf $1 links, um eine Verbindung herzustellen"
|
||||
"warning.click-to-connect": "Bitte klicken Sie zuerst auf $1 links, um eine Verbindung herzustellen",
|
||||
"reset": "Zurücksetzen",
|
||||
"read-resource": "Ressourcen lesen",
|
||||
"enter": "Eingabe"
|
||||
}
|
@ -105,5 +105,8 @@
|
||||
"command": "Command",
|
||||
"env-var": "Environment variables",
|
||||
"log": "Logs",
|
||||
"warning.click-to-connect": "Please first click on $1 on the left to connect"
|
||||
"warning.click-to-connect": "Please first click on $1 on the left to connect",
|
||||
"reset": "Reset",
|
||||
"read-resource": "Read resources",
|
||||
"enter": "Input"
|
||||
}
|
@ -105,5 +105,8 @@
|
||||
"command": "Commande",
|
||||
"env-var": "Variables d'environnement",
|
||||
"log": "Journaux",
|
||||
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter"
|
||||
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter",
|
||||
"reset": "Réinitialiser",
|
||||
"read-resource": "Lire les ressources",
|
||||
"enter": "Entrée"
|
||||
}
|
@ -105,5 +105,8 @@
|
||||
"command": "コマンド",
|
||||
"env-var": "環境変数",
|
||||
"log": "ログ",
|
||||
"warning.click-to-connect": "まず左側の$1をクリックして接続してください"
|
||||
"warning.click-to-connect": "まず左側の$1をクリックして接続してください",
|
||||
"reset": "リセット",
|
||||
"read-resource": "リソースを読み込む",
|
||||
"enter": "入力"
|
||||
}
|
@ -105,5 +105,8 @@
|
||||
"command": "명령",
|
||||
"env-var": "환경 변수",
|
||||
"log": "로그",
|
||||
"warning.click-to-connect": "먼저 왼쪽의 $1을 클릭하여 연결하십시오"
|
||||
"warning.click-to-connect": "먼저 왼쪽의 $1을 클릭하여 연결하십시오",
|
||||
"reset": "재설정",
|
||||
"read-resource": "리소스 읽기",
|
||||
"enter": "입력"
|
||||
}
|
@ -105,5 +105,8 @@
|
||||
"command": "Команда",
|
||||
"env-var": "Переменные среды",
|
||||
"log": "Логи",
|
||||
"warning.click-to-connect": "Пожалуйста, сначала нажмите на $1 слева для подключения"
|
||||
"warning.click-to-connect": "Пожалуйста, сначала нажмите на $1 слева для подключения",
|
||||
"reset": "Сброс",
|
||||
"read-resource": "Чтение ресурсов",
|
||||
"enter": "Ввод"
|
||||
}
|
@ -105,5 +105,8 @@
|
||||
"command": "命令",
|
||||
"env-var": "环境变量",
|
||||
"log": "日志",
|
||||
"warning.click-to-connect": "请先点击左侧的 $1 进行连接"
|
||||
"warning.click-to-connect": "请先点击左侧的 $1 进行连接",
|
||||
"reset": "重置",
|
||||
"read-resource": "读取资源",
|
||||
"enter": "输入"
|
||||
}
|
@ -105,5 +105,8 @@
|
||||
"command": "命令",
|
||||
"env-var": "環境變數",
|
||||
"log": "日誌",
|
||||
"warning.click-to-connect": "請先點擊左側的 $1 進行連接"
|
||||
"warning.click-to-connect": "請先點擊左側的 $1 進行連接",
|
||||
"reset": "重置",
|
||||
"read-resource": "讀取資源",
|
||||
"enter": "輸入"
|
||||
}
|
@ -24,7 +24,6 @@ const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.connection-option .output-content {
|
||||
border-radius: .5em;
|
||||
padding: 15px;
|
||||
|
@ -52,7 +52,7 @@ bridge.addCommandListener('connect', data => {
|
||||
const { code, msg } = data;
|
||||
connectionResult.success = (code === 200);
|
||||
connectionResult.logString = msg;
|
||||
});
|
||||
}, { once: false });
|
||||
|
||||
|
||||
</script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user