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

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 (193) hide show
  1. package/.github/workflows/docker-community-push-latest.yml +13 -23
  2. package/.github/workflows/docker-image-tag-community-tag-push.yml +12 -22
  3. package/CHANGELOG.md +8 -129
  4. package/Dockerfile +5 -4
  5. package/angular.json +3 -21
  6. package/docs/changelog/this-branch.md +0 -36
  7. package/env.sample +2 -3
  8. package/nginx.conf +2 -22
  9. package/package.json +3 -10
  10. package/src/app/app.component.html +2 -2
  11. package/src/app/app.component.scss +14 -25
  12. package/src/app/app.component.spec.ts +6 -21
  13. package/src/app/app.component.ts +9 -10
  14. package/src/app/app.module.ts +0 -13
  15. package/src/app/component/conversation-detail/conversation/conversation.component.html +11 -25
  16. package/src/app/component/conversation-detail/conversation/conversation.component.scss +2 -40
  17. package/src/app/component/conversation-detail/conversation/conversation.component.spec.ts +75 -644
  18. package/src/app/component/conversation-detail/conversation/conversation.component.ts +14 -100
  19. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +13 -25
  20. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +5 -123
  21. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +0 -1
  22. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +10 -23
  23. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +1 -19
  24. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +149 -242
  25. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +5 -8
  26. package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +3 -53
  27. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +96 -200
  28. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +6 -211
  29. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +78 -452
  30. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +76 -291
  31. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +53 -113
  32. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.scss +4 -12
  33. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts +29 -274
  34. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.html +9 -23
  35. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts +8 -80
  36. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.html +23 -29
  37. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts +16 -185
  38. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +14 -34
  39. package/src/app/component/error-alert/error-alert.component.spec.ts +5 -65
  40. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.html +7 -16
  41. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.scss +0 -21
  42. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts +7 -89
  43. package/src/app/component/form/form-builder/form-builder.component.html +1 -1
  44. package/src/app/component/form/form-builder/form-builder.component.spec.ts +21 -163
  45. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.html +4 -8
  46. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.scss +5 -10
  47. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts +16 -90
  48. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts +0 -26
  49. package/src/app/component/form/inputs/form-label/form-label.component.spec.ts +11 -45
  50. package/src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts +6 -24
  51. package/src/app/component/form/inputs/form-select/form-select.component.spec.ts +5 -14
  52. package/src/app/component/form/inputs/form-text/form-text.component.html +12 -14
  53. package/src/app/component/form/inputs/form-text/form-text.component.scss +1 -11
  54. package/src/app/component/form/inputs/form-text/form-text.component.spec.ts +17 -113
  55. package/src/app/component/form/inputs/form-text/form-text.component.ts +3 -35
  56. package/src/app/component/form/inputs/form-textarea/form-textarea.component.html +11 -13
  57. package/src/app/component/form/inputs/form-textarea/form-textarea.component.scss +5 -6
  58. package/src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts +13 -149
  59. package/src/app/component/form/inputs/form-textarea/form-textarea.component.ts +0 -26
  60. package/src/app/component/form/prechat-form/prechat-form.component.html +11 -14
  61. package/src/app/component/form/prechat-form/prechat-form.component.spec.ts +10 -102
  62. package/src/app/component/form/prechat-form/prechat-form.component.ts +1 -8
  63. package/src/app/component/home/home.component.html +31 -38
  64. package/src/app/component/home/home.component.scss +2 -4
  65. package/src/app/component/home/home.component.spec.ts +11 -226
  66. package/src/app/component/home-conversations/home-conversations.component.html +26 -30
  67. package/src/app/component/home-conversations/home-conversations.component.scss +0 -3
  68. package/src/app/component/home-conversations/home-conversations.component.spec.ts +36 -212
  69. package/src/app/component/last-message/last-message.component.html +9 -15
  70. package/src/app/component/last-message/last-message.component.scss +2 -16
  71. package/src/app/component/last-message/last-message.component.spec.ts +23 -204
  72. package/src/app/component/last-message/last-message.component.ts +1 -4
  73. package/src/app/component/launcher-button/launcher-button.component.html +13 -8
  74. package/src/app/component/launcher-button/launcher-button.component.spec.ts +8 -104
  75. package/src/app/component/list-all-conversations/list-all-conversations.component.html +17 -12
  76. package/src/app/component/list-all-conversations/list-all-conversations.component.scss +0 -2
  77. package/src/app/component/list-conversations/list-conversations.component.html +22 -22
  78. package/src/app/component/menu-options/menu-options.component.html +20 -30
  79. package/src/app/component/menu-options/menu-options.component.spec.ts +9 -125
  80. package/src/app/component/message/audio/audio.component.html +15 -13
  81. package/src/app/component/message/audio/audio.component.spec.ts +5 -140
  82. package/src/app/component/message/audio/audio.component.ts +5 -1
  83. package/src/app/component/message/avatar/avatar.component.html +2 -2
  84. package/src/app/component/message/avatar/avatar.component.spec.ts +7 -99
  85. package/src/app/component/message/bubble-message/bubble-message.component.html +51 -38
  86. package/src/app/component/message/bubble-message/bubble-message.component.scss +1 -54
  87. package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +57 -154
  88. package/src/app/component/message/bubble-message/bubble-message.component.ts +11 -89
  89. package/src/app/component/message/buttons/action-button/action-button.component.html +4 -3
  90. package/src/app/component/message/buttons/action-button/action-button.component.spec.ts +5 -49
  91. package/src/app/component/message/buttons/link-button/link-button.component.scss +8 -5
  92. package/src/app/component/message/buttons/link-button/link-button.component.spec.ts +5 -50
  93. package/src/app/component/message/buttons/text-button/text-button.component.spec.ts +5 -44
  94. package/src/app/component/message/carousel/carousel.component.html +16 -29
  95. package/src/app/component/message/carousel/carousel.component.scss +8 -20
  96. package/src/app/component/message/carousel/carousel.component.spec.ts +3 -80
  97. package/src/app/component/message/carousel/carousel.component.ts +0 -16
  98. package/src/app/component/message/frame/frame.component.html +4 -9
  99. package/src/app/component/message/frame/frame.component.spec.ts +15 -34
  100. package/src/app/component/message/frame/frame.component.ts +2 -7
  101. package/src/app/component/message/html/html.component.html +1 -1
  102. package/src/app/component/message/html/html.component.scss +1 -1
  103. package/src/app/component/message/html/html.component.spec.ts +7 -24
  104. package/src/app/component/message/image/image.component.html +10 -12
  105. package/src/app/component/message/image/image.component.scss +0 -16
  106. package/src/app/component/message/image/image.component.spec.ts +15 -101
  107. package/src/app/component/message/image/image.component.ts +51 -90
  108. package/src/app/component/message/info-message/info-message.component.spec.ts +14 -26
  109. package/src/app/component/message/like-unlike/like-unlike.component.html +9 -7
  110. package/src/app/component/message/like-unlike/like-unlike.component.spec.ts +3 -31
  111. package/src/app/component/message/return-receipt/return-receipt.component.spec.ts +17 -38
  112. package/src/app/component/message/text/text.component.html +3 -3
  113. package/src/app/component/message/text/text.component.scss +86 -80
  114. package/src/app/component/message/text/text.component.spec.ts +13 -106
  115. package/src/app/component/message-attachment/message-attachment.component.spec.ts +13 -134
  116. package/src/app/component/selection-department/selection-department.component.html +23 -21
  117. package/src/app/component/selection-department/selection-department.component.spec.ts +14 -159
  118. package/src/app/component/selection-department/selection-department.component.ts +1 -8
  119. package/src/app/component/send-button/send-button.component.html +13 -5
  120. package/src/app/component/send-button/send-button.component.spec.ts +2 -2
  121. package/src/app/component/star-rating-widget/star-rating-widget.component.html +81 -51
  122. package/src/app/directives/tooltip.directive.spec.ts +4 -8
  123. package/src/app/modals/confirm-close/confirm-close.component.html +8 -20
  124. package/src/app/modals/confirm-close/confirm-close.component.scss +0 -3
  125. package/src/app/modals/confirm-close/confirm-close.component.spec.ts +4 -13
  126. package/src/app/modals/confirm-close/confirm-close.component.ts +1 -8
  127. package/src/app/pipe/html-entites-encode.pipe.spec.ts +2 -35
  128. package/src/app/pipe/marked.pipe.spec.ts +2 -38
  129. package/src/app/pipe/marked.pipe.ts +41 -51
  130. package/src/app/providers/app-config.service.ts +2 -4
  131. package/src/app/providers/brand.service.spec.ts +2 -23
  132. package/src/app/providers/brand.service.ts +1 -1
  133. package/src/app/providers/global-settings.service.spec.ts +14 -1009
  134. package/src/app/providers/global-settings.service.ts +2 -82
  135. package/src/app/providers/translator.service.ts +6 -26
  136. package/src/app/sass/_variables.scss +0 -3
  137. package/src/app/sass/animations.scss +1 -19
  138. package/src/app/utils/globals.ts +1 -21
  139. package/src/app/utils/utils-resources.ts +1 -1
  140. package/src/assets/i18n/en.json +99 -106
  141. package/src/assets/i18n/es.json +100 -107
  142. package/src/assets/i18n/fr.json +100 -107
  143. package/src/assets/i18n/it.json +98 -107
  144. package/src/assets/twp/index-dev.html +0 -18
  145. package/src/chat21-core/models/message.ts +1 -2
  146. package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +2 -3
  147. package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +0 -12
  148. package/src/chat21-core/providers/scripts/script.service.spec.ts +2 -12
  149. package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
  150. package/src/chat21-core/utils/utils-message.ts +0 -7
  151. package/src/chat21-core/utils/utils.ts +2 -5
  152. package/src/widget-config-template.json +1 -4
  153. package/src/widget-config.json +1 -4
  154. package/tsconfig.json +0 -5
  155. package/.angular-mcp-cache/package.json +0 -1
  156. package/.cursor/angular18-accessibility-auditor-skill.md +0 -442
  157. package/.cursor/mcp.json +0 -15
  158. package/.github/workflows/build.yml +0 -22
  159. package/.github/workflows/playwright.yml +0 -27
  160. package/mocks/voice-websocket-mock/server.cjs +0 -245
  161. package/playwright.config.ts +0 -41
  162. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +0 -46
  163. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +0 -83
  164. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +0 -192
  165. package/src/app/component/form/prechat-form-test-mock.ts +0 -35
  166. package/src/app/component/message/audio-sync/audio-sync.component.html +0 -18
  167. package/src/app/component/message/audio-sync/audio-sync.component.scss +0 -65
  168. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +0 -103
  169. package/src/app/component/message/audio-sync/audio-sync.component.ts +0 -643
  170. package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +0 -117
  171. package/src/app/providers/tts-audio-playback-coordinator.service.ts +0 -109
  172. package/src/app/providers/voice/STT&TTS/openai-voice.config.ts +0 -12
  173. package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +0 -171
  174. package/src/app/providers/voice/STT&TTS/speech-provider.abstract.ts +0 -39
  175. package/src/app/providers/voice/audio.types.ts +0 -40
  176. package/src/app/providers/voice/vad.service.spec.ts +0 -28
  177. package/src/app/providers/voice/vad.service.ts +0 -70
  178. package/src/app/providers/voice/voice-streaming.service.spec.ts +0 -23
  179. package/src/app/providers/voice/voice-streaming.service.ts +0 -702
  180. package/src/app/providers/voice/voice-streaming.types.ts +0 -112
  181. package/src/app/providers/voice/voice.service.spec.ts +0 -227
  182. package/src/app/providers/voice/voice.service.ts +0 -973
  183. package/src/app/shims/onnxruntime-web-wasm.ts +0 -4
  184. package/src/assets/onnx/ort-wasm-simd-threaded.mjs +0 -59
  185. package/src/assets/onnx/ort-wasm-simd-threaded.wasm +0 -0
  186. package/src/assets/sounds/keyboard.mp3 +0 -0
  187. package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +0 -14
  188. package/src/assets/vad/silero_vad_legacy.onnx +0 -0
  189. package/src/assets/vad/vad.worklet.bundle.min.js +0 -1
  190. package/src/chat21-core/providers/chat-manager.spec.ts +0 -72
  191. package/tests/widget-form-rich.spec.ts +0 -67
  192. package/tests/widget-index-dev-settings.spec.ts +0 -52
  193. package/tests/widget-twp-iframe.spec.ts +0 -39
