push save MVP to 95%
This commit is contained in:
parent
03d79d0222
commit
3976670295
4
.gitignore
vendored
4
.gitignore
vendored
@ -4,4 +4,6 @@ node_modules
|
|||||||
.vscode-test/
|
.vscode-test/
|
||||||
*.vsix
|
*.vsix
|
||||||
.env
|
.env
|
||||||
resources
|
resources
|
||||||
|
.DS_Store
|
||||||
|
.exe
|
@ -1,5 +0,0 @@
|
|||||||
import { defineConfig } from '@vscode/test-cli';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
files: 'out/test/**/*.test.js',
|
|
||||||
});
|
|
@ -1,5 +1,8 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## [main] 0.0.5
|
||||||
|
- 支持对已经打开过的文件项目进行管理
|
||||||
|
|
||||||
## [main] 0.0.4
|
## [main] 0.0.4
|
||||||
- 修复选择模型后点击确认跳转回 deepseek 的 bug
|
- 修复选择模型后点击确认跳转回 deepseek 的 bug
|
||||||
- 修复 mcp 项目初始化点击工具全部都是空的 bug
|
- 修复 mcp 项目初始化点击工具全部都是空的 bug
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "openmcp",
|
"name": "openmcp",
|
||||||
"displayName": "OpenMCP",
|
"displayName": "OpenMCP",
|
||||||
"description": "An all in one MCP Client/TestTool",
|
"description": "An all in one MCP Client/TestTool",
|
||||||
"version": "0.0.4",
|
"version": "0.0.5",
|
||||||
"publisher": "kirigaya",
|
"publisher": "kirigaya",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "kirigaya",
|
"name": "kirigaya",
|
||||||
|
@ -3,19 +3,19 @@
|
|||||||
<h3>{{ currentResource.template?.name }}</h3>
|
<h3>{{ currentResource.template?.name }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="resource-reader-container">
|
<div class="resource-reader-container">
|
||||||
<el-form :model="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"
|
<el-form-item v-for="param in currentResource?.params" :key="param.name"
|
||||||
:label="param.name" :prop="param.name">
|
:label="param.name" :prop="param.name">
|
||||||
<!-- 根据不同类型渲染不同输入组件 -->
|
<!-- 根据不同类型渲染不同输入组件 -->
|
||||||
<el-input v-if="param.type === 'string'" v-model="formData[param.name]"
|
<el-input v-if="param.type === 'string'" v-model="tabStorage.formData[param.name]"
|
||||||
:placeholder="param.placeholder || `请输入${param.name}`"
|
:placeholder="param.placeholder || `请输入${param.name}`"
|
||||||
@keydown.enter.prevent="handleSubmit" />
|
@keydown.enter.prevent="handleSubmit" />
|
||||||
|
|
||||||
<el-input-number v-else-if="param.type === 'number'" v-model="formData[param.name]"
|
<el-input-number v-else-if="param.type === 'number'" v-model="tabStorage.formData[param.name]"
|
||||||
:placeholder="param.placeholder || `请输入${param.name}`"
|
:placeholder="param.placeholder || `请输入${param.name}`"
|
||||||
@keydown.enter.prevent="handleSubmit" />
|
@keydown.enter.prevent="handleSubmit" />
|
||||||
|
|
||||||
<el-switch v-else-if="param.type === 'boolean'" v-model="formData[param.name]" />
|
<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>
|
||||||
@ -38,6 +38,7 @@ import { tabs } from '../panel';
|
|||||||
import { parseResourceTemplate, resourcesManager, ResourceStorage } from './resources';
|
import { parseResourceTemplate, resourcesManager, ResourceStorage } from './resources';
|
||||||
import { CasualRestAPI, ResourcesReadResponse } from '@/hook/type';
|
import { CasualRestAPI, ResourcesReadResponse } from '@/hook/type';
|
||||||
import { useMessageBridge } from '@/api/message-bridge';
|
import { useMessageBridge } from '@/api/message-bridge';
|
||||||
|
import { getDefaultValue, normaliseJavascriptType } from '@/hook/mcp';
|
||||||
|
|
||||||
defineComponent({ name: 'resource-reader' });
|
defineComponent({ name: 'resource-reader' });
|
||||||
|
|
||||||
@ -53,9 +54,12 @@ const props = defineProps({
|
|||||||
const tab = tabs.content[props.tabId];
|
const tab = tabs.content[props.tabId];
|
||||||
const tabStorage = tab.storage as ResourceStorage;
|
const tabStorage = tab.storage as ResourceStorage;
|
||||||
|
|
||||||
|
if (!tabStorage.formData) {
|
||||||
|
tabStorage.formData = {};
|
||||||
|
}
|
||||||
|
|
||||||
// 表单相关状态
|
// 表单相关状态
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
const formData = ref<Record<string, any>>({});
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const responseData = ref<ResourcesReadResponse>();
|
const responseData = ref<ResourcesReadResponse>();
|
||||||
|
|
||||||
@ -94,12 +98,21 @@ const formRules = computed<FormRules>(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 初始化表单数据
|
// 初始化表单数据
|
||||||
const initFormData = () => {
|
const initFormData = () => {
|
||||||
formData.value = {}
|
if (!currentResource.value?.params) return;
|
||||||
currentResource.value?.params.forEach(param => {
|
|
||||||
formData.value[param.name] = param.type === 'number' ? 0 :
|
const newSchemaDataForm: Record<string, number | boolean | string> = {};
|
||||||
param.type === 'boolean' ? false : ''
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +125,7 @@ const resetForm = () => {
|
|||||||
// 提交表单
|
// 提交表单
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
const fillFn = currentResource.value.fill;
|
const fillFn = currentResource.value.fill;
|
||||||
const uri = fillFn(formData.value);
|
const uri = fillFn(tabStorage.formData);
|
||||||
|
|
||||||
const bridge = useMessageBridge();
|
const bridge = useMessageBridge();
|
||||||
|
|
||||||
|
@ -64,7 +64,6 @@ function reloadResources(option: { first: boolean }) {
|
|||||||
|
|
||||||
function handleClick(template: ResourceTemplate) {
|
function handleClick(template: ResourceTemplate) {
|
||||||
tabStorage.currentResourceName = template.name;
|
tabStorage.currentResourceName = template.name;
|
||||||
// TODO: 恢复这部分响应?
|
|
||||||
tabStorage.lastResourceReadResponse = undefined;
|
tabStorage.lastResourceReadResponse = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,9 +73,9 @@ onMounted(() => {
|
|||||||
commandCancel = bridge.addCommandListener('resources/templates/list', (data: CasualRestAPI<ResourceTemplatesListResponse>) => {
|
commandCancel = bridge.addCommandListener('resources/templates/list', (data: CasualRestAPI<ResourceTemplatesListResponse>) => {
|
||||||
resourcesManager.templates = data.msg.resourceTemplates || [];
|
resourcesManager.templates = data.msg.resourceTemplates || [];
|
||||||
|
|
||||||
if (resourcesManager.templates.length > 0) {
|
const targetResource = resourcesManager.templates.find(template => template.name === tabStorage.currentResourceName);
|
||||||
|
if (targetResource === undefined) {
|
||||||
tabStorage.currentResourceName = resourcesManager.templates[0].name;
|
tabStorage.currentResourceName = resourcesManager.templates[0].name;
|
||||||
// TODO: 恢复这部分响应?
|
|
||||||
tabStorage.lastResourceReadResponse = undefined;
|
tabStorage.lastResourceReadResponse = undefined;
|
||||||
}
|
}
|
||||||
}, { once: false });
|
}, { once: false });
|
||||||
|
@ -13,6 +13,7 @@ export const resourcesManager = reactive<{
|
|||||||
export interface ResourceStorage {
|
export interface ResourceStorage {
|
||||||
currentResourceName: string;
|
currentResourceName: string;
|
||||||
lastResourceReadResponse?: ResourcesReadResponse;
|
lastResourceReadResponse?: ResourcesReadResponse;
|
||||||
|
formData: Record<string, number | string | boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,7 +23,7 @@ export interface ResourceStorage {
|
|||||||
*/
|
*/
|
||||||
export function parseResourceTemplate(template: string): {
|
export function parseResourceTemplate(template: string): {
|
||||||
params: string[],
|
params: string[],
|
||||||
fill: (params: Record<string, string>) => string
|
fill: (params: Record<string, number | boolean | string>) => string
|
||||||
} {
|
} {
|
||||||
// 1. 提取所有参数名
|
// 1. 提取所有参数名
|
||||||
const paramRegex = /\{([^}]+)\}/g;
|
const paramRegex = /\{([^}]+)\}/g;
|
||||||
@ -36,7 +37,7 @@ export function parseResourceTemplate(template: string): {
|
|||||||
const paramList = Array.from(params);
|
const paramList = Array.from(params);
|
||||||
|
|
||||||
// 2. 创建填充函数
|
// 2. 创建填充函数
|
||||||
const fill = (values: Record<string, string>): string => {
|
const fill = (values: Record<string, number | boolean | string>): string => {
|
||||||
let result = template;
|
let result = template;
|
||||||
|
|
||||||
// 验证所有必填参数
|
// 验证所有必填参数
|
||||||
@ -48,7 +49,7 @@ export function parseResourceTemplate(template: string): {
|
|||||||
|
|
||||||
// 替换所有参数
|
// 替换所有参数
|
||||||
for (const param of paramList) {
|
for (const param of paramList) {
|
||||||
result = result.replace(new RegExp(`\\{${param}\\}`, 'g'), values[param]);
|
result = result.replace(new RegExp(`\\{${param}\\}`, 'g'), values[param].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -54,6 +54,7 @@ import type { FormInstance, FormRules } from 'element-plus';
|
|||||||
import { tabs } from '../panel';
|
import { tabs } from '../panel';
|
||||||
import { callTool, toolsManager, ToolStorage } from './tools';
|
import { callTool, toolsManager, ToolStorage } from './tools';
|
||||||
import { pinkLog } from '@/views/setting/util';
|
import { pinkLog } from '@/views/setting/util';
|
||||||
|
import { getDefaultValue, normaliseJavascriptType } from '@/hook/mcp';
|
||||||
|
|
||||||
defineComponent({ name: 'tool-executor' });
|
defineComponent({ name: 'tool-executor' });
|
||||||
|
|
||||||
@ -99,15 +100,6 @@ const formRules = computed<FormRules>(() => {
|
|||||||
return rules;
|
return rules;
|
||||||
});
|
});
|
||||||
|
|
||||||
const getDefaultValue = (property: any) => {
|
|
||||||
if (property.type === 'number' || property.type === 'integer') {
|
|
||||||
return 0;
|
|
||||||
} else if (property.type === 'boolean') {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const initFormData = () => {
|
const initFormData = () => {
|
||||||
// 初始化,根据输入的 inputSchema 校验
|
// 初始化,根据输入的 inputSchema 校验
|
||||||
@ -120,11 +112,8 @@ const initFormData = () => {
|
|||||||
|
|
||||||
Object.entries(currentTool.value.inputSchema.properties).forEach(([name, property]) => {
|
Object.entries(currentTool.value.inputSchema.properties).forEach(([name, property]) => {
|
||||||
newSchemaDataForm[name] = getDefaultValue(property);
|
newSchemaDataForm[name] = getDefaultValue(property);
|
||||||
let originType: string = typeof tabStorage.formData[name];
|
const originType = normaliseJavascriptType(typeof tabStorage.formData[name]);
|
||||||
if (originType === 'number') {
|
|
||||||
originType = 'integer';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tabStorage.formData[name] !== undefined && originType === property.type) {
|
if (tabStorage.formData[name] !== undefined && originType === property.type) {
|
||||||
newSchemaDataForm[name] = tabStorage.formData[name];
|
newSchemaDataForm[name] = tabStorage.formData[name];
|
||||||
}
|
}
|
||||||
@ -135,7 +124,6 @@ const initFormData = () => {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formRef.value?.resetFields();
|
formRef.value?.resetFields();
|
||||||
tabStorage.lastToolCallResponse = undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handleExecute() {
|
async function handleExecute() {
|
||||||
|
30
renderer/src/hook/mcp.ts
Normal file
30
renderer/src/hook/mcp.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { SchemaProperty } from "./type";
|
||||||
|
|
||||||
|
interface TypeAble {
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultValue(property: TypeAble) {
|
||||||
|
if (property.type === 'number' || property.type === 'integer') {
|
||||||
|
return 0;
|
||||||
|
} else if (property.type === 'boolean') {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normaliseJavascriptType(type: string) {
|
||||||
|
switch (type) {
|
||||||
|
case 'integer':
|
||||||
|
return 'number';
|
||||||
|
case 'number':
|
||||||
|
return 'integer';
|
||||||
|
case 'boolean':
|
||||||
|
return 'boolean';
|
||||||
|
case 'string':
|
||||||
|
return 'string';
|
||||||
|
default:
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"currentIndex": 1,
|
"currentIndex": 2,
|
||||||
"tabs": [
|
"tabs": [
|
||||||
{
|
{
|
||||||
"name": "交互测试",
|
"name": "交互测试",
|
||||||
@ -133,12 +133,33 @@
|
|||||||
"content": [
|
"content": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"text": "CityWeather(city_name_en='hangzhou', city_name_cn='杭州', city_code='101210101', temp='20.7', wd='', ws='', sd='93%', aqi='13', weather='阴')"
|
"text": "CityWeather(city_name_en='hangzhou', city_name_cn='杭州', city_code='101210101', temp='20.3', wd='', ws='', sd='89%', aqi='38', weather='阴')"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"isError": false
|
"isError": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "资源",
|
||||||
|
"icon": "icon-file",
|
||||||
|
"type": "blank",
|
||||||
|
"componentIndex": 0,
|
||||||
|
"storage": {
|
||||||
|
"currentResourceName": "greeting",
|
||||||
|
"formData": {
|
||||||
|
"name": "kirigaya"
|
||||||
|
},
|
||||||
|
"lastResourceReadResponse": {
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"uri": "greeting://kirigaya",
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"text": "Hello, kirigaya!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user