@developpement/tp-chatbot-widget 0.0.6 → 0.0.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@developpement/tp-chatbot-widget",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Chatbot widget for TravelPlanet / Makitizy",
5
5
  "main": "src/chatbot.js",
6
6
  "files": [
package/src/chatbot.css CHANGED
@@ -32,8 +32,8 @@
32
32
  .tp-chatbot-window {
33
33
  position: absolute;
34
34
  bottom: 70px;
35
- width: 360px;
36
- height: 500px;
35
+ width: 380px;
36
+ height: 580px;
37
37
  background: white;
38
38
  border-radius: 16px;
39
39
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
package/src/chatbot.js CHANGED
@@ -2,10 +2,90 @@
2
2
  'use strict';
3
3
 
4
4
  const CLIENT_THEMES = {
5
- flix: { primary: '#7b1fa2', name: 'Flix Corporate', position: 'left' },
6
- sncf: { primary: '#1a6b5a', name: 'SNCF', position: 'right' },
5
+ flix: {
6
+ primary: '#73d700',
7
+ name: 'Flix Corporate',
8
+ position: 'left',
9
+ suggestions: [
10
+ {
11
+ FR: 'Comment réserver un billet ?',
12
+ EN: 'How to book a ticket?',
13
+ DE: 'Wie buche ich ein Ticket?',
14
+ ES: '¿Cómo reservar un billete?',
15
+ IT: 'Come prenotare un biglietto?',
16
+ NL: 'Hoe boek ik een ticket?',
17
+ },
18
+ {
19
+ FR: 'Annuler ma réservation',
20
+ EN: 'Cancel my booking',
21
+ DE: 'Meine Buchung stornieren',
22
+ ES: 'Cancelar mi reserva',
23
+ IT: 'Cancellare la mia prenotazione',
24
+ NL: 'Mijn boeking annuleren',
25
+ },
26
+ {
27
+ FR: 'Politique bagages',
28
+ EN: 'Baggage policy',
29
+ DE: 'Gepäckrichtlinien',
30
+ ES: 'Política de equipaje',
31
+ IT: 'Politica bagagli',
32
+ NL: 'Bagagebeleid',
33
+ },
34
+ {
35
+ FR: 'Mon compte entreprise',
36
+ EN: 'My corporate account',
37
+ DE: 'Mein Unternehmenskonto',
38
+ ES: 'Mi cuenta empresarial',
39
+ IT: 'Il mio account aziendale',
40
+ NL: 'Mijn zakelijk account',
41
+ },
42
+ ],
43
+ },
44
+ sncf: {
45
+ primary: '#1a6b5a',
46
+ name: 'SNCF',
47
+ position: 'right',
48
+ suggestions: [
49
+ {
50
+ FR: 'Comment réserver ?',
51
+ EN: 'How to book?',
52
+ DE: 'Wie buche ich?',
53
+ ES: '¿Cómo reservar?',
54
+ IT: 'Come prenotare?',
55
+ NL: 'Hoe boek ik?',
56
+ },
57
+ {
58
+ FR: 'Annuler un billet',
59
+ EN: 'Cancel a ticket',
60
+ DE: 'Ticket stornieren',
61
+ ES: 'Cancelar un billete',
62
+ IT: 'Cancellare un biglietto',
63
+ NL: 'Ticket annuleren',
64
+ },
65
+ {
66
+ FR: 'Politique bagages',
67
+ EN: 'Baggage policy',
68
+ DE: 'Gepäckrichtlinien',
69
+ ES: 'Política de equipaje',
70
+ IT: 'Politica bagagli',
71
+ NL: 'Bagagebeleid',
72
+ },
73
+ {
74
+ FR: 'Retard ou incident',
75
+ EN: 'Delay or incident',
76
+ DE: 'Verspätung oder Vorfall',
77
+ ES: 'Retraso o incidente',
78
+ IT: 'Ritardo o incidente',
79
+ NL: 'Vertraging of incident',
80
+ },
81
+ ],
82
+ },
7
83
  };
8
- const DEFAULT_THEME = { primary: '#7b1fa2', name: 'Support', position: 'right' };
84
+
85
+ const DEFAULT_THEME = { primary: '#7b1fa2', name: 'Support', position: 'right', suggestions: [] };
86
+
87
+ const WINDOW_WIDTH = '380px';
88
+ const WINDOW_HEIGHT = '580px';
9
89
 
10
90
  function getClientTheme(client_id) {
11
91
  return CLIENT_THEMES[client_id] || DEFAULT_THEME;
@@ -20,7 +100,7 @@
20
100
  first_name: payload.firstName || '',
21
101
  last_name: payload.lastName || '',
22
102
  company_name: payload.companyName || '',
23
- language: payload.language || 'FR',
103
+ language: payload.language || 'EN',
24
104
  site_id: payload.siteId || '',
25
105
  };
26
106
  } catch {
@@ -63,6 +143,8 @@
63
143
  this.sound_enabled = true;
64
144
  this.client_id = 'flix';
65
145
  this.access_token = '';
146
+ this.is_fullscreen = false;
147
+ this.is_minimized = false;
66
148
  }
67
149
 
68
150
  static get observedAttributes() {
@@ -98,6 +180,8 @@
98
180
  this.polling_interval = null;
99
181
  this.is_closed = false;
100
182
  this.view = 'list';
183
+ this.is_fullscreen = false;
184
+ this.is_minimized = false;
101
185
 
102
186
  this.render();
103
187
  this.applyTheme();
@@ -127,6 +211,185 @@
127
211
  if (this.polling_interval) clearInterval(this.polling_interval);
128
212
  }
129
213
 
214
+ // ─── i18n ─────────────────────────────────────────────────────────────────
215
+
216
+ getMessages() {
217
+ const lang = (this.user_info?.language || 'EN').toUpperCase();
218
+ const msgs = {
219
+ FR: {
220
+ closed: '✅ Conversation clôturée',
221
+ csat_question: 'Cette conversation vous a-t-elle été utile ?',
222
+ csat_positive: '👍 Merci pour votre retour !',
223
+ csat_negative: '👎 Merci, nous allons nous améliorer.',
224
+ new_conv: '💬 Nouvelle conversation',
225
+ back_to_list: '← Voir toutes les conversations',
226
+ agent_taken: 'Un agent a pris en charge votre conversation. Vous pouvez lui écrire directement.',
227
+ agent_released: 'Notre assistant virtuel reprend la conversation. Comment puis-je vous aider ?',
228
+ close_confirm: 'Voulez-vous terminer cette conversation ?',
229
+ terminated: '✅ Conversation terminée',
230
+ virtual: '🤖 Assistant virtuel',
231
+ human_agent: '👤 Agent humain',
232
+ loading: 'Chargement...',
233
+ no_conv: 'Aucune conversation pour le moment.',
234
+ new_conv_btn: '💬 Nouvelle conversation',
235
+ error_load: 'Erreur de chargement.',
236
+ recent_conv: 'Vos conversations récentes',
237
+ hello: 'Bonjour',
238
+ close_btn: '✅ Terminer la conversation',
239
+ speak_agent: '👤 Parler à un agent',
240
+ write_msg: 'Écrivez votre message...',
241
+ limit_reached: 'Vous avez atteint la limite de 30 messages. Veuillez contacter un agent ou écrire à support@travelplanet.com.',
242
+ too_many: 'Trop de messages. Veuillez patienter avant de réessayer.',
243
+ error_occurred: 'Une erreur est survenue. Veuillez réessayer.',
244
+ },
245
+ EN: {
246
+ closed: '✅ Conversation closed',
247
+ csat_question: 'Was this conversation helpful?',
248
+ csat_positive: '👍 Thank you for your feedback!',
249
+ csat_negative: '👎 Thank you, we will improve.',
250
+ new_conv: '💬 New conversation',
251
+ back_to_list: '← Back to conversations',
252
+ agent_taken: 'An agent has taken over your conversation. You can now write to them directly.',
253
+ agent_released: 'Our virtual assistant is back. How can I help you?',
254
+ close_confirm: 'Do you want to end this conversation?',
255
+ terminated: '✅ Conversation ended',
256
+ virtual: '🤖 Virtual assistant',
257
+ human_agent: '👤 Human agent',
258
+ loading: 'Loading...',
259
+ no_conv: 'No conversations yet.',
260
+ new_conv_btn: '💬 New conversation',
261
+ error_load: 'Loading error.',
262
+ recent_conv: 'Your recent conversations',
263
+ hello: 'Hello',
264
+ close_btn: '✅ End conversation',
265
+ speak_agent: '👤 Talk to an agent',
266
+ write_msg: 'Write your message...',
267
+ limit_reached: 'You have reached the 30 message limit. Please contact an agent or write to support@travelplanet.com.',
268
+ too_many: 'Too many messages. Please wait before retrying.',
269
+ error_occurred: 'An error occurred. Please try again.',
270
+ },
271
+ DE: {
272
+ closed: '✅ Gespräch beendet',
273
+ csat_question: 'War dieses Gespräch hilfreich?',
274
+ csat_positive: '👍 Vielen Dank für Ihr Feedback!',
275
+ csat_negative: '👎 Danke, wir werden uns verbessern.',
276
+ new_conv: '💬 Neues Gespräch',
277
+ back_to_list: '← Zurück zur Liste',
278
+ agent_taken: 'Ein Agent hat Ihr Gespräch übernommen. Sie können ihm jetzt direkt schreiben.',
279
+ agent_released: 'Unser virtueller Assistent ist zurück. Wie kann ich Ihnen helfen?',
280
+ close_confirm: 'Möchten Sie das Gespräch beenden?',
281
+ terminated: '✅ Gespräch beendet',
282
+ virtual: '🤖 Virtueller Assistent',
283
+ human_agent: '👤 Menschlicher Agent',
284
+ loading: 'Laden...',
285
+ no_conv: 'Noch keine Gespräche.',
286
+ new_conv_btn: '💬 Neues Gespräch',
287
+ error_load: 'Ladefehler.',
288
+ recent_conv: 'Ihre letzten Gespräche',
289
+ hello: 'Hallo',
290
+ close_btn: '✅ Gespräch beenden',
291
+ speak_agent: '👤 Mit einem Agenten sprechen',
292
+ write_msg: 'Schreiben Sie Ihre Nachricht...',
293
+ limit_reached: 'Sie haben das Limit von 30 Nachrichten erreicht.',
294
+ too_many: 'Zu viele Nachrichten. Bitte warten Sie.',
295
+ error_occurred: 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.',
296
+ },
297
+ ES: {
298
+ closed: '✅ Conversación cerrada',
299
+ csat_question: '¿Fue útil esta conversación?',
300
+ csat_positive: '👍 ¡Gracias por su opinión!',
301
+ csat_negative: '👎 Gracias, mejoraremos.',
302
+ new_conv: '💬 Nueva conversación',
303
+ back_to_list: '← Volver a las conversaciones',
304
+ agent_taken: 'Un agente ha tomado su conversación. Ahora puede escribirle directamente.',
305
+ agent_released: 'Nuestro asistente virtual ha vuelto. ¿En qué puedo ayudarle?',
306
+ close_confirm: '¿Desea finalizar esta conversación?',
307
+ terminated: '✅ Conversación terminada',
308
+ virtual: '🤖 Asistente virtual',
309
+ human_agent: '👤 Agente humano',
310
+ loading: 'Cargando...',
311
+ no_conv: 'No hay conversaciones por el momento.',
312
+ new_conv_btn: '💬 Nueva conversación',
313
+ error_load: 'Error de carga.',
314
+ recent_conv: 'Sus conversaciones recientes',
315
+ hello: 'Hola',
316
+ close_btn: '✅ Finalizar conversación',
317
+ speak_agent: '👤 Hablar con un agente',
318
+ write_msg: 'Escriba su mensaje...',
319
+ limit_reached: 'Ha alcanzado el límite de 30 mensajes.',
320
+ too_many: 'Demasiados mensajes. Por favor espere.',
321
+ error_occurred: 'Se produjo un error. Por favor, inténtelo de nuevo.',
322
+ },
323
+ IT: {
324
+ closed: '✅ Conversazione chiusa',
325
+ csat_question: 'Questa conversazione è stata utile?',
326
+ csat_positive: '👍 Grazie per il suo feedback!',
327
+ csat_negative: '👎 Grazie, miglioreremo.',
328
+ new_conv: '💬 Nuova conversazione',
329
+ back_to_list: '← Torna alle conversazioni',
330
+ agent_taken: 'Un agente ha preso in carico la sua conversazione.',
331
+ agent_released: 'Il nostro assistente virtuale è tornato. Come posso aiutarla?',
332
+ close_confirm: 'Vuole terminare questa conversazione?',
333
+ terminated: '✅ Conversazione terminata',
334
+ virtual: '🤖 Assistente virtuale',
335
+ human_agent: '👤 Agente umano',
336
+ loading: 'Caricamento...',
337
+ no_conv: 'Nessuna conversazione per il momento.',
338
+ new_conv_btn: '💬 Nuova conversazione',
339
+ error_load: 'Errore di caricamento.',
340
+ recent_conv: 'Le sue conversazioni recenti',
341
+ hello: 'Ciao',
342
+ close_btn: '✅ Termina conversazione',
343
+ speak_agent: '👤 Parla con un agente',
344
+ write_msg: 'Scrivi il tuo messaggio...',
345
+ limit_reached: 'Hai raggiunto il limite di 30 messaggi.',
346
+ too_many: 'Troppi messaggi. Attendere prego.',
347
+ error_occurred: 'Si è verificato un errore. Riprova.',
348
+ },
349
+ NL: {
350
+ closed: '✅ Gesprek gesloten',
351
+ csat_question: 'Was dit gesprek nuttig?',
352
+ csat_positive: '👍 Bedankt voor uw feedback!',
353
+ csat_negative: '👎 Bedankt, we zullen verbeteren.',
354
+ new_conv: '💬 Nieuw gesprek',
355
+ back_to_list: '← Terug naar gesprekken',
356
+ agent_taken: 'Een agent heeft uw gesprek overgenomen.',
357
+ agent_released: 'Onze virtuele assistent is terug. Hoe kan ik u helpen?',
358
+ close_confirm: 'Wilt u dit gesprek beëindigen?',
359
+ terminated: '✅ Gesprek beëindigd',
360
+ virtual: '🤖 Virtuele assistent',
361
+ human_agent: '👤 Menselijke agent',
362
+ loading: 'Laden...',
363
+ no_conv: 'Geen gesprekken op dit moment.',
364
+ new_conv_btn: '💬 Nieuw gesprek',
365
+ error_load: 'Laadфout.',
366
+ recent_conv: 'Uw recente gesprekken',
367
+ hello: 'Hallo',
368
+ close_btn: '✅ Gesprek beëindigen',
369
+ speak_agent: '👤 Praat met een agent',
370
+ write_msg: 'Schrijf uw bericht...',
371
+ limit_reached: 'U heeft de limiet van 30 berichten bereikt.',
372
+ too_many: 'Te veel berichten. Wacht even.',
373
+ error_occurred: 'Er is een fout opgetreden. Probeer het opnieuw.',
374
+ },
375
+ };
376
+ return msgs[lang] || msgs['EN'];
377
+ }
378
+
379
+ getWelcomeMessage(first_name = '') {
380
+ const lang = (this.user_info?.language || 'EN').toUpperCase();
381
+ const client_name = getClientTheme(this.client_id).name;
382
+ const msgs = {
383
+ FR: `Bonjour ${first_name} ! 👋 Je m'appelle Maria, votre assistante virtuelle ${client_name}. Comment puis-je vous aider aujourd'hui ?`,
384
+ EN: `Hello ${first_name}! 👋 My name is Maria, your ${client_name} virtual assistant. How can I help you today?`,
385
+ DE: `Hallo ${first_name}! 👋 Ich bin Maria, Ihre virtuelle Assistentin von ${client_name}. Wie kann ich Ihnen helfen?`,
386
+ ES: `¡Hola ${first_name}! 👋 Me llamo Maria, tu asistente virtual de ${client_name}. ¿En qué puedo ayudarte?`,
387
+ IT: `Ciao ${first_name}! 👋 Mi chiamo Maria, la tua assistente virtuale di ${client_name}. Come posso aiutarti?`,
388
+ NL: `Hallo ${first_name}! 👋 Ik ben Maria, uw virtuele assistent van ${client_name}. Hoe kan ik u helpen?`,
389
+ };
390
+ return msgs[lang] || msgs['EN'];
391
+ }
392
+
130
393
  // ─── Theme ────────────────────────────────────────────────────────────────
131
394
 
132
395
  getThemeColor() {
@@ -167,10 +430,9 @@
167
430
  document.head.appendChild(style);
168
431
 
169
432
  const position = getClientTheme(this.client_id).position || 'right';
170
-
171
433
  const host = this.querySelector('.tp-chatbot-host');
172
434
  if (host) {
173
- host.style.width = '360px';
435
+ host.style.width = WINDOW_WIDTH;
174
436
  host.style.left = position === 'left' ? '24px' : 'auto';
175
437
  host.style.right = position === 'left' ? 'auto' : '24px';
176
438
  }
@@ -200,22 +462,24 @@
200
462
  <div class="tp-chatbot-avatar">M</div>
201
463
  <div style="flex:1">
202
464
  <div class="tp-chatbot-title">${theme.name} Support</div>
203
- <div class="tp-chatbot-subtitle" id="tp-subtitle">🤖 Assistant virtuel</div>
465
+ <div class="tp-chatbot-subtitle" id="tp-subtitle">🤖 Assistant</div>
204
466
  </div>
205
- <button id="tp-sound-btn" style="background:none;border:none;color:white;font-size:16px;cursor:pointer;padding:4px 8px;" title="Activer/désactiver le son">🔔</button>
206
- <button id="tp-back-btn" style="display:none;background:none;border:none;color:white;font-size:18px;cursor:pointer;padding:4px 8px;">←</button>
467
+ <button id="tp-sound-btn" style="background:none;border:none;color:white;font-size:15px;cursor:pointer;padding:4px 6px;" title="Sound">🔔</button>
468
+ <button id="tp-minimize-btn" style="background:none;border:none;color:white;font-size:15px;cursor:pointer;padding:4px 6px;" title="Minimize">─</button>
469
+ <button id="tp-maximize-btn" style="background:none;border:none;color:white;font-size:15px;cursor:pointer;padding:4px 6px;" title="Fullscreen">□</button>
470
+ <button id="tp-back-btn" style="display:none;background:none;border:none;color:white;font-size:18px;cursor:pointer;padding:4px 6px;">←</button>
207
471
  </div>
208
472
  <div class="tp-chatbot-messages" id="tp-messages"></div>
209
- <div class="tp-chatbot-agent-bar" id="tp-agent-bar">
210
- <button class="tp-chatbot-agent-btn" id="tp-agent-btn">👤 Parler à un agent</button>
473
+ <div class="tp-chatbot-agent-bar" id="tp-agent-bar" style="display:none;">
474
+ <button class="tp-chatbot-agent-btn" id="tp-agent-btn">👤 Talk to an agent</button>
211
475
  </div>
212
- <div class="tp-chatbot-input-bar" id="tp-chatbot-input-bar">
213
- <textarea class="tp-chatbot-input" id="tp-input" placeholder="Écrivez votre message..." rows="1"></textarea>
476
+ <div class="tp-chatbot-input-bar" id="tp-chatbot-input-bar" style="display:none;">
477
+ <textarea class="tp-chatbot-input" id="tp-input" placeholder="Write your message..." rows="1"></textarea>
214
478
  <button class="tp-chatbot-send" id="tp-send">➤</button>
215
479
  </div>
216
480
  <div id="tp-close-bar" style="padding:8px 12px;border-top:1px solid #ede8f5;text-align:center;display:none;">
217
481
  <button id="tp-close-btn" style="background:none;border:1px solid #e5e7eb;border-radius:8px;padding:6px 16px;font-size:12px;color:#9ca3af;cursor:pointer;font-weight:600;">
218
- Terminer la conversation
482
+ End conversation
219
483
  </button>
220
484
  </div>
221
485
  </div>
@@ -224,28 +488,115 @@
224
488
  `;
225
489
  }
226
490
 
491
+ // ─── Window controls ──────────────────────────────────────────────────────
492
+
493
+ minimize() {
494
+ const win = this.querySelector('#tp-window');
495
+ // Exit fullscreen first if needed
496
+ if (this.is_fullscreen) this.exitFullscreen(false);
497
+ this.is_minimized = true;
498
+ win.style.height = '56px';
499
+ win.style.overflow = 'hidden';
500
+ }
501
+
502
+ enterFullscreen() {
503
+ const win = this.querySelector('#tp-window');
504
+ const host = this.querySelector('.tp-chatbot-host');
505
+ this.is_fullscreen = true;
506
+ this.is_minimized = false;
507
+ win.style.position = 'fixed';
508
+ win.style.top = '0';
509
+ win.style.left = '0';
510
+ win.style.width = '100vw';
511
+ win.style.height = '100vh';
512
+ win.style.borderRadius = '0';
513
+ win.style.zIndex = '99999';
514
+ host.style.width = '100vw';
515
+ this.querySelector('#tp-maximize-btn').textContent = '⤡';
516
+ }
517
+
518
+ exitFullscreen(restore_height = true) {
519
+ const win = this.querySelector('#tp-window');
520
+ const position = getClientTheme(this.client_id).position || 'right';
521
+ const host = this.querySelector('.tp-chatbot-host');
522
+ this.is_fullscreen = false;
523
+ win.style.position = 'absolute';
524
+ win.style.top = '';
525
+ win.style.left = '0';
526
+ win.style.width = WINDOW_WIDTH;
527
+ win.style.borderRadius = '16px';
528
+ win.style.zIndex = '';
529
+ host.style.width = WINDOW_WIDTH;
530
+ host.style.left = position === 'left' ? '24px' : 'auto';
531
+ host.style.right = position === 'left' ? 'auto' : '24px';
532
+ if (restore_height) win.style.height = WINDOW_HEIGHT;
533
+ win.style.overflow = 'hidden';
534
+ this.querySelector('#tp-maximize-btn').textContent = '□';
535
+ }
536
+
537
+ // ─── Events ───────────────────────────────────────────────────────────────
538
+
227
539
  attachEvents() {
228
540
  this.querySelector('#tp-bubble').addEventListener('click', () => this.toggleChat());
229
541
  this.querySelector('#tp-send').addEventListener('click', () => this.sendMessage());
230
542
  this.querySelector('#tp-agent-btn').addEventListener('click', () => this.requestAgent());
231
543
  this.querySelector('#tp-back-btn').addEventListener('click', () => this.showConversationList());
544
+
232
545
  this.querySelector('#tp-input').addEventListener('keydown', e => {
233
546
  if (e.key === 'Enter' && !e.shiftKey) {
234
547
  e.preventDefault();
235
548
  this.sendMessage();
236
549
  }
237
550
  });
551
+
238
552
  this.querySelector('#tp-sound-btn').addEventListener('click', () => {
239
553
  this.sound_enabled = !this.sound_enabled;
240
- const btn = this.querySelector('#tp-sound-btn');
241
- btn.textContent = this.sound_enabled ? '🔔' : '🔕';
242
- btn.title = this.sound_enabled ? 'Désactiver le son' : 'Activer le son';
554
+ this.querySelector('#tp-sound-btn').textContent = this.sound_enabled ? '🔔' : '🔕';
243
555
  });
556
+
244
557
  this.querySelector('#tp-close-btn').addEventListener('click', () => {
245
- if (window.confirm('Voulez-vous terminer cette conversation ?')) {
558
+ if (window.confirm(this.getMessages().close_confirm)) {
246
559
  this.closeByUser();
247
560
  }
248
561
  });
562
+
563
+ this.querySelector('#tp-minimize-btn').addEventListener('click', () => {
564
+ if (this.is_minimized) {
565
+ // Restore
566
+ this.is_minimized = false;
567
+ const win = this.querySelector('#tp-window');
568
+ win.style.height = WINDOW_HEIGHT;
569
+ win.style.overflow = 'hidden';
570
+ } else {
571
+ this.minimize();
572
+ }
573
+ });
574
+
575
+ this.querySelector('#tp-maximize-btn').addEventListener('click', () => {
576
+ if (this.is_fullscreen) {
577
+ this.exitFullscreen(true);
578
+ } else {
579
+ if (this.is_minimized) {
580
+ this.is_minimized = false;
581
+ const win = this.querySelector('#tp-window');
582
+ win.style.height = WINDOW_HEIGHT;
583
+ win.style.overflow = 'hidden';
584
+ }
585
+ this.enterFullscreen();
586
+ }
587
+ });
588
+ }
589
+
590
+ updateUILanguage() {
591
+ const m = this.getMessages();
592
+ const agent_btn = this.querySelector('#tp-agent-btn');
593
+ if (agent_btn) agent_btn.textContent = m.speak_agent;
594
+ const close_btn = this.querySelector('#tp-close-btn');
595
+ if (close_btn) close_btn.textContent = m.close_btn;
596
+ const input = this.querySelector('#tp-input');
597
+ if (input) input.placeholder = m.write_msg;
598
+ const subtitle = this.querySelector('#tp-subtitle');
599
+ if (subtitle) subtitle.textContent = m.virtual;
249
600
  }
250
601
 
251
602
  toggleChat() {
@@ -255,9 +606,16 @@
255
606
  if (this.is_open) {
256
607
  window_el.classList.add('open');
257
608
  bubble.textContent = '✕';
609
+ // Restore from minimized if needed
610
+ if (this.is_minimized) {
611
+ this.is_minimized = false;
612
+ window_el.style.height = WINDOW_HEIGHT;
613
+ window_el.style.overflow = 'hidden';
614
+ }
258
615
  } else {
259
616
  window_el.classList.remove('open');
260
617
  bubble.textContent = '💬';
618
+ if (this.is_fullscreen) this.exitFullscreen(true);
261
619
  }
262
620
  }
263
621
 
@@ -282,10 +640,55 @@
282
640
  return data.result?.conversation_id;
283
641
  }
284
642
 
643
+ // ─── Suggestions ──────────────────────────────────────────────────────────
644
+
645
+ showSuggestions(suggestions) {
646
+ const existing = this.querySelector('#tp-suggestions');
647
+ if (existing) existing.remove();
648
+ if (!suggestions || suggestions.length === 0) return;
649
+
650
+ const lang = (this.user_info?.language || 'EN').toUpperCase();
651
+ const color = this.getThemeColor();
652
+ const container = this.querySelector('#tp-messages');
653
+
654
+ const wrap = document.createElement('div');
655
+ wrap.id = 'tp-suggestions';
656
+ wrap.style.cssText = 'padding:8px 12px 12px;display:flex;flex-direction:column;gap:6px;';
657
+
658
+ suggestions.forEach(s => {
659
+ const label = s[lang] || s['EN'];
660
+ const btn = document.createElement('button');
661
+ btn.style.cssText = `padding:8px 14px;border-radius:20px;border:1.5px solid ${color};background:white;color:${color};font-size:12px;font-weight:600;cursor:pointer;text-align:left;font-family:inherit;transition:all 0.12s;`;
662
+ btn.textContent = `→ ${label}`;
663
+ btn.addEventListener('mouseenter', () => {
664
+ btn.style.background = color;
665
+ btn.style.color = 'white';
666
+ });
667
+ btn.addEventListener('mouseleave', () => {
668
+ btn.style.background = 'white';
669
+ btn.style.color = color;
670
+ });
671
+ btn.addEventListener('click', () => {
672
+ const sugg_el = this.querySelector('#tp-suggestions');
673
+ if (sugg_el) sugg_el.remove();
674
+ const input = this.querySelector('#tp-input');
675
+ if (input) {
676
+ input.value = label;
677
+ this.sendMessage();
678
+ }
679
+ });
680
+ wrap.appendChild(btn);
681
+ });
682
+
683
+ container.appendChild(wrap);
684
+ container.scrollTop = container.scrollHeight;
685
+ }
686
+
285
687
  // ─── Conversation List ────────────────────────────────────────────────────
286
688
 
287
689
  async showConversationList() {
288
690
  this.view = 'list';
691
+ const m = this.getMessages();
289
692
 
290
693
  if (this.polling_interval) {
291
694
  clearInterval(this.polling_interval);
@@ -293,7 +696,7 @@
293
696
  }
294
697
 
295
698
  const subtitle = this.querySelector('#tp-subtitle');
296
- if (subtitle) subtitle.textContent = '🤖 Assistant virtuel';
699
+ if (subtitle) subtitle.textContent = m.virtual;
297
700
 
298
701
  const back_btn = this.querySelector('#tp-back-btn');
299
702
  if (back_btn) back_btn.style.display = 'none';
@@ -308,31 +711,29 @@
308
711
  if (close_bar) close_bar.style.display = 'none';
309
712
 
310
713
  const container = this.querySelector('#tp-messages');
311
- container.innerHTML = '<div style="padding:16px;text-align:center;color:#aaa;font-size:12px;">Chargement...</div>';
714
+ container.innerHTML = `<div style="padding:16px;text-align:center;color:#aaa;font-size:12px;">${m.loading}</div>`;
312
715
 
313
716
  try {
314
717
  const url = `${this.api_url}/chat/conversations?user_id=${encodeURIComponent(this.user_id)}&limit=5`;
315
718
  const response = await fetch(url, { headers: this.getHeaders() });
316
719
  const data = await response.json();
317
720
  const conversations = data.result?.conversations || [];
318
-
319
721
  const color = this.getThemeColor();
320
- const dark = this.shadeColor(color, -20);
321
722
 
322
723
  container.innerHTML = '';
323
724
 
324
725
  const header_el = document.createElement('div');
325
726
  header_el.style.cssText = 'padding:16px;border-bottom:1px solid #ede8f5;';
326
727
  header_el.innerHTML = `
327
- <div style="font-size:13px;font-weight:700;color:#1a1a2e;margin-bottom:4px;">Bonjour ${this.user_info?.first_name || ''} 👋</div>
328
- <div style="font-size:12px;color:#aaa;">Vos conversations récentes</div>
728
+ <div style="font-size:13px;font-weight:700;color:#1a1a2e;margin-bottom:4px;">${m.hello} ${this.user_info?.first_name || ''} 👋</div>
729
+ <div style="font-size:12px;color:#aaa;">${m.recent_conv}</div>
329
730
  `;
330
731
  container.appendChild(header_el);
331
732
 
332
733
  if (conversations.length === 0) {
333
734
  const empty = document.createElement('div');
334
735
  empty.style.cssText = 'padding:24px 16px;text-align:center;color:#aaa;font-size:13px;';
335
- empty.textContent = 'Aucune conversation pour le moment.';
736
+ empty.textContent = m.no_conv;
336
737
  container.appendChild(empty);
337
738
  } else {
338
739
  conversations.forEach(conv => {
@@ -342,14 +743,13 @@
342
743
 
343
744
  const is_closed = conv.status === 'closed';
344
745
  const status_label = is_closed
345
- ? '✅ Fermée'
746
+ ? '✅ Closed'
346
747
  : conv.status === 'agent'
347
748
  ? '👤 Agent'
348
749
  : conv.status === 'waiting_agent'
349
- ? '⏳ En attente'
750
+ ? '⏳ Waiting'
350
751
  : '🤖 Bot';
351
752
  const status_color = is_closed ? '#9ca3af' : conv.status === 'waiting_agent' ? '#f59e0b' : color;
352
-
353
753
  const date = new Date(conv.updated_at).toLocaleDateString('fr-FR', {
354
754
  day: '2-digit',
355
755
  month: '2-digit',
@@ -364,7 +764,6 @@
364
764
  </div>
365
765
  <div style="font-size:11px;color:#aaa;">${conv.message_count} message${conv.message_count > 1 ? 's' : ''}</div>
366
766
  `;
367
-
368
767
  item.addEventListener('click', () => this.openConversation(conv.conversation_id, is_closed));
369
768
  container.appendChild(item);
370
769
  });
