@peers-app/peers-ui 0.14.0 → 0.15.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 (438) hide show
  1. package/.github/workflows/publish.yml +8 -5
  2. package/babel.config.js +4 -4
  3. package/biome.json +191 -0
  4. package/dist/command-palette/command-palette-ui.d.ts +1 -2
  5. package/dist/command-palette/command-palette-ui.js +175 -244
  6. package/dist/command-palette/command-palette.js +65 -64
  7. package/dist/components/chat-overlay.d.ts +2 -2
  8. package/dist/components/chat-overlay.js +160 -217
  9. package/dist/components/checkbox.d.ts +5 -4
  10. package/dist/components/checkbox.js +4 -7
  11. package/dist/components/group-switcher.d.ts +1 -2
  12. package/dist/components/group-switcher.js +119 -159
  13. package/dist/components/input-date.d.ts +3 -3
  14. package/dist/components/input-date.js +6 -6
  15. package/dist/components/input-number.d.ts +7 -6
  16. package/dist/components/input-number.js +25 -20
  17. package/dist/components/input.d.ts +5 -4
  18. package/dist/components/input.js +4 -7
  19. package/dist/components/inverse-lazy-list.d.ts +3 -3
  20. package/dist/components/inverse-lazy-list.js +13 -47
  21. package/dist/components/io-schema-values.d.ts +5 -6
  22. package/dist/components/io-schema-values.js +28 -65
  23. package/dist/components/io-schema.d.ts +4 -5
  24. package/dist/components/io-schema.js +42 -79
  25. package/dist/components/lazy-list.d.ts +3 -3
  26. package/dist/components/lazy-list.js +38 -58
  27. package/dist/components/list-screen.d.ts +3 -8
  28. package/dist/components/list-screen.js +28 -23
  29. package/dist/components/loading-indicator.d.ts +1 -2
  30. package/dist/components/loading-indicator.js +2 -6
  31. package/dist/components/markdown-editor/autolink-plugin.js +5 -8
  32. package/dist/components/markdown-editor/editor-inline.d.ts +2 -3
  33. package/dist/components/markdown-editor/editor-inline.js +2 -6
  34. package/dist/components/markdown-editor/editor.d.ts +6 -6
  35. package/dist/components/markdown-editor/editor.js +9 -19
  36. package/dist/components/markdown-editor/markdown-plugin.d.ts +1 -1
  37. package/dist/components/markdown-editor/markdown-plugin.js +20 -21
  38. package/dist/components/markdown-editor/mention-node.d.ts +2 -2
  39. package/dist/components/markdown-editor/mention-node.js +24 -24
  40. package/dist/components/markdown-editor/mentions-plugin.d.ts +2 -2
  41. package/dist/components/markdown-editor/mentions-plugin.js +61 -62
  42. package/dist/components/markdown-editor/theme.js +28 -28
  43. package/dist/components/markdown-editor/toolbar.d.ts +2 -3
  44. package/dist/components/markdown-editor/toolbar.js +32 -49
  45. package/dist/components/markdown-with-mentions.d.ts +1 -1
  46. package/dist/components/markdown-with-mentions.js +43 -43
  47. package/dist/components/message-logs/message-logs.d.ts +1 -2
  48. package/dist/components/message-logs/message-logs.js +91 -116
  49. package/dist/components/messages/avatar.d.ts +3 -4
  50. package/dist/components/messages/avatar.js +37 -46
  51. package/dist/components/messages/channel-message-list.d.ts +5 -6
  52. package/dist/components/messages/channel-message-list.js +34 -34
  53. package/dist/components/messages/channel-view.d.ts +1 -2
  54. package/dist/components/messages/channel-view.js +23 -57
  55. package/dist/components/messages/message-compose.d.ts +3 -4
  56. package/dist/components/messages/message-compose.js +27 -38
  57. package/dist/components/messages/message-display.d.ts +2 -3
  58. package/dist/components/messages/message-display.js +42 -95
  59. package/dist/components/messages/thread-message-list.d.ts +4 -5
  60. package/dist/components/messages/thread-message-list.js +29 -29
  61. package/dist/components/router.d.ts +1 -2
  62. package/dist/components/router.js +58 -66
  63. package/dist/components/save-button.d.ts +3 -3
  64. package/dist/components/save-button.js +23 -33
  65. package/dist/components/sortable-list.d.ts +11 -12
  66. package/dist/components/sortable-list.js +42 -22
  67. package/dist/components/tabs.d.ts +3 -3
  68. package/dist/components/tabs.js +16 -47
  69. package/dist/components/tooltip.d.ts +4 -4
  70. package/dist/components/tooltip.js +4 -9
  71. package/dist/components/trust-level-badge.d.ts +2 -3
  72. package/dist/components/trust-level-badge.js +16 -16
  73. package/dist/components/trust-level-dropdown.d.ts +3 -4
  74. package/dist/components/trust-level-dropdown.js +32 -55
  75. package/dist/components/typeahead.d.ts +3 -3
  76. package/dist/components/typeahead.js +48 -89
  77. package/dist/components/voice-indicator.d.ts +2 -2
  78. package/dist/components/voice-indicator.js +93 -106
  79. package/dist/components/voice-subscribe-events.d.ts +32 -0
  80. package/dist/components/voice-subscribe-events.js +2 -0
  81. package/dist/globals.d.ts +3 -5
  82. package/dist/globals.js +22 -33
  83. package/dist/hooks.d.ts +5 -5
  84. package/dist/hooks.js +22 -12
  85. package/dist/hooks.test.js +129 -145
  86. package/dist/index.d.ts +9 -8
  87. package/dist/index.js +13 -12
  88. package/dist/mention-configs.d.ts +2 -2
  89. package/dist/mention-configs.js +55 -42
  90. package/dist/screens/assistants/assistant-config.d.ts +2 -3
  91. package/dist/screens/assistants/assistant-config.js +9 -22
  92. package/dist/screens/assistants/assistant-details.d.ts +1 -2
  93. package/dist/screens/assistants/assistant-details.js +13 -28
  94. package/dist/screens/assistants/assistant-info.d.ts +2 -3
  95. package/dist/screens/assistants/assistant-info.js +3 -17
  96. package/dist/screens/assistants/assistant-list.d.ts +1 -2
  97. package/dist/screens/assistants/assistant-list.js +15 -56
  98. package/dist/screens/assistants/assistant-tools.d.ts +2 -3
  99. package/dist/screens/assistants/assistant-tools.js +10 -24
  100. package/dist/screens/console-logs/console-logs-list.d.ts +1 -2
  101. package/dist/screens/console-logs/console-logs-list.js +130 -134
  102. package/dist/screens/console-logs/log-display.d.ts +2 -3
  103. package/dist/screens/console-logs/log-display.js +40 -42
  104. package/dist/screens/console-logs/log-filters.d.ts +1 -2
  105. package/dist/screens/console-logs/log-filters.js +2 -24
  106. package/dist/screens/console-logs/mobile-log-card.d.ts +2 -3
  107. package/dist/screens/console-logs/mobile-log-card.js +64 -67
  108. package/dist/screens/console-logs/resizable-table-header.d.ts +1 -2
  109. package/dist/screens/console-logs/resizable-table-header.js +31 -67
  110. package/dist/screens/contacts/contact-details.d.ts +1 -2
  111. package/dist/screens/contacts/contact-details.js +16 -46
  112. package/dist/screens/contacts/contact-list.d.ts +1 -2
  113. package/dist/screens/contacts/contact-list.js +44 -103
  114. package/dist/screens/contacts/index.d.ts +4 -4
  115. package/dist/screens/contacts/index.js +1 -1
  116. package/dist/screens/contacts/user-connect.d.ts +1 -2
  117. package/dist/screens/contacts/user-connect.js +85 -186
  118. package/dist/screens/data-explorer/data-explorer.d.ts +1 -2
  119. package/dist/screens/data-explorer/data-explorer.js +61 -181
  120. package/dist/screens/data-explorer/index.d.ts +2 -2
  121. package/dist/screens/data-explorer/query-executor.d.ts +1 -2
  122. package/dist/screens/data-explorer/query-executor.js +56 -166
  123. package/dist/screens/groups/group-details.d.ts +1 -2
  124. package/dist/screens/groups/group-details.js +27 -122
  125. package/dist/screens/groups/group-invite-listener.d.ts +2 -3
  126. package/dist/screens/groups/group-invite-listener.js +8 -104
  127. package/dist/screens/groups/group-list.d.ts +1 -2
  128. package/dist/screens/groups/group-list.js +56 -133
  129. package/dist/screens/groups/group-members.d.ts +2 -3
  130. package/dist/screens/groups/group-members.js +62 -132
  131. package/dist/screens/groups/index.d.ts +4 -4
  132. package/dist/screens/groups/index.js +1 -1
  133. package/dist/screens/join-group/index.d.ts +2 -2
  134. package/dist/screens/join-group/join-group.d.ts +1 -2
  135. package/dist/screens/join-group/join-group.js +9 -109
  136. package/dist/screens/network-viewer/connection-troubleshooter.d.ts +2 -3
  137. package/dist/screens/network-viewer/connection-troubleshooter.js +89 -193
  138. package/dist/screens/network-viewer/cpu-usage-graph.d.ts +1 -2
  139. package/dist/screens/network-viewer/cpu-usage-graph.js +60 -99
  140. package/dist/screens/network-viewer/device-details-modal.d.ts +1 -2
  141. package/dist/screens/network-viewer/device-details-modal.js +25 -177
  142. package/dist/screens/network-viewer/group-details-modal.d.ts +1 -2
  143. package/dist/screens/network-viewer/group-details-modal.js +31 -142
  144. package/dist/screens/network-viewer/index.d.ts +4 -4
  145. package/dist/screens/network-viewer/index.js +3 -3
  146. package/dist/screens/network-viewer/network-viewer-ipc.d.ts +22 -0
  147. package/dist/screens/network-viewer/network-viewer-ipc.js +6 -0
  148. package/dist/screens/network-viewer/network-viewer.d.ts +1 -2
  149. package/dist/screens/network-viewer/network-viewer.js +91 -296
  150. package/dist/screens/network-viewer/usage-graph.d.ts +1 -2
  151. package/dist/screens/network-viewer/usage-graph.js +78 -110
  152. package/dist/screens/packages/package-details.d.ts +1 -2
  153. package/dist/screens/packages/package-details.js +35 -41
  154. package/dist/screens/packages/package-info.d.ts +2 -2
  155. package/dist/screens/packages/package-info.js +33 -86
  156. package/dist/screens/packages/package-list.d.ts +1 -2
  157. package/dist/screens/packages/package-list.js +42 -106
  158. package/dist/screens/packages/package-new-local.d.ts +1 -2
  159. package/dist/screens/packages/package-new-local.js +13 -19
  160. package/dist/screens/packages/package-versions.d.ts +2 -3
  161. package/dist/screens/packages/package-versions.js +29 -96
  162. package/dist/screens/peer-types/peer-type-details.d.ts +3 -4
  163. package/dist/screens/peer-types/peer-type-details.js +26 -78
  164. package/dist/screens/peer-types/peer-type-list.d.ts +1 -2
  165. package/dist/screens/peer-types/peer-type-list.js +13 -24
  166. package/dist/screens/search/global-search.d.ts +1 -2
  167. package/dist/screens/search/global-search.js +104 -182
  168. package/dist/screens/settings/color-mode-dropdown.d.ts +3 -4
  169. package/dist/screens/settings/color-mode-dropdown.js +18 -37
  170. package/dist/screens/settings/settings-page.d.ts +1 -1
  171. package/dist/screens/settings/settings-page.js +86 -213
  172. package/dist/screens/settings/voice-settings-agent.d.ts +1 -1
  173. package/dist/screens/settings/voice-settings-agent.js +7 -44
  174. package/dist/screens/settings/voice-settings-api-keys.d.ts +2 -2
  175. package/dist/screens/settings/voice-settings-api-keys.js +2 -29
  176. package/dist/screens/settings/voice-settings-output.d.ts +2 -2
  177. package/dist/screens/settings/voice-settings-output.js +2 -40
  178. package/dist/screens/settings/voice-settings-providers.d.ts +2 -2
  179. package/dist/screens/settings/voice-settings-providers.js +2 -19
  180. package/dist/screens/settings/voice-settings-types.d.ts +4 -4
  181. package/dist/screens/settings/voice-settings-types.js +31 -31
  182. package/dist/screens/settings/voice-settings-wake-word.d.ts +2 -2
  183. package/dist/screens/settings/voice-settings-wake-word.js +2 -33
  184. package/dist/screens/settings/voice-settings.d.ts +1 -1
  185. package/dist/screens/settings/voice-settings.js +35 -112
  186. package/dist/screens/setup-user.d.ts +1 -2
  187. package/dist/screens/setup-user.js +38 -116
  188. package/dist/screens/tools/tool-code.d.ts +2 -3
  189. package/dist/screens/tools/tool-code.js +9 -13
  190. package/dist/screens/tools/tool-details.d.ts +1 -2
  191. package/dist/screens/tools/tool-details.js +26 -39
  192. package/dist/screens/tools/tool-info.d.ts +2 -3
  193. package/dist/screens/tools/tool-info.js +9 -48
  194. package/dist/screens/tools/tool-list.d.ts +1 -2
  195. package/dist/screens/tools/tool-list.js +33 -65
  196. package/dist/screens/tools/tool-schema.d.ts +2 -3
  197. package/dist/screens/tools/tool-schema.js +2 -13
  198. package/dist/screens/tools/tool-test-details.d.ts +1 -2
  199. package/dist/screens/tools/tool-test-details.js +12 -28
  200. package/dist/screens/tools/tool-test-list.d.ts +1 -2
  201. package/dist/screens/tools/tool-test-list.js +17 -56
  202. package/dist/screens/variables/variable-details.d.ts +1 -2
  203. package/dist/screens/variables/variable-details.js +19 -86
  204. package/dist/screens/variables/variable-list.d.ts +1 -2
  205. package/dist/screens/variables/variable-list.js +16 -27
  206. package/dist/screens/welcome-modal.d.ts +1 -2
  207. package/dist/screens/welcome-modal.js +44 -111
  208. package/dist/screens/workflows/workflow-details.d.ts +1 -2
  209. package/dist/screens/workflows/workflow-details.js +17 -31
  210. package/dist/screens/workflows/workflow-info.d.ts +2 -3
  211. package/dist/screens/workflows/workflow-info.js +2 -9
  212. package/dist/screens/workflows/workflow-instructions.d.ts +2 -3
  213. package/dist/screens/workflows/workflow-instructions.js +23 -55
  214. package/dist/screens/workflows/workflow-list.d.ts +1 -2
  215. package/dist/screens/workflows/workflow-list.js +23 -62
  216. package/dist/setupTests.d.ts +1 -1
  217. package/dist/setupTests.js +10 -11
  218. package/dist/system-apps/assistants.app.d.ts +1 -1
  219. package/dist/system-apps/assistants.app.js +3 -3
  220. package/dist/system-apps/console-logs.app.d.ts +1 -1
  221. package/dist/system-apps/console-logs.app.js +3 -3
  222. package/dist/system-apps/contacts.app.d.ts +1 -1
  223. package/dist/system-apps/contacts.app.js +4 -4
  224. package/dist/system-apps/data-explorer.app.d.ts +1 -1
  225. package/dist/system-apps/data-explorer.app.js +4 -4
  226. package/dist/system-apps/groups.app.d.ts +1 -1
  227. package/dist/system-apps/groups.app.js +4 -4
  228. package/dist/system-apps/index.d.ts +17 -17
  229. package/dist/system-apps/index.js +52 -52
  230. package/dist/system-apps/join-group.app.d.ts +1 -1
  231. package/dist/system-apps/join-group.app.js +4 -4
  232. package/dist/system-apps/mobile-settings.app.d.ts +1 -1
  233. package/dist/system-apps/mobile-settings.app.js +3 -3
  234. package/dist/system-apps/network-viewer.app.d.ts +1 -1
  235. package/dist/system-apps/network-viewer.app.js +4 -4
  236. package/dist/system-apps/packages.app.d.ts +1 -1
  237. package/dist/system-apps/packages.app.js +3 -3
  238. package/dist/system-apps/search.app.d.ts +1 -1
  239. package/dist/system-apps/search.app.js +4 -4
  240. package/dist/system-apps/settings.app.d.ts +1 -1
  241. package/dist/system-apps/settings.app.js +3 -3
  242. package/dist/system-apps/threads.app.d.ts +1 -1
  243. package/dist/system-apps/threads.app.js +3 -3
  244. package/dist/system-apps/tools.app.d.ts +1 -1
  245. package/dist/system-apps/tools.app.js +3 -3
  246. package/dist/system-apps/types.app.d.ts +1 -1
  247. package/dist/system-apps/types.app.js +3 -3
  248. package/dist/system-apps/variables.app.d.ts +1 -1
  249. package/dist/system-apps/variables.app.js +3 -3
  250. package/dist/system-apps/workflows.app.d.ts +1 -1
  251. package/dist/system-apps/workflows.app.js +3 -3
  252. package/dist/tabs-layout/tabs-layout.d.ts +2 -3
  253. package/dist/tabs-layout/tabs-layout.js +215 -246
  254. package/dist/tabs-layout/tabs-state.d.ts +2 -2
  255. package/dist/tabs-layout/tabs-state.js +73 -61
  256. package/dist/ui-defaults/index.d.ts +2 -2
  257. package/dist/ui-defaults/list-screen.d.ts +2 -3
  258. package/dist/ui-defaults/list-screen.js +33 -37
  259. package/dist/ui-defaults/markdown-field.js +24 -56
  260. package/dist/ui-router/routes-loader.d.ts +1 -1
  261. package/dist/ui-router/routes-loader.js +17 -13
  262. package/dist/ui-router/ui-loader.d.ts +6 -6
  263. package/dist/ui-router/ui-loader.js +172 -268
  264. package/dist/utils.js +49 -39
  265. package/jest.config.js +16 -16
  266. package/package.json +16 -14
  267. package/src/command-palette/command-palette-ui.tsx +261 -237
  268. package/src/command-palette/command-palette.ts +81 -78
  269. package/src/components/chat-overlay.tsx +366 -261
  270. package/src/components/checkbox.tsx +15 -12
  271. package/src/components/group-switcher.tsx +150 -105
  272. package/src/components/input-date.tsx +17 -16
  273. package/src/components/input-number.tsx +47 -31
  274. package/src/components/input.tsx +15 -12
  275. package/src/components/inverse-lazy-list.tsx +14 -13
  276. package/src/components/io-schema-values.tsx +51 -69
  277. package/src/components/io-schema.tsx +94 -69
  278. package/src/components/lazy-list.tsx +51 -34
  279. package/src/components/list-screen.tsx +51 -35
  280. package/src/components/loading-indicator.tsx +2 -4
  281. package/src/components/markdown-editor/autolink-plugin.tsx +4 -11
  282. package/src/components/markdown-editor/editor-inline.tsx +3 -4
  283. package/src/components/markdown-editor/editor.tsx +53 -51
  284. package/src/components/markdown-editor/markdown-plugin.tsx +48 -40
  285. package/src/components/markdown-editor/mention-node.ts +39 -38
  286. package/src/components/markdown-editor/mentions-plugin.tsx +99 -101
  287. package/src/components/markdown-editor/theme.ts +28 -29
  288. package/src/components/markdown-editor/toolbar.tsx +53 -47
  289. package/src/components/markdown-with-mentions.tsx +56 -46
  290. package/src/components/message-logs/message-logs.tsx +225 -165
  291. package/src/components/messages/avatar.tsx +70 -52
  292. package/src/components/messages/channel-message-list.tsx +80 -68
  293. package/src/components/messages/channel-view.tsx +34 -33
  294. package/src/components/messages/message-compose.tsx +84 -67
  295. package/src/components/messages/message-display.tsx +103 -89
  296. package/src/components/messages/thread-message-list.tsx +53 -44
  297. package/src/components/router.tsx +42 -43
  298. package/src/components/save-button.tsx +43 -39
  299. package/src/components/sortable-list.tsx +77 -49
  300. package/src/components/tabs.tsx +31 -31
  301. package/src/components/tooltip.tsx +21 -28
  302. package/src/components/trust-level-badge.tsx +15 -11
  303. package/src/components/trust-level-dropdown.tsx +49 -19
  304. package/src/components/typeahead.tsx +57 -59
  305. package/src/components/voice-indicator.tsx +158 -141
  306. package/src/components/voice-subscribe-events.ts +20 -0
  307. package/src/globals.tsx +42 -40
  308. package/src/hooks.test.tsx +141 -134
  309. package/src/hooks.ts +80 -48
  310. package/src/index.tsx +17 -10
  311. package/src/mention-configs.ts +122 -68
  312. package/src/screens/assistants/assistant-config.tsx +28 -18
  313. package/src/screens/assistants/assistant-details.tsx +35 -36
  314. package/src/screens/assistants/assistant-info.tsx +16 -11
  315. package/src/screens/assistants/assistant-list.tsx +37 -34
  316. package/src/screens/assistants/assistant-tools.tsx +41 -20
  317. package/src/screens/console-logs/console-logs-list.tsx +173 -140
  318. package/src/screens/console-logs/log-display.tsx +65 -38
  319. package/src/screens/console-logs/log-filters.tsx +4 -3
  320. package/src/screens/console-logs/mobile-log-card.tsx +78 -71
  321. package/src/screens/console-logs/resizable-table-header.tsx +29 -21
  322. package/src/screens/contacts/contact-details.tsx +29 -30
  323. package/src/screens/contacts/contact-list.tsx +71 -60
  324. package/src/screens/contacts/index.ts +5 -5
  325. package/src/screens/contacts/user-connect.tsx +177 -171
  326. package/src/screens/data-explorer/data-explorer.tsx +134 -98
  327. package/src/screens/data-explorer/index.ts +2 -3
  328. package/src/screens/data-explorer/query-executor.tsx +90 -80
  329. package/src/screens/groups/group-details.tsx +120 -101
  330. package/src/screens/groups/group-invite-listener.tsx +34 -37
  331. package/src/screens/groups/group-list.tsx +119 -103
  332. package/src/screens/groups/group-members.tsx +225 -164
  333. package/src/screens/groups/index.ts +5 -6
  334. package/src/screens/join-group/index.ts +2 -2
  335. package/src/screens/join-group/join-group.tsx +41 -39
  336. package/src/screens/network-viewer/connection-troubleshooter.tsx +145 -104
  337. package/src/screens/network-viewer/cpu-usage-graph.tsx +39 -43
  338. package/src/screens/network-viewer/device-details-modal.tsx +46 -59
  339. package/src/screens/network-viewer/group-details-modal.tsx +68 -49
  340. package/src/screens/network-viewer/index.ts +4 -5
  341. package/src/screens/network-viewer/network-viewer-ipc.ts +23 -0
  342. package/src/screens/network-viewer/network-viewer.tsx +261 -236
  343. package/src/screens/network-viewer/usage-graph.tsx +57 -49
  344. package/src/screens/packages/package-details.tsx +43 -35
  345. package/src/screens/packages/package-info.tsx +107 -66
  346. package/src/screens/packages/package-list.tsx +175 -98
  347. package/src/screens/packages/package-new-local.tsx +28 -26
  348. package/src/screens/packages/package-versions.tsx +102 -77
  349. package/src/screens/peer-types/peer-type-details.tsx +60 -50
  350. package/src/screens/peer-types/peer-type-list.tsx +20 -30
  351. package/src/screens/search/global-search.tsx +153 -137
  352. package/src/screens/settings/color-mode-dropdown.tsx +52 -35
  353. package/src/screens/settings/settings-page.tsx +215 -141
  354. package/src/screens/settings/voice-settings-agent.tsx +13 -12
  355. package/src/screens/settings/voice-settings-api-keys.tsx +14 -12
  356. package/src/screens/settings/voice-settings-output.tsx +12 -11
  357. package/src/screens/settings/voice-settings-providers.tsx +7 -3
  358. package/src/screens/settings/voice-settings-types.ts +52 -49
  359. package/src/screens/settings/voice-settings-wake-word.tsx +25 -9
  360. package/src/screens/settings/voice-settings.tsx +66 -43
  361. package/src/screens/setup-user.tsx +88 -41
  362. package/src/screens/tools/tool-code.tsx +12 -17
  363. package/src/screens/tools/tool-details.tsx +28 -28
  364. package/src/screens/tools/tool-info.tsx +14 -19
  365. package/src/screens/tools/tool-list.tsx +58 -40
  366. package/src/screens/tools/tool-schema.tsx +16 -9
  367. package/src/screens/tools/tool-test-details.tsx +11 -22
  368. package/src/screens/tools/tool-test-list.tsx +29 -30
  369. package/src/screens/variables/variable-details.tsx +63 -51
  370. package/src/screens/variables/variable-list.tsx +29 -30
  371. package/src/screens/welcome-modal.tsx +68 -48
  372. package/src/screens/workflows/workflow-details.tsx +40 -30
  373. package/src/screens/workflows/workflow-info.tsx +4 -11
  374. package/src/screens/workflows/workflow-instructions.tsx +35 -28
  375. package/src/screens/workflows/workflow-list.tsx +50 -40
  376. package/src/setupTests.ts +14 -13
  377. package/src/system-apps/assistants.app.ts +5 -5
  378. package/src/system-apps/console-logs.app.ts +4 -4
  379. package/src/system-apps/contacts.app.ts +6 -6
  380. package/src/system-apps/data-explorer.app.ts +5 -5
  381. package/src/system-apps/groups.app.ts +6 -6
  382. package/src/system-apps/index.ts +49 -49
  383. package/src/system-apps/join-group.app.ts +5 -5
  384. package/src/system-apps/mobile-settings.app.ts +4 -5
  385. package/src/system-apps/network-viewer.app.ts +5 -5
  386. package/src/system-apps/packages.app.ts +5 -5
  387. package/src/system-apps/search.app.ts +6 -6
  388. package/src/system-apps/settings.app.ts +5 -5
  389. package/src/system-apps/threads.app.ts +5 -5
  390. package/src/system-apps/tools.app.ts +5 -5
  391. package/src/system-apps/types.app.ts +5 -5
  392. package/src/system-apps/variables.app.ts +5 -5
  393. package/src/system-apps/workflows.app.ts +5 -5
  394. package/src/tabs-layout/tabs-layout.tsx +345 -254
  395. package/src/tabs-layout/tabs-state.ts +100 -81
  396. package/src/ui-defaults/index.ts +2 -3
  397. package/src/ui-defaults/list-screen.tsx +45 -40
  398. package/src/ui-defaults/markdown-field.tsx +22 -26
  399. package/src/ui-router/routes-loader.ts +40 -24
  400. package/src/ui-router/ui-loader.tsx +312 -214
  401. package/src/utils.ts +68 -81
  402. package/tsconfig.json +5 -10
  403. package/dist/components/input-datetime.d.ts +0 -7
  404. package/dist/components/input-datetime.js +0 -35
  405. package/dist/components/lazy-sortable-list.d.ts +0 -29
  406. package/dist/components/lazy-sortable-list.js +0 -12
  407. package/dist/components/left-bar.d.ts +0 -5
  408. package/dist/components/left-bar.js +0 -207
  409. package/dist/components/main-content-container.d.ts +0 -2
  410. package/dist/components/main-content-container.js +0 -92
  411. package/dist/components/messages/thread-view.d.ts +0 -6
  412. package/dist/components/messages/thread-view.js +0 -174
  413. package/dist/components/off-canvas.d.ts +0 -13
  414. package/dist/components/off-canvas.js +0 -89
  415. package/dist/components/text-list-editor.tsx/text-list-editor.d.ts +0 -6
  416. package/dist/components/text-list-editor.tsx/text-list-editor.js +0 -13
  417. package/dist/components/top-bar.d.ts +0 -2
  418. package/dist/components/top-bar.js +0 -51
  419. package/dist/components/typeahead/mentions-plugin.d.ts +0 -7
  420. package/dist/components/typeahead/mentions-plugin.js +0 -203
  421. package/dist/components/typeahead/typeahead-editor.d.ts +0 -15
  422. package/dist/components/typeahead/typeahead-editor.js +0 -134
  423. package/dist/components/typeahead/typeahead.d.ts +0 -12
  424. package/dist/components/typeahead/typeahead.js +0 -94
  425. package/dist/screens/profile.d.ts +0 -2
  426. package/dist/screens/profile.js +0 -76
  427. package/src/components/input-datetime.tsx +0 -41
  428. package/src/components/lazy-sortable-list.tsx +0 -51
  429. package/src/components/left-bar.tsx +0 -322
  430. package/src/components/main-content-container.tsx +0 -79
  431. package/src/components/messages/thread-view.tsx +0 -214
  432. package/src/components/off-canvas.tsx +0 -83
  433. package/src/components/text-list-editor.tsx/text-list-editor.tsx +0 -13
  434. package/src/components/top-bar.tsx +0 -119
  435. package/src/components/typeahead/mentions-plugin.tsx +0 -265
  436. package/src/components/typeahead/typeahead-editor.tsx +0 -140
  437. package/src/components/typeahead/typeahead.tsx +0 -77
  438. package/src/screens/profile.tsx +0 -75
