# ๐ŸŽ™๏ธ 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-๊ฐ•๋„) - [โšก ์‹ค์‹œ๊ฐ„ ๋กค๋ง ์š”์•ฝ](#-์‹ค์‹œ๊ฐ„-๋กค๋ง-์š”์•ฝ) - [๐Ÿ’พ ํšŒ์˜๋ก ์ €์žฅ/์กฐํšŒ/๊ฒ€์ƒ‰](#-ํšŒ์˜๋ก-์ €์žฅ์กฐํšŒ๊ฒ€์ƒ‰) - [โœ… ์•ก์…˜ ์•„์ดํ…œ ์ž๋™ ์ถ”์ถœ](#-์•ก์…˜-์•„์ดํ…œ-์ž๋™-์ถ”์ถœ) - [๐Ÿ”‘ AI ํ”„๋กœ๋ฐ”์ด๋” & API ํ‚ค ์„ค์ •](#-ai-ํ”„๋กœ๋ฐ”์ด๋”--api-ํ‚ค-์„ค์ •) - [๐Ÿ“‹ Google Docs ํ˜ธํ™˜ ๋ณต์‚ฌ](#-google-docs-ํ˜ธํ™˜-๋ณต์‚ฌ) - [๐Ÿš€ ์‹œ์ž‘ํ•˜๊ธฐ](#-์‹œ์ž‘ํ•˜๊ธฐ) - [Docker Compose (๊ถŒ์žฅ)](#docker-compose-๊ถŒ์žฅ) - [๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ](#๋กœ์ปฌ-๊ฐœ๋ฐœ-ํ™˜๊ฒฝ) - [๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ](#๏ธ-๊ธฐ์ˆ -์Šคํƒ) - [๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ](#-ํ”„๋กœ์ ํŠธ-๊ตฌ์กฐ) - [๐Ÿ—บ๏ธ ๋กœ๋“œ๋งต](#๏ธ-๋กœ๋“œ๋งต) - [๐Ÿ”’ ๋ณด์•ˆ ๋ชจ๋ธ](#-๋ณด์•ˆ-๋ชจ๋ธ) - [โš ๏ธ ์•Œ๋ ค์ง„ ์ œ์•ฝ](#๏ธ-์•Œ๋ ค์ง„-์ œ์•ฝ) - [๐Ÿค ๊ธฐ์—ฌํ•˜๊ธฐ](#-๊ธฐ์—ฌํ•˜๊ธฐ) - [๐Ÿ“„ ๋ผ์ด์„ ์Šค](#-๋ผ์ด์„ ์Šค) --- ## โœจ ์ฃผ์š” ๊ธฐ๋Šฅ ### ๐ŸŽค ์‹ค์‹œ๊ฐ„ ๋…น์Œ & ์ „์‚ฌ ๋ธŒ๋ผ์šฐ์ € ๋‚ด์žฅ Web Speech API(Chrome `ko-KR`)๋กœ ๋งˆ์ดํฌ ์ž…๋ ฅ์„ ์‹ค์‹œ๊ฐ„ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. **ํŠน์ง•** - ์ฆ‰์‹œ ์‹œ์ž‘ โ€” ๋ณ„๋„ STT ์„œ๋ฒ„๋‚˜ ํ‚ค ์—†์ด ์ž‘๋™ - **์˜ค๋””์˜ค ์›๋ณธ ๋™์‹œ ๋…น์Œ** โ€” ์ „์‚ฌ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ํšŒ์˜ ์›์Œ์„ ํŒŒ์ผ๋กœ ๋ณด๊ด€ - **์ž๋™ ์žฌ์—ฐ๊ฒฐ** โ€” ๋„คํŠธ์›Œํฌ ๋Š๊น€ ์‹œ ์ตœ๋Œ€ 8ํšŒ ์žฌ์‹œ๋„, ๋ˆ„์  transcript ๋ณด์กด - ํ™•์ • ์ „ ํ…์ŠคํŠธ๋Š” ํšŒ์ƒ‰ ์ดํƒค๋ฆญ + ๊นœ๋นก์ด๋Š” ์ปค์„œ๋กœ ์‹œ๊ฐํ™” - ๋…น์Œ ์ค‘ `๐Ÿ”ด N๋‹จ์–ด ยท M๊ฐœ ๊ตฌ๊ฐ„` ์‹ค์‹œ๊ฐ„ ์นด์šดํ„ฐ > โš ๏ธ **Web Speech API๋Š” ๋ฐœํ™”๋ฅผ ์ƒ๋‹น๋Ÿ‰ ๋†“์นฉ๋‹ˆ๋‹ค.** ์›๊ฑฐ๋ฆฌยท๋‹ค์ธ ๋Œ€๋ฉด ํšŒ์˜์—์„œ > ์‹ค์ธก ํฌ์ฐฉ๋ฅ ์ด 10% ์•ˆํŒŽ์ด์—ˆ์Šต๋‹ˆ๋‹ค(46๋ถ„ ํšŒ์˜ / 489์–ด์ ˆ). ํšŒ์˜ ์ „์‚ฌ์šฉ์œผ๋กœ > ์„ค๊ณ„๋œ ์—”์ง„์ด ์•„๋‹ˆ๋ผ ๊ทผ์ ‘ ์Œ์„ฑ ๋ช…๋ น์šฉ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ **์˜ค๋””์˜ค ์›๋ณธ์„ ๋ฐ˜๋“œ์‹œ > ํ•จ๊ป˜ ๋‚จ๊น๋‹ˆ๋‹ค** โ€” ๋†“์นœ ๋ฐœํ™”๋Š” ๋‚˜์ค‘์— ์„œ๋ฒ„ STT๋กœ ๋‹ค์‹œ ์‚ด๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ### ๐ŸŽง ์˜ค๋””์˜ค ์›๋ณธ ๋ณด๊ด€ ๋…น์Œ์„ ์‹œ์ž‘ํ•˜๋ฉด ์ „์‚ฌ์™€ **๋ณ„๊ฐœ๋กœ** ๋งˆ์ดํฌ ์ž…๋ ฅ์„ ์˜ค๋””์˜ค ํŒŒ์ผ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. - 15์ดˆ๋งˆ๋‹ค ์กฐ๊ฐ์„ ์„œ๋ฒ„์— ์ด์–ด ๋ถ™์—ฌ **ํƒญ์ด๋‚˜ ์„œ๋ฒ„๊ฐ€ ์ฃฝ์–ด๋„ ๊ทธ๋•Œ๊นŒ์ง€์˜ ์˜ค๋””์˜ค๋Š” ๋‚จ์Šต๋‹ˆ๋‹ค** - ์„œ๋ฒ„ ์ €์žฅ์ด ์‹คํŒจํ•ด๋„ ๋…น์Œ์€ ์ค‘๋‹จ๋˜์ง€ ์•Š๊ณ , ๋ธŒ๋ผ์šฐ์ € ์‚ฌ๋ณธ์„ **โฌ‡๏ธ ๋‚ด๋ ค๋ฐ›๊ธฐ**๋กœ ํšŒ์ˆ˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค - ํ•œ ์กฐ๊ฐ๋„ ๋ชป ๋ฐ›์•˜์œผ๋ฉด "๋ณด๊ด€๋จ"์ด๋ผ๊ณ  ํ•˜์ง€ ์•Š๊ณ  ์˜ค๋ฅ˜๋ฅผ ๋„์›๋‹ˆ๋‹ค - ์ €์žฅ ์œ„์น˜: `uploads/recordings/` (Docker์—์„œ๋Š” `uploads` ๋ณผ๋ฅจ โ†’ ์žฌ์‹œ์ž‘ํ•ด๋„ ์œ ์ง€) **์บก์ฒ˜ ์„ค์ •** โ€” ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ๊ฐ’์€ ์ „ํ™” ํ†ตํ™”์šฉ ํŠœ๋‹์ด๋ผ ๋ฉ€๋ฆฌ ์•‰์€ ํ™”์ž๋ฅผ ๋…ธ์ด์ฆˆ๋กœ ์ง€์›Œ๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋…ธ์ด์ฆˆ ์–ต์ œยท์—์ฝ” ์ œ๊ฑฐ๋ฅผ ๋„๊ณ  AGC๋งŒ ๋‚จ๊น๋‹ˆ๋‹ค (`src/lib/recording.ts`์˜ `RECORDING_AUDIO_CONSTRAINTS`). **์‚ฌ์šฉ** 1. ํ™ˆ์—์„œ **๐ŸŽค ๋…น์Œ ์‹œ์ž‘** ํด๋ฆญ 2. ๋งˆ์ดํฌ ๊ถŒํ•œ ํ—ˆ์šฉ 3. ๋ฐœํ™” โ†’ ์ขŒ์ธก ํŒจ๋„์— ํ…์ŠคํŠธ๊ฐ€ ์Œ“์ž„ 4. **๋…น์Œ ์ค‘์ง€** โ†’ ํ…์ŠคํŠธ๊ฐ€ textarea๋กœ ์ด๋™, ์ง์ ‘ ํŽธ์ง‘ ๊ฐ€๋Šฅ --- ### ๐Ÿค– AI ํšŒ์˜๋ก (6 ํ…œํ”Œ๋ฆฟ ร— 3 ๊ฐ•๋„) ํšŒ์˜ ์™ธ์—๋„ ๋‹ค์–‘ํ•œ ์šฉ๋„์— ๋งž์ถฐ ์ถœ๋ ฅ ๊ตฌ์กฐ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. | ํ…œํ”Œ๋ฆฟ | ์ƒ์„ฑ๋˜๋Š” ๊ตฌ์กฐ | ๊ธฐ๋ณธ ๊ฐ•๋„ | ๋ผ์ด๋ธŒ ์š”์•ฝ | |---|---|---|---| | ๐Ÿ—‚๏ธ ํšŒ์˜๋ก | ์š”์•ฝ / **๋‚ด์šฉ ๊ธฐ๋ฐ˜ ์ฃผ์ œ ์„น์…˜** / ์•ก์…˜ / ๊ฒฐ์ • | ํ‘œ์ค€ | โœ… | | ๐ŸŽ“ ๊ฐ•์˜ยท์„ธ๋ฏธ๋‚˜ ๋…ธํŠธ | ํ•ต์‹ฌ ๊ฐœ๋… / ์˜ˆ์‹œ / ์ธ์šฉ / ํ›„์† ์งˆ๋ฌธ | ์ƒ์„ธ | โœ… | | ๐Ÿค 1:1 ๋ฏธํŒ… | ์š”์ง€ / **๋‚ด์šฉ ๊ธฐ๋ฐ˜ ์ฃผ์ œ ์„น์…˜** / ๊ณ ๋ฏผ / ํ”ผ๋“œ๋ฐฑ / ๋‹ค์Œ ์•ก์…˜ | ํ‘œ์ค€ | โœ… | | ๐Ÿ’ก ๋ธŒ๋ ˆ์ธ์Šคํ† ๋ฐ | ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์•„์ด๋””์–ด / ์ฆ‰์‹œ ์‹œ๋„ / ๋ณด๋ฅ˜ | ์ƒ์„ธ | โœ… | | ๐ŸŽค ์ธํ„ฐ๋ทฐ | Q&A ํฌ๋งท / ์ธ์ƒ์  ๋ฐœ์–ธ / ์ข…ํ•ฉ | ํ‘œ์ค€ | โŒ | | ๐Ÿ“ ์›๋ฌธ ์ •๋ฆฌ | ์š”์•ฝ ์—†์ด ๋ฌธ๋‹จํ™”ยท์˜คํƒˆ์ž ์ •๋ฆฌ๋งŒ | ์ƒ์„ธ ๊ณ ์ • | โŒ | | โš™๏ธ ์ปค์Šคํ…€ | ์ž์œ  ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ | - | โœ… | **๋‚ด์šฉ ๊ธฐ๋ฐ˜ ์ฃผ์ œ ์„น์…˜** ํšŒ์˜๋กยท1:1 ํ…œํ”Œ๋ฆฟ์€ `## ์ฃผ์š” ๋…ผ์˜ ์‚ฌํ•ญ` ๊ฐ™์€ ๊ณ ์ • ์ œ๋ชฉ์„ ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ ํšŒ์˜๊ฐ€ ์‹ค์ œ๋กœ ๋ฌด์—‡์„ ๋‹ค๋ค˜๋Š”์ง€์— ๋”ฐ๋ผ AI๊ฐ€ ์„น์…˜ ์ œ๋ชฉ์„ ์ง์ ‘ ์ง“์Šต๋‹ˆ๋‹ค โ€” ์˜ˆ๋ฅผ ๋“ค์–ด `## ๋‹จ๊ณ„๋ณ„ ๊ฐœ๋ฐœ ๊ณ„ํš`, `## ์ˆ˜์ตํ™” ๋ฐฉ์•ˆ`, `## ๊ธฐ์ˆ ์  ๊ณ ๋ ค์‚ฌํ•ญ` ์ฒ˜๋Ÿผ. ์ œ๋ชฉ๋งŒ ํ›‘์–ด๋„ ํšŒ์˜ ๋‚ด์šฉ์ด ํŒŒ์•…๋ฉ๋‹ˆ๋‹ค. **์•ก์…˜ ์•„์ดํ…œ ๋‹ด๋‹น์ž** ๋ฐœํ™”์—์„œ ๋‹ด๋‹น์ž๊ฐ€ ํŒŒ์•…๋˜๋ฉด `- [ ] (๊น€์ง€ํ›ˆ) ๊ฒฝ์Ÿ ์‚ฌ์ดํŠธ ๋ฆฌ์ŠคํŠธ์—… โ€” 5/17๊นŒ์ง€` ํ˜•ํƒœ๋กœ ์ด๋ฆ„๊ณผ ๊ธฐํ•œ์ด ๋ถ™์Šต๋‹ˆ๋‹ค. ๋‹ด๋‹น์ž๊ฐ€ ์ •ํ•ด์ง€์ง€ ์•Š์€ ํ•ญ๋ชฉ๋„ ๋ฒ„๋ฆฌ์ง€ ์•Š๊ณ  ๊ทธ๋Œ€๋กœ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค. **๊ฐ•๋„ 3๋‹จ๊ณ„** - **๊ฐ„๊ฒฐ** โ€” ๊ฐ ์„น์…˜ 3์ค„ ์ด๋‚ด - **ํ‘œ์ค€** โ€” ๋งฅ๋ฝ์ด ์ดํ•ด๋  ์ •๋„ - **์ƒ์„ธ** โ€” ์„ธ๋ถ€์‚ฌํ•ญยท์ˆ˜์น˜ ๋ˆ„๋ฝ ์—†์ด ๋ณด์กด **์‚ฌ์šฉ** 1. ๋ณ€ํ™˜ ๋ชจ๋“œ = `Gemini AI ์š”์•ฝ` ์„ ํƒ 2. ํ…œํ”Œ๋ฆฟ ์„ ํƒ (์˜ˆ: `๐ŸŽ“ ๊ฐ•์˜ยท์„ธ๋ฏธ๋‚˜ ๋…ธํŠธ`) 3. ๊ฐ•๋„ ์„ ํƒ 4. ๋…น์Œ ๋˜๋Š” ํ…์ŠคํŠธ ์ž…๋ ฅ โ†’ **ํšŒ์˜๋ก ์ƒ์„ฑ** --- ### โšก ์‹ค์‹œ๊ฐ„ ๋กค๋ง ์š”์•ฝ ๋…น์Œ ์ค‘ **30์ดˆ๋งˆ๋‹ค** Gemini๊ฐ€ ๊ทธ๋™์•ˆ์˜ ๋ฐœํ™”๋ฅผ ๋ถ„์„ํ•ด ์ค‘๊ฐ„ ํšŒ์˜๋ก์„ ์ž๋™ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. **๋™์ž‘** - ์ฒซ ๊ฐฑ์‹  ์กฐ๊ฑด: 25๋‹จ์–ด ์ด์ƒ - ์ดํ›„ ๊ฐฑ์‹  ์กฐ๊ฑด: ์ง์ „ ์š”์•ฝ ํ›„ 40๋‹จ์–ด ์ฆ๊ฐ€ - ํ™”๋ฉด ์šฐ์ธก์— ์ง„ํ–‰๋ฅ  ๋ฐ” + ๊ฐฑ์‹  ์‹œ๊ฐ ํ‘œ์‹œ - 429 ์ฟผํ„ฐ ์ดˆ๊ณผ ์‹œ 60์ดˆ ์ž๋™ ์ฟจ๋‹ค์šด, UI์— ์นด์šดํŠธ๋‹ค์šด ๋…ธ์ถœ - ์ผ๋ฐ˜ ์‹คํŒจ ์‹œ ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„ (15s โ†’ 30s โ†’ ์ตœ๋Œ€ 120s) **๋น„์šฉ ๊ฐ€๋“œ (Gemini 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` | ๋งˆํฌ๋‹ค์šด ์žฌํŒŒ์‹ฑ | --- ### ๐Ÿ”‘ AI ํ”„๋กœ๋ฐ”์ด๋” & API ํ‚ค ์„ค์ • `.env`๋ฅผ ๋งŒ์ง€์ง€ ์•Š๊ณ  `/settings` ํŽ˜์ด์ง€์—์„œ ํ”„๋กœ๋ฐ”์ด๋”ยท๋ชจ๋ธยทํ‚ค๋ฅผ ์„ ํƒํ•˜๊ณ  ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. **์„ ํƒ ๊ฐ€๋Šฅํ•œ ํ”„๋กœ๋ฐ”์ด๋”** | ํ”„๋ฆฌ์…‹ | ์—”๋“œํฌ์ธํŠธ | ๋น„๊ณ  | |--------|-----------|------| | **Google Gemini** (๊ธฐ๋ณธ) | Google ์ง์ ‘ ํ˜ธ์ถœ | ๋ฌด๋ฃŒ ํ‹ฐ์–ด๊ฐ€ ์žˆ์–ด ๊ฐ€์žฅ ๊ฐ„๋‹จ | | **OpenAI** | `https://api.openai.com/v1` | Chat Completions | | **OrcaRouter** | `https://api.orcarouter.ai/v1` | ํ•˜๋‚˜์˜ ํ‚ค๋กœ ์—ฌ๋Ÿฌ ์ œ๊ณต์‚ฌ ๋ชจ๋ธ | | **๋กœ์ปฌ ๋ชจ๋ธ** | `http://localhost:11434/v1` | Ollama ยท LM Studio ยท vLLM โ€” ํšŒ์˜ ๋‚ด์šฉ์ด ์™ธ๋ถ€๋กœ ๋‚˜๊ฐ€์ง€ ์•Š์Œ | | **์ง์ ‘ ์ž…๋ ฅ** | ์‚ฌ์šฉ์ž ์ง€์ • | OpenAI ํ˜ธํ™˜์ด๋ฉด ๋ฌด์—‡์ด๋“  | Gemini ์™ธ ํ”„๋ฆฌ์…‹์€ ๋ชจ๋‘ ๋™์ผํ•œ **OpenAI Chat Completions** ์–ด๋Œ‘ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (`src/lib/providers/openai-compatible.ts`). base URL๊ณผ ๋ชจ๋ธ ID๋งŒ ๋ฐ”๊พธ๋ฉด ์ƒˆ ์„œ๋น„์Šค๊ฐ€ ๋ถ™์Šต๋‹ˆ๋‹ค. **์ €์žฅ ์œ„์น˜** - ๋ธŒ๋ผ์šฐ์ € LocalStorage (์„œ๋ฒ„ DB์— ์ €์žฅ๋˜์ง€ ์•Š์Œ) - ์š”์ฒญ body๋กœ ํ•จ๊ป˜ ์ „์†ก๋˜์–ด ์ผํšŒ์„ฑ์œผ๋กœ ์‚ฌ์šฉ **์šฐ์„ ์ˆœ์œ„** ``` 1. ์š”์ฒญ body์˜ provider/apiKey/baseUrl/model (๋ธŒ๋ผ์šฐ์ € LocalStorage) โ†“ ์—†์œผ๋ฉด 2. ์„œ๋ฒ„ ํ™˜๊ฒฝ๋ณ€์ˆ˜ (GEMINI_API_KEY ๋˜๋Š” LLM_PROVIDER / LLM_BASE_URL / LLM_MODEL / LLM_API_KEY) โ†“ ์—†์œผ๋ฉด 3. summarize โ†’ ๋‹จ์ˆœ ๋ณ€ํ™˜ ํด๋ฐฑ (warning ํ‘œ์‹œ) summarize-live โ†’ 503 + ์•ˆ๋‚ด ``` **๊ธฐ๋Šฅ** - ๋งˆ์Šคํ‚น๋œ ํ˜„์žฌ ํ‚ค ํ‘œ์‹œ (`AIzaโ€ขโ€ขโ€ขโ€ขXYZ12`) - ๋ณด์ด๊ธฐ/์ˆจ๊ธฐ๊ธฐ ํ† ๊ธ€ - **๐Ÿงช ํ…Œ์ŠคํŠธ ํ˜ธ์ถœ** โ€” ๊ฐ€๋ฒผ์šด ์‘๋‹ต์œผ๋กœ ์ฆ‰์‹œ ์„ค์ • ๊ฒ€์ฆ (์‚ฌ์šฉ๋œ ๋ชจ๋ธ๋ช… ํ‘œ์‹œ) - ํ˜„์žฌ ์†Œ์Šค ๋ฐฐ์ง€ (`๋ธŒ๋ผ์šฐ์ €` / `ํ™˜๊ฒฝ๋ณ€์ˆ˜` / `์—†์Œ`) - ๊ธฐ์กด์— Gemini ํ‚ค๋งŒ ์ €์žฅํ•ด๋‘” ์‚ฌ์šฉ์ž๋Š” **์žฌ์ž…๋ ฅ ์—†์ด ๊ทธ๋Œ€๋กœ ๋™์ž‘** (์ž๋™ ์Šน๊ณ„) **๋ณด์•ˆ** - HTTPS ๊ถŒ์žฅ (LocalStorage๋Š” ๋™์ผ ์ถœ์ฒ˜ ์ •์ฑ… ์˜์กด) - GET ์‘๋‹ต์— ์ ˆ๋Œ€ ํ’€ ํ‚ค ๋…ธ์ถœ ์•ˆ ํ•จ - base URL์€ `http`/`https`๋งŒ ํ—ˆ์šฉ โ€” ์ž์„ธํ•œ ๋‚ด์šฉ์€ [SECURITY.md](SECURITY.md) --- ### ๐Ÿ“‹ 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๊ฐ€ ๋ช…์‹œ์  ์—๋Ÿฌ๋กœ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ๋ณด์•ˆ์„ ์œ„ํ•œ ์˜๋„๋œ ๋™์ž‘. > ๐Ÿ’ก **AI ํ”„๋กœ๋ฐ”์ด๋” ์„ค์ •์€ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ• ์ค‘ ์„ ํƒ** > > - **(A) ์›น UI** โ€” ์•ฑ ๊ธฐ๋™ ํ›„ `/settings`์—์„œ ํ”„๋กœ๋ฐ”์ด๋” ์„ ํƒ + ํ‚ค ์ž…๋ ฅ (๋ธŒ๋ผ์šฐ์ € LocalStorage, ์ถ”์ฒœ) > - **(B) `.env` ํ™˜๊ฒฝ๋ณ€์ˆ˜** โ€” `GEMINI_API_KEY=AIza...` ๋˜๋Š” `LLM_PROVIDER` / `LLM_BASE_URL` / `LLM_MODEL` / `LLM_API_KEY` > > ๊ธฐ๋ณธ๊ฐ’์ธ Gemini ํ‚ค๋Š” [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 3.5 Flash Lite (๊ธฐ๋ณธ) ยท OpenAI ํ˜ธํ™˜ ์—”๋“œํฌ์ธํŠธ ์„ ํƒ ๊ฐ€๋Šฅ | | DB | PostgreSQL 16 + Prisma 7 (driver adapter `@prisma/adapter-pg`) | | Markdown | `marked` + `isomorphic-dompurify` | | ์Šคํƒ€์ผ | Tailwind CSS v4 | | ํ…Œ์ŠคํŠธ | Vitest 4 (jsdom, 141 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/ โ”‚ โ”œโ”€โ”€ providers/ # AI ํ”„๋กœ๋ฐ”์ด๋” ์–ด๋Œ‘ํ„ฐ โ”‚ โ”‚ โ”œโ”€โ”€ index.ts # complete() ๋””์ŠคํŒจ์น˜ โ”‚ โ”‚ โ”œโ”€โ”€ types.ts # ProviderSettings / CompletionResult โ”‚ โ”‚ โ”œโ”€โ”€ presets.ts # ์„ค์ • UI์šฉ ํ”„๋ฆฌ์…‹ ๋ชฉ๋ก โ”‚ โ”‚ โ”œโ”€โ”€ gemini.ts # Google Generative Language API โ”‚ โ”‚ โ””โ”€โ”€ openai-compatible.ts # OpenAI Chat Completions ํ˜ธํ™˜ โ”‚ โ”œโ”€โ”€ 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์— ์œ„์ž„) ### API ํ‚ค - LocalStorage ์ €์žฅ (๋ธŒ๋ผ์šฐ์ € ๋™์ผ ์ถœ์ฒ˜ ์ •์ฑ…์œผ๋กœ ๋ณดํ˜ธ) - ์„œ๋ฒ„ DB์— ์ €์žฅ๋˜์ง€ ์•Š์Œ - ์š”์ฒญ ์‹œ์ ์—๋งŒ body๋กœ ์ „์†ก, ์ผํšŒ์„ฑ ์‚ฌ์šฉ - HTTPS ํ™˜๊ฒฝ ๊ถŒ์žฅ (HTTP๋Š” localhost ํ•œ์ •) ### ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†ก์ถœ - **Web Speech API** โ€” Chrome์ด ๋งˆ์ดํฌ ์˜ค๋””์˜ค๋ฅผ Google ์„œ๋ฒ„๋กœ ์ „์†กํ•˜์—ฌ ์ „์‚ฌ (Chrome ์ž์ฒด ๋™์ž‘, ์šฐ๋ฆฌ ์„œ๋ฒ„ ๊ฒฝ์œ  X) - **AI ์š”์•ฝ API** โ€” ์‚ฌ์šฉ์ž๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ ํ™œ์„ฑํ™”ํ•œ ๊ฒฝ์šฐ์—๋งŒ transcript๋ฅผ ์„ ํƒํ•œ ํ”„๋กœ๋ฐ”์ด๋”๋กœ ์ „์†ก - Gemini / OpenAI ์ง์ ‘ ํ˜ธ์ถœ โ†’ ํ•ด๋‹น ํšŒ์‚ฌ ์„œ๋ฒ„ 1๊ณณ - OrcaRouter ๋“ฑ ์ค‘๊ณ„ ๋ผ์šฐํ„ฐ โ†’ **๋ผ์šฐํ„ฐ ์šด์˜์‚ฌ + ์‹ค์ œ ๋ชจ๋ธ ์ œ๊ณต์‚ฌ** ์–‘์ชฝ - ๋กœ์ปฌ ๋ชจ๋ธ (Ollama / LM Studio / vLLM) โ†’ **์™ธ๋ถ€ ์ „์†ก ์—†์Œ** - **๊ทธ ์™ธ** โ€” ์™ธ๋ถ€ ํ˜ธ์ถœ ์—†์Œ (ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ / ๋ถ„์„ ๋„๊ตฌ ๋ฏธ์„ค์น˜) > โš ๏ธ **ํšŒ์‚ฌ ํšŒ์˜ ๋“ฑ ๋ฏผ๊ฐ ์ •๋ณด๊ฐ€ ์™ธ๋ถ€ ํด๋ผ์šฐ๋“œ๋กœ ์†ก์ถœ๋˜๋Š” ์ ์— ์œ ์˜.** ์‚ฌ๋‚ด ์ปดํ”Œ๋ผ์ด์–ธ์Šค ์ •์ฑ… ํ™•์ธ ํ›„ ์‚ฌ์šฉ ๊ถŒ์žฅ. > ์™ธ๋ถ€ ์ „์†ก์ด ๊ณค๋ž€ํ•˜๋‹ค๋ฉด `/settings`์—์„œ **๋กœ์ปฌ ๋ชจ๋ธ**์„ ์„ ํƒํ•˜์„ธ์š”. ### ๊ถŒ์žฅ ๋ฐฐํฌ ๋ฐฉ์‹ **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๋Š” ์ œํ•œ์  | ์„œ๋ฒ„ ์‚ฌ์ด๋“œ STT ์ „ํ™˜ ์˜ˆ์ • | | **์ „์‚ฌ ํฌ์ฐฉ๋ฅ  ๋‚ฎ์Œ** | ์›๊ฑฐ๋ฆฌยท๋‹ค์ธ ๋Œ€๋ฉด ํšŒ์˜ ์‹ค์ธก 10% ์•ˆํŒŽ | ์˜ค๋””์˜ค ์›๋ณธ ๋ณด๊ด€ โ†’ ์„œ๋ฒ„ STT ์žฌ์ „์‚ฌ (์ง„ํ–‰ ์˜ˆ์ •) | | **ํ™”์ž ๊ตฌ๋ถ„ ์—†์Œ** | ๋ฏธ๊ตฌํ˜„ (PR #16 / #17 ๊ฒ€ํ†  ์ค‘) | โ€” | | **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