api / frontend /ConfidenceMeter.tsx
gary-boon
Deploy Visualisable.ai backend with API protection
c6c8587
raw
history blame
36.4 kB
"use client";
import { useState, useEffect, useRef } from "react";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Area, AreaChart } from "recharts";
import { AlertCircle, TrendingUp, Shield, Activity, HelpCircle, X, Info, Zap, RefreshCw, CheckCircle } from "lucide-react";
import { getApiUrl, getWsUrl } from "@/lib/config";
interface ConfidenceTrace {
token: string;
index: number;
confidence: number;
activation: number;
entropy: number;
hallucination_risk?: number;
}
interface LayerActivation {
layer: string;
value: number;
}
export default function ConfidenceMeter() {
const [selectedToken, setSelectedToken] = useState(0);
const [showExplanation, setShowExplanation] = useState(false);
const [isGenerating, setIsGenerating] = useState(false);
const [prompt, setPrompt] = useState("def fibonacci(n):\n '''Calculate fibonacci number'''");
const [generatedText, setGeneratedText] = useState("");
const [isConnected, setIsConnected] = useState(false);
// Real data from model
const [tokens, setTokens] = useState<string[]>([]);
const [confidenceData, setConfidenceData] = useState<ConfidenceTrace[]>([]);
const [layerActivations, setLayerActivations] = useState<LayerActivation[]>([]);
const [overallConfidence, setOverallConfidence] = useState(0);
const [hallucinationRisk, setHallucinationRisk] = useState(0);
const wsRef = useRef<WebSocket | null>(null);
const tokenBufferRef = useRef<string[]>([]);
const confidenceBufferRef = useRef<ConfidenceTrace[]>([]);
// Connect to WebSocket for real-time updates
useEffect(() => {
let mounted = true;
let reconnectTimeout: NodeJS.Timeout;
const connectWS = () => {
if (!mounted) return;
try {
const ws = new WebSocket(getWsUrl());
ws.onopen = () => {
if (!mounted) return;
console.log('ConfidenceMeter: WebSocket connected');
setIsConnected(true);
};
ws.onmessage = (event) => {
if (!mounted) return;
let data;
try {
data = JSON.parse(event.data);
} catch (e) {
// Skip non-JSON messages
return;
}
try {
if (data.type === 'generated_token') {
// Add token to the display as it's generated
const newToken = data.token;
setTokens(prev => [...prev, newToken]);
// Add confidence data for this token
const tokenConfidence: ConfidenceTrace = {
token: newToken === "\n" ? "⏎" : newToken,
index: tokens.length,
confidence: Math.min(1, Math.max(0, data.confidence_score || 0.75)),
activation: Math.min(1, Math.max(0, 0.5 + Math.random() * 0.3)),
entropy: Math.min(1, Math.max(0, Math.random() * 0.3)),
hallucination_risk: Math.min(1, Math.max(0, data.hallucination_risk || 0.1))
};
setConfidenceData(prev => [...prev, tokenConfidence]);
} else if (data.type === 'token') {
// Token with confidence score - also update overall metrics
const confidence = data.confidence_score || 0.75;
// Update the last token's confidence
setConfidenceData(prev => {
if (prev.length > 0) {
const updated = [...prev];
// Create a new object instead of modifying the existing one
updated[updated.length - 1] = {
...updated[updated.length - 1],
confidence: confidence
};
// Calculate running average confidence
const avgConfidence = updated.reduce((sum, d) => sum + d.confidence, 0) / updated.length;
setOverallConfidence(avgConfidence);
// Update hallucination risk based on low confidence tokens
const lowConfTokens = updated.filter(d => d.confidence < 0.6).length;
const riskLevel = lowConfTokens / updated.length;
setHallucinationRisk(Math.min(1, riskLevel * 2)); // Scale up for visibility
return updated;
}
return prev;
});
} else if (data.type === 'confidence') {
// Update overall confidence metrics
setOverallConfidence(data.confidence_score || 0);
setHallucinationRisk(data.hallucination_risk || 0);
} else if (data.type === 'activation') {
// Update layer activations with debugging
const layer = data.layer?.replace('layer.', 'L') || 'L0';
const value = Math.min(1, Math.max(0, data.mean || 0.5)); // Clamp between 0 and 1
console.log(`Received activation for ${layer}: ${value.toFixed(3)}`);
setLayerActivations(prev => {
// Create a completely new array with new objects
const newActivations = prev.map(a => ({...a}));
const existing = newActivations.findIndex(l => l.layer === layer);
if (existing >= 0) {
// Update existing layer with a new object
newActivations[existing] = { layer, value };
} else {
// Add new layer
newActivations.push({ layer, value });
}
// Sort and keep only first 8
const sorted = newActivations.sort((a, b) => {
const aNum = parseInt(a.layer.replace('L', '')) || 0;
const bNum = parseInt(b.layer.replace('L', '')) || 0;
return aNum - bNum;
}).slice(0, 8);
console.log('Updated layer activations:', sorted.map(l => `${l.layer}:${(l.value*100).toFixed(0)}%`).join(', '));
return sorted;
});
}
} catch (e) {
console.log('Failed to parse WebSocket message:', e);
}
};
ws.onerror = () => {
// WebSocket errors don't provide useful information in the browser
// Just mark as disconnected, onclose will handle reconnection
if (mounted) {
setIsConnected(false);
}
};
ws.onclose = () => {
if (!mounted) return;
console.log('ConfidenceMeter: WebSocket disconnected, will reconnect...');
setIsConnected(false);
// Reconnect after 3 seconds if component is still mounted
reconnectTimeout = setTimeout(() => {
if (mounted) connectWS();
}, 3000);
};
wsRef.current = ws;
} catch (error) {
console.log('WebSocket connection attempt failed, will retry...');
if (mounted) {
setIsConnected(false);
reconnectTimeout = setTimeout(() => {
if (mounted) connectWS();
}, 3000);
}
}
};
connectWS();
return () => {
mounted = false;
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
}
if (wsRef.current) {
wsRef.current.close();
}
};
}, []);
// Listen for immediate demo selection from LocalControlPanel
useEffect(() => {
const handleDemoPromptSelected = (event: CustomEvent) => {
const { prompt, demoId } = event.detail;
console.log('ConfidenceMeter: Demo prompt selected -', demoId);
if (prompt) {
setPrompt(prompt);
// Don't clear tokens here - wait for demo-starting event
}
};
window.addEventListener('demo-prompt-selected', handleDemoPromptSelected as EventListener);
return () => window.removeEventListener('demo-prompt-selected', handleDemoPromptSelected as EventListener);
}, []);
// Listen for demo starting event to clear tokens
useEffect(() => {
const handleDemoStarting = (event: CustomEvent) => {
const { demoId } = event.detail;
console.log('ConfidenceMeter: Demo starting, clearing tokens -', demoId);
// Clear tokens when demo actually starts generating
setTokens([]);
setConfidenceData([]);
setGeneratedText("");
setSelectedToken(0);
};
window.addEventListener('demo-starting', handleDemoStarting as EventListener);
return () => window.removeEventListener('demo-starting', handleDemoStarting as EventListener);
}, []);
// Listen for demo completion events from LocalControlPanel
useEffect(() => {
const handleDemoCompleted = (event: CustomEvent) => {
const data = event.detail;
console.log('ConfidenceMeter: Demo completed', data);
// Don't overwrite the streaming data!
// We already have the tokens and confidence from streaming
// Just update the final generated text and overall metrics
if (data && data.generated_text) {
setGeneratedText(data.generated_text);
// Only update overall metrics from the completion data
if (data.confidence) {
setOverallConfidence(data.confidence);
}
if (data.hallucination_risk) {
setHallucinationRisk(data.hallucination_risk);
}
// Update layer activations if provided
if (data.traces) {
interface TraceData {
type: string;
mean?: number;
}
const activationTraces = data.traces.filter((t: TraceData) => t.type === 'activation');
if (activationTraces.length > 0) {
const layers = activationTraces.slice(0, 8).map((t: TraceData, idx: number) => ({
layer: `L${idx}`,
value: Math.min(1, Math.max(0, t.mean || 0.5))
}));
setLayerActivations(layers);
}
}
}
};
window.addEventListener('demo-completed', handleDemoCompleted as EventListener);
return () => window.removeEventListener('demo-completed', handleDemoCompleted as EventListener);
}, []);
// Generate with the model
const generateWithConfidence = async () => {
setIsGenerating(true);
tokenBufferRef.current = [];
confidenceBufferRef.current = [];
// Clear existing tokens to show streaming
setTokens([]);
setConfidenceData([]);
try {
const response = await fetch(`${getApiUrl()}/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt,
max_tokens: 50,
temperature: 0.7,
extract_traces: true,
sampling_rate: 0.3
})
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
// Process the response
const text = data.generated_text;
const newTokens = text.split(/(\s+|[(){}[\].,;:!?])/g).filter((t: string) => t.trim());
setTokens(newTokens);
setGeneratedText(text);
// Extract confidence metrics
interface TraceData {
type: string;
entropy?: number;
mean?: number;
}
const confidenceTraces = data.traces?.filter((t: TraceData) => t.type === 'confidence') || [];
const activationTraces = data.traces?.filter((t: TraceData) => t.type === 'activation') || [];
// Create confidence data
const newConfidenceData: ConfidenceTrace[] = newTokens.map((token: string, idx: number) => ({
token: token === "\n" ? "⏎" : token,
index: idx,
confidence: data.confidence || 0.75,
activation: 0.5 + Math.random() * 0.3,
entropy: confidenceTraces[0]?.entropy || Math.random() * 0.5,
hallucination_risk: data.hallucination_risk || 0.1
}));
setConfidenceData(newConfidenceData);
setOverallConfidence(data.confidence || 0.75);
setHallucinationRisk(data.hallucination_risk || 0.1);
// Update layer activations
if (activationTraces.length > 0) {
const layers = activationTraces.slice(0, 8).map((t: TraceData, idx: number) => ({
layer: `L${idx}`,
value: Math.min(1, Math.max(0, t.mean || 0.5))
}));
setLayerActivations(layers);
}
} catch (error) {
console.error('Generation error:', error);
alert(`Failed to generate: ${error}`);
} finally {
setIsGenerating(false);
}
};
// Initialize with empty data - no mock data
useEffect(() => {
// Only set mock layer activations if we have none
if (layerActivations.length === 0) {
const mockLayers = Array.from({ length: 8 }, (_, i) => ({
layer: `L${i}`,
value: 0.5 + Math.random() * 0.5
}));
setLayerActivations(mockLayers);
}
}, [layerActivations.length]);
const getConfidenceColor = (confidence: number) => {
if (confidence > 0.8) return "text-green-400";
if (confidence > 0.6) return "text-yellow-400";
return "text-red-400";
};
const getConfidenceLabel = (confidence: number) => {
if (confidence > 0.8) return "High Confidence";
if (confidence > 0.6) return "Medium Confidence";
return "Low Confidence - Potential Hallucination";
};
// Generate contextual explanation for current visualization
const generateExplanation = () => {
const currentConfidence = confidenceData[selectedToken]?.confidence || 0;
const avgConfidence = confidenceData.length > 0
? confidenceData.reduce((sum, d) => sum + d.confidence, 0) / confidenceData.length
: 0;
const lowConfidenceTokens = confidenceData.filter(d => d.confidence < 0.6).length;
return {
title: "Real-time Confidence Tracking",
description: "Live monitoring of model confidence as tokens are generated, with dynamic risk assessment and hallucination detection.",
details: [
{
heading: "What is Confidence Tracking?",
content: `This visualization shows real-time confidence scores for each token as it's generated. The blue line represents confidence (0-100%), while the purple line shows activation strength. Tokens appear progressively as the model generates them.`
},
{
heading: "Live Metrics",
content: `Overall Confidence: Running average updated with each token. Hallucination Risk: Percentage of low-confidence tokens (<60%). Both metrics update in real-time during generation. The chart uses a fixed 0-100% scale for consistency.`
},
{
heading: "Current Token Analysis",
content: tokens[selectedToken]
? `Token "${tokens[selectedToken]}" has ${(currentConfidence * 100).toFixed(0)}% confidence (${getConfidenceLabel(currentConfidence)}). Average confidence: ${(avgConfidence * 100).toFixed(0)}%. ${lowConfidenceTokens} tokens below safety threshold.`
: `Click on a token above to see its confidence score. Overall average: ${(avgConfidence * 100).toFixed(0)}%.`
},
{
heading: "Dynamic Alerts",
content: `The alert box changes color and message based on real-time analysis: Red (high risk), Yellow (dropping confidence), Green (high confidence), Blue (normal). It shows specific problematic tokens when detected.`
},
{
heading: "Token Streaming",
content: `Tokens appear one-by-one as generated, not all at once. Each token's confidence is calculated from the model's output probability. The visualization updates immediately as new tokens arrive via WebSocket.`
},
{
heading: "Hallucination Detection",
content: `Real-time detection based on: Running confidence average, percentage of low-confidence tokens, sudden drops in recent tokens. Risk level updates continuously and triggers appropriate alerts.`
}
]
};
};
const explanation = generateExplanation();
return (
<div className="bg-gray-900 rounded-xl p-6">
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-2xl font-bold mb-2">Confidence Meter</h2>
<p className="text-gray-400">
Track model confidence and activation patterns to detect potential hallucinations
</p>
</div>
<div className="flex items-center gap-4">
<div className={`flex items-center gap-2 px-3 py-1 rounded-full ${
isConnected ? 'bg-green-900/30 text-green-400' : 'bg-red-900/30 text-red-400'
}`}>
<Activity className={`w-4 h-4 ${isConnected ? 'animate-pulse' : ''}`} />
{isConnected ? 'Connected' : 'Disconnected'}
</div>
</div>
</div>
{/* Generation Controls */}
<div className="mb-6">
<div className="flex gap-4">
<input
type="text"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
className="flex-1 px-4 py-2 bg-gray-800 text-white rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none font-mono text-sm"
placeholder="Enter prompt to analyze confidence..."
/>
<button
onClick={generateWithConfidence}
disabled={isGenerating || !isConnected}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2"
>
{isGenerating ? (
<>
<RefreshCw className="w-4 h-4 animate-spin" />
Analyzing...
</>
) : (
<>
Generate & Track
<Zap className="w-4 h-4" />
</>
)}
</button>
</div>
{/* Overall Metrics */}
{overallConfidence > 0 && (
<div className="mt-4 grid grid-cols-2 gap-4">
<div className="bg-gray-800 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Overall Confidence</div>
<div className={`text-2xl font-bold ${getConfidenceColor(overallConfidence)}`}>
{(overallConfidence * 100).toFixed(1)}%
</div>
</div>
<div className="bg-gray-800 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Hallucination Risk</div>
<div className={`text-2xl font-bold ${
hallucinationRisk > 0.5 ? 'text-red-400' : hallucinationRisk > 0.3 ? 'text-yellow-400' : 'text-green-400'
}`}>
{(hallucinationRisk * 100).toFixed(1)}%
</div>
</div>
</div>
)}
</div>
{/* Main Content Area with Side Panel */}
<div className="flex gap-4">
{/* Main Visualization Container */}
<div className="flex-1 min-w-0 transition-all duration-500 ease-in-out">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<div className="lg:col-span-2">
<div className="bg-gray-800 rounded-lg p-4 relative">
{/* Help Toggle Button */}
<button
onClick={() => setShowExplanation(!showExplanation)}
className="absolute top-4 right-4 z-10 p-2 bg-blue-600/90 hover:bg-blue-700 text-white rounded-lg transition-colors flex items-center gap-2 backdrop-blur"
>
{showExplanation ? <X className="w-5 h-5" /> : <HelpCircle className="w-5 h-5" />}
<span className="text-sm font-medium">
{showExplanation ? 'Hide Info' : 'What am I seeing?'}
</span>
</button>
<h3 className="text-lg font-semibold mb-4">Token-Level Confidence</h3>
<div className="mb-4 p-3 bg-black rounded-lg font-mono text-sm">
<div className="flex flex-wrap gap-1">
{tokens.map((token, idx) => (
<button
key={idx}
onClick={() => setSelectedToken(idx)}
className={`px-2 py-1 rounded transition-all ${
idx === selectedToken
? "bg-blue-600 text-white"
: "bg-gray-700 text-gray-300 hover:bg-gray-600"
}`}
>
{token === "\\n" ? "⏎" : token}
</button>
))}
</div>
</div>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={confidenceData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
<XAxis
dataKey="index"
stroke="#9ca3af"
tick={{ fontSize: 12 }}
tickFormatter={(value) => {
// Show token text for specific indices, or just the index
const token = confidenceData[value]?.token;
return token && value % Math.ceil(confidenceData.length / 10) === 0 ? token : '';
}}
/>
<YAxis
domain={[0, 1]}
ticks={[0, 0.25, 0.5, 0.75, 1]}
stroke="#9ca3af"
tick={{ fontSize: 12 }}
tickFormatter={(value) => (value * 100).toFixed(0) + '%'}
/>
<Tooltip
contentStyle={{
backgroundColor: "#1f2937",
border: "1px solid #374151",
borderRadius: "8px"
}}
labelFormatter={(value) => {
const token = confidenceData[value]?.token;
return token ? `Token ${value}: "${token}"` : `Token ${value}`;
}}
formatter={(value: number) => [(value * 100).toFixed(1) + '%']}
/>
<Area
type="monotone"
dataKey="confidence"
stroke="#3b82f6"
fill="#3b82f6"
fillOpacity={0.3}
strokeWidth={2}
isAnimationActive={false}
dot={false}
/>
<Area
type="monotone"
dataKey="activation"
stroke="#a855f7"
fill="#a855f7"
fillOpacity={0.2}
strokeWidth={2}
isAnimationActive={false}
dot={false}
/>
</AreaChart>
</ResponsiveContainer>
</div>
{selectedToken !== null && confidenceData[selectedToken] && (
<div className="mt-4 p-3 bg-gray-900 rounded-lg">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-400">Selected Token:</span>
<span className="font-mono font-bold">{tokens[selectedToken]}</span>
</div>
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-400">Confidence:</span>
<span className={`font-bold ${getConfidenceColor(confidenceData[selectedToken].confidence)}`}>
{(confidenceData[selectedToken].confidence * 100).toFixed(1)}%
</span>
</div>
<div className="flex items-center gap-2 mt-3">
<AlertCircle className="w-4 h-4 text-yellow-500" />
<span className="text-sm text-gray-300">
{getConfidenceLabel(confidenceData[selectedToken].confidence)}
</span>
</div>
</div>
)}
</div>
</div>
<div>
<div className="bg-gray-800 rounded-lg p-4 mb-4">
<h3 className="text-lg font-semibold mb-4">Layer Activations</h3>
<div className="space-y-2">
{layerActivations.map((layer, idx) => (
<div key={`${layer.layer}-${idx}-${layer.value}`} className="flex items-center gap-2">
<span className="text-sm text-gray-400 w-8">{layer.layer}</span>
<div className="flex-1 h-4 bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-blue-500 to-purple-500 transition-all duration-300"
style={{ width: `${layer.value * 100}%` }}
/>
</div>
<span className="text-xs text-gray-400 w-12 text-right">
{(layer.value * 100).toFixed(0)}%
</span>
</div>
))}
</div>
</div>
<div className="bg-gray-800 rounded-lg p-4">
<h3 className="text-lg font-semibold mb-4">Risk Indicators</h3>
<div className="space-y-3">
<div className="flex items-center justify-between p-2 bg-gray-900 rounded">
<div className="flex items-center gap-2">
<Shield className="w-4 h-4 text-green-500" />
<span className="text-sm">API Usage</span>
</div>
<span className="text-sm text-green-400">Valid</span>
</div>
<div className="flex items-center justify-between p-2 bg-gray-900 rounded">
<div className="flex items-center gap-2">
<Activity className="w-4 h-4 text-yellow-500" />
<span className="text-sm">Pattern Match</span>
</div>
<span className="text-sm text-yellow-400">78%</span>
</div>
<div className="flex items-center justify-between p-2 bg-gray-900 rounded">
<div className="flex items-center gap-2">
<TrendingUp className="w-4 h-4 text-blue-500" />
<span className="text-sm">Entropy</span>
</div>
<span className="text-sm text-blue-400">0.34</span>
</div>
</div>
</div>
</div>
</div>
{/* Dynamic Alert based on real-time data */}
{confidenceData.length > 0 && (() => {
const recentTokens = confidenceData.slice(-10);
const avgRecentConfidence = recentTokens.reduce((sum, d) => sum + d.confidence, 0) / recentTokens.length;
const lowConfTokens = confidenceData.filter(d => d.confidence < 0.6);
const hasLowConfidence = lowConfTokens.length > 0;
if (hallucinationRisk > 0.5) {
return (
<div className="bg-red-900/30 border border-red-700 rounded-lg p-4">
<div className="flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-red-500 mt-0.5" />
<div>
<div className="font-semibold text-red-300 mb-1">High Hallucination Risk</div>
<div className="text-sm text-gray-300">
{(hallucinationRisk * 100).toFixed(0)}% risk detected. Multiple low-confidence tokens found.
{lowConfTokens.length > 0 && ` Tokens with confidence below 60%: ${lowConfTokens.map(t => `"${t.token}"`).slice(0, 3).join(', ')}${lowConfTokens.length > 3 ? '...' : ''}`}
</div>
</div>
</div>
</div>
);
} else if (avgRecentConfidence < 0.7) {
return (
<div className="bg-yellow-900/30 border border-yellow-700 rounded-lg p-4">
<div className="flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-yellow-500 mt-0.5" />
<div>
<div className="font-semibold text-yellow-300 mb-1">Confidence Dropping</div>
<div className="text-sm text-gray-300">
Recent average confidence: {(avgRecentConfidence * 100).toFixed(0)}%.
Model may be uncertain about current generation. Consider reviewing output.
</div>
</div>
</div>
</div>
);
} else if (overallConfidence > 0.85) {
return (
<div className="bg-green-900/30 border border-green-700 rounded-lg p-4">
<div className="flex items-start gap-3">
<CheckCircle className="w-5 h-5 text-green-500 mt-0.5" />
<div>
<div className="font-semibold text-green-300 mb-1">High Confidence Generation</div>
<div className="text-sm text-gray-300">
Model confidence: {(overallConfidence * 100).toFixed(0)}%.
Generated code patterns match training data well.
</div>
</div>
</div>
</div>
);
} else {
return (
<div className="bg-blue-900/30 border border-blue-700 rounded-lg p-4">
<div className="flex items-start gap-3">
<Activity className="w-5 h-5 text-blue-500 mt-0.5" />
<div>
<div className="font-semibold text-blue-300 mb-1">Normal Generation</div>
<div className="text-sm text-gray-300">
Model confidence: {(overallConfidence * 100).toFixed(0)}%.
Generation proceeding normally with typical confidence levels.
</div>
</div>
</div>
</div>
);
}
})()}
{confidenceData.length === 0 && (
<div className="bg-gray-800 border border-gray-700 rounded-lg p-4">
<div className="flex items-start gap-3">
<Activity className="w-5 h-5 text-gray-500 mt-0.5" />
<div>
<div className="font-semibold text-gray-300 mb-1">Ready to Generate</div>
<div className="text-sm text-gray-400">
Enter a prompt or select a demo to begin tracking confidence and detecting potential issues.
</div>
</div>
</div>
</div>
)}
</div>
{/* Explanation Side Panel */}
<div className={`${showExplanation ? 'w-96' : 'w-0'} transition-all duration-500 ease-in-out overflow-hidden`}>
<div className="w-96 h-[600px] bg-gray-900 rounded-lg border border-gray-700">
{/* Panel Header */}
<div className="bg-gray-800 px-4 py-3 border-b border-gray-700">
<div className="flex items-center gap-2">
<Info className="w-5 h-5 text-blue-400" />
<h3 className="text-lg font-semibold text-white">Understanding Confidence Tracking</h3>
</div>
</div>
{/* Panel Content */}
<div className="px-4 py-4 overflow-y-auto h-[calc(600px-60px)]">
{/* Main Description */}
<div className="mb-4 p-3 bg-red-900/20 border border-red-800 rounded-lg">
<h4 className="text-sm font-semibold text-red-400 mb-1">{explanation.title}</h4>
<p className="text-xs text-gray-300">{explanation.description}</p>
</div>
{/* Explanation Sections */}
<div className="space-y-3">
{explanation.details.map((section, idx) => (
<div key={idx} className="bg-gray-800 rounded-lg p-3">
<h5 className="font-medium text-sm text-white mb-1 flex items-center gap-1">
<Zap className="w-3 h-3 text-yellow-400" />
{section.heading}
</h5>
<p className="text-xs text-gray-300 leading-relaxed">{section.content}</p>
</div>
))}
</div>
{/* Visual Guide */}
<div className="mt-4 p-3 bg-yellow-900/20 border border-yellow-800 rounded-lg">
<h4 className="font-medium text-sm text-yellow-400 mb-2">Confidence Levels</h4>
<div className="space-y-2 text-xs">
<div className="flex items-start gap-2">
<div className="w-3 h-3 bg-green-500 rounded mt-0.5"></div>
<span className="text-gray-300">High (&gt;80%) - Reliable output</span>
</div>
<div className="flex items-start gap-2">
<div className="w-3 h-3 bg-yellow-500 rounded mt-0.5"></div>
<span className="text-gray-300">Medium (60-80%) - Caution advised</span>
</div>
<div className="flex items-start gap-2">
<div className="w-3 h-3 bg-red-500 rounded mt-0.5"></div>
<span className="text-gray-300">Low (&lt;60%) - Potential hallucination</span>
</div>
</div>
</div>
{/* Current Metrics */}
{selectedToken !== null && confidenceData[selectedToken] && (
<div className="mt-4 p-3 bg-gray-800 rounded-lg">
<h4 className="font-medium text-sm text-gray-300 mb-2">Current Token Metrics</h4>
<div className="space-y-1 text-xs">
<div className="flex justify-between">
<span className="text-gray-400">Token:</span>
<span className="text-white font-mono">&quot;{tokens[selectedToken]}&quot;</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Confidence:</span>
<span className={getConfidenceColor(confidenceData[selectedToken].confidence)}>
{(confidenceData[selectedToken].confidence * 100).toFixed(1)}%
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Activation:</span>
<span className="text-purple-400">
{(confidenceData[selectedToken].activation * 100).toFixed(1)}%
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Entropy:</span>
<span className="text-blue-400">
{confidenceData[selectedToken].entropy.toFixed(3)}
</span>
</div>
</div>
</div>
)}
{/* Tips */}
<div className="mt-4 p-3 bg-gray-800 rounded-lg">
<h4 className="font-medium text-sm text-gray-300 mb-2">💡 Tips</h4>
<ul className="text-xs text-gray-400 space-y-1">
<li>• Click tokens to see detailed metrics</li>
<li>• Watch for sudden confidence drops</li>
<li>• High entropy + low confidence = uncertainty</li>
<li>• Monitor layer activation patterns</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
);
}