"use client"; import { Handle, Position, useNodes, useReactFlow } from "@xyflow/react"; import { ConditionNodeData, NodeKind, OutputSchemaSourceKey, UINode, } from "lib/ai/workflow/workflow.interface"; import { PlusIcon, TrashIcon } from "lucide-react"; import { Button } from "ui/button"; import { Separator } from "ui/separator"; import { VariableSelect } from "../variable-select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "ui/select"; import { VariableMentionItem } from "../variable-mention-item"; import { BooleanConditionOperator, ConditionBranch, ConditionOperator, ConditionRule, getFirstConditionOperator, NumberConditionOperator, StringConditionOperator, } from "lib/ai/workflow/condition"; import { useCallback, useMemo, useState } from "react"; import { findJsonSchemaByPath } from "lib/ai/workflow/shared.workflow"; import { Badge } from "ui/badge"; import { cn, generateUUID } from "lib/utils"; import { NodeSelect } from "../node-select"; import { useUpdate } from "@/hooks/use-update"; import { createAppendNode } from "../create-append-node"; import { useTranslations } from "next-intl"; import { Input } from "ui/input"; export function ConditionNodeDataConfig({ data, }: { data: ConditionNodeData; }) { const t = useTranslations(); const { updateNodeData, setEdges, getEdges } = useReactFlow(); const updateIfBranch = useCallback( (branch: ConditionBranch) => { updateNodeData(data.id, (node) => { const prev = node.data as ConditionNodeData; return { branches: { ...prev.branches, if: branch }, }; }); }, [data.id], ); const updateElseIfBranch = useCallback( (index: number, branch: ConditionBranch) => { updateNodeData(data.id, (node) => { const prev = node.data as ConditionNodeData; return { branches: { ...prev.branches, elseIf: prev.branches.elseIf?.map((item, i) => i == index ? branch : item, ) ?? [branch], }, }; }); }, [data.id], ); const addElseIfBranch = useCallback(() => { updateNodeData(data.id, (node) => { const prev = node.data as ConditionNodeData; return { branches: { ...prev.branches, elseIf: [ ...(prev.branches.elseIf ?? []), { id: generateUUID(), type: "elseIf", conditions: [], logicalOperator: "AND", }, ], }, }; }); }, [data.id]); const removeElseIfBranch = useCallback( (index: number) => { const edges = getEdges(); const connectedEdges = edges .filter((edge) => edge.sourceHandle == data.branches.elseIf![index].id) .map((edge) => edge.id); if (connectedEdges.length) { setEdges(edges.filter((edge) => !connectedEdges.includes(edge.id))); } updateNodeData(data.id, (node) => { const prev = node.data as ConditionNodeData; return { branches: { ...prev.branches, elseIf: prev.branches.elseIf?.filter((_, i) => i !== index), }, }; }); }, [data.id, data.branches.elseIf?.length], ); return (
{!data.branches.elseIf?.length && ( <>

ELSE IF

{t("Workflow.elseIfDescription")}

)}
{data.branches.elseIf?.map((branch, i) => (
{i > 0 && } updateElseIfBranch(i, branch)} onDelete={() => removeElseIfBranch(i)} type={"else If"} />
))}

ELSE

CASE {(data.branches.elseIf?.length ?? 0) + 2}

{t("Workflow.elseDescription")}

); } ConditionNodeDataConfig.displayName = "ConditionNodeDataConfig"; interface ConditionBranchProps { currentNodeId: string; branch: ConditionBranch; onChange: (branch: ConditionBranch) => void; caseNumber: number; onDelete?: () => void; type: "if" | "else If" | "else"; } function ConditionBranchItem({ currentNodeId, branch, onChange, caseNumber, onDelete, type, }: ConditionBranchProps) { const { getNode } = useReactFlow(); const nodes = useNodes() as UINode[]; const t = useTranslations(); const addCondition = useCallback( (source: OutputSchemaSourceKey) => { const node = getNode(source.nodeId)!; const sourceSchema = findJsonSchemaByPath( node.data.outputSchema, source.path, ); onChange({ ...branch, conditions: [ ...branch.conditions, { source, operator: getFirstConditionOperator( sourceSchema?.type as "string" | "number" | "boolean", ), value: "", }, ], }); }, [branch, onChange], ); const updateCondition = useCallback( (index: number, condition: ConditionRule) => { onChange({ ...branch, conditions: branch.conditions.map((item, i) => i == index ? condition : item, ), }); }, [branch, onChange], ); const removeCondition = useCallback( (index: number) => { onChange({ ...branch, conditions: branch.conditions.filter((_, i) => i !== index), }); }, [branch, onChange], ); return (

{type?.toUpperCase()}

CASE {caseNumber}
{branch.conditions.length > 0 && (
{branch.conditions.length > 1 && ( <>
)}
{branch.conditions.map((condition, i) => (
updateCondition(i, condition)} />
))}
)}
{ addCondition(source); }} allowedTypes={["number", "boolean", "string"]} > {t("Workflow.addCondition")} {onDelete && ( )}
); } interface ConditionRuleProps { currentNodeId: string; nodes: UINode[]; item: ConditionRule; onChange: (item: ConditionRule) => void; } function ConditionRuleItem({ currentNodeId, nodes, item, onChange, }: ConditionRuleProps) { const target = useMemo(() => { const node = nodes.find((node) => node.data.id === item.source.nodeId); if (!node) { return { nodeName: "Not Found", path: item.source.path, notFound: true, }; } return { nodeName: node.data.name, path: item.source.path, }; }, [item, nodes]); const itemType = useMemo(() => { const node = nodes.find((node) => node.data.id === item.source.nodeId); if (!node) { return "string"; } return findJsonSchemaByPath(node.data.outputSchema, item.source.path)?.type; }, [item, nodes]); const operatorItems = useMemo(() => { let operatorItems: Record = StringConditionOperator; if (itemType == "number") operatorItems = NumberConditionOperator; if (itemType == "boolean") operatorItems = BooleanConditionOperator; return Object.entries(operatorItems).map(([key, value]) => ({ label: key, value, })); }, [itemType]); return (
{ onChange({ ...item, source, }); }} >
{itemType == "string" || itemType == "number" ? ( <> onChange({ ...item, value: e.target.value })} /> ) : null}
); } export function ConditionNodeDataOutputStack({ data, }: { data: ConditionNodeData; }) { const [sourceHandle, setSourceHandle] = useState(""); const update = useUpdate(); const { addNodes, addEdges, updateNode, getNodes, getEdges } = useReactFlow(); const appendNode = (kind: NodeKind) => { if (!sourceHandle) return; setSourceHandle(""); const allNodes = getNodes() as UINode[]; const { node: newNode, edge: newEdge } = createAppendNode({ sourceNode: allNodes.find((node) => node.data.id === data.id)!, kind, allNodes, edge: { sourceHandle, }, allEdges: getEdges(), }); addNodes([newNode]); if (newEdge) { addEdges([newEdge]); } update(() => { updateNode(data.id, { selected: false, }); }); }; return (
{ setSourceHandle(data.branches.if.id); }} /> { if (!open) { setSourceHandle(""); } }} > {data.branches.elseIf?.map((branch, i) => ( { setSourceHandle(branch.id); }} /> ))} { setSourceHandle(data.branches.else.id); }} />
); } function ConditionHandle({ type, id, caseNumber, onMouseUp, }: { type: "if" | "elseIf" | "else"; id: string; caseNumber?: number; onMouseUp?: () => void; }) { return (
{type.toUpperCase()} {caseNumber && ( CASE {caseNumber} )}
); }