@chat21/chat21-web-widget 5.1.26 → 5.1.30-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 (42) 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 +97 -13
  4. package/Dockerfile +4 -5
  5. package/angular.json +2 -1
  6. package/deploy_amazon_beta.sh +17 -7
  7. package/docs/changelog/badge_Bot_Umano.md +85 -0
  8. package/docs/changelog/this-branch.md +36 -0
  9. package/package.json +1 -1
  10. package/src/app/app.component.html +9 -2
  11. package/src/app/app.component.scss +59 -0
  12. package/src/app/app.component.ts +144 -36
  13. package/src/app/component/conversation-detail/conversation/conversation.component.html +13 -2
  14. package/src/app/component/conversation-detail/conversation/conversation.component.scss +30 -2
  15. package/src/app/component/conversation-detail/conversation/conversation.component.ts +85 -5
  16. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +16 -3
  17. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +12 -9
  18. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +15 -1
  19. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +1 -1
  20. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +103 -80
  21. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +40 -13
  22. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +40 -1
  23. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +3 -3
  24. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.ts +1 -0
  25. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.ts +0 -18
  26. package/src/app/component/home/home.component.html +3 -3
  27. package/src/app/component/last-message/last-message.component.ts +4 -1
  28. package/src/app/component/launcher-button/launcher-button.component.html +1 -1
  29. package/src/app/component/launcher-button/launcher-button.component.ts +3 -2
  30. package/src/app/providers/global-settings.service.ts +26 -0
  31. package/src/app/providers/translator.service.ts +2 -0
  32. package/src/app/sass/_variables.scss +1 -0
  33. package/src/app/utils/conversation-sender-classifier.ts +116 -0
  34. package/src/app/utils/globals.ts +33 -5
  35. package/src/assets/i18n/en.json +2 -0
  36. package/src/assets/i18n/es.json +2 -0
  37. package/src/assets/i18n/fr.json +2 -0
  38. package/src/assets/i18n/it.json +2 -0
  39. package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
  40. package/src/chat21-core/utils/utils.ts +5 -2
  41. package/src/iframe-style.css +5 -5
  42. package/deploy_amazon_prod.sh +0 -41
@@ -106,6 +106,11 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
106
106
 
107
107
  forceDisconnect: boolean = false;
108
108
 
109
+ //network status
110
+ isOnline: boolean = true;
111
+ loading: boolean = false;
112
+ private calloutScheduleTimeout: any = null;
113
+
109
114
  // alert error message
110
115
  isShowErrorMessage: boolean = false;
111
116
  errorMessage: string = '';
@@ -149,7 +154,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
149
154
  this.logger.info('[APP-CONF]---------------- ngAfterViewInit: APP.COMPONENT ---------------- ')
150
155
 
151
156
  // Initialize translation map and enable buttons
152
- const keys = ['MAXIMIZE', 'MINIMIZE', 'CENTER', 'BUTTON_CLOSE_TO_ICON'];
157
+ const keys = ['MAXIMIZE', 'MINIMIZE', 'CENTER', 'BUTTON_CLOSE_TO_ICON', 'LABEL_LOADING'];
153
158
  this.translationMap = this.translateService.translateLanguage(keys);
154
159
  this.isButtonsDisabled = false;
155
160
 
@@ -164,13 +169,13 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
164
169
  if (conversation.attributes && conversation.attributes['subtype'] === 'info') {
165
170
  return;
166
171
  }
167
- if (conversation.is_new && this.isInitialized) {
172
+ if (conversation.is_new && that.isInitialized) {
168
173
  that.manageTabNotification(false, 'conv-added')
169
174
  // this.soundMessage();
170
175
  }
171
- if(this.g.isOpen === false){
172
- that.lastConversation = conversation;
176
+ if(this.g.isOpen === false && conversation.sender !== this.g.senderId && !isInfo(conversation)){
173
177
  that.g.isOpenNewMessage = true;
178
+ that.lastConversation = conversation;
174
179
  }
175
180
  } else {
176
181
  //widget closed
@@ -218,6 +223,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
218
223
  that.lastConversation = conversation;
219
224
  that.g.isOpenNewMessage = true;
220
225
  that.logger.debug('[APP-COMP] lastconversationnn', that.lastConversation)
226
+ that.logger.debug('[APP-COMP] lastconversationnn message' + JSON.stringify(that.lastConversation?.attributes?.commands))
221
227
  }
222
228
  let badgeNewConverstionNumber = that.conversationsHandlerService.countIsNew()
223
229
  that.g.setParameter('conversationsBadge', badgeNewConverstionNumber);
@@ -316,8 +322,19 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
316
322
  this.g.setIsOpen(isOpen)
317
323
  this.appStorageService.setItem('isOpen', isOpen)
318
324
  }