@@ -372,16 +771,12 @@
372
771
 
373
772
  const new_btn_wrap = document.createElement('div');
374
773
  new_btn_wrap.style.cssText = 'padding:16px;';
375
- new_btn_wrap.innerHTML = `
376
- <button class="tp-conv-new-btn" style="width:100%;padding:12px;color:white;border:none;border-radius:10px;font-size:13px;font-weight:700;cursor:pointer;">
377
- 💬 Nouvelle conversation
378
- </button>
379
- `;
774
+ new_btn_wrap.innerHTML = `<button class="tp-conv-new-btn" style="width:100%;padding:12px;color:white;border:none;border-radius:10px;font-size:13px;font-weight:700;cursor:pointer;">${m.new_conv_btn}</button>`;
380
775
  new_btn_wrap.querySelector('button').addEventListener('click', () => this.startNewConversation());
381
776
  container.appendChild(new_btn_wrap);
382
777
  } catch (e) {
383
778
  console.error('showConversationList error:', e);
384
- container.innerHTML = '<div style="padding:16px;text-align:center;color:#ef4444;font-size:13px;">Erreur de chargement.</div>';
779
+ container.innerHTML = `<div style="padding:16px;text-align:center;color:#ef4444;font-size:13px;">${m.error_load}</div>`;
385
780
  }
