From 6fe0e7015e406d7ba81b3d989cafe6b31b0b2234 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 27 Sep 2026 02:08:13 +0000 Subject: [PATCH 1/2] feat: link provider detail to the plan usage page Open the provider's own usage page from the detail screen when that page has a stable URL, so the panel can point at history it does not reconstruct. Co-authored-by: wjcjttl9 --- domain/provider-presentation.ts | 9 ++++ entrypoints/sidepanel/Cockpit.test.tsx | 33 ++++++++++++++ entrypoints/sidepanel/Cockpit.tsx | 1 + entrypoints/sidepanel/components/Icon.tsx | 9 +++- .../sidepanel/components/ProviderCard.tsx | 1 + entrypoints/sidepanel/styles.css | 13 ++++++ .../sidepanel/views/ProviderDetailView.tsx | 44 +++++++++++++------ locales/en.yml | 2 + locales/zh_CN.yml | 2 + providers/catalog.test.ts | 8 ++++ 10 files changed, 108 insertions(+), 14 deletions(-) diff --git a/domain/provider-presentation.ts b/domain/provider-presentation.ts index 8d50a35..5b2c717 100644 --- a/domain/provider-presentation.ts +++ b/domain/provider-presentation.ts @@ -5,6 +5,8 @@ export interface ProviderPresentation { readonly darkMarkPath?: string; readonly apiKeySetupUrl?: string; readonly apiKeyGuide?: "elevenlabs" | "newapi"; + /** Provider usage page with a stable public URL. Absent when none is known. */ + readonly usagePageUrl?: string; } export const providerCatalog = { @@ -12,12 +14,14 @@ export const providerCatalog = { displayName: "ChatGPT", presentation: { markPath: "/provider-marks/chatgpt.svg", + usagePageUrl: "https://chatgpt.com/codex/settings/usage", }, }, claude: { displayName: "Claude", presentation: { markPath: "/provider-marks/claude.svg", + usagePageUrl: "https://claude.ai/settings/usage", }, }, kimi: { @@ -25,6 +29,7 @@ export const providerCatalog = { presentation: { markPath: "/provider-marks/kimi.svg", darkMarkPath: "/provider-marks/kimi-dark.svg", + usagePageUrl: "https://www.kimi.com/membership/subscription", }, }, cursor: { @@ -32,6 +37,7 @@ export const providerCatalog = { presentation: { markPath: "/provider-marks/cursor.svg", darkMarkPath: "/provider-marks/cursor-dark.svg", + usagePageUrl: "https://cursor.com/dashboard/spending", }, }, grok: { @@ -45,12 +51,14 @@ export const providerCatalog = { displayName: "Mistral", presentation: { markPath: "/provider-marks/mistral.svg", + usagePageUrl: "https://admin.mistral.ai/organization/usage", }, }, perplexity: { displayName: "Perplexity", presentation: { markPath: "/provider-marks/perplexity.svg", + usagePageUrl: "https://www.perplexity.ai/account/usage", }, }, elevenlabs: { @@ -125,6 +133,7 @@ export const providerCatalog = { presentation: { markPath: "/provider-marks/openai.svg", apiKeySetupUrl: "https://platform.openai.com/api-keys", + usagePageUrl: "https://platform.openai.com/usage", }, }, groqcloud: { diff --git a/entrypoints/sidepanel/Cockpit.test.tsx b/entrypoints/sidepanel/Cockpit.test.tsx index e83c370..1e1bc25 100644 --- a/entrypoints/sidepanel/Cockpit.test.tsx +++ b/entrypoints/sidepanel/Cockpit.test.tsx @@ -1474,6 +1474,39 @@ describe("Cockpit", () => { expect(screen.getByRole("button", { name: "Disconnect ChatGPT" })).toHaveFocus(); }); + it("links provider detail to the plan usage page when that page has a stable URL", () => { + render( + , + ); + + fireEvent.click( + screen.getByRole("button", { name: "Open ChatGPT details" }), + ); + const usagePage = screen.getByRole("link", { + name: "Open the ChatGPT usage page", + }); + expect(usagePage).toHaveAttribute( + "href", + "https://chatgpt.com/codex/settings/usage", + ); + expect(usagePage).toHaveAttribute("target", "_blank"); + expect(usagePage).toHaveAttribute("rel", "noopener noreferrer"); + expect(usagePage).toHaveTextContent("Usage page"); + + fireEvent.click(screen.getByRole("button", { name: "Overview" })); + fireEvent.click(screen.getByRole("button", { name: "Open Grok details" })); + expect( + screen.queryByRole("link", { name: /usage page/i }), + ).not.toBeInTheDocument(); + }); + it("returns to Overview from Settings after disconnecting the provider that opened it", () => { const onDisconnectInstance = vi.fn(); render( diff --git a/entrypoints/sidepanel/Cockpit.tsx b/entrypoints/sidepanel/Cockpit.tsx index 5179fc3..af208e9 100644 --- a/entrypoints/sidepanel/Cockpit.tsx +++ b/entrypoints/sidepanel/Cockpit.tsx @@ -389,6 +389,7 @@ export function providerView( : undefined, detailTables: snapshot?.detailTables, now, + usagePageUrl: providerPresentation(provider.providerKind).usagePageUrl, }; } diff --git a/entrypoints/sidepanel/components/Icon.tsx b/entrypoints/sidepanel/components/Icon.tsx index 7bd62e5..868615d 100644 --- a/entrypoints/sidepanel/components/Icon.tsx +++ b/entrypoints/sidepanel/components/Icon.tsx @@ -14,7 +14,8 @@ export type IconName = | "settings" | "trash" | "trending-down" - | "trending-up"; + | "trending-up" + | "external"; export interface IconProps { name: IconName; @@ -83,6 +84,12 @@ export function Icon({ name, className = "" }: IconProps) { + ) : name === "external" ? ( + <> + + + + ) : name === "code" ? ( ) : ( diff --git a/entrypoints/sidepanel/components/ProviderCard.tsx b/entrypoints/sidepanel/components/ProviderCard.tsx index d99f870..f0d8344 100644 --- a/entrypoints/sidepanel/components/ProviderCard.tsx +++ b/entrypoints/sidepanel/components/ProviderCard.tsx @@ -81,6 +81,7 @@ export interface ProviderCardProps { onClick: () => void; }; headingLevel?: 2 | 3; + usagePageUrl?: string; openDetailsFocusKey?: string; onOpenDetails?: () => void; onOpenHistory?: (metricId: string) => void; diff --git a/entrypoints/sidepanel/styles.css b/entrypoints/sidepanel/styles.css index 1aa0cd7..ac7daf3 100644 --- a/entrypoints/sidepanel/styles.css +++ b/entrypoints/sidepanel/styles.css @@ -1951,17 +1951,30 @@ section[aria-labelledby="overview-title"] .provider-list { overflow-wrap: anywhere; } +.detail-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + .detail-history-action { display: inline-flex; min-height: 44px; align-items: center; justify-content: center; gap: 6px; + padding: 0 12px; border: 1px solid var(--border); border-radius: 11px; color: var(--text); font-size: 12px; font-weight: 600; + text-decoration: none; +} + +a.detail-history-action:hover { + color: var(--text); + border-color: var(--quota); } .connection-surface__disconnect, diff --git a/entrypoints/sidepanel/views/ProviderDetailView.tsx b/entrypoints/sidepanel/views/ProviderDetailView.tsx index 5fa9990..13ec2ec 100644 --- a/entrypoints/sidepanel/views/ProviderDetailView.tsx +++ b/entrypoints/sidepanel/views/ProviderDetailView.tsx @@ -265,19 +265,37 @@ export function ProviderDetailView({ /> ) : null} - {firstWindow ? ( - + {firstWindow || provider.usagePageUrl ? ( +
+ {firstWindow ? ( + + ) : null} + {provider.usagePageUrl ? ( + + + {l10n.t("providerDetail.openUsage")} + + ) : null} +
) : null}
diff --git a/locales/en.yml b/locales/en.yml index d7a4326..d36e3f8 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -122,6 +122,8 @@ providerDetail: usageGroup: Usage group openHistory: Open history openHistoryNamed: "Open {label} history" + openUsage: Usage page + openUsageNamed: "Open the {label} usage page" connectionTitle: Connection and capabilities scheduledNoTab: Scheduled refresh reads the existing session without opening a tab. disconnectExplanation: Disconnecting permanently deletes this provider's credentials, configuration, usage, and history. diff --git a/locales/zh_CN.yml b/locales/zh_CN.yml index fb52089..375afe8 100644 --- a/locales/zh_CN.yml +++ b/locales/zh_CN.yml @@ -122,6 +122,8 @@ providerDetail: usageGroup: 用量组 openHistory: 打开历史 openHistoryNamed: "打开 {label} 的历史" + openUsage: 用量页面 + openUsageNamed: "打开 {label} 的用量页面" connectionTitle: 连接与能力 scheduledNoTab: 定时刷新会读取现有会话,不会打开标签页。 disconnectExplanation: 断开连接会永久删除该服务的凭据、配置、用量和历史。 diff --git a/providers/catalog.test.ts b/providers/catalog.test.ts index 1e0ce24..18e4600 100644 --- a/providers/catalog.test.ts +++ b/providers/catalog.test.ts @@ -51,6 +51,7 @@ describe("provider catalog", () => { darkMarkPath?: string; apiKeySetupUrl?: string; apiKeyGuide?: "elevenlabs" | "newapi"; + usagePageUrl?: string; }; const providerPresentation = ( catalog as typeof catalog & { @@ -70,20 +71,24 @@ describe("provider catalog", () => { { providerId: "chatgpt", markPath: "/provider-marks/chatgpt.svg", + usagePageUrl: "https://chatgpt.com/codex/settings/usage", }, { providerId: "claude", markPath: "/provider-marks/claude.svg", + usagePageUrl: "https://claude.ai/settings/usage", }, { providerId: "kimi", markPath: "/provider-marks/kimi.svg", darkMarkPath: "/provider-marks/kimi-dark.svg", + usagePageUrl: "https://www.kimi.com/membership/subscription", }, { providerId: "cursor", markPath: "/provider-marks/cursor.svg", darkMarkPath: "/provider-marks/cursor-dark.svg", + usagePageUrl: "https://cursor.com/dashboard/spending", }, { providerId: "grok", @@ -93,10 +98,12 @@ describe("provider catalog", () => { { providerId: "mistral", markPath: "/provider-marks/mistral.svg", + usagePageUrl: "https://admin.mistral.ai/organization/usage", }, { providerId: "perplexity", markPath: "/provider-marks/perplexity.svg", + usagePageUrl: "https://www.perplexity.ai/account/usage", }, { providerId: "elevenlabs", @@ -149,6 +156,7 @@ describe("provider catalog", () => { providerId: "openai", markPath: "/provider-marks/openai.svg", apiKeySetupUrl: "https://platform.openai.com/api-keys", + usagePageUrl: "https://platform.openai.com/usage", }, { providerId: "groqcloud", From 4fe7f54ee46e6ed246d711144de8a35287a8cc97 Mon Sep 17 00:00:00 2001 From: TiantianFlow <177855728+TiantianFlow@users.noreply.github.com> Date: Thu, 1 Oct 2026 20:50:40 -0400 Subject: [PATCH 2/2] fix: point Kimi and Perplexity usage links at the live settings pages --- domain/provider-presentation.ts | 4 ++-- entrypoints/sidepanel/Cockpit.test.tsx | 29 +++++++++++++++++++++++++- providers/catalog.test.ts | 4 ++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/domain/provider-presentation.ts b/domain/provider-presentation.ts index 5b2c717..cb6d665 100644 --- a/domain/provider-presentation.ts +++ b/domain/provider-presentation.ts @@ -29,7 +29,7 @@ export const providerCatalog = { presentation: { markPath: "/provider-marks/kimi.svg", darkMarkPath: "/provider-marks/kimi-dark.svg", - usagePageUrl: "https://www.kimi.com/membership/subscription", + usagePageUrl: "https://www.kimi.com/settings/subscription", }, }, cursor: { @@ -58,7 +58,7 @@ export const providerCatalog = { displayName: "Perplexity", presentation: { markPath: "/provider-marks/perplexity.svg", - usagePageUrl: "https://www.perplexity.ai/account/usage", + usagePageUrl: "https://www.perplexity.ai/#settings/usage", }, }, elevenlabs: { diff --git a/entrypoints/sidepanel/Cockpit.test.tsx b/entrypoints/sidepanel/Cockpit.test.tsx index 1e1bc25..e4e876c 100644 --- a/entrypoints/sidepanel/Cockpit.test.tsx +++ b/entrypoints/sidepanel/Cockpit.test.tsx @@ -1475,9 +1475,22 @@ describe("Cockpit", () => { }); it("links provider detail to the plan usage page when that page has a stable URL", () => { + const state = createFixtureState(NOW); + const kimi = state.instances.find((instance) => instance.providerKind === "kimi"); + if (kimi) { + state.instances.push({ + ...kimi, + id: "perplexity:default", + providerKind: "perplexity", + snapshot: kimi.snapshot + ? { ...kimi.snapshot, providerKind: "perplexity" } + : undefined, + }); + } + render( { expect(usagePage).toHaveAttribute("rel", "noopener noreferrer"); expect(usagePage).toHaveTextContent("Usage page"); + fireEvent.click(screen.getByRole("button", { name: "Overview" })); + fireEvent.click(screen.getByRole("button", { name: "Open Kimi details" })); + expect( + screen.getByRole("link", { name: "Open the Kimi usage page" }), + ).toHaveAttribute("href", "https://www.kimi.com/settings/subscription"); + + fireEvent.click(screen.getByRole("button", { name: "Overview" })); + fireEvent.click( + screen.getByRole("button", { name: "Open Perplexity details" }), + ); + expect( + screen.getByRole("link", { name: "Open the Perplexity usage page" }), + ).toHaveAttribute("href", "https://www.perplexity.ai/#settings/usage"); + fireEvent.click(screen.getByRole("button", { name: "Overview" })); fireEvent.click(screen.getByRole("button", { name: "Open Grok details" })); expect( diff --git a/providers/catalog.test.ts b/providers/catalog.test.ts index 18e4600..2453741 100644 --- a/providers/catalog.test.ts +++ b/providers/catalog.test.ts @@ -82,7 +82,7 @@ describe("provider catalog", () => { providerId: "kimi", markPath: "/provider-marks/kimi.svg", darkMarkPath: "/provider-marks/kimi-dark.svg", - usagePageUrl: "https://www.kimi.com/membership/subscription", + usagePageUrl: "https://www.kimi.com/settings/subscription", }, { providerId: "cursor", @@ -103,7 +103,7 @@ describe("provider catalog", () => { { providerId: "perplexity", markPath: "/provider-marks/perplexity.svg", - usagePageUrl: "https://www.perplexity.ai/account/usage", + usagePageUrl: "https://www.perplexity.ai/#settings/usage", }, { providerId: "elevenlabs",