319
-
320
-
325
+
326
+ if(this.g.onPageChangeVisibilityDesktop === 'last'){
327
+ this.logger.debug('[APP-COMP2] ------this.g.isOpen: ', this.g.isOpen)
328
+ if(this.g.isOpen){
329
+ this.g.autoStart = true;
330
+ }
331
+ }
332
+
333
+ // STEP-2: schedule callout after settings are loaded,
334
+ // independently from auth/sign-in.
335
+ this.scheduleCalloutFromSettings();
336
+
337
+
321
338
  /**CHECK IF JWT IS IN URL PARAMETERS */
322
339
  this.logger.debug('[APP-COMP] check if token is passed throw url: ', this.g.jwt);
323
340
  if (this.g.jwt) {
@@ -354,10 +371,24 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
354
371
  this.subscriptions.push(obsSettingsService);
355
372
  this.globalSettingsService.initWidgetParamiters(this.g, this.el);
356
373
 
357
- // SET AUDIO
358
- this.audio = new Audio();
359
- this.audio.src = this.g.baseLocation + URL_SOUND_LIST_CONVERSATION;
360
- this.audio.load();
374
+ }
375
+
376
+ private scheduleCalloutFromSettings() {
377
+ if (this.calloutScheduleTimeout) {
378
+ clearTimeout(this.calloutScheduleTimeout);
379
+ this.calloutScheduleTimeout = null;
380
+ }
381
+
382
+ const calloutTimer = Number(this.g.calloutTimer);
383
+ if (isNaN(calloutTimer) || calloutTimer < 0) {
384
+ return;
385
+ }
386
+
387
+ const delayMs = calloutTimer * 1000;
388
+ this.calloutScheduleTimeout = setTimeout(() => {
389
+ this.calloutScheduleTimeout = null;
390
+ this.showCallout();
391
+ }, delayMs);
361
392
  }
362
393
 
363
394
  private initAll() {
@@ -422,6 +453,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
422
453
  }
423
454
 
424
455
  const autoStart = this.g.autoStart;
456
+ const startHidden = this.g.startHidden;
425
457
  that.stateLoggedUser = state;
