@chat21/chat21-web-widget 5.1.32-rc9 → 5.1.33-rc11

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