forked from Chatbot/v3-api
535 lines
17 KiB
TypeScript
535 lines
17 KiB
TypeScript
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<string, string> = {}): Record<string, string> {
|
|
const headers: Record<string, string> = {
|
|
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<string, string>),
|
|
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<string, string>),
|
|
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);
|
|
}
|
|
}
|
|
}
|