import { Handle, type NodeProps, Position } from "@xyflow/react" import { AlertCircle, Braces, Clock, Code2, Database, Globe, Radio, } from "lucide-react" import { memo } from "react" import type { MessageSpec, NodeDef_Input } from "@/client" import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" import { cn } from "@/lib/utils" import { portOf } from "./deriveEdges" import { useNodeStatus } from "./liveStore" const NODE_ICONS = { python: Code2, mqtt: Radio, http: Globe, influxdb: Database, delay: Clock, mlp: Braces, } as const const STATUS_STYLES = { running: { dot: "bg-primary animate-pulse", label: "Running" }, success: { dot: "bg-status-success", label: "Last run succeeded" }, error: { dot: "bg-destructive", label: "Failed" }, } as const export type FlowNodeData = { definition: NodeDef_Input flow: string typeLabel: string issues: number issueText: string [key: string]: unknown } /** Vertically distribute handles so several ports stay reachable. */ function handleOffset(index: number, total: number): string { if (total <= 1) return "50%" const span = 60 return `${50 - span / 2 + (span / (total - 1)) * index}%` } function PortHandles({ specs, type, position, }: { specs: MessageSpec[] type: "source" | "target" position: Position }) { return ( <> {specs.map((spec, index) => { const port = portOf(spec) return ( ) })} ) } function FlowNodeComponent({ data, selected }: NodeProps) { const { definition, flow, typeLabel, issues, issueText } = data as FlowNodeData const live = useNodeStatus(`${flow}.${definition.id}`) const Icon = NODE_ICONS[definition.type as keyof typeof NODE_ICONS] ?? Code2 const status = live?.status === "active" ? undefined : live?.status const style = status ? STATUS_STYLES[status as keyof typeof STATUS_STYLES] : undefined return (
{definition.title || definition.id} {typeLabel} {style ? ( {live?.error ?? style.label} ) : null}
{issues > 0 ? ( {issueText} ) : null}
) } export const FlowNode = memo(FlowNodeComponent)