import { useState, useEffect } from 'react'; import { useApiRequest } from './useApi'; // Import the centralized workflow interfaces import type { Workflow, WorkflowMessage, WorkflowStats, WorkflowDocument, WorkflowLog } from '../components/Dashboard/DashboardChat/dashboardChatAreaTypes'; export type { Workflow, WorkflowMessage, WorkflowStats, WorkflowDocument, WorkflowLog }; export interface StartWorkflowRequest { prompt: string; listFileId: string[]; } export interface StartWorkflowResponse { id: string; status: string; message: string; } // Workflows list hook export function useWorkflows() { const [workflows, setWorkflows] = useState([]); const { request, isLoading: loading, error } = useApiRequest(); const fetchWorkflows = async () => { try { const data = await request({ url: '/api/workflows/', method: 'get' }); setWorkflows(data); } catch (error) { // Error is already handled by useApiRequest } }; useEffect(() => { fetchWorkflows(); }, []); return { workflows, loading, error, refetch: fetchWorkflows }; } // Workflow operations hook export function useWorkflowOperations() { const [startingWorkflow, setStartingWorkflow] = useState(false); const [stoppingWorkflows, setStoppingWorkflows] = useState>(new Set()); const [deletingWorkflows, setDeletingWorkflows] = useState>(new Set()); const { request, isLoading } = useApiRequest(); const [startError, setStartError] = useState(null); const [stopError, setStopError] = useState(null); const [deleteError, setDeleteError] = useState(null); const startWorkflow = async (workflowData: StartWorkflowRequest, workflowId?: string) => { setStartError(null); setStartingWorkflow(true); try { const response = await request({ url: '/api/workflows/start', method: 'post', data: workflowData, params: workflowId ? { workflowId } : undefined }) as StartWorkflowResponse; return { success: true, data: response }; } catch (error: any) { setStartError(error.message); return { success: false, error: error.message }; } finally { setStartingWorkflow(false); } }; const stopWorkflow = async (workflowId: string) => { setStopError(null); setStoppingWorkflows(prev => new Set(prev).add(workflowId)); try { await request({ url: `/api/workflows/${workflowId}/stop`, method: 'post' }); return true; } catch (error: any) { setStopError(error.message); return false; } finally { setStoppingWorkflows(prev => { const newSet = new Set(prev); newSet.delete(workflowId); return newSet; }); } }; const deleteWorkflow = async (workflowId: string) => { setDeleteError(null); setDeletingWorkflows(prev => new Set(prev).add(workflowId)); try { await request({ url: `/api/workflows/${workflowId}`, method: 'delete' }); return true; } catch (error: any) { setDeleteError(error.message); return false; } finally { setDeletingWorkflows(prev => { const newSet = new Set(prev); newSet.delete(workflowId); return newSet; }); } }; const updateWorkflow = async (workflowId: string, updateData: Partial<{ name: string }>) => { setDeleteError(null); // Reuse delete error state for update operations try { const updatedWorkflow = await request({ url: `/api/workflows/${workflowId}`, method: 'put', data: updateData }); return { success: true, data: updatedWorkflow }; } catch (error: any) { setDeleteError(error.message); return { success: false, error: error.message }; } }; return { startingWorkflow, stoppingWorkflows, deletingWorkflows, startError, stopError, deleteError, startWorkflow, stopWorkflow, deleteWorkflow, updateWorkflow, isLoading }; } // Workflow status hook export function useWorkflowStatus(workflowId: string | null) { const [status, setStatus] = useState(null); const { request, isLoading: loading, error } = useApiRequest(); const fetchStatus = async () => { if (!workflowId) { // Clear status when no workflow is selected setStatus(null); return; } try { const data = await request({ url: `/api/workflows/${workflowId}/status`, method: 'get' }); setStatus(data); } catch (error) { // Error is already handled by useApiRequest } }; useEffect(() => { fetchStatus(); }, [workflowId]); return { status, loading, error, refetch: fetchStatus }; } // Enhanced workflow messages hook with better typing export function useWorkflowMessages(workflowId: string | null) { const [messages, setMessages] = useState([]); const [lastFetchTime, setLastFetchTime] = useState(0); const { request, isLoading: loading, error } = useApiRequest(); const fetchMessages = async () => { if (!workflowId) { setMessages([]); return []; } try { console.log(`📡 Fetching messages for workflow: ${workflowId}`); const data = await request({ url: `/api/workflows/${workflowId}/messages`, method: 'get' }); console.log(`📨 Raw API response for messages:`, { url: `/api/workflows/${workflowId}/messages`, responseType: typeof data, isArray: Array.isArray(data), length: data?.length, rawData: data, firstMessage: data?.[0], firstMessageKeys: data?.[0] ? Object.keys(data[0]) : [] }); // Only update if data has actually changed const hasChanged = JSON.stringify(data) !== JSON.stringify(messages); if (hasChanged) { console.log(`📝 Messages updated: ${messages.length} → ${data?.length || 0}`); setMessages(data); setLastFetchTime(Date.now()); } else { console.log(`📝 No changes in messages (${data?.length || 0} messages)`); } return data; } catch (error) { console.error('Failed to fetch workflow messages:', error); return []; } }; useEffect(() => { fetchMessages(); }, [workflowId]); return { messages, loading, error, refetch: fetchMessages, lastFetchTime }; } // Get single workflow hook export function useWorkflow(workflowId: string | null) { const [workflow, setWorkflow] = useState(null); const { request, isLoading: loading, error } = useApiRequest(); const fetchWorkflow = async () => { if (!workflowId) { setWorkflow(null); return null; } try { const data = await request({ url: `/api/workflows/${workflowId}`, method: 'get' }); setWorkflow(data); return data; } catch (error) { console.error('Failed to fetch workflow:', error); return null; } }; useEffect(() => { fetchWorkflow(); }, [workflowId]); return { workflow, loading, error, refetch: fetchWorkflow }; } // Enhanced workflow logs hook with better typing export function useWorkflowLogs(workflowId: string | null) { const [logs, setLogs] = useState([]); const [lastFetchTime, setLastFetchTime] = useState(0); const { request, isLoading: loading, error } = useApiRequest(); const fetchLogs = async () => { if (!workflowId) { setLogs([]); return []; } try { console.log(`📡 Fetching logs for workflow: ${workflowId}`); const data = await request({ url: `/api/workflows/${workflowId}/logs`, method: 'get' }); console.log(`📋 Raw API response for logs:`, { url: `/api/workflows/${workflowId}/logs`, responseType: typeof data, isArray: Array.isArray(data), length: data?.length, rawData: data, firstLog: data?.[0], firstLogKeys: data?.[0] ? Object.keys(data[0]) : [] }); // Only update if data has actually changed const hasChanged = JSON.stringify(data) !== JSON.stringify(logs); if (hasChanged) { console.log(`📋 Logs updated: ${logs.length} → ${data?.length || 0}`); setLogs(data || []); setLastFetchTime(Date.now()); } else { console.log(`📋 No changes in logs (${data?.length || 0} logs)`); } return data || []; } catch (error) { console.error('Failed to fetch workflow logs:', error); return []; } }; useEffect(() => { fetchLogs(); }, [workflowId]); return { logs, loading, error, refetch: fetchLogs, lastFetchTime }; } // File preview hook export function useFilePreview() { const [previewContent, setPreviewContent] = useState(null); const [fileMetadata, setFileMetadata] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const { request } = useApiRequest(); const fetchPreview = async (fileId: string | number) => { if (!fileId) { setError("File ID not available"); return; } setIsLoading(true); setError(null); setPreviewContent(null); setFileMetadata(null); try { // Convert fileId to number since backend expects integer let numericFileId: number; if (typeof fileId === 'number') { numericFileId = fileId; } else { numericFileId = parseInt(String(fileId), 10); } if (isNaN(numericFileId)) { throw new Error(`Invalid file ID format: "${fileId}" (type: ${typeof fileId}). Expected a numeric file ID, but got a document UUID. Make sure the document object has a 'fileId' property with the numeric file ID.`); } const response = await request({ url: `/api/files/${numericFileId}/preview`, method: 'get' }); // Handle response as object with metadata and preview content if (typeof response === 'object' && response !== null) { setFileMetadata(response); // Debug: log the full response console.log('Full backend response:', response); console.log('Response keys:', Object.keys(response)); // Try different possible property names for the content const content = response.preview || response.content || response.data || response.previewContent; console.log('Extracted content:', content ? 'has content' : 'null/empty'); console.log('Content type:', typeof content); console.log('Content length:', content?.length); // If base64Encoded is true and we have content, try to decode it let processedContent = content; if (response.base64Encoded && content && typeof content === 'string') { try { processedContent = atob(content); console.log('Decoded base64 content:', processedContent.substring(0, 200)); } catch (e) { console.error('Failed to decode base64 content:', e); } } // If no preview content but file should be previewable, try to fetch raw content if (!processedContent && response.name) { const fileExtension = response.name.split('.').pop()?.toLowerCase(); const shouldBePreviewable = ['md', 'markdown', 'txt', 'py', 'js', 'ts', 'jsx', 'tsx', 'html', 'css', 'json', 'xml', 'yaml', 'yml'].includes(fileExtension || ''); if (shouldBePreviewable) { console.log('File should be previewable, attempting to fetch raw content...'); try { // Try to fetch the raw file content using download endpoint const rawResponse = await request({ url: `/api/files/${numericFileId}/download`, method: 'get', additionalConfig: { responseType: 'text' } }); console.log('Raw content fetched:', typeof rawResponse, rawResponse?.substring?.(0, 200)); setPreviewContent(rawResponse || null); return; } catch (rawError) { console.error('Failed to fetch raw content:', rawError); } } } setPreviewContent(processedContent || null); } else { // Fallback if response is just the content setPreviewContent(response); } } catch (err: any) { setError(err.message || "Failed to load preview"); } finally { setIsLoading(false); } }; const clearPreview = () => { setPreviewContent(null); setFileMetadata(null); setError(null); setIsLoading(false); }; return { previewContent, fileMetadata, isLoading, error, fetchPreview, clearPreview }; } // File download hook export function useFileDownload() { const [isDownloading, setIsDownloading] = useState(false); const [error, setError] = useState(null); const { request } = useApiRequest(); const downloadFile = async (fileId: string | number, fileName?: string) => { if (!fileId) { setError("File ID not available"); return; } setIsDownloading(true); setError(null); try { // Convert fileId to number since backend expects integer let numericFileId: number; if (typeof fileId === 'number') { numericFileId = fileId; } else { numericFileId = parseInt(String(fileId), 10); } if (isNaN(numericFileId)) { throw new Error(`Invalid file ID format: "${fileId}" (type: ${typeof fileId}). Expected a numeric file ID, but got a document UUID. Make sure the document object has a 'fileId' property with the numeric file ID.`); } // Use the same approach as useFiles.ts - use request with blob response type const blob = await request({ url: `/api/files/${numericFileId}/download`, method: 'get', // Override axios config for blob response additionalConfig: { responseType: 'blob' } }); // Use provided fileName or fallback to 'download' const downloadFileName = fileName || 'download'; // Create download link and trigger download (same as useFiles.ts) const url = window.URL.createObjectURL(new Blob([blob])); const link = document.createElement('a'); link.href = url; link.setAttribute('download', downloadFileName); document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); return true; } catch (err: any) { setError(err.message || "Failed to download file"); return false; } finally { setIsDownloading(false); } }; const clearError = () => { setError(null); }; return { isDownloading, error, downloadFile, clearError }; }