diff --git a/package.json b/package.json index 4178f38..fc65365 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "netfelix-audio-fix", - "version": "2026.04.15.4", + "version": "2026.04.15.5", "scripts": { "dev:server": "NODE_ENV=development bun --hot server/index.tsx", "dev:client": "vite", diff --git a/src/features/pipeline/ProcessingColumn.tsx b/src/features/pipeline/ProcessingColumn.tsx index c998bbb..5aef2aa 100644 --- a/src/features/pipeline/ProcessingColumn.tsx +++ b/src/features/pipeline/ProcessingColumn.tsx @@ -23,6 +23,25 @@ export function ProcessingColumn({ items, progress, queueStatus, onMutate }: Pro return () => clearInterval(t); }, [job]); + // Local sleep countdown. Server emits the sleep duration once when the + // pause begins; the client anchors "deadline = receivedAt + seconds*1000" + // and ticks a 1s timer so the UI shows a live countdown, not a static number. + const [sleepDeadline, setSleepDeadline] = useState(null); + const [sleepNow, setSleepNow] = useState(() => Date.now()); + useEffect(() => { + if (queueStatus?.status === "sleeping" && typeof queueStatus.seconds === "number") { + setSleepDeadline(Date.now() + queueStatus.seconds * 1000); + } else { + setSleepDeadline(null); + } + }, [queueStatus?.status, queueStatus?.seconds]); + useEffect(() => { + if (sleepDeadline == null) return; + const t = setInterval(() => setSleepNow(Date.now()), 1000); + return () => clearInterval(t); + }, [sleepDeadline]); + const sleepRemaining = sleepDeadline != null ? Math.max(0, Math.ceil((sleepDeadline - sleepNow) / 1000)) : null; + // Only trust progress if it belongs to the current job — stale events from // a previous job would otherwise show wrong numbers until the new job emits. const liveProgress = job && progress && progress.id === job.id ? progress : null; @@ -55,9 +74,9 @@ export function ProcessingColumn({ items, progress, queueStatus, onMutate }: Pro actions={job ? [{ label: "Stop", onClick: stop, danger: true }] : undefined} > {queueStatus && queueStatus.status !== "running" && ( -
+
{queueStatus.status === "paused" && <>Paused until {queueStatus.until}} - {queueStatus.status === "sleeping" && <>Sleeping {queueStatus.seconds}s between jobs} + {queueStatus.status === "sleeping" && <>Next job in {sleepRemaining ?? queueStatus.seconds ?? 0}s} {queueStatus.status === "idle" && <>Idle}
)} diff --git a/src/features/settings/SettingsPage.tsx b/src/features/settings/SettingsPage.tsx index c789f2a..03402a9 100644 --- a/src/features/settings/SettingsPage.tsx +++ b/src/features/settings/SettingsPage.tsx @@ -60,6 +60,49 @@ function LockedInput({ locked, ...props }: { locked: boolean } & React.InputHTML // ─── Secret input (password-masked with eye-icon reveal) ────────────────────── +function EyeIcon({ open }: { open: boolean }) { + // GNOME-style eye / crossed-eye glyphs as inline SVG so they inherit + // currentColor instead of fighting emoji rendering across OSes. + if (open) { + return ( + + ); + } + return ( + + ); +} + /** * Input for API keys / passwords. Shows "***" masked when the server returns * a secret value (the raw key never reaches this component by default). Eye @@ -101,31 +144,33 @@ function SecretInput({ }; return ( -
+
onChange(e.target.value)} placeholder={placeholder} - className={`pr-16 ${className ?? ""}`} + className="pr-9" /> - - {locked && ( + {locked ? ( 🔒 + ) : ( + )}
); @@ -300,7 +345,7 @@ function ConnSection({ value={url} onChange={(e) => setUrl(e.target.value)} placeholder={urlPlaceholder} - className="mt-0.5 max-w-sm" + className="mt-0.5" />