feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
This commit is contained in:
parent
5cd1de6859
commit
708e20f747
406 changed files with 42464 additions and 6199 deletions
282
apps/desktop/src/main/services/stt/STTManager.ts
Normal file
282
apps/desktop/src/main/services/stt/STTManager.ts
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
// apps/desktop/src/main/services/stt/STTManager.ts
|
||||
// 통합 STT 매니저: Local Whisper 및 클라우드 AI 서비스 6종 디스패처 및 자동 폴백
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
import { getLogger } from '../LoggerService'
|
||||
import { configGet, configSet } from '../ConfigService'
|
||||
import { getLocalSTTService } from '../LocalSTTService'
|
||||
import type { TranscriptionResult, TranscribeOptions } from '../LocalSTTService'
|
||||
import type {
|
||||
STTProviderType,
|
||||
STTProviderInfo,
|
||||
STTProviderConfig,
|
||||
TestSTTConnectionParams,
|
||||
TestSTTConnectionResult,
|
||||
STTStatus,
|
||||
} from '@d3ro/core/types'
|
||||
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
|
||||
import type { ISTTDriver } from './types'
|
||||
import { OpenAIDriver } from './drivers/OpenAIDriver'
|
||||
import { GroqDriver } from './drivers/GroqDriver'
|
||||
import { DeepgramDriver } from './drivers/DeepgramDriver'
|
||||
import { AssemblyAIDriver } from './drivers/AssemblyAIDriver'
|
||||
import { GoogleDriver } from './drivers/GoogleDriver'
|
||||
import { CustomDriver } from './drivers/CustomDriver'
|
||||
import { D3ROCloudDriver } from './drivers/D3ROCloudDriver'
|
||||
|
||||
const logger = getLogger('STTManager')
|
||||
|
||||
export const STT_PROVIDERS_META: STTProviderInfo[] = [
|
||||
{
|
||||
id: 'local',
|
||||
name: 'Local Whisper',
|
||||
description: '100% 완전 오프라인, 무료, 프라이버시 보호',
|
||||
badge: 'Offline · Free',
|
||||
requiresApiKey: false,
|
||||
defaultModel: 'large-v3-turbo',
|
||||
models: ['tiny', 'base', 'small', 'medium', 'large-v3', 'large-v3-turbo'],
|
||||
isCloud: false,
|
||||
},
|
||||
{
|
||||
id: 'd3ro-cloud',
|
||||
name: 'D3RO Cloud STT (Managed)',
|
||||
description: 'D3RO 클라우드 매니지드 전사 서비스 (키 설정 불필요)',
|
||||
badge: 'Cloud · Zero Config',
|
||||
requiresApiKey: false,
|
||||
defaultModel: 'default',
|
||||
defaultBaseUrl: 'http://localhost:5000',
|
||||
models: ['default', 'whisper-large-v3-turbo', 'nova-3', 'gemini-2.0-flash'],
|
||||
isCloud: true,
|
||||
},
|
||||
{
|
||||
id: 'openai',
|
||||
name: 'OpenAI Whisper',
|
||||
description: '공식 OpenAI 고품질 다국어 음성 인식',
|
||||
badge: 'Standard · High Accuracy',
|
||||
requiresApiKey: true,
|
||||
defaultModel: 'whisper-1',
|
||||
defaultBaseUrl: 'https://api.openai.com/v1',
|
||||
models: ['whisper-1', 'gpt-4o-audio-preview'],
|
||||
isCloud: true,
|
||||
},
|
||||
{
|
||||
id: 'groq',
|
||||
name: 'Groq Whisper LPU',
|
||||
description: 'LPU 가속 기반 초저지연(~200ms) 초고속 인식',
|
||||
badge: 'Ultra Fast (~200ms)',
|
||||
requiresApiKey: true,
|
||||
defaultModel: 'whisper-large-v3-turbo',
|
||||
defaultBaseUrl: 'https://api.groq.com/openai/v1',
|
||||
models: ['whisper-large-v3-turbo', 'whisper-large-v3', 'distil-whisper-large-v3-en'],
|
||||
isCloud: true,
|
||||
},
|
||||
{
|
||||
id: 'deepgram',
|
||||
name: 'Deepgram Nova-3',
|
||||
description: '업계 최고 인식률, 스마트 문장 부호 및 포맷팅',
|
||||
badge: 'Industry Benchmark',
|
||||
requiresApiKey: true,
|
||||
defaultModel: 'nova-3',
|
||||
defaultBaseUrl: 'https://api.deepgram.com',
|
||||
models: ['nova-3', 'nova-2', 'nova-2-general', 'nova-2-meeting'],
|
||||
isCloud: true,
|
||||
},
|
||||
{
|
||||
id: 'assemblyai',
|
||||
name: 'AssemblyAI Universal-2',
|
||||
description: '고도화된 음향 모델 및 문맥 인식 STT',
|
||||
badge: 'Advanced Acoustic',
|
||||
requiresApiKey: true,
|
||||
defaultModel: 'best',
|
||||
defaultBaseUrl: 'https://api.assemblyai.com/v2',
|
||||
models: ['best', 'nano'],
|
||||
isCloud: true,
|
||||
},
|
||||
{
|
||||
id: 'google',
|
||||
name: 'Google Gemini 2.0 Flash / Cloud STT',
|
||||
description: 'Gemini 2.0 Flash 기반 한국어/다국어 인식',
|
||||
badge: 'Multilingual · Asian Languages',
|
||||
requiresApiKey: true,
|
||||
defaultModel: 'gemini-2.0-flash',
|
||||
defaultBaseUrl: 'https://generativelanguage.googleapis.com/v1beta',
|
||||
models: ['gemini-2.0-flash', 'gemini-1.5-flash'],
|
||||
isCloud: true,
|
||||
},
|
||||
{
|
||||
id: 'custom',
|
||||
name: 'Custom (OpenAI 호환)',
|
||||
description: 'vLLM, 사내 프라이빗 서버, Together 등 커스텀 엔드포인트',
|
||||
badge: 'Self-Hosted / Proxy',
|
||||
requiresApiKey: false,
|
||||
defaultModel: 'whisper-1',
|
||||
defaultBaseUrl: 'http://localhost:8000/v1',
|
||||
models: ['whisper-1', 'custom'],
|
||||
isCloud: true,
|
||||
},
|
||||
]
|
||||
|
||||
export class STTManager extends EventEmitter {
|
||||
private _drivers = new Map<STTProviderType, ISTTDriver>()
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this._registerDrivers()
|
||||
}
|
||||
|
||||
private _registerDrivers(): void {
|
||||
this._drivers.set('d3ro-cloud', new D3ROCloudDriver())
|
||||
this._drivers.set('openai', new OpenAIDriver())
|
||||
this._drivers.set('groq', new GroqDriver())
|
||||
this._drivers.set('deepgram', new DeepgramDriver())
|
||||
this._drivers.set('assemblyai', new AssemblyAIDriver())
|
||||
this._drivers.set('google', new GoogleDriver())
|
||||
this._drivers.set('custom', new CustomDriver())
|
||||
}
|
||||
|
||||
getProviders(): STTProviderInfo[] {
|
||||
return STT_PROVIDERS_META
|
||||
}
|
||||
|
||||
getActiveProvider(): STTProviderType {
|
||||
const provider = configGet('sttProvider') as STTProviderType | undefined
|
||||
return provider || 'local'
|
||||
}
|
||||
|
||||
setProvider(provider: STTProviderType): void {
|
||||
logger.info(`STT provider changed to: ${provider}`)
|
||||
configSet('sttProvider', provider)
|
||||
this.emit('provider-changed', { provider })
|
||||
}
|
||||
|
||||
getProviderConfig(provider: STTProviderType): STTProviderConfig {
|
||||
const configs = (configGet('sttProviderConfigs') || {}) as Record<STTProviderType, STTProviderConfig>
|
||||
const specificConfig = configs[provider] || {}
|
||||
const meta = STT_PROVIDERS_META.find((p) => p.id === provider)
|
||||
|
||||
return {
|
||||
apiKey: specificConfig.apiKey ?? '',
|
||||
baseUrl: specificConfig.baseUrl ?? meta?.defaultBaseUrl ?? '',
|
||||
modelId: specificConfig.modelId ?? meta?.defaultModel ?? '',
|
||||
temperature: specificConfig.temperature ?? 0,
|
||||
}
|
||||
}
|
||||
|
||||
setProviderConfig(provider: STTProviderType, config: STTProviderConfig): void {
|
||||
const configs = { ...((configGet('sttProviderConfigs') || {}) as Record<STTProviderType, STTProviderConfig>) }
|
||||
configs[provider] = {
|
||||
...configs[provider],
|
||||
...config,
|
||||
}
|
||||
configSet('sttProviderConfigs', configs)
|
||||
this.emit('config-changed', { provider, config: configs[provider] })
|
||||
logger.info(`STT provider config updated for: ${provider}`)
|
||||
}
|
||||
|
||||
async testConnection(params: TestSTTConnectionParams): Promise<TestSTTConnectionResult> {
|
||||
const { provider, apiKey, baseUrl, modelId } = params
|
||||
|
||||
if (provider === 'local') {
|
||||
const localStt = getLocalSTTService()
|
||||
const status = localStt.getStatus()
|
||||
return {
|
||||
success: true,
|
||||
latencyMs: 0,
|
||||
message: `로컬 Whisper 준비됨 (상태: ${status.engineState}, 모델: ${status.activeModel || '미선택'})`,
|
||||
}
|
||||
}
|
||||
|
||||
const driver = this._drivers.get(provider)
|
||||
if (!driver) {
|
||||
return {
|
||||
success: false,
|
||||
latencyMs: 0,
|
||||
message: `지원되지 않는 STT 공급자입니다: ${provider}`,
|
||||
}
|
||||
}
|
||||
|
||||
const currentConfig = this.getProviderConfig(provider)
|
||||
const effectiveConfig: STTProviderConfig = {
|
||||
...currentConfig,
|
||||
apiKey: apiKey !== undefined ? apiKey : currentConfig.apiKey,
|
||||
baseUrl: baseUrl !== undefined ? baseUrl : currentConfig.baseUrl,
|
||||
modelId: modelId !== undefined ? modelId : currentConfig.modelId,
|
||||
}
|
||||
|
||||
return driver.testConnection(effectiveConfig)
|
||||
}
|
||||
|
||||
async transcribe(audioBuffer: Buffer, options?: TranscribeOptions): Promise<TranscriptionResult> {
|
||||
const provider = this.getActiveProvider()
|
||||
|
||||
// 1. Local STT
|
||||
if (provider === 'local') {
|
||||
return getLocalSTTService().transcribe(audioBuffer, options)
|
||||
}
|
||||
|
||||
// 2. Cloud STT Driver
|
||||
const driver = this._drivers.get(provider)
|
||||
if (!driver) {
|
||||
logger.warn(`Driver not found for provider ${provider}, falling back to local`)
|
||||
return getLocalSTTService().transcribe(audioBuffer, options)
|
||||
}
|
||||
|
||||
const config = this.getProviderConfig(provider)
|
||||
const fallbackToLocal = configGet('sttFallbackToLocal') ?? true
|
||||
|
||||
try {
|
||||
logger.info(`Transcribing with cloud STT provider: ${provider} (model: ${config.modelId})`)
|
||||
return await driver.transcribe(audioBuffer, options, config)
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||
logger.warn(`Cloud STT (${provider}) failed: ${errorMsg}`)
|
||||
|
||||
if (fallbackToLocal) {
|
||||
logger.info(`Auto-fallback: executing Local Whisper transcription...`)
|
||||
this.emit('fallback-to-local', { provider, reason: errorMsg })
|
||||
try {
|
||||
return await getLocalSTTService().transcribe(audioBuffer, options)
|
||||
} catch (localError) {
|
||||
logger.error(`Local STT fallback also failed: ${localError}`)
|
||||
throw error // 원래 에러 유지
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
getStatus(): STTStatus {
|
||||
const provider = this.getActiveProvider()
|
||||
if (provider === 'local') {
|
||||
return {
|
||||
...getLocalSTTService().getStatus(),
|
||||
activeProvider: 'local',
|
||||
}
|
||||
}
|
||||
|
||||
const config = this.getProviderConfig(provider)
|
||||
const meta = STT_PROVIDERS_META.find((p) => p.id === provider)
|
||||
return {
|
||||
engineState: 'ready' as import('@d3ro/core/types').STTEngineState,
|
||||
activeModel: config.modelId || meta?.defaultModel || null,
|
||||
engineVersion: meta?.name || null,
|
||||
gpuAccelerated: false,
|
||||
activeProvider: provider,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _instance: STTManager | null = null
|
||||
|
||||
export function getSTTManager(): STTManager {
|
||||
if (!_instance) {
|
||||
_instance = new STTManager()
|
||||
}
|
||||
return _instance
|
||||
}
|
||||
|
||||
export function resetSTTManagerForTests(): void {
|
||||
if (_instance) _instance.removeAllListeners()
|
||||
_instance = null
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue