diff --git a/frontend/src/app/api/queries/useGetSearchQuery.ts b/frontend/src/app/api/queries/useGetSearchQuery.ts index 50905fcc..3db14cb2 100644 --- a/frontend/src/app/api/queries/useGetSearchQuery.ts +++ b/frontend/src/app/api/queries/useGetSearchQuery.ts @@ -50,6 +50,7 @@ export interface File { | "failed" | "hidden" | "sync"; + error?: string; chunks?: ChunkResult[]; } diff --git a/frontend/src/app/api/queries/useGetTasksQuery.ts b/frontend/src/app/api/queries/useGetTasksQuery.ts index 1ea59d26..f7420dd7 100644 --- a/frontend/src/app/api/queries/useGetTasksQuery.ts +++ b/frontend/src/app/api/queries/useGetTasksQuery.ts @@ -4,6 +4,24 @@ import { useQueryClient, } from "@tanstack/react-query"; +export interface TaskFileEntry { + status?: + | "pending" + | "running" + | "processing" + | "completed" + | "failed" + | "error"; + result?: unknown; + error?: string; + retry_count?: number; + created_at?: string; + updated_at?: string; + duration_seconds?: number; + filename?: string; + [key: string]: unknown; +} + export interface Task { task_id: string; status: @@ -24,7 +42,7 @@ export interface Task { duration_seconds?: number; result?: Record; error?: string; - files?: Record>; + files?: Record; } export interface TasksResponse { diff --git a/frontend/src/app/knowledge/page.tsx b/frontend/src/app/knowledge/page.tsx index 334f8e6f..385fa9be 100644 --- a/frontend/src/app/knowledge/page.tsx +++ b/frontend/src/app/knowledge/page.tsx @@ -26,6 +26,14 @@ import GoogleDriveIcon from "../settings/icons/google-drive-icon"; import OneDriveIcon from "../settings/icons/one-drive-icon"; import SharePointIcon from "../settings/icons/share-point-icon"; import { KnowledgeSearchInput } from "@/components/knowledge-search-input"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; // Function to get the appropriate icon for a connector type function getSourceIcon(connectorType?: string) { @@ -77,6 +85,7 @@ function SearchPage() { size: taskFile.size, connector_type: taskFile.connector_type, status: taskFile.status, + error: taskFile.error, }; }); @@ -128,7 +137,6 @@ function SearchPage() { // Read status directly from data on each render const status = data?.status || "active"; const isActive = status === "active"; - console.log(data?.filename, status, "a"); return (
) => { - console.log(data?.filename, data?.status, "b"); - // Default to 'active' status if no status is provided const status = data?.status || "active"; + const error = + typeof data?.error === "string" && data.error.trim().length > 0 + ? data.error.trim() + : undefined; + if (status === "failed" && error) { + return ( + + + + + + + Ingestion failed + + {data?.filename || "Unknown file"} + + +
+ {error} +
+
+
+ ); + } return ; }, }, diff --git a/frontend/src/components/task-notification-menu.tsx b/frontend/src/components/task-notification-menu.tsx index 6cb968e8..7e6dcb0a 100644 --- a/frontend/src/components/task-notification-menu.tsx +++ b/frontend/src/components/task-notification-menu.tsx @@ -169,95 +169,87 @@ export function TaskNotificationMenu() { {activeTasks.length > 0 && (

Active Tasks

- {activeTasks.map((task) => ( - - -
- - {getTaskIcon(task.status)} - Task {task.task_id.substring(0, 8)}... - -
- - Started {formatRelativeTime(task.created_at)} - {formatDuration(task.duration_seconds) && ( - - • {formatDuration(task.duration_seconds)} - - )} - -
- {formatTaskProgress(task) && ( - -
-
- Progress: {formatTaskProgress(task)?.basic} -
- {formatTaskProgress(task)?.detailed && ( -
-
-
- - {formatTaskProgress(task)?.detailed.successful} success - -
-
-
- - {formatTaskProgress(task)?.detailed.failed} failed - -
-
-
- - {formatTaskProgress(task)?.detailed.running} running - -
-
-
- - {formatTaskProgress(task)?.detailed.pending} pending - + {activeTasks.map((task) => { + const progress = formatTaskProgress(task) + const showCancel = + task.status === 'pending' || + task.status === 'running' || + task.status === 'processing' + + return ( + + +
+ + {getTaskIcon(task.status)} + Task {task.task_id.substring(0, 8)}... + +
+ + Started {formatRelativeTime(task.created_at)} + {formatDuration(task.duration_seconds) && ( + + • {formatDuration(task.duration_seconds)} + + )} + +
+ {(progress || showCancel) && ( + + {progress && ( +
+
+ Progress: {progress.basic}
+ {progress.detailed && ( +
+
+
+ + {progress.detailed.successful} success + +
+
+
+ + {progress.detailed.failed} failed + +
+
+
+ + {progress.detailed.running} running + +
+
+
+ + {progress.detailed.pending} pending + +
+
+ )}
)} -
- {/* Cancel button in bottom right */} - {(task.status === 'pending' || task.status === 'running' || task.status === 'processing') && ( -
- -
- )} - - )} - {/* Cancel button for tasks without progress */} - {!formatTaskProgress(task) && (task.status === 'pending' || task.status === 'running' || task.status === 'processing') && ( - -
- -
-
- )} - - ))} + {showCancel && ( +
+ +
+ )} + + )} + + ) + })}
)} @@ -282,43 +274,47 @@ export function TaskNotificationMenu() { {isExpanded && (
- {recentTasks.map((task) => ( -
- {getTaskIcon(task.status)} -
-
- Task {task.task_id.substring(0, 8)}... -
-
- {formatRelativeTime(task.updated_at)} - {formatDuration(task.duration_seconds) && ( - - • {formatDuration(task.duration_seconds)} - - )} -
- {/* Show final results for completed tasks */} - {task.status === 'completed' && formatTaskProgress(task)?.detailed && ( -
- {formatTaskProgress(task)?.detailed.successful} success, {' '} - {formatTaskProgress(task)?.detailed.failed} failed - {(formatTaskProgress(task)?.detailed.running || 0) > 0 && ( - , {formatTaskProgress(task)?.detailed.running} running + {recentTasks.map((task) => { + const progress = formatTaskProgress(task) + + return ( +
+ {getTaskIcon(task.status)} +
+
+ Task {task.task_id.substring(0, 8)}... +
+
+ {formatRelativeTime(task.updated_at)} + {formatDuration(task.duration_seconds) && ( + + • {formatDuration(task.duration_seconds)} + )}
- )} - {task.status === 'failed' && task.error && ( -
- {task.error} -
- )} + {/* Show final results for completed tasks */} + {task.status === 'completed' && progress?.detailed && ( +
+ {progress.detailed.successful} success,{' '} + {progress.detailed.failed} failed + {(progress.detailed.running || 0) > 0 && ( + , {progress.detailed.running} running + )} +
+ )} + {task.status === 'failed' && task.error && ( +
+ {task.error} +
+ )} +
+ {getStatusBadge(task.status)}
- {getStatusBadge(task.status)} -
- ))} + ) + })}
)}
@@ -338,4 +334,4 @@ export function TaskNotificationMenu() {
) -} \ No newline at end of file +} diff --git a/frontend/src/contexts/task-context.tsx b/frontend/src/contexts/task-context.tsx index 9b3d9908..475c36f6 100644 --- a/frontend/src/contexts/task-context.tsx +++ b/frontend/src/contexts/task-context.tsx @@ -14,6 +14,7 @@ import { toast } from "sonner"; import { useCancelTaskMutation } from "@/app/api/mutations/useCancelTaskMutation"; import { type Task, + type TaskFileEntry, useGetTasksQuery, } from "@/app/api/queries/useGetTasksQuery"; import { useAuth } from "@/contexts/auth-context"; @@ -31,6 +32,7 @@ export interface TaskFile { task_id: string; created_at: string; updated_at: string; + error?: string; } interface TaskContextType { tasks: Task[]; @@ -105,6 +107,7 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { task_id: taskId, created_at: now, updated_at: now, + error: file.error, })); setFiles((prevFiles) => [...prevFiles, ...filesToAdd]); @@ -138,12 +141,13 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { taskFileEntries.forEach(([filePath, fileInfo]) => { if (typeof fileInfo === "object" && fileInfo) { + const fileInfoEntry = fileInfo as TaskFileEntry; // Use the filename from backend if available, otherwise extract from path const fileName = - (fileInfo as any).filename || + fileInfoEntry.filename || filePath.split("/").pop() || filePath; - const fileStatus = fileInfo.status as string; + const fileStatus = fileInfoEntry.status ?? "processing"; // Map backend file status to our TaskFile status let mappedStatus: TaskFile["status"]; @@ -162,6 +166,23 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { mappedStatus = "processing"; } + const fileError = (() => { + if ( + typeof fileInfoEntry.error === "string" && + fileInfoEntry.error.trim().length > 0 + ) { + return fileInfoEntry.error.trim(); + } + if ( + mappedStatus === "failed" && + typeof currentTask.error === "string" && + currentTask.error.trim().length > 0 + ) { + return currentTask.error.trim(); + } + return undefined; + })(); + setFiles((prevFiles) => { const existingFileIndex = prevFiles.findIndex( (f) => @@ -185,13 +206,14 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { status: mappedStatus, task_id: currentTask.task_id, created_at: - typeof fileInfo.created_at === "string" - ? fileInfo.created_at + typeof fileInfoEntry.created_at === "string" + ? fileInfoEntry.created_at : now, updated_at: - typeof fileInfo.updated_at === "string" - ? fileInfo.updated_at + typeof fileInfoEntry.updated_at === "string" + ? fileInfoEntry.updated_at : now, + error: fileError, }; if (existingFileIndex >= 0) {