386
781
  }
387
782
 
@@ -394,6 +789,7 @@
394
789
  this.agent_mode = false;
395
790
  this.agent_requested = false;
396
791
 
792
+ const m = this.getMessages();
397
793
  const back_btn = this.querySelector('#tp-back-btn');
398
794
  if (back_btn) back_btn.style.display = 'block';
399
795
 
@@ -433,16 +829,17 @@
433
829
  if (conv.status === 'agent') {
434
830
  this.agent_mode = true;
435
831
  const subtitle = this.querySelector('#tp-subtitle');
436
- if (subtitle) subtitle.textContent = '👤 Agent humain';
832
+ if (subtitle) subtitle.textContent = m.human_agent;
437
833
  if (agent_bar) agent_bar.style.display = 'none';
438
834
  if (input_bar) input_bar.style.display = 'flex';
439
835
  } else {
440
836
  if (input_bar) input_bar.style.display = 'flex';
441
- if (agent_bar) agent_bar.style.display = 'block';
837
+ if (agent_bar) agent_bar.style.display = 'none';
442
838
  const subtitle = this.querySelector('#tp-subtitle');
443
- if (subtitle) subtitle.textContent = '🤖 Assistant virtuel';
839
+ if (subtitle) subtitle.textContent = m.virtual;
444
840
  }
