Files
yara724api/src/ai/ai.service.ts
2026-05-30 08:59:57 +03:30

205 lines
6.2 KiB
TypeScript

import { createReadStream, existsSync } from "node:fs";
import { join } from "node:path";
import {
HttpException,
HttpStatus,
Injectable,
OnModuleInit,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { AxiosRequestConfig } from "axios"; // TODO: Change all axios usages to HttpModule
@Injectable()
export class AiService implements OnModuleInit {
private apiKey: string;
private accessToken: string = null;
// These configurations are for authentication and getting the API key.
private readonly loginOptions: AxiosRequestConfig = {
method: "POST",
headers: { "Content-Type": "application/json" },
url: `${this.configService.get<string>("AI_URL_V2")}/auth/login`,
data: {
username: this.configService.get<string>("AI_USERNAME"),
password: this.configService.get<string>("AI_PASSWORD"),
},
timeout: 1000, // TODO: Make this ENV
};
constructor(private readonly configService: ConfigService) {}
private get profileOptions(): AxiosRequestConfig {
return {
method: "GET",
url: `${this.configService.get<string>("AI_URL_V2")}/auth/profile`,
headers: {
Authorization: `Bearer ${this.accessToken}`,
},
};
}
// This getter dynamically creates the base options for the image processing request.
private get imageProcessOptions(): AxiosRequestConfig {
return {
method: "POST",
url: `${process.env.AI_URL_V2}/services/car-damage/detector?version=ai-v7`,
headers: {
Authorization: `Bearer ${this.accessToken}`,
"gateway-api-key": `${this.apiKey}`,
},
};
}
async onModuleInit() {
try {
const res = await this.login();
// if (res?.accessToken) {
// this.accessToken = res.accessToken;
// await this.getApiKey();
// }
} catch (error) {
// Don't prevent app startup if AI service is temporarily unavailable
// The service will attempt to re-authenticate when aiRequestImage is called
// Reset tokens so re-authentication will be attempted
this.accessToken = null;
this.apiKey = null;
}
}
private async login() {
// const loginResponse = await axios.request(this.loginOptions);
// return loginResponse.data;
}
private async getApiKey() {
// const profileResponse = await axios.request(this.profileOptions);
// this.apiKey = profileResponse.data.apiKey.key;
// return this.apiKey;
}
public async aiRequestImage(file: {
path: string;
fileName?: string;
}): Promise<any> {
// Ensure authentication is set up
if (!this.accessToken || !this.apiKey) {
try {
const res = await this.login();
// if (res?.accessToken) {
// this.accessToken = res.accessToken;
// await this.getApiKey();
// } else {
// throw new HttpException(
// "AI Service authentication failed",
// HttpStatus.UNAUTHORIZED,
// );
// }
} catch (error) {
throw new HttpException(
"AI Service authentication failed",
HttpStatus.UNAUTHORIZED,
);
}
}
// Resolve relative paths to absolute paths
const filePath = file.path.startsWith("/")
? file.path
: join(process.cwd(), file.path.replace(/^\.\//, ""));
// Check if file exists
if (!existsSync(filePath)) {
throw new HttpException(
`File not found: ${file.path}`,
HttpStatus.NOT_FOUND,
);
}
// const form = new FormData();
const fileStream = createReadStream(filePath);
// Append file with filename if available
if (file.fileName) {
// form.append("images", fileStream, file.fileName);
} else {
// Extract filename from path if not provided
const pathParts = filePath.split("/");
const extractedFileName = pathParts[pathParts.length - 1];
// form.append("images", fileStream, extractedFileName);
}
try {
const requestHeaders = {
...this.imageProcessOptions.headers,
// ...form.getHeaders(),
};
const fs = require("fs");
const stats = fs.statSync(filePath);
// const response = await axios.request({
// ...this.imageProcessOptions,
// headers: requestHeaders,
// // data: form,
// maxContentLength: Infinity,
// maxBodyLength: Infinity,
// });
// Validate response structure
// if (!response.data) {
// throw new HttpException(
// "AI Service returned empty response",
// HttpStatus.BAD_GATEWAY,
// );
// }
// // Check for error in response first (AI service returns 201 with error in body)
// if (response.data.error) {
// throw new HttpException(
// `AI Service error: ${response.data.error}`,
// HttpStatus.BAD_GATEWAY,
// );
// }
// // Check for processed image (downloadLink)
// if (!response.data.downloadLink) {
// throw new HttpException(
// "AI Service did not return processed image (downloadLink missing)",
// HttpStatus.BAD_GATEWAY,
// );
// }
// return response.data;
} catch (er) {
// Determine error source
let errorSource = "UNKNOWN";
let errorMessage = er.message;
let errorDetails = "No error details available";
if (er.response) {
errorSource = "AI_SERVICE_RESPONSE";
errorMessage =
er.response?.data?.message ||
er.message ||
`HTTP ${er.response.status}`;
errorDetails = er.response?.data
? JSON.stringify(er.response.data, null, 2)
: `Status: ${er.response.status}, StatusText: ${er.response.statusText}`;
} else if (er.request) {
errorSource = "NETWORK_ERROR";
errorMessage = "Network error - AI service did not respond";
errorDetails = "Request was made but no response received";
} else {
errorSource = "REQUEST_SETUP_ERROR";
errorMessage = er.message || "Error setting up request";
}
// Re-throw with detailed error information
throw new HttpException(
`[${errorSource}] ${errorMessage}`,
er.response?.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}