@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
@@ -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
 
@@ -1,23 +1,25 @@
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
+ }
12
+ .close-chat-container{
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: center;
16
+ justify-content: center;
17
+ gap: 8px;
11
18
 
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%;
19
+ .c21-close{
20
+ height: 30px !important;
21
+ margin: 0px !important;
15
22
  }
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
- }
20
-
21
23
  }
22
24
 
23
25
  .icons-container{
@@ -419,3 +421,28 @@ textarea:active{
419
421
  border: none;
420
422
  // margin: -2px -2px 0px;
421
423
  }
424
+
425
+
426
+ // aggiungi un'animazione di fade in e fade out quando .star-rating-widget è visibile con transition
427
+ .star-rating-widget {
428
+ transition: all 0.5s ease-in-out;
429
+ }
430
+
431
+ .star-rating-widget {
432
+ position: absolute;
433
+ left: 0;
434
+ right: 0;
435
+ bottom: -52px;
436
+ height: 100%;
437
+ width: 100%;
438
+ flex-direction: row;
439
+ justify-content: center;
440
+ background-color: rgb(255, 255, 255);
441
+ flex-wrap: nowrap;
442
+ &.active {
443
+ bottom: 0px;
444
+ }
445
+ &.inactive {
446
+ bottom: -52px;
447
+ }
448
+ }
@@ -42,6 +42,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
42
42
  @Input() isEmojiiPickerShow: boolean;
43
43
  @Input() footerMessagePlaceholder: string;
44
44
  @Input() fileUploadAccept: string;
45
+ @Input() closeChatInConversation: boolean;
45
46
  @Input() dropEvent: Event;
46
47
  @Input() poweredBy: string;
47
48
  @Input() stylesMap: Map<string, string>
@@ -52,6 +53,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
52
53
  @Output() onChangeTextArea = new EventEmitter<any>();
53
54
  @Output() onAttachmentFileButtonClicked = new EventEmitter<any>();
54
55
  @Output() onNewConversationButtonClicked = new EventEmitter();
56
+ @Output() onCloseChatButtonClicked = new EventEmitter();
55
57
 
56
58
  @ViewChild('chat21_file') public chat21_file: ElementRef;
57
59
  // @ViewChild('emojii_container', {read: ViewContainerRef}) selector;
@@ -87,6 +89,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
87
89
 
88
90
  file_size_limit = FILE_SIZE_LIMIT;
89
91
  attachmentTooltip: string = '';
92
+ isErrorNetwork: boolean = false;
90
93
 
91
94
 
92
95
  convertColorToRGBA = convertColorToRGBA;
@@ -513,14 +516,46 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
513
516
  onSendRecording(audioBlob: Blob | null) {
514
517
  this.isStartRec = false;
515
518
  if (audioBlob) {
516
- this.convertBlobToBase64(audioBlob);
519
+ this.prepareAndUpload(audioBlob);
520
+ // this.convertBlobToBase64(audioBlob);
517
521
  this.isStopRec = false;
518
522
  } else {
519
523
  this.isStopRec = false;
520
524
  }
521
525
  }
522
526
 
527
+ prepareAndUpload(audioBlob: Blob) {
523
528
 
529
+ this.isFilePendingToUpload = true;
530
+
531
+ // ⭐ NON modificare il MIME
532
+ const mimeType = audioBlob.type;
533
+
534
+ const size = audioBlob.size;
535
+ const uid = Date.now().toString(36);
536
+
537
+ // estensione coerente col MIME REALE
538
+ let ext = 'mp3';
539
+ const fileName = `audio-${uid}.${ext}`;
540
+
541
+ const file = new File([audioBlob], fileName, {
542
+ type: mimeType,
543
+ lastModified: Date.now()
544
+ });
545
+
546
+ // ✅ metadata SENZA base64
547
+ const metadata = {
548
+ name: fileName,
549
+ type: 'audio/mp3',
550
+ uid: uid,
551
+ size: size
552
+ };
553
+
554
+ this.logger.log('[UPLOAD] metadata:', metadata);
555
+
556
+ // stesso metodo che già usi
557
+ this.uploadSingle(metadata, file, '');
558
+ }
524
559
 
525
560
  // Funzione per convertire Blob in Base64 usando FileReader
526
561
  async convertBlobToBase64(audioBlob: Blob) {
@@ -677,6 +712,10 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
677
712
  this.onNewConversationButtonClicked.emit();
678
713
  }
679
714
 
715
+ onCloseChat(event){
716
+ this.onCloseChatButtonClicked.emit();
717
+ }
718
+
680
719
  // onContinueConversation(){
681
720
  // this.hideTextAreaContent = false;
682
721
  // this.onBackButton.emit(false)
@@ -27,7 +27,7 @@
27
27
 
28
28
 
29
29
  <!-- ICON MAXIMIZE -->
30
- <button *ngIf="size === 'min' && !fullscreenMode" [attr.disabled]="(isButtonsDisabled)?true:null" tabindex="-1" class="c21-header-button c21-right c21-button-clean" [ngStyle]="{'display': (hideHeaderConversationOptionsMenu)?'none':'flex'}" (click)="onChangeSize('max')" >
30
+ <button *ngIf="size === 'min' && !fullscreenMode && !isMobile" [attr.disabled]="(isButtonsDisabled)?true:null" tabindex="-1" class="c21-header-button c21-right c21-button-clean" [ngStyle]="{'display': (hideHeaderConversationOptionsMenu)?'none':'flex'}" (click)="onChangeSize('max')" >
31
31
  <svg role="img" aria-labelledby="altIconTitle" [ngStyle]="{'fill': stylesMap?.get('foregroundColor') }" xmlns="http://www.w3.org/2000/svg" transform="matrix(-1,0,0,1,0,0)"
32
32
  width="17" height="17" viewBox="0 0 17 17">
33
33
  <path d="M6.49001 8.30999L3.69 11.11V9.40999C3.69 8.93999 3.31 8.55999 2.84 8.55999C2.37 8.55999 1.99001 8.93999 1.99001 9.40999V14.01H6.59C7.06 14.01 7.44 13.63 7.44 13.16C7.44 12.69 7.06 12.31 6.59 12.31H4.89L7.69 9.50999L6.49001 8.30999ZM9.41 1.98999C8.94 1.98999 8.56001 2.36999 8.56001 2.83999C8.56001 3.30999 8.94 3.68999 9.41 3.68999H11.11L8.31001 6.48999L9.51 7.68999L12.31 4.88999V6.58999C12.31 7.05999 12.69 7.43999 13.16 7.43999C13.63 7.43999 14.01 7.05999 14.01 6.58999V1.98999H9.41Z"></path>
@@ -36,7 +36,7 @@
36
36
  </button>
37
37
 
38
38
  <!-- ICON MINIMIZE -->
39
- <button *ngIf="size==='top' && !fullscreenMode" [attr.disabled]="(isButtonsDisabled)?true:null" tabindex="-1" class="c21-header-button c21-right c21-button-clean" [ngStyle]="{'display': (hideHeaderConversationOptionsMenu)?'none':'flex'}" (click)="onChangeSize('min')" >
39
+ <button *ngIf="size==='top' && !fullscreenMode && !isMobile" [attr.disabled]="(isButtonsDisabled)?true:null" tabindex="-1" class="c21-header-button c21-right c21-button-clean" [ngStyle]="{'display': (hideHeaderConversationOptionsMenu)?'none':'flex'}" (click)="onChangeSize('min')" >
40
40
  <svg role="img" aria-labelledby="altIconTitle" class="icon-menu" [ngStyle]="{'fill': stylesMap?.get('foregroundColor')}" xmlns="http://www.w3.org/2000/svg" transform="matrix(-1,0,0,1,0,0)"
41
41
  width="17" height="17" viewBox="0 0 17 17">
42
42
  <path d="M13.59 5.31h-1.7l3.3-3.3-1.2-1.2-3.3 3.3v-1.7a.85.85 0 1 0-1.7 0v4.6h4.6a.85.85 0 1 0 0-1.7M1.57 9.84c0 .47.38.85.85.85h1.7l-3.3 3.3 1.2 1.2 3.3-3.3v1.7a.85.85 0 1 0 1.7 0v-4.6h-4.6a.85.85 0 0 0-.85.85"></path>
@@ -45,7 +45,7 @@
45
45
  </button>
46
46
 
47
47
  <!-- ICON TOP -->
48
- <button *ngIf="size==='max' && !fullscreenMode" [attr.disabled]="(isButtonsDisabled)?true:null" tabindex="-1" class="c21-header-button c21-right c21-button-clean" [ngStyle]="{'display': (hideHeaderConversationOptionsMenu)?'none':'flex'}" (click)="onChangeSize('top')" >
48
+ <button *ngIf="size==='max' && !fullscreenMode && !isMobile" [attr.disabled]="(isButtonsDisabled)?true:null" tabindex="-1" class="c21-header-button c21-right c21-button-clean" [ngStyle]="{'display': (hideHeaderConversationOptionsMenu)?'none':'flex'}" (click)="onChangeSize('top')" >
49
49
  <svg role="img" aria-labelledby="altIconTitle" [ngStyle]="{'fill': stylesMap?.get('foregroundColor')}" xmlns="http://www.w3.org/2000/svg" transform="matrix(-1,0,0,1,0,0)"
50
50
  width="17" height="17" viewBox="0 0 17 17">
51
51
  <path d="M3.7,7.6L2.5,6.4C2.2,6,2.2,5.5,2.5,5.2s0.9-0.4,1.2-0.1L7,8.4l-3.3,3.3c-0.4,0.4-0.8,0.3-1.1,0s-0.4-0.9-0.1-1.2l1.2-1.2 H1.5l0-1.7H3.7z"/>
@@ -21,6 +21,7 @@ export class ConversationHeaderComponent implements OnInit, OnChanges {
21
21
  @Input() isTypings: boolean;
22
22
  @Input() nameUserTypingNow: string;
23
23
  @Input() typingLocation: string;
24
+ @Input() isMobile: boolean;
24
25
  @Input() isTrascriptDownloadEnabled: boolean;
25
26
  @Input() fullscreenMode: boolean;
26
27
  @Input() size: 'min' | 'max' | 'top';
@@ -50,24 +50,6 @@ export class EyeeyeCatcherCardComponent implements OnInit {
50
50
  this.displayEyeCatcherCardCloseBtnWrapper = 'none';
51
51
  this.displayEyeCatcherCardCloseBtnIsMobileWrapper = 'none';
52
52
  this.displayEyeCatcherCardCloseBtn = 'none';
53
- /* EYE-CATCHER CLOSE BUTTON SWITCH */
54
- this.openIfCallOutTimer();
55
- }
56
-
57
-
58
- /**
59
- * OPEN THE EYE-CATCHER CARD
60
- * if calloutTimer >= 0
61
- */
62
- private openIfCallOutTimer() {
63
- const that = this;
64
- const calloutTimer = this.g.calloutTimer;
65
- if (calloutTimer >= 0) {
66
- const waitingTime = calloutTimer * 1000;
67
- setTimeout(function () {
68
- that.openEyeCatcher();
69
- }, waitingTime);
70
- }
71
53
  }
72
54
 
73
55
  /**
@@ -14,7 +14,7 @@
14
14
  <div class="c21-header-button" [ngStyle]="{'display': (g.hideHeaderCloseButton)?'none':'block'}">
15
15
 
16
16
  <!-- ICON MAXIMIZE -->
17
- <div *ngIf="size === 'min' && !fullscreenMode" class="c21-size-button">
17
+ <div *ngIf="size === 'min' && !fullscreenMode && !g?.isMobile" class="c21-size-button">
18
18
  <button [attr.disabled]="(isButtonsDisabled)?true:null" tabindex="-1" class="c21-close-button-body" [ngStyle]="{'display': (hideHeaderConversationOptionsMenu)?'none':'flex'}" (click)="onChangeSize('max')" >
19
19
  <svg role="img" aria-labelledby="altIconTitle" [ngStyle]="{'fill': stylesMap?.get('foregroundColor') }" xmlns="http://www.w3.org/2000/svg" transform="matrix(-1,0,0,1,0,0)"
20
20
  width="17" height="17" viewBox="0 0 17 17">
@@ -25,7 +25,7 @@
25
25
  </div>
26
26
 
27
27
  <!-- ICON MINIMIZE -->
28
- <div *ngIf="size==='top' && !fullscreenMode" class="c21-size-button">
28
+ <div *ngIf="size==='top' && !fullscreenMode && !g?.isMobile" class="c21-size-button">
29
29
  <button [attr.disabled]="(isButtonsDisabled)?true:null" tabindex="-1" class="c21-close-button-body" [ngStyle]="{'display': (hideHeaderConversationOptionsMenu)?'none':'flex'}" (click)="onChangeSize('min')" >
30
30
  <svg role="img" aria-labelledby="altIconTitle" class="icon-menu" [ngStyle]="{'fill': stylesMap?.get('foregroundColor')}" xmlns="http://www.w3.org/2000/svg" transform="matrix(-1,0,0,1,0,0)"
31
31
  width="17" height="17" viewBox="0 0 17 17">
@@ -36,7 +36,7 @@
36
36
  </div>
37
37
 
38
38
  <!-- ICON TOP -->
39
- <div *ngIf="size==='max' && !fullscreenMode" class="c21-size-button">
39
+ <div *ngIf="size==='max' && !fullscreenMode && !g?.isMobile" class="c21-size-button">
40
40
  <button [attr.disabled]="(isButtonsDisabled)?true:null" tabindex="-1" class="c21-close-button-body" [ngStyle]="{'display': (hideHeaderConversationOptionsMenu)?'none':'flex'}" (click)="onChangeSize('top')" >
41
41
  <svg role="img" aria-labelledby="altIconTitle" [ngStyle]="{'fill': stylesMap?.get('foregroundColor')}" xmlns="http://www.w3.org/2000/svg" transform="matrix(-1,0,0,1,0,0)"
42
42
  width="17" height="17" viewBox="0 0 17 17">
@@ -12,7 +12,7 @@ import { MIN_WIDTH_IMAGES } from 'src/app/utils/constants';
12
12
  import { ConversationModel } from 'src/chat21-core/models/conversation';
13
13
  import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service';
14
14
  import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
15
- import { commandToMessage, conversationToMessage, isEmojii, isFrame, isImage, isSameSender } from 'src/chat21-core/utils/utils-message';
15
+ import { commandToMessage, conversationToMessage, isEmojii, isFrame, isImage, isMine, isSameSender, isSender } from 'src/chat21-core/utils/utils-message';
16
16
 
17
17
 
18
18
  @Component({
@@ -59,6 +59,9 @@ export class LastMessageComponent implements OnInit, AfterViewInit, OnDestroy {
59
59
  ngOnChanges(changes: SimpleChanges) {
60
60
  this.logger.debug('[LASTMESSAGE] onChanges', changes)
61
61
  if(this.conversation){
62
+
63
+ /** if the message is sent by the logged user, do not add it to the messages array */
64
+ if(isSender(this.conversation.sender, this.g.senderId)) return;
62
65
 
63
66
  if(this.conversation.attributes && this.conversation.attributes.commands){
64
67
  this.addCommandMessage(this.conversation)