Spaces:
Sleeping
Sleeping
| import { useState, useEffect, useCallback, useRef } from 'react'; | |
| import { useAppStore } from '../store'; | |
| import { listProfiles } from '../api/profiles'; | |
| import { listHistory } from '../api/generate'; | |
| import { listProjects } from '../api/projects'; | |
| import { listDubHistory } from '../api/dub'; | |
| import { listExportHistory, exportAction, exportReveal, exportRecord } from '../api/exports'; | |
| import { modelStatus as apiModelStatus } from '../api/system'; | |
| import { useSysinfo, useModelStatus } from '../api/hooks'; | |
| import useRealtimeEvents from './useRealtimeEvents'; | |
| import { isTauri, fileToMediaUrl } from '../utils/media'; | |
| import { toast } from 'react-hot-toast'; | |
| /** | |
| * Encapsulates all data-loading effects, localStorage persistence, | |
| * real-time WebSocket updates, and model-status pill management. | |
| */ | |
| export default function useAppData() { | |
| const mode = useAppStore(s => s.mode); | |
| const setMode = useAppStore(s => s.setMode); | |
| const uiScale = useAppStore(s => s.uiScale); | |
| const setUiScale = useAppStore(s => s.setUiScale); | |
| const setText = useAppStore(s => s.setText); | |
| const text = useAppStore(s => s.text); | |
| const setLanguage = useAppStore(s => s.setLanguage); | |
| const language = useAppStore(s => s.language); | |
| const setIsSidebarCollapsed = useAppStore(s => s.setIsSidebarCollapsed); | |
| const isSidebarCollapsed = useAppStore(s => s.isSidebarCollapsed); | |
| const setSidebarTab = useAppStore(s => s.setSidebarTab); | |
| const sidebarTab = useAppStore(s => s.sidebarTab); | |
| const setVdStates = useAppStore(s => s.setVdStates); | |
| const vdStates = useAppStore(s => s.vdStates); | |
| const speed = useAppStore(s => s.speed); | |
| const setSpeed = useAppStore(s => s.setSpeed); | |
| const steps = useAppStore(s => s.steps); | |
| const setSteps = useAppStore(s => s.setSteps); | |
| const cfg = useAppStore(s => s.cfg); | |
| const setCfg = useAppStore(s => s.setCfg); | |
| const denoise = useAppStore(s => s.denoise); | |
| const setDenoise = useAppStore(s => s.setDenoise); | |
| const dubJobId = useAppStore(s => s.dubJobId); | |
| const setDubJobId = useAppStore(s => s.setDubJobId); | |
| const dubFilename = useAppStore(s => s.dubFilename); | |
| const setDubFilename = useAppStore(s => s.setDubFilename); | |
| const dubDuration = useAppStore(s => s.dubDuration); | |
| const setDubDuration = useAppStore(s => s.setDubDuration); | |
| const dubSegments = useAppStore(s => s.dubSegments); | |
| const setDubSegments = useAppStore(s => s.setDubSegments); | |
| const dubLang = useAppStore(s => s.dubLang); | |
| const setDubLang = useAppStore(s => s.setDubLang); | |
| const dubLangCode = useAppStore(s => s.dubLangCode); | |
| const setDubLangCode = useAppStore(s => s.setDubLangCode); | |
| const dubTracks = useAppStore(s => s.dubTracks); | |
| const setDubTracks = useAppStore(s => s.setDubTracks); | |
| const dubStep = useAppStore(s => s.dubStep); | |
| const setDubStep = useAppStore(s => s.setDubStep); | |
| const dubTranscript = useAppStore(s => s.dubTranscript); | |
| const setDubTranscript = useAppStore(s => s.setDubTranscript); | |
| const exportTracks = useAppStore(s => s.exportTracks); | |
| const setExportTracks = useAppStore(s => s.setExportTracks); | |
| const preserveBg = useAppStore(s => s.preserveBg); | |
| const setPreserveBg = useAppStore(s => s.setPreserveBg); | |
| const defaultTrack = useAppStore(s => s.defaultTrack); | |
| const setDefaultTrack = useAppStore(s => s.setDefaultTrack); | |
| const [profiles, setProfiles] = useState([]); | |
| const [history, setHistory] = useState([]); | |
| const [dubHistory, setDubHistory] = useState([]); | |
| const [studioProjects, setStudioProjects] = useState([]); | |
| const [exportHistory, setExportHistory] = useState([]); | |
| const [showOverrides, setShowOverrides] = useState(false); | |
| // ββ Model status + sysinfo (TanStack Query) ββ | |
| const sysQuery = useSysinfo(); | |
| const msQuery = useModelStatus(); | |
| const sysStats = sysQuery.data ?? null; | |
| const modelStatus = msQuery.data?.status ?? 'idle'; | |
| const modelSubStage = msQuery.data?.sub_stage ?? null; | |
| const modelDetail = msQuery.data?.detail ?? ''; | |
| const modelError = msQuery.data?.error ?? null; | |
| const modelProgress = msQuery.data?.progress ?? null; | |
| // ββ Model loading pill ββ | |
| const prevModelStatusRef = useRef(modelStatus); | |
| useEffect(() => { | |
| const prev = prevModelStatusRef.current; | |
| prevModelStatusRef.current = modelStatus; | |
| const pill = useAppStore.getState(); | |
| if (modelStatus === 'loading') { | |
| const label = modelDetail || 'Loading modelβ¦'; | |
| if (prev !== 'loading' && pill.stage === 'idle') pill.showPill('loading-model', label); | |
| else if (pill.stage === 'loading-model') pill.setPillLabel(label); | |
| // Forward real-time percentage from backend | |
| if (modelProgress !== null && pill.stage === 'loading-model') { | |
| pill.setPillProgress(modelProgress); | |
| } | |
| } | |
| if (modelStatus === 'ready' && prev === 'loading' && pill.stage === 'loading-model') pill.completePill('Model ready'); | |
| if (modelSubStage === 'error' && modelError && pill.stage === 'loading-model') pill.errorPill(modelError); | |
| }, [modelStatus, modelSubStage, modelDetail, modelError, modelProgress]); | |
| // ββ Data loading callbacks ββ | |
| const loadProfiles = useCallback(async () => { try { setProfiles(await listProfiles()); } catch (e) {} }, []); | |
| const loadHistory = useCallback(async () => { try { setHistory(await listHistory()); } catch (e) {} }, []); | |
| const loadDubHistory = useCallback(async () => { try { setDubHistory(await listDubHistory()); } catch (e) {} }, []); | |
| const loadProjects = useCallback(async () => { try { setStudioProjects(await listProjects()); } catch (e) {} }, []); | |
| const loadExportHistory = useCallback(async () => { try { setExportHistory(await listExportHistory()); } catch (e) {} }, []); | |
| // ββ WebSocket real-time updates ββ | |
| useRealtimeEvents({ | |
| projects: () => loadProjects(), | |
| profiles: () => loadProfiles(), | |
| dub_history: () => loadDubHistory(), | |
| export_history: () => loadExportHistory(), | |
| generation_history: () => loadHistory(), | |
| }); | |
| // ββ Initial data load with backend retry ββ | |
| useEffect(() => { | |
| let cancelled = false; | |
| const loadAll = async () => { | |
| let delay = 1000; | |
| while (!cancelled) { | |
| try { await apiModelStatus(); break; } catch (e) {} | |
| await new Promise(r => setTimeout(r, delay)); | |
| delay = Math.min(delay * 2, 4000); | |
| } | |
| if (cancelled) return; | |
| loadProfiles(); loadHistory(); loadDubHistory(); loadProjects(); loadExportHistory(); | |
| }; | |
| loadAll(); | |
| // Restore local UI state | |
| try { | |
| const saved = JSON.parse(localStorage.getItem('omni_ui') || '{}'); | |
| if (saved.uiScale) setUiScale(saved.uiScale); | |
| if (saved.text) setText(saved.text); | |
| if (saved.mode) setMode(saved.mode); | |
| if (saved.vdStates) setVdStates(saved.vdStates); | |
| if (saved.language) setLanguage(saved.language); | |
| if (saved.isSidebarCollapsed !== undefined) setIsSidebarCollapsed(saved.isSidebarCollapsed); | |
| if (saved.sidebarTab) setSidebarTab(saved.sidebarTab); | |
| if (saved.dubJobId) setDubJobId(saved.dubJobId); | |
| if (saved.dubFilename) setDubFilename(saved.dubFilename); | |
| if (saved.dubDuration !== undefined) setDubDuration(saved.dubDuration); | |
| if (saved.dubSegments) setDubSegments(saved.dubSegments.map(s => ({ ...s, text_original: s.text_original || s.text || '' }))); | |
| if (saved.dubLang) setDubLang(saved.dubLang); | |
| if (saved.dubLangCode) setDubLangCode(saved.dubLangCode); | |
| if (saved.dubTracks) setDubTracks(saved.dubTracks); | |
| if (saved.dubStep) setDubStep(saved.dubStep); | |
| if (saved.dubTranscript) setDubTranscript(saved.dubTranscript); | |
| if (saved.exportTracks) setExportTracks(saved.exportTracks); | |
| if (saved.preserveBg !== undefined) setPreserveBg(saved.preserveBg); | |
| if (saved.defaultTrack) setDefaultTrack(saved.defaultTrack); | |
| if (saved.exportHistory) setExportHistory(saved.exportHistory); | |
| if (saved.speed) setSpeed(saved.speed); | |
| if (saved.steps) setSteps(saved.steps); | |
| if (saved.cfg) setCfg(saved.cfg); | |
| if (saved.denoise !== undefined) setDenoise(saved.denoise); | |
| if (saved.showOverrides !== undefined) setShowOverrides(saved.showOverrides); | |
| } catch (e) {} | |
| return () => { cancelled = true; }; | |
| }, []); | |
| // ββ Persist to localStorage ββ | |
| useEffect(() => { | |
| localStorage.setItem('omni_ui', JSON.stringify({ | |
| uiScale, text, mode, vdStates, language, | |
| isSidebarCollapsed, sidebarTab, | |
| dubJobId, dubFilename, dubDuration, dubSegments, | |
| dubLang, dubLangCode, dubTracks, dubStep, dubTranscript, | |
| exportTracks, preserveBg, defaultTrack, exportHistory, | |
| speed, steps, cfg, denoise, showOverrides | |
| })); | |
| }, [uiScale, text, mode, vdStates, language, isSidebarCollapsed, sidebarTab, | |
| dubJobId, dubFilename, dubDuration, dubSegments, dubLang, dubLangCode, | |
| dubTracks, dubStep, dubTranscript, exportTracks, preserveBg, defaultTrack, | |
| exportHistory, speed, steps, cfg, denoise, showOverrides | |
| ]); | |
| return { | |
| profiles, history, dubHistory, studioProjects, exportHistory, | |
| showOverrides, setShowOverrides, | |
| sysStats, modelStatus, | |
| loadProfiles, loadHistory, loadDubHistory, loadProjects, loadExportHistory, | |
| }; | |
| } | |