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

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 (55) 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 +22 -118
  4. package/Dockerfile +4 -4
  5. package/README.md +1 -1
  6. package/docs/ACCESSIBILITY-STATEMENT.md +388 -0
  7. package/docs/TILEDESK_WIDGET_ACCESSIBILITY_ALIGNMENT.md +60 -0
  8. package/docs/TILEDESK_WIDGET_ACCESSIBILITY_STATEMENT_COMPLETE.md +386 -0
  9. package/docs/changelog/this-branch.md +0 -36
  10. package/nginx.conf +2 -22
  11. package/package.json +1 -1
  12. package/src/app/app.component.ts +9 -10
  13. package/src/app/component/conversation-detail/conversation/conversation.component.html +2 -2
  14. package/src/app/component/conversation-detail/conversation/conversation.component.scss +2 -2
  15. package/src/app/component/conversation-detail/conversation/conversation.component.ts +16 -34
  16. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +3 -3
  17. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +2 -2
  18. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +0 -1
  19. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +52 -63
  20. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +17 -11
  21. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +10 -4
  22. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +5 -8
  23. package/src/app/component/form/inputs/form-text/form-text.component.ts +1 -1
  24. package/src/app/component/last-message/last-message.component.ts +1 -4
  25. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +17 -8
  26. package/src/app/component/message/audio-sync/audio-sync.component.ts +96 -25
  27. package/src/app/component/message/bubble-message/bubble-message.component.html +12 -9
  28. package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +38 -45
  29. package/src/app/component/message/bubble-message/bubble-message.component.ts +49 -45
  30. package/src/app/component/message/json-sources/json-sources.component.html +6 -5
  31. package/src/app/component/message/json-sources/json-sources.component.scss +26 -18
  32. package/src/app/component/message/json-sources/json-sources.component.ts +41 -0
  33. package/src/app/providers/global-settings.service.ts +0 -42
  34. package/src/app/providers/json-sources-parser.service.ts +13 -1
  35. package/src/app/providers/translator.service.ts +1 -4
  36. package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +7 -8
  37. package/src/app/providers/tts-audio-playback-coordinator.service.ts +13 -0
  38. package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +67 -82
  39. package/src/app/providers/voice/voice.service.spec.ts +35 -35
  40. package/src/app/providers/voice/voice.service.ts +3 -7
  41. package/src/app/sass/_variables.scss +0 -1
  42. package/src/app/utils/globals.ts +2 -8
  43. package/src/assets/i18n/en.json +22 -1
  44. package/src/assets/i18n/es.json +22 -1
  45. package/src/assets/i18n/fr.json +22 -1
  46. package/src/assets/i18n/it.json +22 -1
  47. package/src/assets/twp/index-dev.html +0 -18
  48. package/src/chat21-core/providers/firebase/firebase-init-service.ts +5 -5
  49. package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
  50. package/src/chat21-core/utils/utils-message.ts +4 -4
  51. package/src/chat21-core/utils/utils.ts +2 -5
  52. package/src/widget-config-template.json +0 -1
  53. package/src/widget-config.json +28 -30
  54. package/.github/workflows/build.yml +0 -22
  55. package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +0 -14
@@ -265,8 +265,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
265
265
  'VOICE_CONNECTING',
266
266
  'VOICE_LISTENING',
267
267
  'VOICE_PROCESSING',
268
- 'STREAM_AUDIO',
269
- 'MAX_ATTACHMENT'
268
+ 'STREAM_AUDIO'
270
269
  ];
271
270
 
272
271
  const keysContent = [
@@ -529,29 +528,25 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
529
528
  return this.isConversationArchived;
530
529
  }
531
530
 
532
- // FALLBACK TO TILEDESK
533
- let requests_list: { requests: any[] };
534
- try {
535
- requests_list = await this.tiledeskRequestService.getMyRequests();
536
- } catch (err) {
531
+ //FALLBACK TO TILEDESK
532
+ const requests_list = await this.tiledeskRequestService.getMyRequests().catch(err => {
537
533
  this.logger.error('[CONV-COMP] getConversationDetail: error getting request from Tiledesk', err);
538
- this.isConversationArchived = true;
539
- return this.isConversationArchived;
540
- }
541
-
534
+ this.isConversationArchived=true
535
+ return { requests: [] }
536
+ });
542
537
  if (requests_list && requests_list.requests.length > 0) {
543
538
  this.logger.debug('[CONV-COMP] getConversationDetail: request exist on Tiledesk', requests_list);
544
- const conversation = requests_list.requests.find((request) => request.request_id === this.conversationId);
545
- if (conversation) {
546
- this.isConversationArchived = false;
547
- return this.isConversationArchived;
539
+ let conversation = requests_list.requests.find((request)=> request.request_id === this.conversationId)
540
+ if(conversation){
541
+ this.isConversationArchived = false
542
+ return this.isConversationArchived
548
543
  }
549
544
  this.logger.debug('[CONV-COMP] getConversationDetail: request NOT EXIST on Tiledesk', requests_list);
550
- this.isConversationArchived = true;
551
- return this.isConversationArchived;
545
+ this.isConversationArchived = true
546
+ return this.isConversationArchived
552
547
  }
553
548
 
554
- this.isConversationArchived = false;
549
+ this.isConversationArchived = true;
555
550
  return null;
556
551
  }
