d3ro-voice/docs/phases/phase-14.5-meeting-enhancement.md
Yun Chan d4928ffa60 Phase 14.5 구현: 회의 모드 고도화 — 다중 문서 생성/편집/내보내기
PLAUD 수준의 기능 확장:
- 전사 편집: 인라인 편집 + 원본 보존 + 수정됨 표시(점선 밑줄)
- 다중 문서 생성: 회의록/보고서/아이디어노트/커스텀 템플릿
- MD 에디터: 렌더링/편집 토글 (react-markdown + remark-gfm)
- 상세 페이지 리디자인: 풀스크린 탭 전환 구조
- 내보내기: MD, PDF, TXT, DOCX 4종 (docx 패키지)
- 커스텀 프롬프트 템플릿 저장/재사용 (electron-store)
- SSOT: 마크다운 파싱 유틸 추출, 후처리에서 자동 문서 생성 제거

DB: meeting_documents 테이블 + edited_transcript 컬럼
IPC: 8+4 채널, 서비스 2개(MeetingModeService 확장 + MeetingDocTemplateService 신규)
UI: 8개 신규 컴포넌트 (meeting/ 디렉토리), 12개 locale i18n
2026-04-08 09:53:54 +09:00

17 KiB

Phase 14.5: 회의 모드 고도화 — 문서 생성/편집/내보내기

PLAUD/Otter.ai 수준의 다중 문서 생성 + 전사 편집 + MD 에디터 + 다운로드 확장 기존 Phase 14 인프라(녹음+전사+메모+LLM) 위에 확장


1. 개요

1.1 사용자 플로우

[회의 종료 후 상세 페이지]
    │
    ├── [Transcript 탭] ← 전사 원문 편집 + 원본/수정본 토글
    │       ├── 세그먼트 클릭 → 인라인 편집 (점선 밑줄 + hover "수정됨")
    │       ├── 원본 보기 토글 (rawTranscript ↔ editedTranscript)
    │       └── [TXT 다운로드] 버튼
    │
    ├── [+ 버튼] → 문서 생성 다이얼로그
    │       ├── 템플릿 선택 (회의록 / 보고서 / 아이디어 노트 / 커스텀)
    │       ├── 커스텀: 이름 + 프롬프트 입력 (저장/재사용 가능)
    │       └── [생성] → LLM 호출 → 새 탭 추가
    │
    └── [문서 탭 (동적)] ← 회의록, 보고서 등
            ├── 미리보기 모드: react-markdown 렌더링
            ├── 편집 모드: 마크다운 원본 textarea
            ├── 토글 버튼 (미리보기 ↔ 편집)
            ├── debounce 자동 저장 (500ms)
            └── [내보내기 ▼] 드롭다운 (MD / PDF / TXT / DOCX)

1.2 핵심 차별점

  • 다중 문서: 하나의 전사에서 여러 양식의 문서 생성 (PLAUD Multidimensional Summary)
  • 전사 편집: 녹음 중 + 종료 후 인라인 편집, 원본 보존, 수정 표시
  • 커스텀 프롬프트: 사용자 정의 AI 프롬프트 저장/재사용
  • 4종 내보내기: MD, PDF, TXT, DOCX
  • MD 에디터: 렌더링/편집 토글

2. DB 스키마

2.1 meeting_sessions 테이블 확장

-- 마이그레이션: 전사 편집 원본 보존용 컬럼 추가
ALTER TABLE meeting_sessions ADD COLUMN edited_transcript TEXT;

2.2 meeting_documents 테이블 (신규)

