| import { useState } from "react"; |
| import { Navigation } from "@/components/Navigation"; |
| import { UploadZone } from "@/components/UploadZone"; |
| import { ResultsDisplay } from "@/components/ResultsDisplay"; |
| import { AboutPage } from "@/components/AboutPage"; |
| import { Heart, Sparkles } from "lucide-react"; |
| import { useToast } from "@/hooks/use-toast"; |
|
|
| const Index = () => { |
| const [activeTab, setActiveTab] = useState<"upload" | "about">("upload"); |
| const [isLoading, setIsLoading] = useState(false); |
| const [results, setResults] = useState<typeof mockResult | null>(null); |
| const { toast } = useToast(); |
|
|
| const handleUpload = async (file: File) => { |
| setIsLoading(true); |
| setResults(null); |
|
|
| |
| try { |
| const formData = new FormData(); |
| formData.append("video", file); |
|
|
| const response = await fetch("/api/analyze", { |
| method: "POST", |
| body: formData, |
| }); |
|
|
| if (!response.ok) { |
| const msg = await response.text(); |
| throw new Error(msg || "Analysis failed"); |
| } |
|
|
| const data = await response.json(); |
| setResults({ |
| ejectionFraction: data.ejectionFraction, |
| heartFunction: data.heartFunction, |
| }); |
|
|
|
|
| toast({ |
| title: "Analysis Complete", |
| description: "Your echocardiography video has been analyzed successfully.", |
| }); |
| } catch (error) { |
| toast({ |
| title: "Analysis Failed", |
| description: "There was an error analyzing your video. Please try again.", |
| variant: "destructive", |
| }); |
| } finally { |
| setIsLoading(false); |
| } |
| }; |
|
|
| return ( |
| <div className="min-h-screen" style={{ background: "var(--gradient-hero)" }}> |
| <Navigation activeTab={activeTab} setActiveTab={setActiveTab} /> |
| |
| <main className="container mx-auto px-4 py-8"> |
| {activeTab === "upload" ? ( |
| <div className="space-y-8"> |
| {/* Hero Section */} |
| <div className="text-center animate-fade-in"> |
| <div className="mb-4 inline-flex items-center gap-2 rounded-full border border-primary/20 bg-primary/5 px-4 py-1.5 text-sm text-primary"> |
| <Sparkles className="h-4 w-4" /> |
| AI-Powered Cardiac Analysis |
| </div> |
| <h1 className="mb-4 font-display text-4xl font-bold tracking-tight sm:text-5xl"> |
| Analyze Your{" "} |
| <span className="gradient-text">Echocardiography</span> |
| </h1> |
| <p className="mx-auto max-w-2xl text-lg text-muted-foreground"> |
| Upload your echocardiography video and let our AI model automatically |
| extract key cardiac measurements and assess heart function. |
| </p> |
| </div> |
| |
| {/* Upload Zone */} |
| <UploadZone onUpload={handleUpload} isLoading={isLoading} /> |
| |
| {/* Results */} |
| {results && <ResultsDisplay results={results} />} |
| |
| {/* Features */} |
| {!results && !isLoading && ( |
| <div className="mx-auto max-w-2xl animate-fade-in" style={{ animationDelay: "200ms" }}> |
| <div className="grid gap-6 sm:grid-cols-2"> |
| <div className="flex items-start gap-3 rounded-xl border border-border/50 bg-card/50 p-4"> |
| <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10"> |
| <Heart className="h-5 w-5 text-primary" /> |
| </div> |
| <div> |
| <h3 className="font-medium">Ejection Fraction</h3> |
| <p className="text-sm text-muted-foreground"> |
| Accurate EF calculation for cardiac assessment |
| </p> |
| </div> |
| </div> |
| <div className="flex items-start gap-3 rounded-xl border border-border/50 bg-card/50 p-4"> |
| <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10"> |
| <Heart className="h-5 w-5 text-primary" /> |
| </div> |
| <div> |
| <h3 className="font-medium">Function Status</h3> |
| <p className="text-sm text-muted-foreground"> |
| AI-based heart function classification |
| </p> |
| </div> |
| </div> |
| </div> |
| </div> |
| )} |
| </div> |
| ) : ( |
| <AboutPage /> |
| )} |
| </main> |
| </div> |
| ); |
| }; |
|
|
| export default Index; |
|
|