malavikapradeep2001 commited on
Commit
09a360c
·
unverified ·
1 Parent(s): b970079

Update frontend/src/components/ResultsPanel.tsx

Browse files
Files changed (1) hide show
  1. frontend/src/components/ResultsPanel.tsx +192 -150
frontend/src/components/ResultsPanel.tsx CHANGED
@@ -1,183 +1,225 @@
1
- import { useState, useEffect } from "react";
2
- import { DownloadIcon, InfoIcon, Loader2Icon } from "lucide-react";
 
 
 
3
 
4
  interface ResultsPanelProps {
5
- uploadedImage: string | null;
6
- result?: any;
7
- loading?: boolean;
8
  }
9
 
10
  export function ResultsPanel({ uploadedImage, result, loading }: ResultsPanelProps) {
11
- const [isModalOpen, setIsModalOpen] = useState(false);
12
- const [modalImage, setModalImage] = useState<string | null>(null);
13
-
14
- useEffect(() => {
15
- if (result?.annotated_image_url) {
16
- setModalImage(result.annotated_image_url);
17
- } else if (uploadedImage) {
18
- setModalImage(uploadedImage);
19
- } else {
20
- setModalImage(null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
- }, [result, uploadedImage]);
23
 
24
- // Loading state UI
25
  if (loading) {
26
- return (
27
- <div className="bg-white rounded-lg shadow-sm p-8 flex flex-col items-center justify-center">
28
- <Loader2Icon className="w-12 h-12 text-transparent bg-clip-text bg-gradient-to-r from-blue-800 to-teal-600 animate-spin mb-4" />
29
- <p className="text-gray-700 font-medium mb-2">Analyzing image... Please wait</p>
30
- <div className="w-56 h-2 rounded-full overflow-hidden mt-2">
31
- <div className="h-full bg-gradient-to-r from-blue-800 to-teal-600 animate-pulse" />
32
- </div>
33
  </div>
34
  );
35
  }
36
 
37
  if (!result) {
38
- return (
39
  <div className="bg-white rounded-lg shadow-sm p-6 text-center text-gray-500">
40
- No analysis result available yet.
41
  </div>
42
  );
43
- }
44
-
45
- const {
46
- prediction,
47
- confidence,
48
- probabilities,
49
- detections,
50
- summary,
51
- annotated_image_url,
52
- model_name,
53
- analysis_type,
54
- } = result;
55
-
56
- const handleDownload = () => {
57
- if (annotated_image_url) {
58
- const link = document.createElement("a");
59
- link.href = annotated_image_url;
60
- link.download = "analysis_result.jpg";
61
- link.click();
62
- }
63
- };
64
-
65
- const openModal = (url?: string) => {
66
- setModalImage(url || annotated_image_url || uploadedImage);
67
- setIsModalOpen(true);
68
- };
69
-
70
- const closeModal = () => {
71
- setIsModalOpen(false);
72
- };
73
 
74
- return (
75
- <div className="bg-white rounded-lg shadow-sm p-6">
76
- {/* Header */}
77
- <div className="flex items-center justify-between mb-6">
78
- <div>
79
- <h2 className="text-2xl font-bold text-gray-800">
80
- {model_name ? model_name.toUpperCase() : "Analysis Result"}
81
- </h2>
82
- <p className="text-sm text-gray-500 capitalize">{analysis_type || "Test Type"}</p>
83
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- {annotated_image_url && (
86
- <button
87
- onClick={handleDownload}
88
- className="flex items-center gap-2 bg-gradient-to-r from-blue-800 to-teal-600 text-white px-4 py-2 rounded-lg hover:opacity-90 transition-all"
89
- >
90
- <DownloadIcon className="w-4 h-4" />
91
- Download Image
92
- </button>
93
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
- {/* Image (click to zoom) */}
97
- <div className="relative mb-6 rounded-lg overflow-hidden border border-gray-200">
98
- <img
99
- src={annotated_image_url || uploadedImage || "/ui.jpg"}
100
- alt="Analysis Result"
101
- className="w-full h-64 object-cover cursor-pointer transition-transform hover:scale-105"
102
- onClick={() => openModal()}
103
- />
104
- </div>
105
 
106
- {/* Results Summary */}
107
- <div className="mb-6">
108
- {prediction && (
109
- <h3
110
- className={`text-3xl font-bold ${
111
- prediction.toLowerCase().includes("malignant") || prediction.toLowerCase().includes("abnormal")
112
- ? "text-red-600"
113
- : "text-green-600"
114
- }`}
115
- >
116
- {prediction}
117
- </h3>
118
- )}
119
-
120
- {confidence && (
121
- <div className="mt-2">
122
  <div className="flex items-center justify-between mb-1">
123
- <span className="font-semibold text-gray-900">Confidence: {(confidence * 100).toFixed(2)}%</span>
124
- <InfoIcon className="w-4 h-4 text-gray-400" />
 
 
125
  </div>
126
- <div className="w-full h-3 bg-gray-200 rounded-full overflow-hidden">
127
  <div
128
- className={`h-full ${
129
- confidence > 0.7 ? "bg-green-500" : confidence > 0.4 ? "bg-yellow-500" : "bg-red-500"
130
- }`}
131
- style={{ width: `${confidence * 100}%` }}
132
  />
133
  </div>
134
  </div>
135
- )}
136
-
137
- {summary && (
138
- <p className="mt-4 text-gray-700 text-sm leading-relaxed">{summary}</p>
139
- )}
140
- </div>
141
-
142
- {/* Detections / Probabilities */}
143
- {detections && detections.length > 0 && (
144
- <div className="mb-6">
145
- <h4 className="font-semibold text-gray-900 mb-3">Detected Regions:</h4>
146
- <ul className="text-sm text-gray-700 list-disc list-inside space-y-1">
147
- {detections.map((det: any, i: number) => (
148
- <li key={i}>{det.name || "object"} – {(det.confidence * 100).toFixed(1)}%</li>
149
- ))}
150
- </ul>
151
- </div>
152
- )}
153
 
154
- {probabilities && (
155
- <div className="mb-6">
156
- <h4 className="font-semibold text-gray-900 mb-3">Class Probabilities:</h4>
157
- <pre className="bg-gray-100 rounded-lg p-3 text-sm">{JSON.stringify(probabilities, null, 2)}</pre>
158
- </div>
159
- )}
160
-
161
- {/* Report Button */}
162
- <button className="w-full bg-gradient-to-r from-blue-800 to-teal-600 text-white py-3 rounded-lg font-medium hover:opacity-95 transition-colors flex items-center justify-center gap-2">
163
- <DownloadIcon className="w-5 h-5" />
164
- Generate Report
165
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- {/* Modal for zoom */}
168
- {isModalOpen && modalImage && (
169
- <div
170
- className="fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4"
171
- onClick={closeModal}
172
- >
173
- <div className="max-w-5xl max-h-[90vh] w-full" onClick={(e) => e.stopPropagation()}>
174
- <img src={modalImage} alt="Zoomed result" className="w-full h-auto object-contain rounded-lg shadow-2xl" />
175
- <div className="mt-3 text-right">
176
- <button onClick={closeModal} className="px-4 py-2 bg-white rounded-md">Close</button>
177
- </div>
178
  </div>
179
  </div>
 
 
 
 
 
180
  )}
181
  </div>
182
- );
183
- }
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import { DownloadIcon, FileTextIcon, Loader2Icon } from "lucide-react";
3
+ import { ImageWithFallback } from "./ImageWithFallback";
4
+ import { ReportModal } from "./ReportModal";
5
+ import axios from "axios";
6
 
7
  interface ResultsPanelProps {
8
+ uploadedImage: string | null;
9
+ result?: any;
10
+ loading?: boolean;
11
  }
12
 
13
  export function ResultsPanel({ uploadedImage, result, loading }: ResultsPanelProps) {
14
+ const [showReportModal, setShowReportModal] = useState(false);
15
+
16
+ const handleGenerateReport = async (formData: FormData) => {
17
+ try {
18
+ const baseURL = import.meta.env.MODE === "development"
19
+ ? "http://127.0.0.1:7860"
20
+ : window.location.origin;
21
+
22
+ const response = await axios.post(`${baseURL}/reports/`, formData, {
23
+ headers: { "Content-Type": "multipart/form-data" },
24
+ });
25
+
26
+ if (response.data.html_url) {
27
+ // Open report in new tab
28
+ window.open(`${baseURL}${response.data.html_url}`, "_blank");
29
+ }
30
+ if (response.data.pdf_url) {
31
+ // Open PDF in new tab when available
32
+ window.open(`${baseURL}${response.data.pdf_url}`, "_blank");
33
+ }
34
+
35
+ setShowReportModal(false);
36
+ } catch (err: any) {
37
+ console.error("Failed to generate report:", err);
38
+ alert(err.response?.data?.error || "Failed to generate report");
39
  }
40
+ };
41
 
 
42
  if (loading) {
43
+ return (
44
+ <div className="bg-white rounded-lg shadow-sm p-6 flex flex-col items-center justify-center">
45
+ <Loader2Icon className="w-10 h-10 text-blue-600 animate-spin mb-3" />
46
+ <p className="text-gray-600 font-medium">Analyzing image...</p>
 
 
 
47
  </div>
48
  );
49
  }
50
 
51
  if (!result) {
52
+ return (
53
  <div className="bg-white rounded-lg shadow-sm p-6 text-center text-gray-500">
54
+ No analysis result available yet.
55
  </div>
56
  );
57
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
+ const {
60
+ model_used,
61
+ detections,
62
+ annotated_image_url,
63
+ summary,
64
+ // prediction (not used here)
65
+ confidence,
66
+ } = result;
67
+
68
+ const handleDownload = () => {
69
+ if (annotated_image_url) {
70
+ const link = document.createElement("a");
71
+ link.href = annotated_image_url;
72
+ link.download = "analysis_result.jpg";
73
+ link.click();
74
+ }
75
+ };
76
+
77
+ return ( <div className="bg-white rounded-lg shadow-sm p-6">
78
+ {/* Header */}
79
+ <div className="flex items-center justify-between mb-6">
80
+ <div>
81
+ <h2 className="text-2xl font-bold text-gray-800">
82
+ {model_used || "Analysis Result"}
83
+ </h2>
84
+ <p className="text-sm text-gray-500">Automated Image Analysis</p>
85
+ </div>
86
 
87
+ <div className="flex items-center gap-3">
88
+ {annotated_image_url && (
89
+ <button
90
+ onClick={handleDownload}
91
+ className="flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors"
92
+ >
93
+ <DownloadIcon className="w-4 h-4" />
94
+ Download Image
95
+ </button>
96
+ )}
97
+ <button
98
+ onClick={() => setShowReportModal(true)}
99
+ className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
100
+ >
101
+ <FileTextIcon className="w-4 h-4" />
102
+ Generate Report
103
+ </button>
104
+ </div>
105
+ </div>
106
+
107
+ {/* Image */}
108
+ <div className="relative mb-6 rounded-lg overflow-hidden border border-gray-200">
109
+ <ImageWithFallback
110
+ src={annotated_image_url || uploadedImage || "/ui.jpg"}
111
+ alt="Analysis Result"
112
+ className="w-full h-64 object-cover"
113
+ />
114
+ </div>
115
+
116
+ {/* Summary Section */}
117
+ {summary && (
118
+ <div className="bg-gray-50 p-4 rounded-lg mb-6">
119
+ <h3 className="text-lg font-semibold text-gray-800 mb-2">
120
+ AI Summary
121
+ </h3>
122
+ <p className="text-gray-700 text-sm leading-relaxed">
123
+ <strong>Abnormal Cells:</strong> {summary.abnormal_cells} <br />
124
+ <strong>Normal Cells:</strong> {summary.normal_cells} <br />
125
+ <strong>Average Confidence:</strong> {summary.avg_confidence?.toFixed(2)}% <br />
126
+ </p>
127
+ <div className="mt-3 text-gray-800 text-sm italic border-t pt-2">
128
+ {summary.ai_interpretation || "No AI interpretation available."}
129
  </div>
130
+ </div>
131
+ )}
132
+
133
+ {/* Detection list */}
134
+ {detections && detections.length > 0 && (
135
+ <div className="mb-6">
136
+ <h4 className="font-semibold text-gray-900 mb-3">
137
+ Detected Objects
138
+ </h4>
139
+ <ul className="text-sm text-gray-700 list-disc list-inside space-y-1">
140
+ {detections.map((det: any, i: number) => (
141
+ <li key={i}>
142
+ {det.name || "Object"} – {(det.confidence * 100).toFixed(1)}%
143
+ </li>
144
+ ))}
145
+ </ul>
146
+ </div>
147
+ )}
148
 
149
+ {/* Probability / MWT visualization */}
150
+ {confidence && (
151
+ <div className="mb-6">
152
+ <h4 className="font-semibold text-gray-900 mb-3">Confidence Levels</h4>
 
 
 
 
 
153
 
154
+ {/* If MWT, CIN, or Histopathology classifier, show a visual bar for average confidence and per-class bars */}
155
+ {model_used && /mwt|cin|histopathology/i.test(model_used) ? (
156
+ <div>
157
+ {/* Average confidence bar */}
158
+ <div className="mb-3">
 
 
 
 
 
 
 
 
 
 
 
159
  <div className="flex items-center justify-between mb-1">
160
+ <span className="text-sm font-medium text-gray-700">Average confidence</span>
161
+ <span className="text-sm font-mono text-gray-600">
162
+ {summary?.avg_confidence ? `${summary.avg_confidence.toFixed(2)}%` : "-"}
163
+ </span>
164
  </div>
165
+ <div className="w-full bg-gray-200 rounded-full h-4 overflow-hidden">
166
  <div
167
+ className="h-4 bg-gradient-to-r from-amber-600 to-amber-400"
168
+ style={{ width: `${summary?.avg_confidence ?? 0}%` }}
 
 
169
  />
170
  </div>
171
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
+ {/* Per-class bars */}
174
+ <div className="space-y-2">
175
+ {Object.entries(confidence).map(([cls, val]) => {
176
+ const num = Number(val as any) || 0;
177
+ const pct = (num * 100);
178
+
179
+ // Color coding: Positive/Malignant/High-grade = red, Negative/Benign/Low-grade = green
180
+ const isNegative = cls.toLowerCase().includes("negative") ||
181
+ cls.toLowerCase().includes("benign") ||
182
+ cls.toLowerCase().includes("low-grade");
183
+
184
+ return (
185
+ <div key={cls}>
186
+ <div className="flex items-center justify-between text-sm mb-1">
187
+ <span className="text-gray-700">{cls}</span>
188
+ <span className="text-gray-600">{pct.toFixed(2)}%</span>
189
+ </div>
190
+ <div className="w-full bg-gray-100 rounded-full h-3">
191
+ <div
192
+ className={`h-3 rounded-full ${isNegative ? "bg-green-500" : "bg-red-500"}`}
193
+ style={{ width: `${pct.toFixed(2)}%` }}
194
+ />
195
+ </div>
196
+ </div>
197
+ );
198
+ })}
199
+ </div>
200
 
201
+ {/* Mistral comment */}
202
+ <div className="mt-4 bg-gray-50 p-3 rounded-lg text-sm italic text-gray-800 border-t">
203
+ {summary?.ai_interpretation || "No AI interpretation available."}
 
 
 
 
 
 
 
 
204
  </div>
205
  </div>
206
+ ) : (
207
+ // Fallback display for non-MWT models
208
+ <pre className="bg-gray-100 rounded-lg p-3 text-sm overflow-x-auto">
209
+ {JSON.stringify(confidence, null, 2)}
210
+ </pre>
211
  )}
212
  </div>
213
+ )}
214
+
215
+ {/* Report Generation Modal */}
216
+ <ReportModal
217
+ isOpen={showReportModal}
218
+ onClose={() => setShowReportModal(false)}
219
+ onSubmit={handleGenerateReport}
220
+ analysisId={annotated_image_url || ""}
221
+ analysisSummaryJson={summary ? JSON.stringify({ ...summary, model_used, confidence }) : "{}"}
222
+ />
223
+ </div>
224
+ );
225
+ }