@chat21/chat21-web-widget 5.1.30 → 5.1.32-rc13

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 (64) hide show
  1. package/.github/workflows/docker-community-push-latest.yml +23 -13
  2. package/.github/workflows/docker-image-tag-community-tag-push.yml +22 -12
  3. package/CHANGELOG.md +89 -2
  4. package/Dockerfile +4 -5
  5. package/angular.json +5 -2
  6. package/deploy_amazon_beta.sh +17 -7
  7. package/docs/changelog/this-branch.md +36 -0
  8. package/nginx.conf +22 -2
  9. package/package.json +4 -1
  10. package/src/app/app.component.ts +10 -9
  11. package/src/app/app.module.ts +11 -0
  12. package/src/app/component/conversation-detail/conversation/conversation.component.html +9 -2
  13. package/src/app/component/conversation-detail/conversation/conversation.component.scss +12 -2
  14. package/src/app/component/conversation-detail/conversation/conversation.component.ts +46 -5
  15. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +9 -5
  16. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +19 -1
  17. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +2 -0
  18. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +128 -80
  19. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +117 -13
  20. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +120 -8
  21. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +43 -0
  22. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +79 -0
  23. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +192 -0
  24. package/src/app/component/last-message/last-message.component.ts +4 -1
  25. package/src/app/component/message/audio/audio.component.ts +0 -5
  26. package/src/app/component/message/audio-sync/audio-sync.component.html +18 -0
  27. package/src/app/component/message/audio-sync/audio-sync.component.scss +64 -0
  28. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +23 -0
  29. package/src/app/component/message/audio-sync/audio-sync.component.ts +558 -0
  30. package/src/app/component/message/bubble-message/bubble-message.component.html +6 -1
  31. package/src/app/component/message/bubble-message/bubble-message.component.ts +2 -1
  32. package/src/app/providers/global-settings.service.ts +21 -0
  33. package/src/app/providers/translator.service.ts +2 -0
  34. package/src/app/providers/tts-audio-playback-coordinator.service.ts +93 -0
  35. package/src/app/providers/voice/STT&TTS/openai-voice.config.ts +12 -0
  36. package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +171 -0
  37. package/src/app/providers/voice/STT&TTS/speech-provider.abstract.ts +39 -0
  38. package/src/app/providers/voice/audio.types.ts +34 -0
  39. package/src/app/providers/voice/vad.service.spec.ts +28 -0
  40. package/src/app/providers/voice/vad.service.ts +70 -0
  41. package/src/app/providers/voice/voice.service.spec.ts +60 -0
  42. package/src/app/providers/voice/voice.service.ts +376 -0
  43. package/src/app/sass/_variables.scss +3 -0
  44. package/src/app/shims/onnxruntime-web-wasm.ts +4 -0
  45. package/src/app/utils/conversation-sender-classifier.ts +21 -0
  46. package/src/app/utils/globals.ts +7 -1
  47. package/src/assets/i18n/en.json +1 -0
  48. package/src/assets/i18n/es.json +1 -0
  49. package/src/assets/i18n/fr.json +1 -0
  50. package/src/assets/i18n/it.json +1 -0
  51. package/src/assets/onnx/ort-wasm-simd-threaded.mjs +59 -0
  52. package/src/assets/onnx/ort-wasm-simd-threaded.wasm +0 -0
  53. package/src/assets/vad/silero_vad_legacy.onnx +0 -0
  54. package/src/assets/vad/vad.worklet.bundle.min.js +1 -0
  55. package/src/chat21-core/models/message.ts +2 -1
  56. package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +3 -2
  57. package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +12 -0
  58. package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
  59. package/src/chat21-core/utils/utils-message.ts +7 -0
  60. package/src/chat21-core/utils/utils.ts +5 -2
  61. package/src/launch.js +41 -32
  62. package/src/launch_template.js +41 -32
  63. package/tsconfig.json +5 -0
  64. package/deploy_amazon_prod.sh +0 -41
@@ -161,6 +161,10 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
161
161
  membersConversation = ['SYSTEM'];
162
162
  // ========== end:: typying =======
163
163
 
164
+ // ========== begin:: stream audio ======= //
165
+ public isStreamAudioActive = false;
166
+ // ========== end:: stream audio ======= //
167
+
164
168
  @ViewChild(ConversationFooterComponent) conversationFooter: ConversationFooterComponent
