mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-12 15:30:53 +02:00
198 lines
7.0 KiB
TypeScript
198 lines
7.0 KiB
TypeScript
"use client";
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { aiUsageAnalyticsQueries } from "@app/lib/queries";
|
|
import type { AiUsageAnalyticsFilters } from "@app/lib/queries";
|
|
import { Card, CardContent, CardHeader } from "@app/components/ui/card";
|
|
import {
|
|
InfoSection,
|
|
InfoSectionContent,
|
|
InfoSections,
|
|
InfoSectionTitle
|
|
} from "@app/components/InfoSection";
|
|
import { ToggleableTrendChart } from "./ToggleableTrendChart";
|
|
import { TopEntitiesList, type TopEntity } from "./TopEntitiesList";
|
|
import {
|
|
SERIES_COLORS,
|
|
buildSeriesFromData,
|
|
compactNumberFormatter,
|
|
formatCost
|
|
} from "./shared";
|
|
|
|
type OverviewTabProps = {
|
|
orgId: string;
|
|
filters: AiUsageAnalyticsFilters;
|
|
};
|
|
|
|
const TOKEN_TYPE_LABELS: Record<string, string> = {
|
|
promptTokens: "Prompt",
|
|
cacheReadTokens: "Cache read",
|
|
cacheWriteTokens: "Cache write",
|
|
completionTokens: "Completion",
|
|
reasoningTokens: "Reasoning"
|
|
};
|
|
|
|
export function OverviewTab(props: OverviewTabProps) {
|
|
const { data, isLoading } = useQuery(
|
|
aiUsageAnalyticsQueries.overview({
|
|
orgId: props.orgId,
|
|
filters: props.filters
|
|
})
|
|
);
|
|
|
|
const requestsSeries = [
|
|
{ key: "requests", label: "Requests", color: SERIES_COLORS[0] }
|
|
];
|
|
const tokensSeries = Object.keys(TOKEN_TYPE_LABELS).map((key, i) => ({
|
|
key,
|
|
label: TOKEN_TYPE_LABELS[key],
|
|
color: SERIES_COLORS[i % SERIES_COLORS.length]
|
|
}));
|
|
const costSeries = [
|
|
{ key: "cost", label: "Cost", color: SERIES_COLORS[0] }
|
|
];
|
|
|
|
const modelCostSeries = buildSeriesFromData(
|
|
data?.modelCostPerDay ?? [],
|
|
(key) => key
|
|
);
|
|
const modelTokensSeries = buildSeriesFromData(
|
|
data?.modelTokensPerDay ?? [],
|
|
(key) => key
|
|
);
|
|
|
|
const topModels: TopEntity[] = (data?.topModels ?? []).map((m) => ({
|
|
key: m.model,
|
|
label: m.model,
|
|
requests: m.requests,
|
|
totalTokens: m.totalTokens,
|
|
costUsd: m.costUsd
|
|
}));
|
|
|
|
return (
|
|
<div className="flex flex-col gap-5">
|
|
<Card>
|
|
<CardHeader>
|
|
<InfoSections cols={4}>
|
|
<InfoSection>
|
|
<InfoSectionTitle>Total requests</InfoSectionTitle>
|
|
<InfoSectionContent>
|
|
{data
|
|
? compactNumberFormatter.format(
|
|
data.totalRequests
|
|
)
|
|
: "--"}
|
|
</InfoSectionContent>
|
|
</InfoSection>
|
|
<InfoSection>
|
|
<InfoSectionTitle>Total tokens</InfoSectionTitle>
|
|
<InfoSectionContent>
|
|
{data
|
|
? compactNumberFormatter.format(
|
|
data.totalTokens
|
|
)
|
|
: "--"}
|
|
</InfoSectionContent>
|
|
</InfoSection>
|
|
<InfoSection>
|
|
<InfoSectionTitle>Total cost</InfoSectionTitle>
|
|
<InfoSectionContent>
|
|
{data ? formatCost(data.totalCost) : "--"}
|
|
</InfoSectionContent>
|
|
</InfoSection>
|
|
<InfoSection>
|
|
<InfoSectionTitle>Estimated</InfoSectionTitle>
|
|
<InfoSectionContent>
|
|
{data
|
|
? `${Math.round(data.estimatedPercent)}%`
|
|
: "--"}
|
|
</InfoSectionContent>
|
|
</InfoSection>
|
|
</InfoSections>
|
|
</CardHeader>
|
|
</Card>
|
|
|
|
<div className="grid lg:grid-cols-3 gap-5">
|
|
<Card>
|
|
<CardHeader>
|
|
<h3 className="font-semibold">Request volume</h3>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ToggleableTrendChart
|
|
data={data?.requestsPerDay ?? []}
|
|
series={requestsSeries}
|
|
isLoading={isLoading}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader>
|
|
<h3 className="font-semibold">Token usage</h3>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ToggleableTrendChart
|
|
data={data?.tokensPerDay ?? []}
|
|
series={tokensSeries}
|
|
isLoading={isLoading}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader>
|
|
<h3 className="font-semibold">Cost</h3>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ToggleableTrendChart
|
|
data={data?.costPerDay ?? []}
|
|
series={costSeries}
|
|
isLoading={isLoading}
|
|
valueFormatter={(v) => formatCost(v)}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="grid lg:grid-cols-2 gap-5">
|
|
<Card>
|
|
<CardHeader>
|
|
<h3 className="font-semibold">Model cost</h3>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ToggleableTrendChart
|
|
data={data?.modelCostPerDay ?? []}
|
|
series={modelCostSeries}
|
|
isLoading={isLoading}
|
|
valueFormatter={(v) => formatCost(v)}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader>
|
|
<h3 className="font-semibold">Model tokens</h3>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ToggleableTrendChart
|
|
data={data?.modelTokensPerDay ?? []}
|
|
series={modelTokensSeries}
|
|
isLoading={isLoading}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<h3 className="font-semibold">Top models</h3>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<TopEntitiesList
|
|
entities={topModels}
|
|
isLoading={isLoading}
|
|
nameColumnLabel="Model"
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|