diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index f0728a4630..27107f1504 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -251,6 +251,59 @@ export const useUpdateAgentSetting = () => { return { data, loading, updateAgentSetting: mutateAsync }; }; +export const useDuplicateAgent = () => { + const queryClient = useQueryClient(); + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: [AgentApiAction.SetAgent, 'duplicate'], + mutationFn: async (agent: Pick) => { + try { + const { data: detail } = await agentService.getAgent(agent.id); + const source = detail?.data; + if (!source) { + message.error(i18n.t('message.requestError')); + return null; + } + + const sourceTitle = agent.title ?? source.title ?? ''; + const { data } = await agentService.createAgent({ + title: i18n.t('flow.copyOfAgentName', { + name: sourceTitle, + defaultValue: `${sourceTitle} (Copy)`, + }), + dsl: source.dsl, + avatar: source.avatar, + description: source.description, + canvas_category: source.canvas_category, + }); + + if (data?.code === 0) { + message.success(i18n.t('message.created')); + queryClient.invalidateQueries({ + queryKey: [AgentApiAction.FetchAgentListByPage], + }); + return data; + } + + message.error(data?.message ?? i18n.t('message.requestError')); + return null; + } catch (error) { + console.error('useDuplicateAgent failed:', error); + message.error( + (error as { message?: string })?.message ?? + i18n.t('message.requestError'), + ); + return null; + } + }, + }); + + return { data, loading, duplicateAgent: mutateAsync }; +}; + export const useDeleteAgent = () => { const queryClient = useQueryClient(); const { diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 25419c4f2f..3b4bdcba87 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -2697,6 +2697,8 @@ This process aggregates variables from multiple branches into a single variable createFromBlank: 'Create from blank', createFromTemplate: 'Create from template', importJsonFile: 'Import JSON file', + duplicate: 'Duplicate', + copyOfAgentName: '{{name}} (copy)', ceateAgent: 'Workflow', createPipeline: 'Ingestion pipeline', chooseAgentType: 'Choose agent type', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index ddedd69359..5670cd95b0 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -2334,6 +2334,8 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系 createFromBlank: '从空白创建', createFromTemplate: '从模板创建', importJsonFile: '导入 JSON 文件', + duplicate: '复制', + copyOfAgentName: '{{name}} (副本)', chooseAgentType: '选择智能体类型', parser: '解析器', parserDescription: '从文件中提取原始文本和结构以供下游处理。', diff --git a/web/src/pages/agents/agent-dropdown.tsx b/web/src/pages/agents/agent-dropdown.tsx index 8c3e569245..e4ed10e084 100644 --- a/web/src/pages/agents/agent-dropdown.tsx +++ b/web/src/pages/agents/agent-dropdown.tsx @@ -9,10 +9,10 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { useDeleteAgent } from '@/hooks/use-agent-request'; +import { useDeleteAgent, useDuplicateAgent } from '@/hooks/use-agent-request'; import { IFlow } from '@/interfaces/database/agent'; -import { PenLine, Tag, Trash2 } from 'lucide-react'; import { MouseEventHandler, PropsWithChildren, useCallback, useState } from 'react'; +import { PenLine, Tag, Trash2 } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { AgentTagEditor } from './agent-tag-editor'; import { useRenameAgent } from './use-rename-agent';