From dc07b6ca8f91ff6f68f8febe01b75a0d32100b84 Mon Sep 17 00:00:00 2001 From: Rene Arredondo <120709323+Rene0422@users.noreply.github.com> Date: Sun, 28 Jun 2026 18:17:44 -0700 Subject: [PATCH] Feat: add duplicate action to agent list (#14769) (#14856) closes #14769 ### What problem does this PR solve? _Briefly describe what this PR aims to solve. Include background context that will help reviewers understand the purpose of the PR._ ### Type of change - [ ] Bug Fix (non-breaking change which fixes an issue) - [x] New Feature (non-breaking change which adds functionality) - [ ] Documentation Update - [ ] Refactoring - [ ] Performance Improvement - [ ] Other (please describe): --------- Co-authored-by: Zhichang Yu --- web/src/hooks/use-agent-request.ts | 53 +++++++++++++++++++++++++ web/src/locales/en.ts | 2 + web/src/locales/zh.ts | 2 + web/src/pages/agents/agent-dropdown.tsx | 4 +- 4 files changed, 59 insertions(+), 2 deletions(-) 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';