import { ApiRequestOptions } from '../hooks/useApi'; import api from '../api'; import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from '../utils/csrfUtils'; import { Message } from '../components/UiComponents/Messages/MessagesTypes'; // ============================================================================ // TYPES & INTERFACES // ============================================================================ export interface UserInputRequest { input: string; workflowId?: string; files?: Array<{ id: string; name: string }>; metadata?: Record; } export interface ChatbotWorkflow { id: string; mandateId: string; status: string; name?: string; currentRound?: number; currentTask?: number; currentAction?: number; startedAt?: number; lastActivity?: number; [key: string]: any; } export interface StartChatbotRequest { prompt: string; listFileId?: string[]; userLanguage?: string; workflowId?: string; metadata?: Record; } export interface StartChatbotResponse extends ChatbotWorkflow { // Workflow object returned from start endpoint } export interface ChatDataItem { type: 'message' | 'log' | 'stat' | 'document' | 'stopped' | 'status'; createdAt: number; item: Message | any; label?: string; // For status events } // Type for the request function passed to API functions export type ApiRequestFunction = (options: ApiRequestOptions) => Promise; // Type for SSE event handler export type SSEEventHandler = (item: ChatDataItem) => void; // ============================================================================ // API REQUEST FUNCTIONS // ============================================================================ /** * Start a new chatbot workflow or continue an existing one with SSE streaming * Endpoint: POST /api/chatbot/{instanceId}/start/stream * * @param instanceId - Feature Instance ID * @param requestBody - Request body with prompt and optional workflowId * @param onEvent - Callback function called for each SSE event * @param onError - Optional error callback * @param onComplete - Optional completion callback * @returns Promise that resolves when stream completes */ export async function startChatbotStreamApi( instanceId: string, requestBody: StartChatbotRequest, onEvent: SSEEventHandler, onError?: (error: Error) => void, onComplete?: () => void ): Promise { try { // Prepare request body console.log('[startChatbotStreamApi] instanceId:', instanceId); console.log('[startChatbotStreamApi] requestBody received:', JSON.stringify(requestBody, null, 2)); const body: any = { prompt: requestBody.prompt, ...(requestBody.listFileId && requestBody.listFileId.length > 0 && { listFileId: requestBody.listFileId }), ...(requestBody.userLanguage && { userLanguage: requestBody.userLanguage }), ...(requestBody.metadata && { metadata: requestBody.metadata }) }; console.log('[startChatbotStreamApi] body being sent:', JSON.stringify(body, null, 2)); // Add workflowId to query params if provided const url = requestBody.workflowId ? `/api/chatbot/${instanceId}/start/stream?workflowId=${encodeURIComponent(requestBody.workflowId)}` : `/api/chatbot/${instanceId}/start/stream`; // Get base URL from api instance const baseURL = api.defaults.baseURL || ''; const fullURL = baseURL + url; // Prepare headers with authentication and CSRF token const headers: Record = { 'Content-Type': 'application/json' }; // Add auth token if available const authToken = localStorage.getItem('authToken'); if (authToken) { headers['Authorization'] = `Bearer ${authToken}`; } // Add CSRF token for POST requests if (!getCSRFToken()) { generateAndStoreCSRFToken(); } addCSRFTokenToHeaders(headers); // Use fetch for SSE streaming (POST with body) const response = await fetch(fullURL, { method: 'POST', headers, body: JSON.stringify(body), credentials: 'include' // Include cookies for authentication }); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`); } if (!response.body) { throw new Error('Response body is null'); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; try { while (true) { const { done, value } = await reader.read(); if (done) { break; } // Decode chunk and add to buffer buffer += decoder.decode(value, { stream: true }); // Process complete SSE messages const lines = buffer.split('\n'); buffer = lines.pop() || ''; // Keep incomplete line in buffer for (const line of lines) { if (line.startsWith('data: ')) { try { const jsonStr = line.slice(6); // Remove 'data: ' prefix if (jsonStr.trim()) { const item: ChatDataItem = JSON.parse(jsonStr); console.log('[SSE] Received event:', item.type, item); onEvent(item); } } catch (parseError) { console.warn('Failed to parse SSE event:', line, parseError); } } else if (line.startsWith(':')) { // Comment/keepalive line, ignore continue; } } } // Process any remaining buffer content if (buffer.trim()) { const lines = buffer.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { try { const jsonStr = line.slice(6); if (jsonStr.trim()) { const item: ChatDataItem = JSON.parse(jsonStr); onEvent(item); } } catch (parseError) { console.warn('Failed to parse SSE event:', line, parseError); } } } } if (onComplete) { onComplete(); } } finally { reader.releaseLock(); } } catch (error: any) { console.error('Error in startChatbotStreamApi:', error); if (onError) { onError(error instanceof Error ? error : new Error(String(error))); } else { throw error; } } } /** * Stop a running chatbot workflow * Endpoint: POST /api/chatbot/{instanceId}/stop/{workflowId} */ export async function stopChatbotApi( request: ApiRequestFunction, instanceId: string, workflowId: string ): Promise { console.log('[stopChatbotApi] Calling stop endpoint:', `/api/chatbot/${instanceId}/stop/${workflowId}`, { instanceId, workflowId }); const data = await request({ url: `/api/chatbot/${instanceId}/stop/${workflowId}`, method: 'post' }); console.log('[stopChatbotApi] Stop response:', data); return data as ChatbotWorkflow; } /** * Get chatbot threads/workflows * Endpoint: GET /api/chatbot/{instanceId}/threads */ export async function getChatbotThreadsApi( request: ApiRequestFunction, instanceId: string, pagination?: { page?: number; pageSize?: number } ): Promise<{ items: ChatbotWorkflow[]; metadata: any }> { const paginationParam = pagination ? JSON.stringify(pagination) : undefined; const requestParams = paginationParam ? { pagination: paginationParam } : undefined; console.log(`[getChatbotThreadsApi] instanceId: ${instanceId}, params:`, requestParams); const data = await request({ url: `/api/chatbot/${instanceId}/threads`, method: 'get', params: requestParams }) as any; console.log(`[getChatbotThreadsApi] Full response:`, JSON.stringify(data, null, 2)); console.log(`[getChatbotThreadsApi] Response structure:`, { hasItems: !!data.items, itemsLength: Array.isArray(data.items) ? data.items.length : 'not an array', hasMetadata: !!data.metadata, metadataKeys: data.metadata ? Object.keys(data.metadata) : [] }); return { items: Array.isArray(data.items) ? data.items : [], metadata: data.metadata || {} }; } /** * Get a specific chatbot thread/workflow with its chat data * Endpoint: GET /api/chatbot/{instanceId}/threads?workflowId={id} * * Backend returns: { workflow: ChatbotWorkflow, chatData: { items: ChatDataItem[] } } * * @param request - API request function * @param instanceId - Feature Instance ID * @param workflowId - ID of the workflow to fetch * @returns Object containing workflow details and chatData with items array */ export async function getChatbotThreadApi( request: ApiRequestFunction, instanceId: string, workflowId: string ): Promise<{ workflow: ChatbotWorkflow; chatData: { items: ChatDataItem[] } }> { console.log(`[getChatbotThreadApi] instanceId: ${instanceId}, workflowId: ${workflowId}`); const data = await request({ url: `/api/chatbot/${instanceId}/threads`, method: 'get', params: { workflowId } }) as { workflow: ChatbotWorkflow; chatData: { items: ChatDataItem[] } }; console.log(`[getChatbotThreadApi] Full response for workflowId ${workflowId}:`, JSON.stringify(data, null, 2)); console.log(`[getChatbotThreadApi] Response structure:`, { hasWorkflow: !!data.workflow, workflowKeys: data.workflow ? Object.keys(data.workflow) : [], hasChatData: !!data.chatData, hasItems: !!data.chatData?.items, chatDataKeys: data.chatData ? Object.keys(data.chatData) : [], itemsLength: Array.isArray(data.chatData?.items) ? data.chatData.items.length : 'not an array', chatDataTypes: Array.isArray(data.chatData?.items) ? data.chatData.items.map((item: ChatDataItem) => item?.type).filter(Boolean) : [] }); return { workflow: data.workflow, chatData: data.chatData || { items: [] } }; } /** * Delete a chatbot workflow * Endpoint: DELETE /api/chatbot/{instanceId}/{workflowId} * * @param request - API request function * @param instanceId - Feature Instance ID * @param workflowId - ID of the workflow to delete * @returns Success status */ export async function deleteChatbotWorkflowApi( request: ApiRequestFunction, instanceId: string, workflowId: string ): Promise { try { await request({ url: `/api/chatbot/${instanceId}/${workflowId}`, method: 'delete' }); return true; } catch (error: any) { console.error('Error deleting chatbot workflow:', error); throw error; } }