@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.
Files changed (55) hide show
  1. package/.github/workflows/docker-community-push-latest.yml +13 -23
  2. package/.github/workflows/docker-image-tag-community-tag-push.yml +12 -22
  3. package/CHANGELOG.md +22 -118
  4. package/Dockerfile +4 -4
  5. package/README.md +1 -1
  6. package/docs/ACCESSIBILITY-STATEMENT.md +388 -0
  7. package/docs/TILEDESK_WIDGET_ACCESSIBILITY_ALIGNMENT.md +60 -0
  8. package/docs/TILEDESK_WIDGET_ACCESSIBILITY_STATEMENT_COMPLETE.md +386 -0
  9. package/docs/changelog/this-branch.md +0 -36
  10. package/nginx.conf +2 -22
  11. package/package.json +1 -1
  12. package/src/app/app.component.ts +9 -10
  13. package/src/app/component/conversation-detail/conversation/conversation.component.html +2 -2
  14. package/src/app/component/conversation-detail/conversation/conversation.component.scss +2 -2
  15. package/src/app/component/conversation-detail/conversation/conversation.component.ts +16 -34
  16. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +3 -3
  17. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +2 -2
  18. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +0 -1
  19. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +52 -63
  20. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +17 -11
  21. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +10 -4
  22. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +5 -8
  23. package/src/app/component/form/inputs/form-text/form-text.component.ts +1 -1
  24. package/src/app/component/last-message/last-message.component.ts +1 -4
  25. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +17 -8
  26. package/src/app/component/message/audio-sync/audio-sync.component.ts +96 -25
  27. package/src/app/component/message/bubble-message/bubble-message.component.html +12 -9
  28. package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +38 -45
  29. package/src/app/component/message/bubble-message/bubble-message.component.ts +49 -45
  30. package/src/app/component/message/json-sources/json-sources.component.html +6 -5
  31. package/src/app/component/message/json-sources/json-sources.component.scss +26 -18
  32. package/src/app/component/message/json-sources/json-sources.component.ts +41 -0
  33. package/src/app/providers/global-settings.service.ts +0 -42
  34. package/src/app/providers/json-sources-parser.service.ts +13 -1
  35. package/src/app/providers/translator.service.ts +1 -4
  36. package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +7 -8
  37. package/src/app/providers/tts-audio-playback-coordinator.service.ts +13 -0
  38. package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +67 -82
  39. package/src/app/providers/voice/voice.service.spec.ts +35 -35
  40. package/src/app/providers/voice/voice.service.ts +3 -7
  41. package/src/app/sass/_variables.scss +0 -1
  42. package/src/app/utils/globals.ts +2 -8
  43. package/src/assets/i18n/en.json +22 -1
  44. package/src/assets/i18n/es.json +22 -1
  45. package/src/assets/i18n/fr.json +22 -1
  46. package/src/assets/i18n/it.json +22 -1
  47. package/src/assets/twp/index-dev.html +0 -18
  48. package/src/chat21-core/providers/firebase/firebase-init-service.ts +5 -5
  49. package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
  50. package/src/chat21-core/utils/utils-message.ts +4 -4
  51. package/src/chat21-core/utils/utils.ts +2 -5
  52. package/src/widget-config-template.json +0 -1
  53. package/src/widget-config.json +28 -30
  54. package/.github/workflows/build.yml +0 -22
  55. 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
- /** Stream con traccia audio reale (Web Audio), richiesto da `createMediaStreamSource` nei test WSS/legacy. */
11
- function createFakeMicStreamWithAudioTrack(): MediaStream {
13
+ /** MediaStream with at least one audio track (required by initAudioAnalyser). */
14
+ function createAudioMediaStream(): MediaStream {
12
15
  const ctx = new AudioContext();
13
- const dest = ctx.createMediaStreamDestination();
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 = new MediaStream();
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 = new MediaStream();
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 = createFakeMicStreamWithAudioTrack();
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 stream = createFakeMicStreamWithAudioTrack();
110
- const track = stream.getAudioTracks()[0];
111
- spyOn(track, 'stop').and.callThrough();
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 = createFakeMicStreamWithAudioTrack();
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.setAudioMuted).not.toHaveBeenCalled();
157
+ expect(voiceStreamingMock.resumeRecording).toHaveBeenCalled();
159
158
  });
