feat(#391): implement tiered TTS provider architecture with base class
Add abstract BaseTTSProvider class that implements common OpenAI-compatible TTS logic using the OpenAI SDK with configurable baseURL. Includes synthesize(), listVoices(), and isHealthy() methods. Create TTS provider factory that dynamically registers Kokoro (default), Chatterbox (premium), and Piper (fallback) providers based on configuration. Update SpeechModule to use the factory for TTS_PROVIDERS injection token. Also fixes lint error in speaches-stt.provider.ts (Array<T> -> T[]). 30 tests added (22 base provider + 8 factory), all passing. Fixes #391
This commit is contained in:
@@ -4,36 +4,60 @@
|
||||
* 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
|
||||
*
|
||||
* Providers:
|
||||
* - SpeechService: High-level speech operations with provider selection
|
||||
* - TTS_PROVIDERS: Empty Map<SpeechTier, ITTSProvider> (populated by provider modules)
|
||||
* - TTS_PROVIDERS: Map<SpeechTier, ITTSProvider> populated by factory based on config
|
||||
*
|
||||
* Exports:
|
||||
* - SpeechService for use by other modules (e.g., controllers, brain)
|
||||
*
|
||||
* Issue #389
|
||||
* Issue #389, #390, #391
|
||||
*/
|
||||
|
||||
import { Module, type OnModuleInit, Logger } from "@nestjs/common";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { speechConfig, validateSpeechConfig } from "./speech.config";
|
||||
import { ConfigModule, ConfigService } from "@nestjs/config";
|
||||
import {
|
||||
speechConfig,
|
||||
validateSpeechConfig,
|
||||
isSttEnabled,
|
||||
type SpeechConfig,
|
||||
} from "./speech.config";
|
||||
import { SpeechService } from "./speech.service";
|
||||
import { TTS_PROVIDERS } from "./speech.constants";
|
||||
import type { SpeechTier } from "./interfaces/speech-types";
|
||||
import type { ITTSProvider } from "./interfaces/tts-provider.interface";
|
||||
import { STT_PROVIDER, TTS_PROVIDERS } from "./speech.constants";
|
||||
import { SpeachesSttProvider } from "./providers/speaches-stt.provider";
|
||||
import { createTTSProviders } from "./providers/tts-provider.factory";
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule.forFeature(speechConfig)],
|
||||
providers: [
|
||||
SpeechService,
|
||||
// Default empty TTS providers map. Provider modules (Kokoro, Chatterbox, etc.)
|
||||
// will register their providers in subsequent tasks.
|
||||
// STT provider: conditionally register SpeachesSttProvider when STT is enabled
|
||||
...(isSttEnabled()
|
||||
? [
|
||||
{
|
||||
provide: STT_PROVIDER,
|
||||
useClass: SpeachesSttProvider,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
provide: TTS_PROVIDERS,
|
||||
useFactory: (): Map<SpeechTier, ITTSProvider> => new Map(),
|
||||
useFactory: (configService: ConfigService) => {
|
||||
const config = configService.get<SpeechConfig>("speech");
|
||||
if (!config) {
|
||||
return new Map();
|
||||
}
|
||||
return createTTSProviders(config);
|
||||
},
|
||||
inject: [ConfigService],
|
||||
},
|
||||
],
|
||||
exports: [SpeechService],
|
||||
|
||||
Reference in New Issue
Block a user