diff --git a/tests/test_h3_window_planner.py b/tests/test_h3_window_planner.py index e22bccd6..d6f68d3f 100644 --- a/tests/test_h3_window_planner.py +++ b/tests/test_h3_window_planner.py @@ -381,7 +381,10 @@ def test_ui_and_runtime_use_explicit_prompt_arrays(self): handler = (APP / "wgp.py").read_text(encoding="utf-8") launch = (APP / "_launch_runtime.py").read_text(encoding="utf-8") llm_router = (APP / "routers" / "llm.py").read_text(encoding="utf-8") - store = (ROOT / "ui" / "src" / "stores" / "useStore.ts").read_text(encoding="utf-8") + store = ( + (ROOT / "ui" / "src" / "stores" / "useStore.ts").read_text(encoding="utf-8") + + (ROOT / "ui" / "src" / "stores" / "llmSlice.ts").read_text(encoding="utf-8") + ) advanced = (ROOT / "ui" / "src" / "components" / "Sidebar" / "AdvancedSettings.tsx").read_text(encoding="utf-8") prompt_input = (ROOT / "ui" / "src" / "components" / "Sidebar" / "PromptInput.tsx").read_text(encoding="utf-8") main_content = (ROOT / "ui" / "src" / "components" / "MainContent" / "MainContent.tsx").read_text(encoding="utf-8") diff --git a/tests/test_minimax_h3.py b/tests/test_minimax_h3.py index 4caaf6c0..f92dbb49 100644 --- a/tests/test_minimax_h3.py +++ b/tests/test_minimax_h3.py @@ -37,6 +37,7 @@ _FULL_DEFAULT_PATH = _APP / "defaults" / "minimax_h3_full.json" _REF2VA_FULL_DEFAULT_PATH = _APP / "defaults" / "minimax_h3_ref2va_full.json" _STORE_PATH = _ROOT / "ui" / "src" / "stores" / "useStore.ts" +_LLM_SLICE_PATH = _ROOT / "ui" / "src" / "stores" / "llmSlice.ts" _PROMPT_INPUT_PATH = _ROOT / "ui" / "src" / "components" / "Sidebar" / "PromptInput.tsx" _DURATION_SLIDER_PATH = _ROOT / "ui" / "src" / "components" / "Sidebar" / "DurationSlider.tsx" _ADVANCED_SETTINGS_PATH = _ROOT / "ui" / "src" / "components" / "Sidebar" / "AdvancedSettings.tsx" @@ -985,6 +986,7 @@ def test_omni_reference_request_and_ui_are_wired_end_to_end(self): main = _read(_MAIN_PATH) wgp = _read(_WGP_PATH) store = _read(_STORE_PATH) + llm_slice = _read(_LLM_SLICE_PATH) section = _read(_OMNI_REFERENCE_SECTION_PATH) generate_button = _read(_GENERATE_BUTTON_PATH) self.assertIn('if _generation_model_def.get("omni_reference"):', launch) @@ -998,9 +1000,9 @@ def test_omni_reference_request_and_ui_are_wired_end_to_end(self): self.assertIn("num_condition_video_rows", main) self.assertIn("const omniReferences = state.params.minimax_h3_references ?? []", store) self.assertIn("delete params.minimax_h3_references", store) - self.assertIn("reference_context: referenceContext", store) - self.assertIn("intent=AUDIO REUSE / PERFORMANCE DRIVER", store) - self.assertIn("intent=VOICE REFERENCE", store) + self.assertIn("reference_context: media.referenceContext", llm_slice) + self.assertIn("intent=AUDIO REUSE / PERFORMANCE DRIVER", llm_slice) + self.assertIn("intent=VOICE REFERENCE", llm_slice) self.assertIn('draggable', section) self.assertIn("Include soundtrack", section) self.assertIn("Attach audio", section) @@ -1016,11 +1018,11 @@ def test_omni_reference_request_and_ui_are_wired_end_to_end(self): ) def test_non_sliding_h3_enhance_request_stays_one_timeline(self): - store = _read(_STORE_PATH) + llm_slice = _read(_LLM_SLICE_PATH) prompt_input = _read(_PROMPT_INPUT_PATH) expected = "supportsSlidingWindows = state.modelOptions?.sliding_window === true" - self.assertIn(expected, store) - self.assertIn("supportsSlidingWindows && stride > 0", store) + self.assertIn(expected, llm_slice) + self.assertIn("supportsSlidingWindows && stride > 0", llm_slice) self.assertIn("supportsSlidingWindows = modelOptions?.sliding_window === true", prompt_input) self.assertIn("supportsSlidingWindows && stride > 0", prompt_input) diff --git a/ui/src/stores/llmSlice.ts b/ui/src/stores/llmSlice.ts new file mode 100644 index 00000000..f260468d --- /dev/null +++ b/ui/src/stores/llmSlice.ts @@ -0,0 +1,327 @@ +import * as api from '../api/client' +import type { + GenerateParams, + GenerationMode, + H3WindowPlan, + LlmModelOption, + LlmStatus, + ModelOptions, + ProductionProfile, + ServicesConfig, +} from '../types' +import type { SliceCreator, SliceSet } from './storeApi' + +export const UNLOADED_LLM_STATUS: LlmStatus = { + loaded: false, + model_id: null, + device: null, + provider: '', +} + +export type LlmSlice = { + llmStatus: LlmStatus | null + llmLoading: boolean + llmModels: LlmModelOption[] + loadLlmStatus: () => Promise + loadLlmModels: () => Promise + loadLlm: () => Promise + unloadLlm: () => Promise + isEnhancing: boolean + enhancePrompt: (ttsMode?: string) => Promise + h3WindowPlan: H3WindowPlan | null + updateH3WindowPrompt: (index: number, prompt: string) => void + clearH3WindowPlan: () => void +} + +/** Host fields the LLM drawer reads or writes without owning generate. */ +export type LlmSliceHost = LlmSlice & { + servicesConfig: ServicesConfig | null + productionProfile: ProductionProfile + params: GenerateParams + generationMode: GenerationMode + startImage: File | null + endImage: File | null + imageRefs: File[] + modelOptions: ModelOptions | null + slidingWindowOverlap: number + durationSeconds: number + slidingWindowSeconds: number + slidingWindowLocked: boolean + ttsVoiceCount: number + _autoParseSpkeakerNames: (text: string, force?: boolean) => void +} + +type EnhanceMedia = { + imagePaths: string[] + referenceContext?: string +} + +type EnhanceWindowLayout = { + fps: number + discardFrames: number + windowCount: number + shouldPlanH3Windows: boolean +} + +function omniAudioLabel(note: string, intent: string, audioIndex: number): string { + if (intent === 'drive') { + return `