@@ -1,29 +1,51 @@
1
1
  /**
2
2
  * Chat Overlay Component
3
- *
3
+ *
4
4
  * Floating UI element combining voice input with chat functionality.
5
5
  * Shows minimized voice button that expands to full chat overlay.
6
6
  * Supports both voice and text input in the same thread.
7
7
  */
8
8
 
9
- import React, { useState, useEffect, useCallback, useRef } from 'react';
10
- import { rpcServerCalls, subscribe, IMessage, Messages, newid, groupUserVar, userVar, PersistentVar, deviceVar } from '@peers-app/peers-sdk';
11
- import { MessageCompose } from './messages/message-compose';
12
- import { MessageDisplay } from './messages/message-display';
13
- import { me } from '../globals';
14
- import { sortBy } from 'lodash';
15
-
16
- type VoiceState = 'disabled' | 'idle' | 'listening' | 'recording' | 'processing' | 'speaking';
9
+ import {
10
+ deviceVar,
11
+ groupUserVar,
12
+ type IMessage,
13
+ Messages,
14
+ newid,
15
+ type PersistentVar,
16
+ rpcServerCalls,
17
+ subscribe,
18
+ userVar,
19
+ } from "@peers-app/peers-sdk";
20
+ import { sortBy } from "lodash";
21
+ import type React from "react";
22
+ import { useCallback, useEffect, useRef, useState } from "react";
23
+ import { me } from "../globals";
24
+ import { MessageCompose } from "./messages/message-compose";
25
+ import { MessageDisplay } from "./messages/message-display";
26
+ import type {
27
+ ChatOpenWithMessagePayload,
28
+ VoiceErrorPayload,
29
+ VoicePlayAudioPayload,
30
+ VoiceSpeakPayload,
31
+ VoiceStatePayload,
32
+ VoiceSubscribeEvent,
33
+ VoiceThreadPayload,
34
+ VoiceTranscriptionPayload,
35
+ VoiceVolumePayload,
36
+ } from "./voice-subscribe-events";
37
+
38
+ type VoiceState = "disabled" | "idle" | "listening" | "recording" | "processing" | "speaking";
17
39
 