557
552
 
@@ -913,20 +908,6 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
913
908
  this.subscriptions.push(subscribe);
914
909
  }
915
910
 
916
- subscribtionKey = 'conversationsAdded';
917
- subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
918
- if(!subscribtion){
919
-
920
- subscribtion = this.chatManager.conversationsHandlerService.conversationChanged.pipe(takeUntil(this.unsubscribe$)).subscribe((conversation) => {
921
- this.logger.debug('[CONV-COMP] ***** DATAIL conversationsChanged *****', conversation, this.conversationWith, this.isConversationArchived);
922
- if(conversation && conversation.recipient === this.conversationId){
923
- this.isConversationArchived = false
924
- }
925
- });
926
- const subscribe = {key: subscribtionKey, value: subscribtion };
927
- this.subscriptions.push(subscribe);
928
- }
929
-
930
911
  subscribtionKey = 'messageWait';
931
912
  subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
932
913
  if (!subscribtion) {
@@ -1449,7 +1430,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1449
1430
  this.onNewConversationButtonClicked.emit()
1450
1431
  }
1451
1432
 
1452
- /** CALLED BY: conv-footer streaming audio button */
1433
+ /** CALLED BY: conv-footer component */
1453
1434
  onStreamAudioActiveChange(event: boolean){
1454
1435
  this.isStreamAudioActive = event
1455
1436
  }
@@ -1462,7 +1443,6 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1462
1443
  this.logger.debug('[CONV-COMP] onCloseChatButtonClicked::::', event)
1463
1444
  this.onCloseChat()
1464
1445
  }
1465
- // =========== END: event emitter function ====== //
1466
1446
 