426
458
  if (state && state === AUTH_STATE_ONLINE) {
427
459
  /** sono loggato */
@@ -448,7 +480,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
448
480
  that.triggerOnAuthStateChanged(that.stateLoggedUser);
449
481
  that.logger.debug('[APP-COMP] 1 - IMPOSTO STATO CONNESSO UTENTE ', autoStart);
450
482
 
451
-
483
+ this.initAudioNotification()
452
484
 
453
485
  new Promise(async (resolve, reject)=> {
454
486
  that.typingService.initialize(this.g.tenant);
@@ -460,7 +492,8 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
460
492
 
461
493
  // this.initConversationsHandler(this.g.tenant, that.g.senderId);
462
494
  /* If singleConversation mode is active wait to showWidget: do it later in initConversationsHandler */
463
- if (autoStart && !that.g.singleConversation) {
495
+ const hasBotsRules = Array.isArray(this.g.botsRules) && this.g.botsRules.length > 0;
496
+ if ((autoStart || hasBotsRules) && !that.g.singleConversation) {
464
497
  that.showWidget();
465
498
  }
466
499
 
@@ -474,26 +507,39 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
474
507
  that.listenToWidgetClick()
475
508
  }
476
509
 
510
+ /** DDP IF AUTOSTART IS FALSE, SHOW WIDGET */
511
+ if(!autoStart){
512
+ that.logger.info('[APP-COMP] AUTOSTART IS FALSE AND LOGGED SUCCESSFULLY ');
513
+ this.g.setParameter('isShown', true, true);
514
+ }
477
515
 
478
516
 
479
517
  } else if (state && state === AUTH_STATE_OFFLINE && !this.forceDisconnect) {
480
518
  /** non sono loggato */
481
519
  that.logger.info('[APP-COMP] OFFLINE - NO CURRENT USER AUTENTICATE: ');
482
520
  that.g.setParameter('isLogged', false);
483
- that.hideWidget();
521
+ // that.hideWidget();
484
522
  // that.g.setParameter('isShown', false, true);
485
523
  that.triggerOnAuthStateChanged(that.stateLoggedUser);
486
- if (autoStart) {
524
+ const shouldAutoAuthenticate = autoStart ||
525
+ this.g.onPageChangeVisibilityDesktop === 'open' ||
526
+ this.g.onPageChangeVisibilityMobile === 'open' ||
527
+ (Array.isArray(this.g.botsRules) && this.g.botsRules.length > 0)
528
+ // || this.g.hasCalloutInWidgetConfig;
529
+ console.log('[APP-COMP] shouldAutoAuthenticate', shouldAutoAuthenticate, startHidden)
530
+ if (shouldAutoAuthenticate) {
487
531
  that.authenticate();
532
+ if(startHidden){ that.hideWidget(); }
533
+ } else {
534
+ that.logger.debug('[APP-COMP] Skip auto-auth: startup conditions not met, show launcher only');
488
535
  }
489
- }else if(state && state === AUTH_STATE_CLOSE ){
536
+ } else if(state && state === AUTH_STATE_CLOSE ){
490
537
  that.logger.info('[APP-COMP] CLOSE - CHANNEL CLOSED: ', this.chatManager);
491
538
  if(this.g.recipientId){
492
539
  this.chatManager.removeConversationHandler(this.g.recipientId)
493
540
  this.g.recipientId = null;
494
541
  }
495
- }
496
-
542
+ }
497
543
 
498
544
  });
499
545
  this.subscriptions.push(subAuthStateChanged);
@@ -738,6 +784,8 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
738
784
  // divWidgetContainer.style.display = 'block';
739
785
  // }
740
786
  // }, 500);
787
+
788
+ this.loading = false;
741
789
  }
742
790
  // ========= end:: START UI ============//
743
791
 
@@ -846,7 +894,13 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
846
894
  this.appStorageService.setItem('attributes', JSON.stringify(attributes));
847
895
  return attributes;
848
896
  }
849
-
897
+
898
+ // SET AUDIO
899
+ private initAudioNotification(){
900
+ this.audio = new Audio();
901
+ this.audio.src = this.g.baseLocation + URL_SOUND_LIST_CONVERSATION;
902
+ this.audio.load();
903
+ }
850
904
 
851
905
  private async initConversationsHandler(tenant: string, senderId: string) {
852
906
  this.logger.debug('[APP-COMP] initialize: ListConversationsComponent');
@@ -1170,14 +1224,32 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
1170
1224
  return this.signOut();
1171
1225
  }
1172
1226
 
1227
+ private canShowCalloutNow(): boolean {
1228
+ if (this.g.isOpen !== false) {
1229
+ return false;
1230
+ }
1231
+ if (this.g.isOpenNewMessage) {
1232
+ return false;
1233
+ }
1234
+ if (!this.g.calloutStaus) {
1235
+ return false;
1236
+ }
1237
+ if (!this.g.hasCalloutInWidgetConfig) {
1238
+ return false;
1239
+ }
1240
+ return true;
1241
+ }
1242
+
1173
1243
  /** show callout */
