// Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Project: chat.spboucher.ai "use client"; import { useCallback, useEffect, useState } from "react"; import { formatTokens, formatUsd } from "./types"; interface UsageTotals { requests: number; prompt_tokens: number; completion_tokens: number; reasoning_tokens: number; total_tokens: number; cost_usd: number; } interface UsageByModel { model_id: string; model_name: string; provider: string | null; requests: number; total_tokens: number; cost_usd: number; } const PERIODS = [ ["today", "Today"], ["7d", "7 days"], ["30d", "30 days"], ["all", "All time"], ] as const; export function SettingsView() { const [period, setPeriod] = useState("7d"); const [totals, setTotals] = useState(null); const [byModel, setByModel] = useState([]); const [syncing, setSyncing] = useState(false); const [syncMsg, setSyncMsg] = useState(null); const [theme, setTheme] = useState("auto"); useEffect(() => { setTheme(localStorage.getItem("spb-theme") ?? "auto"); }, []); const loadUsage = useCallback(async (p: string) => { const res = await fetch(`/api/usage?period=${p}`); if (!res.ok) return; const json = await res.json(); setTotals(json.totals); setByModel(json.byModel); }, []); useEffect(() => { loadUsage(period); }, [period, loadUsage]); async function syncCatalog() { setSyncing(true); setSyncMsg(null); try { const res = await fetch("/api/models/sync", { method: "POST" }); const json = await res.json(); setSyncMsg(res.ok ? `Synced ${json.total} models (${json.added} new).` : json.error ?? "Sync failed."); } catch { setSyncMsg("Sync failed. Check connectivity."); } setSyncing(false); } function applyTheme(next: string) { setTheme(next); if (next === "auto") { localStorage.removeItem("spb-theme"); delete document.documentElement.dataset.theme; } else { localStorage.setItem("spb-theme", next); document.documentElement.dataset.theme = next; } } return (
← Back to chat

Settings

Usage

{PERIODS.map(([k, label]) => ( ))}
{totals?.requests ?? "—"}
Requests
{formatTokens(totals?.prompt_tokens)}
Input tokens
{formatTokens(totals?.completion_tokens)}
Output tokens
{formatUsd(totals?.cost_usd)}
Total cost
{byModel.length > 0 && ( <>

By model

{byModel.map((m) => ( ))}
Model Requests Tokens Cost
{m.model_name} {m.requests} {formatTokens(m.total_tokens)} {formatUsd(m.cost_usd)}
)}

Appearance

Theme
Dark is the native face of the instrument.
{["auto", "dark", "light"].map((t) => ( ))}

Model catalog

Refresh catalog
{syncMsg ?? "Pull the latest models from OpenRouter."}
); }