Spaces:
Sleeping
Sleeping
| import React from 'react'; | |
| const EpisodeEndOverlay = ({ isOpen, onClose, metrics, gameState }) => { | |
| if (!isOpen) return null; | |
| const handleDownload = () => { | |
| if (!gameState) return; | |
| const sc = gameState.scenario || {}; | |
| const allAgents = gameState.agents || {}; | |
| const allMessages = Object.values(allAgents).flatMap(a => a?.messages || []); | |
| const unifiedSummary = generateUnifiedSummary(); | |
| let report = `=================================================================\n`; | |
| report += ` NEXUS INCIDENT INVESTIGATION REPORT \n`; | |
| report += `=================================================================\n\n`; | |
| report += `[ SCENARIO METADATA ]\n`; | |
| report += `Title: ${sc.id || 'N/A'}\n`; | |
| report += `Domain: ${sc.domain || 'N/A'}\n`; | |
| report += `Difficulty: ${sc.difficulty || 'N/A'}\n`; | |
| report += `Final Score: ${Number(gameState?.cumulativeReward || metrics?.score || 0).toFixed(4)} / 1.00\n`; | |
| report += `Total Steps: ${gameState?.step || metrics?.steps || 'N/A'}\n`; | |
| report += `Active Agents: ${Object.keys(allAgents).length}\n`; | |
| report += `Status: ${unifiedSummary?.isSuccess ? 'SUCCESS' : 'INCONCLUSIVE'}\n\n`; | |
| report += `[ AGENTS DEPLOYED ]\n`; | |
| Object.entries(allAgents).forEach(([agentId, agentData], idx) => { | |
| const msgs = agentData?.messages || []; | |
| const msgCount = msgs.filter(m => m.type === 'message').length; | |
| const toolCount = msgs.filter(m => m.type === 'tool_call').length; | |
| report += `${idx + 1}. ${agentId}: ${msgCount} messages, ${toolCount} tool calls\n`; | |
| }); | |
| report += `\n`; | |
| // UNIFIED SUMMARY SECTION | |
| if (unifiedSummary) { | |
| report += `=================================================================\n`; | |
| report += `[ UNIFIED INVESTIGATION SUMMARY ]\n`; | |
| report += `=================================================================\n\n`; | |
| report += `## Combined Agent Conclusions\n`; | |
| report += `${unifiedSummary.conclusionText || 'No conclusions recorded.'}\n\n`; | |
| report += `## Key Findings & Clues\n`; | |
| report += `${unifiedSummary.keyFindings || 'None recorded.'}\n\n`; | |
| report += `## Key Tool Results\n`; | |
| report += `${unifiedSummary.toolSummary}\n\n`; | |
| } | |
| report += `[ STEP REWARDS ]\n`; | |
| if (gameState?.rewardHistory && gameState.rewardHistory.length > 0) { | |
| gameState.rewardHistory.forEach((r, i) => { | |
| report += `Step ${i + 1}: ${r.toFixed(4)}\n`; | |
| }); | |
| report += `Average: ${(gameState.rewardHistory.reduce((a, b) => a + b, 0) / gameState.rewardHistory.length).toFixed(4)}\n`; | |
| report += `Final Score: ${Number(gameState.cumulativeReward || 0).toFixed(4)}\n\n`; | |
| } else { | |
| report += `No step rewards recorded.\n\n`; | |
| } | |
| report += `[ REWARD BREAKDOWN ]\n`; | |
| if (gameState?.rewardBreakdown && Object.keys(gameState.rewardBreakdown).length > 0) { | |
| Object.entries(gameState.rewardBreakdown).forEach(([key, val]) => { | |
| report += `${key}: ${typeof val === 'number' ? val.toFixed(4) : val}\n`; | |
| }); | |
| report += `\n`; | |
| } | |
| report += `[ INCIDENT DESCRIPTION & PROBLEM ]\n`; | |
| report += `${sc.description || 'No description provided.'}\n\n`; | |
| report += `[ CONTEXT & ROOT CAUSE ]\n`; | |
| report += `${sc.context || 'No context provided.'}\n`; | |
| report += `Root Cause Validation: ${metrics?.rootCause || 'N/A'}\n\n`; | |
| report += `=================================================================\n`; | |
| report += `[ INVESTIGATION LOG & DETAILED TRACE ]\n`; | |
| report += `=================================================================\n\n`; | |
| const allErrors = []; | |
| const allTools = []; | |
| allMessages.forEach(msg => { | |
| if (msg.type === 'tool_call') { | |
| allTools.push(`- ${msg.tool_name}(${JSON.stringify(msg.params)})`); | |
| } | |
| if (msg.type === 'tool_result' && !msg.success) { | |
| allErrors.push(`- Error from ${msg.tool_name}: ${msg.result}`); | |
| } | |
| if (msg.type === 'tool_result' && msg.result?.toLowerCase().includes('error')) { | |
| allErrors.push(`- Log/Cmd Error: ${msg.result}`); | |
| } | |
| }); | |
| report += `> EXECUTED TOOLS & COMMANDS:\n`; | |
| if (allTools.length > 0) { | |
| allTools.forEach(t => report += `${t}\n`); | |
| } else { | |
| report += `None.\n`; | |
| } | |
| report += `\n`; | |
| report += `> SYSTEMS ERRORS DETECTED:\n`; | |
| if (allErrors.length > 0) { | |
| [...new Set(allErrors)].forEach(err => report += `${err}\n`); | |
| } else { | |
| report += `No significant system errors found.\n`; | |
| } | |
| report += `\n`; | |
| report += `=================================================================\n`; | |
| report += `[ SOLUTION & FIX VERIFICATION ]\n`; | |
| report += `=================================================================\n\n`; | |
| const resCall = gameState?.tool_calls_made?.find(c => c.tool_name === 'submit_resolution'); | |
| if (resCall?.params) { | |
| report += `Root Cause Service: ${resCall.params.root_cause_service || 'UNKNOWN'}\n`; | |
| report += `Root Cause Description: ${resCall.params.root_cause_description || 'None'}\n`; | |
| report += `Fix Applied: ${resCall.params.fix_applied || 'None'}\n`; | |
| } else { | |
| report += `No resolution submitted.\n`; | |
| } | |
| report += `\n`; | |
| report += `=================================================================\n`; | |
| report += `[ RECOMMENDATIONS ]\n`; | |
| report += `=================================================================\n\n`; | |
| if (allTools.length > 15) { | |
| report += `1. EFFICIENCY: ${allTools.length} tool calls made. Consider refining hypotheses.\n`; | |
| } else { | |
| report += `1. EFFICIENCY: Tool usage was concise (${allTools.length} calls).\n`; | |
| } | |
| if (allErrors.length > 5) { | |
| report += `2. ACCURACY: Multiple errors encountered. Verify tool syntax.\n`; | |
| } | |
| report += `3. CAUSE-ANALYSIS: Check error logs before database queries.\n`; | |
| report += `4. REMEDIATION: Establish better alerting for ${sc.domain || 'general'} domain.\n`; | |
| const blob = new Blob([report], { type: 'text/plain;charset=utf-8' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `nexus_investigation_report_${sc.id || 'export'}.txt`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| }; | |
| const generateUnifiedSummary = () => { | |
| const allAgents = gameState?.agents || {}; | |
| const agentEntries = Object.entries(allAgents); | |
| if (agentEntries.length === 0) return null; | |
| const allConclusions = []; | |
| const allToolResults = []; | |
| const allClues = gameState?.clues_found || []; | |
| agentEntries.forEach(([agentId, agentData]) => { | |
| const msgs = agentData?.messages || []; | |
| const textMsgs = msgs.filter(m => m.type === 'message'); | |
| const lastMsg = textMsgs[textMsgs.length - 1]; | |
| if (lastMsg) { | |
| allConclusions.push({ | |
| agentId, | |
| content: lastMsg.content || lastMsg.text || lastMsg.message || '', | |
| role: agentData.role || agentId | |
| }); | |
| } | |
| const toolResults = msgs.filter(m => m.type === 'tool_result'); | |
| toolResults.forEach(tr => { | |
| if (tr.result && !tr.result.toLowerCase().includes('error')) { | |
| allToolResults.push({ | |
| agentId, | |
| tool: tr.tool_name || tr.tool, | |
| result: tr.result | |
| }); | |
| } | |
| }); | |
| }); | |
| const conclusionText = allConclusions.map(c => c.content).join('\n\n'); | |
| const keyFindings = allClues.slice(0, 5).join('\n• '); | |
| const toolSummary = allToolResults.length > 0 | |
| ? allToolResults.slice(0, 3).map(t => `• ${t.tool}: ${t.result.substring(0, 100)}...`).join('\n') | |
| : 'No tool results recorded.'; | |
| const isSuccess = Number(gameState?.cumulativeReward || metrics?.score || 0) >= 0.5; | |
| return { | |
| conclusionText, | |
| keyFindings: keyFindings || 'No clues recorded.', | |
| toolSummary, | |
| agentCount: agentEntries.length, | |
| isSuccess | |
| }; | |
| }; | |
| const unifiedSummary = generateUnifiedSummary(); | |
| return ( | |
| <div className="fixed inset-0 z-[100] flex items-center justify-center p-4 md:p-8 animate-in fade-in duration-500"> | |
| {/* Particle/Pulse Background */} | |
| <div className="absolute inset-0 bg-background/40 backdrop-blur-sm pointer-events-none"> | |
| <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] opacity-10"> | |
| <div className="w-full h-full rounded-full border-[1px] border-primary-container/20 animate-[ping_4s_infinite]"></div> | |
| </div> | |
| </div> | |
| {/* Summary Modal */} | |
| <div className="relative w-full max-w-5xl max-h-[90vh] glass-panel rounded-xl overflow-hidden shadow-[0_0_80px_rgba(0,0,0,0.8)] border border-white/10 flex flex-col"> | |
| {/* Modal Header */} | |
| <div className="flex items-center justify-between p-6 bg-surface-container-highest/20 border-b border-white/5"> | |
| <div className="flex items-center gap-3"> | |
| <div className="p-1 rounded bg-primary-container/20 border border-primary-container/40"> | |
| <span className="text-primary material-symbols-outlined text-xl">task_alt</span> | |
| </div> | |
| <h2 className="font-headline font-bold text-lg tracking-widest text-on-surface uppercase">Episode_Execution_Complete</h2> | |
| </div> | |
| <button onClick={onClose} className="text-outline hover:text-white transition-colors"> | |
| <span className="material-symbols-outlined">close</span> | |
| </button> | |
| </div> | |
| {/* Scrollable Content Area */} | |
| <div className="flex-1 overflow-y-auto custom-scrollbar"> | |
| <div className="p-8 grid grid-cols-1 md:grid-cols-2 gap-8"> | |
| {/* Primary Metrics */} | |
| <div className="space-y-6"> | |
| <div className="space-y-2"> | |
| <span className="font-mono text-[10px] text-outline tracking-widest uppercase">Final Grading Score</span> | |
| <div className="flex items-baseline gap-2"> | |
| <span className="font-headline text-8xl font-bold text-transparent bg-clip-text bg-gradient-to-br from-primary to-primary-container drop-shadow-[0_0_15px_rgba(0,212,255,0.3)]"> | |
| {Number(gameState?.cumulativeReward || metrics?.score || 0).toFixed(2)} | |
| </span> | |
| <span className="font-headline text-2xl text-primary/40 font-light">/ 1.00</span> | |
| </div> | |
| </div> | |
| {/* Reward Breakdown from Episode */} | |
| {gameState?.rewardBreakdown && Object.keys(gameState.rewardBreakdown).length > 0 && ( | |
| <div className="p-4 bg-surface-container-lowest/50 border border-white/10 rounded-lg"> | |
| <span className="font-mono text-[10px] text-outline uppercase block mb-3">Step Reward Breakdown</span> | |
| <div className="grid grid-cols-4 gap-2"> | |
| {Object.entries(gameState.rewardBreakdown).map(([key, val]) => ( | |
| <div key={key} className="text-center bg-surface-container-high/30 rounded p-2"> | |
| <div className="text-[8px] text-slate-500 uppercase truncate">{key.replace(/_/g, ' ')}</div> | |
| <div className={`font-mono text-sm font-bold ${val > 0 ? 'text-primary' : 'text-slate-600'}`}> | |
| {typeof val === 'number' ? val.toFixed(3) : val} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {/* Reward History */} | |
| {gameState?.rewardHistory && gameState.rewardHistory.length > 0 && ( | |
| <div className="p-4 bg-surface-container-lowest/50 border border-white/10 rounded-lg"> | |
| <span className="font-mono text-[10px] text-outline uppercase block mb-3">Step Rewards</span> | |
| <div className="flex items-end gap-1 h-16"> | |
| {gameState.rewardHistory.map((r, i) => ( | |
| <div key={i} className="flex-1 bg-primary/60 rounded-t" | |
| style={{ height: `${Math.max(5, (r / 1) * 100)}%` }} | |
| title={`Step ${i + 1}: ${r.toFixed(3)}`}> | |
| </div> | |
| ))} | |
| </div> | |
| <div className="flex justify-between mt-2 text-[9px] font-mono text-slate-500"> | |
| <span>Avg: {(gameState.rewardHistory.reduce((a, b) => a + b, 0) / gameState.rewardHistory.length).toFixed(3)}</span> | |
| <span>Max: {Math.max(...gameState.rewardHistory).toFixed(3)}</span> | |
| </div> | |
| </div> | |
| )} | |
| <div className="grid grid-cols-2 gap-4"> | |
| <div className="bg-surface-container-lowest/50 p-4 border-l border-primary/20 refractive-edge"> | |
| <span className="font-mono text-[9px] text-outline uppercase block mb-1">Clues Found</span> | |
| <span className="font-headline text-2xl font-medium">{gameState?.clues_found?.length || 0}</span> | |
| </div> | |
| <div className="bg-surface-container-lowest/50 p-4 border-l border-primary/20 refractive-edge"> | |
| <span className="font-mono text-[9px] text-outline uppercase block mb-1">Steps Executed</span> | |
| <span className="font-headline text-2xl font-medium">{gameState?.step !== undefined ? gameState.step : (metrics?.steps !== undefined ? metrics.steps : '—')}</span> | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-4 p-5 bg-tertiary/5 border border-tertiary/10 rounded-lg"> | |
| <div className="p-3 rounded-full bg-tertiary/10 text-tertiary"> | |
| <span className="material-symbols-outlined">troubleshoot</span> | |
| </div> | |
| <div> | |
| <span className="font-mono text-[10px] text-tertiary/60 uppercase block">State Validation</span> | |
| <span className="text-sm font-medium tracking-wide">Status: <span className="font-mono text-tertiary">{metrics?.rootCause || '—'}</span></span> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Right Column: Agent Metrics */} | |
| <div className="space-y-6"> | |
| <h3 className="font-mono text-[10px] text-outline tracking-widest uppercase mb-4">Agent Performance Breakdown</h3> | |
| {Object.entries(gameState?.agents || {}).map(([agentId, agentData], idx) => { | |
| const colors = ['primary', 'secondary', 'tertiary', 'error', 'success']; | |
| const color = colors[idx % colors.length]; | |
| const msgs = agentData?.messages || []; | |
| const msgCount = msgs.filter(m => m.type === 'message').length; | |
| const toolCount = msgs.filter(m => m.type === 'tool_call').length; | |
| const errCount = msgs.filter(m => m.type === 'tool_result' && m.result?.toLowerCase().includes('error')).length; | |
| const agentNames = ['ALPHA', 'BRAVO', 'CHARLIE', 'DELTA', 'ECHO']; | |
| const agentName = agentNames[idx % agentNames.length]; | |
| return ( | |
| <div key={agentId} className="relative group"> | |
| <div className={`absolute -left-4 top-0 bottom-0 w-1 ${idx === 0 ? 'bg-primary' : idx === 1 ? 'bg-secondary' : idx === 2 ? 'bg-tertiary' : 'bg-error'}`}></div> | |
| <div className="bg-surface-container-low/40 p-5 space-y-4 border border-white/5 rounded-r-lg"> | |
| <div className="flex justify-between items-center"> | |
| <span className={`font-headline font-bold tracking-tighter uppercase ${idx === 0 ? 'text-primary' : idx === 1 ? 'text-secondary' : idx === 2 ? 'text-tertiary' : 'text-error'}`}>Agent_{agentName}</span> | |
| <span className={`font-mono text-[10px] ${idx === 0 ? 'text-primary' : idx === 1 ? 'text-secondary' : idx === 2 ? 'text-tertiary' : 'text-error'}/50`}>{agentId.toUpperCase()}</span> | |
| </div> | |
| <div className="grid grid-cols-3 gap-2 text-center"> | |
| <div> | |
| <span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">chat</span> MSGS</span> | |
| <span className={`font-headline text-lg font-medium ${idx === 0 ? 'text-primary' : idx === 1 ? 'text-secondary' : idx === 2 ? 'text-tertiary' : 'text-error'}`}>{msgCount}</span> | |
| </div> | |
| <div className="border-x border-white/5"> | |
| <span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">build</span> TOOLS</span> | |
| <span className={`font-headline text-lg font-medium ${idx === 0 ? 'text-primary' : idx === 1 ? 'text-secondary' : idx === 2 ? 'text-tertiary' : 'text-error'}`}>{toolCount}</span> | |
| </div> | |
| <div> | |
| <span className="font-mono text-[9px] text-outline flex flex-col items-center justify-center gap-1 uppercase"><span className="material-symbols-outlined text-[12px]">warning</span> ERRS</span> | |
| <span className={`font-headline text-lg font-medium ${idx === 0 ? 'text-primary' : idx === 1 ? 'text-secondary' : idx === 2 ? 'text-tertiary' : 'text-error'}`}>{errCount}</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| {/* Submit Resolution Report Panel */} | |
| {(() => { | |
| const resCall = gameState?.tool_calls_made?.find(c => c.tool_name === 'submit_resolution'); | |
| if (!resCall) return null; | |
| const p = resCall.params || {}; | |
| return ( | |
| <div className="px-8 pb-4"> | |
| <div className="p-6 bg-surface-container-low/40 border border-primary/20 rounded-lg"> | |
| <h3 className="font-headline font-bold text-primary tracking-widest uppercase mb-4 flex items-center gap-2"> | |
| <span className="material-symbols-outlined">description</span> | |
| Incident Resolution Report | |
| </h3> | |
| <div className="space-y-4"> | |
| <div> | |
| <span className="font-mono text-[10px] text-outline uppercase block mb-1">Root Cause Service</span> | |
| <span className="font-mono text-sm text-on-surface bg-surface-container p-1 px-2 rounded border border-white/5">{p.root_cause_service || 'UNKNOWN'}</span> | |
| </div> | |
| <div> | |
| <span className="font-mono text-[10px] text-outline uppercase block mb-1">Root Cause Description</span> | |
| <p className="text-sm text-on-surface/80">{p.root_cause_description || 'No description provided.'}</p> | |
| </div> | |
| <div className="p-4 bg-tertiary/5 border-l-2 border-tertiary rounded-r"> | |
| <span className="font-mono text-[10px] text-tertiary uppercase block mb-1">Fix Applied</span> | |
| <p className="text-sm text-on-surface">{p.fix_applied || 'No fix described.'}</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| })()} | |
| {/* Unified Investigation Summary */} | |
| {unifiedSummary && ( | |
| <div className="px-8 pb-8"> | |
| <div className="p-6 bg-gradient-to-br from-primary/5 to-secondary/5 border border-primary/20 rounded-lg"> | |
| <h3 className="font-headline font-bold text-primary tracking-widest uppercase mb-4 flex items-center gap-2"> | |
| <span className="material-symbols-outlined">psychology</span> | |
| Unified Investigation Summary | |
| </h3> | |
| {/* Success/Failure Badge */} | |
| <div className="mb-4"> | |
| <span className={`inline-flex items-center gap-2 px-3 py-1 rounded-full text-xs font-mono font-bold uppercase ${ | |
| unifiedSummary.isSuccess | |
| ? 'bg-tertiary/20 text-tertiary border border-tertiary/30' | |
| : 'bg-error/20 text-error border border-error/30' | |
| }`}> | |
| <span className={`w-2 h-2 rounded-full ${unifiedSummary.isSuccess ? 'bg-tertiary' : 'bg-error'}`}></span> | |
| {unifiedSummary.isSuccess ? 'Investigation Successful' : 'Investigation Inconclusive'} | |
| </span> | |
| <span className="ml-3 text-[10px] text-outline font-mono uppercase"> | |
| {unifiedSummary.agentCount} Agents Collaborated | |
| </span> | |
| </div> | |
| {/* Combined Conclusions */} | |
| <div className="mb-4"> | |
| <span className="font-mono text-[10px] text-primary/60 uppercase tracking-widest block mb-2">Combined Agent Conclusions</span> | |
| <div className="p-4 bg-surface-container-low/50 rounded border border-white/5 max-h-48 overflow-y-auto custom-scrollbar"> | |
| <p className="text-sm text-on-surface/90 leading-relaxed whitespace-pre-wrap"> | |
| {unifiedSummary.conclusionText || 'No conclusions recorded.'} | |
| </p> | |
| </div> | |
| </div> | |
| {/* Key Findings */} | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div> | |
| <span className="font-mono text-[10px] text-secondary/60 uppercase tracking-widest block mb-2">Key Findings & Clues</span> | |
| <div className="p-3 bg-surface-container-low/50 rounded border border-white/5"> | |
| <ul className="text-xs text-on-surface/80 space-y-1 list-disc list-inside"> | |
| {unifiedSummary.keyFindings.split('\n• ').map((finding, i) => ( | |
| finding && <li key={i}>{finding}</li> | |
| ))} | |
| </ul> | |
| </div> | |
| </div> | |
| <div> | |
| <span className="font-mono text-[10px] text-tertiary/60 uppercase tracking-widest block mb-2">Key Tool Results</span> | |
| <div className="p-3 bg-surface-container-low/50 rounded border border-white/5 max-h-32 overflow-y-auto custom-scrollbar"> | |
| <pre className="text-[10px] text-on-surface/70 whitespace-pre-wrap font-mono"> | |
| {unifiedSummary.toolSummary} | |
| </pre> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* Modal Footer */} | |
| <div className="p-6 bg-surface-container-lowest/90 border-t border-white/5 flex flex-col md:flex-row justify-between items-center gap-4"> | |
| <div className="flex items-center gap-2 text-outline/40"> | |
| <span className="material-symbols-outlined text-sm">info</span> | |
| <span className="font-mono text-[9px] uppercase tracking-wider">Session telemetry encrypted and cached locally</span> | |
| </div> | |
| <div className="flex gap-4 w-full md:w-auto"> | |
| <button onClick={handleDownload} className="flex-1 md:flex-none px-8 py-2.5 bg-transparent border border-outline-variant/30 text-on-surface hover:bg-white/5 transition-all font-mono text-xs tracking-widest uppercase"> | |
| Export Log | |
| </button> | |
| <button onClick={onClose} className="flex-1 md:flex-none px-12 py-2.5 bg-primary/20 border border-primary text-primary hover:bg-primary/30 transition-all font-mono text-xs tracking-widest font-bold uppercase shadow-[0_0_20px_rgba(0,212,255,0.1)]"> | |
| Dismiss | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default EpisodeEndOverlay; | |