feat(conversation): OpenAI gpt-realtime-2.1 라이브 음성 대화 Premium 백엔드
- realtime-token Edge Function: 티어 검증 + realtime_session 쿼터 + ephemeral key 발급 - useRealtimeConversation 훅: WebRTC 직결 (마이크 → OpenAI, 오디오 자동 재생) - VoiceConversationPage: conversationBackend 분기 + 연결 실패 시 로컬 fallback - AppConfig.conversationBackend + SettingsModal 음성 대화 엔진 선택 - ErrorCode 799 ConversationRealtimeTokenFailed, i18n ko/en
This commit is contained in:
parent
983c60cda2
commit
8f7d300b89
15 changed files with 762 additions and 40 deletions
|
|
@ -5,8 +5,13 @@ import { ipcMain } from 'electron'
|
|||
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
|
||||
import { ErrorCode, ipcSuccess, ipcError } from '@d3ro/core/errors'
|
||||
import { getVoiceConversationService } from '../services/VoiceConversationService'
|
||||
import { getCloudSyncService } from '../services/CloudSyncService'
|
||||
import { getLogger } from '../services/LoggerService'
|
||||
import type { ConversationSendParams } from '@d3ro/core/types'
|
||||
import type {
|
||||
ConversationSendParams,
|
||||
RealtimeTokenParams,
|
||||
RealtimeTokenResult,
|
||||
} from '@d3ro/core/types'
|
||||
|
||||
const logger = getLogger('voice-conversation-handlers')
|
||||
|
||||
|
|
@ -84,6 +89,47 @@ export function registerVoiceConversationHandlers(): void {
|
|||
}
|
||||
})
|
||||
|
||||
// OpenAI Realtime ephemeral token — 렌더러가 WebRTC 직결에 사용
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.VOICE_CONVERSATION.GET_REALTIME_TOKEN,
|
||||
async (_event, params: RealtimeTokenParams) => {
|
||||
try {
|
||||
const { data, error } = await getCloudSyncService().invokeFunction(
|
||||
'realtime-token',
|
||||
{ ...params },
|
||||
)
|
||||
if (error) {
|
||||
return ipcError(ErrorCode.ConversationRealtimeTokenFailed, error.message)
|
||||
}
|
||||
const raw = data as {
|
||||
value?: string
|
||||
expires_at?: number
|
||||
model?: string
|
||||
tier?: string
|
||||
error?: string
|
||||
message?: string
|
||||
}
|
||||
if (!raw.value) {
|
||||
return ipcError(
|
||||
ErrorCode.ConversationRealtimeTokenFailed,
|
||||
raw.error ?? raw.message ?? 'No token in response',
|
||||
)
|
||||
}
|
||||
const result: RealtimeTokenResult = {
|
||||
value: raw.value,
|
||||
expiresAt: raw.expires_at ?? 0,
|
||||
model: raw.model ?? 'unknown',
|
||||
tier: (raw.tier as RealtimeTokenResult['tier']) ?? 'free',
|
||||
}
|
||||
return ipcSuccess(result)
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
logger.error('Realtime token request failed:', msg)
|
||||
return ipcError(ErrorCode.ConversationRealtimeTokenFailed, msg)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// finishListening — 렌더러에서 녹음 종료 버튼 클릭 시
|
||||
ipcMain.handle('voiceConversation:finishListening', async () => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ const CONFIG_DEFAULTS: AppConfig = {
|
|||
// Phase 3.2: 기본값은 'local' — 누구나 로그인 없이 로컬 Ollama로 쓸 수 있는
|
||||
// 엔트리 전략. 사용자가 Settings에서 'premium'으로 전환 시 로그인 + 구독 필요.
|
||||
llmBackend: 'local' as const,
|
||||
// 라이브 음성 대화 백엔드 — 'realtime'은 로그인+구독 필요 (OpenAI Realtime WebRTC)
|
||||
conversationBackend: 'local' as const,
|
||||
defaultLLMAction: 'refine',
|
||||
dictationShortcut: {
|
||||
keyCode: 0xa5, // Right Alt
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue