@chat21/chat21-web-widget 5.1.26 → 5.1.27-rc1

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 (39) 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 +88 -13
  4. package/Dockerfile +4 -5
  5. package/angular.json +2 -1
  6. package/deploy_amazon_beta.sh +17 -7
  7. package/docs/changelog/this-branch.md +36 -0
  8. package/package.json +1 -1
  9. package/src/app/app.component.html +9 -2
  10. package/src/app/app.component.scss +59 -0
  11. package/src/app/app.component.ts +128 -33
  12. package/src/app/component/conversation-detail/conversation/conversation.component.html +13 -2
  13. package/src/app/component/conversation-detail/conversation/conversation.component.scss +30 -2
  14. package/src/app/component/conversation-detail/conversation/conversation.component.ts +190 -5
  15. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +16 -3
  16. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +12 -9
  17. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +15 -1
  18. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +1 -1
  19. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +103 -80
  20. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +40 -13
  21. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +40 -1
  22. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +4 -4
  23. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.ts +1 -0
  24. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.ts +0 -18
  25. package/src/app/component/home/home.component.html +3 -3
  26. package/src/app/component/launcher-button/launcher-button.component.html +1 -1
  27. package/src/app/component/launcher-button/launcher-button.component.ts +3 -2
  28. package/src/app/providers/global-settings.service.ts +38 -0
  29. package/src/app/providers/translator.service.ts +2 -0
  30. package/src/app/sass/_variables.scss +1 -0
  31. package/src/app/utils/globals.ts +8 -2
  32. package/src/assets/i18n/en.json +2 -0
  33. package/src/assets/i18n/es.json +2 -0
  34. package/src/assets/i18n/fr.json +2 -0
  35. package/src/assets/i18n/it.json +2 -0
  36. package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
  37. package/src/chat21-core/utils/utils.ts +5 -2
  38. package/src/iframe-style.css +5 -5
  39. package/deploy_amazon_prod.sh +0 -41
@@ -113,6 +113,15 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
113
113
  // availableAgentsStatus = false; // indica quando è impostato lo stato degli agenti nel subscribe
114
114
  messages: Array<MessageModel> = [];
115
115
 
116
+ // Temporary "thinking" state after a client message is sent.
117
+ public showThinkingMessage: boolean = false;
118
+ private waitingServerReply: boolean = false;
119
+
120
+ // Badge "ultimo messaggio ricevuto dal server" (bot/umano)
121
+ public showLastServerSenderBadge: boolean = false;
122
+ public lastServerSenderKind: 'bot' | 'human' | null = null;
123
+ public lastServerSenderBadgeText: string = '';
124
+
116
125
 
117
126
  CLIENT_BROWSER: string = navigator.userAgent;
118
127
 
@@ -235,7 +244,8 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
235
244
  'CONTINUE',
236
245
  'EMOJI_NOT_ELLOWED',
237
246
  'ATTACHMENT',
238
- 'EMOJI'
247
+ 'EMOJI',
248
+ 'CLOSE_CHAT'
239
249
  ];
240
250
 
241
251
  const keysContent = [
@@ -252,6 +262,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
252
262
  'LABEL_TODAY',
253
263
  'LABEL_TOMORROW',
254
264
  'LABEL_LOADING',
265
+ 'LABEL_THINKING',
255
266
  'LABEL_TO',
256
267
  'ARRAY_DAYS',
257
268
  ];
@@ -356,6 +367,130 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
356
367
 
357
368
  }
358
369
 