@@ -1,4 +1,26 @@
1
- <div class="bubble-message messages primary-color">
1
+ <!-- [ngClass]="{'button-in-msg' : message.metadata && message.metadata.button}" -->
2
+ <!-- ngStyle]="{'padding': (isImage(message) || isFrame(message))?'1px':'0 8px'}" -->
3
+ <!-- 'width': (isImage(message) || isFrame(message))? sizeImage?.width + 'px': null -->
4
+
5
+
6
+
7
+ <!-- <div id="bubble-message" *ngIf="isAudio(message)" [ngStyle]="{'padding': '0'}" class="messages primary-color">
8
+ <div>
9
+ <chat-audio-track *ngIf="isAudio(message)"
10
+ [metadata]="message.metadata"
11
+ ></chat-audio-track>
12
+ </div>
13
+ </div>
14
+
15
+
16
+ [ngStyle]="{'padding': (isImage(message) || isFrame(message) || isAudio(message))?'0 0px':'0 8px'}"
17
+ -->
18
+
19
+
20
+
21
+
22
+
23
+ <div id="bubble-message" *ngIf="hasRenderableContent()" class="messages primary-color">
2
24
  <div>
3
25
 
4
26
  <div *ngIf="messageType(MESSAGE_TYPE_OTHERS, message) && !isSameSender"
