Phase 4 구현: Ollama LLM 연동 (다듬기, 번역, 스트리밍)

- LocalLLMService: Ollama REST API, 스트리밍 NDJSON 파싱, 5초 가용성 폴링
- 시스템 프롬프트: refine/translate/summarize/grammar/expand/custom 6개 액션
- VoiceModeService: 전사→LLM 후처리→텍스트 삽입, LLM 실패 시 원본 폴백
- LLM IPC 핸들러: status/models/process/cancel/serverUrl 8개
- StatusBar: Ollama 연결 상태 + 활성 모델 표시
- Preload: llm API 섹션 추가
- Bootstrap: llm-polling 초기화 단계 추가
This commit is contained in:
Yun Chan 2026-04-05 02:18:26 +09:00
parent 517210af2f
commit 4a5cf6c819
10 changed files with 663 additions and 9 deletions

View file

@ -0,0 +1,69 @@
// src/renderer/components/StatusBar.tsx
// 하단 상태 표시: Ollama 연결 상태
import { useState, useEffect } from 'react'
import { Box, Chip } from '@mui/material'
import CircleIcon from '@mui/icons-material/Circle'
import type { LLMStatus } from '@shared/types'
export function StatusBar(): React.ReactElement {
const [llmStatus, setLlmStatus] = useState<LLMStatus | null>(null)
useEffect(() => {
// 초기 상태 조회
window.electronAPI.llm.getStatus().then((result) => {
if (result.success) setLlmStatus(result.data)
})
// 상태 변경 구독
const unsub = window.electronAPI.llm.onStatusChanged((event) => {
setLlmStatus(event.status)
})
// 5초마다 폴링 (main에서 이벤트를 보내지 않을 수 있으므로)
const interval = setInterval(() => {
window.electronAPI.llm.getStatus().then((result) => {
if (result.success) setLlmStatus(result.data)
})
}, 5000)
return () => {
unsub()
clearInterval(interval)
}
}, [])
const connected = llmStatus?.connectionState === 'connected'
return (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
px: 2,
py: 0.5,
borderTop: 1,
borderColor: 'divider',
bgcolor: 'background.paper'
}}
>
<Chip
icon={<CircleIcon sx={{ fontSize: 8 }} />}
label={connected ? 'Ollama Connected' : 'Ollama Offline'}
size="small"
variant="outlined"
color={connected ? 'success' : 'default'}
sx={{ height: 22, '& .MuiChip-label': { fontSize: 11 } }}
/>
{llmStatus?.activeModel && (
<Chip
label={llmStatus.activeModel}
size="small"
variant="outlined"
sx={{ height: 22, '& .MuiChip-label': { fontSize: 11 } }}
/>
)}
</Box>
)
}