370
+ private classifyMessageSenderKind(msg: MessageModel | null | undefined): 'bot' | 'human' | 'system' | 'unknown' {
371
+ if (!msg) return 'unknown';
372
+
373
+ const sender = msg.sender;
374
+ const senderFullname = msg.sender_fullname;
375
+ const senderFullnameLower = (senderFullname || '').toString().toLowerCase();
376
+
377
+ // System messages are always from "system".
378
+ if (sender === 'system' || senderFullnameLower === 'system') {
379
+ return 'system';
380
+ }
381
+
382
+ const chatbotId = msg?.attributes?.flowAttributes?.chatbot_id;
383
+ if (chatbotId && sender && String(chatbotId) === String(sender)) {
384
+ return 'bot';
385
+ }
386
+
387
+ // Fallback heuristics (used when chatbot_id is missing)
388
+ if (sender && String(sender).includes('bot_')) {
389
+ return 'bot';
390
+ }
391
+ if (senderFullnameLower.includes('bot')) {
392
+ return 'bot';
393
+ }
394
+
395
+ return 'human';
396
+ }
397
+
398
+ /**
399
+ * Detects explicit handoff-to-human system messages.
400
+ * Example:
401
+ * attributes.subtype = "info"
402
+ * attributes.updateconversation = true
403
+ * attributes.messagelabel.key = "MEMBER_JOINED_GROUP"
404
+ * attributes.messagelabel.parameters.member_id = "<human-agent-id>"
405
+ */
406
+ private isHumanHandoffSystemMessage(msg: MessageModel | null | undefined): boolean {
407
+ if (!msg) return false;
408
+ if (msg.sender !== 'system') return false;
409
+
410
+ const attrs: any = msg.attributes || {};
411
+ const key = attrs?.messagelabel?.key;
412
+ const memberId = attrs?.messagelabel?.parameters?.member_id;
413
+
414
+ if (attrs?.subtype !== 'info') return false;
415
+ if (attrs?.updateconversation !== true) return false;
416
+ if (key !== 'MEMBER_JOINED_GROUP') return false;
417
+ if (!memberId || typeof memberId !== 'string') return false;
418
+
419
+ // Exclude system/bot/self joins.
420
+ if (memberId === 'system') return false;
421
+ if (memberId.startsWith('bot_')) return false;
422
+ if (this.senderId && memberId === this.senderId) return false;
423
+
424
+ return true;
425
+ }
426
+
427
+ /**
428
+ * Finds the last server message (sender != client) and classifies it as bot/human.
429
+ * If the last server message is "system", it scans backward to find the last non-system server message.
430
+ */
431
+ private refreshLastServerSenderBadge() {
432
+ const senderId = this.senderId;
433
+ const msgs = this.messages || [];
434
+
435
+ let found: 'bot' | 'human' | null = null;
436
+ let latestServerMsg: MessageModel | null = null;
437
+
438
+ // messages are kept sorted by the handler, but we still scan from the end for "latest".
439
+ for (let i = msgs.length - 1; i >= 0; i--) {
440
+ const m = msgs[i];
441
+ if (!m) continue;
442
+
443
+ // Skip messages sent by the current client/user.
444
+ if (senderId && m.sender === senderId) continue;
445
+
446
+ if (!latestServerMsg) {
447
+ latestServerMsg = m;
448
+ }
449
+
450
+ const kind = this.classifyMessageSenderKind(m);
451
+ if (kind === 'system') continue;
452
+ if (kind === 'bot' || kind === 'human') {
453
+ found = kind;
454
+ break;
455
+ }
456
+ }
457
+
458
+ // Priority rule requested: if the latest server message is a system handoff message,
459
+ // consider the conversation as "human".
460
+ if (this.isHumanHandoffSystemMessage(latestServerMsg)) {
461
+ found = 'human';
462
+ }
463
+
464
+ this.lastServerSenderKind = found;
465
+ this.showLastServerSenderBadge = found !== null;
466
+ this.lastServerSenderBadgeText = found === 'bot' ? 'Bot' : (found === 'human' ? 'Umano' : '');
467
+ }
468
+
469
+ private startThinkingMessage() {
470
+ this.waitingServerReply = true;
471
+ this.showThinkingMessage = true;
472
+ }
473
+
474
+ private stopThinkingMessageImmediately() {
475
+ if (!this.waitingServerReply) {
476
+ return;
477
+ }
478
+ this.waitingServerReply = false;
479
+ this.showThinkingMessage = false;
480
+ }
481
+
482
+ private shouldShowThinkingForBot(): boolean {
483
+ // Primary source: latest server-side classification already computed.
484
+ if (this.lastServerSenderKind === 'bot') {
485
+ return true;
486
+ }
487
+ // Safe fallback for bot-targeted direct conversations.
488
+ if (this.conversationWith && this.conversationWith.includes('bot_')) {
489
+ return true;
490
+ }
491
+ return false;
492
+ }
493
+
359
494
  /**
360
495
  * do per scontato che this.userId esiste!!!
361
496
  */
@@ -368,6 +503,11 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
368
503
  // this.connectConversation();
369
504
  await this.initConversationHandler();
370
505
 
506
+ // After loading/connecting, compute "ultimo messaggio ricevuto dal server"
507
+ // (excluding messages sent by the client).
508
+ this.refreshLastServerSenderBadge();
509
+ setTimeout(() => this.refreshLastServerSenderBadge(), 300);
510
+
371
511
  this.logger.debug('[CONV-COMP] ------ 4: initializeChatManager ------ ');
