From 834fa4fdb026c337e2f46f45ab3b8a2052c38245 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 21 May 2026 20:30:01 -0500 Subject: [PATCH] feat(ui): top navbar ai info --- .../src/app/settings/ai-providers/page.tsx | 262 +++++++++++++++++- .../templates/DashboardPageLayout.tsx | 4 +- .../components/templates/DashboardShell.tsx | 21 +- 3 files changed, 280 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/settings/ai-providers/page.tsx b/frontend/src/app/settings/ai-providers/page.tsx index 170865f..0c17ff8 100644 --- a/frontend/src/app/settings/ai-providers/page.tsx +++ b/frontend/src/app/settings/ai-providers/page.tsx @@ -2,7 +2,7 @@ export const dynamic = "force-dynamic"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { Bot, CheckCircle2, @@ -26,7 +26,11 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ConfirmActionDialog } from "@/components/ui/confirm-action-dialog"; import { useOrganizationMembership } from "@/lib/use-organization-membership"; -import type { ProviderCredentialRead, ProviderUsageLiveRead } from "@/api/generated/model"; +import type { + ProviderCredentialRead, + ProviderUsageLiveRead, + SubscriptionWindowRead, +} from "@/api/generated/model"; import { customFetch } from "@/api/mutator"; import { listProviderCredentialsApiV1ProviderCredentialsGet, @@ -812,6 +816,194 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider ); } +// --------------------------------------------------------------------------- +// Navbar provider status — compact mirror of the active provider card +// --------------------------------------------------------------------------- + +function providerShortLabel(provider: string): string { + const config = PROVIDERS.find((item) => item.id === provider); + if (!config) return provider; + return config.label.split(" ")[0].replace("/", ""); +} + +function clampPct(pct: number): number { + return Math.max(0, Math.min(100, pct)); +} + +function remainingPct(window: SubscriptionWindowRead | null): number | null { + if (!window) return null; + return clampPct(100 - window.pct_used); +} + +function remainingColor(pct: number | null): string { + if (pct == null) return "bg-[color:var(--text-quiet)]"; + if (pct <= 10) return "bg-[color:var(--danger)]"; + if (pct <= 25) return "bg-[color:var(--warning)]"; + return "bg-[color:var(--success)]"; +} + +function findSessionWindow(usage: ProviderUsageLiveRead | null | undefined) { + const windows = usage?.subscription_windows ?? []; + return ( + windows.find((window) => { + const key = `${window.key} ${window.label}`.toLowerCase(); + return ( + key.includes("current") || + key.includes("session") || + key.includes("five") || + key.includes("hour") + ); + }) ?? + windows[0] ?? + null + ); +} + +function findModelsWindow( + usage: ProviderUsageLiveRead | null | undefined, + sessionWindow: SubscriptionWindowRead | null, +) { + const windows = usage?.subscription_windows ?? []; + return ( + windows.find((window) => { + if (window.key === sessionWindow?.key) return false; + const key = `${window.key} ${window.label}`.toLowerCase(); + return key.includes("all") || key.includes("model") || key.includes("week"); + }) ?? + windows.find((window) => window.key !== sessionWindow?.key) ?? + null + ); +} + +function resetLabel( + sessionWindow: SubscriptionWindowRead | null, + modelsWindow: SubscriptionWindowRead | null, +): string { + const resetInMs = sessionWindow?.reset_in_ms ?? modelsWindow?.reset_in_ms; + return resetInMs == null ? "—" : fmtResetMs(resetInMs); +} + +function keyLabel(cred: ProviderCredentialRead): string { + if (cred.has_api_key && cred.api_key_last_four) return `API ••••${cred.api_key_last_four}`; + if (cred.has_api_key) return "API set"; + if (cred.base_url) return "API local"; + return "API missing"; +} + +function tokenLabel( + cred: ProviderCredentialRead, + usage: ProviderUsageLiveRead | null | undefined, +): string { + if ((usage?.subscription_windows?.length ?? 0) > 0) return "Token connected"; + if (cred.has_session_key) return "Token set"; + return "Token auto"; +} + +function MiniRemainingBar({ + pct, + className = "w-10", +}: { + pct: number | null; + className?: string; +}) { + const width = pct == null ? 0 : clampPct(pct); + return ( +