1467
1447
  /**
1468
1448
  * True quando è visibile il pulsante chiudi stream (`.close-stream-button`, `isStreamAudioActive`).
@@ -1471,6 +1451,8 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1471
1451
  closeStreamButtonActiveForSheetBottom(): boolean {
1472
1452
  return !!(this.g?.showAudioStreamFooterButton && (this.isStreamAudioActive || this.isStreamAudioConnecting));
1473
1453
  }
1454
+ // =========== END: event emitter function ====== //
1455
+
1474
1456
 
1475
1457
  openInputFiles() {
1476
1458
  alert('ok');
@@ -27,7 +27,7 @@
27
27
  <div *ngFor="let message of messages; let first = first; let last = last; let i = index" class="rowMsg">
28
28
 
29
29
  <!-- message SENDER:: -->
30
- <div role="article" *ngIf="messageType(MESSAGE_TYPE_MINE, message) && !message.isJustRecived" class="msg_container base_sent">
30
+ <div role="article" *ngIf="messageType(MESSAGE_TYPE_MINE, message) && !message.isJustRecived" class="msg_container base_sent">
31
31
 
32
32
  <!--backgroundColor non viene ancora usato -->
33
33
  <!-- class="messages msg_sent slide-in-right" -->
@@ -55,7 +55,7 @@
55
55
  <!-- message RECIPIENT:: -->
56
56
  <div role="article" *ngIf="messageType(MESSAGE_TYPE_OTHERS, message)" class="msg_container base_receive">
57
57
 
58
- <chat-avatar-image *ngIf="!isSameSender(message?.sender, i) && !isStreamAudioActive"
58
+ <chat-avatar-image *ngIf="!isSameSender(message?.sender, i) && !isStreamAudioActive"
59
59
  [ngClass]="{'slide-in-left': false}"
60
60
  [senderID]="message?.sender"
61
61
  [senderFullname]="message?.sender_fullname"
@@ -69,7 +69,7 @@
69
69
  [class.no-background]="(isImage(message) || isFrame(message) || isCarousel(message)) && ((message?.text && message?.text.trim() === '') || !message?.text)"
70
70
  [class.emoticon]="isEmojii(message?.text)"
71
71
  [class.fullSizeMessage]="isStreamAudioActive"
72
- [style.margin-left]="isSameSender(message?.sender, i) ? 'calc(var(--avatar-width) + 10px)' : null"
72
+ [style.margin-left]="isSameSender(message?.sender, i) && !isStreamAudioActive ? 'calc(var(--avatar-width) + 10px)' : null"
73
73
  [ngStyle]="{'background': stylesMap.get('bubbleReceivedBackground'), 'color': stylesMap.get('bubbleReceivedTextColor'), 'width':isFrame(message) ?'100%' : null}"
74
74
  [isSameSender]="isSameSender(message?.sender, i)"
75
75
  [message]="message"
@@ -28,7 +28,7 @@
28
28
  }
29
29
 
30
30
  :host .loading.fullSize ::ng-deep > div.spinner{
31
- margin: 15px 0px !important;
31
+ margin: 50px 0px !important;
32
32
  }
33
33
 
34
34
  // ============= CSS c21-body ================= //
@@ -48,7 +48,7 @@
48
48
  top: 0;
49
49
  right: 0;
50
50
  left: 0;
51
- bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height) + var(--chat-footer-close-button-height));
51
+ bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height));
52
52
  overflow: hidden;
53
53
  .time{
54
54
  margin-bottom: 20px;
@@ -263,5 +263,4 @@ describe('ConversationContentComponent', () => {
263
263
  expect(spy).not.toHaveBeenCalled();
264
264
  });
265
265
  });
266
-
267
266
  });
@@ -5,11 +5,11 @@
5
5
  </div>
6
6
 
7
7
  <!-- ALERT EMOJI -->
8
- <div id="textAlert"
9
- *ngIf="!hideTextAreaContent && showAlertEmoji"
10
- role="alert"
11
- aria-live="assertive"
12
- class="fade-in-bottom"
8
+ <div id="textAlert"
9
+ *ngIf="!hideTextAreaContent && showAlertEmoji"
10
+ role="alert"
11
+ aria-live="assertive"
12
+ class="fade-in-bottom"
13
13
  [class.hideTextReply]="hideTextReply">
14
14
  <svg aria-hidden="true" focusable="false" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" version="1.1" viewBox="0 0 110 135">
15
15
  <path d="M55,25.8c-23,0-41.7,18.7-41.7,41.7s18.7,41.7,41.7,41.7,41.7-18.7,41.7-41.7-18.7-41.7-41.7-41.7ZM55,91.5c-3.4,0-6.2-2.8-6.2-6.2s2.8-6.2,6.2-6.2,6.2,2.8,6.2,6.2-2.8,6.2-6.2,6.2ZM60.3,70.1c-.2,2.8-2.5,4.9-5.3,4.9s-5.1-2.2-5.3-4.9l-1.6-22.3c-.3-4,2.9-7.4,6.9-7.4s7.2,3.4,6.9,7.4l-1.6,22.3Z"/>
@@ -42,42 +42,40 @@
42
42
  <div class="textarea-container-wrapper" *ngIf="!hideTextAreaContent && !hideTextReply">
43
43
  <!-- TEXTAREA + ICONS: conv active-->
44
44
  <div class="textarea-container" [class.voice-mode]="isStreamAudioActive || isStreamAudioConnecting">
45
-
45
+
46
46
  <div *ngIf="!isStopRec && !(isStreamAudioActive || isStreamAudioConnecting)" class="icons-container">
47
47
  <!-- ICON ATTACHMENT -->
48
- <label *ngIf="showAttachmentFooterButton"
49
- for="chat21-file"
48
+ <label *ngIf="showAttachmentFooterButton"
49
+ for="chat21-file"
50
50
  role="button"
51
51
  tabindex="0"
52
52
  [attr.aria-label]="translationMap?.get('BUTTON_ATTACH_FILE')"
53
53
  [attr.title]="'MAX_ATTACHMENT' | translate: { FILE_SIZE_LIMIT: file_size_limit }"
54
- class="chat21-textarea-button"
55
- [class.active]="!isFilePendingToUpload && !hideTextReply"
54
+ class="chat21-textarea-button"
55
+ [class.active]="!isFilePendingToUpload && !hideTextReply"
56
56
  id="chat21-start-upload-doc"
57
57
  (keydown.enter)="$event.preventDefault(); chat21_file.click()"
58
58
  (keydown.space)="$event.preventDefault(); chat21_file.click()">
59
59
  <span class="v-align-center">
60
60
  <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="24px" height="24" viewBox="0 0 24 24" fill="currentColor">
61
61
  <path d="M9.9,22.7c0,0-.1,0-.2,0-1.9.3-3.7-.2-5.2-1.4-3-2.3-3.6-6.4-1.4-9.5L9.5,2.5c.4-.5,1.1-.6,1.6-.3.5.4.6,1.1.3,1.6l-6.5,9.4c-1.4,2-1,4.8.9,6.3,1,.8,2.2,1.1,3.5.9,1.3-.2,2.4-.9,3.1-1.9l6-8.7c.9-1.2.6-3-.6-3.9-.6-.5-1.4-.6-2.1-.5-.8.1-1.4.5-1.9,1.1l-5.8,8.2c-.3.5-.2,1.1.2,1.5.2.2.5.3.8.2.3,0,.6-.2.7-.4l4.7-6.2c.4-.5,1.1-.6,1.6-.2.5.4.6,1.1.2,1.6l-4.7,6.2c-.5.7-1.4,1.2-2.3,1.3-.9.1-1.8-.2-2.5-.7-1.4-1.1-1.6-3.1-.6-4.6l5.8-8.2c.8-1.1,2-1.9,3.4-2.1,1.4-.2,2.7.1,3.8,1,2.2,1.7,2.7,4.8,1.1,7.1l-6,8.7c-1.1,1.5-2.6,2.5-4.4,2.8h0Z"/>
62
- <title id="altIconTitle">{{ maxAttachmentLabel }}</title>
63
62
  </svg>
64
-
65
63
  </span>
66
64
  <input
67
- [attr.disabled]="(isFilePendingToUpload || isConversationArchived || hideTextReply)? true : null"
65
+ [attr.disabled] = "(isFilePendingToUpload || isConversationArchived || hideTextReply)? true : null"
68
66
  tabindex="-1"
69
- type="file"
67
+ type="file"
70
68
  [attr.aria-label]="translationMap?.get('BUTTON_ATTACH_FILE')"
71
69
  [accept]="fileUploadAccept"
72
70
  name="chat21-file"
73
71
  id="chat21-file"
74
72
  #chat21_file
75
- class="inputfile"
73
+ class="inputfile"
76
74
  [ngStyle]="{'display': 'block', height:'1px', width:'1px', overflow: 'hidden' }"
77
75
  (change)="detectFiles($event)"/>
78
76
  </label>
79
77
  <!-- ICON EMOJII -->
80
- <label *ngIf="showEmojiFooterButton"
78
+ <label *ngIf="showEmojiFooterButton"
81
79
  for="chat21-emojii"
82
80
  role="button"
83
81
  tabindex="0"
@@ -90,7 +88,7 @@
90
88
  (keydown.enter)="$event.preventDefault(); onEmojiiPickerClicked()"
91
89
  (keydown.space)="$event.preventDefault(); onEmojiiPickerClicked()">
92
90
  <span class="v-align-center">
93
- <svg role="img" aria-labelledby="altIconTitle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
91
+ <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
94
92
  <path stroke-width=".4px" stroke="currentColor" d="M12,20.8c-5.1,0-9.3-4.2-9.3-9.3S6.9,2.2,12,2.2s9.3,4.2,9.3,9.3-4.2,9.3-9.3,9.3ZM12,3.6c-4.4,0-7.9,3.6-7.9,7.9s3.6,7.9,7.9,7.9,7.9-3.6,7.9-7.9-3.6-7.9-7.9-7.9Z"/>
95
93
  <path stroke-width=".4px" stroke="currentColor" d="M12,17.2c-2.7,0-4.3-1.9-4.6-2.3-.2-.3-.2-.7.1-1s.7-.2,1,.1c.1.2,1.4,1.8,3.5,1.8s2.2,0,3.5-1.8c.2-.3.7-.4,1-.1s.4.7.1,1c-1.7,2.2-4.1,2.3-4.6,2.3Z"/>
96
94
  <path d="M8.7,10.9c-.9,0-1.6-.7-1.6-1.6s.7-1.6,1.6-1.6,1.6.7,1.6,1.6-.7,1.6-1.6,1.6Z"/>
@@ -107,7 +105,7 @@
107
105
  class="visible-text-area"
108
106
  [class.stream-active]="isStreamAudioActive || isStreamAudioConnecting"
109
107
  [class.hasError]="showAlertEmoji"
110
- [class.disabled]="(isConversationArchived || hideTextReply)? true : null">
108
+ [class.disabled] = "( isConversationArchived || hideTextReply)? true : null">
111
109
  <!-- Voice mode: inline status chip -->
112
110
  <div class="voice-inline-status" *ngIf="isStreamAudioActive || isStreamAudioConnecting" role="status" aria-live="polite">
113
111
  <span class="voice-inline-dot"
@@ -117,10 +115,10 @@
117
115
  <span class="voice-inline-label">{{ voiceStatusLabel }}</span>
118
116
  </div>
119
117
  <!-- Normal mode: textarea -->
120
- <textarea
118
+ <textarea
121
119
  *ngIf="!(isStreamAudioActive || isStreamAudioConnecting)"
122
- [attr.disabled]="(hideTextReply)? true : null"
123
- [attr.placeholder]="(footerMessagePlaceholder)? footerMessagePlaceholder : translationMap?.get('LABEL_PLACEHOLDER')"
120
+ [attr.disabled] = "(hideTextReply)? true : null"
121
+ [attr.placeholder] ="(footerMessagePlaceholder)? footerMessagePlaceholder : translationMap?.get('LABEL_PLACEHOLDER')"
124
122
  [attr.aria-label]="(footerMessagePlaceholder)? footerMessagePlaceholder : translationMap?.get('LABEL_PLACEHOLDER')"
125
123
  [attr.aria-multiline]="true"
126
124
  [attr.aria-invalid]="showAlertEmoji ? 'true' : 'false'"
@@ -135,7 +133,7 @@
135
133
  (keydown)="onkeydown($event)"
136
134
  (paste)="onPaste($event)">
137
135
  </textarea>
138
-
136
+
139
137
  </div>
140
138
 
141
139
  <!-- ICON SEND -->
@@ -156,12 +154,8 @@
156
154
  </button>
157
155
 
158
156
  <!-- ICON REC -->
159
- <div *ngIf="showAudioRecorderFooterButton && !textInputTextArea"
160
- class="chat21-audio-button"
161
- [class.active]="isStopRec"
162
- id="chat21-button-rec">
157
+ <div *ngIf="showAudioRecorderFooterButton && !textInputTextArea && !isStreamAudioActive && !isStreamAudioConnecting" tabindex="-1" class="chat21-audio-button" [class.active]="isStopRec" id="chat21-button-rec">
163
158
  <chat-audio-recorder
164
- [translationMap]="translationMap"
165
159
  (startRecordingEvent)="onStartRecording()"
166
160
  (deleteRecordingEvent)="onDeleteRecording()"
167
161
  (endRecordingEvent)="onEndRecording($event)"
@@ -183,30 +177,26 @@
183
177
  [translationMap]="translationMap">
184
178
  </chat-stream-audio-spectrum>
185
179
  </div>
186
- </div>
187
180
 
181
+ <div class="close-chat-container" *ngIf="closeChatInConversation">
182
+ <button type="button" aflistconv #aflistconv class="c21-button-primary c21-close" (click)="onCloseChat($event)" [ngStyle]="{'background-color': stylesMap.get('themeColor'), 'border-color': stylesMap.get('themeColor'), 'color': stylesMap?.get('foregroundColor')}">
183
+ <span class="v-align-center">
184
+ <svg [ngStyle]="{'stroke': stylesMap?.get('foregroundColor'), 'fill': stylesMap?.get('foregroundColor') }" role="img" id="archive" aria-labelledby="altIconTitle" class="icon-menu" xmlns="http://www.w3.org/2000/svg"
185
+ width="15px" height="15px" viewBox="0 0 512 512">
186
+ <path d="M80 152v256a40.12 40.12 0 0040 40h272a40.12 40.12 0 0040-40V152" stroke-linecap="round" stroke-linejoin="round" stroke-width="50px" fill="none"></path>
187
+ <rect x="48" y="64" width="416" height="80" rx="28" ry="28" stroke-linejoin="round" stroke-width="50px" fill="none" ></rect>
188
+ <path stroke-linecap="round" stroke-linejoin="round" d="M320 304l-64 64-64-64M256 345.89V224" stroke-width="50px" fill="none"></path>
189
+ <title id="altIconTitle">{{ translationMap?.get('CLOSE_CHAT') }}</title>
190
+ </svg>
191
+ </span>
192
+ <span class="v-align-center c21-label-button">
193
+ {{translationMap?.get('CLOSE_CHAT')}}
194
+ </span>
195
+ <div class="clear"></div>
196
+ </button>
197
+ </div>
188
198
 
189
- <div class="close-chat-container" *ngIf="closeChatInConversation">
190
- <button tabindex="1040" aflistconv #aflistconv class="c21-button-primary c21-close" (click)="onCloseChat($event)" [ngStyle]="{'background-color': stylesMap.get('themeColor'), 'border-color': stylesMap.get('themeColor'), 'color': stylesMap?.get('foregroundColor')}">
191
- <span class="v-align-center">
192
- <!-- <svg [ngStyle]="{'fill': 'yellow' }" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
193
- <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" [ngStyle]="{'fill': stylesMap?.get('foregroundColor')}"/>
194
- </svg> -->
195
- <svg [ngStyle]="{'stroke': stylesMap?.get('foregroundColor'), 'fill': stylesMap?.get('foregroundColor') }" role="img" id="archive" aria-labelledby="altIconTitle" class="icon-menu" xmlns="http://www.w3.org/2000/svg"
196
- width="15px" height="15px" viewBox="0 0 512 512">
197
- <path d="M80 152v256a40.12 40.12 0 0040 40h272a40.12 40.12 0 0040-40V152" stroke-linecap="round" stroke-linejoin="round" stroke-width="50px" fill="none"></path>
198
- <rect x="48" y="64" width="416" height="80" rx="28" ry="28" stroke-linejoin="round" stroke-width="50px" fill="none" ></rect>
199
- <path stroke-linecap="round" stroke-linejoin="round" d="M320 304l-64 64-64-64M256 345.89V224" stroke-width="50px" fill="none"></path>
200
- <title id="altIconTitle">{{ translationMap?.get('CLOSE_CHAT') }}</title>
201
- </svg>
202
- </span>
203
- <span class="v-align-center c21-label-button">
204
- {{translationMap?.get('CLOSE_CHAT')}}
205
- </span>
206
- <div class="clear"></div>
207
- </button>
208
199
  </div>
209
-
210
200
  </div>
211
201
 
212
202
 
@@ -218,23 +208,22 @@
218
208
  [attr.aria-hidden]="!isEmojiiPickerShow"
219
209
  [attr.aria-label]="translationMap?.get('EMOJI')"
220
210
  #emoji_mart_container>
221
- <!-- <ng-container #emojii_container></ng-container> -->
222
- <emoji-mart id="emoji-mart"
223
- *ngIf="showEmojiPicker"
224
- class="emoji-mart"
225
- [showPreview]="emojiiOptions?.showPreview"
226
- [color]="stylesMap?.get('themeColor')"
227
- [perLine]="emojiiOptions?.emojiPerLine"
228
- [totalFrequentLines]="emojiiOptions?.totalFrequentLines"
229
- [enableSearch]="emojiiOptions?.enableSearch"
230
- [darkMode]="emojiiOptions?.darkMode"
231
- [include]="emojiiOptions?.include"
232
- (emojiSelect)="addEmoji($event)">
233
- </emoji-mart>
211
+ <emoji-mart id="emoji-mart"
212
+ *ngIf="showEmojiPicker"
213
+ class="emoji-mart"
214
+ [showPreview]="emojiiOptions?.showPreview"
215
+ [color]="stylesMap?.get('themeColor')"
216
+ [perLine]="emojiiOptions?.emojiPerLine"
217
+ [totalFrequentLines]="emojiiOptions?.totalFrequentLines"
218
+ [enableSearch]="emojiiOptions?.enableSearch"
219
+ [darkMode]="emojiiOptions?.darkMode"
220
+ [include]="emojiiOptions?.include"
221
+ (emojiSelect)="addEmoji($event)">
222
+ </emoji-mart>
234
223
  </div>
235
224
 
236
225
  <!-- NEW CONV & CONTINE buttons: conv archived-->
237
- <div id="floating-container" *ngIf="hideTextAreaContent" class="fade-in-bottom" start-focus-chat21-conversation-component>
226
+ <div id="floating-container" *ngIf="hideTextAreaContent" class="fade-in-bottom" start-focus-chat21-conversation-component>
238
227
  <button type="button" aflistconv #aflistconv class="c21-button-primary" (click)="openNewConversation()" [ngStyle]="{'background-color': stylesMap.get('themeColor'), 'border-color': stylesMap.get('themeColor'), 'color': stylesMap?.get('foregroundColor')}">
239
228
  <span class="v-align-center">
240
229
  <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
@@ -243,7 +232,7 @@
243
232
  </span>
244
233
  <span class="v-align-center c21-label-button">
245
234
  {{translationMap?.get('LABEL_START_NW_CONV')}}
246
- </span>
235
+ </span>
247
236
  <div class="clear"></div>
248
237
  </button>
249
238
  </div>
@@ -1,10 +1,10 @@
1
- .textarea-container-wrapper{
2
- display: flex;
3
- flex-direction: column;
4
- gap: 8px;
5
- }
1
+
6
2
  .textarea-container{
3
+ // padding: 8px 34px;
4
+ // padding-left: 70px;
5
+ // padding-right: 45px;
7
6
  display: flex;
7
+ // width: 100%;
8
8
  align-items: center;
9
9
  justify-content: space-between;
10
10
  gap: 8px;
@@ -17,6 +17,7 @@
17
17
  &:has(:not(#chat21-start-upload-doc)):has(:not(#chat21-emoticon-picker)) .visible-text-area {
18
18
  width: 90%;
19
19
  }
20
+
20
21
  }
21
22
 
22
23
  .close-chat-container{
@@ -25,11 +26,6 @@
25
26
  align-items: center;
26
27
  justify-content: center;
27
28
  gap: 8px;
28
-
29
- .c21-close{
30
- height: 30px !important;
31
- margin: 0px !important;
32
- }
33
29
  }
34
30
 
35
31
  .icons-container{
@@ -62,6 +58,15 @@
62
58
  display: flex;
63
59
  align-items: center;
64
60
  }
61
+
62
+ //if attachment icon OR emoji icon is not in DOM -> increment textarea width
63
+ &:has(:not(#chat21-start-upload-doc), :not(#chat21-emoticon-picker)) .visible-text-area {
64
+ width: 80%;
65
+ }
66
+ //if attachment icon AND emoji icon is not in DOM -> increment textarea width
67
+ &:has(:not(#chat21-start-upload-doc)):has(:not(#chat21-emoticon-picker)) .visible-text-area {
68
+ width: 90%;
69
+ }
65
70
  }
66
71
 
67
72
  .chat21-textarea-button {
@@ -429,7 +434,8 @@ textarea:active{
429
434
  position: absolute;
430
435
  // padding: 8px 16px;
431
436
  overflow: hidden;
432
- background-color: color-mix(in srgb, var(--content-background-color) 34%, transparent);
437
+ background-color: var(--content-background-color);
438
+ // background-color: color-mix(in srgb, var(--content-background-color) 34%, transparent);
433
439
  backdrop-filter: blur(20px) saturate(1.2);
434
440
  -webkit-backdrop-filter: blur(20px) saturate(1.2);
435
441
  box-shadow:
@@ -16,14 +16,12 @@ import { ConversationHandlerService } from 'src/chat21-core/providers/abstract/c
16
16
  import { VoiceService } from 'src/app/providers/voice/voice.service';
17
17
  import { TtsAudioPlaybackCoordinator } from 'src/app/providers/tts-audio-playback-coordinator.service';
18
18
  import { TiledeskAuthService } from 'src/chat21-core/providers/tiledesk/tiledesk-auth.service';
19
+ import { Globals } from 'src/app/utils/globals';
19
20
 
20
21
  describe('ConversationFooterComponent', () => {
21
22
  let component: ConversationFooterComponent;
22
23
  let fixture: ComponentFixture<ConversationFooterComponent>;
23
24
 
24
- const ngxlogger = jasmine.createSpyObj('NGXLogger', ['log', 'trace', 'debug', 'warn', 'error', 'info']);
25
- const customLogger = new CustomLogger(ngxlogger);
26
-
27
25
  const voiceServiceMock = {
28
26
  startSession: () => Promise.resolve(),
29
27
  stopSession: () => Promise.resolve({ voiceIngressResultUrl: null as string | null }),
@@ -32,7 +30,14 @@ describe('ConversationFooterComponent', () => {
32
30
  volume$: { subscribe: () => ({ unsubscribe: () => undefined }) },
33
31
  isAcquisitionBlocked$: { subscribe: () => ({ unsubscribe: () => undefined }) },
34
32
  };
35
- const ttsMock = { stopAll: () => undefined, isTTSPlaying$: { subscribe: () => ({ unsubscribe: () => undefined }) } };
33
+ const ttsMock = {
34
+ cancelAll: () => undefined,
35
+ stopAll: () => undefined,
36
+ isTTSPlaying$: { subscribe: () => ({ unsubscribe: () => undefined }) },
37
+ };
38
+
39
+ const ngxlogger = jasmine.createSpyObj('NGXLogger', ['log', 'trace', 'debug', 'warn', 'error', 'info']);
40
+ const customLogger = new CustomLogger(ngxlogger);
36
41
 
37
42
  const conversationHandlerStub = {
38
43
  sendMessage: jasmine.createSpy('sendMessage').and.returnValue({ uid: 'm1' }),
@@ -60,6 +65,7 @@ describe('ConversationFooterComponent', () => {
60
65
  imports: [FormsModule, ReactiveFormsModule],
61
66
  providers: [
62
67
  FormBuilder,
68
+ Globals,
63
69
  { provide: ChatManager, useValue: chatManagerStub },
64
70
  { provide: TypingService, useValue: typingStub },
65
71
  { provide: UploadService, useValue: uploadServiceStub as unknown as UploadService },
@@ -41,6 +41,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
41
41
  @Input() showAudioRecorderFooterButton: boolean;
42
42
  @Input() showAudioStreamFooterButton: boolean;
43
43
  // @Input() showContinueConversationButton: boolean;
44
+ @Input() closeChatInConversation: boolean;
44
45
  @Input() isConversationArchived: boolean;
45
46
  @Input() hideTextAreaContent: boolean;
46
47
  @Input() hideTextReply: boolean;
@@ -48,7 +49,6 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
48
49
  @Input() isEmojiiPickerShow: boolean;
49
50
  @Input() footerMessagePlaceholder: string;
50
51
  @Input() fileUploadAccept: string;
51
- @Input() closeChatInConversation: boolean;
52
52
  @Input() dropEvent: Event;
53
53
  @Input() poweredBy: string;
54
54
  @Input() stylesMap: Map<string, string>
@@ -124,15 +124,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
124
124
  return this.translationMap?.get('VOICE_LISTENING') || 'Listening...';
125
125
  }
126
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
- }
132
-
133
127
  file_size_limit = FILE_SIZE_LIMIT;
134
128
  attachmentTooltip: string = '';
135
- isErrorNetwork: boolean = false;
136
129
 
137
130
 
138
131
  convertColorToRGBA = convertColorToRGBA;
@@ -155,6 +148,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
155
148
  if(changes['conversationWith'] && changes['conversationWith'].currentValue !== undefined){
156
149
  this.conversationHandlerService = this.chatManager.getConversationHandlerByConversationId(this.conversationWith);
157
150
  this.isStreamAudioActive = false;
151
+ this.ttsPlayback.cancelAll();
158
152
  void this.stopVoice();
159
153
  }
160
154
  if(changes['hideTextReply'] && changes['hideTextReply'].currentValue !== undefined){
@@ -857,6 +851,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
857
851
  } catch (e) {
858
852
  this.logger.error('[CONV-FOOTER] onStreamPressed: initVoice failed', e);
859
853
  this.isStreamAudioActive = false;
854
+ this.ttsPlayback.cancelAll();
860
855
  } finally {
861
856
  this.isStreamAudioConnecting = false;
862
857
  this.onStreamAudioConnectingChange.emit(false);
@@ -864,6 +859,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
864
859
  } else {
865
860
  await this.stopVoice();
866
861
  this.isStreamAudioActive = false;
862
+ // Close-stream-button clicked: stop any playing/queued TTS audio.
863
+ this.ttsPlayback.cancelAll();
867
864
  this.isStreamAudioConnecting = false;
868
865
  this.onStreamAudioConnectingChange.emit(false);
869
866
  }
@@ -20,7 +20,6 @@ export class FormTextComponent implements OnInit, OnDestroy {
20
20
  @ViewChild('div_input') input: ElementRef;
21
21
  form: FormGroup<any>;
22
22
  inputType: string = 'text'
23
- private valueChangesSub?: Subscription;
24
23
 
25
24
  get fieldBaseId(): string {
26
25
  const raw = this.element?.name || this.controlName || 'field';
@@ -46,6 +45,7 @@ export class FormTextComponent implements OnInit, OnDestroy {
46
45
  }
47
46
  return this.form.controls[name].invalid ? 'true' : 'false';
48
47
  }
48
+ private valueChangesSub?: Subscription;
49
49
 
50
50
  constructor(private rootFormGroup: FormGroupDirective,
51
51
  private elementRef: ElementRef) { }
@@ -12,7 +12,7 @@ import { MIN_WIDTH_IMAGES } from 'src/app/utils/constants';
12
12
  import { ConversationModel } from 'src/chat21-core/models/conversation';
13
13
  import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service';
14
14
  import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
15
- import { commandToMessage, conversationToMessage, isEmojii, isFrame, isImage, isMine, isSameSender, isSender } from 'src/chat21-core/utils/utils-message';
15
+ import { commandToMessage, conversationToMessage, isEmojii, isFrame, isImage, isSameSender } from 'src/chat21-core/utils/utils-message';
16
16
 
17
17
 
18
18
  @Component({
@@ -59,9 +59,6 @@ export class LastMessageComponent implements OnInit, AfterViewInit, OnDestroy {
59
59
  ngOnChanges(changes: SimpleChanges) {
60
60
  this.logger.debug('[LASTMESSAGE] onChanges', changes)
61
61
  if(this.conversation){
62
-
63
- /** if the message is sent by the logged user, do not add it to the messages array */
64
- if(isSender(this.conversation.sender, this.g.senderId)) return;
65
62
 