1174
1244
  private showCallout() {
1175
- if (this.g.isOpen === false) {
1176
- // this.g.setParameter('calloutTimer', 1)
1177
- this.eyeeyeCatcherCardComponent.openEyeCatcher();
1178
- this.g.setParameter('displayEyeCatcherCard', 'block');
1179
- this.triggerOnOpenEyeCatcherEvent();
1245
+ if (!this.canShowCalloutNow()) {
1246
+ return;
1247
+ }
1248
+ if (!this.eyeeyeCatcherCardComponent) {
1249
+ return;
1180
1250
  }
1251
+ // Delegate visibility logic to the eye-catcher component.
1252
+ this.eyeeyeCatcherCardComponent.openEyeCatcher();
1181
1253
  }
1182
1254
 
1183
1255
  /** open popup conversation */
@@ -1185,6 +1257,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
1185
1257
  const senderId = this.g.senderId;
1186
1258
  this.logger.debug('[APP-COMP] f21_open senderId: ', senderId);
1187
1259
  if (senderId) {
1260
+ this.enforceMobileFullscreenOnOpen();
1188
1261
  // chiudo callout
1189
1262
  this.g.setParameter('displayEyeCatcherCard', 'none');
1190
1263
  // this.g.isOpen = true; // !this.isOpen;
@@ -1605,23 +1678,47 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
1605
1678
  this.f21_close();
1606
1679
  }
1607
1680
 
1681
+
1682
+ /** DDP reload widget */
1683
+ async reloadWidget() {
1684
+ this.openCloseWidget();
1685
+ this.logger.debug('[APP-COMP-1] AAA - hideWidget');
1686
+ await Promise.all([
1687
+ this.authenticate(),
1688
+ // this.initAll()
1689
+ ]);
1690
+ this.logger.debug('[APP-COMP-1] CCC - showWidget');
1691
+ }
1692
+
1693
+
1608
1694
  /**
1609
1695
  * LAUNCHER BUTTON:
1610
1696
  * onClick button open/close widget
1611
1697
  */
1612
1698
  onOpenCloseWidget($event) {
1699
+ this.logger.debug('[APP-COMP] onOpenCloseWidget', $event, this.g.isLogged);
1700
+ if(!this.g.isLogged){
1701
+ this.reloadWidget();
1702
+ } else {
1703
+ this.openCloseWidget();
1704
+ }
1705
+ }
1706
+
1707
+ /** DDP show widget */
1708
+ openCloseWidget() {
1613
1709
  this.g.setParameter('displayEyeCatcherCard', 'none');
1614
1710
  // const conversationActive: ConversationModel = JSON.parse(this.appStorageService.getItem('activeConversation'));
1615
1711
  const recipientId : string = this.appStorageService.getItem('recipientId')
1616
1712
  this.g.setParameter('recipientId', recipientId);
1617
1713
  this.logger.debug('[APP-COMP] openCloseWidget', recipientId, this.g.isOpen, this.g.startFromHome);
1714
+
1618
1715
  if (this.g.isOpen === false) {
1716
+ this.enforceMobileFullscreenOnOpen();
1619
1717
  if(this.forceDisconnect){
1620
1718
  this.logger.log('[FORCE] onOpenCloseWidget --> reconnect', this.forceDisconnect)
1621
1719
  this.messagingAuthService.createCustomToken(this.g.tiledeskToken)
1622
1720
  this.forceDisconnect = false;
1623
1721
  }
1624
-
1625
1722
  if (!recipientId) {
1626
1723
  if(this.g.singleConversation){
1627
1724
  this.isOpenHome = false;
@@ -1641,29 +1738,22 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
1641
1738
  this.isOpenHome = false;
1642
1739
  this.isOpenConversation = true;
1643
1740
  this.startUI()
1644
- // this.isOpenSelectionDepartment = false;
1645
1741
  }
1646
- // if (!conversationActive && !this.g.startFromHome) {
1647
- // this.isOpenHome = false;
1648
- // this.isOpenConversation = true;
1649
- // this.startNwConversation();
1650
- // } else if (conversationActive) {
1651
- // this.isOpenHome = false;
1652
- // this.isOpenConversation = true;
1653
- // }
1654
- // this.g.startFromHome = true;
1655
1742
  this.triggerOnOpenEvent();
1656
-
1657
1743
  } else {
1658
1744
  this.triggerOnCloseEvent();
1659
1745
  }
1660
1746
  //change status to the widget
1661
1747
  this.g.setIsOpen(!this.g.isOpen);
1662
1748
  this.appStorageService.setItem('isOpen', this.g.isOpen);
1663
-
1749
+ //show loading if widget is open and user is not logged
1750
+ if(this.g.isOpen === true && !this.g.isLogged){
1751
+ this.loading = true;
1752
+ }
1664
1753
  // this.saveBadgeNewConverstionNumber();
1665
1754
  }
1666
1755
 
1756
+
1667
1757
  /**
1668
1758
  * MODAL SELECTION DEPARTMENT:
1669
1759
  * selected department
@@ -2039,6 +2129,12 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
2039
2129
  }
2040
2130
 
2041
2131
  onWidgetSizeChange(mode: any) {
2132
+ if (this.g?.isMobile) {
2133
+ this.g.fullscreenMode = true;
2134
+ this.g.size = 'max';
2135
+ return;
2136
+ }
2137
+
2042
2138
  const normalize = (val: any): 'min' | 'max' | 'top' => {
2043
2139
  const v = (typeof val === 'string') ? val.toLowerCase().trim() : '';
2044
2140
  return (v === 'min' || v === 'max' || v === 'top') ? (v as any) : 'min';
@@ -2084,6 +2180,13 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
2084
2180
  }
2085
2181
  }
2086
2182
 
2183
+ private enforceMobileFullscreenOnOpen() {
2184
+ if (this.g?.isMobile) {
2185
+ this.g.fullscreenMode = true;
2186
+ this.g.size = 'max';
2187
+ }
2188
+ }
2189
+
2087
2190
  /**
2088
2191
  * MODAL RATING WIDGET:
2089
2192
  * close modal page
@@ -2217,6 +2320,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
2217
2320
  this.el.nativeElement.style.setProperty('--chat-header-height', this.g.hideHeaderConversation? '0px': null)
2218
2321
  this.el.nativeElement.style.setProperty('--font-size-bubble-message', this.g.fontSize)
2219
2322
  this.el.nativeElement.style.setProperty('--font-family-bubble-message', this.g.fontFamily)
2323
+ this.el.nativeElement.style.setProperty('--chat-footer-close-button-height', this.g.closeChatInConversation? '30px': '0px')
2220
2324
 
2221
2325
  }
2222
2326
 
@@ -2266,6 +2370,10 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
2266
2370
  /** elimino tutte le sottoscrizioni */
2267
2371
  ngOnDestroy() {
2268
2372
  this.logger.debug('[APP-COMP] this.subscriptions', this.subscriptions);
2373
+ if (this.calloutScheduleTimeout) {
2374
+ clearTimeout(this.calloutScheduleTimeout);
2375
+ this.calloutScheduleTimeout = null;
2376
+ }
2269
2377
  const windowContext = this.g.windowContext;
2270
2378
  if (windowContext && windowContext['tiledesk']) {
2271
2379
  windowContext['tiledesk']['angularcomponent'] = null;
@@ -30,12 +30,21 @@
30
30
  [isTypings]="isTypings"
31
31
  [nameUserTypingNow]="nameUserTypingNow"
32
32
  [typingLocation]="g?.typingLocation"
33
+ [isMobile]="g?.isMobile"
33
34
  (onBack)="onBackHomeFN()"
34
35
  (onCloseWidget)="onCloseWidgetFN()"
35
36
  (onMenuOptionClick)="onMenuOptionClick($event)"
36
37
  (onMenuOptionShow)="onMenuOption($event)">
37
38
  </chat-conversation-header>
38
39
 
40
+ <!-- Badge: natura dell'ultimo messaggio ricevuto dal server -->
41
+ <!-- <div
42
+ *ngIf="showLastServerSenderBadge"
43
+ id="chat21-last-server-sender-badge"
44
+ [ngClass]="lastServerSenderKind">
45
+ {{ lastServerSenderBadgeText }}
46
+ </div> -->
47
+
39
48
  <div id="dropZone_container" *ngIf="isHovering"
40
49
  [class.hideTextReply]="hideFooterTextReply && g?.poweredBy">
41
50
  <div class="drop">
@@ -54,6 +63,7 @@
54
63
  [idUserTypingNow]="idUserTypingNow"
55
64
  [nameUserTypingNow]="nameUserTypingNow"
56
65
  [typingLocation]="g?.typingLocation"
66
+ [showThinkingMessage]="showThinkingMessage"
57
67
  [fullscreenMode]="g?.fullscreenMode"
58
68
  [translationMap]="translationMapContent"
59
69
  [stylesMap]="stylesMap"
@@ -68,7 +78,6 @@
68
78
  (dragleave)="drag($event)" >
69
79
  </chat-conversation-content>
70
80
 
71
-
72
81
 
73
82
  <!-- INTERNAL FRAME FOR SELF ACTION LINK BUTTONS-->
74
83
  <chat-internal-frame *ngIf="isButtonUrl"
@@ -128,6 +137,7 @@
128
137
  [isMobile]="g?.isMobile"
129
138
  [isEmojiiPickerShow]="isEmojiiPickerShow"
130
139
  [footerMessagePlaceholder]="footerMessagePlaceholder"
140
+ [closeChatInConversation]="g?.closeChatInConversation"
131
141
  [fileUploadAccept]="g?.fileUploadAccept"
132
142
  [dropEvent]="dropEvent"
133
143
  [poweredBy]="g?.poweredBy"
@@ -138,7 +148,8 @@
138
148
  (onAfterSendMessage)="onAfterSendMessageFN($event)"
139
149
  (onChangeTextArea)="onChangeTextArea($event)"
140
150
  (onAttachmentFileButtonClicked)="onAttachmentFileButtonClicked($event)"
141
- (onNewConversationButtonClicked)="onNewConversationButtonClickedFN($event)">
151
+ (onNewConversationButtonClicked)="onNewConversationButtonClickedFN($event)"
152
+ (onCloseChatButtonClicked)="onCloseChatButtonClickedFN($event)">
142
153
  </chat-conversation-footer>
143
154
 
144
155
  </div>
@@ -137,7 +137,7 @@
137
137
  #dropZone_container{
138
138
  position: absolute;
139
139
  top: 52px;
140
- bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height));
140
+ bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height) + var(--chat-footer-close-button-height));
141
141
  left: 0;
