mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 02:02:42 +00:00
* feat(app): streamline sidebar usage and stacked sheets * fix(app): restore compact sidebar navigation clicks The close button container emitted box-none through Unistyles as invalid CSS and intercepted New workspace clicks. Let React Native apply click-through behavior at the view boundary. Keep the import journey on its existing New workspace screen and assert only the visible transcript. * fix(app): cap menu sheets at eighty percent height Keep long sidebar usage reports recognizably sheet-sized while allowing the content to scroll. Short menu sheets retain dynamic sizing.
133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import type { ZodType } from "zod";
|
|
import type { JsonValue } from "@getpaseo/protocol/agent-types";
|
|
|
|
export interface UsageWindow {
|
|
id: string;
|
|
label: string;
|
|
/**
|
|
* A few characters naming the window where space is tight, e.g. "5h" or "wk". An empty string
|
|
* shows the percent alone; leaving it out shows `label`.
|
|
*/
|
|
shortLabel?: string;
|
|
/** Shown in the usage summary until the user pins windows of their own. */
|
|
summary?: boolean;
|
|
usedPct?: number | null;
|
|
remainingPct?: number | null;
|
|
resetsAt?: string | null;
|
|
runsOutAt?: string | null;
|
|
shortfallPct?: number | null;
|
|
tone?: "default" | "ok" | "warning" | "danger";
|
|
}
|
|
|
|
export interface UsageBalance {
|
|
id: string;
|
|
label: string;
|
|
used?: number | null;
|
|
remaining?: number | null;
|
|
limit?: number | null;
|
|
unit: "usd" | "credits" | "requests" | "tokens";
|
|
resetsAt?: string | null;
|
|
tone?: UsageWindow["tone"];
|
|
}
|
|
|
|
export interface UsageDetail {
|
|
id: string;
|
|
label: string;
|
|
value: string;
|
|
tone?: UsageWindow["tone"];
|
|
}
|
|
|
|
export interface UsageReport {
|
|
status: "available" | "unavailable" | "error";
|
|
planLabel?: string;
|
|
windows: UsageWindow[];
|
|
balances?: UsageBalance[];
|
|
details?: UsageDetail[];
|
|
error?: string;
|
|
}
|
|
|
|
export interface UsageSourceRegistration {
|
|
id: string;
|
|
label: string;
|
|
icon?: string;
|
|
input: ZodType;
|
|
/** Stable account identity, resolved without fetching usage. */
|
|
identify(input: unknown): Promise<{ key: string; label?: string } | null>;
|
|
fetch(input: unknown): Promise<UsageReport>;
|
|
discover(): Promise<JsonValue[]>;
|
|
}
|
|
|
|
export function windowFromUsedPct(input: {
|
|
id: string;
|
|
label: string;
|
|
shortLabel?: string;
|
|
summary?: boolean;
|
|
utilizationPct: number | null | undefined;
|
|
resetsAt?: string | null;
|
|
tone?: UsageWindow["tone"];
|
|
}): UsageWindow {
|
|
const usedPct = typeof input.utilizationPct === "number" ? input.utilizationPct : null;
|
|
const window: UsageWindow = {
|
|
id: input.id,
|
|
label: input.label,
|
|
usedPct,
|
|
remainingPct: usedPct === null ? null : Math.max(0, 100 - usedPct),
|
|
resetsAt: input.resetsAt ?? null,
|
|
};
|
|
if (input.shortLabel !== undefined) window.shortLabel = input.shortLabel;
|
|
if (input.summary) window.summary = true;
|
|
if (input.tone) window.tone = input.tone;
|
|
return window;
|
|
}
|
|
|
|
/**
|
|
* The tone scale for anything measured against a known limit, windows and balances alike.
|
|
*
|
|
* Thresholds match `deriveTone` in the app's provider-usage/tone.ts, which is what the
|
|
* client falls back to when a window arrives without a tone. Healthy is "ok" rather than
|
|
* "default" because that is what every provider setting a tone has always sent, and it is
|
|
* what the bars render today below their thresholds.
|
|
*/
|
|
export function toneFromUsedPct(usedPct: number | null | undefined): UsageWindow["tone"] {
|
|
if (typeof usedPct !== "number") return "default";
|
|
if (usedPct > 90) return "danger";
|
|
if (usedPct >= 70) return "warning";
|
|
return "ok";
|
|
}
|
|
|
|
/**
|
|
* Tone for a balance with no known limit, where a percentage cannot be computed and the
|
|
* only signal is whether anything is left. Prefer `toneFromUsedPct` when a limit exists:
|
|
* this one stays "ok" until the balance is completely spent.
|
|
*/
|
|
export function balanceToneFromRemaining(
|
|
remaining: number | null | undefined,
|
|
): UsageBalance["tone"] {
|
|
if (typeof remaining !== "number") return "default";
|
|
if (remaining <= 0) return "danger";
|
|
return "ok";
|
|
}
|
|
|
|
/** Percentage of a limit consumed, or null when either side is unknown. */
|
|
export function usedPctOf(
|
|
used: number | null | undefined,
|
|
limit: number | null | undefined,
|
|
): number | null {
|
|
if (typeof used !== "number" || typeof limit !== "number" || limit <= 0) return null;
|
|
return (used / limit) * 100;
|
|
}
|
|
|
|
export function hashAccountKey(value: string): string {
|
|
return createHash("sha256").update(value).digest("hex");
|
|
}
|
|
|
|
export function unavailableUsage(): UsageReport {
|
|
return {
|
|
status: "unavailable",
|
|
windows: [],
|
|
balances: [],
|
|
details: [],
|
|
};
|
|
}
|