@chat21/chat21-web-widget 5.1.32-rc9 → 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 +61 -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.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 +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 +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 +198 -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 +46 -18
- package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +60 -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 +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 -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 +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 +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 +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 +691 -17
- 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,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,38 @@ 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
|
+
}
|
|
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
|
+
}
|
|
103
132
|
|
|
104
133
|
file_size_limit = FILE_SIZE_LIMIT;
|
|
105
134
|
attachmentTooltip: string = '';
|
|
@@ -108,11 +137,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
108
137
|
|
|
109
138
|
convertColorToRGBA = convertColorToRGBA;
|
|
110
139
|
private logger: LoggerService = LoggerInstance.getInstance()
|
|
111
|
-
constructor(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
) {}
|
|
116
149
|
|
|
117
150
|
ngOnInit() {
|
|
118
151
|
// this.updateAttachmentTooltip();
|
|
@@ -122,7 +155,6 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
122
155
|
if(changes['conversationWith'] && changes['conversationWith'].currentValue !== undefined){
|
|
123
156
|
this.conversationHandlerService = this.chatManager.getConversationHandlerByConversationId(this.conversationWith);
|
|
124
157
|
this.isStreamAudioActive = false;
|
|
125
|
-
this.ttsPlayback.cancelAll();
|
|
126
158
|
void this.stopVoice();
|
|
127
159
|
}
|
|
128
160
|
if(changes['hideTextReply'] && changes['hideTextReply'].currentValue !== undefined){
|
|
@@ -164,37 +196,141 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
164
196
|
// }
|
|
165
197
|
|
|
166
198
|
/**
|
|
167
|
-
*
|
|
199
|
+
* Stream voce: con `voiceIngress` solo WSS (no VAD) — transcript + TTS dal server.
|
|
200
|
+
* Senza ingresso WSS: VAD + upload per segmento.
|
|
168
201
|
*/
|
|
169
202
|
async initVoice() {
|
|
170
203
|
this.voiceAudioSubscription?.unsubscribe();
|
|
171
204
|
this.voiceVolumeSubscription?.unsubscribe();
|
|
205
|
+
this.botSpeakingSub?.unsubscribe();
|
|
206
|
+
this.voiceTranscriptSubscription?.unsubscribe();
|
|
172
207
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
+
}
|
|
176
218
|
});
|
|
219
|
+
|
|
177
220
|
this.voiceVolumeSubscription = this.voiceService.volume$.subscribe((volume) => {
|
|
178
221
|
this.currentVolume = volume;
|
|
179
222
|
});
|
|
180
|
-
|
|
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
|
+
};
|
|
181
263
|
}
|
|
182
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
|
+
}
|
|
183
308
|
async stopVoice(options?: { discardInProgressSegment?: boolean }) {
|
|
309
|
+
// Stop all active TTS audio immediately and reveal all text.
|
|
310
|
+
this.ttsPlayback.stopAll();
|
|
311
|
+
|
|
184
312
|
this.voiceAudioSubscription?.unsubscribe();
|
|
185
313
|
this.voiceAudioSubscription = undefined;
|
|
186
314
|
|
|
315
|
+
this.voiceTranscriptSubscription?.unsubscribe();
|
|
316
|
+
this.voiceTranscriptSubscription = undefined;
|
|
317
|
+
|
|
187
318
|
this.voiceVolumeSubscription?.unsubscribe();
|
|
188
319
|
this.voiceVolumeSubscription = undefined;
|
|
189
320
|
|
|
321
|
+
this.botSpeakingSub?.unsubscribe();
|
|
322
|
+
this.botSpeakingSub = undefined;
|
|
323
|
+
this.isBotSpeaking = false;
|
|
324
|
+
|
|
190
325
|
await this.voiceService.stopSession(options);
|
|
191
326
|
this.currentVolume = 0;
|
|
327
|
+
this.textInputTextArea = '';
|
|
328
|
+
this.lastVoiceTranscript = '';
|
|
192
329
|
}
|
|
193
330
|
|
|
194
331
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* 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).
|
|
198
334
|
*/
|
|
199
335
|
interruptStreamDueToPeerMessage(): void {
|
|
200
336
|
if (!this.isStreamAudioActive) {
|
|
@@ -449,40 +585,14 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
449
585
|
// msg = replaceEndOfLine(msg);
|
|
450
586
|
// msg = msg.trim();
|
|
451
587
|
|
|
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
|
-
}
|
|
588
|
+
const {
|
|
589
|
+
recipientFullname,
|
|
590
|
+
attributes,
|
|
591
|
+
senderId,
|
|
592
|
+
projectid,
|
|
593
|
+
channelType,
|
|
594
|
+
conversationWith,
|
|
595
|
+
} = this.buildSendMessageContext(additional_attributes);
|
|
486
596
|
|
|
487
597
|
this.onBeforeMessageSent.emit({
|
|
488
598
|
senderFullname: recipientFullname,
|
|
@@ -734,8 +844,12 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
734
844
|
if (this.showAlertEmoji) {
|
|
735
845
|
return;
|
|
736
846
|
}
|
|
737
|
-
|
|
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 });
|
|
738
850
|
if (turningOn) {
|
|
851
|
+
this.isStreamAudioConnecting = true;
|
|
852
|
+
this.onStreamAudioConnectingChange.emit(true);
|
|
739
853
|
try {
|
|
740
854
|
this.currentVolume = 0;
|
|
741
855
|
await this.initVoice();
|
|
@@ -743,13 +857,15 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
743
857
|
} catch (e) {
|
|
744
858
|
this.logger.error('[CONV-FOOTER] onStreamPressed: initVoice failed', e);
|
|
745
859
|
this.isStreamAudioActive = false;
|
|
746
|
-
|
|
860
|
+
} finally {
|
|
861
|
+
this.isStreamAudioConnecting = false;
|
|
862
|
+
this.onStreamAudioConnectingChange.emit(false);
|
|
747
863
|
}
|
|
748
864
|
} else {
|
|
749
865
|
await this.stopVoice();
|
|
750
866
|
this.isStreamAudioActive = false;
|
|
751
|
-
|
|
752
|
-
this.
|
|
867
|
+
this.isStreamAudioConnecting = false;
|
|
868
|
+
this.onStreamAudioConnectingChange.emit(false);
|
|
753
869
|
}
|
|
754
870
|
this.onStreamAudioActiveChange.emit(this.isStreamAudioActive);
|
|
755
871
|
this.logger.log('[CONV-FOOTER] isStreamAudioActive', this.isStreamAudioActive);
|
|
@@ -846,48 +962,46 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
|
|
|
846
962
|
}
|
|
847
963
|
|
|
848
964
|
/**
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
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.
|
|
856
973
|
* @param event
|
|
857
974
|
*/
|
|
858
|
-
|
|
975
|
+
onkeydown(event: KeyboardEvent) {
|
|
859
976
|
const keyCode = event.which || event.keyCode;
|
|
860
|
-
|
|
861
|
-
if (keyCode === 13) { // ENTER
|
|
862
|
-
|
|
863
|
-
|
|
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;
|
|
864
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
|
+
|
|
865
997
|
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
998
|
this.sendMessage(this.textInputTextArea, TYPE_MSG_TEXT);
|
|
872
|
-
// this.restoreTextArea();
|
|
873
999
|
}
|
|
874
|
-
|
|
875
|
-
event.preventDefault();
|
|
1000
|
+
return;
|
|
876
1001
|
}
|
|
877
|
-
}
|
|
878
1002
|
|
|
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){
|
|
1003
|
+
if (keyCode === 9) { // TAB
|
|
888
1004
|
event.preventDefault();
|
|
889
|
-
this.textInputTextArea += '\r\n';
|
|
890
|
-
this.resizeInputField();
|
|
891
1005
|
}
|
|
892
1006
|
}
|
|
893
1007
|
|