feat: 회의록 자동 작성 웹 서비스 구현

- 오디오 파일 업로드 + 유효성 검사
- 실시간 음성 인식 (Web Speech API)
- 회의록 생성 (단순 STT→MD 변환 / Gemini AI 요약)
- 마크다운/HTML 내보내기
- PostgreSQL + Prisma ORM
- Docker Compose 배포 지원
- TDD: 31개 테스트, 96% 커버리지
This commit is contained in:
nad4 committed 2026-04-11 23:18:05 +09:00
1 parent b5743fe896
commit adb48c2352
31 files changed
+4288 -82

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
export interface TranscriptChunk {
text: string
startTime: number
endTime: number
isFinal: boolean
}
function formatTimestamp(seconds: number): string {
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = Math.floor(seconds % 60)
if (h > 0) {
return `[${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}]`
}
return `[${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}]`
}
export function formatTranscriptChunks(chunks: readonly TranscriptChunk[]): string {
if (chunks.length === 0) return ''
return chunks
.map((chunk) => `${formatTimestamp(chunk.startTime)} ${chunk.text}`)
.join('\n')
}
export function mergeAdjacentChunks(
chunks: readonly TranscriptChunk[],
gapThreshold: number,
): TranscriptChunk[] {
if (chunks.length === 0) return []
const result: TranscriptChunk[] = [{ ...chunks[0] }]
for (let i = 1; i < chunks.length; i++) {
const current = chunks[i]
const last = result[result.length - 1]
if (current.startTime - last.endTime <= gapThreshold) {
result[result.length - 1] = {
text: `${last.text} ${current.text}`,
startTime: last.startTime,
endTime: current.endTime,
isFinal: current.isFinal,
}
} else {
result.push({ ...current })
}
}
return result
}