From a6fe743ebe8055142eedba8cbbf9f62fc676a571 Mon Sep 17 00:00:00 2001 From: Radouane Elhajali Date: Thu, 26 Mar 2026 14:55:54 +0100 Subject: [PATCH] studio: humanize ETA display for long training runs (#4608) * studio: humanize ETA display for long training runs When training takes hours or days, the ETA displayed raw minutes (e.g. '560m 50s'). This changes the format to: - Under 1 hour: Xm Ys (unchanged) - 1-24 hours: Xh Ym Zs - Over 24 hours: Xd Xh Xm * Fix formatDuration edge cases and consolidate duplicate for PR #4608 - Guard NaN/Infinity inputs with Number.isFinite() (matches formatNumber in same file) - Add sub-minute branch so 30s displays as "30s" instead of "0m 30s" - Accept undefined in type signature to match formatNumber pattern - Remove duplicate formatDuration from history-card-grid.tsx and import the shared one --------- Co-authored-by: Daniel Han --- .../src/features/studio/history-card-grid.tsx | 11 +---------- .../studio/sections/progress-section-lib.ts | 15 ++++++++++----- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/studio/frontend/src/features/studio/history-card-grid.tsx b/studio/frontend/src/features/studio/history-card-grid.tsx index 78859d2f81..9e72c9f75c 100644 --- a/studio/frontend/src/features/studio/history-card-grid.tsx +++ b/studio/frontend/src/features/studio/history-card-grid.tsx @@ -14,6 +14,7 @@ import { import { Button } from "@/components/ui/button"; import type { TrainingRunSummary } from "@/features/training"; import { deleteTrainingRun, listTrainingRuns } from "@/features/training"; +import { formatDuration } from "@/features/studio/sections/progress-section-lib"; import { cn } from "@/lib/utils"; import { Delete02Icon } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; @@ -129,16 +130,6 @@ function formatRelativeTime(isoDate: string): string { return `${days}d ago`; } -function formatDuration(seconds: number | null): string { - if (seconds == null) return "--"; - const total = Math.floor(seconds); - if (total < 60) return `${total}s`; - const min = Math.floor(total / 60); - const sec = total % 60; - if (min < 60) return `${min}m ${sec}s`; - const hrs = Math.floor(min / 60); - return `${hrs}h ${min % 60}m`; -} interface HistoryCardGridProps { onSelectRun: (runId: string) => void; diff --git a/studio/frontend/src/features/studio/sections/progress-section-lib.ts b/studio/frontend/src/features/studio/sections/progress-section-lib.ts index b0f484f0eb..d8bf87c288 100644 --- a/studio/frontend/src/features/studio/sections/progress-section-lib.ts +++ b/studio/frontend/src/features/studio/sections/progress-section-lib.ts @@ -35,12 +35,17 @@ export const phaseColors: Record = { stopped: "bg-muted text-muted-foreground", }; -export function formatDuration(seconds: number | null): string { - if (seconds == null || seconds < 0) return "--"; +export function formatDuration(seconds: number | null | undefined): string { + if (seconds == null || !Number.isFinite(seconds) || seconds < 0) return "--"; const total = Math.floor(seconds); - const min = Math.floor(total / 60); - const sec = total % 60; - return `${min}m ${sec}s`; + const d = Math.floor(total / 86400); + const h = Math.floor((total % 86400) / 3600); + const m = Math.floor((total % 3600) / 60); + const s = total % 60; + if (d > 0) return `${d}d ${h}h ${m}m`; + if (h > 0) return `${h}h ${m}m ${s}s`; + if (m > 0) return `${m}m ${s}s`; + return `${s}s`; } export function formatNumber(value: number | null | undefined, digits: number): string {