/** * InboxStudyCard - Card displaying an inbox study with actions * * Shows study status, files, and provides actions for: * - Running introspection * - Generating README * - Finalizing the study */ import React, { useState, useEffect } from 'react'; import { FileText, Folder, Trash2, Play, CheckCircle, Clock, AlertCircle, Loader2, ChevronDown, ChevronRight, Sparkles, ArrowRight, Eye, Save, } from 'lucide-react'; import { InboxStudy, SpecStatus, ExpressionInfo, InboxStudyDetail } from '../../types/intake'; import { intakeApi } from '../../api/intake'; import { FileDropzone } from './FileDropzone'; import { ContextFileUpload } from './ContextFileUpload'; import { ExpressionList } from './ExpressionList'; interface InboxStudyCardProps { study: InboxStudy; onRefresh: () => void; onSelect: (studyName: string) => void; } const statusConfig: Record = { draft: { icon: , color: 'text-dark-400 bg-dark-600', label: 'Draft', }, introspected: { icon: , color: 'text-blue-400 bg-blue-500/10', label: 'Introspected', }, configured: { icon: , color: 'text-green-400 bg-green-500/10', label: 'Configured', }, validated: { icon: , color: 'text-green-400 bg-green-500/10', label: 'Validated', }, ready: { icon: , color: 'text-primary-400 bg-primary-500/10', label: 'Ready', }, running: { icon: , color: 'text-yellow-400 bg-yellow-500/10', label: 'Running', }, completed: { icon: , color: 'text-green-400 bg-green-500/10', label: 'Completed', }, failed: { icon: , color: 'text-red-400 bg-red-500/10', label: 'Failed', }, }; export const InboxStudyCard: React.FC = ({ study, onRefresh, onSelect, }) => { const [isExpanded, setIsExpanded] = useState(false); const [isIntrospecting, setIsIntrospecting] = useState(false); const [isGeneratingReadme, setIsGeneratingReadme] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [error, setError] = useState(null); // Introspection data (fetched when expanded) const [studyDetail, setStudyDetail] = useState(null); const [isLoadingDetail, setIsLoadingDetail] = useState(false); const [selectedExpressions, setSelectedExpressions] = useState([]); const [showReadme, setShowReadme] = useState(false); const [readmeContent, setReadmeContent] = useState(null); const [isSavingDVs, setIsSavingDVs] = useState(false); const [dvSaveMessage, setDvSaveMessage] = useState(null); const status = statusConfig[study.status] || statusConfig.draft; // Fetch study details when expanded for the first time useEffect(() => { if (isExpanded && !studyDetail && !isLoadingDetail) { loadStudyDetail(); } }, [isExpanded]); const loadStudyDetail = async () => { setIsLoadingDetail(true); try { const detail = await intakeApi.getInboxStudy(study.study_name); setStudyDetail(detail); // Auto-select candidate expressions const introspection = detail.spec?.model?.introspection; if (introspection?.expressions) { const candidates = introspection.expressions .filter((e: ExpressionInfo) => e.is_candidate) .map((e: ExpressionInfo) => e.name); setSelectedExpressions(candidates); } } catch (err) { console.error('Failed to load study detail:', err); } finally { setIsLoadingDetail(false); } }; const handleIntrospect = async () => { setIsIntrospecting(true); setError(null); try { await intakeApi.introspect({ study_name: study.study_name }); // Reload study detail to get new introspection data await loadStudyDetail(); onRefresh(); } catch (err) { setError(err instanceof Error ? err.message : 'Introspection failed'); } finally { setIsIntrospecting(false); } }; const handleGenerateReadme = async () => { setIsGeneratingReadme(true); setError(null); try { const response = await intakeApi.generateReadme(study.study_name); setReadmeContent(response.content); setShowReadme(true); onRefresh(); } catch (err) { setError(err instanceof Error ? err.message : 'README generation failed'); } finally { setIsGeneratingReadme(false); } }; const handleDelete = async () => { if (!confirm(`Delete inbox study "${study.study_name}"? This cannot be undone.`)) { return; } setIsDeleting(true); try { await intakeApi.deleteInboxStudy(study.study_name); onRefresh(); } catch (err) { setError(err instanceof Error ? err.message : 'Delete failed'); setIsDeleting(false); } }; const handleSaveDesignVariables = async () => { if (selectedExpressions.length === 0) { setError('Please select at least one expression to use as a design variable'); return; } setIsSavingDVs(true); setError(null); setDvSaveMessage(null); try { const result = await intakeApi.createDesignVariables(study.study_name, selectedExpressions); setDvSaveMessage(`Created ${result.total_created} design variable(s)`); // Reload study detail to see updated spec await loadStudyDetail(); onRefresh(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to save design variables'); } finally { setIsSavingDVs(false); } }; const canIntrospect = study.status === 'draft' && study.model_files.length > 0; const canGenerateReadme = study.status === 'introspected'; const canFinalize = ['introspected', 'configured'].includes(study.status); const canSaveDVs = study.status === 'introspected' && selectedExpressions.length > 0; return (
{/* Header - Always visible */} {/* Expanded Content */} {isExpanded && (
{/* Error Display */} {error && (
{error}
)} {/* Success Message */} {dvSaveMessage && (
{dvSaveMessage}
)} {/* Files Section */} {study.model_files.length > 0 && (
Model Files
{study.model_files.map((file) => ( {file} ))}
)} {/* Model File Upload Section */}
Upload Model Files
{/* Context File Upload Section */} {/* Introspection Results - Expressions */} {isLoadingDetail && (
Loading introspection data...
)} {studyDetail?.spec?.model?.introspection?.expressions && studyDetail.spec.model.introspection.expressions.length > 0 && ( )} {/* README Preview Section */} {(readmeContent || study.status === 'configured') && (
README.md
{showReadme && readmeContent && (
                    {readmeContent}
                  
)}
)} {/* No Files Warning */} {study.model_files.length === 0 && (
No model files found. Upload .prt, .sim, or .fem files to continue.
)} {/* Actions */}
{/* Introspect */} {canIntrospect && ( )} {/* Save Design Variables */} {canSaveDVs && ( )} {/* Generate README */} {canGenerateReadme && ( )} {/* Finalize */} {canFinalize && ( )} {/* Delete */}
{/* Workflow Hint */} {study.status === 'draft' && study.model_files.length > 0 && (

Next step: Run introspection to discover expressions and model properties.

)} {study.status === 'introspected' && (

Next step: Generate README with Claude AI, then finalize to create the study.

)}
)}
); }; export default InboxStudyCard;