From 8ac542bc86247dd0d571fc5b59796ce7c5c9f658 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Sat, 28 Feb 2026 18:00:39 -0800 Subject: [PATCH] feat(ui): add WeightedRoundRobinFlow component --- .../routing_groups/WeightedRoundRobinFlow.tsx | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/routing_groups/WeightedRoundRobinFlow.tsx diff --git a/ui/litellm-dashboard/src/components/routing_groups/WeightedRoundRobinFlow.tsx b/ui/litellm-dashboard/src/components/routing_groups/WeightedRoundRobinFlow.tsx new file mode 100644 index 00000000000..01468ff2760 --- /dev/null +++ b/ui/litellm-dashboard/src/components/routing_groups/WeightedRoundRobinFlow.tsx @@ -0,0 +1,122 @@ +"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; + weight?: number; + avg_latency_ms: number; + percent_of_total: number; + request_count: number; +} + +interface WeightedRoundRobinFlowProps { + deployments: Deployment[]; + routingGroupName: string; +} + +const CARD_HEIGHT = 76; +const CARD_GAP = 28; +const CARD_WIDTH = 220; +const LEFT_PADDING = 40; +const HUB_RADIUS = 28; + +export default function WeightedRoundRobinFlow({ deployments }: WeightedRoundRobinFlowProps) { + // Sort by weight desc (highest traffic first = top position) + const sorted = [...deployments].sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0)); + const totalHeight = sorted.length * CARD_HEIGHT + (sorted.length - 1) * CARD_GAP + 80; + const svgWidth = 600; + + const hubX = svgWidth * 0.32; + const hubY = totalHeight / 2; + + const cardX = svgWidth - CARD_WIDTH - LEFT_PADDING; + + const blockHeight = sorted.length * CARD_HEIGHT + (sorted.length - 1) * CARD_GAP; + const blockTop = (totalHeight - blockHeight) / 2; + const cardCenterYs = sorted.map((_, i) => blockTop + i * (CARD_HEIGHT + CARD_GAP) + CARD_HEIGHT / 2); + + // Max thickness 14px for 100%, min 2px + const maxPct = Math.max(...sorted.map((d) => d.percent_of_total), 1); + const getThickness = (pct: number) => Math.max(2, Math.round((pct / maxPct) * 14)); + + // Bezier path from hub to each card center + const getPath = (targetY: number) => { + const cp1x = hubX + 80; + const cp2x = cardX - 60; + return `M ${hubX} ${hubY} C ${cp1x} ${hubY} ${cp2x} ${targetY} ${cardX} ${targetY}`; + }; + + return ( +
+ + {/* Client → hub */} + + + {/* Hub */} + + + LITELLM + + + {/* Fan-out streams */} + {sorted.map((dep, i) => ( + + ))} + + + {/* "client" label */} +
+ client +
+ + {/* Cards */} +
+
+ {sorted.map((dep) => ( + + ))} +
+
+
+ ); +}