mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Improve usage limit display
This commit is contained in:
parent
d06e329a78
commit
7f4387706b
7 changed files with 82 additions and 19 deletions
|
|
@ -215,7 +215,15 @@ function RateLimitPanel({ rateLimit }: { rateLimit: RateLimitSummary | null }):
|
|||
>
|
||||
<div className="codex-panel__limit-panel-label">{row.label}</div>
|
||||
<div className="codex-panel__limit-panel-value">{row.value}</div>
|
||||
<div className="codex-panel__limit-panel-meter">
|
||||
<div
|
||||
className={[
|
||||
"codex-panel__limit-panel-meter",
|
||||
row.meterDivisions ? "codex-panel__limit-panel-meter--divided" : "",
|
||||
row.meterDivisions ? `codex-panel__limit-panel-meter--${String(row.meterDivisions)}` : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
>
|
||||
<div className="codex-panel__limit-panel-fill" style={{ width: `${String(row.percent)}%` }} />
|
||||
</div>
|
||||
<div className="codex-panel__limit-panel-reset">{row.resetLabel ?? ""}</div>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export interface RateLimitSummaryRow {
|
|||
resetLabel: string | null;
|
||||
title: string;
|
||||
percent: number;
|
||||
meterDivisions: number | null;
|
||||
level: "ok" | "warn" | "danger";
|
||||
}
|
||||
|
||||
|
|
@ -228,6 +229,7 @@ function rateLimitWindowSummary(
|
|||
resetLabel,
|
||||
title: `${name} ${label}: ${String(percent)}% used.${resetText}${reachedText}`,
|
||||
percent,
|
||||
meterDivisions: rateLimitMeterDivisions(window.windowDurationMins),
|
||||
level,
|
||||
};
|
||||
}
|
||||
|
|
@ -247,10 +249,17 @@ function spendControlLimitSummary(limit: SpendControlLimitSnapshot | null, name:
|
|||
resetLabel,
|
||||
title: `${name} monthly limit: ${value} used, ${String(remainingPercent)}% remaining.${resetText}`,
|
||||
percent,
|
||||
meterDivisions: null,
|
||||
level,
|
||||
};
|
||||
}
|
||||
|
||||
function rateLimitMeterDivisions(minutes: number | null): number | null {
|
||||
if (minutes === 300) return 5;
|
||||
if (minutes === 10_080) return 7;
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatRateLimitDuration(minutes: number): string {
|
||||
if (minutes === 10_080) return "1w";
|
||||
if (minutes % 60 === 0) return `${String(minutes / 60)}h`;
|
||||
|
|
|
|||
|
|
@ -116,19 +116,15 @@
|
|||
}
|
||||
|
||||
.codex-panel__limit-panel-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--codex-panel-panel-gap);
|
||||
display: grid;
|
||||
grid-template-columns: max-content max-content minmax(0, 1fr) max-content;
|
||||
gap: var(--codex-panel-panel-gap) var(--codex-panel-section-gap);
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-row {
|
||||
display: grid;
|
||||
grid-template-columns: 2ch 4ch minmax(64px, 1fr) max-content;
|
||||
gap: var(--codex-panel-control-gap) var(--codex-panel-item-gap);
|
||||
align-items: center;
|
||||
padding: var(--codex-panel-control-gap) var(--codex-panel-section-gap);
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-label,
|
||||
|
|
@ -142,22 +138,53 @@
|
|||
user-select: text;
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-label {
|
||||
padding: var(--codex-panel-control-gap) 0 var(--codex-panel-control-gap) var(--codex-panel-section-gap);
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-value {
|
||||
padding: var(--codex-panel-control-gap) 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-reset {
|
||||
padding: var(--codex-panel-control-gap) var(--codex-panel-section-gap) var(--codex-panel-control-gap) 0;
|
||||
color: var(--codex-panel-text-faint);
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-meter {
|
||||
align-self: center;
|
||||
position: relative;
|
||||
height: var(--codex-panel-meter-bar-height);
|
||||
margin: 0 var(--codex-panel-panel-gap);
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border-radius: var(--codex-panel-radius-pill);
|
||||
background: var(--codex-panel-border-color);
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-meter--divided::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
transparent 0,
|
||||
transparent calc((100% / var(--codex-panel-limit-panel-divisions)) - 1px),
|
||||
color-mix(in srgb, var(--codex-panel-surface) 58%, transparent) calc((100% / var(--codex-panel-limit-panel-divisions)) - 1px),
|
||||
color-mix(in srgb, var(--codex-panel-surface) 58%, transparent) calc(100% / var(--codex-panel-limit-panel-divisions))
|
||||
);
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-meter--5 {
|
||||
--codex-panel-limit-panel-divisions: 5;
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-meter--7 {
|
||||
--codex-panel-limit-panel-divisions: 7;
|
||||
}
|
||||
|
||||
.codex-panel__limit-panel-fill {
|
||||
display: block;
|
||||
height: 100%;
|
||||
|
|
|
|||
|
|
@ -17,8 +17,16 @@ describe("status line helpers", () => {
|
|||
title: "Codex usage limits",
|
||||
level: "warn",
|
||||
rows: [
|
||||
{ label: "5h", value: "72%", percent: 72, level: "warn", title: "5h usage", resetLabel: "reset in 2h" },
|
||||
{ label: "1w", value: "15%", percent: 15, level: "ok", title: "1w usage", resetLabel: null },
|
||||
{
|
||||
label: "5h",
|
||||
value: "72%",
|
||||
percent: 72,
|
||||
meterDivisions: 5,
|
||||
level: "warn",
|
||||
title: "5h usage",
|
||||
resetLabel: "reset in 2h",
|
||||
},
|
||||
{ label: "1w", value: "15%", percent: 15, meterDivisions: 7, level: "ok", title: "1w usage", resetLabel: null },
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ describe("toolbar renderer decisions", () => {
|
|||
resetLabel: "reset in 2h",
|
||||
title: "Codex 5h: 42% used.",
|
||||
percent: 42,
|
||||
meterDivisions: 5,
|
||||
level: "ok",
|
||||
},
|
||||
{
|
||||
|
|
@ -122,6 +123,7 @@ describe("toolbar renderer decisions", () => {
|
|||
resetLabel: "reset in 3d 4h",
|
||||
title: "Codex 1w: 21% used.",
|
||||
percent: 21,
|
||||
meterDivisions: 7,
|
||||
level: "ok",
|
||||
},
|
||||
],
|
||||
|
|
@ -138,6 +140,10 @@ describe("toolbar renderer decisions", () => {
|
|||
expect(parent.querySelector(".codex-panel__limit-panel")?.textContent).toContain("1w");
|
||||
expect(parent.querySelector(".codex-panel__limit-panel")?.textContent).toContain("21%");
|
||||
expect(parent.querySelector(".codex-panel__limit-panel")?.textContent).toContain("reset in 3d 4h");
|
||||
const meters = [...parent.querySelectorAll<HTMLElement>(".codex-panel__limit-panel-meter")];
|
||||
expect(meters.map((meter) => meter.classList.contains("codex-panel__limit-panel-meter--divided"))).toEqual([true, true]);
|
||||
expect(meters[0]?.classList.contains("codex-panel__limit-panel-meter--5")).toBe(true);
|
||||
expect(meters[1]?.classList.contains("codex-panel__limit-panel-meter--7")).toBe(true);
|
||||
});
|
||||
|
||||
it("renders connection diagnostics in the status menu", () => {
|
||||
|
|
|
|||
|
|
@ -573,7 +573,7 @@ describe("runtime settings", () => {
|
|||
1_799_991_600_000,
|
||||
),
|
||||
).toMatchObject({
|
||||
rows: [{ label: "5h", value: "72%", resetLabel: "reset in 2h 20m", percent: 72 }],
|
||||
rows: [{ label: "5h", value: "72%", resetLabel: "reset in 2h 20m", percent: 72, meterDivisions: 5 }],
|
||||
level: "warn",
|
||||
});
|
||||
|
||||
|
|
@ -614,8 +614,8 @@ describe("runtime settings", () => {
|
|||
),
|
||||
).toMatchObject({
|
||||
rows: [
|
||||
{ label: "5h", value: "15%" },
|
||||
{ label: "1w", value: "38%" },
|
||||
{ label: "5h", value: "15%", meterDivisions: 5 },
|
||||
{ label: "1w", value: "38%", meterDivisions: 7 },
|
||||
],
|
||||
level: "ok",
|
||||
});
|
||||
|
|
@ -657,7 +657,7 @@ describe("runtime settings", () => {
|
|||
1_799_991_600_000,
|
||||
),
|
||||
).toMatchObject({
|
||||
rows: [{ label: "monthly", value: "$72 / $100", resetLabel: "reset in 2h 20m", percent: 72 }],
|
||||
rows: [{ label: "monthly", value: "$72 / $100", resetLabel: "reset in 2h 20m", percent: 72, meterDivisions: null }],
|
||||
level: "warn",
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -130,12 +130,17 @@ describe("chat toolbar CSS", () => {
|
|||
expect(threadsRenameInput).not.toContain("appearance: none");
|
||||
});
|
||||
|
||||
it("keeps usage limit meter columns aligned across percent widths", () => {
|
||||
it("lets the usage limit meter absorb panel width changes", () => {
|
||||
const limitList = /\.codex-panel__limit-panel-list \{(?<body>[^}]+)\}/.exec(styles)?.groups?.["body"] ?? "";
|
||||
const limitRow = /\.codex-panel__limit-panel-row \{(?<body>[^}]+)\}/.exec(styles)?.groups?.["body"] ?? "";
|
||||
const limitMeter = /\.codex-panel__limit-panel-meter \{(?<body>[^}]+)\}/.exec(styles)?.groups?.["body"] ?? "";
|
||||
const limitValue = /\.codex-panel__limit-panel-value \{(?<body>[^}]+)\}/.exec(styles)?.groups?.["body"] ?? "";
|
||||
|
||||
expect(limitRow).toContain("grid-template-columns: 2ch 4ch minmax(64px, 1fr) max-content");
|
||||
expect(limitRow).toContain("gap: var(--codex-panel-control-gap) var(--codex-panel-item-gap)");
|
||||
expect(limitList).toContain("display: grid");
|
||||
expect(limitList).toContain("grid-template-columns: max-content max-content minmax(0, 1fr) max-content");
|
||||
expect(limitList).toContain("gap: var(--codex-panel-panel-gap) var(--codex-panel-section-gap)");
|
||||
expect(limitRow).toContain("display: contents");
|
||||
expect(limitMeter).toContain("margin: 0");
|
||||
expect(limitValue).toContain("font-variant-numeric: tabular-nums");
|
||||
expect(limitValue).not.toContain("text-align: right");
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue