@chat21/chat21-web-widget 5.1.34-rc1 → 5.2.1
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 +13 -23
- package/.github/workflows/docker-image-tag-community-tag-push.yml +12 -22
- package/CHANGELOG.md +22 -118
- package/Dockerfile +4 -4
- package/README.md +1 -1
- package/docs/ACCESSIBILITY-STATEMENT.md +388 -0
- package/docs/TILEDESK_WIDGET_ACCESSIBILITY_ALIGNMENT.md +60 -0
- package/docs/TILEDESK_WIDGET_ACCESSIBILITY_STATEMENT_COMPLETE.md +386 -0
- package/docs/changelog/this-branch.md +0 -36
- package/nginx.conf +2 -22
- package/package.json +1 -1
- package/src/app/app.component.ts +9 -10
- package/src/app/component/conversation-detail/conversation/conversation.component.html +2 -2
- package/src/app/component/conversation-detail/conversation/conversation.component.scss +2 -2
- package/src/app/component/conversation-detail/conversation/conversation.component.ts +16 -34
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +3 -3
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +2 -2
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +0 -1
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +52 -63
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +17 -11
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +10 -4
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +5 -8
- package/src/app/component/form/inputs/form-text/form-text.component.ts +1 -1
- package/src/app/component/last-message/last-message.component.ts +1 -4
- package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +17 -8
- package/src/app/component/message/audio-sync/audio-sync.component.ts +96 -25
- package/src/app/component/message/bubble-message/bubble-message.component.html +12 -9
- package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +38 -45
- package/src/app/component/message/bubble-message/bubble-message.component.ts +49 -45
- package/src/app/component/message/json-sources/json-sources.component.html +6 -5
- package/src/app/component/message/json-sources/json-sources.component.scss +26 -18
- package/src/app/component/message/json-sources/json-sources.component.ts +41 -0
- package/src/app/providers/global-settings.service.ts +0 -42
- package/src/app/providers/json-sources-parser.service.ts +13 -1
- package/src/app/providers/translator.service.ts +1 -4
- package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +7 -8
- package/src/app/providers/tts-audio-playback-coordinator.service.ts +13 -0
- package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +67 -82
- package/src/app/providers/voice/voice.service.spec.ts +35 -35
- package/src/app/providers/voice/voice.service.ts +3 -7
- package/src/app/sass/_variables.scss +0 -1
- package/src/app/utils/globals.ts +2 -8
- package/src/assets/i18n/en.json +22 -1
- package/src/assets/i18n/es.json +22 -1
- package/src/assets/i18n/fr.json +22 -1
- package/src/assets/i18n/it.json +22 -1
- package/src/assets/twp/index-dev.html +0 -18
- package/src/chat21-core/providers/firebase/firebase-init-service.ts +5 -5
- package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
- package/src/chat21-core/utils/utils-message.ts +4 -4
- package/src/chat21-core/utils/utils.ts +2 -5
- package/src/widget-config-template.json +0 -1
- package/src/widget-config.json +28 -30
- package/.github/workflows/build.yml +0 -22
- package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +0 -14
|
@@ -265,8 +265,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
|
|
|
265
265
|
'VOICE_CONNECTING',
|
|
266
266
|
'VOICE_LISTENING',
|
|
267
267
|
'VOICE_PROCESSING',
|
|
268
|
-
'STREAM_AUDIO'
|
|
269
|
-
'MAX_ATTACHMENT'
|
|
268
|
+
'STREAM_AUDIO'
|
|
270
269
|
];
|
|
271
270
|
|
|
272
271
|
const keysContent = [
|
|
@@ -529,29 +528,25 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
|
|
|
529
528
|
return this.isConversationArchived;
|
|
530
529
|
}
|
|
531
530
|
|
|
532
|
-
//
|
|
533
|
-
|
|
534
|
-
try {
|
|
535
|
-
requests_list = await this.tiledeskRequestService.getMyRequests();
|
|
536
|
-
} catch (err) {
|
|
531
|
+
//FALLBACK TO TILEDESK
|
|
532
|
+
const requests_list = await this.tiledeskRequestService.getMyRequests().catch(err => {
|
|
537
533
|
this.logger.error('[CONV-COMP] getConversationDetail: error getting request from Tiledesk', err);
|
|
538
|
-
this.isConversationArchived
|
|
539
|
-
return
|
|
540
|
-
}
|
|
541
|
-
|
|
534
|
+
this.isConversationArchived=true
|
|
535
|
+
return { requests: [] }
|
|
536
|
+
});
|
|
542
537
|
if (requests_list && requests_list.requests.length > 0) {
|
|
543
538
|
this.logger.debug('[CONV-COMP] getConversationDetail: request exist on Tiledesk', requests_list);
|
|
544
|
-
|
|
545
|
-
if
|
|
546
|
-
this.isConversationArchived = false
|
|
547
|
-
return this.isConversationArchived
|
|
539
|
+
let conversation = requests_list.requests.find((request)=> request.request_id === this.conversationId)
|
|
540
|
+
if(conversation){
|
|
541
|
+
this.isConversationArchived = false
|
|
542
|
+
return this.isConversationArchived
|
|
548
543
|
}
|
|
549
544
|
this.logger.debug('[CONV-COMP] getConversationDetail: request NOT EXIST on Tiledesk', requests_list);
|
|
550
|
-
this.isConversationArchived = true
|
|
551
|
-
return this.isConversationArchived
|
|
545
|
+
this.isConversationArchived = true
|
|
546
|
+
return this.isConversationArchived
|
|
552
547
|
}
|
|
553
548
|
|
|
554
|
-
this.isConversationArchived =
|
|
549
|
+
this.isConversationArchived = true;
|
|
555
550
|
return null;
|
|
556
551
|
}
|
|
557
552
|
|
|
@@ -913,20 +908,6 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
|
|
|
913
908
|
this.subscriptions.push(subscribe);
|
|
914
909
|
}
|
|
915
910
|
|
|
916
|
-
subscribtionKey = 'conversationsAdded';
|
|
917
|
-
subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
|
|
918
|
-
if(!subscribtion){
|
|
919
|
-
|
|
920
|
-
subscribtion = this.chatManager.conversationsHandlerService.conversationChanged.pipe(takeUntil(this.unsubscribe$)).subscribe((conversation) => {
|
|
921
|
-
this.logger.debug('[CONV-COMP] ***** DATAIL conversationsChanged *****', conversation, this.conversationWith, this.isConversationArchived);
|
|
922
|
-
if(conversation && conversation.recipient === this.conversationId){
|
|
923
|
-
this.isConversationArchived = false
|
|
924
|
-
}
|
|
925
|
-
});
|
|
926
|
-
const subscribe = {key: subscribtionKey, value: subscribtion };
|
|
927
|
-
this.subscriptions.push(subscribe);
|
|
928
|
-
}
|
|
929
|
-
|
|
930
911
|
subscribtionKey = 'messageWait';
|
|
931
912
|
subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
|
|
932
913
|
if (!subscribtion) {
|
|
@@ -1449,7 +1430,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
|
|
|
1449
1430
|
this.onNewConversationButtonClicked.emit()
|
|
1450
1431
|
}
|
|
1451
1432
|
|
|
1452
|
-
/** CALLED BY: conv-footer
|
|
1433
|
+
/** CALLED BY: conv-footer component */
|
|
1453
1434
|
onStreamAudioActiveChange(event: boolean){
|
|
1454
1435
|
this.isStreamAudioActive = event
|
|
1455
1436
|
}
|
|
@@ -1462,7 +1443,6 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
|
|
|
1462
1443
|
this.logger.debug('[CONV-COMP] onCloseChatButtonClicked::::', event)
|
|
1463
1444
|
this.onCloseChat()
|
|
1464
1445
|
}
|
|
1465
|
-
// =========== END: event emitter function ====== //
|
|
1466
1446
|
|
|
1467
1447
|
/**
|
|
1468
1448
|
* True quando è visibile il pulsante chiudi stream (`.close-stream-button`, `isStreamAudioActive`).
|
|
@@ -1471,6 +1451,8 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
|
|
|
1471
1451
|
closeStreamButtonActiveForSheetBottom(): boolean {
|
|
1472
1452
|
return !!(this.g?.showAudioStreamFooterButton && (this.isStreamAudioActive || this.isStreamAudioConnecting));
|
|
1473
1453
|
}
|
|
1454
|
+
// =========== END: event emitter function ====== //
|
|
1455
|
+
|
|
1474
1456
|
|
|
1475
1457
|
openInputFiles() {
|
|
1476
1458
|
alert('ok');
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<div *ngFor="let message of messages; let first = first; let last = last; let i = index" class="rowMsg">
|
|
28
28
|
|
|
29
29
|
<!-- message SENDER:: -->
|
|
30
|
-
|
|
30
|
+
<div role="article" *ngIf="messageType(MESSAGE_TYPE_MINE, message) && !message.isJustRecived" class="msg_container base_sent">
|
|
31
31
|
|
|
32
32
|
<!--backgroundColor non viene ancora usato -->
|
|
33
33
|
<!-- class="messages msg_sent slide-in-right" -->
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
<!-- message RECIPIENT:: -->
|
|
56
56
|
<div role="article" *ngIf="messageType(MESSAGE_TYPE_OTHERS, message)" class="msg_container base_receive">
|
|
57
57
|
|
|
58
|
-
<chat-avatar-image *ngIf="!isSameSender(message?.sender, i) && !isStreamAudioActive"
|
|
58
|
+
<chat-avatar-image *ngIf="!isSameSender(message?.sender, i) && !isStreamAudioActive"
|
|
59
59
|
[ngClass]="{'slide-in-left': false}"
|
|
60
60
|
[senderID]="message?.sender"
|
|
61
61
|
[senderFullname]="message?.sender_fullname"
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
[class.no-background]="(isImage(message) || isFrame(message) || isCarousel(message)) && ((message?.text && message?.text.trim() === '') || !message?.text)"
|
|
70
70
|
[class.emoticon]="isEmojii(message?.text)"
|
|
71
71
|
[class.fullSizeMessage]="isStreamAudioActive"
|
|
72
|
-
[style.margin-left]="isSameSender(message?.sender, i) ? 'calc(var(--avatar-width) + 10px)' : null"
|
|
72
|
+
[style.margin-left]="isSameSender(message?.sender, i) && !isStreamAudioActive ? 'calc(var(--avatar-width) + 10px)' : null"
|
|
73
73
|
[ngStyle]="{'background': stylesMap.get('bubbleReceivedBackground'), 'color': stylesMap.get('bubbleReceivedTextColor'), 'width':isFrame(message) ?'100%' : null}"
|
|
74
74
|
[isSameSender]="isSameSender(message?.sender, i)"
|
|
75
75
|
[message]="message"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
:host .loading.fullSize ::ng-deep > div.spinner{
|
|
31
|
-
margin:
|
|
31
|
+
margin: 50px 0px !important;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// ============= CSS c21-body ================= //
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
top: 0;
|
|
49
49
|
right: 0;
|
|
50
50
|
left: 0;
|
|
51
|
-
bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height)
|
|
51
|
+
bottom: calc(var(--chat-footer-logo-height) + var(--chat-footer-height));
|
|
52
52
|
overflow: hidden;
|
|
53
53
|
.time{
|
|
54
54
|
margin-bottom: 20px;
|
package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
7
|
<!-- ALERT EMOJI -->
|
|
8
|
-
<div id="textAlert"
|
|
9
|
-
*ngIf="!hideTextAreaContent && showAlertEmoji"
|
|
10
|
-
role="alert"
|
|
11
|
-
aria-live="assertive"
|
|
12
|
-
class="fade-in-bottom"
|
|
8
|
+
<div id="textAlert"
|
|
9
|
+
*ngIf="!hideTextAreaContent && showAlertEmoji"
|
|
10
|
+
role="alert"
|
|
11
|
+
aria-live="assertive"
|
|
12
|
+
class="fade-in-bottom"
|
|
13
13
|
[class.hideTextReply]="hideTextReply">
|
|
14
14
|
<svg aria-hidden="true" focusable="false" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" version="1.1" viewBox="0 0 110 135">
|
|
15
15
|
<path d="M55,25.8c-23,0-41.7,18.7-41.7,41.7s18.7,41.7,41.7,41.7,41.7-18.7,41.7-41.7-18.7-41.7-41.7-41.7ZM55,91.5c-3.4,0-6.2-2.8-6.2-6.2s2.8-6.2,6.2-6.2,6.2,2.8,6.2,6.2-2.8,6.2-6.2,6.2ZM60.3,70.1c-.2,2.8-2.5,4.9-5.3,4.9s-5.1-2.2-5.3-4.9l-1.6-22.3c-.3-4,2.9-7.4,6.9-7.4s7.2,3.4,6.9,7.4l-1.6,22.3Z"/>
|
|
@@ -42,42 +42,40 @@
|
|
|
42
42
|
<div class="textarea-container-wrapper" *ngIf="!hideTextAreaContent && !hideTextReply">
|
|
43
43
|
<!-- TEXTAREA + ICONS: conv active-->
|
|
44
44
|
<div class="textarea-container" [class.voice-mode]="isStreamAudioActive || isStreamAudioConnecting">
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
<div *ngIf="!isStopRec && !(isStreamAudioActive || isStreamAudioConnecting)" class="icons-container">
|
|
47
47
|
<!-- ICON ATTACHMENT -->
|
|
48
|
-
<label *ngIf="showAttachmentFooterButton"
|
|
49
|
-
for="chat21-file"
|
|
48
|
+
<label *ngIf="showAttachmentFooterButton"
|
|
49
|
+
for="chat21-file"
|
|
50
50
|
role="button"
|
|
51
51
|
tabindex="0"
|
|
52
52
|
[attr.aria-label]="translationMap?.get('BUTTON_ATTACH_FILE')"
|
|
53
53
|
[attr.title]="'MAX_ATTACHMENT' | translate: { FILE_SIZE_LIMIT: file_size_limit }"
|
|
54
|
-
class="chat21-textarea-button"
|
|
55
|
-
[class.active]="!isFilePendingToUpload && !hideTextReply"
|
|
54
|
+
class="chat21-textarea-button"
|
|
55
|
+
[class.active]="!isFilePendingToUpload && !hideTextReply"
|
|
56
56
|
id="chat21-start-upload-doc"
|
|
57
57
|
(keydown.enter)="$event.preventDefault(); chat21_file.click()"
|
|
58
58
|
(keydown.space)="$event.preventDefault(); chat21_file.click()">
|
|
59
59
|
<span class="v-align-center">
|
|
60
60
|
<svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="24px" height="24" viewBox="0 0 24 24" fill="currentColor">
|
|
61
61
|
<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"/>
|
|
62
|
-
<title id="altIconTitle">{{ maxAttachmentLabel }}</title>
|
|
63
62
|
</svg>
|
|
64
|
-
|
|
65
63
|
</span>
|
|
66
64
|
<input
|
|
67
|
-
[attr.disabled]="(isFilePendingToUpload || isConversationArchived || hideTextReply)? true : null"
|
|
65
|
+
[attr.disabled] = "(isFilePendingToUpload || isConversationArchived || hideTextReply)? true : null"
|
|
68
66
|
tabindex="-1"
|
|
69
|
-
type="file"
|
|
67
|
+
type="file"
|
|
70
68
|
[attr.aria-label]="translationMap?.get('BUTTON_ATTACH_FILE')"
|
|
71
69
|
[accept]="fileUploadAccept"
|
|
72
70
|
name="chat21-file"
|
|
73
71
|
id="chat21-file"
|
|
74
72
|
#chat21_file
|
|
75
|
-
class="inputfile"
|
|
73
|
+
class="inputfile"
|
|
76
74
|
[ngStyle]="{'display': 'block', height:'1px', width:'1px', overflow: 'hidden' }"
|
|
77
75
|
(change)="detectFiles($event)"/>
|
|
78
76
|
</label>
|
|
79
77
|
<!-- ICON EMOJII -->
|
|
80
|
-
<label *ngIf="showEmojiFooterButton"
|
|
78
|
+
<label *ngIf="showEmojiFooterButton"
|
|
81
79
|
for="chat21-emojii"
|
|
82
80
|
role="button"
|
|
83
81
|
tabindex="0"
|
|
@@ -90,7 +88,7 @@
|
|
|
90
88
|
(keydown.enter)="$event.preventDefault(); onEmojiiPickerClicked()"
|
|
91
89
|
(keydown.space)="$event.preventDefault(); onEmojiiPickerClicked()">
|
|
92
90
|
<span class="v-align-center">
|
|
93
|
-
<svg
|
|
91
|
+
<svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
|
|
94
92
|
<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"/>
|
|
95
93
|
<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"/>
|
|
96
94
|
<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"/>
|
|
@@ -107,7 +105,7 @@
|
|
|
107
105
|
class="visible-text-area"
|
|
108
106
|
[class.stream-active]="isStreamAudioActive || isStreamAudioConnecting"
|
|
109
107
|
[class.hasError]="showAlertEmoji"
|
|
110
|
-
[class.disabled]="(isConversationArchived || hideTextReply)? true : null">
|
|
108
|
+
[class.disabled] = "( isConversationArchived || hideTextReply)? true : null">
|
|
111
109
|
<!-- Voice mode: inline status chip -->
|
|
112
110
|
<div class="voice-inline-status" *ngIf="isStreamAudioActive || isStreamAudioConnecting" role="status" aria-live="polite">
|
|
113
111
|
<span class="voice-inline-dot"
|
|
@@ -117,10 +115,10 @@
|
|
|
117
115
|
<span class="voice-inline-label">{{ voiceStatusLabel }}</span>
|
|
118
116
|
</div>
|
|
119
117
|
<!-- Normal mode: textarea -->
|
|
120
|
-
<textarea
|
|
118
|
+
<textarea
|
|
121
119
|
*ngIf="!(isStreamAudioActive || isStreamAudioConnecting)"
|
|
122
|
-
[attr.disabled]="(hideTextReply)? true : null"
|
|
123
|
-
[attr.placeholder]="(footerMessagePlaceholder)? footerMessagePlaceholder : translationMap?.get('LABEL_PLACEHOLDER')"
|
|
120
|
+
[attr.disabled] = "(hideTextReply)? true : null"
|
|
121
|
+
[attr.placeholder] ="(footerMessagePlaceholder)? footerMessagePlaceholder : translationMap?.get('LABEL_PLACEHOLDER')"
|
|
124
122
|
[attr.aria-label]="(footerMessagePlaceholder)? footerMessagePlaceholder : translationMap?.get('LABEL_PLACEHOLDER')"
|
|
125
123
|
[attr.aria-multiline]="true"
|
|
126
124
|
[attr.aria-invalid]="showAlertEmoji ? 'true' : 'false'"
|
|
@@ -135,7 +133,7 @@
|
|
|
135
133
|
(keydown)="onkeydown($event)"
|
|
136
134
|
(paste)="onPaste($event)">
|
|
137
135
|
</textarea>
|
|
138
|
-
|
|
136
|
+
|
|
139
137
|
</div>
|
|
140
138
|
|
|
141
139
|
<!-- ICON SEND -->
|
|
@@ -156,12 +154,8 @@
|
|
|
156
154
|
</button>
|
|
157
155
|
|
|
158
156
|
<!-- ICON REC -->
|
|
159
|
-
<div *ngIf="showAudioRecorderFooterButton && !textInputTextArea"
|
|
160
|
-
class="chat21-audio-button"
|
|
161
|
-
[class.active]="isStopRec"
|
|
162
|
-
id="chat21-button-rec">
|
|
157
|
+
<div *ngIf="showAudioRecorderFooterButton && !textInputTextArea && !isStreamAudioActive && !isStreamAudioConnecting" tabindex="-1" class="chat21-audio-button" [class.active]="isStopRec" id="chat21-button-rec">
|
|
163
158
|
<chat-audio-recorder
|
|
164
|
-
[translationMap]="translationMap"
|
|
165
159
|
(startRecordingEvent)="onStartRecording()"
|
|
166
160
|
(deleteRecordingEvent)="onDeleteRecording()"
|
|
167
161
|
(endRecordingEvent)="onEndRecording($event)"
|
|
@@ -183,30 +177,26 @@
|
|
|
183
177
|
[translationMap]="translationMap">
|
|
184
178
|
</chat-stream-audio-spectrum>
|
|
185
179
|
</div>
|
|
186
|
-
</div>
|
|
187
180
|
|
|
181
|
+
<div class="close-chat-container" *ngIf="closeChatInConversation">
|
|
182
|
+
<button type="button" 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')}">
|
|
183
|
+
<span class="v-align-center">
|
|
184
|
+
<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"
|
|
185
|
+
width="15px" height="15px" viewBox="0 0 512 512">
|
|
186
|
+
<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>
|
|
187
|
+
<rect x="48" y="64" width="416" height="80" rx="28" ry="28" stroke-linejoin="round" stroke-width="50px" fill="none" ></rect>
|
|
188
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M320 304l-64 64-64-64M256 345.89V224" stroke-width="50px" fill="none"></path>
|
|
189
|
+
<title id="altIconTitle">{{ translationMap?.get('CLOSE_CHAT') }}</title>
|
|
190
|
+
</svg>
|
|
191
|
+
</span>
|
|
192
|
+
<span class="v-align-center c21-label-button">
|
|
193
|
+
{{translationMap?.get('CLOSE_CHAT')}}
|
|
194
|
+
</span>
|
|
195
|
+
<div class="clear"></div>
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
188
198
|
|
|
189
|
-
<div class="close-chat-container" *ngIf="closeChatInConversation">
|
|
190
|
-
<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')}">
|
|
191
|
-
<span class="v-align-center">
|
|
192
|
-
<!-- <svg [ngStyle]="{'fill': 'yellow' }" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
|
|
193
|
-
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" [ngStyle]="{'fill': stylesMap?.get('foregroundColor')}"/>
|
|
194
|
-
</svg> -->
|
|
195
|
-
<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"
|
|
196
|
-
width="15px" height="15px" viewBox="0 0 512 512">
|
|
197
|
-
<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>
|
|
198
|
-
<rect x="48" y="64" width="416" height="80" rx="28" ry="28" stroke-linejoin="round" stroke-width="50px" fill="none" ></rect>
|
|
199
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M320 304l-64 64-64-64M256 345.89V224" stroke-width="50px" fill="none"></path>
|
|
200
|
-
<title id="altIconTitle">{{ translationMap?.get('CLOSE_CHAT') }}</title>
|
|
201
|
-
</svg>
|
|
202
|
-
</span>
|
|
203
|
-
<span class="v-align-center c21-label-button">
|
|
204
|
-
{{translationMap?.get('CLOSE_CHAT')}}
|
|
205
|
-
</span>
|
|
206
|
-
<div class="clear"></div>
|
|
207
|
-
</button>
|
|
208
199
|
</div>
|
|
209
|
-
|
|
210
200
|
</div>
|
|
211
201
|
|
|
212
202
|
|
|
@@ -218,23 +208,22 @@
|
|
|
218
208
|
[attr.aria-hidden]="!isEmojiiPickerShow"
|
|
219
209
|
[attr.aria-label]="translationMap?.get('EMOJI')"
|
|
220
210
|
#emoji_mart_container>
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
</emoji-mart>
|
|
211
|
+
<emoji-mart id="emoji-mart"
|
|
212
|
+
*ngIf="showEmojiPicker"
|
|
213
|
+
class="emoji-mart"
|
|
214
|
+
[showPreview]="emojiiOptions?.showPreview"
|
|
215
|
+
[color]="stylesMap?.get('themeColor')"
|
|
216
|
+
[perLine]="emojiiOptions?.emojiPerLine"
|
|
217
|
+
[totalFrequentLines]="emojiiOptions?.totalFrequentLines"
|
|
218
|
+
[enableSearch]="emojiiOptions?.enableSearch"
|
|
219
|
+
[darkMode]="emojiiOptions?.darkMode"
|
|
220
|
+
[include]="emojiiOptions?.include"
|
|
221
|
+
(emojiSelect)="addEmoji($event)">
|
|
222
|
+
</emoji-mart>
|
|
234
223
|
</div>
|
|
235
224
|
|
|
236
225
|
<!-- NEW CONV & CONTINE buttons: conv archived-->
|
|
237
|
-
<div id="floating-container" *ngIf="hideTextAreaContent" class="fade-in-bottom" start-focus-chat21-conversation-component>
|
|
226
|
+
<div id="floating-container" *ngIf="hideTextAreaContent" class="fade-in-bottom" start-focus-chat21-conversation-component>
|
|
238
227
|
<button type="button" aflistconv #aflistconv class="c21-button-primary" (click)="openNewConversation()" [ngStyle]="{'background-color': stylesMap.get('themeColor'), 'border-color': stylesMap.get('themeColor'), 'color': stylesMap?.get('foregroundColor')}">
|
|
239
228
|
<span class="v-align-center">
|
|
240
229
|
<svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
|
|
@@ -243,7 +232,7 @@
|
|
|
243
232
|
</span>
|
|
244
233
|
<span class="v-align-center c21-label-button">
|
|
245
234
|
{{translationMap?.get('LABEL_START_NW_CONV')}}
|
|
246
|
-
</span>
|
|
235
|
+
</span>
|
|
247
236
|
<div class="clear"></div>
|
|
248
237
|
</button>
|
|
249
238
|
</div>
|
package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
gap: 8px;
|
|
5
|
-
}
|
|
1
|
+
|
|
6
2
|
.textarea-container{
|
|
3
|
+
// padding: 8px 34px;
|
|
4
|
+
// padding-left: 70px;
|
|
5
|
+
// padding-right: 45px;
|
|
7
6
|
display: flex;
|
|
7
|
+
// width: 100%;
|
|
8
8
|
align-items: center;
|
|
9
9
|
justify-content: space-between;
|
|
10
10
|
gap: 8px;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
&:has(:not(#chat21-start-upload-doc)):has(:not(#chat21-emoticon-picker)) .visible-text-area {
|
|
18
18
|
width: 90%;
|
|
19
19
|
}
|
|
20
|
+
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
.close-chat-container{
|
|
@@ -25,11 +26,6 @@
|
|
|
25
26
|
align-items: center;
|
|
26
27
|
justify-content: center;
|
|
27
28
|
gap: 8px;
|
|
28
|
-
|
|
29
|
-
.c21-close{
|
|
30
|
-
height: 30px !important;
|
|
31
|
-
margin: 0px !important;
|
|
32
|
-
}
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
.icons-container{
|
|
@@ -62,6 +58,15 @@
|
|
|
62
58
|
display: flex;
|
|
63
59
|
align-items: center;
|
|
64
60
|
}
|
|
61
|
+
|
|
62
|
+
//if attachment icon OR emoji icon is not in DOM -> increment textarea width
|
|
63
|
+
&:has(:not(#chat21-start-upload-doc), :not(#chat21-emoticon-picker)) .visible-text-area {
|
|
64
|
+
width: 80%;
|
|
65
|
+
}
|
|
66
|
+
//if attachment icon AND emoji icon is not in DOM -> increment textarea width
|
|
67
|
+
&:has(:not(#chat21-start-upload-doc)):has(:not(#chat21-emoticon-picker)) .visible-text-area {
|
|
68
|
+
width: 90%;
|
|
69
|
+
}
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
.chat21-textarea-button {
|
|
@@ -429,7 +434,8 @@ textarea:active{
|
|
|
429
434
|
position: absolute;
|
|
430
435
|
// padding: 8px 16px;
|
|
431
436
|
overflow: hidden;
|
|
432
|
-
background-color:
|
|
437
|
+
background-color: var(--content-background-color);
|
|
438
|
+
// background-color: color-mix(in srgb, var(--content-background-color) 34%, transparent);
|
|
433
439
|
backdrop-filter: blur(20px) saturate(1.2);
|
|
434
440
|
-webkit-backdrop-filter: blur(20px) saturate(1.2);
|
|
435
441
|
box-shadow:
|
|
@@ -16,14 +16,12 @@ import { ConversationHandlerService } from 'src/chat21-core/providers/abstract/c
|
|
|
16
16
|
import { VoiceService } from 'src/app/providers/voice/voice.service';
|
|
17
17
|
import { TtsAudioPlaybackCoordinator } from 'src/app/providers/tts-audio-playback-coordinator.service';
|
|
18
18
|
import { TiledeskAuthService } from 'src/chat21-core/providers/tiledesk/tiledesk-auth.service';
|
|
19
|
+
import { Globals } from 'src/app/utils/globals';
|
|
19
20
|
|
|
20
21
|
describe('ConversationFooterComponent', () => {
|
|
21
22
|
let component: ConversationFooterComponent;
|
|
22
23
|
let fixture: ComponentFixture<ConversationFooterComponent>;
|
|
23
24
|
|
|
24
|
-
const ngxlogger = jasmine.createSpyObj('NGXLogger', ['log', 'trace', 'debug', 'warn', 'error', 'info']);
|
|
25
|
-
const customLogger = new CustomLogger(ngxlogger);
|
|
26
|
-
|
|
27
25
|
const voiceServiceMock = {
|
|
28
26
|
startSession: () => Promise.resolve(),
|
|
29
27
|
stopSession: () => Promise.resolve({ voiceIngressResultUrl: null as string | null }),
|
|
@@ -32,7 +30,14 @@ describe('ConversationFooterComponent', () => {
|
|
|
32
30
|
volume$: { subscribe: () => ({ unsubscribe: () => undefined }) },
|
|
33
31
|
isAcquisitionBlocked$: { subscribe: () => ({ unsubscribe: () => undefined }) },
|
|
34
32
|
};
|
|
35
|
-
const ttsMock = {
|
|
33
|
+
const ttsMock = {
|
|
34
|
+
cancelAll: () => undefined,
|
|
35
|
+
stopAll: () => undefined,
|
|
36
|
+
isTTSPlaying$: { subscribe: () => ({ unsubscribe: () => undefined }) },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const ngxlogger = jasmine.createSpyObj('NGXLogger', ['log', 'trace', 'debug', 'warn', 'error', 'info']);
|
|
40
|
+
const customLogger = new CustomLogger(ngxlogger);
|
|
36
41
|
|
|
37
42
|
const conversationHandlerStub = {
|
|
38
43
|
sendMessage: jasmine.createSpy('sendMessage').and.returnValue({ uid: 'm1' }),
|
|
@@ -60,6 +65,7 @@ describe('ConversationFooterComponent', () => {
|
|
|
60
65
|
imports: [FormsModule, ReactiveFormsModule],
|
|
61
66
|
providers: [
|
|
62
67
|
FormBuilder,
|
|
68
|
+
Globals,
|
|
63
69
|
{ provide: ChatManager, useValue: chatManagerStub },
|
|
64
70
|
{ provide: TypingService, useValue: typingStub },
|
|
65
71
|
{ provide: UploadService, useValue: uploadServiceStub as unknown as UploadService },
|
package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts
CHANGED
|
@@ -41,6 +41,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
41
41
|
@Input() showAudioRecorderFooterButton: boolean;
|
|
42
42
|
@Input() showAudioStreamFooterButton: boolean;
|
|
43
43
|
// @Input() showContinueConversationButton: boolean;
|
|
44
|
+
@Input() closeChatInConversation: boolean;
|
|
44
45
|
@Input() isConversationArchived: boolean;
|
|
45
46
|
@Input() hideTextAreaContent: boolean;
|
|
46
47
|
@Input() hideTextReply: boolean;
|
|
@@ -48,7 +49,6 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
48
49
|
@Input() isEmojiiPickerShow: boolean;
|
|
49
50
|
@Input() footerMessagePlaceholder: string;
|
|
50
51
|
@Input() fileUploadAccept: string;
|
|
51
|
-
@Input() closeChatInConversation: boolean;
|
|
52
52
|
@Input() dropEvent: Event;
|
|
53
53
|
@Input() poweredBy: string;
|
|
54
54
|
@Input() stylesMap: Map<string, string>
|
|
@@ -124,15 +124,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
124
124
|
return this.translationMap?.get('VOICE_LISTENING') || 'Listening...';
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
get maxAttachmentLabel(): string {
|
|
128
|
-
const template = this.translationMap?.get('MAX_ATTACHMENT')
|
|
129
|
-
|| `Max allowed size {{FILE_SIZE_LIMIT}}Mb`;
|
|
130
|
-
return template.replace(/\{\{FILE_SIZE_LIMIT\}\}/g, String(this.file_size_limit));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
127
|
file_size_limit = FILE_SIZE_LIMIT;
|
|
134
128
|
attachmentTooltip: string = '';
|
|
135
|
-
isErrorNetwork: boolean = false;
|
|
136
129
|
|
|
137
130
|
|
|
138
131
|
convertColorToRGBA = convertColorToRGBA;
|
|
@@ -155,6 +148,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
155
148
|
if(changes['conversationWith'] && changes['conversationWith'].currentValue !== undefined){
|
|
156
149
|
this.conversationHandlerService = this.chatManager.getConversationHandlerByConversationId(this.conversationWith);
|
|
157
150
|
this.isStreamAudioActive = false;
|
|
151
|
+
this.ttsPlayback.cancelAll();
|
|
158
152
|
void this.stopVoice();
|
|
159
153
|
}
|
|
160
154
|
if(changes['hideTextReply'] && changes['hideTextReply'].currentValue !== undefined){
|
|
@@ -857,6 +851,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
857
851
|
} catch (e) {
|
|
858
852
|
this.logger.error('[CONV-FOOTER] onStreamPressed: initVoice failed', e);
|
|
859
853
|
this.isStreamAudioActive = false;
|
|
854
|
+
this.ttsPlayback.cancelAll();
|
|
860
855
|
} finally {
|
|
861
856
|
this.isStreamAudioConnecting = false;
|
|
862
857
|
this.onStreamAudioConnectingChange.emit(false);
|
|
@@ -864,6 +859,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
864
859
|
} else {
|
|
865
860
|
await this.stopVoice();
|
|
866
861
|
this.isStreamAudioActive = false;
|
|
862
|
+
// Close-stream-button clicked: stop any playing/queued TTS audio.
|
|
863
|
+
this.ttsPlayback.cancelAll();
|
|
867
864
|
this.isStreamAudioConnecting = false;
|
|
868
865
|
this.onStreamAudioConnectingChange.emit(false);
|
|
869
866
|
}
|
|
@@ -20,7 +20,6 @@ export class FormTextComponent implements OnInit, OnDestroy {
|
|
|
20
20
|
@ViewChild('div_input') input: ElementRef;
|
|
21
21
|
form: FormGroup<any>;
|
|
22
22
|
inputType: string = 'text'
|
|
23
|
-
private valueChangesSub?: Subscription;
|
|
24
23
|
|
|
25
24
|
get fieldBaseId(): string {
|
|
26
25
|
const raw = this.element?.name || this.controlName || 'field';
|
|
@@ -46,6 +45,7 @@ export class FormTextComponent implements OnInit, OnDestroy {
|
|
|
46
45
|
}
|
|
47
46
|
return this.form.controls[name].invalid ? 'true' : 'false';
|
|
48
47
|
}
|
|
48
|
+
private valueChangesSub?: Subscription;
|
|
49
49
|
|
|
50
50
|
constructor(private rootFormGroup: FormGroupDirective,
|
|
51
51
|
private elementRef: ElementRef) { }
|
|
@@ -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,
|
|
15
|
+
import { commandToMessage, conversationToMessage, isEmojii, isFrame, isImage, isSameSender } from 'src/chat21-core/utils/utils-message';
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@Component({
|
|
@@ -59,9 +59,6 @@ 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;
|
|
65
62
|
|
|
66
63
|
if(this.conversation.attributes && this.conversation.attributes.commands){
|
|
67
64
|
this.addCommandMessage(this.conversation)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
1
|
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
3
|
+
import { Subject } from 'rxjs';
|
|
3
4
|
|
|
4
5
|
import { AudioSyncComponent } from './audio-sync.component';
|
|
5
6
|
import { TtsAudioPlaybackCoordinator } from 'src/app/providers/tts-audio-playback-coordinator.service';
|
|
@@ -9,12 +10,22 @@ import { Globals } from 'src/app/utils/globals';
|
|
|
9
10
|
describe('AudioSyncComponent', () => {
|
|
10
11
|
let component: AudioSyncComponent;
|
|
11
12
|
let fixture: ComponentFixture<AudioSyncComponent>;
|
|
12
|
-
let voiceService: {
|
|
13
|
+
let voiceService: {
|
|
14
|
+
proxyTtsStreamUrl: string | null;
|
|
15
|
+
proxyTtsUrl: string | null;
|
|
16
|
+
speechStart$: ReturnType<Subject<void>['asObservable']>;
|
|
17
|
+
};
|
|
13
18
|
|
|
14
19
|
beforeEach(async () => {
|
|
20
|
+
const speechStartSource = new Subject<void>();
|
|
21
|
+
const cancelAllSource = new Subject<void>();
|
|
22
|
+
const stopAllSource = new Subject<void>();
|
|
23
|
+
const preemptSource = new Subject<string>();
|
|
24
|
+
|
|
15
25
|
voiceService = {
|
|
16
26
|
proxyTtsStreamUrl: 'https://speech.example.com/api/tts/stream',
|
|
17
27
|
proxyTtsUrl: 'https://speech.example.com/api/tts',
|
|
28
|
+
speechStart$: speechStartSource.asObservable(),
|
|
18
29
|
};
|
|
19
30
|
|
|
20
31
|
await TestBed.configureTestingModule({
|
|
@@ -27,15 +38,15 @@ describe('AudioSyncComponent', () => {
|
|
|
27
38
|
requestStart: (_ownerId: string, start: () => void) => start(),
|
|
28
39
|
releaseIfCurrent: jasmine.createSpy('releaseIfCurrent'),
|
|
29
40
|
release: jasmine.createSpy('release'),
|
|
30
|
-
stopAllPlayback$:
|
|
31
|
-
preemptPlayback$:
|
|
41
|
+
stopAllPlayback$: stopAllSource.asObservable(),
|
|
42
|
+
preemptPlayback$: preemptSource.asObservable(),
|
|
43
|
+
cancelAll$: cancelAllSource.asObservable(),
|
|
32
44
|
},
|
|
33
45
|
},
|
|
34
46
|
{ provide: Globals, useValue: { tiledeskToken: 'JWT test-token', jwt: '' } },
|
|
35
47
|
{ provide: VoiceService, useValue: voiceService },
|
|
36
48
|
],
|
|
37
|
-
})
|
|
38
|
-
.compileComponents();
|
|
49
|
+
}).compileComponents();
|
|
39
50
|
|
|
40
51
|
fixture = TestBed.createComponent(AudioSyncComponent);
|
|
41
52
|
component = fixture.componentInstance;
|
|
@@ -79,7 +90,6 @@ describe('AudioSyncComponent', () => {
|
|
|
79
90
|
|
|
80
91
|
expect(body).toEqual({
|
|
81
92
|
text: 'hello',
|
|
82
|
-
streaming: true,
|
|
83
93
|
outputFormat: 'mp3_44100_128',
|
|
84
94
|
});
|
|
85
95
|
});
|
|
@@ -96,7 +106,6 @@ describe('AudioSyncComponent', () => {
|
|
|
96
106
|
|
|
97
107
|
expect(body).toEqual({
|
|
98
108
|
text: 'hello',
|
|
99
|
-
streaming: true,
|
|
100
109
|
outputFormat: 'pcm_16000',
|
|
101
110
|
});
|
|
102
111
|
});
|