165
169
  @ViewChild(ConversationContentComponent) conversationContent: ConversationContentComponent
166
170
  conversationHandlerService: ConversationHandlerService
@@ -246,7 +250,9 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
246
250
  'CONTINUE',
247
251
  'EMOJI_NOT_ELLOWED',
248
252
  'ATTACHMENT',
249
- 'EMOJI'
253
+ 'EMOJI',
254
+ 'CLOSE_CHAT',
255
+ 'CLOSE'
250
256
  ];
251
257
 
252
258
  const keysContent = [
@@ -501,7 +507,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
501
507
  return this.isConversationArchived;
502
508
  }
503
509
 
504
- //FALLBACK TO TILEDESK
510
+ // //FALLBACK TO TILEDESK
505
511
  const requests_list = await this.tiledeskRequestService.getMyRequests().catch(err => {
506
512
  this.logger.error('[CONV-COMP] getConversationDetail: error getting request from Tiledesk', err);
507
513
  this.isConversationArchived=true
@@ -519,9 +525,9 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
519
525
  return this.isConversationArchived
520
526
  }
521
527
 
522
- this.isConversationArchived = true;
523
- return null;
524
- }
528
+ this.isConversationArchived = false;
529
+ return null;
530
+ }
525
531
 
526
532
  /**
527
533
  * this.g.recipientId:
@@ -822,6 +828,10 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
822
828
  this.showThinkingMessage = false;
823
829
  }
824
830
 
831
+ if (this.isStreamAudioActive && msg.sender !== this.senderId) {
832
+ this.conversationFooter?.interruptStreamDueToPeerMessage();
833
+ }
834
+
825
835
  that.newMessageAdded(msg);
826
836
  // Update badge based on the latest message received from the server.
827
837
  // We rely on `messages` being kept in-sync by the conversation handler.
@@ -877,6 +887,20 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
877
887
  this.subscriptions.push(subscribe);
878
888
  }
879
889
 
890
+ subscribtionKey = 'conversationsAdded';
891
+ subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
892
+ if(!subscribtion){
893
+
894
+ subscribtion = this.chatManager.conversationsHandlerService.conversationChanged.pipe(takeUntil(this.unsubscribe$)).subscribe((conversation) => {
895
+ this.logger.debug('[CONV-COMP] ***** DATAIL conversationsChanged *****', conversation, this.conversationWith, this.isConversationArchived);
896
+ if(conversation && conversation.recipient === this.conversationId){
897
+ this.isConversationArchived = false
898
+ }
899
+ });
900
+ const subscribe = {key: subscribtionKey, value: subscribtion };
901
+ this.subscriptions.push(subscribe);
902
+ }
903
+
880
904
  subscribtionKey = 'messageWait';
881
905
  subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
882
906
  if (!subscribtion) {
@@ -1383,8 +1407,25 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1383
1407
  this.logger.debug('[CONV-COMP] floating onNewConversationButtonClicked')
1384
1408
  this.onNewConversationButtonClicked.emit()
1385
1409
  }
1410
+
1411
+ /** CALLED BY: conv-footer streaming audio button */
1412
+ onStreamAudioActiveChange(event: boolean){
1413
+ this.isStreamAudioActive = event
1414
+ }
1415
+ /** CALLED BY: conv-footer component */
1416
+ onCloseChatButtonClickedFN(event){
1417
+ this.logger.debug('[CONV-COMP] onCloseChatButtonClicked::::', event)
1418
+ this.onCloseChat()
1419
+ }
1386
1420
  // =========== END: event emitter function ====== //
1387
1421
 
1422
+ /**
1423
+ * True quando è visibile il pulsante chiudi stream (`.close-stream-button`, `isStreamAudioActive`).
1424
+ * Solo in quel caso il bottom del foglio include `--chat-footer-stream-button-height`.
1425
+ */
1426
+ closeStreamButtonActiveForSheetBottom(): boolean {
1427
+ return !!(this.g?.showAudioStreamFooterButton && this.isStreamAudioActive);
1428
+ }
1388
1429
 
