"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}
)}
);
}