CREATE TABLE IF NOT EXISTS meeting_documents (
  id               TEXT PRIMARY KEY,
  session_id       TEXT NOT NULL,
  template_type    TEXT NOT NULL,    -- 'minutes' | 'report' | 'idea-note' | 'custom'
  title            TEXT NOT NULL,
  content          TEXT NOT NULL DEFAULT '',
  prompt_used      TEXT,             -- 커스텀 프롬프트 기록용
  llm_model        TEXT,
  llm_latency_ms   INTEGER,
  created_at       INTEGER NOT NULL,
  updated_at       INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_meeting_documents_session_id ON meeting_documents(session_id);

2.3 drizzle-orm 스키마 (src/main/db/schema.ts)

// meetingSessions에 추가
editedTranscript: text('edited_transcript'),

// 신규 테이블
export const meetingDocuments = sqliteTable(
  'meeting_documents',
  {
    id: text('id').primaryKey(),
    sessionId: text('session_id').notNull(),
    templateType: text('template_type', {
      enum: ['minutes', 'report', 'idea-note', 'custom'],
    }).notNull(),
    title: text('title').notNull(),
    content: text('content').notNull().default(''),
    promptUsed: text('prompt_used'),
    llmModel: text('llm_model'),
    llmLatencyMs: integer('llm_latency_ms'),
    createdAt: integer('created_at').notNull(),
    updatedAt: integer('updated_at').notNull(),
  },
  (table) => [
    index('idx_meeting_documents_session_id').on(table.sessionId),
  ]
)

3. 공유 타입 (src/shared/types.ts)

// ============================================================
// Phase 14.5: Meeting Document Types
// ============================================================

export type MeetingDocTemplateType = 'minutes' | 'report' | 'idea-note' | 'custom'

export interface MeetingDocTemplate {
  id: string
  name: string
  description: string
  templateType: MeetingDocTemplateType
  systemPrompt: string
  isBuiltin: boolean
  createdAt: number
  updatedAt: number
}

export interface MeetingDocument {
  id: string
  sessionId: string
  templateType: MeetingDocTemplateType
  title: string
  content: string
  promptUsed: string | null
  llmModel: string | null
  llmLatencyMs: number | null
  createdAt: number
  updatedAt: number
}

export interface MeetingGenerateDocParams {
  sessionId: string
  templateId: string
  customPrompt?: string
  customTitle?: string
}

export interface MeetingGenerateDocResult {
  document: MeetingDocument
}

export interface MeetingUpdateDocParams {
  documentId: string
  content: string
}

export interface MeetingDeleteDocParams {
  documentId: string
}

export interface MeetingGetDocsParams {
  sessionId: string
}

export interface MeetingUpdateTranscriptParams {
  sessionId: string
  editedTranscript: string
}

export type MeetingExportFormat = 'md' | 'pdf' | 'txt' | 'docx'

export interface MeetingExportDocParams {
  documentId: string
  format: MeetingExportFormat
}

export interface MeetingExportTranscriptParams {
  sessionId: string
}

export interface MeetingDocGeneratingProgress {
  sessionId: string
  templateId: string
  percent: number
}

export interface CreateMeetingDocTemplateParams {
  name: string
  description: string
  systemPrompt: string
}

export interface UpdateMeetingDocTemplateParams {
  id: string
  name?: string
  description?: string
  systemPrompt?: string
}

export interface DeleteMeetingDocTemplateParams {
  id: string
}

MeetingSessionDetail 확장:

export interface MeetingSessionDetail extends MeetingSessionSummary {
  rawTranscript: string | null
  editedTranscript: string | null     // 추가
  minutesMarkdown: string | null
  minutes: MeetingMinutes | null
  memos: MeetingMemo[]
  documents: MeetingDocument[]         // 추가
  sttModel: string | null
  llmModel: string | null
  errorMessage: string | null
}

4. 에러 코드 (src/shared/errors.ts)

// === Phase 14.5: Meeting Enhancement (886-892) ===
MeetingDocumentNotFound       = 886,
MeetingDocGenerationFailed    = 887,
MeetingDocExportFailed        = 888,
MeetingTranscriptUpdateFailed = 889,
MeetingDocTemplateNotFound    = 890,
MeetingDocTemplateBuiltinDelete = 891,
MeetingDocxExportFailed       = 892,

5. IPC 채널 (src/shared/ipc-channels.ts)

// MEETING_MODE 블록에 추가
UPDATE_TRANSCRIPT:      'meetingMode:updateTranscript',
GENERATE_DOCUMENT:      'meetingMode:generateDocument',
GET_DOCUMENTS:          'meetingMode:getDocuments',
UPDATE_DOCUMENT:        'meetingMode:updateDocument',
DELETE_DOCUMENT:        'meetingMode:deleteDocument',
EXPORT_DOCUMENT:        'meetingMode:exportDocument',
EXPORT_TRANSCRIPT:      'meetingMode:exportTranscript',
DOC_GENERATING_PROGRESS: 'meetingMode:docGeneratingProgress',

// 신규 그룹
MEETING_DOC_TEMPLATE: {
  GET_ALL:  'meetingDocTemplate:getAll',
  CREATE:   'meetingDocTemplate:create',
  UPDATE:   'meetingDocTemplate:update',
  DELETE:   'meetingDocTemplate:delete',
},

6. 서비스 명세

6.1 MeetingDocTemplateService (신규)

파일: src/main/services/MeetingDocTemplateService.ts 패턴: electron-store 전용 인스턴스 (DictationTemplateService 패턴)

빌트인 3종:

const BUILTIN_TEMPLATES: MeetingDocTemplate[] = [
  {
    id: 'builtin-minutes',
    name: '회의록',
    templateType: 'minutes',
    systemPrompt: `전사록을 분석하여 구조화된 회의록을 작성하세요.
## 요약 (3-5줄)
## 핵심 결정사항 (- 항목)
## 할 일 목록 (- [ ] 담당자: 할일)
## 타임라인 (| 시간 | 내용 |)`,
    isBuiltin: true,
  },
  {
    id: 'builtin-report',
    name: '보고서',
    templateType: 'report',
    systemPrompt: `전사록을 분석하여 제출용 보고서를 작성하세요.
## 개요 (배경, 목적)
## 핵심 내용 (주요 논의 사항)
## 결론 및 제안
## 첨부 참고사항`,
    isBuiltin: true,
  },
  {
    id: 'builtin-idea-note',
    name: '아이디어 노트',
    templateType: 'idea-note',
    systemPrompt: `전사록에서 아이디어와 인사이트를 추출하세요.
## 핵심 아이디어 (번호 매기기)
## 각 아이디어의 장단점
## 우선순위 제안
## 다음 단계`,
    isBuiltin: true,
  },
]

메서드: getAll(), getById(), create(), update(), delete()

6.2 MeetingModeService 확장

새 메서드:

메서드 설명
updateTranscript(sessionId, editedTranscript) 수정 전사 저장
generateDocument(params) 전사+템플릿→LLM→meeting_documents 저장
getDocuments(sessionId) 세션의 문서 목록
updateDocument(documentId, content) MD 편집 저장
deleteDocument(documentId) 문서 삭제
exportDocument(documentId, format) MD/PDF/TXT/DOCX 내보내기
exportTranscript(sessionId) 전사 TXT 다운로드

후처리 변경: _runPostProcessing()에서 자동 회의록 생성 제거. 전사 합산 + DB 저장(rawTranscript, status=completed)만 수행. 사용자가 [+]로 직접 문서 생성.

_loadSessionDetail 확장: editedTranscript + documents[] 포함

6.3 유틸 추출 (SSOT)

src/main/utils/meeting-markdown.ts:

  • parseMinutes(md) — MeetingModeService._parseMinutes() 추출
  • buildExportMarkdown(session) — _buildExportMarkdown() 추출
  • markdownToSimpleHtml(md) — _markdownToSimpleHtml() 추출

src/main/utils/markdown-to-docx.ts:

  • markdownToDocx(md, title): Promise<Buffer> — docx 패키지

7. Preload API (src/preload/index.ts)

// meetingMode 확장
updateTranscript: (params) => invoke(ch.UPDATE_TRANSCRIPT, params),
generateDocument: (params) => invoke(ch.GENERATE_DOCUMENT, params),
getDocuments: (params) => invoke(ch.GET_DOCUMENTS, params),
updateDocument: (params) => invoke(ch.UPDATE_DOCUMENT, params),
deleteDocument: (params) => invoke(ch.DELETE_DOCUMENT, params),
exportDocument: (params) => invoke(ch.EXPORT_DOCUMENT, params),
exportTranscript: (params) => invoke(ch.EXPORT_TRANSCRIPT, params),
onDocGeneratingProgress: (cb) => on(ch.DOC_GENERATING_PROGRESS, cb),

// 신규 그룹
meetingDocTemplate: {
  getAll: () => invoke(ch.GET_ALL),
  create: (params) => invoke(ch.CREATE, params),
  update: (params) => invoke(ch.UPDATE, params),
  delete: (params) => invoke(ch.DELETE, params),
},

8. UI 컴포넌트

8.1 컴포넌트 디렉토리: src/renderer/components/meeting/

컴포넌트 설명
EditableSegment.tsx 전사 세그먼트 인라인 편집. 클릭→TextField, 점선 밑줄, hover "수정됨" 툴팁
MarkdownRenderer.tsx react-markdown + remark-gfm 래퍼. d3roPalette 스타일
MarkdownEditor.tsx 미리보기/편집 토글. 편집 모드=textarea, debounce 자동저장(500ms)
ExportMenu.tsx MD/PDF/TXT/DOCX 드롭다운
TranscriptTab.tsx 전사 편집 탭. 원본/수정본 토글, EditableSegment 목록, TXT 다운로드
DocumentTab.tsx 문서 탭. MarkdownEditor + ExportMenu + 삭제
AddDocumentDialog.tsx [+] 버튼 다이얼로그. 템플릿 선택(빌트인3+커스텀), 프롬프트 입력, 생성
MeetingDetailTabs.tsx 탭 바 + 콘텐츠 라우팅 컨테이너

8.2 상세 뷰 레이아웃

┌──────────────────────────────────────────────────┐
│ [←] 프로젝트 킥오프 ✏️    [Export ▼] [🗑]         │
│ 2026-04-06 14:30 ~ 15:12 · 42분                  │
├──────────────────────────────────────────────────┤
│ [📝 Transcript] [회의록] [보고서] [+ 추가]        │
├──────────────────────────────────────────────────┤
│                                                  │
│  (선택된 탭의 풀스크린 콘텐츠)                     │
│                                                  │
│  Transcript탭: EditableSegment 목록               │
│  문서탭: MarkdownEditor (렌더링↔편집 토글)         │
│                                                  │
└──────────────────────────────────────────────────┘

8.3 녹음 뷰 세그먼트 편집

┌─ 실시간 전사 ScreenPanel ────────────────┐
│ [00:15] 안녕하세요 오늘 회의를...         │ ← 클릭 시 편집 가능
│ ✏̣[̣0̣1̣:̣3̣0̣]̣ ̣예̣산̣ ̣관̣련̣인̣데̣요̣.̣.̣.̣          │ ← 수정됨: 점선 밑줄
│ [02:00] 다음 안건은...                   │
└──────────────────────────────────────────┘

9. i18n 키

ko.json 추가

"meeting.transcriptTab": "전사",
"meeting.addDocument": "문서 추가",
"meeting.selectTemplate": "템플릿 선택",
"meeting.generate": "생성",
"meeting.generating": "문서 생성 중...",
"meeting.editMode": "편집",
"meeting.previewMode": "미리보기",
"meeting.modified": "수정됨",
"meeting.originalText": "원본 보기",
"meeting.editedText": "수정본 보기",
"meeting.exportDocx": "DOCX 내보내기",
"meeting.exportTxt": "TXT 내보내기",
"meeting.exportFormat": "내보내기 형식",
"meeting.customPrompt": "커스텀 프롬프트",
"meeting.templateName": "템플릿 이름",
"meeting.templatePrompt": "AI 프롬프트",
"meeting.templateSave": "템플릿 저장",
"meeting.documentDeleted": "문서가 삭제되었습니다",
"meeting.autoSaved": "자동 저장됨",
"meeting.minutes": "회의록",
"meeting.report": "보고서",
"meeting.ideaNote": "아이디어 노트",
"meeting.custom": "커스텀"

10. 구현 순서

Step A: 기반 인프라 (타입 + DB + IPC 채널 + 에러코드)

  1. src/shared/types.ts — 새 타입 추가 + MeetingSessionDetail 확장
  2. src/shared/ipc-channels.ts — MEETING_MODE 확장 + MEETING_DOC_TEMPLATE 추가
  3. src/shared/errors.ts — 886-892 추가
  4. src/main/db/schema.ts — meetingDocuments + editedTranscript
  5. src/main/db/index.ts — 마이그레이션 SQL

Step B: npm 패키지 설치

  1. npm install react-markdown remark-gfm docx

Step C: 유틸 추출 (SSOT)

  1. src/main/utils/meeting-markdown.ts — 파싱/내보내기 로직 추출
  2. src/main/utils/markdown-to-docx.ts — DOCX 변환

Step D: 서비스 계층

  1. src/main/services/MeetingDocTemplateService.ts — 신규
  2. src/main/services/MeetingModeService.ts — 확장 (7 메서드 + 후처리 변경 + _loadSessionDetail)

Step E: IPC + Preload

  1. src/main/ipc/meeting-doc-template-handlers.ts — 신규
  2. src/main/ipc/meeting-mode-handlers.ts — 확장
  3. src/main/ipc/index.ts — import 추가
  4. src/preload/index.ts — API 확장

Step F: UI 컴포넌트

15-22. src/renderer/components/meeting/ — 8개 컴포넌트

Step G: 페이지 통합

  1. src/renderer/pages/MeetingModePage.tsx — 상세 뷰 리디자인 + 녹음 뷰 편집

Step H: i18n

  1. 12개 locale 키 추가

Step I: 마무리

  1. typecheck + build + test
  2. bootstrap 초기화 단계 추가 (MeetingDocTemplateService)

11. TODO 항목 (향후 구현)

  • AI 채팅: 회의록 레퍼런스 기반 Q&A (여러 레퍼런스 선택 + 대화)
  • 웹 공유: 만료 링크 공유 (웹 서비스 연동 대비 설계)
  • 마인드맵: AI 시각적 마인드맵 생성 (JPEG/SVG 내보내기)
  • Auto Polish: AI 전사 다듬기 (필러 제거, 가독성 향상)
  • 화자 구분: 스피커 라벨링 + 이름 지정

파일 목록

신규 (12개)

파일 설명
src/main/services/MeetingDocTemplateService.ts 문서 템플릿 CRUD
src/main/ipc/meeting-doc-template-handlers.ts 템플릿 IPC
src/main/utils/meeting-markdown.ts MD 파싱 유틸
src/main/utils/markdown-to-docx.ts DOCX 변환
src/renderer/components/meeting/MeetingDetailTabs.tsx 탭 컨테이너
src/renderer/components/meeting/TranscriptTab.tsx 전사 편집 탭
src/renderer/components/meeting/DocumentTab.tsx 문서 뷰/편집 탭
src/renderer/components/meeting/AddDocumentDialog.tsx 문서 생성 다이얼로그
src/renderer/components/meeting/MarkdownRenderer.tsx MD 렌더러 래퍼
src/renderer/components/meeting/MarkdownEditor.tsx 렌더링/편집 토글
src/renderer/components/meeting/EditableSegment.tsx 인라인 편집 세그먼트
src/renderer/components/meeting/ExportMenu.tsx 내보내기 드롭다운

수정 (10+12 locale)

파일 변경
src/shared/types.ts 새 타입 + MeetingSessionDetail 확장
src/shared/ipc-channels.ts 8+4 채널
src/shared/errors.ts 7개 에러 코드
src/main/db/schema.ts meetingDocuments + editedTranscript
src/main/db/index.ts 마이그레이션
src/main/services/MeetingModeService.ts 7 메서드 + 후처리 변경
src/main/ipc/meeting-mode-handlers.ts 7 핸들러
src/main/ipc/index.ts import 추가
src/preload/index.ts API 확장
src/renderer/pages/MeetingModePage.tsx 상세 뷰 리디자인
src/renderer/i18n/*.json 12개 locale