refactor: 删除无用组件

This commit is contained in:
Wenjie Zhang 2026-04-27 09:26:16 +08:00
parent 04db4357b4
commit e9678e32d3
5 changed files with 139 additions and 3475 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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>

View File

@ -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;