18
40
  interface ChatOverlayProps {
19
41
  /** Position of the overlay */
20
- position?: 'bottom-right' | 'bottom-left';
42
+ position?: "bottom-right" | "bottom-left";
21
43
  }
22
44
 
23
45
  // Voice settings interface (must match voice-settings.tsx)
24
46
  interface VoiceSettingsData {
25
47
  enabled: boolean;
26
- voiceOutputEnabled?: boolean; // TTS responses. Defaults to true when voice input enabled.
48
+ voiceOutputEnabled?: boolean; // TTS responses. Defaults to true when voice input enabled.
27
49
  wakeWord: string;
28
50
  wakeWordSensitivity: number;
29
51
  speechSensitivity: number;
@@ -36,11 +58,11 @@ interface VoiceSettingsData {
36
58
  const DEFAULT_VOICE_SETTINGS: VoiceSettingsData = {
37
59
  enabled: false,
38
60
  voiceOutputEnabled: true,
39
- wakeWord: 'COMPUTER',
61
+ wakeWord: "COMPUTER",
40
62
  wakeWordSensitivity: 0.5,
41
63
  speechSensitivity: 0.3,
42
- sttProvider: 'auto',
43
- ttsProvider: 'browser',
64
+ sttProvider: "auto",
65
+ ttsProvider: "browser",
44
66
  ttsSpeed: 1.0,
45
67
  };
46
68
 
@@ -49,29 +71,35 @@ let chatOverlayThreadPvar: PersistentVar<string | null> | null = null;
49
71
 
50
72
  function getChatOverlayThreadPvar(): PersistentVar<string | null> {
51
73
  if (!chatOverlayThreadPvar) {
52
- chatOverlayThreadPvar = groupUserVar<string | null>('chatOverlayThreadId', { defaultValue: null });
74
+ chatOverlayThreadPvar = groupUserVar<string | null>("chatOverlayThreadId", {
75
+ defaultValue: null,
76
+ });
53
77
  }
54
78
  return chatOverlayThreadPvar;
55
79
  }
56
80
 
57
- const voiceHubActive = deviceVar<boolean>('voiceHub:active', { defaultValue: false });
81
+ const voiceHubActive = deviceVar<boolean>("voiceHub:active", { defaultValue: false });
58
82
 
59
83
  // Voice settings pvar (shared with voice-settings.tsx and voice-service.ts)
60
- const voiceSettingsPvar = userVar<VoiceSettingsData>('voiceSettings', { defaultValue: DEFAULT_VOICE_SETTINGS });
84
+ const voiceSettingsPvar = userVar<VoiceSettingsData>("voiceSettings", {
85
+ defaultValue: DEFAULT_VOICE_SETTINGS,
86
+ });
61
87
 
62
88
  // Position pvar for overlay placement
63
89
  interface OverlayPosition {
64
- x: number; // distance from right edge
65
- y: number; // distance from bottom edge
90
+ x: number; // distance from right edge
91
+ y: number; // distance from bottom edge
66
92
  }
67
93
  const DEFAULT_POSITION: OverlayPosition = { x: 20, y: 20 };
68
- const overlayPositionPvar = deviceVar<OverlayPosition>('chatOverlayPosition', { defaultValue: DEFAULT_POSITION });
94
+ const overlayPositionPvar = deviceVar<OverlayPosition>("chatOverlayPosition", {
95
+ defaultValue: DEFAULT_POSITION,
96
+ });
69
97
 
70
98
  export const ChatOverlay: React.FC<ChatOverlayProps> = ({
71
- position = 'bottom-right',
99
+ position: _position = "bottom-right",
72
100
  }) => {
73
- const [voiceState, setVoiceState] = useState<VoiceState>('disabled');
74
- const [transcription, setTranscription] = useState<string>('');
101
+ const [voiceState, setVoiceState] = useState<VoiceState>("disabled");
102
+ const [_transcription, setTranscription] = useState<string>("");
75
103
  const [volumeLevel, setVolumeLevel] = useState<number>(0);
76
104
  const [error, setError] = useState<string | null>(null);
77
105
  const [isExpanded, setIsExpanded] = useState(false);
@@ -83,11 +111,22 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
83
111
  const [overlayPosition, setOverlayPosition] = useState<OverlayPosition>(DEFAULT_POSITION);
84
112
  const [isDragging, setIsDragging] = useState(false);
85
113
  const [pendingInitialMessage, setPendingInitialMessage] = useState<string | undefined>(undefined);
86
-
114
+
87
115
  const messagesEndRef = useRef<HTMLDivElement>(null);
88
116
  const messagesContainerRef = useRef<HTMLDivElement>(null);
89
117
  const currentAudioRef = useRef<HTMLAudioElement | null>(null);
90
- const dragStartRef = useRef<{ mouseX: number; mouseY: number; posX: number; posY: number } | null>(null);
118
+ const dragStartRef = useRef<{
119
+ mouseX: number;
120
+ mouseY: number;
121
+ posX: number;
122
+ posY: number;
123
+ } | null>(null);
124
+
125
+ const scrollToBottom = useCallback(() => {
126
+ setTimeout(() => {
127
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
128
+ }, 100);
129
+ }, []);
91
130
 
92
131
  // Load thread from pvar on mount
93
132
  useEffect(() => {
@@ -100,7 +139,7 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
100
139
  rpcServerCalls.voiceSetThreadId?.(savedThreadId).catch(() => {});
101
140
  }
102
141
  });
103
-
142
+
104
143
  // Subscribe to pvar changes (from other devices)
105
144
  const sub = pvar.subscribe((newThreadId) => {
106
145
  if (newThreadId !== threadId) {
@@ -108,9 +147,9 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
108
147
  rpcServerCalls.voiceSetThreadId?.(newThreadId).catch(() => {});
109
148
  }
110
149
  });
111
-
150
+
112
151
  return () => sub.dispose();
113
- }, []);
152
+ }, [threadId]);
114
153
 
