'use client' import * as React from 'react' import { useQuery } from '@tanstack/react-query' import { BarChart2, ChevronRight, Loader2 } from 'lucide-react' import { useT } from '@open-mercato/shared/lib/i18n/context' import { Button } from '@open-mercato/ui/primitives/button' import { Input } from '@open-mercato/ui/primitives/input' import { Label } from '@open-mercato/ui/primitives/label' import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '@open-mercato/ui/primitives/dialog' import { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall' type DailyRow = { id: string tenantId: string organizationId: string | null day: string agentId: string modelId: string providerId: string inputTokens: string outputTokens: string cachedInputTokens: string reasoningTokens: string stepCount: string turnCount: string sessionCount: string createdAt: string updatedAt: string } type DailyResponse = { rows: DailyRow[] total: number } type SessionSummary = { sessionId: string agentId: string moduleId: string userId: string startedAt: string lastEventAt: string stepCount: number turnCount: number inputTokens: number outputTokens: number cachedInputTokens: number reasoningTokens: number } type SessionsResponse = { sessions: SessionSummary[] total: number limit: number offset: number } type StepEvent = { id: string tenantId: string organizationId: string | null userId: string agentId: string moduleId: string sessionId: string turnId: string stepIndex: number providerId: string modelId: string inputTokens: number outputTokens: number cachedInputTokens: number | null reasoningTokens: number | null finishReason: string | null loopAbortReason: string | null createdAt: string updatedAt: string } type SessionDetailResponse = { events: StepEvent[] total: number sessionId: string } function todayIso(): string { return new Date().toISOString().slice(0, 10) } function daysAgoIso(days: number): string { const date = new Date() date.setDate(date.getDate() - days) return date.toISOString().slice(0, 10) } async function fetchDailyRollup(from: string, to: string): Promise { const params = new URLSearchParams({ from, to }) const { result, status } = await apiCallOrThrow( `/api/ai_assistant/usage/daily?${params}`, { method: 'GET', credentials: 'include' }, { errorMessage: 'Failed to load daily token usage' }, ) if (!result) throw new Error(`Failed to load daily usage (${status})`) return result } async function fetchSessions(from: string, to: string, offset: number): Promise { const params = new URLSearchParams({ from, to, limit: '50', offset: String(offset) }) const { result, status } = await apiCallOrThrow( `/api/ai_assistant/usage/sessions?${params}`, { method: 'GET', credentials: 'include' }, { errorMessage: 'Failed to load session list' }, ) if (!result) throw new Error(`Failed to load sessions (${status})`) return result } async function fetchSessionDetail(sessionId: string): Promise { const { result, status } = await apiCallOrThrow( `/api/ai_assistant/usage/sessions/${encodeURIComponent(sessionId)}`, { method: 'GET', credentials: 'include' }, { errorMessage: 'Failed to load session detail' }, ) if (!result) throw new Error(`Failed to load session detail (${status})`) return result } function sumBigintRows(rows: DailyRow[], field: keyof DailyRow): number { return rows.reduce((acc, row) => acc + parseInt(String(row[field] ?? '0'), 10), 0) } function formatNumber(value: number): string { return value.toLocaleString() } function formatDate(iso: string): string { return new Date(iso).toLocaleString() } function shortId(id: string): string { return id.slice(0, 8) } export function AiUsageStatsPageClient() { const t = useT() const defaultFrom = daysAgoIso(30) const defaultTo = todayIso() const [from, setFrom] = React.useState(defaultFrom) const [to, setTo] = React.useState(defaultTo) const [appliedFrom, setAppliedFrom] = React.useState(defaultFrom) const [appliedTo, setAppliedTo] = React.useState(defaultTo) const [sessionsOffset, setSessionsOffset] = React.useState(0) const [selectedSessionId, setSelectedSessionId] = React.useState(null) const dailyQuery = useQuery({ queryKey: ['ai-usage-daily', appliedFrom, appliedTo], queryFn: () => fetchDailyRollup(appliedFrom, appliedTo), }) const sessionsQuery = useQuery({ queryKey: ['ai-usage-sessions', appliedFrom, appliedTo, sessionsOffset], queryFn: () => fetchSessions(appliedFrom, appliedTo, sessionsOffset), }) const sessionDetailQuery = useQuery({ queryKey: ['ai-usage-session-detail', selectedSessionId], queryFn: () => fetchSessionDetail(selectedSessionId!), enabled: selectedSessionId !== null, }) function applyFilter() { setSessionsOffset(0) setAppliedFrom(from) setAppliedTo(to) } const dailyRows = dailyQuery.data?.rows ?? [] const totalInputTokens = sumBigintRows(dailyRows, 'inputTokens') const totalOutputTokens = sumBigintRows(dailyRows, 'outputTokens') const totalSteps = sumBigintRows(dailyRows, 'stepCount') const totalSessions = sumBigintRows(dailyRows, 'sessionCount') return (

{t('ai_assistant.usage.title', 'Token Usage Statistics')}

{/* Date range filter */}
setFrom(e.target.value)} className="w-40" />
setTo(e.target.value)} className="w-40" />
{/* Summary tiles */} {dailyQuery.isLoading && (
{t('ai_assistant.usage.loading', 'Loading usage data...')}
)} {dailyQuery.isError && (

{t('ai_assistant.usage.error', 'Failed to load usage data.')}

)} {dailyQuery.isSuccess && (
{[ { label: t('ai_assistant.usage.inputTokens', 'Input tokens'), value: formatNumber(totalInputTokens) }, { label: t('ai_assistant.usage.outputTokens', 'Output tokens'), value: formatNumber(totalOutputTokens) }, { label: t('ai_assistant.usage.steps', 'Steps'), value: formatNumber(totalSteps) }, { label: t('ai_assistant.usage.sessions', 'Sessions'), value: formatNumber(totalSessions) }, ].map((tile) => (

{tile.label}

{tile.value}

))}
)} {/* Daily breakdown table */} {dailyQuery.isSuccess && dailyRows.length > 0 && (

{t('ai_assistant.usage.dailyBreakdown', 'Daily breakdown')}

{dailyRows.map((row) => ( ))}
{t('ai_assistant.usage.col.day', 'Day')} {t('ai_assistant.usage.col.agent', 'Agent')} {t('ai_assistant.usage.col.inputTokens', 'Input')} {t('ai_assistant.usage.col.outputTokens', 'Output')} {t('ai_assistant.usage.col.sessions', 'Sessions')}
{row.day} {row.agentId} {formatNumber(parseInt(row.inputTokens, 10))} {formatNumber(parseInt(row.outputTokens, 10))} {row.sessionCount}
)} {/* Sessions list */}

