"use client"; import { useMemo, useRef } from "react"; import { CalendarDays } from "lucide-react"; import type { AgentState } from "@/features/agents/state/store"; import { useApprovalMetrics } from "@/features/office/hooks/useApprovalMetrics"; import { useOfficeUsageAnalyticsViewModel } from "@/features/office/hooks/useOfficeUsageAnalyticsViewModel"; import { usePerformanceAnalytics } from "@/features/office/hooks/usePerformanceAnalytics"; import type { RunRecord } from "@/features/office/hooks/useRunLog"; import type { GatewayClient, GatewayStatus } from "@/lib/gateway/GatewayClient"; import { formatCurrency, formatNumber, } from "@/lib/office/usageAnalyticsPresentation"; import type { StudioSettingsCoordinator } from "@/lib/studio/coordinator"; const formatPercent = (value: number | null | undefined) => { if (value === null || value === undefined) return "n/a"; return `${Math.round(value * 100)}%`; }; const formatDuration = (valueMs: number | null | undefined) => { if (!valueMs) return "n/a"; const seconds = Math.round(valueMs / 1000); if (seconds < 60) return `${seconds}s`; const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; if (minutes < 60) { return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`; } const hours = Math.floor(minutes / 60); const remainingMinutes = minutes % 60; return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`; }; const formatBudgetInput = (value: number | null) => (value === null ? "" : String(value)); const parseBudgetInput = (value: string): number | null => { const trimmed = value.trim(); if (!trimmed) return null; const parsed = Number(trimmed); if (!Number.isFinite(parsed) || parsed < 0) return null; return parsed; }; const StatCard = ({ label, value, hint, }: { label: string; value: string; hint: string; }) => (
{label}
{value}
{hint}
); const openNativeDatePicker = (input: HTMLInputElement | null) => { if (!input) return; if (typeof input.showPicker === "function") { input.showPicker(); return; } input.focus(); }; const DatePickerField = ({ label, value, onChange, }: { label: string; value: string; onChange: (value: string) => void; }) => { const inputRef = useRef(null); return ( ); }; export function AnalyticsPanel({ client, status, approvalsEnabled = true, agents, runLog, gatewayUrl, settingsCoordinator, onSelectAgent, }: { client: GatewayClient; status: GatewayStatus; approvalsEnabled?: boolean; agents: AgentState[]; runLog: RunRecord[]; gatewayUrl: string; settingsCoordinator: StudioSettingsCoordinator; onSelectAgent: (agentId: string) => void; }) { const { startDate, setStartDate, endDate, setEndDate, budgets, settingsLoaded, usage, updateBudget, } = useOfficeUsageAnalyticsViewModel({ client, status, agents, gatewayUrl, settingsCoordinator, }); const approvalMetrics = useApprovalMetrics({ client, status, enabled: approvalsEnabled, agents, }); const performance = usePerformanceAnalytics({ agents, runLog, approvalByAgent: approvalMetrics.byAgent, }); const dailyChartMax = useMemo(() => { return usage.costDaily.reduce((max, entry) => Math.max(max, entry.totalCost), 0); }, [usage.costDaily]); const alertBannerClass = usage.budgetAlerts.some((alert) => alert.severity === "danger") ? "border-rose-500/30 bg-rose-500/10 text-rose-100" : "border-amber-500/30 bg-amber-500/10 text-amber-100"; return (
Analytics
Real usage, spend, and agent trust metrics for headquarters.
{usage.lastRefreshedAt ? `Last refresh ${new Date(usage.lastRefreshedAt).toLocaleTimeString()}` : "No analytics snapshot yet"}
{usage.error ? (
{usage.error}
) : null} {usage.budgetAlerts.length > 0 ? (
{usage.budgetAlerts.map((alert) => (
{alert.label}: {formatCurrency(alert.currentUsd)} / {formatCurrency(alert.limitUsd)}.
))}
) : settingsLoaded ? (
Budgets are within threshold.
) : null}
Budget Limits
Daily Cost
{usage.loading ? (
Loading usage data.
) : usage.costDaily.length === 0 ? (
No cost data in the selected range.
) : (
{usage.costDaily.map((entry) => { const heightPct = dailyChartMax > 0 ? (entry.totalCost / dailyChartMax) * 100 : 0; return (
{formatCurrency(entry.totalCost)}
{entry.date.slice(5)}
); })}
)}
Cost Breakdown
Input: {formatCurrency(usage.totals.inputCost)}.
Output: {formatCurrency(usage.totals.outputCost)}.
Cache read: {formatCurrency(usage.totals.cacheReadCost)}.
Cache write: {formatCurrency(usage.totals.cacheWriteCost)}.
Top Agents By Spend
{usage.aggregates.byAgent.slice(0, 6).map((entry) => ( ))} {usage.aggregates.byAgent.length === 0 ? (
No agent spend data yet.
) : null}
Model Breakdown
{usage.aggregates.byModel.slice(0, 6).map((entry) => (
{entry.provider ?? "unknown"} / {entry.model ?? "unknown"} {formatCurrency(entry.totals.totalCost)}
))} {usage.aggregates.byModel.length === 0 ? (
No model usage data yet.
) : null}
Performance
{performance.rows.map((row) => ( ))} {performance.rows.length === 0 ? (
No performance data is available yet.
) : null}
); }