File size: 3,748 Bytes
900a32d e4daa3b 986fe1a 900a32d 722753e 900a32d e4daa3b 900a32d e4daa3b 900a32d e4daa3b 722753e 900a32d 722753e e4daa3b 900a32d e4daa3b 722753e e4daa3b 722753e e4daa3b 722753e 900a32d e4daa3b 900a32d e4daa3b 986fe1a 900a32d 986fe1a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
import { useState } from "react";
import { Layout } from "./components/Layout";
import { CaseSelector } from "./components/CaseSelector";
import { NiiVueViewer } from "./components/NiiVueViewer";
import { MetricsPanel } from "./components/MetricsPanel";
import { ProgressIndicator } from "./components/ProgressIndicator";
import { ErrorBoundary } from "./components/ErrorBoundary";
import { useSegmentation } from "./hooks/useSegmentation";
function AppContent() {
const [selectedCase, setSelectedCase] = useState<string | null>(null);
const {
result,
isLoading,
error,
jobStatus,
progress,
progressMessage,
elapsedSeconds,
runSegmentation,
cancelJob,
} = useSegmentation();
const handleRunSegmentation = async () => {
if (selectedCase) {
await runSegmentation(selectedCase);
}
};
// Show progress indicator when job is active
const showProgress = isLoading && jobStatus && jobStatus !== "completed";
return (
<Layout>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Panel: Controls */}
<div className="space-y-4">
<CaseSelector
selectedCase={selectedCase}
onSelectCase={setSelectedCase}
/>
<button
onClick={handleRunSegmentation}
disabled={!selectedCase || isLoading}
className="w-full bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600
disabled:cursor-not-allowed text-white font-medium
py-3 px-4 rounded-lg transition-colors"
>
{isLoading ? "Processing..." : "Run Segmentation"}
</button>
{/* Cancel button when processing */}
{isLoading && (
<button
onClick={cancelJob}
className="w-full bg-gray-700 hover:bg-gray-600 text-gray-300
font-medium py-2 px-4 rounded-lg transition-colors text-sm"
>
Cancel
</button>
)}
{/* Progress indicator */}
{showProgress && (
<ProgressIndicator
progress={progress}
message={progressMessage}
status={jobStatus}
elapsedSeconds={elapsedSeconds}
/>
)}
{/* Error display */}
{error && !isLoading && (
<div
role="alert"
className="bg-red-900/50 text-red-300 p-3 rounded-lg text-sm"
>
<p className="font-medium">Error</p>
<p className="mt-1">{error}</p>
</div>
)}
{/* Results metrics */}
{result && <MetricsPanel metrics={result.metrics} />}
</div>
{/* Right Panel: Viewer */}
<div className="lg:col-span-2">
{result ? (
<NiiVueViewer
backgroundUrl={result.dwiUrl}
overlayUrl={result.predictionUrl}
/>
) : (
<div className="bg-gray-900 rounded-lg h-[500px] flex items-center justify-center">
<p className="text-gray-400">
{isLoading
? "Processing segmentation..."
: "Select a case and run segmentation to view results"}
</p>
</div>
)}
</div>
</div>
</Layout>
);
}
/**
* Main App component wrapped in ErrorBoundary.
*
* The ErrorBoundary catches React rendering errors (e.g., WebGL failures
* in NiiVueViewer) and displays a user-friendly error message instead
* of crashing to a white screen.
*/
export default function App() {
return (
<ErrorBoundary>
<AppContent />
</ErrorBoundary>
);
}
|