142
142
  right: 0;
143
143
  background-color: rgba(240,248,255,0.6);
@@ -153,6 +153,34 @@
153
153
  }
154
154
  }
155
155
 
156
+ #chat21-last-server-sender-badge{
157
+ position: absolute;
158
+ top: 58px;
159
+ right: 12px;
160
+ z-index: 25;
161
+ padding: 6px 10px;
162
+ border-radius: 999px;
163
+ font-size: 0.95em;
164
+ font-weight: 600;
165
+ line-height: 1.1;
166
+ border: 1px solid rgba(0,0,0,0.08);
167
+ color: rgba(0,0,0,0.65);
168
+ background: rgba(0,0,0,0.03);
169
+ user-select: none;
170
+
171
+ &.bot{
172
+ border-color: rgba(0, 150, 136, 0.45);
173
+ color: rgba(0, 150, 136, 1);
174
+ background: rgba(0, 150, 136, 0.12);
175
+ }
176
+
177
+ &.human{
178
+ border-color: rgba(63, 81, 181, 0.45);
179
+ color: rgba(63, 81, 181, 1);
180
+ background: rgba(63, 81, 181, 0.12);
181
+ }
182
+ }
183
+
156
184
  dialog:-internal-dialog-in-top-layer{
157
185
  border: 0px;
158
186
  border-radius: 16px;
@@ -212,7 +240,7 @@ dialog:-internal-dialog-in-top-layer{
212
240
 
213
241
 
214
242
  ::ng-deep .chat21-sheet-content{
215
- bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height) + 34px)!important;
243
+ bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height) + var(--chat-footer-close-button-height) + 34px)!important;
216
244
  }
