@chat21/chat21-web-widget 5.1.33 → 5.1.34-rc1

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 (201) hide show
  1. package/.angular-mcp-cache/package.json +1 -0
  2. package/.cursor/angular18-accessibility-auditor-skill.md +442 -0
  3. package/.cursor/mcp.json +15 -0
  4. package/.github/workflows/docker-community-push-latest.yml +23 -13
  5. package/.github/workflows/docker-image-tag-community-tag-push.yml +22 -12
  6. package/.github/workflows/playwright.yml +27 -0
  7. package/CHANGELOG.md +130 -6
  8. package/Dockerfile +4 -5
  9. package/angular.json +24 -4
  10. package/docs/changelog/this-branch.md +36 -0
  11. package/env.sample +3 -2
  12. package/mocks/voice-websocket-mock/server.cjs +245 -0
  13. package/nginx.conf +22 -2
  14. package/package.json +10 -3
  15. package/playwright.config.ts +41 -0
  16. package/src/app/app.component.html +2 -2
  17. package/src/app/app.component.scss +25 -14
  18. package/src/app/app.component.spec.ts +21 -6
  19. package/src/app/app.component.ts +10 -9
  20. package/src/app/app.module.ts +15 -0
  21. package/src/app/component/conversation-detail/conversation/conversation.component.html +25 -11
  22. package/src/app/component/conversation-detail/conversation/conversation.component.scss +40 -2
  23. package/src/app/component/conversation-detail/conversation/conversation.component.spec.ts +644 -75
  24. package/src/app/component/conversation-detail/conversation/conversation.component.ts +100 -14
  25. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +25 -13
  26. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +123 -5
  27. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +1 -0
  28. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +23 -10
  29. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +33 -2
  30. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +242 -149
  31. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +8 -6
  32. package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +53 -3
  33. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +200 -96
  34. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +211 -6
  35. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +452 -78
  36. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +291 -76
  37. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +113 -53
  38. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.scss +12 -4
  39. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts +274 -29
  40. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.html +23 -9
  41. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts +80 -8
  42. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.html +29 -23
  43. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts +185 -16
  44. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +34 -14
  45. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +46 -0
  46. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +83 -0
  47. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +192 -0
  48. package/src/app/component/error-alert/error-alert.component.spec.ts +65 -5
  49. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.html +16 -7
  50. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.scss +21 -0
  51. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts +89 -7
  52. package/src/app/component/form/form-builder/form-builder.component.html +1 -1
  53. package/src/app/component/form/form-builder/form-builder.component.spec.ts +163 -21
  54. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.html +8 -4
  55. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.scss +10 -5
  56. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts +90 -16
  57. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts +26 -0
  58. package/src/app/component/form/inputs/form-label/form-label.component.spec.ts +45 -11
  59. package/src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts +24 -6
  60. package/src/app/component/form/inputs/form-select/form-select.component.spec.ts +14 -5
  61. package/src/app/component/form/inputs/form-text/form-text.component.html +14 -12
  62. package/src/app/component/form/inputs/form-text/form-text.component.scss +11 -1
  63. package/src/app/component/form/inputs/form-text/form-text.component.spec.ts +113 -17
  64. package/src/app/component/form/inputs/form-text/form-text.component.ts +35 -3
  65. package/src/app/component/form/inputs/form-textarea/form-textarea.component.html +13 -11
  66. package/src/app/component/form/inputs/form-textarea/form-textarea.component.scss +6 -5
  67. package/src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts +149 -13
  68. package/src/app/component/form/inputs/form-textarea/form-textarea.component.ts +26 -0
  69. package/src/app/component/form/prechat-form/prechat-form.component.html +14 -11
  70. package/src/app/component/form/prechat-form/prechat-form.component.spec.ts +102 -10
  71. package/src/app/component/form/prechat-form/prechat-form.component.ts +8 -1
  72. package/src/app/component/form/prechat-form-test-mock.ts +35 -0
  73. package/src/app/component/home/home.component.html +38 -31
  74. package/src/app/component/home/home.component.scss +4 -2
  75. package/src/app/component/home/home.component.spec.ts +226 -11
  76. package/src/app/component/home-conversations/home-conversations.component.html +30 -26
  77. package/src/app/component/home-conversations/home-conversations.component.scss +3 -0
  78. package/src/app/component/home-conversations/home-conversations.component.spec.ts +212 -36
  79. package/src/app/component/last-message/last-message.component.html +15 -9
  80. package/src/app/component/last-message/last-message.component.scss +16 -2
  81. package/src/app/component/last-message/last-message.component.spec.ts +204 -23
  82. package/src/app/component/last-message/last-message.component.ts +4 -1
  83. package/src/app/component/launcher-button/launcher-button.component.html +8 -13
  84. package/src/app/component/launcher-button/launcher-button.component.spec.ts +104 -8
  85. package/src/app/component/list-all-conversations/list-all-conversations.component.html +12 -17
  86. package/src/app/component/list-all-conversations/list-all-conversations.component.scss +2 -0
  87. package/src/app/component/list-conversations/list-conversations.component.html +22 -22
  88. package/src/app/component/menu-options/menu-options.component.html +30 -20
  89. package/src/app/component/menu-options/menu-options.component.spec.ts +125 -9
  90. package/src/app/component/message/audio/audio.component.html +13 -15
  91. package/src/app/component/message/audio/audio.component.spec.ts +140 -5
  92. package/src/app/component/message/audio/audio.component.ts +1 -5
  93. package/src/app/component/message/audio-sync/audio-sync.component.html +18 -0
  94. package/src/app/component/message/audio-sync/audio-sync.component.scss +65 -0
  95. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +103 -0
  96. package/src/app/component/message/audio-sync/audio-sync.component.ts +643 -0
  97. package/src/app/component/message/avatar/avatar.component.html +2 -2
  98. package/src/app/component/message/avatar/avatar.component.spec.ts +99 -7
  99. package/src/app/component/message/bubble-message/bubble-message.component.html +43 -51
  100. package/src/app/component/message/bubble-message/bubble-message.component.scss +59 -1
  101. package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +154 -57
  102. package/src/app/component/message/bubble-message/bubble-message.component.ts +152 -109
  103. package/src/app/component/message/buttons/action-button/action-button.component.html +3 -4
  104. package/src/app/component/message/buttons/action-button/action-button.component.spec.ts +49 -5
  105. package/src/app/component/message/buttons/link-button/link-button.component.scss +5 -8
  106. package/src/app/component/message/buttons/link-button/link-button.component.spec.ts +50 -5
  107. package/src/app/component/message/buttons/text-button/text-button.component.spec.ts +44 -5
  108. package/src/app/component/message/carousel/carousel.component.html +29 -16
  109. package/src/app/component/message/carousel/carousel.component.scss +20 -8
  110. package/src/app/component/message/carousel/carousel.component.spec.ts +80 -3
  111. package/src/app/component/message/carousel/carousel.component.ts +16 -0
  112. package/src/app/component/message/frame/frame.component.html +9 -4
  113. package/src/app/component/message/frame/frame.component.spec.ts +34 -15
  114. package/src/app/component/message/frame/frame.component.ts +7 -2
  115. package/src/app/component/message/html/html.component.html +1 -1
  116. package/src/app/component/message/html/html.component.scss +1 -1
  117. package/src/app/component/message/html/html.component.spec.ts +24 -7
  118. package/src/app/component/message/image/image.component.html +12 -10
  119. package/src/app/component/message/image/image.component.scss +16 -0
  120. package/src/app/component/message/image/image.component.spec.ts +101 -15
  121. package/src/app/component/message/image/image.component.ts +90 -51
  122. package/src/app/component/message/info-message/info-message.component.spec.ts +26 -14
  123. package/src/app/component/message/json-sources/json-sources.component.html +38 -0
  124. package/src/app/component/message/json-sources/json-sources.component.scss +201 -0
  125. package/src/app/component/message/json-sources/json-sources.component.ts +89 -0
  126. package/src/app/component/message/like-unlike/like-unlike.component.html +7 -9
  127. package/src/app/component/message/like-unlike/like-unlike.component.spec.ts +31 -3
  128. package/src/app/component/message/return-receipt/return-receipt.component.spec.ts +38 -17
  129. package/src/app/component/message/text/text.component.html +3 -3
  130. package/src/app/component/message/text/text.component.scss +80 -86
  131. package/src/app/component/message/text/text.component.spec.ts +106 -13
  132. package/src/app/component/message-attachment/message-attachment.component.spec.ts +134 -13
  133. package/src/app/component/selection-department/selection-department.component.html +21 -23
  134. package/src/app/component/selection-department/selection-department.component.spec.ts +159 -14
  135. package/src/app/component/selection-department/selection-department.component.ts +8 -1
  136. package/src/app/component/send-button/send-button.component.html +5 -13
  137. package/src/app/component/send-button/send-button.component.spec.ts +2 -2
  138. package/src/app/component/star-rating-widget/star-rating-widget.component.html +51 -81
  139. package/src/app/directives/tooltip.directive.spec.ts +8 -4
  140. package/src/app/modals/confirm-close/confirm-close.component.html +20 -8
  141. package/src/app/modals/confirm-close/confirm-close.component.scss +3 -0
  142. package/src/app/modals/confirm-close/confirm-close.component.spec.ts +13 -4
  143. package/src/app/modals/confirm-close/confirm-close.component.ts +8 -1
  144. package/src/app/pipe/html-entites-encode.pipe.spec.ts +35 -2
  145. package/src/app/pipe/marked.pipe.spec.ts +38 -2
  146. package/src/app/pipe/marked.pipe.ts +51 -41
  147. package/src/app/providers/app-config.service.ts +4 -2
  148. package/src/app/providers/brand.service.spec.ts +23 -2
  149. package/src/app/providers/brand.service.ts +1 -1
  150. package/src/app/providers/global-settings.service.spec.ts +1009 -14
  151. package/src/app/providers/global-settings.service.ts +82 -2
  152. package/src/app/providers/json-sources-parser.service.ts +175 -0
  153. package/src/app/providers/translator.service.ts +26 -6
  154. package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +117 -0
  155. package/src/app/providers/tts-audio-playback-coordinator.service.ts +109 -0
  156. package/src/app/providers/url-preview.service.ts +82 -0
  157. package/src/app/providers/voice/STT&TTS/openai-voice.config.ts +12 -0
  158. package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +171 -0
  159. package/src/app/providers/voice/STT&TTS/speech-provider.abstract.ts +39 -0
  160. package/src/app/providers/voice/audio.types.ts +40 -0
  161. package/src/app/providers/voice/vad.service.spec.ts +28 -0
  162. package/src/app/providers/voice/vad.service.ts +70 -0
  163. package/src/app/providers/voice/voice-streaming.service.spec.ts +23 -0
  164. package/src/app/providers/voice/voice-streaming.service.ts +702 -0
  165. package/src/app/providers/voice/voice-streaming.types.ts +112 -0
  166. package/src/app/providers/voice/voice.service.spec.ts +227 -0
  167. package/src/app/providers/voice/voice.service.ts +973 -0
  168. package/src/app/sass/_variables.scss +3 -0
  169. package/src/app/sass/animations.scss +19 -1
  170. package/src/app/shims/onnxruntime-web-wasm.ts +4 -0
  171. package/src/app/utils/globals.ts +21 -1
  172. package/src/app/utils/json-sources-utils.ts +27 -0
  173. package/src/app/utils/url-utils.ts +98 -0
  174. package/src/app/utils/utils-resources.ts +1 -1
  175. package/src/assets/i18n/en.json +106 -99
  176. package/src/assets/i18n/es.json +107 -100
  177. package/src/assets/i18n/fr.json +107 -100
  178. package/src/assets/i18n/it.json +107 -98
  179. package/src/assets/onnx/ort-wasm-simd-threaded.mjs +59 -0
  180. package/src/assets/onnx/ort-wasm-simd-threaded.wasm +0 -0
  181. package/src/assets/sounds/keyboard.mp3 +0 -0
  182. package/src/assets/twp/chatbot-panel.html +3 -1
  183. package/src/assets/twp/index-dev.html +18 -0
  184. package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +14 -0
  185. package/src/assets/vad/silero_vad_legacy.onnx +0 -0
  186. package/src/assets/vad/vad.worklet.bundle.min.js +1 -0
  187. package/src/chat21-core/models/message.ts +2 -1
  188. package/src/chat21-core/providers/chat-manager.spec.ts +72 -0
  189. package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +3 -2
  190. package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +12 -0
  191. package/src/chat21-core/providers/scripts/script.service.spec.ts +12 -2
  192. package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
  193. package/src/chat21-core/utils/constants.ts +4 -0
  194. package/src/chat21-core/utils/utils-message.ts +45 -6
  195. package/src/chat21-core/utils/utils.ts +5 -2
  196. package/src/widget-config-template.json +4 -1
  197. package/src/widget-config.json +4 -1
  198. package/tests/widget-form-rich.spec.ts +67 -0
  199. package/tests/widget-index-dev-settings.spec.ts +52 -0
  200. package/tests/widget-twp-iframe.spec.ts +39 -0
  201. package/tsconfig.json +5 -0
