Dashboard: - Add Studio page with drag-drop model upload and Claude chat - Add intake system for study creation workflow - Improve session manager and context builder - Add intake API routes and frontend components Optimization Engine: - Add CLI module for command-line operations - Add intake module for study preprocessing - Add validation module with gate checks - Improve Zernike extractor documentation - Update spec models with better validation - Enhance solve_simulation robustness Documentation: - Add ATOMIZER_STUDIO.md planning doc - Add ATOMIZER_UX_SYSTEM.md for UX patterns - Update extractor library docs - Add study-readme-generator skill Tools: - Add test scripts for extraction validation - Add Zernike recentering test Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
148 lines
4.7 KiB
TypeScript
148 lines
4.7 KiB
TypeScript
/**
|
|
* InboxSection - Section displaying inbox studies on Home page
|
|
*
|
|
* Shows the "Create New Study" card and lists all inbox studies
|
|
* with their current status and available actions.
|
|
*/
|
|
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { Inbox, RefreshCw, ChevronDown, ChevronRight } from 'lucide-react';
|
|
import { intakeApi } from '../../api/intake';
|
|
import { InboxStudy, TopicInfo } from '../../types/intake';
|
|
import { CreateStudyCard } from './CreateStudyCard';
|
|
import { InboxStudyCard } from './InboxStudyCard';
|
|
import { FinalizeModal } from './FinalizeModal';
|
|
|
|
interface InboxSectionProps {
|
|
onStudyFinalized?: () => void;
|
|
}
|
|
|
|
export const InboxSection: React.FC<InboxSectionProps> = ({ onStudyFinalized }) => {
|
|
const [inboxStudies, setInboxStudies] = useState<InboxStudy[]>([]);
|
|
const [topics, setTopics] = useState<TopicInfo[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isExpanded, setIsExpanded] = useState(true);
|
|
const [selectedStudyForFinalize, setSelectedStudyForFinalize] = useState<string | null>(null);
|
|
|
|
const loadData = useCallback(async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const [inboxResponse, topicsResponse] = await Promise.all([
|
|
intakeApi.listInbox(),
|
|
intakeApi.listTopics(),
|
|
]);
|
|
setInboxStudies(inboxResponse.studies);
|
|
setTopics(topicsResponse.topics);
|
|
} catch (err) {
|
|
console.error('Failed to load inbox data:', err);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, [loadData]);
|
|
|
|
const handleStudyCreated = (_studyName: string) => {
|
|
loadData();
|
|
};
|
|
|
|
const handleStudyFinalized = (_finalPath: string) => {
|
|
setSelectedStudyForFinalize(null);
|
|
loadData();
|
|
onStudyFinalized?.();
|
|
};
|
|
|
|
const pendingStudies = inboxStudies.filter(
|
|
(s) => !['ready', 'running', 'completed'].includes(s.status)
|
|
);
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Section Header */}
|
|
<button
|
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
className="w-full flex items-center justify-between px-2 py-1 hover:bg-white/5 rounded-lg transition-colors"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 rounded-lg bg-primary-400/10 flex items-center justify-center">
|
|
<Inbox className="w-4 h-4 text-primary-400" />
|
|
</div>
|
|
<div className="text-left">
|
|
<h2 className="text-lg font-semibold text-white">Study Inbox</h2>
|
|
<p className="text-sm text-dark-400">
|
|
{pendingStudies.length} pending studies
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
loadData();
|
|
}}
|
|
className="p-2 hover:bg-white/5 rounded-lg transition-colors text-dark-400 hover:text-primary-400"
|
|
title="Refresh"
|
|
>
|
|
<RefreshCw className={`w-4 h-4 ${isLoading ? 'animate-spin' : ''}`} />
|
|
</button>
|
|
{isExpanded ? (
|
|
<ChevronDown className="w-5 h-5 text-dark-400" />
|
|
) : (
|
|
<ChevronRight className="w-5 h-5 text-dark-400" />
|
|
)}
|
|
</div>
|
|
</button>
|
|
|
|
{/* Content */}
|
|
{isExpanded && (
|
|
<div className="space-y-4">
|
|
{/* Create Study Card */}
|
|
<CreateStudyCard topics={topics} onStudyCreated={handleStudyCreated} />
|
|
|
|
{/* Inbox Studies List */}
|
|
{inboxStudies.length > 0 && (
|
|
<div className="space-y-3">
|
|
<h3 className="text-sm font-medium text-dark-400 px-2">
|
|
Inbox Studies ({inboxStudies.length})
|
|
</h3>
|
|
{inboxStudies.map((study) => (
|
|
<InboxStudyCard
|
|
key={study.study_name}
|
|
study={study}
|
|
onRefresh={loadData}
|
|
onSelect={setSelectedStudyForFinalize}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Empty State */}
|
|
{!isLoading && inboxStudies.length === 0 && (
|
|
<div className="text-center py-8 text-dark-400">
|
|
<Inbox className="w-12 h-12 mx-auto mb-3 opacity-30" />
|
|
<p>No studies in inbox</p>
|
|
<p className="text-sm text-dark-500">
|
|
Create a new study to get started
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Finalize Modal */}
|
|
{selectedStudyForFinalize && (
|
|
<FinalizeModal
|
|
studyName={selectedStudyForFinalize}
|
|
topics={topics}
|
|
onClose={() => setSelectedStudyForFinalize(null)}
|
|
onFinalized={handleStudyFinalized}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default InboxSection;
|