+ {onDismiss ? (
+
+ ) : null}
+ {!usage ? (
+
No usage snapshot available yet for this provider.
) : null}
{usage ? (
-
+
{rows.map((row) => {
const percentUsed = clampUsagePercentUsed(row.window?.percentUsed ?? null);
const percentRemaining = toRemainingUsagePercent(percentUsed);
return (
{row.label}
@@ -173,30 +125,6 @@ export const ProviderUsageNotice = memo(function ProviderUsageNotice({
})}
) : null}
- {metadata.usageUrl || metadata.dashboardUrl ? (
-
- ) : null}
);
diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx
index ea8af13d..eb679f43 100644
--- a/apps/web/src/components/settings/SettingsPanels.tsx
+++ b/apps/web/src/components/settings/SettingsPanels.tsx
@@ -4,6 +4,7 @@ export { SettingsModelsPanel } from "./SettingsModelsPanel";
export { SettingsGitPanel } from "./SettingsGitPanel";
export { SettingsEnhancePanel } from "./SettingsEnhancePanel";
export { SettingsProvidersPanel } from "./SettingsProvidersPanel";
+export { SettingsUsagePanel } from "./SettingsUsagePanel";
export { SettingsAdvancedPanel } from "./SettingsAdvancedPanel";
export { SettingsAboutPanel } from "./SettingsAboutPanel";
export { ArchivedThreadsPanel } from "./SettingsArchivedPanel";
diff --git a/apps/web/src/components/settings/SettingsProviderUsageSection.tsx b/apps/web/src/components/settings/SettingsProviderUsageSection.tsx
new file mode 100644
index 00000000..76da04b1
--- /dev/null
+++ b/apps/web/src/components/settings/SettingsProviderUsageSection.tsx
@@ -0,0 +1,289 @@
+import type { ServerProviderUsage, ServerProviderUsageWindow } from "@t3tools/contracts";
+import type { ProviderUsageMetadata } from "@t3tools/shared/provider-usage";
+
+import { cn } from "../../lib/utils";
+import {
+ clampUsagePercentUsed,
+ deriveSessionResetFromWeeklyReset,
+ selectPrimaryUsageWindows,
+ toRemainingUsagePercent,
+} from "../../providerUsageDisplay";
+
+function getUsageStatusLabel(status: "ready" | "limited" | "exhausted" | "unknown" | "error") {
+ switch (status) {
+ case "ready":
+ return "Usage healthy";
+ case "limited":
+ return "Usage limited";
+ case "exhausted":
+ return "Usage exhausted";
+ case "error":
+ return "Usage check failed";
+ case "unknown":
+ default:
+ return "Usage unknown";
+ }
+}
+
+function normalizeUsageText(value: string): string {
+ return value.trim().replaceAll(/\s+/g, " ").toLowerCase();
+}
+
+function shouldHideUsageSummary(input: {
+ readonly summary: string | null;
+ readonly planName: string | null;
+ readonly loginMethod: string | null;
+}): boolean {
+ if (!input.summary) {
+ return false;
+ }
+
+ const normalizedSummary = normalizeUsageText(input.summary);
+ if (input.planName) {
+ const normalizedPlan = normalizeUsageText(input.planName);
+ if (
+ normalizedSummary === `plan: ${normalizedPlan}` ||
+ normalizedSummary === `plan ${normalizedPlan}`
+ ) {
+ return true;
+ }
+ }
+
+ if (input.loginMethod) {
+ const normalizedLoginMethod = normalizeUsageText(input.loginMethod);
+ if (
+ normalizedSummary === `login: ${normalizedLoginMethod}` ||
+ normalizedSummary === `login ${normalizedLoginMethod}`
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function usageBarToneClass(percentRemaining: number | null): string {
+ if (percentRemaining === null) {
+ return "bg-muted-foreground/30";
+ }
+ if (percentRemaining <= 5) {
+ return "bg-destructive";
+ }
+ if (percentRemaining <= 20) {
+ return "bg-warning";
+ }
+ return "bg-success";
+}
+
+function isSameLocalDay(a: Date, b: Date): boolean {
+ return (
+ a.getFullYear() === b.getFullYear() &&
+ a.getMonth() === b.getMonth() &&
+ a.getDate() === b.getDate()
+ );
+}
+
+function formatUsageResetLabel(resetAt: string): string {
+ const resetDate = new Date(resetAt);
+ if (Number.isNaN(resetDate.getTime())) {
+ return `Resets ${resetAt}`;
+ }
+
+ const now = new Date();
+ const formatted = isSameLocalDay(resetDate, now)
+ ? new Intl.DateTimeFormat(undefined, {
+ hour: "numeric",
+ minute: "2-digit",
+ }).format(resetDate)
+ : new Intl.DateTimeFormat(undefined, {
+ month: "short",
+ day: "numeric",
+ year: "numeric",
+ hour: "numeric",
+ minute: "2-digit",
+ })
+ .format(resetDate)
+ .replace(", ", " ");
+
+ return `Resets ${formatted}`;
+}
+
+function getUsageLimitTitle(input: { key: "session" | "weekly"; label: string }): string {
+ if (input.key === "session") {
+ return "5 hour usage limit";
+ }
+ return `${input.label} usage limit`;
+}
+
+function getUsageWindowUnavailableLabel(window: { percentUsed: number | null } | null): string {
+ if (window) {
+ return "Usage data unavailable";
+ }
+ return "Usage source unavailable";
+}
+
+function hasRenderableUsagePercent(window: { percentUsed: number | null } | null): boolean {
+ return clampUsagePercentUsed(window?.percentUsed ?? null) !== null;
+}
+
+interface UsageBarRow {
+ readonly key: "session" | "weekly";
+ readonly label: string;
+ readonly window: ServerProviderUsageWindow | null;
+}
+
+function buildUsageBarRows(input: {
+ readonly usage: ServerProviderUsage;
+ readonly metadata: ProviderUsageMetadata;
+}): ReadonlyArray
{
+ const usageWindows = input.usage.windows;
+ const { sessionWindow, weeklyWindow } = selectPrimaryUsageWindows({
+ windows: usageWindows,
+ sessionLabel: input.metadata.sessionLabel,
+ weeklyLabel: input.metadata.weeklyLabel,
+ });
+ const sessionResetAt =
+ sessionWindow?.resetAt ??
+ deriveSessionResetFromWeeklyReset({
+ weeklyResetAt: weeklyWindow?.resetAt ?? null,
+ });
+
+ return [
+ {
+ key: "session",
+ label: input.metadata.sessionLabel,
+ window: sessionWindow
+ ? {
+ ...sessionWindow,
+ resetAt: sessionResetAt,
+ }
+ : sessionResetAt
+ ? {
+ key: "derived-session-reset",
+ label: input.metadata.sessionLabel,
+ percentUsed: null,
+ resetAt: sessionResetAt,
+ }
+ : null,
+ },
+ {
+ key: "weekly",
+ label: input.metadata.weeklyLabel,
+ window: weeklyWindow,
+ },
+ ];
+}
+
+export function SettingsProviderUsageSection({
+ usage,
+ metadata,
+}: {
+ usage: ServerProviderUsage;
+ metadata: ProviderUsageMetadata;
+}) {
+ const usageSummary = !shouldHideUsageSummary({
+ summary: usage.summary,
+ planName: usage.identity.planName,
+ loginMethod: usage.identity.loginMethod,
+ })
+ ? usage.summary
+ : null;
+ const usageDetail = usage.detail ?? null;
+ const usageBarRows = buildUsageBarRows({ usage, metadata });
+
+ return (
+
+
+ {getUsageStatusLabel(usage.status)}
+ {usage.stale ? (
+ Stale
+ ) : null}
+ {usage.identity.planName ? Plan: {usage.identity.planName} : null}
+ {usage.identity.loginMethod ? Login: {usage.identity.loginMethod} : null}
+
+ {usageSummary || usageDetail ? (
+
+ {usageSummary ?? usageDetail}
+ {usageSummary && usageDetail ? ` - ${usageDetail}` : null}
+
+ ) : null}
+
+ {usageBarRows.map((row) => {
+ const percentUsed = clampUsagePercentUsed(row.window?.percentUsed ?? null);
+ const percentRemaining = toRemainingUsagePercent(percentUsed);
+ const hasRemaining = percentRemaining !== null && hasRenderableUsagePercent(row.window);
+ const unavailableLabel = getUsageWindowUnavailableLabel(row.window);
+ const primaryStatusLabel = row.window?.resetAt
+ ? formatUsageResetLabel(row.window.resetAt)
+ : unavailableLabel;
+ return (
+
+
+ {getUsageLimitTitle({ key: row.key, label: row.label })}
+
+
+ {hasRemaining ? (
+ <>
+
+ {`${Math.round(percentRemaining)}%`}
+
+ remaining
+ >
+ ) : (
+
+ {primaryStatusLabel}
+
+ )}
+
+
+
{primaryStatusLabel}
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/apps/web/src/components/settings/SettingsProvidersSection.tsx b/apps/web/src/components/settings/SettingsProvidersSection.tsx
index 69cab1ee..236ec716 100644
--- a/apps/web/src/components/settings/SettingsProvidersSection.tsx
+++ b/apps/web/src/components/settings/SettingsProvidersSection.tsx
@@ -1,11 +1,4 @@
-import {
- ChevronDownIcon,
- InfoIcon,
- LoaderIcon,
- PlusIcon,
- RefreshCwIcon,
- XIcon,
-} from "lucide-react";
+import { ChevronDownIcon, InfoIcon, PlusIcon, RefreshCwIcon, XIcon } from "lucide-react";
import { useCallback, useRef, useState } from "react";
import {
PROVIDER_DISPLAY_NAMES,
@@ -15,19 +8,13 @@ import {
} from "@t3tools/contracts";
import { DEFAULT_UNIFIED_SETTINGS, type UnifiedSettings } from "@t3tools/contracts/settings";
import { normalizeModelSlug, resolveUtilityModelSelectionDefault } from "@t3tools/shared/model";
-import { PROVIDER_USAGE_METADATA } from "@t3tools/shared/provider-usage";
import { Equal } from "effect";
import { cn } from "../../lib/utils";
import { MAX_CUSTOM_MODEL_LENGTH, resolveAppModelSelectionState } from "../../modelSelection";
import { ensureNativeApi } from "../../nativeApi";
-import {
- clampUsagePercentUsed,
- selectPrimaryUsageWindows,
- toRemainingUsagePercent,
-} from "../../providerUsageDisplay";
-import { useProviderUsages } from "../../rpc/providerUsageState";
import { useServerProviders } from "../../rpc/serverState";
+import { formatRelativeTime } from "../../timestampFormat";
import { Button } from "../ui/button";
import { Collapsible, CollapsibleContent } from "../ui/collapsible";
import { Input } from "../ui/input";
@@ -35,7 +22,6 @@ import { Switch } from "../ui/switch";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import { SettingResetButton, SettingsSection } from "./SettingsPanelPrimitives";
import { PROVIDER_SETTINGS } from "./settingsProviderConfig";
-import { formatRelativeTime } from "../../timestampFormat";
type SettingsUpdater = (patch: Partial) => void;
@@ -112,112 +98,6 @@ function getProviderVersionLabel(version: string | null | undefined) {
return version.startsWith("v") ? version : `v${version}`;
}
-function getUsageStatusLabel(status: "ready" | "limited" | "exhausted" | "unknown" | "error") {
- switch (status) {
- case "ready":
- return "Usage healthy";
- case "limited":
- return "Usage limited";
- case "exhausted":
- return "Usage exhausted";
- case "error":
- return "Usage check failed";
- case "unknown":
- default:
- return "Usage unknown";
- }
-}
-
-function normalizeUsageText(value: string): string {
- return value.trim().replaceAll(/\s+/g, " ").toLowerCase();
-}
-
-function shouldHideUsageSummary(input: {
- readonly summary: string | null;
- readonly planName: string | null;
- readonly loginMethod: string | null;
-}): boolean {
- if (!input.summary) {
- return false;
- }
-
- const normalizedSummary = normalizeUsageText(input.summary);
- if (input.planName) {
- const normalizedPlan = normalizeUsageText(input.planName);
- if (
- normalizedSummary === `plan: ${normalizedPlan}` ||
- normalizedSummary === `plan ${normalizedPlan}`
- ) {
- return true;
- }
- }
-
- if (input.loginMethod) {
- const normalizedLoginMethod = normalizeUsageText(input.loginMethod);
- if (
- normalizedSummary === `login: ${normalizedLoginMethod}` ||
- normalizedSummary === `login ${normalizedLoginMethod}`
- ) {
- return true;
- }
- }
-
- return false;
-}
-
-function usageBarToneClass(percentRemaining: number | null): string {
- if (percentRemaining === null) {
- return "bg-muted-foreground/30";
- }
- if (percentRemaining <= 5) {
- return "bg-destructive";
- }
- if (percentRemaining <= 20) {
- return "bg-warning";
- }
- return "bg-success";
-}
-
-function isSameLocalDay(a: Date, b: Date): boolean {
- return (
- a.getFullYear() === b.getFullYear() &&
- a.getMonth() === b.getMonth() &&
- a.getDate() === b.getDate()
- );
-}
-
-function formatUsageResetLabel(resetAt: string): string {
- const resetDate = new Date(resetAt);
- if (Number.isNaN(resetDate.getTime())) {
- return `Resets ${resetAt}`;
- }
-
- const now = new Date();
- const formatted = isSameLocalDay(resetDate, now)
- ? new Intl.DateTimeFormat(undefined, {
- hour: "numeric",
- minute: "2-digit",
- }).format(resetDate)
- : new Intl.DateTimeFormat(undefined, {
- month: "short",
- day: "numeric",
- year: "numeric",
- hour: "numeric",
- minute: "2-digit",
- })
- .format(resetDate)
- .replace(", ", " ");
-
- return `Resets ${formatted}`;
-}
-
-function getUsageLimitTitle(input: { key: "session" | "weekly"; label: string }): string {
- if (input.key === "session") {
- return "5 hour usage limit";
- }
- return `${input.label} usage limit`;
-}
-
function ProviderLastChecked({ lastCheckedAt }: { lastCheckedAt: string | null }) {
const lastCheckedRelative = lastCheckedAt ? formatRelativeTime(lastCheckedAt) : null;
@@ -267,11 +147,8 @@ export function SettingsProvidersSection({
const [customModelErrorByProvider, setCustomModelErrorByProvider] = useState<
Partial>
>({});
- const [isRefreshingProviders, setIsRefreshingProviders] = useState(false);
- const refreshingRef = useRef(false);
const modelListRefs = useRef>>({});
const serverProviders = useServerProviders();
- const providerUsages = useProviderUsages();
const textGenerationModelSelection = resolveAppModelSelectionState(settings, serverProviders);
const textGenProvider = textGenerationModelSelection.provider;
@@ -290,19 +167,10 @@ export function SettingsProvidersSection({
).provider;
const refreshProviders = useCallback(() => {
- if (refreshingRef.current) return;
- refreshingRef.current = true;
- setIsRefreshingProviders(true);
- void Promise.all([
- ensureNativeApi().server.refreshProviders(),
- ensureNativeApi().server.refreshUsageStatus(),
- ])
+ void ensureNativeApi()
+ .server.refreshProviders()
.catch((error: unknown) => {
console.warn("Failed to refresh providers", error);
- })
- .finally(() => {
- refreshingRef.current = false;
- setIsRefreshingProviders(false);
});
}, []);
@@ -402,8 +270,6 @@ export function SettingsProvidersSection({
(candidate) => candidate.provider === providerSettings.provider,
);
const providerConfig = settings.providers[providerSettings.provider];
- const providerUsage =
- providerUsages.find((usage) => usage.provider === providerSettings.provider) ?? null;
const defaultProviderConfig = DEFAULT_UNIFIED_SETTINGS.providers[providerSettings.provider];
const statusKey = liveProvider?.status ?? (providerConfig.enabled ? "warning" : "disabled");
const summary = getProviderSummary(liveProvider);
@@ -431,8 +297,6 @@ export function SettingsProvidersSection({
statusStyle: PROVIDER_STATUS_STYLES[statusKey],
summary,
versionLabel: getProviderVersionLabel(liveProvider?.version),
- providerUsage,
- providerUsageMetadata: PROVIDER_USAGE_METADATA[providerSettings.provider],
};
});
@@ -457,19 +321,14 @@ export function SettingsProvidersSection({
size="icon-xs"
variant="ghost"
className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground"
- disabled={isRefreshingProviders}
onClick={() => void refreshProviders()}
- aria-label="Refresh provider status and usage"
+ aria-label="Refresh provider status"
>
- {isRefreshingProviders ? (
-
- ) : (
-
- )}
+
}
/>
- Refresh provider status and usage
+ Refresh provider status
}
@@ -479,34 +338,6 @@ export function SettingsProvidersSection({
const customModelError = customModelErrorByProvider[providerCard.provider] ?? null;
const providerDisplayName =
PROVIDER_DISPLAY_NAMES[providerCard.provider] ?? providerCard.title;
- const usageSummary =
- providerCard.providerUsage &&
- !shouldHideUsageSummary({
- summary: providerCard.providerUsage.summary,
- planName: providerCard.providerUsage.identity.planName,
- loginMethod: providerCard.providerUsage.identity.loginMethod,
- })
- ? providerCard.providerUsage.summary
- : null;
- const usageDetail = providerCard.providerUsage?.detail ?? null;
- const usageWindows = providerCard.providerUsage?.windows ?? [];
- const { sessionWindow, weeklyWindow } = selectPrimaryUsageWindows({
- windows: usageWindows,
- sessionLabel: providerCard.providerUsageMetadata.sessionLabel,
- weeklyLabel: providerCard.providerUsageMetadata.weeklyLabel,
- });
- const usageBarRows = [
- {
- key: "session" as const,
- label: providerCard.providerUsageMetadata.sessionLabel,
- window: sessionWindow,
- },
- {
- key: "weekly" as const,
- label: providerCard.providerUsageMetadata.weeklyLabel,
- window: weeklyWindow,
- },
- ];
return (
@@ -548,105 +379,6 @@ export function SettingsProvidersSection({
{providerCard.summary.headline}
{providerCard.summary.detail ? ` - ${providerCard.summary.detail}` : null}
- {providerCard.providerUsage ? (
-
-
-
- {getUsageStatusLabel(providerCard.providerUsage.status)}
-
- {providerCard.providerUsage.stale ? (
-
- Stale
-
- ) : null}
- {providerCard.providerUsage.identity.planName ? (
- Plan: {providerCard.providerUsage.identity.planName}
- ) : null}
- {providerCard.providerUsage.identity.loginMethod ? (
- Login: {providerCard.providerUsage.identity.loginMethod}
- ) : null}
-
- {usageSummary || usageDetail ? (
-
- {usageSummary ?? usageDetail}
- {usageSummary && usageDetail ? ` - ${usageDetail}` : null}
-
- ) : null}
-
- {usageBarRows.map((row) => {
- const percentUsed = clampUsagePercentUsed(
- row.window?.percentUsed ?? null,
- );
- const percentRemaining = toRemainingUsagePercent(percentUsed);
- const hasRemaining = percentRemaining !== null;
- return (
-
-
- {getUsageLimitTitle({ key: row.key, label: row.label })}
-
-
-
- {hasRemaining ? `${Math.round(percentRemaining)}%` : "--"}
-
-
- remaining
-
-
-
-
- {row.window?.resetAt
- ? formatUsageResetLabel(row.window.resetAt)
- : "Resets unavailable"}
-
-
- );
- })}
-
-
-
- ) : null}