217
245
 
218
246
  }
@@ -40,6 +40,7 @@ import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance'
40
40
  import { TiledeskRequestsService } from 'src/chat21-core/providers/tiledesk/tiledesk-requests.service';
41
41
  import { ConversationContentComponent } from '../conversation-content/conversation-content.component';
42
42
  import { checkAcceptedFile } from 'src/app/utils/utils';
43
+ import { computeConversationBadgeState } from 'src/app/utils/conversation-sender-classifier';
43
44
  // import { TranslateService } from '@ngx-translate/core';
44
45
 
45
46
  @Component({
@@ -113,6 +114,16 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
113
114
  // availableAgentsStatus = false; // indica quando è impostato lo stato degli agenti nel subscribe
114
115
  messages: Array<MessageModel> = [];
115
116
 
117
+ // Temporary "thinking" state after a client message is sent.
118
+ public showThinkingMessage: 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
+ // Diagnostics/internal state: kind of the latest *server* message (including system).
125
+ public latestServerMessageKind: 'bot' | 'human' | 'system' | 'unknown' = 'unknown';
126
+
116
127
 
117
128
  CLIENT_BROWSER: string = navigator.userAgent;
118
129
 
@@ -235,7 +246,8 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
235
246
  'CONTINUE',
236
247
  'EMOJI_NOT_ELLOWED',
237
248
  'ATTACHMENT',
238
- 'EMOJI'
249
+ 'EMOJI',
250
+ 'CLOSE_CHAT'
239
251
  ];
240
252
 
241
253
  const keysContent = [
@@ -252,6 +264,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
252
264
  'LABEL_TODAY',
253
265
  'LABEL_TOMORROW',
254
266
  'LABEL_LOADING',
267
+ 'LABEL_THINKING',
255
268
  'LABEL_TO',
256
269
  'ARRAY_DAYS',
257
270
  ];
@@ -356,6 +369,21 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
356
369
 
357
370
  }