115
154
  // Load voice settings from pvar
116
155
  useEffect(() => {
@@ -120,14 +159,14 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
120
159
  setVoiceSettings(saved);
121
160
  }
122
161
  });
123
-
162
+
124
163
  // Subscribe to settings changes
125
164
  const sub = voiceSettingsPvar.subscribe((newSettings) => {
126
165
  if (newSettings) {
127
166
  setVoiceSettings(newSettings);
128
167
  }
129
168
  });
130
-
169
+
131
170
  return () => sub.dispose();
132
171
  }, []);
133
172
 
@@ -142,32 +181,41 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
142
181
  }, []);
143
182
 
144
183
  // Drag handlers
145
- const handleDragStart = useCallback((e: React.MouseEvent) => {
146
- // Only start drag on middle-click or when holding shift
147
- if (e.button === 1 || e.shiftKey) {
148
- e.preventDefault();
149
- setIsDragging(true);
150
- dragStartRef.current = {
151
- mouseX: e.clientX,
152
- mouseY: e.clientY,
153
- posX: overlayPosition.x,
154
- posY: overlayPosition.y,
155
- };
156
- }
157
- }, [overlayPosition]);
184
+ const handleDragStart = useCallback(
185
+ (e: React.MouseEvent) => {
186
+ // Only start drag on middle-click or when holding shift
187
+ if (e.button === 1 || e.shiftKey) {
188
+ e.preventDefault();
189
+ setIsDragging(true);
190
+ dragStartRef.current = {
191
+ mouseX: e.clientX,
192
+ mouseY: e.clientY,
193
+ posX: overlayPosition.x,
194
+ posY: overlayPosition.y,
195
+ };
196
+ }
197
+ },
198
+ [overlayPosition],
199
+ );
158
200
 
