diff --git a/src/renderer/src/components/stats/ClaudeUsagePane.tsx b/src/renderer/src/components/stats/ClaudeUsagePane.tsx index d7fb5d00b7c..0beae4aada1 100644 --- a/src/renderer/src/components/stats/ClaudeUsagePane.tsx +++ b/src/renderer/src/components/stats/ClaudeUsagePane.tsx @@ -25,8 +25,10 @@ import { import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip' import { ClaudeUsageDailyChart } from './ClaudeUsageDailyChart' import { ClaudeUsageLoadingState } from './ClaudeUsageLoadingState' +import { ClaudeUsageRecentSessionsTable } from './ClaudeUsageRecentSessionsTable' import { ShareUsageButton } from './ShareUsageButton' import { StatCard } from './StatCard' +import { formatCost, formatTokens, formatUpdatedAt } from './usage-formatters' import { translate } from '@/i18n/i18n' const RANGE_OPTIONS: ClaudeUsageRange[] = ['7d', '30d', '90d', 'all'] @@ -47,43 +49,6 @@ const RANGE_LABELS: Record = { all: 'All time' } -function formatTokens(value: number): string { - if (value >= 1_000_000) { - return `${(value / 1_000_000).toFixed(1)}M` - } - if (value >= 1_000) { - return `${(value / 1_000).toFixed(1)}k` - } - return value.toLocaleString() -} - -function formatCost(value: number | null): string { - if (value === null) { - return 'n/a' - } - return value < 0.01 ? `$${value.toFixed(4)}` : `$${value.toFixed(2)}` -} - -function formatUpdatedAt(timestamp: number | null): string { - if (!timestamp) { - return 'Not scanned yet' - } - return `Updated ${new Date(timestamp).toLocaleString()}` -} - -function formatSessionTime(timestamp: string): string { - const parsed = new Date(timestamp) - if (Number.isNaN(parsed.getTime())) { - return timestamp - } - return parsed.toLocaleString(undefined, { - month: 'short', - day: 'numeric', - hour: 'numeric', - minute: '2-digit' - }) -} - export function ClaudeUsagePane(): React.JSX.Element { const scanState = useAppStore((state) => state.claudeUsageScanState) const summary = useAppStore((state) => state.claudeUsageSummary) @@ -412,72 +377,7 @@ export function ClaudeUsagePane(): React.JSX.Element { -
-
-

- {translate('auto.components.stats.ClaudeUsagePane.7e76c84153', 'Recent sessions')} -

-

- {translate('auto.components.stats.ClaudeUsagePane.abfc4a4943', 'Cache reuse rate:')}{' '} - {summary?.cacheReuseRate !== null && summary?.cacheReuseRate !== undefined - ? `${Math.round(summary.cacheReuseRate * 100)}%` - : translate('auto.components.stats.ClaudeUsagePane.7765a4c3e1', 'n/a')} -

-
-
- - - - - - - - - - - - - - {recentSessions.map((row) => ( - - - - - - - - - - ))} - -
- {translate('auto.components.stats.ClaudeUsagePane.01476891c7', 'Last active')} - - {translate('auto.components.stats.ClaudeUsagePane.c17bed0416', 'Project')} - - {translate('auto.components.stats.ClaudeUsagePane.1afc25eb06', 'Model')} - - {translate('auto.components.stats.ClaudeUsagePane.0f03975d59', 'Turns')} - - {translate('auto.components.stats.ClaudeUsagePane.faf3444859', 'Input')} - - {translate('auto.components.stats.ClaudeUsagePane.a8b7487ff7', 'Output')} - - {translate('auto.components.stats.ClaudeUsagePane.21ea00bfa8', 'Cache')} -
- {formatSessionTime(row.lastActiveAt)} - {row.projectLabel} - {row.model ?? - translate('auto.components.stats.ClaudeUsagePane.cfe2282ffa', 'Unknown')} - {row.turns} - {formatTokens(row.inputTokens)} - - {formatTokens(row.outputTokens)} - - {formatTokens(row.cacheReadTokens + row.cacheWriteTokens)} -
-
-
+ )} diff --git a/src/renderer/src/components/stats/CodexUsagePane.tsx b/src/renderer/src/components/stats/CodexUsagePane.tsx index 58f46830ef3..8df30ccba81 100644 --- a/src/renderer/src/components/stats/CodexUsagePane.tsx +++ b/src/renderer/src/components/stats/CodexUsagePane.tsx @@ -24,8 +24,10 @@ import { import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip' import { ClaudeUsageLoadingState } from './ClaudeUsageLoadingState' import { CodexUsageDailyChart } from './CodexUsageDailyChart' +import { CodexUsageRecentSessionsTable } from './CodexUsageRecentSessionsTable' import { ShareUsageButton } from './ShareUsageButton' import { StatCard } from './StatCard' +import { formatCost, formatTokens, formatUpdatedAt } from './usage-formatters' import { translate } from '@/i18n/i18n' const RANGE_OPTIONS: CodexUsageRange[] = ['7d', '30d', '90d', 'all'] @@ -46,43 +48,6 @@ const RANGE_LABELS: Record = { all: 'All time' } -function formatTokens(value: number): string { - if (value >= 1_000_000) { - return `${(value / 1_000_000).toFixed(1)}M` - } - if (value >= 1_000) { - return `${(value / 1_000).toFixed(1)}k` - } - return value.toLocaleString() -} - -function formatCost(value: number | null): string { - if (value === null) { - return 'n/a' - } - return value < 0.01 ? `$${value.toFixed(4)}` : `$${value.toFixed(2)}` -} - -function formatUpdatedAt(timestamp: number | null): string { - if (!timestamp) { - return 'Not scanned yet' - } - return `Updated ${new Date(timestamp).toLocaleString()}` -} - -function formatSessionTime(timestamp: string): string { - const parsed = new Date(timestamp) - if (Number.isNaN(parsed.getTime())) { - return timestamp - } - return parsed.toLocaleString(undefined, { - month: 'short', - day: 'numeric', - hour: 'numeric', - minute: '2-digit' - }) -} - export function CodexUsagePane(): React.JSX.Element { const scanState = useAppStore((state) => state.codexUsageScanState) const summary = useAppStore((state) => state.codexUsageSummary) @@ -396,73 +361,7 @@ export function CodexUsagePane(): React.JSX.Element { -
-
-

- {translate('auto.components.stats.CodexUsagePane.0cb0983c07', 'Recent sessions')} -

-

- {translate( - 'auto.components.stats.CodexUsagePane.0bd8655475', - 'Most recent local Codex sessions in this scope.' - )} -

-
-
- - - - - - - - - - - - - - {recentSessions.map((row) => ( - - - - - - - - - - ))} - -
- {translate('auto.components.stats.CodexUsagePane.0c36b100be', 'Last active')} - - {translate('auto.components.stats.CodexUsagePane.1a65900aea', 'Project')} - - {translate('auto.components.stats.CodexUsagePane.c2478bcc3c', 'Model')} - - {translate('auto.components.stats.CodexUsagePane.bd0822ca47', 'Events')} - - {translate('auto.components.stats.CodexUsagePane.3acc582214', 'Input')} - - {translate('auto.components.stats.CodexUsagePane.bbd20344b8', 'Output')} - - {translate('auto.components.stats.CodexUsagePane.e0b988599d', 'Total')} -
- {formatSessionTime(row.lastActiveAt)} - {row.projectLabel} - {row.model ?? - translate('auto.components.stats.CodexUsagePane.bf6cf2d4dd', 'Unknown')} - {row.hasInferredPricing ? ' *' : ''} - {row.events} - {formatTokens(row.inputTokens)} - - {formatTokens(row.outputTokens)} - - {formatTokens(row.totalTokens)} -
-
-
+ )} diff --git a/src/renderer/src/components/stats/OpenCodeUsagePane.tsx b/src/renderer/src/components/stats/OpenCodeUsagePane.tsx index b21dfb5dbf3..721cc3e3fae 100644 --- a/src/renderer/src/components/stats/OpenCodeUsagePane.tsx +++ b/src/renderer/src/components/stats/OpenCodeUsagePane.tsx @@ -27,7 +27,9 @@ import { import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip' import { ClaudeUsageLoadingState } from './ClaudeUsageLoadingState' import { CodexUsageDailyChart } from './CodexUsageDailyChart' +import { OpenCodeUsageRecentSessionsTable } from './OpenCodeUsageRecentSessionsTable' import { StatCard } from './StatCard' +import { formatCost, formatTokens, formatUpdatedAt } from './usage-formatters' import { translate } from '@/i18n/i18n' const RANGE_OPTIONS: OpenCodeUsageRange[] = ['7d', '30d', '90d', 'all'] @@ -51,43 +53,6 @@ const RANGE_LABELS: Record = { all: 'All time' } -function formatTokens(value: number): string { - if (value >= 1_000_000) { - return `${(value / 1_000_000).toFixed(1)}M` - } - if (value >= 1_000) { - return `${(value / 1_000).toFixed(1)}k` - } - return value.toLocaleString() -} - -function formatCost(value: number | null): string { - if (value === null) { - return 'n/a' - } - return value < 0.01 ? `$${value.toFixed(4)}` : `$${value.toFixed(2)}` -} - -function formatUpdatedAt(timestamp: number | null): string { - if (!timestamp) { - return 'Not scanned yet' - } - return `Updated ${new Date(timestamp).toLocaleString()}` -} - -function formatSessionTime(timestamp: string): string { - const parsed = new Date(timestamp) - if (Number.isNaN(parsed.getTime())) { - return timestamp - } - return parsed.toLocaleString(undefined, { - month: 'short', - day: 'numeric', - hour: 'numeric', - minute: '2-digit' - }) -} - export function OpenCodeUsagePane(): React.JSX.Element { const scanState = useAppStore((state) => state.openCodeUsageScanState) const summary = useAppStore((state) => state.openCodeUsageSummary) @@ -422,78 +387,7 @@ export function OpenCodeUsagePane(): React.JSX.Element { -
-
-

- {translate('auto.components.stats.OpenCodeUsagePane.4799177b1c', 'Recent sessions')} -

-

- {translate( - 'auto.components.stats.OpenCodeUsagePane.81817a641a', - 'Most recent local OpenCode sessions in this scope.' - )} -

-
-
- - - - - - - - - - - - - - {recentSessions.map((row) => ( - - - - - - - - - - ))} - -
- {translate( - 'auto.components.stats.OpenCodeUsagePane.d97bdf6e27', - 'Last active' - )} - - {translate('auto.components.stats.OpenCodeUsagePane.a4738de041', 'Project')} - - {translate('auto.components.stats.OpenCodeUsagePane.08c78441b7', 'Model')} - - {translate('auto.components.stats.OpenCodeUsagePane.d416f5cf92', 'Events')} - - {translate('auto.components.stats.OpenCodeUsagePane.0f2f266c9d', 'Input')} - - {translate('auto.components.stats.OpenCodeUsagePane.dfc4513657', 'Output')} - - {translate('auto.components.stats.OpenCodeUsagePane.349f7c3f5c', 'Total')} -
- {formatSessionTime(row.lastActiveAt)} - {row.projectLabel} - {row.model ?? - translate( - 'auto.components.stats.OpenCodeUsagePane.362231082f', - 'Unknown' - )} - {row.events} - {formatTokens(row.inputTokens)} - - {formatTokens(row.outputTokens)} - - {formatTokens(row.totalTokens)} -
-
-
+ )}