diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index ca1922c..2ce9827 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -92,6 +92,7 @@ import { formatTimestamp, parseTimestamp, } from "@/lib/formatters"; +import { selectPreferredProviderCredential } from "@/lib/provider-credential-selection"; type SessionSummary = { key: string; @@ -865,11 +866,27 @@ export default function DashboardPage() { const credentialsRes = await listProviderCredentialsApiV1ProviderCredentialsGet(); if (credentialsRes.status !== 200) return []; - const credentials = (credentialsRes.data ?? []).filter( - (cred) => cred.active && cred.has_session_key, + const credentials = credentialsRes.data ?? []; + const providerIds = Array.from( + new Set( + credentials + .filter((cred) => cred.active && cred.has_session_key) + .map((cred) => cred.provider), + ), ); + const selectedCredentials = providerIds + .map((providerId) => + selectPreferredProviderCredential( + credentials.filter((cred) => cred.has_session_key), + providerId, + ), + ) + .filter( + (cred): cred is (typeof credentials)[number] => + Boolean(cred?.active && cred.has_session_key), + ); const settled = await Promise.allSettled( - credentials.map((cred) => + selectedCredentials.map((cred) => getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet( cred.id, ).then((res) => ({ cred, res })), diff --git a/frontend/src/components/organisms/ProviderNavbarStatus.tsx b/frontend/src/components/organisms/ProviderNavbarStatus.tsx index 693ceb4..63feea8 100644 --- a/frontend/src/components/organisms/ProviderNavbarStatus.tsx +++ b/frontend/src/components/organisms/ProviderNavbarStatus.tsx @@ -18,6 +18,10 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { + credentialIsUsable, + selectPreferredProviderCredential, +} from "@/lib/provider-credential-selection"; type NavbarProviderId = "anthropic" | "openai"; @@ -134,42 +138,6 @@ function providerStatusColor(status: ProviderNavbarItem["status"]): string { return "bg-[color:var(--text-quiet)]"; } -function credentialIsUsable(cred: ProviderCredentialRead): boolean { - return cred.active && (cred.has_api_key || cred.has_session_key || Boolean(cred.base_url)); -} - -function navbarCredentialScore(cred: ProviderCredentialRead): number { - let score = 0; - - if (cred.active) score += 100; - if (cred.has_session_key) score += 50; - if (cred.account_key === "default") score += 40; - if (cred.display_name.toLowerCase().includes("local")) score += 30; - if (cred.has_api_key) score += 10; - if (cred.base_url) score += 5; - - return score; -} - -function selectNavbarCredential( - credentials: ProviderCredentialRead[], - providerId: NavbarProviderId, -): ProviderCredentialRead | null { - const providerCredentials = credentials.filter((cred) => cred.provider === providerId); - const usableCredentials = providerCredentials.filter(credentialIsUsable); - - return ( - [...usableCredentials].sort((a, b) => { - const scoreDelta = navbarCredentialScore(b) - navbarCredentialScore(a); - if (scoreDelta !== 0) return scoreDelta; - return a.account_key.localeCompare(b.account_key); - })[0] ?? - providerCredentials.find((cred) => cred.active) ?? - providerCredentials[0] ?? - null - ); -} - function MiniRemainingBar({ pct, className = "w-10", @@ -202,7 +170,7 @@ function buildProviderNavbarItems({ isLoading: boolean; }): ProviderNavbarItem[] { return NAVBAR_PROVIDER_IDS.map((providerId) => { - const credential = selectNavbarCredential(credentials, providerId); + const credential = selectPreferredProviderCredential(credentials, providerId); const activeCredential = credential && credentialIsUsable(credential) ? credential : null; const usage = activeCredential ? usageByCredentialId[activeCredential.id] : null; const status: ProviderNavbarItem["status"] = activeCredential @@ -293,7 +261,7 @@ export function ProviderNavbarStatus() { const usageCredentials = useMemo(() => { return NAVBAR_PROVIDER_IDS.map((providerId) => - selectNavbarCredential(credentials, providerId), + selectPreferredProviderCredential(credentials, providerId), ).filter((cred): cred is ProviderCredentialRead => Boolean(cred && credentialIsUsable(cred))); }, [credentials]); diff --git a/frontend/src/lib/provider-credential-selection.ts b/frontend/src/lib/provider-credential-selection.ts new file mode 100644 index 0000000..fabd53f --- /dev/null +++ b/frontend/src/lib/provider-credential-selection.ts @@ -0,0 +1,37 @@ +import type { ProviderCredentialRead } from "@/api/generated/model"; + +export function credentialIsUsable(cred: ProviderCredentialRead): boolean { + return cred.active && (cred.has_api_key || cred.has_session_key || Boolean(cred.base_url)); +} + +function providerCredentialScore(cred: ProviderCredentialRead): number { + let score = 0; + + if (cred.active) score += 100; + if (cred.has_session_key) score += 50; + if (cred.account_key === "default") score += 40; + if (cred.display_name.toLowerCase().includes("local")) score += 30; + if (cred.has_api_key) score += 10; + if (cred.base_url) score += 5; + + return score; +} + +export function selectPreferredProviderCredential( + credentials: ProviderCredentialRead[], + providerId: string, +): ProviderCredentialRead | null { + const providerCredentials = credentials.filter((cred) => cred.provider === providerId); + const usableCredentials = providerCredentials.filter(credentialIsUsable); + + return ( + [...usableCredentials].sort((a, b) => { + const scoreDelta = providerCredentialScore(b) - providerCredentialScore(a); + if (scoreDelta !== 0) return scoreDelta; + return a.account_key.localeCompare(b.account_key); + })[0] ?? + providerCredentials.find((cred) => cred.active) ?? + providerCredentials[0] ?? + null + ); +}