feat: 添加工具市场前端页面
- 新增 Tools.vue 页面:工具列表、分类筛选、搜索、新建/编辑/删除/测试 - 新增路由 /tools - 导航菜单添加"工具市场"入口 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,10 @@
|
||||
<el-icon><Connection /></el-icon>
|
||||
<span>数据源管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="tools" @click="router.push('/tools')">
|
||||
<el-icon><Tools /></el-icon>
|
||||
<span>工具市场</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="model-configs" @click="router.push('/model-configs')">
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span>模型配置管理</span>
|
||||
@@ -90,7 +94,7 @@
|
||||
import { computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { Document, User, List, Connection, Setting, Star, Lock, Monitor, Bell, Grid, DataAnalysis } from '@element-plus/icons-vue'
|
||||
import { Document, User, List, Connection, Setting, Star, Lock, Monitor, Bell, Grid, DataAnalysis, Tools } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -104,6 +108,7 @@ const activeMenu = computed(() => {
|
||||
if (route.path === '/agents' || route.path.startsWith('/agents/')) return 'agents'
|
||||
if (route.path === '/executions' || route.path.startsWith('/executions/')) return 'executions'
|
||||
if (route.path === '/data-sources') return 'data-sources'
|
||||
if (route.path === '/tools') return 'tools'
|
||||
if (route.path === '/model-configs') return 'model-configs'
|
||||
if (route.path === '/node-templates') return 'node-templates'
|
||||
if (route.path === '/permissions') return 'permissions'
|
||||
@@ -127,6 +132,8 @@ const handleMenuSelect = (key: string) => {
|
||||
router.push('/executions')
|
||||
} else if (key === 'data-sources') {
|
||||
router.push('/data-sources')
|
||||
} else if (key === 'tools') {
|
||||
router.push('/tools')
|
||||
} else if (key === 'model-configs') {
|
||||
router.push('/model-configs')
|
||||
} else if (key === 'node-templates') {
|
||||
|
||||
@@ -112,6 +112,12 @@ const router = createRouter({
|
||||
component: () => import('@/views/NodeTemplates.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/tools',
|
||||
name: 'tools',
|
||||
component: () => import('@/views/Tools.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/agent-chat',
|
||||
name: 'agent-chat',
|
||||
|
||||
604
frontend/src/views/Tools.vue
Normal file
604
frontend/src/views/Tools.vue
Normal file
@@ -0,0 +1,604 @@
|
||||
<template>
|
||||
<MainLayout>
|
||||
<div class="tools-page">
|
||||
<!-- 工具栏 -->
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="搜索工具名称或描述..."
|
||||
clearable
|
||||
style="width: 280px"
|
||||
@input="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-select v-model="categoryFilter" placeholder="全部分类" clearable style="width: 160px" @change="fetchTools">
|
||||
<el-option
|
||||
v-for="cat in categories"
|
||||
:key="cat"
|
||||
:label="cat"
|
||||
:value="cat"
|
||||
/>
|
||||
</el-select>
|
||||
<el-radio-group v-model="scopeFilter" @change="fetchTools">
|
||||
<el-radio-button value="public">公开工具</el-radio-button>
|
||||
<el-radio-button value="mine">我的工具</el-radio-button>
|
||||
<el-radio-button value="all">全部</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<el-button type="primary" @click="showCreateDialog">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新建工具
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工具列表 -->
|
||||
<el-table :data="tools" v-loading="loading" stripe style="width: 100%">
|
||||
<el-table-column prop="name" label="名称" min-width="140">
|
||||
<template #default="{ row }">
|
||||
<div class="tool-name">
|
||||
<span class="name-text">{{ row.name }}</span>
|
||||
<el-tag v-if="row.implementation_type === 'builtin'" size="small" type="info">内置</el-tag>
|
||||
<el-tag v-else-if="row.implementation_type === 'code'" size="small" type="warning">代码</el-tag>
|
||||
<el-tag v-else-if="row.implementation_type === 'http'" size="small" type="success">HTTP</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="category" label="分类" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.category" size="small" effect="plain">{{ row.category }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="use_count" label="使用次数" width="100" align="center" />
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="showTestDialog(row)">测试</el-button>
|
||||
<el-button
|
||||
v-if="row.implementation_type !== 'builtin'"
|
||||
size="small"
|
||||
@click="showEditDialog(row)"
|
||||
>编辑</el-button>
|
||||
<el-popconfirm
|
||||
v-if="row.implementation_type !== 'builtin'"
|
||||
title="确定删除此工具?"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<el-empty v-if="!loading && tools.length === 0" description="暂无工具">
|
||||
<el-button type="primary" @click="showCreateDialog">创建第一个工具</el-button>
|
||||
</el-empty>
|
||||
|
||||
<!-- 创建/编辑对话框 -->
|
||||
<el-dialog
|
||||
v-model="formDialogVisible"
|
||||
:title="isEditing ? '编辑工具' : '新建工具'"
|
||||
width="700px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form :model="form" label-position="top" :rules="formRules" ref="formRef">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工具名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="唯一名称,如 translate_text" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-select v-model="form.category" placeholder="选择分类" clearable filterable allow-create style="width:100%">
|
||||
<el-option v-for="cat in categories" :key="cat" :label="cat" :value="cat" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" :rows="2" placeholder="工具功能描述" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实现类型" prop="implementation_type">
|
||||
<el-select v-model="form.implementation_type" placeholder="选择类型" @change="onTypeChange">
|
||||
<el-option label="代码 (Python)" value="code" />
|
||||
<el-option label="HTTP 请求" value="http" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否公开">
|
||||
<el-switch v-model="form.is_public" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 代码类型配置 -->
|
||||
<template v-if="form.implementation_type === 'code'">
|
||||
<el-form-item label="Python 代码" prop="implementation_config.source">
|
||||
<el-input
|
||||
v-model="form.implementation_config.source"
|
||||
type="textarea"
|
||||
:rows="10"
|
||||
placeholder="def run(args): ''' args: dict, 包含 function_schema.parameters 中定义的参数 returns: Any (将被 JSON 序列化) ''' x = args.get('x', 0) y = args.get('y', 0) return x + y"
|
||||
font-family="monospace"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- HTTP 类型配置 -->
|
||||
<template v-if="form.implementation_type === 'http'">
|
||||
<el-form-item label="请求 URL" prop="implementation_config.url">
|
||||
<el-input v-model="form.implementation_config.url" placeholder="https://api.example.com/endpoint" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="请求方法" prop="implementation_config.method">
|
||||
<el-select v-model="form.implementation_config.method" style="width:100%">
|
||||
<el-option label="GET" value="GET" />
|
||||
<el-option label="POST" value="POST" />
|
||||
<el-option label="PUT" value="PUT" />
|
||||
<el-option label="DELETE" value="DELETE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="超时(秒)">
|
||||
<el-input-number v-model="form.implementation_config.timeout" :min="1" :max="120" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="请求头 (JSON)">
|
||||
<el-input
|
||||
v-model="formHeadersStr"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder='{"Authorization": "Bearer xxx", "Content-Type": "application/json"}'
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- Parameters Schema -->
|
||||
<el-divider>参数定义 (function_schema.parameters)</el-divider>
|
||||
<el-form-item label="参数 JSON Schema">
|
||||
<el-input
|
||||
v-model="formSchemaStr"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder='{"type": "object", "properties": {"x": {"type": "integer", "description": "第一个数"}, "y": {"type": "integer", "description": "第二个数"}}, "required": ["x", "y"]}'
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="formDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSaveForm" :loading="saving">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 测试对话框 -->
|
||||
<el-dialog
|
||||
v-model="testDialogVisible"
|
||||
:title="'测试工具: ' + (testingTool?.name || '')"
|
||||
width="650px"
|
||||
>
|
||||
<template v-if="testingTool">
|
||||
<!-- 代码工具测试 -->
|
||||
<template v-if="testingTool.implementation_type === 'code'">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="测试参数 (JSON)">
|
||||
<el-input
|
||||
v-model="testArgsStr"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder='{"x": 1, "y": 2}'
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<!-- HTTP 工具测试 -->
|
||||
<template v-else-if="testingTool.implementation_type === 'http'">
|
||||
<el-form label-position="top">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="URL">
|
||||
<el-input v-model="testHttpUrl" :placeholder="testingTool.implementation_config?.url || ''" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="方法">
|
||||
<el-select v-model="testHttpMethod" style="width:100%">
|
||||
<el-option label="GET" value="GET" />
|
||||
<el-option label="POST" value="POST" />
|
||||
<el-option label="PUT" value="PUT" />
|
||||
<el-option label="DELETE" value="DELETE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="请求头 (JSON)">
|
||||
<el-input v-model="testHttpHeadersStr" type="textarea" :rows="3" placeholder='{"Content-Type": "application/json"}' />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求体 (JSON)">
|
||||
<el-input v-model="testHttpBodyStr" type="textarea" :rows="4" placeholder='{"key": "value"}' />
|
||||
</el-form-item>
|
||||
<el-form-item label="参数 (将被替换到 URL) (JSON)">
|
||||
<el-input v-model="testArgsStr" type="textarea" :rows="3" placeholder='{"query": "hello"}' />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<!-- 通用参数 -->
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="参数 (function_schema args)">
|
||||
<el-input
|
||||
v-model="testArgsStr"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder='{"arg1": "value1"}'
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 测试结果 -->
|
||||
<div v-if="testResult" class="test-result">
|
||||
<el-divider>测试结果</el-divider>
|
||||
<el-tag :type="testResult.success ? 'success' : 'danger'" effect="dark" style="margin-bottom: 10px">
|
||||
{{ testResult.success ? '成功' : '失败' }}
|
||||
<span v-if="testResult.elapsed_ms !== undefined"> ({{ testResult.elapsed_ms }}ms)</span>
|
||||
</el-tag>
|
||||
<el-input
|
||||
v-model="testResultOutput"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="testDialogVisible = false">关闭</el-button>
|
||||
<el-button type="primary" @click="handleTest" :loading="testing">测试运行</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</MainLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Search, Plus } from '@element-plus/icons-vue'
|
||||
import api from '@/api'
|
||||
|
||||
interface Tool {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
category?: string
|
||||
function_schema: Record<string, any>
|
||||
implementation_type: string
|
||||
implementation_config?: Record<string, any>
|
||||
is_public: boolean
|
||||
use_count: number
|
||||
user_id?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const testing = ref(false)
|
||||
const tools = ref<Tool[]>([])
|
||||
const categories = ref<string[]>([])
|
||||
const searchQuery = ref('')
|
||||
const categoryFilter = ref('')
|
||||
const scopeFilter = ref('public')
|
||||
|
||||
// 表单
|
||||
const formDialogVisible = ref(false)
|
||||
const isEditing = ref(false)
|
||||
const editingId = ref('')
|
||||
const formRef = ref<any>(null)
|
||||
const form = ref({
|
||||
name: '',
|
||||
description: '',
|
||||
category: '',
|
||||
implementation_type: 'code',
|
||||
implementation_config: { source: '', url: '', method: 'GET', timeout: 30, headers: {} },
|
||||
is_public: false,
|
||||
})
|
||||
const formHeadersStr = ref('{}')
|
||||
const formSchemaStr = ref('{"type": "object", "properties": {}, "required": []}')
|
||||
|
||||
// 测试
|
||||
const testDialogVisible = ref(false)
|
||||
const testingTool = ref<Tool | null>(null)
|
||||
const testArgsStr = ref('{}')
|
||||
const testHttpUrl = ref('')
|
||||
const testHttpMethod = ref('GET')
|
||||
const testHttpHeadersStr = ref('{}')
|
||||
const testHttpBodyStr = ref('')
|
||||
const testResult = ref<any>(null)
|
||||
|
||||
const formRules = {
|
||||
name: [{ required: true, message: '请输入工具名称', trigger: 'blur' }],
|
||||
description: [{ required: true, message: '请输入工具描述', trigger: 'blur' }],
|
||||
implementation_type: [{ required: true, message: '请选择实现类型', trigger: 'change' }],
|
||||
}
|
||||
|
||||
const testResultOutput = computed(() => {
|
||||
if (!testResult.value) return ''
|
||||
const parts: string[] = []
|
||||
if (testResult.value.error) parts.push(`错误: ${testResult.value.error}`)
|
||||
if (testResult.value.status_code) parts.push(`状态码: ${testResult.value.status_code}`)
|
||||
if (testResult.value.result !== undefined && testResult.value.result !== null) {
|
||||
parts.push(`结果: ${JSON.stringify(testResult.value.result, null, 2)}`)
|
||||
}
|
||||
if (testResult.value.body) parts.push(`响应体: ${testResult.value.body}`)
|
||||
return parts.join('\n')
|
||||
})
|
||||
|
||||
// 切换类型时重置配置
|
||||
function onTypeChange() {
|
||||
if (form.value.implementation_type === 'code') {
|
||||
form.value.implementation_config = { source: '' }
|
||||
} else if (form.value.implementation_type === 'http') {
|
||||
form.value.implementation_config = { url: '', method: 'GET', timeout: 30 }
|
||||
formHeadersStr.value = '{}'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取工具列表
|
||||
async function fetchTools() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: Record<string, any> = { scope: scopeFilter.value }
|
||||
if (categoryFilter.value) params.category = categoryFilter.value
|
||||
if (searchQuery.value) params.search = searchQuery.value
|
||||
const res = await api.get('/api/v1/tools', { params })
|
||||
tools.value = res.data || []
|
||||
} catch (e: any) {
|
||||
console.error('获取工具列表失败', e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分类列表
|
||||
async function fetchCategories() {
|
||||
try {
|
||||
const res = await api.get('/api/v1/tools/categories')
|
||||
categories.value = res.data || []
|
||||
} catch (e) {
|
||||
console.error('获取分类列表失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索防抖
|
||||
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
function handleSearch() {
|
||||
if (searchTimer) clearTimeout(searchTimer)
|
||||
searchTimer = setTimeout(fetchTools, 300)
|
||||
}
|
||||
|
||||
// 显示创建对话框
|
||||
function showCreateDialog() {
|
||||
isEditing.value = false
|
||||
editingId.value = ''
|
||||
form.value = {
|
||||
name: '',
|
||||
description: '',
|
||||
category: '',
|
||||
implementation_type: 'code',
|
||||
implementation_config: { source: '' },
|
||||
is_public: false,
|
||||
}
|
||||
formHeadersStr.value = '{}'
|
||||
formSchemaStr.value = '{"type": "object", "properties": {}, "required": []}'
|
||||
formDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 显示编辑对话框
|
||||
function showEditDialog(row: Tool) {
|
||||
isEditing.value = true
|
||||
editingId.value = row.id
|
||||
form.value = {
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
category: row.category || '',
|
||||
implementation_type: row.implementation_type,
|
||||
implementation_config: row.implementation_config ? { ...row.implementation_config } : {},
|
||||
is_public: row.is_public,
|
||||
}
|
||||
formSchemaStr.value = JSON.stringify(row.function_schema?.parameters || { type: 'object', properties: {} }, null, 2)
|
||||
formHeadersStr.value = JSON.stringify(row.implementation_config?.headers || {}, null, 2)
|
||||
formDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 保存表单
|
||||
async function handleSaveForm() {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
let headers: Record<string, any> = {}
|
||||
try {
|
||||
headers = JSON.parse(formHeadersStr.value || '{}')
|
||||
} catch {
|
||||
ElMessage.warning('请求头 JSON 格式不正确')
|
||||
return
|
||||
}
|
||||
|
||||
let params: Record<string, any> = { type: 'object', properties: {} }
|
||||
try {
|
||||
params = JSON.parse(formSchemaStr.value || '{}')
|
||||
} catch {
|
||||
ElMessage.warning('参数 Schema JSON 格式不正确')
|
||||
return
|
||||
}
|
||||
|
||||
const config = { ...form.value.implementation_config }
|
||||
if (form.value.implementation_type === 'http') {
|
||||
config.headers = headers
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name: form.value.name,
|
||||
description: form.value.description,
|
||||
category: form.value.category || null,
|
||||
implementation_type: form.value.implementation_type,
|
||||
implementation_config: config,
|
||||
is_public: form.value.is_public,
|
||||
function_schema: {
|
||||
name: form.value.name,
|
||||
description: form.value.description,
|
||||
parameters: params,
|
||||
},
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
if (isEditing.value) {
|
||||
await api.put(`/api/v1/tools/${editingId.value}`, payload)
|
||||
ElMessage.success('工具已更新')
|
||||
} else {
|
||||
await api.post('/api/v1/tools', payload)
|
||||
ElMessage.success('工具已创建')
|
||||
}
|
||||
formDialogVisible.value = false
|
||||
fetchTools()
|
||||
fetchCategories()
|
||||
} catch (e: any) {
|
||||
console.error('保存工具失败', e)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除工具
|
||||
async function handleDelete(row: Tool) {
|
||||
try {
|
||||
await api.delete(`/api/v1/tools/${row.id}`)
|
||||
ElMessage.success('工具已删除')
|
||||
fetchTools()
|
||||
} catch (e) {
|
||||
console.error('删除工具失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示测试对话框
|
||||
function showTestDialog(row: Tool) {
|
||||
testingTool.value = row
|
||||
testArgsStr.value = '{}'
|
||||
testResult.value = null
|
||||
testHttpUrl.value = row.implementation_config?.url || ''
|
||||
testHttpMethod.value = row.implementation_config?.method || 'GET'
|
||||
testHttpHeadersStr.value = JSON.stringify(row.implementation_config?.headers || {}, null, 2)
|
||||
testHttpBodyStr.value = ''
|
||||
testDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 执行测试
|
||||
async function handleTest() {
|
||||
if (!testingTool.value) return
|
||||
|
||||
let args: Record<string, any> = {}
|
||||
try {
|
||||
args = JSON.parse(testArgsStr.value || '{}')
|
||||
} catch {
|
||||
ElMessage.warning('参数 JSON 格式不正确')
|
||||
return
|
||||
}
|
||||
|
||||
testing.value = true
|
||||
testResult.value = null
|
||||
try {
|
||||
if (testingTool.value.implementation_type === 'code') {
|
||||
const config = testingTool.value.implementation_config
|
||||
const source = config?.source || ''
|
||||
const res = await api.post('/api/v1/tools/test/code', { source, args })
|
||||
testResult.value = res.data
|
||||
} else if (testingTool.value.implementation_type === 'http') {
|
||||
let headers: Record<string, any> = {}
|
||||
try {
|
||||
headers = JSON.parse(testHttpHeadersStr.value || '{}')
|
||||
} catch { /* ignore */ }
|
||||
let body: Record<string, any> | undefined
|
||||
try {
|
||||
body = testHttpBodyStr.value ? JSON.parse(testHttpBodyStr.value) : undefined
|
||||
} catch { /* ignore */ }
|
||||
const res = await api.post('/api/v1/tools/test/http', {
|
||||
url: testHttpUrl.value,
|
||||
method: testHttpMethod.value,
|
||||
headers,
|
||||
body,
|
||||
args,
|
||||
timeout: 30,
|
||||
})
|
||||
testResult.value = res.data
|
||||
}
|
||||
} catch (e: any) {
|
||||
testResult.value = { success: false, error: e.message || '测试执行失败' }
|
||||
} finally {
|
||||
testing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(scopeFilter, fetchTools)
|
||||
watch(categoryFilter, fetchTools)
|
||||
|
||||
onMounted(() => {
|
||||
fetchTools()
|
||||
fetchCategories()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tools-page {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.toolbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tool-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.name-text {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.test-result {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user