package/CHANGELOG.md CHANGED
@@ -6,17 +6,122 @@
6
6
  ### **Copyrigth**:
7
7
  *Tiledesk SRL*
8
8
 
9
- # 5.1.33
10
- - **bug fixed**: widget not loaded because blob block loading in lauch.js
11
9
 
12
- # 5.1.31
13
- - **bug fixed**: bug fix disabled user-typing with human and user-typing with human is not available
10
+ # 5.1.34-rc1
11
+ - **added**: onPageChangeVisibilityDesktop:'open' and 'onPageChangeVisibilityMobile: 'open' in chatbot-panel.html file
12
+
13
+ # 5.1.33-rc12
14
+ - **bug-fixed**: if last message is ulr_preview shows previous message buttons
15
+
16
+ # 5.1.33-rc11
17
+ - **changed**: **Stream audio** — updated the streaming/voice-mode footer icon.
18
+ - **added**: **Stream audio** — tooltip on the stream button (i18n key `STREAM_AUDIO`, e.g. “Use voice mode”) via SVG `<title>` and `aria-label`.
19
+
20
+ # 5.1.33-rc10
21
+ - **bug-fixed**: fixed bug with knowledge base json sources without URLs
22
+
23
+ # 5.1.33-rc9
24
+ - **changed**: **Conversation footer** — accessibility-focused markup (ARIA roles/labels, live regions, semantic send control), stream-audio layout (wrapper + voice mode: hide attach/emoji while streaming, inline status, stream button + spectrum), optional **Close chat** action when `closeChatInConversation` is enabled; emoji restriction alert uses assertive live region semantics.
25
+ - **bug-fixed**: **`getConversationDetail` Tiledesk fallback** — when `getMyRequests()` rejects, the conversation is treated as archived (`isConversationArchived = true`) and the handler returns immediately instead of resetting state from an empty fallback payload.
26
+ - **bug-fixed**: **`VoiceService`** — skip Web Audio `createMediaStreamSource` when the `MediaStream` has no audio tracks (avoids `InvalidStateError` under mocked `getUserMedia` in unit tests and edge browsers).
27
+ - **bug-fixed**: **`TtsAudioPlaybackCoordinator`** — avoid emitting a duplicate `isTTSPlaying$` `true` when preempting an already-playing owner (keeps emission sequence stable for consumers).
28
+ - **bug-fixed**: **Unit tests** — aligned `BubbleMessageComponent` specs with `calcImageSize` + DI mocks; `AudioSyncComponent` spec module setup (`declarations` + `CommonModule`) and TTS streaming call expectations; `VoiceService` specs with realistic mic streams and updated expectations for proxy `listening` / `barge_in` (no `setAudioMuted` on those paths); `TtsAudioPlaybackCoordinator` `stopAll` test asserts `_stopAll$` broadcast via spy; `ConversationComponent` spec for Tiledesk error path.
29
+
30
+ # 5.1.33-rc7
31
+ - **added**: added more URL source types in kb_json_sources
32
+
33
+ # 5.1.33-rc5
34
+ - **added**: cssSource tiledeskSettings property to manage and override widget style
35
+
36
+ # 5.1.33-rc4
37
+ - **bug fixed**: bug fixed extractUrlsFromText
38
+
39
+ # 5.1.32-rc18
40
+ - **bug fixed**: bug fix css kb_json_sources
41
+
42
+ # 5.1.32-rc17
43
+ - **bug fixed**: empty message in preview URLs
44
+
45
+ # 5.1.32-rc16
46
+ - **added**: added chat-json-sources to preview URLs
47
+
48
+ # 5.1.32-rc15
49
+ - **changed**: redemptionMs: 800
50
+
51
+ # 5.1.32-rc14
52
+ - **changed**: minor ui fixed
53
+
54
+ # 5.1.32-rc13
55
+ - **added**: VAD speech state events (`speechStart$`, `speechEnd$`) to improve UI/state transitions around user speech
56
+ - **changed**: stream audio footer UI — stream button expands into a “Terminate” pill with animated level bars driven by mic intensity; recorder icon hidden while streaming; textarea width adjusted while streaming
57
+ - **changed**: `StreamAudioSpectrum` — consolidated stream spectrum + stream button visuals into a single component with improved silence vs speaking handling and volume-driven bar heights
58
+ - **changed**: conversation layout while streaming — adjusted received bubble sizing (`fullSizeMessage`) and loading spinner spacing (`fullSize`) for full-width stream mode
59
+
60
+
61
+
62
+ # 5.1.32-rc12
63
+ - **changed**: voice acquisition blocking during TTS response — pause VAD after user speech ends until the TTS response cycle completes; added safety timeout and `isAcquisitionBlocked$` to drive UI (e.g. greyed spectrum)
64
+ - **chore**: version bump to `5.1.32-rc12`
65
+
66
+ # 5.1.32-rc11
67
+ - **added**: global TTS stop — `TtsAudioPlaybackCoordinator.stopAll()` + `stopAllPlayback$` to abort current and queued TTS playback and reveal full message text
68
+ - **changed**: stop TTS playback when closing stream audio
69
+ - **chore**: version bump to `5.1.32-rc11`
70
+
71
+ # 5.1.32-rc10
72
+ - **added**: TTS playback state (`isTTSPlaying$`) to coordinate voice UI and suppress mic segment emission while the bot is speaking
73
+ - **changed**: stream spectrum theme color turns grey while TTS is playing
74
+
75
+
76
+ # 5.1.32-rc9
77
+ - **added**: mic-triggered TTS interruption — when VAD detects user speech, stop current TTS playback, clear the queue, and reveal the full message text
78
+ - **added**: global TTS stop API (`TtsAudioPlaybackCoordinator.stopAll()` + `stopAllPlayback$`) to stop current + queued TTS playback from UI/events (e.g. close stream)
79
+ - **changed**: `chat-audio-sync` TTS playback now streams audio via authenticated POST to `message.metadata.src`, sending `voiceSettings` + `text` and `streaming: true`
80
+ - **changed**: stream UI spectrum — removed circular orb and stretched the spectrum line to fill the `#streamAudioAlert` width with 10px side padding
81
+ - **changed**: conversation content layout while streaming — adjusted received bubble left margin and loading spinner margins for full-size mode
82
+
83
+
84
+ # 5.1.32-rc8
85
+ - **changed**: updated the dev environment defaults to align with the stage setup (remote config URL, API endpoints, logging level, storage prefix, and related settings)
86
+
87
+ # 5.1.32-rc7
88
+ - **added**: `StreamAudioSpectrum` component for audio visualization in the streaming footer UI
89
+ - **added**: TTS playback coordinator queue — ensures TTS messages play sequentially without interrupting the previous one
90
+ - **changed**: `chat-audio-sync` — updated TTS audio handling to support streaming playback and improved autoplay/animation timing
91
+ - **changed**: iframe loader (`launch.js`, `launch_template.js`) — streamlined loading logic and improved error handling, with fixes for localhost environments
92
+
93
+ # 5.1.32-rc4
94
+ - **added**: “Close stream” control (`.close-stream-button`) — content and sheet bottom offset in fullscreen using `--chat-footer-stream-button-height` only while the stream is listening (`isStreamAudioActive`); variables in `_variables.scss`.
95
+ - **added**: `VoiceService.discardCurrentRecordingSegment()` — when a message arrives from another sender during streaming, the current WebM segment is discarded (no upload) without stopping mic/VAD; `interruptStreamDueToPeerMessage()` in the footer no longer clears `isStreamAudioActive`.
96
+ - **changed**: `#streamAudioAlert` — band above the footer with a frosted-glass look (`backdrop-filter`, semi-transparent `color-mix`).
97
+
98
+ # 5.1.32-rc3
99
+ - **changed**: `nginx.conf` (Docker image) — explicit MIME types for `.mjs`, `.wasm`, `.onnx` and `default_type` at `http` level (avoids `text/plain` on ONNX/VAD modules behind containerized deploys).
100
+ - **chore**: removed deprecated Amazon beta/prod deploy scripts from the repository.
101
+
102
+ # 5.1.32-rc2
103
+ - **bug fixed**: minor streaming icon UI fixed
104
+ - **changed**: Refactor stream audio button UI in the conversation footer (layout / classes).
105
+
106
+ # 5.1.32-rc1
107
+ - **added**: Voice pipeline — VAD (`@ricky0123/vad-web`) with ONNX Runtime WASM served from `/assets/onnx` (`copy-onnx-wasm`), `VoiceService` with `audioSegment$` (WebM segments) and optional STT/TTS via unified OpenAI provider using `HttpClient`, transcript / error fields on segment payloads.
108
+ - **added**: Stream audio UI in conversation footer — toggle, real-time volume stream and animated waveform (`volume$`); mic session lifecycle wired to upload segments on speech end.
109
+ - **added**: `MessageModel.isJustRecived` — set when ingesting messages (MQTT/Firebase `addCommandMessage` for `command.type === "message"`, and default for non-command messages in `addedMessage` / `addedNew`) to distinguish “new in session” vs history.
110
+ - **added**: `chat-audio-sync` for TTS messages — karaoke-style word sync to audio, full `message` input, typography aligned with text bubbles; skips animation when `isJustRecived === false`; after playback ends sets `message.isJustRecived = false` so replays show full text without re-animating.
111
+ - **bug fixed**: `AnalyserNode.getByteFrequencyData` TypeScript error — `Uint8Array` created from an explicit `ArrayBuffer` for correct DOM typings.
112
+ - **bug fixed**: `isStreamAudioActive` no longer derived from per-frame mic level (`volume > 1`), which caused the stream button / active state to flash continuously while listening.
14
113
 
