Spaces:
Running
Running
Commit
·
3e369f2
1
Parent(s):
bf5da6b
Initial Space
Browse files
frontend/src/App.tsx
CHANGED
|
@@ -103,12 +103,10 @@ return ( <div className="flex flex-col min-h-screen w-full bg-gray-50"> <Header
|
|
| 103 |
onAnalyze={handleAnalyze}
|
| 104 |
/>
|
| 105 |
|
| 106 |
-
{/* Results Panel */}
|
| 107 |
-
{showResults && (
|
| 108 |
<ResultsPanel
|
| 109 |
-
uploadedImage={
|
| 110 |
-
apiResult?.annotated_image_url || uploadedImage
|
| 111 |
-
}
|
| 112 |
result={apiResult}
|
| 113 |
loading={loading}
|
| 114 |
/>
|
|
|
|
| 103 |
onAnalyze={handleAnalyze}
|
| 104 |
/>
|
| 105 |
|
| 106 |
+
{/* Results Panel (show while loading OR when results ready) */}
|
| 107 |
+
{(loading || showResults) && (
|
| 108 |
<ResultsPanel
|
| 109 |
+
uploadedImage={apiResult?.annotated_image_url || uploadedImage}
|
|
|
|
|
|
|
| 110 |
result={apiResult}
|
| 111 |
loading={loading}
|
| 112 |
/>
|
frontend/src/components/Header.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
export function Header() {
|
| 2 |
return (
|
| 3 |
<div className="w-full bg-white">
|
|
@@ -17,9 +18,11 @@ export function Header() {
|
|
| 17 |
alt="Manalife Logo"
|
| 18 |
className="h-16 w-auto"
|
| 19 |
/>
|
| 20 |
-
<h1 className="text-white text-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
| 23 |
</div>
|
| 24 |
</div>
|
| 25 |
</div>
|
|
|
|
| 1 |
+
|
| 2 |
export function Header() {
|
| 3 |
return (
|
| 4 |
<div className="w-full bg-white">
|
|
|
|
| 18 |
alt="Manalife Logo"
|
| 19 |
className="h-16 w-auto"
|
| 20 |
/>
|
| 21 |
+
<h1 className="text-white text-3xl font-bold">Pathora</h1>
|
| 22 |
+
<div className="h-8 border-l border-white/70" />
|
| 23 |
+
<h3 className="text-white text-1xl font-semibold tracking-wide">
|
| 24 |
+
Manalife's AI Pathology Assistant
|
| 25 |
+
</h3>
|
| 26 |
</div>
|
| 27 |
</div>
|
| 28 |
</div>
|
frontend/src/components/ResultsPanel.tsx
CHANGED
|
@@ -1,143 +1,183 @@
|
|
|
|
|
| 1 |
import { DownloadIcon, InfoIcon, Loader2Icon } from "lucide-react";
|
| 2 |
|
| 3 |
interface ResultsPanelProps {
|
| 4 |
-
uploadedImage: string | null;
|
| 5 |
-
result?: any;
|
| 6 |
-
loading?: boolean;
|
| 7 |
}
|
| 8 |
|
| 9 |
export function ResultsPanel({ uploadedImage, result, loading }: ResultsPanelProps) {
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
const handleDownload = () => {
|
| 33 |
-
if (annotated_image_url) {
|
| 34 |
-
const link = document.createElement("a");
|
| 35 |
-
link.href = annotated_image_url;
|
| 36 |
-
link.download = "analysis_result.jpg";
|
| 37 |
-
link.click();
|
| 38 |
-
}
|
| 39 |
-
};
|
| 40 |
-
|
| 41 |
-
return ( <div className="bg-white rounded-lg shadow-sm p-6">
|
| 42 |
-
{/* Header */} <div className="flex items-center justify-between mb-6"> <div> <h2 className="text-2xl font-bold text-gray-800">
|
| 43 |
-
{model_name ? model_name.toUpperCase() : "Analysis Result"} </h2> <p className="text-sm text-gray-500 capitalize">
|
| 44 |
-
{analysis_type || "Test Type"} </p> </div>
|
| 45 |
-
{annotated_image_url && ( <button
|
| 46 |
-
onClick={handleDownload}
|
| 47 |
-
className="flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors"
|
| 48 |
-
> <DownloadIcon className="w-4 h-4" />
|
| 49 |
-
Download Image </button>
|
| 50 |
-
)} </div>
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
{/* Image */}
|
| 54 |
-
<div className="relative mb-6 rounded-lg overflow-hidden border border-gray-200">
|
| 55 |
-
<img
|
| 56 |
-
src={annotated_image_url || uploadedImage || "/ui.jpg"}
|
| 57 |
-
alt="Analysis Result"
|
| 58 |
-
className="w-full h-64 object-cover"
|
| 59 |
-
/>
|
| 60 |
-
</div>
|
| 61 |
-
|
| 62 |
-
{/* Results Summary */}
|
| 63 |
-
<div className="mb-6">
|
| 64 |
-
{prediction && (
|
| 65 |
-
<h3
|
| 66 |
-
className={`text-3xl font-bold ${
|
| 67 |
-
prediction.toLowerCase().includes("malignant") ||
|
| 68 |
-
prediction.toLowerCase().includes("abnormal")
|
| 69 |
-
? "text-red-600"
|
| 70 |
-
: "text-green-600"
|
| 71 |
-
}`}
|
| 72 |
-
>
|
| 73 |
-
{prediction}
|
| 74 |
-
</h3>
|
| 75 |
-
)}
|
| 76 |
-
|
| 77 |
-
{confidence && (
|
| 78 |
-
<div className="mt-2">
|
| 79 |
-
<div className="flex items-center justify-between mb-1">
|
| 80 |
-
<span className="font-semibold text-gray-900">
|
| 81 |
-
Confidence: {(confidence * 100).toFixed(2)}%
|
| 82 |
-
</span>
|
| 83 |
-
<InfoIcon className="w-4 h-4 text-gray-400" />
|
| 84 |
</div>
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
</div>
|
| 98 |
-
)}
|
| 99 |
-
|
| 100 |
-
{summary && (
|
| 101 |
-
<p className="mt-4 text-gray-700 text-sm leading-relaxed">
|
| 102 |
-
{summary}
|
| 103 |
-
</p>
|
| 104 |
-
)}
|
| 105 |
-
</div>
|
| 106 |
-
|
| 107 |
-
{/* Detections / Probabilities */}
|
| 108 |
-
{detections && detections.length > 0 && (
|
| 109 |
-
<div className="mb-6">
|
| 110 |
-
<h4 className="font-semibold text-gray-900 mb-3">
|
| 111 |
-
Detected Regions:
|
| 112 |
-
</h4>
|
| 113 |
-
<ul className="text-sm text-gray-700 list-disc list-inside space-y-1">
|
| 114 |
-
{detections.map((det: any, i: number) => (
|
| 115 |
-
<li key={i}>
|
| 116 |
-
{det.name || "object"} – {(det.confidence * 100).toFixed(1)}%
|
| 117 |
-
</li>
|
| 118 |
-
))}
|
| 119 |
-
</ul>
|
| 120 |
-
</div>
|
| 121 |
-
)}
|
| 122 |
-
|
| 123 |
-
{probabilities && (
|
| 124 |
-
<div className="mb-6">
|
| 125 |
-
<h4 className="font-semibold text-gray-900 mb-3">
|
| 126 |
-
Class Probabilities:
|
| 127 |
-
</h4>
|
| 128 |
-
<pre className="bg-gray-100 rounded-lg p-3 text-sm">
|
| 129 |
-
{JSON.stringify(probabilities, null, 2)}
|
| 130 |
-
</pre>
|
| 131 |
-
</div>
|
| 132 |
-
)}
|
| 133 |
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
}
|
|
|
|
| 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 |
}
|
frontend/src/components/Sidebar.tsx
CHANGED
|
@@ -1,54 +1,62 @@
|
|
| 1 |
-
import
|
| 2 |
-
|
| 3 |
interface SidebarProps {
|
| 4 |
selectedTest: string;
|
| 5 |
onTestChange: (test: string) => void;
|
| 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 |
</div>
|
|
|
|
| 42 |
{/* History */}
|
| 43 |
<button className="w-full flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors">
|
| 44 |
<FileTextIcon className="w-5 h-5" />
|
| 45 |
<span>History</span>
|
| 46 |
</button>
|
|
|
|
| 47 |
{/* Help */}
|
| 48 |
<button className="w-full flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors">
|
| 49 |
<HelpCircleIcon className="w-5 h-5" />
|
| 50 |
<span>Help</span>
|
| 51 |
</button>
|
| 52 |
</div>
|
| 53 |
-
</aside
|
|
|
|
| 54 |
}
|
|
|
|
| 1 |
+
import { FileTextIcon, HelpCircleIcon } from 'lucide-react';
|
| 2 |
+
|
| 3 |
interface SidebarProps {
|
| 4 |
selectedTest: string;
|
| 5 |
onTestChange: (test: string) => void;
|
| 6 |
}
|
| 7 |
+
|
| 8 |
+
export function Sidebar({ selectedTest, onTestChange }: SidebarProps) {
|
| 9 |
+
const testTypes = [
|
| 10 |
+
{ value: 'cytology', label: 'Cytology Analysis' },
|
| 11 |
+
{ value: 'colposcopy', label: 'Colposcopy Analysis' },
|
| 12 |
+
{ value: 'histopathology', label: 'Histopathology Analysis' },
|
| 13 |
+
];
|
| 14 |
+
|
| 15 |
+
return (
|
| 16 |
+
<aside className="w-64 bg-white border-r border-gray-200 p-4">
|
| 17 |
+
<div className="space-y-4">
|
| 18 |
+
{/* Gradient Header */}
|
| 19 |
+
<h3 className="text-lg font-semibold text-white mb-3 px-3 py-2 rounded-md bg-gradient-to-r from-blue-800 to-teal-600 shadow-sm">
|
| 20 |
+
New Test
|
| 21 |
+
</h3>
|
| 22 |
+
|
| 23 |
+
{/* Test Selection */}
|
| 24 |
+
<div className="flex flex-col gap-2">
|
| 25 |
+
{testTypes.map((test) => (
|
| 26 |
+
<label
|
| 27 |
+
key={test.value}
|
| 28 |
+
className={`flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer border transition-all duration-200
|
| 29 |
+
${
|
| 30 |
+
selectedTest === test.value
|
| 31 |
+
? 'border-blue-600 bg-blue-50 text-blue-900 font-medium shadow-sm'
|
| 32 |
+
: 'border-gray-200 hover:border-blue-300 hover:bg-gray-50 text-gray-700'
|
| 33 |
+
}`}
|
| 34 |
+
>
|
| 35 |
+
<input
|
| 36 |
+
type="radio"
|
| 37 |
+
name="testType"
|
| 38 |
+
value={test.value}
|
| 39 |
+
checked={selectedTest === test.value}
|
| 40 |
+
onChange={() => onTestChange(test.value)}
|
| 41 |
+
className="form-radio h-4 w-4 text-blue-600 focus:ring-blue-500"
|
| 42 |
+
/>
|
| 43 |
+
<span>{test.label}</span>
|
| 44 |
+
</label>
|
| 45 |
+
))}
|
| 46 |
</div>
|
| 47 |
+
|
| 48 |
{/* History */}
|
| 49 |
<button className="w-full flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors">
|
| 50 |
<FileTextIcon className="w-5 h-5" />
|
| 51 |
<span>History</span>
|
| 52 |
</button>
|
| 53 |
+
|
| 54 |
{/* Help */}
|
| 55 |
<button className="w-full flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors">
|
| 56 |
<HelpCircleIcon className="w-5 h-5" />
|
| 57 |
<span>Help</span>
|
| 58 |
</button>
|
| 59 |
</div>
|
| 60 |
+
</aside>
|
| 61 |
+
);
|
| 62 |
}
|
frontend/src/components/UploadSection.tsx
CHANGED
|
@@ -24,16 +24,16 @@ export function UploadSection({
|
|
| 24 |
|
| 25 |
const modelOptions = {
|
| 26 |
cytology: [
|
| 27 |
-
{ value: 'mwt', label: '
|
| 28 |
-
{ value: 'yolo', label: '
|
| 29 |
],
|
| 30 |
colposcopy: [
|
| 31 |
-
{ value: 'cin', label: '
|
| 32 |
-
|
| 33 |
],
|
| 34 |
histopathology: [
|
| 35 |
-
{ value: 'histopathology', label: '
|
| 36 |
-
|
| 37 |
],
|
| 38 |
};
|
| 39 |
|
|
@@ -156,7 +156,11 @@ export function UploadSection({
|
|
| 156 |
<button
|
| 157 |
onClick={onAnalyze}
|
| 158 |
disabled={!uploadedImage || !selectedModel}
|
| 159 |
-
className=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
>
|
| 161 |
Analyze
|
| 162 |
</button>
|
|
@@ -174,16 +178,12 @@ export function UploadSection({
|
|
| 174 |
(img, index) => (
|
| 175 |
<div
|
| 176 |
key={index}
|
| 177 |
-
className={`w-20 h-20 rounded-lg border-2 cursor-pointer transition-transform hover:scale-105
|
| 178 |
-
uploadedImage === img ? '
|
| 179 |
}`}
|
| 180 |
onClick={() => handleSampleClick(img)}
|
| 181 |
>
|
| 182 |
-
<img
|
| 183 |
-
src={img}
|
| 184 |
-
alt={`Sample ${index + 1}`}
|
| 185 |
-
className="w-full h-full object-cover"
|
| 186 |
-
/>
|
| 187 |
</div>
|
| 188 |
)
|
| 189 |
)}
|
|
|
|
| 24 |
|
| 25 |
const modelOptions = {
|
| 26 |
cytology: [
|
| 27 |
+
{ value: 'mwt', label: 'Manalife_AI_MWT' },
|
| 28 |
+
{ value: 'yolo', label: 'Manalife_AI_YOLOv8' },
|
| 29 |
],
|
| 30 |
colposcopy: [
|
| 31 |
+
{ value: 'cin', label: 'Manalife_MaANIA_Colpo' },
|
| 32 |
+
|
| 33 |
],
|
| 34 |
histopathology: [
|
| 35 |
+
{ value: 'histopathology', label: 'ManalifeAI__Path Foundation Model' },
|
| 36 |
+
|
| 37 |
],
|
| 38 |
};
|
| 39 |
|
|
|
|
| 156 |
<button
|
| 157 |
onClick={onAnalyze}
|
| 158 |
disabled={!uploadedImage || !selectedModel}
|
| 159 |
+
className={`w-full mt-6 text-white py-3 rounded-lg font-medium transition-colors ${
|
| 160 |
+
!uploadedImage || !selectedModel
|
| 161 |
+
? 'bg-gray-300 disabled:cursor-not-allowed'
|
| 162 |
+
: 'bg-gradient-to-r from-blue-800 to-teal-600 hover:opacity-95'
|
| 163 |
+
}`}
|
| 164 |
>
|
| 165 |
Analyze
|
| 166 |
</button>
|
|
|
|
| 178 |
(img, index) => (
|
| 179 |
<div
|
| 180 |
key={index}
|
| 181 |
+
className={`w-20 h-20 rounded-lg border-2 cursor-pointer transition-transform hover:scale-105 overflow-hidden ${
|
| 182 |
+
uploadedImage === img ? 'ring-2 ring-offset-1 ring-blue-800' : 'border-gray-300'
|
| 183 |
}`}
|
| 184 |
onClick={() => handleSampleClick(img)}
|
| 185 |
>
|
| 186 |
+
<img src={img} alt={`Sample ${index + 1}`} className="w-full h-full object-cover" />
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
</div>
|
| 188 |
)
|
| 189 |
)}
|