Phase 14 구현: 회의 모드 (Meeting Mode)

실시간 녹음 + 타임스탬프 메모 + STT 전사 + LLM 구조화 회의록 + PDF/MD 내보내기.
CaptionService 연동, 동시 사용 충돌 방지, 긴 회의 2-pass 요약 지원.
DB 2테이블, IPC 15채널, UI 3뷰(목록/녹음/상세), 12개 locale i18n.
This commit is contained in:
Yun Chan 2026-04-08 01:16:52 +09:00
parent dd68fc9ccd
commit d0c4f9ee53
38 changed files with 2550 additions and 769 deletions

View file

@ -9,6 +9,7 @@ import MenuBookIcon from '@mui/icons-material/MenuBook'
import ExtensionIcon from '@mui/icons-material/Extension'
import RecordVoiceOverIcon from '@mui/icons-material/RecordVoiceOver'
import AutoStoriesIcon from '@mui/icons-material/AutoStories'
import GroupsIcon from '@mui/icons-material/Groups'
import SettingsIcon from '@mui/icons-material/Settings'
import { Led } from './ds'
import { DashboardPage } from '../pages/DashboardPage'
@ -17,6 +18,7 @@ import { DictionaryPage } from '../pages/DictionaryPage'
import { CommandsPage } from '../pages/CommandsPage'
import { VoiceConversationPage } from '../pages/VoiceConversationPage'
import { KnowledgeBasePage } from '../pages/KnowledgeBasePage'
import { MeetingModePage } from '../pages/MeetingModePage'
import { SettingsModal } from './SettingsModal'
import { LicenseModal } from './LicenseModal'
import { OnboardingModal } from './OnboardingModal'
@ -26,7 +28,7 @@ import { useI18n } from '../i18n'
import type { TranslationKey } from '../i18n'
import type { LicenseTier } from '@shared/types'
type Route = 'dashboard' | 'history' | 'dictionary' | 'commands' | 'conversation' | 'knowledge'
type Route = 'dashboard' | 'history' | 'dictionary' | 'commands' | 'conversation' | 'knowledge' | 'meeting'
interface NavItem {
route: Route
@ -42,6 +44,7 @@ const NAV_ITEMS: NavItem[] = [
{ route: 'commands', labelKey: 'nav.commands', abbr: 'CMD', icon: <ExtensionIcon sx={{ fontSize: 20 }} /> },
{ route: 'conversation', labelKey: 'nav.conversation', abbr: 'TALK', icon: <RecordVoiceOverIcon sx={{ fontSize: 20 }} /> },
{ route: 'knowledge', labelKey: 'nav.knowledge', abbr: 'RAG', icon: <AutoStoriesIcon sx={{ fontSize: 20 }} /> },
{ route: 'meeting', labelKey: 'nav.meeting', abbr: 'MTG', icon: <GroupsIcon sx={{ fontSize: 20 }} /> },
]
function tierToLedColor(tier: LicenseTier): 'amber' | 'green' {
@ -225,6 +228,7 @@ export function AppLayout(): React.ReactElement {
{currentRoute === 'commands' && <CommandsPage />}
{currentRoute === 'conversation' && <VoiceConversationPage />}
{currentRoute === 'knowledge' && <KnowledgeBasePage />}
{currentRoute === 'meeting' && <MeetingModePage />}
</Box>
</Box>
<StatusBar />

View file

@ -455,10 +455,10 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac
<MenuItem value="auto">{t('settings.theme.system')}</MenuItem>
<MenuItem value="light">{t('settings.theme.light')}</MenuItem>
<MenuItem value="dark">{t('settings.theme.dark')}</MenuItem>
<MenuItem value="nord">Nord</MenuItem>
<MenuItem value="solarized">Solarized</MenuItem>
<MenuItem value="catppuccin">Catppuccin</MenuItem>
<MenuItem value="dracula">Dracula</MenuItem>
<MenuItem value="nord">{t('settings.theme.nord')}</MenuItem>
<MenuItem value="solarized">{t('settings.theme.solarized')}</MenuItem>
<MenuItem value="catppuccin">{t('settings.theme.catppuccin')}</MenuItem>
<MenuItem value="dracula">{t('settings.theme.dracula')}</MenuItem>
</Select>
</FormControl>
@ -627,10 +627,10 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac
onChange={(e) => updateConfig('sttLanguage', e.target.value)}
>
<MenuItem value="auto">{t('settings.sttLang.auto')}</MenuItem>
<MenuItem value="ko"></MenuItem>
<MenuItem value="en">English</MenuItem>
<MenuItem value="ja"></MenuItem>
<MenuItem value="zh"></MenuItem>
<MenuItem value="ko">{t('settings.sttLang.ko')}</MenuItem>
<MenuItem value="en">{t('settings.sttLang.en')}</MenuItem>
<MenuItem value="ja">{t('settings.sttLang.ja')}</MenuItem>
<MenuItem value="zh">{t('settings.sttLang.zh')}</MenuItem>
</Select>
</FormControl>
</Box>

View file

@ -277,7 +277,7 @@ export function TemplateSection(): React.ReactElement {
fullWidth
multiline
rows={3}
helperText="Use {{fieldName}} for placeholders"
helperText={t('template.outputHelperText')}
/>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>

View file

@ -56,10 +56,10 @@ export function MetalDial({
background: `
repeating-radial-gradient(
circle at 50% 50%,
#e6e7e9 0px,
#e6e7e9 1px,
#b0b2b5 1.5px,
#b0b2b5 2.5px
${d3roPalette.text.disabled} 0px,
${d3roPalette.text.disabled} 1px,
${d3roPalette.text.label} 1.5px,
${d3roPalette.text.label} 2.5px
)
`,
transform: `rotate(${rotation}deg)`,