15
114
  # 5.1.30
16
115
  - **bug fixed**: startHidden is not working properly
17
116
 
18
- # 5.1.28
19
- - **bug fixed**: header option menu is deactivated on mobile
117
+ # 5.1.30-rc3
118
+ - **bug fixed**: bug fix user-typing with human is not available
119
+
120
+ # 5.1.30-rc2
121
+ - **bug fixed**: bug fix disabled user-typing with human
122
+
123
+ # 5.1.30-rc1
124
+ - **bug fixed**: startHidden is not working properly
20
125
 
21
126
  # 5.1.28
22
127
  - **bug fixed**: fixed Bot/Human conversation detection by correctly classifying bot replies
@@ -28,8 +133,22 @@
28
133
  - **changed**: Set the default autoStart value to false
29
134
  - **added**: Added the open widget loading spinner
30
135
  - **changed**: Load the widget without authentication and display the speech bubble
136
+
137
+ # 5.1.27-rc3
138
+ - **bug fixed**: fixed Bot/Human conversation detection by correctly classifying bot replies
139
+
140
+ # 5.1.27-rc2
141
+ - **bug fixed**: centralized fullscreen management on mobile and handled the case of the closed widget that remained fullscreen
142
+
143
+ # 5.1.27-rc1
144
+ - **added**: closeChatInConversation parameters
145
+ - **added**: close chat button under textarea footer component
146
+
147
+ # 5.1.26-rc6
31
148
  - **changed**: mobile always opens fullscreen and ignores legacy stored size”.
