shin / frontend /src /components /usage /TopEndpoints.tsx
shinmentakezo07
feat: theme dashboard charts and expose admin UI on HF Spaces
8bdd0d7
Raw
History Blame Contribute Delete
3.65 kB
import { useState } from "react"
import {
buildTopByModel,
buildTopByProvider,
chartPalette,
formatTokens,
type EndpointUsage,
} from "@/lib/usage-data"
import { cn } from "@/lib/utils"
import type { UsageRecord } from "@/lib/types"
import { Badge } from "../ui/badge"
import { ChartCard } from "./chart-primitives"
import { useResolvedTheme } from "./use-resolved-theme"
type GroupBy = "model" | "provider"
const GROUP_OPTIONS: { key: GroupBy; label: string }[] = [
{ key: "model", label: "Model" },
{ key: "provider", label: "Provider" },
]
function BarListRow({ item, index, maxTokens }: { item: EndpointUsage; index: number; maxTokens: number }) {
const dark = useResolvedTheme() === "dark"
const color = chartPalette(dark)[index % 8]
const share = maxTokens > 0 ? (item.tokens / maxTokens) * 100 : 0
return (
<li>
<div className="flex items-center justify-between gap-2 text-xs">
<span className="flex min-w-0 items-center gap-2">
<span className="w-4 shrink-0 text-right font-mono text-muted-foreground">
{index + 1}
</span>
<span className="truncate font-mono">{item.name}</span>
{item.errors > 0 ? (
<Badge variant="error" className="px-1.5 py-0 text-[10px] font-medium">
{item.errors} err
</Badge>
) : null}
</span>
<span className="flex shrink-0 items-center gap-2 font-mono">
<span className="text-muted-foreground">
{item.requests} req
</span>
<span className="w-12 text-right">{formatTokens(item.tokens)}</span>
</span>
</div>
<div className="mt-1 ml-6 h-1.5 overflow-hidden rounded-full bg-muted">
<div
className="h-full rounded-full transition-all"
style={{ width: `${share}%`, backgroundColor: color }}
/>
</div>
</li>
)
}
export function TopEndpoints({ records }: { records: UsageRecord[] }) {
const [groupBy, setGroupBy] = useState<GroupBy>("model")
const items =
groupBy === "model" ? buildTopByModel(records) : buildTopByProvider(records)
const maxTokens = items.length > 0 ? items[0].tokens : 0
return (
<ChartCard
title="Top endpoints"
subtitle={
groupBy === "model"
? "Gateway models ranked by token usage"
: "Providers ranked by token usage"
}
className="h-full"
actions={
<div
className="flex items-center gap-1 rounded-lg border bg-muted/40 p-0.5"
role="group"
aria-label="Group by"
>
{GROUP_OPTIONS.map((option) => (
<button
key={option.key}
type="button"
className={cn(
"rounded-md px-2 py-1 text-xs font-medium transition-colors",
groupBy === option.key
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground",
)}
onClick={() => setGroupBy(option.key)}
>
{option.label}
</button>
))}
</div>
}
>
{items.length > 0 ? (
<ol className="space-y-3">
{items.map((item, index) => (
<BarListRow
key={`${groupBy}-${item.name}`}
item={item}
index={index}
maxTokens={maxTokens}
/>
))}
</ol>
) : (
<p className="py-8 text-center text-sm text-muted-foreground">
No requests in this window yet.
</p>
)}
</ChartCard>
)
}