@chat21/chat21-web-widget 5.1.32-rc8 → 5.1.33-rc8

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 (246) 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/playwright.yml +27 -0
  5. package/.playwright-mcp/console-2026-05-08T15-31-09-000Z.log +17 -0
  6. package/.playwright-mcp/console-2026-05-08T15-32-19-412Z.log +89 -0
  7. package/.playwright-mcp/console-2026-05-08T16-18-48-424Z.log +133 -0
  8. package/.playwright-mcp/console-2026-05-11T12-54-06-869Z.log +13 -0
  9. package/.playwright-mcp/console-2026-05-11T12-54-56-229Z.log +147 -0
  10. package/.playwright-mcp/console-2026-05-11T12-55-47-174Z.log +183 -0
  11. package/.playwright-mcp/console-2026-05-11T15-34-03-590Z.log +210 -0
  12. package/.playwright-mcp/console-2026-05-12T15-07-31-880Z.log +118 -0
  13. package/.playwright-mcp/page-2026-05-08T15-32-19-900Z.yml +851 -0
  14. package/.playwright-mcp/page-2026-05-08T15-32-47-264Z.yml +857 -0
  15. package/.playwright-mcp/page-2026-05-08T15-33-17-089Z.yml +1110 -0
  16. package/.playwright-mcp/page-2026-05-08T15-33-23-486Z.yml +1069 -0
  17. package/.playwright-mcp/page-2026-05-08T15-33-45-390Z.yml +1076 -0
  18. package/.playwright-mcp/page-2026-05-08T15-33-52-666Z.yml +1072 -0
  19. package/.playwright-mcp/page-2026-05-08T15-34-01-338Z.yml +1085 -0
  20. package/.playwright-mcp/page-2026-05-08T15-34-07-227Z.yml +1072 -0
  21. package/.playwright-mcp/page-2026-05-08T15-34-13-875Z.yml +1072 -0
  22. package/.playwright-mcp/page-2026-05-08T15-34-21-885Z.yml +1109 -0
  23. package/.playwright-mcp/page-2026-05-08T15-34-32-755Z.yml +1109 -0
  24. package/.playwright-mcp/page-2026-05-08T15-35-09-607Z.yml +1119 -0
  25. package/.playwright-mcp/page-2026-05-08T15-35-14-242Z.yml +1109 -0
  26. package/.playwright-mcp/page-2026-05-08T16-18-48-671Z.yml +44 -0
  27. package/.playwright-mcp/page-2026-05-08T16-18-52-753Z.png +0 -0
  28. package/.playwright-mcp/page-2026-05-08T16-19-13-919Z.yml +68 -0
  29. package/.playwright-mcp/page-2026-05-08T16-19-17-977Z.png +0 -0
  30. package/.playwright-mcp/page-2026-05-08T16-19-25-733Z.yml +120 -0
  31. package/.playwright-mcp/page-2026-05-08T16-19-29-252Z.png +0 -0
  32. package/.playwright-mcp/page-2026-05-08T16-19-39-269Z.yml +80 -0
  33. package/.playwright-mcp/page-2026-05-08T16-19-43-915Z.png +0 -0
  34. package/.playwright-mcp/page-2026-05-08T16-20-04-407Z.yml +81 -0
  35. package/.playwright-mcp/page-2026-05-08T16-20-08-984Z.png +0 -0
  36. package/.playwright-mcp/page-2026-05-08T16-20-32-397Z.png +0 -0
  37. package/.playwright-mcp/page-2026-05-08T16-20-58-658Z.png +0 -0
  38. package/.playwright-mcp/page-2026-05-08T16-21-12-320Z.yml +86 -0
  39. package/.playwright-mcp/page-2026-05-08T16-21-39-154Z.yml +91 -0
  40. package/.playwright-mcp/page-2026-05-08T16-21-45-420Z.png +0 -0
  41. package/.playwright-mcp/page-2026-05-08T16-22-21-062Z.yml +0 -0
  42. package/.playwright-mcp/page-2026-05-08T16-22-58-232Z.yml +91 -0
  43. package/.playwright-mcp/page-2026-05-08T16-23-36-520Z.yml +0 -0
  44. package/.playwright-mcp/page-2026-05-08T16-23-46-805Z.yml +100 -0
  45. package/.playwright-mcp/page-2026-05-08T16-23-55-169Z.png +0 -0
  46. package/.playwright-mcp/page-2026-05-08T16-24-26-574Z.yml +91 -0
  47. package/.playwright-mcp/page-2026-05-08T16-25-34-414Z.png +0 -0
  48. package/.playwright-mcp/page-2026-05-08T16-25-59-831Z.png +0 -0
  49. package/.playwright-mcp/page-2026-05-08T16-26-21-809Z.yml +91 -0
  50. package/.playwright-mcp/page-2026-05-08T16-26-47-443Z.yml +105 -0
  51. package/.playwright-mcp/page-2026-05-08T16-26-56-136Z.png +0 -0
  52. package/.playwright-mcp/page-2026-05-08T16-27-59-610Z.yml +48 -0
  53. package/.playwright-mcp/page-2026-05-11T12-54-07-180Z.yml +44 -0
  54. package/.playwright-mcp/page-2026-05-11T12-54-56-946Z.yml +4 -0
  55. package/.playwright-mcp/page-2026-05-11T12-55-47-503Z.yml +24 -0
  56. package/.playwright-mcp/page-2026-05-11T12-56-00-766Z.yml +28 -0
  57. package/.playwright-mcp/page-2026-05-11T12-56-06-438Z.yml +90 -0
  58. package/.playwright-mcp/page-2026-05-11T12-57-56-838Z.yml +106 -0
  59. package/.playwright-mcp/page-2026-05-11T12-58-00-124Z.yml +106 -0
  60. package/.playwright-mcp/page-2026-05-11T12-59-08-836Z.yml +61 -0
  61. package/.playwright-mcp/page-2026-05-11T12-59-12-088Z.yml +61 -0
  62. package/.playwright-mcp/page-2026-05-11T12-59-26-215Z.yml +69 -0
  63. package/.playwright-mcp/page-2026-05-11T12-59-29-519Z.yml +69 -0
  64. package/.playwright-mcp/page-2026-05-11T12-59-37-309Z.yml +0 -0
  65. package/.playwright-mcp/page-2026-05-11T12-59-39-968Z.yml +79 -0
  66. package/.playwright-mcp/page-2026-05-11T12-59-45-983Z.yml +78 -0
  67. package/.playwright-mcp/page-2026-05-11T12-59-49-951Z.yml +78 -0
  68. package/.playwright-mcp/page-2026-05-11T15-34-04-515Z.yml +0 -0
  69. package/.playwright-mcp/page-2026-05-12T15-07-32-171Z.yml +44 -0
  70. package/.playwright-mcp/page-2026-05-12T15-08-09-820Z.yml +119 -0
  71. package/CHANGELOG.md +61 -3
  72. package/angular.json +20 -3
  73. package/deploy_amazon_beta.sh +7 -17
  74. package/deploy_amazon_prod.sh +41 -0
  75. package/docs/TILEDESK_WIDGET_ACCESSIBILITY_STATEMENT_COMPLETE.md +379 -0
  76. package/env.sample +3 -2
  77. package/mocks/voice-websocket-mock/server.cjs +245 -0
  78. package/package.json +7 -3
  79. package/playwright-report/index.html +90 -0
  80. package/playwright.config.ts +41 -0
  81. package/src/app/app.component.html +2 -2
  82. package/src/app/app.component.scss +25 -14
  83. package/src/app/app.component.spec.ts +21 -6
  84. package/src/app/app.module.ts +4 -0
  85. package/src/app/component/conversation-detail/conversation/conversation.component.html +19 -11
  86. package/src/app/component/conversation-detail/conversation/conversation.component.scss +28 -0
  87. package/src/app/component/conversation-detail/conversation/conversation.component.spec.ts +644 -75
  88. package/src/app/component/conversation-detail/conversation/conversation.component.ts +61 -17
  89. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +25 -13
  90. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +123 -5
  91. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +1 -0
  92. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +22 -9
  93. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +23 -1
  94. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +249 -149
  95. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +0 -1
  96. package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +53 -3
  97. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component copy.html +172 -0
  98. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +112 -62
  99. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +133 -7
  100. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +452 -78
  101. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +193 -79
  102. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +113 -53
  103. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.scss +12 -4
  104. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts +274 -29
  105. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.html +23 -9
  106. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts +80 -8
  107. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.html +29 -23
  108. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts +185 -16
  109. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +34 -14
  110. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +43 -19
  111. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +63 -10
  112. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +142 -12
  113. package/src/app/component/error-alert/error-alert.component.spec.ts +65 -5
  114. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.html +16 -7
  115. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.scss +21 -0
  116. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts +89 -7
  117. package/src/app/component/form/form-builder/form-builder.component.html +1 -1
  118. package/src/app/component/form/form-builder/form-builder.component.spec.ts +163 -21
  119. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.html +8 -4
  120. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.scss +10 -5
  121. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts +90 -16
  122. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts +26 -0
  123. package/src/app/component/form/inputs/form-label/form-label.component.spec.ts +45 -11
  124. package/src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts +24 -6
  125. package/src/app/component/form/inputs/form-select/form-select.component.spec.ts +14 -5
  126. package/src/app/component/form/inputs/form-text/form-text.component.html +14 -12
  127. package/src/app/component/form/inputs/form-text/form-text.component.scss +11 -1
  128. package/src/app/component/form/inputs/form-text/form-text.component.spec.ts +113 -17
  129. package/src/app/component/form/inputs/form-text/form-text.component.ts +26 -0
  130. package/src/app/component/form/inputs/form-textarea/form-textarea.component.html +13 -11
  131. package/src/app/component/form/inputs/form-textarea/form-textarea.component.scss +6 -5
  132. package/src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts +149 -13
  133. package/src/app/component/form/inputs/form-textarea/form-textarea.component.ts +26 -0
  134. package/src/app/component/form/prechat-form/prechat-form.component.html +14 -11
  135. package/src/app/component/form/prechat-form/prechat-form.component.spec.ts +102 -10
  136. package/src/app/component/form/prechat-form/prechat-form.component.ts +8 -1
  137. package/src/app/component/form/prechat-form-test-mock.ts +35 -0
  138. package/src/app/component/home/home.component.html +38 -31
  139. package/src/app/component/home/home.component.scss +4 -2
  140. package/src/app/component/home/home.component.spec.ts +226 -11
  141. package/src/app/component/home-conversations/home-conversations.component.html +30 -26
  142. package/src/app/component/home-conversations/home-conversations.component.scss +3 -0
  143. package/src/app/component/home-conversations/home-conversations.component.spec.ts +212 -36
  144. package/src/app/component/last-message/last-message.component.html +15 -9
  145. package/src/app/component/last-message/last-message.component.scss +16 -2
  146. package/src/app/component/last-message/last-message.component.spec.ts +204 -23
  147. package/src/app/component/launcher-button/launcher-button.component.html +8 -13
  148. package/src/app/component/launcher-button/launcher-button.component.spec.ts +104 -8
  149. package/src/app/component/list-all-conversations/list-all-conversations.component.html +12 -17
  150. package/src/app/component/list-all-conversations/list-all-conversations.component.scss +2 -0
  151. package/src/app/component/list-conversations/list-conversations.component.html +22 -22
  152. package/src/app/component/menu-options/menu-options.component.html +30 -20
  153. package/src/app/component/menu-options/menu-options.component.spec.ts +125 -9
  154. package/src/app/component/message/audio/audio.component.html +13 -15
  155. package/src/app/component/message/audio/audio.component.spec.ts +140 -5
  156. package/src/app/component/message/audio/audio.component.ts +1 -0
  157. package/src/app/component/message/audio-sync/audio-sync.component.scss +1 -1
  158. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +81 -1
  159. package/src/app/component/message/audio-sync/audio-sync.component.ts +134 -24
  160. package/src/app/component/message/avatar/avatar.component.html +2 -2
  161. package/src/app/component/message/avatar/avatar.component.spec.ts +99 -7
  162. package/src/app/component/message/bubble-message/bubble-message.component.html +39 -52
  163. package/src/app/component/message/bubble-message/bubble-message.component.scss +54 -1
  164. package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +154 -57
  165. package/src/app/component/message/bubble-message/bubble-message.component.ts +138 -110
  166. package/src/app/component/message/buttons/action-button/action-button.component.html +3 -4
  167. package/src/app/component/message/buttons/action-button/action-button.component.spec.ts +49 -5
  168. package/src/app/component/message/buttons/link-button/link-button.component.scss +5 -8
  169. package/src/app/component/message/buttons/link-button/link-button.component.spec.ts +50 -5
  170. package/src/app/component/message/buttons/text-button/text-button.component.spec.ts +44 -5
  171. package/src/app/component/message/carousel/carousel.component.html +29 -16
  172. package/src/app/component/message/carousel/carousel.component.scss +20 -8
  173. package/src/app/component/message/carousel/carousel.component.spec.ts +80 -3
  174. package/src/app/component/message/carousel/carousel.component.ts +16 -0
  175. package/src/app/component/message/frame/frame.component.html +9 -4
  176. package/src/app/component/message/frame/frame.component.spec.ts +34 -15
  177. package/src/app/component/message/frame/frame.component.ts +7 -2
  178. package/src/app/component/message/html/html.component.html +1 -1
  179. package/src/app/component/message/html/html.component.scss +1 -1
  180. package/src/app/component/message/html/html.component.spec.ts +24 -7
  181. package/src/app/component/message/image/image.component.html +12 -10
  182. package/src/app/component/message/image/image.component.scss +16 -0
  183. package/src/app/component/message/image/image.component.spec.ts +101 -15
  184. package/src/app/component/message/image/image.component.ts +90 -51
  185. package/src/app/component/message/info-message/info-message.component.spec.ts +26 -14
  186. package/src/app/component/message/json-sources/json-sources.component.html +38 -0
  187. package/src/app/component/message/json-sources/json-sources.component.scss +197 -0
  188. package/src/app/component/message/json-sources/json-sources.component.ts +89 -0
  189. package/src/app/component/message/like-unlike/like-unlike.component.html +7 -9
  190. package/src/app/component/message/like-unlike/like-unlike.component.spec.ts +31 -3
  191. package/src/app/component/message/return-receipt/return-receipt.component.spec.ts +38 -17
  192. package/src/app/component/message/text/text.component.html +3 -3
  193. package/src/app/component/message/text/text.component.scss +80 -86
  194. package/src/app/component/message/text/text.component.spec.ts +106 -13
  195. package/src/app/component/message-attachment/message-attachment.component.spec.ts +134 -13
  196. package/src/app/component/selection-department/selection-department.component.html +21 -23
  197. package/src/app/component/selection-department/selection-department.component.spec.ts +159 -14
  198. package/src/app/component/selection-department/selection-department.component.ts +8 -1
  199. package/src/app/component/send-button/send-button.component.html +5 -13
  200. package/src/app/component/send-button/send-button.component.spec.ts +2 -2
  201. package/src/app/component/star-rating-widget/star-rating-widget.component.html +51 -81
  202. package/src/app/directives/tooltip.directive.spec.ts +8 -4
  203. package/src/app/modals/confirm-close/confirm-close.component.html +20 -8
  204. package/src/app/modals/confirm-close/confirm-close.component.scss +3 -0
  205. package/src/app/modals/confirm-close/confirm-close.component.spec.ts +13 -4
  206. package/src/app/modals/confirm-close/confirm-close.component.ts +8 -1
  207. package/src/app/pipe/html-entites-encode.pipe.spec.ts +35 -2
  208. package/src/app/pipe/marked.pipe.spec.ts +38 -2
  209. package/src/app/providers/app-config.service.ts +4 -2
  210. package/src/app/providers/brand.service.spec.ts +23 -2
  211. package/src/app/providers/brand.service.ts +1 -1
  212. package/src/app/providers/global-settings.service.spec.ts +1009 -14
  213. package/src/app/providers/global-settings.service.ts +30 -2
  214. package/src/app/providers/json-sources-parser.service.ts +182 -0
  215. package/src/app/providers/translator.service.ts +24 -6
  216. package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +117 -0
  217. package/src/app/providers/tts-audio-playback-coordinator.service.ts +45 -7
  218. package/src/app/providers/url-preview.service.ts +82 -0
  219. package/src/app/providers/voice/audio.types.ts +6 -0
  220. package/src/app/providers/voice/voice-streaming.service.spec.ts +23 -0
  221. package/src/app/providers/voice/voice-streaming.service.ts +710 -0
  222. package/src/app/providers/voice/voice-streaming.types.ts +113 -0
  223. package/src/app/providers/voice/voice.service.spec.ts +203 -3
  224. package/src/app/providers/voice/voice.service.ts +521 -12
  225. package/src/app/sass/_variables.scss +1 -1
  226. package/src/app/sass/animations.scss +19 -1
  227. package/src/app/utils/globals.ts +4 -0
  228. package/src/app/utils/json-sources-utils.ts +27 -0
  229. package/src/app/utils/url-utils.ts +98 -0
  230. package/src/app/utils/utils-resources.ts +1 -1
  231. package/src/assets/i18n/en.json +26 -1
  232. package/src/assets/i18n/es.json +106 -101
  233. package/src/assets/i18n/fr.json +106 -101
  234. package/src/assets/i18n/it.json +106 -99
  235. package/src/assets/twp/index-dev.html +18 -0
  236. package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +14 -0
  237. package/src/chat21-core/providers/chat-manager.spec.ts +72 -0
  238. package/src/chat21-core/providers/scripts/script.service.spec.ts +12 -2
  239. package/src/chat21-core/utils/constants.ts +4 -0
  240. package/src/chat21-core/utils/utils-message.ts +23 -1
  241. package/src/widget-config-template.json +3 -1
  242. package/src/widget-config.json +28 -27
  243. package/test-results/.last-run.json +4 -0
  244. package/tests/widget-form-rich.spec.ts +67 -0
  245. package/tests/widget-index-dev-settings.spec.ts +52 -0
  246. package/tests/widget-twp-iframe.spec.ts +39 -0
