| | |
| | | <template> |
| | | <ElDrawer |
| | | :model-value="visible" |
| | | title="MCP 工具预览" |
| | | :title="t('pages.system.aiMcpMount.toolsDrawer.title')" |
| | | size="760px" |
| | | @update:model-value="handleVisibleChange" |
| | | > |
| | | <div class="space-y-4"> |
| | | <div class="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-4"> |
| | | <div |
| | | class="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-4" |
| | | > |
| | | <div> |
| | | <div class="text-sm font-semibold text-[var(--art-gray-900)]">{{ mountName || '当前挂载' }}</div> |
| | | <div class="mt-1 text-xs text-[var(--art-gray-500)]">可预览工具列表,并对工具入参做联调测试。</div> |
| | | <div class="text-sm font-semibold text-[var(--art-gray-900)]"> |
| | | {{ mountName || t('pages.system.aiMcpMount.toolsDrawer.currentMount') }} |
| | | </div> |
| | | <div class="mt-1 text-xs text-[var(--art-gray-500)]"> |
| | | {{ t('pages.system.aiMcpMount.toolsDrawer.description') }} |
| | | </div> |
| | | </div> |
| | | <ElSpace wrap> |
| | | <ElButton :loading="toolsLoading" @click="loadTools">刷新工具</ElButton> |
| | | <ElButton :loading="connectivityLoading" @click="handleConnectivityTest">连通性测试</ElButton> |
| | | <ElButton :loading="toolsLoading" @click="loadTools"> |
| | | {{ t('pages.system.aiMcpMount.toolsDrawer.refreshTools') }} |
| | | </ElButton> |
| | | <ElButton :loading="connectivityLoading" @click="handleConnectivityTest"> |
| | | {{ t('pages.system.aiMcpMount.toolsDrawer.connectivityTest') }} |
| | | </ElButton> |
| | | </ElSpace> |
| | | </div> |
| | | |
| | | <ElAlert v-if="connectivityResult" :type="connectivityResult.healthStatus === 'HEALTHY' ? 'success' : 'error'" :closable="false"> |
| | | <ElAlert |
| | | v-if="connectivityResult" |
| | | :type="connectivityResult.healthStatus === 'HEALTHY' ? 'success' : 'error'" |
| | | :closable="false" |
| | | > |
| | | <div class="space-y-1 text-sm"> |
| | | <div>{{ connectivityResult.message || '--' }}</div> |
| | | <div v-if="connectivityResult.initElapsedMs !== undefined && connectivityResult.initElapsedMs !== null"> |
| | | 初始化耗时 {{ connectivityResult.initElapsedMs }} ms |
| | | <div>{{ connectivityResult.message || emptyText }}</div> |
| | | <div |
| | | v-if=" |
| | | connectivityResult.initElapsedMs !== undefined && |
| | | connectivityResult.initElapsedMs !== null |
| | | " |
| | | > |
| | | {{ |
| | | t('pages.system.aiMcpMount.messages.initElapsedMs', { |
| | | value: connectivityResult.initElapsedMs |
| | | }) |
| | | }} |
| | | </div> |
| | | <div v-if="connectivityResult.testedAt">{{ connectivityResult.testedAt }}</div> |
| | | </div> |
| | | </ElAlert> |
| | | |
| | | <ElSkeleton :loading="toolsLoading" animated :rows="8"> |
| | | <ElEmpty v-if="!tools.length" description="暂无工具信息" :image-size="100" /> |
| | | <ElEmpty |
| | | v-if="!tools.length" |
| | | :description="t('pages.system.aiMcpMount.toolsDrawer.empty')" |
| | | :image-size="100" |
| | | /> |
| | | |
| | | <div v-else class="space-y-4"> |
| | | <div |
| | |
| | | <div class="flex flex-wrap items-start justify-between gap-3"> |
| | | <div> |
| | | <div class="text-base font-semibold text-[var(--art-gray-900)]">{{ tool.name }}</div> |
| | | <div class="mt-1 text-sm text-[var(--art-gray-500)]">{{ tool.description || '--' }}</div> |
| | | <div class="mt-1 text-sm text-[var(--art-gray-500)]">{{ tool.description || emptyText }}</div> |
| | | </div> |
| | | <ElButton :loading="testingToolName === tool.name" @click="handleToolTest(tool.name)">工具测试</ElButton> |
| | | <ElButton :loading="testingToolName === tool.name" @click="handleToolTest(tool.name)"> |
| | | {{ t('pages.system.aiMcpMount.toolsDrawer.toolTest') }} |
| | | </ElButton> |
| | | </div> |
| | | |
| | | <div class="mt-4 grid gap-4 md:grid-cols-2"> |
| | | <div class="space-y-2"> |
| | | <div class="text-xs text-[var(--art-gray-500)]">输入参数 JSON</div> |
| | | <div class="text-xs text-[var(--art-gray-500)]"> |
| | | {{ t('pages.system.aiMcpMount.toolsDrawer.inputJson') }} |
| | | </div> |
| | | <ElInput |
| | | v-model="toolInputs[tool.name]" |
| | | type="textarea" |
| | | :rows="8" |
| | | placeholder='请输入 JSON,例如 {"taskCode":"TK001"}' |
| | | :placeholder="t('pages.system.aiMcpMount.toolsDrawer.inputJsonPlaceholder')" |
| | | /> |
| | | </div> |
| | | <div class="space-y-2"> |
| | | <div class="text-xs text-[var(--art-gray-500)]">工具输出</div> |
| | | <div class="text-xs text-[var(--art-gray-500)]"> |
| | | {{ t('pages.system.aiMcpMount.toolsDrawer.output') }} |
| | | </div> |
| | | <ElInput |
| | | :model-value="toolOutputs[tool.name] || ''" |
| | | type="textarea" |
| | | :rows="8" |
| | | readonly |
| | | placeholder="工具输出会显示在这里" |
| | | :placeholder="t('pages.system.aiMcpMount.toolsDrawer.outputPlaceholder')" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="tool.inputSchema" class="mt-4 rounded-xl bg-[var(--art-main-bg-color)] p-3"> |
| | | <div class="text-xs text-[var(--art-gray-500)]">输入 Schema</div> |
| | | <pre class="mt-2 whitespace-pre-wrap break-all text-xs leading-6 text-[var(--art-gray-900)]">{{ formatSchema(tool.inputSchema) }}</pre> |
| | | <div class="text-xs text-[var(--art-gray-500)]"> |
| | | {{ t('pages.system.aiMcpMount.toolsDrawer.inputSchema') }} |
| | | </div> |
| | | <pre class="mt-2 whitespace-pre-wrap break-all text-xs leading-6 text-[var(--art-gray-900)]">{{ |
| | | formatSchema(tool.inputSchema) |
| | | }}</pre> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { useI18n } from 'vue-i18n' |
| | | import { ElMessage } from 'element-plus' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { fetchPreviewAiMcpTools, fetchTestAiMcpConnectivity, fetchTestAiMcpTool } from '@/api/ai-config' |
| | |
| | | }) |
| | | |
| | | const emit = defineEmits(['update:visible']) |
| | | const { t } = useI18n() |
| | | const tools = ref([]) |
| | | const toolsLoading = ref(false) |
| | | const connectivityLoading = ref(false) |
| | |
| | | const toolInputs = reactive({}) |
| | | const toolOutputs = reactive({}) |
| | | const testingToolName = ref('') |
| | | |
| | | const emptyText = computed(() => t('common.placeholder.empty')) |
| | | |
| | | function resetState() { |
| | | tools.value = [] |
| | |
| | | toolsLoading.value = true |
| | | try { |
| | | const response = await guardRequestWithMessage(fetchPreviewAiMcpTools(props.mountId), [], { |
| | | timeoutMessage: '工具列表加载超时,已停止等待' |
| | | timeoutMessage: t('pages.system.aiMcpMount.messages.toolsTimeout') |
| | | }) |
| | | tools.value = Array.isArray(response) ? response : [] |
| | | } catch (error) { |
| | | tools.value = [] |
| | | ElMessage.error(error?.message || '获取工具列表失败') |
| | | ElMessage.error(error?.message || t('pages.system.aiMcpMount.toolsDrawer.toolsLoadFailed')) |
| | | } finally { |
| | | toolsLoading.value = false |
| | | } |
| | |
| | | connectivityLoading.value = true |
| | | try { |
| | | connectivityResult.value = await guardRequestWithMessage(fetchTestAiMcpConnectivity(props.mountId), null, { |
| | | timeoutMessage: '连通性测试超时,已停止等待' |
| | | timeoutMessage: t('pages.system.aiMcpMount.messages.connectivityTimeout') |
| | | }) |
| | | ElMessage.success(connectivityResult.value?.message || '连通性测试成功') |
| | | ElMessage.success( |
| | | connectivityResult.value?.message || t('pages.system.aiMcpMount.messages.connectivitySuccess') |
| | | ) |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || '连通性测试失败') |
| | | ElMessage.error(error?.message || t('pages.system.aiMcpMount.messages.connectivityFailed')) |
| | | } finally { |
| | | connectivityLoading.value = false |
| | | } |
| | |
| | | if (!props.mountId) return |
| | | const inputJson = toolInputs[toolName]?.trim?.() || '' |
| | | if (!inputJson) { |
| | | ElMessage.warning('请输入工具测试入参 JSON') |
| | | ElMessage.warning(t('pages.system.aiMcpMount.toolsDrawer.toolInputRequired')) |
| | | return |
| | | } |
| | | testingToolName.value = toolName |
| | |
| | | }), |
| | | null, |
| | | { |
| | | timeoutMessage: '工具测试超时,已停止等待' |
| | | timeoutMessage: t('pages.system.aiMcpMount.messages.toolTestTimeout') |
| | | } |
| | | ) |
| | | toolOutputs[toolName] = result?.output || JSON.stringify(result || {}, null, 2) |
| | | ElMessage.success('工具测试成功') |
| | | ElMessage.success(t('pages.system.aiMcpMount.toolsDrawer.toolTestSuccess')) |
| | | } catch (error) { |
| | | toolOutputs[toolName] = error?.message || '工具测试失败' |
| | | ElMessage.error(error?.message || '工具测试失败') |
| | | toolOutputs[toolName] = error?.message || t('pages.system.aiMcpMount.toolsDrawer.toolTestFailed') |
| | | ElMessage.error(error?.message || t('pages.system.aiMcpMount.toolsDrawer.toolTestFailed')) |
| | | } finally { |
| | | testingToolName.value = '' |
| | | } |