// src/main/ipc/voice-conversation-handlers.ts // Phase 13.1: 음성 대화 모드 IPC 핸들러 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, RealtimeTokenParams, RealtimeTokenResult, } from '@d3ro/core/types' const logger = getLogger('voice-conversation-handlers') export function registerVoiceConversationHandlers(): void { ipcMain.handle(IPC_CHANNELS.VOICE_CONVERSATION.START_SESSION, async () => { try { await getVoiceConversationService().startSession() return ipcSuccess(undefined) } catch (err) { const msg = err instanceof Error ? err.message : String(err) logger.error('Conversation start failed:', msg) return ipcError(ErrorCode.ConversationSessionAlreadyActive, msg) } }) ipcMain.handle(IPC_CHANNELS.VOICE_CONVERSATION.STOP_SESSION, async () => { try { getVoiceConversationService().stopSession() return ipcSuccess(undefined) } catch (err) { const msg = err instanceof Error ? err.message : String(err) return ipcError(ErrorCode.ConversationNoActiveSession, msg) } }) ipcMain.handle( IPC_CHANNELS.VOICE_CONVERSATION.SEND_MESSAGE, async (_event, params: ConversationSendParams) => { try { await getVoiceConversationService().sendTextMessage(params.text) return ipcSuccess(undefined) } catch (err) { const msg = err instanceof Error ? err.message : String(err) logger.error('Conversation send failed:', msg) return ipcError(ErrorCode.ConversationLLMFailed, msg) } }, ) ipcMain.handle(IPC_CHANNELS.VOICE_CONVERSATION.GET_STATE, async () => { try { return ipcSuccess(getVoiceConversationService().getSessionInfo()) } catch (err) { const msg = err instanceof Error ? err.message : String(err) return ipcError(ErrorCode.UnknownError, msg) } }) ipcMain.handle(IPC_CHANNELS.VOICE_CONVERSATION.GET_HISTORY, async () => { try { return ipcSuccess(getVoiceConversationService().getHistory()) } catch (err) { const msg = err instanceof Error ? err.message : String(err) return ipcError(ErrorCode.UnknownError, msg) } }) ipcMain.handle(IPC_CHANNELS.VOICE_CONVERSATION.CLEAR_HISTORY, async () => { try { getVoiceConversationService().clearHistory() return ipcSuccess(undefined) } catch (err) { const msg = err instanceof Error ? err.message : String(err) return ipcError(ErrorCode.UnknownError, msg) } }) ipcMain.handle(IPC_CHANNELS.VOICE_CONVERSATION.CANCEL_RESPONSE, async () => { try { getVoiceConversationService().cancelResponse() return ipcSuccess(undefined) } catch (err) { const msg = err instanceof Error ? err.message : String(err) return ipcError(ErrorCode.UnknownError, msg) } }) // 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(IPC_CHANNELS.VOICE_CONVERSATION.FINISH_LISTENING, async () => { try { await getVoiceConversationService().finishListening() return ipcSuccess(undefined) } catch (err) { const msg = err instanceof Error ? err.message : String(err) return ipcError(ErrorCode.ConversationLLMFailed, msg) } }) }