fix(meeting): persist transcript segment edits, restore live recording view, and route settings deep links by tab id

This commit is contained in:
Yun Chan 2026-09-28 00:54:03 +09:00
parent a8983c583a
commit a85b24d385
15 changed files with 974 additions and 69 deletions

View file

@ -6,7 +6,11 @@ import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError, D3ROError } from '@d3ro/core/errors'
import { getMeetingModeService } from '../services/MeetingModeService'
import { getLogger } from '../services/LoggerService'
import { LiveSegmentEditor } from '../services/meeting/live-segment-edits'
import type { LiveSegmentEditResult } from '../services/meeting/live-segment-edits'
import type {
CaptionSegment,
MeetingSessionDetail,
MeetingAddMemoParams,
MeetingGetSessionsParams,
MeetingGetSessionParams,
@ -19,8 +23,47 @@ import type {
const logger = getLogger('meeting-mode-handlers')
/** 렌더러 editSegment 요청 — preload `meetingMode.editSegment` 와 같은 모양 */
interface MeetingEditSegmentParams {
sessionId: string
segmentId: string
text: string
}
let liveSegmentEditor: LiveSegmentEditor | null = null
/** MeetingModeService 를 LiveMeetingPort 로 감싸 실시간 세그먼트 편집기를 붙인다(재등록 시 이전 구독 해지). */
function attachLiveSegmentEditor(): LiveSegmentEditor {
liveSegmentEditor?.dispose()
const service = getMeetingModeService()
liveSegmentEditor = new LiveSegmentEditor(
{
getStateInfo: () => service.getStateInfo(),
onSegment: (listener: (segment: CaptionSegment) => void) => {
service.on('segment', listener)
return () => { service.off('segment', listener) }
},
onSessionCompleted: (listener: (detail: MeetingSessionDetail) => void) => {
service.on('session-completed', listener)
return () => { service.off('session-completed', listener) }
},
saveEditedTranscript: (sessionId, editedTranscript) =>
service.updateTranscript(sessionId, editedTranscript),
},
logger,
)
return liveSegmentEditor
}
const EDIT_SEGMENT_FAILURES: Readonly<Record<Exclude<LiveSegmentEditResult, 'ok'>, { code: ErrorCode; message: string }>> = {
'not-live': { code: ErrorCode.MeetingNotRecording, message: '진행 중인 회의 세션이 아닙니다' },
'unknown-segment': { code: ErrorCode.MeetingTranscriptUpdateFailed, message: '수정할 발화를 찾을 수 없습니다' },
empty: { code: ErrorCode.MeetingTranscriptUpdateFailed, message: '빈 내용으로는 수정할 수 없습니다' },
}
export function registerMeetingModeHandlers(): void {
const ch = IPC_CHANNELS.MEETING_MODE
const liveEditor = attachLiveSegmentEditor()
ipcMain.handle(ch.START_RECORDING, async (_event, params?: { force?: boolean }) => {
try {
@ -143,6 +186,23 @@ export function registerMeetingModeHandlers(): void {
}
})
// 녹음 중 실시간 세그먼트 수정 — 세션 완료 시 editedTranscript 로 반영된다.
ipcMain.handle(ch.EDIT_SEGMENT, (_event, params: MeetingEditSegmentParams) => {
try {
const result = liveEditor.edit(params.sessionId, params.segmentId, params.text)
if (result === 'ok') return ipcSuccess(undefined)
const failure = EDIT_SEGMENT_FAILURES[result]
logger.warn(`editSegment 거부(${result}): sessionId=${params.sessionId}, segmentId=${params.segmentId}`)
return ipcError(failure.code, failure.message)
} catch (err) {
logger.error(`editSegment 실패: ${err instanceof Error ? err.message : String(err)}`)
return ipcError(
ErrorCode.MeetingTranscriptUpdateFailed,
err instanceof Error ? err.message : String(err),
)
}
})
ipcMain.handle(ch.GENERATE_DOCUMENT, async (_event, params: MeetingGenerateDocParams) => {
try {
const doc = await getMeetingModeService().generateDocument(params)