- {pools.length === 0 && notices.length === 0 ? (
+ {pools.length === 0 && notices.length === 0 && !cursorPrompt ? (
No provider on the selected environments reports subscription limits.
) : null}
- {pools.map((pool) => (
-
+ {pools.map((pool, index) => (
+
+ {index === cursorPromptAt ? cursorPrompt : null}
+
+
))}
+ {cursorPromptAt === pools.length ? cursorPrompt : null}
);
diff --git a/apps/web/src/components/usage/UsagePage.tsx b/apps/web/src/components/usage/UsagePage.tsx
index b3748e120d1d..1387bb3a9d9e 100644
--- a/apps/web/src/components/usage/UsagePage.tsx
+++ b/apps/web/src/components/usage/UsagePage.tsx
@@ -1,6 +1,7 @@
import { RefreshIcon } from "~/components/ui/refresh-icon";
import { useAtomValue } from "@effect/atom-react";
import {
+ ProviderDriverKind,
USAGE_CONTRACT_VERSION,
type EnvironmentId,
type UsageProviderKind,
@@ -40,6 +41,7 @@ import {
makeWindow,
} from "@t3tools/shared/usageFormat";
import { Button, InlineButton } from "../ui/button";
+import { ProviderInstanceIcon } from "../chat/ProviderInstanceIcon";
import {
Menu,
MenuCheckboxItem,
@@ -53,6 +55,7 @@ import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../
import { SidebarInset } from "../ui/sidebar";
import { Skeleton } from "../ui/skeleton";
import { Toggle, ToggleGroup } from "../ui/toggle-group";
+import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import {
WorkspaceBreadcrumb,
WorkspaceBreadcrumbItem,
@@ -118,6 +121,25 @@ export function UsagePage() {
selectedEnvironmentIds,
);
const presentations = useAtomValue(environmentPresentations.presentationsAtom);
+ const cursorAccessEnvironments = selectedEnvironments.filter((environment) =>
+ environment.summary?.sources.some((source) => source.action === "enableCursorKeychain"),
+ );
+ const sourceMessages = [
+ ...new Set(
+ selectedEnvironments.flatMap(
+ (environment) =>
+ environment.summary?.sources.flatMap((source) =>
+ source.message &&
+ !source.action &&
+ (source.status === "partial" ||
+ source.status === "failed" ||
+ source.fingerprint.provider === "cursor")
+ ? [source.message]
+ : [],
+ ) ?? [],
+ ),
+ ),
+ ];
const refreshProviders = useAtomCommand(serverEnvironment.refreshProviders, {
reportFailure: false,
});
@@ -147,6 +169,17 @@ export function UsagePage() {
[breakdown, merged.models, metric],
);
const activeProviders = useMemo(() => providersWithUsage(merged.providers), [merged.providers]);
+ const summaryRows: Array<
+ | { readonly kind: "usage"; readonly provider: UsageProviderKind }
+ | { readonly kind: "enable"; readonly environment: EnvironmentUsageStatus }
+ > = activeProviders.map((provider) => ({ kind: "usage", provider }));
+ const cursorInsertAt =
+ Math.max(activeProviders.indexOf("codex"), activeProviders.indexOf("claude")) + 1;
+ summaryRows.splice(
+ cursorInsertAt,
+ 0,
+ ...cursorAccessEnvironments.map((environment) => ({ kind: "enable" as const, environment })),
+ );
const timeValueColumnWidth = `${60 / (activeProviders.length + 2)}%`;
const selectWindow = (days: number) => {
@@ -165,7 +198,7 @@ export function UsagePage() {
setPreferences(nextPreferences);
saveUsagePagePreferences(nextPreferences);
};
- const refreshLimits = async (automatic = false) => {
+ const refreshLimits = async (automatic = false, afterPending = false) => {
try {
await Promise.all(
Array.from(presentations, ([environmentId, presentation]) => {
@@ -175,6 +208,7 @@ export function UsagePage() {
environmentId,
() => refreshProviders({ environmentId, input: {} }),
automatic,
+ afterPending,
);
}
}),
@@ -380,11 +414,30 @@ export function UsagePage() {
: `Select an environment to see ${showingLimits ? "limits" : "usage"}.`}
) : showingLimits ? (
-