first commit
This commit is contained in:
170
ui/app/workspace/model-catalog/views/modelCatalogView.tsx
Normal file
170
ui/app/workspace/model-catalog/views/modelCatalogView.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import FullPageLoader from "@/components/fullPageLoader";
|
||||
import { NoPermissionView } from "@/components/noPermissionView";
|
||||
import { ProviderNames } from "@/lib/constants/logs";
|
||||
import { useGetModelsQuery, useGetProvidersQuery, useLazyGetLogsModelHistogramQuery, useLazyGetLogsStatsQuery } from "@/lib/store";
|
||||
import { KnownProvider } from "@/lib/types/config";
|
||||
import { LogStats } from "@/lib/types/logs";
|
||||
import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { ModelCatalogEmptyState } from "./modelCatalogEmptyState";
|
||||
import ModelCatalogTable, { ModelCatalogRow } from "./modelCatalogTable";
|
||||
|
||||
export default function ModelCatalogView() {
|
||||
const hasAccess = useRbac(RbacResource.ModelProvider, RbacOperation.View);
|
||||
|
||||
const [providerFilter, setProviderFilter] = useState("");
|
||||
const [statsMap, setStatsMap] = useState<Map<string, LogStats>>(new Map());
|
||||
const [modelsUsedMap, setModelsUsedMap] = useState<Map<string, string[]>>(new Map());
|
||||
const [isLoadingModels, setIsLoadingModels] = useState(true);
|
||||
|
||||
const {
|
||||
data: providers,
|
||||
isLoading: isLoadingProviders,
|
||||
error: providersError,
|
||||
refetch: refetchProviders,
|
||||
} = useGetProvidersQuery(undefined, { skip: !hasAccess });
|
||||
const { data: modelsData } = useGetModelsQuery({ unfiltered: true }, { skip: !hasAccess });
|
||||
|
||||
// Global 24h stats for summary cards (lazy so we get fresh timestamps)
|
||||
const [triggerGlobalStats, { data: globalStats }] = useLazyGetLogsStatsQuery();
|
||||
|
||||
// Per-provider traffic stats (lazy, fired when providers load)
|
||||
const [triggerStats] = useLazyGetLogsStatsQuery();
|
||||
const [triggerModelHistogram] = useLazyGetLogsModelHistogramQuery();
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasAccess) return;
|
||||
const now = new Date().toISOString();
|
||||
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
||||
triggerGlobalStats({ filters: { start_time: dayAgo, end_time: now } });
|
||||
}, [hasAccess, triggerGlobalStats]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!providers || providers.length === 0) return;
|
||||
let cancelled = false;
|
||||
const now = new Date().toISOString();
|
||||
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
||||
|
||||
Promise.all(
|
||||
providers.map((p) =>
|
||||
triggerStats({ filters: { providers: [p.name], start_time: dayAgo, end_time: now } })
|
||||
.unwrap()
|
||||
.then((stats) => [p.name, stats] as const)
|
||||
.catch(
|
||||
() =>
|
||||
[
|
||||
p.name,
|
||||
{
|
||||
total_requests: 0,
|
||||
success_rate: 0,
|
||||
user_facing_success_rate: 0,
|
||||
average_latency: 0,
|
||||
user_facing_total_requests:0,
|
||||
total_tokens: 0,
|
||||
total_cost: 0
|
||||
},
|
||||
] as const,
|
||||
),
|
||||
),
|
||||
).then((results) => {
|
||||
if (!cancelled) setStatsMap(new Map(results));
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [providers, triggerStats]);
|
||||
|
||||
// Per-provider models used in last 30 days
|
||||
useEffect(() => {
|
||||
if (!providers || providers.length === 0) return;
|
||||
let cancelled = false;
|
||||
setIsLoadingModels(true);
|
||||
const now = new Date().toISOString();
|
||||
const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
||||
|
||||
Promise.all(
|
||||
providers.map((p) =>
|
||||
triggerModelHistogram({ filters: { providers: [p.name], start_time: monthAgo, end_time: now } })
|
||||
.unwrap()
|
||||
.then((data): [string, string[]] => [p.name, data.models ?? []])
|
||||
.catch((): [string, string[]] => [p.name, []]),
|
||||
),
|
||||
).then((results) => {
|
||||
if (!cancelled) {
|
||||
setModelsUsedMap(new Map(results));
|
||||
setIsLoadingModels(false);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [providers, triggerModelHistogram]);
|
||||
|
||||
// Build table rows
|
||||
const rows: ModelCatalogRow[] = useMemo(() => {
|
||||
if (!providers) return [];
|
||||
|
||||
return providers.map((p) => {
|
||||
const isCustom = !ProviderNames.includes(p.name as KnownProvider);
|
||||
const modelsUsed = modelsUsedMap.get(p.name) ?? [];
|
||||
|
||||
const providerStats = statsMap.get(p.name);
|
||||
const totalTraffic24h = providerStats?.total_requests ?? 0;
|
||||
const totalCost24h = providerStats?.total_cost ?? 0;
|
||||
|
||||
return {
|
||||
providerName: p.name,
|
||||
isCustom,
|
||||
baseProviderType: p.custom_provider_config?.base_provider_type,
|
||||
modelsUsed,
|
||||
totalTraffic24h,
|
||||
totalCost24h,
|
||||
};
|
||||
});
|
||||
}, [providers, statsMap, modelsUsedMap]);
|
||||
|
||||
// Filter rows by provider
|
||||
const filteredRows = useMemo(() => {
|
||||
if (!providerFilter) return rows;
|
||||
return rows.filter((r) => r.providerName === providerFilter);
|
||||
}, [rows, providerFilter]);
|
||||
|
||||
if (isLoadingProviders) {
|
||||
return <FullPageLoader />;
|
||||
}
|
||||
|
||||
if (!hasAccess) {
|
||||
return <NoPermissionView entity="model catalog" />;
|
||||
}
|
||||
|
||||
if (providersError) {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-4 text-center">
|
||||
<p className="text-muted-foreground text-sm">Failed to load providers</p>
|
||||
<button type="button" data-testid="model-catalog-retry-btn" onClick={refetchProviders} className="text-sm underline">
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!providers || providers.length === 0) {
|
||||
return <ModelCatalogEmptyState />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-7xl">
|
||||
<ModelCatalogTable
|
||||
rows={filteredRows}
|
||||
providers={(providers ?? []).map((p) => p.name)}
|
||||
providerFilter={providerFilter}
|
||||
onProviderFilterChange={setProviderFilter}
|
||||
totalProviders={(providers ?? []).length}
|
||||
totalModels={modelsData?.total ?? 0}
|
||||
totalRequests24h={globalStats?.total_requests ?? 0}
|
||||
totalCost24h={globalStats?.total_cost ?? 0}
|
||||
isLoadingModels={isLoadingModels}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user