66
63
  if(this.conversation.attributes && this.conversation.attributes.commands){
67
64
  this.addCommandMessage(this.conversation)
@@ -1,5 +1,6 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
1
  import { CommonModule } from '@angular/common';
2
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
3
+ import { Subject } from 'rxjs';
3
4
 
4
5
  import { AudioSyncComponent } from './audio-sync.component';
5
6
  import { TtsAudioPlaybackCoordinator } from 'src/app/providers/tts-audio-playback-coordinator.service';
@@ -9,12 +10,22 @@ import { Globals } from 'src/app/utils/globals';
9
10
  describe('AudioSyncComponent', () => {
10
11
  let component: AudioSyncComponent;
11
12
  let fixture: ComponentFixture<AudioSyncComponent>;
12
- let voiceService: { proxyTtsStreamUrl: string | null; proxyTtsUrl: string | null };
13
+ let voiceService: {
14
+ proxyTtsStreamUrl: string | null;
15
+ proxyTtsUrl: string | null;
16
+ speechStart$: ReturnType<Subject<void>['asObservable']>;
17
+ };
13
18
 
14
19
  beforeEach(async () => {
20
+ const speechStartSource = new Subject<void>();
21
+ const cancelAllSource = new Subject<void>();
22
+ const stopAllSource = new Subject<void>();
23
+ const preemptSource = new Subject<string>();
24
+
15
25
  voiceService = {
16
26
  proxyTtsStreamUrl: 'https://speech.example.com/api/tts/stream',
17
27
  proxyTtsUrl: 'https://speech.example.com/api/tts',
28
+ speechStart$: speechStartSource.asObservable(),
18
29
  };
19
30
 
20
31
  await TestBed.configureTestingModule({
@@ -27,15 +38,15 @@ describe('AudioSyncComponent', () => {
27
38
  requestStart: (_ownerId: string, start: () => void) => start(),
28
39
  releaseIfCurrent: jasmine.createSpy('releaseIfCurrent'),
29
40
  release: jasmine.createSpy('release'),
30
- stopAllPlayback$: { subscribe: () => ({ unsubscribe: () => undefined }) },
31
- preemptPlayback$: { subscribe: () => ({ unsubscribe: () => undefined }) },
41
+ stopAllPlayback$: stopAllSource.asObservable(),
42
+ preemptPlayback$: preemptSource.asObservable(),
43
+ cancelAll$: cancelAllSource.asObservable(),
32
44
  },
33
45
  },
34
46
  { provide: Globals, useValue: { tiledeskToken: 'JWT test-token', jwt: '' } },
35
47
  { provide: VoiceService, useValue: voiceService },
36
48
  ],
37
- })
38
- .compileComponents();
49
+ }).compileComponents();
39
50
 
40
51
  fixture = TestBed.createComponent(AudioSyncComponent);
41
52
  component = fixture.componentInstance;
@@ -79,7 +90,6 @@ describe('AudioSyncComponent', () => {
79
90
 
80
91
  expect(body).toEqual({
81
92
  text: 'hello',
82
- streaming: true,
83
93
  outputFormat: 'mp3_44100_128',
84
94
  });
85
95
  });
@@ -96,7 +106,6 @@ describe('AudioSyncComponent', () => {
96
106
 
97
107
  expect(body).toEqual({
98
108
  text: 'hello',
99
- streaming: true,
100
109
  outputFormat: 'pcm_16000',
101
110
  });
102
111
  });