32
149
  - **changed**: changed user-typing
150
+
151
+ # 5.1.26-rc5
33
152
  - **changed**: Hide the resize-widget button when on mobile
34
153
  - **added**: added "I'm thinking" when the bot responds
35
154
 
@@ -91,6 +210,11 @@
91
210
  - **bug-fixed**: check showEmojiFooterButton to enable/disable emojii
92
211
  - **bug-fixed**: markdown is fired as an emojii and blocked by isEmojii check fn
93
212
 
213
+ # 5.1.7-rc7
214
+ - **bug-fixed**: button new_conversation always appear. added subscription to conversationAdded
215
+
216
+ # 5.1.7-rc6
217
+ - **added**: Added MAX_ATTACHMENT_ERROR error message when uploading a file larger than 10 MB
94
218
 
95
219
  # 5.1.7-rc5
96
220
  - **bug-fixed**: bug fixed BUTTON STYLES
package/Dockerfile CHANGED
@@ -1,7 +1,7 @@
1
1
  ### STAGE 1: Build ###
2
2
 
3
3
  # We label our stage as ‘builder’
4
- FROM node:20.12.2-alpine3.19 as builder
4
+ FROM --platform=$BUILDPLATFORM node:20.12.2-alpine3.19 as builder
5
5
 
6
6
  COPY package.json package-lock.json ./