@@ -6,7 +28,17 @@
6
28
  [ngStyle]="{'margin': (isImage(message) || isFrame(message))? '12px 16px 8px 16px': '12px 16px 0px 16px'}" class="message_sender_fullname">
7
29
  {{message?.sender_fullname}}
8
30
  </div>
31
+ <!-- message type:: image -->
32
+ <!-- <div *ngIf="message.type == 'image' && message.metadata" [ngStyle] = "{ 'max-width': getSizeImg(message).width, 'max-height': getSizeImg(message).height }">
33
+ <img class="message-contentX message-content-imageX" [src]="message.metadata.src" />
34
+ </div> -->
35
+
36
+ <!-- <img *ngIf="message.type == 'image' && message.metadata" class="message-contentX message-content-imageX"
37
+ [src]="message.metadata.src" [width]="getSizeImg(message).width"
38
+ [height]="getSizeImg(message).height" /> -->
9
39
 
40
+ <!-- [width]="getMetadataSize(message.metadata).width"
41
+ [height]="getMetadataSize(message.metadata).height" -->
10
42
  <chat-image *ngIf="isImage(message)"
11
43
  [metadata]="message.metadata"
12
44
  [width]="sizeImage?.width"
@@ -21,42 +53,22 @@
21
53
  (onElementRendered)="onElementRenderedFN($event)">
22
54
  </chat-frame>
23
55
 
56
+ <!-- <chat-audio *ngIf="isAudio(message)"
57
+ [metadata]="message.metadata"
58
+ (onElementRendered)="onElementRenderedFN($event)">
59
+ </chat-audio> -->
24
60
 
25
61
  <chat-audio *ngIf="isAudio(message)"
26
62
  [metadata]="message.metadata"
27
63
  [color]="fontColor"
28
- [stylesMap]="stylesMap"
29
- [translationMap]="translationMap">
64
+ [stylesMap]="stylesMap">
30
65
  </chat-audio>
31
-
32
- <!-- Json sources -->
66
+
33
67
  <chat-json-sources *ngIf="jsonSources !== null && jsonSources.length > 0"
34
68
  [items]="jsonSources"
35
69
  (onElementRendered)="onElementRenderedFN($event)">
36
70
  </chat-json-sources>
