'use client' import { useMemo, useState } from 'react' import Link from 'next/link' import { AudioUploader } from '@/components/upload/AudioUploader' import { LiveRecorder } from '@/components/recorder/LiveRecorder' import { MinutesViewer } from '@/components/minutes/MinutesViewer' import { getProviderRequestPayload } from '@/lib/api-key-storage' import { TEMPLATES, DEFAULT_TEMPLATE_ID, DEFAULT_DEPTH, depthAdjustableFor, liveEnabledFor, type SummaryDepth, type TemplateId, } from '@/lib/templates' type Tab = 'upload' | 'record' type SummaryMode = 'simple' | 'gemini' interface MinutesResult { markdown: string mode: SummaryMode warning?: string } const DEPTH_LABELS: Record = { concise: '간결', standard: '표준', detailed: '상세', } export default function HomePage() { const [tab, setTab] = useState('record') const [title, setTitle] = useState('') const [transcript, setTranscript] = useState('') const [summaryMode, setSummaryMode] = useState('gemini') const [template, setTemplate] = useState(DEFAULT_TEMPLATE_ID) const [depth, setDepth] = useState(DEFAULT_DEPTH) const [customPrompt, setCustomPrompt] = useState('') const [result, setResult] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [uploadedFile, setUploadedFile] = useState(null) const templateList = useMemo( () => [ ...Object.values(TEMPLATES), { id: 'custom' as const, name: '커스텀', icon: '⚙️', description: '직접 프롬프트 작성', }, ], [], ) const activeTemplateMeta = useMemo(() => { if (template === 'custom') { return { adjustable: false, live: true } } return { adjustable: depthAdjustableFor(template), live: liveEnabledFor(template), } }, [template]) function handleTemplateChange(id: TemplateId) { setTemplate(id) if (id !== 'custom') { const tpl = TEMPLATES[id] setDepth(tpl.defaultDepth) } } async function handleUpload(file: File) { setUploadedFile(file) setError(null) const formData = new FormData() formData.append('audio', file) formData.append('title', title || file.name.replace(/\.[^.]+$/, '')) try { const res = await fetch('/api/upload', { method: 'POST', body: formData }) const data = await res.json() if (!res.ok) { setError(data.error) return } if (!title) setTitle(data.title) } catch { setError('파일 업로드에 실패했습니다.') } } async function generateMinutes(sourceTranscript: string, mode: SummaryMode) { const text = sourceTranscript.trim() if (!text) { setError('변환할 텍스트가 없습니다. 녹음하거나 파일을 업로드해주세요.') return } setLoading(true) setError(null) try { const res = await fetch('/api/summarize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: title || '무제 회의', transcript: text, mode, template, depth, customPrompt: template === 'custom' ? customPrompt : undefined, ...getProviderRequestPayload(), }), }) const data = await res.json() if (!res.ok) { setError(data.error) return } setResult({ markdown: data.markdown, mode: data.mode, warning: data.warning, }) } catch { setError('회의록 생성에 실패했습니다.') } finally { setLoading(false) } } function handleTranscriptReady(text: string) { setTranscript(text) generateMinutes(text, summaryMode) } const liveSummaryActive = summaryMode === 'gemini' && activeTemplateMeta.live && tab === 'record' return (

Meeting Minutes

음성을 텍스트로, 용도에 맞는 템플릿으로 정리

📚 회의록 ⚙️ 설정
setTitle(e.target.value)} placeholder="예: 주간 스프린트 회의" className="w-full rounded-xl border border-neutral-300 px-4 py-3 text-neutral-800 placeholder:text-neutral-400 focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-100 transition-all" />
{summaryMode === 'gemini' && ( <>
{templateList.map((tpl) => ( ))}

{template === 'custom' ? '직접 프롬프트 작성' : TEMPLATES[template]?.description}

{activeTemplateMeta.adjustable && (
{(['concise', 'standard', 'detailed'] as const).map( (d) => ( ), )}
)} {template === 'custom' && (