From a35fd853538cd96241c4431d2870fb2771598ce2 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Sun, 24 May 2026 00:47:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Agent=20=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=9D=A2=E6=9D=BF=20+=20ModelManageView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 AgentManagePanel 组件,集中管理 Agent 实例 - ModelConfigView 重命名为 ModelProviderManagePanel - 新增 ModelManageView 页面整合模型与 Agent 管理 - 更新路由、侧边栏导航 - HomeView 调整布局,新增 Agent 管理入口 --- web/src/assets/defaults/agent.png | Bin 0 -> 32912 bytes .../model-management/AgentManagePanel.vue | 1006 +++++++++++++++++ .../ModelProviderManagePanel.vue} | 303 ++--- web/src/layouts/AppLayout.vue | 4 +- web/src/router/index.js | 10 +- web/src/views/HomeView.vue | 28 +- web/src/views/ModelManageView.vue | 136 +++ 7 files changed, 1223 insertions(+), 264 deletions(-) create mode 100644 web/src/assets/defaults/agent.png create mode 100644 web/src/components/model-management/AgentManagePanel.vue rename web/src/{views/ModelConfigView.vue => components/model-management/ModelProviderManagePanel.vue} (88%) create mode 100644 web/src/views/ModelManageView.vue diff --git a/web/src/assets/defaults/agent.png b/web/src/assets/defaults/agent.png new file mode 100644 index 0000000000000000000000000000000000000000..d21e957d8d2ffb77816366320c0cd59593dafa6e GIT binary patch literal 32912 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
RR)A$a;pV#jWuwpJS4_=3 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 xNnfU5ib&2i)T!W!F +import { computed, onMounted, reactive, ref } from 'vue' +import { message, Modal } from 'ant-design-vue' +import { + Plus, + RefreshCw, + Trash2, + Edit3, + Bot, + Microscope, + MoreVertical, + Upload, + Info, + SlidersHorizontal, + Wrench, + MoreHorizontal +} from 'lucide-vue-next' + +import { agentApi } from '@/apis/agent_api' +import { userApi } from '@/apis/user_api' +import AgentRuntimeConfigForm from '@/components/AgentRuntimeConfigForm.vue' +import ShareConfigForm from '@/components/ShareConfigForm.vue' +import { isBuiltinAgent, useAgentStore } from '@/stores/agent' +import { useUserStore } from '@/stores/user' +import PageShoulder from '@/components/shared/PageShoulder.vue' +import InfoCard from '@/components/shared/InfoCard.vue' +import ExtensionCardGrid from '@/components/extensions/ExtensionCardGrid.vue' +import defaultAgentIcon from '@/assets/defaults/agent.png' +import { MAX_IMAGE_UPLOAD_SIZE_BYTES, MAX_IMAGE_UPLOAD_SIZE_MB } from '@/utils/upload_limits' + +const userStore = useUserStore() +const agentStore = useAgentStore() +const agentLoading = ref(false) +const saving = ref(false) +const searchQuery = ref('') + +const agentBackendOptions = ref([]) + +const showAgentModal = ref(false) +const editingAgentId = ref(null) +const agentModalActiveTab = ref('basic') +const agentIconUploading = ref(false) +const agentShareConfigFormRef = ref(null) +const runtimeConfigFormRef = ref(null) +const agentShareConfig = ref({ access_level: 'user', department_ids: [], user_uids: [] }) +const agentForm = reactive({ + slug: '', + name: '', + backend_id: 'ChatbotAgent', + description: '', + icon: '' +}) + +const runtimeAgentModalTabs = ['model', 'tools', 'other'] +const agentModalMenuItems = computed(() => { + const items = [{ key: 'basic', label: '基本信息', icon: Info }] + if (editingAgentId.value) { + items.push( + { key: 'model', label: '模型配置', icon: SlidersHorizontal }, + { key: 'tools', label: '工具配置', icon: Wrench }, + { key: 'other', label: '其他配置', icon: MoreHorizontal } + ) + } + return items +}) +const runtimeConfigSegment = computed(() => + runtimeAgentModalTabs.includes(agentModalActiveTab.value) ? agentModalActiveTab.value : 'model' +) +const isRuntimeAgentModalTab = (key) => runtimeAgentModalTabs.includes(key) + +const filteredAgents = computed(() => { + const keyword = searchQuery.value.trim().toLowerCase() + const list = agentStore.agents || [] + const filtered = keyword + ? list.filter( + (agent) => + String(agent.name || '').toLowerCase().includes(keyword) || + String(agent.id || '').toLowerCase().includes(keyword) || + String(agent.backend_id || '').toLowerCase().includes(keyword) + ) + : list + return [...filtered].sort((a, b) => { + if (isBuiltinAgent(a) !== isBuiltinAgent(b)) return isBuiltinAgent(a) ? -1 : 1 + return String(a.name || a.id).localeCompare(String(b.name || b.id), 'zh-CN') + }) +}) + +const agentStats = computed(() => ({ + total: agentStore.agents.length, + builtin: agentStore.agents.filter(isBuiltinAgent).length, + manageable: agentStore.agents.filter((agent) => agent.can_manage).length, + global: agentStore.agents.filter((agent) => agent.share_config?.access_level === 'global').length +})) +const getDefaultBackendId = () => agentBackendOptions.value[0]?.value || 'ChatbotAgent' + +const getInitialShareConfig = () => ({ + access_level: userStore.isAdmin ? 'global' : 'user', + department_ids: [], + user_uids: userStore.uid ? [userStore.uid] : [] +}) + +const normalizeShareConfigForPayload = () => { + if (isBuiltinAgent({ id: editingAgentId.value })) { + return { access_level: 'global', department_ids: [], user_uids: [] } + } + const config = agentShareConfig.value || getInitialShareConfig() + const accessLevel = userStore.isAdmin ? config.access_level : 'user' + return { + access_level: accessLevel, + department_ids: accessLevel === 'department' ? config.department_ids || [] : [], + user_uids: accessLevel === 'user' ? config.user_uids || [] : [] + } +} + +const isEditingBuiltinAgent = computed(() => isBuiltinAgent({ id: editingAgentId.value })) +const canEditAgentShareConfig = computed(() => !isEditingBuiltinAgent.value) +const getAgentShareAllowedLevels = () => { + if (isEditingBuiltinAgent.value) return ['global'] + return userStore.isAdmin ? ['global', 'department', 'user'] : ['user'] +} + +const canManageAgent = (agent) => !!agent?.can_manage +const agentModalTitle = computed(() => (editingAgentId.value ? '编辑智能体' : '新增智能体')) +const getAgentIconSrc = (agent) => agent?.icon || defaultAgentIcon +const agentPreviewIcon = computed(() => agentForm.icon?.trim() || defaultAgentIcon) +const selectedBackendOption = computed(() => + agentBackendOptions.value.find((backend) => backend.value === agentForm.backend_id) +) +const selectedBackendLabel = computed(() => selectedBackendOption.value?.label || agentForm.backend_id || '未选择') +const selectedBackendIcon = computed(() => { + const backendText = `${agentForm.backend_id} ${selectedBackendLabel.value}`.toLowerCase() + return backendText.includes('deep') || backendText.includes('search') ? Microscope : Bot +}) + +// ============ Agent Operations ============ +const loadAgentBackends = async () => { + try { + const response = await agentApi.getAgentBackends() + agentBackendOptions.value = (response.backends || []).map((backend) => ({ + label: backend.name || backend.backend_id, + value: backend.backend_id + })) + } catch (error) { + message.error(error.message || '加载智能体后端失败') + } +} + +const loadAgents = async () => { + agentLoading.value = true + try { + await agentStore.fetchAgents() + } catch (error) { + message.error(error.message || '加载智能体失败') + } finally { + agentLoading.value = false + } +} + +const resetAgentForm = () => { + Object.assign(agentForm, { + slug: '', + name: '', + backend_id: getDefaultBackendId(), + description: '', + icon: '' + }) + agentShareConfig.value = getInitialShareConfig() +} + +const openCreateAgentModal = () => { + editingAgentId.value = null + agentModalActiveTab.value = 'basic' + resetAgentForm() + agentStore.resetAgentConfig() + showAgentModal.value = true +} + +const openEditAgentModal = async (agent) => { + if (!canManageAgent(agent)) return + const detail = await agentStore.fetchAgentDetail(agent.id, true) + editingAgentId.value = detail.id + agentModalActiveTab.value = 'basic' + Object.assign(agentForm, { + slug: detail.id || detail.slug || '', + name: detail.name || '', + backend_id: detail.backend_id || 'ChatbotAgent', + description: detail.description || '', + icon: detail.icon || '' + }) + agentShareConfig.value = isBuiltinAgent(detail) + ? { access_level: 'global', department_ids: [], user_uids: [] } + : detail.share_config || getInitialShareConfig() + await agentStore.selectAgent(detail.id) + showAgentModal.value = true +} + +const closeAgentModal = () => { + if (saving.value || agentIconUploading.value) return + showAgentModal.value = false +} + +const beforeAgentIconUpload = (file) => { + if (!file.type.startsWith('image/')) { + message.error('只能上传图片文件') + return false + } + + if (file.size > MAX_IMAGE_UPLOAD_SIZE_BYTES) { + message.error(`图片大小不能超过 ${MAX_IMAGE_UPLOAD_SIZE_MB}MB`) + return false + } + + uploadAgentIcon(file) + return false +} + +const uploadAgentIcon = async (file) => { + agentIconUploading.value = true + try { + const data = await userApi.uploadImage(file) + agentForm.icon = data.image_url || data.url || '' + message.success('图标上传成功') + } catch (error) { + message.error(error.message || '图标上传失败') + } finally { + agentIconUploading.value = false + } +} + +const buildAgentPayload = () => { + const payload = { + name: agentForm.name.trim(), + description: agentForm.description.trim() || null, + icon: agentForm.icon.trim() || null, + share_config: normalizeShareConfigForPayload() + } + + if (!editingAgentId.value) { + payload.slug = agentForm.slug.trim() || undefined + payload.backend_id = agentForm.backend_id + } + + return payload +} + +const saveAgent = async () => { + if (!agentForm.name.trim()) { + agentModalActiveTab.value = 'basic' + message.error('请填写智能体名称') + return + } + + const validation = canEditAgentShareConfig.value ? agentShareConfigFormRef.value?.validate?.() : null + if (validation && !validation.valid) { + agentModalActiveTab.value = 'basic' + message.error(validation.message) + return + } + + saving.value = true + try { + const payload = buildAgentPayload() + if (editingAgentId.value) { + const validatedConfig = runtimeConfigFormRef.value?.validateAndFilterConfig?.() + if (validatedConfig && JSON.stringify(validatedConfig) !== JSON.stringify(agentStore.agentConfig)) { + agentStore.updateAgentConfig(validatedConfig) + } + if (agentStore.hasConfigChanges) { + payload.config_json = { context: agentStore.agentConfig } + } + await agentStore.updateAgentProfile(editingAgentId.value, payload) + agentStore.originalAgentConfig = { ...agentStore.agentConfig } + message.success('智能体已保存') + } else { + await agentStore.createAgent(payload) + message.success('智能体已创建') + } + showAgentModal.value = false + } catch (error) { + message.error(error.message || '保存智能体失败') + } finally { + saving.value = false + } +} + +const deleteAgent = async (agent) => { + if (isBuiltinAgent(agent)) { + message.warning('内置智能体不能删除') + return + } + Modal.confirm({ + title: `删除 ${agent.name}`, + content: '删除后不可恢复,已绑定该智能体的历史对话仍保留原始绑定信息。', + okText: '删除', + okType: 'danger', + cancelText: '取消', + async onOk() { + try { + await agentStore.deleteAgent(agent.id) + message.success('智能体已删除') + } catch (error) { + message.error(error.message || '删除智能体失败') + } + } + }) +} + +onMounted(async () => { + await Promise.all([loadAgentBackends(), loadAgents()]) +}) + +defineExpose({ + loading: agentLoading, + stats: agentStats, + refresh: loadAgents +}) + + + + + + diff --git a/web/src/views/ModelConfigView.vue b/web/src/components/model-management/ModelProviderManagePanel.vue similarity index 88% rename from web/src/views/ModelConfigView.vue rename to web/src/components/model-management/ModelProviderManagePanel.vue index af3f225b..f2848e79 100644 --- a/web/src/views/ModelConfigView.vue +++ b/web/src/components/model-management/ModelProviderManagePanel.vue @@ -1,25 +1,14 @@ +