37
- <!-- TTS player: only when voice proxy is NOT active (avoids double playback)
38
- and the message was not already played by the proxy (avoids replay on session end) -->
39
- <chat-audio-sync *ngIf="isAudioTTS(message) && !(voiceService.isWssVoiceActive$ | async) && !voiceService.wasProxyHandled(message?.uid)"
40
- [message]="message"
41
- [color]="fontColor">
42
- </chat-audio-sync>
43
-
44
- <!-- Karaoke display for TTS messages while a WSS voice session is active -->
45
- <p *ngIf="isAudioTTS(message) && (voiceService.isWssVoiceActive$ | async) && _wssKaraokeWords$"
46
- class="wss-karaoke"
47
- [style.color]="fontColor">
48
- <span *ngFor="let w of (_wssKaraokeWords$ | async); trackBy: trackKaraokeWord"
49
- class="wss-word"
50
- [class.future]="w.state === 'future'"
51
- [class.active]="w.state === 'active'"
52
- [class.past]="w.state === 'past'">{{ w.text }}&nbsp;</span>
53
- </p>
54
-
55
- <!-- Text fallback for TTS messages after the voice proxy session ended -->
56
- <chat-text *ngIf="isAudioTTS(message) && !(voiceService.isWssVoiceActive$ | async) && voiceService.wasProxyHandled(message?.uid) && message?.text"
57
- [text]="message?.text"
58
- [color]="fontColor">
59
- </chat-text>
71
+
60
72
 
61
73
  <!-- <chat-frame *ngIf="message.metadata && message.metadata.type && message.metadata.type.includes('video')"
62
74
  [metadata]="message.metadata"
@@ -68,17 +80,10 @@
68
80
  <!-- <div *ngIf="message.type == 'text'"> -->
69
81
 
70
82
  <!-- tooltip="{{message.timestamp | dateAgo}} ({{message.timestamp | date:'shortDate'}} {{message.timestamp | date:'HH:mm:ss'}})" placement="bottom" -->
71
- <div *ngIf="message?.text && (!isAudio(message) && !isAudioTTS(message)) && !isJsonSources(message)" >
72
-
73
- <!-- Word-by-word streaming reveal during an active voice session -->
74
- <p *ngIf="_isStreaming" class="streaming-text" [style.color]="fontColor">
75
- <span *ngFor="let w of _streamingWords; trackBy: trackWord"
76
- class="stream-word"
77
- [style.animation-delay]="(w.index * 80) + 'ms'">{{w.word}} </span>
78
- </p>
83
+ <div *ngIf="(message?.text && !isAudio(message)) && !isJsonSources(message)">
79
84
 
80
85
  <!-- [htmlEnabled]="(message?.type==='html')? true : false" -->
81
- <chat-text *ngIf="message?.type !=='html'"
86
+ <chat-text *ngIf="jsonSources === null && message?.type !=='html'"
82
87
  [text]="message?.text"
83
88
  [color]="fontColor"
84
89
  (onBeforeMessageRender)="onBeforeMessageRenderFN($event)"
@@ -93,5 +98,13 @@
93
98
  </chat-html>
94
99
 
95
100
  </div>
101
+
96
102
  </div>
97
- </div>
103
+
104
+ </div>
105
+
106
+
107
+
108
+
109
+
110
+
@@ -37,57 +37,4 @@
37
37
  chat-audio {
38
38
  display: flex;
39
39
  }
40
- }
41
- .streaming-text {
42
- padding: 0;
43
- margin: 0;
44
- line-height: 1.5;
45
-
46
- .stream-word {
47
- display: inline;
48
- opacity: 0;
49
- animation: stream-word-in 200ms ease-out forwards;
50
- }
51
- }
52
-
53
- @keyframes stream-word-in {
54
- from {
55
- opacity: 0;
56
- transform: translateY(3px);
57
- }
58
- to {
59
- opacity: 1;
60
- transform: translateY(0);
61
- }
62
- }
63
-
64
- // -- WSS TTS Karaoke ----------------------------------------------------------
65
- .wss-karaoke {
66
- // Match chat-text (ShadowDom) visual layout so there's no jump when voice opens.
67
- // font-size must be set explicitly: the parent has font-size:10px but chat-text
68
- // overrides it via :host { font-size: var(--font-size-bubble-message, 14px) }.
69
- display: block;
70
- margin: 0;
71
- padding: 12px 16px;
72
- font-size: var(--font-size-bubble-message, 14px);
73
- line-height: 1.4em;
74
- font-weight: 300;
75
- overflow: hidden;
76
- }
77
-
78
- .wss-word {
79
- display: inline;
80
- transition: opacity 120ms ease;
81
-
82
- &.future {
83
- opacity: 0.35;
84
- }
85
-
86
- &.active {
87
- opacity: 1;
88
- }
89
-
90
- &.past {
91
- opacity: 1;
92
- }
93
- }
40
+ }
@@ -1,62 +1,26 @@
1
- import { NO_ERRORS_SCHEMA } from '@angular/core';
2
- import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
1
+ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3
2
  import { By } from '@angular/platform-browser';
4
- import { of } from 'rxjs';
5
- import { MAX_WIDTH_IMAGES, MIN_WIDTH_IMAGES } from 'src/chat21-core/utils/constants';
6
- import { VoiceService } from 'src/app/providers/voice/voice.service';
7
- import { JsonSourcesParserService } from 'src/app/providers/json-sources-parser.service';
8
3
 
