import { HttpStatus, Injectable } from '@nestjs/common'; import axios from 'axios'; import { BaseResponseDTO } from 'src/common/dto/base-response.dto'; import * as FormData from 'form-data'; import * as fs from 'fs'; import * as path from 'path'; @Injectable() export class AiServiceService { /** Headers for every AI HTTP call (`X-API-Key` from `AI_API_KEY`). */ private aiHeaders(extra: Record = {}): Record { const headers: Record = { accept: 'application/json', ...extra, }; const key = process.env.AI_API_KEY?.trim(); if (key) { headers['X-API-Key'] = key; } return headers; } async ask(body, session) { try { if (!session) { const data = JSON.stringify({ user_input: body.user_input ?? body.question, chat_history_raw: body.chat_history_raw ?? [], user_insurance_data: body.user_insurance_data, user_installments_data: body.user_installments_data, }); let axiosConfig = { method: 'post', maxBodyLength: Infinity, url: process.env.AI_QUERY_URL, headers: this.aiHeaders({ 'Content-Type': 'application/json' }), data: data, }; const chat = await axios.request(axiosConfig); const aiResponse = chat.data.response; return aiResponse; } else { let transformedData = body; let axiosConfig = { method: 'post', maxBodyLength: Infinity, url: process.env.AI_QUERY_URL, headers: this.aiHeaders({ 'Content-Type': 'application/json' }), data: transformedData, }; const newChat = await axios.request(axiosConfig); if (!newChat.data && newChat.data.response) throw new Error('AI_ERROR'); const aiResponse = newChat.data.response; return aiResponse; } } catch (err) { console.log(err); if (err.isAxiosError) { const status = err.response?.status || HttpStatus.INTERNAL_SERVER_ERROR; const message = err.response?.data || 'Internal Server Error'; return new BaseResponseDTO(status, 'ai_service_error', { error: message, }); } return new BaseResponseDTO( HttpStatus.BAD_REQUEST, 'something_wrong', null, ); } } async wrapUp(data) { try { let axiosConfig = { method: 'post', maxBodyLength: Infinity, url: process.env.AI_WRAPUP_URL, headers: this.aiHeaders({ 'Content-Type': 'application/json' }), data: data, }; const wrapup = await axios.request(axiosConfig); const aiResponse = wrapup.data.title; return aiResponse; } catch (err) { console.log(err); throw new BaseResponseDTO(err.status, err.response, null); } } async aiUploadTxt(file) { try { console.log(`[aiUploadTxt] Attempting to upload file: ${file.originalName}`); const formData = new FormData(); const fileStream = fs.createReadStream(file.filePath); formData.append('file', fileStream, { filename: file.originalName, contentType: file.mimetype }); // Upload the file const uploadResponse = await axios.post(process.env.AI_UPLOAD_URL, formData, { headers: this.aiHeaders(formData.getHeaders() as Record), timeout: 30000 }); console.log(`[aiUploadTxt] File upload response for ${file.originalName}:`, uploadResponse.data); // Check if upload was successful if (uploadResponse.status === 200) { console.log(`[aiUploadTxt] File ${file.originalName} uploaded successfully. Initiating QA generation.`); const qaResponse = await axios.post( `${process.env.AI_QA_GENERATOR_URL}?file_name=${encodeURIComponent(file.originalName)}`, {}, { headers: this.aiHeaders(), timeout: 120000 // 2 minutes timeout for QA generation } ); console.log('QA Generation response:', qaResponse.data); // Consider any HTTP 200 as success regardless of body shape if (qaResponse.status === 200) { return { success: true, fileName: file.originalName, }; } throw new Error('QA generation failed'); } else { throw new Error('File upload failed'); } } catch (err) { console.error('Error uploading file to AI service:', err); // Handle different types of errors let status = 500; let errorData = null; if (err.response) { // The request was made and the server responded with a status code status = err.response.status; errorData = err.response.data; } else if (err.request) { // The request was made but no response was received errorData = 'No response received from AI service'; } else { // Something happened in setting up the request errorData = err.message; } throw new BaseResponseDTO(status, errorData, null); } } async aiUploadCsv(file) { try { console.log(`[aiUploadCsv] Attempting to upload file: ${file.originalName}`); const formData = new FormData(); formData.append('file', fs.createReadStream(file.filePath), { filename: file.originalName, contentType: file.mimetype, }); const axiosConfig = { method: 'post', url: process.env.AI_UPLOAD_URL, headers: this.aiHeaders(formData.getHeaders() as Record), data: formData, }; const response = await axios.request(axiosConfig); console.log(`[aiUploadCsv] File upload successful for ${file.originalName}. Response:`, response.data); return response.data; } catch (err) { console.error('[aiUploadCsv] Error uploading file to AI service:', err); // Handle different types of errors let status = 500; let errorData = null; if (err.response) { // The request was made and the server responded with a status code status = err.response.status; errorData = err.response.data; } else if (err.request) { // The request was made but no response was received errorData = 'No response received from AI service'; } else { // Something happened in setting up the request errorData = err.message; } throw new BaseResponseDTO(status, errorData, null); } } async newCollection(collection_name: string, filename: string, description: string) { try { console.log(`[newCollection] Attempting to create new collection: ${collection_name} with filename: ${filename} and description: ${description}`); const params = new URLSearchParams(); params.append('collection_name', collection_name); params.append('filename', filename); params.append('description', description); const axiosConfig = { method: 'post', url: `${process.env.AI_COLLECTION_URL}/new-collection?${params.toString()}`, headers: this.aiHeaders(), }; const response = await axios.request(axiosConfig); console.log(`[newCollection] Collection ${collection_name} created successfully. Response:`, response.data); return response.data; } catch (err) { console.error('[newCollection] Error creating new collection:', err); // Handle different types of errors let status = 500; let errorData = null; if (err.response) { // The request was made and the server responded with a status code status = err.response.status; errorData = err.response.data; } else if (err.request) { // The request was made but no response was received errorData = 'No response received from AI service'; } else { // Something happened in setting up the request errorData = err.message; } throw new BaseResponseDTO(status, errorData, null); } } async getCollections() { try { console.log(`[getCollections] Attempting to retrieve all collections.`); const axiosConfig = { method: 'get', url: `${process.env.AI_COLLECTION_URL}/get-collections`, headers: this.aiHeaders(), }; const response = await axios.request(axiosConfig); console.log(`[getCollections] Collections retrieved successfully. Response:`, response.data); return response.data; } catch (err) { console.error('[getCollections] Error getting collections:', err); // Handle different types of errors let status = 500; let errorData = null; if (err.response) { // The request was made and the server responded with a status code status = err.response.status; errorData = err.response.data; } else if (err.request) { // The request was made but no response was received errorData = 'No response received from AI service'; } else { // Something happened in setting up the request errorData = err.message; } throw new BaseResponseDTO(status, errorData, null); } } async updateCollection(collection_name: string, filename: string, description: string) { try { console.log(`[updateCollection] Attempting to update collection: ${collection_name} with filename: ${filename} and description: ${description}`); const params = new URLSearchParams(); params.append('collection_name', collection_name); params.append('filename', filename); params.append('description', description); const axiosConfig = { method: 'post', url: `${process.env.AI_COLLECTION_URL}/update-collection?${params.toString()}`, headers: this.aiHeaders(), }; const response = await axios.request(axiosConfig); console.log(`[updateCollection] Collection ${collection_name} updated successfully. Response:`, response.data); return response.data; } catch (err) { console.error('[updateCollection] Error updating collection:', err); // Handle different types of errors let status = 500; let errorData = null; if (err.response) { // The request was made and the server responded with a status code status = err.response.status; errorData = err.response.data; } else if (err.request) { // The request was made but no response was received errorData = 'No response received from AI service'; } else { // Something happened in setting up the request errorData = err.message; } throw new BaseResponseDTO(status, errorData, null); } } async replaceCollection(collection_name: string, filename: string, description?: string) { try { console.log(`[replaceCollection] Attempting to replace collection: ${collection_name} with filename: ${filename}${description ? ` and description: ${description}` : ''}`); const params = new URLSearchParams(); params.append('collection_name', collection_name); params.append('filename', filename); if (description) { params.append('description', description); } const axiosConfig = { method: 'post', url: `${process.env.AI_COLLECTION_URL}/replace-collection?${params.toString()}`, headers: this.aiHeaders(), }; const response = await axios.request(axiosConfig); console.log(`[replaceCollection] Collection ${collection_name} replaced successfully. Response:`, response.data); return response.data; } catch (err) { console.error('Error replacing collection:', err); // Handle different types of errors let status = 500; let errorData = null; if (err.response) { // The request was made and the server responded with a status code status = err.response.status; errorData = err.response.data; } else if (err.request) { // The request was made but no response was received errorData = 'No response received from AI service'; } else { // Something happened in setting up the request errorData = err.message; } throw new BaseResponseDTO(status, errorData, null); } } async deactivateCollection(collection_name: string) { try { console.log(`[deactivateCollection] Attempting to deactivate collection: ${collection_name}`); const params = new URLSearchParams(); params.append('collection_name', collection_name); const axiosConfig = { method: 'post', url: `${process.env.AI_COLLECTION_URL}/deactivate-collection?${params.toString()}`, headers: this.aiHeaders(), }; const response = await axios.request(axiosConfig); console.log(`[deactivateCollection] Collection ${collection_name} deactivated successfully. Response:`, response.data); return response.data; } catch (err) { console.error('[deactivateCollection] Error deactivating collection:', err); // Handle different types of errors let status = 500; let errorData = null; if (err.response) { // The request was made and the server responded with a status code status = err.response.status; errorData = err.response.data; } else if (err.request) { // The request was made but no response was received errorData = 'No response received from AI service'; } else { // Something happened in setting up the request errorData = err.message; } throw new BaseResponseDTO(status, errorData, null); } } private collectionBaseUrl(): string { return String(process.env.AI_COLLECTION_URL || '').replace(/\/$/, ''); } private throwAiError(err: any): never { let status = 500; let errorData: unknown = null; if (err?.response) { status = err.response.status; errorData = err.response.data; } else if (err?.request) { errorData = 'No response received from AI service'; } else { errorData = err?.message ?? err; } throw new BaseResponseDTO(status, errorData as any, null); } async getCollectionsWithDescriptions() { try { const response = await axios.get( `${this.collectionBaseUrl()}/get-collections-with-descriptions`, { headers: this.aiHeaders(), timeout: 60_000 }, ); return response.data; } catch (err) { this.throwAiError(err); } } async getCollectionDescription(collectionName: string) { try { const response = await axios.get( `${this.collectionBaseUrl()}/get-collection-description/${encodeURIComponent(collectionName)}`, { headers: this.aiHeaders(), timeout: 60_000 }, ); return response.data; } catch (err) { this.throwAiError(err); } } async checkCollections() { try { const response = await axios.get( `${this.collectionBaseUrl()}/check-collections`, { headers: this.aiHeaders(), timeout: 60_000 }, ); return response.data; } catch (err) { this.throwAiError(err); } } async exportCollectionPage( collectionName: string, page = 1, pageSize = 50, ) { try { const response = await axios.get( `${this.collectionBaseUrl()}/sync/export/${encodeURIComponent(collectionName)}`, { params: { page, page_size: pageSize }, headers: this.aiHeaders(), timeout: 120_000, }, ); return response.data as { collection: string; page: number; page_size: number; total_items: number; total_pages: number; items: Array<{ id: string; q: string; a: string }>; }; } catch (err) { this.throwAiError(err); } } async createExportItem( collectionName: string, body: { q: string; a: string }, ) { try { const response = await axios.post( `${this.collectionBaseUrl()}/sync/export/${encodeURIComponent(collectionName)}`, body, { headers: this.aiHeaders({ 'Content-Type': 'application/json' }), timeout: 60_000, }, ); return response.data; } catch (err) { this.throwAiError(err); } } async updateExportItem( collectionName: string, itemId: string, body: { q: string; a: string }, ) { try { const response = await axios.put( `${this.collectionBaseUrl()}/sync/export/${encodeURIComponent(collectionName)}/${encodeURIComponent(itemId)}`, body, { headers: this.aiHeaders({ 'Content-Type': 'application/json' }), timeout: 60_000, }, ); return response.data; } catch (err) { this.throwAiError(err); } } async deleteExportItem(collectionName: string, itemId: string) { try { const response = await axios.delete( `${this.collectionBaseUrl()}/sync/export/${encodeURIComponent(collectionName)}/${encodeURIComponent(itemId)}`, { headers: this.aiHeaders(), timeout: 60_000, }, ); return response.data; } catch (err) { this.throwAiError(err); } } }