160
159
 
161
- it('empty-audio path: sendPlaybackComplete after flush but acquisition stays blocked until "listening"', async () => {
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 no binary audio arms unblock; flush sends playback complete to proxy
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
- expect(voiceStreamingMock.sendPlaybackComplete).not.toHaveBeenCalled();
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 unblocks acquisition without mic mute toggles (AEC keeps capture open)', async () => {
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.setAudioMuted).not.toHaveBeenCalled();
188
- expect((service as any)._isAcquisitionBlocked$.getValue()).toBe(false);
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 only after flush for the new turn', async () => {
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
- wsControl$.next({ event: 'done' } as VoiceWsControlMessage);
201
+ // _cancelAllTtsAudio() resets _activeTtsSources=0, _unblockAfterTts=false
203
202
 
204
- expect(voiceStreamingMock.sendPlaybackComplete).not.toHaveBeenCalled();
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?: VoiceStreamingSessionConfig | null = null;
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: 800,//1920,
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);
@@ -36,7 +36,6 @@
36
36
 
37
37
  --chat-footer-height: 64px;
38
38
  --chat-footer-logo-height: 30px;
39
- --chat-footer-close-button-height: 30px;
40
39
  --chat-footer-border-radius: 16px;
41
40
  --chat-footer-stream-button-height: 72px;
42
41
  --chat-footer-stream-button-padding: 10px 0;
@@ -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 = true;
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
- /** remote CSS override URL (from window.tiledeskSettings.cssSource only) */
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
 
@@ -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
+ }
@@ -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://tiledesk.com/privacy.html' target='_blank'>Política de privacidad</a>",
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...",
@@ -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://tiledesk.com/privacy.html' target='_blank'>Politique de confidentialité</a>",
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...",
@@ -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://tiledesk.com/privacy.html' target='_blank'>Norme sulla privacy</a>",
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 isJsonSources(message: any) {
54
- if (message && message.type && message.type === 'url_preview') {
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 isAudioTTS(message: any) {
61
- if (message && message.type && message.type === 'tts' && message.metadata && message.metadata.src && message.metadata.type.includes('audio') ) {
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
  }
@@ -1,32 +1,30 @@
1
1
  {
2
- "chatEngine": "mqtt",
3
- "uploadEngine": "native",
4
- "pushEngine":"none",
5
- "logLevel":"INFO",
6
- "remoteTranslationsUrl": "http://localhost:3000/",
7
- "firebaseConfig": {
8
- "apiKey": "CHANGEIT",
9
- "authDomain": "CHANGEIT",
10
- "databaseURL": "CHANGEIT",
11
- "projectId": "CHANGEIT",
12
- "storageBucket": "CHANGEIT",
13
- "messagingSenderId": "CHANGEIT",
14
- "appId": "CHANGEIT",
15
- "tenant": "CHANGEIT"
16
- },
17
- "chat21Config": {
18
- "appId": "tilechat",
19
- "MQTTendpoint": "ws://localhost:15675/ws",
20
- "APIendpoint": "http://localhost:8004/api"
21
- },
22
- "apiUrl": "http://localhost:3000/",
23
- "baseImageUrl": "http://localhost:3000/",
24
- "dashboardUrl": "http://localhost:4500/",
25
- "authPersistence": "LOCAL",
26
- "enbedJs": true,
27
- "brandSrc": "${BRAND_SRC}",
28
- "voiceProxyWsUrl": "wss://localhost:3000/speech/ws/voice",
29
- "voiceProxyApiBaseUrl": "https://localhost:3000/speech/api",
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
- }