2026-04-27 02:19:01 +08:00
|
|
|
<template>
|
|
|
|
|
<div class="subagent-cards-page extension-page-root">
|
|
|
|
|
<PageShoulder search-placeholder="搜索 SubAgent..." v-model:search="searchQuery">
|
|
|
|
|
<template #actions>
|
|
|
|
|
<a-button type="primary" @click="handleSubagentAdd" class="lucide-icon-btn">
|
|
|
|
|
<Plus :size="14" />
|
|
|
|
|
<span>添加</span>
|
|
|
|
|
</a-button>
|
|
|
|
|
<a-tooltip title="刷新 Subagents">
|
|
|
|
|
<a-button class="lucide-icon-btn" :disabled="loading" @click="fetchSubAgents">
|
|
|
|
|
<RefreshCw :size="14" />
|
|
|
|
|
</a-button>
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
</template>
|
|
|
|
|
</PageShoulder>
|
|
|
|
|
|
2026-04-27 02:24:52 +08:00
|
|
|
<div
|
|
|
|
|
v-if="filteredEnabledSubAgents.length === 0 && filteredDisabledSubAgents.length === 0"
|
|
|
|
|
class="extension-card-grid-empty-state"
|
|
|
|
|
>
|
2026-04-27 02:19:01 +08:00
|
|
|
<a-empty :image="false" :description="searchQuery ? '无匹配 SubAgent' : '暂无 SubAgent'" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
<div v-if="filteredEnabledSubAgents.length" class="extension-section-header">已添加</div>
|
|
|
|
|
<ExtensionCardGrid>
|
2026-04-27 10:22:41 +08:00
|
|
|
<InfoCard
|
2026-04-27 02:19:01 +08:00
|
|
|
v-for="agent in filteredEnabledSubAgents"
|
|
|
|
|
:key="agent.name"
|
|
|
|
|
:title="agent.name"
|
|
|
|
|
:description="agent.description || '暂无描述'"
|
|
|
|
|
:default-icon="BotIcon"
|
2026-04-27 02:24:52 +08:00
|
|
|
:tags="agent.is_builtin ? [{ name: '内置' }] : [{ name: '添加' }]"
|
2026-04-27 02:19:01 +08:00
|
|
|
:status="{ label: '已添加', level: 'success' }"
|
|
|
|
|
@click="navigateToDetail(agent)"
|
|
|
|
|
>
|
2026-04-27 10:22:41 +08:00
|
|
|
</InfoCard>
|
2026-04-27 02:19:01 +08:00
|
|
|
</ExtensionCardGrid>
|
|
|
|
|
|
|
|
|
|
<div v-if="filteredDisabledSubAgents.length" class="extension-section-header">可添加</div>
|
|
|
|
|
<ExtensionCardGrid v-if="filteredDisabledSubAgents.length">
|
2026-04-27 10:22:41 +08:00
|
|
|
<InfoCard
|
2026-04-27 02:19:01 +08:00
|
|
|
v-for="agent in filteredDisabledSubAgents"
|
|
|
|
|
:key="agent.name"
|
|
|
|
|
:title="agent.name"
|
|
|
|
|
:description="agent.description || '暂无描述'"
|
|
|
|
|
:default-icon="BotIcon"
|
2026-04-27 02:24:52 +08:00
|
|
|
:tags="agent.is_builtin ? [{ name: '内置' }] : [{ name: '添加' }]"
|
2026-04-27 02:19:01 +08:00
|
|
|
action-label="添加"
|
|
|
|
|
@click="navigateToDetail(agent)"
|
|
|
|
|
@action-click="handleSetAgentEnabled(agent, true)"
|
|
|
|
|
>
|
2026-04-27 10:22:41 +08:00
|
|
|
</InfoCard>
|
2026-04-27 02:19:01 +08:00
|
|
|
</ExtensionCardGrid>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<a-modal
|
|
|
|
|
v-model:open="formModalVisible"
|
|
|
|
|
title="添加 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 名称(唯一标识)" />
|
|
|
|
|
</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"
|
|
|
|
|
/>
|
2026-04-27 02:24:52 +08:00
|
|
|
<a-button v-if="form.model" type="link" size="small" @click="form.model = ''"
|
|
|
|
|
>清空</a-button
|
|
|
|
|
>
|
2026-04-27 02:19:01 +08:00
|
|
|
</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-form>
|
|
|
|
|
</a-modal>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
import { message } from 'ant-design-vue'
|
|
|
|
|
import { Plus, RefreshCw, Bot } from 'lucide-vue-next'
|
|
|
|
|
import { subagentApi } from '@/apis/subagent_api'
|
|
|
|
|
import { toolApi } from '@/apis/tool_api'
|
|
|
|
|
import ExtensionCardGrid from './ExtensionCardGrid.vue'
|
2026-04-27 10:22:41 +08:00
|
|
|
import InfoCard from '@/components/shared/InfoCard.vue'
|
2026-04-27 02:19:01 +08:00
|
|
|
import PageShoulder from '@/components/shared/PageShoulder.vue'
|
|
|
|
|
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue'
|
|
|
|
|
|
|
|
|
|
const BotIcon = Bot
|
|
|
|
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const subagents = ref([])
|
|
|
|
|
const searchQuery = ref('')
|
|
|
|
|
const availableTools = ref([])
|
|
|
|
|
|
|
|
|
|
const formModalVisible = ref(false)
|
|
|
|
|
const formLoading = 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)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const navigateToDetail = (agent) => {
|
|
|
|
|
router.push({ path: `/extensions/subagent/${encodeURIComponent(agent.name)}` })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSubagentAdd = () => {
|
|
|
|
|
Object.assign(form, { name: '', description: '', system_prompt: '', tools: [], model: '' })
|
|
|
|
|
formModalVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 handleModelSelect = (spec) => {
|
|
|
|
|
form.model = spec || ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
const result = await subagentApi.createSubAgent(data)
|
|
|
|
|
if (result.success) {
|
|
|
|
|
message.success('SubAgent 创建成功')
|
|
|
|
|
formModalVisible.value = false
|
|
|
|
|
await fetchSubAgents()
|
|
|
|
|
} else {
|
|
|
|
|
message.error(result.message || '创建失败')
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
message.error(err.message || '操作失败')
|
|
|
|
|
} finally {
|
|
|
|
|
formLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
message.error(err.message || '操作失败')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchSubAgents = async () => {
|
|
|
|
|
try {
|
|
|
|
|
loading.value = true
|
|
|
|
|
const result = await subagentApi.getSubAgents()
|
|
|
|
|
if (result.success) {
|
|
|
|
|
subagents.value = result.data || []
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
message.error(err.message || '获取列表失败')
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
fetchSubAgents()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
defineExpose({ fetchSubAgents, loading })
|
|
|
|
|
</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%;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-27 02:24:52 +08:00
|
|
|
</style>
|