@@ -17,6 +17,9 @@ import { findAndRemoveEmoji, isImage } from 'src/chat21-core/utils/utils-message
17
17
  import { ProjectModel } from 'src/models/project';
18
18
  import { Subscription } from 'rxjs';
19
19
  import { VoiceService } from 'src/app/providers/voice/voice.service';
20
+ import { VoiceStreamingSessionConfig } from 'src/app/providers/voice/voice-streaming.types';
21
+ import { TtsAudioPlaybackCoordinator } from 'src/app/providers/tts-audio-playback-coordinator.service';
22
+ import { TiledeskAuthService } from 'src/chat21-core/providers/tiledesk/tiledesk-auth.service';
20
23
 
21
24
  @Component({
22
25
  selector: 'chat-conversation-footer',
@@ -57,6 +60,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
57
60
  @Output() onAttachmentFileButtonClicked = new EventEmitter<any>();
58
61
  @Output() onNewConversationButtonClicked = new EventEmitter();
59
62
  @Output() onStreamAudioActiveChange = new EventEmitter<boolean>();
63
+ @Output() onStreamAudioConnectingChange = new EventEmitter<boolean>();
60
64
  @Output() onCloseChatButtonClicked = new EventEmitter();
61
65
 
62
66
  @ViewChild('chat21_file') public chat21_file: ElementRef;
@@ -93,12 +97,32 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
93
97
 
94
98
  /** Stream audio UI: icona equalizer → X; alert con onde animate sopra il footer */
95
99
  isStreamAudioActive = false;
100
+ /** True while the WebSocket session is being established (between click and session_started). */
101
+ isStreamAudioConnecting = false;
102
+ /** True while the bot's TTS audio is playing — mic segments are suppressed, spectrum turns grey. */
103
+ isBotSpeaking = false;
96
104
  /** Sottoscrizione ai segmenti audio (VAD → WebM) dal {@link VoiceService}. */
97
105
  private voiceAudioSubscription?: Subscription;
106
+ /** Sottoscrizione a `transcript` finale dalla WSS. */
107
+ private voiceTranscriptSubscription?: Subscription;
98
108
  /** Sottoscrizione al volume audio (real-time) dal {@link VoiceService}. */
99
109
  private voiceVolumeSubscription?: Subscription;
110
+ /** Sottoscrizione allo stato TTS (bot sta parlando). */
111
+ private botSpeakingSub?: Subscription;
100
112
  /** Passato a {@link StreamAudioSpectrumComponent} per disegnare la linea spettro. */
101
113
  currentVolume = 0;
114
+ /** Last user utterance transcribed — persists during bot processing to show in voice panel. */
115
+ lastVoiceTranscript = '';
116
+
117
+ get voiceStatusLabel(): string {
118
+ if (this.isStreamAudioConnecting && !this.isStreamAudioActive) {
119
+ return this.translationMap?.get('VOICE_CONNECTING') || 'Connecting...';
120
+ }
121
+ if (this.isStreamAudioActive && this.isBotSpeaking) {
122
+ return this.translationMap?.get('VOICE_PROCESSING') || 'Processing...';
123
+ }
124
+ return this.translationMap?.get('VOICE_LISTENING') || 'Listening...';
125
+ }
102
126
 
103
127
  file_size_limit = FILE_SIZE_LIMIT;
104
128
  attachmentTooltip: string = '';
@@ -107,10 +131,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
107
131
 
108
132
  convertColorToRGBA = convertColorToRGBA;
109
133
  private logger: LoggerService = LoggerInstance.getInstance()
110
- constructor(private chatManager: ChatManager,
111
- private typingService: TypingService,
112
- private uploadService: UploadService,
113
- private voiceService: VoiceService) { }
134
+ constructor(
135
+ private chatManager: ChatManager,
136
+ private typingService: TypingService,
137
+ private uploadService: UploadService,
138
+ private voiceService: VoiceService,
139
+ private ttsPlayback: TtsAudioPlaybackCoordinator,
140
+ private tiledeskAuthService: TiledeskAuthService,
141
+ public g: Globals,
142
+ ) {}
114
143
 
115
144
  ngOnInit() {
116
145
  // this.updateAttachmentTooltip();
@@ -161,37 +190,141 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
161
190
  // }
162
191
 
163
192
  /**
164
- * Microfono + VAD: ogni fine parlato il servizio emette su `audioSegment$` upload.
193
+ * Stream voce: con `voiceIngress` solo WSS (no VAD) transcript + TTS dal server.
194
+ * Senza ingresso WSS: VAD + upload per segmento.
165
195
  */
166
196
  async initVoice() {
167
197
  this.voiceAudioSubscription?.unsubscribe();
168
198
  this.voiceVolumeSubscription?.unsubscribe();
199
+ this.botSpeakingSub?.unsubscribe();
200
+ this.voiceTranscriptSubscription?.unsubscribe();
169
201
 
170
- this.voiceAudioSubscription = this.voiceService.audioSegment$.subscribe((rec) => {
171
- console.log('[CONV-FOOTER] audioSegment$', rec);
172
- this.prepareAndUpload(rec.blob);
202
+ const voiceIngress = this.buildVoiceIngressStreamConfig();
203
+ this.voiceAudioSubscription = undefined;
204
+ this.voiceTranscriptSubscription = this.voiceService.voiceTranscript$.subscribe(({ text }) => {
205
+ // Guard: stop accepting transcript text once the proxy is processing (thinking/speaking)
206
+ if (text && !this.isBotSpeaking) {
207
+ this.textInputTextArea = text;
208
+ this.lastVoiceTranscript = text;
209
+ // The proxy publishes the user utterance to Chat21 via AMQP on utterance-end;
210
+ // no sendMessage call is needed here — doing so would produce duplicate messages.
211
+ }
173
212
  });
213
+
174
214
  this.voiceVolumeSubscription = this.voiceService.volume$.subscribe((volume) => {
175
215
  this.currentVolume = volume;
176
216
  });
177
- await this.voiceService.startSession();
217
+ this.botSpeakingSub = this.voiceService.isAcquisitionBlocked$.subscribe((blocked) => {
218
+ this.isBotSpeaking = blocked;
219
+ if (blocked) {
220
+ // Proxy has started thinking/speaking — clear the textarea preview
221
+ this.textInputTextArea = '';
222
+ }
223
+ });
224
+ await this.voiceService.startSession(voiceIngress ? { voiceIngressStream: voiceIngress } : {});
178
225
  }
179
226
 
227
+ private buildVoiceIngressStreamConfig(): VoiceStreamingSessionConfig | null {
228
+ const token = this.tiledeskAuthService.getTiledeskToken() ?? '';
229
+ const sender = this.tiledeskAuthService.getCurrentUser()?.uid ?? '';
230
+ const recipient = this.conversationWith ?? '';
231
+ if (!token || !sender || !recipient) {
232
+ this.logger.warn('[CONV-FOOTER] buildVoiceIngressStreamConfig: missing required fields', {
233
+ hasToken: !!token,
234
+ hasSender: !!sender,
235
+ hasRecipient: !!recipient,
236
+ });
237
+ return null;
238
+ }
239
+ const { recipientFullname, attributes, channelType } = this.buildSendMessageContext();
240
+ this.logger.log('[CONV-FOOTER] buildVoiceIngressStreamConfig', { sender, recipient, channelType });
241
+ return {
242
+ token,
243
+ sender,
244
+ recipient,
245
+ // Use Deepgram multilingual code-switching so the model detects the spoken
246
+ // language from the audio stream regardless of browser locale.
247
+ // Source: https://developers.deepgram.com/docs/multilingual-code-switching
248
+ lang: 'multi',
249
+ text: '',
250
+ type: 'text',
251
+ recipient_fullname: recipientFullname ?? '',
252
+ sender_fullname: recipientFullname ?? '',
253
+ attributes: attributes ?? {},
254
+ metadata: '',
255
+ channel_type: channelType ?? '',
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Merge `attributes` di componente con `additional_attributes` e risolve `recipientFullname` come in sendMessage.
261
+ */
262
+ private buildSendMessageContext(additional_attributes?: any) {
263
+ let recipientFullname = this.translationMap.get('GUEST_LABEL');
264
+ const g_attributes = this.attributes;
265
+ const attributes = <any>{};
266
+ if (g_attributes) {
267
+ for (const [key, value] of Object.entries(g_attributes)) {
268
+ attributes[key] = value;
269
+ }
270
+ }
271
+ if (additional_attributes) {
272
+ for (const [key, value] of Object.entries(additional_attributes)) {
273
+ attributes[key] = value;
274
+ }
275
+ }
276
+ const senderId = this.senderId;
277
+ const projectid = this.project.id;
278
+ const channelType = this.channelType;
279
+ const userFullname = this.userFullname;
280
+ const userEmail = this.userEmail;
281
+ const conversationWith = this.conversationWith;
282
+
283
+ if (userFullname) {
284
+ recipientFullname = userFullname;
285
+ } else if (userEmail) {
286
+ recipientFullname = userEmail;
287
+ } else if (attributes && attributes['userFullname']) {
288
+ recipientFullname = attributes['userFullname'];
289
+ } else {
290
+ recipientFullname = this.translationMap.get('GUEST_LABEL');
291
+ }
292
+
293
+ return {
294
+ recipientFullname,
295
+ attributes,
296
+ senderId,
297
+ projectid,
298
+ channelType,
299
+ conversationWith,
300
+ };
301
+ }
180
302
  async stopVoice(options?: { discardInProgressSegment?: boolean }) {
303
+ // Stop all active TTS audio immediately and reveal all text.
304
+ this.ttsPlayback.stopAll();
305
+
181
306
  this.voiceAudioSubscription?.unsubscribe();
182
307
  this.voiceAudioSubscription = undefined;
183
308
 
309
+ this.voiceTranscriptSubscription?.unsubscribe();
310
+ this.voiceTranscriptSubscription = undefined;
311
+
184
312
  this.voiceVolumeSubscription?.unsubscribe();
185
313
  this.voiceVolumeSubscription = undefined;
186
314
 
315
+ this.botSpeakingSub?.unsubscribe();
316
+ this.botSpeakingSub = undefined;
317
+ this.isBotSpeaking = false;
318
+
187
319
  await this.voiceService.stopSession(options);
188
320
  this.currentVolume = 0;
321
+ this.textInputTextArea = '';
322
+ this.lastVoiceTranscript = '';
189
323
  }
190
324
 
191
325
  /**
192
- * CHIAMATO DA: conversation.component.ts
193
- * Messaggio in arrivo da un altro mittente mentre lo stream è attivo: scarta solo il segmento
194
- * registrato in quel momento (nessun upload); mic + VAD restano attivi, `isStreamAudioActive` true.
326
+ * Messaggio in arrivo da un altro mittente mentre lo stream è attivo: con VAD legacy scarta il segmento in corso.
327
+ * Con sola sessione WSS non ha effetto sul mic (nessun recorder a segmenti locale).
195
328
  */
196
329
  interruptStreamDueToPeerMessage(): void {
197
330
  if (!this.isStreamAudioActive) {
@@ -446,40 +579,14 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
446
579
  // msg = replaceEndOfLine(msg);
447
580
  // msg = msg.trim();
448
581
 
449
- let recipientFullname = this.translationMap.get('GUEST_LABEL');
450
- // sponziello: adds ADDITIONAL ATTRIBUTES TO THE MESSAGE
451
- const g_attributes = this.attributes;
452
- // added <any> to resolve the Error occurred during the npm installation: Property 'userFullname' does not exist on type '{}'
453
- const attributes = <any>{};
454
- if (g_attributes) {
455
- for (const [key, value] of Object.entries(g_attributes)) {
456
- attributes[key] = value;
457
- }
458
- }
459
- if (additional_attributes) {
460
- for (const [key, value] of Object.entries(additional_attributes)) {
461
- attributes[key] = value;
462
- }
463
- }
464
- // fine-sponziello
465
- // this.conversationHandlerService = this.chatManager.getConversationHandlerByConversationId(this.conversationWith)
466
- const senderId = this.senderId;
467
- const projectid = this.project.id;
468
- const channelType = this.channelType;
469
- const userFullname = this.userFullname;
470
- const userEmail = this.userEmail;
471
- const conversationWith = this.conversationWith;
472
-
473
-
474
- if (userFullname) {
475
- recipientFullname = userFullname;
476
- } else if (userEmail) {
477
- recipientFullname = userEmail;
478
- } else if (attributes && attributes['userFullname']) {
479
- recipientFullname = attributes['userFullname'];
480
- } else {
481
- recipientFullname = this.translationMap.get('GUEST_LABEL');
482
- }
582
+ const {
583
+ recipientFullname,
584
+ attributes,
585
+ senderId,
586
+ projectid,
587
+ channelType,
588
+ conversationWith,
589
+ } = this.buildSendMessageContext(additional_attributes);
483
590
 
484
591
  this.onBeforeMessageSent.emit({
485
592
  senderFullname: recipientFullname,
@@ -731,8 +838,12 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
731
838
  if (this.showAlertEmoji) {
732
839
  return;
733
840
  }
734
- const turningOn = !this.isStreamAudioActive;
841
+ // Treat a click during connecting as a cancel request (same as turning off).
842
+ const turningOn = !this.isStreamAudioActive && !this.isStreamAudioConnecting;
843
+ this.logger.log('[CONV-FOOTER] onStreamPressed', { turningOn });
735
844
  if (turningOn) {
845
+ this.isStreamAudioConnecting = true;
846
+ this.onStreamAudioConnectingChange.emit(true);
736
847
  try {
737
848
  this.currentVolume = 0;
738
849
  await this.initVoice();
@@ -740,10 +851,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
740
851
  } catch (e) {
741
852
  this.logger.error('[CONV-FOOTER] onStreamPressed: initVoice failed', e);
742
853
  this.isStreamAudioActive = false;
854
+ } finally {
855
+ this.isStreamAudioConnecting = false;
856
+ this.onStreamAudioConnectingChange.emit(false);
743
857
  }
744
858
  } else {
745
859
  await this.stopVoice();
746
860
  this.isStreamAudioActive = false;
861
+ this.isStreamAudioConnecting = false;
862
+ this.onStreamAudioConnectingChange.emit(false);
747
863
  }
748
864
  this.onStreamAudioActiveChange.emit(this.isStreamAudioActive);
749
865
  this.logger.log('[CONV-FOOTER] isStreamAudioActive', this.isStreamAudioActive);
@@ -840,48 +956,46 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
840
956
  }
841
957
 
842
958
  /**
843
- * when I press a key I call this method which:
844
- * check if 'enter' has been pressed
845
- * if you clear text
846
- * set field height as min by default
847
- * takes out the focus and resets it after a few moments
848
- * (this is a patch to keep the focus and eliminate the br of the send !!!)
849
- * send message
959
+ * Single keyboard handler for the message textarea.
960
+ *
961
+ * - Enter (no modifier) -> send message
962
+ * - Shift / Alt / Ctrl / Meta + Enter -> insert a newline (default browser behavior)
963
+ * - Tab -> prevented, to keep focus inside the chat
964
+ *
965
+ * Modifier check is intentionally on `keydown` because `keypress` is deprecated
966
+ * and does not consistently fire for modifier combos across browsers.
850
967
  * @param event
851
968
  */
852
- onkeypress(event) {
969
+ onkeydown(event: KeyboardEvent) {
853
970
  const keyCode = event.which || event.keyCode;
854
- this.textInputTextArea = ((document.getElementById('chat21-main-message-context') as HTMLInputElement).value);
855
- if (keyCode === 13) { // ENTER pressed
856
- if(this.showAlertEmoji){
857
- return;
971
+
972
+ if (keyCode === 13) { // ENTER
973
+ const hasModifier = event.metaKey || event.shiftKey || event.altKey || event.ctrlKey;
974
+ if (hasModifier) {
975
+ // Let the textarea insert a newline on its own (do not preventDefault).
976
+ return;
858
977
  }
978
+
979
+ // Plain Enter -> send the message
980
+ event.preventDefault();
981
+
982
+ if (this.showAlertEmoji) {
983
+ return;
984
+ }
985
+
986
+ const target = document.getElementById('chat21-main-message-context') as HTMLInputElement;
987
+ if (target) {
988
+ this.textInputTextArea = target.value;
989
+ }
990
+
859
991
  if (this.textInputTextArea && this.textInputTextArea.trim() !== '') {
860
- // that.logger.log('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
861
- // this.resizeInputField();
862
- // this.messagingService.sendMessage(msg, TYPE_MSG_TEXT);
863
- // this.setDepartment();
864
- // this.textInputTextArea = replaceBr(this.textInputTextArea);
865
992
  this.sendMessage(this.textInputTextArea, TYPE_MSG_TEXT);
866
- // this.restoreTextArea();
867
993
  }
868
- } else if (keyCode === 9) { // TAB pressed
869
- event.preventDefault();
994
+ return;
870
995
  }
871
- }
872
996
 
873
-
874
- /**
875
- * HANDLE: cmd+enter, shiftKey+enter, alt+enter, ctrl+enter
876
- * @param event
877
- */
878
- onkeydown(event){
879
- const keyCode = event.which || event.keyCode;
880
- // metaKey -> COMMAND , shiftKey -> SHIFT, altKey -> ALT, ctrlKey -> CONTROL
881
- if( (event.metaKey || event.shiftKey || event.altKey || event.ctrlKey) && keyCode===13){
997
+ if (keyCode === 9) { // TAB
882
998
  event.preventDefault();
883
- this.textInputTextArea += '\r\n';
884
- this.resizeInputField();
885
999
  }
886
1000
  }
887
1001