159
201
  useEffect(() => {
160
202
  if (!isDragging) return;
161
203
 
162
204
  const handleMouseMove = (e: MouseEvent) => {
163
205
  if (!dragStartRef.current) return;
164
-
206
+
165
207
  const deltaX = dragStartRef.current.mouseX - e.clientX;
166
208
  const deltaY = dragStartRef.current.mouseY - e.clientY;
167
-
168
- const newX = Math.max(10, Math.min(window.innerWidth - 80, dragStartRef.current.posX + deltaX));
169
- const newY = Math.max(10, Math.min(window.innerHeight - 80, dragStartRef.current.posY + deltaY));
170
-
209
+
210
+ const newX = Math.max(
211
+ 10,
212
+ Math.min(window.innerWidth - 80, dragStartRef.current.posX + deltaX),
213
+ );
214
+ const newY = Math.max(
215
+ 10,
216
+ Math.min(window.innerHeight - 80, dragStartRef.current.posY + deltaY),
217
+ );
218
+
171
219
  setOverlayPosition({ x: newX, y: newY });
172
220
  };
173
221
 
@@ -178,12 +226,12 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
178
226
  overlayPositionPvar(overlayPosition);
179
227
  };
180
228
 
181
- document.addEventListener('mousemove', handleMouseMove);
182
- document.addEventListener('mouseup', handleMouseUp);
183
-
229
+ document.addEventListener("mousemove", handleMouseMove);
230
+ document.addEventListener("mouseup", handleMouseUp);
231
+
184
232
  return () => {
185
- document.removeEventListener('mousemove', handleMouseMove);
186
- document.removeEventListener('mouseup', handleMouseUp);
233
+ document.removeEventListener("mousemove", handleMouseMove);
234
+ document.removeEventListener("mouseup", handleMouseUp);
187
235
  };
188
236
  }, [isDragging, overlayPosition]);
189
237
 
@@ -197,39 +245,43 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
197
245
  }
198
246
  }, 50);
199
247
  }
200
- }, [isExpanded, messages, parentMessage]);
248
+ }, [isExpanded]);
201
249
 
202
250
  // Subscribe to voice events
