import { BadRequestException, Injectable, ServiceUnavailableException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; const MAX_AUDIO_BYTES = 8 * 1024 * 1024; @Injectable() export class AiSttService { constructor(private readonly config: ConfigService) {} isEnabled(): boolean { return ( this.config.get('STT_ENABLED') === 'true' && Boolean(this.sttUrl()) ); } async transcribe(file: Express.Multer.File | undefined) { if (!this.isEnabled()) { throw new ServiceUnavailableException('تبدیل صوت به متن فعال نیست'); } if (!file?.buffer?.length) { throw new BadRequestException('فایل صوتی ارسال نشده است'); } if (file.size > MAX_AUDIO_BYTES) { throw new BadRequestException('حجم فایل صوتی بیش از حد مجاز است'); } const form = new FormData(); form.append( 'audio', new Blob([new Uint8Array(file.buffer)], { type: file.mimetype || 'audio/wav', }), file.originalname || 'audio.wav', ); const timeoutMs = Number(this.config.get('STT_TIMEOUT_MS') || 180000); const headers: Record = {}; const token = this.config.get('STT_INTERNAL_TOKEN')?.trim(); if (token) headers['X-STT-Token'] = token; let res: Response; try { res = await fetch(`${this.sttUrl()}/transcribe`, { method: 'POST', headers, body: form, signal: AbortSignal.timeout(timeoutMs), }); } catch { throw new ServiceUnavailableException( 'سرویس تبدیل صوت در دسترس نیست. Pipecat STT را اجرا کنید.', ); } const payload = (await res.json().catch(() => ({}))) as { text?: string; detail?: string | Array<{ msg?: string }>; }; if (!res.ok) { const detail = Array.isArray(payload.detail) ? payload.detail.map((item) => item.msg).filter(Boolean).join('، ') : payload.detail; const raw = String(detail || '').trim(); let message = raw || 'تبدیل صوت به متن ناموفق بود'; if (raw.length > 220) { message = 'تبدیل صوت به متن ناموفق بود. سرویس STT محلی را بررسی کنید.'; } throw new ServiceUnavailableException(message); } return { text: (payload.text ?? '').trim() }; } private sttUrl(): string { return (this.config.get('STT_URL') ?? '').trim().replace(/\/$/, ''); } }