327 lines
10 KiB
TypeScript
327 lines
10 KiB
TypeScript
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<string, any>;
|
|
}
|
|
|
|
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<string, any>;
|
|
}
|
|
|
|
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<any>) => Promise<any>;
|
|
|
|
// 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<void> {
|
|
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<string, string> = {
|
|
'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<ChatbotWorkflow> {
|
|
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<boolean> {
|
|
try {
|
|
await request({
|
|
url: `/api/chatbot/${instanceId}/${workflowId}`,
|
|
method: 'delete'
|
|
});
|
|
return true;
|
|
} catch (error: any) {
|
|
console.error('Error deleting chatbot workflow:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|