@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.
- package/.github/workflows/publish.yml +8 -5
- package/babel.config.js +4 -4
- package/biome.json +191 -0
- package/dist/command-palette/command-palette-ui.d.ts +1 -2
- package/dist/command-palette/command-palette-ui.js +175 -244
- package/dist/command-palette/command-palette.js +65 -64
- package/dist/components/chat-overlay.d.ts +2 -2
- package/dist/components/chat-overlay.js +160 -217
- package/dist/components/checkbox.d.ts +5 -4
- package/dist/components/checkbox.js +4 -7
- package/dist/components/group-switcher.d.ts +1 -2
- package/dist/components/group-switcher.js +119 -159
- package/dist/components/input-date.d.ts +3 -3
- package/dist/components/input-date.js +6 -6
- package/dist/components/input-number.d.ts +7 -6
- package/dist/components/input-number.js +25 -20
- package/dist/components/input.d.ts +5 -4
- package/dist/components/input.js +4 -7
- package/dist/components/inverse-lazy-list.d.ts +3 -3
- package/dist/components/inverse-lazy-list.js +13 -47
- package/dist/components/io-schema-values.d.ts +5 -6
- package/dist/components/io-schema-values.js +28 -65
- package/dist/components/io-schema.d.ts +4 -5
- package/dist/components/io-schema.js +42 -79
- package/dist/components/lazy-list.d.ts +3 -3
- package/dist/components/lazy-list.js +38 -58
- package/dist/components/list-screen.d.ts +3 -8
- package/dist/components/list-screen.js +28 -23
- package/dist/components/loading-indicator.d.ts +1 -2
- package/dist/components/loading-indicator.js +2 -6
- package/dist/components/markdown-editor/autolink-plugin.js +5 -8
- package/dist/components/markdown-editor/editor-inline.d.ts +2 -3
- package/dist/components/markdown-editor/editor-inline.js +2 -6
- package/dist/components/markdown-editor/editor.d.ts +6 -6
- package/dist/components/markdown-editor/editor.js +9 -19
- package/dist/components/markdown-editor/markdown-plugin.d.ts +1 -1
- package/dist/components/markdown-editor/markdown-plugin.js +20 -21
- package/dist/components/markdown-editor/mention-node.d.ts +2 -2
- package/dist/components/markdown-editor/mention-node.js +24 -24
- package/dist/components/markdown-editor/mentions-plugin.d.ts +2 -2
- package/dist/components/markdown-editor/mentions-plugin.js +61 -62
- package/dist/components/markdown-editor/theme.js +28 -28
- package/dist/components/markdown-editor/toolbar.d.ts +2 -3
- package/dist/components/markdown-editor/toolbar.js +32 -49
- package/dist/components/markdown-with-mentions.d.ts +1 -1
- package/dist/components/markdown-with-mentions.js +43 -43
- package/dist/components/message-logs/message-logs.d.ts +1 -2
- package/dist/components/message-logs/message-logs.js +91 -116
- package/dist/components/messages/avatar.d.ts +3 -4
- package/dist/components/messages/avatar.js +37 -46
- package/dist/components/messages/channel-message-list.d.ts +5 -6
- package/dist/components/messages/channel-message-list.js +34 -34
- package/dist/components/messages/channel-view.d.ts +1 -2
- package/dist/components/messages/channel-view.js +23 -57
- package/dist/components/messages/message-compose.d.ts +3 -4
- package/dist/components/messages/message-compose.js +27 -38
- package/dist/components/messages/message-display.d.ts +2 -3
- package/dist/components/messages/message-display.js +42 -95
- package/dist/components/messages/thread-message-list.d.ts +4 -5
- package/dist/components/messages/thread-message-list.js +29 -29
- package/dist/components/router.d.ts +1 -2
- package/dist/components/router.js +58 -66
- package/dist/components/save-button.d.ts +3 -3
- package/dist/components/save-button.js +23 -33
- package/dist/components/sortable-list.d.ts +11 -12
- package/dist/components/sortable-list.js +42 -22
- package/dist/components/tabs.d.ts +3 -3
- package/dist/components/tabs.js +16 -47
- package/dist/components/tooltip.d.ts +4 -4
- package/dist/components/tooltip.js +4 -9
- package/dist/components/trust-level-badge.d.ts +2 -3
- package/dist/components/trust-level-badge.js +16 -16
- package/dist/components/trust-level-dropdown.d.ts +3 -4
- package/dist/components/trust-level-dropdown.js +32 -55
- package/dist/components/typeahead.d.ts +3 -3
- package/dist/components/typeahead.js +48 -89
- package/dist/components/voice-indicator.d.ts +2 -2
- package/dist/components/voice-indicator.js +93 -106
- package/dist/components/voice-subscribe-events.d.ts +32 -0
- package/dist/components/voice-subscribe-events.js +2 -0
- package/dist/globals.d.ts +3 -5
- package/dist/globals.js +22 -33
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +22 -12
- package/dist/hooks.test.js +129 -145
- package/dist/index.d.ts +9 -8
- package/dist/index.js +13 -12
- package/dist/mention-configs.d.ts +2 -2
- package/dist/mention-configs.js +55 -42
- package/dist/screens/assistants/assistant-config.d.ts +2 -3
- package/dist/screens/assistants/assistant-config.js +9 -22
- package/dist/screens/assistants/assistant-details.d.ts +1 -2
- package/dist/screens/assistants/assistant-details.js +13 -28
- package/dist/screens/assistants/assistant-info.d.ts +2 -3
- package/dist/screens/assistants/assistant-info.js +3 -17
- package/dist/screens/assistants/assistant-list.d.ts +1 -2
- package/dist/screens/assistants/assistant-list.js +15 -56
- package/dist/screens/assistants/assistant-tools.d.ts +2 -3
- package/dist/screens/assistants/assistant-tools.js +10 -24
- package/dist/screens/console-logs/console-logs-list.d.ts +1 -2
- package/dist/screens/console-logs/console-logs-list.js +130 -134
- package/dist/screens/console-logs/log-display.d.ts +2 -3
- package/dist/screens/console-logs/log-display.js +40 -42
- package/dist/screens/console-logs/log-filters.d.ts +1 -2
- package/dist/screens/console-logs/log-filters.js +2 -24
- package/dist/screens/console-logs/mobile-log-card.d.ts +2 -3
- package/dist/screens/console-logs/mobile-log-card.js +64 -67
- package/dist/screens/console-logs/resizable-table-header.d.ts +1 -2
- package/dist/screens/console-logs/resizable-table-header.js +31 -67
- package/dist/screens/contacts/contact-details.d.ts +1 -2
- package/dist/screens/contacts/contact-details.js +16 -46
- package/dist/screens/contacts/contact-list.d.ts +1 -2
- package/dist/screens/contacts/contact-list.js +44 -103
- package/dist/screens/contacts/index.d.ts +4 -4
- package/dist/screens/contacts/index.js +1 -1
- package/dist/screens/contacts/user-connect.d.ts +1 -2
- package/dist/screens/contacts/user-connect.js +85 -186
- package/dist/screens/data-explorer/data-explorer.d.ts +1 -2
- package/dist/screens/data-explorer/data-explorer.js +61 -181
- package/dist/screens/data-explorer/index.d.ts +2 -2
- package/dist/screens/data-explorer/query-executor.d.ts +1 -2
- package/dist/screens/data-explorer/query-executor.js +56 -166
- package/dist/screens/groups/group-details.d.ts +1 -2
- package/dist/screens/groups/group-details.js +27 -122
- package/dist/screens/groups/group-invite-listener.d.ts +2 -3
- package/dist/screens/groups/group-invite-listener.js +8 -104
- package/dist/screens/groups/group-list.d.ts +1 -2
- package/dist/screens/groups/group-list.js +56 -133
- package/dist/screens/groups/group-members.d.ts +2 -3
- package/dist/screens/groups/group-members.js +62 -132
- package/dist/screens/groups/index.d.ts +4 -4
- package/dist/screens/groups/index.js +1 -1
- package/dist/screens/join-group/index.d.ts +2 -2
- package/dist/screens/join-group/join-group.d.ts +1 -2
- package/dist/screens/join-group/join-group.js +9 -109
- package/dist/screens/network-viewer/connection-troubleshooter.d.ts +2 -3
- package/dist/screens/network-viewer/connection-troubleshooter.js +89 -193
- package/dist/screens/network-viewer/cpu-usage-graph.d.ts +1 -2
- package/dist/screens/network-viewer/cpu-usage-graph.js +60 -99
- package/dist/screens/network-viewer/device-details-modal.d.ts +1 -2
- package/dist/screens/network-viewer/device-details-modal.js +25 -177
- package/dist/screens/network-viewer/group-details-modal.d.ts +1 -2
- package/dist/screens/network-viewer/group-details-modal.js +31 -142
- package/dist/screens/network-viewer/index.d.ts +4 -4
- package/dist/screens/network-viewer/index.js +3 -3
- package/dist/screens/network-viewer/network-viewer-ipc.d.ts +22 -0
- package/dist/screens/network-viewer/network-viewer-ipc.js +6 -0
- package/dist/screens/network-viewer/network-viewer.d.ts +1 -2
- package/dist/screens/network-viewer/network-viewer.js +91 -296
- package/dist/screens/network-viewer/usage-graph.d.ts +1 -2
- package/dist/screens/network-viewer/usage-graph.js +78 -110
- package/dist/screens/packages/package-details.d.ts +1 -2
- package/dist/screens/packages/package-details.js +35 -41
- package/dist/screens/packages/package-info.d.ts +2 -2
- package/dist/screens/packages/package-info.js +33 -86
- package/dist/screens/packages/package-list.d.ts +1 -2
- package/dist/screens/packages/package-list.js +42 -106
- package/dist/screens/packages/package-new-local.d.ts +1 -2
- package/dist/screens/packages/package-new-local.js +13 -19
- package/dist/screens/packages/package-versions.d.ts +2 -3
- package/dist/screens/packages/package-versions.js +29 -96
- package/dist/screens/peer-types/peer-type-details.d.ts +3 -4
- package/dist/screens/peer-types/peer-type-details.js +26 -78
- package/dist/screens/peer-types/peer-type-list.d.ts +1 -2
- package/dist/screens/peer-types/peer-type-list.js +13 -24
- package/dist/screens/search/global-search.d.ts +1 -2
- package/dist/screens/search/global-search.js +104 -182
- package/dist/screens/settings/color-mode-dropdown.d.ts +3 -4
- package/dist/screens/settings/color-mode-dropdown.js +18 -37
- package/dist/screens/settings/settings-page.d.ts +1 -1
- package/dist/screens/settings/settings-page.js +86 -213
- package/dist/screens/settings/voice-settings-agent.d.ts +1 -1
- package/dist/screens/settings/voice-settings-agent.js +7 -44
- package/dist/screens/settings/voice-settings-api-keys.d.ts +2 -2
- package/dist/screens/settings/voice-settings-api-keys.js +2 -29
- package/dist/screens/settings/voice-settings-output.d.ts +2 -2
- package/dist/screens/settings/voice-settings-output.js +2 -40
- package/dist/screens/settings/voice-settings-providers.d.ts +2 -2
- package/dist/screens/settings/voice-settings-providers.js +2 -19
- package/dist/screens/settings/voice-settings-types.d.ts +4 -4
- package/dist/screens/settings/voice-settings-types.js +31 -31
- package/dist/screens/settings/voice-settings-wake-word.d.ts +2 -2
- package/dist/screens/settings/voice-settings-wake-word.js +2 -33
- package/dist/screens/settings/voice-settings.d.ts +1 -1
- package/dist/screens/settings/voice-settings.js +35 -112
- package/dist/screens/setup-user.d.ts +1 -2
- package/dist/screens/setup-user.js +38 -116
- package/dist/screens/tools/tool-code.d.ts +2 -3
- package/dist/screens/tools/tool-code.js +9 -13
- package/dist/screens/tools/tool-details.d.ts +1 -2
- package/dist/screens/tools/tool-details.js +26 -39
- package/dist/screens/tools/tool-info.d.ts +2 -3
- package/dist/screens/tools/tool-info.js +9 -48
- package/dist/screens/tools/tool-list.d.ts +1 -2
- package/dist/screens/tools/tool-list.js +33 -65
- package/dist/screens/tools/tool-schema.d.ts +2 -3
- package/dist/screens/tools/tool-schema.js +2 -13
- package/dist/screens/tools/tool-test-details.d.ts +1 -2
- package/dist/screens/tools/tool-test-details.js +12 -28
- package/dist/screens/tools/tool-test-list.d.ts +1 -2
- package/dist/screens/tools/tool-test-list.js +17 -56
- package/dist/screens/variables/variable-details.d.ts +1 -2
- package/dist/screens/variables/variable-details.js +19 -86
- package/dist/screens/variables/variable-list.d.ts +1 -2
- package/dist/screens/variables/variable-list.js +16 -27
- package/dist/screens/welcome-modal.d.ts +1 -2
- package/dist/screens/welcome-modal.js +44 -111
- package/dist/screens/workflows/workflow-details.d.ts +1 -2
- package/dist/screens/workflows/workflow-details.js +17 -31
- package/dist/screens/workflows/workflow-info.d.ts +2 -3
- package/dist/screens/workflows/workflow-info.js +2 -9
- package/dist/screens/workflows/workflow-instructions.d.ts +2 -3
- package/dist/screens/workflows/workflow-instructions.js +23 -55
- package/dist/screens/workflows/workflow-list.d.ts +1 -2
- package/dist/screens/workflows/workflow-list.js +23 -62
- package/dist/setupTests.d.ts +1 -1
- package/dist/setupTests.js +10 -11
- package/dist/system-apps/assistants.app.d.ts +1 -1
- package/dist/system-apps/assistants.app.js +3 -3
- package/dist/system-apps/console-logs.app.d.ts +1 -1
- package/dist/system-apps/console-logs.app.js +3 -3
- package/dist/system-apps/contacts.app.d.ts +1 -1
- package/dist/system-apps/contacts.app.js +4 -4
- package/dist/system-apps/data-explorer.app.d.ts +1 -1
- package/dist/system-apps/data-explorer.app.js +4 -4
- package/dist/system-apps/groups.app.d.ts +1 -1
- package/dist/system-apps/groups.app.js +4 -4
- package/dist/system-apps/index.d.ts +17 -17
- package/dist/system-apps/index.js +52 -52
- package/dist/system-apps/join-group.app.d.ts +1 -1
- package/dist/system-apps/join-group.app.js +4 -4
- package/dist/system-apps/mobile-settings.app.d.ts +1 -1
- package/dist/system-apps/mobile-settings.app.js +3 -3
- package/dist/system-apps/network-viewer.app.d.ts +1 -1
- package/dist/system-apps/network-viewer.app.js +4 -4
- package/dist/system-apps/packages.app.d.ts +1 -1
- package/dist/system-apps/packages.app.js +3 -3
- package/dist/system-apps/search.app.d.ts +1 -1
- package/dist/system-apps/search.app.js +4 -4
- package/dist/system-apps/settings.app.d.ts +1 -1
- package/dist/system-apps/settings.app.js +3 -3
- package/dist/system-apps/threads.app.d.ts +1 -1
- package/dist/system-apps/threads.app.js +3 -3
- package/dist/system-apps/tools.app.d.ts +1 -1
- package/dist/system-apps/tools.app.js +3 -3
- package/dist/system-apps/types.app.d.ts +1 -1
- package/dist/system-apps/types.app.js +3 -3
- package/dist/system-apps/variables.app.d.ts +1 -1
- package/dist/system-apps/variables.app.js +3 -3
- package/dist/system-apps/workflows.app.d.ts +1 -1
- package/dist/system-apps/workflows.app.js +3 -3
- package/dist/tabs-layout/tabs-layout.d.ts +2 -3
- package/dist/tabs-layout/tabs-layout.js +215 -246
- package/dist/tabs-layout/tabs-state.d.ts +2 -2
- package/dist/tabs-layout/tabs-state.js +73 -61
- package/dist/ui-defaults/index.d.ts +2 -2
- package/dist/ui-defaults/list-screen.d.ts +2 -3
- package/dist/ui-defaults/list-screen.js +33 -37
- package/dist/ui-defaults/markdown-field.js +24 -56
- package/dist/ui-router/routes-loader.d.ts +1 -1
- package/dist/ui-router/routes-loader.js +17 -13
- package/dist/ui-router/ui-loader.d.ts +6 -6
- package/dist/ui-router/ui-loader.js +172 -268
- package/dist/utils.js +49 -39
- package/jest.config.js +16 -16
- package/package.json +16 -14
- package/src/command-palette/command-palette-ui.tsx +261 -237
- package/src/command-palette/command-palette.ts +81 -78
- package/src/components/chat-overlay.tsx +366 -261
- package/src/components/checkbox.tsx +15 -12
- package/src/components/group-switcher.tsx +150 -105
- package/src/components/input-date.tsx +17 -16
- package/src/components/input-number.tsx +47 -31
- package/src/components/input.tsx +15 -12
- package/src/components/inverse-lazy-list.tsx +14 -13
- package/src/components/io-schema-values.tsx +51 -69
- package/src/components/io-schema.tsx +94 -69
- package/src/components/lazy-list.tsx +51 -34
- package/src/components/list-screen.tsx +51 -35
- package/src/components/loading-indicator.tsx +2 -4
- package/src/components/markdown-editor/autolink-plugin.tsx +4 -11
- package/src/components/markdown-editor/editor-inline.tsx +3 -4
- package/src/components/markdown-editor/editor.tsx +53 -51
- package/src/components/markdown-editor/markdown-plugin.tsx +48 -40
- package/src/components/markdown-editor/mention-node.ts +39 -38
- package/src/components/markdown-editor/mentions-plugin.tsx +99 -101
- package/src/components/markdown-editor/theme.ts +28 -29
- package/src/components/markdown-editor/toolbar.tsx +53 -47
- package/src/components/markdown-with-mentions.tsx +56 -46
- package/src/components/message-logs/message-logs.tsx +225 -165
- package/src/components/messages/avatar.tsx +70 -52
- package/src/components/messages/channel-message-list.tsx +80 -68
- package/src/components/messages/channel-view.tsx +34 -33
- package/src/components/messages/message-compose.tsx +84 -67
- package/src/components/messages/message-display.tsx +103 -89
- package/src/components/messages/thread-message-list.tsx +53 -44
- package/src/components/router.tsx +42 -43
- package/src/components/save-button.tsx +43 -39
- package/src/components/sortable-list.tsx +77 -49
- package/src/components/tabs.tsx +31 -31
- package/src/components/tooltip.tsx +21 -28
- package/src/components/trust-level-badge.tsx +15 -11
- package/src/components/trust-level-dropdown.tsx +49 -19
- package/src/components/typeahead.tsx +57 -59
- package/src/components/voice-indicator.tsx +158 -141
- package/src/components/voice-subscribe-events.ts +20 -0
- package/src/globals.tsx +42 -40
- package/src/hooks.test.tsx +141 -134
- package/src/hooks.ts +80 -48
- package/src/index.tsx +17 -10
- package/src/mention-configs.ts +122 -68
- package/src/screens/assistants/assistant-config.tsx +28 -18
- package/src/screens/assistants/assistant-details.tsx +35 -36
- package/src/screens/assistants/assistant-info.tsx +16 -11
- package/src/screens/assistants/assistant-list.tsx +37 -34
- package/src/screens/assistants/assistant-tools.tsx +41 -20
- package/src/screens/console-logs/console-logs-list.tsx +173 -140
- package/src/screens/console-logs/log-display.tsx +65 -38
- package/src/screens/console-logs/log-filters.tsx +4 -3
- package/src/screens/console-logs/mobile-log-card.tsx +78 -71
- package/src/screens/console-logs/resizable-table-header.tsx +29 -21
- package/src/screens/contacts/contact-details.tsx +29 -30
- package/src/screens/contacts/contact-list.tsx +71 -60
- package/src/screens/contacts/index.ts +5 -5
- package/src/screens/contacts/user-connect.tsx +177 -171
- package/src/screens/data-explorer/data-explorer.tsx +134 -98
- package/src/screens/data-explorer/index.ts +2 -3
- package/src/screens/data-explorer/query-executor.tsx +90 -80
- package/src/screens/groups/group-details.tsx +120 -101
- package/src/screens/groups/group-invite-listener.tsx +34 -37
- package/src/screens/groups/group-list.tsx +119 -103
- package/src/screens/groups/group-members.tsx +225 -164
- package/src/screens/groups/index.ts +5 -6
- package/src/screens/join-group/index.ts +2 -2
- package/src/screens/join-group/join-group.tsx +41 -39
- package/src/screens/network-viewer/connection-troubleshooter.tsx +145 -104
- package/src/screens/network-viewer/cpu-usage-graph.tsx +39 -43
- package/src/screens/network-viewer/device-details-modal.tsx +46 -59
- package/src/screens/network-viewer/group-details-modal.tsx +68 -49
- package/src/screens/network-viewer/index.ts +4 -5
- package/src/screens/network-viewer/network-viewer-ipc.ts +23 -0
- package/src/screens/network-viewer/network-viewer.tsx +261 -236
- package/src/screens/network-viewer/usage-graph.tsx +57 -49
- package/src/screens/packages/package-details.tsx +43 -35
- package/src/screens/packages/package-info.tsx +107 -66
- package/src/screens/packages/package-list.tsx +175 -98
- package/src/screens/packages/package-new-local.tsx +28 -26
- package/src/screens/packages/package-versions.tsx +102 -77
- package/src/screens/peer-types/peer-type-details.tsx +60 -50
- package/src/screens/peer-types/peer-type-list.tsx +20 -30
- package/src/screens/search/global-search.tsx +153 -137
- package/src/screens/settings/color-mode-dropdown.tsx +52 -35
- package/src/screens/settings/settings-page.tsx +215 -141
- package/src/screens/settings/voice-settings-agent.tsx +13 -12
- package/src/screens/settings/voice-settings-api-keys.tsx +14 -12
- package/src/screens/settings/voice-settings-output.tsx +12 -11
- package/src/screens/settings/voice-settings-providers.tsx +7 -3
- package/src/screens/settings/voice-settings-types.ts +52 -49
- package/src/screens/settings/voice-settings-wake-word.tsx +25 -9
- package/src/screens/settings/voice-settings.tsx +66 -43
- package/src/screens/setup-user.tsx +88 -41
- package/src/screens/tools/tool-code.tsx +12 -17
- package/src/screens/tools/tool-details.tsx +28 -28
- package/src/screens/tools/tool-info.tsx +14 -19
- package/src/screens/tools/tool-list.tsx +58 -40
- package/src/screens/tools/tool-schema.tsx +16 -9
- package/src/screens/tools/tool-test-details.tsx +11 -22
- package/src/screens/tools/tool-test-list.tsx +29 -30
- package/src/screens/variables/variable-details.tsx +63 -51
- package/src/screens/variables/variable-list.tsx +29 -30
- package/src/screens/welcome-modal.tsx +68 -48
- package/src/screens/workflows/workflow-details.tsx +40 -30
- package/src/screens/workflows/workflow-info.tsx +4 -11
- package/src/screens/workflows/workflow-instructions.tsx +35 -28
- package/src/screens/workflows/workflow-list.tsx +50 -40
- package/src/setupTests.ts +14 -13
- package/src/system-apps/assistants.app.ts +5 -5
- package/src/system-apps/console-logs.app.ts +4 -4
- package/src/system-apps/contacts.app.ts +6 -6
- package/src/system-apps/data-explorer.app.ts +5 -5
- package/src/system-apps/groups.app.ts +6 -6
- package/src/system-apps/index.ts +49 -49
- package/src/system-apps/join-group.app.ts +5 -5
- package/src/system-apps/mobile-settings.app.ts +4 -5
- package/src/system-apps/network-viewer.app.ts +5 -5
- package/src/system-apps/packages.app.ts +5 -5
- package/src/system-apps/search.app.ts +6 -6
- package/src/system-apps/settings.app.ts +5 -5
- package/src/system-apps/threads.app.ts +5 -5
- package/src/system-apps/tools.app.ts +5 -5
- package/src/system-apps/types.app.ts +5 -5
- package/src/system-apps/variables.app.ts +5 -5
- package/src/system-apps/workflows.app.ts +5 -5
- package/src/tabs-layout/tabs-layout.tsx +345 -254
- package/src/tabs-layout/tabs-state.ts +100 -81
- package/src/ui-defaults/index.ts +2 -3
- package/src/ui-defaults/list-screen.tsx +45 -40
- package/src/ui-defaults/markdown-field.tsx +22 -26
- package/src/ui-router/routes-loader.ts +40 -24
- package/src/ui-router/ui-loader.tsx +312 -214
- package/src/utils.ts +68 -81
- package/tsconfig.json +5 -10
- package/dist/components/input-datetime.d.ts +0 -7
- package/dist/components/input-datetime.js +0 -35
- package/dist/components/lazy-sortable-list.d.ts +0 -29
- package/dist/components/lazy-sortable-list.js +0 -12
- package/dist/components/left-bar.d.ts +0 -5
- package/dist/components/left-bar.js +0 -207
- package/dist/components/main-content-container.d.ts +0 -2
- package/dist/components/main-content-container.js +0 -92
- package/dist/components/messages/thread-view.d.ts +0 -6
- package/dist/components/messages/thread-view.js +0 -174
- package/dist/components/off-canvas.d.ts +0 -13
- package/dist/components/off-canvas.js +0 -89
- package/dist/components/text-list-editor.tsx/text-list-editor.d.ts +0 -6
- package/dist/components/text-list-editor.tsx/text-list-editor.js +0 -13
- package/dist/components/top-bar.d.ts +0 -2
- package/dist/components/top-bar.js +0 -51
- package/dist/components/typeahead/mentions-plugin.d.ts +0 -7
- package/dist/components/typeahead/mentions-plugin.js +0 -203
- package/dist/components/typeahead/typeahead-editor.d.ts +0 -15
- package/dist/components/typeahead/typeahead-editor.js +0 -134
- package/dist/components/typeahead/typeahead.d.ts +0 -12
- package/dist/components/typeahead/typeahead.js +0 -94
- package/dist/screens/profile.d.ts +0 -2
- package/dist/screens/profile.js +0 -76
- package/src/components/input-datetime.tsx +0 -41
- package/src/components/lazy-sortable-list.tsx +0 -51
- package/src/components/left-bar.tsx +0 -322
- package/src/components/main-content-container.tsx +0 -79
- package/src/components/messages/thread-view.tsx +0 -214
- package/src/components/off-canvas.tsx +0 -83
- package/src/components/text-list-editor.tsx/text-list-editor.tsx +0 -13
- package/src/components/top-bar.tsx +0 -119
- package/src/components/typeahead/mentions-plugin.tsx +0 -265
- package/src/components/typeahead/typeahead-editor.tsx +0 -140
- package/src/components/typeahead/typeahead.tsx +0 -77
- 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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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?:
|
|
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;
|
|
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:
|
|
61
|
+
wakeWord: "COMPUTER",
|
|
40
62
|
wakeWordSensitivity: 0.5,
|
|
41
63
|
speechSensitivity: 0.3,
|
|
42
|
-
sttProvider:
|
|
43
|
-
ttsProvider:
|
|
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>(
|
|
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>(
|
|
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>(
|
|
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;
|
|
65
|
-
y: number;
|
|
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>(
|
|
94
|
+
const overlayPositionPvar = deviceVar<OverlayPosition>("chatOverlayPosition", {
|
|
95
|
+
defaultValue: DEFAULT_POSITION,
|
|
96
|
+
});
|
|
69
97
|
|
|
70
98
|
export const ChatOverlay: React.FC<ChatOverlayProps> = ({
|
|
71
|
-
position =
|
|
99
|
+
position: _position = "bottom-right",
|
|
72
100
|
}) => {
|
|
73
|
-
const [voiceState, setVoiceState] = useState<VoiceState>(
|
|
74
|
-
const [
|
|
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<{
|
|
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(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
e.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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(
|
|
169
|
-
|
|
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(
|
|
182
|
-
document.addEventListener(
|
|
183
|
-
|
|
229
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
230
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
231
|
+
|
|
184
232
|
return () => {
|
|
185
|
-
document.removeEventListener(
|
|
186
|
-
document.removeEventListener(
|
|
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
|
|
248
|
+
}, [isExpanded]);
|
|
201
249
|
|
|
202
250
|
// Subscribe to voice events
|
|
203
251
|
useEffect(() => {
|
|
204
252
|
const subscriptions = [
|
|
205
|
-
subscribe(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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 ===
|
|
271
|
+
if (event.data.state === "recording" && !voiceHubActive()) {
|
|
220
272
|
setIsExpanded(true);
|
|
221
273
|
}
|
|
222
274
|
}),
|
|
223
|
-
subscribe(
|
|
275
|
+
subscribe("voice:transcription", (event: VoiceSubscribeEvent<VoiceTranscriptionPayload>) => {
|
|
224
276
|
setTranscription(event.data.text);
|
|
225
277
|
}),
|
|
226
|
-
subscribe(
|
|
278
|
+
subscribe("voice:volumeLevel", (event: VoiceSubscribeEvent<VoiceVolumePayload>) => {
|
|
227
279
|
setVolumeLevel(event.data.level);
|
|
228
280
|
}),
|
|
229
|
-
subscribe(
|
|
281
|
+
subscribe("voice:error", (event: VoiceSubscribeEvent<VoiceErrorPayload>) => {
|
|
230
282
|
setError(event.data.message);
|
|
231
283
|
}),
|
|
232
|
-
subscribe(
|
|
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
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
rpcServerCalls.voiceNotifyPlaybackComplete?.().catch(() => {});
|
|
281
|
-
}
|
|
282
|
-
});
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
);
|
|
283
346
|
|
|
284
|
-
const stopHandler = subscribe(
|
|
285
|
-
if (
|
|
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(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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(
|
|
399
|
+
const parent = await Messages().get(currentThreadId);
|
|
332
400
|
setParentMessage(parent || null);
|
|
333
|
-
|
|
401
|
+
|
|
334
402
|
const msgs = await Messages().list(
|
|
335
|
-
{ messageParentId:
|
|
336
|
-
{ sortBy: [
|
|
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 (
|
|
347
|
-
|
|
414
|
+
if (
|
|
415
|
+
evt.dataObject.messageParentId === currentThreadId ||
|
|
416
|
+
evt.dataObject.messageId === currentThreadId
|
|
417
|
+
) {
|
|
418
|
+
void loadMessages();
|
|
348
419
|
}
|
|
349
420
|
});
|
|
350
421
|
|
|
351
|
-
return () => {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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 ===
|
|
369
|
-
window.location.hash =
|
|
438
|
+
if (voiceState === "disabled") {
|
|
439
|
+
window.location.hash = "#settings?tab=voice";
|
|
370
440
|
return;
|
|
371
441
|
}
|
|
372
442
|
|
|
373
|
-
if (voiceState ===
|
|
443
|
+
if (voiceState === "recording") {
|
|
374
444
|
await rpcServerCalls.voiceStopRecording();
|
|
375
|
-
} else if (voiceState ===
|
|
445
|
+
} else if (voiceState === "speaking") {
|
|
376
446
|
await rpcServerCalls.voiceStopPlayback();
|
|
377
|
-
} else if (voiceState ===
|
|
447
|
+
} else if (voiceState === "idle" || voiceState === "listening") {
|
|
378
448
|
await rpcServerCalls.voiceStartRecording();
|
|
379
449
|
}
|
|
380
450
|
} catch (e) {
|
|
381
|
-
console.error(
|
|
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(
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
422
|
-
|
|
423
|
-
case
|
|
424
|
-
|
|
425
|
-
case
|
|
426
|
-
|
|
427
|
-
|
|
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
|
|
434
|
-
|
|
435
|
-
case
|
|
436
|
-
|
|
437
|
-
case
|
|
438
|
-
|
|
439
|
-
|
|
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 =
|
|
531
|
+
const base = "btn rounded-circle shadow";
|
|
445
532
|
switch (voiceState) {
|
|
446
|
-
case
|
|
447
|
-
|
|
448
|
-
case
|
|
449
|
-
|
|
450
|
-
case
|
|
451
|
-
|
|
452
|
-
|
|
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:
|
|
551
|
+
position: "fixed",
|
|
458
552
|
zIndex: 1050,
|
|
459
553
|
bottom: `${overlayPosition.y}px`,
|
|
460
554
|
right: `${overlayPosition.x}px`,
|
|
461
|
-
cursor: isDragging ?
|
|
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:
|
|
472
|
-
maxHeight:
|
|
473
|
-
backgroundColor:
|
|
474
|
-
display:
|
|
475
|
-
flexDirection:
|
|
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 ?
|
|
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 ?
|
|
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:
|
|
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(
|
|
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 ?
|
|
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(
|
|
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 ?
|
|
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) =>
|
|
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:
|
|
564
|
-
|
|
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) =>
|
|
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:
|
|
584
|
-
|
|
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:
|
|
604
|
-
maxHeight:
|
|
605
|
-
minHeight:
|
|
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 !==
|
|
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 ===
|
|
643
|
-
<div className="progress flex-grow-1" style={{ height:
|
|
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 ||
|
|
761
|
+
channelId={me?.userId || "default"}
|
|
659
762
|
threadId={threadId || newid()}
|
|
660
763
|
onMessageSubmit={handleMessageSubmit}
|
|
661
764
|
initialMessage={pendingInitialMessage}
|
|
662
|
-
key={
|
|
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 ?
|
|
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:
|
|
681
|
-
height:
|
|
682
|
-
fontSize:
|
|
683
|
-
transition:
|
|
684
|
-
position:
|
|
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 ===
|
|
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:
|
|
701
|
-
border:
|
|
702
|
-
animation:
|
|
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 ===
|
|
709
|
-
<span
|
|
813
|
+
{voiceState === "processing" && (
|
|
814
|
+
<span
|
|
710
815
|
className="spinner-border spinner-border-sm position-absolute"
|
|
711
|
-
style={{ top:
|
|
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 ?
|
|
720
|
-
style={{
|
|
721
|
-
width:
|
|
722
|
-
height:
|
|
723
|
-
fontSize:
|
|
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 ?
|
|
833
|
+
<i className={`bi ${isExpanded ? "bi-chat-dots-fill" : "bi-chat-dots"}`}></i>
|
|
729
834
|
</button>
|
|
730
835
|
</div>
|
|
731
836
|
|