358
371
 
372
+ /**
373
+ * Backward-compat wrappers: keep component API stable while delegating
374
+ * the sender classification logic to a pure utility module.
375
+ */
376
+ private refreshLastServerSenderBadge() {
377
+ const state = computeConversationBadgeState(this.messages || [], this.senderId);
378
+ this.latestServerMessageKind = state.latestServerMessageKind;
379
+ this.lastServerSenderKind = state.latestNonSystemResponderKind;
380
+ this.showLastServerSenderBadge = state.showBadge;
381
+ this.lastServerSenderBadgeText = state.badgeText;
382
+ }
383
+
384
+ // (Implementation moved to src/app/utils/conversation-sender-classifier.ts)
385
+
386
+
359
387
  /**
360
388
  * do per scontato che this.userId esiste!!!
361
389
  */
@@ -368,6 +396,10 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
368
396
  // this.connectConversation();
369
397
  await this.initConversationHandler();
370
398
 
399
+ // After loading/connecting, compute "ultimo messaggio ricevuto dal server"
400
+ // (excluding messages sent by the client).
401
+ this.refreshLastServerSenderBadge();
402
+
371
403
  this.logger.debug('[CONV-COMP] ------ 4: initializeChatManager ------ ');
372
404
  //this.initializeChatManager();
373
405
 
@@ -470,7 +502,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
470
502
  return this.isConversationArchived;
471
503
  }
472
504
 
473
- //FALLBACK TO TILEDESK
505
+ // //FALLBACK TO TILEDESK
474
506
  const requests_list = await this.tiledeskRequestService.getMyRequests().catch(err => {
475
507
  this.logger.error('[CONV-COMP] getConversationDetail: error getting request from Tiledesk', err);
476
508
  this.isConversationArchived=true
@@ -488,9 +520,9 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
488
520
  return this.isConversationArchived
489
521
  }
490
522
 
491
- this.isConversationArchived = true;
492
- return null;
493
- }
523
+ this.isConversationArchived = false;
524
+ return null;
525
+ }
494
526
 
