"use client"; import { useReactFlow } from "@xyflow/react"; import { UINode } from "lib/ai/workflow/workflow.interface"; import { ChevronRightIcon, SearchIcon, VariableIcon } from "lucide-react"; import { ReactNode, useMemo, useRef, useState } from "react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "ui/dropdown-menu"; import { Input } from "ui/input"; import { JSONSchema7 } from "json-schema"; import { findAccessibleNodeIds } from "lib/ai/workflow/shared.workflow"; import { cn } from "lib/utils"; import { useTranslations } from "next-intl"; interface VariableSelectProps { currentNodeId: string; allowedTypes?: string[]; children: React.ReactNode; onChange: (item: { nodeId: string; path: string[]; nodeName: string; type: string; }) => void; } export function VariableSelect({ currentNodeId, onChange, children, allowedTypes, }: VariableSelectProps) { const [open, setOpen] = useState(false); return ( {children} { setOpen(false); }} onChange={(item) => { onChange(item); setOpen(false); }} /> ); } export function VariableSelectContent({ currentNodeId, onChange, allowedTypes, onClose, }: Omit & { onClose?: () => void; }) { const [query, setQuery] = useState(""); const { getNodes, getEdges } = useReactFlow(); const nodes = getNodes(); const edges = getEdges(); const t = useTranslations(); const firstNodeRef = useRef(null); const accessibleSchemas = useMemo(() => { const accessibleNodes = findAccessibleNodeIds({ nodeId: currentNodeId, nodes: nodes.map((node) => node.data), edges, }); return nodes .filter((node) => accessibleNodes.includes(node.id)) .map((node) => { return { id: node.data.id, name: node.data.name, schema: node.data.outputSchema?.properties, kind: node.data.kind, }; }) .filter((v) => { return v.schema && Object.keys(v.schema).length; }); }, [nodes, currentNodeId, edges]); const filteredNodes = useMemo(() => { const first = [firstNodeRef]; return accessibleSchemas .map(({ name, id, schema }) => { const items = Array.from(Object.entries(schema ?? {})) .filter(([key]) => key.includes(query)) .map(([key, schema]) => { const ref = first.shift()!; return ( { onChange({ nodeId: id, path, nodeName: name, type: schema.type as string, }); }} /> ); }); if (!items.length) return null; return ( {name} {items} ); }) .filter(Boolean); }, [accessibleSchemas, query]); return ( { e.stopPropagation(); }} > { if (e.key === "Escape") { onClose?.(); } if (e.key === "Backspace" && query.length === 0) { onClose?.(); } if (e.key === "ArrowDown") { firstNodeRef.current?.focus(); } }} onChange={(e) => { e.stopPropagation(); setQuery(e.target.value); }} /> {nodes.length === 0 || filteredNodes.length === 0 ? ( {t("Workflow.noVariablesFound")} ) : ( filteredNodes )} ); } function SchemaItem({ name, schema, path, onChange, allowedTypes, ref, }: { name: string; schema: JSONSchema7; path: string[]; ref?: React.RefObject; allowedTypes?: string[]; onChange: (path: string[]) => void; }) { const disabled = useMemo(() => { return ( allowedTypes?.length && !allowedTypes.includes(schema.type as string) ); }, [allowedTypes, schema.type]); if ( schema.type === "object" && schema.properties && Object.keys(schema.properties).length > 0 ) { return ( { if (disabled) return; onChange([...path, name]); }} icon={ <> {schema.type} > } className="text-xs text-muted-foreground flex items-center gap-1" > {name} {Object.entries(schema.properties ?? {}).map(([key, schema]) => { return ( ); })} ); } return ( { if (disabled) return; onChange([...path, name]); }} > {name} {schema.type} ); }
{t("Workflow.noVariablesFound")}