240 lines
6.6 KiB
TypeScript
240 lines
6.6 KiB
TypeScript
/**
|
|
* Chatbot V2 API
|
|
*
|
|
* Context-aware chat: upload files for extraction first, then chat.
|
|
* Endpoints: /api/chatbotv2/{instanceId}/...
|
|
*/
|
|
|
|
import { ApiRequestOptions } from '../hooks/useApi';
|
|
import api from '../api';
|
|
import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
|
import { Message } from '../components/UiComponents/Messages/MessagesTypes';
|
|
|
|
// ============================================================================
|
|
// TYPES
|
|
// ============================================================================
|
|
|
|
export interface ChatbotV2Workflow {
|
|
id: string;
|
|
mandateId?: string;
|
|
featureInstanceId?: string;
|
|
status: string; // extracting | ready | running | stopped
|
|
name?: string;
|
|
currentRound?: number;
|
|
lastActivity?: number;
|
|
startedAt?: number;
|
|
extractedContextId?: string;
|
|
contextFiles?: Array<{ fileId: string; fileName: string; mimeType?: string }>;
|
|
[key: string]: any;
|
|
}
|
|
|
|
export interface UploadChatbotV2Request {
|
|
listFileId: string[];
|
|
}
|
|
|
|
export interface UploadChatbotV2Response {
|
|
conversationId: string;
|
|
status: string;
|
|
message?: string;
|
|
}
|
|
|
|
export interface StartChatbotV2Request {
|
|
prompt: string;
|
|
workflowId: string; // conversationId - required for V2
|
|
userLanguage?: string;
|
|
}
|
|
|
|
export interface ChatDataItem {
|
|
type: 'message' | 'log' | 'stat' | 'document' | 'stopped' | 'status';
|
|
createdAt?: number;
|
|
item: Message | any;
|
|
label?: string;
|
|
}
|
|
|
|
export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<any>;
|
|
export type SSEEventHandler = (item: ChatDataItem) => void;
|
|
|
|
// ============================================================================
|
|
// API FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Upload files as context and start extraction.
|
|
* Files must be uploaded to central storage first via /api/files/upload.
|
|
*/
|
|
export async function uploadChatbotV2Api(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
listFileId: string[]
|
|
): Promise<UploadChatbotV2Response> {
|
|
const data = await request({
|
|
url: `/api/chatbotv2/${instanceId}/upload`,
|
|
method: 'post',
|
|
data: { listFileId }
|
|
});
|
|
return data as UploadChatbotV2Response;
|
|
}
|
|
|
|
/**
|
|
* Get chatbot V2 threads (conversations)
|
|
*/
|
|
export async function getChatbotV2ThreadsApi(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
pagination?: { page?: number; pageSize?: number }
|
|
): Promise<{ items: ChatbotV2Workflow[]; pagination?: any }> {
|
|
const params = pagination ? { pagination: JSON.stringify(pagination) } : undefined;
|
|
const data = await request({
|
|
url: `/api/chatbotv2/${instanceId}/threads`,
|
|
method: 'get',
|
|
params
|
|
}) as any;
|
|
return {
|
|
items: Array.isArray(data.items) ? data.items : [],
|
|
pagination: data.pagination ?? data.metadata
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get a specific thread with chat data
|
|
*/
|
|
export async function getChatbotV2ThreadApi(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
workflowId: string
|
|
): Promise<{ workflow: ChatbotV2Workflow; chatData: { items: ChatDataItem[] } }> {
|
|
const data = await request({
|
|
url: `/api/chatbotv2/${instanceId}/threads`,
|
|
method: 'get',
|
|
params: { workflowId }
|
|
}) as { workflow: ChatbotV2Workflow; chatData: { items: ChatDataItem[] } };
|
|
return {
|
|
workflow: data.workflow,
|
|
chatData: data.chatData || { items: [] }
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Start or continue chat with SSE streaming.
|
|
* Requires conversationId (workflowId) - must have completed extraction first.
|
|
*/
|
|
export async function startChatbotV2StreamApi(
|
|
instanceId: string,
|
|
requestBody: StartChatbotV2Request,
|
|
onEvent: SSEEventHandler,
|
|
onError?: (error: Error) => void,
|
|
onComplete?: () => void
|
|
): Promise<void> {
|
|
try {
|
|
const url = `/api/chatbotv2/${instanceId}/start/stream?workflowId=${encodeURIComponent(requestBody.workflowId)}`;
|
|
const baseURL = api.defaults.baseURL || '';
|
|
const fullURL = baseURL + url;
|
|
|
|
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
const authToken = localStorage.getItem('authToken');
|
|
if (authToken) headers['Authorization'] = `Bearer ${authToken}`;
|
|
if (!getCSRFToken()) generateAndStoreCSRFToken();
|
|
addCSRFTokenToHeaders(headers);
|
|
|
|
const response = await fetch(fullURL, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({
|
|
prompt: requestBody.prompt,
|
|
userLanguage: requestBody.userLanguage || navigator.language || 'de'
|
|
}),
|
|
credentials: 'include'
|
|
});
|
|
|
|
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;
|
|
buffer += decoder.decode(value, { stream: true });
|
|
const lines = buffer.split('\n');
|
|
buffer = lines.pop() || '';
|
|
|
|
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 {
|
|
// ignore parse errors
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
onComplete?.();
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
} catch (error: any) {
|
|
if (onError) {
|
|
onError(error instanceof Error ? error : new Error(String(error)));
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop a running chat
|
|
*/
|
|
export async function stopChatbotV2Api(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
workflowId: string
|
|
): Promise<ChatbotV2Workflow> {
|
|
const data = await request({
|
|
url: `/api/chatbotv2/${instanceId}/stop/${workflowId}`,
|
|
method: 'post'
|
|
});
|
|
return data as ChatbotV2Workflow;
|
|
}
|
|
|
|
/**
|
|
* Delete a conversation
|
|
*/
|
|
export async function deleteChatbotV2Api(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
workflowId: string
|
|
): Promise<void> {
|
|
await request({
|
|
url: `/api/chatbotv2/${instanceId}/conversations/${workflowId}`,
|
|
method: 'delete'
|
|
});
|
|
}
|