diff --git a/src/tui/usage/app.ts b/src/tui/usage/app.ts index 99faa77..8d95c96 100644 --- a/src/tui/usage/app.ts +++ b/src/tui/usage/app.ts @@ -1,6 +1,6 @@ import { getVisibleTabs } from "./constants.ts"; import type { Tab } from "./types.ts"; -import { fetchBalance, fetchClients, fetchStatus, fetchUsage, fetchUsageSummary, hasAnyNpubs, isDaemonRunning, type BalanceInfo, type ClientInfo, type StatusInfo } from "./data.ts"; +import { fetchBalance, fetchClients, fetchStatus, fetchUsageSummary, hasAnyNpubs, isDaemonRunning, type BalanceInfo, type ClientInfo, type StatusInfo } from "./data.ts"; import { applyScrollToContent, exitSearchMode, @@ -89,7 +89,7 @@ export async function runUsageTui(): Promise { const height = getHeight(); if (forceFetch || shouldUpdate) { - stats = await fetchUsageSummary() ?? await fetchUsage(10000); + stats = await fetchUsageSummary(); balance = await fetchBalance(); status = await fetchStatus(); clients = await fetchClients(); diff --git a/src/tui/usage/data.ts b/src/tui/usage/data.ts index b7dde44..b3e6681 100644 --- a/src/tui/usage/data.ts +++ b/src/tui/usage/data.ts @@ -76,42 +76,6 @@ export async function fetchBalance(): Promise { } } -export async function fetchUsage(limit = 10000): Promise { - try { - const running = await isDaemonRunning(); - if (!running) return null; - - const result = await callDaemon(`/usage?limit=${limit}`); - if (result.error) return null; - - // The auth proxy filters usage to the authenticated npub's clients and - // returns client IDs without the owner suffix. - const entries = result.output as UsageTrackingEntry[] | undefined; - const visibleEntries = Array.isArray(entries) ? entries : []; - - // Calculate totals from visible entries - const totals = visibleEntries.reduce( - (acc, entry) => ({ - promptTokens: acc.promptTokens + entry.promptTokens, - completionTokens: acc.completionTokens + entry.completionTokens, - totalTokens: acc.totalTokens + entry.totalTokens, - satsCost: acc.satsCost + entry.satsCost, - }), - { promptTokens: 0, completionTokens: 0, totalTokens: 0, satsCost: 0 }, - ); - - return { - entries: visibleEntries, - totalEntries: visibleEntries.length, - totalSatsCost: totals.satsCost, - recentSatsCost: totals.satsCost, // For now, recent = total since we don't have time window - limit, - }; - } catch { - return null; - } -} - export async function fetchUsageSummary(): Promise { try { const running = await isDaemonRunning(); diff --git a/src/tui/usage/render.ts b/src/tui/usage/render.ts index 6026e69..c5a6432 100644 --- a/src/tui/usage/render.ts +++ b/src/tui/usage/render.ts @@ -1,17 +1,8 @@ import { CLIENT_COLORS, COLORS, MODEL_COLORS } from "./constants.ts"; import type { Tab } from "./types.ts"; import { - formatDate, formatNumber, formatTime, - getClientStats, - getDayStats, - getHourlyToday, - getModelStats, - getNpubStats, - getProviderStats, - getTodayStart, - getTotals, type ClientInfo, } from "./data.ts"; import { vimState } from "./state.ts"; @@ -120,10 +111,7 @@ export function renderBarChart( export function renderOverview(stats: UsageStats, balance: BalanceInfo | null, status: StatusInfo | null, width: number): string { - // Token totals must come from the all-time summary (when present) to stay on - // the same horizon as totalRequests/totalSatsCost below — stats.entries holds - // only the latest 50 in summary mode. Fall back to entries for the legacy path. - const totals = stats.summary?.totals ?? getTotals(stats.entries); + const totals = stats.summary.totals; const totalRequests = stats.totalEntries; // Use server's total count, not entries.length const totalVisibleCost = stats.totalSatsCost; // <-- Use server's total, not client-side sum of limited entries const avgCost = totalRequests > 0 ? totalVisibleCost / totalRequests : 0; @@ -242,7 +230,7 @@ export function renderOverview(stats: UsageStats, balance: BalanceInfo | null, s output = renderBox(statusLines, width, "System Status") + "\n" + output; } - const modelStats = stats.summary?.models ?? getModelStats(stats.entries); + const modelStats = stats.summary.models; if (modelStats.length > 0) { const maxCost = modelStats[0]!.satsCost; const totalCost = Math.max(totalVisibleCost, 1); @@ -261,7 +249,7 @@ export function renderOverview(stats: UsageStats, balance: BalanceInfo | null, s output += "\n" + renderBox(modelLines, width, "Top Models by Cost"); } - const clientStats = stats.summary?.clients ?? getClientStats(stats.entries); + const clientStats = stats.summary.clients; if (clientStats.length > 0) { const maxCost = clientStats[0]!.satsCost; const totalCost = Math.max(totalVisibleCost, 1); @@ -308,23 +296,6 @@ export function renderToday(stats: UsageStats, width: number): string { // days[0] is only today when there was activity today. recentDays = days.filter((d) => d.date !== todayDateStr).slice(0, 6); hourlyMap = new Map(hoursToday.map((h) => [h.hour, { requests: h.requests, satsCost: h.satsCost }])); - } else { - // Fallback: compute from raw entries - const todayStart = getTodayStart(); - todayStats = { date: todayDateStr, requests: 0, satsCost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 }; - for (const entry of stats.entries) { - if (entry.timestamp >= todayStart) { - todayStats.requests++; - todayStats.satsCost += entry.satsCost; - todayStats.promptTokens += entry.promptTokens; - todayStats.completionTokens += entry.completionTokens; - todayStats.totalTokens += entry.totalTokens; - } - } - recentDays = Array.from(getDayStats(stats.entries).values()).slice(1, 7); - hourlyMap = new Map( - Array.from(getHourlyToday(stats.entries).entries()).map(([h, s]) => [h, { requests: s.requests, satsCost: s.satsCost }]) - ); } const summaryLines = [ @@ -375,7 +346,7 @@ export function renderToday(stats: UsageStats, width: number): string { } export function renderModels(stats: UsageStats, width: number): string { - const modelStats = stats.summary?.models ?? getModelStats(stats.entries); + const modelStats = stats.summary.models; if (modelStats.length === 0) return renderBox(["No model data available"], width, "Models"); // Use totalSatsCost (all-time) for percentage calculations to match header @@ -402,7 +373,7 @@ export function renderModels(stats: UsageStats, width: number): string { } export function renderProviders(stats: UsageStats, width: number): string { - const providerStats = stats.summary?.providers ?? getProviderStats(stats.entries); + const providerStats = stats.summary.providers; if (providerStats.length === 0) return renderBox(["No provider data available"], width, "Providers"); const lines: string[] = []; @@ -418,8 +389,8 @@ export function renderProviders(stats: UsageStats, width: number): string { } export function renderTokens(stats: UsageStats, width: number): string { - const totals = stats.summary?.totals ?? getTotals(stats.entries); - const modelStats = stats.summary?.models ?? getModelStats(stats.entries); + const totals = stats.summary.totals; + const modelStats = stats.summary.models; const summaryLines = [ `${COLORS.bold}Total Prompt Tokens:${COLORS.reset} ${formatNumber(totals.promptTokens)}`, `${COLORS.bold}Total Completion Tokens:${COLORS.reset} ${formatNumber(totals.completionTokens)}`, @@ -439,25 +410,7 @@ export function renderTokens(stats: UsageStats, width: number): string { output += "\n" + renderBox(tokenLines, width, "Tokens by Model"); } - const sizeBuckets = stats.summary?.sizeBuckets ?? (() => { - const b = { - tiny: { min: 0, max: 1000, count: 0, cost: 0 }, - small: { min: 1000, max: 10000, count: 0, cost: 0 }, - medium: { min: 10000, max: 50000, count: 0, cost: 0 }, - large: { min: 50000, max: 100000, count: 0, cost: 0 }, - huge: { min: 100000, max: Infinity, count: 0, cost: 0 }, - }; - for (const entry of stats.entries) { - for (const bucket of Object.values(b)) { - if (entry.totalTokens >= bucket.min && entry.totalTokens < bucket.max) { - bucket.count++; - bucket.cost += entry.satsCost; - break; - } - } - } - return b; - })(); + const sizeBuckets = stats.summary.sizeBuckets; const sizeLines = Object.entries(sizeBuckets).map(([name, bucket]) => `${name.padEnd(6)}: ${formatReqs(bucket.count).padStart(5)} reqs, ${formatCost(bucket.cost)} sats`); output += "\n" + renderBox(sizeLines, width, "Request Size Distribution"); @@ -465,7 +418,7 @@ export function renderTokens(stats: UsageStats, width: number): string { } export function renderClients(stats: UsageStats, width: number): string { - const clientStats = stats.summary?.clients ?? getClientStats(stats.entries); + const clientStats = stats.summary.clients; if (clientStats.length === 0) return renderBox(["No client data available (API key auth not used)"], width, "Client Breakdown"); // Use totalSatsCost (all-time) for percentage calculations to match header @@ -522,32 +475,6 @@ export function renderClients(stats: UsageStats, width: number): string { } clientModelLines.push(""); } - } else { - // Fallback: compute from raw entries; exclude the "unknown" bucket to match - // the summary path. - const clientModelMap = new Map>(); - for (const entry of stats.entries) { - const client = entry.client || "unknown"; - const model = entry.modelId; - if (!clientModelMap.has(client)) clientModelMap.set(client, new Map()); - const modelMap = clientModelMap.get(client)!; - const existing = modelMap.get(model) || { requests: 0, satsCost: 0, tokens: 0 }; - modelMap.set(model, { - requests: existing.requests + 1, - satsCost: existing.satsCost + entry.satsCost, - tokens: existing.tokens + entry.totalTokens, - }); - } - for (const topClient of clientStats.filter((c) => c.client !== "unknown").slice(0, 3)) { - const modelMap = clientModelMap.get(topClient.client); - if (!modelMap) continue; - const models = Array.from(modelMap.entries()).sort((a, b) => b[1].satsCost - a[1].satsCost).slice(0, 5); - clientModelLines.push(`${COLORS.bold}${topClient.client}${COLORS.reset} (${formatReqs(topClient.requests)} reqs, ${formatCost(topClient.satsCost)} sats)`); - for (const [model, data] of models) { - clientModelLines.push(` ${(MODEL_COLORS[model] || MODEL_COLORS.default)}${model.padEnd(18)}${COLORS.reset} ${formatNumber(data.tokens).padEnd(8)} tokens ${formatCost(data.satsCost)} sats`); - } - clientModelLines.push(""); - } } if (clientModelLines.length > 0) { @@ -557,7 +484,7 @@ export function renderClients(stats: UsageStats, width: number): string { } export function renderNpubs(stats: UsageStats, clients: ClientInfo[], width: number): string { - const npubStats = stats.summary?.npubs ?? getNpubStats(stats.entries, clients); + const npubStats = stats.summary.npubs; if (npubStats.length === 0) return renderBox(["No npub data available"], width, "Npub Breakdown"); const totalCost = stats.totalSatsCost; @@ -614,36 +541,6 @@ export function renderNpubs(stats: UsageStats, clients: ClientInfo[], width: num } npubModelLines.push(""); } - } else { - // Fallback: compute from raw entries - const npubModelMap = new Map>(); - const clientNpubMap = new Map(); - for (const c of clients) { - if (c.ownerNpub) clientNpubMap.set(c.clientId, c.ownerNpub); - } - for (const entry of stats.entries) { - const npub = clientNpubMap.get(entry.client || ""); - if (!npub) continue; - const model = entry.modelId; - if (!npubModelMap.has(npub)) npubModelMap.set(npub, new Map()); - const modelMap = npubModelMap.get(npub)!; - const existing = modelMap.get(model) || { requests: 0, satsCost: 0, tokens: 0 }; - modelMap.set(model, { - requests: existing.requests + 1, - satsCost: existing.satsCost + entry.satsCost, - tokens: existing.tokens + entry.totalTokens, - }); - } - for (const topNpub of npubStats.slice(0, 5)) { - const modelMap = npubModelMap.get(topNpub.npub); - if (!modelMap) continue; - const models = Array.from(modelMap.entries()).sort((a, b) => b[1].satsCost - a[1].satsCost).slice(0, 5); - npubModelLines.push(`${COLORS.bold}${truncateNpub(topNpub.npub)}${COLORS.reset} (${formatReqs(topNpub.requests)} reqs, ${formatCost(topNpub.satsCost)} sats)`); - for (const [model, data] of models) { - npubModelLines.push(` ${(MODEL_COLORS[model] || MODEL_COLORS.default)}${model.padEnd(18)}${COLORS.reset} ${formatNumber(data.tokens).padEnd(8)} tokens ${formatCost(data.satsCost)} sats`); - } - npubModelLines.push(""); - } } if (npubModelLines.length > 0) { diff --git a/src/tui/usage/types.ts b/src/tui/usage/types.ts index d49476d..3db517f 100644 --- a/src/tui/usage/types.ts +++ b/src/tui/usage/types.ts @@ -10,7 +10,7 @@ export interface UsageStats { recentSatsCost: number; limit: number; /** Present when data was fetched via /usage/summary (server-aggregated path). */ - summary?: UsageSummary; + summary: UsageSummary; } export interface DayStats {