zhou zhou
3 天以前 50e95b985a72fcec4a93a2470e9efdfb2620148a
rsf-design/src/views/system/ai-mcp-mount/modules/ai-mcp-tools-drawer.vue
@@ -1,34 +1,61 @@
<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
@@ -39,36 +66,46 @@
            <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>
@@ -78,6 +115,7 @@
</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'
@@ -89,6 +127,7 @@
  })
  const emit = defineEmits(['update:visible'])
  const { t } = useI18n()
  const tools = ref([])
  const toolsLoading = ref(false)
  const connectivityLoading = ref(false)
@@ -96,6 +135,8 @@
  const toolInputs = reactive({})
  const toolOutputs = reactive({})
  const testingToolName = ref('')
  const emptyText = computed(() => t('common.placeholder.empty'))
  function resetState() {
    tools.value = []
@@ -118,12 +159,12 @@
    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
    }
@@ -134,11 +175,13 @@
    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
    }
@@ -148,7 +191,7 @@
    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
@@ -160,14 +203,14 @@
        }),
        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 = ''
    }