@chat21/chat21-web-widget 5.1.32-rc8 → 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 +61 -3
- 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 +22 -9
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +23 -1
- 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 +193 -79
- 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 -19
- package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +63 -10
- package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +142 -12
- 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 -1
- 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 +134 -24
- 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 +45 -7
- 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 +521 -12
- 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,6 +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';
|
|
21
|
+
import { TtsAudioPlaybackCoordinator } from 'src/app/providers/tts-audio-playback-coordinator.service';
|
|
22
|
+
import { TiledeskAuthService } from 'src/chat21-core/providers/tiledesk/tiledesk-auth.service';
|
|
20
23
|
|
|
21
24
|
@Component({
|
|
22
25
|
selector: 'chat-conversation-footer',
|
|
@@ -57,6 +60,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
57
60
|
@Output() onAttachmentFileButtonClicked = new EventEmitter<any>();
|
|
58
61
|
@Output() onNewConversationButtonClicked = new EventEmitter();
|
|
59
62
|
@Output() onStreamAudioActiveChange = new EventEmitter<boolean>();
|
|
63
|
+
@Output() onStreamAudioConnectingChange = new EventEmitter<boolean>();
|
|
60
64
|
@Output() onCloseChatButtonClicked = new EventEmitter();
|
|
61
65
|
|
|
62
66
|
@ViewChild('chat21_file') public chat21_file: ElementRef;
|
|
@@ -93,12 +97,32 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
93
97
|
|
|
94
98
|
/** Stream audio UI: icona equalizer → X; alert con onde animate sopra il footer */
|
|
95
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;
|
|
96
104
|
/** Sottoscrizione ai segmenti audio (VAD → WebM) dal {@link VoiceService}. */
|
|
97
105
|
private voiceAudioSubscription?: Subscription;
|
|
106
|
+
/** Sottoscrizione a `transcript` finale dalla WSS. */
|
|
107
|
+
private voiceTranscriptSubscription?: Subscription;
|
|
98
108
|
/** Sottoscrizione al volume audio (real-time) dal {@link VoiceService}. */
|
|
99
109
|
private voiceVolumeSubscription?: Subscription;
|
|
110
|
+
/** Sottoscrizione allo stato TTS (bot sta parlando). */
|
|
111
|
+
private botSpeakingSub?: Subscription;
|
|
100
112
|
/** Passato a {@link StreamAudioSpectrumComponent} per disegnare la linea spettro. */
|
|
101
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
|
+
}
|
|
102
126
|
|
|
103
127
|
file_size_limit = FILE_SIZE_LIMIT;
|
|
104
128
|
attachmentTooltip: string = '';
|
|
@@ -107,10 +131,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
107
131
|
|
|
108
132
|
convertColorToRGBA = convertColorToRGBA;
|
|
109
133
|
private logger: LoggerService = LoggerInstance.getInstance()
|
|
110
|
-
constructor(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
) {}
|
|
114
143
|
|
|
115
144
|
ngOnInit() {
|
|
116
145
|
// this.updateAttachmentTooltip();
|
|
@@ -161,37 +190,141 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
161
190
|
// }
|
|
162
191
|
|
|
163
192
|
/**
|
|
164
|
-
*
|
|
193
|
+
* Stream voce: con `voiceIngress` solo WSS (no VAD) — transcript + TTS dal server.
|
|
194
|
+
* Senza ingresso WSS: VAD + upload per segmento.
|
|
165
195
|
*/
|
|
166
196
|
async initVoice() {
|
|
167
197
|
this.voiceAudioSubscription?.unsubscribe();
|
|
168
198
|
this.voiceVolumeSubscription?.unsubscribe();
|
|
199
|
+
this.botSpeakingSub?.unsubscribe();
|
|
200
|
+
this.voiceTranscriptSubscription?.unsubscribe();
|
|
169
201
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
}
|
|
173
212
|
});
|
|
213
|
+
|
|
174
214
|
this.voiceVolumeSubscription = this.voiceService.volume$.subscribe((volume) => {
|
|
175
215
|
this.currentVolume = volume;
|
|
176
216
|
});
|
|
177
|
-
|
|
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 } : {});
|
|
178
225
|
}
|
|
179
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
|
+
}
|
|
180
302
|
async stopVoice(options?: { discardInProgressSegment?: boolean }) {
|
|
303
|
+
// Stop all active TTS audio immediately and reveal all text.
|
|
304
|
+
this.ttsPlayback.stopAll();
|
|
305
|
+
|
|
181
306
|
this.voiceAudioSubscription?.unsubscribe();
|
|
182
307
|
this.voiceAudioSubscription = undefined;
|
|
183
308
|
|
|
309
|
+
this.voiceTranscriptSubscription?.unsubscribe();
|
|
310
|
+
this.voiceTranscriptSubscription = undefined;
|
|
311
|
+
|
|
184
312
|
this.voiceVolumeSubscription?.unsubscribe();
|
|
185
313
|
this.voiceVolumeSubscription = undefined;
|
|
186
314
|
|
|
315
|
+
this.botSpeakingSub?.unsubscribe();
|
|
316
|
+
this.botSpeakingSub = undefined;
|
|
317
|
+
this.isBotSpeaking = false;
|
|
318
|
+
|
|
187
319
|
await this.voiceService.stopSession(options);
|
|
188
320
|
this.currentVolume = 0;
|
|
321
|
+
this.textInputTextArea = '';
|
|
322
|
+
this.lastVoiceTranscript = '';
|
|
189
323
|
}
|
|
190
324
|
|
|
191
325
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* 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).
|
|
195
328
|
*/
|
|
196
329
|
interruptStreamDueToPeerMessage(): void {
|
|
197
330
|
if (!this.isStreamAudioActive) {
|
|
@@ -446,40 +579,14 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
446
579
|
// msg = replaceEndOfLine(msg);
|
|
447
580
|
// msg = msg.trim();
|
|
448
581
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
if (additional_attributes) {
|
|
460
|
-
for (const [key, value] of Object.entries(additional_attributes)) {
|
|
461
|
-
attributes[key] = value;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
// fine-sponziello
|
|
465
|
-
// this.conversationHandlerService = this.chatManager.getConversationHandlerByConversationId(this.conversationWith)
|
|
466
|
-
const senderId = this.senderId;
|
|
467
|
-
const projectid = this.project.id;
|
|
468
|
-
const channelType = this.channelType;
|
|
469
|
-
const userFullname = this.userFullname;
|
|
470
|
-
const userEmail = this.userEmail;
|
|
471
|
-
const conversationWith = this.conversationWith;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (userFullname) {
|
|
475
|
-
recipientFullname = userFullname;
|
|
476
|
-
} else if (userEmail) {
|
|
477
|
-
recipientFullname = userEmail;
|
|
478
|
-
} else if (attributes && attributes['userFullname']) {
|
|
479
|
-
recipientFullname = attributes['userFullname'];
|
|
480
|
-
} else {
|
|
481
|
-
recipientFullname = this.translationMap.get('GUEST_LABEL');
|
|
482
|
-
}
|
|
582
|
+
const {
|
|
583
|
+
recipientFullname,
|
|
584
|
+
attributes,
|
|
585
|
+
senderId,
|
|
586
|
+
projectid,
|
|
587
|
+
channelType,
|
|
588
|
+
conversationWith,
|
|
589
|
+
} = this.buildSendMessageContext(additional_attributes);
|
|
483
590
|
|
|
484
591
|
this.onBeforeMessageSent.emit({
|
|
485
592
|
senderFullname: recipientFullname,
|
|
@@ -731,8 +838,12 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
731
838
|
if (this.showAlertEmoji) {
|
|
732
839
|
return;
|
|
733
840
|
}
|
|
734
|
-
|
|
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 });
|
|
735
844
|
if (turningOn) {
|
|
845
|
+
this.isStreamAudioConnecting = true;
|
|
846
|
+
this.onStreamAudioConnectingChange.emit(true);
|
|
736
847
|
try {
|
|
737
848
|
this.currentVolume = 0;
|
|
738
849
|
await this.initVoice();
|
|
@@ -740,10 +851,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
740
851
|
} catch (e) {
|
|
741
852
|
this.logger.error('[CONV-FOOTER] onStreamPressed: initVoice failed', e);
|
|
742
853
|
this.isStreamAudioActive = false;
|
|
854
|
+
} finally {
|
|
855
|
+
this.isStreamAudioConnecting = false;
|
|
856
|
+
this.onStreamAudioConnectingChange.emit(false);
|
|
743
857
|
}
|
|
744
858
|
} else {
|
|
745
859
|
await this.stopVoice();
|
|
746
860
|
this.isStreamAudioActive = false;
|
|
861
|
+
this.isStreamAudioConnecting = false;
|
|
862
|
+
this.onStreamAudioConnectingChange.emit(false);
|
|
747
863
|
}
|
|
748
864
|
this.onStreamAudioActiveChange.emit(this.isStreamAudioActive);
|
|
749
865
|
this.logger.log('[CONV-FOOTER] isStreamAudioActive', this.isStreamAudioActive);
|
|
@@ -840,48 +956,46 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
840
956
|
}
|
|
841
957
|
|
|
842
958
|
/**
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
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.
|
|
850
967
|
* @param event
|
|
851
968
|
*/
|
|
852
|
-
|
|
969
|
+
onkeydown(event: KeyboardEvent) {
|
|
853
970
|
const keyCode = event.which || event.keyCode;
|
|
854
|
-
|
|
855
|
-
if (keyCode === 13) { // ENTER
|
|
856
|
-
|
|
857
|
-
|
|
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;
|
|
858
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
|
+
|
|
859
991
|
if (this.textInputTextArea && this.textInputTextArea.trim() !== '') {
|
|
860
|
-
// that.logger.log('[CONV-FOOTER] sendMessage -> ', this.textInputTextArea);
|
|
861
|
-
// this.resizeInputField();
|
|
862
|
-
// this.messagingService.sendMessage(msg, TYPE_MSG_TEXT);
|
|
863
|
-
// this.setDepartment();
|
|
864
|
-
// this.textInputTextArea = replaceBr(this.textInputTextArea);
|
|
865
992
|
this.sendMessage(this.textInputTextArea, TYPE_MSG_TEXT);
|
|
866
|
-
// this.restoreTextArea();
|
|
867
993
|
}
|
|
868
|
-
|
|
869
|
-
event.preventDefault();
|
|
994
|
+
return;
|
|
870
995
|
}
|
|
871
|
-
}
|
|
872
996
|
|
|
873
|
-
|
|
874
|
-
/**
|
|
875
|
-
* HANDLE: cmd+enter, shiftKey+enter, alt+enter, ctrl+enter
|
|
876
|
-
* @param event
|
|
877
|
-
*/
|
|
878
|
-
onkeydown(event){
|
|
879
|
-
const keyCode = event.which || event.keyCode;
|
|
880
|
-
// metaKey -> COMMAND , shiftKey -> SHIFT, altKey -> ALT, ctrlKey -> CONTROL
|
|
881
|
-
if( (event.metaKey || event.shiftKey || event.altKey || event.ctrlKey) && keyCode===13){
|
|
997
|
+
if (keyCode === 9) { // TAB
|
|
882
998
|
event.preventDefault();
|
|
883
|
-
this.textInputTextArea += '\r\n';
|
|
884
|
-
this.resizeInputField();
|
|
885
999
|
}
|
|
886
1000
|
}
|
|
887
1001
|
|