Download packages/workbench/src/ui/components/flows/flow-node.tsx from AUXteam/Midday: direct link, hf CLI and curl.
- Browser
- Download file 3.98 kB
-
https://huggingface.co/spaces/AUXteam/Midday/resolve/main/packages/workbench/src/ui/components/flows/flow-node.tsx
- Command line
-
hf download hf://spaces/AUXteam/Midday/packages/workbench/src/ui/components/flows/flow-node.tsx
-
curl -L -o flow-node.tsx https://huggingface.co/spaces/AUXteam/Midday/resolve/main/packages/workbench/src/ui/components/flows/flow-node.tsx
3.98 kB
| import { Handle, Position } from "@xyflow/react"; | |
| import { | |
| CheckCircle2, | |
| Circle, | |
| Clock, | |
| Loader2, | |
| Pause, | |
| XCircle, | |
| } from "lucide-react"; | |
| import { memo } from "react"; | |
| import type { FlowNode as FlowNodeType } from "@/core/types"; | |
| import { cn } from "@/lib/utils"; | |
| export interface FlowNodeData extends Record<string, unknown> { | |
| flowNode: FlowNodeType; | |
| onClick?: (flowNode: FlowNodeType) => void; | |
| } | |
| interface FlowNodeProps { | |
| data: FlowNodeData; | |
| } | |
| const statusConfig: Record< | |
| string, | |
| { | |
| icon: typeof CheckCircle2; | |
| color: string; | |
| bg: string; | |
| border: string; | |
| animate?: boolean; | |
| } | |
| > = { | |
| completed: { | |
| icon: CheckCircle2, | |
| color: "text-emerald-600 dark:text-emerald-400", | |
| bg: "bg-emerald-50 dark:bg-emerald-500/10", | |
| border: "border-emerald-200 dark:border-emerald-500/20", | |
| }, | |
| active: { | |
| icon: Loader2, | |
| color: "text-blue-600 dark:text-blue-400", | |
| bg: "bg-blue-50 dark:bg-blue-500/10", | |
| border: "border-blue-200 dark:border-blue-500/20", | |
| animate: true, | |
| }, | |
| waiting: { | |
| icon: Circle, | |
| color: "text-muted-foreground", | |
| bg: "bg-muted/30", | |
| border: "border-border", | |
| }, | |
| delayed: { | |
| icon: Clock, | |
| color: "text-amber-600 dark:text-amber-400", | |
| bg: "bg-amber-50 dark:bg-amber-500/10", | |
| border: "border-amber-200 dark:border-amber-500/20", | |
| }, | |
| failed: { | |
| icon: XCircle, | |
| color: "text-destructive", | |
| bg: "bg-red-50 dark:bg-red-500/10", | |
| border: "border-red-200 dark:border-red-500/20", | |
| }, | |
| paused: { | |
| icon: Pause, | |
| color: "text-muted-foreground", | |
| bg: "bg-muted/30", | |
| border: "border-border", | |
| }, | |
| unknown: { | |
| icon: Circle, | |
| color: "text-muted-foreground", | |
| bg: "bg-muted/30", | |
| border: "border-border", | |
| }, | |
| }; | |
| function formatDuration(ms: number): string { | |
| if (ms < 1000) return `${ms}ms`; | |
| if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; | |
| return `${(ms / 60000).toFixed(1)}m`; | |
| } | |
| function FlowNodeComponent({ data }: FlowNodeProps) { | |
| const { flowNode, onClick } = data; | |
| const { job, queueName } = flowNode; | |
| const config = statusConfig[job.status] || statusConfig.unknown; | |
| const Icon = config.icon; | |
| return ( | |
| <div | |
| className={cn( | |
| "relative min-w-[180px] px-3 py-2.5 transition-all", | |
| "hover:bg-accent/50 cursor-pointer", | |
| "bg-background border", | |
| config.border, | |
| )} | |
| onClick={() => onClick?.(flowNode)} | |
| > | |
| {/* Input handle (top) */} | |
| <Handle | |
| type="target" | |
| position={Position.Top} | |
| className="!w-2 !h-2 !bg-muted-foreground/40 !border-0" | |
| /> | |
| {/* Content */} | |
| <div className="flex items-start gap-2.5"> | |
| <div className={cn("p-1.5 shrink-0", config.bg)}> | |
| <Icon | |
| className={cn( | |
| "h-3.5 w-3.5", | |
| config.color, | |
| config.animate && "animate-spin", | |
| )} | |
| /> | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <div className="font-medium text-sm truncate text-foreground"> | |
| {job.name} | |
| </div> | |
| <div className="text-[11px] text-muted-foreground truncate mt-0.5"> | |
| {queueName} | |
| </div> | |
| {job.duration !== undefined && ( | |
| <div className="text-[11px] text-muted-foreground mt-1 font-mono tabular-nums"> | |
| {formatDuration(job.duration)} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| {/* Status badge */} | |
| <div | |
| className={cn( | |
| "absolute -top-1.5 -right-1.5 px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-wide", | |
| "bg-background border", | |
| config.color, | |
| config.border, | |
| )} | |
| > | |
| {job.status} | |
| </div> | |
| {/* Output handle (bottom) */} | |
| <Handle | |
| type="source" | |
| position={Position.Bottom} | |
| className="!w-2 !h-2 !bg-muted-foreground/40 !border-0" | |
| /> | |
| </div> | |
| ); | |
| } | |
| export const FlowNodeMemo = memo(FlowNodeComponent); | |