372
512
  //this.initializeChatManager();
373
513
 
@@ -470,7 +610,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
470
610
  return this.isConversationArchived;
471
611
  }
472
612
 
473
- //FALLBACK TO TILEDESK
613
+ // //FALLBACK TO TILEDESK
474
614
  const requests_list = await this.tiledeskRequestService.getMyRequests().catch(err => {
475
615
  this.logger.error('[CONV-COMP] getConversationDetail: error getting request from Tiledesk', err);
476
616
  this.isConversationArchived=true
@@ -488,9 +628,9 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
488
628
  return this.isConversationArchived
489
629
  }
490
630
 
491
- this.isConversationArchived = true;
492
- return null;
493
- }
631
+ this.isConversationArchived = false;
632
+ return null;
633
+ }
494
634
 
495
635
  /**
496
636
  * this.g.recipientId:
@@ -787,8 +927,14 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
787
927
  subscribtion = this.conversationHandlerService.messageAdded.pipe(takeUntil(this.unsubscribe$)).subscribe((msg: MessageModel) => {
788
928
  this.logger.debug('[CONV-COMP] ***** DETAIL messageAdded *****', msg);
789
929
  if (msg) {
930
+ if (msg.sender !== this.senderId) {
931
+ this.stopThinkingMessageImmediately();
932
+ }
790
933
 
791
934
  that.newMessageAdded(msg);
935
+ // Update badge based on the latest message received from the server.
936
+ // We rely on `messages` being kept in-sync by the conversation handler.
937
+ that.refreshLastServerSenderBadge();
792
938
  this.checkMessagesLegntForTranscriptDownloadMenuOption();
793
939
  this.resetTimeout();
794
940
 
@@ -840,6 +986,20 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
840
986
  this.subscriptions.push(subscribe);
841
987
  }
842
988
 
989
+ subscribtionKey = 'conversationsAdded';
990
+ subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
991
+ if(!subscribtion){
992
+
993
+ subscribtion = this.chatManager.conversationsHandlerService.conversationChanged.pipe(takeUntil(this.unsubscribe$)).subscribe((conversation) => {
994
+ this.logger.debug('[CONV-COMP] ***** DATAIL conversationsChanged *****', conversation, this.conversationWith, this.isConversationArchived);
995
+ if(conversation && conversation.recipient === this.conversationId){
996
+ this.isConversationArchived = false
997
+ }
998
+ });
999
+ const subscribe = {key: subscribtionKey, value: subscribtion };
1000
+ this.subscriptions.push(subscribe);
1001
+ }
1002
+
843
1003
  subscribtionKey = 'messageWait';
844
1004
  subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
845
1005
  if (!subscribtion) {
@@ -1088,6 +1248,13 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1088
1248
  }
1089
1249
  /** CALLED BY: conv-header component */
1090
1250
  onWidgetSizeChange(mode: any){
1251
+ if (this.g?.isMobile) {
1252
+ this.g.fullscreenMode = true;
1253
+ this.g.size = 'max';
1254
+ this.isMenuShow = false;
1255
+ return;
1256
+ }
1257
+
1091
1258
  const normalize = (val: any): 'min' | 'max' | 'top' => {
1092
1259
  const v = (typeof val === 'string') ? val.toLowerCase().trim() : '';
1093
1260
  return (v === 'min' || v === 'max' || v === 'top') ? (v as any) : 'min';
@@ -1294,6 +1461,16 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1294
1461
  }
1295
1462
  /** CALLED BY: conv-footer component */
1296
1463
  onAfterSendMessageFN(message: MessageModel){
1464
+ // Manage thinking state only for messages sent by the current client.
1465
+ // Do not force-hide here for other message types/events.
1466
+ if (message && message.sender === this.senderId) {
1467
+ if (this.shouldShowThinkingForBot()) {
1468
+ this.startThinkingMessage();
1469
+ } else {
1470
+ this.showThinkingMessage = false;
1471
+ this.waitingServerReply = false;
1472
+ }
1473
+ }
1297
1474
  this.onAfterSendMessage.emit(message)
1298
1475
  }
1299
1476
  /** CALLED BY: conv-footer component */
@@ -1325,6 +1502,12 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1325
1502
  this.logger.debug('[CONV-COMP] floating onNewConversationButtonClicked')
1326
1503
  this.onNewConversationButtonClicked.emit()
1327
1504
  }
