feat(agy): show the signed-in quota on the official card

- Ask agy's own /usage command (agy -p /usage --output-format json) instead of
  reading credentials; it makes no model call and opens no conversation.
- Keep the Gemini group and the Claude/GPT group, each with its 5-hour and
  weekly window, as four tiers; unknown buckets are skipped.
- On the card each group shows only its window with the least left, since
  four tiers do not fit; the expanded view lists all four.
- A missing agy is treated as no login (nothing shown); a timeout propagates
  so the last good value is kept; other failures do not echo agy's output.
This commit is contained in:
Jason committed 2026-10-21 07:26:26 +08:00
1 parent dfe100813f
commit 1b4cd547c4
13 files changed
+365 -15

No files matched your search

@@ -11,7 +11,9 @@ vi.mock("@/components/providers/ProviderCardActions", () => ({
vi.mock("@/components/ProviderIcon", () => ({ ProviderIcon: () => null }));
vi.mock("@/components/UsageFooter", () => ({ default: () => null }));
vi.mock("@/components/SubscriptionQuotaFooter", () => ({
default: () => null,
default: ({ appId }: { appId: string }) => (
<span data-testid="subscription-quota">{appId}</span>
),
}));
vi.mock("@/components/CopilotQuotaFooter", () => ({ default: () => null }));
vi.mock("@/components/CodexOauthQuotaFooter", () => ({ default: () => null }));
@@ -75,3 +77,35 @@ describe("ProviderCard Antigravity model", () => {
expect(screen.queryByText("Gemini 3.1 Pro (High)")).not.toBeInTheDocument();
});
});
describe("ProviderCard Antigravity official quota", () => {
const official: Provider = {
id: "official",
name: "Antigravity",
category: "official",
settingsConfig: { config: {} },
meta: {
usage_script: {
enabled: true,
language: "javascript",
code: "",
templateType: "official_subscription",
},
},
};
it("shows the signed-in quota once official usage is turned on", () => {
renderCard(official);
expect(screen.getByTestId("subscription-quota")).toHaveTextContent("agy");
});
it("stays hidden while usage is off", () => {
renderCard({
...official,
meta: {
usage_script: { ...official.meta!.usage_script!, enabled: false },
},
});
expect(screen.queryByTestId("subscription-quota")).not.toBeInTheDocument();
});
});
@@ -194,6 +194,41 @@ describe("Claude Fable subscription quota", () => {
});
});
describe("Antigravity quota groups", () => {
const agyTiers: QuotaTier[] = [
{ name: "agy_gemini_five_hour", utilization: 10, resetsAt: null },
{
name: "agy_gemini_weekly",
utilization: 40,
resetsAt: "2026-09-12T00:00:00Z",
},
{ name: "agy_third_party_five_hour", utilization: 95, resetsAt: null },
{ name: "agy_third_party_weekly", utilization: 20, resetsAt: null },
];
it("keeps one line per group on the card: the window with the least left", () => {
renderQuota(agyTiers);
const lines = screen.getByRole("button").children;
expect(lines).toHaveLength(2);
expect(lines[0]).toHaveTextContent("Gemini 剩余 60%");
expect(lines[1]).toHaveTextContent("Claude/GPT 剩余 5%");
// 倒计时跟着留下的那档(Gemini 每周)
expect(screen.getByText("2d12h")).toBeInTheDocument();
});
it("lists every group and window when expanded", () => {
renderQuota(agyTiers, false);
for (const name of [
"Gemini 5 小时: 剩余 90%",
"Gemini 每周: 剩余 60%",
"Claude/GPT 5 小时: 剩余 5%",
"Claude/GPT 每周: 剩余 80%",
]) {
expect(screen.getByRole("meter", { name })).toBeInTheDocument();
}
});
});
describe("credential failures", () => {
const failed = (credentialStatus: SubscriptionQuota["credentialStatus"]) =>
renderQuota([], true, {