fix: improve code formatting and reduce auto-refresh interval
This commit is contained in:
@@ -54,7 +54,7 @@ export default function UsageStats() {
|
|||||||
.map(([key, data]) => ({
|
.map(([key, data]) => ({
|
||||||
...data,
|
...data,
|
||||||
key,
|
key,
|
||||||
totalTokens: (data.promptTokens || 0) + (data.completionTokens || 0)
|
totalTokens: (data.promptTokens || 0) + (data.completionTokens || 0),
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
let valA = a[sortBy];
|
let valA = a[sortBy];
|
||||||
@@ -70,8 +70,14 @@ export default function UsageStats() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortedModels = useMemo(() => sortData(stats?.byModel), [stats?.byModel, sortBy, sortOrder]);
|
const sortedModels = useMemo(
|
||||||
const sortedAccounts = useMemo(() => sortData(stats?.byAccount), [stats?.byAccount, sortBy, sortOrder]);
|
() => sortData(stats?.byModel),
|
||||||
|
[stats?.byModel, sortBy, sortOrder]
|
||||||
|
);
|
||||||
|
const sortedAccounts = useMemo(
|
||||||
|
() => sortData(stats?.byAccount),
|
||||||
|
[stats?.byAccount, sortBy, sortOrder]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchStats();
|
fetchStats();
|
||||||
@@ -82,7 +88,7 @@ export default function UsageStats() {
|
|||||||
if (autoRefresh) {
|
if (autoRefresh) {
|
||||||
interval = setInterval(() => {
|
interval = setInterval(() => {
|
||||||
fetchStats(false); // fetch without loading skeleton
|
fetchStats(false); // fetch without loading skeleton
|
||||||
}, 5000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [autoRefresh]);
|
}, [autoRefresh]);
|
||||||
@@ -104,7 +110,10 @@ export default function UsageStats() {
|
|||||||
|
|
||||||
if (loading) return <CardSkeleton />;
|
if (loading) return <CardSkeleton />;
|
||||||
|
|
||||||
if (!stats) return <div className="text-text-muted">Failed to load usage statistics.</div>;
|
if (!stats)
|
||||||
|
return (
|
||||||
|
<div className="text-text-muted">Failed to load usage statistics.</div>
|
||||||
|
);
|
||||||
|
|
||||||
// Format number with commas
|
// Format number with commas
|
||||||
const fmt = (n) => new Intl.NumberFormat().format(n || 0);
|
const fmt = (n) => new Intl.NumberFormat().format(n || 0);
|
||||||
@@ -116,13 +125,17 @@ export default function UsageStats() {
|
|||||||
<h2 className="text-xl font-semibold">Usage Overview</h2>
|
<h2 className="text-xl font-semibold">Usage Overview</h2>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<label className="text-sm font-medium text-text-muted flex items-center gap-2 cursor-pointer">
|
<label className="text-sm font-medium text-text-muted flex items-center gap-2 cursor-pointer">
|
||||||
<span>Auto Refresh (5s)</span>
|
<span>Auto Refresh (1s)</span>
|
||||||
<div
|
<div
|
||||||
onClick={() => setAutoRefresh(!autoRefresh)}
|
onClick={() => setAutoRefresh(!autoRefresh)}
|
||||||
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none ${autoRefresh ? 'bg-primary' : 'bg-bg-subtle border border-border'}`}
|
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none ${
|
||||||
|
autoRefresh ? "bg-primary" : "bg-bg-subtle border border-border"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${autoRefresh ? 'translate-x-5' : 'translate-x-1'}`}
|
className={`inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${
|
||||||
|
autoRefresh ? "translate-x-5" : "translate-x-1"
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
@@ -134,11 +147,15 @@ export default function UsageStats() {
|
|||||||
<Card className="p-4 flex flex-col gap-1">
|
<Card className="p-4 flex flex-col gap-1">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="text-text-muted text-sm uppercase font-semibold">Total Requests</span>
|
<span className="text-text-muted text-sm uppercase font-semibold">
|
||||||
<span className="text-2xl font-bold">{fmt(stats.totalRequests)}</span>
|
Total Requests
|
||||||
|
</span>
|
||||||
|
<span className="text-2xl font-bold">
|
||||||
|
{fmt(stats.totalRequests)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<MiniBarGraph
|
<MiniBarGraph
|
||||||
data={(stats.last10Minutes || []).map(m => m.requests)}
|
data={(stats.last10Minutes || []).map((m) => m.requests)}
|
||||||
colorClass="bg-text-muted/30"
|
colorClass="bg-text-muted/30"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,11 +163,15 @@ export default function UsageStats() {
|
|||||||
<Card className="p-4 flex flex-col gap-1">
|
<Card className="p-4 flex flex-col gap-1">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="text-text-muted text-sm uppercase font-semibold">Total Input Tokens</span>
|
<span className="text-text-muted text-sm uppercase font-semibold">
|
||||||
<span className="text-2xl font-bold text-primary">{fmt(stats.totalPromptTokens)}</span>
|
Total Input Tokens
|
||||||
|
</span>
|
||||||
|
<span className="text-2xl font-bold text-primary">
|
||||||
|
{fmt(stats.totalPromptTokens)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<MiniBarGraph
|
<MiniBarGraph
|
||||||
data={(stats.last10Minutes || []).map(m => m.promptTokens)}
|
data={(stats.last10Minutes || []).map((m) => m.promptTokens)}
|
||||||
colorClass="bg-primary/50"
|
colorClass="bg-primary/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,11 +179,15 @@ export default function UsageStats() {
|
|||||||
<Card className="p-4 flex flex-col gap-1">
|
<Card className="p-4 flex flex-col gap-1">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="text-text-muted text-sm uppercase font-semibold">Total Output Tokens</span>
|
<span className="text-text-muted text-sm uppercase font-semibold">
|
||||||
<span className="text-2xl font-bold text-success">{fmt(stats.totalCompletionTokens)}</span>
|
Total Output Tokens
|
||||||
|
</span>
|
||||||
|
<span className="text-2xl font-bold text-success">
|
||||||
|
{fmt(stats.totalCompletionTokens)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<MiniBarGraph
|
<MiniBarGraph
|
||||||
data={(stats.last10Minutes || []).map(m => m.completionTokens)}
|
data={(stats.last10Minutes || []).map((m) => m.completionTokens)}
|
||||||
colorClass="bg-success/50"
|
colorClass="bg-success/50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -178,23 +203,71 @@ export default function UsageStats() {
|
|||||||
<table className="w-full text-sm text-left">
|
<table className="w-full text-sm text-left">
|
||||||
<thead className="bg-bg-subtle/30 text-text-muted uppercase text-xs">
|
<thead className="bg-bg-subtle/30 text-text-muted uppercase text-xs">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("rawModel")}>
|
<th
|
||||||
Model <SortIcon field="rawModel" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("rawModel")}
|
||||||
|
>
|
||||||
|
Model{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="rawModel"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("provider")}>
|
<th
|
||||||
Provider <SortIcon field="provider" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("provider")}
|
||||||
|
>
|
||||||
|
Provider{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="provider"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("requests")}>
|
<th
|
||||||
Requests <SortIcon field="requests" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("requests")}
|
||||||
|
>
|
||||||
|
Requests{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="requests"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("promptTokens")}>
|
<th
|
||||||
Input Tokens <SortIcon field="promptTokens" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("promptTokens")}
|
||||||
|
>
|
||||||
|
Input Tokens{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="promptTokens"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("completionTokens")}>
|
<th
|
||||||
Output Tokens <SortIcon field="completionTokens" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("completionTokens")}
|
||||||
|
>
|
||||||
|
Output Tokens{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="completionTokens"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("totalTokens")}>
|
<th
|
||||||
Total Tokens <SortIcon field="totalTokens" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("totalTokens")}
|
||||||
|
>
|
||||||
|
Total Tokens{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="totalTokens"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -203,17 +276,28 @@ export default function UsageStats() {
|
|||||||
<tr key={data.key} className="hover:bg-bg-subtle/20">
|
<tr key={data.key} className="hover:bg-bg-subtle/20">
|
||||||
<td className="px-6 py-3 font-medium">{data.rawModel}</td>
|
<td className="px-6 py-3 font-medium">{data.rawModel}</td>
|
||||||
<td className="px-6 py-3">
|
<td className="px-6 py-3">
|
||||||
<Badge variant="neutral" size="sm">{data.provider}</Badge>
|
<Badge variant="neutral" size="sm">
|
||||||
|
{data.provider}
|
||||||
|
</Badge>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-3 text-right">{fmt(data.requests)}</td>
|
<td className="px-6 py-3 text-right">{fmt(data.requests)}</td>
|
||||||
<td className="px-6 py-3 text-right text-text-muted">{fmt(data.promptTokens)}</td>
|
<td className="px-6 py-3 text-right text-text-muted">
|
||||||
<td className="px-6 py-3 text-right text-text-muted">{fmt(data.completionTokens)}</td>
|
{fmt(data.promptTokens)}
|
||||||
<td className="px-6 py-3 text-right font-medium">{fmt(data.totalTokens)}</td>
|
</td>
|
||||||
|
<td className="px-6 py-3 text-right text-text-muted">
|
||||||
|
{fmt(data.completionTokens)}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-3 text-right font-medium">
|
||||||
|
{fmt(data.totalTokens)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
{sortedModels.length === 0 && (
|
{sortedModels.length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="px-6 py-8 text-center text-text-muted">
|
<td
|
||||||
|
colSpan={6}
|
||||||
|
className="px-6 py-8 text-center text-text-muted"
|
||||||
|
>
|
||||||
No usage recorded yet. Make some requests to see data here.
|
No usage recorded yet. Make some requests to see data here.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -232,26 +316,82 @@ export default function UsageStats() {
|
|||||||
<table className="w-full text-sm text-left">
|
<table className="w-full text-sm text-left">
|
||||||
<thead className="bg-bg-subtle/30 text-text-muted uppercase text-xs">
|
<thead className="bg-bg-subtle/30 text-text-muted uppercase text-xs">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("rawModel")}>
|
<th
|
||||||
Model <SortIcon field="rawModel" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("rawModel")}
|
||||||
|
>
|
||||||
|
Model{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="rawModel"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("provider")}>
|
<th
|
||||||
Provider <SortIcon field="provider" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("provider")}
|
||||||
|
>
|
||||||
|
Provider{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="provider"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("accountName")}>
|
<th
|
||||||
Account <SortIcon field="accountName" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("accountName")}
|
||||||
|
>
|
||||||
|
Account{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="accountName"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("requests")}>
|
<th
|
||||||
Requests <SortIcon field="requests" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("requests")}
|
||||||
|
>
|
||||||
|
Requests{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="requests"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("promptTokens")}>
|
<th
|
||||||
Input Tokens <SortIcon field="promptTokens" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("promptTokens")}
|
||||||
|
>
|
||||||
|
Input Tokens{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="promptTokens"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("completionTokens")}>
|
<th
|
||||||
Output Tokens <SortIcon field="completionTokens" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("completionTokens")}
|
||||||
|
>
|
||||||
|
Output Tokens{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="completionTokens"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50" onClick={() => toggleSort("totalTokens")}>
|
<th
|
||||||
Total Tokens <SortIcon field="totalTokens" currentSort={sortBy} currentOrder={sortOrder} />
|
className="px-6 py-3 text-right cursor-pointer hover:bg-bg-subtle/50"
|
||||||
|
onClick={() => toggleSort("totalTokens")}
|
||||||
|
>
|
||||||
|
Total Tokens{" "}
|
||||||
|
<SortIcon
|
||||||
|
field="totalTokens"
|
||||||
|
currentSort={sortBy}
|
||||||
|
currentOrder={sortOrder}
|
||||||
|
/>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -260,21 +400,36 @@ export default function UsageStats() {
|
|||||||
<tr key={data.key} className="hover:bg-bg-subtle/20">
|
<tr key={data.key} className="hover:bg-bg-subtle/20">
|
||||||
<td className="px-6 py-3 font-medium">{data.rawModel}</td>
|
<td className="px-6 py-3 font-medium">{data.rawModel}</td>
|
||||||
<td className="px-6 py-3">
|
<td className="px-6 py-3">
|
||||||
<Badge variant="neutral" size="sm">{data.provider}</Badge>
|
<Badge variant="neutral" size="sm">
|
||||||
|
{data.provider}
|
||||||
|
</Badge>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-3">
|
<td className="px-6 py-3">
|
||||||
<span className="font-medium">{data.accountName || `Account ${data.connectionId?.slice(0, 8)}...`}</span>
|
<span className="font-medium">
|
||||||
|
{data.accountName ||
|
||||||
|
`Account ${data.connectionId?.slice(0, 8)}...`}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-3 text-right">{fmt(data.requests)}</td>
|
<td className="px-6 py-3 text-right">{fmt(data.requests)}</td>
|
||||||
<td className="px-6 py-3 text-right text-text-muted">{fmt(data.promptTokens)}</td>
|
<td className="px-6 py-3 text-right text-text-muted">
|
||||||
<td className="px-6 py-3 text-right text-text-muted">{fmt(data.completionTokens)}</td>
|
{fmt(data.promptTokens)}
|
||||||
<td className="px-6 py-3 text-right font-medium">{fmt(data.totalTokens)}</td>
|
</td>
|
||||||
|
<td className="px-6 py-3 text-right text-text-muted">
|
||||||
|
{fmt(data.completionTokens)}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-3 text-right font-medium">
|
||||||
|
{fmt(data.totalTokens)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
{sortedAccounts.length === 0 && (
|
{sortedAccounts.length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7} className="px-6 py-8 text-center text-text-muted">
|
<td
|
||||||
No account-specific usage recorded yet. Make requests using OAuth accounts to see data here.
|
colSpan={7}
|
||||||
|
className="px-6 py-8 text-center text-text-muted"
|
||||||
|
>
|
||||||
|
No account-specific usage recorded yet. Make requests using
|
||||||
|
OAuth accounts to see data here.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user