445
841
 
842
+ this.updateUILanguage();
446
843
  this.polling_interval = setInterval(() => this.pollMessages(), 3000);
447
844
  } catch (e) {
448
845
  console.error('openConversation error:', e);
@@ -458,6 +855,8 @@
458
855
  this.last_message_count = 0;
459
856
  this.conversation_id = null;
460
857
 
858
+ const m = this.getMessages();
859
+
461
860
  const back_btn = this.querySelector('#tp-back-btn');
462
861
  if (back_btn) back_btn.style.display = 'block';
463
862
 
@@ -468,7 +867,7 @@
468
867
  container.innerHTML = '';
469
868
 
470
869
  const subtitle = this.querySelector('#tp-subtitle');
471
- if (subtitle) subtitle.textContent = '🤖 Assistant virtuel';
870
+ if (subtitle) subtitle.textContent = m.virtual;
472
871
 
473
872
  const input_bar = this.querySelector('#tp-chatbot-input-bar');
474
873
  if (input_bar) input_bar.style.display = 'flex';
@@ -476,8 +875,16 @@
476
875
  const agent_bar = this.querySelector('#tp-agent-bar');
477
876
  if (agent_bar) agent_bar.style.display = 'none';
478
877
 
878
+ this.updateUILanguage();
479
879
  this.conversation_id = await this.createConversation();
480
- this.addMessage('assistant', `Bonjour ${this.user_info?.first_name || ''} ! Comment puis-je vous aider ?`);
880
+ this.addMessage('assistant', this.getWelcomeMessage(this.user_info?.first_name || ''));
881
+ this.last_message_count = 1;
882
+
883
+ const theme = getClientTheme(this.client_id);
884
+ if (theme.suggestions && theme.suggestions.length > 0) {
885
+ this.showSuggestions(theme.suggestions);
886
+ }
887
+
481
888
  this.polling_interval = setInterval(() => this.pollMessages(), 3000);
482
889
  }