203
251
  useEffect(() => {
204
252
  const subscriptions = [
205
- subscribe('chat:openWithMessage', (event: any) => {
206
- const { message } = event.data || {};
207
- if (message) {
208
- setPendingInitialMessage(message);
209
- }
210
- setIsExpanded(true);
211
- setShowSettings(false);
212
- }),
213
- subscribe('voice:stateChanged', (event: any) => {
214
- setVoiceState(event.data.state);
215
- if (event.data.state === 'idle') {
216
- setTranscription('');
253
+ subscribe(
254
+ "chat:openWithMessage",
255
+ (event: VoiceSubscribeEvent<ChatOpenWithMessagePayload>) => {
256
+ const { message } = event.data || {};
257
+ if (message) {
258
+ const text = typeof message === "string" ? message : message.message;
259
+ setPendingInitialMessage(text);
260
+ }
261
+ setIsExpanded(true);
262
+ setShowSettings(false);
263
+ },
264
+ ),
265
+ subscribe("voice:stateChanged", (event: VoiceSubscribeEvent<VoiceStatePayload>) => {
266
+ setVoiceState(event.data.state as VoiceState);
267
+ if (event.data.state === "idle") {
268
+ setTranscription("");
217
269
  setError(null);
218
270
  }
219
- if (event.data.state === 'recording' && !voiceHubActive()) {
271
+ if (event.data.state === "recording" && !voiceHubActive()) {
220
272
  setIsExpanded(true);
221
273
  }
222
274
  }),
223
- subscribe('voice:transcription', (event: any) => {
275
+ subscribe("voice:transcription", (event: VoiceSubscribeEvent<VoiceTranscriptionPayload>) => {
224
276
  setTranscription(event.data.text);
225
277
  }),
226
- subscribe('voice:volumeLevel', (event: any) => {
278
+ subscribe("voice:volumeLevel", (event: VoiceSubscribeEvent<VoiceVolumePayload>) => {
227
279
  setVolumeLevel(event.data.level);
228
280
  }),
229
- subscribe('voice:error', (event: any) => {
281
+ subscribe("voice:error", (event: VoiceSubscribeEvent<VoiceErrorPayload>) => {
230
282
  setError(event.data.message);
231
283
  }),
232
- subscribe('voice:threadChanged', (event: any) => {
284
+ subscribe("voice:threadChanged", (event: VoiceSubscribeEvent<VoiceThreadPayload>) => {
233
285
  const newThreadId = event.data.threadId;
234
286
  setThreadId(newThreadId);
235
287
  // Sync to pvar
@@ -239,50 +291,61 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
239
291
  ];
240
292
 
241
293
  // Load initial state
242
- rpcServerCalls.voiceGetState().then(({ state }) => {
243
- setVoiceState(state);
244
- }).catch(() => {});
294
+ rpcServerCalls
295
+ .voiceGetState()
296
+ .then(({ state }) => {
297
+ setVoiceState(state);
298
+ })
299
+ .catch(() => {});
245
300
 
246
301
  // Load initial thread from voice service
247
- rpcServerCalls.voiceGetThreadId?.().then((id) => {
248
- if (id) {
249
- setThreadId(id);
250
- const pvar = getChatOverlayThreadPvar();
251
- pvar(id);
252
- }
253
- }).catch(() => {});
302
+ rpcServerCalls
303
+ .voiceGetThreadId?.()
304
+ .then((id) => {
305
+ if (id) {
306
+ setThreadId(id);
307
+ const pvar = getChatOverlayThreadPvar();
308
+ pvar(id);
309
+ }
310
+ })
311
+ .catch(() => {});
254
312
 
255
313
  return () => {
256
- subscriptions.forEach(sub => sub.unsubscribe());
314
+ for (const sub of subscriptions) {
315
+ sub.unsubscribe();
316
+ }
257
317
  };
258
318
  }, []);
259
319
 
260
320
  // Handle browser TTS events
261
321
  useEffect(() => {
262
- const speakHandler = subscribe('voice:speakText', (event: any) => {
263
- const { text, voice, rate } = event.data;
264
- if ('speechSynthesis' in window) {
265
- const utterance = new SpeechSynthesisUtterance(text);
266
- if (voice) {
267
- const voices = speechSynthesis.getVoices();
268
- const selectedVoice = voices.find(v => v.name === voice);
269
- if (selectedVoice) utterance.voice = selectedVoice;
270
- }
271
- if (rate) utterance.rate = rate;
272
- utterance.onend = () => {
273
- rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => {});
274
- };
275
- utterance.onerror = () => {
322
+ const speakHandler = subscribe(
323
+ "voice:speakText",
324
+ (event: VoiceSubscribeEvent<VoiceSpeakPayload>) => {
325
+ const { text, voice, rate } = event.data;
326
+ if ("speechSynthesis" in window) {
327
+ const utterance = new SpeechSynthesisUtterance(text);
328
+ if (voice) {
329
+ const voices = speechSynthesis.getVoices();
330
+ const selectedVoice = voices.find((v) => v.name === voice);
331
+ if (selectedVoice) utterance.voice = selectedVoice;
332
+ }
333
+ if (rate) utterance.rate = rate;
334
+ utterance.onend = () => {
335
+ rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => {});
336
+ };
337
+ utterance.onerror = () => {
338
+ rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => {});
339
+ };
340
+ speechSynthesis.speak(utterance);
341
+ } else {
276
342
  rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => {});
277
- };
278
- speechSynthesis.speak(utterance);
279
- } else {
280
- rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => {});
281
- }
282
- });
343
+ }
344
+ },
345
+ );
283
346
 
284
- const stopHandler = subscribe('voice:stopSpeaking', () => {
285
- if ('speechSynthesis' in window) {
347
+ const stopHandler = subscribe("voice:stopSpeaking", () => {
348
+ if ("speechSynthesis" in window) {
286
349
  speechSynthesis.cancel();
287
350
  }
288
351
  if (currentAudioRef.current) {
@@ -291,26 +354,29 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
291
354
  }
292
355
  });
293
356
 
294
- const playHandler = subscribe('voice:playAudio', async (event: any) => {
295
- const { audioBase64, mimeType } = event.data;
296
- try {
297
- const audioData = Uint8Array.from(atob(audioBase64), c => c.charCodeAt(0));
298
- const blob = new Blob([audioData], { type: mimeType });
299
- const url = URL.createObjectURL(blob);
300
- const audio = new Audio(url);
301
- currentAudioRef.current = audio;
302
- audio.onended = () => {
303
- URL.revokeObjectURL(url);
357
+ const playHandler = subscribe(
358
+ "voice:playAudio",
359
+ async (event: VoiceSubscribeEvent<VoicePlayAudioPayload>) => {
360
+ const { audioBase64, mimeType } = event.data;
361
+ try {
362
+ const audioData = Uint8Array.from(atob(audioBase64), (c) => c.charCodeAt(0));
363
+ const blob = new Blob([audioData], { type: mimeType });
364
+ const url = URL.createObjectURL(blob);
365
+ const audio = new Audio(url);
366
+ currentAudioRef.current = audio;
367
+ audio.onended = () => {
368
+ URL.revokeObjectURL(url);
369
+ currentAudioRef.current = null;
370
+ rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => {});
371
+ };
372
+ await audio.play();
373
+ } catch (e) {
374
+ console.error("Failed to play audio:", e);
304
375
  currentAudioRef.current = null;
305
376
  rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => {});
306
- };
307
- await audio.play();
308
- } catch (e) {
309
- console.error('Failed to play audio:', e);
310
- currentAudioRef.current = null;
311
- rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => {});
312
- }
313
- });
377
+ }
378
+ },
379
+ );
314
380
 
315
381
  return () => {
316
382
  speakHandler.unsubscribe();
@@ -327,58 +393,62 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
327
393
  return;
328
394
  }
329
395
 
396
+ const currentThreadId = threadId;
397
+
330
398
  async function loadMessages() {
331
- const parent = await Messages().get(threadId!);
399
+ const parent = await Messages().get(currentThreadId);
332
400
  setParentMessage(parent || null);
333
-
401
+
334
402
  const msgs = await Messages().list(
335
- { messageParentId: threadId },
336
- { sortBy: ['createdAt', 'messageId'] }
403
+ { messageParentId: currentThreadId },
404
+ { sortBy: ["createdAt", "messageId"] },
337
405
  );
338
406
  setMessages(msgs);
339
407
  scrollToBottom();
340
408
  }
341
409
 
342
- loadMessages();
410
+ void loadMessages();
343
411
 
344
412
  // Subscribe to message changes
345
413
  const sub = Messages().dataChanged.subscribe((evt) => {
346
- if (evt.dataObject.messageParentId === threadId || evt.dataObject.messageId === threadId) {
347
- loadMessages();
414
+ if (
415
+ evt.dataObject.messageParentId === currentThreadId ||
416
+ evt.dataObject.messageId === currentThreadId
417
+ ) {
418
+ void loadMessages();
348
419
  }
349
420
  });
350
421
 
351
- return () => { sub.unsubscribe(); };
352
- }, [threadId]);
353
-
354
- const scrollToBottom = () => {
355
- setTimeout(() => {
356
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
357
- }, 100);
358
- };
359
-
360
- const updateVoiceSetting = useCallback(<K extends keyof VoiceSettingsData>(key: K, value: VoiceSettingsData[K]) => {
361
- const newSettings = { ...voiceSettings, [key]: value };
362
- setVoiceSettings(newSettings);
363
- voiceSettingsPvar(newSettings);
364
- }, [voiceSettings]);
422
+ return () => {
423
+ sub.unsubscribe();
424
+ };
425
+ }, [threadId, scrollToBottom]);
426
+
427
+ const updateVoiceSetting = useCallback(
428
+ <K extends keyof VoiceSettingsData>(key: K, value: VoiceSettingsData[K]) => {
429
+ const newSettings = { ...voiceSettings, [key]: value };
430
+ setVoiceSettings(newSettings);
431
+ voiceSettingsPvar(newSettings);
432
+ },
433
+ [voiceSettings],
434
+ );
365
435
 
366
436
  const handleVoiceClick = useCallback(async () => {
367
437
  try {
368
- if (voiceState === 'disabled') {
369
- window.location.hash = '#settings?tab=voice';
438
+ if (voiceState === "disabled") {
439
+ window.location.hash = "#settings?tab=voice";
370
440
  return;
371
441
  }
372
442
 
373
- if (voiceState === 'recording') {
443
+ if (voiceState === "recording") {
374
444
  await rpcServerCalls.voiceStopRecording();
375
- } else if (voiceState === 'speaking') {
445
+ } else if (voiceState === "speaking") {
376
446
  await rpcServerCalls.voiceStopPlayback();
377
- } else if (voiceState === 'idle' || voiceState === 'listening') {
447
+ } else if (voiceState === "idle" || voiceState === "listening") {
378
448
  await rpcServerCalls.voiceStartRecording();
379
449
  }
380
450
  } catch (e) {
381
- console.error('Voice action failed:', e);
451
+ console.error("Voice action failed:", e);
382
452
  }
383
453
  }, [voiceState]);
384
454
 
@@ -391,122 +461,143 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
391
461
  getChatOverlayThreadPvar()(null);
392
462
  }, []);
393
463
 
394
- const handleMessageSubmit = useCallback(async (message: IMessage) => {
395
- // Notify voice service of text activity (resets inactivity timer)
396
- rpcServerCalls.voiceNotifyTextActivity?.().catch(() => {});
464
+ const handleMessageSubmit = useCallback(
465
+ async (message: IMessage) => {
466
+ // Notify voice service of text activity (resets inactivity timer)
467
+ rpcServerCalls.voiceNotifyTextActivity?.().catch(() => {});
468
+
469
+ // Clear any pending initial message that was pre-filled
470
+ setPendingInitialMessage(undefined);
471
+
472
+ // If no thread yet, this message becomes the thread root
473
+ if (!threadId) {
474
+ message.messageParentId = undefined;
475
+ await Messages().save(message);
476
+ setThreadId(message.messageId);
477
+ // Sync to voice service and pvar
478
+ rpcServerCalls.voiceSetThreadId?.(message.messageId).catch(() => {});
479
+ getChatOverlayThreadPvar()(message.messageId);
480
+ } else {
481
+ message.messageParentId = threadId;
482
+ await Messages().save(message);
483
+ }
397
484
 
398
- // Clear any pending initial message that was pre-filled
399
- setPendingInitialMessage(undefined);
400
-
401
- // If no thread yet, this message becomes the thread root
402
- if (!threadId) {
403
- message.messageParentId = undefined;
404
- await Messages().save(message);
405
- setThreadId(message.messageId);
406
- // Sync to voice service and pvar
407
- rpcServerCalls.voiceSetThreadId?.(message.messageId).catch(() => {});
408
- getChatOverlayThreadPvar()(message.messageId);
409
- } else {
410
- message.messageParentId = threadId;
411
- await Messages().save(message);
412
- }
413
-
414
- // Add to local state immediately for responsiveness
415
- setMessages(prev => sortBy([...prev, message], 'createdAt'));
416
- scrollToBottom();
417
- }, [threadId]);
485
+ // Add to local state immediately for responsiveness
486
+ setMessages((prev) => sortBy([...prev, message], "createdAt"));
487
+ scrollToBottom();
488
+ },
489
+ [threadId, scrollToBottom],
490
+ );
418
491
 
419
492
  const getVoiceIcon = (): string => {
420
493
  switch (voiceState) {
421
- case 'disabled': return 'bi-mic-mute';
422
- case 'idle': return 'bi-mic';
423
- case 'listening': return 'bi-ear';
424
- case 'recording': return 'bi-mic-fill';
425
- case 'processing': return 'bi-hourglass-split';
426
- case 'speaking': return 'bi-volume-up-fill';
427
- default: return 'bi-mic';
494
+ case "disabled":
495
+ return "bi-mic-mute";
496
+ case "idle":
497
+ return "bi-mic";
498
+ case "listening":
499
+ return "bi-ear";
500
+ case "recording":
501
+ return "bi-mic-fill";
502
+ case "processing":
503
+ return "bi-hourglass-split";
504
+ case "speaking":
505
+ return "bi-volume-up-fill";
506
+ default:
507
+ return "bi-mic";
428
508
  }
429
509
  };
430
510
 
431
511
  const getStateText = (): string => {
432
512
  switch (voiceState) {
433
- case 'disabled': return 'Voice disabled';
434
- case 'idle': return 'Say wake word or click';
435
- case 'listening': return 'Listening...';
436
- case 'recording': return 'Recording...';
437
- case 'processing': return 'Processing...';
438
- case 'speaking': return 'Speaking... (click to stop)';
439
- default: return '';
513
+ case "disabled":
514
+ return "Voice disabled";
515
+ case "idle":
516
+ return "Say wake word or click";
517
+ case "listening":
518
+ return "Listening...";
519
+ case "recording":
520
+ return "Recording...";
521
+ case "processing":
522
+ return "Processing...";
523
+ case "speaking":
524
+ return "Speaking... (click to stop)";
525
+ default:
526
+ return "";
440
527
  }
441
528
  };
442
529
 
443
530
  const getButtonClass = (): string => {
444
- const base = 'btn rounded-circle shadow';
531
+ const base = "btn rounded-circle shadow";
445
532
  switch (voiceState) {
446
- case 'disabled': return `${base} btn-secondary`;
447
- case 'idle': return `${base} btn-outline-primary`;
448
- case 'listening': return `${base} btn-info`;
449
- case 'recording': return `${base} btn-danger`;
450
- case 'processing': return `${base} btn-warning`;
451
- case 'speaking': return `${base} btn-success`;
452
- default: return `${base} btn-secondary`;
533
+ case "disabled":
534
+ return `${base} btn-secondary`;
535
+ case "idle":
536
+ return `${base} btn-outline-primary`;
537
+ case "listening":
538
+ return `${base} btn-info`;
539
+ case "recording":
540
+ return `${base} btn-danger`;
541
+ case "processing":
542
+ return `${base} btn-warning`;
543
+ case "speaking":
544
+ return `${base} btn-success`;
545
+ default:
546
+ return `${base} btn-secondary`;
453
547
  }
454
548
  };
455
549
 
456
550
  const positionStyles: React.CSSProperties = {
457
- position: 'fixed',
551
+ position: "fixed",
458
552
  zIndex: 1050,
459
553
  bottom: `${overlayPosition.y}px`,
460
554
  right: `${overlayPosition.x}px`,
461
- cursor: isDragging ? 'grabbing' : undefined,
555
+ cursor: isDragging ? "grabbing" : undefined,
462
556
  };
463
557
 
464
558
  return (
465
559
  <div style={positionStyles}>
466
560
  {/* Expanded chat panel */}
467
561
  {isExpanded && (
468
- <div
562
+ <div
469
563
  className="card shadow mb-2"
470
- style={{
471
- width: '400px',
472
- maxHeight: '70vh',
473
- backgroundColor: 'var(--bs-body-bg)',
474
- display: 'flex',
475
- flexDirection: 'column',
564
+ style={{
565
+ width: "400px",
566
+ maxHeight: "70vh",
567
+ backgroundColor: "var(--bs-body-bg)",
568
+ display: "flex",
569
+ flexDirection: "column",
476
570
  }}
477
571
  >
478
572
  {/* Header */}
479
573
  <div className="card-header py-2 px-3 d-flex justify-content-between align-items-center">
480
574
  <div className="d-flex align-items-center gap-2">
481
- <button
575
+ <button
482
576
  className="btn btn-sm btn-outline-secondary"
483
577
  onClick={startNewThread}
484
578
  title="Start new thread"
485
579
  >
486
580
  <i className="bi bi-plus-lg"></i>
487
581
  </button>
488
- <button
489
- className={`btn btn-sm ${showSettings ? 'btn-secondary' : 'btn-outline-secondary'}`}
582
+ <button
583
+ className={`btn btn-sm ${showSettings ? "btn-secondary" : "btn-outline-secondary"}`}
490
584
  onClick={() => setShowSettings(!showSettings)}
491
585
  title="Voice settings"
492
586
  >
493
587
  <i className="bi bi-gear"></i>
494
588
  </button>
495
589
  <small className="text-muted">
496
- {showSettings ? 'Settings' : (threadId ? 'Thread' : 'New conversation')}
590
+ {showSettings ? "Settings" : threadId ? "Thread" : "New conversation"}
497
591
  </small>
498
592
  </div>
499
- <button
500
- className="btn btn-sm btn-link p-0"
501
- onClick={() => setIsExpanded(false)}
502
- >
593
+ <button className="btn btn-sm btn-link p-0" onClick={() => setIsExpanded(false)}>
503
594
  <i className="bi bi-chevron-down"></i>
504
595
  </button>
505
596
  </div>
506
597
 
507
598
  {/* Settings panel */}
508
599
  {showSettings && (
509
- <div className="card-body p-3" style={{ maxHeight: '50vh', overflowY: 'auto' }}>
600
+ <div className="card-body p-3" style={{ maxHeight: "50vh", overflowY: "auto" }}>
510
601
  {/* Voice Input Enabled Toggle */}
511
602
  <div className="d-flex align-items-center justify-content-between mb-2">
512
603
  <label className="form-label mb-0">Voice Input</label>
@@ -516,11 +607,11 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
516
607
  type="checkbox"
517
608
  id="voiceEnabledToggle"
518
609
  checked={voiceSettings.enabled}
519
- onChange={(e) => updateVoiceSetting('enabled', e.target.checked)}
610
+ onChange={(e) => updateVoiceSetting("enabled", e.target.checked)}
520
611
  role="switch"
521
612
  />
522
613
  <label className="form-check-label" htmlFor="voiceEnabledToggle">
523
- {voiceSettings.enabled ? 'On' : 'Off'}
614
+ {voiceSettings.enabled ? "On" : "Off"}
524
615
  </label>
525
616
  </div>
526
617
  </div>
@@ -535,11 +626,11 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
535
626
  type="checkbox"
536
627
  id="voiceOutputEnabledToggle"
537
628
  checked={voiceSettings.voiceOutputEnabled !== false}
538
- onChange={(e) => updateVoiceSetting('voiceOutputEnabled', e.target.checked)}
629
+ onChange={(e) => updateVoiceSetting("voiceOutputEnabled", e.target.checked)}
539
630
  role="switch"
540
631
  />
541
632
  <label className="form-check-label" htmlFor="voiceOutputEnabledToggle">
542
- {voiceSettings.voiceOutputEnabled !== false ? 'On' : 'Off'}
633
+ {voiceSettings.voiceOutputEnabled !== false ? "On" : "Off"}
543
634
  </label>
544
635
  </div>
545
636
  </div>
@@ -557,11 +648,17 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
557
648
  max="1"
558
649
  step="0.05"
559
650
  value={voiceSettings.wakeWordSensitivity}
560
- onChange={(e) => updateVoiceSetting('wakeWordSensitivity', parseFloat(e.target.value))}
651
+ onChange={(e) =>
652
+ updateVoiceSetting("wakeWordSensitivity", parseFloat(e.target.value))
653
+ }
561
654
  />
562
655
  <div className="d-flex justify-content-between">
563
- <small className="text-muted" style={{ fontSize: '0.7rem' }}>Less sensitive</small>
564
- <small className="text-muted" style={{ fontSize: '0.7rem' }}>More sensitive</small>
656
+ <small className="text-muted" style={{ fontSize: "0.7rem" }}>
657
+ Less sensitive
658
+ </small>
659
+ <small className="text-muted" style={{ fontSize: "0.7rem" }}>
660
+ More sensitive
661
+ </small>
565
662
  </div>
566
663
  </div>
567
664
 
@@ -577,11 +674,17 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
577
674
  max="1"
578
675
  step="0.05"
579
676
  value={voiceSettings.speechSensitivity}
580
- onChange={(e) => updateVoiceSetting('speechSensitivity', parseFloat(e.target.value))}
677
+ onChange={(e) =>
678
+ updateVoiceSetting("speechSensitivity", parseFloat(e.target.value))
679
+ }
581
680
  />
582
681
  <div className="d-flex justify-content-between">
583
- <small className="text-muted" style={{ fontSize: '0.7rem' }}>Quiet (louder speech)</small>
584
- <small className="text-muted" style={{ fontSize: '0.7rem' }}>Noisy (softer speech)</small>
682
+ <small className="text-muted" style={{ fontSize: "0.7rem" }}>
683
+ Quiet (louder speech)
684
+ </small>
685
+ <small className="text-muted" style={{ fontSize: "0.7rem" }}>
686
+ Noisy (softer speech)
687
+ </small>
585
688
  </div>
586
689
  </div>
587
690
 
@@ -595,14 +698,14 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
595
698
 
596
699
  {/* Messages area */}
597
700
  {!showSettings && (
598
- <div
701
+ <div
599
702
  ref={messagesContainerRef}
600
703
  className="card-body p-2"
601
- style={{
602
- flex: 1,
603
- overflowY: 'auto',
604
- maxHeight: '40vh',
605
- minHeight: '100px',
704
+ style={{
705
+ flex: 1,
706
+ overflowY: "auto",
707
+ maxHeight: "40vh",
708
+ minHeight: "100px",
606
709
  }}
607
710
  >
608
711
  {/* Parent message if exists */}
@@ -615,9 +718,9 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
615
718
 
616
719
  {/* Thread messages */}
617
720
  {messages.map((message, index) => (
618
- <MessageDisplay
619
- key={message.messageId}
620
- message={message}
721
+ <MessageDisplay
722
+ key={message.messageId}
723
+ message={message}
621
724
  messageAbove={messages[index - 1]}
622
725
  />
623
726
  ))}
@@ -635,14 +738,14 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
635
738
  )}
636
739
 
637
740
  {/* Voice status bar - only show when not in settings */}
638
- {!showSettings && voiceState !== 'disabled' && voiceState !== 'idle' && (
741
+ {!showSettings && voiceState !== "disabled" && voiceState !== "idle" && (
639
742
  <div className="px-3 py-1 border-top">
640
743
  <div className="d-flex align-items-center gap-2">
641
744
  <small className="text-muted">{getStateText()}</small>
642
- {voiceState === 'recording' && (
643
- <div className="progress flex-grow-1" style={{ height: '4px' }}>
644
- <div
645
- className="progress-bar bg-danger"
745
+ {voiceState === "recording" && (
746
+ <div className="progress flex-grow-1" style={{ height: "4px" }}>
747
+ <div
748
+ className="progress-bar bg-danger"
646
749
  style={{ width: `${Math.min(100, volumeLevel * 500)}%` }}
647
750
  />
648
751
  </div>
@@ -655,11 +758,13 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
655
758
  {!showSettings && (
656
759
  <div className="card-footer p-2">
657
760
  <MessageCompose
658
- channelId={me?.userId || 'default'}
761
+ channelId={me?.userId || "default"}
659
762
  threadId={threadId || newid()}
660
763
  onMessageSubmit={handleMessageSubmit}
661
764
  initialMessage={pendingInitialMessage}
662
- key={pendingInitialMessage ? `pre-${pendingInitialMessage.slice(0, 20)}` : 'default'}
765
+ key={
766
+ pendingInitialMessage ? `pre-${pendingInitialMessage.slice(0, 20)}` : "default"
767
+ }
663
768
  />
664
769
  </div>
665
770
  )}
@@ -667,48 +772,48 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
667
772
  )}
668
773
 
669
774
  {/* Floating button row */}
670
- <div
775
+ <div
671
776
  className="d-flex gap-2 justify-content-end"
672
777
  onMouseDown={handleDragStart}
673
- style={{ userSelect: isDragging ? 'none' : undefined }}
778
+ style={{ userSelect: isDragging ? "none" : undefined }}
674
779
  >
675
780
  {/* Voice button - only show when voice is enabled and chat is expanded */}
676
781
  {voiceSettings.enabled && isExpanded && (
677
782
  <button
678
783
  className={getButtonClass()}
679
- style={{
680
- width: '56px',
681
- height: '56px',
682
- fontSize: '24px',
683
- transition: 'all 0.2s ease',
684
- position: 'relative',
784
+ style={{
785
+ width: "56px",
786
+ height: "56px",
787
+ fontSize: "24px",
788
+ transition: "all 0.2s ease",
789
+ position: "relative",
685
790
  }}
686
791
  onClick={handleVoiceClick}
687
792
  title={getStateText()}
688
793
  >
689
794
  <i className={`bi ${getVoiceIcon()}`}></i>
690
-
795
+
691
796
  {/* Pulsing animation for listening/recording */}
692
- {(voiceState === 'listening' || voiceState === 'recording') && (
693
- <span
797
+ {(voiceState === "listening" || voiceState === "recording") && (
798
+ <span
694
799
  className="position-absolute"
695
800
  style={{
696
801
  top: 0,
697
802
  left: 0,
698
803
  right: 0,
699
804
  bottom: 0,
700
- borderRadius: '50%',
701
- border: '2px solid currentColor',
702
- animation: 'pulse 1.5s ease-out infinite',
805
+ borderRadius: "50%",
806
+ border: "2px solid currentColor",
807
+ animation: "pulse 1.5s ease-out infinite",
703
808
  }}
704
809
  />
705
810
  )}
706
811
 
707
812
  {/* Processing spinner */}
708
- {voiceState === 'processing' && (
709
- <span
813
+ {voiceState === "processing" && (
814
+ <span
710
815
  className="spinner-border spinner-border-sm position-absolute"
711
- style={{ top: '4px', right: '4px' }}
816
+ style={{ top: "4px", right: "4px" }}
712
817
  />
713
818
  )}
714
819
  </button>
@@ -716,16 +821,16 @@ export const ChatOverlay: React.FC<ChatOverlayProps> = ({
716
821
 
717
822
  {/* Chat expand/collapse button - always visible, stays on right */}
718
823
  <button
719
- className={`btn rounded-circle shadow ${isExpanded ? 'btn-primary' : 'btn-light'}`}
720
- style={{
721
- width: '56px',
722
- height: '56px',
723
- fontSize: '20px',
824
+ className={`btn rounded-circle shadow ${isExpanded ? "btn-primary" : "btn-light"}`}
825
+ style={{
826
+ width: "56px",
827
+ height: "56px",
828
+ fontSize: "20px",
724
829
  }}
725
830
  onClick={() => setIsExpanded(!isExpanded)}
726
831
  title={isExpanded ? "Close chat (Shift+drag to move)" : "Open chat (Shift+drag to move)"}
727
832
  >
728
- <i className={`bi ${isExpanded ? 'bi-chat-dots-fill' : 'bi-chat-dots'}`}></i>
833
+ <i className={`bi ${isExpanded ? "bi-chat-dots-fill" : "bi-chat-dots"}`}></i>
729
834
  </button>
730
835
  </div>
731
836