1389
1430
  openInputFiles() {
1390
1431
  alert('ok');
@@ -22,7 +22,7 @@
22
22
  <div *ngFor="let message of messages; let first = first; let last = last; let i = index" tabindex="1521" class="rowMsg">
23
23
 
24
24
  <!-- message SENDER:: -->
25
- <div role="messaggio" *ngIf="messageType(MESSAGE_TYPE_MINE, message)" class="msg_container base_sent">
25
+ <div role="messaggio" *ngIf="messageType(MESSAGE_TYPE_MINE, message) && (!isStreamAudioActive && !message.isJustRecived)" class="msg_container base_sent">
26
26
 
27
27
  <!--backgroundColor non viene ancora usato -->
28
28
  <!-- class="messages msg_sent slide-in-right" -->
@@ -49,7 +49,7 @@
49
49
  <!-- message RECIPIENT:: -->
50
50
  <div role="messaggio" *ngIf="messageType(MESSAGE_TYPE_OTHERS, message)" class="msg_container base_receive">
51
51
 
52
- <chat-avatar-image *ngIf="!isSameSender(message?.sender, i)"
52
+ <chat-avatar-image *ngIf="!isSameSender(message?.sender, i) && !isStreamAudioActive"
53
53
  [ngClass]="{'slide-in-left': false}"
54
54
  [senderID]="message?.sender"
55
55
  [senderFullname]="message?.sender_fullname"
@@ -62,7 +62,8 @@
62
62
  [ngClass]="{'slide-in-left': false}"
63
63
  [class.no-background]="(isImage(message) || isFrame(message) || isCarousel(message)) && ((message?.text && message?.text.trim() === '') || !message?.text)"
64
64
  [class.emoticon]="isEmojii(message?.text)"
65
- [style.margin-left]="isSameSender(message?.sender, i)? 'calc(var(--avatar-width) + 10px)': null"
65
+ [class.fullSizeMessage]="isStreamAudioActive"
66
+ [style.margin-left]="isSameSender(message?.sender, i) ? 'calc(var(--avatar-width) + 10px)' : null"
66
67
  [ngStyle]="{'background': stylesMap.get('bubbleReceivedBackground'), 'color': stylesMap.get('bubbleReceivedTextColor'), 'width':isFrame(message) ?'100%' : null}"
67
68
  [isSameSender]="isSameSender(message?.sender, i)"
68
69
  [message]="message"
@@ -134,18 +135,21 @@
134
135
  [senderFullname]="nameUserTypingNow"
135
136
  [baseLocation]="baseLocation">
136
137
  </chat-avatar-image>
138
+
137
139
  <user-typing
138
- [ngClass]="{'userTypingNowExist': !idUserTypingNow}"
139
140
  [color]="stylesMap?.get('iconColor')"
141
+ [ngClass]="{'userTypingNowExist': !idUserTypingNow}"
140
142
  [translationMap]="translationMap"
141
143
  [idUserTypingNow]="idUserTypingNow"
142
144
  [nameUserTypingNow]="nameUserTypingNow">
143
145
  </user-typing>
144
146
  </div>
145
147
 
146
- <div *ngIf="showThinkingMessage" class="msg_container base_receive thinking_receive">
148
+ <div *ngIf="showThinkingMessage && lastServerSenderKind === 'bot'" class="msg_container base_receive thinking_receive">
147
149
  <user-typing class="loading thinking-dots"
150
+ [class.fullSize]="isStreamAudioActive"
148
151
  [color]="stylesMap?.get('iconColor')"
152
+ [class.fullSize]="isStreamAudioActive"
149
153
  [translationMap]="translationMap"
150
154
  [idUserTypingNow]="idUserTypingNow"
151
155
  [nameUserTypingNow]="nameUserTypingNow">
@@ -27,6 +27,10 @@
27
27
  margin: 25px 50px
28
28
  }
29
29
 
30
+ :host .loading.fullSize ::ng-deep > div.spinner{
31
+ margin: 50px 0px !important;
32
+ }
33
+
30
34
  // ============= CSS c21-body ================= //
31
35
  .c21-body {
32
36
  // -webkit-box-shadow: inset 0 10px 10px -10px rgba(0,0,0,0.4);
@@ -44,7 +48,7 @@
44
48
  top: 0;
45
49
  right: 0;
46
50
  left: 0;
47
- bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height));
51
+ bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height) + var(--chat-footer-close-button-height));
48
52
  overflow: hidden;
49
53
  .time{
50
54
  margin-bottom: 20px;
@@ -236,6 +240,11 @@
236
240
  height: fit-content;
237
241
  width: auto;
238
242
 
243
+ &.fullSizeMessage {
244
+ max-width: 100%;
245
+ margin: auto 0 auto 0 !important;
246
+ }
247
+
239
248
  }