483
890
 
@@ -489,7 +896,6 @@
489
896
 
490
897
  const input_bar = this.querySelector('#tp-chatbot-input-bar');
491
898
  if (input_bar) input_bar.style.display = 'none';
492
-
493
899
  const agent_bar = this.querySelector('#tp-agent-bar');
494
900
  if (agent_bar) agent_bar.style.display = 'none';
495
901
 
@@ -499,18 +905,17 @@
499
905
  const form_el = document.createElement('div');
500
906
  form_el.id = 'tp-guest-form';
501
907
  form_el.innerHTML = `
502
- <div style="padding: 16px; display: flex; flex-direction: column; gap: 12px;">
503
- <p style="margin: 0; font-size: 13px; color: #1a1a2e; font-weight: 600;">Avant de commencer, merci de vous identifier :</p>
504
- <input id="tp-guest-firstname" type="text" placeholder="Prénom *" style="padding: 10px 14px; border-radius: 8px; border: 1.5px solid #ede8f5; font-size: 13px; font-family: inherit; outline: none;" />
505
- <input id="tp-guest-company" type="text" placeholder="Nom d'entreprise *" style="padding: 10px 14px; border-radius: 8px; border: 1.5px solid #ede8f5; font-size: 13px; font-family: inherit; outline: none;" />
506
- <input id="tp-guest-email" type="email" placeholder="Email *" style="padding: 10px 14px; border-radius: 8px; border: 1.5px solid #ede8f5; font-size: 13px; font-family: inherit; outline: none;" />
507
- <button id="tp-guest-submit" style="padding: 10px; background: linear-gradient(135deg, ${color}, ${dark}); color: white; border: none; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer;">
508
- Commencer la discussion
908
+ <div style="padding:16px;display:flex;flex-direction:column;gap:12px;">
909
+ <p style="margin:0;font-size:13px;color:#1a1a2e;font-weight:600;">Before we start, please identify yourself:</p>
910
+ <input id="tp-guest-firstname" type="text" placeholder="First name *" style="padding:10px 14px;border-radius:8px;border:1.5px solid #ede8f5;font-size:13px;font-family:inherit;outline:none;" />
911
+ <input id="tp-guest-company" type="text" placeholder="Company name *" style="padding:10px 14px;border-radius:8px;border:1.5px solid #ede8f5;font-size:13px;font-family:inherit;outline:none;" />
912
+ <input id="tp-guest-email" type="email" placeholder="Email *" style="padding:10px 14px;border-radius:8px;border:1.5px solid #ede8f5;font-size:13px;font-family:inherit;outline:none;" />
913
+ <button id="tp-guest-submit" style="padding:10px;background:linear-gradient(135deg,${color},${dark});color:white;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;">
914
+ Start conversation
509
915
  </button>
510
- <p id="tp-guest-error" style="margin: 0; font-size: 12px; color: #c0392b; display: none;"></p>
916
+ <p id="tp-guest-error" style="margin:0;font-size:12px;color:#c0392b;display:none;"></p>
511
917
  </div>
512
918
  `;
