frontend_nyla/src/hooks/useWorkflows.ts

334 lines
No EOL
8.8 KiB
TypeScript

import { useState, useEffect, useCallback, useRef } from 'react';
import { useApiRequest } from './useApi';
// Workflow interfaces
export interface Workflow {
id: string;
name?: string;
status: string;
startedAt?: string;
lastActivity?: string;
currentRound?: number;
dataStats?: Record<string, any>;
userId?: number;
messageIds?: string[];
}
export interface WorkflowMessage {
id: string;
content: string;
role: 'user' | 'assistant' | 'system';
timestamp?: string;
sequenceNo?: number;
fileIds?: number[];
}
export interface StartWorkflowRequest {
prompt: string;
listFileId: number[];
}
export interface StartWorkflowResponse {
id: string;
status: string;
message: string;
}
// Workflows list hook
export function useWorkflows() {
const [workflows, setWorkflows] = useState<Workflow[]>([]);
const { request, isLoading: loading, error } = useApiRequest<null, Workflow[]>();
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<Set<string>>(new Set());
const [deletingWorkflows, setDeletingWorkflows] = useState<Set<string>>(new Set());
const { request, error: apiError, isLoading } = useApiRequest();
const [startError, setStartError] = useState<string | null>(null);
const [stopError, setStopError] = useState<string | null>(null);
const [deleteError, setDeleteError] = useState<string | null>(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;
});
}
};
return {
startingWorkflow,
stoppingWorkflows,
deletingWorkflows,
startError,
stopError,
deleteError,
startWorkflow,
stopWorkflow,
deleteWorkflow,
isLoading
};
}
// Workflow status hook
export function useWorkflowStatus(workflowId: string | null) {
const [status, setStatus] = useState<Workflow | null>(null);
const { request, isLoading: loading, error } = useApiRequest<null, Workflow>();
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 };
}
// Workflow messages hook
export function useWorkflowMessages(workflowId: string | null, messageId?: string) {
const [messages, setMessages] = useState<WorkflowMessage[]>([]);
const { request, isLoading: loading, error } = useApiRequest<null, WorkflowMessage[]>();
const fetchMessages = async () => {
if (!workflowId) {
// Clear messages when no workflow is selected
setMessages([]);
return;
}
try {
const data = await request({
url: `/api/workflows/${workflowId}/messages`,
method: 'get',
params: messageId ? { messageId } : undefined
});
setMessages(data);
} catch (error) {
// Error is already handled by useApiRequest
}
};
useEffect(() => {
fetchMessages();
}, [workflowId, messageId]);
return { messages, loading, error, refetch: fetchMessages };
}
// Workflow logs hook
export function useWorkflowLogs(workflowId: string | null, logId?: string) {
const [logs, setLogs] = useState<any[]>([]);
const { request, isLoading: loading, error } = useApiRequest<null, any[]>();
const fetchLogs = async () => {
if (!workflowId) return;
try {
const data = await request({
url: `/api/workflows/${workflowId}/logs`,
method: 'get',
params: logId ? { logId } : undefined
});
setLogs(data);
} catch (error) {
// Error is already handled by useApiRequest
}
};
useEffect(() => {
fetchLogs();
}, [workflowId, logId]);
return { logs, loading, error, refetch: fetchLogs };
}
// File preview hook
export function useFilePreview() {
const [previewContent, setPreviewContent] = useState<string | null>(null);
const [fileMetadata, setFileMetadata] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { request } = useApiRequest();
const fetchPreview = async (fileId: string | number) => {
console.log('fetchPreview called with fileId:', fileId, 'type:', typeof fileId);
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);
}
console.log('Parsed fileId:', numericFileId, 'isNaN:', isNaN(numericFileId));
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.`);
}
console.log('Making API request to:', `/api/workflows/files/${numericFileId}/preview`);
const response = await request({
url: `/api/workflows/files/${numericFileId}/preview`,
method: 'get'
});
console.log('API response:', response);
// Handle response as object with metadata and preview content
if (typeof response === 'object' && response !== null) {
setFileMetadata(response);
// Try different possible property names for the content
const content = response.preview || response.content || response.data || response.previewContent;
// Debug for PDF issues only
if (response.mimeType === 'application/pdf') {
console.log('PDF Preview Debug:', {
hasPreview: !!response.preview,
previewLength: response.preview?.length,
hasBase64Flag: response.base64Encoded,
mimeType: response.mimeType
});
}
setPreviewContent(content || null);
} else {
// Fallback if response is just the content
setPreviewContent(response);
}
} catch (err: any) {
console.error('File preview error:', err);
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
};
}