Spaces:
Sleeping
Sleeping
| 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<string, unknown>; | |
| } | |
| 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<FastApiQuizResponse> { | |
| 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<AtRiskAnalysisResponse> { | |
| 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<IndexPdfResponse> { | |
| 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; | |
| } | |
| } |