Files
stack/apps/api/src/speech/speech.module.ts
Jason Woltje 28c9e6fe65
All checks were successful
ci/woodpecker/push/api Pipeline was successful
feat(#397): implement WebSocket streaming transcription gateway
Add SpeechGateway with Socket.IO namespace /speech for real-time
streaming transcription. Supports start-transcription, audio-chunk,
and stop-transcription events with session management, authentication,
and buffer size rate limiting. Includes 29 unit tests covering
authentication, session lifecycle, error handling, cleanup, and
client isolation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 02:54:41 -06:00

83 lines
2.7 KiB
TypeScript

/**
* SpeechModule
*
* NestJS module for speech-to-text (STT) and text-to-speech (TTS) services.
* Provides a provider abstraction layer with graceful fallback for TTS tiers.
*
* TTS providers are created dynamically based on configuration:
* - default: Kokoro-FastAPI (CPU, always available)
* - premium: Chatterbox (GPU, voice cloning)
* - fallback: Piper via OpenedAI Speech (ultra-lightweight CPU)
*
* Imports:
* - ConfigModule.forFeature(speechConfig) for speech configuration
* - AuthModule for WebSocket authentication
* - PrismaModule for workspace membership queries
*
* Providers:
* - SpeechService: High-level speech operations with provider selection
* - SpeechGateway: WebSocket gateway for streaming transcription (Issue #397)
* - TTS_PROVIDERS: Map<SpeechTier, ITTSProvider> populated by factory based on config
*
* Exports:
* - SpeechService for use by other modules (e.g., controllers, brain)
*
* Issue #389, #390, #391, #397
*/
import { Module, type OnModuleInit, Logger } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import {
speechConfig,
validateSpeechConfig,
isSttEnabled,
type SpeechConfig,
} from "./speech.config";
import { SpeechService } from "./speech.service";
import { SpeechController } from "./speech.controller";
import { SpeechGateway } from "./speech.gateway";
import { STT_PROVIDER, TTS_PROVIDERS } from "./speech.constants";
import { SpeachesSttProvider } from "./providers/speaches-stt.provider";
import { createTTSProviders } from "./providers/tts-provider.factory";
import { AuthModule } from "../auth/auth.module";
import { PrismaModule } from "../prisma/prisma.module";
@Module({
imports: [ConfigModule.forFeature(speechConfig), AuthModule, PrismaModule],
controllers: [SpeechController],
providers: [
SpeechService,
SpeechGateway,
// STT provider: conditionally register SpeachesSttProvider when STT is enabled
...(isSttEnabled()
? [
{
provide: STT_PROVIDER,
useClass: SpeachesSttProvider,
},
]
: []),
{
provide: TTS_PROVIDERS,
useFactory: (configService: ConfigService) => {
const config = configService.get<SpeechConfig>("speech");
if (!config) {
return new Map();
}
return createTTSProviders(config);
},
inject: [ConfigService],
},
],
exports: [SpeechService],
})
export class SpeechModule implements OnModuleInit {
private readonly logger = new Logger(SpeechModule.name);
onModuleInit(): void {
// Validate configuration at startup (fail fast)
validateSpeechConfig();
this.logger.log("Speech module initialized");
}
}