513
-
514
919
  container.appendChild(form_el);
515
920
  this.querySelector('#tp-guest-submit').addEventListener('click', () => this.submitGuestForm());
516
921
  }
@@ -522,36 +927,41 @@
522
927
  const error_el = this.querySelector('#tp-guest-error');
523
928
 
524
929
  if (!first_name || !company_name || !email) {
525
- error_el.textContent = 'Veuillez remplir tous les champs.';
930
+ error_el.textContent = 'Please fill in all fields.';
526
931
  error_el.style.display = 'block';
527
932
  return;
528
933
  }
529
-
530
- const email_valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
531
- if (!email_valid) {
532
- error_el.textContent = 'Veuillez entrer un email valide.';
934
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
935
+ error_el.textContent = 'Please enter a valid email.';
533
936
  error_el.style.display = 'block';
534
937
  return;
535
938
  }
536
939
 
537
- this.user_info = { user_id: `guest-${email}`, first_name, last_name: '', company_name, email, language: 'FR', site_id: '' };
940
+ this.user_info = { user_id: `guest-${email}`, first_name, last_name: '', company_name, email, language: 'EN', site_id: '' };
538
941
  this.user_id = this.user_info.user_id;
539
942
 
540
943
  const form_el = this.querySelector('#tp-guest-form');