495
527
  /**
496
528
  * this.g.recipientId:
@@ -787,8 +819,14 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
787
819
  subscribtion = this.conversationHandlerService.messageAdded.pipe(takeUntil(this.unsubscribe$)).subscribe((msg: MessageModel) => {
788
820
  this.logger.debug('[CONV-COMP] ***** DETAIL messageAdded *****', msg);
789
821
  if (msg) {
822
+ if (msg.sender !== this.senderId) {
823
+ this.showThinkingMessage = false;
824
+ }
790
825
 
791
826
  that.newMessageAdded(msg);
827
+ // Update badge based on the latest message received from the server.
828
+ // We rely on `messages` being kept in-sync by the conversation handler.
829
+ that.refreshLastServerSenderBadge();
792
830
  this.checkMessagesLegntForTranscriptDownloadMenuOption();
793
831
  this.resetTimeout();
794
832
 
@@ -840,6 +878,20 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
840
878
  this.subscriptions.push(subscribe);
841
879
  }
842
880
 
881
+ subscribtionKey = 'conversationsAdded';
882
+ subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
883
+ if(!subscribtion){
884
+
885
+ subscribtion = this.chatManager.conversationsHandlerService.conversationChanged.pipe(takeUntil(this.unsubscribe$)).subscribe((conversation) => {
886
+ this.logger.debug('[CONV-COMP] ***** DATAIL conversationsChanged *****', conversation, this.conversationWith, this.isConversationArchived);
887
+ if(conversation && conversation.recipient === this.conversationId){
888
+ this.isConversationArchived = false
889
+ }
890
+ });
891
+ const subscribe = {key: subscribtionKey, value: subscribtion };
892
+ this.subscriptions.push(subscribe);
893
+ }
894
+
843
895
  subscribtionKey = 'messageWait';
844
896
  subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
845
897
  if (!subscribtion) {
@@ -1088,6 +1140,13 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1088
1140
  }
1089
1141
  /** CALLED BY: conv-header component */
1090
1142
  onWidgetSizeChange(mode: any){
1143
+ if (this.g?.isMobile) {
1144
+ this.g.fullscreenMode = true;
1145
+ this.g.size = 'max';
1146
+ this.isMenuShow = false;
1147
+ return;
1148
+ }
1149
+
1091
1150
  const normalize = (val: any): 'min' | 'max' | 'top' => {
1092
1151
  const v = (typeof val === 'string') ? val.toLowerCase().trim() : '';
1093
1152
  return (v === 'min' || v === 'max' || v === 'top') ? (v as any) : 'min';
@@ -1294,6 +1353,20 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1294
1353
  }
1295
1354
  /** CALLED BY: conv-footer component */
1296
1355
  onAfterSendMessageFN(message: MessageModel){
1356
+ // Manage thinking state only for messages sent by the current client.
1357
+ // Do not force-hide here for other message types/events.
1358
+ this.logger.debug('[CONV-COMP] onAfterSendMessageFN::::')
1359
+ if (message && message.sender === this.senderId) {
1360
+ this.logger.debug('[CONV-COMP] onAfterSendMessageFN:::: message', message)
1361
+ // if (this.shouldShowThinkingForBot()) {
1362
+ // this.logger.debug('[CONV-COMP] shouldShowThinkingForBot::::', true)
1363
+ // this.startThinkingMessage();
1364
+ // } else {
1365
+ // this.logger.debug('[CONV-COMP] shouldShowThinkingForBot::::', false)
1366
+ // this.showThinkingMessage = false;
1367
+ // }
1368
+ this.showThinkingMessage = true;
1369
+ }
1297
1370
  this.onAfterSendMessage.emit(message)
1298
1371
  }
1299
1372
  /** CALLED BY: conv-footer component */
@@ -1325,6 +1398,12 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1325
1398
  this.logger.debug('[CONV-COMP] floating onNewConversationButtonClicked')
1326
1399
  this.onNewConversationButtonClicked.emit()
1327
1400
  }
1401
+
1402
+ /** CALLED BY: conv-footer component */
1403
+ onCloseChatButtonClickedFN(event){
1404
+ this.logger.debug('[CONV-COMP] onCloseChatButtonClicked::::', event)
1405
+ this.onCloseChat()
1406
+ }
1328
1407
  // =========== END: event emitter function ====== //
1329
1408
 
1330
1409
 
@@ -1345,6 +1424,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1345
1424
  //this.storageService.removeItem('activeConversation');
1346
1425
  this.isConversationArchived = false;
1347
1426
  this.hideTextAreaContent = false;
1427
+ this.showThinkingMessage = false;
1348
1428
  this.conversationFooter.textInputTextArea='';
1349
1429
  this.hideFooterTextReply = false;
1350
1430
  this.footerMessagePlaceholder = '';