feat: allow multi-file selection for automation pipeline (#88)

* feat: allow multi-file selection for automation pipeline

Add two ways to import server-stored files into the pipeline:

1. Files page: "Pipeline" bulk action button and "Open in Pipeline"
   button in file details panel — navigates to /automate with selected
   file IDs via React Router state.

2. Automate page: "Import from Library" button opens a modal with
   thumbnails, search, and multi-select checkboxes to pick files from
   the user's server-stored library.

Both paths download the selected files and load them into the existing
useFileStore, reusing the batch pipeline processing infrastructure.

Closes #35

* fix: resolve 8 pre-existing test failures across unit and integration suites

- file-validation.ts: Return valid:false when Sharp fails to read
  metadata for standard formats (PNG, JPEG, BMP) instead of silently
  accepting corrupt buffers. CLI-decoded formats already skip Sharp.

- pipeline.ts: Enforce hard cap of 20 steps via .max() instead of
  relying on MAX_PIPELINE_STEPS env var (default 0 = unlimited).
  Tighten name limit to 100 chars and description to 500 chars to
  match test expectations.

- env.ts: Change MAX_LOGO_SIZE_KB default from 2048 to 500 to match
  the branding upload size limit the tests verify.
This commit is contained in:
Ashim
2026-04-22 00:02:00 +08:00
committed by GitHub
parent 9a015c8501
commit 2d7a61c18f
7 changed files with 373 additions and 26 deletions
+15 -3
View File
@@ -1,5 +1,5 @@
import { TOOLS } from "@ashim/shared";
import { FileImage } from "lucide-react";
import { FileImage, Workflow } from "lucide-react";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import {
@@ -165,8 +165,8 @@ export function FileDetails({ mobile = false }: FileDetailsProps) {
</div>
</div>
{/* Open File button */}
<div className={cn("border-t border-border", mobile ? "pt-3" : "pt-4")}>
{/* Action buttons */}
<div className={cn("border-t border-border flex flex-col gap-2", mobile ? "pt-3" : "pt-4")}>
<button
type="button"
onClick={handleOpenFile}
@@ -174,6 +174,18 @@ export function FileDetails({ mobile = false }: FileDetailsProps) {
>
Open File
</button>
<button
type="button"
onClick={() => {
const { checkedIds } = useFilesPageStore.getState();
const ids = checkedIds.size > 1 ? Array.from(checkedIds) : [details.id];
navigate("/automate", { state: { libraryFileIds: ids } });
}}
className="w-full px-4 py-2 border border-primary text-primary text-sm font-medium rounded-lg hover:bg-primary/5 transition-colors flex items-center justify-center gap-2"
>
<Workflow className="h-4 w-4" />
Open in Pipeline
</button>
</div>
</div>
);