diff --git a/backend/automation_script_api_test.go b/backend/automation_script_api_test.go index a05cd07b..fa088a4a 100644 --- a/backend/automation_script_api_test.go +++ b/backend/automation_script_api_test.go @@ -17,8 +17,8 @@ func TestAutomationScriptListSeedsDefaultScriptsOnFreshApp(t *testing.T) { if err != nil { t.Fatalf("AutomationScriptList returned error: %v", err) } - if len(items) != 3 { - t.Fatalf("expected three default scripts, got %d", len(items)) + if len(items) != 4 { + t.Fatalf("expected four default scripts, got %d", len(items)) } byID := make(map[string]automation.ScriptRecord, len(items)) @@ -29,6 +29,7 @@ func TestAutomationScriptListSeedsDefaultScriptsOnFreshApp(t *testing.T) { expectedNames := map[string]string{ "dual-instance-runtime-switch": "双实例启动与 Runtime 切换", "news-query-txt": "查询新闻并写 TXT", + "proton-mail-first-message": "Proton 邮件搜索并读取最新邮件", "web-image-generate-download": "网页图片生成并下载", } @@ -105,13 +106,14 @@ func TestAutomationScriptListAddsMissingBuiltinWhenLegacyMarkerExists(t *testing if err != nil { t.Fatalf("AutomationScriptList returned error: %v", err) } - if len(items) != 4 { - t.Fatalf("expected custom script plus three defaults, got %d items", len(items)) + if len(items) != 5 { + t.Fatalf("expected custom script plus four defaults, got %d items", len(items)) } expectedDefaultIDs := []string{ automation.DualInstanceRuntimeScriptID, automation.NewsQueryTXTScriptID, + automation.ProtonMailFirstMessageID, automation.WebImageGenerateScriptID, } for _, scriptID := range expectedDefaultIDs { diff --git a/backend/automation_script_defaults.go b/backend/automation_script_defaults.go index 17f34357..f303b30f 100644 --- a/backend/automation_script_defaults.go +++ b/backend/automation_script_defaults.go @@ -74,10 +74,17 @@ func (a *App) ensureAutomationScriptDefaults(store *automation.ScriptStore) erro return a.markAutomationScriptDefaultsInitialized() } + importedCount := 0 + var lastImportErr error for _, bundle := range defaults { if _, err := store.ImportBundle(bundle); err != nil { - return err + lastImportErr = err + continue } + importedCount++ + } + if importedCount == 0 && lastImportErr != nil { + return lastImportErr } return a.markAutomationScriptDefaultsInitialized() } @@ -92,15 +99,11 @@ func (a *App) ensureAutomationScriptDefaults(store *automation.ScriptStore) erro if existing, exists := existingByID[bundle.Record.ID]; exists { if existing.Source.Type == "builtin" { bundle.Record = mergeBuiltinDefaultScriptForMigration(existing, bundle.Record) - if _, err := store.ImportBundle(bundle); err != nil { - return err - } + _, _ = store.ImportBundle(bundle) } continue } - if _, err := store.ImportBundle(bundle); err != nil { - return err - } + _, _ = store.ImportBundle(bundle) } } return a.markAutomationScriptDefaultsInitialized() diff --git a/frontend/src/modules/browser/pages/AutomationPage.helpers.ts b/frontend/src/modules/browser/pages/AutomationPage.helpers.ts index 03d9a796..c168bee6 100644 --- a/frontend/src/modules/browser/pages/AutomationPage.helpers.ts +++ b/frontend/src/modules/browser/pages/AutomationPage.helpers.ts @@ -12,7 +12,6 @@ import type { BrowserProfile } from "../types"; export type ImportMode = | "local" | "git"; -export type LocalImportKind = "file" | "directory"; export const DUAL_INSTANCE_SCRIPT_ID = "dual-instance-runtime-switch"; export const NEWS_SCRIPT_ID = "news-query-txt"; diff --git a/frontend/src/modules/browser/pages/AutomationPage.tsx b/frontend/src/modules/browser/pages/AutomationPage.tsx index e6a49f61..49c4b757 100644 --- a/frontend/src/modules/browser/pages/AutomationPage.tsx +++ b/frontend/src/modules/browser/pages/AutomationPage.tsx @@ -34,7 +34,6 @@ import { resolveDualLaunchCodes, type AutomationCardPresentation, type ImportMode, - type LocalImportKind, } from "./AutomationPage.helpers"; export function AutomationPage() { const navigate = useNavigate(); @@ -55,7 +54,6 @@ export function AutomationPage() { useState("playwright-cdp"); const [createName, setCreateName] = useState(""); const [importMode, setImportMode] = useState("local"); - const [localImportKind, setLocalImportKind] = useState("file"); const [gitURL, setGitURL] = useState(""); const [gitRef, setGitRef] = useState(""); const [gitScriptPath, setGitScriptPath] = useState(""); @@ -200,7 +198,6 @@ export function AutomationPage() { const resetImportModal = () => { setImportMode("local"); - setLocalImportKind("file"); setGitURL(""); setGitRef(""); setGitScriptPath(""); @@ -239,44 +236,52 @@ export function AutomationPage() { } }; + const finishImport = (imported: AutomationScriptRecord[], failedCount = 0) => { + if (imported.length === 0) { + throw new Error("未导入任何脚本"); + } + + setScripts((current) => mergeImportedScripts(current, imported)); + setImportOpen(false); + resetImportModal(); + if (imported.length === 1 && failedCount === 0) { + toast.success("脚本已导入"); + openScript(imported[0].id); + return; + } + + toast.success(`已导入 ${imported.length} 个脚本`); + if (failedCount > 0) { + toast.warning(`${failedCount} 个脚本包导入失败`); + } + }; + + const handleLocalImport = async (kind: "file" | "directory") => { + setBusyAction("import"); + try { + const imported = kind === "directory" + ? await importAutomationScriptFromLocalDirectory() + : await importAutomationScriptFromLocalFile(); + finishImport([imported]); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : "脚本导入失败"; + toast.error(message); + } finally { + setBusyAction("none"); + } + }; + const handleImport = async () => { setBusyAction("import"); try { - let imported: AutomationScriptRecord[] = []; - let failedCount = 0; - switch (importMode) { - case "local": - imported = [ - localImportKind === "directory" - ? await importAutomationScriptFromLocalDirectory() - : await importAutomationScriptFromLocalFile(), - ]; - break; case "git": - imported = [ + finishImport([ await importAutomationScriptFromGit(gitURL, gitRef, gitScriptPath), - ]; + ]); break; default: - throw new Error("不支持的导入方式"); - } - - if (imported.length === 0) { - throw new Error("未导入任何脚本"); - } - - setScripts((current) => mergeImportedScripts(current, imported)); - setImportOpen(false); - resetImportModal(); - if (imported.length === 1 && failedCount === 0) { - toast.success("脚本已导入"); - openScript(imported[0].id); - } else { - toast.success(`已导入 ${imported.length} 个脚本`); - if (failedCount > 0) { - toast.warning(`${failedCount} 个脚本包导入失败`); - } + throw new Error("请选择要导入的文件或文件夹"); } } catch (error: unknown) { const message = error instanceof Error ? error.message : "脚本导入失败"; @@ -391,14 +396,14 @@ export function AutomationPage() { open={importOpen} busyAction={modalBusyAction} importMode={importMode} - localImportKind={localImportKind} gitURL={gitURL} gitRef={gitRef} gitScriptPath={gitScriptPath} onClose={closeImportModal} onImport={handleImport} + onImportLocalFile={() => handleLocalImport("file")} + onImportLocalDirectory={() => handleLocalImport("directory")} onImportModeChange={setImportMode} - onLocalImportKindChange={setLocalImportKind} onGitURLChange={setGitURL} onGitRefChange={setGitRef} onGitScriptPathChange={setGitScriptPath} diff --git a/frontend/src/modules/browser/pages/AutomationPageModals.tsx b/frontend/src/modules/browser/pages/AutomationPageModals.tsx index a56a7330..c5db9453 100644 --- a/frontend/src/modules/browser/pages/AutomationPageModals.tsx +++ b/frontend/src/modules/browser/pages/AutomationPageModals.tsx @@ -1,6 +1,6 @@ import { Button, FormItem, Input, Modal, Select } from "../../../shared/components"; import { AUTOMATION_SCRIPT_TYPE_OPTIONS, type AutomationScriptType } from "../automationScripts"; -import type { ImportMode, LocalImportKind } from "./AutomationPage.helpers"; +import type { ImportMode } from "./AutomationPage.helpers"; interface CreateAutomationScriptModalProps { open: boolean; @@ -73,14 +73,14 @@ interface ImportAutomationScriptModalProps { open: boolean; busyAction: "none" | "create" | "import"; importMode: ImportMode; - localImportKind: LocalImportKind; gitURL: string; gitRef: string; gitScriptPath: string; onClose: () => void; onImport: () => Promise; + onImportLocalFile: () => Promise; + onImportLocalDirectory: () => Promise; onImportModeChange: (value: ImportMode) => void; - onLocalImportKindChange: (value: LocalImportKind) => void; onGitURLChange: (value: string) => void; onGitRefChange: (value: string) => void; onGitScriptPathChange: (value: string) => void; @@ -90,14 +90,14 @@ export function ImportAutomationScriptModal({ open, busyAction, importMode, - localImportKind, gitURL, gitRef, gitScriptPath, onClose, onImport, + onImportLocalFile, + onImportLocalDirectory, onImportModeChange, - onLocalImportKindChange, onGitURLChange, onGitRefChange, onGitScriptPathChange, @@ -117,12 +117,14 @@ export function ImportAutomationScriptModal({ > 取消 - + {importMode === "git" ? ( + + ) : null} } > @@ -151,28 +153,21 @@ export function ImportAutomationScriptModal({ {importMode === "local" ? ( -
-
- {[ - { value: "file", label: "ZIP / 文件" }, - { value: "directory", label: "文件夹" }, - ].map((item) => ( - - ))} -
-
- {localImportKind === "directory" - ? "点击导入后选择脚本文件夹,适合一整套本地脚本目录。" - : "点击导入后选择本地文件,支持标准 ZIP 脚本包、JSON 模板和单文件脚本。"} -
+
+ +
) : null}