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
This commit is contained in:
parent
6197ceb132
commit
d4928ffa60
38 changed files with 4791 additions and 438 deletions
245
src/main/services/MeetingDocTemplateService.ts
Normal file
245
src/main/services/MeetingDocTemplateService.ts
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
// src/main/services/MeetingDocTemplateService.ts
|
||||
// Phase 14.5: 회의 문서 템플릿 서비스 (electron-store 기반)
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
import { nanoid } from 'nanoid'
|
||||
import Store from 'electron-store'
|
||||
import { getLogger } from './LoggerService'
|
||||
import { D3ROError, ErrorCode } from '@shared/errors'
|
||||
import type {
|
||||
MeetingDocTemplate,
|
||||
CreateMeetingDocTemplateParams,
|
||||
UpdateMeetingDocTemplateParams,
|
||||
MeetingDocTemplateType,
|
||||
} from '@shared/types'
|
||||
|
||||
const logger = getLogger('MeetingDocTemplateService')
|
||||
|
||||
// ── 빌트인 템플릿 ──
|
||||
const BUILTIN_TEMPLATES: MeetingDocTemplate[] = [
|
||||
{
|
||||
id: 'builtin-minutes',
|
||||
name: '회의록',
|
||||
description: '결정사항, 할 일, 타임라인 구조의 표준 회의록',
|
||||
templateType: 'minutes' as MeetingDocTemplateType,
|
||||
systemPrompt: `당신은 전문 회의록 작성 비서입니다.
|
||||
전사록을 분석하여 구조화된 회의록을 작성합니다.
|
||||
|
||||
규칙:
|
||||
- 전사록에 없는 내용을 추가하지 마세요
|
||||
- 시간 순서를 유지하세요
|
||||
- 한국어로 작성하세요 (원문이 영어면 원어 유지)
|
||||
|
||||
출력 형식:
|
||||
## 요약
|
||||
(3-5줄 핵심 요약)
|
||||
|
||||
## 핵심 결정사항
|
||||
- (결정 1)
|
||||
- (결정 2)
|
||||
|
||||
## 할 일 목록
|
||||
- [ ] (담당자가 있으면 포함) (할 일)
|
||||
|
||||
## 타임라인
|
||||
| 시간 | 내용 |
|
||||
|------|------|
|
||||
| MM:SS | 주요 발언/이벤트 |`,
|
||||
isBuiltin: true,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'builtin-report',
|
||||
name: '보고서',
|
||||
description: '개요, 핵심 내용, 결론, 제안 구조의 보고서',
|
||||
templateType: 'report' as MeetingDocTemplateType,
|
||||
systemPrompt: `당신은 전문 비즈니스 보고서 작성 비서입니다.
|
||||
회의 전사록을 분석하여 체계적인 보고서를 작성합니다.
|
||||
|
||||
규칙:
|
||||
- 전사록에 없는 내용을 추가하지 마세요
|
||||
- 사실 기반으로 작성하세요
|
||||
- 한국어로 작성하세요
|
||||
|
||||
출력 형식:
|
||||
## 개요
|
||||
(회의 목적과 배경 2-3줄)
|
||||
|
||||
## 핵심 내용
|
||||
- (주요 논의 사항 1)
|
||||
- (주요 논의 사항 2)
|
||||
- (주요 논의 사항 3)
|
||||
|
||||
## 결론
|
||||
(합의된 결론 및 방향 2-3줄)
|
||||
|
||||
## 제안사항
|
||||
- (제안 1)
|
||||
- (제안 2)`,
|
||||
isBuiltin: true,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'builtin-idea-note',
|
||||
name: '아이디어 노트',
|
||||
description: '핵심 아이디어, 장단점, 우선순위, 다음 단계 구조',
|
||||
templateType: 'idea-note' as MeetingDocTemplateType,
|
||||
systemPrompt: `당신은 창의적 아이디어 정리 전문가입니다.
|
||||
회의 전사록에서 아이디어와 제안을 추출하여 정리합니다.
|
||||
|
||||
규칙:
|
||||
- 전사록에 등장한 아이디어만 정리하세요
|
||||
- 실행 가능성 중심으로 평가하세요
|
||||
- 한국어로 작성하세요
|
||||
|
||||
출력 형식:
|
||||
## 핵심 아이디어
|
||||
- (아이디어 1)
|
||||
- (아이디어 2)
|
||||
|
||||
## 장점
|
||||
- (장점 1)
|
||||
- (장점 2)
|
||||
|
||||
## 단점 / 리스크
|
||||
- (단점 1)
|
||||
- (단점 2)
|
||||
|
||||
## 우선순위
|
||||
1. (최우선 항목)
|
||||
2. (차순위 항목)
|
||||
|
||||
## 다음 단계
|
||||
- [ ] (액션 아이템 1)
|
||||
- [ ] (액션 아이템 2)`,
|
||||
isBuiltin: true,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
]
|
||||
|
||||
interface MeetingDocTemplateStoreSchema {
|
||||
templates: MeetingDocTemplate[]
|
||||
}
|
||||
|
||||
class MeetingDocTemplateService extends EventEmitter {
|
||||
private _store: Store<MeetingDocTemplateStoreSchema>
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this._store = new Store<MeetingDocTemplateStoreSchema>({
|
||||
name: 'meeting-doc-templates',
|
||||
defaults: {
|
||||
templates: [...BUILTIN_TEMPLATES],
|
||||
},
|
||||
})
|
||||
this._ensureBuiltins()
|
||||
}
|
||||
|
||||
// ── CRUD ──
|
||||
|
||||
getAll(): MeetingDocTemplate[] {
|
||||
return this._store.get('templates', [])
|
||||
}
|
||||
|
||||
getById(id: string): MeetingDocTemplate | null {
|
||||
return this.getAll().find((t) => t.id === id) ?? null
|
||||
}
|
||||
|
||||
create(params: CreateMeetingDocTemplateParams): MeetingDocTemplate {
|
||||
const template: MeetingDocTemplate = {
|
||||
id: nanoid(),
|
||||
name: params.name,
|
||||
description: params.description,
|
||||
templateType: 'custom' as MeetingDocTemplateType,
|
||||
systemPrompt: params.systemPrompt,
|
||||
isBuiltin: false,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
}
|
||||
|
||||
const templates = this.getAll()
|
||||
templates.push(template)
|
||||
this._store.set('templates', templates)
|
||||
logger.info(`회의 문서 템플릿 생성: ${template.id} (${template.name})`)
|
||||
return template
|
||||
}
|
||||
|
||||
update(params: UpdateMeetingDocTemplateParams): MeetingDocTemplate {
|
||||
const templates = this.getAll()
|
||||
const idx = templates.findIndex((t) => t.id === params.id)
|
||||
if (idx === -1) {
|
||||
throw new D3ROError(
|
||||
ErrorCode.MeetingDocTemplateNotFound,
|
||||
`템플릿을 찾을 수 없습니다: ${params.id}`,
|
||||
)
|
||||
}
|
||||
|
||||
const existing = templates[idx]
|
||||
const updated: MeetingDocTemplate = {
|
||||
...existing,
|
||||
...(params.name !== undefined && { name: params.name }),
|
||||
...(params.description !== undefined && { description: params.description }),
|
||||
...(params.systemPrompt !== undefined && { systemPrompt: params.systemPrompt }),
|
||||
updatedAt: Date.now(),
|
||||
}
|
||||
|
||||
templates[idx] = updated
|
||||
this._store.set('templates', templates)
|
||||
logger.info(`회의 문서 템플릿 수정: ${params.id}`)
|
||||
return updated
|
||||
}
|
||||
|
||||
delete(id: string): void {
|
||||
const templates = this.getAll()
|
||||
const template = templates.find((t) => t.id === id)
|
||||
if (!template) {
|
||||
throw new D3ROError(
|
||||
ErrorCode.MeetingDocTemplateNotFound,
|
||||
`템플릿을 찾을 수 없습니다: ${id}`,
|
||||
)
|
||||
}
|
||||
if (template.isBuiltin) {
|
||||
throw new D3ROError(
|
||||
ErrorCode.MeetingDocTemplateBuiltinDelete,
|
||||
'빌트인 템플릿은 삭제할 수 없습니다',
|
||||
)
|
||||
}
|
||||
|
||||
this._store.set(
|
||||
'templates',
|
||||
templates.filter((t) => t.id !== id),
|
||||
)
|
||||
logger.info(`회의 문서 템플릿 삭제: ${id}`)
|
||||
}
|
||||
|
||||
private _ensureBuiltins(): void {
|
||||
const templates = this.getAll()
|
||||
let changed = false
|
||||
for (const builtin of BUILTIN_TEMPLATES) {
|
||||
if (!templates.find((t) => t.id === builtin.id)) {
|
||||
templates.push(builtin)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
this._store.set('templates', templates)
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.removeAllListeners()
|
||||
}
|
||||
}
|
||||
|
||||
// ── 싱글톤 ──
|
||||
let instance: MeetingDocTemplateService | null = null
|
||||
|
||||
export function getMeetingDocTemplateService(): MeetingDocTemplateService {
|
||||
if (!instance) {
|
||||
instance = new MeetingDocTemplateService()
|
||||
}
|
||||
return instance
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue