@developpement/tp-chatbot-widget 0.0.8 → 0.0.10
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 +2 -3
- package/src/chatbot.css +254 -39
- package/src/chatbot.js +420 -258
package/src/chatbot.js
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
// ─── SVG Icons ──────────────────────────────────────────────────────────────
|
|
5
|
+
// viewBox 0 0 20 20 | stroke-width 1.6 | round caps & joins | fill none | stroke currentColor
|
|
6
|
+
|
|
7
|
+
const ICONS = {
|
|
8
|
+
bell: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10 2a6 6 0 0 0-6 6c0 3.5-1.5 5-1.5 5h15s-1.5-1.5-1.5-5a6 6 0 0 0-6-6Z"/><path d="M11.73 17a2 2 0 0 1-3.46 0"/></svg>`,
|
|
9
|
+
bell_off: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4 16 16M8.27 3.05A6 6 0 0 1 16 8c0 2.2.5 3.7 1 4.7M6.15 6.17A5.97 5.97 0 0 0 4 8c0 3.5-1.5 5-1.5 5H14M11.73 17a2 2 0 0 1-3.46 0"/></svg>`,
|
|
10
|
+
minimize: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="10" x2="16" y2="10"/></svg>`,
|
|
11
|
+
restore: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="3" width="10" height="10" rx="1.5"/><path d="M3 7v10h10"/></svg>`,
|
|
12
|
+
back: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M13 16 7 10l6-6"/></svg>`,
|
|
13
|
+
close: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5l10 10M15 5 5 15"/></svg>`,
|
|
14
|
+
user: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="10" cy="7" r="3.5"/><path d="M3 17c0-3.3 3.1-6 7-6s7 2.7 7 6"/></svg>`,
|
|
15
|
+
agent: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10a7 7 0 1 1 14 0"/><path d="M2 10h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1Z"/><path d="M16 10h1a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1Z"/><path d="M16 13v1a3 3 0 0 1-3 3h-2"/></svg>`,
|
|
16
|
+
bot: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="14" height="10" rx="2"/><path d="M7 11h.01M13 11h.01M7 14.5h6"/><circle cx="10" cy="4" r="1.5"/><line x1="10" y1="5.5" x2="10" y2="7"/></svg>`,
|
|
17
|
+
send: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3 3 9l5 2 2 5 7-13Z"/><path d="m8 11 4-4"/></svg>`,
|
|
18
|
+
attach: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M16.5 10.5 9 18a5 5 0 0 1-7-7l8.5-8.5a3 3 0 0 1 4.24 4.24L6 15.5a1 1 0 0 1-1.42-1.42L12 7"/></svg>`,
|
|
19
|
+
helpful: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M7 10V17M7 10l3-7a3 3 0 0 1 3 3v2h3.5a1 1 0 0 1 1 1.15l-.9 5A1 1 0 0 1 15.6 17H7"/><rect x="3" y="10" width="4" height="7" rx="1"/></svg>`,
|
|
20
|
+
not_helpful: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M13 10V3M13 10l-3 7a3 3 0 0 1-3-3v-2H3.5a1 1 0 0 1-1-1.15l.9-5A1 1 0 0 1 4.4 3H13"/><rect x="13" y="3" width="4" height="7" rx="1"/></svg>`,
|
|
21
|
+
check: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M4 10l5 5 7-8"/></svg>`,
|
|
22
|
+
msg_plus: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h2l2 3 2-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2Z"/><path d="M10 7v5M7.5 9.5h5"/></svg>`,
|
|
23
|
+
msg: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h2l2 3 2-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2Z"/></svg>`,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function icon(name, size = 18, extra_style = '') {
|
|
27
|
+
const svg = ICONS[name];
|
|
28
|
+
if (!svg) return '';
|
|
29
|
+
return svg.replace(
|
|
30
|
+
'<svg ',
|
|
31
|
+
`<svg width="${size}" height="${size}" style="display:inline-block;vertical-align:middle;flex-shrink:0;${extra_style}" `
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── Clients ─────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
4
37
|
const CLIENT_THEMES = {
|
|
5
38
|
flix: {
|
|
6
|
-
primary: '#
|
|
39
|
+
primary: '#97d700',
|
|
7
40
|
name: 'Flix Corporate',
|
|
8
41
|
position: 'left',
|
|
9
42
|
suggestions: [
|
|
@@ -83,7 +116,6 @@
|
|
|
83
116
|
};
|
|
84
117
|
|
|
85
118
|
const DEFAULT_THEME = { primary: '#7b1fa2', name: 'Support', position: 'right', suggestions: [] };
|
|
86
|
-
|
|
87
119
|
const WINDOW_WIDTH = '380px';
|
|
88
120
|
const WINDOW_HEIGHT = '580px';
|
|
89
121
|
|
|
@@ -111,19 +143,21 @@
|
|
|
111
143
|
function playSound() {
|
|
112
144
|
try {
|
|
113
145
|
const ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
114
|
-
const
|
|
146
|
+
const osc = ctx.createOscillator();
|
|
115
147
|
const gain = ctx.createGain();
|
|
116
|
-
|
|
148
|
+
osc.connect(gain);
|
|
117
149
|
gain.connect(ctx.destination);
|
|
118
|
-
|
|
119
|
-
|
|
150
|
+
osc.frequency.value = 880;
|
|
151
|
+
osc.type = 'sine';
|
|
120
152
|
gain.gain.setValueAtTime(0.2, ctx.currentTime);
|
|
121
153
|
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.4);
|
|
122
|
-
|
|
123
|
-
|
|
154
|
+
osc.start(ctx.currentTime);
|
|
155
|
+
osc.stop(ctx.currentTime + 0.4);
|
|
124
156
|
} catch {}
|
|
125
157
|
}
|
|
126
158
|
|
|
159
|
+
// ─── Component ───────────────────────────────────────────────────────────────
|
|
160
|
+
|
|
127
161
|
class TpChatbot extends HTMLElement {
|
|
128
162
|
constructor() {
|
|
129
163
|
super();
|
|
@@ -169,6 +203,30 @@
|
|
|
169
203
|
}
|
|
170
204
|
}
|
|
171
205
|
|
|
206
|
+
async fetchTheme() {
|
|
207
|
+
try {
|
|
208
|
+
const response = await fetch(`${this.api_url}/chat/config/theme/${this.client_id}`);
|
|
209
|
+
if (!response.ok) return;
|
|
210
|
+
const data = await response.json();
|
|
211
|
+
const t = data.result?.result;
|
|
212
|
+
if (!t) return;
|
|
213
|
+
if (CLIENT_THEMES[this.client_id]) {
|
|
214
|
+
if (t.primary) CLIENT_THEMES[this.client_id].primary = t.primary;
|
|
215
|
+
if (t.position) CLIENT_THEMES[this.client_id].position = t.position;
|
|
216
|
+
if (t.name) CLIENT_THEMES[this.client_id].name = t.name;
|
|
217
|
+
} else {
|
|
218
|
+
CLIENT_THEMES[this.client_id] = {
|
|
219
|
+
primary: t.primary || DEFAULT_THEME.primary,
|
|
220
|
+
position: t.position || DEFAULT_THEME.position,
|
|
221
|
+
name: t.name || DEFAULT_THEME.name,
|
|
222
|
+
suggestions: [],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {
|
|
226
|
+
console.warn('[tp-chatbot] fetchTheme failed, using defaults:', e.message);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
172
230
|
connectedCallback() {
|
|
173
231
|
this.client_id = this.getAttribute('client-id') || 'flix';
|
|
174
232
|
this.access_token = this.getAttribute('access-token') || '';
|
|
@@ -183,194 +241,266 @@
|
|
|
183
241
|
this.is_fullscreen = false;
|
|
184
242
|
this.is_minimized = false;
|
|
185
243
|
|
|
186
|
-
this.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
244
|
+
this.fetchTheme().then(() => {
|
|
245
|
+
this.render();
|
|
246
|
+
this.applyTheme();
|
|
247
|
+
this.attachEvents();
|
|
248
|
+
this._initialized = true;
|
|
190
249
|
|
|
191
|
-
if (this.access_token) {
|
|
192
|
-
this.user_info = extractUserInfo(this.access_token);
|
|
193
|
-
this.user_id = this.user_info?.user_id;
|
|
194
|
-
this.showConversationList();
|
|
195
|
-
} else {
|
|
196
250
|
setTimeout(() => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
251
|
+
this.toggleChat();
|
|
252
|
+
}, 300);
|
|
253
|
+
|
|
254
|
+
if (this.access_token) {
|
|
255
|
+
this.user_info = extractUserInfo(this.access_token);
|
|
256
|
+
this.user_id = this.user_info?.user_id;
|
|
257
|
+
this.showConversationList();
|
|
258
|
+
} else {
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
const token = this.getAttribute('access-token');
|
|
261
|
+
if (token) {
|
|
262
|
+
this.access_token = token;
|
|
263
|
+
this.user_info = extractUserInfo(token);
|
|
264
|
+
this.user_id = this.user_info?.user_id;
|
|
265
|
+
this.showConversationList();
|
|
266
|
+
} else {
|
|
267
|
+
this.showGuestForm();
|
|
268
|
+
}
|
|
269
|
+
}, 500);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
208
272
|
}
|
|
209
273
|
|
|
210
274
|
disconnectedCallback() {
|
|
211
275
|
if (this.polling_interval) clearInterval(this.polling_interval);
|
|
212
276
|
}
|
|
213
277
|
|
|
214
|
-
// ─── i18n
|
|
278
|
+
// ─── i18n ──────────────────────────────────────────────────────────────────
|
|
215
279
|
|
|
216
280
|
getMessages() {
|
|
217
281
|
const lang = (this.user_info?.language || 'EN').toUpperCase();
|
|
218
282
|
const msgs = {
|
|
219
283
|
FR: {
|
|
220
|
-
closed: '
|
|
284
|
+
closed: 'Conversation clôturée',
|
|
221
285
|
csat_question: 'Cette conversation vous a-t-elle été utile ?',
|
|
222
|
-
csat_positive: '
|
|
223
|
-
csat_negative: '
|
|
224
|
-
new_conv: '
|
|
225
|
-
back_to_list: '
|
|
286
|
+
csat_positive: 'Merci pour votre retour !',
|
|
287
|
+
csat_negative: 'Merci, nous allons nous améliorer.',
|
|
288
|
+
new_conv: 'Nouvelle conversation',
|
|
289
|
+
back_to_list: 'Voir toutes les conversations',
|
|
226
290
|
agent_taken: 'Un agent a pris en charge votre conversation. Vous pouvez lui écrire directement.',
|
|
227
291
|
agent_released: 'Notre assistant virtuel reprend la conversation. Comment puis-je vous aider ?',
|
|
228
292
|
close_confirm: 'Voulez-vous terminer cette conversation ?',
|
|
229
|
-
terminated: '
|
|
230
|
-
virtual: '
|
|
231
|
-
human_agent: '
|
|
293
|
+
terminated: 'Conversation terminée',
|
|
294
|
+
virtual: 'Assistant virtuel',
|
|
295
|
+
human_agent: 'Agent humain',
|
|
232
296
|
loading: 'Chargement...',
|
|
233
297
|
no_conv: 'Aucune conversation pour le moment.',
|
|
234
|
-
new_conv_btn: '
|
|
298
|
+
new_conv_btn: 'Nouvelle conversation',
|
|
235
299
|
error_load: 'Erreur de chargement.',
|
|
236
300
|
recent_conv: 'Vos conversations récentes',
|
|
237
301
|
hello: 'Bonjour',
|
|
238
|
-
close_btn: '
|
|
239
|
-
speak_agent: '
|
|
302
|
+
close_btn: 'Terminer la conversation',
|
|
303
|
+
speak_agent: 'Parler à un agent',
|
|
240
304
|
write_msg: 'Écrivez votre message...',
|
|
241
305
|
limit_reached: 'Vous avez atteint la limite de 30 messages. Veuillez contacter un agent ou écrire à support@travelplanet.com.',
|
|
242
306
|
too_many: 'Trop de messages. Veuillez patienter avant de réessayer.',
|
|
243
307
|
error_occurred: 'Une erreur est survenue. Veuillez réessayer.',
|
|
308
|
+
outside_hours: next => `Notre support est actuellement fermé. Prochain créneau : ${next}.`,
|
|
309
|
+
no_agents: 'Aucun agent disponible pour le moment. Veuillez réessayer plus tard.',
|
|
310
|
+
waiting_agent: 'Votre demande a été transmise. Un agent va prendre en charge votre conversation.',
|
|
311
|
+
waiting_agent_already: "Vous êtes déjà en file d'attente. Merci de patienter.",
|
|
312
|
+
agent_already: 'Un agent gère déjà votre conversation.',
|
|
313
|
+
suggest_agent_escalation: "Je n'arrive pas à trouver une réponse adaptée. Souhaitez-vous parler à un agent ?",
|
|
314
|
+
status_closed: 'Fermé',
|
|
315
|
+
status_agent: 'Agent',
|
|
316
|
+
status_waiting: 'En attente',
|
|
317
|
+
status_bot: 'Bot',
|
|
318
|
+
messages: 'message',
|
|
244
319
|
},
|
|
245
320
|
EN: {
|
|
246
|
-
closed: '
|
|
321
|
+
closed: 'Conversation closed',
|
|
247
322
|
csat_question: 'Was this conversation helpful?',
|
|
248
|
-
csat_positive: '
|
|
249
|
-
csat_negative: '
|
|
250
|
-
new_conv: '
|
|
251
|
-
back_to_list: '
|
|
323
|
+
csat_positive: 'Thank you for your feedback!',
|
|
324
|
+
csat_negative: 'Thank you, we will improve.',
|
|
325
|
+
new_conv: 'New conversation',
|
|
326
|
+
back_to_list: 'Back to conversations',
|
|
252
327
|
agent_taken: 'An agent has taken over your conversation. You can now write to them directly.',
|
|
253
328
|
agent_released: 'Our virtual assistant is back. How can I help you?',
|
|
254
329
|
close_confirm: 'Do you want to end this conversation?',
|
|
255
|
-
terminated: '
|
|
256
|
-
virtual: '
|
|
257
|
-
human_agent: '
|
|
330
|
+
terminated: 'Conversation ended',
|
|
331
|
+
virtual: 'Virtual assistant',
|
|
332
|
+
human_agent: 'Human agent',
|
|
258
333
|
loading: 'Loading...',
|
|
259
334
|
no_conv: 'No conversations yet.',
|
|
260
|
-
new_conv_btn: '
|
|
335
|
+
new_conv_btn: 'New conversation',
|
|
261
336
|
error_load: 'Loading error.',
|
|
262
337
|
recent_conv: 'Your recent conversations',
|
|
263
338
|
hello: 'Hello',
|
|
264
|
-
close_btn: '
|
|
265
|
-
speak_agent: '
|
|
339
|
+
close_btn: 'End conversation',
|
|
340
|
+
speak_agent: 'Talk to an agent',
|
|
266
341
|
write_msg: 'Write your message...',
|
|
267
342
|
limit_reached: 'You have reached the 30 message limit. Please contact an agent or write to support@travelplanet.com.',
|
|
268
343
|
too_many: 'Too many messages. Please wait before retrying.',
|
|
269
344
|
error_occurred: 'An error occurred. Please try again.',
|
|
345
|
+
outside_hours: next => `Our support is currently closed. Next available slot: ${next}.`,
|
|
346
|
+
no_agents: 'No agent available at the moment. Please try again later.',
|
|
347
|
+
waiting_agent: 'Your request has been sent. An agent will take over shortly.',
|
|
348
|
+
waiting_agent_already: 'You are already in the queue. Please wait.',
|
|
349
|
+
agent_already: 'An agent is already handling your conversation.',
|
|
350
|
+
suggest_agent_escalation: "I'm having trouble finding a suitable answer. Would you like to speak to an agent?",
|
|
351
|
+
status_closed: 'Closed',
|
|
352
|
+
status_agent: 'Agent',
|
|
353
|
+
status_waiting: 'Waiting',
|
|
354
|
+
status_bot: 'Bot',
|
|
355
|
+
messages: 'message',
|
|
270
356
|
},
|
|
271
357
|
DE: {
|
|
272
|
-
closed: '
|
|
358
|
+
closed: 'Gespräch beendet',
|
|
273
359
|
csat_question: 'War dieses Gespräch hilfreich?',
|
|
274
|
-
csat_positive: '
|
|
275
|
-
csat_negative: '
|
|
276
|
-
new_conv: '
|
|
277
|
-
back_to_list: '
|
|
360
|
+
csat_positive: 'Vielen Dank für Ihr Feedback!',
|
|
361
|
+
csat_negative: 'Danke, wir werden uns verbessern.',
|
|
362
|
+
new_conv: 'Neues Gespräch',
|
|
363
|
+
back_to_list: 'Zurück zur Liste',
|
|
278
364
|
agent_taken: 'Ein Agent hat Ihr Gespräch übernommen. Sie können ihm jetzt direkt schreiben.',
|
|
279
365
|
agent_released: 'Unser virtueller Assistent ist zurück. Wie kann ich Ihnen helfen?',
|
|
280
366
|
close_confirm: 'Möchten Sie das Gespräch beenden?',
|
|
281
|
-
terminated: '
|
|
282
|
-
virtual: '
|
|
283
|
-
human_agent: '
|
|
367
|
+
terminated: 'Gespräch beendet',
|
|
368
|
+
virtual: 'Virtueller Assistent',
|
|
369
|
+
human_agent: 'Menschlicher Agent',
|
|
284
370
|
loading: 'Laden...',
|
|
285
371
|
no_conv: 'Noch keine Gespräche.',
|
|
286
|
-
new_conv_btn: '
|
|
372
|
+
new_conv_btn: 'Neues Gespräch',
|
|
287
373
|
error_load: 'Ladefehler.',
|
|
288
374
|
recent_conv: 'Ihre letzten Gespräche',
|
|
289
375
|
hello: 'Hallo',
|
|
290
|
-
close_btn: '
|
|
291
|
-
speak_agent: '
|
|
376
|
+
close_btn: 'Gespräch beenden',
|
|
377
|
+
speak_agent: 'Mit einem Agenten sprechen',
|
|
292
378
|
write_msg: 'Schreiben Sie Ihre Nachricht...',
|
|
293
379
|
limit_reached: 'Sie haben das Limit von 30 Nachrichten erreicht.',
|
|
294
380
|
too_many: 'Zu viele Nachrichten. Bitte warten Sie.',
|
|
295
381
|
error_occurred: 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.',
|
|
382
|
+
outside_hours: next => `Unser Support ist derzeit geschlossen. Nächster Termin: ${next}.`,
|
|
383
|
+
no_agents: 'Derzeit kein Agent verfügbar. Bitte versuchen Sie es später.',
|
|
384
|
+
waiting_agent: 'Ihre Anfrage wurde übermittelt. Ein Agent übernimmt gleich.',
|
|
385
|
+
waiting_agent_already: 'Sie sind bereits in der Warteschlange. Bitte warten.',
|
|
386
|
+
agent_already: 'Ein Agent betreut bereits Ihr Gespräch.',
|
|
387
|
+
suggest_agent_escalation: 'Ich kann keine passende Antwort finden. Möchten Sie mit einem Agenten sprechen?',
|
|
388
|
+
status_closed: 'Geschlossen',
|
|
389
|
+
status_agent: 'Agent',
|
|
390
|
+
status_waiting: 'Wartend',
|
|
391
|
+
status_bot: 'Bot',
|
|
392
|
+
messages: 'Nachricht',
|
|
296
393
|
},
|
|
297
394
|
ES: {
|
|
298
|
-
closed: '
|
|
395
|
+
closed: 'Conversación cerrada',
|
|
299
396
|
csat_question: '¿Fue útil esta conversación?',
|
|
300
|
-
csat_positive: '
|
|
301
|
-
csat_negative: '
|
|
302
|
-
new_conv: '
|
|
303
|
-
back_to_list: '
|
|
397
|
+
csat_positive: '¡Gracias por su opinión!',
|
|
398
|
+
csat_negative: 'Gracias, mejoraremos.',
|
|
399
|
+
new_conv: 'Nueva conversación',
|
|
400
|
+
back_to_list: 'Volver a las conversaciones',
|
|
304
401
|
agent_taken: 'Un agente ha tomado su conversación. Ahora puede escribirle directamente.',
|
|
305
402
|
agent_released: 'Nuestro asistente virtual ha vuelto. ¿En qué puedo ayudarle?',
|
|
306
403
|
close_confirm: '¿Desea finalizar esta conversación?',
|
|
307
|
-
terminated: '
|
|
308
|
-
virtual: '
|
|
309
|
-
human_agent: '
|
|
404
|
+
terminated: 'Conversación terminada',
|
|
405
|
+
virtual: 'Asistente virtual',
|
|
406
|
+
human_agent: 'Agente humano',
|
|
310
407
|
loading: 'Cargando...',
|
|
311
408
|
no_conv: 'No hay conversaciones por el momento.',
|
|
312
|
-
new_conv_btn: '
|
|
409
|
+
new_conv_btn: 'Nueva conversación',
|
|
313
410
|
error_load: 'Error de carga.',
|
|
314
411
|
recent_conv: 'Sus conversaciones recientes',
|
|
315
412
|
hello: 'Hola',
|
|
316
|
-
close_btn: '
|
|
317
|
-
speak_agent: '
|
|
413
|
+
close_btn: 'Finalizar conversación',
|
|
414
|
+
speak_agent: 'Hablar con un agente',
|
|
318
415
|
write_msg: 'Escriba su mensaje...',
|
|
319
416
|
limit_reached: 'Ha alcanzado el límite de 30 mensajes.',
|
|
320
417
|
too_many: 'Demasiados mensajes. Por favor espere.',
|
|
321
418
|
error_occurred: 'Se produjo un error. Por favor, inténtelo de nuevo.',
|
|
419
|
+
outside_hours: next => `Nuestro soporte está cerrado. Próximo horario: ${next}.`,
|
|
420
|
+
no_agents: 'No hay agentes disponibles. Inténtelo más tarde.',
|
|
421
|
+
waiting_agent: 'Su solicitud ha sido enviada. Un agente le atenderá pronto.',
|
|
422
|
+
waiting_agent_already: 'Ya está en la cola de espera. Por favor, espere.',
|
|
423
|
+
agent_already: 'Un agente ya está gestionando su conversación.',
|
|
424
|
+
suggest_agent_escalation: 'No encuentro una respuesta adecuada. ¿Desea hablar con un agente?',
|
|
425
|
+
status_closed: 'Cerrado',
|
|
426
|
+
status_agent: 'Agente',
|
|
427
|
+
status_waiting: 'Esperando',
|
|
428
|
+
status_bot: 'Bot',
|
|
429
|
+
messages: 'mensaje',
|
|
322
430
|
},
|
|
323
431
|
IT: {
|
|
324
|
-
closed: '
|
|
432
|
+
closed: 'Conversazione chiusa',
|
|
325
433
|
csat_question: 'Questa conversazione è stata utile?',
|
|
326
|
-
csat_positive: '
|
|
327
|
-
csat_negative: '
|
|
328
|
-
new_conv: '
|
|
329
|
-
back_to_list: '
|
|
434
|
+
csat_positive: 'Grazie per il suo feedback!',
|
|
435
|
+
csat_negative: 'Grazie, miglioreremo.',
|
|
436
|
+
new_conv: 'Nuova conversazione',
|
|
437
|
+
back_to_list: 'Torna alle conversazioni',
|
|
330
438
|
agent_taken: 'Un agente ha preso in carico la sua conversazione.',
|
|
331
439
|
agent_released: 'Il nostro assistente virtuale è tornato. Come posso aiutarla?',
|
|
332
440
|
close_confirm: 'Vuole terminare questa conversazione?',
|
|
333
|
-
terminated: '
|
|
334
|
-
virtual: '
|
|
335
|
-
human_agent: '
|
|
441
|
+
terminated: 'Conversazione terminata',
|
|
442
|
+
virtual: 'Assistente virtuale',
|
|
443
|
+
human_agent: 'Agente umano',
|
|
336
444
|
loading: 'Caricamento...',
|
|
337
445
|
no_conv: 'Nessuna conversazione per il momento.',
|
|
338
|
-
new_conv_btn: '
|
|
446
|
+
new_conv_btn: 'Nuova conversazione',
|
|
339
447
|
error_load: 'Errore di caricamento.',
|
|
340
448
|
recent_conv: 'Le sue conversazioni recenti',
|
|
341
449
|
hello: 'Ciao',
|
|
342
|
-
close_btn: '
|
|
343
|
-
speak_agent: '
|
|
450
|
+
close_btn: 'Termina conversazione',
|
|
451
|
+
speak_agent: 'Parla con un agente',
|
|
344
452
|
write_msg: 'Scrivi il tuo messaggio...',
|
|
345
453
|
limit_reached: 'Hai raggiunto il limite di 30 messaggi.',
|
|
346
454
|
too_many: 'Troppi messaggi. Attendere prego.',
|
|
347
455
|
error_occurred: 'Si è verificato un errore. Riprova.',
|
|
456
|
+
outside_hours: next => `Il supporto è chiuso. Prossima disponibilità: ${next}.`,
|
|
457
|
+
no_agents: 'Nessun agente disponibile. Riprova più tardi.',
|
|
458
|
+
waiting_agent: 'Richiesta inviata. Un agente prenderà in carico la conversazione.',
|
|
459
|
+
waiting_agent_already: 'È già in coda. Attenda.',
|
|
460
|
+
agent_already: 'Un agente sta già gestendo la conversazione.',
|
|
461
|
+
suggest_agent_escalation: 'Non riesco a trovare una risposta adeguata. Desidera parlare con un agente?',
|
|
462
|
+
status_closed: 'Chiuso',
|
|
463
|
+
status_agent: 'Agente',
|
|
464
|
+
status_waiting: 'Attesa',
|
|
465
|
+
status_bot: 'Bot',
|
|
466
|
+
messages: 'messaggio',
|
|
348
467
|
},
|
|
349
468
|
NL: {
|
|
350
|
-
closed: '
|
|
469
|
+
closed: 'Gesprek gesloten',
|
|
351
470
|
csat_question: 'Was dit gesprek nuttig?',
|
|
352
|
-
csat_positive: '
|
|
353
|
-
csat_negative: '
|
|
354
|
-
new_conv: '
|
|
355
|
-
back_to_list: '
|
|
471
|
+
csat_positive: 'Bedankt voor uw feedback!',
|
|
472
|
+
csat_negative: 'Bedankt, we zullen verbeteren.',
|
|
473
|
+
new_conv: 'Nieuw gesprek',
|
|
474
|
+
back_to_list: 'Terug naar gesprekken',
|
|
356
475
|
agent_taken: 'Een agent heeft uw gesprek overgenomen.',
|
|
357
476
|
agent_released: 'Onze virtuele assistent is terug. Hoe kan ik u helpen?',
|
|
358
477
|
close_confirm: 'Wilt u dit gesprek beëindigen?',
|
|
359
|
-
terminated: '
|
|
360
|
-
virtual: '
|
|
361
|
-
human_agent: '
|
|
478
|
+
terminated: 'Gesprek beëindigd',
|
|
479
|
+
virtual: 'Virtuele assistent',
|
|
480
|
+
human_agent: 'Menselijke agent',
|
|
362
481
|
loading: 'Laden...',
|
|
363
482
|
no_conv: 'Geen gesprekken op dit moment.',
|
|
364
|
-
new_conv_btn: '
|
|
365
|
-
error_load: '
|
|
483
|
+
new_conv_btn: 'Nieuw gesprek',
|
|
484
|
+
error_load: 'Laadout.',
|
|
366
485
|
recent_conv: 'Uw recente gesprekken',
|
|
367
486
|
hello: 'Hallo',
|
|
368
|
-
close_btn: '
|
|
369
|
-
speak_agent: '
|
|
487
|
+
close_btn: 'Gesprek beëindigen',
|
|
488
|
+
speak_agent: 'Praat met een agent',
|
|
370
489
|
write_msg: 'Schrijf uw bericht...',
|
|
371
490
|
limit_reached: 'U heeft de limiet van 30 berichten bereikt.',
|
|
372
491
|
too_many: 'Te veel berichten. Wacht even.',
|
|
373
492
|
error_occurred: 'Er is een fout opgetreden. Probeer het opnieuw.',
|
|
493
|
+
outside_hours: next => `Onze support is gesloten. Volgende beschikbare tijd: ${next}.`,
|
|
494
|
+
no_agents: 'Geen agent beschikbaar. Probeer het later opnieuw.',
|
|
495
|
+
waiting_agent: 'Uw verzoek is verzonden. Een agent neemt het gesprek over.',
|
|
496
|
+
waiting_agent_already: 'U staat al in de wachtrij. Even geduld.',
|
|
497
|
+
agent_already: 'Een agent beheert uw gesprek al.',
|
|
498
|
+
suggest_agent_escalation: 'Ik kan geen passend antwoord vinden. Wilt u met een agent spreken?',
|
|
499
|
+
status_closed: 'Gesloten',
|
|
500
|
+
status_agent: 'Agent',
|
|
501
|
+
status_waiting: 'Wachten',
|
|
502
|
+
status_bot: 'Bot',
|
|
503
|
+
messages: 'bericht',
|
|
374
504
|
},
|
|
375
505
|
};
|
|
376
506
|
return msgs[lang] || msgs['EN'];
|
|
@@ -380,17 +510,17 @@
|
|
|
380
510
|
const lang = (this.user_info?.language || 'EN').toUpperCase();
|
|
381
511
|
const client_name = getClientTheme(this.client_id).name;
|
|
382
512
|
const msgs = {
|
|
383
|
-
FR: `Bonjour ${first_name} !
|
|
384
|
-
EN: `Hello ${first_name}!
|
|
385
|
-
DE: `Hallo ${first_name}!
|
|
386
|
-
ES: `¡Hola ${first_name}!
|
|
387
|
-
IT: `Ciao ${first_name}!
|
|
388
|
-
NL: `Hallo ${first_name}!
|
|
513
|
+
FR: `Bonjour ${first_name} ! Je m'appelle Maria, votre assistante virtuelle ${client_name}. Comment puis-je vous aider aujourd'hui ?`,
|
|
514
|
+
EN: `Hello ${first_name}! My name is Maria, your ${client_name} virtual assistant. How can I help you today?`,
|
|
515
|
+
DE: `Hallo ${first_name}! Ich bin Maria, Ihre virtuelle Assistentin von ${client_name}. Wie kann ich Ihnen helfen?`,
|
|
516
|
+
ES: `¡Hola ${first_name}! Me llamo Maria, tu asistente virtual de ${client_name}. ¿En qué puedo ayudarte?`,
|
|
517
|
+
IT: `Ciao ${first_name}! Mi chiamo Maria, la tua assistente virtuale di ${client_name}. Come posso aiutarti?`,
|
|
518
|
+
NL: `Hallo ${first_name}! Ik ben Maria, uw virtuele assistent van ${client_name}. Hoe kan ik u helpen?`,
|
|
389
519
|
};
|
|
390
520
|
return msgs[lang] || msgs['EN'];
|
|
391
521
|
}
|
|
392
522
|
|
|
393
|
-
// ─── Theme
|
|
523
|
+
// ─── Theme ─────────────────────────────────────────────────────────────────
|
|
394
524
|
|
|
395
525
|
getThemeColor() {
|
|
396
526
|
return getClientTheme(this.client_id).primary;
|
|
@@ -426,6 +556,9 @@
|
|
|
426
556
|
#tp-new-conversation { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
|
|
427
557
|
.tp-conv-item:hover { background: ${color}11 !important; }
|
|
428
558
|
.tp-conv-new-btn { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
|
|
559
|
+
.tp-badge-closed { background: ${color}22 !important; color: ${dark} !important; }
|
|
560
|
+
.tp-attach-btn { color: ${color} !important; }
|
|
561
|
+
.tp-attach-btn:hover { background: ${color}18 !important; }
|
|
429
562
|
`;
|
|
430
563
|
document.head.appendChild(style);
|
|
431
564
|
|
|
@@ -436,13 +569,11 @@
|
|
|
436
569
|
host.style.left = position === 'left' ? '24px' : 'auto';
|
|
437
570
|
host.style.right = position === 'left' ? 'auto' : '24px';
|
|
438
571
|
}
|
|
439
|
-
|
|
440
572
|
const bubble = this.querySelector('.tp-chatbot-bubble');
|
|
441
573
|
if (bubble) {
|
|
442
574
|
bubble.style.marginLeft = position === 'left' ? '0' : 'auto';
|
|
443
575
|
bubble.style.marginRight = position === 'left' ? 'auto' : '0';
|
|
444
576
|
}
|
|
445
|
-
|
|
446
577
|
const win = this.querySelector('.tp-chatbot-window');
|
|
447
578
|
if (win) {
|
|
448
579
|
win.style.left = '0';
|
|
@@ -451,7 +582,7 @@
|
|
|
451
582
|
}
|
|
452
583
|
}
|
|
453
584
|
|
|
454
|
-
// ─── Render
|
|
585
|
+
// ─── Render ────────────────────────────────────────────────────────────────
|
|
455
586
|
|
|
456
587
|
render() {
|
|
457
588
|
const theme = getClientTheme(this.client_id);
|
|
@@ -460,39 +591,40 @@
|
|
|
460
591
|
<div class="tp-chatbot-window" id="tp-window">
|
|
461
592
|
<div class="tp-chatbot-header" id="tp-header">
|
|
462
593
|
<div class="tp-chatbot-avatar">M</div>
|
|
463
|
-
<div style="flex:1">
|
|
594
|
+
<div style="flex:1;min-width:0;">
|
|
464
595
|
<div class="tp-chatbot-title">${theme.name} Support</div>
|
|
465
|
-
<div class="tp-chatbot-subtitle" id="tp-subtitle"
|
|
596
|
+
<div class="tp-chatbot-subtitle" id="tp-subtitle">${icon('bot', 12, 'margin-right:4px')} Assistant</div>
|
|
466
597
|
</div>
|
|
467
|
-
<button id="tp-sound-btn"
|
|
468
|
-
<button id="tp-minimize-btn"
|
|
469
|
-
<button id="tp-maximize-btn"
|
|
470
|
-
<button id="tp-back-btn" style="display:none;
|
|
598
|
+
<button id="tp-sound-btn" class="tp-header-btn" title="Sound">${icon('bell', 16)}</button>
|
|
599
|
+
<button id="tp-minimize-btn" class="tp-header-btn" title="Minimize">${icon('minimize', 16)}</button>
|
|
600
|
+
<button id="tp-maximize-btn" class="tp-header-btn" title="Fullscreen">${icon('restore', 16)}</button>
|
|
601
|
+
<button id="tp-back-btn" class="tp-header-btn" style="display:none;" title="Back">${icon('back', 16)}</button>
|
|
471
602
|
</div>
|
|
472
603
|
<div class="tp-chatbot-messages" id="tp-messages"></div>
|
|
473
604
|
<div class="tp-chatbot-agent-bar" id="tp-agent-bar" style="display:none;">
|
|
474
|
-
<button class="tp-chatbot-agent-btn" id="tp-agent-btn"
|
|
605
|
+
<button class="tp-chatbot-agent-btn" id="tp-agent-btn">
|
|
606
|
+
<span style="display:inline-flex;align-items:center;gap:6px;">${icon('agent', 16)} Talk to an agent</span>
|
|
607
|
+
</button>
|
|
475
608
|
</div>
|
|
476
609
|
<div class="tp-chatbot-input-bar" id="tp-chatbot-input-bar" style="display:none;">
|
|
477
610
|
<textarea class="tp-chatbot-input" id="tp-input" placeholder="Write your message..." rows="1"></textarea>
|
|
478
|
-
<button class="tp-chatbot-send" id="tp-send"
|
|
611
|
+
<button class="tp-chatbot-send" id="tp-send">${icon('send', 16, 'color:white')}</button>
|
|
479
612
|
</div>
|
|
480
613
|
<div id="tp-close-bar" style="padding:8px 12px;border-top:1px solid #ede8f5;text-align:center;display:none;">
|
|
481
|
-
<button id="tp-close-btn"
|
|
482
|
-
|
|
614
|
+
<button id="tp-close-btn" class="tp-close-btn">
|
|
615
|
+
<span style="display:inline-flex;align-items:center;gap:6px;">${icon('check', 14)} End conversation</span>
|
|
483
616
|
</button>
|
|
484
617
|
</div>
|
|
485
618
|
</div>
|
|
486
|
-
<div class="tp-chatbot-bubble" id="tp-bubble"
|
|
619
|
+
<div class="tp-chatbot-bubble" id="tp-bubble">${icon('msg', 24, 'color:white')}</div>
|
|
487
620
|
</div>
|
|
488
621
|
`;
|
|
489
622
|
}
|
|
490
623
|
|
|
491
|
-
// ─── Window controls
|
|
624
|
+
// ─── Window controls ───────────────────────────────────────────────────────
|
|
492
625
|
|
|
493
626
|
minimize() {
|
|
494
627
|
const win = this.querySelector('#tp-window');
|
|
495
|
-
// Exit fullscreen first if needed
|
|
496
628
|
if (this.is_fullscreen) this.exitFullscreen(false);
|
|
497
629
|
this.is_minimized = true;
|
|
498
630
|
win.style.height = '56px';
|
|
@@ -512,7 +644,8 @@
|
|
|
512
644
|
win.style.borderRadius = '0';
|
|
513
645
|
win.style.zIndex = '99999';
|
|
514
646
|
host.style.width = '100vw';
|
|
515
|
-
this.querySelector('#tp-maximize-btn')
|
|
647
|
+
const btn = this.querySelector('#tp-maximize-btn');
|
|
648
|
+
if (btn) btn.innerHTML = icon('restore', 16);
|
|
516
649
|
}
|
|
517
650
|
|
|
518
651
|
exitFullscreen(restore_height = true) {
|
|
@@ -531,10 +664,11 @@
|
|
|
531
664
|
host.style.right = position === 'left' ? 'auto' : '24px';
|
|
532
665
|
if (restore_height) win.style.height = WINDOW_HEIGHT;
|
|
533
666
|
win.style.overflow = 'hidden';
|
|
534
|
-
this.querySelector('#tp-maximize-btn')
|
|
667
|
+
const btn = this.querySelector('#tp-maximize-btn');
|
|
668
|
+
if (btn) btn.innerHTML = icon('restore', 16);
|
|
535
669
|
}
|
|
536
670
|
|
|
537
|
-
// ─── Events
|
|
671
|
+
// ─── Events ────────────────────────────────────────────────────────────────
|
|
538
672
|
|
|
539
673
|
attachEvents() {
|
|
540
674
|
this.querySelector('#tp-bubble').addEventListener('click', () => this.toggleChat());
|
|
@@ -551,18 +685,15 @@
|
|
|
551
685
|
|
|
552
686
|
this.querySelector('#tp-sound-btn').addEventListener('click', () => {
|
|
553
687
|
this.sound_enabled = !this.sound_enabled;
|
|
554
|
-
this.querySelector('#tp-sound-btn').
|
|
688
|
+
this.querySelector('#tp-sound-btn').innerHTML = icon(this.sound_enabled ? 'bell' : 'bell_off', 16);
|
|
555
689
|
});
|
|
556
690
|
|
|
557
691
|
this.querySelector('#tp-close-btn').addEventListener('click', () => {
|
|
558
|
-
if (window.confirm(this.getMessages().close_confirm))
|
|
559
|
-
this.closeByUser();
|
|
560
|
-
}
|
|
692
|
+
if (window.confirm(this.getMessages().close_confirm)) this.closeByUser();
|
|
561
693
|
});
|
|
562
694
|
|
|
563
695
|
this.querySelector('#tp-minimize-btn').addEventListener('click', () => {
|
|
564
696
|
if (this.is_minimized) {
|
|
565
|
-
// Restore
|
|
566
697
|
this.is_minimized = false;
|
|
567
698
|
const win = this.querySelector('#tp-window');
|
|
568
699
|
win.style.height = WINDOW_HEIGHT;
|
|
@@ -590,13 +721,15 @@
|
|
|
590
721
|
updateUILanguage() {
|
|
591
722
|
const m = this.getMessages();
|
|
592
723
|
const agent_btn = this.querySelector('#tp-agent-btn');
|
|
593
|
-
if (agent_btn)
|
|
724
|
+
if (agent_btn)
|
|
725
|
+
agent_btn.innerHTML = `<span style="display:inline-flex;align-items:center;gap:6px;">${icon('agent', 16)} ${m.speak_agent}</span>`;
|
|
594
726
|
const close_btn = this.querySelector('#tp-close-btn');
|
|
595
|
-
if (close_btn)
|
|
727
|
+
if (close_btn)
|
|
728
|
+
close_btn.innerHTML = `<span style="display:inline-flex;align-items:center;gap:6px;">${icon('check', 14)} ${m.close_btn}</span>`;
|
|
596
729
|
const input = this.querySelector('#tp-input');
|
|
597
730
|
if (input) input.placeholder = m.write_msg;
|
|
598
731
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
599
|
-
if (subtitle) subtitle.
|
|
732
|
+
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
600
733
|
}
|
|
601
734
|
|
|
602
735
|
toggleChat() {
|
|
@@ -605,8 +738,7 @@
|
|
|
605
738
|
const bubble = this.querySelector('#tp-bubble');
|
|
606
739
|
if (this.is_open) {
|
|
607
740
|
window_el.classList.add('open');
|
|
608
|
-
bubble.
|
|
609
|
-
// Restore from minimized if needed
|
|
741
|
+
bubble.innerHTML = icon('close', 20, 'color:white');
|
|
610
742
|
if (this.is_minimized) {
|
|
611
743
|
this.is_minimized = false;
|
|
612
744
|
window_el.style.height = WINDOW_HEIGHT;
|
|
@@ -614,7 +746,7 @@
|
|
|
614
746
|
}
|
|
615
747
|
} else {
|
|
616
748
|
window_el.classList.remove('open');
|
|
617
|
-
bubble.
|
|
749
|
+
bubble.innerHTML = icon('msg', 24, 'color:white');
|
|
618
750
|
if (this.is_fullscreen) this.exitFullscreen(true);
|
|
619
751
|
}
|
|
620
752
|
}
|
|
@@ -630,11 +762,7 @@
|
|
|
630
762
|
const response = await fetch(`${this.api_url}/chat/conversations`, {
|
|
631
763
|
method: 'POST',
|
|
632
764
|
headers: this.getHeaders(true),
|
|
633
|
-
body: JSON.stringify({
|
|
634
|
-
user_id: this.user_id,
|
|
635
|
-
user_info: this.user_info,
|
|
636
|
-
client_id: this.client_id,
|
|
637
|
-
}),
|
|
765
|
+
body: JSON.stringify({ user_id: this.user_id, user_info: this.user_info, client_id: this.client_id }),
|
|
638
766
|
});
|
|
639
767
|
const data = await response.json();
|
|
640
768
|
return data.result?.conversation_id;
|
|
@@ -646,15 +774,12 @@
|
|
|
646
774
|
const existing = this.querySelector('#tp-suggestions');
|
|
647
775
|
if (existing) existing.remove();
|
|
648
776
|
if (!suggestions || suggestions.length === 0) return;
|
|
649
|
-
|
|
650
777
|
const lang = (this.user_info?.language || 'EN').toUpperCase();
|
|
651
778
|
const color = this.getThemeColor();
|
|
652
779
|
const container = this.querySelector('#tp-messages');
|
|
653
|
-
|
|
654
780
|
const wrap = document.createElement('div');
|
|
655
781
|
wrap.id = 'tp-suggestions';
|
|
656
782
|
wrap.style.cssText = 'padding:8px 12px 12px;display:flex;flex-direction:column;gap:6px;';
|
|
657
|
-
|
|
658
783
|
suggestions.forEach(s => {
|
|
659
784
|
const label = s[lang] || s['EN'];
|
|
660
785
|
const btn = document.createElement('button');
|
|
@@ -679,38 +804,29 @@
|
|
|
679
804
|
});
|
|
680
805
|
wrap.appendChild(btn);
|
|
681
806
|
});
|
|
682
|
-
|
|
683
807
|
container.appendChild(wrap);
|
|
684
808
|
container.scrollTop = container.scrollHeight;
|
|
685
809
|
}
|
|
686
810
|
|
|
687
|
-
// ─── Conversation List
|
|
811
|
+
// ─── Conversation List ─────────────────────────────────────────────────────
|
|
688
812
|
|
|
689
813
|
async showConversationList() {
|
|
690
814
|
this.view = 'list';
|
|
691
815
|
const m = this.getMessages();
|
|
692
|
-
|
|
693
816
|
if (this.polling_interval) {
|
|
694
817
|
clearInterval(this.polling_interval);
|
|
695
818
|
this.polling_interval = null;
|
|
696
819
|
}
|
|
697
820
|
|
|
698
821
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
699
|
-
if (subtitle) subtitle.
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const input_bar = this.querySelector('#tp-chatbot-input-bar');
|
|
705
|
-
if (input_bar) input_bar.style.display = 'none';
|
|
706
|
-
|
|
707
|
-
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
708
|
-
if (agent_bar) agent_bar.style.display = 'none';
|
|
709
|
-
|
|
710
|
-
const close_bar = this.querySelector('#tp-close-bar');
|
|
711
|
-
if (close_bar) close_bar.style.display = 'none';
|
|
822
|
+
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
823
|
+
this.querySelector('#tp-back-btn').style.display = 'none';
|
|
824
|
+
this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
|
|
825
|
+
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
826
|
+
this.querySelector('#tp-close-bar').style.display = 'none';
|
|
712
827
|
|
|
713
828
|
const container = this.querySelector('#tp-messages');
|
|
829
|
+
container.style.background = '#f5f5f7';
|
|
714
830
|
container.innerHTML = `<div style="padding:16px;text-align:center;color:#aaa;font-size:12px;">${m.loading}</div>`;
|
|
715
831
|
|
|
716
832
|
try {
|
|
@@ -721,35 +837,40 @@
|
|
|
721
837
|
const color = this.getThemeColor();
|
|
722
838
|
|
|
723
839
|
container.innerHTML = '';
|
|
840
|
+
container.style.background = 'white';
|
|
841
|
+
container.style.padding = '0';
|
|
724
842
|
|
|
843
|
+
// Header greeting
|
|
725
844
|
const header_el = document.createElement('div');
|
|
726
|
-
header_el.style.cssText = 'padding:16px;border-bottom:1px solid #
|
|
845
|
+
header_el.style.cssText = 'padding:16px;border-bottom:1px solid #f0f0f0;';
|
|
727
846
|
header_el.innerHTML = `
|
|
728
|
-
<div style="font-size:
|
|
847
|
+
<div style="font-size:14px;font-weight:700;color:#1a1a2e;margin-bottom:2px;">${m.hello} ${this.user_info?.first_name || ''} 👋</div>
|
|
729
848
|
<div style="font-size:12px;color:#aaa;">${m.recent_conv}</div>
|
|
730
849
|
`;
|
|
731
850
|
container.appendChild(header_el);
|
|
732
851
|
|
|
733
852
|
if (conversations.length === 0) {
|
|
734
853
|
const empty = document.createElement('div');
|
|
735
|
-
empty.style.cssText = 'padding:
|
|
736
|
-
empty.
|
|
854
|
+
empty.style.cssText = 'padding:32px 16px;text-align:center;color:#bbb;font-size:13px;';
|
|
855
|
+
empty.innerHTML = `<div style="margin-bottom:8px;opacity:0.4;">${icon('msg', 32)}</div>${m.no_conv}`;
|
|
737
856
|
container.appendChild(empty);
|
|
738
857
|
} else {
|
|
739
858
|
conversations.forEach(conv => {
|
|
740
859
|
const item = document.createElement('div');
|
|
741
860
|
item.className = 'tp-conv-item';
|
|
742
|
-
item.style.cssText =
|
|
861
|
+
item.style.cssText =
|
|
862
|
+
'padding:14px 16px;border-bottom:1px solid #f5f5f5;cursor:pointer;transition:background 0.15s;display:flex;justify-content:space-between;align-items:center;';
|
|
743
863
|
|
|
744
864
|
const is_closed = conv.status === 'closed';
|
|
745
865
|
const status_label = is_closed
|
|
746
|
-
?
|
|
866
|
+
? m.status_closed
|
|
747
867
|
: conv.status === 'agent'
|
|
748
|
-
?
|
|
868
|
+
? m.status_agent
|
|
749
869
|
: conv.status === 'waiting_agent'
|
|
750
|
-
?
|
|
751
|
-
:
|
|
752
|
-
|
|
870
|
+
? m.status_waiting
|
|
871
|
+
: m.status_bot;
|
|
872
|
+
|
|
873
|
+
const badge_color = is_closed ? color : conv.status === 'waiting_agent' ? '#f59e0b' : color;
|
|
753
874
|
const date = new Date(conv.updated_at).toLocaleDateString('fr-FR', {
|
|
754
875
|
day: '2-digit',
|
|
755
876
|
month: '2-digit',
|
|
@@ -758,20 +879,27 @@
|
|
|
758
879
|
});
|
|
759
880
|
|
|
760
881
|
item.innerHTML = `
|
|
761
|
-
<div
|
|
762
|
-
<
|
|
763
|
-
<
|
|
882
|
+
<div>
|
|
883
|
+
<div style="font-size:13px;font-weight:600;color:${is_closed ? '#888' : '#1a1a2e'};margin-bottom:3px;">${date}</div>
|
|
884
|
+
<div style="font-size:11px;color:#bbb;">${conv.message_count} ${m.messages}${conv.message_count > 1 ? 's' : ''}</div>
|
|
764
885
|
</div>
|
|
765
|
-
<
|
|
886
|
+
<span class="tp-badge-closed" style="display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;background:${badge_color}18;color:${badge_color};">
|
|
887
|
+
${is_closed ? icon('check', 11) : ''} ${status_label}
|
|
888
|
+
</span>
|
|
766
889
|
`;
|
|
767
890
|
item.addEventListener('click', () => this.openConversation(conv.conversation_id, is_closed));
|
|
768
891
|
container.appendChild(item);
|
|
769
892
|
});
|
|
770
893
|
}
|
|
771
894
|
|
|
895
|
+
// New conversation button
|
|
772
896
|
const new_btn_wrap = document.createElement('div');
|
|
773
897
|
new_btn_wrap.style.cssText = 'padding:16px;';
|
|
774
|
-
new_btn_wrap.innerHTML =
|
|
898
|
+
new_btn_wrap.innerHTML = `
|
|
899
|
+
<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;display:flex;align-items:center;justify-content:center;gap:8px;">
|
|
900
|
+
${icon('msg_plus', 16, 'color:white')} ${m.new_conv_btn}
|
|
901
|
+
</button>
|
|
902
|
+
`;
|
|
775
903
|
new_btn_wrap.querySelector('button').addEventListener('click', () => this.startNewConversation());
|
|
776
904
|
container.appendChild(new_btn_wrap);
|
|
777
905
|
} catch (e) {
|
|
@@ -790,10 +918,10 @@
|
|
|
790
918
|
this.agent_requested = false;
|
|
791
919
|
|
|
792
920
|
const m = this.getMessages();
|
|
793
|
-
|
|
794
|
-
if (back_btn) back_btn.style.display = 'block';
|
|
795
|
-
|
|
921
|
+
this.querySelector('#tp-back-btn').style.display = 'block';
|
|
796
922
|
const container = this.querySelector('#tp-messages');
|
|
923
|
+
container.style.background = '#f5f5f7';
|
|
924
|
+
container.style.padding = '16px';
|
|
797
925
|
container.innerHTML = '';
|
|
798
926
|
|
|
799
927
|
const input_bar = this.querySelector('#tp-chatbot-input-bar');
|
|
@@ -809,10 +937,9 @@
|
|
|
809
937
|
|
|
810
938
|
conv.messages.forEach(msg => {
|
|
811
939
|
if (['user', 'assistant', 'agent', 'system'].includes(msg.role)) {
|
|
812
|
-
this.addMessage(msg.role, msg.content);
|
|
940
|
+
this.addMessage(msg.role, msg.content, msg.message_id || null);
|
|
813
941
|
}
|
|
814
942
|
});
|
|
815
|
-
|
|
816
943
|
this.last_message_count = conv.messages.length;
|
|
817
944
|
|
|
818
945
|
if (conv.status === 'closed') {
|
|
@@ -829,14 +956,14 @@
|
|
|
829
956
|
if (conv.status === 'agent') {
|
|
830
957
|
this.agent_mode = true;
|
|
831
958
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
832
|
-
if (subtitle) subtitle.
|
|
959
|
+
if (subtitle) subtitle.innerHTML = `${icon('user', 12, 'margin-right:4px')} ${m.human_agent}`;
|
|
833
960
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
834
961
|
if (input_bar) input_bar.style.display = 'flex';
|
|
835
962
|
} else {
|
|
836
963
|
if (input_bar) input_bar.style.display = 'flex';
|
|
837
964
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
838
965
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
839
|
-
if (subtitle) subtitle.
|
|
966
|
+
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
840
967
|
}
|
|
841
968
|
|
|
842
969
|
this.updateUILanguage();
|
|
@@ -856,52 +983,40 @@
|
|
|
856
983
|
this.conversation_id = null;
|
|
857
984
|
|
|
858
985
|
const m = this.getMessages();
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
if (back_btn) back_btn.style.display = 'block';
|
|
862
|
-
|
|
863
|
-
const close_bar = this.querySelector('#tp-close-bar');
|
|
864
|
-
if (close_bar) close_bar.style.display = 'block';
|
|
986
|
+
this.querySelector('#tp-back-btn').style.display = 'block';
|
|
987
|
+
this.querySelector('#tp-close-bar').style.display = 'block';
|
|
865
988
|
|
|
866
989
|
const container = this.querySelector('#tp-messages');
|
|
990
|
+
container.style.background = '#f5f5f7';
|
|
991
|
+
container.style.padding = '16px';
|
|
867
992
|
container.innerHTML = '';
|
|
868
993
|
|
|
869
994
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
870
|
-
if (subtitle) subtitle.
|
|
871
|
-
|
|
872
|
-
const input_bar = this.querySelector('#tp-chatbot-input-bar');
|
|
873
|
-
if (input_bar) input_bar.style.display = 'flex';
|
|
874
|
-
|
|
875
|
-
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
876
|
-
if (agent_bar) agent_bar.style.display = 'none';
|
|
995
|
+
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
877
996
|
|
|
997
|
+
this.querySelector('#tp-chatbot-input-bar').style.display = 'flex';
|
|
998
|
+
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
878
999
|
this.updateUILanguage();
|
|
1000
|
+
|
|
879
1001
|
this.conversation_id = await this.createConversation();
|
|
880
1002
|
this.addMessage('assistant', this.getWelcomeMessage(this.user_info?.first_name || ''));
|
|
881
1003
|
this.last_message_count = 1;
|
|
882
1004
|
|
|
883
1005
|
const theme = getClientTheme(this.client_id);
|
|
884
|
-
if (theme.suggestions && theme.suggestions.length > 0)
|
|
885
|
-
this.showSuggestions(theme.suggestions);
|
|
886
|
-
}
|
|
1006
|
+
if (theme.suggestions && theme.suggestions.length > 0) this.showSuggestions(theme.suggestions);
|
|
887
1007
|
|
|
888
1008
|
this.polling_interval = setInterval(() => this.pollMessages(), 3000);
|
|
889
1009
|
}
|
|
890
1010
|
|
|
891
|
-
// ─── Guest form
|
|
1011
|
+
// ─── Guest form ────────────────────────────────────────────────────────────
|
|
892
1012
|
|
|
893
1013
|
showGuestForm() {
|
|
894
1014
|
const container = this.querySelector('#tp-messages');
|
|
895
1015
|
if (!container) return;
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
if (input_bar) input_bar.style.display = 'none';
|
|
899
|
-
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
900
|
-
if (agent_bar) agent_bar.style.display = 'none';
|
|
901
|
-
|
|
1016
|
+
this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
|
|
1017
|
+
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
902
1018
|
const color = this.getThemeColor();
|
|
903
1019
|
const dark = this.shadeColor(color, -20);
|
|
904
|
-
|
|
905
1020
|
const form_el = document.createElement('div');
|
|
906
1021
|
form_el.id = 'tp-guest-form';
|
|
907
1022
|
form_el.innerHTML = `
|
|
@@ -910,9 +1025,7 @@
|
|
|
910
1025
|
<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
1026
|
<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
1027
|
<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 →
|
|
915
|
-
</button>
|
|
1028
|
+
<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;">Start conversation →</button>
|
|
916
1029
|
<p id="tp-guest-error" style="margin:0;font-size:12px;color:#c0392b;display:none;"></p>
|
|
917
1030
|
</div>
|
|
918
1031
|
`;
|
|
@@ -925,7 +1038,6 @@
|
|
|
925
1038
|
const company_name = this.querySelector('#tp-guest-company')?.value.trim();
|
|
926
1039
|
const email = this.querySelector('#tp-guest-email')?.value.trim();
|
|
927
1040
|
const error_el = this.querySelector('#tp-guest-error');
|
|
928
|
-
|
|
929
1041
|
if (!first_name || !company_name || !email) {
|
|
930
1042
|
error_el.textContent = 'Please fill in all fields.';
|
|
931
1043
|
error_el.style.display = 'block';
|
|
@@ -946,57 +1058,86 @@
|
|
|
946
1058
|
this.view = 'chat';
|
|
947
1059
|
this.conversation_id = await this.createConversation();
|
|
948
1060
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
const
|
|
954
|
-
|
|
1061
|
+
this.querySelector('#tp-chatbot-input-bar').style.display = 'flex';
|
|
1062
|
+
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
1063
|
+
this.querySelector('#tp-close-bar').style.display = 'block';
|
|
1064
|
+
|
|
1065
|
+
const container = this.querySelector('#tp-messages');
|
|
1066
|
+
container.style.background = '#f5f5f7';
|
|
1067
|
+
container.style.padding = '16px';
|
|
955
1068
|
|
|
956
1069
|
this.updateUILanguage();
|
|
957
1070
|
this.addMessage('assistant', this.getWelcomeMessage(first_name));
|
|
958
1071
|
this.last_message_count = 1;
|
|
959
1072
|
|
|
960
1073
|
const theme = getClientTheme(this.client_id);
|
|
961
|
-
if (theme.suggestions && theme.suggestions.length > 0)
|
|
962
|
-
this.showSuggestions(theme.suggestions);
|
|
963
|
-
}
|
|
1074
|
+
if (theme.suggestions && theme.suggestions.length > 0) this.showSuggestions(theme.suggestions);
|
|
964
1075
|
|
|
965
1076
|
this.polling_interval = setInterval(() => this.pollMessages(), 3000);
|
|
966
1077
|
}
|
|
967
1078
|
|
|
968
|
-
// ─── Messages
|
|
1079
|
+
// ─── Messages ──────────────────────────────────────────────────────────────
|
|
969
1080
|
|
|
970
|
-
addMessage(role, content) {
|
|
971
|
-
this.messages.push({ role, content, created_at: new Date().toISOString() });
|
|
1081
|
+
addMessage(role, content, message_id = null) {
|
|
1082
|
+
this.messages.push({ role, content, message_id, created_at: new Date().toISOString() });
|
|
972
1083
|
const container = this.querySelector('#tp-messages');
|
|
973
1084
|
|
|
974
1085
|
const role_label =
|
|
975
1086
|
role === 'user'
|
|
976
|
-
?
|
|
1087
|
+
? `${icon('user', 11, 'margin-right:3px')} ${this.user_info?.first_name || 'You'}`
|
|
977
1088
|
: role === 'agent'
|
|
978
|
-
? '
|
|
1089
|
+
? `${icon('agent', 11, 'margin-right:3px')} Agent`
|
|
979
1090
|
: role === 'system'
|
|
980
|
-
? '
|
|
981
|
-
: '
|
|
1091
|
+
? 'Info'
|
|
1092
|
+
: `${icon('bot', 11, 'margin-right:3px')} Maria`;
|
|
982
1093
|
|
|
983
1094
|
const msg_el = document.createElement('div');
|
|
984
1095
|
msg_el.className = `tp-chatbot-message ${role}`;
|
|
985
1096
|
const rendered_content =
|
|
986
1097
|
role === 'assistant' || role === 'agent' ? (typeof marked !== 'undefined' ? marked.parse(content) : content) : content;
|
|
987
1098
|
|
|
1099
|
+
const feedback_html =
|
|
1100
|
+
role === 'assistant' && message_id
|
|
1101
|
+
? `<div class="tp-feedback-bar" data-message-id="${message_id}">
|
|
1102
|
+
<button class="tp-feedback-btn" data-rating="positive" title="Helpful">${icon('helpful', 14)}</button>
|
|
1103
|
+
<button class="tp-feedback-btn" data-rating="negative" title="Not helpful">${icon('not_helpful', 14)}</button>
|
|
1104
|
+
</div>`
|
|
1105
|
+
: '';
|
|
1106
|
+
|
|
988
1107
|
msg_el.innerHTML = `
|
|
989
1108
|
<div class="tp-chatbot-bubble-msg">
|
|
990
1109
|
<div class="tp-chatbot-role">${role_label}</div>
|
|
991
1110
|
<div class="tp-chatbot-content tp-chatbot-markdown">${rendered_content}</div>
|
|
1111
|
+
${feedback_html}
|
|
992
1112
|
</div>
|
|
993
1113
|
`;
|
|
1114
|
+
|
|
1115
|
+
if (role === 'assistant' && message_id) {
|
|
1116
|
+
const bar = msg_el.querySelector('.tp-feedback-bar');
|
|
1117
|
+
bar.querySelectorAll('.tp-feedback-btn').forEach(btn => {
|
|
1118
|
+
btn.addEventListener('click', async () => {
|
|
1119
|
+
if (bar.dataset.voted) return;
|
|
1120
|
+
bar.dataset.voted = '1';
|
|
1121
|
+
const rating = btn.dataset.rating;
|
|
1122
|
+
bar.querySelectorAll('.tp-feedback-btn').forEach(b => {
|
|
1123
|
+
b.style.opacity = b === btn ? '1' : '0.3';
|
|
1124
|
+
b.style.cursor = 'default';
|
|
1125
|
+
});
|
|
1126
|
+
try {
|
|
1127
|
+
await fetch(`${this.api_url}/chat/conversations/${this.conversation_id}/feedback`, {
|
|
1128
|
+
method: 'POST',
|
|
1129
|
+
headers: this.getHeaders(true),
|
|
1130
|
+
body: JSON.stringify({ message_id, rating, user_id: this.user_id }),
|
|
1131
|
+
});
|
|
1132
|
+
} catch {}
|
|
1133
|
+
});
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
|
|
994
1137
|
container.appendChild(msg_el);
|
|
995
1138
|
container.scrollTop = container.scrollHeight;
|
|
996
1139
|
|
|
997
|
-
if ((role === 'assistant' || role === 'agent') && this.sound_enabled)
|
|
998
|
-
playSound();
|
|
999
|
-
}
|
|
1140
|
+
if ((role === 'assistant' || role === 'agent') && this.sound_enabled) playSound();
|
|
1000
1141
|
}
|
|
1001
1142
|
|
|
1002
1143
|
showTyping() {
|
|
@@ -1010,8 +1151,8 @@
|
|
|
1010
1151
|
}
|
|
1011
1152
|
|
|
1012
1153
|
hideTyping() {
|
|
1013
|
-
const
|
|
1014
|
-
if (
|
|
1154
|
+
const t = this.querySelector('#tp-typing');
|
|
1155
|
+
if (t) t.remove();
|
|
1015
1156
|
}
|
|
1016
1157
|
|
|
1017
1158
|
showClosed(existing_csat = null) {
|
|
@@ -1023,14 +1164,11 @@
|
|
|
1023
1164
|
|
|
1024
1165
|
const m = this.getMessages();
|
|
1025
1166
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
1026
|
-
if (subtitle) subtitle.
|
|
1167
|
+
if (subtitle) subtitle.innerHTML = `${icon('check', 12, 'margin-right:4px')} ${m.terminated}`;
|
|
1027
1168
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
if (agent_bar) agent_bar.style.display = 'none';
|
|
1032
|
-
const close_bar = this.querySelector('#tp-close-bar');
|
|
1033
|
-
if (close_bar) close_bar.style.display = 'none';
|
|
1169
|
+
this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
|
|
1170
|
+
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
1171
|
+
this.querySelector('#tp-close-bar').style.display = 'none';
|
|
1034
1172
|
const sugg_el = this.querySelector('#tp-suggestions');
|
|
1035
1173
|
if (sugg_el) sugg_el.remove();
|
|
1036
1174
|
|
|
@@ -1048,17 +1186,21 @@
|
|
|
1048
1186
|
? `<div style="font-size:13px;color:#888;margin-bottom:14px;">${existing_csat === 'positive' ? m.csat_positive : m.csat_negative}</div>`
|
|
1049
1187
|
: `<div style="font-size:12px;color:#888;margin-bottom:10px;">${m.csat_question}</div>
|
|
1050
1188
|
<div style="display:flex;gap:10px;justify-content:center;margin-bottom:14px;">
|
|
1051
|
-
<button id="tp-csat-positive" style="
|
|
1052
|
-
<button id="tp-csat-negative" style="
|
|
1189
|
+
<button id="tp-csat-positive" class="tp-csat-btn" style="border-color:#22c55e;color:#22c55e;">${icon('helpful', 20)}</button>
|
|
1190
|
+
<button id="tp-csat-negative" class="tp-csat-btn" style="border-color:#ef4444;color:#ef4444;">${icon('not_helpful', 20)}</button>
|
|
1053
1191
|
</div>`;
|
|
1054
1192
|
|
|
1055
1193
|
const banner = document.createElement('div');
|
|
1056
1194
|
banner.id = 'tp-closed-banner';
|
|
1057
1195
|
banner.innerHTML = `
|
|
1058
|
-
<div style="margin:16px;padding:16px;background:#
|
|
1059
|
-
<div style="font-size:13px;color:#1a1a2e;font-weight:600;margin-bottom:
|
|
1196
|
+
<div style="margin:16px;padding:16px;background:#f9f9f9;border:1px solid #ede8f5;border-radius:12px;text-align:center;">
|
|
1197
|
+
<div style="display:inline-flex;align-items:center;gap:6px;font-size:13px;color:#1a1a2e;font-weight:600;margin-bottom:10px;">
|
|
1198
|
+
${icon('check', 14)} ${m.closed}
|
|
1199
|
+
</div>
|
|
1060
1200
|
<div id="tp-csat-block">${csat_html}</div>
|
|
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:
|
|
1201
|
+
<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:700;cursor:pointer;margin-bottom:8px;width:100%;display:flex;align-items:center;justify-content:center;gap:8px;">
|
|
1202
|
+
${icon('msg_plus', 15, 'color:white')} ${m.new_conv}
|
|
1203
|
+
</button>
|
|
1062
1204
|
<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>
|
|
1063
1205
|
</div>
|
|
1064
1206
|
`;
|
|
@@ -1088,7 +1230,7 @@
|
|
|
1088
1230
|
this.querySelector('#tp-back-to-list').addEventListener('click', () => this.showConversationList());
|
|
1089
1231
|
}
|
|
1090
1232
|
|
|
1091
|
-
// ─── Send / Poll
|
|
1233
|
+
// ─── Send / Poll ───────────────────────────────────────────────────────────
|
|
1092
1234
|
|
|
1093
1235
|
async sendMessage() {
|
|
1094
1236
|
if (this.is_closed || !this.conversation_id) return;
|
|
@@ -1103,8 +1245,7 @@
|
|
|
1103
1245
|
const user_messages = this.messages.filter(msg => msg.role === 'user').length;
|
|
1104
1246
|
if (user_messages >= 30) {
|
|
1105
1247
|
this.addMessage('system', m.limit_reached);
|
|
1106
|
-
|
|
1107
|
-
if (input_bar) input_bar.style.display = 'none';
|
|
1248
|
+
this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
|
|
1108
1249
|
return;
|
|
1109
1250
|
}
|
|
1110
1251
|
|
|
@@ -1119,7 +1260,6 @@
|
|
|
1119
1260
|
headers: this.getHeaders(true),
|
|
1120
1261
|
body: JSON.stringify({ query, user_id: this.user_id, user_info: this.user_info }),
|
|
1121
1262
|
});
|
|
1122
|
-
|
|
1123
1263
|
const data = await response.json();
|
|
1124
1264
|
this.hideTyping();
|
|
1125
1265
|
|
|
@@ -1131,19 +1271,24 @@
|
|
|
1131
1271
|
|
|
1132
1272
|
this.agent_mode = data.result.agent_mode;
|
|
1133
1273
|
const source = data.result.source || null;
|
|
1134
|
-
|
|
1135
1274
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
1136
|
-
if (subtitle)
|
|
1275
|
+
if (subtitle)
|
|
1276
|
+
subtitle.innerHTML = this.agent_mode
|
|
1277
|
+
? `${icon('user', 12, 'margin-right:4px')} ${m.human_agent}`
|
|
1278
|
+
: `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
1137
1279
|
|
|
1138
1280
|
if (data.result.reply) {
|
|
1139
|
-
this.addMessage(this.agent_mode ? 'agent' : 'assistant', data.result.reply);
|
|
1140
|
-
this.last_message_count += 2;
|
|
1281
|
+
this.addMessage(this.agent_mode ? 'agent' : 'assistant', data.result.reply, data.result.message_id || null);
|
|
1282
|
+
this.last_message_count += 2;
|
|
1141
1283
|
}
|
|
1142
1284
|
|
|
1143
1285
|
const user_msg_count = this.messages.filter(msg => msg.role === 'user').length;
|
|
1144
1286
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1287
|
+
const suggest_agent = data.result.suggest_agent || false;
|
|
1288
|
+
|
|
1145
1289
|
if (agent_bar && !this.agent_requested && !this.agent_mode) {
|
|
1146
1290
|
if (
|
|
1291
|
+
suggest_agent ||
|
|
1147
1292
|
source === 'fallback' ||
|
|
1148
1293
|
source === 'clarification' ||
|
|
1149
1294
|
source === 'no_match' ||
|
|
@@ -1153,9 +1298,14 @@
|
|
|
1153
1298
|
agent_bar.style.display = 'block';
|
|
1154
1299
|
}
|
|
1155
1300
|
}
|
|
1301
|
+
|
|
1302
|
+
if (suggest_agent && !this.agent_requested) {
|
|
1303
|
+
this.addMessage('system', m.suggest_agent_escalation);
|
|
1304
|
+
this.last_message_count += 1;
|
|
1305
|
+
}
|
|
1156
1306
|
} catch {
|
|
1157
1307
|
this.hideTyping();
|
|
1158
|
-
this.addMessage('assistant',
|
|
1308
|
+
this.addMessage('assistant', this.getMessages().error_occurred);
|
|
1159
1309
|
}
|
|
1160
1310
|
|
|
1161
1311
|
this.is_loading = false;
|
|
@@ -1174,15 +1324,28 @@
|
|
|
1174
1324
|
body: JSON.stringify({ user_id: this.user_id }),
|
|
1175
1325
|
});
|
|
1176
1326
|
const data = await response.json();
|
|
1177
|
-
this.
|
|
1178
|
-
|
|
1327
|
+
const m = this.getMessages();
|
|
1328
|
+
const status = data.result?.status;
|
|
1329
|
+
|
|
1330
|
+
let msg = '';
|
|
1331
|
+
if (status === 'outside_hours') msg = m.outside_hours(data.result.next_opening);
|
|
1332
|
+
else if (status === 'no_agents') msg = m.no_agents;
|
|
1333
|
+
else if (status === 'waiting_agent') msg = m.waiting_agent;
|
|
1334
|
+
else if (status === 'waiting_agent_already') msg = m.waiting_agent_already;
|
|
1335
|
+
else if (status === 'agent') msg = m.agent_already;
|
|
1336
|
+
|
|
1337
|
+
if (msg) {
|
|
1338
|
+
this.addMessage('system', msg);
|
|
1339
|
+
this.last_message_count += 1;
|
|
1340
|
+
}
|
|
1179
1341
|
|
|
1180
|
-
if (
|
|
1342
|
+
if (status === 'no_agents' || status === 'outside_hours' || status === 'waiting_agent_already') {
|
|
1181
1343
|
this.agent_requested = false;
|
|
1182
1344
|
if (agent_bar) agent_bar.style.display = 'block';
|
|
1183
1345
|
}
|
|
1184
1346
|
} catch {
|
|
1185
1347
|
this.agent_requested = false;
|
|
1348
|
+
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1186
1349
|
if (agent_bar) agent_bar.style.display = 'block';
|
|
1187
1350
|
}
|
|
1188
1351
|
}
|
|
@@ -1207,7 +1370,6 @@
|
|
|
1207
1370
|
const url = `${this.api_url}/chat/conversations/${this.conversation_id}?user_id=${encodeURIComponent(this.user_id)}`;
|
|
1208
1371
|
const response = await fetch(url, { headers: this.getHeaders() });
|
|
1209
1372
|
if (!response.ok) return;
|
|
1210
|
-
|
|
1211
1373
|
const data = await response.json();
|
|
1212
1374
|
const conv = data.result?.conversation;
|
|
1213
1375
|
if (!conv) return;
|
|
@@ -1228,7 +1390,7 @@
|
|
|
1228
1390
|
const typing = document.createElement('div');
|
|
1229
1391
|
typing.className = 'tp-chatbot-message agent';
|
|
1230
1392
|
typing.id = 'tp-agent-typing';
|
|
1231
|
-
typing.innerHTML = `<div class="tp-chatbot-bubble-msg"><div class="tp-chatbot-role"
|
|
1393
|
+
typing.innerHTML = `<div class="tp-chatbot-bubble-msg"><div class="tp-chatbot-role">${icon('agent', 11, 'margin-right:3px')} Agent</div><div class="tp-chatbot-typing"><span></span><span></span><span></span></div></div>`;
|
|
1232
1394
|
container.appendChild(typing);
|
|
1233
1395
|
container.scrollTop = container.scrollHeight;
|
|
1234
1396
|
}
|
|
@@ -1243,9 +1405,9 @@
|
|
|
1243
1405
|
if (msg.role === 'agent') {
|
|
1244
1406
|
const typing_el = this.querySelector('#tp-agent-typing');
|
|
1245
1407
|
if (typing_el) typing_el.remove();
|
|
1246
|
-
this.addMessage('agent', msg.content);
|
|
1408
|
+
this.addMessage('agent', msg.content, msg.message_id || null);
|
|
1247
1409
|
} else if (msg.role === 'assistant') {
|
|
1248
|
-
this.addMessage('assistant', msg.content);
|
|
1410
|
+
this.addMessage('assistant', msg.content, msg.message_id || null);
|
|
1249
1411
|
} else if (msg.role === 'system') {
|
|
1250
1412
|
this.addMessage('system', msg.content);
|
|
1251
1413
|
}
|
|
@@ -1256,7 +1418,7 @@
|
|
|
1256
1418
|
if (server_status === 'agent' && !this.agent_mode) {
|
|
1257
1419
|
this.agent_mode = true;
|
|
1258
1420
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
1259
|
-
if (subtitle) subtitle.
|
|
1421
|
+
if (subtitle) subtitle.innerHTML = `${icon('user', 12, 'margin-right:4px')} ${m.human_agent}`;
|
|
1260
1422
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1261
1423
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
1262
1424
|
this.addMessage('system', m.agent_taken);
|
|
@@ -1265,7 +1427,7 @@
|
|
|
1265
1427
|
this.agent_mode = false;
|
|
1266
1428
|
this.agent_requested = false;
|
|
1267
1429
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
1268
|
-
if (subtitle) subtitle.
|
|
1430
|
+
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
1269
1431
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1270
1432
|
if (agent_bar) agent_bar.style.display = 'block';
|
|
1271
1433
|
this.addMessage('system', m.agent_released);
|