From e5939f7b5a810da53a3c9af90eba31f342666c6a Mon Sep 17 00:00:00 2001 From: SnapOtter Date: Fri, 8 May 2026 20:55:48 +0800 Subject: [PATCH] feat: add pipeline export as JSON file --- apps/web/src/pages/automate-page.tsx | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apps/web/src/pages/automate-page.tsx b/apps/web/src/pages/automate-page.tsx index 97687a40..4a2b6df8 100644 --- a/apps/web/src/pages/automate-page.tsx +++ b/apps/web/src/pages/automate-page.tsx @@ -237,6 +237,27 @@ export function AutomatePage() { [loadSteps], ); + const handleExportPipeline = useCallback((pipeline: SavedPipeline) => { + const exportData = { + format: "snapotter-pipeline" as const, + version: 1, + name: pipeline.name, + description: pipeline.description, + steps: pipeline.steps, + }; + const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const slug = pipeline.name + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, ""); + const a = document.createElement("a"); + a.href = url; + a.download = `${slug || "pipeline"}.snapotter.json`; + a.click(); + URL.revokeObjectURL(url); + }, []); + const handleDownloadAll = useCallback(() => { if (!batchZipBlob) return; const url = URL.createObjectURL(batchZipBlob); @@ -522,6 +543,13 @@ export function AutomatePage() { ({p.steps.length} step{p.steps.length !== 1 ? "s" : ""}) +