// VoiceMode session-completed → 결과 라우팅(템플릿 필드 등) + History 저장. // bootstrap과 테스트가 같은 함수를 호출한다. import { app } from 'electron' import { D3ROError, ErrorCode } from '@d3ro/core/errors' import { getHistoryService } from './services/HistoryService' import { getLogger } from './services/LoggerService' import { routeCompletedVoiceText } from './voice-text-routing' import type { HistoryEntry } from '@d3ro/core/types' const logger = getLogger('voice-persist') export interface PersistableVoiceSession { id: string transcription: string processedText: string | null mode: string startedAt: number } /** * @param audioPath VoiceMode 가 완료 전에 실제로 저장한 녹음 경로(RecordingStore.save 결과). * 오디오가 없거나 저장에 실패했으면 null — 존재하지 않는 경로를 히스토리에 남기지 않는다. */ export function persistCompletedVoiceSession( session: PersistableVoiceSession, finalText: string, audioPath: string | null = null, ): HistoryEntry { const wordCount = finalText.split(/\s+/).filter((w) => w.length > 0).length const mode = session.mode === 'hands-free' ? 'dictation' : session.mode return getHistoryService().create({ originalText: session.transcription || finalText, polishedText: session.processedText, mode: mode as HistoryEntry['mode'], status: 'completed', duration: (Date.now() - session.startedAt) / 1000, wordCount, audioLocalPath: audioPath, appVersion: typeof app.getVersion === 'function' ? app.getVersion() : '1.0.0', }) } /** * 완료된 받아쓰기 결과를 소비자(진행 중인 받아쓰기 템플릿 세션의 현재 필드 등)에게 넘긴다. * 소비되면 true — 일반 삽입 경로는 isVoiceSessionClaimed(session.id)로 이를 확인한다. */ export function routeCompletedVoiceSession(session: PersistableVoiceSession, finalText: string): boolean { return routeCompletedVoiceText(session.id, finalText) } export function persistCompletedVoiceSessionSafe( emitError: (error: D3ROError, session: PersistableVoiceSession) => void, session: PersistableVoiceSession, finalText: string, audioPath: string | null = null, ): HistoryEntry | null { // 히스토리 저장이 실패해도 결과는 소비자에게 전달돼야 하므로 먼저 라우팅한다 routeCompletedVoiceSession(session, finalText) try { return persistCompletedVoiceSession(session, finalText, audioPath) } catch (error) { const d3ro = error instanceof D3ROError ? error : new D3ROError( ErrorCode.DBWriteFailed, `Failed to save history: ${error instanceof Error ? error.message : String(error)}`, ) logger.error(d3ro.message) emitError(d3ro, session) return null } }