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 ? "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 (
);
}
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
)}
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} }
{/* 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 })}
>
)}
);
}