reachy_mini_minder / frontend /src /components /ObservabilityPanel.tsx
Boopster's picture
feat: Display newest conversation messages first in the observability panel, emit tool execution results via WebSocket, and adjust onboarding flow for medication saving.
6d89fcf
"use client";
import React, { useState, useRef, useEffect } from "react";
import { createPortal } from "react-dom";
import {
Activity,
X,
Server,
MessageSquare,
Wrench,
Layers,
Clock,
Zap,
CheckCircle2,
XCircle,
Loader2,
DollarSign,
ChevronDown,
ChevronRight,
} from "lucide-react";
import { useObservability } from "@/hooks/useObservability";
import { Message } from "@/hooks/useConversation";
interface ObservabilityPanelProps {
isOpen?: boolean;
onToggle?: () => void;
messages: Message[];
isSessionActive: boolean;
isConnected: boolean;
}
export function ObservabilityPanel({
isOpen,
onToggle,
messages,
isSessionActive,
isConnected,
}: ObservabilityPanelProps) {
const data = useObservability({ messages, isSessionActive });
if (!isOpen) {
return (
<button
onClick={onToggle}
className="flex flex-col items-center gap-1 p-2 text-gray-400 hover:text-white transition-all"
title="Observability"
>
<Activity className="w-5 h-5" />
<span className="text-[10px]">Monitor</span>
</button>
);
}
return (
<>
<button onClick={onToggle} className="btn btn-secondary p-2">
<Activity className="w-5 h-5" />
</button>
{createPortal(
<div
className="modal-overlay"
onClick={(e) => {
if (e.target === e.currentTarget) onToggle?.();
}}
>
<div className="modal-fullscreen p-0">
{/* Header — merged with status strip */}
<div
style={{
padding: "16px 24px",
borderBottom: "1px solid rgba(255, 255, 255, 0.08)",
background: "var(--color-surface-subtle)",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
flexShrink: 0,
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<div
style={{
width: 36,
height: 36,
borderRadius: 10,
background: "var(--color-accent-pink)",
display: "flex",
alignItems: "center",
justifyContent: "center",
boxShadow: "0 2px 8px rgba(255, 193, 204, 0.3)",
}}
>
<Activity className="w-5 h-5" style={{ color: "#1a1a1a" }} />
</div>
<div>
<h3
style={{
fontSize: 20,
fontWeight: 700,
color: "var(--color-text-primary)",
margin: 0,
letterSpacing: "-0.01em",
}}
>
System Monitor
</h3>
<p
style={{
fontSize: 11,
color: "var(--color-text-muted)",
textTransform: "uppercase",
letterSpacing: "0.1em",
fontWeight: 500,
margin: 0,
}}
>
Real-Time Observability
</p>
</div>
</div>
{/* Inline status indicators */}
<div style={{ display: "flex", alignItems: "center", gap: 20 }}>
{/* Connection */}
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
<div
style={{
width: 8,
height: 8,
borderRadius: "50%",
background: isConnected ? "var(--color-success)" : "var(--color-error)",
boxShadow: isConnected
? "0 0 8px var(--color-success)"
: "0 0 8px var(--color-error)",
}}
/>
<span
style={{
fontSize: 14,
fontWeight: 500,
color: isConnected ? "var(--color-success)" : "var(--color-error)",
}}
>
{isConnected ? "Connected" : "Disconnected"}
</span>
</div>
<div style={{ width: 1, height: 16, background: "rgba(255,255,255,0.1)" }} />
{/* Session uptime */}
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
<Clock className="w-3.5 h-3.5" style={{ color: "var(--color-text-muted)" }} />
<span style={{ fontSize: 14, color: "var(--color-text-secondary)" }}>
{isSessionActive && data.sessionUptimeSeconds != null
? formatDuration(data.sessionUptimeSeconds)
: "No session"}
</span>
</div>
<div style={{ width: 1, height: 16, background: "rgba(255,255,255,0.1)" }} />
{/* Events/sec */}
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
<Zap className="w-3.5 h-3.5" style={{ color: "var(--color-accent-pink)" }} />
<span
style={{
fontSize: 14,
fontFamily: "var(--font-mono)",
color: "var(--color-accent-pink)",
}}
>
{data.eventsPerSecond.toFixed(1)} evt/s
</span>
</div>
<div style={{ width: 1, height: 16, background: "rgba(255,255,255,0.1)" }} />
<button
onClick={onToggle}
className="btn btn-ghost p-1.5 hover:bg-surface-elevated rounded-lg transition-colors"
>
<X className="w-5 h-5 text-secondary" />
</button>
</div>
</div>
{/* Content — 3-column bento grid */}
<div
style={{
flex: 1,
overflow: "hidden",
display: "grid",
gridTemplateColumns: "1fr 1fr 1.5fr",
gridTemplateRows: "1fr 1fr",
gap: 0,
minHeight: 0,
}}
>
{/* ===== LEFT COLUMN: Messages + GenUI ===== */}
<div
style={{
padding: 20,
borderRight: "1px solid rgba(255, 255, 255, 0.06)",
display: "flex",
flexDirection: "column",
gap: 24,
overflow: "auto",
minHeight: 0,
}}
>
{/* Messages */}
<div>
<SectionLabel icon={<MessageSquare className="w-3.5 h-3.5" />} label="Messages" />
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10, marginTop: 10 }}>
<StatCard label="User" value={data.userMessageCount} color="var(--color-cta)" />
<StatCard label="Assistant" value={data.assistantMessageCount} color="var(--color-accent-cyan)" />
<StatCard label="Tool" value={data.toolMessageCount} color="var(--color-accent-pink)" />
</div>
</div>
{/* GenUI Components */}
{data.genuiComponentCount > 0 && (
<div>
<SectionLabel icon={<Layers className="w-3.5 h-3.5" />} label="GenUI Components" />
<div style={{ marginTop: 10, display: "flex", flexWrap: "wrap", gap: 8 }}>
<span className="pill pill-lavender text-xs font-bold">
{data.genuiComponentCount} rendered
</span>
{data.genuiComponentNames.map((name) => (
<span key={name} className="pill pill-cyan text-xs">
{name}
</span>
))}
</div>
</div>
)}
</div>
{/* ===== MIDDLE COLUMN: Token Usage + Server ===== */}
<div
style={{
padding: 20,
borderRight: "1px solid rgba(255, 255, 255, 0.06)",
display: "flex",
flexDirection: "column",
gap: 24,
overflow: "auto",
minHeight: 0,
}}
>
{/* Token Usage */}
<div>
<SectionLabel icon={<DollarSign className="w-3.5 h-3.5" />} label="Token Usage" />
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10, marginTop: 10 }}>
<StatCard
label="Input"
value={
data.costMetrics
? formatTokenCount(data.costMetrics.input_text + data.costMetrics.input_audio)
: "—"
}
color="var(--color-cta)"
/>
<StatCard
label="Output"
value={
data.costMetrics
? formatTokenCount(data.costMetrics.output_text + data.costMetrics.output_audio)
: "—"
}
color="var(--color-accent-cyan)"
/>
<StatCard
label="Est. Cost"
value={
data.costMetrics
? `$${data.costMetrics.total_cost.toFixed(4)}`
: "—"
}
color="var(--color-success)"
/>
</div>
{data.costMetrics && data.costMetrics.response_count > 0 && (
<div style={{ marginTop: 10, display: "flex", flexWrap: "wrap", gap: 6 }}>
<span className="pill pill-cyan text-[9px]">
{data.costMetrics.response_count} responses
</span>
{data.costMetrics.input_audio > 0 && (
<span className="pill pill-lavender text-[9px]">
🎙 {formatTokenCount(data.costMetrics.input_audio)} audio in
</span>
)}
{data.costMetrics.output_audio > 0 && (
<span className="pill pill-pink text-[9px]">
🔊 {formatTokenCount(data.costMetrics.output_audio)} audio out
</span>
)}
{data.costMetrics.cache_hit_rate > 0 && (
<span
className="pill text-[9px] font-bold"
style={{
background: "rgba(34, 197, 94, 0.15)",
border: "1px solid rgba(34, 197, 94, 0.3)",
color: "var(--color-success)",
}}
>
💾 {Math.round(data.costMetrics.cache_hit_rate * 100)}% cached
</span>
)}
</div>
)}
</div>
{/* Server Stats */}
<div>
<SectionLabel icon={<Server className="w-3.5 h-3.5" />} label="Server" />
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10, marginTop: 10 }}>
<StatCard
label="Clients"
value={data.serverMetrics?.connected_clients ?? "—"}
color="var(--color-accent-cyan)"
/>
<StatCard
label="Uptime"
value={
data.serverMetrics?.uptime_seconds != null
? formatDuration(data.serverMetrics.uptime_seconds)
: "—"
}
color="var(--color-success)"
/>
<StatCard
label="Events"
value={data.serverMetrics?.total_events_broadcast ?? "—"}
color="var(--color-cta)"
/>
</div>
</div>
</div>
{/* ===== RIGHT COLUMN: Tool Calls ===== */}
<div
style={{
padding: 20,
display: "flex",
flexDirection: "column",
overflow: "hidden",
minHeight: 0,
}}
>
<SectionLabel icon={<Wrench className="w-3.5 h-3.5" />} label="Tool Calls" />
<div
style={{
marginTop: 10,
flex: 1,
overflowY: "auto",
display: "flex",
flexDirection: "column",
gap: 6,
}}
>
{data.toolCalls.length === 0 ? (
<p
style={{
fontSize: 12,
color: "var(--color-text-muted)",
fontStyle: "italic",
textAlign: "center",
padding: "24px 0",
}}
>
No tool calls yet this session
</p>
) : (
data.toolCalls.map((tool) => (
<ToolCallRow key={tool.id} tool={tool} />
))
)}
</div>
</div>
{/* ===== BOTTOM ROW: Conversation Log (full-width) ===== */}
<div
style={{
gridColumn: "1 / -1",
borderTop: "1px solid rgba(255, 255, 255, 0.06)",
overflow: "hidden",
minHeight: 0,
}}
>
<ConversationLog messages={messages} />
</div>
</div>
{/* Footer */}
<div
style={{
padding: "12px 24px",
background: "var(--color-surface-subtle)",
borderTop: "1px solid rgba(255, 255, 255, 0.08)",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
flexShrink: 0,
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<div
style={{
padding: "2px 8px",
borderRadius: 4,
background: "rgba(255, 193, 204, 0.1)",
border: "1px solid rgba(255, 193, 204, 0.2)",
color: "var(--color-accent-pink)",
fontSize: 9,
fontWeight: 900,
textTransform: "uppercase",
letterSpacing: "0.15em",
}}
>
Dev
</div>
<p style={{ fontSize: 11, color: "var(--color-text-muted)", fontWeight: 500, margin: 0 }}>
WS events: <strong>{data.totalWsEvents}</strong> this session
</p>
</div>
<Zap className="w-4 h-4" style={{ color: "rgba(255, 193, 204, 0.3)" }} />
</div>
</div>
</div>,
document.body
)}
</>
);
}
// ---- Sub-components ----
function SectionLabel({ icon, label }: { icon: React.ReactNode; label: string }) {
return (
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
fontSize: 12,
fontWeight: 900,
textTransform: "uppercase",
letterSpacing: "0.15em",
color: "var(--color-text-muted)",
}}
>
{icon}
{label}
</div>
);
}
function StatCard({
label,
value,
color,
}: {
label: string;
value: string | number;
color: string;
}) {
return (
<div
style={{
padding: "14px 12px",
background: "rgba(36, 36, 36, 0.6)",
border: "1px solid rgba(255, 255, 255, 0.08)",
borderRadius: 12,
textAlign: "center",
}}
>
<div
style={{
fontSize: 28,
fontWeight: 700,
color,
fontFamily: "var(--font-mono)",
lineHeight: 1.2,
}}
>
{value}
</div>
<div
style={{
fontSize: 12,
fontWeight: 600,
color: "var(--color-text-muted)",
textTransform: "uppercase",
letterSpacing: "0.1em",
marginTop: 4,
}}
>
{label}
</div>
</div>
);
}
function ToolCallRow({ tool }: { tool: { id: string; name: string; status: string; duration: number | null } }) {
const statusIcon =
tool.status === "completed" ? (
<CheckCircle2 className="w-3.5 h-3.5" style={{ color: "var(--color-success)" }} />
) : tool.status === "error" ? (
<XCircle className="w-3.5 h-3.5" style={{ color: "var(--color-error)" }} />
) : (
<Loader2 className="w-3.5 h-3.5 animate-spin" style={{ color: "var(--color-accent-cyan)" }} />
);
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "8px 12px",
background: "rgba(36, 36, 36, 0.5)",
border: "1px solid rgba(255, 255, 255, 0.06)",
borderRadius: 8,
}}
>
<div className="flex items-center gap-2">
{statusIcon}
<span style={{ fontSize: 14, fontWeight: 500, color: "var(--color-text-primary)" }}>
{tool.name}
</span>
</div>
<div className="flex items-center gap-2">
{tool.duration != null && (
<span
style={{
fontSize: 10,
fontFamily: "var(--font-mono)",
color: "var(--color-text-muted)",
}}
>
{tool.duration}ms
</span>
)}
<span
className={`pill text-[9px] font-bold ${
tool.status === "completed"
? "pill-cyan"
: tool.status === "error"
? "pill-pink"
: "pill-lavender"
}`}
>
{tool.status}
</span>
</div>
</div>
);
}
// ---- Conversation Log (Chat-style) ----
function relativeTime(timestamp: string): string {
try {
const now = Date.now();
const then = new Date(timestamp).getTime();
const diff = Math.max(0, Math.floor((now - then) / 1000));
if (diff < 5) return "just now";
if (diff < 60) return `${diff}s ago`;
const mins = Math.floor(diff / 60);
if (mins < 60) return `${mins}m ago`;
const hrs = Math.floor(mins / 60);
return `${hrs}h ${mins % 60}m ago`;
} catch {
return "";
}
}
function fullTime(timestamp: string): string {
try {
return new Date(timestamp).toLocaleTimeString("en-GB", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
} catch {
return "";
}
}
/** Friendly tool name: "log_entry" → "Log Entry" */
function friendlyToolName(name: string): string {
return name
.replace(/_/g, " ")
.replace(/\b\w/g, (c) => c.toUpperCase());
}
function ToolDetailBlock({ data, label }: { data: Record<string, unknown>; label: string }) {
const [expanded, setExpanded] = useState(false);
const keys = Object.keys(data);
const summary =
keys.length <= 3
? keys
.map((k) => {
const v = data[k];
const display =
typeof v === "string"
? v.length > 40
? v.slice(0, 40) + "…"
: v
: JSON.stringify(v);
return `${k}: ${display}`;
})
.join(", ")
: `${keys.length} fields`;
return (
<div style={{ marginTop: 4 }}>
<button
onClick={() => setExpanded((p) => !p)}
style={{
background: "none",
border: "none",
cursor: "pointer",
padding: 0,
display: "inline-flex",
alignItems: "center",
gap: 4,
fontSize: 11,
color: "var(--color-text-muted)",
}}
>
<span style={{ fontSize: 9, opacity: 0.7 }}>{expanded ? "▾" : "▸"}</span>
<span
style={{
padding: "1px 5px",
borderRadius: 3,
background: "rgba(255, 255, 255, 0.05)",
border: "1px solid rgba(255, 255, 255, 0.08)",
fontSize: 9,
fontWeight: 700,
textTransform: "uppercase",
letterSpacing: "0.05em",
}}
>
{label}
</span>
{!expanded && (
<span style={{ opacity: 0.5, fontSize: 11, fontFamily: "var(--font-mono)" }}>
{summary}
</span>
)}
</button>
{expanded && (
<pre
style={{
marginTop: 4,
padding: "8px 10px",
background: "rgba(20, 20, 20, 0.8)",
border: "1px solid rgba(255, 255, 255, 0.06)",
borderRadius: 6,
fontSize: 11,
lineHeight: 1.5,
fontFamily: "var(--font-mono)",
color: "var(--color-text-secondary)",
overflowX: "auto",
maxHeight: 200,
whiteSpace: "pre-wrap",
wordBreak: "break-word",
}}
>
{JSON.stringify(data, null, 2)}
</pre>
)}
</div>
);
}
function ConversationLog({ messages }: { messages: Message[] }) {
const [isOpen, setIsOpen] = useState(true);
const scrollRef = useRef<HTMLDivElement>(null);
// Re-render every 30s so relative timestamps update
const [, setTick] = useState(0);
useEffect(() => {
const id = setInterval(() => setTick((t) => t + 1), 30_000);
return () => clearInterval(id);
}, []);
// No auto-scroll needed — newest messages are at the top
// Newest first
const logMessages = messages.filter((m) => !m.isPartial).slice().reverse();
return (
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
{/* Header toggle */}
<button
onClick={() => setIsOpen((prev) => !prev)}
style={{
display: "flex",
alignItems: "center",
gap: 6,
width: "100%",
background: "none",
border: "none",
padding: "12px 20px",
cursor: "pointer",
}}
>
<SectionLabel
icon={<MessageSquare className="w-3.5 h-3.5" />}
label="Conversation"
/>
<div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 4 }}>
{logMessages.length > 0 && (
<span className="pill pill-cyan text-[9px] font-bold">{logMessages.length}</span>
)}
{isOpen ? (
<ChevronDown className="w-3.5 h-3.5 text-muted" />
) : (
<ChevronRight className="w-3.5 h-3.5 text-muted" />
)}
</div>
</button>
{isOpen && (
<div
ref={scrollRef}
style={{
flex: 1,
overflowY: "auto",
background: "rgba(18, 18, 18, 0.95)",
borderTop: "1px solid rgba(255, 255, 255, 0.06)",
padding: "16px 20px",
display: "flex",
flexDirection: "column",
gap: 6,
}}
>
{logMessages.length === 0 ? (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 8,
padding: "32px 0",
color: "var(--color-text-muted)",
}}
>
<MessageSquare className="w-6 h-6" style={{ opacity: 0.3 }} />
<p style={{ fontSize: 13, fontStyle: "italic", margin: 0 }}>
Start talking to see the conversation here
</p>
</div>
) : (
logMessages.map((msg) => {
const isUser = msg.role === "user";
const isTool = msg.role === "tool" && msg.tool;
const isSystem = msg.role === "system";
const isGenUI = msg.role === "assistant" && !!msg.component;
// ── System event chip (centered) ──
if (isSystem) {
return (
<div
key={msg.id}
style={{
display: "flex",
justifyContent: "center",
padding: "4px 0",
}}
>
<span
title={fullTime(msg.timestamp)}
style={{
display: "inline-flex",
alignItems: "center",
gap: 6,
padding: "4px 14px",
borderRadius: 20,
background: "rgba(255, 255, 255, 0.04)",
border: "1px solid rgba(255, 255, 255, 0.08)",
fontSize: 11,
color: "var(--color-text-muted)",
fontWeight: 500,
}}
>
{msg.content}
</span>
</div>
);
}
// ── Tool call badge (compact inline) ──
if (isTool) {
const toolStatus = msg.tool!.status;
const statusEmoji =
toolStatus === "completed" ? "✓" : toolStatus === "error" ? "✗" : "⋯";
const statusColor =
toolStatus === "completed"
? "var(--color-success)"
: toolStatus === "error"
? "var(--color-error)"
: "var(--color-accent-cyan)";
return (
<div key={msg.id} style={{ padding: "2px 0" }}>
<div
style={{
display: "inline-flex",
alignItems: "center",
gap: 6,
padding: "5px 12px",
borderRadius: 8,
background: "rgba(255, 193, 204, 0.06)",
border: "1px solid rgba(255, 193, 204, 0.12)",
fontSize: 12,
color: "var(--color-text-secondary)",
}}
>
<span style={{ fontSize: 13 }}>🛠</span>
<span style={{ fontWeight: 600, color: "var(--color-accent-pink)" }}>
{friendlyToolName(msg.tool!.name)}
</span>
<span
style={{
fontWeight: 700,
fontSize: 11,
color: statusColor,
}}
>
{statusEmoji}
</span>
<span
title={fullTime(msg.timestamp)}
style={{ fontSize: 10, color: "var(--color-text-muted)", marginLeft: 4 }}
>
{relativeTime(msg.timestamp)}
</span>
</div>
{/* Expandable args/result (hidden by default) */}
{msg.tool!.args && Object.keys(msg.tool!.args).length > 0 && (
<div style={{ marginLeft: 16, marginTop: 2 }}>
<ToolDetailBlock data={msg.tool!.args} label="Input" />
</div>
)}
{msg.tool!.result && Object.keys(msg.tool!.result).length > 0 && (
<div style={{ marginLeft: 16, marginTop: 2 }}>
<ToolDetailBlock data={msg.tool!.result} label="Output" />
</div>
)}
</div>
);
}
// ── GenUI badge (compact) ──
if (isGenUI) {
return (
<div key={msg.id} style={{ padding: "2px 0" }}>
<div
style={{
display: "inline-flex",
alignItems: "center",
gap: 6,
padding: "5px 12px",
borderRadius: 8,
background: "rgba(168, 218, 220, 0.06)",
border: "1px solid rgba(168, 218, 220, 0.12)",
fontSize: 12,
}}
>
<span style={{ fontSize: 13 }}>🖥</span>
<span style={{ fontWeight: 600, color: "var(--color-accent-cyan)" }}>
{msg.component!.name}
</span>
<span style={{ fontSize: 10, color: "var(--color-text-muted)" }}>shown</span>
<span
title={fullTime(msg.timestamp)}
style={{ fontSize: 10, color: "var(--color-text-muted)", marginLeft: 4 }}
>
{relativeTime(msg.timestamp)}
</span>
</div>
{Object.keys(msg.component!.props).length > 0 && (
<div style={{ marginLeft: 16, marginTop: 2 }}>
<ToolDetailBlock data={msg.component!.props} label="Props" />
</div>
)}
</div>
);
}
// ── Chat bubble (user = left, assistant = right) ──
return (
<div
key={msg.id}
style={{
display: "flex",
flexDirection: "column",
alignItems: isUser ? "flex-start" : "flex-end",
maxWidth: "85%",
alignSelf: isUser ? "flex-start" : "flex-end",
}}
>
{/* Sender label */}
<span
style={{
fontSize: 10,
fontWeight: 700,
textTransform: "uppercase",
letterSpacing: "0.08em",
color: isUser ? "var(--color-cta)" : "var(--color-accent-cyan)",
marginBottom: 3,
paddingLeft: isUser ? 2 : 0,
paddingRight: isUser ? 0 : 2,
}}
>
{isUser ? "You" : "Reachy"}
</span>
{/* Bubble */}
<div
style={{
padding: "10px 14px",
borderRadius: isUser ? "2px 14px 14px 14px" : "14px 2px 14px 14px",
background: isUser
? "rgba(255, 255, 255, 0.06)"
: "rgba(168, 218, 220, 0.10)",
border: isUser
? "1px solid rgba(255, 255, 255, 0.10)"
: "1px solid rgba(168, 218, 220, 0.18)",
fontSize: 14,
lineHeight: 1.6,
color: "var(--color-text-primary)",
wordBreak: "break-word",
}}
>
{msg.content || "(no text)"}
</div>
{/* Timestamp */}
<span
title={fullTime(msg.timestamp)}
style={{
fontSize: 10,
color: "var(--color-text-muted)",
marginTop: 3,
paddingLeft: isUser ? 2 : 0,
paddingRight: isUser ? 0 : 2,
opacity: 0.7,
}}
>
{relativeTime(msg.timestamp)}
</span>
</div>
);
})
)}
</div>
)}
</div>
);
}
// ---- Helpers ----
function formatDuration(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
if (mins > 0) {
return `${mins}m ${secs.toString().padStart(2, "0")}s`;
}
return `${secs}s`;
}
function formatTokenCount(count: number): string {
if (count >= 1_000_000) {
return `${(count / 1_000_000).toFixed(1)}M`;
}
if (count >= 10_000) {
return `${(count / 1_000).toFixed(1)}k`;
}
return new Intl.NumberFormat().format(count);
}