@chat21/chat21-web-widget 5.1.32-rc9 → 5.1.33-rc8
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/.angular-mcp-cache/package.json +1 -0
- package/.cursor/angular18-accessibility-auditor-skill.md +442 -0
- package/.cursor/mcp.json +15 -0
- package/.github/workflows/playwright.yml +27 -0
- package/.playwright-mcp/console-2026-05-08T15-31-09-000Z.log +17 -0
- package/.playwright-mcp/console-2026-05-08T15-32-19-412Z.log +89 -0
- package/.playwright-mcp/console-2026-05-08T16-18-48-424Z.log +133 -0
- package/.playwright-mcp/console-2026-05-11T12-54-06-869Z.log +13 -0
- package/.playwright-mcp/console-2026-05-11T12-54-56-229Z.log +147 -0
- package/.playwright-mcp/console-2026-05-11T12-55-47-174Z.log +183 -0
- package/.playwright-mcp/console-2026-05-11T15-34-03-590Z.log +210 -0
- package/.playwright-mcp/console-2026-05-12T15-07-31-880Z.log +118 -0
- package/.playwright-mcp/page-2026-05-08T15-32-19-900Z.yml +851 -0
- package/.playwright-mcp/page-2026-05-08T15-32-47-264Z.yml +857 -0
- package/.playwright-mcp/page-2026-05-08T15-33-17-089Z.yml +1110 -0
- package/.playwright-mcp/page-2026-05-08T15-33-23-486Z.yml +1069 -0
- package/.playwright-mcp/page-2026-05-08T15-33-45-390Z.yml +1076 -0
- package/.playwright-mcp/page-2026-05-08T15-33-52-666Z.yml +1072 -0
- package/.playwright-mcp/page-2026-05-08T15-34-01-338Z.yml +1085 -0
- package/.playwright-mcp/page-2026-05-08T15-34-07-227Z.yml +1072 -0
- package/.playwright-mcp/page-2026-05-08T15-34-13-875Z.yml +1072 -0
- package/.playwright-mcp/page-2026-05-08T15-34-21-885Z.yml +1109 -0
- package/.playwright-mcp/page-2026-05-08T15-34-32-755Z.yml +1109 -0
- package/.playwright-mcp/page-2026-05-08T15-35-09-607Z.yml +1119 -0
- package/.playwright-mcp/page-2026-05-08T15-35-14-242Z.yml +1109 -0
- package/.playwright-mcp/page-2026-05-08T16-18-48-671Z.yml +44 -0
- package/.playwright-mcp/page-2026-05-08T16-18-52-753Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-19-13-919Z.yml +68 -0
- package/.playwright-mcp/page-2026-05-08T16-19-17-977Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-19-25-733Z.yml +120 -0
- package/.playwright-mcp/page-2026-05-08T16-19-29-252Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-19-39-269Z.yml +80 -0
- package/.playwright-mcp/page-2026-05-08T16-19-43-915Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-20-04-407Z.yml +81 -0
- package/.playwright-mcp/page-2026-05-08T16-20-08-984Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-20-32-397Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-20-58-658Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-21-12-320Z.yml +86 -0
- package/.playwright-mcp/page-2026-05-08T16-21-39-154Z.yml +91 -0
- package/.playwright-mcp/page-2026-05-08T16-21-45-420Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-22-21-062Z.yml +0 -0
- package/.playwright-mcp/page-2026-05-08T16-22-58-232Z.yml +91 -0
- package/.playwright-mcp/page-2026-05-08T16-23-36-520Z.yml +0 -0
- package/.playwright-mcp/page-2026-05-08T16-23-46-805Z.yml +100 -0
- package/.playwright-mcp/page-2026-05-08T16-23-55-169Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-24-26-574Z.yml +91 -0
- package/.playwright-mcp/page-2026-05-08T16-25-34-414Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-25-59-831Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-26-21-809Z.yml +91 -0
- package/.playwright-mcp/page-2026-05-08T16-26-47-443Z.yml +105 -0
- package/.playwright-mcp/page-2026-05-08T16-26-56-136Z.png +0 -0
- package/.playwright-mcp/page-2026-05-08T16-27-59-610Z.yml +48 -0
- package/.playwright-mcp/page-2026-05-11T12-54-07-180Z.yml +44 -0
- package/.playwright-mcp/page-2026-05-11T12-54-56-946Z.yml +4 -0
- package/.playwright-mcp/page-2026-05-11T12-55-47-503Z.yml +24 -0
- package/.playwright-mcp/page-2026-05-11T12-56-00-766Z.yml +28 -0
- package/.playwright-mcp/page-2026-05-11T12-56-06-438Z.yml +90 -0
- package/.playwright-mcp/page-2026-05-11T12-57-56-838Z.yml +106 -0
- package/.playwright-mcp/page-2026-05-11T12-58-00-124Z.yml +106 -0
- package/.playwright-mcp/page-2026-05-11T12-59-08-836Z.yml +61 -0
- package/.playwright-mcp/page-2026-05-11T12-59-12-088Z.yml +61 -0
- package/.playwright-mcp/page-2026-05-11T12-59-26-215Z.yml +69 -0
- package/.playwright-mcp/page-2026-05-11T12-59-29-519Z.yml +69 -0
- package/.playwright-mcp/page-2026-05-11T12-59-37-309Z.yml +0 -0
- package/.playwright-mcp/page-2026-05-11T12-59-39-968Z.yml +79 -0
- package/.playwright-mcp/page-2026-05-11T12-59-45-983Z.yml +78 -0
- package/.playwright-mcp/page-2026-05-11T12-59-49-951Z.yml +78 -0
- package/.playwright-mcp/page-2026-05-11T15-34-04-515Z.yml +0 -0
- package/.playwright-mcp/page-2026-05-12T15-07-32-171Z.yml +44 -0
- package/.playwright-mcp/page-2026-05-12T15-08-09-820Z.yml +119 -0
- package/CHANGELOG.md +54 -4
- package/angular.json +20 -3
- package/deploy_amazon_beta.sh +7 -17
- package/deploy_amazon_prod.sh +41 -0
- package/docs/TILEDESK_WIDGET_ACCESSIBILITY_STATEMENT_COMPLETE.md +379 -0
- package/env.sample +3 -2
- package/mocks/voice-websocket-mock/server.cjs +245 -0
- package/package.json +7 -3
- package/playwright-report/index.html +90 -0
- package/playwright.config.ts +41 -0
- package/src/app/app.component.html +2 -2
- package/src/app/app.component.scss +25 -14
- package/src/app/app.component.spec.ts +21 -6
- package/src/app/app.module.ts +4 -0
- package/src/app/component/conversation-detail/conversation/conversation.component.html +19 -11
- package/src/app/component/conversation-detail/conversation/conversation.component.scss +28 -0
- package/src/app/component/conversation-detail/conversation/conversation.component.spec.ts +644 -75
- package/src/app/component/conversation-detail/conversation/conversation.component.ts +61 -17
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +25 -13
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +123 -5
- package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +1 -0
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +17 -7
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +15 -3
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +249 -149
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +0 -1
- package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +53 -3
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component copy.html +172 -0
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +112 -62
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +133 -7
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +452 -78
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +192 -84
- package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +113 -53
- package/src/app/component/conversation-detail/conversation-header/conversation-header.component.scss +12 -4
- package/src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts +274 -29
- package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.html +23 -9
- package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts +80 -8
- package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.html +29 -23
- package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts +185 -16
- package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +34 -14
- package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +43 -18
- package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +56 -2
- package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +135 -5
- package/src/app/component/error-alert/error-alert.component.spec.ts +65 -5
- package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.html +16 -7
- package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.scss +21 -0
- package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts +89 -7
- package/src/app/component/form/form-builder/form-builder.component.html +1 -1
- package/src/app/component/form/form-builder/form-builder.component.spec.ts +163 -21
- package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.html +8 -4
- package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.scss +10 -5
- package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts +90 -16
- package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts +26 -0
- package/src/app/component/form/inputs/form-label/form-label.component.spec.ts +45 -11
- package/src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts +24 -6
- package/src/app/component/form/inputs/form-select/form-select.component.spec.ts +14 -5
- package/src/app/component/form/inputs/form-text/form-text.component.html +14 -12
- package/src/app/component/form/inputs/form-text/form-text.component.scss +11 -1
- package/src/app/component/form/inputs/form-text/form-text.component.spec.ts +113 -17
- package/src/app/component/form/inputs/form-text/form-text.component.ts +26 -0
- package/src/app/component/form/inputs/form-textarea/form-textarea.component.html +13 -11
- package/src/app/component/form/inputs/form-textarea/form-textarea.component.scss +6 -5
- package/src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts +149 -13
- package/src/app/component/form/inputs/form-textarea/form-textarea.component.ts +26 -0
- package/src/app/component/form/prechat-form/prechat-form.component.html +14 -11
- package/src/app/component/form/prechat-form/prechat-form.component.spec.ts +102 -10
- package/src/app/component/form/prechat-form/prechat-form.component.ts +8 -1
- package/src/app/component/form/prechat-form-test-mock.ts +35 -0
- package/src/app/component/home/home.component.html +38 -31
- package/src/app/component/home/home.component.scss +4 -2
- package/src/app/component/home/home.component.spec.ts +226 -11
- package/src/app/component/home-conversations/home-conversations.component.html +30 -26
- package/src/app/component/home-conversations/home-conversations.component.scss +3 -0
- package/src/app/component/home-conversations/home-conversations.component.spec.ts +212 -36
- package/src/app/component/last-message/last-message.component.html +15 -9
- package/src/app/component/last-message/last-message.component.scss +16 -2
- package/src/app/component/last-message/last-message.component.spec.ts +204 -23
- package/src/app/component/launcher-button/launcher-button.component.html +8 -13
- package/src/app/component/launcher-button/launcher-button.component.spec.ts +104 -8
- package/src/app/component/list-all-conversations/list-all-conversations.component.html +12 -17
- package/src/app/component/list-all-conversations/list-all-conversations.component.scss +2 -0
- package/src/app/component/list-conversations/list-conversations.component.html +22 -22
- package/src/app/component/menu-options/menu-options.component.html +30 -20
- package/src/app/component/menu-options/menu-options.component.spec.ts +125 -9
- package/src/app/component/message/audio/audio.component.html +13 -15
- package/src/app/component/message/audio/audio.component.spec.ts +140 -5
- package/src/app/component/message/audio/audio.component.ts +1 -0
- package/src/app/component/message/audio-sync/audio-sync.component.scss +1 -0
- package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +81 -1
- package/src/app/component/message/audio-sync/audio-sync.component.ts +133 -86
- package/src/app/component/message/avatar/avatar.component.html +2 -2
- package/src/app/component/message/avatar/avatar.component.spec.ts +99 -7
- package/src/app/component/message/bubble-message/bubble-message.component.html +39 -52
- package/src/app/component/message/bubble-message/bubble-message.component.scss +54 -1
- package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +154 -57
- package/src/app/component/message/bubble-message/bubble-message.component.ts +138 -110
- package/src/app/component/message/buttons/action-button/action-button.component.html +3 -4
- package/src/app/component/message/buttons/action-button/action-button.component.spec.ts +49 -5
- package/src/app/component/message/buttons/link-button/link-button.component.scss +5 -8
- package/src/app/component/message/buttons/link-button/link-button.component.spec.ts +50 -5
- package/src/app/component/message/buttons/text-button/text-button.component.spec.ts +44 -5
- package/src/app/component/message/carousel/carousel.component.html +29 -16
- package/src/app/component/message/carousel/carousel.component.scss +20 -8
- package/src/app/component/message/carousel/carousel.component.spec.ts +80 -3
- package/src/app/component/message/carousel/carousel.component.ts +16 -0
- package/src/app/component/message/frame/frame.component.html +9 -4
- package/src/app/component/message/frame/frame.component.spec.ts +34 -15
- package/src/app/component/message/frame/frame.component.ts +7 -2
- package/src/app/component/message/html/html.component.html +1 -1
- package/src/app/component/message/html/html.component.scss +1 -1
- package/src/app/component/message/html/html.component.spec.ts +24 -7
- package/src/app/component/message/image/image.component.html +12 -10
- package/src/app/component/message/image/image.component.scss +16 -0
- package/src/app/component/message/image/image.component.spec.ts +101 -15
- package/src/app/component/message/image/image.component.ts +90 -51
- package/src/app/component/message/info-message/info-message.component.spec.ts +26 -14
- package/src/app/component/message/json-sources/json-sources.component.html +38 -0
- package/src/app/component/message/json-sources/json-sources.component.scss +197 -0
- package/src/app/component/message/json-sources/json-sources.component.ts +89 -0
- package/src/app/component/message/like-unlike/like-unlike.component.html +7 -9
- package/src/app/component/message/like-unlike/like-unlike.component.spec.ts +31 -3
- package/src/app/component/message/return-receipt/return-receipt.component.spec.ts +38 -17
- package/src/app/component/message/text/text.component.html +3 -3
- package/src/app/component/message/text/text.component.scss +80 -86
- package/src/app/component/message/text/text.component.spec.ts +106 -13
- package/src/app/component/message-attachment/message-attachment.component.spec.ts +134 -13
- package/src/app/component/selection-department/selection-department.component.html +21 -23
- package/src/app/component/selection-department/selection-department.component.spec.ts +159 -14
- package/src/app/component/selection-department/selection-department.component.ts +8 -1
- package/src/app/component/send-button/send-button.component.html +5 -13
- package/src/app/component/send-button/send-button.component.spec.ts +2 -2
- package/src/app/component/star-rating-widget/star-rating-widget.component.html +51 -81
- package/src/app/directives/tooltip.directive.spec.ts +8 -4
- package/src/app/modals/confirm-close/confirm-close.component.html +20 -8
- package/src/app/modals/confirm-close/confirm-close.component.scss +3 -0
- package/src/app/modals/confirm-close/confirm-close.component.spec.ts +13 -4
- package/src/app/modals/confirm-close/confirm-close.component.ts +8 -1
- package/src/app/pipe/html-entites-encode.pipe.spec.ts +35 -2
- package/src/app/pipe/marked.pipe.spec.ts +38 -2
- package/src/app/providers/app-config.service.ts +4 -2
- package/src/app/providers/brand.service.spec.ts +23 -2
- package/src/app/providers/brand.service.ts +1 -1
- package/src/app/providers/global-settings.service.spec.ts +1009 -14
- package/src/app/providers/global-settings.service.ts +30 -2
- package/src/app/providers/json-sources-parser.service.ts +182 -0
- package/src/app/providers/translator.service.ts +24 -6
- package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +117 -0
- package/src/app/providers/tts-audio-playback-coordinator.service.ts +39 -16
- package/src/app/providers/url-preview.service.ts +82 -0
- package/src/app/providers/voice/audio.types.ts +6 -0
- package/src/app/providers/voice/voice-streaming.service.spec.ts +23 -0
- package/src/app/providers/voice/voice-streaming.service.ts +710 -0
- package/src/app/providers/voice/voice-streaming.types.ts +113 -0
- package/src/app/providers/voice/voice.service.spec.ts +203 -3
- package/src/app/providers/voice/voice.service.ts +517 -13
- package/src/app/sass/_variables.scss +1 -1
- package/src/app/sass/animations.scss +19 -1
- package/src/app/utils/globals.ts +4 -0
- package/src/app/utils/json-sources-utils.ts +27 -0
- package/src/app/utils/url-utils.ts +98 -0
- package/src/app/utils/utils-resources.ts +1 -1
- package/src/assets/i18n/en.json +26 -1
- package/src/assets/i18n/es.json +106 -101
- package/src/assets/i18n/fr.json +106 -101
- package/src/assets/i18n/it.json +106 -99
- package/src/assets/twp/index-dev.html +18 -0
- package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +14 -0
- package/src/chat21-core/providers/chat-manager.spec.ts +72 -0
- package/src/chat21-core/providers/scripts/script.service.spec.ts +12 -2
- package/src/chat21-core/utils/constants.ts +4 -0
- package/src/chat21-core/utils/utils-message.ts +23 -1
- package/src/widget-config-template.json +3 -1
- package/src/widget-config.json +28 -27
- package/test-results/.last-run.json +4 -0
- package/tests/widget-form-rich.spec.ts +67 -0
- package/tests/widget-index-dev-settings.spec.ts +52 -0
- package/tests/widget-twp-iframe.spec.ts +39 -0
package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts
CHANGED
|
@@ -17,7 +17,9 @@ import { findAndRemoveEmoji, isImage } from 'src/chat21-core/utils/utils-message
|
|
|
17
17
|
import { ProjectModel } from 'src/models/project';
|
|
18
18
|
import { Subscription } from 'rxjs';
|
|
19
19
|
import { VoiceService } from 'src/app/providers/voice/voice.service';
|
|
20
|
+
import { VoiceStreamingSessionConfig } from 'src/app/providers/voice/voice-streaming.types';
|
|
20
21
|
import { TtsAudioPlaybackCoordinator } from 'src/app/providers/tts-audio-playback-coordinator.service';
|
|
22
|
+
import { TiledeskAuthService } from 'src/chat21-core/providers/tiledesk/tiledesk-auth.service';
|
|
21
23
|
|
|
22
24
|
@Component({
|
|
23
25
|
selector: 'chat-conversation-footer',
|
|
@@ -58,6 +60,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
58
60
|
@Output() onAttachmentFileButtonClicked = new EventEmitter<any>();
|
|
59
61
|
@Output() onNewConversationButtonClicked = new EventEmitter();
|
|
60
62
|
@Output() onStreamAudioActiveChange = new EventEmitter<boolean>();
|
|
63
|
+
@Output() onStreamAudioConnectingChange = new EventEmitter<boolean>();
|
|
61
64
|
@Output() onCloseChatButtonClicked = new EventEmitter();
|
|
62
65
|
|
|
63
66
|
@ViewChild('chat21_file') public chat21_file: ElementRef;
|
|
@@ -94,12 +97,32 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
94
97
|
|
|
95
98
|
/** Stream audio UI: icona equalizer → X; alert con onde animate sopra il footer */
|
|
96
99
|
isStreamAudioActive = false;
|
|
100
|
+
/** True while the WebSocket session is being established (between click and session_started). */
|
|
101
|
+
isStreamAudioConnecting = false;
|
|
102
|
+
/** True while the bot's TTS audio is playing — mic segments are suppressed, spectrum turns grey. */
|
|
103
|
+
isBotSpeaking = false;
|
|
97
104
|
/** Sottoscrizione ai segmenti audio (VAD → WebM) dal {@link VoiceService}. */
|
|
98
105
|
private voiceAudioSubscription?: Subscription;
|
|
106
|
+
/** Sottoscrizione a `transcript` finale dalla WSS. */
|
|
107
|
+
private voiceTranscriptSubscription?: Subscription;
|
|
99
108
|
/** Sottoscrizione al volume audio (real-time) dal {@link VoiceService}. */
|
|
100
109
|
private voiceVolumeSubscription?: Subscription;
|
|
110
|
+
/** Sottoscrizione allo stato TTS (bot sta parlando). */
|
|
111
|
+
private botSpeakingSub?: Subscription;
|
|
101
112
|
/** Passato a {@link StreamAudioSpectrumComponent} per disegnare la linea spettro. */
|
|
102
113
|
currentVolume = 0;
|
|
114
|
+
/** Last user utterance transcribed — persists during bot processing to show in voice panel. */
|
|
115
|
+
lastVoiceTranscript = '';
|
|
116
|
+
|
|
117
|
+
get voiceStatusLabel(): string {
|
|
118
|
+
if (this.isStreamAudioConnecting && !this.isStreamAudioActive) {
|
|
119
|
+
return this.translationMap?.get('VOICE_CONNECTING') || 'Connecting...';
|
|
120
|
+
}
|
|
121
|
+
if (this.isStreamAudioActive && this.isBotSpeaking) {
|
|
122
|
+
return this.translationMap?.get('VOICE_PROCESSING') || 'Processing...';
|
|
123
|
+
}
|
|
124
|
+
return this.translationMap?.get('VOICE_LISTENING') || 'Listening...';
|
|
125
|
+
}
|
|
103
126
|
|
|
104
127
|
file_size_limit = FILE_SIZE_LIMIT;
|
|
105
128
|
attachmentTooltip: string = '';
|
|
@@ -108,11 +131,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
108
131
|
|
|
109
132
|
convertColorToRGBA = convertColorToRGBA;
|
|
110
133
|
private logger: LoggerService = LoggerInstance.getInstance()
|
|
111
|
-
constructor(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
134
|
+
constructor(
|
|
135
|
+
private chatManager: ChatManager,
|
|
136
|
+
private typingService: TypingService,
|
|
137
|
+
private uploadService: UploadService,
|
|
138
|
+
private voiceService: VoiceService,
|
|
139
|
+
private ttsPlayback: TtsAudioPlaybackCoordinator,
|
|
140
|
+
private tiledeskAuthService: TiledeskAuthService,
|
|
141
|
+
public g: Globals,
|
|
142
|
+
) {}
|
|
116
143
|
|
|
117
144
|
ngOnInit() {
|
|
118
145
|
// this.updateAttachmentTooltip();
|
|
@@ -122,7 +149,6 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
122
149
|
if(changes['conversationWith'] && changes['conversationWith'].currentValue !== undefined){
|
|
123
150
|
this.conversationHandlerService = this.chatManager.getConversationHandlerByConversationId(this.conversationWith);
|
|
124
151
|
this.isStreamAudioActive = false;
|
|
125
|
-
this.ttsPlayback.cancelAll();
|
|
126
152
|
void this.stopVoice();
|
|
127
153
|
}
|
|
128
154
|
if(changes['hideTextReply'] && changes['hideTextReply'].currentValue !== undefined){
|
|
@@ -164,37 +190,141 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
164
190
|
// }
|
|
165
191
|
|
|
166
192
|
/**
|
|
167
|
-
*
|
|
193
|
+
* Stream voce: con `voiceIngress` solo WSS (no VAD) — transcript + TTS dal server.
|
|
194
|
+
* Senza ingresso WSS: VAD + upload per segmento.
|
|
168
195
|
*/
|
|
169
196
|
async initVoice() {
|
|
170
197
|
this.voiceAudioSubscription?.unsubscribe();
|
|
171
198
|
this.voiceVolumeSubscription?.unsubscribe();
|
|
199
|
+
this.botSpeakingSub?.unsubscribe();
|
|
200
|
+
this.voiceTranscriptSubscription?.unsubscribe();
|
|
172
201
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
202
|
+
const voiceIngress = this.buildVoiceIngressStreamConfig();
|
|
203
|
+
this.voiceAudioSubscription = undefined;
|
|
204
|
+
this.voiceTranscriptSubscription = this.voiceService.voiceTranscript$.subscribe(({ text }) => {
|
|
205
|
+
// Guard: stop accepting transcript text once the proxy is processing (thinking/speaking)
|
|
206
|
+
if (text && !this.isBotSpeaking) {
|
|
207
|
+
this.textInputTextArea = text;
|
|
208
|
+
this.lastVoiceTranscript = text;
|
|
209
|
+
// The proxy publishes the user utterance to Chat21 via AMQP on utterance-end;
|
|
210
|
+
// no sendMessage call is needed here — doing so would produce duplicate messages.
|
|
211
|
+
}
|
|
176
212
|
});
|
|
213
|
+
|
|
177
214
|
this.voiceVolumeSubscription = this.voiceService.volume$.subscribe((volume) => {
|
|
178
215
|
this.currentVolume = volume;
|
|
179
216
|
});
|
|
180
|
-
|
|
217
|
+
this.botSpeakingSub = this.voiceService.isAcquisitionBlocked$.subscribe((blocked) => {
|
|
218
|
+
this.isBotSpeaking = blocked;
|
|
219
|
+
if (blocked) {
|
|
220
|
+
// Proxy has started thinking/speaking — clear the textarea preview
|
|
221
|
+
this.textInputTextArea = '';
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
await this.voiceService.startSession(voiceIngress ? { voiceIngressStream: voiceIngress } : {});
|
|
181
225
|
}
|
|
182
226
|
|
|
227
|
+
private buildVoiceIngressStreamConfig(): VoiceStreamingSessionConfig | null {
|
|
228
|
+
const token = this.tiledeskAuthService.getTiledeskToken() ?? '';
|
|
229
|
+
const sender = this.tiledeskAuthService.getCurrentUser()?.uid ?? '';
|
|
230
|
+
const recipient = this.conversationWith ?? '';
|
|
231
|
+
if (!token || !sender || !recipient) {
|
|
232
|
+
this.logger.warn('[CONV-FOOTER] buildVoiceIngressStreamConfig: missing required fields', {
|
|
233
|
+
hasToken: !!token,
|
|
234
|
+
hasSender: !!sender,
|
|
235
|
+
hasRecipient: !!recipient,
|
|
236
|
+
});
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const { recipientFullname, attributes, channelType } = this.buildSendMessageContext();
|
|
240
|
+
this.logger.log('[CONV-FOOTER] buildVoiceIngressStreamConfig', { sender, recipient, channelType });
|
|
241
|
+
return {
|
|
242
|
+
token,
|
|
243
|
+
sender,
|
|
244
|
+
recipient,
|
|
245
|
+
// Use Deepgram multilingual code-switching so the model detects the spoken
|
|
246
|
+
// language from the audio stream regardless of browser locale.
|
|
247
|
+
// Source: https://developers.deepgram.com/docs/multilingual-code-switching
|
|
248
|
+
lang: 'multi',
|
|
249
|
+
text: '',
|
|
250
|
+
type: 'text',
|
|
251
|
+
recipient_fullname: recipientFullname ?? '',
|
|
252
|
+
sender_fullname: recipientFullname ?? '',
|
|
253
|
+
attributes: attributes ?? {},
|
|
254
|
+
metadata: '',
|
|
255
|
+
channel_type: channelType ?? '',
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Merge `attributes` di componente con `additional_attributes` e risolve `recipientFullname` come in sendMessage.
|
|
261
|
+
*/
|
|
262
|
+
private buildSendMessageContext(additional_attributes?: any) {
|
|
263
|
+
let recipientFullname = this.translationMap.get('GUEST_LABEL');
|
|
264
|
+
const g_attributes = this.attributes;
|
|
265
|
+
const attributes = <any>{};
|
|
266
|
+
if (g_attributes) {
|
|
267
|
+
for (const [key, value] of Object.entries(g_attributes)) {
|
|
268
|
+
attributes[key] = value;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (additional_attributes) {
|
|
272
|
+
for (const [key, value] of Object.entries(additional_attributes)) {
|
|
273
|
+
attributes[key] = value;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const senderId = this.senderId;
|
|
277
|
+
const projectid = this.project.id;
|
|
278
|
+
const channelType = this.channelType;
|
|
279
|
+
const userFullname = this.userFullname;
|
|
280
|
+
const userEmail = this.userEmail;
|
|
281
|
+
const conversationWith = this.conversationWith;
|
|
282
|
+
|
|
283
|
+
if (userFullname) {
|
|
284
|
+
recipientFullname = userFullname;
|
|
285
|
+
} else if (userEmail) {
|
|
286
|
+
recipientFullname = userEmail;
|
|
287
|
+
} else if (attributes && attributes['userFullname']) {
|
|
288
|
+
recipientFullname = attributes['userFullname'];
|
|
289
|
+
} else {
|
|
290
|
+
recipientFullname = this.translationMap.get('GUEST_LABEL');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
recipientFullname,
|
|
295
|
+
attributes,
|
|
296
|
+
senderId,
|
|
297
|
+
projectid,
|
|
298
|
+
channelType,
|
|
299
|
+
conversationWith,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
183
302
|
async stopVoice(options?: { discardInProgressSegment?: boolean }) {
|
|
303
|
+
// Stop all active TTS audio immediately and reveal all text.
|
|
304
|
+
this.ttsPlayback.stopAll();
|
|
305
|
+
|
|
184
306
|
this.voiceAudioSubscription?.unsubscribe();
|
|
185
307
|
this.voiceAudioSubscription = undefined;
|
|
186
308
|
|
|
309
|
+
this.voiceTranscriptSubscription?.unsubscribe();
|
|
310
|
+
this.voiceTranscriptSubscription = undefined;
|
|
311
|
+
|
|
187
312
|
this.voiceVolumeSubscription?.unsubscribe();
|
|
188
313
|
this.voiceVolumeSubscription = undefined;
|
|
189
314
|
|
|
315
|
+
this.botSpeakingSub?.unsubscribe();
|
|
316
|
+
this.botSpeakingSub = undefined;
|
|
317
|
+
this.isBotSpeaking = false;
|
|
318
|
+
|
|
190
319
|
await this.voiceService.stopSession(options);
|
|
191
320
|
this.currentVolume = 0;
|
|
321
|
+
this.textInputTextArea = '';
|
|
322
|
+
this.lastVoiceTranscript = '';
|
|
192
323
|
}
|
|
193
324
|
|
|
194
325
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* registrato in quel momento (nessun upload); mic + VAD restano attivi, `isStreamAudioActive` true.
|
|
326
|
+
* Messaggio in arrivo da un altro mittente mentre lo stream è attivo: con VAD legacy scarta il segmento in corso.
|
|
327
|
+
* Con sola sessione WSS non ha effetto sul mic (nessun recorder a segmenti locale).
|
|
198
328
|
*/
|
|
199
329
|
interruptStreamDueToPeerMessage(): void {
|
|
200
330
|
if (!this.isStreamAudioActive) {
|
|
@@ -449,40 +579,14 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
449
579
|
// msg = replaceEndOfLine(msg);
|
|
450
580
|
// msg = msg.trim();
|
|
451
581
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
if (additional_attributes) {
|
|
463
|
-
for (const [key, value] of Object.entries(additional_attributes)) {
|
|
464
|
-
attributes[key] = value;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
// fine-sponziello
|
|
468
|
-
// this.conversationHandlerService = this.chatManager.getConversationHandlerByConversationId(this.conversationWith)
|
|
469
|
-
const senderId = this.senderId;
|
|
470
|
-
const projectid = this.project.id;
|
|
471
|
-
const channelType = this.channelType;
|
|
472
|
-
const userFullname = this.userFullname;
|
|
473
|
-
const userEmail = this.userEmail;
|
|
474
|
-
const conversationWith = this.conversationWith;
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (userFullname) {
|
|
478
|
-
recipientFullname = userFullname;
|
|
479
|
-
} else if (userEmail) {
|
|
480
|
-
recipientFullname = userEmail;
|
|
481
|
-
} else if (attributes && attributes['userFullname']) {
|
|
482
|
-
recipientFullname = attributes['userFullname'];
|
|
483
|
-
} else {
|
|
484
|
-
recipientFullname = this.translationMap.get('GUEST_LABEL');
|
|
485
|
-
}
|
|
582
|
+
const {
|
|
583
|
+
recipientFullname,
|
|
584
|
+
attributes,
|
|
585
|
+
senderId,
|
|
586
|
+
projectid,
|
|
587
|
+
channelType,
|
|
588
|
+
conversationWith,
|
|
589
|
+
} = this.buildSendMessageContext(additional_attributes);
|
|
486
590
|
|
|
487
591
|
this.onBeforeMessageSent.emit({
|
|
488
592
|
senderFullname: recipientFullname,
|
|
@@ -734,8 +838,12 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
734
838
|
if (this.showAlertEmoji) {
|
|
735
839
|
return;
|
|
736
840
|
}
|
|
737
|
-
|
|
841
|
+
// Treat a click during connecting as a cancel request (same as turning off).
|
|
842
|
+
const turningOn = !this.isStreamAudioActive && !this.isStreamAudioConnecting;
|
|
843
|
+
this.logger.log('[CONV-FOOTER] onStreamPressed', { turningOn });
|
|
738
844
|
if (turningOn) {
|
|
845
|
+
this.isStreamAudioConnecting = true;
|
|
846
|
+
this.onStreamAudioConnectingChange.emit(true);
|
|
739
847
|
try {
|
|
740
848
|
this.currentVolume = 0;
|
|
741
849
|
await this.initVoice();
|
|
@@ -743,13 +851,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
743
851
|
} catch (e) {
|
|
744
852
|
this.logger.error('[CONV-FOOTER] onStreamPressed: initVoice failed', e);
|
|
745
853
|
this.isStreamAudioActive = false;
|
|
746
|
-
|
|
854
|
+
} finally {
|
|
855
|
+
this.isStreamAudioConnecting = false;
|
|
856
|
+
this.onStreamAudioConnectingChange.emit(false);
|
|
747
857
|
}
|
|
748
858
|
} else {
|
|
749
859
|
await this.stopVoice();
|
|
750
860
|
this.isStreamAudioActive = false;
|
|
751
|
-
|
|
752
|
-
this.
|
|
861
|
+
this.isStreamAudioConnecting = false;
|
|
862
|
+
this.onStreamAudioConnectingChange.emit(false);
|
|
753
863
|
}
|
|
754
864
|
this.onStreamAudioActiveChange.emit(this.isStreamAudioActive);
|
|
755
865
|
this.logger.log('[CONV-FOOTER] isStreamAudioActive', this.isStreamAudioActive);
|
|
@@ -846,48 +956,46 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
846
956
|
}
|
|
847
957
|
|
|
848
958
|
/**
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
959
|
+
* Single keyboard handler for the message textarea.
|
|
960
|
+
*
|
|
961
|
+
* - Enter (no modifier) -> send message
|
|
962
|
+
* - Shift / Alt / Ctrl / Meta + Enter -> insert a newline (default browser behavior)
|
|
963
|
+
* - Tab -> prevented, to keep focus inside the chat
|
|
964
|
+
*
|
|
965
|
+
* Modifier check is intentionally on `keydown` because `keypress` is deprecated
|
|
966
|
+
* and does not consistently fire for modifier combos across browsers.
|
|
856
967
|
* @param event
|
|
857
968
|
*/
|
|
858
|
-
|
|
969
|
+
onkeydown(event: KeyboardEvent) {
|
|
859
970
|
const keyCode = event.which || event.keyCode;
|
|
860
|
-
|
|
861
|
-
if (keyCode === 13) { // ENTER
|
|
862
|
-
|
|
863
|
-
|
|
971
|
+
|
|
972
|
+
if (keyCode === 13) { // ENTER
|
|
973
|
+
const hasModifier = event.metaKey || event.shiftKey || event.altKey || event.ctrlKey;
|
|
974
|
+
if (hasModifier) {
|
|
975
|
+
// Let the textarea insert a newline on its own (do not preventDefault).
|
|
976
|
+
return;
|
|
864
977
|
}
|
|
978
|
+
|
|
979
|
+
// Plain Enter -> send the message
|
|
980
|
+
event.preventDefault();
|
|
981
|
+
|
|
982
|
+
if (this.showAlertEmoji) {
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const target = document.getElementById('chat21-main-message-context') as HTMLInputElement;
|
|
987
|
+
if (target) {
|
|
988
|
+
this.textInputTextArea = target.value;
|
|
989
|
+
}
|
|
990
|
+
|
|
865
991
|
if (this.textInputTextArea && this.textInputTextArea.trim() !== '') {
|
|
866
|
-
// that.logger.log('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
|
|
867
|
-
// this.resizeInputField();
|
|
868
|
-
// this.messagingService.sendMessage(msg, TYPE_MSG_TEXT);
|
|
869
|
-
// this.setDepartment();
|
|
870
|
-
// this.textInputTextArea = replaceBr(this.textInputTextArea);
|
|
871
992
|
this.sendMessage(this.textInputTextArea, TYPE_MSG_TEXT);
|
|
872
|
-
// this.restoreTextArea();
|
|
873
993
|
}
|
|
874
|
-
|
|
875
|
-
event.preventDefault();
|
|
994
|
+
return;
|
|
876
995
|
}
|
|
877
|
-
}
|
|
878
996
|
|
|
879
|
-
|
|
880
|
-
/**
|
|
881
|
-
* HANDLE: cmd+enter, shiftKey+enter, alt+enter, ctrl+enter
|
|
882
|
-
* @param event
|
|
883
|
-
*/
|
|
884
|
-
onkeydown(event){
|
|
885
|
-
const keyCode = event.which || event.keyCode;
|
|
886
|
-
// metaKey -> COMMAND , shiftKey -> SHIFT, altKey -> ALT, ctrlKey -> CONTROL
|
|
887
|
-
if( (event.metaKey || event.shiftKey || event.altKey || event.ctrlKey) && keyCode===13){
|
|
997
|
+
if (keyCode === 9) { // TAB
|
|
888
998
|
event.preventDefault();
|
|
889
|
-
this.textInputTextArea += '\r\n';
|
|
890
|
-
this.resizeInputField();
|
|
891
999
|
}
|
|
892
1000
|
}
|
|
893
1001
|
|