[verified] refactor: unify dashboard rate windows
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
import { toast, Toaster } from 'sonner';
|
||||
import { api } from '@/lib/api';
|
||||
import { clearToken, getToken, setToken } from '@/lib/storage';
|
||||
import { flattenNumericMetrics, formatDate, titleizeMetric } from '@/lib/utils';
|
||||
import { summarizeUsageWindows, formatDate, formatDurationSeconds } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
@@ -568,10 +568,6 @@ function Dashboard() {
|
||||
onError: (error: Error) => toast.error(error.message),
|
||||
});
|
||||
|
||||
const metricCards = useMemo(() => {
|
||||
return flattenNumericMetrics(summaryQuery.data?.aggregatedUsage).slice(0, 6);
|
||||
}, [summaryQuery.data?.aggregatedUsage]);
|
||||
|
||||
if (summaryQuery.isLoading || userQuery.isLoading) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center text-slate-300">
|
||||
@@ -601,12 +597,27 @@ function Dashboard() {
|
||||
|
||||
const summary = summaryQuery.data!;
|
||||
const user = userQuery.data!;
|
||||
const firstMetric = metricCards[0]?.value ?? 0;
|
||||
const secondMetric = metricCards[1]?.value ?? 0;
|
||||
const progressValue =
|
||||
firstMetric + secondMetric > 0
|
||||
? (firstMetric / (firstMetric + secondMetric)) * 100
|
||||
: 0;
|
||||
const usageWindows = summarizeUsageWindows(
|
||||
summary.accounts.map((account) => account.usage),
|
||||
);
|
||||
const windowCards = [
|
||||
{
|
||||
title: 'Primary window',
|
||||
tone: 'text-sky-300',
|
||||
window: usageWindows.primary,
|
||||
},
|
||||
{
|
||||
title: 'Secondary window',
|
||||
tone: 'text-violet-300',
|
||||
window: usageWindows.secondary,
|
||||
},
|
||||
].filter(
|
||||
(item): item is {
|
||||
title: string;
|
||||
tone: string;
|
||||
window: NonNullable<typeof usageWindows.primary>;
|
||||
} => item.window !== null,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx-auto min-h-screen max-w-7xl px-4 py-5 sm:px-6 lg:px-8">
|
||||
@@ -647,25 +658,53 @@ function Dashboard() {
|
||||
<CardTitle>Unified capacity</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-end justify-between gap-4">
|
||||
<div>
|
||||
<div className="text-4xl font-semibold text-white">
|
||||
{firstMetric.toLocaleString()}
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-slate-400">
|
||||
{titleizeMetric(metricCards[0]?.label ?? 'Primary metric')}
|
||||
</div>
|
||||
{windowCards.length > 0 ? (
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{windowCards.map((item) => {
|
||||
const usedPercent = item.window.usedPercent;
|
||||
const progressValue =
|
||||
usedPercent !== null && usedPercent !== undefined
|
||||
? Math.max(0, Math.min(100, usedPercent))
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.title}
|
||||
className="rounded-2xl border border-white/10 bg-white/4 p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div className="text-sm text-slate-400">{item.title}</div>
|
||||
<div className={`mt-2 text-3xl font-semibold ${item.tone}`}>
|
||||
{usedPercent !== null && usedPercent !== undefined
|
||||
? `${usedPercent.toFixed(0)}%`
|
||||
: '—'}
|
||||
</div>
|
||||
<div className="mt-1 text-xs uppercase tracking-[0.2em] text-slate-500">
|
||||
{item.window.accountCount
|
||||
? `Window data from ${item.window.accountCount} account${item.window.accountCount === 1 ? '' : 's'}`
|
||||
: 'Used'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right text-sm text-slate-400">
|
||||
{item.window.limitWindowSeconds !== null ? (
|
||||
<div>{formatDurationSeconds(item.window.limitWindowSeconds)}</div>
|
||||
) : null}
|
||||
<div className={item.window.limitWindowSeconds !== null ? 'mt-1' : ''}>
|
||||
Resets {formatDate(item.window.resetAt)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Progress value={progressValue} className="mt-4" />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-2xl font-semibold text-slate-100">
|
||||
{secondMetric.toLocaleString()}
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-slate-500">
|
||||
{titleizeMetric(metricCards[1]?.label ?? 'Secondary metric')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-2xl border border-dashed border-white/10 bg-white/3 p-6 text-sm text-slate-400">
|
||||
No rate-limit window data yet. Connect an OpenAI account and refresh to load Codex usage windows.
|
||||
</div>
|
||||
</div>
|
||||
<Progress value={progressValue} />
|
||||
)}
|
||||
<div className="flex flex-wrap gap-3 text-sm text-slate-400">
|
||||
<span>Accounts: {summary.totals.totalAccounts}</span>
|
||||
<span>Healthy: {summary.totals.activeAccounts}</span>
|
||||
@@ -705,35 +744,6 @@ function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="mt-6 min-w-0">
|
||||
<CardHeader>
|
||||
<CardTitle>Usage metrics</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="min-w-0">
|
||||
{metricCards.length === 0 ? (
|
||||
<div className="rounded-2xl border border-dashed border-white/10 bg-white/3 p-6 text-sm text-slate-400">
|
||||
No usage data yet. Connect an OpenAI account and complete the sign-in flow to start refreshing.
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{metricCards.map((metric) => (
|
||||
<div
|
||||
key={metric.label}
|
||||
className="min-w-0 rounded-2xl border border-white/10 bg-white/4 p-4"
|
||||
>
|
||||
<div className="text-sm text-slate-400 break-words">
|
||||
{titleizeMetric(metric.label)}
|
||||
</div>
|
||||
<div className="mt-3 text-2xl font-semibold text-white">
|
||||
{metric.value.toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Connected OpenAI accounts</CardTitle>
|
||||
|
||||
@@ -13,6 +13,181 @@ export function formatDate(value: string | null) {
|
||||
}).format(new Date(value));
|
||||
}
|
||||
|
||||
type UsageWindowSummary = {
|
||||
usedPercent: number | null;
|
||||
limitWindowSeconds: number | null;
|
||||
resetAt: string | null;
|
||||
};
|
||||
|
||||
type AggregatedUsageWindowSummary = UsageWindowSummary & {
|
||||
accountCount: number;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function toFiniteNumber(value: unknown) {
|
||||
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function getPath(value: unknown, path: string[]) {
|
||||
let current: unknown = value;
|
||||
for (const segment of path) {
|
||||
if (!isRecord(current) || !(segment in current)) {
|
||||
return null;
|
||||
}
|
||||
current = current[segment];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function normalizeResetAt(value: unknown) {
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return new Date(value * 1000).toISOString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractWindow(value: unknown, paths: string[][]): UsageWindowSummary | null {
|
||||
for (const path of paths) {
|
||||
const windowValue = getPath(value, path);
|
||||
|
||||
if (!isRecord(windowValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const summary = {
|
||||
usedPercent: toFiniteNumber(windowValue.used_percent),
|
||||
limitWindowSeconds:
|
||||
toFiniteNumber(windowValue.limit_window_seconds) ??
|
||||
(toFiniteNumber(windowValue.window_minutes) !== null
|
||||
? toFiniteNumber(windowValue.window_minutes)! * 60
|
||||
: null),
|
||||
resetAt: normalizeResetAt(windowValue.reset_at ?? windowValue.resets_at),
|
||||
};
|
||||
|
||||
if (
|
||||
summary.usedPercent === null &&
|
||||
summary.limitWindowSeconds === null &&
|
||||
summary.resetAt === null
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function extractUsageWindows(value: unknown) {
|
||||
return {
|
||||
primary: extractWindow(value, [
|
||||
['rate_limit', 'primary_window'],
|
||||
['rate_limits', 'primary'],
|
||||
['primary_window'],
|
||||
]),
|
||||
secondary: extractWindow(value, [
|
||||
['rate_limit', 'secondary_window'],
|
||||
['rate_limits', 'secondary'],
|
||||
['secondary_window'],
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
function average(values: number[]) {
|
||||
if (values.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
||||
}
|
||||
|
||||
function earliestDate(values: Array<string | null>) {
|
||||
const validDates = values
|
||||
.filter((value): value is string => Boolean(value))
|
||||
.map((value) => ({ value, timestamp: Date.parse(value) }))
|
||||
.filter((value) => Number.isFinite(value.timestamp))
|
||||
.sort((left, right) => left.timestamp - right.timestamp);
|
||||
|
||||
return validDates[0]?.value ?? null;
|
||||
}
|
||||
|
||||
function summarizeWindowCollection(
|
||||
windows: Array<UsageWindowSummary | null>,
|
||||
): AggregatedUsageWindowSummary | null {
|
||||
const presentWindows = windows.filter(
|
||||
(window): window is UsageWindowSummary => window !== null,
|
||||
);
|
||||
|
||||
if (presentWindows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const usedPercentValues = presentWindows
|
||||
.map((window) => window.usedPercent)
|
||||
.filter((value): value is number => value !== null);
|
||||
|
||||
const limitWindowValues = presentWindows
|
||||
.map((window) => window.limitWindowSeconds)
|
||||
.filter((value): value is number => value !== null);
|
||||
|
||||
const uniqueLimitWindowValues = [...new Set(limitWindowValues)];
|
||||
|
||||
return {
|
||||
accountCount: presentWindows.length,
|
||||
usedPercent: average(usedPercentValues),
|
||||
limitWindowSeconds:
|
||||
limitWindowValues.length === presentWindows.length &&
|
||||
uniqueLimitWindowValues.length === 1
|
||||
? uniqueLimitWindowValues[0]
|
||||
: null,
|
||||
resetAt: earliestDate(presentWindows.map((window) => window.resetAt)),
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeUsageWindows(values: Array<unknown>) {
|
||||
const extractedWindows = values.map((value) => extractUsageWindows(value));
|
||||
|
||||
return {
|
||||
primary: summarizeWindowCollection(
|
||||
extractedWindows.map((windows) => windows.primary),
|
||||
),
|
||||
secondary: summarizeWindowCollection(
|
||||
extractedWindows.map((windows) => windows.secondary),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function formatDurationSeconds(value: number | null) {
|
||||
if (!value || value <= 0) {
|
||||
return 'Unknown window';
|
||||
}
|
||||
|
||||
if (value % 86_400 === 0) {
|
||||
const days = value / 86_400;
|
||||
return `${days}d window`;
|
||||
}
|
||||
|
||||
if (value % 3_600 === 0) {
|
||||
const hours = value / 3_600;
|
||||
return `${hours}h window`;
|
||||
}
|
||||
|
||||
if (value % 60 === 0) {
|
||||
const minutes = value / 60;
|
||||
return `${minutes}m window`;
|
||||
}
|
||||
|
||||
return `${value}s window`;
|
||||
}
|
||||
|
||||
export function flattenNumericMetrics(
|
||||
value: unknown,
|
||||
path: string[] = [],
|
||||
|
||||
Reference in New Issue
Block a user