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);
// Make loading detection robust: sometimes values arrive as the string "true" from deployed envs
const isLoading = loading === true || String(loading) === "true";
// Helpful debug information when checking issues on deployed spaces (open browser devtools)
// Keep as debug (console.debug) so it doesn't clutter normal logs.
console.debug("ResultsPanel: props", { loading, isLoading, result });
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");
}
};
// Safely destructure result (keep undefined values when result is null) so we can render
// a stable panel while loading and avoid early returns that change layout.
const {
model_used,
detections,
annotated_image_url,
summary,
// prediction (not used here)
confidence,
} = (result || {}) as any;
const handleDownload = () => {
if (annotated_image_url) {
const link = document.createElement("a");
link.href = annotated_image_url;
link.download = "analysis_result.jpg";
// For Firefox it is necessary to add the link to the DOM
document.body.appendChild(link);
link.click();
link.remove();
}
};
return (
)}
{/* Probability / MWT visualization */}
{confidence && (
Confidence Levels
{/* 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) ? (
{/* Average confidence bar */}
{/* Average confidence removed from visualization */}
{/* Per-class bars */}
{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 (
{cls}{pct.toFixed(2)}%
);
})}
{/* Mistral comment */}
{summary?.ai_interpretation || "No AI interpretation available."}
) : (
// Fallback display for non-MWT models
{JSON.stringify(confidence, null, 2)}
)}
)}
{/* Report Generation Modal */}
{/* Move Generate Report button here so it's shown after analysis details. */}
{/* Show only when not loading and we have at least a result (so Generate Report is available even if summary/confidence are missing) */}
{!isLoading && result && (
{annotated_image_url && (
)}
)}
setShowReportModal(false)}
onSubmit={handleGenerateReport}
analysisId={annotated_image_url || ""}
// Include annotated_image_url in the analysis summary so the backend can embed it
analysisSummaryJson={summary ? JSON.stringify({ ...summary, model_used, confidence, annotated_image_url }) : "{}"}
/>