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 <rockchinq@gmail.com>
This commit is contained in:
Copilot
2025-11-08 14:03:41 +08:00
committed by GitHub
parent 3edae3e678
commit 8d7976190d
6 changed files with 55 additions and 6 deletions
@@ -286,8 +286,8 @@ export default function PipelineExtension({
variant="outline" variant="outline"
className="flex items-center gap-1 mt-1" className="flex items-center gap-1 mt-1"
> >
<Wrench className="h-3 w-3 text-white" /> <Wrench className="h-3 w-3 text-black dark:text-white" />
<span className="text-xs text-white"> <span className="text-xs text-black dark:text-white">
{t('pipelines.extensions.toolCount', { {t('pipelines.extensions.toolCount', {
count: server.runtime_info.tool_count || 0, count: server.runtime_info.tool_count || 0,
})} })}
@@ -416,14 +416,17 @@ export default function PipelineExtension({
</div> </div>
{server.runtime_info && {server.runtime_info &&
server.runtime_info.status === 'connected' && ( server.runtime_info.status === 'connected' && (
<div className="flex items-center gap-1 mt-1"> <Badge
<Wrench className="h-3 w-3 text-muted-foreground" /> variant="outline"
<span className="text-xs text-muted-foreground"> className="flex items-center gap-1 mt-1"
>
<Wrench className="h-3 w-3 text-black dark:text-white" />
<span className="text-xs text-black dark:text-white">
{t('pipelines.extensions.toolCount', { {t('pipelines.extensions.toolCount', {
count: server.runtime_info.tool_count || 0, count: server.runtime_info.tool_count || 0,
})} })}
</span> </span>
</div> </Badge>
)} )}
</div> </div>
{!server.enable && ( {!server.enable && (
@@ -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 ( return (
<> <>
<div className="!max-w-[70vw] max-w-6xl h-full p-0 flex flex-col bg-white dark:bg-black"> <div className="!max-w-[70vw] max-w-6xl h-full p-0 flex flex-col bg-white dark:bg-black">
@@ -478,6 +504,18 @@ export default function PipelineFormComponent({
{t('pipelines.defaultPipelineCannotDelete')} {t('pipelines.defaultPipelineCannotDelete')}
</div> </div>
)} )}
{isEditMode && (
<Button
type="button"
variant="default"
onClick={handleCopy}
className="bg-green-600 hover:bg-green-700 text-white"
>
{t('common.copy')}
</Button>
)}
<Button type="submit" form="pipeline-form"> <Button type="submit" form="pipeline-form">
{isEditMode ? t('common.save') : t('common.submit')} {isEditMode ? t('common.save') : t('common.submit')}
</Button> </Button>
+2
View File
@@ -39,6 +39,7 @@ const enUS = {
deleteSuccess: 'Deleted successfully', deleteSuccess: 'Deleted successfully',
deleteError: 'Delete failed: ', deleteError: 'Delete failed: ',
addRound: 'Add Round', addRound: 'Add Round',
copy: 'Copy',
copySuccess: 'Copy Successfully', copySuccess: 'Copy Successfully',
test: 'Test', test: 'Test',
forgotPassword: 'Forgot Password?', forgotPassword: 'Forgot Password?',
@@ -440,6 +441,7 @@ const enUS = {
createError: 'Creation failed: ', createError: 'Creation failed: ',
saveSuccess: 'Saved successfully', saveSuccess: 'Saved successfully',
saveError: 'Save failed: ', saveError: 'Save failed: ',
copySuffix: ' Copy',
deleteConfirmation: deleteConfirmation:
'Are you sure you want to delete this pipeline? Bots bound to this pipeline will not work.', 'Are you sure you want to delete this pipeline? Bots bound to this pipeline will not work.',
defaultPipelineCannotDelete: 'Default pipeline cannot be deleted', defaultPipelineCannotDelete: 'Default pipeline cannot be deleted',
+2
View File
@@ -40,6 +40,7 @@ const jaJP = {
deleteSuccess: '削除に成功しました', deleteSuccess: '削除に成功しました',
deleteError: '削除に失敗しました:', deleteError: '削除に失敗しました:',
addRound: 'ラウンドを追加', addRound: 'ラウンドを追加',
copy: 'コピー',
copySuccess: 'コピーに成功しました', copySuccess: 'コピーに成功しました',
test: 'テスト', test: 'テスト',
forgotPassword: 'パスワードを忘れた?', forgotPassword: 'パスワードを忘れた?',
@@ -443,6 +444,7 @@ const jaJP = {
createError: '作成に失敗しました:', createError: '作成に失敗しました:',
saveSuccess: '保存に成功しました', saveSuccess: '保存に成功しました',
saveError: '保存に失敗しました:', saveError: '保存に失敗しました:',
copySuffix: ' Copy',
deleteConfirmation: deleteConfirmation:
'本当にこのパイプラインを削除しますか?このパイプラインに紐付けられたボットは動作しなくなります。', '本当にこのパイプラインを削除しますか?このパイプラインに紐付けられたボットは動作しなくなります。',
defaultPipelineCannotDelete: 'デフォルトパイプラインは削除できません', defaultPipelineCannotDelete: 'デフォルトパイプラインは削除できません',
+2
View File
@@ -39,6 +39,7 @@ const zhHans = {
deleteSuccess: '删除成功', deleteSuccess: '删除成功',
deleteError: '删除失败:', deleteError: '删除失败:',
addRound: '添加回合', addRound: '添加回合',
copy: '复制',
copySuccess: '复制成功', copySuccess: '复制成功',
test: '测试', test: '测试',
forgotPassword: '忘记密码?', forgotPassword: '忘记密码?',
@@ -423,6 +424,7 @@ const zhHans = {
createError: '创建失败:', createError: '创建失败:',
saveSuccess: '保存成功', saveSuccess: '保存成功',
saveError: '保存失败:', saveError: '保存失败:',
copySuffix: ' Copy',
deleteConfirmation: deleteConfirmation:
'你确定要删除这个流水线吗?已绑定此流水线的机器人将无法使用。', '你确定要删除这个流水线吗?已绑定此流水线的机器人将无法使用。',
defaultPipelineCannotDelete: '默认流水线不可删除', defaultPipelineCannotDelete: '默认流水线不可删除',
+2
View File
@@ -39,6 +39,7 @@ const zhHant = {
deleteSuccess: '刪除成功', deleteSuccess: '刪除成功',
deleteError: '刪除失敗:', deleteError: '刪除失敗:',
addRound: '新增回合', addRound: '新增回合',
copy: '複製',
copySuccess: '複製成功', copySuccess: '複製成功',
test: '測試', test: '測試',
forgotPassword: '忘記密碼?', forgotPassword: '忘記密碼?',
@@ -421,6 +422,7 @@ const zhHant = {
createError: '建立失敗:', createError: '建立失敗:',
saveSuccess: '儲存成功', saveSuccess: '儲存成功',
saveError: '儲存失敗:', saveError: '儲存失敗:',
copySuffix: ' Copy',
deleteConfirmation: deleteConfirmation:
'您確定要刪除這個流程線嗎?已綁定此流程線的機器人將無法使用。', '您確定要刪除這個流程線嗎?已綁定此流程線的機器人將無法使用。',
defaultPipelineCannotDelete: '預設流程線不可刪除', defaultPipelineCannotDelete: '預設流程線不可刪除',