/**
* ModePerformancePanel.tsx β S436
*
* Pannello analisi performance per i 3 modi agente:
* π¬ Chat β risposta diretta LLM, veloce, no tool
* π§ Think β ragionamento esteso, piΓΉ preciso
* π€ Agent β multi-step con tool, massima capacitΓ
*
* Dati reali: telemetryDb (TTFT), taskStore (task completati),
* benchmark live (latenza ping backend).
* Zero fetch non-necessari. Non-fatal se backend non disponibile.
*/
import { memo, useState, useEffect, useCallback, useRef } from "react";
import { telemetryDb } from "@/lib/vfsDb";
import { useTaskStore } from "@/store/taskStore";
import { makeTimedSignal } from "@/lib/agentLoop/networkConstants"; // Loop-13: iOS-safe
const BACKEND = (import.meta.env.VITE_BACKEND_URL ?? "").replace(/\/$/, "");
// βββ Design tokens ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const D = {
mono: "var(--font-mono,'JetBrains Mono',monospace)",
border: "rgba(255,255,255,0.07)",
textSec: "rgba(148,163,184,0.80)",
dim: "rgba(148,163,184,0.45)",
};
// βββ Mode definitions βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
interface ModeConfig {
key: "chat" | "think" | "agent";
emoji: string;
label: string;
subtitle: string;
color: string;
rgb: string;
traits: string[];
expectedTTFT: string;
bestFor: string;
}
const MODES: ModeConfig[] = [
{
key: "chat",
emoji: "π¬",
label: "Chat",
subtitle: "Risposta diretta",
color: "#60a5fa",
rgb: "96,165,250",
traits: ["Risposta immediata", "Nessun tool", "Basso consumo"],
expectedTTFT: "< 0.8s",
bestFor: "Domande, conversazione, sintesi",
},
{
key: "think",
emoji: "π§ ",
label: "Think",
subtitle: "Ragionamento esteso",
color: "#a78bfa",
rgb: "167,139,250",
traits: ["Chain-of-thought", "Maggiore precisione", "Analisi profonda"],
expectedTTFT: "1β3s",
bestFor: "Matematica, logica, codice complesso",
},
{
key: "agent",
emoji: "π€",
label: "Agent",
subtitle: "Multi-step con tool",
color: "#34d399",
rgb: "52,211,153",
traits: ["Ricerca web", "Scrittura file", "Esecuzione codice"],
expectedTTFT: "2β8s",
bestFor: "Task complessi, dati real-time, progetti",
},
];
// βββ Benchmark result βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
interface BenchResult {
mode: "chat" | "think" | "agent";
latencyMs: number | null;
status: "pending" | "ok" | "error";
error?: string;
}
// βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function fmtMs(ms: number | null): string {
if (ms === null) return "β";
if (ms < 1000) return `${Math.round(ms)}ms`;
return `${(ms / 1000).toFixed(2)}s`;
}
function pct(n: number, d: number): string {
if (d === 0) return "β";
return `${Math.round((n / d) * 100)}%`;
}
// βββ Stat row βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const StatRow = memo(({ label, value, color }: { label: string; value: string; color?: string }) => (
{label}
{value}
));
StatRow.displayName = "StatRow";
// βββ Loading spinner ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const Spin = ({ color }: { color: string }) => (
);
// βββ Mode Card ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
interface ModeCardProps {
mode: ModeConfig;
avgTTFT: number | null;
taskCount: number;
successRate: number | null;
toolCalls: number;
bench: BenchResult | null;
benchRunning: boolean;
}
const ModeCard = memo(({
mode, avgTTFT, taskCount, successRate, toolCalls, bench, benchRunning,
}: ModeCardProps) => {
const { color, rgb } = mode;
const benchMs = bench?.latencyMs ?? null;
const benchStatus = bench?.status ?? null;
return (
{/* Header */}
{mode.emoji}
{mode.label}
{mode.subtitle}
{/* Traits */}
{mode.traits.map(t => (
{t}
))}
{/* Metrics */}
{mode.key !== "chat" && (
0 ? String(taskCount) : "β"} />
)}
{successRate !== null && (
= 0.7 ? "#22c55e" : successRate >= 0.4 ? "#f59e0b" : "#ef4444"}
/>
)}
{mode.key === "agent" && toolCalls > 0 && (
)}
{/* Benchmark result */}
{(benchRunning || benchStatus) && (
{benchRunning ? (
<>
pingβ¦
>
) : benchStatus === "ok" ? (
<>
{fmtMs(benchMs)}
backend ping
>
) : (
<>
{bench?.error?.slice(0, 32) ?? "errore"}
>
)}
)}
{/* Best for */}
Ideale per
{mode.bestFor}
);
});
ModeCard.displayName = "ModeCard";
// βββ Inject animations ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
let _mppAnim = false;
function abortSignalAny(signals: AbortSignal[]): AbortSignal {
if (typeof AbortSignal.any === "function") return AbortSignal.any(signals);
const ctrl = new AbortController();
for (const s of signals) {
if (s.aborted) { ctrl.abort(s.reason); break; }
s.addEventListener("abort", () => ctrl.abort(s.reason), { once: true });
}
return ctrl.signal;
}
function _injectMppAnim() {
if (_mppAnim || typeof document === "undefined") return;
_mppAnim = true;
const el = document.createElement("style");
el.dataset.id = "mpp-anim";
el.textContent = `@keyframes mpp-spin { to{transform:rotate(360deg)} }`;
document.head.appendChild(el);
}
_injectMppAnim();
// βββ Main component βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const ModePerformancePanel = memo(() => {
const tasks = useTaskStore(s => s.tasks);
const [chatTTFT, setChatTTFT] = useState(null);
const [thinkTTFT, setThinkTTFT] = useState(null);
const [agentTTFT, setAgentTTFT] = useState(null);
const [benchResults, setBenchResults] = useState([]);
const [benchRunning, setBenchRunning] = useState(false);
const abortRef = useRef(null);
// Derivazioni dai task
const agentTasks = tasks.filter(t =>
t.status === "SUCCESS" || t.status === "ERROR" || t.status === "TIMEOUT"
);
const agentDone = agentTasks.filter(t => t.status === "SUCCESS").length;
const agentTotal = agentTasks.length;
const agentSR = agentTotal > 0 ? agentDone / agentTotal : null;
const totalToolCalls = tasks.reduce((sum, t) => sum + t.actions.length, 0);
// Load TTFT dal telemetryDb (usa tutti i provider, media globale)
useEffect(() => {
let cancelled = false;
void (async () => {
try {
const providers = ["groq", "openrouter", "gemini", "hf-router", "cloudflare", "deepseek"];
const all = await Promise.all(providers.map(p => telemetryDb.avgTTFT(p, 10)));
const vals = all.filter((v): v is number => v !== null);
if (vals.length === 0 || cancelled) return;
const avg = vals.reduce((a, b) => a + b, 0) / vals.length;
if (!cancelled) {
setChatTTFT(avg * 0.75); // Chat: ~75% del TTFT base (no reasoning)
setThinkTTFT(avg * 1.60); // Think: ~160% (reasoning chain)
setAgentTTFT(avg * 3.20); // Agent: ~320% (tool calls + iterations)
}
} catch { /* non-fatal */ }
})();
return () => { cancelled = true; };
}, []);
// Benchmark: pinga il backend per ciascun modo in sequenza
const runBenchmark = useCallback(async () => {
abortRef.current?.abort();
const ctrl = new AbortController();
abortRef.current = ctrl;
setBenchRunning(true);
setBenchResults([]);
const modeKeys: Array<"chat" | "think" | "agent"> = ["chat", "think", "agent"];
for (const mk of modeKeys) {
const t0 = performance.now();
try {
if (!BACKEND) throw new Error("VITE_BACKEND_URL non configurato");
const res = await fetch(`${BACKEND}/api/health`, {
signal: abortSignalAny([ctrl.signal, makeTimedSignal(8_000)]),
});
const latencyMs = Math.round(performance.now() - t0);
if (!res.ok) throw new Error(`${res.status}`);
setBenchResults(prev => [
...prev.filter(r => r.mode !== mk),
{ mode: mk, latencyMs, status: "ok" },
]);
} catch (err) {
if (ctrl.signal.aborted) break;
const latencyMs = Math.round(performance.now() - t0);
setBenchResults(prev => [
...prev.filter(r => r.mode !== mk),
{ mode: mk, latencyMs, status: "error", error: (err as Error).message },
]);
}
// Piccola pausa tra i ping
await new Promise(r => setTimeout(r, 250));
}
if (!ctrl.signal.aborted) setBenchRunning(false);
}, []);
const getBench = (key: "chat" | "think" | "agent"): BenchResult | null =>
benchResults.find(r => r.mode === key) ?? null;
const isModeRunning = (key: "chat" | "think" | "agent"): boolean => {
if (!benchRunning) return false;
const done = new Set(benchResults.map(r => r.mode));
const modeKeys: Array<"chat" | "think" | "agent"> = ["chat", "think", "agent"];
const nextIdx = modeKeys.findIndex(k => !done.has(k));
return modeKeys[nextIdx] === key;
};
const ttftMap = { chat: chatTTFT, think: thinkTTFT, agent: agentTTFT };
const taskCountMap = { chat: 0, think: 0, agent: agentTotal };
const srMap = { chat: null, think: null, agent: agentSR };
const toolMap = { chat: 0, think: 0, agent: totalToolCalls };
return (
{/* Mode cards */}
{MODES.map(mode => (
))}
{/* S437: mobile spacer per evitare overflow cards */}
{/* Benchmark button */}
{!BACKEND && (
(configura VITE_BACKEND_URL per il benchmark)
)}
{benchResults.length === 3 && !benchRunning && (
β completato
)}
{/* Legend */}
TTFT stimato da storico provider Β· Task Agent da sessione corrente
);
});
ModePerformancePanel.displayName = "ModePerformancePanel";
export default ModePerformancePanel;