Files
EJClaw/src/dashboard-usage-rows.ts
Eyejoker fc85fece0f refactor: split unified-dashboard into usage-rows and codex-collector modules
Extract dashboard-usage-rows.ts (UsageRow, formatResetRemaining,
mergeClaudeDashboardAccounts, buildClaudeUsageRows, extractCodexUsageRows)
and codex-usage-collector.ts (fetchCodexUsage, applyCodexUsageToAccount,
buildCodexUsageRowsFromState, refresh functions returning data instead of
mutating module state). unified-dashboard.ts drops from 926 to 563 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 12:32:41 +09:00

104 lines
3.3 KiB
TypeScript

import { getClaudeProfile, type ClaudeAccountUsage } from './claude-usage.js';
import type { StatusSnapshot } from './status-dashboard.js';
export type UsageRow = {
name: string;
h5pct: number;
h5reset: string;
d7pct: number;
d7reset: string;
};
export function formatResetRemaining(value: string | number): string {
if (value === '' || value == null) return '';
try {
const date =
typeof value === 'number' ? new Date(value * 1000) : new Date(value);
if (Number.isNaN(date.getTime())) return '';
const diffMs = date.getTime() - Date.now();
if (diffMs <= 0) return ' reset';
const hours = Math.floor(diffMs / 3_600_000);
const minutes = Math.floor((diffMs % 3_600_000) / 60_000);
if (hours >= 24) {
const days = Math.floor(hours / 24);
const remH = hours % 24;
return `${String(days).padStart(2)}d ${String(remH).padStart(2)}h`;
}
return `${String(hours).padStart(2)}h ${String(minutes).padStart(2)}m`;
} catch {
return String(value).padStart(6);
}
}
export function mergeClaudeDashboardAccounts(
liveAccounts: ClaudeAccountUsage[] | null | undefined,
cachedAccounts: ClaudeAccountUsage[],
): ClaudeAccountUsage[] {
if (!liveAccounts) return cachedAccounts;
const cachedByIndex = new Map(
cachedAccounts.map((account) => [account.index, account]),
);
return liveAccounts.map((account) => ({
...account,
usage: account.usage || cachedByIndex.get(account.index)?.usage || null,
}));
}
export function buildClaudeUsageRows(
claudeAccounts: ClaudeAccountUsage[],
): UsageRow[] {
const isMultiAccount = claudeAccounts.length > 1;
return claudeAccounts.map((account) => {
const usage = account.usage;
const h5 = usage?.five_hour;
const d7 = usage?.seven_day;
const profile = getClaudeProfile(account.index);
const planSuffix = profile ? ` ${profile.planType}` : '';
const label = isMultiAccount
? `Claude${account.index + 1}${account.isActive ? '*' : ''}${account.isRateLimited ? '!' : ''}${planSuffix}`
: `Claude${account.isActive ? '*' : ''}${account.isRateLimited ? '!' : ''}${planSuffix}`;
return {
name: label,
h5pct: h5
? h5.utilization > 1
? Math.round(h5.utilization)
: Math.round(h5.utilization * 100)
: -1,
h5reset: h5 ? formatResetRemaining(h5.resets_at) : '',
d7pct: d7
? d7.utilization > 1
? Math.round(d7.utilization)
: Math.round(d7.utilization * 100)
: -1,
d7reset: d7 ? formatResetRemaining(d7.resets_at) : '',
};
});
}
/**
* Extract Codex usage rows from a snapshot, applying staleness check.
* Returns real rows if usageRowsFetchedAt is within maxAgeMs, otherwise a
* single degraded row. Returns empty array if no usage data present.
*/
export function extractCodexUsageRows(
snapshot: StatusSnapshot | undefined,
maxAgeMs: number,
now: number = Date.now(),
): UsageRow[] {
if (!snapshot?.usageRows || snapshot.usageRows.length === 0) return [];
const fetchedAt = snapshot.usageRowsFetchedAt
? new Date(snapshot.usageRowsFetchedAt).getTime()
: 0;
const usageAge = now - fetchedAt;
if (usageAge <= maxAgeMs) {
return [...snapshot.usageRows];
}
// Usage data is stale — return degraded indicator
return [{ name: 'Codex', h5pct: -1, h5reset: '', d7pct: -1, d7reset: '' }];
}