Skip to content

Commit f5fbbfc

Browse files
committed
feat: add McpServerInputDialog component for variable input and handling
1 parent 7a0daed commit f5fbbfc

File tree

2 files changed

+117
-2
lines changed

2 files changed

+117
-2
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<template>
2+
<el-dialog
3+
width="600"
4+
title="设置变量"
5+
v-model="dialogVisible"
6+
:close-on-click-modal="false"
7+
:close-on-press-escape="false"
8+
:append-to-body="true"
9+
>
10+
<el-form label-position="top" ref="formRef" :model="form" require-asterisk-position="right">
11+
<el-form-item v-for="item in input_field_list" :label="item" :key="item" :prop="item"
12+
:rules="{ required: true, message: $t('dynamicsForm.tip.requiredMessage'), trigger: 'blur' }">
13+
<el-input v-model="form[item]"></el-input>
14+
</el-form-item>
15+
</el-form>
16+
<template #footer>
17+
<span class="dialog-footer">
18+
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
19+
<el-button type="primary" @click="submit(formRef)" :loading="loading">
20+
{{ $t('common.save') }}
21+
</el-button>
22+
</span>
23+
</template>
24+
</el-dialog>
25+
</template>
26+
27+
<script setup lang="ts">
28+
import { ref } from 'vue'
29+
import type { FormInstance } from 'element-plus'
30+
31+
const emit = defineEmits<{
32+
(e: 'refresh', value: any): void;
33+
}>();
34+
35+
const dialogVisible = ref(false)
36+
const loading = ref(false)
37+
const formRef = ref<FormInstance>()
38+
const form = ref<any>({})
39+
40+
const input_field_list = ref<string[]>([])
41+
42+
function open(vars: string[]) {
43+
dialogVisible.value = true
44+
input_field_list.value = vars
45+
}
46+
47+
const submit = async (formEl: FormInstance | undefined) => {
48+
if (!formEl) return
49+
await formEl.validate((valid) => {
50+
if (valid) {
51+
emit('refresh', form.value)
52+
dialogVisible.value = false
53+
}
54+
})
55+
}
56+
57+
58+
defineExpose({open})
59+
</script>
60+
<style scoped lang="scss">
61+
62+
</style>

ui/src/workflow/nodes/mcp-node/index.vue

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@
246246
</div>
247247
</template>
248248
</NodeContainer>
249+
<McpServerInputDialog ref="mcpServerInputDialogRef" @refresh="handleMcpVariables" />
249250
</template>
250251
<script setup lang="ts">
251252
import { cloneDeep, set } from 'lodash'
@@ -256,6 +257,7 @@ import { t } from '@/locales'
256257
import { MsgError, MsgSuccess } from '@/utils/message'
257258
import TooltipLabel from '@/components/dynamics-form/items/label/TooltipLabel.vue'
258259
import NodeCascader from '@/workflow/common/NodeCascader.vue'
260+
import McpServerInputDialog from "./component/McpServerInputDialog.vue";
259261
import { useRoute } from 'vue-router'
260262
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
261263
import { resetUrl } from '@/utils/common'
@@ -339,12 +341,22 @@ function getTools() {
339341
}
340342
try {
341343
JSON.parse(form_data.value.mcp_servers)
344+
const vars = extractPlaceholders(form_data.value.mcp_servers)
345+
if (vars.length > 0) {
346+
mcpServerInputDialogRef.value.open(vars)
347+
return
348+
}
342349
} catch (e) {
343350
MsgError(t('views.applicationWorkflow.nodes.mcpNode.mcpServerTip'))
344351
return
345352
}
346-
loadSharedApi({ type: 'application', systemType: apiType.value })
347-
.getMcpTools(id, form_data.value.mcp_servers, loading)
353+
// 一切正常,获取tool
354+
_getTools(form_data.value.mcp_servers)
355+
}
356+
357+
function _getTools(mcp_servers: any) {
358+
loadSharedApi({ type: 'application', systemType: apiType.value })
359+
.getMcpTools(id, mcp_servers, loading)
348360
.then((res: any) => {
349361
form_data.value.mcp_tools = res.data
350362
MsgSuccess(t('views.applicationWorkflow.nodes.mcpNode.getToolsSuccess'))
@@ -355,6 +367,47 @@ function getTools() {
355367
})
356368
}
357369
370+
const mcpServerInputDialogRef = ref()
371+
// 提取 JSON 中所有占位符({{...}})的变量路径
372+
function extractPlaceholders(input: unknown): string[] {
373+
const re = /\{\{\s*([a-zA-Z_][\w.]*)\s*\}\}/g; // 捕获 {{ path.like.this }}
374+
const found = new Set<string>();
375+
376+
const visit = (v: unknown) => {
377+
if (typeof v === 'string') {
378+
let m: RegExpExecArray | null;
379+
while ((m = re.exec(v)) !== null) found.add(m[1]);
380+
} else if (Array.isArray(v)) {
381+
v.forEach(visit);
382+
} else if (v && typeof v === 'object') {
383+
Object.values(v as Record<string, unknown>).forEach(visit);
384+
}
385+
};
386+
387+
// 如果传入的是 JSON 字符串,尝试解析,否则按字符串/对象处理
388+
if (typeof input === 'string') {
389+
try {
390+
visit(JSON.parse(input));
391+
} catch {
392+
visit(input);
393+
}
394+
} else {
395+
visit(input);
396+
}
397+
398+
return [...found];
399+
}
400+
401+
function handleMcpVariables(vars: any) {
402+
let mcp_servers = form_data.value.mcp_servers
403+
for (const item in vars) {
404+
mcp_servers = mcp_servers.replace(`{{${item}}}`, vars[item])
405+
}
406+
407+
// 一切正常,获取tool
408+
_getTools(mcp_servers)
409+
}
410+
358411
function changeTool() {
359412
form_data.value.mcp_server = form_data.value.mcp_tools.find(
360413
(item: any) => item.name === form_data.value.mcp_tool,

0 commit comments

Comments
 (0)