9
4
  import { BubbleMessageComponent } from './bubble-message.component';
5
+ import { NO_ERRORS_SCHEMA } from '@angular/core';
10
6
 
11
7
  describe('BubbleMessageComponent', () => {
12
8
  let component: BubbleMessageComponent;
13
9
  let fixture: ComponentFixture<BubbleMessageComponent>;
14
10
 
15
- const voiceServiceMock = {
16
- isWssVoiceActive: false,
17
- markProxyHandled: jasmine.createSpy('markProxyHandled'),
18
- voiceTtsKaraoke$: of({ text: '', words: [], activeIndex: -1 }),
19
- };
20
-
21
- const jsonSourcesParserMock = {
22
- parseBaseFromMessage: jasmine.createSpy('parseBaseFromMessage').and.returnValue(null),
23
- enrichSources: jasmine.createSpy('enrichSources').and.resolveTo(null),
24
- };
25
-
26
- const textMessage: any = {
27
- attributes: { projectId: 'p1' },
28
- channel_type: 'group',
29
- recipient: 'support-group-x',
30
- recipient_fullname: 'Guest ',
31
- sender: 'bot_1',
32
- sender_fullname: 'BOT2',
33
- status: 150,
34
- text: 'Hello',
35
- timestamp: 1629273999970,
36
- type: 'text',
37
- uid: 'm1',
38
- isSender: false,
39
- };
40
-
41
- beforeEach(waitForAsync(() => {
11
+ beforeEach(async(() => {
42
12
  TestBed.configureTestingModule({
43
- declarations: [BubbleMessageComponent],
13
+ declarations: [ BubbleMessageComponent ],
44
14
  schemas: [NO_ERRORS_SCHEMA],
45
- providers: [
46
- { provide: VoiceService, useValue: voiceServiceMock },
47
- { provide: JsonSourcesParserService, useValue: jsonSourcesParserMock },
15
+ imports: [
48
16
  ],
49
- }).compileComponents();
17
+ })
18
+ .compileComponents();
50
19
  }));
51
20
 
52
21
  beforeEach(() => {
53
22
  fixture = TestBed.createComponent(BubbleMessageComponent);
54
23
  component = fixture.componentInstance;
55
- component.stylesMap = new Map([
56
- ['buttonFontSize', '14px'],
57
- ['themeColor', '#000'],
58
- ['foregroundColor', '#fff'],
59
- ]);
60
24
  fixture.detectChanges();
61
25
  });
62
26
 
@@ -64,115 +28,54 @@ describe('BubbleMessageComponent', () => {
64
28
  expect(component).toBeTruthy();
65
29
  });
66
30
 
67
- it('should have a chat-text child for plain text messages', () => {
68
- component.message = textMessage;
69
- fixture.detectChanges();
70
- expect(fixture.debugElement.query(By.css('chat-text'))).toBeTruthy();
71
- });
72
-
73
- it('should bind chat-text inputs from message', () => {
74
- component.message = textMessage;
75
- fixture.detectChanges();
76
- const textChild = fixture.debugElement.query(By.css('chat-text'));
77
- expect(textChild.properties.text).toEqual(textMessage.text);
78
- });
79
-
80
- describe('ngOnChanges', () => {
81
- it('should compute sizeImage from message metadata object', () => {
82
- component.message = {
83
- ...textMessage,
84
- metadata: { width: 100, height: 50 },
85
- };
86
- component.ngOnChanges();
87
- expect(component.sizeImage.width).toBe(100);
88
- });
89
-
90
- it('should cap width when metadata exceeds MAX_WIDTH_IMAGES (calcImageSize)', () => {
91
- component.message = {
92
- ...textMessage,
93
- metadata: { width: MAX_WIDTH_IMAGES * 2, height: 100 },
94
- };
95
- component.ngOnChanges();
96
- expect(component.sizeImage.width).toBe(MAX_WIDTH_IMAGES);
97
- });
98
-
99
- it('should scale up narrow thumbnails when width <= 55 (calcImageSize)', () => {
100
- component.message = {
101
- ...textMessage,
102
- metadata: { width: 40, height: 80 },
103
- };
104
- component.ngOnChanges();
105
- expect(component.sizeImage.width).toBe(MIN_WIDTH_IMAGES);
106
- expect(component.sizeImage.height).toBe(MIN_WIDTH_IMAGES / (40 / 80));
107
- });
108
-
109
- it('should keep metadata dimensions for mid-sized images', () => {
110
- component.message = {
111
- ...textMessage,
112
- metadata: { width: 120, height: 60 },
113
- };
114
- component.ngOnChanges();
115
- expect(component.sizeImage.width).toBe(120);
116
- expect(component.sizeImage.height).toBe(60);
117
- });
118
-
119
- it('should leave width undefined when metadata has no width (calcImageSize)', () => {
120
- component.message = {
121
- ...textMessage,
122
- metadata: { width: undefined, height: 10 },
123
- };
124
- component.ngOnChanges();
125
- expect(component.sizeImage.width).toBeUndefined();
126
- expect(component.sizeImage.height).toBe(10);
127
- });
128
-
129
- it('should ignore non-object metadata', () => {
130
- component.message = { ...textMessage, metadata: 'x' as any };
131
- component.ngOnChanges();
132
- expect(component.sizeImage).toEqual({ width: 0, height: 0 });
133
- });
134
-
135
- it('should derive fullnameColor from fontColor', () => {
136
- component.message = textMessage;
137
- component.fontColor = '#ff0000';
138
- component.ngOnChanges();
139
- expect(component.fullnameColor).toBeTruthy();
140
- });
141
-
142
- it('should prefer sender fullname color when name present', () => {
143
- component.message = { ...textMessage, sender_fullname: 'Anna' };
144
- component.fontColor = '#00ff00';
145
- component.ngOnChanges();
146
- expect(component.fullnameColor).toBeTruthy();
147
- });
148
- });
149
-
150
- describe('emitters', () => {
151
- beforeEach(() => {
152
- component.message = textMessage;
153
- });
154
-
155
- it('onBeforeMessageRenderFN should emit with sanitizer and message', () => {
156
- spyOn(component.onBeforeMessageRender, 'emit');
157
- const ev = { messageEl: {}, component: {} };
158
- component.onBeforeMessageRenderFN(ev);
159
- expect(component.onBeforeMessageRender.emit).toHaveBeenCalled();
160
- const arg = (component.onBeforeMessageRender.emit as jasmine.Spy).calls.mostRecent().args[0];
161
- expect(arg.message).toBe(component.message);
162
- expect(arg.sanitizer).toBe(component.sanitizer);
163
- });
164
-
165
- it('onAfterMessageRenderFN should emit', () => {
166
- spyOn(component.onAfterMessageRender, 'emit');
167
- const ev = { messageEl: {}, component: {} };
168
- component.onAfterMessageRenderFN(ev);
169
- expect(component.onAfterMessageRender.emit).toHaveBeenCalled();
170
- });
171
-
172
- it('onElementRenderedFN should forward element and status', () => {
173
- spyOn(component.onElementRendered, 'emit');
174
- component.onElementRenderedFN({ element: 'image', status: true });
175
- expect(component.onElementRendered.emit).toHaveBeenCalledWith({ element: 'image', status: true });
176
- });
177
- });
31
+ it('should have a "chat-text" child element', () => {
32
+ const messages: any = {
33
+ attributes: {
34
+ projectId: "6013ec749b32000045be650e",
35
+ tiledesk_message_id: "611cbf8ffb379b00346660e7"
36
+ },
37
+ channel_type: "group",
38
+ recipient: "support-group-6013ec749b32000045be650e-4904aee91f8b487aad117bcda860549d",
39
+ recipient_fullname: "Guest ",
40
+ sender: "bot_602256f6c001b800342cb76f",
41
+ sender_fullname: "BOT2",
42
+ status: 150,
43
+ text: "Hello 👋. I'm a bot 🤖.\n\nChoose one of the options below or write a message to reach our staff.",
44
+ timestamp: 1629273999970,
45
+ type: "text",
46
+ uid: "-MhNI3eaIoLTOLoX3TAu",
47
+ isSender: false
48
+ }
49
+ component.message = messages
50
+ // component.textColor = 'black'
51
+ fixture.detectChanges()
52
+ const textChild = fixture.debugElement.query(By.css('chat-text'))
53
+ textChild.properties.text
54
+ expect(textChild).toBeTruthy();
55
+ })
56
+
57
+ it('should have a text inside "chat-text" child element', () => {
58
+ const messages: any = {
59
+ attributes: {
60
+ projectId: "6013ec749b32000045be650e",
61
+ tiledesk_message_id: "611cbf8ffb379b00346660e7"
62
+ },
63
+ channel_type: "group",
64
+ recipient: "support-group-6013ec749b32000045be650e-4904aee91f8b487aad117bcda860549d",
65
+ recipient_fullname: "Guest ",
66
+ sender: "bot_602256f6c001b800342cb76f",
67
+ sender_fullname: "BOT2",
68
+ status: 150,
69
+ text: "Hello 👋. I'm a bot 🤖.\n\nChoose one of the options below or write a message to reach our staff.",
70
+ timestamp: 1629273999970,
71
+ type: "text",
72
+ uid: "-MhNI3eaIoLTOLoX3TAu",
73
+ isSender: false
74
+ }
75
+ component.message = messages
76
+ // component.textColor = 'black'
77
+ fixture.detectChanges()
78
+ const textChild = fixture.debugElement.query(By.css('chat-text'))
79
+ expect(textChild.properties.text).toEqual(messages.text)
80
+ })
178
81
  });
@@ -1,39 +1,25 @@
1
- import { Component, EventEmitter, HostBinding, Input, OnDestroy, OnInit, Output } from '@angular/core';
2
- import { Observable, Subscription } from 'rxjs';
3
- import { map, startWith } from 'rxjs/operators';
1
+ import { Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';
4
2
  import { DomSanitizer } from '@angular/platform-browser';
5
3
  import { MessageModel } from 'src/chat21-core/models/message';
6
4
  import { MESSAGE_TYPE_MINE, MESSAGE_TYPE_OTHERS, TYPE_MSG_URL_PREVIEW } from 'src/chat21-core/utils/constants';
7
5
  import { convertColorToRGBA } from 'src/chat21-core/utils/utils';
8
- import { JsonSourcesParserService } from 'src/app/providers/json-sources-parser.service';
9
- import { calcImageSize, isAudio, isAudioTTS, isFile, isFrame, isImage, isJsonSources, messageType } from 'src/chat21-core/utils/utils-message';
6
+ import { calcImageSize, isAudio, isFile, isFrame, isImage, isJsonSources, messageType } from 'src/chat21-core/utils/utils-message';
10
7
  import { getColorBck } from 'src/chat21-core/utils/utils-user';
11
- import { VoiceService } from 'src/app/providers/voice/voice.service';
8
+ import { JsonSourcesParserService } from 'src/app/providers/json-sources-parser.service';
12
9
  import { JsonSourceItem } from '../json-sources/json-sources.component';
13
- import { VoiceTtsKaraokeWord } from 'src/app/providers/voice/voice-streaming.types';
14
10
 
15
11
  @Component({
16
12
  selector: 'chat-bubble-message',
17
13
  templateUrl: './bubble-message.component.html',
18
14
  styleUrls: ['./bubble-message.component.scss']
19
15
  })
20
- export class BubbleMessageComponent implements OnInit, OnDestroy {
16
+ export class BubbleMessageComponent {
21
17
 
22
18
  @Input() message: MessageModel;
23
19
  @Input() isSameSender: boolean;
24
20
  @Input() fontColor: string;
25
21
  @Input() stylesMap: Map<string, string>;
26
- @Input() translationMap: Map<string, string>;
27
- /** When true, a newly-arrived bot text message reveals its words one by one. */
28
- @Input() streamOnArrival = false;
29
- /** One-shot flag: set once in ngOnChanges, never reverts so animation isn't replayed. */
30
- _isStreaming = false;
31
- /** Precomputed word list; rebuilt only when the message text changes. */
32
- _streamingWords: Array<{ word: string; index: number }> = [];
33
- /** Live karaoke word states driven by voiceTtsKaraoke$ during an active WSS session. */
34
- _wssKaraokeWords$?: Observable<VoiceTtsKaraokeWord[]>;
35
-
36
- private _kSub?: Subscription;
22
+
37
23
  @Output() onBeforeMessageRender = new EventEmitter();
38
24
  @Output() onAfterMessageRender = new EventEmitter();
39
25
  @Output() onElementRendered = new EventEmitter<{ element: string; status: boolean }>();
@@ -58,56 +44,22 @@ export class BubbleMessageComponent implements OnInit, OnDestroy {
58
44
  readonly isFrame = isFrame;
59
45
  readonly isAudio = isAudio;
60
46
  readonly isJsonSources = isJsonSources;
61
- readonly isAudioTTS = isAudioTTS;
62
47
  readonly messageType = messageType;
63
48
  readonly convertColorToRGBA = convertColorToRGBA;
64
49
  readonly MESSAGE_TYPE_MINE = MESSAGE_TYPE_MINE;
65
50
  readonly MESSAGE_TYPE_OTHERS = MESSAGE_TYPE_OTHERS;
66
51
 
67
- sizeImage: { width: number; height: number } = { width: 0, height: 0 };
68
- fullnameColor: string = '';
52
+ sizeImage: { width: number; height: number };
53
+ fullnameColor: string;
69
54
  jsonSources: JsonSourceItem[] | null = null;
70
55
  isUrlPreviewMessage = false;
71
56
 
72
57
  private urlPreviewReqId = 0;
73
58
 
74
59
  constructor(
75
- public sanitizer: DomSanitizer,
76
- public voiceService: VoiceService,
60
+ public sanitizer: DomSanitizer,
77
61
  private jsonSourcesParser: JsonSourcesParserService
78
- ) { }
79
-
80
- ngOnInit() {
81
- // If this TTS message arrived while the voice proxy was active, mark it so
82
- // audio-sync never replays it after the session ends.
83
- if (isAudioTTS(this.message) && this.voiceService.isWssVoiceActive && this.message?.uid) {
84
- this.voiceService.markProxyHandled(this.message.uid);
85
- }
86
-
87
- // Set up karaoke observable for TTS messages during WSS sessions.
88
- if (isAudioTTS(this.message) && this.message?.text) {
89
- const text = this.message.text;
90
- const rawWords = text.trim().split(/\s+/).filter((w) => w.length > 0);
91
- // Always start as 'past' (fully visible). The karaoke RAF loop will drive
92
- // words through future→active→past for the current speaking turn; using
93
- // 'future' here would dimm old/history messages the moment voice opens.
94
- const initialWords: VoiceTtsKaraokeWord[] = rawWords.map((w) => ({ text: w, state: 'past' as const }));
95
-
96
- this._wssKaraokeWords$ = this.voiceService.voiceTtsKaraoke$.pipe(
97
- startWith({ text, words: initialWords, activeIndex: -1 }),
98
- map((frame) =>
99
- frame.text === text
100
- ? (frame.words as VoiceTtsKaraokeWord[])
101
- : initialWords,
102
- ),
103
- );
104
- }
105
- }
106
-
107
- ngOnDestroy(): void {
108
- this._kSub?.unsubscribe();
109
- this._kSub = undefined;
110
- }
62
+ ) {}
111
63
 
112
64
  ngOnChanges(): void {
113
65
  if (this.message?.metadata && typeof this.message.metadata === 'object') {
@@ -122,30 +74,8 @@ export class BubbleMessageComponent implements OnInit, OnDestroy {
122
74
  this.fullnameColor = getColorBck(this.message.sender_fullname);
123
75
  }
124
76
 
125
- // One-shot: activate word streaming for newly-arrived bot text messages during a voice session.
126
- // Reset isJustRecived so the animation never replays on subsequent change detection cycles.
127
- if (
128
- !this._isStreaming &&
129
- this.streamOnArrival &&
130
- this.message?.isJustRecived === true &&
131
- this.messageType(this.MESSAGE_TYPE_OTHERS, this.message) &&
132
- !this.isAudio(this.message) &&
133
- !this.isAudioTTS(this.message) &&
134
- this.message?.type !== 'html'
135
- ) {
136
- this._isStreaming = true;
137
- this._streamingWords = (this.message.text ?? '')
138
- .trim()
139
- .split(/\s+/)
140
- .filter(w => w.length > 0)
141
- .map((word, index) => ({ word, index }));
142
- this.message.isJustRecived = false;
143
- }
144
-
145
- if (this.message?.type !== TYPE_MSG_URL_PREVIEW) {
146
- this.jsonSources = null;
147
- return;
148
- }
77
+ // Reset on every message change: we must not "leak" sources across different messages.
78
+ this.jsonSources = null;
149
79
 
150
80
  // url_preview payload can live on message root OR inside metadata/attributes depending on the integration.
151
81
  const urlPreviewLike =
@@ -169,14 +99,6 @@ export class BubbleMessageComponent implements OnInit, OnDestroy {
169
99
  this.jsonSources = enriched;
170
100
  }
171
101
 
172
- trackWord(_index: number, item: { word: string; index: number }): number {
173
- return item.index;
174
- }
175
-
176
- trackKaraokeWord(index: number): number {
177
- return index;
178
- }
179
-
180
102
  onBeforeMessageRenderFN(event: any): void {
181
103
  this.onBeforeMessageRender.emit({ message: this.message, sanitizer: this.sanitizer, messageEl: event.messageEl, component: event.component });
182
104
  }
@@ -1,7 +1,8 @@
1
- <div #actionButton class="button-in-msg action action-button"
1
+ <div #actionButton id="actionButton" class="button-in-msg action"
2
2
  [ngClass]="{'disabled': isConversationArchived}"
3
- (click)="actionButtonAction()"
4
- (mouseover)="onMouseOver($event)"
3
+ (click)="actionButtonAction()"
4
+ (mouseover)="onMouseOver($event)"
5
5
  (mouseout)="onMouseOut($event)">
6
6
  {{button?.value}}
7
7
  </div>
8
+ <!-- title="{{button?.value}}" -->
@@ -1,6 +1,4 @@
1
- import { SimpleChange } from '@angular/core';
2
- import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing';
3
- import { By } from '@angular/platform-browser';
1
+ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
4
2
 
5
3
  import { ActionButtonComponent } from './action-button.component';
6
4
 
@@ -8,62 +6,20 @@ describe('ActionButtonComponent', () => {
8
6
  let component: ActionButtonComponent;
9
7
  let fixture: ComponentFixture<ActionButtonComponent>;
10
8
 
11
- beforeEach(waitForAsync(() => {
9
+ beforeEach(async(() => {
12
10
  TestBed.configureTestingModule({
13
- declarations: [ActionButtonComponent],
14
- }).compileComponents();
11
+ declarations: [ ActionButtonComponent ]
12
+ })
13
+ .compileComponents();
15
14
  }));
16
15
 
17
16
  beforeEach(() => {
18
17
  fixture = TestBed.createComponent(ActionButtonComponent);
19
18
  component = fixture.componentInstance;
20
- component.button = { value: 'OK', action: 'go' };
21
19
  fixture.detectChanges();
22
20
  });
23
21
 
24
22
  it('should create', () => {
25
23
  expect(component).toBeTruthy();
26
24
  });
27
-
28
- it('ngOnChanges should map theme CSS variables onto .action', () => {
29
- component.fontSize = '16px';
30
- component.backgroundColor = '#111';
31
- component.textColor = '#222';
32
- component.hoverBackgroundColor = '#333';
33
- component.hoverTextColor = '#444';
34
- component.ngOnChanges({
35
- fontSize: new SimpleChange(null, '16px', true),
36
- });
37
- const el = fixture.nativeElement.querySelector('.action') as HTMLElement;
38
- expect(el.style.getPropertyValue('--buttonFontSize').trim()).toBe('16px');
39
- expect(el.style.getPropertyValue('--buttonBackgroundColor').trim()).toBeTruthy();
40
- });
41
-
42
- it('actionButtonAction should emit when action present', fakeAsync(() => {
43
- spyOn(component.onButtonClicked, 'emit');
44
- const de = fixture.debugElement.query(By.css('.action'));
45
- de.triggerEventHandler('click', {});
46
- tick(500);
47
- expect(component.onButtonClicked.emit).toHaveBeenCalled();
48
- }));
49
-
50
- it('actionButtonAction should no-op when button has no action', () => {
51
- component.button = { value: 'X', action: '' };
52
- fixture.detectChanges();
53
- spyOn(component.onButtonClicked, 'emit');
54
- component.actionButtonAction();
55
- expect(component.onButtonClicked.emit).not.toHaveBeenCalled();
56
- });
57
-
58
- it('mouseover and mouseout should not throw', () => {
59
- expect(() => component.onMouseOver({} as any)).not.toThrow();
60
- expect(() => component.onMouseOut({} as any)).not.toThrow();
61
- });
62
-
63
- it('template should mark archived conversations as disabled', () => {
64
- component.isConversationArchived = true;
65
- fixture.detectChanges();
66
- const el = fixture.nativeElement.querySelector('.action');
67
- expect(el.classList.contains('disabled')).toBe(true);
68
- });
69
25
  });