1505
+
1506
+ /** CALLED BY: conv-footer component */
1507
+ onCloseChatButtonClickedFN(event){
1508
+ this.logger.debug('[CONV-COMP] onCloseChatButtonClicked::::', event)
1509
+ this.onCloseChat()
1510
+ }
1328
1511
  // =========== END: event emitter function ====== //
1329
1512
 
1330
1513
 
@@ -1345,6 +1528,8 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1345
1528
  //this.storageService.removeItem('activeConversation');
1346
1529
  this.isConversationArchived = false;
1347
1530
  this.hideTextAreaContent = false;
1531
+ this.showThinkingMessage = false;
1532
+ this.waitingServerReply = false;
1348
1533
  this.conversationFooter.textInputTextArea='';
1349
1534
  this.hideFooterTextReply = false;
1350
1535
  this.footerMessagePlaceholder = '';
@@ -38,19 +38,32 @@ export class ConversationAudioRecorderComponent {
38
38
  this.startTime = Date.now();
39
39
  navigator.mediaDevices.getUserMedia({ audio: true })
40
40
  .then(stream => {
41
+
42
+ this.audioChunks = [];
43
+
41
44
  this.mediaRecorder = new MediaRecorder(stream);
42
45
  this.mediaRecorder.start();
43
46
  this.isRecording = true;
44
47
  this.startRecordingEvent.emit();
45
48
  this.mediaRecorder.addEventListener('dataavailable', (event) => {
46
- this.audioChunks.push(event.data);
49
+ if (event.data && event.data.size > 0) {
50
+ this.audioChunks.push(event.data);
51
+ }
47
52
  });
48
53
 
49
54
  this.mediaRecorder.addEventListener('stop', () => {
50
- this.audioBlob = new Blob(this.audioChunks, { type: 'audio/mpeg' });
55
+ // MIME REALE del recorder
56
+ const mimeType = this.mediaRecorder.mimeType;
57
+ this.audioBlob = new Blob(this.audioChunks, {
58
+ type: mimeType
59
+ });
60
+
61
+ // ⭐ chiude microfono
62
+ stream.getTracks().forEach(track => track.stop());
63
+
64
+ this.audioChunks = [];
51
65
  this.rawAudioUrl = URL.createObjectURL(this.audioBlob);
52
66
  this.audioUrl = this.sanitizer.bypassSecurityTrustUrl(this.rawAudioUrl);
53
- this.audioChunks = [];
54
67
  this.endRecordingEvent.emit(this.audioBlob);
55
68
  });
56
69
  })
@@ -13,7 +13,6 @@
13
13
  [nameUserTypingNow]="nameUserTypingNow">
14
14
  </user-typing>
15
15
  </span>
16
-
17
16
 
18
17
  <div id="chat21-sheet-content" class="chat21-sheet-content">
19
18
  <div class="chat21-conversation-parts-container">
@@ -120,25 +119,21 @@
120
119
 
121
120
  <!-- FILE PENDING UPLOAD -->
122
121
  <!-- -->
123
- <div *ngIf="showUploadProgress" class="msg_container base_sent" >
122
+ <div *ngIf="showUploadProgress && !showThinkingMessage" class="msg_container base_sent" >
124
123
  <div class="chat21-spinner active" id="chat21-spinner" style="margin: 0px 6px 0px;">
125
124
  <div class="chat21-bounce1" [ngStyle]="{'background-color': stylesMap.get('iconColor')}"></div>
126
125
  <div class="chat21-bounce2" [ngStyle]="{'background-color': stylesMap.get('iconColor'), 'opacity': 0.4}"></div>
127
126
  <div class="chat21-bounce3" [ngStyle]="{'background-color': stylesMap.get('iconColor'), 'opacity': 0.6}"></div>
128
127
  </div>
129
-
130
-
131
128
  </div>
132
129
 
133
130
 
134
- <div *ngIf="isTypings && typingLocation==='content' " class="msg_container base_receive">
135
- <!-- !isSameSender(idUserTypingNow, i) -->
131
+ <div *ngIf="isTypings && !showThinkingMessage && typingLocation==='content'" class="msg_container base_receive">
136
132
  <chat-avatar-image *ngIf="idUserTypingNow " class="slide-in-left"
137
133
  [senderID]="idUserTypingNow"
138
134
  [senderFullname]="nameUserTypingNow"
139
135
  [baseLocation]="baseLocation">
140
136
  </chat-avatar-image>
141
-
142
137
  <user-typing
143
138
  [ngClass]="{'userTypingNowExist': !idUserTypingNow}"
144
139
  [color]="stylesMap?.get('iconColor')"
@@ -147,7 +142,16 @@
147
142
  [nameUserTypingNow]="nameUserTypingNow">
148
143
  </user-typing>
149
144
  </div>
150
-
145
+
146
+ <div *ngIf="showThinkingMessage" class="msg_container base_receive thinking_receive">
147
+ <user-typing class="loading thinking-dots"
148
+ [color]="stylesMap?.get('iconColor')"
149
+ [translationMap]="translationMap"
150
+ [idUserTypingNow]="idUserTypingNow"
151
+ [nameUserTypingNow]="nameUserTypingNow">
152
+ </user-typing>
153
+ </div>
154
+
151
155
  </div>
152
156
  </div>
153
157
  </div>
@@ -155,7 +159,6 @@
155
159
  </div>
156
160
  </div>
157
161
  </div>
158
-
159
162
  </div>
160
163
 
161
164
 
@@ -44,7 +44,7 @@
44
44
  top: 0;
45
45
  right: 0;
46
46
  left: 0;
47
- bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height));
47
+ bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height) + var(--chat-footer-close-button-height));
48
48
  overflow: hidden;
