mirror of
https://github.com/nad4-su/meeting-minutes.git
synced 2026-08-12 22:33:25 +09:00
회의록 외 다양한 용도로 쓸 수 있도록 출력 구조와 요약 깊이를 분리. 템플릿: - 🗂️ 회의록 (표준, 라이브 O) - 🎓 강의·세미나 노트 (상세, 라이브 O) - 🤝 1:1 미팅 (표준, 라이브 O) - 💡 브레인스토밍 (상세, 라이브 O) - 🎤 인터뷰 (표준, 라이브 X — Q&A 포맷) - 📝 원문 정리 (상세 고정, 라이브 X — 요약 없이 문단화만) - ⚙️ 커스텀 (자유 프롬프트) 강도: 간결 / 표준 / 상세 — 템플릿별 기본값 프리셋, 언제든 override. 변경: - src/lib/templates.ts 신규 — 프리셋 + 프롬프트 빌더 - minutes-generator / live-summary 가 buildPrompt 공유 - /api/summarize{-live} 가 template/depth/customPrompt 파라미터 수용 - page.tsx UI: 템플릿 픽커, 강도 라디오, 커스텀 textarea - LiveRecorder 가 설정을 useLiveSummary 로 전달 (ref 기반 최신값) - 단순 변환 모드는 기존 동작 유지 (템플릿 미적용) - 프롬프트 빌더 단위 테스트 19개 (총 57 tests)
88 lines
2.3 KiB
TypeScript
88 lines
2.3 KiB
TypeScript
import {
|
|
buildPrompt,
|
|
resolveDepth,
|
|
type SummaryDepth,
|
|
type TemplateId,
|
|
} from './templates'
|
|
|
|
type LiveSummaryResult =
|
|
| { success: true; markdown: string }
|
|
| { success: false; error: string; rateLimited?: boolean }
|
|
|
|
interface LiveSummaryOptions {
|
|
apiKey: string
|
|
template?: TemplateId
|
|
depth?: SummaryDepth
|
|
customPrompt?: string
|
|
fetchFn?: typeof fetch
|
|
}
|
|
|
|
export async function generateLiveSummary(
|
|
transcript: string,
|
|
options: LiveSummaryOptions,
|
|
): Promise<LiveSummaryResult> {
|
|
const { apiKey, fetchFn = fetch } = options
|
|
|
|
if (!apiKey || apiKey.trim().length === 0) {
|
|
return { success: false, error: 'Gemini API 키가 필요합니다.' }
|
|
}
|
|
|
|
const trimmed = transcript.trim()
|
|
if (trimmed.length === 0) {
|
|
return { success: false, error: '요약할 텍스트가 비어 있습니다.' }
|
|
}
|
|
|
|
const templateId = options.template ?? 'meeting'
|
|
const depth = resolveDepth(templateId, options.depth)
|
|
const prompt = buildPrompt({
|
|
templateId,
|
|
depth,
|
|
transcript: trimmed,
|
|
live: true,
|
|
customPrompt: options.customPrompt,
|
|
})
|
|
|
|
const url =
|
|
'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent'
|
|
|
|
try {
|
|
const response = await fetchFn(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-goog-api-key': apiKey,
|
|
},
|
|
body: JSON.stringify({
|
|
contents: [{ parts: [{ text: prompt }] }],
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 429) {
|
|
return {
|
|
success: false,
|
|
error: 'Gemini 요청 한도 초과. 잠시 후 자동 재시도됩니다.',
|
|
rateLimited: true,
|
|
}
|
|
}
|
|
return {
|
|
success: false,
|
|
error: `Gemini API 호출 실패: ${response.status}`,
|
|
}
|
|
}
|
|
|
|
const data = await response.json()
|
|
const markdown: string =
|
|
data?.candidates?.[0]?.content?.parts?.[0]?.text ?? ''
|
|
|
|
if (markdown.trim().length === 0) {
|
|
return { success: false, error: '요약 결과가 비어 있습니다.' }
|
|
}
|
|
|
|
return { success: true, markdown }
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : '알 수 없는 오류'
|
|
return { success: false, error: `실시간 요약 중 오류: ${message}` }
|
|
}
|
|
}
|