diff --git a/frontend/src/components/task-notification-menu.tsx b/frontend/src/components/task-notification-menu.tsx
index 372717d4..c6f94959 100644
--- a/frontend/src/components/task-notification-menu.tsx
+++ b/frontend/src/components/task-notification-menu.tsx
@@ -76,6 +76,22 @@ export function TaskNotificationMenu() {
return null
}
+ const formatDuration = (seconds?: number) => {
+ if (!seconds || seconds < 0) return null
+
+ if (seconds < 60) {
+ return `${Math.round(seconds)}s`
+ } else if (seconds < 3600) {
+ const mins = Math.floor(seconds / 60)
+ const secs = Math.round(seconds % 60)
+ return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`
+ } else {
+ const hours = Math.floor(seconds / 3600)
+ const mins = Math.floor((seconds % 3600) / 60)
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`
+ }
+ }
+
const formatRelativeTime = (dateString: string) => {
// Handle different timestamp formats
let date: Date
@@ -153,6 +169,11 @@ export function TaskNotificationMenu() {
Started {formatRelativeTime(task.created_at)}
+ {formatDuration(task.duration_seconds) && (
+
+ • {formatDuration(task.duration_seconds)}
+
+ )}
{formatTaskProgress(task) && (
@@ -256,6 +277,11 @@ export function TaskNotificationMenu() {
{formatRelativeTime(task.updated_at)}
+ {formatDuration(task.duration_seconds) && (
+
+ • {formatDuration(task.duration_seconds)}
+
+ )}
{/* Show final results for completed tasks */}
{task.status === 'completed' && formatTaskProgress(task)?.detailed && (
diff --git a/frontend/src/contexts/task-context.tsx b/frontend/src/contexts/task-context.tsx
index 77d1e57e..c132f39b 100644
--- a/frontend/src/contexts/task-context.tsx
+++ b/frontend/src/contexts/task-context.tsx
@@ -13,6 +13,7 @@ export interface Task {
failed_files?: number
created_at: string
updated_at: string
+ duration_seconds?: number
result?: Record
error?: string
files?: Record>
diff --git a/src/models/tasks.py b/src/models/tasks.py
index db67f8bf..236927ab 100644
--- a/src/models/tasks.py
+++ b/src/models/tasks.py
@@ -20,6 +20,11 @@ class FileTask:
retry_count: int = 0
created_at: float = field(default_factory=time.time)
updated_at: float = field(default_factory=time.time)
+
+ @property
+ def duration_seconds(self) -> float:
+ """Duration in seconds from creation to last update"""
+ return self.updated_at - self.created_at
@dataclass
@@ -33,3 +38,8 @@ class UploadTask:
status: TaskStatus = TaskStatus.PENDING
created_at: float = field(default_factory=time.time)
updated_at: float = field(default_factory=time.time)
+
+ @property
+ def duration_seconds(self) -> float:
+ """Duration in seconds from creation to last update"""
+ return self.updated_at - self.created_at
diff --git a/src/services/task_service.py b/src/services/task_service.py
index f3d22234..8e69d4ae 100644
--- a/src/services/task_service.py
+++ b/src/services/task_service.py
@@ -224,6 +224,7 @@ class TaskService:
"retry_count": file_task.retry_count,
"created_at": file_task.created_at,
"updated_at": file_task.updated_at,
+ "duration_seconds": file_task.duration_seconds,
}
return {
@@ -235,6 +236,7 @@ class TaskService:
"failed_files": upload_task.failed_files,
"created_at": upload_task.created_at,
"updated_at": upload_task.updated_at,
+ "duration_seconds": upload_task.duration_seconds,
"files": file_statuses,
}
@@ -262,6 +264,7 @@ class TaskService:
"failed_files": upload_task.failed_files,
"created_at": upload_task.created_at,
"updated_at": upload_task.updated_at,
+ "duration_seconds": upload_task.duration_seconds,
}
# First, add user-owned tasks; then shared anonymous;