diff --git a/ui/litellm-dashboard/src/components/routing_groups/OrderedFallbackFlow.tsx b/ui/litellm-dashboard/src/components/routing_groups/OrderedFallbackFlow.tsx new file mode 100644 index 00000000000..14ec20c5fc2 --- /dev/null +++ b/ui/litellm-dashboard/src/components/routing_groups/OrderedFallbackFlow.tsx @@ -0,0 +1,129 @@ +"use client"; +import React from "react"; +import AnimatedStream from "./AnimatedStream"; +import DeploymentCard from "./DeploymentCard"; + +const PROVIDER_COLORS: Record = { + nebius: "#14b8a6", + fireworks_ai: "#f97316", + azure: "#8b5cf6", + openai: "#10b981", + anthropic: "#d97706", + google: "#3b82f6", + aws: "#ef4444", +}; +const getProviderColor = (p: string) => PROVIDER_COLORS[p.toLowerCase()] ?? "#6b7280"; + +interface Deployment { + deployment_id: string; + provider: string; + display_name: string; + priority?: number; + avg_latency_ms: number; + percent_of_total: number; + request_count: number; +} + +interface OrderedFallbackFlowProps { + deployments: Deployment[]; + routingGroupName: string; +} + +const CARD_HEIGHT = 76; +const CARD_GAP = 48; // space between cards (for the "fail" arrow) +const CARD_WIDTH = 220; +const LEFT_PADDING = 40; +const HUB_RADIUS = 28; + +export default function OrderedFallbackFlow({ deployments, routingGroupName }: OrderedFallbackFlowProps) { + const sorted = [...deployments].sort((a, b) => (a.priority ?? 999) - (b.priority ?? 999)); + const totalHeight = sorted.length * CARD_HEIGHT + (sorted.length - 1) * CARD_GAP + 40; + const svgWidth = 600; + + // Hub sits vertically centered, 1/3 from left + const hubX = svgWidth * 0.32; + const hubY = totalHeight / 2; + + // Cards start at x = svgWidth - CARD_WIDTH - LEFT_PADDING + const cardX = svgWidth - CARD_WIDTH - LEFT_PADDING; + + // Y positions for each card (centered block) + const blockHeight = sorted.length * CARD_HEIGHT + (sorted.length - 1) * CARD_GAP; + const blockTop = (totalHeight - blockHeight) / 2; + const cardYs = sorted.map((_, i) => blockTop + i * (CARD_HEIGHT + CARD_GAP)); + const cardCenterYs = cardYs.map((y) => y + CARD_HEIGHT / 2); + + // Primary stream: from hub to first card center + const primaryPath = `M ${hubX} ${hubY} L ${cardX} ${cardCenterYs[0]}`; + + // Fallback dashed paths between cards + const fallbackPaths = sorted.slice(0, -1).map((_, i) => { + const x = cardX + CARD_WIDTH / 2; + const fromY = cardYs[i] + CARD_HEIGHT; + const toY = cardYs[i + 1]; + return { path: `M ${x} ${fromY} L ${x} ${toY}`, midY: (fromY + toY) / 2 }; + }); + + return ( +
+ {/* SVG layer */} + + {/* Client → hub stream */} + + + {/* Hub circle */} + + + LITELLM + + + {/* Primary stream: hub → first card */} + + + {/* Fallback dashed arrows between cards */} + {fallbackPaths.map(({ path, midY }, i) => ( + + + + fail → + + + ))} + + + {/* "client" label */} +
+ client +
+ + {/* Deployment cards */} +
+
+ {sorted.map((dep, i) => ( + + ))} +
+
+
+ ); +}