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>
  );
}