541
944
  if (form_el) form_el.remove();
945
+
542
946
  this.view = 'chat';
543
947
  this.conversation_id = await this.createConversation();
544
948
 
545
949
  const input_bar = this.querySelector('#tp-chatbot-input-bar');
546
950
  if (input_bar) input_bar.style.display = 'flex';
547
-
548
951
  const agent_bar = this.querySelector('#tp-agent-bar');
549
- if (agent_bar) agent_bar.style.display = 'block';
550
-
952
+ if (agent_bar) agent_bar.style.display = 'none';
551
953
  const close_bar = this.querySelector('#tp-close-bar');
552
954
  if (close_bar) close_bar.style.display = 'block';
553
955
 
554
- this.addMessage('assistant', `Bonjour ${first_name} ! Comment puis-je vous aider aujourd'hui ?`);
956
+ this.updateUILanguage();
957
+ this.addMessage('assistant', this.getWelcomeMessage(first_name));
958
+ this.last_message_count = 1;
959
+
960
+ const theme = getClientTheme(this.client_id);
961
+ if (theme.suggestions && theme.suggestions.length > 0) {
962
+ this.showSuggestions(theme.suggestions);
963
+ }
964
+
555
965
  this.polling_interval = setInterval(() => this.pollMessages(), 3000);
556
966
  }
557
967
 
@@ -563,12 +973,12 @@
563
973
 
564
974
  const role_label =
565
975
  role === 'user'
566
- ? `👤 ${this.user_info?.first_name || 'Vous'}`
976
+ ? `👤 ${this.user_info?.first_name || 'You'}`
567
977
  : role === 'agent'
568
978
  ? '👨‍💼 Agent'
569
979
  : role === 'system'
570
980
  ? 'ℹ️ Info'
571
- : '🤖 Assistant';
981
+ : '🤖 Maria';
572
982
 
573
983
  const msg_el = document.createElement('div');
574
984
  msg_el.className = `tp-chatbot-message ${role}`;
@@ -611,32 +1021,32 @@
611
1021
  this.polling_interval = null;
612
1022
  }
613
1023
 
1024
+ const m = this.getMessages();
614
1025
  const subtitle = this.querySelector('#tp-subtitle');
615
- if (subtitle) subtitle.textContent = '✅ Conversation terminée';
1026
+ if (subtitle) subtitle.textContent = m.terminated;
616
1027
 
617
1028
  const input_bar = this.querySelector('#tp-chatbot-input-bar');
618
1029
  if (input_bar) input_bar.style.display = 'none';
619
-
620
1030
  const agent_bar = this.querySelector('#tp-agent-bar');
621
1031
  if (agent_bar) agent_bar.style.display = 'none';
622
-
623
1032
  const close_bar = this.querySelector('#tp-close-bar');
624
1033
  if (close_bar) close_bar.style.display = 'none';
1034
+ const sugg_el = this.querySelector('#tp-suggestions');
1035
+ if (sugg_el) sugg_el.remove();
625
1036
 
626
1037
  const container = this.querySelector('#tp-messages');
627
-
628
1038
  const existing_banner = this.querySelector('#tp-closed-banner');
629
1039
  if (existing_banner) {
630
- if (existing_banner.querySelector('#tp-csat-block')) return; // déjà complet
631
- existing_banner.remove(); // incomplet, on recrée
1040
+ if (existing_banner.querySelector('#tp-csat-block')) return;
1041
+ existing_banner.remove();
632
1042
  }
633
1043
 
634
1044
  const color = this.getThemeColor();
635
1045
  const dark = this.shadeColor(color, -20);
636
1046
 
637
1047
  const csat_html = existing_csat
638
- ? `<div style="font-size:13px;color:#888;margin-bottom:14px;">${existing_csat === 'positive' ? '👍 Merci pour votre retour !' : '👎 Merci, nous allons nous améliorer.'}</div>`
639
- : `<div style="font-size:12px;color:#888;margin-bottom:10px;">Cette conversation vous a-t-elle été utile ?</div>
1048
+ ? `<div style="font-size:13px;color:#888;margin-bottom:14px;">${existing_csat === 'positive' ? m.csat_positive : m.csat_negative}</div>`
1049
+ : `<div style="font-size:12px;color:#888;margin-bottom:10px;">${m.csat_question}</div>
640
1050
  <div style="display:flex;gap:10px;justify-content:center;margin-bottom:14px;">
641
1051
  <button id="tp-csat-positive" style="padding:8px 20px;border-radius:8px;border:1.5px solid #22c55e;background:white;color:#22c55e;font-size:18px;cursor:pointer;font-weight:700;">👍</button>
642
1052
  <button id="tp-csat-negative" style="padding:8px 20px;border-radius:8px;border:1.5px solid #ef4444;background:white;color:#ef4444;font-size:18px;cursor:pointer;font-weight:700;">👎</button>
@@ -646,14 +1056,10 @@
646
1056
  banner.id = 'tp-closed-banner';
647
1057
  banner.innerHTML = `
648
1058
  <div style="margin:16px;padding:16px;background:#f9f5ff;border:1px solid #ede8f5;border-radius:12px;text-align:center;">
649
- <div style="font-size:13px;color:#1a1a2e;font-weight:600;margin-bottom:8px;">✅ Conversation clôturée</div>
1059
+ <div style="font-size:13px;color:#1a1a2e;font-weight:600;margin-bottom:8px;">${m.closed}</div>
650
1060
  <div id="tp-csat-block">${csat_html}</div>
651
- <button id="tp-new-conversation" style="padding:10px 20px;background:linear-gradient(135deg,${color},${dark});color:white;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;margin-bottom:8px;width:100%;">
652
- 💬 Nouvelle conversation
653
- </button>
654
- <button id="tp-back-to-list" style="padding:8px 20px;background:none;color:${color};border:1px solid #ede8f5;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;width:100%;">
655
- ← Voir toutes les conversations
656
- </button>
1061
+ <button id="tp-new-conversation" style="padding:10px 20px;background:linear-gradient(135deg,${color},${dark});color:white;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;margin-bottom:8px;width:100%;">${m.new_conv}</button>
1062
+ <button id="tp-back-to-list" style="padding:8px 20px;background:none;color:${color};border:1px solid #ede8f5;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;width:100%;">${m.back_to_list}</button>
657
1063
  </div>
658
1064
  `;
659
1065
  container.appendChild(banner);
@@ -672,9 +1078,8 @@
672
1078
  } catch (e) {
673
1079
  console.error('CSAT error:', e);
674
1080
  }