7
7
 
@@ -15,12 +15,11 @@ COPY . .
15
15
 
16
16
  ## Build the angular app in production mode and store the artifacts in dist folder
17
17
 
18
- RUN npx ng build --configuration="prod" --output-path=dist --base-href=./ --output-hashing=none
19
18
 
19
+ RUN npx ng build --configuration="prod" --output-path=dist --base-href=./ --output-hashing=none
20
20
 
21
21
  ### STAGE 2: Setup ###
22
-
23
- FROM nginx:1.14.1-alpine
22
+ FROM --platform=$BUILDPLATFORM nginx:1.14.1-alpine
24
23
 
25
24
  ## Copy our default nginx config
26
25
  COPY nginx.conf /etc/nginx/nginx.conf
@@ -33,4 +32,4 @@ COPY --from=builder /ng-app/dist/browser /usr/share/nginx/html
33
32
 
34
33
  RUN echo "Chat21 Web Widget Started!!"
35
34
 
36
- CMD ["/bin/sh", "-c", "envsubst < /usr/share/nginx/html/widget-config-template.json > /usr/share/nginx/html/widget-config.json && exec nginx -g 'daemon off;'"]
35
+ CMD ["/bin/sh", "-c", "envsubst < /usr/share/nginx/html/widget-config-template.json > /usr/share/nginx/html/widget-config.json && exec nginx -g 'daemon off;'"]
package/angular.json CHANGED
@@ -44,7 +44,9 @@
44
44
  "src/environments/real_data/widget-config-docker.json",
45
45
  "src/environments/real_data/widget-config-native-mqtt.json",
46
46
  "src/environments/real_data/widget-config-native-prod.json",
47
- "src/environments/real_data/widget-config-aws-stage.json"
47
+ "src/environments/real_data/widget-config-aws-stage.json",
48
+ "src/environments/real_data/widget-config-aws-aruba.json",
49
+ "src/environments/real_data/widget-config-regione-puglia.json"
48
50
  ],
49
51
  "styles": [
50
52
  "src/app/sass/styles.scss"
@@ -59,7 +61,9 @@
59
61
  "idb",
60
62
  "accept-language-parser",
61
63
  "file-saver",
62
- "dayjs"
64
+ "dayjs",
65
+ "onnxruntime-web",
66
+ "@ricky0123/vad-web"
63
67
  ],
64
68
  "sourceMap": true,
65
69
  "optimization": false,
@@ -158,7 +162,19 @@
158
162
  "src/styles.scss"
159
163
  ],
160
164
  "scripts": [],
161
- "codeCoverage": true
165
+ "codeCoverage": true,
166
+ "codeCoverageExclude": [
167
+ "src/chat21-core/providers/firebase/**/*.ts",
168
+ "src/chat21-core/providers/mqtt/**/*.ts",
169
+ "src/main.ts",
170
+ "src/polyfills.ts",
171
+ "src/zone-flag.ts",
172
+ "src/models/**/*.ts",
173
+ "src/chat21-core/models/**/*.ts",
174
+ "src/chat21-core/providers/native/**/*.ts",
175
+ "src/chat21-core/providers/logger/customLogger.ts",
176
+ "src/chat21-core/providers/logger/loggerInstance.ts"
177
+ ]
162
178
  }
163
179
  },
164
180
  "e2e": {
@@ -180,5 +196,9 @@
180
196
  "defaultConfiguration": "development"
181
197
  }
182
198
  }
183
- }}
199
+ }
200
+ },
201
+ "cli": {
202
+ "analytics": false
203
+ }
184
204
  }
