Spaces:
Sleeping
Sleeping
Download src/modules/mock-interview/mock-interview.controller.ts from mahrooshishaq/SmartfolioBackend: direct link, hf CLI and curl.
- Browser
- Download file 6.32 kB
-
https://huggingface.co/spaces/mahrooshishaq/SmartfolioBackend/resolve/main/src/modules/mock-interview/mock-interview.controller.ts
- Command line
-
hf download hf://spaces/mahrooshishaq/SmartfolioBackend/src/modules/mock-interview/mock-interview.controller.ts
-
curl -L -o mock-interview.controller.ts https://huggingface.co/spaces/mahrooshishaq/SmartfolioBackend/resolve/main/src/modules/mock-interview/mock-interview.controller.ts
6.32 kB
| import { | |
| Controller, | |
| Post, | |
| Get, | |
| Body, | |
| UseGuards, | |
| UseInterceptors, | |
| UploadedFile, | |
| Req, | |
| Res, | |
| HttpCode, | |
| HttpStatus, | |
| Param, | |
| Logger, | |
| BadRequestException, | |
| ServiceUnavailableException, | |
| } from '@nestjs/common'; | |
| import { FileInterceptor } from '@nestjs/platform-express'; | |
| import type { Response } from 'express'; | |
| import { | |
| ApiTags, | |
| ApiBearerAuth, | |
| ApiOperation, | |
| ApiOkResponse, | |
| ApiUnauthorizedResponse, | |
| ApiConsumes, | |
| } from '@nestjs/swagger'; | |
| import { JwtAuthGuard } from '../auth/jwt-auth.guard'; | |
| import { AuthenticatedRequest } from '../../common/interfaces/authenticated-request.interface'; | |
| import { MockInterviewService } from './mock-interview.service'; | |
| import { GenerateTestDto } from './dto/generate-test.dto'; | |
| import { SubmitAnswersDto } from './dto/submit-answers.dto'; | |
| import { FollowUpDto } from './dto/follow-up.dto'; | |
| import { TtsDto } from './dto/tts.dto'; | |
| import { TranscribeDto } from './dto/transcribe.dto'; | |
| ('Mock Interview') | |
| () | |
| ({ description: 'Missing or invalid JWT token' }) | |
| ('mock-interview') | |
| (JwtAuthGuard) | |
| export class MockInterviewController { | |
| private readonly logger = new Logger(MockInterviewController.name); | |
| constructor(private readonly service: MockInterviewService) {} | |
| ('generate') | |
| (HttpStatus.OK) | |
| ({ | |
| summary: 'Generate a 3-round mock interview from a job description', | |
| description: | |
| 'Length depends on lengthTier: quick (~5 min, 5 Q), standard (~10 min, 10 Q), full (~15 min, 15 Q). ' + | |
| 'Returns the questions (no answer keys) plus a sessionId to use in /submit.', | |
| }) | |
| ({ | |
| description: 'Interview generated successfully', | |
| schema: { | |
| example: { | |
| sessionId: 'uuid', | |
| lengthTier: 'full', | |
| questions: [ | |
| { id: 1, round: 'hr', type: 'behavioral', question: '...' }, | |
| { id: 6, round: 'technical', type: 'mcq', question: '...', options: ['A', 'B', 'C', 'D'] }, | |
| ], | |
| }, | |
| }, | |
| }) | |
| async generate( | |
| () req: AuthenticatedRequest, | |
| () dto: GenerateTestDto, | |
| ) { | |
| return this.service.generateTest(req.user.id, dto); | |
| } | |
| ('submit') | |
| (HttpStatus.OK) | |
| ({ | |
| summary: 'Submit answers and get evaluation', | |
| description: 'Returns overall score, per-question feedback, and improvement tips.', | |
| }) | |
| async submit( | |
| () req: AuthenticatedRequest, | |
| () dto: SubmitAnswersDto, | |
| ) { | |
| return this.service.submitAnswers(req.user.id, dto); | |
| } | |
| ('follow-up') | |
| (HttpStatus.OK) | |
| ({ | |
| summary: 'Ask for an adaptive follow-up to an open-ended answer', | |
| description: 'Returns { followUp }. A one-sentence probing question, or null if none is warranted.', | |
| }) | |
| async followUp( | |
| () req: AuthenticatedRequest, | |
| () dto: FollowUpDto, | |
| ) { | |
| return this.service.requestFollowUp(req.user.id, dto); | |
| } | |
| ('tts') | |
| ({ | |
| summary: 'Synthesize a natural voice for interviewer speech (Phase 3.4, Kokoro via Python)', | |
| description: | |
| 'Streams audio (wav or ogg/opus per the format field) piped straight from the Python service. ' + | |
| 'On failure returns 503 so the client falls back to browser TTS.', | |
| }) | |
| async tts(() dto: TtsDto, () res: Response) { | |
| try { | |
| const { stream, contentType } = await this.service.synthesizeSpeech( | |
| dto.text, | |
| dto.voice, | |
| dto.format, | |
| ); | |
| res.setHeader('Content-Type', contentType); | |
| res.setHeader('Cache-Control', 'no-store'); | |
| res.status(HttpStatus.OK); | |
| stream.on('error', (err: Error) => { | |
| // Upstream died mid-stream — close what we have; headers are already sent. | |
| this.logger.warn(`TTS stream error: ${String(err).slice(0, 160)}`); | |
| res.end(); | |
| }); | |
| stream.pipe(res); | |
| } catch (err) { | |
| // TTS is optional — never surface a hard error; let the client fall back. | |
| this.logger.warn(`TTS unavailable: ${String(err).slice(0, 160)}`); | |
| res.status(HttpStatus.SERVICE_UNAVAILABLE).json({ message: 'TTS unavailable' }); | |
| } | |
| } | |
| ('transcribe') | |
| (HttpStatus.OK) | |
| (FileInterceptor('audio', { limits: { fileSize: 25 * 1024 * 1024 } })) | |
| ('multipart/form-data') | |
| ({ | |
| summary: 'Transcribe a spoken answer (Whisper large-v3 via Groq)', | |
| description: | |
| 'Accepts recorded answer audio (webm/ogg/mp4/wav, field name "audio", ≤25MB) plus an ' + | |
| 'optional "context" field (the question) used as a vocabulary hint. Returns { text }. ' + | |
| 'On failure returns 503 so the client falls back to the browser transcript.', | |
| }) | |
| ({ schema: { example: { text: 'I led the migration to NestJS…' } } }) | |
| async transcribe( | |
| () audio: Express.Multer.File, | |
| () dto: TranscribeDto, | |
| ) { | |
| if (!audio?.buffer?.length) throw new BadRequestException('No audio uploaded.'); | |
| try { | |
| const text = await this.service.transcribeAnswer( | |
| audio.buffer, | |
| audio.originalname || 'answer.webm', | |
| dto.context, | |
| ); | |
| return { text }; | |
| } catch (err) { | |
| // STT is an enhancement — the client keeps the browser transcript on 503. | |
| this.logger.warn(`Transcription unavailable: ${String(err).slice(0, 160)}`); | |
| throw new ServiceUnavailableException('Transcription unavailable'); | |
| } | |
| } | |
| ('sessions') | |
| ({ summary: 'Get current user past interview sessions' }) | |
| async listSessions(() req: AuthenticatedRequest) { | |
| return this.service.getUserSessions(req.user.id); | |
| } | |
| // Declared before the ':id' route so "progress" isn't captured as an id. | |
| ('progress') | |
| ({ | |
| summary: 'Get the user\'s score trend across submitted interviews', | |
| description: 'Returns { points: [...], summary: { attempts, best, latest, average, delta } }.', | |
| }) | |
| async progress(() req: AuthenticatedRequest) { | |
| return this.service.getProgress(req.user.id); | |
| } | |
| (':id') | |
| ({ summary: 'Get details of a specific interview session' }) | |
| async getSession( | |
| () req: AuthenticatedRequest, | |
| ('id') id: string, | |
| ) { | |
| return this.service.getSessionDetail(req.user.id, id); | |
| } | |
| } | |