49
49
  .time{
50
50
  margin-bottom: 20px;
@@ -122,6 +122,11 @@
122
122
  text-align: center;
123
123
  padding: 0px 0px 6px 0px
124
124
  }
125
+ .thinking_receive {
126
+ .thinking-dots ::ng-deep > div.spinner{
127
+ margin: 25px 30px;
128
+ }
129
+ }
125
130
  /* ====== SET MESSAGES ====== */
126
131
  .messages {
127
132
  border-radius: var(--border-radius-bubble-message);
@@ -265,6 +270,15 @@
265
270
  }// end c21-body-container
266
271
  }// end c21-body
267
272
 
273
+ @keyframes thinking-dot {
274
+ 0%, 80%, 100% {
275
+ opacity: 0.2;
276
+ }
277
+ 40% {
278
+ opacity: 1;
279
+ }
280
+ }
281
+
268
282
  /* LOADING */
269
283
  /*http://tobiasahlin.com/spinkit/*/
270
284
  #chat21-spinner {
@@ -23,6 +23,7 @@ export class ConversationContentComponent implements OnInit {
23
23
  @Input() idUserTypingNow: string;
24
24
  @Input() nameUserTypingNow: string;
25
25
  @Input() typingLocation: string;
26
+ @Input() showThinkingMessage: boolean;
26
27
  @Input() fullscreenMode: boolean;
27
28
  @Input() translationMap: Map< string, string>;
28
29
  @Input() stylesMap: Map<string, string>;
@@ -224,7 +225,6 @@ export class ConversationContentComponent implements OnInit {
224
225
  // return false;
225
226
  }
226
227
 
227
-
228
228
  hideOutsideElements(){
229
229
  this.onMenuOptionShow.emit(false)
230
230
  this.onEmojiiPickerShow.emit(false)
@@ -14,94 +14,117 @@
14
14
 
15
15
  </div>
16
16
 
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()">
45
- <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>
17
+ <div class="textarea-container-wrapper" *ngIf="!hideTextAreaContent && !hideTextReply">
18
+ <!-- TEXTAREA + ICONS: conv active-->
19
+ <div class="textarea-container">
20
+
21
+ <div *ngIf="!isStopRec" class="icons-container">
22
+ <!-- ICON ATTACHMENT -->
23
+ <label *ngIf="showAttachmentFooterButton" tabindex="1502" aria-label="allegati" for="chat21-file" class="chat21-textarea-button" [class.active]="!isFilePendingToUpload && !hideTextReply" id="chat21-start-upload-doc">
24
+ <span class="v-align-center">
25
+ <svg role="img" aria-labelledby="altIconTitle" xmlns="http://www.w3.org/2000/svg" width="24px" height="24" viewBox="0 0 24 24" fill="currentColor">
26
+ <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"/>
27
+ <title id="altIconTitle">{{ 'MAX_ATTACHMENT' | translate: { FILE_SIZE_LIMIT: file_size_limit } }}</title>
28
+ </svg>
29
+
30
+ </span>
31
+ <input
32
+ [attr.disabled] = "(isFilePendingToUpload || isConversationArchived || hideTextReply)? true : null"
33
+ tabindex="1503"
34
+ type="file"
35
+ aria-label="seleziona allegato"
36
+ [accept]="fileUploadAccept"
37
+ name="chat21-file"
38
+ id="chat21-file"
39
+ #chat21_file
40
+ class="inputfile"
41
+ [ngStyle]="{'display': 'block', height:'1px', width:'1px', overflow: 'hidden' }"
42
+ (change)="detectFiles($event)"/>
43
+ </label>
44
+ <!-- ICON EMOJII -->
45
+ <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()">
46
+ <span class="v-align-center">
47
+ <svg role="img" aria-labelledby="altIconTitle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
48
+ <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"/>
49
+ <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"/>
50
+ <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"/>
51
+ <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"/>
52
+ <title id="altIconTitle">{{ translationMap?.get('EMOJI') }}</title>
52
53
 
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"/> -->
57
- </svg>
58
- </span>
59
- </label>
60
- </div>
54
+ <!-- <path d="M0,0H20.57V20.57H0V0Z" fill="none"/>
55
+ <circle cx="15.02" cy="9.86" r="1.29"/>
56
+ <circle cx="9.02" cy="9.86" r="1.29"/>
57
+ <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"/> -->
58
+ </svg>
59
+ </span>
60
+ </label>
61
+ </div>
61
62
 
62
63
 
63
64
 
64
65
 
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
-
85
- </div>
66
+ <div *ngIf="!isStopRec" class="visible-text-area" [class.hasError]="showAlertEmoji" [class.disabled] = "( isConversationArchived || hideTextReply)? true : null">
67
+ <!-- isFilePendingToUpload || -->
68
+ <textarea
69
+ [attr.disabled] = "(hideTextReply)? true : null"
70
+ [attr.placeholder] ="(footerMessagePlaceholder)? footerMessagePlaceholder : translationMap?.get('LABEL_PLACEHOLDER')"
71
+ start-focus-chat21-conversation-component
72
+ inputTextArea
73
+ #textbox
74
+ tabindex="1501"
75
+ aria-labelledby="altTextArea"
76
+ rows="1"
77
+ id="chat21-main-message-context"
78
+ class='f21textarea c21-button-clean'
79
+ [(ngModel)]="textInputTextArea"
80
+ (ngModelChange)="onTextAreaChange()"
81
+ (keypress)="onkeypress($event)"
82
+ (keydown)="onkeydown($event)"
83
+ (paste)="onPaste($event)">
84
+ </textarea>
85
+
86
+ </div>
86
87
 
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>
88
+ <!-- ICON SEND -->
89
+ <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)">
90
+ <span class="v-align-center">
91
+ <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">
92
+ <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"/>
93
+ </svg>
94
+ </span>
95
+ </div>
96
+
97
+ <!-- ICON REC -->
98
+ <div *ngIf="showAudioRecorderFooterButton && !textInputTextArea" tabindex="-1" class="chat21-audio-button" [class.active]="isStopRec" id="chat21-button-rec">
99
+ <chat-audio-recorder
100
+ (startRecordingEvent)="onStartRecording()"
101
+ (deleteRecordingEvent)="onDeleteRecording()"
102
+ (endRecordingEvent)="onEndRecording($event)"
103
+ (sendRecordingEvent)="onSendRecording($event)"
104
+ [stylesMap]="stylesMap">
105
+ </chat-audio-recorder>
106
+ </div>
94
107
  </div>
95
108
 
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>
109
+ <div class="close-chat-container" *ngIf="closeChatInConversation">
110
+ <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')}">
111
+ <span class="v-align-center">
112
+ <!-- <svg [ngStyle]="{'fill': 'yellow' }" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
113
+ <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" [ngStyle]="{'fill': stylesMap?.get('foregroundColor')}"/>
114
+ </svg> -->
115
+ <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"
116
+ width="15px" height="15px" viewBox="0 0 512 512">
117
+ <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>
118
+ <rect x="48" y="64" width="416" height="80" rx="28" ry="28" stroke-linejoin="round" stroke-width="50px" fill="none" ></rect>
119
+ <path stroke-linecap="round" stroke-linejoin="round" d="M320 304l-64 64-64-64M256 345.89V224" stroke-width="50px" fill="none"></path>
120
+ <title id="altIconTitle">{{ translationMap?.get('CLOSE_CHAT') }}</title>
121
+ </svg>
122
+ </span>
123
+ <span class="v-align-center c21-label-button">
124
+ {{translationMap?.get('CLOSE_CHAT')}}
125
+ </span>
126
+ <div class="clear"></div>
127
+ </button>
105
128
  </div>
106
129
  </div>
107
130