@@ -45,3 +45,39 @@ Questo branch migliora il feedback in conversazione e rende il comportamento del
45
45
  - Il callout non compare quando il widget e' aperto o quando la preview nuovo messaggio e' attiva.
46
46
  - La UI della conversazione indica chiaramente se l'ultimo responder e' bot o umano.
47
47
  - "Sto pensando..." compare solo nelle conversazioni bot e ha un comportamento prevedibile.
48
+ # This branch: identificazione bot o umano
49
+
50
+ ## Obiettivo
51
+
52
+ In questo branch e' stata introdotta una logica esplicita per capire, all'apertura della conversazione, se l'ultimo responder lato server e' un **bot** oppure un **umano**.
53
+
54
+ ## Come viene fatta l'identificazione
55
+
56
+ - La valutazione parte dai messaggi gia' caricati in conversazione.
57
+ - Viene cercato l'**ultimo messaggio ricevuto dal server** (non inviato dal client corrente).
58
+ - Quel messaggio viene classificato con una funzione dedicata (`classifyMessageSenderKind`) che usa piu' segnali:
59
+ - `attributes.flowAttributes.chatbot_id` (quando presente indica bot)
60
+ - pattern del mittente (es. `senderId` con prefisso bot, quando applicabile)
61
+ - informazioni del mittente (`sender_fullname` e metadati associati)
62
+
63
+ ## Regola speciale per messaggi di sistema
64
+
65
+ Se l'ultimo messaggio utile e' di tipo `system`, viene fatto un controllo aggiuntivo:
66
+
67
+ - se in `attributes` e' presente un evento con `messagelabel.key = MEMBER_JOINED_GROUP`
68
+ - e rappresenta il passaggio della conversazione a un operatore
69
+
70
+ allora la conversazione viene forzata a **Umano** anche se altri indizi potrebbero suggerire bot.
71
+
72
+ ## Risultato in UI
73
+
74
+ - In apertura conversazione viene mostrato un badge con stato:
75
+ - `Bot`
76
+ - `Umano`
77
+ - Questo stato viene ricalcolato al variare dei messaggi ricevuti.
78
+
79
+ ## Effetto sui feedback utente
80
+
81
+ - Il messaggio temporaneo `"sto pensando..."` viene mostrato solo quando la conversazione risulta di tipo **Bot**.
82
+ - Alla ricezione della prima risposta dal server, `"sto pensando..."` viene nascosto **immediatamente**.
83
+ - Non e' previsto alcun tempo minimo di visualizzazione del messaggio.
package/env.sample CHANGED
@@ -19,5 +19,6 @@ API_URL=CHANGEIT
19
19
  API_BASEIMAGE_URL=CHANGEIT
20
20
  ENBED_JS=true
21
21
 
