feat: TTS服务 + 数字人面试组件 (P1)

This commit is contained in:
yuzhiran
2026-06-12 09:42:06 +08:00
parent 065fe7a186
commit a55cb56be2
11 changed files with 553 additions and 32 deletions
@@ -28,7 +28,9 @@ export class InterviewController {
@Param('id') id: string,
@CurrentUser('userId') userId: string,
@Body('answer') answer: string,
@Body('avatar') avatar?: boolean,
) {
if (avatar) return this.interviewService.answerWithAvatar(id, userId, answer)
return this.interviewService.answer(id, userId, answer)
}
@@ -5,6 +5,7 @@ import { InterviewService } from './interview.service'
import { Interview, InterviewSchema } from './interview.schema'
import { Progress, ProgressSchema } from '../schemas/progress.schema'
import { UserModule } from '../user/user.module'
import { TtsModule } from '../tts/tts.module'
@Module({
imports: [
@@ -13,6 +14,7 @@ import { UserModule } from '../user/user.module'
{ name: Progress.name, schema: ProgressSchema },
]),
UserModule,
TtsModule,
],
controllers: [InterviewController],
providers: [InterviewService],
@@ -6,6 +6,7 @@ import { Progress, ProgressDocument } from '../schemas/progress.schema'
import { AiService } from '../ai/ai.service'
import { UserService } from '../user/user.service'
import { QuotaService } from '../user/quota.service'
import { TtsService } from '../tts/tts.service'
import { analyzeSpeech } from '../../common/utils/filler-words'
@Injectable()
@@ -16,6 +17,7 @@ export class InterviewService {
private aiService: AiService,
private userService: UserService,
private quotaService: QuotaService,
private ttsService: TtsService,
) {}
async create(userId: string, position: string) {
@@ -99,6 +101,20 @@ ${conversationHistory}
}
}
async answerWithAvatar(interviewId: string, userId: string, answer: string) {
const base = await this.answer(interviewId, userId, answer)
const aiMsg = base.messages?.find(m => m.role === 'ai')
if (aiMsg?.content) {
try {
const tts = await this.ttsService.synthesize(aiMsg.content)
return { ...base, ttsHash: tts.hash, ttsDurationMs: tts.durationMs }
} catch {
// TTS failure is non-critical, return without audio
}
}
return base
}
async complete(interviewId: string, userId: string) {
const interview = await this.interviewModel.findOne({ _id: interviewId, userId }).exec()
if (!interview) throw new HttpException('面试不存在', HttpStatus.NOT_FOUND)