From 8d7976190d7ee1a8dc75e34cc2f88b9f3978c702 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:03:41 +0800 Subject: [PATCH] Add pipeline copy button to duplicate existing configurations (#1767) * Initial plan * Add copy button to pipeline configuration page Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> * Add i18n support for copy suffix and address code review feedback Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> * Show new pipeline name in copy toast and close dialog after copy Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> * perf: tool list style in extension tab --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com> Co-authored-by: Junyan Qin --- .../pipeline-extensions/PipelineExtension.tsx | 15 +++++--- .../pipeline-form/PipelineFormComponent.tsx | 38 +++++++++++++++++++ web/src/i18n/locales/en-US.ts | 2 + web/src/i18n/locales/ja-JP.ts | 2 + web/src/i18n/locales/zh-Hans.ts | 2 + web/src/i18n/locales/zh-Hant.ts | 2 + 6 files changed, 55 insertions(+), 6 deletions(-) diff --git a/web/src/app/home/pipelines/components/pipeline-extensions/PipelineExtension.tsx b/web/src/app/home/pipelines/components/pipeline-extensions/PipelineExtension.tsx index 0bd7d664..b1bf4922 100644 --- a/web/src/app/home/pipelines/components/pipeline-extensions/PipelineExtension.tsx +++ b/web/src/app/home/pipelines/components/pipeline-extensions/PipelineExtension.tsx @@ -286,8 +286,8 @@ export default function PipelineExtension({ variant="outline" className="flex items-center gap-1 mt-1" > - - + + {t('pipelines.extensions.toolCount', { count: server.runtime_info.tool_count || 0, })} @@ -416,14 +416,17 @@ export default function PipelineExtension({ {server.runtime_info && server.runtime_info.status === 'connected' && ( -
- - + + + {t('pipelines.extensions.toolCount', { count: server.runtime_info.tool_count || 0, })} -
+ )} {!server.enable && ( diff --git a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx index 0ff2a2cd..b26d34f7 100644 --- a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx +++ b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx @@ -346,6 +346,32 @@ export default function PipelineFormComponent({ } }; + const handleCopy = () => { + if (pipelineId) { + let newPipelineName = ''; + httpClient + .getPipeline(pipelineId) + .then((resp) => { + const originalPipeline = resp.pipeline; + newPipelineName = `${originalPipeline.name}${t('pipelines.copySuffix')}`; + const newPipeline: Pipeline = { + name: newPipelineName, + description: originalPipeline.description, + config: originalPipeline.config, + }; + return httpClient.createPipeline(newPipeline); + }) + .then(() => { + onFinish(); + toast.success(`${t('common.copySuccess')}: ${newPipelineName}`); + onCancel(); + }) + .catch((err) => { + toast.error(t('pipelines.createError') + err.message); + }); + } + }; + return ( <>
@@ -478,6 +504,18 @@ export default function PipelineFormComponent({ {t('pipelines.defaultPipelineCannotDelete')}
)} + + {isEditMode && ( + + )} + diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index b3aa685a..95261cf3 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -39,6 +39,7 @@ const enUS = { deleteSuccess: 'Deleted successfully', deleteError: 'Delete failed: ', addRound: 'Add Round', + copy: 'Copy', copySuccess: 'Copy Successfully', test: 'Test', forgotPassword: 'Forgot Password?', @@ -440,6 +441,7 @@ const enUS = { createError: 'Creation failed: ', saveSuccess: 'Saved successfully', saveError: 'Save failed: ', + copySuffix: ' Copy', deleteConfirmation: 'Are you sure you want to delete this pipeline? Bots bound to this pipeline will not work.', defaultPipelineCannotDelete: 'Default pipeline cannot be deleted', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 478a0608..197632d5 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -40,6 +40,7 @@ const jaJP = { deleteSuccess: '削除に成功しました', deleteError: '削除に失敗しました:', addRound: 'ラウンドを追加', + copy: 'コピー', copySuccess: 'コピーに成功しました', test: 'テスト', forgotPassword: 'パスワードを忘れた?', @@ -443,6 +444,7 @@ const jaJP = { createError: '作成に失敗しました:', saveSuccess: '保存に成功しました', saveError: '保存に失敗しました:', + copySuffix: ' Copy', deleteConfirmation: '本当にこのパイプラインを削除しますか?このパイプラインに紐付けられたボットは動作しなくなります。', defaultPipelineCannotDelete: 'デフォルトパイプラインは削除できません', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index be40bae7..daa0cd0a 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -39,6 +39,7 @@ const zhHans = { deleteSuccess: '删除成功', deleteError: '删除失败:', addRound: '添加回合', + copy: '复制', copySuccess: '复制成功', test: '测试', forgotPassword: '忘记密码?', @@ -423,6 +424,7 @@ const zhHans = { createError: '创建失败:', saveSuccess: '保存成功', saveError: '保存失败:', + copySuffix: ' Copy', deleteConfirmation: '你确定要删除这个流水线吗?已绑定此流水线的机器人将无法使用。', defaultPipelineCannotDelete: '默认流水线不可删除', diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index 95309b25..59b66a41 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -39,6 +39,7 @@ const zhHant = { deleteSuccess: '刪除成功', deleteError: '刪除失敗:', addRound: '新增回合', + copy: '複製', copySuccess: '複製成功', test: '測試', forgotPassword: '忘記密碼?', @@ -421,6 +422,7 @@ const zhHant = { createError: '建立失敗:', saveSuccess: '儲存成功', saveError: '儲存失敗:', + copySuffix: ' Copy', deleteConfirmation: '您確定要刪除這個流程線嗎?已綁定此流程線的機器人將無法使用。', defaultPipelineCannotDelete: '預設流程線不可刪除',