# πŸŽ™οΈ Meeting Minutes **μŒμ„±μ„ ν…μŠ€νŠΈλ‘œ, ν…μŠ€νŠΈλ₯Ό 회의둝으둜** β€” λΈŒλΌμš°μ € 기반 μ‹€μ‹œκ°„ AI 회의둝 μžλ™ μž‘μ„± 도ꡬ. λ…ΉμŒ μ‹œμž‘ ν•œ 번이면 끝. Gemini AIκ°€ 30μ΄ˆλ§ˆλ‹€ νšŒμ˜λ‘μ„ κ°±μ‹ ν•˜κ³ , μ’…λ£Œ μ‹œ μžλ™μœΌλ‘œ μ΅œμ’… λ¬Έμ„œλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. 회의둝 외에도 κ°•μ˜ λ…ΈνŠΈ, 1:1 λ―ΈνŒ…, λΈŒλ ˆμΈμŠ€ν† λ° λ“± 6κ°€μ§€ ν…œν”Œλ¦Ώμ„ μ œκ³΅ν•˜λ©°, Google Docs에 μ„œμ‹ κ·ΈλŒ€λ‘œ λΆ™μ—¬λ„£κΈ°κΉŒμ§€ ν•œ λ²ˆμ—. ```bash git clone https://github.com/nad4-su/meeting-minutes.git cd meeting-minutes && cp .env.example .env # ⚠️ .env νŒŒμΌμ„ μ—΄μ–΄ POSTGRES_PASSWORDλ₯Ό κ°•λ ₯ν•œ κ°’μœΌλ‘œ μ±„μ›Œμ£Όμ„Έμš” # 예: echo "POSTGRES_PASSWORD=$(openssl rand -base64 24)" >> .env docker compose --profile tools run --rm migrate docker compose up -d # β†’ http://127.0.0.1:3000 (Chrome ꢌμž₯, localhost μ „μš©) ``` > ⚠️ **λ³΄μ•ˆ 주의**: 회의 λ‚΄μš©μ— 민감 정보가 μžˆλ‹€λ©΄ [SECURITY.md](SECURITY.md)의 μœ„ν˜‘ λͺ¨λΈ μ„Ήμ…˜μ„ λ¨Όμ € μ½μ–΄μ£Όμ„Έμš”. μ‹€μ‹œκ°„ λ…ΉμŒμ€ Chrome Web Speech APIλ₯Ό μ‚¬μš©ν•˜μ—¬ μŒμ„±μ„ Google μ„œλ²„λ‘œ μ „μ†‘ν•©λ‹ˆλ‹€. > > πŸ‘₯ **직원/νŒ€μ›μ—κ²Œ κ³΅μœ ν•  λ•Œ**: [docs/EMPLOYEE_QUICKSTART.md](docs/EMPLOYEE_QUICKSTART.md) β€” 5λΆ„ μ‹œμž‘ κ°€μ΄λ“œ (사내 μ •μ±… μ•ˆλ‚΄ 포함) --- ## πŸ“‘ λͺ©μ°¨ - [✨ μ£Όμš” κΈ°λŠ₯](#-μ£Όμš”-κΈ°λŠ₯) - [🎀 μ‹€μ‹œκ°„ λ…ΉμŒ & 전사](#-μ‹€μ‹œκ°„-λ…ΉμŒ--전사) - [πŸ€– AI 회의둝 (6 ν…œν”Œλ¦Ώ Γ— 3 강도)](#-ai-회의둝-6-ν…œν”Œλ¦Ώ--3-강도) - [⚑ μ‹€μ‹œκ°„ 둀링 μš”μ•½](#-μ‹€μ‹œκ°„-둀링-μš”μ•½) - [πŸ’Ύ 회의둝 μ €μž₯/쑰회/검색](#-회의둝-μ €μž₯μ‘°νšŒκ²€μƒ‰) - [βœ… μ•‘μ…˜ μ•„μ΄ν…œ μžλ™ μΆ”μΆœ](#-μ•‘μ…˜-μ•„μ΄ν…œ-μžλ™-μΆ”μΆœ) - [πŸ”‘ μ›Ήμ—μ„œ API ν‚€ μ„€μ •](#-μ›Ήμ—μ„œ-api-ν‚€-μ„€μ •) - [πŸ“‹ Google Docs ν˜Έν™˜ 볡사](#-google-docs-ν˜Έν™˜-볡사) - [πŸš€ μ‹œμž‘ν•˜κΈ°](#-μ‹œμž‘ν•˜κΈ°) - [Docker Compose (ꢌμž₯)](#docker-compose-ꢌμž₯) - [둜컬 개발 ν™˜κ²½](#둜컬-개발-ν™˜κ²½) - [πŸ› οΈ 기술 μŠ€νƒ](#️-기술-μŠ€νƒ) - [πŸ“ ν”„λ‘œμ νŠΈ ꡬ쑰](#-ν”„λ‘œμ νŠΈ-ꡬ쑰) - [πŸ—ΊοΈ λ‘œλ“œλ§΅](#️-λ‘œλ“œλ§΅) - [πŸ”’ λ³΄μ•ˆ λͺ¨λΈ](#-λ³΄μ•ˆ-λͺ¨λΈ) - [⚠️ μ•Œλ €μ§„ μ œμ•½](#️-μ•Œλ €μ§„-μ œμ•½) - [🀝 κΈ°μ—¬ν•˜κΈ°](#-κΈ°μ—¬ν•˜κΈ°) - [πŸ“„ λΌμ΄μ„ μŠ€](#-λΌμ΄μ„ μŠ€) --- ## ✨ μ£Όμš” κΈ°λŠ₯ ### 🎀 μ‹€μ‹œκ°„ λ…ΉμŒ & 전사 λΈŒλΌμš°μ € λ‚΄μž₯ Web Speech API(Chrome `ko-KR`)둜 마이크 μž…λ ₯을 μ‹€μ‹œκ°„ ν…μŠ€νŠΈλ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€. **νŠΉμ§•** - μ¦‰μ‹œ μ‹œμž‘ β€” 별도 STT μ„œλ²„λ‚˜ ν‚€ 없이 μž‘λ™ - **μžλ™ μž¬μ—°κ²°** β€” λ„€νŠΈμ›Œν¬ λŠκΉ€ μ‹œ μ΅œλŒ€ 8회 μž¬μ‹œλ„, λˆ„μ  transcript 보쑴 - ν™•μ • μ „ ν…μŠ€νŠΈλŠ” νšŒμƒ‰ 이타릭 + κΉœλΉ‘μ΄λŠ” μ»€μ„œλ‘œ μ‹œκ°ν™” - λ…ΉμŒ 쀑 `πŸ”΄ N단어 Β· M개 ꡬ간` μ‹€μ‹œκ°„ μΉ΄μš΄ν„° **μ‚¬μš©** 1. ν™ˆμ—μ„œ **🎀 λ…ΉμŒ μ‹œμž‘** 클릭 2. 마이크 κΆŒν•œ ν—ˆμš© 3. λ°œν™” β†’ 쒌츑 νŒ¨λ„μ— ν…μŠ€νŠΈκ°€ μŒ“μž„ 4. **λ…ΉμŒ 쀑지** β†’ ν…μŠ€νŠΈκ°€ textarea둜 이동, 직접 νŽΈμ§‘ κ°€λŠ₯ --- ### πŸ€– AI 회의둝 (6 ν…œν”Œλ¦Ώ Γ— 3 강도) 회의 외에도 λ‹€μ–‘ν•œ μš©λ„μ— 맞좰 좜λ ₯ ꡬ쑰λ₯Ό 선택할 수 μžˆμŠ΅λ‹ˆλ‹€. | ν…œν”Œλ¦Ώ | μƒμ„±λ˜λŠ” ꡬ쑰 | κΈ°λ³Έ 강도 | 라이브 μš”μ•½ | |---|---|---|---| | πŸ—‚οΈ 회의둝 | μš”μ•½ / λ…Όμ˜ / μ•‘μ…˜ / κ²°μ • | ν‘œμ€€ | βœ… | | πŸŽ“ κ°•μ˜Β·μ„Έλ―Έλ‚˜ λ…ΈνŠΈ | 핡심 κ°œλ… / μ˜ˆμ‹œ / 인용 / 후속 질문 | 상세 | βœ… | | 🀝 1:1 λ―ΈνŒ… | 주제 / κ³ λ―Ό / ν”Όλ“œλ°± / λ‹€μŒ μ•‘μ…˜ | ν‘œμ€€ | βœ… | | πŸ’‘ λΈŒλ ˆμΈμŠ€ν† λ° | μΉ΄ν…Œκ³ λ¦¬λ³„ 아이디어 / μ¦‰μ‹œ μ‹œλ„ / 보λ₯˜ | 상세 | βœ… | | 🎀 인터뷰 | Q&A 포맷 / 인상적 λ°œμ–Έ / μ’…ν•© | ν‘œμ€€ | ❌ | | πŸ“ 원문 정리 | μš”μ•½ 없이 λ¬Έλ‹¨ν™”Β·μ˜€νƒˆμž μ •λ¦¬λ§Œ | 상세 κ³ μ • | ❌ | | βš™οΈ μ»€μŠ€ν…€ | 자유 ν”„λ‘¬ν”„νŠΈ μž…λ ₯ | - | βœ… | **강도 3단계** - **κ°„κ²°** β€” 각 μ„Ήμ…˜ 3쀄 이내 - **ν‘œμ€€** β€” λ§₯락이 이해될 정도 - **상세** β€” μ„ΈλΆ€μ‚¬ν•­Β·μˆ˜μΉ˜ λˆ„λ½ 없이 보쑴 **μ‚¬μš©** 1. λ³€ν™˜ λͺ¨λ“œ = `Gemini AI μš”μ•½` 선택 2. ν…œν”Œλ¦Ώ 선택 (예: `πŸŽ“ κ°•μ˜Β·μ„Έλ―Έλ‚˜ λ…ΈνŠΈ`) 3. 강도 선택 4. λ…ΉμŒ λ˜λŠ” ν…μŠ€νŠΈ μž…λ ₯ β†’ **회의둝 생성** --- ### ⚑ μ‹€μ‹œκ°„ 둀링 μš”μ•½ λ…ΉμŒ 쀑 **30μ΄ˆλ§ˆλ‹€** Geminiκ°€ κ·Έλ™μ•ˆμ˜ λ°œν™”λ₯Ό 뢄석해 쀑간 νšŒμ˜λ‘μ„ μžλ™ κ°±μ‹ ν•©λ‹ˆλ‹€. **λ™μž‘** - 첫 κ°±μ‹  쑰건: 25단어 이상 - 이후 κ°±μ‹  쑰건: 직전 μš”μ•½ ν›„ 40단어 증가 - ν™”λ©΄ μš°μΈ‘μ— μ§„ν–‰λ₯  λ°” + κ°±μ‹  μ‹œκ° ν‘œμ‹œ - 429 μΏΌν„° 초과 μ‹œ 60초 μžλ™ μΏ¨λ‹€μš΄, UI에 μΉ΄μš΄νŠΈλ‹€μš΄ λ…ΈμΆœ - 일반 μ‹€νŒ¨ μ‹œ μ§€μˆ˜ λ°±μ˜€ν”„ (15s β†’ 30s β†’ μ΅œλŒ€ 120s) **λΉ„μš© κ°€λ“œ (Gemini 2.5 Flash Lite 무료 λ“±κΈ‰ κΈ°μ€€)** - 15 RPM / 1000 RPD / 250K TPM - 30초 폴링 + 증뢄 게이트 β†’ 1μ‹œκ°„ 회의 ≀ 60회 호좜, λ°œν™”λŸ‰ 적으면 훨씬 적음 - 개인 μ‚¬μš© μ‹œ 일일 ν•œλ„ 도달 거의 λΆˆκ°€ --- ### πŸ’Ύ 회의둝 μ €μž₯/쑰회/검색 μƒμ„±ν•œ νšŒμ˜λ‘μ„ PostgreSQL에 μ˜μ† μ €μž₯ν•˜κ³  λ‚˜μ€‘μ— λ‹€μ‹œ 쑰회/νŽΈμ§‘/검색할 수 μžˆμŠ΅λ‹ˆλ‹€. **κΈ°λŠ₯** - **`πŸ“Œ μ €μž₯`** λ²„νŠΌ β†’ DB에 μ €μž₯ β†’ 상세 νŽ˜μ΄μ§€ 링크 제곡 - **`/meetings`** μΉ΄λ“œ κ·Έλ¦¬λ“œ β€” 제λͺ© / λ‚ μ§œ / ν…œν”Œλ¦Ώ λ°°μ§€ / λ―Έμ™„λ£Œ μ•‘μ…˜ 카운트 - **검색** β€” 300ms debounce, 제λͺ©Β·transcriptΒ·λ³Έλ¬Έ λΆ€λΆ„ λ§€μΉ­, URL 동기화 - **`/meetings/[id]`** 상세 β€” Markdown λ Œλ”λ§ + 인라인 νŽΈμ§‘ (쒌 textarea / 우 미리보기) - **μ°Έμ„μž / νƒœκ·Έ** νŽΈμ§‘ (λͺ©λ‘ 필터링은 Phase 3 μ˜ˆμ •) - μ‚­μ œ (확인 λ‹€μ΄μ–Όλ‘œκ·Έ) **API** | λ©”μ„œλ“œ | 경둜 | μš©λ„ | |---|---|---| | `GET` | `/api/meetings` | λͺ©λ‘ + 검색 (`?q=`) + νŽ˜μ΄μ§€λ„€μ΄μ…˜ | | `POST` | `/api/meetings` | μƒˆ 회의둝 μ €μž₯ (μ•‘μ…˜ μ•„μ΄ν…œ μžλ™ μΆ”μΆœ λ™λ°˜) | | `GET` | `/api/meetings/[id]` | 상세 (actionItems 포함) | | `PUT` | `/api/meetings/[id]` | λ³Έλ¬Έ/메타 νŽΈμ§‘ | | `DELETE` | `/api/meetings/[id]` | μ‚­μ œ | --- ### βœ… μ•‘μ…˜ μ•„μ΄ν…œ μžλ™ μΆ”μΆœ 회의둝 μ €μž₯ μ‹œ λ§ˆν¬λ‹€μš΄μ˜ `- [ ]` / `- [x]` ν•­λͺ©μ„ **μžλ™μœΌλ‘œ ꡬ쑰화** μΆ”μΆœν•˜μ—¬ 별도 체크리슀트둜 κ΄€λ¦¬ν•©λ‹ˆλ‹€. **κΈ°λŠ₯** - μ €μž₯ μ‹œ μžλ™ μΆ”μΆœ (Gemini μΆ”κ°€ 호좜 0 β€” νœ΄λ¦¬μŠ€ν‹± νŒŒμ‹±) - 상세 νŽ˜μ΄μ§€μ— **βœ… μ•‘μ…˜ μ•„μ΄ν…œ** μ„Ήμ…˜ - μ²΄ν¬λ°•μŠ€ ν† κΈ€ (낙관적 μ—…λ°μ΄νŠΈ, μ¦‰μ‹œ 반영) - μ§„ν–‰λ₯  λ°°μ§€ (`μ™„λ£Œ/전체`) - κ°œλ³„ μ‚­μ œ (hover βœ•) - **πŸ”„ μž¬μΆ”μΆœ** β€” λ§ˆν¬λ‹€μš΄ νŽΈμ§‘ ν›„ 동기화 (확인 ν›„ ν† κΈ€ μƒνƒœ μ΄ˆκΈ°ν™”) - `/meetings` 상단 μœ„μ ― "λ‚΄ λ―Έμ™„λ£Œ μ•‘μ…˜ (졜근 5건)" - 회의 μΉ΄λ“œμ— λ―Έμ™„λ£Œ 카운트 λ°°μ§€ **API** | λ©”μ„œλ“œ | 경둜 | μš©λ„ | |---|---|---| | `PATCH` | `/api/action-items/[id]` | ν† κΈ€ / task μˆ˜μ • | | `DELETE` | `/api/action-items/[id]` | κ°œλ³„ μ‚­μ œ | | `POST` | `/api/meetings/[id]/reparse-action-items` | λ§ˆν¬λ‹€μš΄ μž¬νŒŒμ‹± | --- ### πŸ”‘ μ›Ήμ—μ„œ API ν‚€ μ„€μ • `.env`λ₯Ό λ§Œμ§€μ§€ μ•Šκ³  `/settings` νŽ˜μ΄μ§€μ—μ„œ Gemini ν‚€λ₯Ό μž…λ ₯Β·μ €μž₯·검증할 수 μžˆμŠ΅λ‹ˆλ‹€. **μ €μž₯ μœ„μΉ˜** - λΈŒλΌμš°μ € LocalStorage (μ„œλ²„ DB에 μ €μž₯λ˜μ§€ μ•ŠμŒ) - μš”μ²­ body둜 ν•¨κ»˜ μ „μ†‘λ˜μ–΄ μΌνšŒμ„±μœΌλ‘œ μ‚¬μš© **μš°μ„ μˆœμœ„** ``` 1. μš”μ²­ body의 apiKey (λΈŒλΌμš°μ € LocalStorage) ↓ μ—†μœΌλ©΄ 2. process.env.GEMINI_API_KEY (μ„œλ²„ ν™˜κ²½λ³€μˆ˜ fallback) ↓ μ—†μœΌλ©΄ 3. summarize β†’ λ‹¨μˆœ λ³€ν™˜ 폴백 (warning ν‘œμ‹œ) summarize-live β†’ 503 + μ•ˆλ‚΄ ``` **κΈ°λŠ₯** - λ§ˆμŠ€ν‚Ήλœ ν˜„μž¬ ν‚€ ν‘œμ‹œ (`AIzaβ€’β€’β€’β€’XYZ12`) - 보이기/숨기기 ν† κΈ€ - **πŸ§ͺ ν…ŒμŠ€νŠΈ 호좜** β€” κ°€λ²Όμš΄ Gemini μ‘λ‹΅μœΌλ‘œ μ¦‰μ‹œ ν‚€ 검증 - ν˜„μž¬ ν‚€ μ†ŒμŠ€ λ°°μ§€ (`λΈŒλΌμš°μ €` / `ν™˜κ²½λ³€μˆ˜` / `μ—†μŒ`) **λ³΄μ•ˆ** - HTTPS ꢌμž₯ (LocalStorageλŠ” 동일 좜처 μ •μ±… 의쑴) - GET 응닡에 μ ˆλŒ€ ν’€ ν‚€ λ…ΈμΆœ μ•ˆ 함 --- ### πŸ“‹ Google Docs ν˜Έν™˜ 볡사 μ„œμ‹(ν—€λ”©/리슀트/ꡡ게/μ½”λ“œ/ν‘œ)을 μœ μ§€ν•œ 채 ν΄λ¦½λ³΄λ“œμ— 볡사 β†’ Google Docs / Word / Notion에 κ·ΈλŒ€λ‘œ λΆ™μ—¬λ„£κΈ°. **원리** - `navigator.clipboard.write([new ClipboardItem({ 'text/html', 'text/plain' })])` - 미지원 λΈŒλΌμš°μ €λŠ” `contenteditable` + `execCommand('copy')` 폴백 **λ²„νŠΌ 라벨링** - `πŸ“‹ .md` β€” λ§ˆν¬λ‹€μš΄ 원문 볡사 - `πŸ“‹ Docs용` β€” μ„œμ‹ μœ μ§€ 볡사 (νŒŒλž‘ κ°•μ‘°) - `⬇ .md` / `⬇ .html` β€” 파일 λ‹€μš΄λ‘œλ“œ --- ## πŸš€ μ‹œμž‘ν•˜κΈ° ### Docker Compose (ꢌμž₯) #### 사전 μš”κ΅¬μ‚¬ν•­ - [Docker Desktop](https://docs.docker.com/get-docker/) λ˜λŠ” Docker Engine + Compose v2 - Chrome λΈŒλΌμš°μ € (Web Speech API) #### 1. μ €μž₯μ†Œ 클둠 ```bash git clone https://github.com/nad4-su/meeting-minutes.git cd meeting-minutes ``` #### 2. ν™˜κ²½ λ³€μˆ˜ (ν•„μˆ˜) ```bash cp .env.example .env ``` `.env` νŒŒμΌμ„ μ—΄μ–΄ λ‹€μŒ 값을 μ±„μ›Œμ£Όμ„Έμš”: ```bash POSTGRES_USER=meetinguser # μž„μ˜ μ‚¬μš©μžλͺ… POSTGRES_DB=meetingminutes POSTGRES_PASSWORD=<κ°•λ ₯ν•œ_μž„μ˜_κ°’> # openssl rand -base64 24 DATABASE_URL=postgresql://meetinguser:<μœ„μ™€_같은_λΉ„λ²ˆ>@localhost:5432/meetingminutes ``` > ⚠️ `POSTGRES_PASSWORD`λ₯Ό λΉ„μ›Œλ‘λ©΄ docker composeκ°€ λͺ…μ‹œμ  μ—λŸ¬λ‘œ μ‹€νŒ¨ν•©λ‹ˆλ‹€. λ³΄μ•ˆμ„ μœ„ν•œ μ˜λ„λœ λ™μž‘. > πŸ’‘ **Gemini API ν‚€λŠ” 두 κ°€μ§€ 방법 쀑 선택** > > - **(A) μ›Ή UI** β€” μ•± 기동 ν›„ `/settings`μ—μ„œ μž…λ ₯ (λΈŒλΌμš°μ € LocalStorage, μΆ”μ²œ) > - **(B) `.env` ν™˜κ²½λ³€μˆ˜** β€” `GEMINI_API_KEY=AIza...` μž‘μ„± > > [Google AI Studio](https://aistudio.google.com/apikey)μ—μ„œ 무료둜 λ°œκΈ‰. ν‚€ 없이도 "λ‹¨μˆœ λ³€ν™˜" λͺ¨λ“œλŠ” λ™μž‘. #### 3. DB λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ (졜초 1회) ```bash docker compose --profile tools run --rm migrate ``` `db` μ»¨ν…Œμ΄λ„ˆλ₯Ό μžλ™ 기동 ν›„ `prisma migrate deploy`둜 ν…Œμ΄λΈ” 생성. #### 4. μ•± 기동 ```bash docker compose up -d ``` λΈŒλΌμš°μ €μ—μ„œ [http://localhost:3000](http://localhost:3000) 접속. #### μ„œλΉ„μŠ€ 관리 ```bash docker compose logs -f app # 둜그 docker compose restart app # μž¬μ‹œμž‘ (데이터 μœ μ§€) docker compose down # 쀑지 docker compose down -v # 쀑지 + λ³Όλ₯¨ μ‚­μ œ (데이터 μ΄ˆκΈ°ν™”) docker compose up -d --build # μ½”λ“œ λ³€κ²½ ν›„ μž¬λΉŒλ“œ ``` #### 데이터 λ°±μ—… ```bash docker compose exec db pg_dump -U meetinguser meetingminutes > backup-$(date +%Y%m%d).sql ``` #### ν…ŒμŠ€νŠΈ ```bash docker compose --profile tools run --rm test ``` 별도 Node μ„€μΉ˜ λΆˆν•„μš”. `tools` ν”„λ‘œν•„μ΄ vitestλ₯Ό μ»¨ν…Œμ΄λ„ˆμ—μ„œ μ‹€ν–‰ (102 tests). --- ### 둜컬 개발 ν™˜κ²½ ```bash npm install # 별도 PostgreSQL ν•„μš” docker compose up -d db npx prisma migrate dev # 개발 μ„œλ²„ npm run dev # http://localhost:3000 npm test # 전체 ν…ŒμŠ€νŠΈ npm run test:watch # μ›ŒμΉ˜ λͺ¨λ“œ npm run test:coverage # 컀버리지 리포트 ``` --- ## πŸ› οΈ 기술 μŠ€νƒ | ꡬ뢄 | 기술 | |------|------| | ν”„λ ˆμž„μ›Œν¬ | Next.js 16 (App Router) + React 19 + TypeScript | | STT (μ‹€μ‹œκ°„) | Web Speech API β€” Chrome λ‚΄μž₯, 무료 | | AI μš”μ•½ | Gemini 2.5 Flash Lite (무료 λ“±κΈ‰ 15 RPM / 1000 RPD) | | DB | PostgreSQL 16 + Prisma 7 (driver adapter `@prisma/adapter-pg`) | | Markdown | `marked` + `isomorphic-dompurify` | | μŠ€νƒ€μΌ | Tailwind CSS v4 | | ν…ŒμŠ€νŠΈ | Vitest 4 (jsdom, 102 tests) | | 배포 | Docker Compose + standalone Next.js λΉŒλ“œ | --- ## πŸ“ ν”„λ‘œμ νŠΈ ꡬ쑰 ``` src/ β”œβ”€β”€ app/ β”‚ β”œβ”€β”€ api/ β”‚ β”‚ β”œβ”€β”€ upload/route.ts # 파일 μ—…λ‘œλ“œ β”‚ β”‚ β”œβ”€β”€ summarize/route.ts # μ΅œμ’… 회의둝 (Gemini + simple fallback) β”‚ β”‚ β”œβ”€β”€ summarize-live/route.ts # μ‹€μ‹œκ°„ 쀑간 μš”μ•½ β”‚ β”‚ β”œβ”€β”€ meetings/ β”‚ β”‚ β”‚ β”œβ”€β”€ route.ts # GET λͺ©λ‘ / POST μ €μž₯ β”‚ β”‚ β”‚ └── [id]/ β”‚ β”‚ β”‚ β”œβ”€β”€ route.ts # GET / PUT / DELETE β”‚ β”‚ β”‚ └── reparse-action-items/route.ts # λ§ˆν¬λ‹€μš΄ μž¬νŒŒμ‹± β”‚ β”‚ β”œβ”€β”€ action-items/ β”‚ β”‚ β”‚ └── [id]/route.ts # PATCH / DELETE β”‚ β”‚ └── settings/ β”‚ β”‚ β”œβ”€β”€ status/route.ts # ν™˜κ²½λ³€μˆ˜ ν‚€ 쑴재 μ—¬λΆ€ β”‚ β”‚ └── test/route.ts # ν‚€ μœ νš¨μ„± 검증 β”‚ β”œβ”€β”€ meetings/ β”‚ β”‚ β”œβ”€β”€ page.tsx # λͺ©λ‘ + 검색 + λ―Έμ™„λ£Œ μœ„μ ― β”‚ β”‚ └── [id]/page.tsx # 상세 + νŽΈμ§‘ β”‚ β”œβ”€β”€ settings/page.tsx # ν‚€ μ„€μ • β”‚ β”œβ”€β”€ page.tsx # 메인 (λ…ΉμŒ/μš”μ•½) β”‚ └── layout.tsx β”œβ”€β”€ lib/ β”‚ β”œβ”€β”€ db.ts # Prisma 싱글톀 (pg adapter) β”‚ β”œβ”€β”€ markdown.ts # marked + DOMPurify + Docs용 볡사 β”‚ β”œβ”€β”€ action-items.ts # - [ ] νœ΄λ¦¬μŠ€ν‹± νŒŒμ„œ β”‚ β”œβ”€β”€ api-keys.ts # μ„œλ²„: μš”μ²­ ν‚€ β†’ env fallback β”‚ β”œβ”€β”€ api-key-storage.ts # 클라: LocalStorage + λ§ˆμŠ€ν‚Ή β”‚ β”œβ”€β”€ templates.ts # ν…œν”Œλ¦Ώ λ ˆμ§€μŠ€νŠΈλ¦¬ + ν”„λ‘¬ν”„νŠΈ λΉŒλ” β”‚ β”œβ”€β”€ minutes-generator.ts # μ΅œμ’… μš”μ•½ 생성 β”‚ β”œβ”€β”€ live-summary.ts # 둀링 μš”μ•½ 생성 β”‚ β”œβ”€β”€ transcript-formatter.ts β”‚ β”œβ”€β”€ audio-validation.ts β”‚ β”œβ”€β”€ upload-handler.ts β”‚ └── export-minutes.ts β”œβ”€β”€ hooks/ β”‚ β”œβ”€β”€ useSpeechRecognition.ts # Web Speech + λ„€νŠΈμ›Œν¬ μž¬μ‹œλ„ β”‚ └── useLiveSummary.ts # 30s 폴링 + 429 μΏ¨λ‹€μš΄ β”œβ”€β”€ components/ β”‚ β”œβ”€β”€ upload/AudioUploader.tsx β”‚ β”œβ”€β”€ recorder/LiveRecorder.tsx # 쒌우 λΆ„ν•  λ·° β”‚ β”œβ”€β”€ minutes/MinutesViewer.tsx # 미리보기 / 원문 / μ €μž₯ β”‚ └── meeting/ β”‚ β”œβ”€β”€ MeetingCard.tsx β”‚ β”œβ”€β”€ MeetingSearchBar.tsx β”‚ β”œβ”€β”€ MeetingDetail.tsx β”‚ └── ActionItemList.tsx └── __tests__/ # 102 tests / 13 files docs/ β”œβ”€β”€ ROADMAP.md # 단계별 κ³„νš β”œβ”€β”€ CREATE_ISSUES.sh # gh CLI용 epic 이슈 생성 └── PR_DRAFT_*.md # PR λ³Έλ¬Έ νžˆμŠ€ν† λ¦¬ prisma/ β”œβ”€β”€ schema.prisma # Meeting + ActionItem └── migrations/ # 2개 (init, add_action_items) ``` --- ## πŸ—ΊοΈ λ‘œλ“œλ§΅ μƒμ„ΈλŠ” [`docs/ROADMAP.md`](docs/ROADMAP.md) λ˜λŠ” [GitHub Milestones](https://github.com/nad4-su/meeting-minutes/milestones). | Phase | λ‚΄μš© | μƒνƒœ | |---|---|---| | **0** | μ‹€μ‹œκ°„ 전사 + Gemini 둀링 μš”μ•½ + μžλ™ μ΅œμ’… | βœ… μ™„λ£Œ | | **0.5** | 6 ν…œν”Œλ¦Ώ + 3 강도 + μ»€μŠ€ν…€ ν”„λ‘¬ν”„νŠΈ | βœ… μ™„λ£Œ | | **1** | 회의 μ €μž₯/쑰회/검색 μ›Œν¬μŠ€νŽ˜μ΄μŠ€ | βœ… μ™„λ£Œ | | **1.5** | μ›Ήμ—μ„œ Gemini API ν‚€ μ„€μ • (LocalStorage) | βœ… μ™„λ£Œ | | **2** | μ•‘μ…˜ μ•„μ΄ν…œ μΆ”μΆœ/체크리슀트 + Google Docs 볡사 | βœ… μ™„λ£Œ | | **3** | μ°Έμ„μž + νƒœκ·Έ μ‹œμŠ€ν…œ (λͺ©λ‘ ν•„ν„°) | πŸ“‹ [`#7`](https://github.com/nad4-su/meeting-minutes/issues/7) | | **v0.1.0** | MVP + Phase 3 + ν΄λ¦¬μ‹œ | 🚧 [`#6`](https://github.com/nad4-su/meeting-minutes/issues/6) | | **4** | μΊ˜λ¦°λ” / AI Q&A / 후속 메일 / 블둝 에디터 | πŸ’‘ [`#8`](https://github.com/nad4-su/meeting-minutes/issues/8) | ### μ˜λ„λœ λΉ„λͺ©ν‘œ (Non-goals) - **ν™”μž ꡬ뢄** β€” 유료 API λΉ„μš© / λ³΅μž‘λ„ λŒ€λΉ„ 개인용 λ²”μœ„μ— 과함 - **곡동 νŽΈμ§‘ (CRDT)** β€” 단일 μ‚¬μš©μž κ°€μ • - **κΆŒν•œ 관리 / 닀쀑 μ‚¬μš©μž** β€” 1인 1μΈμŠ€ν„΄μŠ€ λͺ¨λΈ - **λͺ¨λ°”일 λ„€μ΄ν‹°λΈŒ μ•±** β€” μ›Ή PWA둜 μΆ©λΆ„ --- ## πŸ”’ λ³΄μ•ˆ λͺ¨λΈ > 이 ν”„λ‘œμ νŠΈλŠ” **개인용 단일 μΈμŠ€ν„΄μŠ€ μ‚¬μš©μ„ κ°€μ •**ν•©λ‹ˆλ‹€. νšŒμ‚¬ 직원이 μ‚¬μš©ν•  경우 각자 본인 λ¨Έμ‹ μ—μ„œ 별도 μΈμŠ€ν„΄μŠ€λ₯Ό μ‹€ν–‰ν•˜λŠ” 방식을 ꢌμž₯ν•©λ‹ˆλ‹€. ### μ‹ λ’° λͺ¨λΈ - 인증 μ‹œμŠ€ν…œ μ—†μŒ β€” `localhost:3000`에 μ ‘κ·Όν•˜λŠ” μ‚¬μš©μžλŠ” λͺ¨λ“  데이터에 μ ‘κ·Ό κ°€λŠ₯ - 같은 머신을 λ‹€λ₯Έ μ‚¬λžŒκ³Ό κ³΅μœ ν•˜μ§€ μ•ŠλŠ” 것을 κ°€μ • - 회의 transcript / νšŒμ˜λ‘μ€ ν‰λ¬ΈμœΌλ‘œ PostgreSQL에 μ €μž₯됨 (λ””μŠ€ν¬ μ•”ν˜Έν™”λŠ” 호슀트 OS에 μœ„μž„) ### Gemini API ν‚€ - LocalStorage μ €μž₯ (λΈŒλΌμš°μ € 동일 좜처 μ •μ±…μœΌλ‘œ 보호) - μ„œλ²„ DB에 μ €μž₯λ˜μ§€ μ•ŠμŒ - μš”μ²­ μ‹œμ μ—λ§Œ body둜 전솑, μΌνšŒμ„± μ‚¬μš© - HTTPS ν™˜κ²½ ꢌμž₯ (HTTPλŠ” localhost ν•œμ •) ### μ™ΈλΆ€ 데이터 μ†‘μΆœ - **Web Speech API** β€” Chrome이 마이크 μ˜€λ””μ˜€λ₯Ό Google μ„œλ²„λ‘œ μ „μ†‘ν•˜μ—¬ 전사 (Chrome 자체 λ™μž‘, 우리 μ„œλ²„ 경유 X) - **Gemini API** β€” μ‚¬μš©μžκ°€ λͺ…μ‹œμ μœΌλ‘œ ν™œμ„±ν™”ν•œ κ²½μš°μ—λ§Œ transcriptλ₯Ό Google μ„œλ²„λ‘œ 전솑 - **κ·Έ μ™Έ** β€” μ™ΈλΆ€ 호좜 μ—†μŒ (ν…”λ ˆλ©”νŠΈλ¦¬ / 뢄석 도ꡬ λ―Έμ„€μΉ˜) > ⚠️ **νšŒμ‚¬ 회의 λ“± 민감 정보가 μ™ΈλΆ€ ν΄λΌμš°λ“œ(Google)둜 μ†‘μΆœλ˜λŠ” 점에 유의.** 사내 μ»΄ν”ŒλΌμ΄μ–ΈμŠ€ μ •μ±… 확인 ν›„ μ‚¬μš© ꢌμž₯. ### ꢌμž₯ 배포 방식 **1인 1μΈμŠ€ν„΄μŠ€ (ꢌμž₯)** - 직원 각자 본인 머신에 `docker compose up` - λ°μ΄ν„°λŠ” 각 머신에 격리됨 - ν‚€ / 회의둝 / μ•‘μ…˜ μ•„μ΄ν…œ λͺ¨λ‘ 본인만 μ ‘κ·Ό **쀑앙 μ„œλ²„ λ°°ν¬λŠ” ꢌμž₯ν•˜μ§€ μ•ŠμŒ** β€” 인증 / κΆŒν•œ / 격리 λ―Έκ΅¬ν˜„ ### λ³΄μ•ˆ 체크리슀트 (배포 μ „) - [ ] `.env`에 κ°•λ ₯ν•œ `POSTGRES_PASSWORD` μ„€μ • - [ ] `docker-compose.yml`의 Postgres 포트 λ…ΈμΆœ (`5432:5432`) μ‚¬μš© ν™˜κ²½μ— 맞게 κ²€ν†  (μ™ΈλΆ€ λ…ΈμΆœ λΆˆν•„μš” μ‹œ 제거) - [ ] HTTPS reverse proxy μΆ”κ°€ (사외 접속 μ‹œ) - [ ] λ°©ν™”λ²½ / VPN λ’€μ—μ„œλ§Œ μ ‘κ·Ό κ°€λŠ₯ν•˜λ„λ‘ ꡬ성 - [ ] μ •κΈ° λ°±μ—… (`pg_dump`) μžμ„Έν•œ λ³΄μ•ˆ κ²€ν†  κ²°κ³ΌλŠ” [`SECURITY.md`](SECURITY.md) μ°Έκ³ . --- ## ⚠️ μ•Œλ €μ§„ μ œμ•½ | μ œμ•½ | μ„€λͺ… | λŒ€μ‘ | |---|---|---| | **Chrome μ „μš©** | Web Speech APIλŠ” λΉ„ν‘œμ€€ β€” Safari / FirefoxλŠ” μ œν•œμ  | Phase 4μ—μ„œ μ„œλ²„ μ‚¬μ΄λ“œ 전사 κ²€ν†  | | **ν™”μž ꡬ뢄 μ—†μŒ** | μ˜λ„μ  비지원 (Non-goal) | β€” | | **Gemini 무료 λ“±κΈ‰ ν•œλ„** | 15 RPM / 1000 RPD | ν•œλ„ 초과 μ‹œ μžλ™ μΏ¨λ‹€μš΄, λ‹¨μˆœ λ³€ν™˜ 폴백 | | **단일 μ‚¬μš©μž** | 인증 μ—†μŒ, 데이터 격리 μ—†μŒ | 1인 1μΈμŠ€ν„΄μŠ€λ‘œ 운용 | | **λ§ˆν¬λ‹€μš΄ 검색** | PostgreSQL `ILIKE` (수천 건 μ΄μƒμ—μ„œ 느렀질 수 있음) | ν•„μš” μ‹œ `tsvector` 인덱슀 μΆ”κ°€ | | **λͺ¨λ°”일 UX** | λ°μŠ€ν¬ν†± μš°μ„  | κΈ°λ³Έ λ™μž‘μ€ κ°€λŠ₯, ν΄λ¦¬μ‹œ 미흑 | --- ## 🀝 κΈ°μ—¬ν•˜κΈ° 이슈 / PR ν™˜μ˜ν•©λ‹ˆλ‹€. κΈ°μ—¬ μ „ λ‹€μŒμ„ ν™•μΈν•΄μ£Όμ„Έμš”: 1. [`docs/ROADMAP.md`](docs/ROADMAP.md)와 [Issues](https://github.com/nad4-su/meeting-minutes/issues)μ—μ„œ 쀑볡 / μ§„ν–‰ 쀑 ν•­λͺ© 확인 2. **ν…ŒμŠ€νŠΈ 톡과 ν•„μˆ˜** β€” `docker compose --profile tools run --rm test` 3. **λΉŒλ“œ 톡과 ν•„μˆ˜** β€” `docker compose build app` 4. κΈ°λŠ₯ μΆ”κ°€ μ‹œ λ‹¨μœ„ ν…ŒμŠ€νŠΈ λ™λ°˜ ꢌμž₯ ### 개발 κ°€μ΄λ“œ ```bash # λ³€κ²½ ν›„ 검증 docker compose --profile tools run --rm test docker compose build app # DB μŠ€ν‚€λ§ˆ λ³€κ²½ μ‹œ # 1. prisma/schema.prisma νŽΈμ§‘ # 2. λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 생성 docker compose --profile tools run --rm \ --entrypoint "" migrate \ sh -c "npm ci && npx prisma migrate dev --name <λ³€κ²½_이름>" ``` ### 컀밋 μ»¨λ²€μ…˜ - `feat:` μƒˆ κΈ°λŠ₯ - `fix:` 버그 μˆ˜μ • - `refactor:` λ¦¬νŒ©ν„°λ§ - `docs:` λ¬Έμ„œ - `test:` ν…ŒμŠ€νŠΈ - `chore:` λΉŒλ“œ/μ„€μ • --- ## πŸ“„ λΌμ΄μ„ μŠ€ [MIT License](LICENSE) Β© 2026 nad4-su