{t('ai_assistant.usage.sessionsList', 'Sessions')}

{sessionsQuery.isLoading && (
{t('ai_assistant.usage.loadingSessions', 'Loading sessions...')}
)} {sessionsQuery.isError && (

{t('ai_assistant.usage.errorSessions', 'Failed to load sessions.')}

)} {sessionsQuery.isSuccess && (sessionsQuery.data?.sessions ?? []).length === 0 && (

{t('ai_assistant.usage.noSessions', 'No sessions found for the selected period.')}

)} {sessionsQuery.isSuccess && (sessionsQuery.data?.sessions ?? []).length > 0 && ( <>
{(sessionsQuery.data?.sessions ?? []).map((session) => ( setSelectedSessionId(session.sessionId)} > ))}
{t('ai_assistant.usage.col.session', 'Session')} {t('ai_assistant.usage.col.agent', 'Agent')} {t('ai_assistant.usage.col.startedAt', 'Started')} {t('ai_assistant.usage.col.inputTokens', 'Input')} {t('ai_assistant.usage.col.outputTokens', 'Output')} {t('ai_assistant.usage.col.steps', 'Steps')}
{shortId(session.sessionId)}… {session.agentId} {formatDate(session.startedAt)} {formatNumber(session.inputTokens)} {formatNumber(session.outputTokens)} {session.stepCount}
{sessionsOffset + 1}–{sessionsOffset + (sessionsQuery.data?.sessions.length ?? 0)} {sessionsQuery.data?.total !== undefined ? ` / ${sessionsQuery.data.total}` : ''}
)}
{/* Session drill-down dialog */} { if (!open) setSelectedSessionId(null) }} > {t('ai_assistant.usage.sessionDetail', 'Session detail')} {selectedSessionId && ( {shortId(selectedSessionId)}… )}
{sessionDetailQuery.isLoading && (
{t('ai_assistant.usage.loadingDetail', 'Loading session events...')}
)} {sessionDetailQuery.isError && (

{t('ai_assistant.usage.errorDetail', 'Failed to load session events.')}

)} {sessionDetailQuery.isSuccess && (
{(sessionDetailQuery.data?.events ?? []).map((event) => ( ))}
{t('ai_assistant.usage.col.step', 'Step')} {t('ai_assistant.usage.col.model', 'Model')} {t('ai_assistant.usage.col.inputTokens', 'Input')} {t('ai_assistant.usage.col.outputTokens', 'Output')} {t('ai_assistant.usage.col.finishReason', 'Finish')}
{event.stepIndex} {event.modelId} {formatNumber(event.inputTokens)} {formatNumber(event.outputTokens)} {event.finishReason ?? '—'}
)}
) }