From d3a03f2c6e56798a98cdb44cd5fce8ee0320fc55 Mon Sep 17 00:00:00 2001 From: ishitaahelleya Date: Tue, 28 Jul 2026 19:04:25 -0700 Subject: [PATCH 1/6] Fix report walkthrough, PDF generation with per-organ gallery, autoRotate mesh prop --- PanTS-Demo/package-lock.json | 49 ++ PanTS-Demo/package.json | 1 + PanTS-Demo/src/components/MeshViewer.tsx | 13 +- .../components/ReportScreen/ReportScreen.tsx | 46 +- PanTS-Demo/src/routes/VisualizationPage.tsx | 13 + flask-server/api/api_blueprint.py | 570 +++++++++++++++--- 6 files changed, 595 insertions(+), 97 deletions(-) diff --git a/PanTS-Demo/package-lock.json b/PanTS-Demo/package-lock.json index 381e0263..7b2911db 100644 --- a/PanTS-Demo/package-lock.json +++ b/PanTS-Demo/package-lock.json @@ -20,6 +20,7 @@ "@tabler/icons-react": "^3.34.1", "@tailwindcss/vite": "^4.1.12", "esbuild": "^0.25.9", + "framer-motion": "^12.42.2", "jszip": "^3.10.1", "pako": "^3.0.1", "react": "^19.1.1", @@ -7891,6 +7892,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.42.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz", + "integrity": "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.42.2", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -9166,6 +9194,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/motion-dom": { + "version": "12.42.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.2.tgz", + "integrity": "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion-utils": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -10651,6 +10694,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tunnel-rat": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz", diff --git a/PanTS-Demo/package.json b/PanTS-Demo/package.json index afbdcf28..288736f0 100644 --- a/PanTS-Demo/package.json +++ b/PanTS-Demo/package.json @@ -25,6 +25,7 @@ "@tabler/icons-react": "^3.34.1", "@tailwindcss/vite": "^4.1.12", "esbuild": "^0.25.9", + "framer-motion": "^12.42.2", "jszip": "^3.10.1", "pako": "^3.0.1", "react": "^19.1.1", diff --git a/PanTS-Demo/src/components/MeshViewer.tsx b/PanTS-Demo/src/components/MeshViewer.tsx index 948721be..d1de7afa 100644 --- a/PanTS-Demo/src/components/MeshViewer.tsx +++ b/PanTS-Demo/src/components/MeshViewer.tsx @@ -18,6 +18,7 @@ type SegmentationMeshViewerProps = { crosshairMm: Vec3 | null customOrgans?: CheckBoxData[]; labelColorMap?: { [key: number]: Color }; + autoRotate?: boolean; }; @@ -31,8 +32,7 @@ export async function fetchMeshManifest(caseId: string): Promise { return res.json(); } -export function SegmentationMeshViewer({ caseId, checkState, loading, opacity, crosshairMm, customOrgans = [], labelColorMap = {}}: SegmentationMeshViewerProps) { - const [manifest, setManifest] = useState(null); +export function SegmentationMeshViewer({ caseId, checkState, loading, opacity, crosshairMm, customOrgans = [], labelColorMap = {}, autoRotate = false}: SegmentationMeshViewerProps) { const [manifest, setManifest] = useState(null); const [loaded, setLoaded] = useState>({}); const crosshairPosition = useMemo(() => { @@ -92,16 +92,15 @@ export function SegmentationMeshViewer({ caseId, checkState, loading, opacity, c - + {organs.map((organ) => { - if (!loaded[organ.id]) return null; - + if (!loaded[organ.id] || !checkState[organ.id]) return null; return ( ); @@ -126,7 +125,7 @@ export function SegmentationMeshViewer({ caseId, checkState, loading, opacity, c )} - + diff --git a/PanTS-Demo/src/components/ReportScreen/ReportScreen.tsx b/PanTS-Demo/src/components/ReportScreen/ReportScreen.tsx index 530488cd..c74215cb 100644 --- a/PanTS-Demo/src/components/ReportScreen/ReportScreen.tsx +++ b/PanTS-Demo/src/components/ReportScreen/ReportScreen.tsx @@ -34,7 +34,7 @@ interface ReportData { type Lang = 'patient' | 'clinical'; type Step = number; -const cache: { [k: string]: ReportData } = {}; +export const cache: { [key: string]: ReportData } = {}; // ─── Styles ─────────────────────────────────────────────────────────────────── @@ -403,6 +403,7 @@ export default function ReportScreen({ id, onClose, onViewChange, onOrganHighlig const [modePromptOpen, setModePromptOpen] = useState(false); const [plain2, setPlain2] = useState([]); const [pLoad, setPLoad] = useState(false); + const [pdfLoading, setPdfLoading] = useState(false); const startRef = useRef(Date.now()); useEffect(() => { @@ -419,6 +420,28 @@ export default function ReportScreen({ id, onClose, onViewChange, onOrganHighlig .catch(() => setLoading(false)); }, [id]); + + const handleDownloadPdf = async () => { + setPdfLoading(true); + try { + const res = await fetch(`${APP_CONSTANTS.API_ORIGIN}/api/generate-report-pdf/${id}`); + if (!res.ok) throw new Error('PDF generation failed'); + const blob = await res.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `case_${id}_report.pdf`; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + } catch (e) { + console.error('PDF download failed:', e); + } finally { + setPdfLoading(false); + } + }; + const fetchPlain = useCallback(async () => { if (plain2.length || !data) return; setPLoad(true); @@ -638,6 +661,27 @@ export default function ReportScreen({ id, onClose, onViewChange, onOrganHighlig )} +