@chat21/chat21-web-widget 5.1.32-rc8 → 5.1.33-rc11
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 +68 -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.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 +63 -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 +242 -149
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +7 -6
- 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 -61
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +133 -16
- 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 +199 -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 +46 -19
- package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +67 -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 +35 -3
- 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 +59 -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 +152 -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 +201 -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/pipe/marked.pipe.ts +51 -41
- 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 +59 -2
- package/src/app/providers/json-sources-parser.service.ts +175 -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 +702 -0
- package/src/app/providers/voice/voice-streaming.types.ts +112 -0
- package/src/app/providers/voice/voice.service.spec.ts +170 -3
- package/src/app/providers/voice/voice.service.ts +695 -16
- package/src/app/sass/_variables.scss +1 -1
- package/src/app/sass/animations.scss +19 -1
- package/src/app/utils/globals.ts +14 -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 +106 -100
- package/src/assets/i18n/es.json +107 -101
- package/src/assets/i18n/fr.json +107 -101
- package/src/assets/i18n/it.json +107 -99
- package/src/assets/sounds/keyboard.mp3 +0 -0
- 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/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,38 @@ 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
|
+
}
|
|
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
|
+
}
|
|
102
132
|
|
|
103
133
|
file_size_limit = FILE_SIZE_LIMIT;
|
|
104
134
|
attachmentTooltip: string = '';
|
|
@@ -107,10 +137,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
107
137
|
|
|
108
138
|
convertColorToRGBA = convertColorToRGBA;
|
|
109
139
|
private logger: LoggerService = LoggerInstance.getInstance()
|
|
110
|
-
constructor(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
140
|
+
constructor(
|
|
141
|
+
private chatManager: ChatManager,
|
|
142
|
+
private typingService: TypingService,
|
|
143
|
+
private uploadService: UploadService,
|
|
144
|
+
private voiceService: VoiceService,
|
|
145
|
+
private ttsPlayback: TtsAudioPlaybackCoordinator,
|
|
146
|
+
private tiledeskAuthService: TiledeskAuthService,
|
|
147
|
+
public g: Globals,
|
|
148
|
+
) {}
|
|
114
149
|
|
|
115
150
|
ngOnInit() {
|
|
116
151
|
// this.updateAttachmentTooltip();
|
|
@@ -161,37 +196,141 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
161
196
|
// }
|
|
162
197
|
|
|
163
198
|
/**
|
|
164
|
-
*
|
|
199
|
+
* Stream voce: con `voiceIngress` solo WSS (no VAD) — transcript + TTS dal server.
|
|
200
|
+
* Senza ingresso WSS: VAD + upload per segmento.
|
|
165
201
|
*/
|
|
166
202
|
async initVoice() {
|
|
167
203
|
this.voiceAudioSubscription?.unsubscribe();
|
|
168
204
|
this.voiceVolumeSubscription?.unsubscribe();
|
|
205
|
+
this.botSpeakingSub?.unsubscribe();
|
|
206
|
+
this.voiceTranscriptSubscription?.unsubscribe();
|
|
169
207
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
208
|
+
const voiceIngress = this.buildVoiceIngressStreamConfig();
|
|
209
|
+
this.voiceAudioSubscription = undefined;
|
|
210
|
+
this.voiceTranscriptSubscription = this.voiceService.voiceTranscript$.subscribe(({ text }) => {
|
|
211
|
+
// Guard: stop accepting transcript text once the proxy is processing (thinking/speaking)
|
|
212
|
+
if (text && !this.isBotSpeaking) {
|
|
213
|
+
this.textInputTextArea = text;
|
|
214
|
+
this.lastVoiceTranscript = text;
|
|
215
|
+
// The proxy publishes the user utterance to Chat21 via AMQP on utterance-end;
|
|
216
|
+
// no sendMessage call is needed here — doing so would produce duplicate messages.
|
|
217
|
+
}
|
|
173
218
|
});
|
|
219
|
+
|
|
174
220
|
this.voiceVolumeSubscription = this.voiceService.volume$.subscribe((volume) => {
|
|
175
221
|
this.currentVolume = volume;
|
|
176
222
|
});
|
|
177
|
-
|
|
223
|
+
this.botSpeakingSub = this.voiceService.isAcquisitionBlocked$.subscribe((blocked) => {
|
|
224
|
+
this.isBotSpeaking = blocked;
|
|
225
|
+
if (blocked) {
|
|
226
|
+
// Proxy has started thinking/speaking — clear the textarea preview
|
|
227
|
+
this.textInputTextArea = '';
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
await this.voiceService.startSession(voiceIngress ? { voiceIngressStream: voiceIngress } : {});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private buildVoiceIngressStreamConfig(): VoiceStreamingSessionConfig | null {
|
|
234
|
+
const token = this.tiledeskAuthService.getTiledeskToken() ?? '';
|
|
235
|
+
const sender = this.tiledeskAuthService.getCurrentUser()?.uid ?? '';
|
|
236
|
+
const recipient = this.conversationWith ?? '';
|
|
237
|
+
if (!token || !sender || !recipient) {
|
|
238
|
+
this.logger.warn('[CONV-FOOTER] buildVoiceIngressStreamConfig: missing required fields', {
|
|
239
|
+
hasToken: !!token,
|
|
240
|
+
hasSender: !!sender,
|
|
241
|
+
hasRecipient: !!recipient,
|
|
242
|
+
});
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const { recipientFullname, attributes, channelType } = this.buildSendMessageContext();
|
|
246
|
+
this.logger.log('[CONV-FOOTER] buildVoiceIngressStreamConfig', { sender, recipient, channelType });
|
|
247
|
+
return {
|
|
248
|
+
token,
|
|
249
|
+
sender,
|
|
250
|
+
recipient,
|
|
251
|
+
// Use Deepgram multilingual code-switching so the model detects the spoken
|
|
252
|
+
// language from the audio stream regardless of browser locale.
|
|
253
|
+
// Source: https://developers.deepgram.com/docs/multilingual-code-switching
|
|
254
|
+
lang: 'multi',
|
|
255
|
+
text: '',
|
|
256
|
+
type: 'text',
|
|
257
|
+
recipient_fullname: recipientFullname ?? '',
|
|
258
|
+
sender_fullname: recipientFullname ?? '',
|
|
259
|
+
attributes: attributes ?? {},
|
|
260
|
+
metadata: '',
|
|
261
|
+
channel_type: channelType ?? '',
|
|
262
|
+
};
|
|
178
263
|
}
|
|
179
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Merge `attributes` di componente con `additional_attributes` e risolve `recipientFullname` come in sendMessage.
|
|
267
|
+
*/
|
|
268
|
+
private buildSendMessageContext(additional_attributes?: any) {
|
|
269
|
+
let recipientFullname = this.translationMap.get('GUEST_LABEL');
|
|
270
|
+
const g_attributes = this.attributes;
|
|
271
|
+
const attributes = <any>{};
|
|
272
|
+
if (g_attributes) {
|
|
273
|
+
for (const [key, value] of Object.entries(g_attributes)) {
|
|
274
|
+
attributes[key] = value;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (additional_attributes) {
|
|
278
|
+
for (const [key, value] of Object.entries(additional_attributes)) {
|
|
279
|
+
attributes[key] = value;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const senderId = this.senderId;
|
|
283
|
+
const projectid = this.project.id;
|
|
284
|
+
const channelType = this.channelType;
|
|
285
|
+
const userFullname = this.userFullname;
|
|
286
|
+
const userEmail = this.userEmail;
|
|
287
|
+
const conversationWith = this.conversationWith;
|
|
288
|
+
|
|
289
|
+
if (userFullname) {
|
|
290
|
+
recipientFullname = userFullname;
|
|
291
|
+
} else if (userEmail) {
|
|
292
|
+
recipientFullname = userEmail;
|
|
293
|
+
} else if (attributes && attributes['userFullname']) {
|
|
294
|
+
recipientFullname = attributes['userFullname'];
|
|
295
|
+
} else {
|
|
296
|
+
recipientFullname = this.translationMap.get('GUEST_LABEL');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
recipientFullname,
|
|
301
|
+
attributes,
|
|
302
|
+
senderId,
|
|
303
|
+
projectid,
|
|
304
|
+
channelType,
|
|
305
|
+
conversationWith,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
180
308
|
async stopVoice(options?: { discardInProgressSegment?: boolean }) {
|
|
309
|
+
// Stop all active TTS audio immediately and reveal all text.
|
|
310
|
+
this.ttsPlayback.stopAll();
|
|
311
|
+
|
|
181
312
|
this.voiceAudioSubscription?.unsubscribe();
|
|
182
313
|
this.voiceAudioSubscription = undefined;
|
|
183
314
|
|
|
315
|
+
this.voiceTranscriptSubscription?.unsubscribe();
|
|
316
|
+
this.voiceTranscriptSubscription = undefined;
|
|
317
|
+
|
|
184
318
|
this.voiceVolumeSubscription?.unsubscribe();
|
|
185
319
|
this.voiceVolumeSubscription = undefined;
|
|
186
320
|
|
|
321
|
+
this.botSpeakingSub?.unsubscribe();
|
|
322
|
+
this.botSpeakingSub = undefined;
|
|
323
|
+
this.isBotSpeaking = false;
|
|
324
|
+
|
|
187
325
|
await this.voiceService.stopSession(options);
|
|
188
326
|
this.currentVolume = 0;
|
|
327
|
+
this.textInputTextArea = '';
|
|
328
|
+
this.lastVoiceTranscript = '';
|
|
189
329
|
}
|
|
190
330
|
|
|
191
331
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* registrato in quel momento (nessun upload); mic + VAD restano attivi, `isStreamAudioActive` true.
|
|
332
|
+
* Messaggio in arrivo da un altro mittente mentre lo stream è attivo: con VAD legacy scarta il segmento in corso.
|
|
333
|
+
* Con sola sessione WSS non ha effetto sul mic (nessun recorder a segmenti locale).
|
|
195
334
|
*/
|
|
196
335
|
interruptStreamDueToPeerMessage(): void {
|
|
197
336
|
if (!this.isStreamAudioActive) {
|
|
@@ -446,40 +585,14 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
446
585
|
// msg = replaceEndOfLine(msg);
|
|
447
586
|
// msg = msg.trim();
|
|
448
587
|
|
|
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
|
-
}
|
|
588
|
+
const {
|
|
589
|
+
recipientFullname,
|
|
590
|
+
attributes,
|
|
591
|
+
senderId,
|
|
592
|
+
projectid,
|
|
593
|
+
channelType,
|
|
594
|
+
conversationWith,
|
|
595
|
+
} = this.buildSendMessageContext(additional_attributes);
|
|
483
596
|
|
|
484
597
|
this.onBeforeMessageSent.emit({
|
|
485
598
|
senderFullname: recipientFullname,
|
|
@@ -731,8 +844,12 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
731
844
|
if (this.showAlertEmoji) {
|
|
732
845
|
return;
|
|
733
846
|
}
|
|
734
|
-
|
|
847
|
+
// Treat a click during connecting as a cancel request (same as turning off).
|
|
848
|
+
const turningOn = !this.isStreamAudioActive && !this.isStreamAudioConnecting;
|
|
849
|
+
this.logger.log('[CONV-FOOTER] onStreamPressed', { turningOn });
|
|
735
850
|
if (turningOn) {
|
|
851
|
+
this.isStreamAudioConnecting = true;
|
|
852
|
+
this.onStreamAudioConnectingChange.emit(true);
|
|
736
853
|
try {
|
|
737
854
|
this.currentVolume = 0;
|
|
738
855
|
await this.initVoice();
|
|
@@ -740,10 +857,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
740
857
|
} catch (e) {
|
|
741
858
|
this.logger.error('[CONV-FOOTER] onStreamPressed: initVoice failed', e);
|
|
742
859
|
this.isStreamAudioActive = false;
|
|
860
|
+
} finally {
|
|
861
|
+
this.isStreamAudioConnecting = false;
|
|
862
|
+
this.onStreamAudioConnectingChange.emit(false);
|
|
743
863
|
}
|
|
744
864
|
} else {
|
|
745
865
|
await this.stopVoice();
|
|
746
866
|
this.isStreamAudioActive = false;
|
|
867
|
+
this.isStreamAudioConnecting = false;
|
|
868
|
+
this.onStreamAudioConnectingChange.emit(false);
|
|
747
869
|
}
|
|
748
870
|
this.onStreamAudioActiveChange.emit(this.isStreamAudioActive);
|
|
749
871
|
this.logger.log('[CONV-FOOTER] isStreamAudioActive', this.isStreamAudioActive);
|
|
@@ -840,48 +962,46 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
840
962
|
}
|
|
841
963
|
|
|
842
964
|
/**
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
965
|
+
* Single keyboard handler for the message textarea.
|
|
966
|
+
*
|
|
967
|
+
* - Enter (no modifier) -> send message
|
|
968
|
+
* - Shift / Alt / Ctrl / Meta + Enter -> insert a newline (default browser behavior)
|
|
969
|
+
* - Tab -> prevented, to keep focus inside the chat
|
|
970
|
+
*
|
|
971
|
+
* Modifier check is intentionally on `keydown` because `keypress` is deprecated
|
|
972
|
+
* and does not consistently fire for modifier combos across browsers.
|
|
850
973
|
* @param event
|
|
851
974
|
*/
|
|
852
|
-
|
|
975
|
+
onkeydown(event: KeyboardEvent) {
|
|
853
976
|
const keyCode = event.which || event.keyCode;
|
|
854
|
-
|
|
855
|
-
if (keyCode === 13) { // ENTER
|
|
856
|
-
|
|
857
|
-
|
|
977
|
+
|
|
978
|
+
if (keyCode === 13) { // ENTER
|
|
979
|
+
const hasModifier = event.metaKey || event.shiftKey || event.altKey || event.ctrlKey;
|
|
980
|
+
if (hasModifier) {
|
|
981
|
+
// Let the textarea insert a newline on its own (do not preventDefault).
|
|
982
|
+
return;
|
|
858
983
|
}
|
|
984
|
+
|
|
985
|
+
// Plain Enter -> send the message
|
|
986
|
+
event.preventDefault();
|
|
987
|
+
|
|
988
|
+
if (this.showAlertEmoji) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const target = document.getElementById('chat21-main-message-context') as HTMLInputElement;
|
|
993
|
+
if (target) {
|
|
994
|
+
this.textInputTextArea = target.value;
|
|
995
|
+
}
|
|
996
|
+
|
|
859
997
|
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
998
|
this.sendMessage(this.textInputTextArea, TYPE_MSG_TEXT);
|
|
866
|
-
// this.restoreTextArea();
|
|
867
999
|
}
|
|
868
|
-
|
|
869
|
-
event.preventDefault();
|
|
1000
|
+
return;
|
|
870
1001
|
}
|
|
871
|
-
}
|
|
872
1002
|
|
|
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){
|
|
1003
|
+
if (keyCode === 9) { // TAB
|
|
882
1004
|
event.preventDefault();
|
|
883
|
-
this.textInputTextArea += '\r\n';
|
|
884
|
-
this.resizeInputField();
|
|
885
1005
|
}
|
|
886
1006
|
}
|
|
887
1007
|
|