22
-
23
-
22
+ # Speech proxy WebSocket base URL (ws:// or wss://).
23
+ # Used by the voice streaming feature; maps to voiceProxyWsBaseUrl in widget-config.json.
24
+ VOICE_PROXY_WS_BASE_URL=CHANGEIT
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Mock proxy voce WSS: salva l'audio in `temp/` e simula gli eventi del protocollo reale.
3
+ *
4
+ * Eventi JSON inviati al client (`msg.event`):
5
+ * session_started | listening | transcript | thinking | speaking | done | error
6
+ * Frame binari: piccolo WAV silenzio (TTS simulato), dopo `speaking`.
7
+ *
8
+ * Uso: `npm run voice-mock`
9
+ * Env: VOICE_MOCK_PORT, VOICE_MOCK_PATH, VOICE_MOCK_SILENCE_MS (debounce fine parlato, default 700),
10
+ * VOICE_MOCK_SEND_ERROR=1 (invia subito `error`)
11
+ */
12
+ /* eslint-disable no-console */
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const http = require('http');
16
+ const { WebSocketServer } = require('ws');
17
+
18
+ const PORT = parseInt(process.env.VOICE_MOCK_PORT || '4587', 10);
19
+ const PATH = process.env.VOICE_MOCK_PATH || '/ws/voice';
20
+ const SILENCE_MS = parseInt(process.env.VOICE_MOCK_SILENCE_MS || '700', 10);
21
+ const SEND_ERROR = process.env.VOICE_MOCK_SEND_ERROR === '1' || process.env.VOICE_MOCK_SEND_ERROR === 'true';
22
+
23
+ const here = __dirname;
24
+ const outDir = path.join(here, 'temp');
25
+ const DEFAULT_TOKEN = 'mock-token-ok';
26
+ const DEFAULT_PROJECT = 'mock-project';
27
+
28
+ if (!fs.existsSync(outDir)) {
29
+ fs.mkdirSync(outDir, { recursive: true });
30
+ }
31
+
32
+ /** WAV PCM 16-bit mono, silenzio (decodificabile da Web Audio). */
33
+ function buildSilenceWav(durationSec = 0.2) {
34
+ const sampleRate = 8000;
35
+ const bitsPerSample = 16;
36
+ const channels = 1;
37
+ const blockAlign = (channels * bitsPerSample) / 8;
38
+ const byteRate = sampleRate * blockAlign;
39
+ const numSamples = Math.floor(sampleRate * durationSec);
40
+ const dataSize = numSamples * blockAlign;
41
+ const buffer = Buffer.alloc(44 + dataSize);
42
+ buffer.write('RIFF', 0);
43
+ buffer.writeUInt32LE(36 + dataSize, 4);
44
+ buffer.write('WAVE', 8);
45
+ buffer.write('fmt ', 12);
46
+ buffer.writeUInt32LE(16, 16);
47
+ buffer.writeUInt16LE(1, 20);
48
+ buffer.writeUInt16LE(channels, 22);
49
+ buffer.writeUInt32LE(sampleRate, 24);
50
+ buffer.writeUInt32LE(byteRate, 28);
51
+ buffer.writeUInt16LE(blockAlign, 32);
52
+ buffer.writeUInt16LE(bitsPerSample, 34);
53
+ buffer.write('data', 36);
54
+ buffer.writeUInt32LE(dataSize, 40);
55
+ return buffer;
56
+ }
57
+
58
+ const TTS_MOCK_CHUNK = buildSilenceWav(0.25);
59
+
60
+ function sendJson(ws, obj) {
61
+ if (ws.readyState !== 1) {
62
+ return;
63
+ }
64
+ try {
65
+ ws.send(JSON.stringify(obj));
66
+ } catch (e) {
67
+ console.error('[voice-mock] sendJson failed', e.message);
68
+ }
69
+ }
70
+
71
+ const server = http.createServer((_req, res) => {
72
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
73
+ res.end(
74
+ 'Tiledesk voice-websocket-mock. Events: session_started, listening, transcript, thinking, speaking, TTS binary, done, error.\n',
75
+ );
76
+ });
77
+
78
+ const wss = new WebSocketServer({ noServer: true });
79
+
80
+ server.on('upgrade', (req, socket, head) => {
81
+ const host = (req.headers.host || 'localhost').split(':')[0];
82
+ const u = new URL(req.url, 'http://' + host);
83
+ if (u.pathname !== PATH) {
84
+ socket.destroy();
85
+ return;
86
+ }
87
+
88
+ wss.handleUpgrade(req, socket, head, (ws) => {
89
+ wss.emit('connection', ws, req, u);
90
+ });
91
+ });
92
+
93
+ wss.on('connection', (ws, _req, urlObj) => {
94
+ const q = urlObj.searchParams;
95
+ const token = q.get('token') || DEFAULT_TOKEN;
96
+ const projectId = q.get('projectId') || DEFAULT_PROJECT;
97
+ const qRequestId = q.get('requestId') || '';
98
+ const mime = q.get('mimeType') || 'application/octet-stream';
99
+ const ext = mime.indexOf('webm') >= 0 ? 'webm' : 'bin';
100
+
101
+ const convId =
102
+ qRequestId && qRequestId !== 'new'
103
+ ? qRequestId
104
+ : `mock-req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
105
+
106
+ const safeTs = new Date().toISOString().replace(/[:.]/g, '-');
107
+ const outFile = path.join(
108
+ outDir,
109
+ `voice-${safeTs}-p${String(projectId).replace(/[^a-zA-Z0-9._-]/g, '_')}-r${String(convId).replace(/[^a-zA-Z0-9._-]/g, '_')}.${ext}`,
110
+ );
111
+ const fileStream = fs.createWriteStream(outFile, { flags: 'a' });
112
+ const tokenOk = String(token).length > 0;
113
+
114
+ let chunkCount = 0;
115
+ let silenceTimer = null;
116
+ let finalPipelineStarted = false;
117
+
118
+ function clearSilenceTimer() {
119
+ if (silenceTimer) {
120
+ clearTimeout(silenceTimer);
121
+ silenceTimer = null;
122
+ }
123
+ }
124
+
125
+ /** Dopo debounce senza nuovi chunk audio: chiude il turno come farebbe il proxy reale. */
126
+ function runAfterUtteranceEnd() {
127
+ if (finalPipelineStarted || ws.readyState !== 1) {
128
+ return;
129
+ }
130
+ finalPipelineStarted = true;
131
+ clearSilenceTimer();
132
+
133
+ sendJson(ws, {
134
+ event: 'transcript',
135
+ text: '[mock] Trascrizione finale simulata.',
136
+ isFinal: true,
137
+ });
138
+
139
+ setTimeout(() => {
140
+ sendJson(ws, { event: 'thinking' });
141
+ }, 150);
142
+
143
+ setTimeout(() => {
144
+ sendJson(ws, { event: 'speaking' });
145
+ try {
146
+ if (ws.readyState === 1) {
147
+ ws.send(TTS_MOCK_CHUNK, { binary: true });
148
+ }
149
+ } catch (e) {
150
+ console.error('[voice-mock] TTS binary send failed', e.message);
151
+ }
152
+ }, 450);
153
+
154
+ setTimeout(() => {
155
+ sendJson(ws, {
156
+ event: 'done',
157
+ url: 'https://example.com/mock-voice-session-complete',
158
+ });
159
+ }, 900);
160
+ }
161
+
162
+ function scheduleUtteranceEndCheck() {
163
+ clearSilenceTimer();
164
+ silenceTimer = setTimeout(runAfterUtteranceEnd, SILENCE_MS);
165
+ }
166
+
167
+ console.log('[voice-mock] client connected', {
168
+ projectId,
169
+ requestId: convId,
170
+ mimeType: mime,
171
+ outFile,
172
+ token: tokenOk ? 'present' : 'missing',
173
+ });
174
+
175
+ if (!tokenOk) {
176
+ sendJson(ws, { event: 'error', message: 'Missing or invalid token' });
177
+ ws.close();
178
+ return;
179
+ }
180
+
181
+ if (SEND_ERROR) {
182
+ sendJson(ws, { event: 'error', message: 'VOICE_MOCK_SEND_ERROR is set' });
183
+ setTimeout(() => ws.close(), 100);
184
+ return;
185
+ }
186
+
187
+ sendJson(ws, { event: 'session_started', requestId: convId });
188
+
189
+ setTimeout(() => {
190
+ sendJson(ws, { event: 'listening' });
191
+ }, 80);
192
+
193
+ ws.on('message', (data, isBinary) => {
194
+ if (isBinary || Buffer.isBuffer(data)) {
195
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
196
+ fileStream.write(buf);
197
+ chunkCount += 1;
198
+
199
+ if (chunkCount === 1) {
200
+ sendJson(ws, {
201
+ event: 'transcript',
202
+ text: '[mock] Utterance…',
203
+ isFinal: false,
204
+ });
205
+ } else if (chunkCount === 4) {
206
+ sendJson(ws, {
207
+ event: 'transcript',
208
+ text: '[mock] Utterance parziale aggiornata.',
209
+ isFinal: false,
210
+ });
211
+ }
212
+
213
+ finalPipelineStarted = false;
214
+ scheduleUtteranceEndCheck();
215
+ return;
216
+ }
217
+
218
+ const line = 'TEXT ' + (typeof data === 'string' ? data : data.toString()) + '\n';
219
+ fileStream.write(Buffer.from(line, 'utf8'));
220
+ });
221
+
222
+ ws.on('close', () => {
223
+ clearSilenceTimer();
224
+ fileStream.end();
225
+ const stat = fs.existsSync(outFile) ? fs.statSync(outFile) : null;
226
+ console.log('[voice-mock] client closed, bytes on disk:', stat ? stat.size : 0, outFile);
227
+ });
228
+
229
+ ws.on('error', (e) => {
230
+ console.error('[voice-mock] socket error', e);
231
+ clearSilenceTimer();
232
+ try {
233
+ fileStream.end();
234
+ } catch {
235
+ // ignore
236
+ }
237
+ });
238
+ });
239
+
240
+ server.listen(PORT, '0.0.0.0', () => {
241
+ console.log(
242
+ '[voice-mock] listening on ws://127.0.0.1:' + PORT + PATH + ' — output dir: ' + outDir,
243
+ );
244
+ console.log('[voice-mock] silence debounce:', SILENCE_MS, 'ms (fire transcript→thinking→speaking→TTS wav→done)');
245
+ });
package/nginx.conf CHANGED
@@ -5,18 +5,38 @@ events {
5
5
  }
6
6
 
7
7
  http {
8
+ include /etc/nginx/mime.types;
9
+ default_type application/octet-stream;
10
+
8
11
  server {
9
12
  listen 80;
10
13
  server_name localhost;
11
14
 
12
15
  root /usr/share/nginx/html;
13
16
  index index.html index.htm;
14
- include /etc/nginx/mime.types;
15
17
 
16
18
  gzip on;
17
19
  gzip_min_length 1000;
18
20
  gzip_proxied expired no-cache no-store private auth;
19
- gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
21
+ gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/wasm;
22
+
23
+ # ONNX Runtime (.mjs), WASM e modelli VAD: il browser rifiuta moduli ES con Content-Type: text/plain
24
+ location ~* \.mjs$ {
25
+ root /usr/share/nginx/html;
26
+ default_type application/javascript;
27
+ charset utf-8;
28
+ add_header Cache-Control "public, max-age=31536000, immutable";
29
+ }
30
+ location ~* \.wasm$ {
31
+ root /usr/share/nginx/html;
32
+ default_type application/wasm;
33
+ add_header Cache-Control "public, max-age=31536000, immutable";
34
+ }
35
+ location ~* \.onnx$ {
36
+ root /usr/share/nginx/html;
37
+ default_type application/octet-stream;
38
+ add_header Cache-Control "public, max-age=31536000, immutable";
39
+ }
20
40
 
21
41
  location / {
22
42
  try_files $uri $uri/ /index.html;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chat21/chat21-web-widget",
3
3
  "author": "Tiledesk SRL",
4
- "version": "5.1.33",
4
+ "version": "5.1.34-rc1",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.tiledesk.com",
7
7
  "repository": {
@@ -10,12 +10,15 @@
10
10
  },
11
11
  "scripts": {
12
12
  "ng": "ng",
13
+ "copy-onnx-wasm": "mkdir -p src/assets/onnx && cp node_modules/onnxruntime-web/dist/ort-wasm-simd-threaded.mjs node_modules/onnxruntime-web/dist/ort-wasm-simd-threaded.wasm src/assets/onnx/",
13
14
  "start": "ng serve",
14
15
  "build": "ng build",
15
16
  "test": "ng test",
17
+ "test:e2e": "playwright test",
16
18
  "lint": "ng lint",
17
19
  "e2e": "ng e2e",
18
- "source-map-explorer": "source-map-explorer dist/*.js"
20
+ "source-map-explorer": "source-map-explorer dist/*.js",
21
+ "voice-mock": "node mocks/voice-websocket-mock/server.cjs"
19
22
  },
20
23
  "private": false,
21
24
  "dependencies": {
@@ -32,6 +35,7 @@
32
35
  "@ctrl/ngx-emoji-mart": "^9.2.0",
33
36
  "@ngx-translate/core": "^16.0.4",
34
37
  "@ngx-translate/http-loader": "^16.0.1",
38
+ "@ricky0123/vad-web": "^0.0.30",
35
39
  "accept-language-parser": "^1.5.0",
36
40
  "bootstrap": "^5.1.3",
37
41
  "dayjs": "^1.11.7",
@@ -40,6 +44,7 @@
40
44
  "humanize-duration-ts": "^2.1.1",
41
45
  "marked": "^16.3.0",
42
46
  "ngx-logger": "^5.0.11",
47
+ "onnxruntime-web": "^1.24.3",
43
48
  "replace": "^1.2.2",
44
49
  "rxjs": "^7.8.2",
45
50
  "source-map-explorer": "^2.5.3",
@@ -52,6 +57,7 @@
52
57
  "@angular/cli": "^18.2.19",
53
58
  "@angular/compiler-cli": "^18.2.13",
54
59
  "@angular/language-service": "^18.2.13",
60
+ "@playwright/test": "^1.59.1",
55
61
  "@types/jasmine": "^3.6.11",
56
62
  "@types/jasminewd2": "~2.0.3",
57
63
  "@types/marked": "^6.0.0",
@@ -68,6 +74,7 @@
68
74
  "protractor": "~7.0.0",
69
75
  "ts-node": "~7.0.0",
70
76
  "tslint": "~6.1.0",
71
- "typescript": "~5.4"
77
+ "typescript": "~5.4",
78
+ "ws": "^8.18.0"
72
79
  }
73
80
  }