Files
kandev/apps/web/components/settings/workflow-pipeline-editor-step-actions.tsx
T
2026-02-19 22:18:01 +00:00

255 lines
8.8 KiB
TypeScript

"use client";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@kandev/ui/select";
import { Checkbox } from "@kandev/ui/checkbox";
import { Label } from "@kandev/ui/label";
import type {
WorkflowStep,
OnEnterAction,
OnTurnStartAction,
OnTurnCompleteAction,
OnExitAction,
} from "@/lib/types/http";
import { cn } from "@/lib/utils";
import {
HelpTip,
getTransitionType,
getOnTurnStartTransitionType,
hasDisablePlanMode,
} from "./workflow-pipeline-editor-helpers";
// --- useStepActions hook ---
type UseStepActionsArgs = {
step: WorkflowStep;
onUpdate: (updates: Partial<WorkflowStep>) => void;
};
export function useStepActions({ step, onUpdate }: UseStepActionsArgs) {
const toggleOnEnterAction = (type: string) => {
const currentEvents = step.events ?? {};
const onEnter = currentEvents.on_enter ?? [];
const exists = onEnter.some((a) => a.type === type);
const newOnEnter = exists
? onEnter.filter((a) => a.type !== type)
: [...onEnter, { type } as OnEnterAction];
onUpdate({ events: { ...currentEvents, on_enter: newOnEnter } });
};
const setTransition = (type: string) => {
const currentEvents = step.events ?? {};
const onTurnComplete = (currentEvents.on_turn_complete ?? []).filter(
(a) => !["move_to_next", "move_to_previous", "move_to_step"].includes(a.type),
);
if (type !== "none") onTurnComplete.push({ type } as OnTurnCompleteAction);
onUpdate({ events: { ...currentEvents, on_turn_complete: onTurnComplete } });
};
const setOnTurnStartTransition = (type: string) => {
const currentEvents = step.events ?? {};
const onTurnStart = (currentEvents.on_turn_start ?? []).filter(
(a) => !["move_to_next", "move_to_previous", "move_to_step"].includes(a.type),
);
if (type !== "none") onTurnStart.push({ type } as OnTurnStartAction);
onUpdate({ events: { ...currentEvents, on_turn_start: onTurnStart } });
};
const toggleDisablePlanMode = () => {
const currentEvents = step.events ?? {};
const onTurnComplete = currentEvents.on_turn_complete ?? [];
const exists = onTurnComplete.some((a) => a.type === "disable_plan_mode");
const newOnTurnComplete = exists
? onTurnComplete.filter((a) => a.type !== "disable_plan_mode")
: [...onTurnComplete, { type: "disable_plan_mode" } as OnTurnCompleteAction];
onUpdate({ events: { ...currentEvents, on_turn_complete: newOnTurnComplete } });
};
const toggleOnExitAction = (type: string) => {
const currentEvents = step.events ?? {};
const onExit = currentEvents.on_exit ?? [];
const exists = onExit.some((a) => a.type === type);
const newOnExit = exists
? onExit.filter((a) => a.type !== type)
: [...onExit, { type } as OnExitAction];
onUpdate({ events: { ...currentEvents, on_exit: newOnExit } });
};
return {
toggleOnEnterAction,
setTransition,
setOnTurnStartTransition,
toggleDisablePlanMode,
toggleOnExitAction,
};
}
// --- TurnStartSelect ---
type StepSelectProps = {
step: WorkflowStep;
otherSteps: WorkflowStep[];
onUpdate: (updates: Partial<WorkflowStep>) => void;
readOnly: boolean;
};
export function TurnStartSelect({
step,
otherSteps,
onUpdate,
setOnTurnStartTransition,
readOnly,
}: StepSelectProps & { setOnTurnStartTransition: (t: string) => void }) {
const transitionType = getOnTurnStartTransitionType(step);
return (
<div className="space-y-2">
<div className="flex items-center gap-1.5">
<Label className="text-xs font-medium">On Turn Start</Label>
<HelpTip text="Runs when a user sends a message. Use for review cycles (e.g., move back to In Progress on feedback)." />
</div>
<Select
value={transitionType}
onValueChange={(value) => {
if (readOnly) return;
setOnTurnStartTransition(value);
}}
disabled={readOnly}
>
<SelectTrigger className="w-full h-8">
<SelectValue placeholder="Select action" />
</SelectTrigger>
<SelectContent position="popper" side="bottom" align="start">
<SelectItem value="none">Do nothing</SelectItem>
<SelectItem value="move_to_next">Move to next step</SelectItem>
<SelectItem value="move_to_previous">Move to previous step</SelectItem>
<SelectItem value="move_to_step">Move to specific step</SelectItem>
</SelectContent>
</Select>
{transitionType === "move_to_step" && (
<Select
value={
(step.events?.on_turn_start?.find((a) => a.type === "move_to_step")?.config
?.step_id as string) ?? ""
}
onValueChange={(value) => {
if (readOnly) return;
const currentEvents = step.events ?? {};
const onTurnStart = (currentEvents.on_turn_start ?? []).map((a) =>
a.type === "move_to_step" ? { ...a, config: { step_id: value } } : a,
);
onUpdate({ events: { ...currentEvents, on_turn_start: onTurnStart } });
}}
disabled={readOnly}
>
<SelectTrigger className="w-full h-8">
<SelectValue placeholder="Select step" />
</SelectTrigger>
<SelectContent position="popper" side="bottom" align="start">
{otherSteps.map((s) => (
<SelectItem key={s.id} value={s.id}>
<div className="flex items-center gap-2">
<div className={cn("w-2 h-2 rounded-full", s.color)} />
{s.name}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
);
}
// --- TurnCompleteSelect ---
type TurnCompleteSelectProps = StepSelectProps & {
setTransition: (t: string) => void;
toggleDisablePlanMode: () => void;
planModeEnabled: boolean;
};
export function TurnCompleteSelect({
step,
otherSteps,
onUpdate,
setTransition,
toggleDisablePlanMode,
planModeEnabled,
readOnly,
}: TurnCompleteSelectProps) {
const transitionType = getTransitionType(step);
return (
<div className="space-y-2">
<div className="flex items-center gap-1.5">
<Label className="text-xs font-medium">On Turn Complete</Label>
<HelpTip text="Runs after the agent finishes a turn. Use to auto-advance tasks through the pipeline." />
</div>
<Select
value={transitionType}
onValueChange={(value) => {
if (readOnly) return;
setTransition(value);
}}
disabled={readOnly}
>
<SelectTrigger className="w-full h-8">
<SelectValue placeholder="Select action" />
</SelectTrigger>
<SelectContent position="popper" side="bottom" align="start">
<SelectItem value="none">Do nothing (wait for user)</SelectItem>
<SelectItem value="move_to_next">Move to next step</SelectItem>
<SelectItem value="move_to_previous">Move to previous step</SelectItem>
<SelectItem value="move_to_step">Move to specific step</SelectItem>
</SelectContent>
</Select>
{transitionType === "move_to_step" && (
<Select
value={
(step.events?.on_turn_complete?.find((a) => a.type === "move_to_step")?.config
?.step_id as string) ?? ""
}
onValueChange={(value) => {
if (readOnly) return;
const currentEvents = step.events ?? {};
const onTurnComplete = (currentEvents.on_turn_complete ?? []).map((a) =>
a.type === "move_to_step" ? { ...a, config: { step_id: value } } : a,
);
onUpdate({ events: { ...currentEvents, on_turn_complete: onTurnComplete } });
}}
disabled={readOnly}
>
<SelectTrigger className="w-full h-8">
<SelectValue placeholder="Select step" />
</SelectTrigger>
<SelectContent position="popper" side="bottom" align="start">
{otherSteps.map((s) => (
<SelectItem key={s.id} value={s.id}>
<div className="flex items-center gap-2">
<div className={cn("w-2 h-2 rounded-full", s.color)} />
{s.name}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
)}
{planModeEnabled && (
<div className="flex items-center gap-2 pt-1">
<Checkbox
id={`${step.id}-disable-plan`}
checked={hasDisablePlanMode(step)}
onCheckedChange={() => {
if (readOnly) return;
toggleDisablePlanMode();
}}
disabled={readOnly}
/>
<Label htmlFor={`${step.id}-disable-plan`} className="text-sm">
Disable plan mode on complete
</Label>
<HelpTip text="Turn off plan mode after the agent finishes this step." />
</div>
)}
</div>
);
}