feat: add quota tracking
This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
import { Plugin } from "@opencode/plugin/tui";
|
||||
import { createResource, createSignal, For, Show } from "solid-js";
|
||||
import type { QuotaStatus, QuotaWindow } from "./quota.js";
|
||||
import { QuotaRpc } from "./quota-rpc.js";
|
||||
|
||||
function duration(seconds: number) {
|
||||
if (seconds % 86_400 === 0) return `${seconds / 86_400}d`;
|
||||
if (seconds % 3_600 === 0) return `${seconds / 3_600}h`;
|
||||
return `${Math.round(seconds / 60)}m`;
|
||||
}
|
||||
|
||||
function percent(value: number) {
|
||||
return value < 0.1 && value > 0 ? "<0.1" : value.toFixed(value < 10 ? 1 : 0);
|
||||
}
|
||||
|
||||
export function formatQuotaWindow(
|
||||
window: QuotaWindow & { remaining: number; sessionConsumed: number },
|
||||
) {
|
||||
const label = window.label
|
||||
? `${window.label} ${duration(window.seconds)}`
|
||||
: duration(window.seconds);
|
||||
return `${label}: ${percent(window.remaining)}% left · ~${percent(window.sessionConsumed)}pp this session`;
|
||||
}
|
||||
|
||||
export async function loadQuotaStatus(load: () => Promise<QuotaStatus | null>) {
|
||||
try {
|
||||
return await load();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Plugin.define({
|
||||
id: "stanp-opencode.quota-tui",
|
||||
setup(context) {
|
||||
const quota = context.client.rpc(QuotaRpc);
|
||||
const [revision, setRevision] = createSignal(0);
|
||||
const retry = setInterval(() => {
|
||||
setRevision((value) => value + 1);
|
||||
}, 60_000);
|
||||
const unsubscribe = quota.events.on("updated", () => {
|
||||
setRevision((value) => value + 1);
|
||||
});
|
||||
|
||||
const Slot = (props: { sessionID: string }) => {
|
||||
const [status] = createResource(
|
||||
() => `${props.sessionID}:${revision()}`,
|
||||
() => loadQuotaStatus(() => quota.status({ sessionID: props.sessionID })),
|
||||
);
|
||||
return (
|
||||
<Show when={status()}>
|
||||
<box flexDirection="column">
|
||||
<text fg={context.theme.text.default}>
|
||||
<b>ChatGPT quota ({status()!.plan})</b>
|
||||
</text>
|
||||
<For each={status()!.windows}>
|
||||
{(window) => (
|
||||
<text fg={context.theme.text.muted}>
|
||||
{formatQuotaWindow(window)}
|
||||
</text>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const remove = context.ui.slot({
|
||||
append: "sidebar.footer",
|
||||
render: ({ sessionID }) => <Slot sessionID={sessionID} />,
|
||||
});
|
||||
return () => {
|
||||
remove();
|
||||
unsubscribe();
|
||||
clearInterval(retry);
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user