fix(runtime): stage runtime installs strictly, time out only on stalls, and make reinstall actually reinstall

This commit is contained in:
Yun Chan 2026-09-28 00:53:44 +09:00
parent ba0dbbd813
commit 524d390bc2
10 changed files with 1160 additions and 238 deletions

View file

@ -51,6 +51,7 @@ import type {
} from '@d3ro/core/types'
import { CodexOAuthGuideModal } from './CodexOAuthGuideModal'
import { CaptionModelSelect } from './CaptionModelSelect'
import { subscribeSttTabEvents } from './stt/sttTabSubscriptions'
interface STTTabProps {
config: Partial<AppConfig>
@ -98,17 +99,6 @@ export function STTTab({ config, updateConfig }: STTTabProps): React.ReactElemen
if (res.success && res.data) setLocalModels(res.data)
})
const unsubDownload = window.electronAPI.stt.onDownloadProgress((e) => {
setDownloadingModelId(e.modelId)
setDownloadPercent(e.percent)
if (e.percent >= 100) {
setDownloadingModelId(null)
window.electronAPI.stt.getModels().then((res) => {
if (res.success && res.data) setLocalModels(res.data)
})
}
})
const loadRuntime = () => {
window.electronAPI.runtime.getStatus().then((res) => {
if (res.success && res.data) setRuntimeStatus(res.data)
@ -116,23 +106,35 @@ export function STTTab({ config, updateConfig }: STTTabProps): React.ReactElemen
}
loadRuntime()
const unsubRuntime = window.electronAPI.runtime.onProgress((e) => {
if (e.component !== 'sidecar' && e.component !== 'ffmpeg') return
if (e.phase === 'done') {
setRuntimeBusy(null)
setRuntimePercent(100)
loadRuntime()
return
}
setRuntimeBusy(e.component)
setRuntimePercent(e.percent)
})
return () => {
unsubDownload()
}
unsubDownload()
unsubRuntime()
return subscribeSttTabEvents(
{
onDownloadProgress: window.electronAPI.stt.onDownloadProgress,
onRuntimeProgress: window.electronAPI.runtime.onProgress,
},
{
onDownloadProgress: (e) => {
setDownloadingModelId(e.modelId)
setDownloadPercent(e.percent)
if (e.percent >= 100) {
setDownloadingModelId(null)
window.electronAPI.stt.getModels().then((res) => {
if (res.success && res.data) setLocalModels(res.data)
})
}
},
onRuntimeProgress: (e) => {
if (e.component !== 'sidecar' && e.component !== 'ffmpeg') return
if (e.phase === 'done') {
setRuntimeBusy(null)
setRuntimePercent(100)
loadRuntime()
return
}
setRuntimeBusy(e.component)
setRuntimePercent(e.percent)
},
},
)
}, [])
// Load specific provider config when activeProvider changes

View file

@ -0,0 +1,33 @@
// src/renderer/components/stt/sttTabSubscriptions.ts
// STT 탭이 구독하는 main → renderer 진행률 이벤트를 한 곳에서 묶고, 해제도 한 번에 한다.
//
// 왜: 예전 effect 는 cleanup 이 `unsubDownload()` 만 부르고 끝났고, `unsubRuntime()` 은
// return 뒤의 도달 불가 코드에 있었다. 설정 창의 TabPanel 은 비활성 탭을 언마운트하므로
// STT 탭을 열 때마다 runtime:progress 리스너가 하나씩 쌓였다. 구독과 해제를 같은 함수가
// 책임지면 새 구독을 추가해도 해제를 빠뜨릴 수 없다.
export type Unsubscribe = () => void
export interface SttTabEventSource<DownloadEvent, RuntimeEvent> {
onDownloadProgress: (cb: (event: DownloadEvent) => void) => Unsubscribe
onRuntimeProgress: (cb: (event: RuntimeEvent) => void) => Unsubscribe
}
export interface SttTabEventHandlers<DownloadEvent, RuntimeEvent> {
onDownloadProgress: (event: DownloadEvent) => void
onRuntimeProgress: (event: RuntimeEvent) => void
}
/** 모든 구독을 등록하고, 전부 해제하는 함수 하나를 돌려준다 (useEffect cleanup 용) */
export function subscribeSttTabEvents<DownloadEvent, RuntimeEvent>(
source: SttTabEventSource<DownloadEvent, RuntimeEvent>,
handlers: SttTabEventHandlers<DownloadEvent, RuntimeEvent>,
): Unsubscribe {
const unsubscribes: Unsubscribe[] = [
source.onDownloadProgress(handlers.onDownloadProgress),
source.onRuntimeProgress(handlers.onRuntimeProgress),
]
return () => {
for (const unsubscribe of unsubscribes.splice(0)) unsubscribe()
}
}