diff --git a/PanTS-Demo/src/routes/UploadPage.css b/PanTS-Demo/src/routes/UploadPage.css index c200874..41e5888 100644 --- a/PanTS-Demo/src/routes/UploadPage.css +++ b/PanTS-Demo/src/routes/UploadPage.css @@ -1110,3 +1110,72 @@ border-top-color: #002D72; animation: upload-spin 0.75s linear infinite; } + +/* ── Simplified config: Auto hint, speed/quality toggle, result note ── */ +.simple-config { + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 16px; +} + +.auto-hint-input { + width: 100%; + padding: 11px 14px; + border: 1px solid rgba(0, 45, 114, 0.16); + border-radius: 10px; + font-size: 13.5px; + font-family: inherit; + color: #1a2b47; + background: #fbfcfe; + outline: none; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} +.auto-hint-input:focus { + border-color: #002D72; + box-shadow: 0 0 0 3px rgba(0, 45, 114, 0.08); +} + +.precision-toggle { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} +.precision-opt { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 3px; + padding: 12px 14px; + border: 1px solid rgba(0, 45, 114, 0.16); + border-radius: 10px; + background: #fff; + cursor: pointer; + text-align: left; + transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease; +} +.precision-opt:hover { border-color: rgba(0, 45, 114, 0.4); } +.precision-opt.active { + border-color: #002D72; + background: rgba(0, 45, 114, 0.05); + box-shadow: 0 0 0 2px rgba(0, 45, 114, 0.12); +} +.precision-opt-title { + font-size: 13.5px; + font-weight: 600; + color: #002D72; +} +.precision-opt-sub { + font-size: 11.5px; + color: #6b7793; +} + +.auto-note { + padding: 10px 13px; + border-radius: 10px; + background: rgba(45, 130, 90, 0.08); + border: 1px solid rgba(45, 130, 90, 0.2); + color: #1f5b3f; + font-size: 12.5px; + line-height: 1.45; +} diff --git a/PanTS-Demo/src/routes/UploadPage.tsx b/PanTS-Demo/src/routes/UploadPage.tsx index c009b9c..ca4714a 100644 --- a/PanTS-Demo/src/routes/UploadPage.tsx +++ b/PanTS-Demo/src/routes/UploadPage.tsx @@ -1,6 +1,7 @@ import React, { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react'; const MODEL_OPTIONS: { id: string; label: string; desc: string }[] = [ + { id: "Auto", label: "Auto — AI picks the best model", desc: "Recommended · a local AI reads your request and chooses for you" }, { id: "None", label: "None", desc: "View only — files never leave your browser" }, { id: "ePAI", label: "ePAI", desc: "For detailed pancreas and tumor analysis" }, { id: "SuPreM", label: "SuPreM", desc: "For whole-body scans from lungs to legs" }, @@ -79,7 +80,13 @@ const UploadPage: React.FC = () => { const [uploadProgress, setUploadProgress] = useState(0); const [isUploading, setIsUploading] = useState(false); const [inferenceCompleted, setInferenceCompleted] = useState(false); - const [selectedModel, setSelectedModel] = useState<"None" | "ePAI" | "SuPreM" | "OpenVAE" | "MedFormer" | "R-Super" | "Atlas-Net" | "">("None"); + const [selectedModel, setSelectedModel] = useState<"Auto" | "None" | "ePAI" | "SuPreM" | "OpenVAE" | "MedFormer" | "R-Super" | "Atlas-Net" | "">("Auto"); + // FP16 = quantized (faster, half the memory, identical masks); the default. + const [selectedPrecision, setSelectedPrecision] = useState<"fp16" | "fp32">("fp16"); + // Optional plain-language hint for the Auto picker, and the note it returns. + const [autoHint, setAutoHint] = useState(""); + const [autoNote, setAutoNote] = useState(""); + const [autoBusy, setAutoBusy] = useState(false); const [modelDropOpen, setModelDropOpen] = useState(false); const modelDropRef = useRef(null); const [preDropOpen, setPreDropOpen] = useState(false); @@ -412,6 +419,7 @@ const UploadPage: React.FC = () => { const inferFd = new FormData(); inferFd.append("session_id", sid); inferFd.append("model_name", model); + inferFd.append("precision", selectedPrecision); inferFd.append("uploaded_filename", uploadedName); const res = await fetch(`${API_BASE}/api/run-epai-inference`, { method: "POST", body: inferFd, signal: controller.signal, @@ -489,6 +497,7 @@ const UploadPage: React.FC = () => { const inferFd = new FormData(); inferFd.append("session_id", sid); inferFd.append("model_name", model); + inferFd.append("precision", selectedPrecision); inferFd.append("uploaded_filename", uploadedName); const res = await fetch(`${API_BASE}/api/run-epai-inference`, { method: "POST", body: inferFd, signal: controller.signal, @@ -541,7 +550,37 @@ const UploadPage: React.FC = () => { return; } - const model = selectedModel; + // Auto mode: ask the backend (local LLM, with a heuristic fallback) to choose + // the model + precision, then run with that choice. Resolves to a concrete + // model name before anything is uploaded. + let model: string = selectedModel; + let precision = selectedPrecision; + if (selectedModel === "Auto") { + try { + setAutoBusy(true); + setMessage("Auto-selecting the best model for your scan…"); + const filename = item ? (item.kind === 'dicom' ? item.label : item.file.name) : (path ? path.split("/").pop() : ""); + const res = await fetch(`${API_BASE}/api/suggest-model`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ hint: autoHint, filename }), + }); + const data = await parseApiResponse(res); + model = data.model || "ePAI"; + precision = (data.precision === "fp32" ? "fp32" : "fp16"); + setSelectedModel(model as typeof selectedModel); + setSelectedPrecision(precision); + setAutoNote(`Auto-selected ${model} · ${precision.toUpperCase()} — ${data.reason || "best fit for this scan."}`); + } catch { + model = "ePAI"; + precision = "fp16"; + setSelectedModel("ePAI"); + setAutoNote("Auto-select was unavailable, so it defaulted to ePAI · FP16."); + } finally { + setAutoBusy(false); + } + } + const sid = crypto.randomUUID(); const itemName = item ? (item.kind === 'dicom' ? item.label : item.file.name) : undefined; const label = bdmapId.trim() || (path ? path.split("/").pop() : itemName) || sid; @@ -567,6 +606,7 @@ const UploadPage: React.FC = () => { const fd = new FormData(); fd.append("session_id", sid); fd.append("model_name", model); + fd.append("precision", selectedPrecision); fd.append("INPUT_SERVER_PATH", path); const res = await fetch(`${API_BASE}/api/run-epai-inference`, { method: "POST", body: fd }); const data = await parseApiResponse(res); @@ -682,6 +722,7 @@ const UploadPage: React.FC = () => { const formData = new FormData(); formData.append("session_id", newSessionId); formData.append("model_name", "ePAI"); + formData.append("precision", selectedPrecision); formData.append("source_reconstruction_session_id", sessionId); try { @@ -969,12 +1010,44 @@ const UploadPage: React.FC = () => { + {/* ── Simple controls: Auto hint, speed/quality, result note ── */} +
+ {selectedModel === "Auto" && ( + setAutoHint(e.target.value)} + /> + )} +
+ + +
+ {autoNote &&
{autoNote}
} +
+ {/* ── Advanced options ── */}