Knowledge documents travel as source-text chunks; each surface embeds them with its own model, the server index is requested through embed-chunks, and documents from the phone are stored without a file and indexed from their chunks. Chunk text is now kept when local embedding fails, so reindexing no longer needs the original file. Recordings upload to the mobile storage contract (audio bucket under the user's folder plus an audio_files row, 50 MiB cap, a Settings > Cloud toggle) and are removed with their record. The history card gains a play button that uses the local file or, for phone recordings, a signed URL. Language (ko/en), system/light/dark theme, auto-polish and the active user command follow the phone's user_settings with its revision rule; changes that arrive from the phone reach the open window.
102 lines
3.5 KiB
TypeScript
102 lines
3.5 KiB
TypeScript
// src/renderer/App.tsx — 루트 컴포넌트
|
|
// 테마 시스템: auto(시스템) / dark / light. 기본은 auto → 다크.
|
|
// i18n: I18nProvider가 전체 트리를 감쌈. ConfigService에서 언어 로드.
|
|
//
|
|
// 인증 정책: 무료 로컬 모드가 사용자 entry point.
|
|
// 앱은 로그인 없이 바로 메인 UI에 진입해서 로컬 STT/LLM을 사용할 수 있다.
|
|
// OAuth 로그인은 Settings → Cloud Sync 섹션에서 선택적으로 수행 (유료 SaaS 경로).
|
|
|
|
import { useState, useEffect, useMemo, useRef } from 'react'
|
|
import { ThemeProvider, CssBaseline, useMediaQuery } from '@mui/material'
|
|
import { getTheme } from '@d3ro/ui/theme'
|
|
import { I18nProvider, useI18n, type I18nStorage, type Locale } from '@d3ro/i18n'
|
|
import { AppLayout } from './components/AppLayout'
|
|
import { UpgradePromptModal } from './components/UpgradePromptModal'
|
|
import { startSystemAudioCapture, stopSystemAudioCapture } from './utils/systemAudioCapture'
|
|
import type { ThemeMode, ConfigChangedEvent } from '@d3ro/core/types'
|
|
|
|
// Electron ConfigService에 바인딩된 i18n 영속화 어댑터
|
|
const electronI18nStorage: I18nStorage = {
|
|
load: async () => {
|
|
const result = await window.electronAPI.config.get({ key: 'language' })
|
|
return result.success && result.data ? (result.data as string) : null
|
|
},
|
|
save: (locale: Locale) => {
|
|
window.electronAPI.config.set({ key: 'language', value: locale })
|
|
}
|
|
}
|
|
|
|
/** 다른 기기(모바일)에서 바꾼 언어가 동기화로 들어오면 화면 언어도 바꾼다. */
|
|
function SyncedLocale(): null {
|
|
const { locale, setLocale } = useI18n()
|
|
useEffect(
|
|
() =>
|
|
window.electronAPI.config.onChanged((e: ConfigChangedEvent) => {
|
|
if (e.key === 'language' && typeof e.value === 'string' && e.value !== locale) {
|
|
setLocale(e.value as Locale)
|
|
}
|
|
}),
|
|
[locale, setLocale],
|
|
)
|
|
return null
|
|
}
|
|
|
|
export function App(): React.ReactElement {
|
|
const [themeMode, setThemeMode] = useState<ThemeMode>('auto')
|
|
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)')
|
|
const systemAudioCleanupRef = useRef<(() => void) | null>(null)
|
|
|
|
// 설정에서 테마 로드 + 변경 감지
|
|
useEffect(() => {
|
|
window.electronAPI.config.getTheme().then((result) => {
|
|
if (result.success) {
|
|
setThemeMode(result.data)
|
|
}
|
|
})
|
|
|
|
const unsub = window.electronAPI.config.onChanged((e: ConfigChangedEvent) => {
|
|
if (e.key === 'theme') {
|
|
setThemeMode(e.value as ThemeMode)
|
|
}
|
|
})
|
|
return unsub
|
|
}, [])
|
|
|
|
// 시스템 오디오 캡처: 메인 프로세스의 시작/중지 요청에 응답
|
|
useEffect(() => {
|
|
const unsubStart = window.electronAPI.caption.onStartSystemAudio(() => {
|
|
startSystemAudioCapture((pcm16Buffer) => {
|
|
window.electronAPI.caption.sendSystemAudioData(pcm16Buffer)
|
|
}).catch(() => {
|
|
// 시스템 오디오 캡처 실패 시 무시 — 마이크 자막만 사용
|
|
})
|
|
})
|
|
|
|
const unsubStop = window.electronAPI.caption.onStopSystemAudio(() => {
|
|
stopSystemAudioCapture()
|
|
})
|
|
|
|
systemAudioCleanupRef.current = () => {
|
|
stopSystemAudioCapture()
|
|
}
|
|
|
|
return () => {
|
|
unsubStart()
|
|
unsubStop()
|
|
systemAudioCleanupRef.current?.()
|
|
}
|
|
}, [])
|
|
|
|
const theme = useMemo(() => getTheme(themeMode, prefersDark), [themeMode, prefersDark])
|
|
|
|
return (
|
|
<I18nProvider storage={electronI18nStorage}>
|
|
<SyncedLocale />
|
|
<ThemeProvider theme={theme}>
|
|
<CssBaseline />
|
|
<AppLayout />
|
|
<UpgradePromptModal />
|
|
</ThemeProvider>
|
|
</I18nProvider>
|
|
)
|
|
}
|