refactor: 删除无用组件
This commit is contained in:
parent
04db4357b4
commit
e9678e32d3
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,595 +0,0 @@
|
||||
<template>
|
||||
<div class="subagents-component extension-page-root">
|
||||
<div v-if="loading" class="loading-bar-wrapper">
|
||||
<div class="loading-bar"></div>
|
||||
</div>
|
||||
<div class="layout-wrapper" :class="{ 'content-loading': loading }">
|
||||
<!-- 左侧:SubAgent 列表 -->
|
||||
<div class="sidebar-list">
|
||||
<!-- 搜索框 -->
|
||||
<div class="sidebar-toolbar">
|
||||
<div class="search-box">
|
||||
<a-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索 SubAgent..."
|
||||
allow-clear
|
||||
class="search-input"
|
||||
>
|
||||
<template #prefix><Search :size="14" class="text-muted" /></template>
|
||||
</a-input>
|
||||
</div>
|
||||
|
||||
<a-tooltip title="刷新 Subagents">
|
||||
<a-button class="sidebar-tool" :disabled="loading" @click="fetchSubAgents">
|
||||
<RotateCw :size="14" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<!-- SubAgent 列表 -->
|
||||
<div class="list-container">
|
||||
<div
|
||||
v-if="!filteredEnabledSubAgents.length && !filteredDisabledSubAgents.length"
|
||||
class="empty-text"
|
||||
>
|
||||
<a-empty
|
||||
:image="false"
|
||||
:description="searchQuery ? '无匹配 SubAgent' : '暂无 SubAgent'"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="filteredEnabledSubAgents.length" class="list-section-title">已添加</div>
|
||||
<template
|
||||
v-for="(agent, index) in filteredEnabledSubAgents"
|
||||
:key="`enabled-${agent.name}`"
|
||||
>
|
||||
<div
|
||||
class="list-item extension-list-item"
|
||||
:class="{ active: currentAgent?.name === agent.name }"
|
||||
@click="selectAgent(agent)"
|
||||
>
|
||||
<div class="item-main-row">
|
||||
<div class="item-header">
|
||||
<Bot :size="16" class="item-icon" />
|
||||
<span class="item-name">{{ agent.name }}</span>
|
||||
</div>
|
||||
<div class="item-status">
|
||||
<span class="status-chip status-chip-success">已添加</span>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-hover-action danger"
|
||||
@click.stop="handleSetAgentEnabled(agent, false)"
|
||||
>
|
||||
移除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-details">
|
||||
<span class="item-desc">{{ agent.description || '暂无描述' }}</span>
|
||||
<div class="item-tags">
|
||||
<span v-if="agent.is_builtin" class="source-tag builtin">内置</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
index < filteredEnabledSubAgents.length - 1 || filteredDisabledSubAgents.length > 0
|
||||
"
|
||||
class="list-separator"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<div v-if="filteredDisabledSubAgents.length" class="list-section-title">可添加</div>
|
||||
<template
|
||||
v-for="(agent, index) in filteredDisabledSubAgents"
|
||||
:key="`disabled-${agent.name}`"
|
||||
>
|
||||
<div
|
||||
class="list-item extension-list-item"
|
||||
:class="{ active: currentAgent?.name === agent.name }"
|
||||
@click="selectAgent(agent)"
|
||||
>
|
||||
<div class="item-main-row">
|
||||
<div class="item-header">
|
||||
<Bot :size="16" class="item-icon" />
|
||||
<span class="item-name">{{ agent.name }}</span>
|
||||
</div>
|
||||
<div class="item-status">
|
||||
<button
|
||||
type="button"
|
||||
class="skill-inline-action skill-inline-action-primary"
|
||||
@click.stop="handleSetAgentEnabled(agent, true)"
|
||||
>
|
||||
添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-details">
|
||||
<span class="item-desc">{{ agent.description || '暂无描述' }}</span>
|
||||
<div class="item-tags">
|
||||
<span v-if="agent.is_builtin" class="source-tag builtin">内置</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="index < filteredDisabledSubAgents.length - 1" class="list-separator"></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:详情面板 -->
|
||||
<div class="main-panel">
|
||||
<div v-if="!currentAgent" class="unselected-state">
|
||||
<div class="hint-box">
|
||||
<Bot :size="40" class="text-muted" />
|
||||
<p>请在左侧选择 SubAgent 进行操作</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="panel-top-bar">
|
||||
<h2 class="panel-title-row">
|
||||
<Bot :size="18" class="panel-title-icon" />
|
||||
<span
|
||||
><strong>{{ currentAgent.name }}</strong></span
|
||||
>
|
||||
</h2>
|
||||
<div class="panel-actions">
|
||||
<a-space :size="8">
|
||||
<a-button
|
||||
size="small"
|
||||
@click="showEditModal(currentAgent)"
|
||||
class="lucide-icon-btn"
|
||||
v-if="!currentAgent.is_builtin"
|
||||
>
|
||||
<Pencil :size="14" />
|
||||
<span>编辑</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
danger
|
||||
ghost
|
||||
:disabled="currentAgent.is_builtin"
|
||||
@click="confirmDeleteAgent(currentAgent)"
|
||||
class="lucide-icon-btn"
|
||||
v-if="!currentAgent.is_builtin"
|
||||
>
|
||||
<Trash2 :size="14" />
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section-container">
|
||||
<div class="detail-section">
|
||||
<div class="section-header">
|
||||
<MessageSquare :size="14" />
|
||||
<span>系统提示词</span>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
<div class="code-panel">
|
||||
<pre class="code-panel-pre">{{ currentAgent.system_prompt }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentAgent.description">
|
||||
<div class="section-header">
|
||||
<FileText :size="14" />
|
||||
<span>描述</span>
|
||||
</div>
|
||||
<div class="section-content description">
|
||||
{{ currentAgent.description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentAgent.model">
|
||||
<div class="section-header">
|
||||
<Cpu :size="14" />
|
||||
<span>模型覆盖</span>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
{{ currentAgent.model }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentAgent.tools && currentAgent.tools.length > 0">
|
||||
<div class="section-header">
|
||||
<Wrench :size="14" />
|
||||
<span>工具</span>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
<a-tag v-for="tool in currentAgent.tools" :key="tool">{{ tool }}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="detail-section"
|
||||
v-if="currentAgent.is_builtin || currentAgent.enabled === false"
|
||||
>
|
||||
<div class="section-header">
|
||||
<Info :size="14" />
|
||||
<span>类型</span>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
<a-tag v-if="currentAgent.is_builtin" color="blue">内置</a-tag>
|
||||
<a-tag v-else color="default">自定义</a-tag>
|
||||
<a-tag v-if="currentAgent.enabled === false" color="default">未添加</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="section-header">
|
||||
<Clock :size="14" />
|
||||
<span>元信息</span>
|
||||
</div>
|
||||
<div class="section-content meta-info">
|
||||
<div class="meta-item">
|
||||
<span class="meta-label">创建时间</span>
|
||||
<span class="meta-value">{{ formatTime(currentAgent.created_at) }}</span>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<span class="meta-label">更新时间</span>
|
||||
<span class="meta-value">{{ formatTime(currentAgent.updated_at) }}</span>
|
||||
</div>
|
||||
<div class="meta-item" v-if="currentAgent.created_by">
|
||||
<span class="meta-label">创建人</span>
|
||||
<span class="meta-value">{{ currentAgent.created_by }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑 SubAgent 模态框 -->
|
||||
<a-modal
|
||||
v-model:open="formModalVisible"
|
||||
:title="editMode ? '编辑 SubAgent' : '添加 SubAgent'"
|
||||
@ok="handleFormSubmit"
|
||||
:confirmLoading="formLoading"
|
||||
@cancel="formModalVisible = false"
|
||||
:maskClosable="false"
|
||||
width="600px"
|
||||
>
|
||||
<a-form layout="vertical" class="extension-form">
|
||||
<a-form-item label="名称" required class="form-item">
|
||||
<a-input
|
||||
v-model:value="form.name"
|
||||
placeholder="请输入 SubAgent 名称(唯一标识)"
|
||||
:disabled="editMode"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="描述" class="form-item">
|
||||
<a-input v-model:value="form.description" placeholder="请输入 SubAgent 描述" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="系统提示词" required class="form-item">
|
||||
<a-textarea v-model:value="form.system_prompt" placeholder="请输入系统提示词" :rows="6" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="工具" class="form-item">
|
||||
<a-select
|
||||
v-model:value="form.tools"
|
||||
mode="tags"
|
||||
placeholder="选择或输入工具名称"
|
||||
style="width: 100%"
|
||||
:options="availableTools"
|
||||
@focus="fetchAvailableTools"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="模型覆盖(可选)" class="form-item">
|
||||
<div class="model-override-row">
|
||||
<ModelSelectorComponent
|
||||
:model_spec="form.model"
|
||||
placeholder="请选择模型"
|
||||
class="model-selector-full"
|
||||
@select-model="handleModelSelect"
|
||||
/>
|
||||
<a-button v-if="form.model" type="link" size="small" @click="form.model = ''">
|
||||
清空
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import {
|
||||
Search,
|
||||
Bot,
|
||||
Pencil,
|
||||
Trash2,
|
||||
RotateCw,
|
||||
Info,
|
||||
MessageSquare,
|
||||
FileText,
|
||||
Cpu,
|
||||
Wrench,
|
||||
Clock
|
||||
} from 'lucide-vue-next'
|
||||
import { subagentApi } from '@/apis/subagent_api'
|
||||
import { toolApi } from '@/apis/tool_api'
|
||||
import { formatFullDateTime } from '@/utils/time'
|
||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue'
|
||||
|
||||
// 状态
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
const subagents = ref([])
|
||||
const searchQuery = ref('')
|
||||
const currentAgent = ref(null)
|
||||
const availableTools = ref([])
|
||||
|
||||
// 表单相关
|
||||
const formModalVisible = ref(false)
|
||||
const formLoading = ref(false)
|
||||
const editMode = ref(false)
|
||||
const form = reactive({
|
||||
name: '',
|
||||
description: '',
|
||||
system_prompt: '',
|
||||
tools: [],
|
||||
model: ''
|
||||
})
|
||||
|
||||
const getSortedSubAgents = (items) => {
|
||||
return [...items].sort((a, b) => {
|
||||
// 内置的排前面
|
||||
if (a.is_builtin !== b.is_builtin) {
|
||||
return a.is_builtin ? -1 : 1
|
||||
}
|
||||
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
|
||||
})
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
const filteredSubAgents = computed(() => {
|
||||
const sorted = getSortedSubAgents(subagents.value)
|
||||
if (!searchQuery.value) return sorted
|
||||
const q = searchQuery.value.toLowerCase()
|
||||
return sorted.filter(
|
||||
(a) => a.name.toLowerCase().includes(q) || (a.description || '').toLowerCase().includes(q)
|
||||
)
|
||||
})
|
||||
|
||||
const filteredEnabledSubAgents = computed(() =>
|
||||
filteredSubAgents.value.filter((item) => item.enabled !== false)
|
||||
)
|
||||
const filteredDisabledSubAgents = computed(() =>
|
||||
filteredSubAgents.value.filter((item) => item.enabled === false)
|
||||
)
|
||||
|
||||
// 获取 SubAgent 列表
|
||||
const fetchSubAgents = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
const result = await subagentApi.getSubAgents()
|
||||
if (result.success) {
|
||||
subagents.value = result.data || []
|
||||
// 保持选中状态
|
||||
if (currentAgent.value) {
|
||||
const latest = subagents.value.find((a) => a.name === currentAgent.value.name)
|
||||
if (latest) {
|
||||
currentAgent.value = latest
|
||||
} else {
|
||||
currentAgent.value = null
|
||||
}
|
||||
}
|
||||
// 默认选中第一个已添加项
|
||||
const defaultList = filteredEnabledSubAgents.value.length
|
||||
? filteredEnabledSubAgents.value
|
||||
: filteredDisabledSubAgents.value
|
||||
if (!currentAgent.value && defaultList.length > 0) {
|
||||
currentAgent.value = defaultList[0]
|
||||
}
|
||||
} else {
|
||||
error.value = result.message || '获取列表失败'
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取 SubAgent 列表失败:', err)
|
||||
error.value = err.message || '获取列表失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取可选工具列表
|
||||
const fetchAvailableTools = async () => {
|
||||
if (availableTools.value.length > 0) return
|
||||
try {
|
||||
const result = await toolApi.getToolOptions()
|
||||
if (result.success && result.data) {
|
||||
availableTools.value = result.data
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取工具选项失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr) => formatFullDateTime(timeStr)
|
||||
|
||||
const handleModelSelect = (spec) => {
|
||||
form.model = spec || ''
|
||||
}
|
||||
|
||||
const handleSetAgentEnabled = async (agent, enabled) => {
|
||||
try {
|
||||
const result = await subagentApi.updateSubAgentStatus(agent.name, enabled)
|
||||
if (result.success) {
|
||||
message.success(result.message || `SubAgent 已${enabled ? '添加' : '移除'}`)
|
||||
await fetchSubAgents()
|
||||
} else {
|
||||
message.error(result.message || '操作失败')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('更新状态失败:', err)
|
||||
message.error(err.message || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 选择 SubAgent
|
||||
const selectAgent = (agent) => {
|
||||
currentAgent.value = agent
|
||||
}
|
||||
|
||||
// 显示添加模态框
|
||||
const showAddModal = () => {
|
||||
editMode.value = false
|
||||
Object.assign(form, {
|
||||
name: '',
|
||||
description: '',
|
||||
system_prompt: '',
|
||||
tools: [],
|
||||
model: ''
|
||||
})
|
||||
formModalVisible.value = true
|
||||
}
|
||||
|
||||
// 显示编辑模态框
|
||||
const showEditModal = async (agent) => {
|
||||
try {
|
||||
const result = await subagentApi.getSubAgent(agent.name)
|
||||
if (result.success && result.data) {
|
||||
editMode.value = true
|
||||
Object.assign(form, {
|
||||
name: result.data.name,
|
||||
description: result.data.description || '',
|
||||
system_prompt: result.data.system_prompt || '',
|
||||
tools: result.data.tools || [],
|
||||
model: result.data.model || ''
|
||||
})
|
||||
formModalVisible.value = true
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取 SubAgent 详情失败,回退使用列表数据:', err)
|
||||
}
|
||||
// 回退:使用列表数据
|
||||
editMode.value = true
|
||||
Object.assign(form, {
|
||||
name: agent.name,
|
||||
description: agent.description || '',
|
||||
system_prompt: agent.system_prompt || '',
|
||||
tools: agent.tools || [],
|
||||
model: agent.model || ''
|
||||
})
|
||||
formModalVisible.value = true
|
||||
}
|
||||
|
||||
// 处理表单提交
|
||||
const handleFormSubmit = async () => {
|
||||
try {
|
||||
// 校验
|
||||
if (!form.name?.trim()) {
|
||||
message.error('名称不能为空')
|
||||
return
|
||||
}
|
||||
if (!form.system_prompt?.trim()) {
|
||||
message.error('系统提示词不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
formLoading.value = true
|
||||
|
||||
const data = {
|
||||
name: form.name.trim(),
|
||||
description: form.description || '',
|
||||
system_prompt: form.system_prompt,
|
||||
tools: form.tools || [],
|
||||
model: form.model || null
|
||||
}
|
||||
|
||||
if (editMode.value) {
|
||||
const result = await subagentApi.updateSubAgent(form.name, data)
|
||||
if (result.success) {
|
||||
message.success('SubAgent 更新成功')
|
||||
} else {
|
||||
message.error(result.message || '更新失败')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
const result = await subagentApi.createSubAgent(data)
|
||||
if (result.success) {
|
||||
message.success('SubAgent 创建成功')
|
||||
} else {
|
||||
message.error(result.message || '创建失败')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
formModalVisible.value = false
|
||||
await fetchSubAgents()
|
||||
} catch (err) {
|
||||
console.error('操作失败:', err)
|
||||
message.error(err.message || '操作失败')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 确认删除 SubAgent
|
||||
const confirmDeleteAgent = (agent) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除 SubAgent',
|
||||
content: `确定要删除 SubAgent "${agent.name}" 吗?此操作不可撤销。`,
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
async onOk() {
|
||||
try {
|
||||
const result = await subagentApi.deleteSubAgent(agent.name)
|
||||
if (result.success) {
|
||||
message.success('SubAgent 删除成功')
|
||||
if (currentAgent.value?.name === agent.name) {
|
||||
currentAgent.value = null
|
||||
}
|
||||
await fetchSubAgents()
|
||||
} else {
|
||||
message.error(result.message || '删除失败')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('删除失败:', err)
|
||||
message.error(err.message || '删除失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
fetchSubAgents()
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
fetchSubAgents,
|
||||
showAddModal
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '@/assets/css/extensions.less';
|
||||
|
||||
.model-override-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.model-selector-full {
|
||||
flex: 1;
|
||||
|
||||
:deep(.model-select) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,292 +0,0 @@
|
||||
<template>
|
||||
<div class="tools-manager-container extension-page-root">
|
||||
<div v-if="loading" class="loading-bar-wrapper">
|
||||
<div class="loading-bar"></div>
|
||||
</div>
|
||||
<div class="layout-wrapper" :class="{ 'content-loading': loading }">
|
||||
<!-- 左侧:工具列表 -->
|
||||
<div class="sidebar-list">
|
||||
<div class="sidebar-toolbar">
|
||||
<div class="search-box">
|
||||
<a-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索工具..."
|
||||
allow-clear
|
||||
class="search-input"
|
||||
>
|
||||
<template #prefix><Search :size="14" class="text-muted" /></template>
|
||||
</a-input>
|
||||
</div>
|
||||
|
||||
<a-tooltip title="刷新工具">
|
||||
<a-button class="sidebar-tool" :disabled="loading" @click="fetchTools">
|
||||
<RotateCw :size="14" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip :title="`当前分类:${currentCategoryLabel}`">
|
||||
<a-dropdown trigger="click">
|
||||
<a-button
|
||||
class="sidebar-tool category-trigger"
|
||||
:class="{ active: !!selectedCategory }"
|
||||
>
|
||||
<SlidersHorizontal :size="14" />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu :selectedKeys="[selectedCategory || 'all']" @click="handleCategorySelect">
|
||||
<a-menu-item key="all">全部分类</a-menu-item>
|
||||
<a-menu-item v-for="cat in categories" :key="cat">
|
||||
{{ categoryLabels[cat] || cat }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="list-container">
|
||||
<div v-if="filteredTools.length === 0" class="empty-text">
|
||||
<a-empty :image="false" description="无匹配工具" />
|
||||
</div>
|
||||
<template v-for="(tool, index) in filteredTools" :key="tool.id">
|
||||
<div
|
||||
class="list-item"
|
||||
:class="{ active: currentTool?.id === tool.id }"
|
||||
@click="selectTool(tool)"
|
||||
>
|
||||
<div class="item-header">
|
||||
<component :is="getToolIcon(tool.id) || Wrench" :size="16" class="item-icon" />
|
||||
<span class="item-name">{{ tool.name }}</span>
|
||||
</div>
|
||||
<div class="item-details item-details-inline">
|
||||
<span class="item-category item-meta">{{
|
||||
categoryLabels[tool.category] || tool.category
|
||||
}}</span>
|
||||
<div class="item-tags">
|
||||
<a-tag v-for="tag in tool.tags" :key="tag" size="small" class="tool-tag">{{
|
||||
tag
|
||||
}}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="index < filteredTools.length - 1" class="list-separator"></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:详情面板 -->
|
||||
<div class="main-panel">
|
||||
<div v-if="!currentTool" class="unselected-state">
|
||||
<div class="hint-box">
|
||||
<Wrench :size="40" class="text-muted" />
|
||||
<p>请在左侧选择工具查看详情</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="panel-top-bar">
|
||||
<div class="panel-title-stack">
|
||||
<h2>
|
||||
<component :is="getToolIcon(currentTool.id) || Wrench" :size="24" />
|
||||
{{ currentTool.name }}
|
||||
</h2>
|
||||
<code class="panel-title-meta">{{ currentTool.id }}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section-container">
|
||||
<div class="detail-section">
|
||||
<div class="section-header">
|
||||
<FileText :size="14" />
|
||||
<span>描述</span>
|
||||
</div>
|
||||
<div class="section-content description">
|
||||
{{ currentTool.description || '无描述' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentTool.config_guide">
|
||||
<div class="section-header">
|
||||
<FileText :size="14" />
|
||||
<span>配置说明</span>
|
||||
</div>
|
||||
<div class="section-content description config-guide">
|
||||
{{ currentTool.config_guide }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="section-header">
|
||||
<Tag :size="14" />
|
||||
<span>分类</span>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
<a-tag :color="categoryColors[currentTool.category] || 'default'">
|
||||
{{ categoryLabels[currentTool.category] || currentTool.category }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="section-header">
|
||||
<Tags :size="14" />
|
||||
<span>标签</span>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
<a-tag v-for="tag in currentTool.tags" :key="tag">{{ tag }}</a-tag>
|
||||
<span v-if="!currentTool.tags?.length" class="text-muted">无</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="currentTool.args?.length">
|
||||
<div class="section-header">
|
||||
<List :size="14" />
|
||||
<span>参数</span>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
<a-table
|
||||
:dataSource="currentTool.args"
|
||||
:columns="argColumns"
|
||||
size="small"
|
||||
:pagination="false"
|
||||
bordered
|
||||
class="args-table"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
Search,
|
||||
Wrench,
|
||||
Tag,
|
||||
Tags,
|
||||
FileText,
|
||||
List,
|
||||
SlidersHorizontal,
|
||||
RotateCw
|
||||
} from 'lucide-vue-next'
|
||||
import { toolApi } from '@/apis/tool_api'
|
||||
import { getToolIcon } from '@/components/ToolCallingResult/toolRegistry'
|
||||
|
||||
const loading = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const selectedCategory = ref('')
|
||||
|
||||
const tools = ref([])
|
||||
const currentTool = ref(null)
|
||||
|
||||
const categories = ['buildin', 'mysql', 'debug']
|
||||
const categoryLabels = {
|
||||
buildin: '内置工具',
|
||||
mysql: 'MySQL',
|
||||
debug: '调试'
|
||||
}
|
||||
const categoryColors = {
|
||||
buildin: 'blue',
|
||||
mysql: 'green',
|
||||
debug: 'orange'
|
||||
}
|
||||
|
||||
const argColumns = [
|
||||
{ title: '参数名', dataIndex: 'name', key: 'name' },
|
||||
{ title: '类型', dataIndex: 'type', key: 'type', width: 80 },
|
||||
{ title: '描述', dataIndex: 'description', key: 'description' }
|
||||
]
|
||||
|
||||
const filteredTools = computed(() => {
|
||||
let result = tools.value
|
||||
if (selectedCategory.value) {
|
||||
result = result.filter((t) => t.category === selectedCategory.value)
|
||||
}
|
||||
if (searchQuery.value) {
|
||||
const q = searchQuery.value.toLowerCase()
|
||||
result = result.filter(
|
||||
(t) =>
|
||||
t.name.toLowerCase().includes(q) ||
|
||||
t.id.toLowerCase().includes(q) ||
|
||||
t.description?.toLowerCase().includes(q) ||
|
||||
t.config_guide?.toLowerCase().includes(q)
|
||||
)
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const currentCategoryLabel = computed(
|
||||
() =>
|
||||
categoryLabels[selectedCategory.value] ||
|
||||
(selectedCategory.value ? selectedCategory.value : '全部分类')
|
||||
)
|
||||
|
||||
const fetchTools = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await toolApi.getTools()
|
||||
tools.value = result?.data || []
|
||||
// 默认选中第一个工具
|
||||
if (!currentTool.value && tools.value.length > 0) {
|
||||
currentTool.value = tools.value[0]
|
||||
}
|
||||
} catch {
|
||||
message.error('加载工具失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const selectTool = (record) => {
|
||||
currentTool.value = record
|
||||
}
|
||||
|
||||
const handleCategorySelect = ({ key }) => {
|
||||
selectedCategory.value = key === 'all' ? '' : key
|
||||
}
|
||||
|
||||
onMounted(fetchTools)
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
fetchTools
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import '@/assets/css/extensions.less';
|
||||
|
||||
.list-item {
|
||||
.item-details {
|
||||
.item-tags {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100px;
|
||||
justify-content: flex-end;
|
||||
.tool-tag {
|
||||
color: var(--gray-500);
|
||||
font-size: 11px;
|
||||
padding: 0 4px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.args-table {
|
||||
:deep(.ant-table) {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.config-guide {
|
||||
white-space: pre-line;
|
||||
}
|
||||
</style>
|
||||
@ -469,6 +469,145 @@ onMounted(() => {
|
||||
@import '@/assets/css/extensions.less';
|
||||
@import '@/assets/css/extension-detail.less';
|
||||
|
||||
|
||||
/* 工具列表样式 */
|
||||
.tools-tab {
|
||||
.tools-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-tools {
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.tools-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.tool-card {
|
||||
background: var(--gray-0);
|
||||
border: 1px solid var(--gray-150);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--gray-200);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.tool-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.tool-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.tool-name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
color: var(--gray-400);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--gray-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tool-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.tool-description {
|
||||
font-size: 13px;
|
||||
color: var(--gray-600);
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse) {
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
.ant-collapse-header {
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
.ant-collapse-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.params-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.param-item {
|
||||
background: var(--gray-50);
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
|
||||
.param-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.param-name {
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
color: var(--gray-900);
|
||||
font-family: @mono-font;
|
||||
}
|
||||
.param-required {
|
||||
font-size: 11px;
|
||||
color: var(--color-error-500);
|
||||
background: var(--color-error-50);
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.param-type {
|
||||
font-size: 11px;
|
||||
color: var(--gray-500);
|
||||
background: var(--gray-100);
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: @mono-font;
|
||||
}
|
||||
}
|
||||
|
||||
.param-desc {
|
||||
font-size: 12px;
|
||||
color: var(--gray-600);
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.mcp-detail {
|
||||
.detail-content-wrapper {
|
||||
flex: 1;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user