From 4a2a5d18980b414cf55fa2f2a6baab20373dd03f Mon Sep 17 00:00:00 2001 From: Aditya Sanjeev Date: Thu, 13 Aug 2026 03:17:11 -0700 Subject: [PATCH] Name uploaded scans by model + date, and let users rename them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uploaded scans were labelled with the raw filename (often 'ct.nii.gz' or a cryptic export name) or, failing that, the session UUID -- meaningless in the history list. Default the name to a friendly ' · ' (e.g. 'ePAI · Aug 13, 2026') via friendlyScanName(), and add inline rename in the history list (pencil -> input, Enter/blur saves, Escape cancels) backed by renameRecentUpload(). The original filename is preserved on the entry (sourceName) and shown as a tooltip, so nothing is lost. --- PanTS-Demo/src/helpers/recentUploads.ts | 39 ++++++++++++++++++++ PanTS-Demo/src/routes/UploadPage.tsx | 49 +++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/PanTS-Demo/src/helpers/recentUploads.ts b/PanTS-Demo/src/helpers/recentUploads.ts index d30b99c..10e47d1 100644 --- a/PanTS-Demo/src/helpers/recentUploads.ts +++ b/PanTS-Demo/src/helpers/recentUploads.ts @@ -5,7 +5,12 @@ export type RecentUploadStatus = "Processing" | "Completed" | "Failed" | "Cancel export type RecentUpload = { sessionId: string; + // User-facing name. Defaults to a friendly " · " (see + // friendlyScanName) rather than the raw upload filename, and is renameable. label: string; + // The original filename, kept for reference (shown as a tooltip) even after + // the label is renamed. Optional so older localStorage entries still parse. + sourceName?: string; model: string; status: RecentUploadStatus; timestamp: number; @@ -128,6 +133,40 @@ export const updateRecentUploadStatus = ( return list; }; +// Rename a scan. Empty/whitespace input falls back to a sensible default so a +// scan is never left nameless. +export const renameRecentUpload = ( + sessionId: string, + label: string +): RecentUpload[] => { + const list = loadRecentUploads().map((u) => { + if (u.sessionId !== sessionId) return u; + const next = label.trim(); + return { ...u, label: next || friendlyScanName(u.model, u.timestamp) }; + }); + persistRecentUploads(list); + return list; +}; + +// A meaningful default name for a scan: the model it was run with plus the date, +// e.g. "ePAI · Aug 13, 2026". Far more useful in the history list than the raw +// upload filename (often "ct.nii.gz" or a cryptic export name). The user can +// rename it afterwards. +export const friendlyScanName = (model: string, timestamp: number): string => { + const who = model && model !== "None" ? model : "Scan"; + let date: string; + try { + date = new Date(timestamp).toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + }); + } catch { + date = new Date(timestamp).toISOString().slice(0, 10); + } + return `${who} · ${date}`; +}; + export const formatRelativeTime = (ts: number): string => { const mins = Math.floor((Date.now() - ts) / 60000); if (mins < 1) return "Just now"; diff --git a/PanTS-Demo/src/routes/UploadPage.tsx b/PanTS-Demo/src/routes/UploadPage.tsx index fda4c91..d53f125 100644 --- a/PanTS-Demo/src/routes/UploadPage.tsx +++ b/PanTS-Demo/src/routes/UploadPage.tsx @@ -67,6 +67,8 @@ const DicomPreview = lazy(() => import("../components/CtPreview/DicomPreview")); import { API_BASE } from "../helpers/constants"; import { addRecentUpload, + friendlyScanName, + renameRecentUpload, formatRelativeTime, groupUploads, isGroupInFlight, @@ -212,6 +214,18 @@ const UploadPage: React.FC = () => { const [recentUploads, setRecentUploads] = useState(() => loadRecentUploads(), ); + // Inline rename of a scan in the history list: which one is being edited and + // the working text. + const [renamingId, setRenamingId] = useState(null); + const [renameValue, setRenameValue] = useState(""); + const startRename = (u: RecentUpload) => { + setRenamingId(u.sessionId); + setRenameValue(u.label); + }; + const commitRename = () => { + if (renamingId) setRecentUploads(renameRecentUpload(renamingId, renameValue)); + setRenamingId(null); + }; // Which batch's "View details" popup is open (null = none). const [detailsBatchId, setDetailsBatchId] = useState(null); // Sub-state of each Active card: "waiting" | "uploading" | "queued" | "running". @@ -962,15 +976,20 @@ const UploadPage: React.FC = () => { batch?: { batchId: string; batchLabel: string }, ) => { const sid = crypto.randomUUID(); - const label = (item.kind === "dicom" ? item.label : item.file.name) || sid; + const ts = Date.now(); + // Keep the raw filename for reference, but name the scan meaningfully by + // default (model + date); the user can rename it later. + const sourceName = (item.kind === "dicom" ? item.label : item.file.name) || undefined; + const label = friendlyScanName(model, ts); setRecentUploads( addRecentUpload({ sessionId: sid, label, + sourceName, model, status: "Processing", - timestamp: Date.now(), + timestamp: ts, isReconstruction: model === "OpenVAE", batchId: batch?.batchId, batchLabel: batch?.batchLabel, @@ -1895,7 +1914,31 @@ const UploadPage: React.FC = () => {
-
{u.label}
+ {renamingId === u.sessionId ? ( + e.stopPropagation()} + onChange={(e) => setRenameValue(e.target.value)} + onBlur={commitRename} + onKeyDown={(e) => { + if (e.key === "Enter") commitRename(); + else if (e.key === "Escape") setRenamingId(null); + }} + style={{ fontFamily: "'Space Grotesk', sans-serif", fontSize: "14px", fontWeight: 600, color: "#111111", border: "1px solid rgba(0,45,114,0.3)", borderRadius: "6px", padding: "2px 6px", width: "100%", maxWidth: "260px" }} + /> + ) : ( +
+ {u.label} + +
+ )}
{u.model ? `${u.model} · ` : ""}{formatRelativeTime(u.timestamp)}