|
@@ -0,0 +1,222 @@
|
|
|
+<template>
|
|
|
+ <el-card
|
|
|
+ class="tools-loading-card"
|
|
|
+ shadow="hover"
|
|
|
+ @click="showDialog = true"
|
|
|
+ :style="{ cursor: 'pointer' }"
|
|
|
+ >
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span class="card-title">{{ toolData.toolName || 'MCP工具调用' }}</span>
|
|
|
+ <el-icon :class="{ 'is-loading': isLoading }">
|
|
|
+ <Loading v-if="isLoading" />
|
|
|
+ <Tools v-else />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <div class="card-content">
|
|
|
+ <p class="tool-description">
|
|
|
+ {{ getToolDescription() }}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 详情对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="showDialog"
|
|
|
+ :title="toolData.toolName || 'MCP工具调用详情'"
|
|
|
+ width="800px"
|
|
|
+ destroy-on-close
|
|
|
+ >
|
|
|
+ <el-tabs v-model="activeTab" type="border-card">
|
|
|
+ <el-tab-pane label="工具信息" name="info">
|
|
|
+ <div class="tab-content">
|
|
|
+ <el-descriptions :column="1" border>
|
|
|
+ <el-descriptions-item label="工具名称">
|
|
|
+ {{ toolData.toolName || '未知工具' }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="调用状态">
|
|
|
+ <el-tag :type="getStatusType()">
|
|
|
+ {{ getStatusText() }}
|
|
|
+ </el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <el-tab-pane label="传入参数" name="params">
|
|
|
+ <div class="tab-content">
|
|
|
+ <div v-if="toolData.params">
|
|
|
+ <pre class="json-content">{{ toolData.params }}</pre>
|
|
|
+ </div>
|
|
|
+ <el-empty v-else description="无传入参数" />
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <el-tab-pane label="返回结果" name="result">
|
|
|
+ <div class="tab-content">
|
|
|
+ <div v-if="toolData.result">
|
|
|
+ <pre class="json-content">{{ toolData.result }}</pre>
|
|
|
+ </div>
|
|
|
+ <div v-else-if="isLoading">
|
|
|
+ <el-skeleton :rows="5" animated />
|
|
|
+ </div>
|
|
|
+ <el-empty v-else description="暂无返回结果" />
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, computed, onMounted } from 'vue'
|
|
|
+import { Loading, Tools } from '@element-plus/icons-vue'
|
|
|
+
|
|
|
+type Props = {
|
|
|
+ data: string
|
|
|
+}
|
|
|
+
|
|
|
+interface ToolData {
|
|
|
+ toolName: string
|
|
|
+ params: string
|
|
|
+ result?: string
|
|
|
+}
|
|
|
+
|
|
|
+const props = defineProps<Props>()
|
|
|
+
|
|
|
+const showDialog = ref(false)
|
|
|
+const activeTab = ref('info')
|
|
|
+const toolData = ref<ToolData>({
|
|
|
+ toolName: '',
|
|
|
+ params: '',
|
|
|
+ result: undefined
|
|
|
+})
|
|
|
+
|
|
|
+// 计算是否为加载状态(2行数据表示正在加载)
|
|
|
+const isLoading = computed(() => {
|
|
|
+ return !toolData.value.result
|
|
|
+})
|
|
|
+
|
|
|
+// 解析传入的数据
|
|
|
+onMounted(() => {
|
|
|
+ try {
|
|
|
+ const decodedData = decodeURIComponent(props.data)
|
|
|
+ const lines = decodedData.trim().split('\n')
|
|
|
+
|
|
|
+ if (lines.length >= 2) {
|
|
|
+ toolData.value = {
|
|
|
+ toolName: lines[0] || '未知工具',
|
|
|
+ params: lines[1] || '无参数',
|
|
|
+ result: lines[2] || undefined // 第三行为结果,可能不存在
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ toolData.value = {
|
|
|
+ toolName: '数据格式错误',
|
|
|
+ params: '应为2-3行格式',
|
|
|
+ result: undefined
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析工具数据失败:', e)
|
|
|
+ toolData.value = {
|
|
|
+ toolName: '数据解析失败',
|
|
|
+ params: '解析错误',
|
|
|
+ result: undefined
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 获取工具描述
|
|
|
+const getToolDescription = () => {
|
|
|
+ if (isLoading.value) {
|
|
|
+ return '工具正在执行中...'
|
|
|
+ }
|
|
|
+ return '工具执行完成,点击查看详情'
|
|
|
+}
|
|
|
+
|
|
|
+// 获取状态类型
|
|
|
+const getStatusType = () => {
|
|
|
+ if (isLoading.value) return 'warning'
|
|
|
+ return 'success'
|
|
|
+}
|
|
|
+
|
|
|
+// 获取状态文本
|
|
|
+const getStatusText = () => {
|
|
|
+ if (isLoading.value) return '执行中'
|
|
|
+ return '执行完成'
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.tools-loading-card {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
+}
|
|
|
+
|
|
|
+.tools-loading-card:hover {
|
|
|
+ border-color: var(--el-color-primary);
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.card-title {
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+}
|
|
|
+
|
|
|
+.el-icon {
|
|
|
+ color: var(--el-color-primary);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.is-loading {
|
|
|
+ color: var(--el-color-warning);
|
|
|
+ animation: rotating 2s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes rotating {
|
|
|
+ 0% {
|
|
|
+ transform: rotate(0deg);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.card-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.tool-description {
|
|
|
+ margin: 0;
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-content {
|
|
|
+ padding: 16px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.json-content {
|
|
|
+ background-color: var(--el-fill-color-light);
|
|
|
+ border: 1px solid var(--el-border-color-light);
|
|
|
+ border-radius: 6px;
|
|
|
+ padding: 16px;
|
|
|
+ margin: 0;
|
|
|
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.5;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-all;
|
|
|
+ overflow-x: auto;
|
|
|
+}
|
|
|
+</style>
|