@happyvertical/smrt-svelte 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +317 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +185 -0
- package/dist/Provider.svelte +204 -0
- package/dist/Provider.svelte.d.ts +73 -0
- package/dist/Provider.svelte.d.ts.map +1 -0
- package/dist/__tests__/app-state.test.js +156 -0
- package/dist/__tests__/warm-clients.test.js +186 -0
- package/dist/browser-ai/adapters/llm/factory.d.ts +38 -0
- package/dist/browser-ai/adapters/llm/factory.d.ts.map +1 -0
- package/dist/browser-ai/adapters/llm/factory.js +91 -0
- package/dist/browser-ai/adapters/llm/index.d.ts +7 -0
- package/dist/browser-ai/adapters/llm/index.d.ts.map +1 -0
- package/dist/browser-ai/adapters/llm/index.js +6 -0
- package/dist/browser-ai/adapters/llm/types.d.ts +182 -0
- package/dist/browser-ai/adapters/llm/types.d.ts.map +1 -0
- package/dist/browser-ai/adapters/llm/types.js +43 -0
- package/dist/browser-ai/adapters/llm/webllm.d.ts +33 -0
- package/dist/browser-ai/adapters/llm/webllm.d.ts.map +1 -0
- package/dist/browser-ai/adapters/llm/webllm.js +225 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts +31 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/browser-speech.js +217 -0
- package/dist/browser-ai/adapters/stt/factory.d.ts +49 -0
- package/dist/browser-ai/adapters/stt/factory.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/factory.js +110 -0
- package/dist/browser-ai/adapters/stt/index.d.ts +9 -0
- package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/index.js +8 -0
- package/dist/browser-ai/adapters/stt/types.d.ts +154 -0
- package/dist/browser-ai/adapters/stt/types.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/types.js +4 -0
- package/dist/browser-ai/adapters/stt/whisper-cpp.d.ts +46 -0
- package/dist/browser-ai/adapters/stt/whisper-cpp.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/whisper-cpp.js +348 -0
- package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts +51 -0
- package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/whisper-wasm.js +380 -0
- package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts +42 -0
- package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts.map +1 -0
- package/dist/browser-ai/adapters/tts/browser-synthesis.js +235 -0
- package/dist/browser-ai/adapters/tts/factory.d.ts +53 -0
- package/dist/browser-ai/adapters/tts/factory.d.ts.map +1 -0
- package/dist/browser-ai/adapters/tts/factory.js +92 -0
- package/dist/browser-ai/adapters/tts/index.d.ts +7 -0
- package/dist/browser-ai/adapters/tts/index.d.ts.map +1 -0
- package/dist/browser-ai/adapters/tts/index.js +6 -0
- package/dist/browser-ai/adapters/tts/types.d.ts +140 -0
- package/dist/browser-ai/adapters/tts/types.d.ts.map +1 -0
- package/dist/browser-ai/adapters/tts/types.js +4 -0
- package/dist/browser-ai/capabilities/detector.d.ts +38 -0
- package/dist/browser-ai/capabilities/detector.d.ts.map +1 -0
- package/dist/browser-ai/capabilities/detector.js +211 -0
- package/dist/browser-ai/core/errors.d.ts +62 -0
- package/dist/browser-ai/core/errors.d.ts.map +1 -0
- package/dist/browser-ai/core/errors.js +92 -0
- package/dist/browser-ai/core/index.d.ts +6 -0
- package/dist/browser-ai/core/index.d.ts.map +1 -0
- package/dist/browser-ai/core/index.js +5 -0
- package/dist/browser-ai/core/types.d.ts +115 -0
- package/dist/browser-ai/core/types.d.ts.map +1 -0
- package/dist/browser-ai/core/types.js +34 -0
- package/dist/browser-ai/index.d.ts +12 -0
- package/dist/browser-ai/index.d.ts.map +1 -0
- package/dist/browser-ai/index.js +16 -0
- package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte +77 -0
- package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte.d.ts +16 -0
- package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte.d.ts.map +1 -0
- package/dist/browser-ai/svelte/components/CapabilityGate.svelte +57 -0
- package/dist/browser-ai/svelte/components/CapabilityGate.svelte.d.ts +15 -0
- package/dist/browser-ai/svelte/components/CapabilityGate.svelte.d.ts.map +1 -0
- package/dist/browser-ai/svelte/components/DownloadProgress.svelte +141 -0
- package/dist/browser-ai/svelte/components/DownloadProgress.svelte.d.ts +15 -0
- package/dist/browser-ai/svelte/components/DownloadProgress.svelte.d.ts.map +1 -0
- package/dist/browser-ai/svelte/components/STTTest.svelte +379 -0
- package/dist/browser-ai/svelte/components/STTTest.svelte.d.ts +9 -0
- package/dist/browser-ai/svelte/components/STTTest.svelte.d.ts.map +1 -0
- package/dist/browser-ai/svelte/components/VoiceInput.svelte +200 -0
- package/dist/browser-ai/svelte/components/VoiceInput.svelte.d.ts +16 -0
- package/dist/browser-ai/svelte/components/VoiceInput.svelte.d.ts.map +1 -0
- package/dist/browser-ai/svelte/index.d.ts +15 -0
- package/dist/browser-ai/svelte/index.d.ts.map +1 -0
- package/dist/browser-ai/svelte/index.js +28 -0
- package/dist/browser-ai/ui.d.ts +16 -0
- package/dist/browser-ai/ui.d.ts.map +1 -0
- package/dist/browser-ai/ui.js +67 -0
- package/dist/components/admin/AgentAdminPanel.svelte +111 -0
- package/dist/components/admin/AgentAdminPanel.svelte.d.ts +25 -0
- package/dist/components/admin/AgentAdminPanel.svelte.d.ts.map +1 -0
- package/dist/components/admin/AgentAdminTabs.svelte +280 -0
- package/dist/components/admin/AgentAdminTabs.svelte.d.ts +23 -0
- package/dist/components/admin/AgentAdminTabs.svelte.d.ts.map +1 -0
- package/dist/components/admin/AgentSettingsShell.svelte +257 -0
- package/dist/components/admin/AgentSettingsShell.svelte.d.ts +33 -0
- package/dist/components/admin/AgentSettingsShell.svelte.d.ts.map +1 -0
- package/dist/components/admin/index.d.ts +5 -0
- package/dist/components/admin/index.d.ts.map +1 -0
- package/dist/components/admin/index.js +6 -0
- package/dist/components/forms/AddressInput.svelte +500 -0
- package/dist/components/forms/AddressInput.svelte.d.ts +36 -0
- package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/CheckboxInput.svelte +208 -0
- package/dist/components/forms/CheckboxInput.svelte.d.ts +20 -0
- package/dist/components/forms/CheckboxInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/DateRangeInput.svelte +628 -0
- package/dist/components/forms/DateRangeInput.svelte.d.ts +33 -0
- package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/DateTimeInput.svelte +521 -0
- package/dist/components/forms/DateTimeInput.svelte.d.ts +24 -0
- package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/FileUpload.svelte +358 -0
- package/dist/components/forms/FileUpload.svelte.d.ts +22 -0
- package/dist/components/forms/FileUpload.svelte.d.ts.map +1 -0
- package/dist/components/forms/Form.svelte +771 -0
- package/dist/components/forms/Form.svelte.d.ts +26 -0
- package/dist/components/forms/Form.svelte.d.ts.map +1 -0
- package/dist/components/forms/FormGroup.svelte +86 -0
- package/dist/components/forms/FormGroup.svelte.d.ts +13 -0
- package/dist/components/forms/FormGroup.svelte.d.ts.map +1 -0
- package/dist/components/forms/FormMicButton.svelte +179 -0
- package/dist/components/forms/FormMicButton.svelte.d.ts +10 -0
- package/dist/components/forms/FormMicButton.svelte.d.ts.map +1 -0
- package/dist/components/forms/Input.svelte +83 -0
- package/dist/components/forms/Input.svelte.d.ts +9 -0
- package/dist/components/forms/Input.svelte.d.ts.map +1 -0
- package/dist/components/forms/MeasurementInput.svelte +505 -0
- package/dist/components/forms/MeasurementInput.svelte.d.ts +35 -0
- package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/MoneyInput.svelte +412 -0
- package/dist/components/forms/MoneyInput.svelte.d.ts +30 -0
- package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/NumberInput.svelte +310 -0
- package/dist/components/forms/NumberInput.svelte.d.ts +28 -0
- package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/PhoneInput.svelte +530 -0
- package/dist/components/forms/PhoneInput.svelte.d.ts +22 -0
- package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/SearchInput.svelte +358 -0
- package/dist/components/forms/SearchInput.svelte.d.ts +33 -0
- package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/Select.svelte +83 -0
- package/dist/components/forms/Select.svelte.d.ts +11 -0
- package/dist/components/forms/Select.svelte.d.ts.map +1 -0
- package/dist/components/forms/SelectInput.svelte +254 -0
- package/dist/components/forms/SelectInput.svelte.d.ts +25 -0
- package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/TextInput.svelte +415 -0
- package/dist/components/forms/TextInput.svelte.d.ts +26 -0
- package/dist/components/forms/TextInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/Textarea.svelte +85 -0
- package/dist/components/forms/Textarea.svelte.d.ts +10 -0
- package/dist/components/forms/Textarea.svelte.d.ts.map +1 -0
- package/dist/components/forms/TextareaInput.svelte +386 -0
- package/dist/components/forms/TextareaInput.svelte.d.ts +26 -0
- package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/Toggle.svelte +217 -0
- package/dist/components/forms/Toggle.svelte.d.ts +37 -0
- package/dist/components/forms/Toggle.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/AddressInput.behavior.test.js +122 -0
- package/dist/components/forms/__tests__/CheckboxInput.test.js +92 -0
- package/dist/components/forms/__tests__/DateRangeInput.behavior.test.js +135 -0
- package/dist/components/forms/__tests__/DateTimeInput.behavior.test.js +103 -0
- package/dist/components/forms/__tests__/FileUpload.test.js +90 -0
- package/dist/components/forms/__tests__/Form.behavior.test.js +137 -0
- package/dist/components/forms/__tests__/Form.test.js +58 -0
- package/dist/components/forms/__tests__/FormGroup.test.js +48 -0
- package/dist/components/forms/__tests__/FormMicButton.test.js +86 -0
- package/dist/components/forms/__tests__/Input.test.js +49 -0
- package/dist/components/forms/__tests__/MeasurementInput.behavior.test.js +129 -0
- package/dist/components/forms/__tests__/MoneyInput.behavior.test.js +124 -0
- package/dist/components/forms/__tests__/NumberInput.behavior.test.js +141 -0
- package/dist/components/forms/__tests__/PhoneInput.behavior.test.js +96 -0
- package/dist/components/forms/__tests__/SearchInput.test.js +79 -0
- package/dist/components/forms/__tests__/Select.test.js +37 -0
- package/dist/components/forms/__tests__/SelectInput.behavior.test.js +132 -0
- package/dist/components/forms/__tests__/TextInput.behavior.test.js +131 -0
- package/dist/components/forms/__tests__/Textarea.test.js +39 -0
- package/dist/components/forms/__tests__/TextareaInput.behavior.test.js +96 -0
- package/dist/components/forms/__tests__/Toggle.test.js +87 -0
- package/dist/components/forms/__tests__/composite-inputs-a11y.test.js +69 -0
- package/dist/components/forms/__tests__/form-group-input.fixture.svelte +16 -0
- package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +33 -0
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +12 -0
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/rich-inputs-a11y.test.js +87 -0
- package/dist/components/forms/index.d.ts +25 -0
- package/dist/components/forms/index.d.ts.map +1 -0
- package/dist/components/forms/index.js +25 -0
- package/dist/components/forms/types.d.ts +33 -0
- package/dist/components/forms/types.d.ts.map +1 -0
- package/dist/components/forms/types.js +4 -0
- package/dist/components/module/ModulePanel.svelte +134 -0
- package/dist/components/module/ModulePanel.svelte.d.ts +22 -0
- package/dist/components/module/ModulePanel.svelte.d.ts.map +1 -0
- package/dist/components/module/index.d.ts +5 -0
- package/dist/components/module/index.d.ts.map +1 -0
- package/dist/components/module/index.js +4 -0
- package/dist/components/workspace/Breadcrumbs.svelte +141 -0
- package/dist/components/workspace/Breadcrumbs.svelte.d.ts +21 -0
- package/dist/components/workspace/Breadcrumbs.svelte.d.ts.map +1 -0
- package/dist/components/workspace/NavTree.svelte +354 -0
- package/dist/components/workspace/NavTree.svelte.d.ts +45 -0
- package/dist/components/workspace/NavTree.svelte.d.ts.map +1 -0
- package/dist/components/workspace/README.md +34 -0
- package/dist/components/workspace/RoleShell.svelte +309 -0
- package/dist/components/workspace/RoleShell.svelte.d.ts +91 -0
- package/dist/components/workspace/RoleShell.svelte.d.ts.map +1 -0
- package/dist/components/workspace/WorkspaceShell.svelte +951 -0
- package/dist/components/workspace/WorkspaceShell.svelte.d.ts +112 -0
- package/dist/components/workspace/WorkspaceShell.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/RoleShell.test.js +772 -0
- package/dist/components/workspace/__tests__/WorkspaceShell.test.js +630 -0
- package/dist/components/workspace/__tests__/breadcrumbs-helpers.test.js +141 -0
- package/dist/components/workspace/__tests__/context-forwarding-harness.svelte +45 -0
- package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts +21 -0
- package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/define-tools-dock.test.js +1010 -0
- package/dist/components/workspace/__tests__/harness.svelte +25 -0
- package/dist/components/workspace/__tests__/harness.svelte.d.ts +14 -0
- package/dist/components/workspace/__tests__/harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/index.test.js +37 -0
- package/dist/components/workspace/__tests__/manifest-nav-helpers.test.js +24 -0
- package/dist/components/workspace/__tests__/manifest-nav.test.js +599 -0
- package/dist/components/workspace/__tests__/nav-helpers.test.js +95 -0
- package/dist/components/workspace/__tests__/render-harness.svelte +66 -0
- package/dist/components/workspace/__tests__/render-harness.svelte.d.ts +32 -0
- package/dist/components/workspace/__tests__/render-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/render-tools-dock.test.js +243 -0
- package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte +58 -0
- package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts +16 -0
- package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte +41 -0
- package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts +13 -0
- package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/test-icon.svelte +17 -0
- package/dist/components/workspace/__tests__/test-icon.svelte.d.ts +19 -0
- package/dist/components/workspace/__tests__/test-icon.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte +38 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts +22 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts +65 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.js +115 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts +15 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.js +7 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture.test.js +115 -0
- package/dist/components/workspace/__tests__/use-harness-orphan.svelte +9 -0
- package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts +19 -0
- package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/use-harness.svelte +23 -0
- package/dist/components/workspace/__tests__/use-harness.svelte.d.ts +8 -0
- package/dist/components/workspace/__tests__/use-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/use-tools-dock.test.js +33 -0
- package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte +43 -0
- package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts +11 -0
- package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/breadcrumbs-helpers.d.ts +44 -0
- package/dist/components/workspace/breadcrumbs-helpers.d.ts.map +1 -0
- package/dist/components/workspace/breadcrumbs-helpers.js +88 -0
- package/dist/components/workspace/index.d.ts +16 -0
- package/dist/components/workspace/index.d.ts.map +1 -0
- package/dist/components/workspace/index.js +14 -0
- package/dist/components/workspace/manifest-nav.d.ts +200 -0
- package/dist/components/workspace/manifest-nav.d.ts.map +1 -0
- package/dist/components/workspace/manifest-nav.js +408 -0
- package/dist/components/workspace/nav-helpers.d.ts +36 -0
- package/dist/components/workspace/nav-helpers.d.ts.map +1 -0
- package/dist/components/workspace/nav-helpers.js +60 -0
- package/dist/components/workspace/server/__tests__/compose-availability.test.js +383 -0
- package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts +78 -0
- package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts.map +1 -0
- package/dist/components/workspace/server/__tests__/typed-context-fixture.js +104 -0
- package/dist/components/workspace/server/compose-availability.d.ts +73 -0
- package/dist/components/workspace/server/compose-availability.d.ts.map +1 -0
- package/dist/components/workspace/server/compose-availability.js +114 -0
- package/dist/components/workspace/server/index.d.ts +13 -0
- package/dist/components/workspace/server/index.d.ts.map +1 -0
- package/dist/components/workspace/server/index.js +11 -0
- package/dist/components/workspace/server/types.d.ts +108 -0
- package/dist/components/workspace/server/types.d.ts.map +1 -0
- package/dist/components/workspace/server/types.js +11 -0
- package/dist/components/workspace/tools-dock/ToolsDock.svelte +565 -0
- package/dist/components/workspace/tools-dock/ToolsDock.svelte.d.ts +14 -0
- package/dist/components/workspace/tools-dock/ToolsDock.svelte.d.ts.map +1 -0
- package/dist/components/workspace/tools-dock/define-tools-dock.svelte.d.ts +143 -0
- package/dist/components/workspace/tools-dock/define-tools-dock.svelte.d.ts.map +1 -0
- package/dist/components/workspace/tools-dock/define-tools-dock.svelte.js +487 -0
- package/dist/components/workspace/tools-dock/use-tools-dock.d.ts +41 -0
- package/dist/components/workspace/tools-dock/use-tools-dock.d.ts.map +1 -0
- package/dist/components/workspace/tools-dock/use-tools-dock.js +50 -0
- package/dist/components/workspace/types.d.ts +372 -0
- package/dist/components/workspace/types.d.ts.map +1 -0
- package/dist/components/workspace/types.js +6 -0
- package/dist/hooks/index.d.ts +11 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +10 -0
- package/dist/hooks/useAppState.svelte.d.ts +46 -0
- package/dist/hooks/useAppState.svelte.d.ts.map +1 -0
- package/dist/hooks/useAppState.svelte.js +59 -0
- package/dist/hooks/useAuth.svelte.d.ts +41 -0
- package/dist/hooks/useAuth.svelte.d.ts.map +1 -0
- package/dist/hooks/useAuth.svelte.js +43 -0
- package/dist/hooks/useLLM.svelte.d.ts +69 -0
- package/dist/hooks/useLLM.svelte.d.ts.map +1 -0
- package/dist/hooks/useLLM.svelte.js +85 -0
- package/dist/hooks/useSTT.svelte.d.ts +68 -0
- package/dist/hooks/useSTT.svelte.d.ts.map +1 -0
- package/dist/hooks/useSTT.svelte.js +97 -0
- package/dist/hooks/useSocket.svelte.d.ts +45 -0
- package/dist/hooks/useSocket.svelte.d.ts.map +1 -0
- package/dist/hooks/useSocket.svelte.js +54 -0
- package/dist/hooks/useTTS.svelte.d.ts +65 -0
- package/dist/hooks/useTTS.svelte.d.ts.map +1 -0
- package/dist/hooks/useTTS.svelte.js +93 -0
- package/dist/hooks/useTheme.d.ts +13 -0
- package/dist/hooks/useTheme.d.ts.map +1 -0
- package/dist/hooks/useTheme.js +16 -0
- package/dist/i18n/__tests__/server.spec.js +50 -0
- package/dist/i18n/server.d.ts +47 -0
- package/dist/i18n/server.d.ts.map +1 -0
- package/dist/i18n/server.js +58 -0
- package/dist/i18n/strings.forms.d.ts +33 -0
- package/dist/i18n/strings.forms.d.ts.map +1 -0
- package/dist/i18n/strings.forms.js +54 -0
- package/dist/i18n/strings.workspace.d.ts +34 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -0
- package/dist/i18n/strings.workspace.js +40 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/state/__tests__/warm-clients.test.js +40 -0
- package/dist/state/app-state.d.ts +308 -0
- package/dist/state/app-state.d.ts.map +1 -0
- package/dist/state/app-state.js +64 -0
- package/dist/state/app-state.svelte.d.ts +196 -0
- package/dist/state/app-state.svelte.d.ts.map +1 -0
- package/dist/state/app-state.svelte.js +774 -0
- package/dist/state/context.d.ts +23 -0
- package/dist/state/context.d.ts.map +1 -0
- package/dist/state/context.js +32 -0
- package/dist/state/form-context.d.ts +59 -0
- package/dist/state/form-context.d.ts.map +1 -0
- package/dist/state/form-context.js +31 -0
- package/dist/state/form-group-context.d.ts +13 -0
- package/dist/state/form-group-context.d.ts.map +1 -0
- package/dist/state/form-group-context.js +28 -0
- package/dist/state/index.d.ts +9 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +8 -0
- package/dist/state/warm-clients.d.ts +136 -0
- package/dist/state/warm-clients.d.ts.map +1 -0
- package/dist/state/warm-clients.js +231 -0
- package/package.json +137 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte 5 reactive app state
|
|
3
|
+
*
|
|
4
|
+
* Uses runes ($state, $derived) for reactivity.
|
|
5
|
+
* This is the Svelte-specific binding layer on top of app-state.ts
|
|
6
|
+
*/
|
|
7
|
+
import { canEnableSmrtMode, detectCapabilities, getLLM, getSTT, getTTS, } from '../browser-ai/index.js';
|
|
8
|
+
import { createInitialState, } from './app-state.js';
|
|
9
|
+
import { getCachedLLM, getCachedSTT, getCachedTTS, setCachedLLM, setCachedSTT, setCachedTTS, updateLLMCacheState, updateSTTCacheState, updateTTSCacheState, } from './warm-clients.js';
|
|
10
|
+
/**
|
|
11
|
+
* Reactive app state manager for Svelte 5
|
|
12
|
+
*/
|
|
13
|
+
export class SmrtAppStateManager {
|
|
14
|
+
// Reactive state using $state rune
|
|
15
|
+
_state = $state(createInitialState());
|
|
16
|
+
// Options
|
|
17
|
+
options;
|
|
18
|
+
// AI configuration
|
|
19
|
+
_aiConfig = null;
|
|
20
|
+
_preloadScheduled = false;
|
|
21
|
+
_idleCallbackId = null;
|
|
22
|
+
// Socket management
|
|
23
|
+
_socket = null;
|
|
24
|
+
_socketConfig = null;
|
|
25
|
+
_reconnectTimeout = null;
|
|
26
|
+
// Track adapters we've already subscribed to (prevents duplicate listeners)
|
|
27
|
+
_subscribedSTTAdapters = new WeakSet();
|
|
28
|
+
_subscribedTTSAdapters = new WeakSet();
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.options = options;
|
|
31
|
+
this._aiConfig = options.ai ?? null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the current socket configuration (for reconnection)
|
|
35
|
+
*/
|
|
36
|
+
get socketConfig() {
|
|
37
|
+
return this._socketConfig;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the current state (readonly)
|
|
41
|
+
*/
|
|
42
|
+
get state() {
|
|
43
|
+
return this._state;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get the current AI configuration
|
|
47
|
+
*/
|
|
48
|
+
get aiConfig() {
|
|
49
|
+
return this._aiConfig;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get the current AI loading state
|
|
53
|
+
*/
|
|
54
|
+
get aiLoading() {
|
|
55
|
+
return this._state.aiLoading;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Initialize the app state
|
|
59
|
+
* Detects capabilities and sets initial mode
|
|
60
|
+
* Note: This is a no-op during SSR as browser-ai requires browser environment
|
|
61
|
+
*/
|
|
62
|
+
async initialize() {
|
|
63
|
+
if (this._state.initialized)
|
|
64
|
+
return;
|
|
65
|
+
// Skip during SSR - browser-ai APIs require browser environment
|
|
66
|
+
if (typeof window === 'undefined') {
|
|
67
|
+
this._state.initialized = true;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Detect capabilities
|
|
71
|
+
const capabilities = detectCapabilities();
|
|
72
|
+
this._state.capabilities = capabilities;
|
|
73
|
+
// Notify callback
|
|
74
|
+
this.options.onCapabilitiesDetected?.(capabilities);
|
|
75
|
+
// Determine initial mode
|
|
76
|
+
if (this.options.initialMode) {
|
|
77
|
+
this._state.mode = this.options.initialMode;
|
|
78
|
+
this._state.modeSource = 'explicit';
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Auto-detect based on capabilities and preferences
|
|
82
|
+
const autoEnable = this._state.session.preferences.autoEnableSmrt ?? true;
|
|
83
|
+
if (autoEnable && canEnableSmrtMode(capabilities)) {
|
|
84
|
+
this._state.mode = 'smrt';
|
|
85
|
+
this._state.modeSource = 'auto';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Apply initial session if provided
|
|
89
|
+
if (this.options.session) {
|
|
90
|
+
this._state.session = {
|
|
91
|
+
...this._state.session,
|
|
92
|
+
...this.options.session,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
this._state.initialized = true;
|
|
96
|
+
// Schedule AI preloading based on strategy
|
|
97
|
+
this.schedulePreload();
|
|
98
|
+
}
|
|
99
|
+
// === AI Preloading Methods ===
|
|
100
|
+
/**
|
|
101
|
+
* Set or update AI configuration
|
|
102
|
+
*/
|
|
103
|
+
setAIConfig(config) {
|
|
104
|
+
this._aiConfig = config;
|
|
105
|
+
// Cancel any pending preload scheduling so we can re-schedule with new config
|
|
106
|
+
if (this._idleCallbackId !== null &&
|
|
107
|
+
typeof cancelIdleCallback !== 'undefined') {
|
|
108
|
+
cancelIdleCallback(this._idleCallbackId);
|
|
109
|
+
this._idleCallbackId = null;
|
|
110
|
+
}
|
|
111
|
+
this._preloadScheduled = false;
|
|
112
|
+
// Re-schedule preloading with new config
|
|
113
|
+
this.schedulePreload();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Schedule preloading based on the configured strategy
|
|
117
|
+
*/
|
|
118
|
+
schedulePreload() {
|
|
119
|
+
if (!this._aiConfig || this._preloadScheduled)
|
|
120
|
+
return;
|
|
121
|
+
const strategy = this._aiConfig.preload ?? 'idle';
|
|
122
|
+
if (strategy === 'none') {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (strategy === 'eager') {
|
|
126
|
+
// Preload immediately
|
|
127
|
+
this._preloadScheduled = true;
|
|
128
|
+
this.executePreload();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (strategy === 'idle') {
|
|
132
|
+
// Schedule during browser idle time
|
|
133
|
+
this._preloadScheduled = true;
|
|
134
|
+
if (typeof requestIdleCallback !== 'undefined') {
|
|
135
|
+
this._idleCallbackId = requestIdleCallback(() => this.executePreload(), { timeout: 5000 });
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Fallback for browsers without requestIdleCallback
|
|
139
|
+
setTimeout(() => this.executePreload(), 100);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// 'on-visible' is handled by components calling triggerPreload()
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Trigger preloading (called by components for 'on-visible' strategy)
|
|
147
|
+
*/
|
|
148
|
+
triggerPreload() {
|
|
149
|
+
if (!this._aiConfig || this._preloadScheduled)
|
|
150
|
+
return;
|
|
151
|
+
if (this._aiConfig.preload !== 'on-visible')
|
|
152
|
+
return;
|
|
153
|
+
this._preloadScheduled = true;
|
|
154
|
+
this.executePreload();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Execute the preloading of configured adapters
|
|
158
|
+
*/
|
|
159
|
+
async executePreload() {
|
|
160
|
+
if (!this._aiConfig)
|
|
161
|
+
return;
|
|
162
|
+
const adaptersToLoad = [];
|
|
163
|
+
// Determine which adapters need loading
|
|
164
|
+
if (this._aiConfig.stt?.enabled !== false && this._aiConfig.stt?.type) {
|
|
165
|
+
adaptersToLoad.push(`stt:${this._aiConfig.stt.type}`);
|
|
166
|
+
}
|
|
167
|
+
if (this._aiConfig.tts?.enabled !== false && this._aiConfig.tts?.type) {
|
|
168
|
+
adaptersToLoad.push(`tts:${this._aiConfig.tts.type}`);
|
|
169
|
+
}
|
|
170
|
+
if (this._aiConfig.llm?.enabled !== false && this._aiConfig.llm?.type) {
|
|
171
|
+
const modelKey = this._aiConfig.llm.model
|
|
172
|
+
? `${this._aiConfig.llm.type}:${this._aiConfig.llm.model}`
|
|
173
|
+
: this._aiConfig.llm.type;
|
|
174
|
+
adaptersToLoad.push(`llm:${modelKey}`);
|
|
175
|
+
}
|
|
176
|
+
if (adaptersToLoad.length === 0) {
|
|
177
|
+
this.updateLoadingState({ phase: 'idle' });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.updateLoadingState({
|
|
181
|
+
phase: 'checking',
|
|
182
|
+
message: 'Checking AI capabilities...',
|
|
183
|
+
loaded: [],
|
|
184
|
+
failed: [],
|
|
185
|
+
});
|
|
186
|
+
// Load adapters sequentially to show progress
|
|
187
|
+
for (const adapterKey of adaptersToLoad) {
|
|
188
|
+
const [category, type] = adapterKey.split(':');
|
|
189
|
+
this.updateLoadingState({
|
|
190
|
+
phase: 'downloading',
|
|
191
|
+
currentAdapter: type,
|
|
192
|
+
message: `Loading ${type}...`,
|
|
193
|
+
});
|
|
194
|
+
try {
|
|
195
|
+
if (category === 'stt') {
|
|
196
|
+
const sttConfig = this._aiConfig.stt;
|
|
197
|
+
await this.initializeSTT({
|
|
198
|
+
type: type,
|
|
199
|
+
allowLocalModels: sttConfig?.allowLocalModels,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
else if (category === 'tts') {
|
|
203
|
+
await this.initializeTTS({ type: type });
|
|
204
|
+
}
|
|
205
|
+
else if (category === 'llm') {
|
|
206
|
+
const llmConfig = this._aiConfig.llm;
|
|
207
|
+
if (llmConfig) {
|
|
208
|
+
await this.initializeLLM(llmConfig.model, { type: llmConfig.type });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
this.updateLoadingState({
|
|
212
|
+
loaded: [...this._state.aiLoading.loaded, type],
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.error(`Failed to preload ${type}:`, error);
|
|
217
|
+
this.updateLoadingState({
|
|
218
|
+
failed: [...this._state.aiLoading.failed, type],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Determine final state
|
|
223
|
+
const hasFailures = this._state.aiLoading.failed.length > 0;
|
|
224
|
+
const allLoaded = this._state.aiLoading.loaded.length === adaptersToLoad.length;
|
|
225
|
+
if (allLoaded) {
|
|
226
|
+
this.updateLoadingState({
|
|
227
|
+
phase: 'ready',
|
|
228
|
+
currentAdapter: null,
|
|
229
|
+
overallProgress: 100,
|
|
230
|
+
message: 'AI ready',
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
else if (hasFailures && this._state.aiLoading.loaded.length === 0) {
|
|
234
|
+
this.updateLoadingState({
|
|
235
|
+
phase: 'error',
|
|
236
|
+
currentAdapter: null,
|
|
237
|
+
message: 'Failed to load AI models',
|
|
238
|
+
error: new Error('All AI adapters failed to load'),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// Partial success
|
|
243
|
+
this.updateLoadingState({
|
|
244
|
+
phase: 'ready',
|
|
245
|
+
currentAdapter: null,
|
|
246
|
+
message: `Loaded with ${this._state.aiLoading.failed.length} failure(s)`,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
this.options.onAILoadingChange?.(this._state.aiLoading);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Update the AI loading state
|
|
253
|
+
*/
|
|
254
|
+
updateLoadingState(updates) {
|
|
255
|
+
this._state.aiLoading = {
|
|
256
|
+
...this._state.aiLoading,
|
|
257
|
+
...updates,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Set the app mode
|
|
262
|
+
*/
|
|
263
|
+
setMode(mode, source = 'toggled') {
|
|
264
|
+
if (this._state.mode === mode)
|
|
265
|
+
return;
|
|
266
|
+
this._state.mode = mode;
|
|
267
|
+
this._state.modeSource = source;
|
|
268
|
+
this.options.onModeChange?.(mode, source);
|
|
269
|
+
// Preload Whisper.cpp when switching to smrt mode
|
|
270
|
+
if (mode === 'smrt') {
|
|
271
|
+
this.initializeSTT({
|
|
272
|
+
type: 'whisper-cpp',
|
|
273
|
+
allowLocalModels: this._aiConfig?.stt?.allowLocalModels,
|
|
274
|
+
}).catch(() => {
|
|
275
|
+
// Error stored in state, don't throw
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Toggle between default and smrt modes
|
|
281
|
+
*/
|
|
282
|
+
toggleMode() {
|
|
283
|
+
const newMode = this._state.mode === 'default' ? 'smrt' : 'default';
|
|
284
|
+
this.setMode(newMode, 'toggled');
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Update user session
|
|
288
|
+
*/
|
|
289
|
+
updateSession(session) {
|
|
290
|
+
this._state.session = {
|
|
291
|
+
...this._state.session,
|
|
292
|
+
...session,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Set user permissions
|
|
297
|
+
*/
|
|
298
|
+
setPermissions(permissions) {
|
|
299
|
+
this._state.session.permissions = permissions;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Check if user has a permission
|
|
303
|
+
*/
|
|
304
|
+
hasPermission(permission) {
|
|
305
|
+
return this._state.session.permissions.includes(permission);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Check if user has all permissions
|
|
309
|
+
*/
|
|
310
|
+
hasAllPermissions(permissions) {
|
|
311
|
+
return permissions.every((p) => this._state.session.permissions.includes(p));
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Check if user has any of the permissions
|
|
315
|
+
*/
|
|
316
|
+
hasAnyPermission(permissions) {
|
|
317
|
+
return permissions.some((p) => this._state.session.permissions.includes(p));
|
|
318
|
+
}
|
|
319
|
+
// === User/Auth Methods ===
|
|
320
|
+
/**
|
|
321
|
+
* Set the current user and permissions
|
|
322
|
+
* Called by SmrtProvider when user prop changes
|
|
323
|
+
*/
|
|
324
|
+
setUser(user, permissions = []) {
|
|
325
|
+
this._state.session.user = user;
|
|
326
|
+
this._state.session.isAuthenticated = user !== null;
|
|
327
|
+
this._state.session.permissions = permissions;
|
|
328
|
+
}
|
|
329
|
+
// === Socket Methods ===
|
|
330
|
+
/**
|
|
331
|
+
* Connect to a WebSocket server
|
|
332
|
+
*/
|
|
333
|
+
connectSocket(config) {
|
|
334
|
+
// Disconnect existing socket if any
|
|
335
|
+
this.disconnectSocket();
|
|
336
|
+
this._socketConfig = config;
|
|
337
|
+
this._state.socket.status = 'connecting';
|
|
338
|
+
this._state.socket.lastError = null;
|
|
339
|
+
try {
|
|
340
|
+
this._socket = new WebSocket(config.url);
|
|
341
|
+
this._socket.onopen = () => {
|
|
342
|
+
this._state.socket.status = 'connected';
|
|
343
|
+
this._state.socket.reconnectAttempts = 0;
|
|
344
|
+
config.onOpen?.();
|
|
345
|
+
};
|
|
346
|
+
this._socket.onmessage = (event) => {
|
|
347
|
+
try {
|
|
348
|
+
const data = JSON.parse(event.data);
|
|
349
|
+
config.onMessage?.(data);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// If not JSON, pass raw data
|
|
353
|
+
config.onMessage?.(event.data);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
this._socket.onclose = (event) => {
|
|
357
|
+
this._state.socket.status = 'disconnected';
|
|
358
|
+
config.onClose?.(event);
|
|
359
|
+
// Attempt reconnection if enabled and not a clean close
|
|
360
|
+
if (!event.wasClean && config.reconnect?.enabled !== false) {
|
|
361
|
+
this.scheduleReconnect();
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
this._socket.onerror = (event) => {
|
|
365
|
+
this._state.socket.lastError = event;
|
|
366
|
+
config.onError?.(event);
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
this._state.socket.status = 'disconnected';
|
|
371
|
+
this._state.socket.lastError = error;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Disconnect from the WebSocket server
|
|
376
|
+
*/
|
|
377
|
+
disconnectSocket() {
|
|
378
|
+
// Clear any pending reconnect
|
|
379
|
+
if (this._reconnectTimeout) {
|
|
380
|
+
clearTimeout(this._reconnectTimeout);
|
|
381
|
+
this._reconnectTimeout = null;
|
|
382
|
+
}
|
|
383
|
+
if (this._socket) {
|
|
384
|
+
this._socket.close();
|
|
385
|
+
this._socket = null;
|
|
386
|
+
}
|
|
387
|
+
this._state.socket.status = 'disconnected';
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Send a message through the WebSocket
|
|
391
|
+
*/
|
|
392
|
+
sendMessage(data) {
|
|
393
|
+
if (this._socket?.readyState === WebSocket.OPEN) {
|
|
394
|
+
this._socket.send(JSON.stringify(data));
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
console.warn('[Socket] Cannot send message - socket not connected');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Schedule a reconnection attempt with exponential backoff
|
|
402
|
+
*/
|
|
403
|
+
scheduleReconnect() {
|
|
404
|
+
if (!this._socketConfig)
|
|
405
|
+
return;
|
|
406
|
+
const { reconnect } = this._socketConfig;
|
|
407
|
+
const maxAttempts = reconnect?.maxAttempts ?? 5;
|
|
408
|
+
const baseDelay = reconnect?.baseDelay ?? 1000;
|
|
409
|
+
if (this._state.socket.reconnectAttempts >= maxAttempts) {
|
|
410
|
+
console.warn('[Socket] Max reconnection attempts reached');
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
this._state.socket.status = 'reconnecting';
|
|
414
|
+
this._state.socket.reconnectAttempts++;
|
|
415
|
+
// Exponential backoff: baseDelay * 2^attempts (capped at 30s)
|
|
416
|
+
const delay = Math.min(baseDelay * 2 ** (this._state.socket.reconnectAttempts - 1), 30000);
|
|
417
|
+
console.log(`[Socket] Reconnecting in ${delay}ms (attempt ${this._state.socket.reconnectAttempts}/${maxAttempts})`);
|
|
418
|
+
this._reconnectTimeout = setTimeout(() => {
|
|
419
|
+
if (this._socketConfig) {
|
|
420
|
+
this.connectSocket(this._socketConfig);
|
|
421
|
+
}
|
|
422
|
+
}, delay);
|
|
423
|
+
}
|
|
424
|
+
// === STT Methods ===
|
|
425
|
+
/**
|
|
426
|
+
* Initialize STT adapter
|
|
427
|
+
* Uses warm client cache to avoid re-downloading models
|
|
428
|
+
*/
|
|
429
|
+
async initializeSTT(options) {
|
|
430
|
+
const requestedType = (options?.type ?? 'browser-speech');
|
|
431
|
+
// Check warm client cache first
|
|
432
|
+
const cached = getCachedSTT(requestedType);
|
|
433
|
+
if (cached && cached.initState === 'ready') {
|
|
434
|
+
// Restore cached adapter to state
|
|
435
|
+
this._state.ai.stt.adapter = cached.adapter;
|
|
436
|
+
this._state.ai.stt.initState = 'ready';
|
|
437
|
+
this._state.ai.stt.error = null;
|
|
438
|
+
this.subscribeToSTTEvents(cached.adapter);
|
|
439
|
+
return cached.adapter;
|
|
440
|
+
}
|
|
441
|
+
// Check if we have a different adapter type loaded
|
|
442
|
+
const currentAdapter = this._state.ai.stt.adapter;
|
|
443
|
+
if (currentAdapter && this._state.ai.stt.initState === 'ready') {
|
|
444
|
+
if (currentAdapter.type === requestedType) {
|
|
445
|
+
return currentAdapter;
|
|
446
|
+
}
|
|
447
|
+
// Type mismatch - dispose old adapter before creating new one
|
|
448
|
+
console.log(`[STT] Switching adapter from ${currentAdapter.type} to ${requestedType}`);
|
|
449
|
+
await currentAdapter.dispose?.();
|
|
450
|
+
}
|
|
451
|
+
this._state.ai.stt.initState = 'initializing';
|
|
452
|
+
this._state.ai.stt.error = null;
|
|
453
|
+
// Update cache state for tracking
|
|
454
|
+
updateSTTCacheState(requestedType, { initState: 'initializing' });
|
|
455
|
+
const onProgress = (progress) => {
|
|
456
|
+
this._state.ai.stt.downloadProgress = progress;
|
|
457
|
+
updateSTTCacheState(requestedType, { downloadProgress: progress });
|
|
458
|
+
// Update overall loading progress
|
|
459
|
+
if (progress.percent > 0) {
|
|
460
|
+
this.updateLoadingState({
|
|
461
|
+
overallProgress: progress.percent,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
try {
|
|
466
|
+
const adapter = await getSTT(options);
|
|
467
|
+
await adapter.ensureInitialized(onProgress);
|
|
468
|
+
// Cache the adapter for future use
|
|
469
|
+
setCachedSTT(requestedType, adapter, 'ready');
|
|
470
|
+
this._state.ai.stt.adapter = adapter;
|
|
471
|
+
this._state.ai.stt.initState = 'ready';
|
|
472
|
+
this._state.ai.stt.downloadProgress = null;
|
|
473
|
+
this.subscribeToSTTEvents(adapter);
|
|
474
|
+
return adapter;
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
478
|
+
this._state.ai.stt.initState = 'error';
|
|
479
|
+
this._state.ai.stt.error = err;
|
|
480
|
+
updateSTTCacheState(requestedType, { initState: 'error', error: err });
|
|
481
|
+
throw error;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Subscribe to STT adapter events
|
|
486
|
+
* Only subscribes once per adapter instance to prevent duplicate listeners
|
|
487
|
+
*/
|
|
488
|
+
subscribeToSTTEvents(adapter) {
|
|
489
|
+
// Prevent duplicate subscriptions
|
|
490
|
+
if (this._subscribedSTTAdapters.has(adapter)) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
this._subscribedSTTAdapters.add(adapter);
|
|
494
|
+
adapter.onResult((result) => {
|
|
495
|
+
if (result.isFinal) {
|
|
496
|
+
// Accumulate final results (for continuous mode where multiple phrases are spoken)
|
|
497
|
+
const existing = this._state.ai.stt.lastResult;
|
|
498
|
+
if (existing) {
|
|
499
|
+
this._state.ai.stt.lastResult = `${existing} ${result.text}`;
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
this._state.ai.stt.lastResult = result.text;
|
|
503
|
+
}
|
|
504
|
+
this._state.ai.stt.interimResult = '';
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
// Interim result - update live
|
|
508
|
+
this._state.ai.stt.interimResult = result.text;
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
adapter.onStart(() => {
|
|
512
|
+
this._state.ai.stt.isListening = true;
|
|
513
|
+
});
|
|
514
|
+
adapter.onEnd(() => {
|
|
515
|
+
this._state.ai.stt.isListening = false;
|
|
516
|
+
});
|
|
517
|
+
adapter.onError((error) => {
|
|
518
|
+
this._state.ai.stt.error = error;
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Start STT listening
|
|
523
|
+
*/
|
|
524
|
+
async startListening(options) {
|
|
525
|
+
const adapter = await this.initializeSTT();
|
|
526
|
+
// Clear ALL previous results when starting fresh
|
|
527
|
+
this._state.ai.stt.lastResult = '';
|
|
528
|
+
this._state.ai.stt.interimResult = '';
|
|
529
|
+
await adapter.start(options);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Stop STT listening
|
|
533
|
+
*/
|
|
534
|
+
async stopListening() {
|
|
535
|
+
await this._state.ai.stt.adapter?.stop();
|
|
536
|
+
// Clear interim result on stop
|
|
537
|
+
this._state.ai.stt.interimResult = '';
|
|
538
|
+
}
|
|
539
|
+
// === TTS Methods ===
|
|
540
|
+
/**
|
|
541
|
+
* Initialize TTS adapter
|
|
542
|
+
* Uses warm client cache to avoid re-initialization
|
|
543
|
+
*/
|
|
544
|
+
async initializeTTS(options) {
|
|
545
|
+
const requestedType = (options?.type ?? 'browser-synthesis');
|
|
546
|
+
// Check warm client cache first
|
|
547
|
+
const cached = getCachedTTS(requestedType);
|
|
548
|
+
if (cached && cached.initState === 'ready') {
|
|
549
|
+
// Restore cached adapter to state
|
|
550
|
+
this._state.ai.tts.adapter = cached.adapter;
|
|
551
|
+
this._state.ai.tts.initState = 'ready';
|
|
552
|
+
this._state.ai.tts.error = null;
|
|
553
|
+
this.subscribeToTTSEvents(cached.adapter);
|
|
554
|
+
return cached.adapter;
|
|
555
|
+
}
|
|
556
|
+
if (this._state.ai.tts.adapter &&
|
|
557
|
+
this._state.ai.tts.initState === 'ready') {
|
|
558
|
+
return this._state.ai.tts.adapter;
|
|
559
|
+
}
|
|
560
|
+
this._state.ai.tts.initState = 'initializing';
|
|
561
|
+
this._state.ai.tts.error = null;
|
|
562
|
+
// Update cache state for tracking
|
|
563
|
+
updateTTSCacheState(requestedType, { initState: 'initializing' });
|
|
564
|
+
const onProgress = (progress) => {
|
|
565
|
+
this._state.ai.tts.downloadProgress = progress;
|
|
566
|
+
updateTTSCacheState(requestedType, { downloadProgress: progress });
|
|
567
|
+
};
|
|
568
|
+
try {
|
|
569
|
+
const adapter = await getTTS(options);
|
|
570
|
+
await adapter.ensureInitialized(onProgress);
|
|
571
|
+
// Cache the adapter for future use
|
|
572
|
+
setCachedTTS(requestedType, adapter, 'ready');
|
|
573
|
+
this._state.ai.tts.adapter = adapter;
|
|
574
|
+
this._state.ai.tts.initState = 'ready';
|
|
575
|
+
this._state.ai.tts.downloadProgress = null;
|
|
576
|
+
this.subscribeToTTSEvents(adapter);
|
|
577
|
+
return adapter;
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
581
|
+
this._state.ai.tts.initState = 'error';
|
|
582
|
+
this._state.ai.tts.error = err;
|
|
583
|
+
updateTTSCacheState(requestedType, { initState: 'error', error: err });
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Subscribe to TTS adapter events
|
|
589
|
+
* Only subscribes once per adapter instance to prevent duplicate listeners
|
|
590
|
+
*/
|
|
591
|
+
subscribeToTTSEvents(adapter) {
|
|
592
|
+
// Prevent duplicate subscriptions
|
|
593
|
+
if (this._subscribedTTSAdapters.has(adapter)) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
this._subscribedTTSAdapters.add(adapter);
|
|
597
|
+
adapter.onStart(() => {
|
|
598
|
+
this._state.ai.tts.isSpeaking = true;
|
|
599
|
+
this._state.ai.tts.isPaused = false;
|
|
600
|
+
});
|
|
601
|
+
adapter.onEnd(() => {
|
|
602
|
+
this._state.ai.tts.isSpeaking = false;
|
|
603
|
+
this._state.ai.tts.isPaused = false;
|
|
604
|
+
});
|
|
605
|
+
adapter.onError((error) => {
|
|
606
|
+
this._state.ai.tts.error = error;
|
|
607
|
+
this._state.ai.tts.isSpeaking = false;
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Speak text using TTS
|
|
612
|
+
*/
|
|
613
|
+
async speak(text, options) {
|
|
614
|
+
const adapter = await this.initializeTTS();
|
|
615
|
+
await adapter.speak(text, options);
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Stop TTS speech
|
|
619
|
+
*/
|
|
620
|
+
stopSpeaking() {
|
|
621
|
+
this._state.ai.tts.adapter?.stop();
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Pause TTS speech
|
|
625
|
+
*/
|
|
626
|
+
pauseSpeaking() {
|
|
627
|
+
this._state.ai.tts.adapter?.pause();
|
|
628
|
+
if (this._state.ai.tts.isSpeaking) {
|
|
629
|
+
this._state.ai.tts.isPaused = true;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Resume TTS speech
|
|
634
|
+
*/
|
|
635
|
+
resumeSpeaking() {
|
|
636
|
+
this._state.ai.tts.adapter?.resume();
|
|
637
|
+
if (this._state.ai.tts.isPaused) {
|
|
638
|
+
this._state.ai.tts.isPaused = false;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Get available TTS voices
|
|
643
|
+
*/
|
|
644
|
+
getTTSVoices() {
|
|
645
|
+
return this._state.ai.tts.adapter?.getVoices() ?? [];
|
|
646
|
+
}
|
|
647
|
+
// === LLM Methods ===
|
|
648
|
+
/**
|
|
649
|
+
* Initialize LLM adapter
|
|
650
|
+
* Uses warm client cache to avoid re-downloading models
|
|
651
|
+
*/
|
|
652
|
+
async initializeLLM(modelId, options) {
|
|
653
|
+
const requestedType = (options?.type ?? 'webllm');
|
|
654
|
+
// Check warm client cache first
|
|
655
|
+
const cached = getCachedLLM(requestedType, modelId);
|
|
656
|
+
if (cached && cached.initState === 'ready') {
|
|
657
|
+
// Restore cached adapter to state
|
|
658
|
+
this._state.ai.llm.adapter = cached.adapter;
|
|
659
|
+
this._state.ai.llm.initState = 'ready';
|
|
660
|
+
this._state.ai.llm.currentModel = cached.adapter.currentModel;
|
|
661
|
+
this._state.ai.llm.error = null;
|
|
662
|
+
return cached.adapter;
|
|
663
|
+
}
|
|
664
|
+
// Check if already initialized with the right model
|
|
665
|
+
if (this._state.ai.llm.adapter &&
|
|
666
|
+
this._state.ai.llm.initState === 'ready' &&
|
|
667
|
+
(!modelId || this._state.ai.llm.currentModel === modelId)) {
|
|
668
|
+
return this._state.ai.llm.adapter;
|
|
669
|
+
}
|
|
670
|
+
this._state.ai.llm.initState = 'initializing';
|
|
671
|
+
this._state.ai.llm.error = null;
|
|
672
|
+
// Update cache state for tracking
|
|
673
|
+
updateLLMCacheState(requestedType, modelId, { initState: 'initializing' });
|
|
674
|
+
const onProgress = (progress) => {
|
|
675
|
+
this._state.ai.llm.downloadProgress = progress;
|
|
676
|
+
updateLLMCacheState(requestedType, modelId, {
|
|
677
|
+
downloadProgress: progress,
|
|
678
|
+
});
|
|
679
|
+
// Update overall loading progress
|
|
680
|
+
if (progress.percent > 0) {
|
|
681
|
+
this.updateLoadingState({
|
|
682
|
+
overallProgress: progress.percent,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
try {
|
|
687
|
+
const adapter = await getLLM(options);
|
|
688
|
+
await adapter.ensureInitialized(modelId, onProgress);
|
|
689
|
+
// Cache the adapter for future use
|
|
690
|
+
setCachedLLM(requestedType, adapter, modelId, 'ready');
|
|
691
|
+
this._state.ai.llm.adapter = adapter;
|
|
692
|
+
this._state.ai.llm.initState = 'ready';
|
|
693
|
+
this._state.ai.llm.currentModel = adapter.currentModel;
|
|
694
|
+
this._state.ai.llm.downloadProgress = null;
|
|
695
|
+
return adapter;
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
699
|
+
this._state.ai.llm.initState = 'error';
|
|
700
|
+
this._state.ai.llm.error = err;
|
|
701
|
+
updateLLMCacheState(requestedType, modelId, {
|
|
702
|
+
initState: 'error',
|
|
703
|
+
error: err,
|
|
704
|
+
});
|
|
705
|
+
throw error;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Send a message to the LLM
|
|
710
|
+
*/
|
|
711
|
+
async chat(text, options) {
|
|
712
|
+
const adapter = await this.initializeLLM();
|
|
713
|
+
this._state.ai.llm.isGenerating = true;
|
|
714
|
+
try {
|
|
715
|
+
const response = await adapter.message(text, {
|
|
716
|
+
systemPrompt: options?.systemPrompt,
|
|
717
|
+
onToken: options?.onToken,
|
|
718
|
+
});
|
|
719
|
+
return response;
|
|
720
|
+
}
|
|
721
|
+
finally {
|
|
722
|
+
this._state.ai.llm.isGenerating = false;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Unload LLM model to free memory
|
|
727
|
+
*/
|
|
728
|
+
async unloadLLM() {
|
|
729
|
+
await this._state.ai.llm.adapter?.unloadModel();
|
|
730
|
+
this._state.ai.llm.adapter = null;
|
|
731
|
+
this._state.ai.llm.initState = 'uninitialized';
|
|
732
|
+
this._state.ai.llm.currentModel = null;
|
|
733
|
+
}
|
|
734
|
+
// === Cleanup ===
|
|
735
|
+
/**
|
|
736
|
+
* Dispose of all resources
|
|
737
|
+
*/
|
|
738
|
+
async dispose() {
|
|
739
|
+
// Cancel pending idle callback
|
|
740
|
+
if (this._idleCallbackId !== null &&
|
|
741
|
+
typeof cancelIdleCallback !== 'undefined') {
|
|
742
|
+
cancelIdleCallback(this._idleCallbackId);
|
|
743
|
+
this._idleCallbackId = null;
|
|
744
|
+
}
|
|
745
|
+
// Disconnect socket
|
|
746
|
+
this.disconnectSocket();
|
|
747
|
+
// Dispose AI adapters (but don't clear the cache - they survive navigation)
|
|
748
|
+
await this._state.ai.stt.adapter?.dispose();
|
|
749
|
+
await this._state.ai.tts.adapter?.dispose();
|
|
750
|
+
await this._state.ai.llm.adapter?.dispose();
|
|
751
|
+
this._state = createInitialState();
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Check if AI is ready to use (all configured adapters loaded)
|
|
755
|
+
*/
|
|
756
|
+
get isAIReady() {
|
|
757
|
+
return (this._state.aiLoading.phase === 'ready' ||
|
|
758
|
+
this._state.aiLoading.phase === 'idle');
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Check if AI is currently loading
|
|
762
|
+
*/
|
|
763
|
+
get isAILoading() {
|
|
764
|
+
return (this._state.aiLoading.phase === 'checking' ||
|
|
765
|
+
this._state.aiLoading.phase === 'downloading' ||
|
|
766
|
+
this._state.aiLoading.phase === 'initializing');
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Create a new reactive app state manager
|
|
771
|
+
*/
|
|
772
|
+
export function createAppState(options) {
|
|
773
|
+
return new SmrtAppStateManager(options);
|
|
774
|
+
}
|