@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.
Files changed (357) hide show
  1. package/AGENTS.md +317 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +185 -0
  5. package/dist/Provider.svelte +204 -0
  6. package/dist/Provider.svelte.d.ts +73 -0
  7. package/dist/Provider.svelte.d.ts.map +1 -0
  8. package/dist/__tests__/app-state.test.js +156 -0
  9. package/dist/__tests__/warm-clients.test.js +186 -0
  10. package/dist/browser-ai/adapters/llm/factory.d.ts +38 -0
  11. package/dist/browser-ai/adapters/llm/factory.d.ts.map +1 -0
  12. package/dist/browser-ai/adapters/llm/factory.js +91 -0
  13. package/dist/browser-ai/adapters/llm/index.d.ts +7 -0
  14. package/dist/browser-ai/adapters/llm/index.d.ts.map +1 -0
  15. package/dist/browser-ai/adapters/llm/index.js +6 -0
  16. package/dist/browser-ai/adapters/llm/types.d.ts +182 -0
  17. package/dist/browser-ai/adapters/llm/types.d.ts.map +1 -0
  18. package/dist/browser-ai/adapters/llm/types.js +43 -0
  19. package/dist/browser-ai/adapters/llm/webllm.d.ts +33 -0
  20. package/dist/browser-ai/adapters/llm/webllm.d.ts.map +1 -0
  21. package/dist/browser-ai/adapters/llm/webllm.js +225 -0
  22. package/dist/browser-ai/adapters/stt/browser-speech.d.ts +31 -0
  23. package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -0
  24. package/dist/browser-ai/adapters/stt/browser-speech.js +217 -0
  25. package/dist/browser-ai/adapters/stt/factory.d.ts +49 -0
  26. package/dist/browser-ai/adapters/stt/factory.d.ts.map +1 -0
  27. package/dist/browser-ai/adapters/stt/factory.js +110 -0
  28. package/dist/browser-ai/adapters/stt/index.d.ts +9 -0
  29. package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -0
  30. package/dist/browser-ai/adapters/stt/index.js +8 -0
  31. package/dist/browser-ai/adapters/stt/types.d.ts +154 -0
  32. package/dist/browser-ai/adapters/stt/types.d.ts.map +1 -0
  33. package/dist/browser-ai/adapters/stt/types.js +4 -0
  34. package/dist/browser-ai/adapters/stt/whisper-cpp.d.ts +46 -0
  35. package/dist/browser-ai/adapters/stt/whisper-cpp.d.ts.map +1 -0
  36. package/dist/browser-ai/adapters/stt/whisper-cpp.js +348 -0
  37. package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts +51 -0
  38. package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts.map +1 -0
  39. package/dist/browser-ai/adapters/stt/whisper-wasm.js +380 -0
  40. package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts +42 -0
  41. package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts.map +1 -0
  42. package/dist/browser-ai/adapters/tts/browser-synthesis.js +235 -0
  43. package/dist/browser-ai/adapters/tts/factory.d.ts +53 -0
  44. package/dist/browser-ai/adapters/tts/factory.d.ts.map +1 -0
  45. package/dist/browser-ai/adapters/tts/factory.js +92 -0
  46. package/dist/browser-ai/adapters/tts/index.d.ts +7 -0
  47. package/dist/browser-ai/adapters/tts/index.d.ts.map +1 -0
  48. package/dist/browser-ai/adapters/tts/index.js +6 -0
  49. package/dist/browser-ai/adapters/tts/types.d.ts +140 -0
  50. package/dist/browser-ai/adapters/tts/types.d.ts.map +1 -0
  51. package/dist/browser-ai/adapters/tts/types.js +4 -0
  52. package/dist/browser-ai/capabilities/detector.d.ts +38 -0
  53. package/dist/browser-ai/capabilities/detector.d.ts.map +1 -0
  54. package/dist/browser-ai/capabilities/detector.js +211 -0
  55. package/dist/browser-ai/core/errors.d.ts +62 -0
  56. package/dist/browser-ai/core/errors.d.ts.map +1 -0
  57. package/dist/browser-ai/core/errors.js +92 -0
  58. package/dist/browser-ai/core/index.d.ts +6 -0
  59. package/dist/browser-ai/core/index.d.ts.map +1 -0
  60. package/dist/browser-ai/core/index.js +5 -0
  61. package/dist/browser-ai/core/types.d.ts +115 -0
  62. package/dist/browser-ai/core/types.d.ts.map +1 -0
  63. package/dist/browser-ai/core/types.js +34 -0
  64. package/dist/browser-ai/index.d.ts +12 -0
  65. package/dist/browser-ai/index.d.ts.map +1 -0
  66. package/dist/browser-ai/index.js +16 -0
  67. package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte +77 -0
  68. package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte.d.ts +16 -0
  69. package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte.d.ts.map +1 -0
  70. package/dist/browser-ai/svelte/components/CapabilityGate.svelte +57 -0
  71. package/dist/browser-ai/svelte/components/CapabilityGate.svelte.d.ts +15 -0
  72. package/dist/browser-ai/svelte/components/CapabilityGate.svelte.d.ts.map +1 -0
  73. package/dist/browser-ai/svelte/components/DownloadProgress.svelte +141 -0
  74. package/dist/browser-ai/svelte/components/DownloadProgress.svelte.d.ts +15 -0
  75. package/dist/browser-ai/svelte/components/DownloadProgress.svelte.d.ts.map +1 -0
  76. package/dist/browser-ai/svelte/components/STTTest.svelte +379 -0
  77. package/dist/browser-ai/svelte/components/STTTest.svelte.d.ts +9 -0
  78. package/dist/browser-ai/svelte/components/STTTest.svelte.d.ts.map +1 -0
  79. package/dist/browser-ai/svelte/components/VoiceInput.svelte +200 -0
  80. package/dist/browser-ai/svelte/components/VoiceInput.svelte.d.ts +16 -0
  81. package/dist/browser-ai/svelte/components/VoiceInput.svelte.d.ts.map +1 -0
  82. package/dist/browser-ai/svelte/index.d.ts +15 -0
  83. package/dist/browser-ai/svelte/index.d.ts.map +1 -0
  84. package/dist/browser-ai/svelte/index.js +28 -0
  85. package/dist/browser-ai/ui.d.ts +16 -0
  86. package/dist/browser-ai/ui.d.ts.map +1 -0
  87. package/dist/browser-ai/ui.js +67 -0
  88. package/dist/components/admin/AgentAdminPanel.svelte +111 -0
  89. package/dist/components/admin/AgentAdminPanel.svelte.d.ts +25 -0
  90. package/dist/components/admin/AgentAdminPanel.svelte.d.ts.map +1 -0
  91. package/dist/components/admin/AgentAdminTabs.svelte +280 -0
  92. package/dist/components/admin/AgentAdminTabs.svelte.d.ts +23 -0
  93. package/dist/components/admin/AgentAdminTabs.svelte.d.ts.map +1 -0
  94. package/dist/components/admin/AgentSettingsShell.svelte +257 -0
  95. package/dist/components/admin/AgentSettingsShell.svelte.d.ts +33 -0
  96. package/dist/components/admin/AgentSettingsShell.svelte.d.ts.map +1 -0
  97. package/dist/components/admin/index.d.ts +5 -0
  98. package/dist/components/admin/index.d.ts.map +1 -0
  99. package/dist/components/admin/index.js +6 -0
  100. package/dist/components/forms/AddressInput.svelte +500 -0
  101. package/dist/components/forms/AddressInput.svelte.d.ts +36 -0
  102. package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -0
  103. package/dist/components/forms/CheckboxInput.svelte +208 -0
  104. package/dist/components/forms/CheckboxInput.svelte.d.ts +20 -0
  105. package/dist/components/forms/CheckboxInput.svelte.d.ts.map +1 -0
  106. package/dist/components/forms/DateRangeInput.svelte +628 -0
  107. package/dist/components/forms/DateRangeInput.svelte.d.ts +33 -0
  108. package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -0
  109. package/dist/components/forms/DateTimeInput.svelte +521 -0
  110. package/dist/components/forms/DateTimeInput.svelte.d.ts +24 -0
  111. package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -0
  112. package/dist/components/forms/FileUpload.svelte +358 -0
  113. package/dist/components/forms/FileUpload.svelte.d.ts +22 -0
  114. package/dist/components/forms/FileUpload.svelte.d.ts.map +1 -0
  115. package/dist/components/forms/Form.svelte +771 -0
  116. package/dist/components/forms/Form.svelte.d.ts +26 -0
  117. package/dist/components/forms/Form.svelte.d.ts.map +1 -0
  118. package/dist/components/forms/FormGroup.svelte +86 -0
  119. package/dist/components/forms/FormGroup.svelte.d.ts +13 -0
  120. package/dist/components/forms/FormGroup.svelte.d.ts.map +1 -0
  121. package/dist/components/forms/FormMicButton.svelte +179 -0
  122. package/dist/components/forms/FormMicButton.svelte.d.ts +10 -0
  123. package/dist/components/forms/FormMicButton.svelte.d.ts.map +1 -0
  124. package/dist/components/forms/Input.svelte +83 -0
  125. package/dist/components/forms/Input.svelte.d.ts +9 -0
  126. package/dist/components/forms/Input.svelte.d.ts.map +1 -0
  127. package/dist/components/forms/MeasurementInput.svelte +505 -0
  128. package/dist/components/forms/MeasurementInput.svelte.d.ts +35 -0
  129. package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -0
  130. package/dist/components/forms/MoneyInput.svelte +412 -0
  131. package/dist/components/forms/MoneyInput.svelte.d.ts +30 -0
  132. package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -0
  133. package/dist/components/forms/NumberInput.svelte +310 -0
  134. package/dist/components/forms/NumberInput.svelte.d.ts +28 -0
  135. package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -0
  136. package/dist/components/forms/PhoneInput.svelte +530 -0
  137. package/dist/components/forms/PhoneInput.svelte.d.ts +22 -0
  138. package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -0
  139. package/dist/components/forms/SearchInput.svelte +358 -0
  140. package/dist/components/forms/SearchInput.svelte.d.ts +33 -0
  141. package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
  142. package/dist/components/forms/Select.svelte +83 -0
  143. package/dist/components/forms/Select.svelte.d.ts +11 -0
  144. package/dist/components/forms/Select.svelte.d.ts.map +1 -0
  145. package/dist/components/forms/SelectInput.svelte +254 -0
  146. package/dist/components/forms/SelectInput.svelte.d.ts +25 -0
  147. package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -0
  148. package/dist/components/forms/TextInput.svelte +415 -0
  149. package/dist/components/forms/TextInput.svelte.d.ts +26 -0
  150. package/dist/components/forms/TextInput.svelte.d.ts.map +1 -0
  151. package/dist/components/forms/Textarea.svelte +85 -0
  152. package/dist/components/forms/Textarea.svelte.d.ts +10 -0
  153. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -0
  154. package/dist/components/forms/TextareaInput.svelte +386 -0
  155. package/dist/components/forms/TextareaInput.svelte.d.ts +26 -0
  156. package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -0
  157. package/dist/components/forms/Toggle.svelte +217 -0
  158. package/dist/components/forms/Toggle.svelte.d.ts +37 -0
  159. package/dist/components/forms/Toggle.svelte.d.ts.map +1 -0
  160. package/dist/components/forms/__tests__/AddressInput.behavior.test.js +122 -0
  161. package/dist/components/forms/__tests__/CheckboxInput.test.js +92 -0
  162. package/dist/components/forms/__tests__/DateRangeInput.behavior.test.js +135 -0
  163. package/dist/components/forms/__tests__/DateTimeInput.behavior.test.js +103 -0
  164. package/dist/components/forms/__tests__/FileUpload.test.js +90 -0
  165. package/dist/components/forms/__tests__/Form.behavior.test.js +137 -0
  166. package/dist/components/forms/__tests__/Form.test.js +58 -0
  167. package/dist/components/forms/__tests__/FormGroup.test.js +48 -0
  168. package/dist/components/forms/__tests__/FormMicButton.test.js +86 -0
  169. package/dist/components/forms/__tests__/Input.test.js +49 -0
  170. package/dist/components/forms/__tests__/MeasurementInput.behavior.test.js +129 -0
  171. package/dist/components/forms/__tests__/MoneyInput.behavior.test.js +124 -0
  172. package/dist/components/forms/__tests__/NumberInput.behavior.test.js +141 -0
  173. package/dist/components/forms/__tests__/PhoneInput.behavior.test.js +96 -0
  174. package/dist/components/forms/__tests__/SearchInput.test.js +79 -0
  175. package/dist/components/forms/__tests__/Select.test.js +37 -0
  176. package/dist/components/forms/__tests__/SelectInput.behavior.test.js +132 -0
  177. package/dist/components/forms/__tests__/TextInput.behavior.test.js +131 -0
  178. package/dist/components/forms/__tests__/Textarea.test.js +39 -0
  179. package/dist/components/forms/__tests__/TextareaInput.behavior.test.js +96 -0
  180. package/dist/components/forms/__tests__/Toggle.test.js +87 -0
  181. package/dist/components/forms/__tests__/composite-inputs-a11y.test.js +69 -0
  182. package/dist/components/forms/__tests__/form-group-input.fixture.svelte +16 -0
  183. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts +9 -0
  184. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts.map +1 -0
  185. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +33 -0
  186. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +12 -0
  187. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +1 -0
  188. package/dist/components/forms/__tests__/rich-inputs-a11y.test.js +87 -0
  189. package/dist/components/forms/index.d.ts +25 -0
  190. package/dist/components/forms/index.d.ts.map +1 -0
  191. package/dist/components/forms/index.js +25 -0
  192. package/dist/components/forms/types.d.ts +33 -0
  193. package/dist/components/forms/types.d.ts.map +1 -0
  194. package/dist/components/forms/types.js +4 -0
  195. package/dist/components/module/ModulePanel.svelte +134 -0
  196. package/dist/components/module/ModulePanel.svelte.d.ts +22 -0
  197. package/dist/components/module/ModulePanel.svelte.d.ts.map +1 -0
  198. package/dist/components/module/index.d.ts +5 -0
  199. package/dist/components/module/index.d.ts.map +1 -0
  200. package/dist/components/module/index.js +4 -0
  201. package/dist/components/workspace/Breadcrumbs.svelte +141 -0
  202. package/dist/components/workspace/Breadcrumbs.svelte.d.ts +21 -0
  203. package/dist/components/workspace/Breadcrumbs.svelte.d.ts.map +1 -0
  204. package/dist/components/workspace/NavTree.svelte +354 -0
  205. package/dist/components/workspace/NavTree.svelte.d.ts +45 -0
  206. package/dist/components/workspace/NavTree.svelte.d.ts.map +1 -0
  207. package/dist/components/workspace/README.md +34 -0
  208. package/dist/components/workspace/RoleShell.svelte +309 -0
  209. package/dist/components/workspace/RoleShell.svelte.d.ts +91 -0
  210. package/dist/components/workspace/RoleShell.svelte.d.ts.map +1 -0
  211. package/dist/components/workspace/WorkspaceShell.svelte +951 -0
  212. package/dist/components/workspace/WorkspaceShell.svelte.d.ts +112 -0
  213. package/dist/components/workspace/WorkspaceShell.svelte.d.ts.map +1 -0
  214. package/dist/components/workspace/__tests__/RoleShell.test.js +772 -0
  215. package/dist/components/workspace/__tests__/WorkspaceShell.test.js +630 -0
  216. package/dist/components/workspace/__tests__/breadcrumbs-helpers.test.js +141 -0
  217. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte +45 -0
  218. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts +21 -0
  219. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts.map +1 -0
  220. package/dist/components/workspace/__tests__/define-tools-dock.test.js +1010 -0
  221. package/dist/components/workspace/__tests__/harness.svelte +25 -0
  222. package/dist/components/workspace/__tests__/harness.svelte.d.ts +14 -0
  223. package/dist/components/workspace/__tests__/harness.svelte.d.ts.map +1 -0
  224. package/dist/components/workspace/__tests__/index.test.js +37 -0
  225. package/dist/components/workspace/__tests__/manifest-nav-helpers.test.js +24 -0
  226. package/dist/components/workspace/__tests__/manifest-nav.test.js +599 -0
  227. package/dist/components/workspace/__tests__/nav-helpers.test.js +95 -0
  228. package/dist/components/workspace/__tests__/render-harness.svelte +66 -0
  229. package/dist/components/workspace/__tests__/render-harness.svelte.d.ts +32 -0
  230. package/dist/components/workspace/__tests__/render-harness.svelte.d.ts.map +1 -0
  231. package/dist/components/workspace/__tests__/render-tools-dock.test.js +243 -0
  232. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte +58 -0
  233. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts +16 -0
  234. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts.map +1 -0
  235. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte +41 -0
  236. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts +13 -0
  237. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts.map +1 -0
  238. package/dist/components/workspace/__tests__/test-icon.svelte +17 -0
  239. package/dist/components/workspace/__tests__/test-icon.svelte.d.ts +19 -0
  240. package/dist/components/workspace/__tests__/test-icon.svelte.d.ts.map +1 -0
  241. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte +38 -0
  242. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts +22 -0
  243. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts.map +1 -0
  244. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts +65 -0
  245. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts.map +1 -0
  246. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.js +115 -0
  247. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts +15 -0
  248. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts.map +1 -0
  249. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.js +7 -0
  250. package/dist/components/workspace/__tests__/typed-tool-fixture.test.js +115 -0
  251. package/dist/components/workspace/__tests__/use-harness-orphan.svelte +9 -0
  252. package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts +19 -0
  253. package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts.map +1 -0
  254. package/dist/components/workspace/__tests__/use-harness.svelte +23 -0
  255. package/dist/components/workspace/__tests__/use-harness.svelte.d.ts +8 -0
  256. package/dist/components/workspace/__tests__/use-harness.svelte.d.ts.map +1 -0
  257. package/dist/components/workspace/__tests__/use-tools-dock.test.js +33 -0
  258. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte +43 -0
  259. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts +11 -0
  260. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts.map +1 -0
  261. package/dist/components/workspace/breadcrumbs-helpers.d.ts +44 -0
  262. package/dist/components/workspace/breadcrumbs-helpers.d.ts.map +1 -0
  263. package/dist/components/workspace/breadcrumbs-helpers.js +88 -0
  264. package/dist/components/workspace/index.d.ts +16 -0
  265. package/dist/components/workspace/index.d.ts.map +1 -0
  266. package/dist/components/workspace/index.js +14 -0
  267. package/dist/components/workspace/manifest-nav.d.ts +200 -0
  268. package/dist/components/workspace/manifest-nav.d.ts.map +1 -0
  269. package/dist/components/workspace/manifest-nav.js +408 -0
  270. package/dist/components/workspace/nav-helpers.d.ts +36 -0
  271. package/dist/components/workspace/nav-helpers.d.ts.map +1 -0
  272. package/dist/components/workspace/nav-helpers.js +60 -0
  273. package/dist/components/workspace/server/__tests__/compose-availability.test.js +383 -0
  274. package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts +78 -0
  275. package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts.map +1 -0
  276. package/dist/components/workspace/server/__tests__/typed-context-fixture.js +104 -0
  277. package/dist/components/workspace/server/compose-availability.d.ts +73 -0
  278. package/dist/components/workspace/server/compose-availability.d.ts.map +1 -0
  279. package/dist/components/workspace/server/compose-availability.js +114 -0
  280. package/dist/components/workspace/server/index.d.ts +13 -0
  281. package/dist/components/workspace/server/index.d.ts.map +1 -0
  282. package/dist/components/workspace/server/index.js +11 -0
  283. package/dist/components/workspace/server/types.d.ts +108 -0
  284. package/dist/components/workspace/server/types.d.ts.map +1 -0
  285. package/dist/components/workspace/server/types.js +11 -0
  286. package/dist/components/workspace/tools-dock/ToolsDock.svelte +565 -0
  287. package/dist/components/workspace/tools-dock/ToolsDock.svelte.d.ts +14 -0
  288. package/dist/components/workspace/tools-dock/ToolsDock.svelte.d.ts.map +1 -0
  289. package/dist/components/workspace/tools-dock/define-tools-dock.svelte.d.ts +143 -0
  290. package/dist/components/workspace/tools-dock/define-tools-dock.svelte.d.ts.map +1 -0
  291. package/dist/components/workspace/tools-dock/define-tools-dock.svelte.js +487 -0
  292. package/dist/components/workspace/tools-dock/use-tools-dock.d.ts +41 -0
  293. package/dist/components/workspace/tools-dock/use-tools-dock.d.ts.map +1 -0
  294. package/dist/components/workspace/tools-dock/use-tools-dock.js +50 -0
  295. package/dist/components/workspace/types.d.ts +372 -0
  296. package/dist/components/workspace/types.d.ts.map +1 -0
  297. package/dist/components/workspace/types.js +6 -0
  298. package/dist/hooks/index.d.ts +11 -0
  299. package/dist/hooks/index.d.ts.map +1 -0
  300. package/dist/hooks/index.js +10 -0
  301. package/dist/hooks/useAppState.svelte.d.ts +46 -0
  302. package/dist/hooks/useAppState.svelte.d.ts.map +1 -0
  303. package/dist/hooks/useAppState.svelte.js +59 -0
  304. package/dist/hooks/useAuth.svelte.d.ts +41 -0
  305. package/dist/hooks/useAuth.svelte.d.ts.map +1 -0
  306. package/dist/hooks/useAuth.svelte.js +43 -0
  307. package/dist/hooks/useLLM.svelte.d.ts +69 -0
  308. package/dist/hooks/useLLM.svelte.d.ts.map +1 -0
  309. package/dist/hooks/useLLM.svelte.js +85 -0
  310. package/dist/hooks/useSTT.svelte.d.ts +68 -0
  311. package/dist/hooks/useSTT.svelte.d.ts.map +1 -0
  312. package/dist/hooks/useSTT.svelte.js +97 -0
  313. package/dist/hooks/useSocket.svelte.d.ts +45 -0
  314. package/dist/hooks/useSocket.svelte.d.ts.map +1 -0
  315. package/dist/hooks/useSocket.svelte.js +54 -0
  316. package/dist/hooks/useTTS.svelte.d.ts +65 -0
  317. package/dist/hooks/useTTS.svelte.d.ts.map +1 -0
  318. package/dist/hooks/useTTS.svelte.js +93 -0
  319. package/dist/hooks/useTheme.d.ts +13 -0
  320. package/dist/hooks/useTheme.d.ts.map +1 -0
  321. package/dist/hooks/useTheme.js +16 -0
  322. package/dist/i18n/__tests__/server.spec.js +50 -0
  323. package/dist/i18n/server.d.ts +47 -0
  324. package/dist/i18n/server.d.ts.map +1 -0
  325. package/dist/i18n/server.js +58 -0
  326. package/dist/i18n/strings.forms.d.ts +33 -0
  327. package/dist/i18n/strings.forms.d.ts.map +1 -0
  328. package/dist/i18n/strings.forms.js +54 -0
  329. package/dist/i18n/strings.workspace.d.ts +34 -0
  330. package/dist/i18n/strings.workspace.d.ts.map +1 -0
  331. package/dist/i18n/strings.workspace.js +40 -0
  332. package/dist/index.d.ts +18 -0
  333. package/dist/index.d.ts.map +1 -0
  334. package/dist/index.js +23 -0
  335. package/dist/state/__tests__/warm-clients.test.js +40 -0
  336. package/dist/state/app-state.d.ts +308 -0
  337. package/dist/state/app-state.d.ts.map +1 -0
  338. package/dist/state/app-state.js +64 -0
  339. package/dist/state/app-state.svelte.d.ts +196 -0
  340. package/dist/state/app-state.svelte.d.ts.map +1 -0
  341. package/dist/state/app-state.svelte.js +774 -0
  342. package/dist/state/context.d.ts +23 -0
  343. package/dist/state/context.d.ts.map +1 -0
  344. package/dist/state/context.js +32 -0
  345. package/dist/state/form-context.d.ts +59 -0
  346. package/dist/state/form-context.d.ts.map +1 -0
  347. package/dist/state/form-context.js +31 -0
  348. package/dist/state/form-group-context.d.ts +13 -0
  349. package/dist/state/form-group-context.d.ts.map +1 -0
  350. package/dist/state/form-group-context.js +28 -0
  351. package/dist/state/index.d.ts +9 -0
  352. package/dist/state/index.d.ts.map +1 -0
  353. package/dist/state/index.js +8 -0
  354. package/dist/state/warm-clients.d.ts +136 -0
  355. package/dist/state/warm-clients.d.ts.map +1 -0
  356. package/dist/state/warm-clients.js +231 -0
  357. 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
+ }