"use client" import React, { createContext, useContext, useState, useEffect, useCallback } from 'react' import { toast } from 'sonner' import { useAuth } from '@/contexts/auth-context' export interface Task { task_id: string status: 'pending' | 'running' | 'processing' | 'completed' | 'failed' | 'error' total_files?: number processed_files?: number successful_files?: number failed_files?: number created_at: string updated_at: string result?: any error?: string files?: { [key: string]: any } } interface TaskContextType { tasks: Task[] addTask: (taskId: string) => void removeTask: (taskId: string) => void refreshTasks: () => Promise cancelTask: (taskId: string) => Promise isPolling: boolean isFetching: boolean isMenuOpen: boolean toggleMenu: () => void } const TaskContext = createContext(undefined) export function TaskProvider({ children }: { children: React.ReactNode }) { const [tasks, setTasks] = useState([]) const [isPolling, setIsPolling] = useState(false) const [isFetching, setIsFetching] = useState(false) const [isMenuOpen, setIsMenuOpen] = useState(false) const [trackedTaskIds, setTrackedTaskIds] = useState>(new Set()) const { isAuthenticated } = useAuth() const fetchTasks = useCallback(async () => { if (!isAuthenticated) return setIsFetching(true) try { const response = await fetch('/api/tasks') if (response.ok) { const data = await response.json() const newTasks = data.tasks || [] // Update tasks and check for status changes in the same state update setTasks(prevTasks => { // Check for newly completed tasks to show toasts if (prevTasks.length > 0) { newTasks.forEach((newTask: Task) => { const oldTask = prevTasks.find(t => t.task_id === newTask.task_id) if (oldTask && oldTask.status !== 'completed' && newTask.status === 'completed') { // Task just completed - show success toast toast.success("Task completed successfully!", { description: `Task ${newTask.task_id} has finished processing.`, action: { label: "View", onClick: () => console.log("View task", newTask.task_id), }, }) } else if (oldTask && oldTask.status !== 'failed' && oldTask.status !== 'error' && (newTask.status === 'failed' || newTask.status === 'error')) { // Task just failed - show error toast toast.error("Task failed", { description: `Task ${newTask.task_id} failed: ${newTask.error || 'Unknown error'}`, }) } }) } return newTasks }) } } catch (error) { console.error('Failed to fetch tasks:', error) } finally { setIsFetching(false) } }, [isAuthenticated]) // Removed 'tasks' from dependencies to prevent infinite loop! const addTask = useCallback((taskId: string) => { setTrackedTaskIds(prev => new Set(prev).add(taskId)) // Immediately start aggressive polling for the new task let pollAttempts = 0 const maxPollAttempts = 30 // Poll for up to 30 seconds const aggressivePoll = async () => { try { const response = await fetch('/api/tasks') if (response.ok) { const data = await response.json() const newTasks = data.tasks || [] const foundTask = newTasks.find((task: Task) => task.task_id === taskId) if (foundTask) { // Task found! Update the tasks state setTasks(prevTasks => { // Check if task is already in the list const exists = prevTasks.some(t => t.task_id === taskId) if (!exists) { return [...prevTasks, foundTask] } // Update existing task return prevTasks.map(t => t.task_id === taskId ? foundTask : t) }) return // Stop polling, we found it } } } catch (error) { console.error('Aggressive polling failed:', error) } pollAttempts++ if (pollAttempts < maxPollAttempts) { // Continue polling every 1 second for new tasks setTimeout(aggressivePoll, 1000) } } // Start aggressive polling after a short delay to allow backend to process setTimeout(aggressivePoll, 500) }, []) const refreshTasks = useCallback(async () => { await fetchTasks() }, [fetchTasks]) const removeTask = useCallback((taskId: string) => { setTrackedTaskIds(prev => { const newSet = new Set(prev) newSet.delete(taskId) return newSet }) }, []) const cancelTask = useCallback(async (taskId: string) => { try { const response = await fetch(`/api/tasks/${taskId}/cancel`, { method: 'POST', }) if (response.ok) { // Immediately refresh tasks to show the updated status await fetchTasks() toast.success("Task cancelled", { description: `Task ${taskId.substring(0, 8)}... has been cancelled` }) } else { const errorData = await response.json().catch(() => ({})) throw new Error(errorData.error || 'Failed to cancel task') } } catch (error) { console.error('Failed to cancel task:', error) toast.error("Failed to cancel task", { description: error instanceof Error ? error.message : 'Unknown error' }) } }, [fetchTasks]) const toggleMenu = useCallback(() => { setIsMenuOpen(prev => !prev) }, []) // Periodic polling for task updates useEffect(() => { if (!isAuthenticated) return setIsPolling(true) // Initial fetch fetchTasks() // Set up polling interval - every 3 seconds (more responsive for active tasks) const interval = setInterval(fetchTasks, 3000) return () => { clearInterval(interval) setIsPolling(false) } }, [isAuthenticated, fetchTasks]) const value: TaskContextType = { tasks, addTask, removeTask, refreshTasks, cancelTask, isPolling, isFetching, isMenuOpen, toggleMenu, } return ( {children} ) } export function useTask() { const context = useContext(TaskContext) if (context === undefined) { throw new Error('useTask must be used within a TaskProvider') } return context }