240
249
 
241
250
 
@@ -270,6 +279,15 @@
270
279
  }// end c21-body-container
271
280
  }// end c21-body
272
281
 
282
+ /* Solo con pulsante chiudi stream (stream in ascolto): altezza extra come #streamAudioAlert */
283
+ :host-context(#chat21-conversation-component.chat21-conversation--close-stream-active) .c21-body .c21-body-container .c21-body-content .chat21-sheet-content {
284
+ bottom: calc(
285
+ var(--chat-footer-logo-height) +
286
+ var(--chat-footer-height) +
287
+ var(--chat-footer-stream-button-height)
288
+ );
289
+ }
290
+
273
291
  @keyframes thinking-dot {
274
292
  0%, 80%, 100% {
275
293
  opacity: 0.2;
@@ -24,7 +24,9 @@ export class ConversationContentComponent implements OnInit {
24
24
  @Input() nameUserTypingNow: string;
25
25
  @Input() typingLocation: string;
26
26
  @Input() showThinkingMessage: boolean;
27
+ @Input() lastServerSenderKind: 'bot' | 'human' | null;
27
28
  @Input() fullscreenMode: boolean;
29
+ @Input() isStreamAudioActive: boolean;
28
30
  @Input() translationMap: Map< string, string>;
29
31
  @Input() stylesMap: Map<string, string>;
30
32
  @Output() onBeforeMessageRender = new EventEmitter();
@@ -12,97 +12,145 @@
12
12
  <div tabindex="-1" class="alertText">{{translationMap.get('EMOJI_NOT_ELLOWED')}}</div>
13
13
  </div>
14
14
 
15
+ <!-- STREAM AUDIO: cerchio con onde animate -->
16
+ <!-- <div id="streamAudioAlert" *ngIf="!hideTextAreaContent && isStreamAudioActive" class="fade-in-bottom stream-audio-alert" [class.hideTextReply]="hideTextReply" role="status" [attr.aria-label]="translationMap?.get('STREAM_AUDIO_LISTENING') || 'Stream audio attivo'">
17
+ <chat-stream-audio-spectrum
18
+ [volume]="currentVolume"
19
+ [accentColor]="isBotSpeaking ? '#b0b0b0' : stylesMap?.get('themeColor')">
20
+ </chat-stream-audio-spectrum>
21
+ </div> -->
22
+
15
23
  </div>
16
24
 
17
- <!-- TEXTAREA + ICONS: conv active-->
18
- <div class="textarea-container" *ngIf="!hideTextAreaContent && !hideTextReply">
19
-
20
- <div *ngIf="!isStopRec" class="icons-container">
21
- <!-- ICON ATTACHMENT -->
22
- <label *ngIf="showAttachmentFooterButton" tabindex="1502" aria-label="allegati" for="chat21-file" class="chat21-textarea-button" [class.active]="!isFilePendingToUpload && !hideTextReply" id="chat21-start-upload-doc">
23
- <span class="v-align-center">
24
- <svg role="img" aria-labelledby="altIconTitle" xmlns="http://www.w3.org/2000/svg" width="24px" height="24" viewBox="0 0 24 24" fill="currentColor">
25
- <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"/>
26
- <title id="altIconTitle">{{ 'MAX_ATTACHMENT' | translate: { FILE_SIZE_LIMIT: file_size_limit } }}</title>
27
- </svg>
28
-
29
- </span>
30
- <input
31
- [attr.disabled] = "(isFilePendingToUpload || isConversationArchived || hideTextReply)? true : null"
32
- tabindex="1503"
33
- type="file"
34
- aria-label="seleziona allegato"
35
- [accept]="fileUploadAccept"
36
- name="chat21-file"
37
- id="chat21-file"
38
- #chat21_file
39
- class="inputfile"
40
- [ngStyle]="{'display': 'block', height:'1px', width:'1px', overflow: 'hidden' }"
41
- (change)="detectFiles($event)"/>
42
- </label>
43
- <!-- ICON EMOJII -->
44
- <label *ngIf="showEmojiFooterButton" tabindex="1504" aria-label="emojii" for="chat21-emojii" class="chat21-textarea-button" [class.active]="!isFilePendingToUpload && !hideTextReply" id="chat21-emoticon-picker" (click)="onEmojiiPickerClicked()">
25
+ <div class="textarea-container-wrapper" *ngIf="!hideTextAreaContent && !hideTextReply">
26
+ <!-- TEXTAREA + ICONS: conv active-->
27
+ <div class="textarea-container">
28
+
29
+ <div *ngIf="!isStopRec" class="icons-container">
30
+ <!-- ICON ATTACHMENT -->
31
+ <label *ngIf="showAttachmentFooterButton" tabindex="1502" aria-label="allegati" for="chat21-file" class="chat21-textarea-button" [class.active]="!isFilePendingToUpload && !hideTextReply" id="chat21-start-upload-doc">
32
+ <span class="v-align-center">
33
+ <svg role="img" aria-labelledby="altIconTitle" xmlns="http://www.w3.org/2000/svg" width="24px" height="24" viewBox="0 0 24 24" fill="currentColor">
34
+ <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"/>
35
+ <title id="altIconTitle">{{ 'MAX_ATTACHMENT' | translate: { FILE_SIZE_LIMIT: file_size_limit } }}</title>
36
+ </svg>
37
+
38
+ </span>
39
+ <input
40
+ [attr.disabled] = "(isFilePendingToUpload || isConversationArchived || hideTextReply)? true : null"
41
+ tabindex="1503"
42
+ type="file"
43
+ aria-label="seleziona allegato"
44
+ [accept]="fileUploadAccept"
45
+ name="chat21-file"
46
+ id="chat21-file"
47
+ #chat21_file
48
+ class="inputfile"
49
+ [ngStyle]="{'display': 'block', height:'1px', width:'1px', overflow: 'hidden' }"
50
+ (change)="detectFiles($event)"/>
51
+ </label>
52
+ <!-- ICON EMOJII -->
53
+ <label *ngIf="showEmojiFooterButton" tabindex="1504" aria-label="emojii" for="chat21-emojii" class="chat21-textarea-button" [class.active]="!isFilePendingToUpload && !hideTextReply" id="chat21-emoticon-picker" (click)="onEmojiiPickerClicked()">
54
+ <span class="v-align-center">
55
+ <svg role="img" aria-labelledby="altIconTitle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
56
+ <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"/>
57
+ <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"/>
58
+ <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"/>
59
+ <path d="M15.5,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"/>
60
+ <title id="altIconTitle">{{ translationMap?.get('EMOJI') }}</title>
61
+
62
+ <!-- <path d="M0,0H20.57V20.57H0V0Z" fill="none"/>
63
+ <circle cx="15.02" cy="9.86" r="1.29"/>
64
+ <circle cx="9.02" cy="9.86" r="1.29"/>
65
+ <path d="M12.02,15.43c-1.27,0-2.36-.69-2.96-1.71h-1.43c.69,1.76,2.39,3,4.39,3s3.7-1.24,4.39-3h-1.43c-.6,1.02-1.69,1.71-2.96,1.71Zm0-12C7.28,3.43,3.45,7.27,3.45,12s3.83,8.57,8.56,8.57,8.58-3.84,8.58-8.57S16.75,3.43,12.01,3.43Zm0,15.43c-3.79,0-6.86-3.07-6.86-6.86s3.07-6.86,6.86-6.86,6.86,3.07,6.86,6.86-3.07,6.86-6.86,6.86Z"/> -->
66
+ </svg>
67
+ </span>
68
+ </label>
69
+ </div>
70
+
71
+
72
+
73
+
74
+ <div *ngIf="!isStopRec" class="visible-text-area" [class.stream-active]="isStreamAudioActive" [class.hasError]="showAlertEmoji" [class.disabled] = "( isConversationArchived || hideTextReply)? true : null">
75
+ <!-- isFilePendingToUpload || -->
76
+ <textarea
77
+ [attr.disabled] = "(hideTextReply)? true : null"
78
+ [attr.placeholder] ="(footerMessagePlaceholder)? footerMessagePlaceholder : translationMap?.get('LABEL_PLACEHOLDER')"
79
+ start-focus-chat21-conversation-component
80
+ inputTextArea
81
+ #textbox
82
+ tabindex="1501"
83
+ aria-labelledby="altTextArea"
84
+ rows="1"
85
+ id="chat21-main-message-context"
86
+ class='f21textarea c21-button-clean'
87
+ [(ngModel)]="textInputTextArea"
88
+ (ngModelChange)="onTextAreaChange()"
89
+ (keypress)="onkeypress($event)"
90
+ (keydown)="onkeydown($event)"
91
+ (paste)="onPaste($event)">
92
+ </textarea>
93
+
94
+ </div>
95
+
96
+ <!-- ICON SEND -->
97
+ <div *ngIf="(textInputTextArea !== '' && !isStopRec) || !showAudioRecorderFooterButton" tabindex="-1" class="chat21-textarea-button" [class.disabled]="showAlertEmoji" [class.active]="textInputTextArea && !hideTextReply" id="chat21-button-send" (click)="onSendPressed($event)">
45
98
  <span class="v-align-center">
46
- <svg role="img" aria-labelledby="altIconTitle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
47
- <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"/>
48
- <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"/>
49
- <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"/>
50
- <path d="M15.5,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"/>
51
- <title id="altIconTitle">{{ translationMap?.get('EMOJI') }}</title>
52
-
53
- <!-- <path d="M0,0H20.57V20.57H0V0Z" fill="none"/>
54
- <circle cx="15.02" cy="9.86" r="1.29"/>
55
- <circle cx="9.02" cy="9.86" r="1.29"/>
56
- <path d="M12.02,15.43c-1.27,0-2.36-.69-2.96-1.71h-1.43c.69,1.76,2.39,3,4.39,3s3.7-1.24,4.39-3h-1.43c-.6,1.02-1.69,1.71-2.96,1.71Zm0-12C7.28,3.43,3.45,7.27,3.45,12s3.83,8.57,8.56,8.57,8.58-3.84,8.58-8.57S16.75,3.43,12.01,3.43Zm0,15.43c-3.79,0-6.86-3.07-6.86-6.86s3.07-6.86,6.86-6.86,6.86,3.07,6.86,6.86-3.07,6.86-6.86,6.86Z"/> -->
99
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="24" width="24" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve" fill="currentColor">
100
+ <path d="M1.8,20.6V3.4l20.2,8.6L1.8,20.6ZM3.9,17.3l12.6-5.4L3.9,6.6v3.7l6.4,1.6-6.4,1.6v3.8ZM3.9,17.3V6.6v10.7Z"/>
57
101
  </svg>
58
102
  </span>
59
- </label>
60
- </div>
61
-
62
-
103
+ </div>
63
104
 
105
+ <!-- ICON REC -->
106
+ <div *ngIf="showAudioRecorderFooterButton && !textInputTextArea && !isStreamAudioActive" tabindex="-1" class="chat21-audio-button" [class.active]="isStopRec" id="chat21-button-rec">
107
+ <chat-audio-recorder
108
+ (startRecordingEvent)="onStartRecording()"
109
+ (deleteRecordingEvent)="onDeleteRecording()"
110
+ (endRecordingEvent)="onEndRecording($event)"
111
+ (sendRecordingEvent)="onSendRecording($event)"
112
+ [stylesMap]="stylesMap">
113
+ </chat-audio-recorder>
114
+ </div>
64
115
 
65
- <div *ngIf="!isStopRec" class="visible-text-area" [class.hasError]="showAlertEmoji" [class.disabled] = "( isConversationArchived || hideTextReply)? true : null">
66
- <!-- isFilePendingToUpload || -->
67
- <textarea
68
- [attr.disabled] = "(hideTextReply)? true : null"
69
- [attr.placeholder] ="(footerMessagePlaceholder)? footerMessagePlaceholder : translationMap?.get('LABEL_PLACEHOLDER')"
70
- start-focus-chat21-conversation-component
71
- inputTextArea
72
- #textbox
73
- tabindex="1501"
74
- aria-labelledby="altTextArea"
75
- rows="1"
76
- id="chat21-main-message-context"
77
- class='f21textarea c21-button-clean'
78
- [(ngModel)]="textInputTextArea"
79
- (ngModelChange)="onTextAreaChange()"
80
- (keypress)="onkeypress($event)"
81
- (keydown)="onkeydown($event)"
82
- (paste)="onPaste($event)">
83
- </textarea>
84
-
116
+ <!-- ICON STREAM / CHIUDI STREAM (cerchio, icone bianche su iconColor) -->
117
+ <div *ngIf="showAudioStreamFooterButton" tabindex="-1" id="chat21-button-stream"
118
+ class="chat21-textarea-button chat21-stream-button"
119
+ [class.active]="isStreamAudioActive || (!textInputTextArea && !hideTextReply)"
120
+ [class.chat21-stream-button--expanded]="isStreamAudioActive"
121
+ (click)="onStreamPressed($event)" [attr.aria-label]="isStreamAudioActive ? (translationMap?.get('CLOSE') || 'Chiudi stream') : (translationMap?.get('STREAM_AUDIO') || 'Stream audio')"
122
+ [ngStyle]="{ 'background-color': stylesMap?.get('iconColor') || stylesMap?.get('themeColor') }">
123
+ <chat-stream-audio-spectrum
124
+ mode="button"
125
+ [active]="isStreamAudioActive"
126
+ [volume]="currentVolume"
127
+ [translationMap]="translationMap">
128
+ </chat-stream-audio-spectrum>
129
+ </div>
85
130
  </div>
86
131
 
87
- <!-- ICON SEND -->
88
- <div *ngIf="(textInputTextArea !== '' && !isStopRec) || !showAudioRecorderFooterButton" tabindex="-1" class="chat21-textarea-button" [class.disabled]="showAlertEmoji" [class.active]="textInputTextArea && !hideTextReply" id="chat21-button-send" (click)="onSendPressed($event)">
89
- <span class="v-align-center">
90
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="24" width="24" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve" fill="currentColor">
91
- <path d="M1.8,20.6V3.4l20.2,8.6L1.8,20.6ZM3.9,17.3l12.6-5.4L3.9,6.6v3.7l6.4,1.6-6.4,1.6v3.8ZM3.9,17.3V6.6v10.7Z"/>
92
- </svg>
93
- </span>
94
- </div>
95
132
 
96
- <!-- ICON REC -->
97
- <div *ngIf="showAudioRecorderFooterButton && !textInputTextArea" tabindex="-1" class="chat21-audio-button" [class.active]="isStopRec" id="chat21-button-rec">
98
- <chat-audio-recorder
99
- (startRecordingEvent)="onStartRecording()"
100
- (deleteRecordingEvent)="onDeleteRecording()"
101
- (endRecordingEvent)="onEndRecording($event)"
102
- (sendRecordingEvent)="onSendRecording($event)"
103
- [stylesMap]="stylesMap">
104
- </chat-audio-recorder>
133
+ <div class="close-chat-container" *ngIf="closeChatInConversation">
134
+ <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')}">
135
+ <span class="v-align-center">
136
+ <!-- <svg [ngStyle]="{'fill': 'yellow' }" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
137
+ <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" [ngStyle]="{'fill': stylesMap?.get('foregroundColor')}"/>
138
+ </svg> -->
139
+ <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"
140
+ width="15px" height="15px" viewBox="0 0 512 512">
141
+ <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>
142
+ <rect x="48" y="64" width="416" height="80" rx="28" ry="28" stroke-linejoin="round" stroke-width="50px" fill="none" ></rect>
143
+ <path stroke-linecap="round" stroke-linejoin="round" d="M320 304l-64 64-64-64M256 345.89V224" stroke-width="50px" fill="none"></path>
144
+ <title id="altIconTitle">{{ translationMap?.get('CLOSE_CHAT') }}</title>
145
+ </svg>
146
+ </span>
147
+ <span class="v-align-center c21-label-button">
148
+ {{translationMap?.get('CLOSE_CHAT')}}
149
+ </span>
150
+ <div class="clear"></div>
151
+ </button>
105
152
  </div>
153
+
106
154
  </div>
107
155
 
108
156
 
@@ -1,23 +1,26 @@
1
-
1
+ .textarea-container-wrapper{
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 8px;
5
+ }
2
6
  .textarea-container{
3
- // padding: 8px 34px;
4
- // padding-left: 70px;
5
- // padding-right: 45px;
6
7
  display: flex;
7
- // width: 100%;
8
8
  align-items: center;
9
9
  justify-content: space-between;
10
10
  gap: 8px;
11
+ }
11
12
 
12
- //if attachment icon OR emoji icon is not in DOM -> increment textarea width
13
- &:has(:not(#chat21-start-upload-doc), :not(#chat21-emoticon-picker)) .visible-text-area {
14
- width: 80%;
15
- }
16
- //if attachment icon AND emoji icon is not in DOM -> increment textarea width
17
- &:has(:not(#chat21-start-upload-doc)):has(:not(#chat21-emoticon-picker)) .visible-text-area {
18
- width: 90%;
19
- }
13
+ .close-chat-container{
14
+ display: flex;
15
+ flex-direction: column;
16
+ align-items: center;
17
+ justify-content: center;
18
+ gap: 8px;
20
19
 
20
+ .c21-close{
21
+ height: 30px !important;
22
+ margin: 0px !important;
23
+ }
21
24
  }
22
25
 
23
26
  .icons-container{
@@ -40,6 +43,10 @@
40
43
  &.hasError{
41
44
  box-shadow: 0 0 0 1px var(--chat-footer-border-color-error) inset;
42
45
  }
46
+
47
+ &.stream-active {
48
+ width: 50%;
49
+ }
43
50
  }
44
51
 
45
52
  .chat21-textarea-button {
@@ -82,6 +89,50 @@
82
89
  border-radius: 50%;
83
90
  }
84
91
 
92
+ /** Stream audio: cerchio pieno, glyph bianco su sfondo iconColor (stylesMap) */
93
+ .chat21-stream-button.chat21-textarea-button {
94
+ width: 36px;
95
+ height: 36px;
96
+ min-width: 36px;
97
+ border-radius: 50%;
98
+ box-sizing: border-box;
99
+ flex-shrink: 0;
100
+ color: #ffffff;
101
+ transition:
102
+ width 180ms ease,
103
+ border-radius 180ms ease,
104
+ padding 180ms ease;
105
+
106
+ .chat21-stream-button__icon svg {
107
+ width: 20px;
108
+ height: 20px;
109
+ path {
110
+ fill: #ffffff;
111
+ }
112
+ }
113
+
114
+ &.chat21-textarea-button span svg:hover {
115
+ background: rgba(255, 255, 255, 0.2) !important;
116
+ border-radius: 50%;
117
+ }
118
+
119
+ &.chat21-stream-button--expanded {
120
+ width: auto;
121
+ // min-width: 150px;
122
+ max-width: 100%;
123
+ border-radius: 999px;
124
+ padding: 0 14px;
125
+ gap: 12px;
126
+ justify-content: center;
127
+
128
+ // stop the circle-hover background from looking odd on pill
129
+ &.chat21-textarea-button span svg:hover {
130
+ background: transparent !important;
131
+ border-radius: 0;
132
+ }
133
+ }
134
+ }
135
+
85
136
  textarea,
86
137
  textarea:visited,
87
138
  textarea:focus,
@@ -364,6 +415,34 @@ textarea:active{
364
415
  }
365
416
  }
366
417
 
418
+ #streamAudioAlert {
419
+ bottom: 100%;
420
+ width: 100%;
421
+ min-height: var(--chat-footer-stream-button-height);
422
+ display: flex;
423
+ align-items: center;
424
+ justify-content: center;
425
+ position: absolute;
426
+ padding: var(--chat-footer-stream-button-padding);
427
+ /* Satinato / vetro: più trasparenza, blur più marcato */
428
+ background-color: color-mix(in srgb, var(--content-background-color) 34%, transparent);
429
+ backdrop-filter: blur(20px) saturate(1.2);
430
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
431
+ box-shadow:
432
+ inset 0 1px 0 rgba(255, 255, 255, 0.28),
433
+ 0 1px 0 rgba(0, 0, 0, 0.05);
434
+
435
+ &.hideTextReply {
436
+ position: unset;
437
+ min-height: auto;
438
+ padding: 16px 0;
439
+ box-shadow: none;
440
+ backdrop-filter: none;
441
+ -webkit-backdrop-filter: none;
442
+ background-color: var(--content-background-color);
443
+ }
444
+ }
445
+
367
446
  #textAlert{
368
447
  bottom: 100%;
369
448
  width: 100%;
@@ -419,3 +498,28 @@ textarea:active{
419
498
  border: none;
420
499
  // margin: -2px -2px 0px;
421
500
  }
501
+
502
+
503
+ // aggiungi un'animazione di fade in e fade out quando .star-rating-widget è visibile con transition
504
+ .star-rating-widget {
505
+ transition: all 0.5s ease-in-out;
506
+ }
507
+
508
+ .star-rating-widget {
509
+ position: absolute;
510
+ left: 0;
511
+ right: 0;
512
+ bottom: -52px;
513
+ height: 100%;
514
+ width: 100%;
515
+ flex-direction: row;
516
+ justify-content: center;
517
+ background-color: rgb(255, 255, 255);
518
+ flex-wrap: nowrap;
519
+ &.active {
520
+ bottom: 0px;
521
+ }
522
+ &.inactive {
523
+ bottom: -52px;
524
+ }
525
+ }