Spaces:
Sleeping
Sleeping
| /** | |
| * Medical Report Analysis Platform - Main Application | |
| * Professional medical-grade interface for AI-powered document analysis | |
| */ | |
| import { useState } from 'react'; | |
| import { FileUpload } from './components/FileUpload'; | |
| import { AnalysisStatus } from './components/AnalysisStatus'; | |
| import { AnalysisResults } from './components/AnalysisResults'; | |
| import { Header } from './components/Header'; | |
| import { ModelInfo } from './components/ModelInfo'; | |
| import './App.css'; | |
| interface JobStatus { | |
| jobId: string; | |
| status: 'idle' | 'uploading' | 'processing' | 'completed' | 'failed'; | |
| progress: number; | |
| message: string; | |
| } | |
| interface AnalysisResult { | |
| job_id: string; | |
| document_type: string; | |
| confidence: number; | |
| analysis: any; | |
| specialized_results: any[]; | |
| summary: string; | |
| timestamp: string; | |
| } | |
| function App() { | |
| const [jobStatus, setJobStatus] = useState<JobStatus>({ | |
| jobId: '', | |
| status: 'idle', | |
| progress: 0, | |
| message: '' | |
| }); | |
| const [analysisResult, setAnalysisResult] = useState<AnalysisResult | null>(null); | |
| const [showModelInfo, setShowModelInfo] = useState(false); | |
| // Use relative URL in production (HuggingFace Spaces), localhost in development | |
| const getApiUrl = () => { | |
| // If VITE_API_URL is explicitly set, use it | |
| if (import.meta.env.VITE_API_URL) { | |
| return import.meta.env.VITE_API_URL; | |
| } | |
| // In production (HuggingFace Spaces), use relative path | |
| if (import.meta.env.PROD) { | |
| return ''; // Relative path - same origin | |
| } | |
| // In development, use localhost | |
| return 'http://localhost:7860'; | |
| }; | |
| const [apiUrl] = useState(getApiUrl()); | |
| const handleFileUpload = async (file: File) => { | |
| try { | |
| setJobStatus({ | |
| jobId: '', | |
| status: 'uploading', | |
| progress: 0, | |
| message: 'Uploading document...' | |
| }); | |
| // Upload file | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| const uploadResponse = await fetch(`${apiUrl}/analyze`, { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!uploadResponse.ok) { | |
| throw new Error('Upload failed'); | |
| } | |
| const uploadData = await uploadResponse.json(); | |
| const jobId = uploadData.job_id; | |
| setJobStatus({ | |
| jobId, | |
| status: 'processing', | |
| progress: uploadData.progress || 0, | |
| message: uploadData.message || 'Analysis started...' | |
| }); | |
| // Poll for status | |
| pollJobStatus(jobId); | |
| } catch (error) { | |
| console.error('Upload error:', error); | |
| setJobStatus({ | |
| jobId: '', | |
| status: 'failed', | |
| progress: 0, | |
| message: error instanceof Error ? error.message : 'Upload failed' | |
| }); | |
| } | |
| }; | |
| const pollJobStatus = async (jobId: string) => { | |
| try { | |
| const statusResponse = await fetch(`${apiUrl}/status/${jobId}`); | |
| if (!statusResponse.ok) { | |
| throw new Error('Status check failed'); | |
| } | |
| const statusData = await statusResponse.json(); | |
| setJobStatus({ | |
| jobId, | |
| status: statusData.status, | |
| progress: statusData.progress || 0, | |
| message: statusData.message || 'Processing...' | |
| }); | |
| if (statusData.status === 'completed') { | |
| // Fetch results | |
| const resultsResponse = await fetch(`${apiUrl}/results/${jobId}`); | |
| if (!resultsResponse.ok) { | |
| throw new Error('Failed to fetch results'); | |
| } | |
| const resultsData = await resultsResponse.json(); | |
| setAnalysisResult(resultsData); | |
| } else if (statusData.status === 'processing') { | |
| // Continue polling | |
| setTimeout(() => pollJobStatus(jobId), 2000); | |
| } else if (statusData.status === 'failed') { | |
| setJobStatus(prev => ({ | |
| ...prev, | |
| status: 'failed', | |
| message: 'Analysis failed. Please try again.' | |
| })); | |
| } | |
| } catch (error) { | |
| console.error('Status polling error:', error); | |
| setJobStatus(prev => ({ | |
| ...prev, | |
| status: 'failed', | |
| message: error instanceof Error ? error.message : 'Status check failed' | |
| })); | |
| } | |
| }; | |
| const handleReset = () => { | |
| setJobStatus({ | |
| jobId: '', | |
| status: 'idle', | |
| progress: 0, | |
| message: '' | |
| }); | |
| setAnalysisResult(null); | |
| }; | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50"> | |
| <Header | |
| onShowModelInfo={() => setShowModelInfo(true)} | |
| onReset={handleReset} | |
| hasActiveAnalysis={jobStatus.status !== 'idle'} | |
| /> | |
| <main className="container mx-auto px-4 py-8 max-w-7xl"> | |
| {/* Hero Section */} | |
| <div className="text-center mb-12"> | |
| <h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4"> | |
| Medical Report Analysis Platform | |
| </h1> | |
| <p className="text-lg md:text-xl text-gray-600 max-w-3xl mx-auto"> | |
| Advanced AI-powered analysis using 50+ specialized medical models across 9 clinical domains | |
| </p> | |
| <div className="mt-4 flex items-center justify-center gap-4 text-sm text-gray-500"> | |
| <div className="flex items-center gap-2"> | |
| <svg className="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| <span>HIPAA Compliant</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <svg className="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| <span>GDPR Compliant</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <svg className="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| <span>FDA Guidance Aligned</span> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Main Content */} | |
| <div className="space-y-8"> | |
| {jobStatus.status === 'idle' && ( | |
| <FileUpload onFileUpload={handleFileUpload} /> | |
| )} | |
| {(jobStatus.status === 'uploading' || jobStatus.status === 'processing') && ( | |
| <AnalysisStatus | |
| status={jobStatus.status} | |
| progress={jobStatus.progress} | |
| message={jobStatus.message} | |
| /> | |
| )} | |
| {jobStatus.status === 'completed' && analysisResult && ( | |
| <AnalysisResults | |
| result={analysisResult} | |
| onReset={handleReset} | |
| /> | |
| )} | |
| {jobStatus.status === 'failed' && ( | |
| <div className="bg-red-50 border border-red-200 rounded-lg p-6 text-center"> | |
| <svg className="w-12 h-12 text-red-500 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| <h3 className="text-xl font-semibold text-red-900 mb-2">Analysis Failed</h3> | |
| <p className="text-red-700 mb-4">{jobStatus.message}</p> | |
| <button | |
| onClick={handleReset} | |
| className="px-6 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors" | |
| > | |
| Try Again | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| {/* Information Cards */} | |
| {jobStatus.status === 'idle' && ( | |
| <div className="grid md:grid-cols-3 gap-6 mt-12"> | |
| <div className="bg-white rounded-lg shadow-md p-6"> | |
| <div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4"> | |
| <svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> | |
| </svg> | |
| </div> | |
| <h3 className="text-lg font-semibold text-gray-900 mb-2">Multi-Format Support</h3> | |
| <p className="text-gray-600"> | |
| Process all types of medical reports: radiology, pathology, lab results, clinical notes, and more | |
| </p> | |
| </div> | |
| <div className="bg-white rounded-lg shadow-md p-6"> | |
| <div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mb-4"> | |
| <svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" /> | |
| </svg> | |
| </div> | |
| <h3 className="text-lg font-semibold text-gray-900 mb-2">Specialized AI Models</h3> | |
| <p className="text-gray-600"> | |
| Leverages 50+ domain-specific models including MedGemma, MONAI, and specialized clinical AI | |
| </p> | |
| </div> | |
| <div className="bg-white rounded-lg shadow-md p-6"> | |
| <div className="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mb-4"> | |
| <svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> | |
| </svg> | |
| </div> | |
| <h3 className="text-lg font-semibold text-gray-900 mb-2">Secure & Compliant</h3> | |
| <p className="text-gray-600"> | |
| Built with medical-grade security, HIPAA compliance, and regulatory alignment (FDA, GDPR) | |
| </p> | |
| </div> | |
| </div> | |
| )} | |
| </main> | |
| {/* Model Info Modal */} | |
| {showModelInfo && ( | |
| <ModelInfo onClose={() => setShowModelInfo(false)} /> | |
| )} | |
| {/* Footer */} | |
| <footer className="mt-16 py-8 border-t border-gray-200"> | |
| <div className="container mx-auto px-4 text-center text-gray-600"> | |
| <p className="text-sm"> | |
| Medical Report Analysis Platform • AI-Powered Clinical Intelligence | |
| </p> | |
| <p className="text-xs mt-2 text-gray-500"> | |
| This platform provides AI-assisted analysis. All results should be reviewed by qualified healthcare professionals. | |
| </p> | |
| </div> | |
| </footer> | |
| </div> | |
| ); | |
| } | |
| export default App; | |