/** * Custom hook for trading analysis with async task support */ "use client"; import { useState, useEffect, useRef } from "react"; import { api } from "@/lib/api"; import type { AnalysisRequest, AnalysisResponse } from "@/lib/types"; export function useAnalysis() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [result, setResult] = useState(null); const [taskId, setTaskId] = useState(null); const [progress, setProgress] = useState(null); const pollingIntervalRef = useRef(null); // Poll for task status const pollTaskStatus = async (id: string) => { try { const status = await api.getTaskStatus(id); // Update progress if (status.progress) { setProgress(status.progress); } // Check if completed if (status.status === "completed") { if (status.result) { setResult(status.result); } setLoading(false); setProgress(null); // Clear pending task since it's completed const { clearPendingTask } = await import('@/lib/pending-task'); clearPendingTask(); // 🧹 Immediately cleanup Redis cache after receiving result // The result is already stored in React state, so Redis data is no longer needed try { await api.cleanupTask(id); console.log("🧹 Redis cache cleaned up immediately after analysis completed"); } catch (cleanupErr) { // Silently fail - cleanup is optional, task will auto-expire anyway console.warn("Redis cleanup failed (will auto-expire):", cleanupErr); } // Stop polling if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); pollingIntervalRef.current = null; } return true; } // Check if failed if (status.status === "failed") { // Check if we have structured error data from result if (status.result && status.result.error) { setError({ error: status.result.error, error_type: status.result.error_type, retry_after: status.result.retry_after, quota_limit: status.result.quota_limit, }); } else { setError(status.error || "Analysis failed"); } setLoading(false); setProgress(null); // Clear pending task since it failed const { clearPendingTask } = await import('@/lib/pending-task'); clearPendingTask(); // 🧹 Cleanup Redis cache for failed task try { await api.cleanupTask(id); console.log("🧹 Redis cache cleaned up after analysis failed"); } catch (cleanupErr) { console.warn("Redis cleanup failed (will auto-expire):", cleanupErr); } // Stop polling if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); pollingIntervalRef.current = null; } return true; } return false; // Still running } catch (err: any) { console.error("Error polling task status:", err); // Don't stop polling on temporary errors return false; } }; // Start polling const startPolling = (id: string) => { // Clear any existing interval if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); } // Poll every 3 seconds pollingIntervalRef.current = setInterval(async () => { await pollTaskStatus(id); }, 3000); // Also poll immediately pollTaskStatus(id); }; // Cleanup on unmount useEffect(() => { return () => { if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); pollingIntervalRef.current = null; } }; }, []); const runAnalysis = async (request: AnalysisRequest) => { setLoading(true); setError(null); setResult(null); setProgress("Submitting analysis request..."); try { // Start analysis task const taskResponse = await api.runAnalysis(request); setTaskId(taskResponse.task_id); setProgress("Analysis started, waiting for results..."); // Save pending task to localStorage for recovery if page closes const { savePendingTask } = await import('@/lib/pending-task'); savePendingTask({ taskId: taskResponse.task_id, ticker: request.ticker, marketType: request.market_type || 'us', analysisDate: request.analysis_date, startedAt: new Date().toISOString(), }); // Start polling for status startPolling(taskResponse.task_id); return taskResponse; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || "Failed to start analysis"; setError(errorMessage); setLoading(false); setProgress(null); throw err; } }; const reset = () => { // Stop polling if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); pollingIntervalRef.current = null; } setLoading(false); setError(null); setResult(null); setTaskId(null); setProgress(null); }; return { runAnalysis, loading, error, result, taskId, progress, reset, }; }