import { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { Eye, RefreshCw, Download, Maximize2, X, Activity, Thermometer, Waves, Grid3X3, Box, AlertCircle, CheckCircle, Clock, FileText } from 'lucide-react'; import { useStudy } from '../context/StudyContext'; import { Card } from '../components/common/Card'; import Plot from 'react-plotly.js'; interface InsightInfo { type: string; name: string; description: string; applicable_to: string[]; } interface GeneratedFile { filename: string; insight_type: string; timestamp: string | null; size_kb: number; modified: number; } interface InsightResult { success: boolean; insight_type: string; plotly_figure: any; summary: Record; html_path: string | null; } const INSIGHT_ICONS: Record = { zernike_wfe: Waves, stress_field: Activity, modal: Box, thermal: Thermometer, design_space: Grid3X3 }; const INSIGHT_COLORS: Record = { zernike_wfe: 'text-blue-400 bg-blue-500/10 border-blue-500/30', stress_field: 'text-red-400 bg-red-500/10 border-red-500/30', modal: 'text-purple-400 bg-purple-500/10 border-purple-500/30', thermal: 'text-orange-400 bg-orange-500/10 border-orange-500/30', design_space: 'text-green-400 bg-green-500/10 border-green-500/30' }; export default function Insights() { const navigate = useNavigate(); const { selectedStudy, isInitialized } = useStudy(); const [availableInsights, setAvailableInsights] = useState([]); const [generatedFiles, setGeneratedFiles] = useState([]); const [loading, setLoading] = useState(true); const [generating, setGenerating] = useState(null); const [error, setError] = useState(null); // Active insight for display const [activeInsight, setActiveInsight] = useState(null); const [fullscreen, setFullscreen] = useState(false); // Redirect if no study useEffect(() => { if (isInitialized && !selectedStudy) { navigate('/'); } }, [selectedStudy, navigate, isInitialized]); // Load available insights and generated files const loadInsights = useCallback(async () => { if (!selectedStudy) return; setLoading(true); setError(null); try { // Load available insights const availRes = await fetch(`/api/insights/studies/${selectedStudy.id}/insights/available`); const availData = await availRes.json(); setAvailableInsights(availData.insights || []); // Load previously generated files const genRes = await fetch(`/api/insights/studies/${selectedStudy.id}/insights/generated`); const genData = await genRes.json(); setGeneratedFiles(genData.files || []); } catch (err) { console.error('Failed to load insights:', err); setError('Failed to load insights data'); } finally { setLoading(false); } }, [selectedStudy]); useEffect(() => { loadInsights(); }, [loadInsights]); // Generate an insight const handleGenerate = async (insightType: string) => { if (!selectedStudy || generating) return; setGenerating(insightType); setError(null); try { const res = await fetch( `/api/insights/studies/${selectedStudy.id}/insights/generate/${insightType}`, { method: 'POST' } ); if (!res.ok) { const errData = await res.json(); throw new Error(errData.detail || 'Generation failed'); } const result: InsightResult = await res.json(); setActiveInsight(result); // Refresh file list loadInsights(); } catch (err: any) { setError(err.message || 'Failed to generate insight'); } finally { setGenerating(null); } }; // View existing insight const handleViewExisting = async (file: GeneratedFile) => { if (!selectedStudy) return; setGenerating(file.insight_type); try { const res = await fetch( `/api/insights/studies/${selectedStudy.id}/insights/generate/${file.insight_type}`, { method: 'POST' } ); if (!res.ok) { throw new Error('Failed to load insight'); } const result: InsightResult = await res.json(); setActiveInsight(result); } catch (err: any) { setError(err.message || 'Failed to load insight'); } finally { setGenerating(null); } }; // Open HTML in new tab const handleOpenHtml = (file: GeneratedFile) => { if (!selectedStudy) return; window.open(`/api/insights/studies/${selectedStudy.id}/insights/view/${file.insight_type}`, '_blank'); }; const getIcon = (type: string) => { const Icon = INSIGHT_ICONS[type] || Eye; return Icon; }; const getColorClass = (type: string) => { return INSIGHT_COLORS[type] || 'text-gray-400 bg-gray-500/10 border-gray-500/30'; }; if (!isInitialized || !selectedStudy) { return (

Loading...

); } return (
{/* Header */}

Insights

Physics visualizations for {selectedStudy.name || selectedStudy.id}

{/* Error Banner */} {error && (

{error}

)}
{/* Left Panel: Available Insights */}
{loading ? (
Loading...
) : availableInsights.length === 0 ? (

No insights available for this study.

Run some trials first.

) : (
{availableInsights.map((insight) => { const Icon = getIcon(insight.type); const colorClass = getColorClass(insight.type); const isGenerating = generating === insight.type; return (

{insight.name}

{insight.description}

); })}
)}
{/* Previously Generated */} {generatedFiles.length > 0 && (
{generatedFiles.map((file) => { const Icon = getIcon(file.insight_type); return (

{file.insight_type}

{file.size_kb} KB

); })}
)}
{/* Right Panel: Visualization */}
{activeInsight ? ( {activeInsight.insight_type.replace('_', ' ').toUpperCase()}
} className="h-full" > {/* Summary Stats */} {activeInsight.summary && Object.keys(activeInsight.summary).length > 0 && (
{Object.entries(activeInsight.summary).slice(0, 8).map(([key, value]) => (
{key.replace(/_/g, ' ')}
{typeof value === 'number' ? value.toExponential ? value.toExponential(3) : value : String(value) }
))}
)} {/* Plotly Figure */} {activeInsight.plotly_figure ? (
) : (

No visualization data available

)} ) : (

No Insight Selected

Select an insight type from the left panel to generate a visualization.

)}
{/* Fullscreen Modal */} {fullscreen && activeInsight?.plotly_figure && (

{activeInsight.insight_type.replace('_', ' ').toUpperCase()}

)} ); }