@chat21/chat21-web-widget 5.1.34-rc1 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/docker-community-push-latest.yml +13 -23
- package/.github/workflows/docker-image-tag-community-tag-push.yml +12 -22
- package/CHANGELOG.md +22 -118
- package/Dockerfile +4 -4
- package/README.md +1 -1
- package/docs/ACCESSIBILITY-STATEMENT.md +388 -0
- package/docs/TILEDESK_WIDGET_ACCESSIBILITY_ALIGNMENT.md +60 -0
- package/docs/TILEDESK_WIDGET_ACCESSIBILITY_STATEMENT_COMPLETE.md +386 -0
- package/docs/changelog/this-branch.md +0 -36
- package/nginx.conf +2 -22
- package/package.json +1 -1
- package/src/app/app.component.ts +9 -10
- package/src/app/component/conversation-detail/conversation/conversation.component.html +2 -2
- package/src/app/component/conversation-detail/conversation/conversation.component.scss +2 -2
- package/src/app/component/conversation-detail/conversation/conversation.component.ts +16 -34
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +3 -3
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +2 -2
- package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +0 -1
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +52 -63
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +17 -11
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +10 -4
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +5 -8
- package/src/app/component/form/inputs/form-text/form-text.component.ts +1 -1
- package/src/app/component/last-message/last-message.component.ts +1 -4
- package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +17 -8
- package/src/app/component/message/audio-sync/audio-sync.component.ts +96 -25
- package/src/app/component/message/bubble-message/bubble-message.component.html +12 -9
- package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +38 -45
- package/src/app/component/message/bubble-message/bubble-message.component.ts +49 -45
- package/src/app/component/message/json-sources/json-sources.component.html +6 -5
- package/src/app/component/message/json-sources/json-sources.component.scss +26 -18
- package/src/app/component/message/json-sources/json-sources.component.ts +41 -0
- package/src/app/providers/global-settings.service.ts +0 -42
- package/src/app/providers/json-sources-parser.service.ts +13 -1
- package/src/app/providers/translator.service.ts +1 -4
- package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +7 -8
- package/src/app/providers/tts-audio-playback-coordinator.service.ts +13 -0
- package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +67 -82
- package/src/app/providers/voice/voice.service.spec.ts +35 -35
- package/src/app/providers/voice/voice.service.ts +3 -7
- package/src/app/sass/_variables.scss +0 -1
- package/src/app/utils/globals.ts +2 -8
- package/src/assets/i18n/en.json +22 -1
- package/src/assets/i18n/es.json +22 -1
- package/src/assets/i18n/fr.json +22 -1
- package/src/assets/i18n/it.json +22 -1
- package/src/assets/twp/index-dev.html +0 -18
- package/src/chat21-core/providers/firebase/firebase-init-service.ts +5 -5
- package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
- package/src/chat21-core/utils/utils-message.ts +4 -4
- package/src/chat21-core/utils/utils.ts +2 -5
- package/src/widget-config-template.json +0 -1
- package/src/widget-config.json +28 -30
- package/.github/workflows/build.yml +0 -22
- package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +0 -14
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
import { TestBed } from '@angular/core/testing';
|
|
2
2
|
import { Subject } from 'rxjs';
|
|
3
|
+
import { NGXLogger } from 'ngx-logger';
|
|
3
4
|
|
|
4
5
|
import { VoiceService } from './voice.service';
|
|
5
6
|
import { VadService } from './vad.service';
|
|
6
7
|
import { VoiceStreamingService } from './voice-streaming.service';
|
|
7
8
|
import { TtsAudioPlaybackCoordinator } from '../tts-audio-playback-coordinator.service';
|
|
8
9
|
import { VoiceWsControlMessage } from './voice-streaming.types';
|
|
10
|
+
import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
|
|
11
|
+
import { CustomLogger } from 'src/chat21-core/providers/logger/customLogger';
|
|
9
12
|
|
|
10
|
-
/**
|
|
11
|
-
function
|
|
13
|
+
/** MediaStream with at least one audio track (required by initAudioAnalyser). */
|
|
14
|
+
function createAudioMediaStream(): MediaStream {
|
|
12
15
|
const ctx = new AudioContext();
|
|
13
|
-
|
|
14
|
-
const osc = ctx.createOscillator();
|
|
15
|
-
const gain = ctx.createGain();
|
|
16
|
-
gain.gain.value = 0.00001;
|
|
17
|
-
osc.connect(gain);
|
|
18
|
-
gain.connect(dest);
|
|
19
|
-
osc.start(0);
|
|
20
|
-
return dest.stream;
|
|
16
|
+
return ctx.createMediaStreamDestination().stream;
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
describe('VoiceService', () => {
|
|
@@ -30,6 +26,9 @@ describe('VoiceService', () => {
|
|
|
30
26
|
let mockVad: { start: jasmine.Spy; pause: jasmine.Spy; destroy: jasmine.Spy };
|
|
31
27
|
|
|
32
28
|
beforeEach(() => {
|
|
29
|
+
const ngxlogger = jasmine.createSpyObj('NGXLogger', ['log', 'trace', 'debug', 'warn', 'error', 'info']);
|
|
30
|
+
LoggerInstance.setInstance(new CustomLogger(ngxlogger));
|
|
31
|
+
|
|
33
32
|
mockVad = {
|
|
34
33
|
start: jasmine.createSpy('start').and.returnValue(Promise.resolve()),
|
|
35
34
|
pause: jasmine.createSpy('pause').and.returnValue(Promise.resolve()),
|
|
@@ -65,14 +64,12 @@ describe('VoiceService', () => {
|
|
|
65
64
|
],
|
|
66
65
|
});
|
|
67
66
|
service = TestBed.inject(VoiceService);
|
|
68
|
-
spyOn(service as any, '_startKeyboardSound').and.stub();
|
|
69
|
-
spyOn(service as any, '_stopKeyboardSound').and.stub();
|
|
70
67
|
});
|
|
71
68
|
|
|
72
69
|
// ── Existing session lifecycle tests ──────────────────────────────────────
|
|
73
70
|
|
|
74
71
|
it('startSession should call ensureOnnxRuntimeEnv', async () => {
|
|
75
|
-
const stream =
|
|
72
|
+
const stream = createAudioMediaStream();
|
|
76
73
|
spyOn(navigator.mediaDevices, 'getUserMedia').and.returnValue(Promise.resolve(stream));
|
|
77
74
|
|
|
78
75
|
await service.startSession({});
|
|
@@ -81,7 +78,7 @@ describe('VoiceService', () => {
|
|
|
81
78
|
});
|
|
82
79
|
|
|
83
80
|
it('startSession should request mic, create MicVAD, and start', async () => {
|
|
84
|
-
const stream =
|
|
81
|
+
const stream = createAudioMediaStream();
|
|
85
82
|
spyOn(navigator.mediaDevices, 'getUserMedia').and.returnValue(Promise.resolve(stream));
|
|
86
83
|
|
|
87
84
|
await service.startSession({
|
|
@@ -94,7 +91,7 @@ describe('VoiceService', () => {
|
|
|
94
91
|
});
|
|
95
92
|
|
|
96
93
|
it('startSession with voiceIngressStream should not use MicVAD', async () => {
|
|
97
|
-
const stream =
|
|
94
|
+
const stream = createAudioMediaStream();
|
|
98
95
|
spyOn(navigator.mediaDevices, 'getUserMedia').and.returnValue(Promise.resolve(stream));
|
|
99
96
|
|
|
100
97
|
await service.startSession({
|
|
@@ -106,9 +103,9 @@ describe('VoiceService', () => {
|
|
|
106
103
|
});
|
|
107
104
|
|
|
108
105
|
it('stopSession should destroy VAD and stop tracks', async () => {
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
spyOn(
|
|
106
|
+
const track = jasmine.createSpyObj<MediaStreamTrack>('MediaStreamTrack', ['stop']);
|
|
107
|
+
const stream = createAudioMediaStream();
|
|
108
|
+
spyOn(stream, 'getTracks').and.returnValue([track]);
|
|
112
109
|
spyOn(navigator.mediaDevices, 'getUserMedia').and.returnValue(Promise.resolve(stream));
|
|
113
110
|
|
|
114
111
|
await service.startSession({ onRecordingComplete: () => {} });
|
|
@@ -123,8 +120,10 @@ describe('VoiceService', () => {
|
|
|
123
120
|
* Start a WSS session and return a helper that tracks _isAcquisitionBlocked$ emissions.
|
|
124
121
|
*/
|
|
125
122
|
async function startWssSession(): Promise<boolean[]> {
|
|
126
|
-
const stream =
|
|
123
|
+
const stream = createAudioMediaStream();
|
|
127
124
|
spyOn(navigator.mediaDevices, 'getUserMedia').and.returnValue(Promise.resolve(stream));
|
|
125
|
+
spyOn(service as any, '_startKeyboardSound').and.stub();
|
|
126
|
+
spyOn(service as any, '_stopKeyboardSound').and.stub();
|
|
128
127
|
const blocked: boolean[] = [];
|
|
129
128
|
service.isAcquisitionBlocked$.subscribe((v) => blocked.push(v));
|
|
130
129
|
await service.startSession({
|
|
@@ -155,54 +154,56 @@ describe('VoiceService', () => {
|
|
|
155
154
|
|
|
156
155
|
const afterListening = blocked[blocked.length - 1];
|
|
157
156
|
expect(afterListening).toBeFalse();
|
|
158
|
-
expect(voiceStreamingMock.
|
|
157
|
+
expect(voiceStreamingMock.resumeRecording).toHaveBeenCalled();
|
|
159
158
|
});
|
|
160
159
|
|
|
161
|
-
it('empty-audio path:
|
|
160
|
+
it('empty-audio path: acquisition stays blocked until "listening"', async () => {
|
|
162
161
|
const blocked = await startWssSession();
|
|
163
162
|
const initialLen = blocked.length;
|
|
164
163
|
|
|
165
|
-
// done with
|
|
164
|
+
// Simulate done arriving with NO binary audio (_activeTtsSources === 0)
|
|
166
165
|
wsControl$.next({ event: 'speaking', text: 'hello' } as VoiceWsControlMessage);
|
|
167
166
|
wsControl$.next({ event: 'done' } as VoiceWsControlMessage);
|
|
168
167
|
|
|
169
|
-
|
|
170
|
-
(service as any)._flushTtsUnblock(false);
|
|
171
|
-
expect(voiceStreamingMock.sendPlaybackComplete).toHaveBeenCalledTimes(1);
|
|
172
|
-
|
|
168
|
+
// Acquisition must still be blocked — proxy hasn't confirmed LISTENING yet
|
|
173
169
|
const afterDone = blocked.slice(initialLen);
|
|
174
170
|
expect(afterDone.every((v) => v === true)).toBeTrue();
|
|
175
171
|
|
|
172
|
+
// Unblock only after proxy confirms
|
|
176
173
|
wsControl$.next({ event: 'listening' } as VoiceWsControlMessage);
|
|
177
174
|
expect(blocked[blocked.length - 1]).toBeFalse();
|
|
178
175
|
});
|
|
179
176
|
|
|
180
|
-
it('"listening" event
|
|
177
|
+
it('"listening" event resumes recording exactly once', async () => {
|
|
181
178
|
await startWssSession();
|
|
182
179
|
|
|
183
180
|
wsControl$.next({ event: 'speaking', text: 'hi' } as VoiceWsControlMessage);
|
|
184
181
|
wsControl$.next({ event: 'done' } as VoiceWsControlMessage);
|
|
185
182
|
wsControl$.next({ event: 'listening' } as VoiceWsControlMessage);
|
|
186
183
|
|
|
187
|
-
expect(voiceStreamingMock.
|
|
188
|
-
expect(
|
|
184
|
+
expect(voiceStreamingMock.resumeRecording).toHaveBeenCalled();
|
|
185
|
+
expect(voiceStreamingMock.resumeRecording).toHaveBeenCalledTimes(1);
|
|
189
186
|
});
|
|
190
187
|
|
|
191
188
|
// ── Audio preemption tests (SPEC-002) ────────────────────────────────────
|
|
192
189
|
|
|
193
|
-
it('second "speaking" cancels first audio: sendPlaybackComplete
|
|
190
|
+
it('second "speaking" cancels first audio: sendPlaybackComplete called exactly once for the new turn', async () => {
|
|
194
191
|
await startWssSession();
|
|
195
192
|
voiceStreamingMock.sendPlaybackComplete.calls.reset();
|
|
196
193
|
|
|
194
|
+
// First turn: audio chunk arrives → _activeTtsSources = 1 (sync) → done sets _unblockAfterTts
|
|
197
195
|
wsControl$.next({ event: 'speaking', text: 'first' } as VoiceWsControlMessage);
|
|
198
|
-
ttsBinaryChunk$.next(new ArrayBuffer(4));
|
|
199
|
-
wsControl$.next({ event: 'done' } as VoiceWsControlMessage);
|
|
196
|
+
ttsBinaryChunk$.next(new ArrayBuffer(4)); // _activeTtsSources++ synchronously
|
|
197
|
+
wsControl$.next({ event: 'done' } as VoiceWsControlMessage); // _unblockAfterTts = true
|
|
200
198
|
|
|
199
|
+
// Second turn preempts while first audio is still "playing"
|
|
201
200
|
wsControl$.next({ event: 'speaking', text: 'second' } as VoiceWsControlMessage);
|
|
202
|
-
|
|
201
|
+
// _cancelAllTtsAudio() resets _activeTtsSources=0, _unblockAfterTts=false
|
|
203
202
|
|
|
204
|
-
|
|
203
|
+
// done with no audio — arms unblock; flush signals proxy once for the new turn
|
|
204
|
+
wsControl$.next({ event: 'done' } as VoiceWsControlMessage);
|
|
205
205
|
(service as any)._flushTtsUnblock(false);
|
|
206
|
+
|
|
206
207
|
expect(voiceStreamingMock.sendPlaybackComplete).toHaveBeenCalledTimes(1);
|
|
207
208
|
});
|
|
208
209
|
|
|
@@ -223,5 +224,4 @@ describe('VoiceService', () => {
|
|
|
223
224
|
// _unblockAfterTts was cleared by cancel; no sendPlaybackComplete should fire
|
|
224
225
|
expect(voiceStreamingMock.sendPlaybackComplete).not.toHaveBeenCalled();
|
|
225
226
|
});
|
|
226
|
-
|
|
227
227
|
});
|
|
@@ -39,7 +39,7 @@ export class VoiceService {
|
|
|
39
39
|
private sessionConstraints: MediaStreamConstraints = DEFAULT_VOICE_MEDIA_STREAM_CONSTRAINTS;
|
|
40
40
|
private onRecordingComplete?: (result: VoiceSegmentPayload) => void;
|
|
41
41
|
private enableTranscription = true;
|
|
42
|
-
private voiceIngressConfig
|
|
42
|
+
private voiceIngressConfig: VoiceStreamingSessionConfig | null = null;
|
|
43
43
|
|
|
44
44
|
private readonly audioSegmentSubject = new Subject<VoiceSegmentPayload>();
|
|
45
45
|
|
|
@@ -64,6 +64,7 @@ export class VoiceService {
|
|
|
64
64
|
private readonly _wsError$ = new Subject<string>();
|
|
65
65
|
readonly wsError$: Observable<string> = this._wsError$.asObservable();
|
|
66
66
|
|
|
67
|
+
// 🔊 REALTIME VOLUME STREAM
|
|
67
68
|
private readonly volumeSubject = new BehaviorSubject<number>(0);
|
|
68
69
|
readonly volume$: Observable<number> = this.volumeSubject.asObservable();
|
|
69
70
|
|
|
@@ -294,7 +295,7 @@ export class VoiceService {
|
|
|
294
295
|
void this.vad?.pause();
|
|
295
296
|
},
|
|
296
297
|
minSpeechMs: 480,
|
|
297
|
-
redemptionMs:
|
|
298
|
+
redemptionMs: 1920,
|
|
298
299
|
preSpeechPadMs: 960,
|
|
299
300
|
});
|
|
300
301
|
|
|
@@ -835,11 +836,6 @@ export class VoiceService {
|
|
|
835
836
|
* 🎧 AUDIO ANALYSER INIT
|
|
836
837
|
*/
|
|
837
838
|
private initAudioAnalyser(stream: MediaStream): void {
|
|
838
|
-
if (!stream?.getAudioTracks?.()?.length) {
|
|
839
|
-
this.logger.log('[VoiceService] initAudioAnalyser: no audio track on stream, skipping analyser');
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
839
|
this.audioContext = new AudioContext();
|
|
844
840
|
|
|
845
841
|
const source = this.audioContext.createMediaStreamSource(stream);
|
package/src/app/utils/globals.ts
CHANGED
|
@@ -235,11 +235,8 @@ export class Globals {
|
|
|
235
235
|
hasCalloutInWidgetConfig: boolean; // ******* new ********
|
|
236
236
|
|
|
237
237
|
fontFamilySource: string; // ******* new ********
|
|
238
|
-
cssSource: string; // ******* new ********
|
|
239
238
|
|
|
240
239
|
size: 'min' | 'max' | 'top'; // ******* new ********
|
|
241
|
-
|
|
242
|
-
closeChatInConversation: boolean; // ******* new ********
|
|
243
240
|
constructor(
|
|
244
241
|
) { }
|
|
245
242
|
|
|
@@ -449,7 +446,7 @@ export class Globals {
|
|
|
449
446
|
/** show/hide rec audio option in footer chat-detail page */
|
|
450
447
|
this.showAudioRecorderFooterButton = true;
|
|
451
448
|
/** show/hide stream audio option in footer chat-detail page */
|
|
452
|
-
this.showAudioStreamFooterButton =
|
|
449
|
+
this.showAudioStreamFooterButton = false;
|
|
453
450
|
/** enabled to set a list of pattern url able to load the widget **/
|
|
454
451
|
this.allowedOnSpecificUrl = false
|
|
455
452
|
/** set a list of pattern url able to load the widget */
|
|
@@ -458,10 +455,7 @@ export class Globals {
|
|
|
458
455
|
this.hasCalloutInWidgetConfig = false;
|
|
459
456
|
/** set widget size from 3 different positions: min, max, top */
|
|
460
457
|
this.size = 'min';
|
|
461
|
-
|
|
462
|
-
this.cssSource = '';
|
|
463
|
-
/** enable to close the chat in conversation */
|
|
464
|
-
this.closeChatInConversation = false;
|
|
458
|
+
|
|
465
459
|
// ============ END: SET EXTERNAL PARAMETERS ==============//
|
|
466
460
|
|
|
467
461
|
|
package/src/assets/i18n/en.json
CHANGED
|
@@ -101,8 +101,29 @@
|
|
|
101
101
|
"MAX_ATTACHMENT": "Max allowed size {{FILE_SIZE_LIMIT}}Mb",
|
|
102
102
|
"MAX_ATTACHMENT_ERROR": "The file exceeds the maximum allowed size",
|
|
103
103
|
"EMOJI": "Emoji",
|
|
104
|
+
"BUTTON_ATTACH_FILE": "Attach file",
|
|
105
|
+
"BUTTON_SEND_MESSAGE": "Send message",
|
|
106
|
+
"BUTTON_RECORD_AUDIO": "Hold to record an audio message",
|
|
107
|
+
"BUTTON_DELETE_AUDIO": "Delete recording",
|
|
108
|
+
"BUTTON_SEND_AUDIO": "Send audio message",
|
|
109
|
+
"BUTTON_PLAY_AUDIO": "Play audio",
|
|
110
|
+
"BUTTON_PAUSE_AUDIO": "Pause audio",
|
|
111
|
+
"BUTTON_LIKE_MESSAGE": "Like message",
|
|
112
|
+
"BUTTON_UNLIKE_MESSAGE": "Dislike message",
|
|
113
|
+
"BUTTON_OPEN_CHAT": "Open chat",
|
|
114
|
+
"BUTTON_CLOSE_PANEL": "Close panel",
|
|
115
|
+
"CONVERSATION_LOG_LABEL": "Conversation messages",
|
|
116
|
+
"BUTTON_OPEN_DETAIL": "Open conversation detail",
|
|
117
|
+
"BUTTON_ALL_CONVERSATIONS": "All conversations",
|
|
118
|
+
"BUTTON_SCROLL_TO_BOTTOM": "Scroll to last message",
|
|
119
|
+
"BUTTON_CLOSE_PREVIEW": "Close preview",
|
|
120
|
+
"CAROUSEL_PREVIOUS": "Previous slide",
|
|
121
|
+
"CAROUSEL_NEXT": "Next slide",
|
|
122
|
+
"CAROUSEL_LABEL": "Cards carousel",
|
|
123
|
+
"CAROUSEL_SLIDE_LABEL": "Slide {current} of {total}",
|
|
124
|
+
"SKIP_TO_COMPOSER": "Skip to message composer",
|
|
104
125
|
"STREAM_AUDIO": "Use voice mode",
|
|
105
126
|
"VOICE_CONNECTING": "Connecting...",
|
|
106
127
|
"VOICE_LISTENING": "Listening...",
|
|
107
128
|
"VOICE_PROCESSING": "Processing..."
|
|
108
|
-
}
|
|
129
|
+
}
|
package/src/assets/i18n/es.json
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"LABEL_PRECHAT_USER_PHONE": "Teléfono",
|
|
75
75
|
"LABEL_PRECHAT_USER_PHONE_ERROR": "Se requiere teléfono",
|
|
76
76
|
"LABEL_PRECHAT_FIRST_MESSAGE": "Tu mensaje para el equipo de soporte",
|
|
77
|
-
"LABEL_PRECHAT_STATIC_TERMS_PRIVACY": "Antes de continuar con la conversación, acepte nuestros <a href='https://tiledesk.com/termsofservice/' target='_blank'>Términos</a> y <a href='https
|
|
77
|
+
"LABEL_PRECHAT_STATIC_TERMS_PRIVACY": "Antes de continuar con la conversación, acepte nuestros <a href='https://tiledesk.com/termsofservice/' target='_blank'>Términos</a> y <a href='https:/ /tiledesk.com/privacy.html' target='_blank'>Política de privacidad</a>",
|
|
78
78
|
"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY": "Acepto",
|
|
79
79
|
"PRECHAT_REQUIRED_ERROR": "Este campo es obligatorio",
|
|
80
80
|
"TICKET_TAKING": "La solicitud ha sido recibida y el personal de asistencia está tratando con ella. \nPara agregar más comentarios, responda a este correo electrónico.",
|
|
@@ -101,6 +101,27 @@
|
|
|
101
101
|
"MAX_ATTACHMENT": "Tamaño máximo permitido {{FILE_SIZE_LIMIT}}Mb",
|
|
102
102
|
"MAX_ATTACHMENT_ERROR": "El archivo supera el tamaño máximo permitido",
|
|
103
103
|
"EMOJI": "Emoji",
|
|
104
|
+
"BUTTON_ATTACH_FILE": "Adjuntar archivo",
|
|
105
|
+
"BUTTON_SEND_MESSAGE": "Enviar mensaje",
|
|
106
|
+
"BUTTON_RECORD_AUDIO": "Mantén pulsado para grabar un audio",
|
|
107
|
+
"BUTTON_DELETE_AUDIO": "Eliminar grabación",
|
|
108
|
+
"BUTTON_SEND_AUDIO": "Enviar mensaje de audio",
|
|
109
|
+
"BUTTON_PLAY_AUDIO": "Reproducir audio",
|
|
110
|
+
"BUTTON_PAUSE_AUDIO": "Pausar audio",
|
|
111
|
+
"BUTTON_LIKE_MESSAGE": "Me gusta",
|
|
112
|
+
"BUTTON_UNLIKE_MESSAGE": "No me gusta",
|
|
113
|
+
"BUTTON_OPEN_CHAT": "Abrir chat",
|
|
114
|
+
"BUTTON_CLOSE_PANEL": "Cerrar panel",
|
|
115
|
+
"CONVERSATION_LOG_LABEL": "Mensajes de la conversación",
|
|
116
|
+
"BUTTON_OPEN_DETAIL": "Abrir detalle de la conversación",
|
|
117
|
+
"BUTTON_ALL_CONVERSATIONS": "Todas las conversaciones",
|
|
118
|
+
"BUTTON_SCROLL_TO_BOTTOM": "Ir al último mensaje",
|
|
119
|
+
"BUTTON_CLOSE_PREVIEW": "Cerrar vista previa",
|
|
120
|
+
"CAROUSEL_PREVIOUS": "Diapositiva anterior",
|
|
121
|
+
"CAROUSEL_NEXT": "Diapositiva siguiente",
|
|
122
|
+
"CAROUSEL_LABEL": "Carrusel de tarjetas",
|
|
123
|
+
"CAROUSEL_SLIDE_LABEL": "Diapositiva {current} de {total}",
|
|
124
|
+
"SKIP_TO_COMPOSER": "Saltar al cuadro de mensaje",
|
|
104
125
|
"STREAM_AUDIO": "Usar modo de voz",
|
|
105
126
|
"VOICE_CONNECTING": "Conectando...",
|
|
106
127
|
"VOICE_LISTENING": "Escuchando...",
|
package/src/assets/i18n/fr.json
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"LABEL_PRECHAT_USER_PHONE": "Téléphone",
|
|
75
75
|
"LABEL_PRECHAT_USER_PHONE_ERROR": "Le téléphone est requis",
|
|
76
76
|
"LABEL_PRECHAT_FIRST_MESSAGE": "Votre message pour l'équipe d'assistance",
|
|
77
|
-
"LABEL_PRECHAT_STATIC_TERMS_PRIVACY": "Avant de poursuivre la conversation, veuillez accepter nos <a href='https://tiledesk.com/termsofservice/' target='_blank'>Conditions</a> et <a href='https
|
|
77
|
+
"LABEL_PRECHAT_STATIC_TERMS_PRIVACY": "Avant de poursuivre la conversation, veuillez accepter nos <a href='https://tiledesk.com/termsofservice/' target='_blank'>Conditions</a> et <a href='https:/ /tiledesk.com/privacy.html' target='_blank'>Politique de confidentialité</a>",
|
|
78
78
|
"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY": "Je suis d'accord",
|
|
79
79
|
"PRECHAT_REQUIRED_ERROR": "Ce champ est obligatoire",
|
|
80
80
|
"TICKET_TAKING": "La demande a été reçue et le personnel d'assistance la traite.\nPour ajouter d'autres commentaires, répondez à cet e-mail.",
|
|
@@ -101,6 +101,27 @@
|
|
|
101
101
|
"MAX_ATTACHMENT": "Taille maximale autorisée {{FILE_SIZE_LIMIT}}Mo",
|
|
102
102
|
"MAX_ATTACHMENT_ERROR": "Le fichier dépasse la taille maximale autorisée",
|
|
103
103
|
"EMOJI": "Emoji",
|
|
104
|
+
"BUTTON_ATTACH_FILE": "Joindre un fichier",
|
|
105
|
+
"BUTTON_SEND_MESSAGE": "Envoyer le message",
|
|
106
|
+
"BUTTON_RECORD_AUDIO": "Maintenez pour enregistrer un message audio",
|
|
107
|
+
"BUTTON_DELETE_AUDIO": "Supprimer l'enregistrement",
|
|
108
|
+
"BUTTON_SEND_AUDIO": "Envoyer le message audio",
|
|
109
|
+
"BUTTON_PLAY_AUDIO": "Lire l'audio",
|
|
110
|
+
"BUTTON_PAUSE_AUDIO": "Mettre l'audio en pause",
|
|
111
|
+
"BUTTON_LIKE_MESSAGE": "J'aime",
|
|
112
|
+
"BUTTON_UNLIKE_MESSAGE": "Je n'aime pas",
|
|
113
|
+
"BUTTON_OPEN_CHAT": "Ouvrir le chat",
|
|
114
|
+
"BUTTON_CLOSE_PANEL": "Fermer le panneau",
|
|
115
|
+
"CONVERSATION_LOG_LABEL": "Messages de la conversation",
|
|
116
|
+
"BUTTON_OPEN_DETAIL": "Ouvrir le détail de la conversation",
|
|
117
|
+
"BUTTON_ALL_CONVERSATIONS": "Toutes les conversations",
|
|
118
|
+
"BUTTON_SCROLL_TO_BOTTOM": "Aller au dernier message",
|
|
119
|
+
"BUTTON_CLOSE_PREVIEW": "Fermer l'aperçu",
|
|
120
|
+
"CAROUSEL_PREVIOUS": "Diapositive précédente",
|
|
121
|
+
"CAROUSEL_NEXT": "Diapositive suivante",
|
|
122
|
+
"CAROUSEL_LABEL": "Carrousel de cartes",
|
|
123
|
+
"CAROUSEL_SLIDE_LABEL": "Diapositive {current} sur {total}",
|
|
124
|
+
"SKIP_TO_COMPOSER": "Aller à la zone de message",
|
|
104
125
|
"STREAM_AUDIO": "Utiliser le mode vocal",
|
|
105
126
|
"VOICE_CONNECTING": "Connexion...",
|
|
106
127
|
"VOICE_LISTENING": "Écoute...",
|
package/src/assets/i18n/it.json
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"LABEL_PRECHAT_USER_PHONE": "Telefono",
|
|
75
75
|
"LABEL_PRECHAT_USER_PHONE_ERROR": "Telefono richiesto",
|
|
76
76
|
"LABEL_PRECHAT_FIRST_MESSAGE": "Il tuo messaggio per il team di supporto",
|
|
77
|
-
"LABEL_PRECHAT_STATIC_TERMS_PRIVACY": "Prima di procedere nella conversazione, accetta i nostri <a href='https://tiledesk.com/termsofservice/' target='_blank'>Termini</a> e <a href='https
|
|
77
|
+
"LABEL_PRECHAT_STATIC_TERMS_PRIVACY": "Prima di procedere nella conversazione, accetta i nostri <a href='https://tiledesk.com/termsofservice/' target='_blank'>Termini</a> e <a href='https:/ /tiledesk.com/privacy.html' target='_blank'>Norme sulla privacy</a>",
|
|
78
78
|
"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY": "Sono d'accordo",
|
|
79
79
|
"PRECHAT_REQUIRED_ERROR": "Questo campo è obbligatorio",
|
|
80
80
|
"TICKET_TAKING": "La richiesta è stata ricevuta e il personale di assistenza se ne sta occupando.\nPer aggiungere ulteriori commenti, rispondi a questa email.",
|
|
@@ -101,6 +101,27 @@
|
|
|
101
101
|
"MAX_ATTACHMENT": "Dimensione massima consentita {{FILE_SIZE_LIMIT}}Mb",
|
|
102
102
|
"MAX_ATTACHMENT_ERROR": "Il file supera la dimensione massima consentita",
|
|
103
103
|
"EMOJI": "Emoji",
|
|
104
|
+
"BUTTON_ATTACH_FILE": "Allega un file",
|
|
105
|
+
"BUTTON_SEND_MESSAGE": "Invia messaggio",
|
|
106
|
+
"BUTTON_RECORD_AUDIO": "Tieni premuto per registrare un audio",
|
|
107
|
+
"BUTTON_DELETE_AUDIO": "Elimina registrazione",
|
|
108
|
+
"BUTTON_SEND_AUDIO": "Invia messaggio audio",
|
|
109
|
+
"BUTTON_PLAY_AUDIO": "Riproduci audio",
|
|
110
|
+
"BUTTON_PAUSE_AUDIO": "Metti in pausa l'audio",
|
|
111
|
+
"BUTTON_LIKE_MESSAGE": "Mi piace",
|
|
112
|
+
"BUTTON_UNLIKE_MESSAGE": "Non mi piace",
|
|
113
|
+
"BUTTON_OPEN_CHAT": "Apri chat",
|
|
114
|
+
"BUTTON_CLOSE_PANEL": "Chiudi pannello",
|
|
115
|
+
"CONVERSATION_LOG_LABEL": "Messaggi della conversazione",
|
|
116
|
+
"BUTTON_OPEN_DETAIL": "Apri dettaglio conversazione",
|
|
117
|
+
"BUTTON_ALL_CONVERSATIONS": "Tutte le conversazioni",
|
|
118
|
+
"BUTTON_SCROLL_TO_BOTTOM": "Vai all'ultimo messaggio",
|
|
119
|
+
"BUTTON_CLOSE_PREVIEW": "Chiudi anteprima",
|
|
120
|
+
"CAROUSEL_PREVIOUS": "Diapositiva precedente",
|
|
121
|
+
"CAROUSEL_NEXT": "Diapositiva successiva",
|
|
122
|
+
"CAROUSEL_LABEL": "Carosello di card",
|
|
123
|
+
"CAROUSEL_SLIDE_LABEL": "Diapositiva {current} di {total}",
|
|
124
|
+
"SKIP_TO_COMPOSER": "Salta al campo del messaggio",
|
|
104
125
|
"STREAM_AUDIO": "Usa la modalità vocale",
|
|
105
126
|
"VOICE_CONNECTING": "Connessione...",
|
|
106
127
|
"VOICE_LISTENING": "In ascolto...",
|
|
@@ -801,13 +801,6 @@
|
|
|
801
801
|
window.Tiledesk('openConversationById', request_id)
|
|
802
802
|
}
|
|
803
803
|
|
|
804
|
-
function onClickCssSource(){
|
|
805
|
-
let cssSource = document.getElementById('cssSource').value
|
|
806
|
-
window.tiledeskSettings['cssSource'] = cssSource
|
|
807
|
-
console.log('onClickCssSource: cssSource-->',window.tiledeskSettings);
|
|
808
|
-
window.Tiledesk('restart')
|
|
809
|
-
}
|
|
810
|
-
|
|
811
804
|
// function onClickParameter(elementName){
|
|
812
805
|
// console.log('onClickParameter: ',elementName)
|
|
813
806
|
// const radios = document.getElementsByName(elementName)
|
|
@@ -1814,17 +1807,6 @@
|
|
|
1814
1807
|
<button class="btn btn-light" onclick="onClickOpenConversationById()">Open conversation <i class="fa-regular fa-comment" aria-hidden="true"></i></button>
|
|
1815
1808
|
</div>
|
|
1816
1809
|
</div>
|
|
1817
|
-
<div class="row section">
|
|
1818
|
-
<div><h3 style="line-height: 0.3;">MANAGE <em><strong> global CSS</strong></em></h3></div>
|
|
1819
|
-
|
|
1820
|
-
<div>Insert an <em><strong>URL</strong></em> of a CSS file to override the global CSS </div>
|
|
1821
|
-
<div class="row" style="margin: 0">
|
|
1822
|
-
<textarea class="form-control" id="cssSource" placeholder="https://example.com/custom-style.css" rows="1" cols="60"></textarea>
|
|
1823
|
-
</div>
|
|
1824
|
-
<div class="row" style="text-align: right; margin:5px 0px">
|
|
1825
|
-
<button class="btn btn-light" onclick="onClickCssSource()">Test this setting <i class="fa fa-magic" aria-hidden="true"></i></button>
|
|
1826
|
-
</div>
|
|
1827
|
-
</div>
|
|
1828
1810
|
<!-- WIDGET POSITION SECTION : start-->
|
|
1829
1811
|
<div class="row section">
|
|
1830
1812
|
<div><h3 style="line-height: 0.3;">Widget <em><strong>POSITION</strong></em></h3></div>
|
|
@@ -14,7 +14,7 @@ import { Injectable } from '@angular/core';
|
|
|
14
14
|
export class FirebaseInitService {
|
|
15
15
|
|
|
16
16
|
public static firebaseInit: any;
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
constructor() {
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -22,10 +22,10 @@ export class FirebaseInitService {
|
|
|
22
22
|
const { default: firebase} = await import("firebase/app");
|
|
23
23
|
if(!FirebaseInitService.firebaseInit){
|
|
24
24
|
if (!firebaseConfig || firebaseConfig.apiKey === 'CHANGEIT') {
|
|
25
|
-
throw new Error('Firebase config is not defined. Please create your widget-config.json. See the Chat21-Web_widget Installation Page');
|
|
26
|
-
}
|
|
27
|
-
FirebaseInitService.firebaseInit = firebase.initializeApp(firebaseConfig);
|
|
25
|
+
throw new Error('Firebase config is not defined. Please create your widget-config-aws-stage.json. See the Chat21-Web_widget Installation Page');
|
|
26
|
+
}
|
|
27
|
+
FirebaseInitService.firebaseInit = firebase.initializeApp(firebaseConfig);
|
|
28
28
|
}
|
|
29
29
|
return FirebaseInitService.firebaseInit
|
|
30
30
|
}
|
|
31
|
-
}
|
|
31
|
+
}
|
|
@@ -71,7 +71,7 @@ export class TiledeskRequestsService {
|
|
|
71
71
|
|
|
72
72
|
public getMyRequests(): Promise<{ requests: Array<any>}> {
|
|
73
73
|
this.tiledeskToken = this.appStorage.getItem('tiledeskToken')
|
|
74
|
-
const url = this.URL_TILEDESK_REQUEST + 'me?preflight=true'
|
|
74
|
+
const url = this.URL_TILEDESK_REQUEST + '/me?preflight=true'
|
|
75
75
|
this.logger.log('[TILEDESK-SERVICE] - GET REQUEST url ', url);
|
|
76
76
|
const httpOptions = {
|
|
77
77
|
headers: new HttpHeaders({
|
|
@@ -50,15 +50,15 @@ export function isAudio(message: any) {
|
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export function
|
|
54
|
-
if (message && message.type && message.type === '
|
|
53
|
+
export function isAudioTTS(message: any) {
|
|
54
|
+
if (message && message.type && message.type === 'tts' && message.metadata && message.metadata.src && message.metadata.type.includes('audio') ) {
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
57
|
return false;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export function
|
|
61
|
-
if (message && message.type && message.type === '
|
|
60
|
+
export function isJsonSources(message: any) {
|
|
61
|
+
if (message && message.type && message.type === 'url_preview') {
|
|
62
62
|
return true;
|
|
63
63
|
}
|
|
64
64
|
return false;
|
|
@@ -773,11 +773,6 @@ export function isAllowedUrlInText(text: string, allowedUrls: string[]) {
|
|
|
773
773
|
return nonWhitelistedDomains.length === 0;
|
|
774
774
|
}
|
|
775
775
|
|
|
776
|
-
// function extractUrls(text: string): string[] {
|
|
777
|
-
// const urlRegex = /https?:\/\/[^\s]+/g;
|
|
778
|
-
// return text.match(urlRegex) || [];
|
|
779
|
-
// }
|
|
780
|
-
|
|
781
776
|
function extractUrls(text: string): string[] {
|
|
782
777
|
// Rileva URL con o senza protocollo (http/https)
|
|
783
778
|
const urlRegex = /\b((https?:\/\/)?(www\.)?[a-z0-9.-]+\.[a-z]{2,})(\/[^\s]*)?/gi;
|
|
@@ -792,3 +787,5 @@ function extractUrls(text: string): string[] {
|
|
|
792
787
|
}
|
|
793
788
|
|
|
794
789
|
|
|
790
|
+
|
|
791
|
+
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"authPersistence": "${AUTH_PERSISTENCE}",
|
|
26
26
|
"enbedJs": "${ENBED_JS}",
|
|
27
27
|
"brandSrc": "${BRAND_SRC}",
|
|
28
|
-
"closeChatInConversation": "${CLOSE_CHAT_IN_CONVERSATION}",
|
|
29
28
|
"voiceProxyWsUrl": "${VOICE_PROXY_WS_URL}",
|
|
30
29
|
"voiceProxyApiBaseUrl": "${VOICE_PROXY_API_BASE_URL}"
|
|
31
30
|
}
|
package/src/widget-config.json
CHANGED
|
@@ -1,32 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"closeChatInConversation": false
|
|
31
|
-
|
|
2
|
+
"chatEngine": "mqtt",
|
|
3
|
+
"uploadEngine": "native",
|
|
4
|
+
"pushEngine": "none",
|
|
5
|
+
"logLevel": "info" ,
|
|
6
|
+
"remoteTranslationsUrl": "http://localhost:3000/api/",
|
|
7
|
+
"firebaseConfig": {
|
|
8
|
+
"apiKey": "CHANGEIT",
|
|
9
|
+
"authDomain": "CHANGEIT.firebaseapp.com",
|
|
10
|
+
"databaseURL": "https://CHANGEIT.firebaseio.com",
|
|
11
|
+
"projectId": "CHANGEIT",
|
|
12
|
+
"storageBucket": "CHANGEIT.appspot.com",
|
|
13
|
+
"messagingSenderId": "CHANGEIT",
|
|
14
|
+
"appId": "",
|
|
15
|
+
"tenant": "tilechat"
|
|
16
|
+
},
|
|
17
|
+
"chat21Config": {
|
|
18
|
+
"appId": "tilechat",
|
|
19
|
+
"MQTTendpoint": "ws://localhost:15672/mqws/ws",
|
|
20
|
+
"APIendpoint": "http://localhost:3001/chatapi/api"
|
|
21
|
+
},
|
|
22
|
+
"apiUrl": "http://localhost:3000/api/",
|
|
23
|
+
"baseImageUrl": "http://localhost:3000/api/",
|
|
24
|
+
"dashboardUrl": "/dashboard/",
|
|
25
|
+
"authPersistence": "LOCAL",
|
|
26
|
+
"enbedJs": "true",
|
|
27
|
+
"brandSrc": "",
|
|
28
|
+
"voiceProxyWsBaseUrl": "ws://localhost:3000/ws/voice",
|
|
29
|
+
"voiceProxyApiBaseUrl": "http://localhost:3000/api"
|
|
32
30
|
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
name: Build
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
branches:
|
|
5
|
-
- master # The default branch
|
|
6
|
-
- (branch|release)-.* # The other branches to be analyzed
|
|
7
|
-
- (features|release)/.*
|
|
8
|
-
pull_request:
|
|
9
|
-
types: [opened, synchronize, reopened]
|
|
10
|
-
jobs:
|
|
11
|
-
sonarcloud:
|
|
12
|
-
name: SonarCloud
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
steps:
|
|
15
|
-
- uses: actions/checkout@v2
|
|
16
|
-
with:
|
|
17
|
-
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
|
18
|
-
- name: SonarCloud Scan
|
|
19
|
-
uses: SonarSource/sonarcloud-github-action@master
|
|
20
|
-
env:
|
|
21
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
|
22
|
-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Esempio di override remoto (tiledeskSettings.cssSource).
|
|
3
|
-
* Con ng serve: usa un path assoluto dalla root dell'app, es. /assets/widget-css-override-example.css
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/* Attenzione: il primo id deve essere l'intera stringa #tiledesk-container (errori tipo #tilede non matchano nulla nel DOM). */
|
|
7
|
-
#tiledesk-container #chat21-home-component .c21-header {
|
|
8
|
-
background-image: linear-gradient(135deg, #c0392b 0%, #8e44ad 100%) !important;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/* Bottone launcher: contorno molto visibile (non richiede di battere background-color inline) */
|
|
12
|
-
#tiledesk-container #c21-launcher-button {
|
|
13
|
-
box-shadow: 0 0 0 3px #f39c12, 0 8px 24px rgba(0, 0, 0, 0.25) !important;
|
|
14
|
-
}
|