"use client"; import { useState, useEffect } from "react"; import { IconDownload, IconTrash } from "@tabler/icons-react"; import { Card, CardContent } from "@kandev/ui/card"; import { Button } from "@kandev/ui/button"; import { Input } from "@kandev/ui/input"; import { Label } from "@kandev/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@kandev/ui/select"; import type { Workflow, WorkflowStep } from "@/lib/types/http"; import { useHealthyAgentProfiles } from "@/hooks/domains/settings/use-healthy-agent-profiles"; import { useRequest } from "@/lib/http/use-request"; import { useToast } from "@/components/toast-provider"; import { WorkflowExportDialog } from "@/components/settings/workflow-export-dialog"; import { UnsavedChangesBadge, UnsavedSaveButton } from "@/components/settings/unsaved-indicator"; import { WorkflowPipelineEditor } from "@/components/settings/workflow-pipeline-editor"; import { listWorkflowStepsAction } from "@/app/actions/workspaces"; import { HelpTip } from "./workflow-pipeline-editor-helpers"; import { WorkflowDeleteDialog, StepDeleteDialog } from "./workflow-card-dialogs"; import { useWorkflowStepActions, useWorkflowDeleteHandlers, useStepDeleteHandlers, useWorkflowSaveActions, handleExportWorkflow, } from "./workflow-card-actions"; type WorkflowCardProps = { workflow: Workflow; isWorkflowDirty: boolean; initialWorkflowSteps?: WorkflowStep[]; templateStepCount?: number; otherWorkflows?: Workflow[]; onUpdateWorkflow: (updates: { name?: string; description?: string; agent_profile_id?: string; }) => void; onDeleteWorkflow: () => Promise; onSaveWorkflow: () => Promise; onWorkflowCreated?: (created: Workflow) => void; }; function useWorkflowSteps( workflowId: string, initialSteps: WorkflowStep[] | undefined, isNewWorkflow: boolean, toast: ReturnType["toast"], ) { const [workflowSteps, setWorkflowSteps] = useState(initialSteps ?? []); const [workflowLoading, setWorkflowLoading] = useState(false); useEffect(() => { if (isNewWorkflow) { setWorkflowSteps(initialSteps ?? []); setWorkflowLoading(false); return; } let cancelled = false; const load = async () => { setWorkflowLoading(true); try { const res = await listWorkflowStepsAction(workflowId); if (!cancelled) setWorkflowSteps(res.steps ?? []); } catch { if (!cancelled) toast({ title: "Failed to load workflow steps", variant: "error" }); } finally { if (!cancelled) setWorkflowLoading(false); } }; load(); return () => { cancelled = true; }; }, [workflowId, initialSteps, isNewWorkflow, toast]); const refreshWorkflowSteps = async () => { try { const res = await listWorkflowStepsAction(workflowId); setWorkflowSteps(res.steps ?? []); } catch { /* ignore */ } }; return { workflowSteps, setWorkflowSteps, workflowLoading, refreshWorkflowSteps }; } type WorkflowDeleteState = { deleteOpen: boolean; setDeleteOpen: (v: boolean) => void; workflowTaskCount: number | null; setWorkflowTaskCount: (v: number | null) => void; workflowDeleteLoading: boolean; setWorkflowDeleteLoading: (v: boolean) => void; targetWorkflowId: string; setTargetWorkflowId: (v: string) => void; targetWorkflowSteps: WorkflowStep[]; setTargetWorkflowSteps: (v: WorkflowStep[]) => void; targetStepId: string; setTargetStepId: (v: string) => void; migrateLoading: boolean; setMigrateLoading: (v: boolean) => void; }; function useWorkflowDeleteState(): WorkflowDeleteState { const [deleteOpen, setDeleteOpen] = useState(false); const [workflowTaskCount, setWorkflowTaskCount] = useState(null); const [workflowDeleteLoading, setWorkflowDeleteLoading] = useState(false); const [targetWorkflowId, setTargetWorkflowId] = useState(""); const [targetWorkflowSteps, setTargetWorkflowSteps] = useState([]); const [targetStepId, setTargetStepId] = useState(""); const [migrateLoading, setMigrateLoading] = useState(false); return { deleteOpen, setDeleteOpen, workflowTaskCount, setWorkflowTaskCount, workflowDeleteLoading, setWorkflowDeleteLoading, targetWorkflowId, setTargetWorkflowId, targetWorkflowSteps, setTargetWorkflowSteps, targetStepId, setTargetStepId, migrateLoading, setMigrateLoading, }; } type StepDeleteState = { stepDeleteOpen: boolean; setStepDeleteOpen: (v: boolean) => void; stepToDelete: string | null; setStepToDelete: (v: string | null) => void; stepTaskCount: number | null; setStepTaskCount: (v: number | null) => void; targetStepForMigration: string; setTargetStepForMigration: (v: string) => void; stepMigrateLoading: boolean; setStepMigrateLoading: (v: boolean) => void; }; function useStepDeleteState(): StepDeleteState { const [stepDeleteOpen, setStepDeleteOpen] = useState(false); const [stepToDelete, setStepToDelete] = useState(null); const [stepTaskCount, setStepTaskCount] = useState(null); const [targetStepForMigration, setTargetStepForMigration] = useState(""); const [stepMigrateLoading, setStepMigrateLoading] = useState(false); return { stepDeleteOpen, setStepDeleteOpen, stepToDelete, setStepToDelete, stepTaskCount, setStepTaskCount, targetStepForMigration, setTargetStepForMigration, stepMigrateLoading, setStepMigrateLoading, }; } type WorkflowCardActionsProps = { isNewWorkflow: boolean; workflowId: string; setExportYaml: (json: string) => void; setExportOpen: (open: boolean) => void; toast: ReturnType["toast"]; onDeleteClick: () => Promise; deleteDisabled: boolean; }; function WorkflowCardActions({ isNewWorkflow, workflowId, setExportYaml, setExportOpen, toast, onDeleteClick, deleteDisabled, }: WorkflowCardActionsProps) { return (
{!isNewWorkflow && ( )}
); } type WorkflowCardDialogsProps = { wfDel: WorkflowDeleteState; otherWorkflows: Workflow[]; deleteWorkflowLoading: boolean; wfDeleteHandlers: { handleDeleteWorkflow: () => Promise; handleMigrateAndDeleteWorkflow: () => Promise; }; exportOpen: boolean; setExportOpen: (open: boolean) => void; exportYaml: string; stepDel: StepDeleteState; stepsForStepMigration: WorkflowStep[]; stepDeleteHandlers: { handleMigrateAndDeleteStep: () => Promise; handleDeleteStepAndTasks: () => Promise; }; }; type WorkflowCardBodyProps = { workflow: Workflow; isWorkflowDirty: boolean; onUpdateWorkflow: (updates: { name?: string; description?: string; agent_profile_id?: string; }) => void; activeSaveRequest: { isLoading: boolean; status: "idle" | "loading" | "success" | "error" }; handleSaveWorkflow: () => Promise; workflowLoading: boolean; workflowSteps: WorkflowStep[]; stepActions: { handleUpdateWorkflowStep: (id: string, updates: Partial) => Promise; handleAddWorkflowStep: () => Promise; handleRemoveWorkflowStep: (id: string) => Promise; handleReorderWorkflowSteps: (steps: WorkflowStep[]) => Promise; }; }; function WorkflowCardBody({ workflow, isWorkflowDirty, onUpdateWorkflow, activeSaveRequest, handleSaveWorkflow, workflowLoading, workflowSteps, stepActions, }: WorkflowCardBodyProps) { const healthyProfiles = useHealthyAgentProfiles(workflow.agent_profile_id); return ( <>
onUpdateWorkflow({ name: e.target.value })} />
{workflowLoading ? (
Loading workflow steps...
) : ( )}
); } function WorkflowCardDialogs({ wfDel, otherWorkflows, deleteWorkflowLoading, wfDeleteHandlers, exportOpen, setExportOpen, exportYaml, stepDel, stepsForStepMigration, stepDeleteHandlers, }: WorkflowCardDialogsProps) { return ( <> ); } function useWorkflowCardState(props: WorkflowCardProps) { const { workflow, initialWorkflowSteps, templateStepCount = 0, otherWorkflows = [] } = props; const { onDeleteWorkflow, onSaveWorkflow, onWorkflowCreated } = props; const { toast } = useToast(); const [exportOpen, setExportOpen] = useState(false); const [exportYaml, setExportYaml] = useState(""); const wfDel = useWorkflowDeleteState(); const stepDel = useStepDeleteState(); const isNewWorkflow = workflow.id.startsWith("temp-"); const deleteWorkflowRequest = useRequest(onDeleteWorkflow); const { workflowSteps, setWorkflowSteps, workflowLoading, refreshWorkflowSteps } = useWorkflowSteps(workflow.id, initialWorkflowSteps, isNewWorkflow, toast); const stepActions = useWorkflowStepActions({ workflow, isNewWorkflow, workflowSteps, setWorkflowSteps, refreshWorkflowSteps, setStepToDelete: stepDel.setStepToDelete, setStepTaskCount: stepDel.setStepTaskCount, setTargetStepForMigration: stepDel.setTargetStepForMigration, setStepDeleteOpen: stepDel.setStepDeleteOpen, toast, }); const { activeSaveRequest, handleSaveWorkflow } = useWorkflowSaveActions({ workflow, isNewWorkflow, workflowSteps, templateStepCount, onSaveWorkflow, onWorkflowCreated, toast, }); const wfDeleteHandlers = useWorkflowDeleteHandlers({ workflow, isNewWorkflow, otherWorkflows, wfDel, deleteWorkflowRun: deleteWorkflowRequest.run, toast, }); const stepDeleteHandlers = useStepDeleteHandlers({ workflow, stepDel, refreshWorkflowSteps, toast, }); const stepsForStepMigration = stepDel.stepToDelete ? workflowSteps.filter((s) => s.id !== stepDel.stepToDelete) : []; return { toast, exportOpen, setExportOpen, exportYaml, setExportYaml, wfDel, stepDel, isNewWorkflow, deleteWorkflowRequest, workflowSteps, workflowLoading, stepActions, activeSaveRequest, handleSaveWorkflow, wfDeleteHandlers, stepDeleteHandlers, stepsForStepMigration, }; } export function WorkflowCard(props: WorkflowCardProps) { const { workflow, isWorkflowDirty, otherWorkflows = [], onUpdateWorkflow } = props; const s = useWorkflowCardState(props); return (
); }