@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.
- package/.github/workflows/docker-community-push-latest.yml +23 -13
- package/.github/workflows/docker-image-tag-community-tag-push.yml +22 -12
- package/CHANGELOG.md +88 -13
- package/Dockerfile +4 -5
- package/angular.json +2 -1
- package/deploy_amazon_beta.sh +17 -7
- package/docs/changelog/this-branch.md +36 -0
- package/package.json +1 -1
- package/src/app/app.component.html +9 -2
- package/src/app/app.component.scss +59 -0
- package/src/app/app.component.ts +128 -33
- package/src/app/component/conversation-detail/conversation/conversation.component.html +13 -2
- package/src/app/component/conversation-detail/conversation/conversation.component.scss +30 -2
- package/src/app/component/conversation-detail/conversation/conversation.component.ts +190 -5
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +16 -3
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +12 -9
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +15 -1
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +1 -1
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +103 -80
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +40 -13
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +40 -1
- package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +4 -4
- package/src/app/component/conversation-detail/conversation-header/conversation-header.component.ts +1 -0
- package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.ts +0 -18
- package/src/app/component/home/home.component.html +3 -3
- package/src/app/component/launcher-button/launcher-button.component.html +1 -1
- package/src/app/component/launcher-button/launcher-button.component.ts +3 -2
- package/src/app/providers/global-settings.service.ts +38 -0
- package/src/app/providers/translator.service.ts +2 -0
- package/src/app/sass/_variables.scss +1 -0
- package/src/app/utils/globals.ts +8 -2
- package/src/assets/i18n/en.json +2 -0
- package/src/assets/i18n/es.json +2 -0
- package/src/assets/i18n/fr.json +2 -0
- package/src/assets/i18n/it.json +2 -0
- package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
- package/src/chat21-core/utils/utils.ts +5 -2
- package/src/iframe-style.css +5 -5
- 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
|
-
|
|
492
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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'
|
|
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 {
|
package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts
CHANGED
|
@@ -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)
|
package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html
CHANGED
|
@@ -14,94 +14,117 @@
|
|
|
14
14
|
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|