import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { DottedSeparator } from "@/components/ui/separator"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { baseRoutingFields } from "@/lib/config/celFieldsRouting"; import { getOperatorLabel } from "@/lib/config/celOperatorsRouting"; import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons"; import { getProviderLabel } from "@/lib/constants/logs"; import { useGetCustomersQuery, useGetTeamsQuery, useGetVirtualKeysQuery } from "@/lib/store/apis/governanceApi"; import { RoutingRule } from "@/lib/types/routingRules"; import { getScopeLabel } from "@/lib/utils/routingRules"; import { formatDistanceToNow } from "date-fns"; import { Check, Copy, GitMerge, Key } from "lucide-react"; import { useMemo, useState } from "react"; import { RuleGroupType, RuleType } from "react-querybuilder"; import { toast } from "sonner"; interface Props { rule: RoutingRule | null; open: boolean; onOpenChange: (open: boolean) => void; } // ─── helpers ──────────────────────────────────────────────────────────────── function getFieldLabel(fieldName: string): string { const field = baseRoutingFields.find((f) => f.name === fieldName); return field?.label ?? fieldName; } function formatRuleValue(value: any): string { if (Array.isArray(value)) return value.join(", "); if (typeof value === "string") return value; return String(value ?? ""); } function useScopeName(scope: string, scopeId?: string): string | undefined { const { data: teamsData } = useGetTeamsQuery(undefined, { skip: scope !== "team" || !scopeId }); const { data: customersData } = useGetCustomersQuery(undefined, { skip: scope !== "customer" || !scopeId }); const { data: vksData } = useGetVirtualKeysQuery(undefined, { skip: scope !== "virtual_key" || !scopeId }); return useMemo(() => { if (!scopeId) return undefined; if (scope === "team") return teamsData?.teams?.find((t) => t.id === scopeId)?.name; if (scope === "customer") return customersData?.customers?.find((c) => c.id === scopeId)?.name; if (scope === "virtual_key") return vksData?.virtual_keys?.find((v) => v.id === scopeId)?.name; return undefined; }, [scope, scopeId, teamsData, customersData, vksData]); } // ─── copy button ───────────────────────────────────────────────────────────── function CopyButton({ value, label, testId }: { value: string; label?: string; testId: string }) { const [copied, setCopied] = useState(false); const handleCopy = async () => { try { await navigator.clipboard.writeText(value); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { toast.error("Failed to copy to clipboard"); } }; return ( {copied ? "Copied!" : `Copy ${label ?? "value"}`} ); } // ─── condition rendering ───────────────────────────────────────────────────── function ConditionRow({ rule }: { rule: RuleType }) { const fieldLabel = getFieldLabel(rule.field); const opLabel = getOperatorLabel(rule.operator); const value = formatRuleValue(rule.value); const isExistence = rule.operator === "null" || rule.operator === "notNull"; // Detect header/param fields for richer display const isHeader = rule.field.startsWith("headers[") || rule.field === "headers"; const isParam = rule.field.startsWith("params[") || rule.field === "params"; const keyMatch = rule.field.match(/\["([^"]+)"\]/); // Bare field (e.g. headers / params) may encode key:value in the value string const bareKeyValue = !keyMatch && (isHeader || isParam) && value ? value.includes(":") ? { key: value.slice(0, value.indexOf(":")), val: value.slice(value.indexOf(":") + 1) } : { key: value, val: "" } : null; const keyName = keyMatch?.[1] ?? bareKeyValue?.key; const displayValue = bareKeyValue !== null ? bareKeyValue.val : value; return (
{isHeader && keyName ? ( header {keyName} ) : isParam && keyName ? ( param {keyName} ) : ( fieldLabel )} {opLabel} {!isExistence && displayValue && ( {displayValue} )}
); } function CombinatorPill({ combinator }: { combinator: string }) { return (
{combinator}
); } function ConditionGroup({ group, depth = 0 }: { group: RuleGroupType; depth?: number }) { const rules = group.rules ?? []; if (rules.length === 0) return null; const content = rules.map((rule, i) => (
{i > 0 && } {"combinator" in rule ? : }
)); if (depth === 0) return
{content}
; return (
Group {content}
); } // ─── target card ───────────────────────────────────────────────────────────── function TargetCard({ target, total }: { target: RoutingRule["targets"][0]; index: number; total: number }) { const providerLabel = target.provider ? getProviderLabel(target.provider) : "Incoming provider"; const weightPercent = total > 0 ? Math.round(target.weight * 100) : 0; return (
{target.provider && }
{providerLabel} {target.model ? ( {target.model} ) : ( Incoming model )}
{weightPercent}%
Weight: {target.weight} (raw)
{target.key_id && (
Pinned key: {target.key_id}
)}
); } // ─── fallback chain ─────────────────────────────────────────────────────────── function FallbackChain({ fallbacks }: { fallbacks: string[] }) { return (
{fallbacks.map((fb, i) => { const parts = fb.split("/"); const provider = parts[0] || "Incoming provider"; const model = parts.length > 1 ? parts.slice(1).join("/") : "Incoming model"; return (
{i > 0 && } {provider && } {model ? `${provider}/${model}` : fb}
); })}
); } // ─── main sheet ────────────────────────────────────────────────────────────── export function RoutingRuleInfoSheet({ rule, open, onOpenChange }: Props) { const targets = rule?.targets ?? []; const fallbacks = rule?.fallbacks ?? []; const hasQuery = rule?.query && (rule.query.rules?.length ?? 0) > 0; const scopeName = useScopeName(rule?.scope ?? "global", rule?.scope_id); return ( {rule && ( <>
{rule.name} {rule.enabled ? "Enabled" : "Disabled"} {rule.chain_rule && ( Chain Rule After this rule matches, routing rules are re-evaluated using the resolved provider/model as the new context. )}
{rule.description && {rule.description}}
{/* Overview */}

Overview

Scope
{getScopeLabel(rule.scope)} {scopeName && {scopeName}}
Priority
{rule.priority}
{/* Conditions */}

Conditions

{hasQuery ? :

Matches all requests

} {/* CEL expression */}
CEL Expression
{rule.cel_expression || true}
{/* Targets */}

Targets ({targets.length})

{targets.length > 0 ? (
{targets.map((target, i) => ( ))}
) : (

No targets configured

)}
{/* Fallback Chain */}

Fallback Chain

{fallbacks.length > 0 ? ( ) : (

No fallbacks configured

)}
{/* Timestamps */}

Created

{formatDistanceToNow(new Date(rule.created_at), { addSuffix: true })}

Last Updated

{formatDistanceToNow(new Date(rule.updated_at), { addSuffix: true })}
)}
); }