675
- csat_block.innerHTML = `<div style="font-size:13px;color:#888;margin-bottom:14px;">${rating === 'positive' ? '👍 Merci pour votre retour !' : '👎 Merci, nous allons nous améliorer.'}</div>`;
1081
+ csat_block.innerHTML = `<div style="font-size:13px;color:#888;margin-bottom:14px;">${rating === 'positive' ? m.csat_positive : m.csat_negative}</div>`;
676
1082
  };
677
-
678
1083
  this.querySelector('#tp-csat-positive').addEventListener('click', () => submit_csat('positive'));
679
1084
  this.querySelector('#tp-csat-negative').addEventListener('click', () => submit_csat('negative'));
680
1085
  }
@@ -687,16 +1092,17 @@
687
1092
 
688
1093
  async sendMessage() {
689
1094
  if (this.is_closed || !this.conversation_id) return;
1095
+ const m = this.getMessages();
690
1096
  const input = this.querySelector('#tp-input');
691
1097
  const query = input.value.trim();
692
1098
  if (!query || this.is_loading) return;
693
1099
 
694
- const user_messages = this.messages.filter(m => m.role === 'user').length;
1100
+ const sugg_el = this.querySelector('#tp-suggestions');
1101
+ if (sugg_el) sugg_el.remove();
1102
+
1103
+ const user_messages = this.messages.filter(msg => msg.role === 'user').length;
695
1104
  if (user_messages >= 30) {
696
- this.addMessage(
697
- 'system',
698
- 'Vous avez atteint la limite de 30 messages. Veuillez contacter un agent ou écrire à support@travelplanet.com.'
699
- );
1105
+ this.addMessage('system', m.limit_reached);
700
1106
  const input_bar = this.querySelector('#tp-chatbot-input-bar');
701
1107
  if (input_bar) input_bar.style.display = 'none';
702
1108
  return;
@@ -718,7 +1124,7 @@
718
1124
  this.hideTyping();
719
1125
 
720
1126
  if (response.status === 429) {
721
- this.addMessage('system', data.error?.message || 'Trop de messages. Veuillez patienter avant de réessayer.');
1127
+ this.addMessage('system', data.error?.message || m.too_many);
722
1128
  this.is_loading = false;
723
1129
  return;
724
1130
  }
@@ -727,23 +1133,29 @@
727
1133
  const source = data.result.source || null;
728
1134
 
729
1135
  const subtitle = this.querySelector('#tp-subtitle');
730
- if (subtitle) subtitle.textContent = this.agent_mode ? '👤 Agent humain' : '🤖 Assistant virtuel';
1136
+ if (subtitle) subtitle.textContent = this.agent_mode ? m.human_agent : m.virtual;
731
1137
 
732
1138
  if (data.result.reply) {
733
1139
  this.addMessage(this.agent_mode ? 'agent' : 'assistant', data.result.reply);
1140
+ this.last_message_count += 2; // fix double message
734
1141
  }
735
1142
 
736
- // Afficher bouton agent si fallback ou ≥ 3 messages user
737
- const user_msg_count = this.messages.filter(m => m.role === 'user').length;
1143
+ const user_msg_count = this.messages.filter(msg => msg.role === 'user').length;
738
1144
  const agent_bar = this.querySelector('#tp-agent-bar');
739
1145
  if (agent_bar && !this.agent_requested && !this.agent_mode) {
740
- if (source === 'fallback' || user_msg_count >= 5) {
1146
+ if (
1147
+ source === 'fallback' ||
1148
+ source === 'clarification' ||
1149
+ source === 'no_match' ||
1150
+ source === 'out_of_scope' ||
1151
+ user_msg_count >= 5
1152
+ ) {
741
1153
  agent_bar.style.display = 'block';
742
1154
  }
743
1155
  }
744
1156
  } catch {
745
1157
  this.hideTyping();
746
- this.addMessage('assistant', 'Une erreur est survenue. Veuillez réessayer.');
1158
+ this.addMessage('assistant', m.error_occurred);
747
1159
  }
748
1160
 
749
1161
  this.is_loading = false;
@@ -763,6 +1175,7 @@
763
1175
  });
764
1176
  const data = await response.json();
765
1177
  this.addMessage('system', data.result.message);
1178
+ this.last_message_count += 1;
766
1179
 
767
1180
  if (data.result.status === 'no_agents' || data.result.status === 'outside_hours') {
768
1181
  this.agent_requested = false;
@@ -802,6 +1215,7 @@
802
1215
  const server_messages = conv.messages || [];
803
1216
  const server_status = conv.status;
804
1217
  const is_typing = conv.is_typing || false;
1218
+ const m = this.getMessages();
805
1219
 
806
1220
  if (server_status === 'closed' && !this.is_closed) {
807
1221
  this.showClosed();
@@ -830,6 +1244,10 @@
830
1244
  const typing_el = this.querySelector('#tp-agent-typing');
831
1245
  if (typing_el) typing_el.remove();
832
1246
  this.addMessage('agent', msg.content);
1247
+ } else if (msg.role === 'assistant') {
1248
+ this.addMessage('assistant', msg.content);
1249
+ } else if (msg.role === 'system') {
1250
+ this.addMessage('system', msg.content);
833
1251
  }
834
1252
  });
835
1253
  this.last_message_count = server_messages.length;
@@ -838,19 +1256,19 @@
838
1256
  if (server_status === 'agent' && !this.agent_mode) {
839
1257
  this.agent_mode = true;
840
1258
  const subtitle = this.querySelector('#tp-subtitle');
841
- if (subtitle) subtitle.textContent = '👤 Agent humain';
1259
+ if (subtitle) subtitle.textContent = m.human_agent;
842
1260
  const agent_bar = this.querySelector('#tp-agent-bar');
843
1261
  if (agent_bar) agent_bar.style.display = 'none';
844
- this.addMessage('system', 'Un agent a pris en charge votre conversation. Vous pouvez lui écrire directement.');
1262
+ this.addMessage('system', m.agent_taken);
845
1263
  this.last_message_count = server_messages.length;
846
1264
  } else if (server_status === 'bot' && this.agent_mode) {
847
1265
  this.agent_mode = false;
848
1266
  this.agent_requested = false;
849
1267
  const subtitle = this.querySelector('#tp-subtitle');
850
- if (subtitle) subtitle.textContent = '🤖 Assistant virtuel';
1268
+ if (subtitle) subtitle.textContent = m.virtual;
851
1269
  const agent_bar = this.querySelector('#tp-agent-bar');
852
1270
  if (agent_bar) agent_bar.style.display = 'block';
853
- this.addMessage('system', 'Notre assistant virtuel reprend la conversation. Comment puis-je vous aider ?');
1271
+ this.addMessage('system', m.agent_released);
854
1272
  this.last_message_count = server_messages.length;
855
1273
  }
856
1274
  } catch (e) {