Pathora / frontend /src /components /ResultsPanel.tsx
malavikapradeep2001's picture
Update
e134c3f
raw
history blame
8.15 kB
import { useState } from "react";
import { DownloadIcon, FileTextIcon, Loader2Icon } from "lucide-react";
import { ImageWithFallback } from "./ImageWithFallback";
import { ReportModal } from "./ReportModal";
import axios from "axios";
interface ResultsPanelProps {
uploadedImage: string | null;
result?: any;
loading?: boolean;
}
export function ResultsPanel({ uploadedImage, result, loading }: ResultsPanelProps) {
const [showReportModal, setShowReportModal] = useState(false);
const handleGenerateReport = async (formData: FormData) => {
try {
const baseURL = import.meta.env.MODE === "development"
? "http://127.0.0.1:7860"
: window.location.origin;
const response = await axios.post(`${baseURL}/reports/`, formData, {
headers: { "Content-Type": "multipart/form-data" },
});
if (response.data.html_url) {
// Open report in new tab
window.open(`${baseURL}${response.data.html_url}`, "_blank");
}
if (response.data.pdf_url) {
// Open PDF in new tab when available
window.open(`${baseURL}${response.data.pdf_url}`, "_blank");
}
setShowReportModal(false);
} catch (err: any) {
console.error("Failed to generate report:", err);
alert(err.response?.data?.error || "Failed to generate report");
}
};
if (loading) {
return (
<div className="bg-white rounded-lg shadow-sm p-6 flex flex-col items-center justify-center">
<Loader2Icon className="w-10 h-10 text-blue-600 animate-spin mb-3" />
<p className="text-teal-700 font-medium">Analyzing image...</p>
</div>
);
}
if (!result) {
return (
<div className="bg-white rounded-lg shadow-sm p-6 text-center text-gray-500">
No analysis result available yet.
</div>
);
}
const {
model_used,
detections,
annotated_image_url,
summary,
// prediction (not used here)
confidence,
} = result;
const handleDownload = () => {
if (annotated_image_url) {
const link = document.createElement("a");
link.href = annotated_image_url;
link.download = "analysis_result.jpg";
link.click();
}
};
return ( <div className="bg-white rounded-lg shadow-sm p-6">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-2xl font-bold text-gray-800">
{model_used || "Analysis Result"}
</h2>
<p className="text-sm text-gray-500">Automated Image Analysis</p>
</div>
<div className="flex items-center gap-3">
{annotated_image_url && (
<button
onClick={handleDownload}
className="flex items-center gap-2 bg-gradient-to-r from-teal-700 via-teal-600 to-teal-700 text-white px-4 py-2 rounded-lg hover:opacity-90 transition-all"
>
<DownloadIcon className="w-4 h-4" />
Download Image
</button>
)}
<button
onClick={() => setShowReportModal(true)}
className="flex items-center gap-2 bg-gradient-to-r from-teal-700 via-teal-600 to-teal-700 text-white px-4 py-2 rounded-lg hover:opacity-90 transition-all"
>
<FileTextIcon className="w-4 h-4" />
Generate Report
</button>
</div>
</div>
{/* Image */}
<div className="relative mb-6 rounded-lg overflow-hidden border border-gray-200">
<ImageWithFallback
src={annotated_image_url || uploadedImage || "/ui.jpg"}
alt="Analysis Result"
className="w-full h-64 object-cover"
/>
</div>
{/* Summary Section */}
{summary && (
<div className="bg-gray-50 p-4 rounded-lg mb-6">
<h3 className="text-lg font-semibold text-gray-800 mb-2">
AI Summary
</h3>
<p className="text-gray-700 text-sm leading-relaxed">
<strong>Abnormal Cells:</strong> {summary.abnormal_cells} <br />
<strong>Normal Cells:</strong> {summary.normal_cells} <br />
<strong>Average Confidence:</strong> {summary.avg_confidence?.toFixed(2)}% <br />
</p>
<div className="mt-3 text-gray-800 text-sm italic border-t pt-2">
{summary.ai_interpretation || "No AI interpretation available."}
</div>
</div>
)}
{/* Detection list */}
{detections && detections.length > 0 && (
<div className="mb-6">
<h4 className="font-semibold text-gray-900 mb-3">
Detected Objects
</h4>
<ul className="text-sm text-gray-700 list-disc list-inside space-y-1">
{detections.map((det: any, i: number) => (
<li key={i}>
{det.name || "Object"} – {(det.confidence * 100).toFixed(1)}%
</li>
))}
</ul>
</div>
)}
{/* Probability / MWT visualization */}
{confidence && (
<div className="mb-6">
<h4 className="font-semibold text-gray-900 mb-3">Confidence Levels</h4>
{/* If MWT, CIN, or Histopathology classifier, show a visual bar for average confidence and per-class bars */}
{model_used && /mwt|cin|histopathology/i.test(model_used) ? (
<div>
{/* Average confidence bar */}
<div className="mb-3">
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium text-gray-700">Average confidence</span>
<span className="text-sm font-mono text-gray-600">
{summary?.avg_confidence ? `${summary.avg_confidence.toFixed(2)}%` : "-"}
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-4 overflow-hidden">
<div
className="h-4 bg-gradient-to-r from-amber-600 to-amber-400"
style={{ width: `${summary?.avg_confidence ?? 0}%` }}
/>
</div>
</div>
{/* Per-class bars */}
<div className="space-y-2">
{Object.entries(confidence).map(([cls, val]) => {
const num = Number(val as any) || 0;
const pct = (num * 100);
// Color coding: Positive/Malignant/High-grade = red, Negative/Benign/Low-grade = green
const isNegative = cls.toLowerCase().includes("negative") ||
cls.toLowerCase().includes("benign") ||
cls.toLowerCase().includes("low-grade");
return (
<div key={cls}>
<div className="flex items-center justify-between text-sm mb-1">
<span className="text-gray-700">{cls}</span>
<span className="text-gray-600">{pct.toFixed(2)}%</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-3">
<div
className={`h-3 rounded-full ${isNegative ? "bg-green-500" : "bg-red-500"}`}
style={{ width: `${pct.toFixed(2)}%` }}
/>
</div>
</div>
);
})}
</div>
{/* Mistral comment */}
<div className="mt-4 bg-gray-50 p-3 rounded-lg text-sm italic text-gray-800 border-t">
{summary?.ai_interpretation || "No AI interpretation available."}
</div>
</div>
) : (
// Fallback display for non-MWT models
<pre className="bg-gray-100 rounded-lg p-3 text-sm overflow-x-auto">
{JSON.stringify(confidence, null, 2)}
</pre>
)}
</div>
)}
{/* Report Generation Modal */}
<ReportModal
isOpen={showReportModal}
onClose={() => setShowReportModal(false)}
onSubmit={handleGenerateReport}
analysisId={annotated_image_url || ""}
analysisSummaryJson={summary ? JSON.stringify({ ...summary, model_used, confidence }) : "{}"}
/>
</div>
);
}