import axios, { AxiosInstance } from 'axios'; import { Logger } from '@nestjs/common'; import FormData from 'form-data'; import { Readable } from 'stream'; export interface IndexPdfResponse { pdf_name: string; chunks_indexed: number; conversation_id: string; } export interface FastApiQuizResponse { title?: string; description?: string; passing_score?: number; questions: Array<{ order: number; topic: string; difficulty: 'easy' | 'medium' | 'hard'; question_text: string; options: Array<{ id: string; text: string }>; correct_answer: string; explanation?: string; }>; metadata?: Record; } export interface AtRiskAnalysisResponse { students: Array<{ sender_id: string; sender_name: string; signals?: { stuck_phrases?: Array<{ text: string; suggested_points: number }>; frustration_phrases?: Array<{ text: string; suggested_points: number }>; unanswered_questions?: Array<{ text: string; suggested_points: number }>; }; }>; } export class FastApiClient { private readonly client: AxiosInstance; constructor(logger: Logger, fastApiUrl?: string, private readonly defaultTimeout = 60000) { const baseUrl = fastApiUrl || process.env.FASTAPI_URL || 'http://localhost:8000/api/v1'; this.client = axios.create({ baseURL: baseUrl, timeout: this.defaultTimeout, }); this.logger = logger; } private readonly logger: Logger; async genQuiz(content: string, kQuestion: number): Promise { this.logger.log(`[FastAPI] genQuiz called: content=${content.length} chars, ${kQuestion} questions`); try { const response = await this.client.post('/gen_quiz', { content, k_question: kQuestion, }); this.logger.log(`[FastAPI] genQuiz response: ${JSON.stringify({ hasData: !!response.data, keys: Object.keys(response.data || {}) })}`); return response.data; } catch (error: unknown) { const axiosError = error as { response?: { status?: number; data?: unknown }; message?: string }; this.logger.error( `[FastAPI] genQuiz failed: ${axiosError.response?.status || 'N/A'} ${JSON.stringify(axiosError.response?.data || {})}`, axiosError.message || 'Unknown error' ); throw error; } } async analyzeAtRisk(roomIds: string[], hours: number): Promise { const response = await this.client.post('/at-risk/analyze', { room_ids: roomIds, hours, }); return response.data; } /** * Index PDF vào Qdrant knowledge base */ async indexPdf(conversationId: string, file: Buffer, filename: string): Promise { const form = new FormData(); form.append('conversation_id', conversationId); // Use form-data package with Readable stream for proper file upload form.append('file', Readable.from(file), { filename, contentType: 'application/pdf' }); const response = await this.client.post('/index_pdf', form, { headers: form.getHeaders() }); return response.data; } }