@developpement/tp-chatbot-widget 0.0.9 → 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 +1 -1
- package/src/chatbot.css +254 -39
- package/src/chatbot.js +410 -296
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,224 +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.',
|
|
244
|
-
outside_hours: next =>
|
|
245
|
-
no_agents: '
|
|
246
|
-
waiting_agent: '
|
|
247
|
-
waiting_agent_already: "
|
|
248
|
-
agent_already: '
|
|
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',
|
|
249
319
|
},
|
|
250
320
|
EN: {
|
|
251
|
-
closed: '
|
|
321
|
+
closed: 'Conversation closed',
|
|
252
322
|
csat_question: 'Was this conversation helpful?',
|
|
253
|
-
csat_positive: '
|
|
254
|
-
csat_negative: '
|
|
255
|
-
new_conv: '
|
|
256
|
-
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',
|
|
257
327
|
agent_taken: 'An agent has taken over your conversation. You can now write to them directly.',
|
|
258
328
|
agent_released: 'Our virtual assistant is back. How can I help you?',
|
|
259
329
|
close_confirm: 'Do you want to end this conversation?',
|
|
260
|
-
terminated: '
|
|
261
|
-
virtual: '
|
|
262
|
-
human_agent: '
|
|
330
|
+
terminated: 'Conversation ended',
|
|
331
|
+
virtual: 'Virtual assistant',
|
|
332
|
+
human_agent: 'Human agent',
|
|
263
333
|
loading: 'Loading...',
|
|
264
334
|
no_conv: 'No conversations yet.',
|
|
265
|
-
new_conv_btn: '
|
|
335
|
+
new_conv_btn: 'New conversation',
|
|
266
336
|
error_load: 'Loading error.',
|
|
267
337
|
recent_conv: 'Your recent conversations',
|
|
268
338
|
hello: 'Hello',
|
|
269
|
-
close_btn: '
|
|
270
|
-
speak_agent: '
|
|
339
|
+
close_btn: 'End conversation',
|
|
340
|
+
speak_agent: 'Talk to an agent',
|
|
271
341
|
write_msg: 'Write your message...',
|
|
272
342
|
limit_reached: 'You have reached the 30 message limit. Please contact an agent or write to support@travelplanet.com.',
|
|
273
343
|
too_many: 'Too many messages. Please wait before retrying.',
|
|
274
344
|
error_occurred: 'An error occurred. Please try again.',
|
|
275
|
-
outside_hours: next =>
|
|
276
|
-
no_agents: '
|
|
277
|
-
waiting_agent: '
|
|
278
|
-
waiting_agent_already: '
|
|
279
|
-
agent_already: '
|
|
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',
|
|
280
356
|
},
|
|
281
357
|
DE: {
|
|
282
|
-
closed: '
|
|
358
|
+
closed: 'Gespräch beendet',
|
|
283
359
|
csat_question: 'War dieses Gespräch hilfreich?',
|
|
284
|
-
csat_positive: '
|
|
285
|
-
csat_negative: '
|
|
286
|
-
new_conv: '
|
|
287
|
-
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',
|
|
288
364
|
agent_taken: 'Ein Agent hat Ihr Gespräch übernommen. Sie können ihm jetzt direkt schreiben.',
|
|
289
365
|
agent_released: 'Unser virtueller Assistent ist zurück. Wie kann ich Ihnen helfen?',
|
|
290
366
|
close_confirm: 'Möchten Sie das Gespräch beenden?',
|
|
291
|
-
terminated: '
|
|
292
|
-
virtual: '
|
|
293
|
-
human_agent: '
|
|
367
|
+
terminated: 'Gespräch beendet',
|
|
368
|
+
virtual: 'Virtueller Assistent',
|
|
369
|
+
human_agent: 'Menschlicher Agent',
|
|
294
370
|
loading: 'Laden...',
|
|
295
371
|
no_conv: 'Noch keine Gespräche.',
|
|
296
|
-
new_conv_btn: '
|
|
372
|
+
new_conv_btn: 'Neues Gespräch',
|
|
297
373
|
error_load: 'Ladefehler.',
|
|
298
374
|
recent_conv: 'Ihre letzten Gespräche',
|
|
299
375
|
hello: 'Hallo',
|
|
300
|
-
close_btn: '
|
|
301
|
-
speak_agent: '
|
|
376
|
+
close_btn: 'Gespräch beenden',
|
|
377
|
+
speak_agent: 'Mit einem Agenten sprechen',
|
|
302
378
|
write_msg: 'Schreiben Sie Ihre Nachricht...',
|
|
303
379
|
limit_reached: 'Sie haben das Limit von 30 Nachrichten erreicht.',
|
|
304
380
|
too_many: 'Zu viele Nachrichten. Bitte warten Sie.',
|
|
305
381
|
error_occurred: 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.',
|
|
306
|
-
outside_hours: next =>
|
|
307
|
-
no_agents: '
|
|
308
|
-
waiting_agent: '
|
|
309
|
-
waiting_agent_already: '
|
|
310
|
-
agent_already: '
|
|
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',
|
|
311
393
|
},
|
|
312
394
|
ES: {
|
|
313
|
-
closed: '
|
|
395
|
+
closed: 'Conversación cerrada',
|
|
314
396
|
csat_question: '¿Fue útil esta conversación?',
|
|
315
|
-
csat_positive: '
|
|
316
|
-
csat_negative: '
|
|
317
|
-
new_conv: '
|
|
318
|
-
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',
|
|
319
401
|
agent_taken: 'Un agente ha tomado su conversación. Ahora puede escribirle directamente.',
|
|
320
402
|
agent_released: 'Nuestro asistente virtual ha vuelto. ¿En qué puedo ayudarle?',
|
|
321
403
|
close_confirm: '¿Desea finalizar esta conversación?',
|
|
322
|
-
terminated: '
|
|
323
|
-
virtual: '
|
|
324
|
-
human_agent: '
|
|
404
|
+
terminated: 'Conversación terminada',
|
|
405
|
+
virtual: 'Asistente virtual',
|
|
406
|
+
human_agent: 'Agente humano',
|
|
325
407
|
loading: 'Cargando...',
|
|
326
408
|
no_conv: 'No hay conversaciones por el momento.',
|
|
327
|
-
new_conv_btn: '
|
|
409
|
+
new_conv_btn: 'Nueva conversación',
|
|
328
410
|
error_load: 'Error de carga.',
|
|
329
411
|
recent_conv: 'Sus conversaciones recientes',
|
|
330
412
|
hello: 'Hola',
|
|
331
|
-
close_btn: '
|
|
332
|
-
speak_agent: '
|
|
413
|
+
close_btn: 'Finalizar conversación',
|
|
414
|
+
speak_agent: 'Hablar con un agente',
|
|
333
415
|
write_msg: 'Escriba su mensaje...',
|
|
334
416
|
limit_reached: 'Ha alcanzado el límite de 30 mensajes.',
|
|
335
417
|
too_many: 'Demasiados mensajes. Por favor espere.',
|
|
336
418
|
error_occurred: 'Se produjo un error. Por favor, inténtelo de nuevo.',
|
|
337
|
-
outside_hours: next =>
|
|
338
|
-
no_agents: '
|
|
339
|
-
waiting_agent: '
|
|
340
|
-
waiting_agent_already: '
|
|
341
|
-
agent_already: '
|
|
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',
|
|
342
430
|
},
|
|
343
431
|
IT: {
|
|
344
|
-
closed: '
|
|
432
|
+
closed: 'Conversazione chiusa',
|
|
345
433
|
csat_question: 'Questa conversazione è stata utile?',
|
|
346
|
-
csat_positive: '
|
|
347
|
-
csat_negative: '
|
|
348
|
-
new_conv: '
|
|
349
|
-
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',
|
|
350
438
|
agent_taken: 'Un agente ha preso in carico la sua conversazione.',
|
|
351
439
|
agent_released: 'Il nostro assistente virtuale è tornato. Come posso aiutarla?',
|
|
352
440
|
close_confirm: 'Vuole terminare questa conversazione?',
|
|
353
|
-
terminated: '
|
|
354
|
-
virtual: '
|
|
355
|
-
human_agent: '
|
|
441
|
+
terminated: 'Conversazione terminata',
|
|
442
|
+
virtual: 'Assistente virtuale',
|
|
443
|
+
human_agent: 'Agente umano',
|
|
356
444
|
loading: 'Caricamento...',
|
|
357
445
|
no_conv: 'Nessuna conversazione per il momento.',
|
|
358
|
-
new_conv_btn: '
|
|
446
|
+
new_conv_btn: 'Nuova conversazione',
|
|
359
447
|
error_load: 'Errore di caricamento.',
|
|
360
448
|
recent_conv: 'Le sue conversazioni recenti',
|
|
361
449
|
hello: 'Ciao',
|
|
362
|
-
close_btn: '
|
|
363
|
-
speak_agent: '
|
|
450
|
+
close_btn: 'Termina conversazione',
|
|
451
|
+
speak_agent: 'Parla con un agente',
|
|
364
452
|
write_msg: 'Scrivi il tuo messaggio...',
|
|
365
453
|
limit_reached: 'Hai raggiunto il limite di 30 messaggi.',
|
|
366
454
|
too_many: 'Troppi messaggi. Attendere prego.',
|
|
367
455
|
error_occurred: 'Si è verificato un errore. Riprova.',
|
|
368
|
-
outside_hours: next =>
|
|
369
|
-
no_agents: '
|
|
370
|
-
waiting_agent: '
|
|
371
|
-
waiting_agent_already: '
|
|
372
|
-
agent_already: '
|
|
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',
|
|
373
467
|
},
|
|
374
468
|
NL: {
|
|
375
|
-
closed: '
|
|
469
|
+
closed: 'Gesprek gesloten',
|
|
376
470
|
csat_question: 'Was dit gesprek nuttig?',
|
|
377
|
-
csat_positive: '
|
|
378
|
-
csat_negative: '
|
|
379
|
-
new_conv: '
|
|
380
|
-
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',
|
|
381
475
|
agent_taken: 'Een agent heeft uw gesprek overgenomen.',
|
|
382
476
|
agent_released: 'Onze virtuele assistent is terug. Hoe kan ik u helpen?',
|
|
383
477
|
close_confirm: 'Wilt u dit gesprek beëindigen?',
|
|
384
|
-
terminated: '
|
|
385
|
-
virtual: '
|
|
386
|
-
human_agent: '
|
|
478
|
+
terminated: 'Gesprek beëindigd',
|
|
479
|
+
virtual: 'Virtuele assistent',
|
|
480
|
+
human_agent: 'Menselijke agent',
|
|
387
481
|
loading: 'Laden...',
|
|
388
482
|
no_conv: 'Geen gesprekken op dit moment.',
|
|
389
|
-
new_conv_btn: '
|
|
390
|
-
error_load: '
|
|
483
|
+
new_conv_btn: 'Nieuw gesprek',
|
|
484
|
+
error_load: 'Laadout.',
|
|
391
485
|
recent_conv: 'Uw recente gesprekken',
|
|
392
486
|
hello: 'Hallo',
|
|
393
|
-
close_btn: '
|
|
394
|
-
speak_agent: '
|
|
487
|
+
close_btn: 'Gesprek beëindigen',
|
|
488
|
+
speak_agent: 'Praat met een agent',
|
|
395
489
|
write_msg: 'Schrijf uw bericht...',
|
|
396
490
|
limit_reached: 'U heeft de limiet van 30 berichten bereikt.',
|
|
397
491
|
too_many: 'Te veel berichten. Wacht even.',
|
|
398
492
|
error_occurred: 'Er is een fout opgetreden. Probeer het opnieuw.',
|
|
399
|
-
outside_hours: next =>
|
|
400
|
-
no_agents: '
|
|
401
|
-
waiting_agent: '
|
|
402
|
-
waiting_agent_already: '
|
|
403
|
-
agent_already: '
|
|
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',
|
|
404
504
|
},
|
|
405
505
|
};
|
|
406
506
|
return msgs[lang] || msgs['EN'];
|
|
@@ -410,17 +510,17 @@
|
|
|
410
510
|
const lang = (this.user_info?.language || 'EN').toUpperCase();
|
|
411
511
|
const client_name = getClientTheme(this.client_id).name;
|
|
412
512
|
const msgs = {
|
|
413
|
-
FR: `Bonjour ${first_name} !
|
|
414
|
-
EN: `Hello ${first_name}!
|
|
415
|
-
DE: `Hallo ${first_name}!
|
|
416
|
-
ES: `¡Hola ${first_name}!
|
|
417
|
-
IT: `Ciao ${first_name}!
|
|
418
|
-
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?`,
|
|
419
519
|
};
|
|
420
520
|
return msgs[lang] || msgs['EN'];
|
|
421
521
|
}
|
|
422
522
|
|
|
423
|
-
// ─── Theme
|
|
523
|
+
// ─── Theme ─────────────────────────────────────────────────────────────────
|
|
424
524
|
|
|
425
525
|
getThemeColor() {
|
|
426
526
|
return getClientTheme(this.client_id).primary;
|
|
@@ -456,6 +556,9 @@
|
|
|
456
556
|
#tp-new-conversation { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
|
|
457
557
|
.tp-conv-item:hover { background: ${color}11 !important; }
|
|
458
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; }
|
|
459
562
|
`;
|
|
460
563
|
document.head.appendChild(style);
|
|
461
564
|
|
|
@@ -466,13 +569,11 @@
|
|
|
466
569
|
host.style.left = position === 'left' ? '24px' : 'auto';
|
|
467
570
|
host.style.right = position === 'left' ? 'auto' : '24px';
|
|
468
571
|
}
|
|
469
|
-
|
|
470
572
|
const bubble = this.querySelector('.tp-chatbot-bubble');
|
|
471
573
|
if (bubble) {
|
|
472
574
|
bubble.style.marginLeft = position === 'left' ? '0' : 'auto';
|
|
473
575
|
bubble.style.marginRight = position === 'left' ? 'auto' : '0';
|
|
474
576
|
}
|
|
475
|
-
|
|
476
577
|
const win = this.querySelector('.tp-chatbot-window');
|
|
477
578
|
if (win) {
|
|
478
579
|
win.style.left = '0';
|
|
@@ -481,7 +582,7 @@
|
|
|
481
582
|
}
|
|
482
583
|
}
|
|
483
584
|
|
|
484
|
-
// ─── Render
|
|
585
|
+
// ─── Render ────────────────────────────────────────────────────────────────
|
|
485
586
|
|
|
486
587
|
render() {
|
|
487
588
|
const theme = getClientTheme(this.client_id);
|
|
@@ -490,39 +591,40 @@
|
|
|
490
591
|
<div class="tp-chatbot-window" id="tp-window">
|
|
491
592
|
<div class="tp-chatbot-header" id="tp-header">
|
|
492
593
|
<div class="tp-chatbot-avatar">M</div>
|
|
493
|
-
<div style="flex:1">
|
|
594
|
+
<div style="flex:1;min-width:0;">
|
|
494
595
|
<div class="tp-chatbot-title">${theme.name} Support</div>
|
|
495
|
-
<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>
|
|
496
597
|
</div>
|
|
497
|
-
<button id="tp-sound-btn"
|
|
498
|
-
<button id="tp-minimize-btn"
|
|
499
|
-
<button id="tp-maximize-btn"
|
|
500
|
-
<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>
|
|
501
602
|
</div>
|
|
502
603
|
<div class="tp-chatbot-messages" id="tp-messages"></div>
|
|
503
604
|
<div class="tp-chatbot-agent-bar" id="tp-agent-bar" style="display:none;">
|
|
504
|
-
<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>
|
|
505
608
|
</div>
|
|
506
609
|
<div class="tp-chatbot-input-bar" id="tp-chatbot-input-bar" style="display:none;">
|
|
507
610
|
<textarea class="tp-chatbot-input" id="tp-input" placeholder="Write your message..." rows="1"></textarea>
|
|
508
|
-
<button class="tp-chatbot-send" id="tp-send"
|
|
611
|
+
<button class="tp-chatbot-send" id="tp-send">${icon('send', 16, 'color:white')}</button>
|
|
509
612
|
</div>
|
|
510
613
|
<div id="tp-close-bar" style="padding:8px 12px;border-top:1px solid #ede8f5;text-align:center;display:none;">
|
|
511
|
-
<button id="tp-close-btn"
|
|
512
|
-
|
|
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>
|
|
513
616
|
</button>
|
|
514
617
|
</div>
|
|
515
618
|
</div>
|
|
516
|
-
<div class="tp-chatbot-bubble" id="tp-bubble"
|
|
619
|
+
<div class="tp-chatbot-bubble" id="tp-bubble">${icon('msg', 24, 'color:white')}</div>
|
|
517
620
|
</div>
|
|
518
621
|
`;
|
|
519
622
|
}
|
|
520
623
|
|
|
521
|
-
// ─── Window controls
|
|
624
|
+
// ─── Window controls ───────────────────────────────────────────────────────
|
|
522
625
|
|
|
523
626
|
minimize() {
|
|
524
627
|
const win = this.querySelector('#tp-window');
|
|
525
|
-
// Exit fullscreen first if needed
|
|
526
628
|
if (this.is_fullscreen) this.exitFullscreen(false);
|
|
527
629
|
this.is_minimized = true;
|
|
528
630
|
win.style.height = '56px';
|
|
@@ -542,7 +644,8 @@
|
|
|
542
644
|
win.style.borderRadius = '0';
|
|
543
645
|
win.style.zIndex = '99999';
|
|
544
646
|
host.style.width = '100vw';
|
|
545
|
-
this.querySelector('#tp-maximize-btn')
|
|
647
|
+
const btn = this.querySelector('#tp-maximize-btn');
|
|
648
|
+
if (btn) btn.innerHTML = icon('restore', 16);
|
|
546
649
|
}
|
|
547
650
|
|
|
548
651
|
exitFullscreen(restore_height = true) {
|
|
@@ -561,10 +664,11 @@
|
|
|
561
664
|
host.style.right = position === 'left' ? 'auto' : '24px';
|
|
562
665
|
if (restore_height) win.style.height = WINDOW_HEIGHT;
|
|
563
666
|
win.style.overflow = 'hidden';
|
|
564
|
-
this.querySelector('#tp-maximize-btn')
|
|
667
|
+
const btn = this.querySelector('#tp-maximize-btn');
|
|
668
|
+
if (btn) btn.innerHTML = icon('restore', 16);
|
|
565
669
|
}
|
|
566
670
|
|
|
567
|
-
// ─── Events
|
|
671
|
+
// ─── Events ────────────────────────────────────────────────────────────────
|
|
568
672
|
|
|
569
673
|
attachEvents() {
|
|
570
674
|
this.querySelector('#tp-bubble').addEventListener('click', () => this.toggleChat());
|
|
@@ -581,18 +685,15 @@
|
|
|
581
685
|
|
|
582
686
|
this.querySelector('#tp-sound-btn').addEventListener('click', () => {
|
|
583
687
|
this.sound_enabled = !this.sound_enabled;
|
|
584
|
-
this.querySelector('#tp-sound-btn').
|
|
688
|
+
this.querySelector('#tp-sound-btn').innerHTML = icon(this.sound_enabled ? 'bell' : 'bell_off', 16);
|
|
585
689
|
});
|
|
586
690
|
|
|
587
691
|
this.querySelector('#tp-close-btn').addEventListener('click', () => {
|
|
588
|
-
if (window.confirm(this.getMessages().close_confirm))
|
|
589
|
-
this.closeByUser();
|
|
590
|
-
}
|
|
692
|
+
if (window.confirm(this.getMessages().close_confirm)) this.closeByUser();
|
|
591
693
|
});
|
|
592
694
|
|
|
593
695
|
this.querySelector('#tp-minimize-btn').addEventListener('click', () => {
|
|
594
696
|
if (this.is_minimized) {
|
|
595
|
-
// Restore
|
|
596
697
|
this.is_minimized = false;
|
|
597
698
|
const win = this.querySelector('#tp-window');
|
|
598
699
|
win.style.height = WINDOW_HEIGHT;
|
|
@@ -620,13 +721,15 @@
|
|
|
620
721
|
updateUILanguage() {
|
|
621
722
|
const m = this.getMessages();
|
|
622
723
|
const agent_btn = this.querySelector('#tp-agent-btn');
|
|
623
|
-
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>`;
|
|
624
726
|
const close_btn = this.querySelector('#tp-close-btn');
|
|
625
|
-
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>`;
|
|
626
729
|
const input = this.querySelector('#tp-input');
|
|
627
730
|
if (input) input.placeholder = m.write_msg;
|
|
628
731
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
629
|
-
if (subtitle) subtitle.
|
|
732
|
+
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
630
733
|
}
|
|
631
734
|
|
|
632
735
|
toggleChat() {
|
|
@@ -635,8 +738,7 @@
|
|
|
635
738
|
const bubble = this.querySelector('#tp-bubble');
|
|
636
739
|
if (this.is_open) {
|
|
637
740
|
window_el.classList.add('open');
|
|
638
|
-
bubble.
|
|
639
|
-
// Restore from minimized if needed
|
|
741
|
+
bubble.innerHTML = icon('close', 20, 'color:white');
|
|
640
742
|
if (this.is_minimized) {
|
|
641
743
|
this.is_minimized = false;
|
|
642
744
|
window_el.style.height = WINDOW_HEIGHT;
|
|
@@ -644,7 +746,7 @@
|
|
|
644
746
|
}
|
|
645
747
|
} else {
|
|
646
748
|
window_el.classList.remove('open');
|
|
647
|
-
bubble.
|
|
749
|
+
bubble.innerHTML = icon('msg', 24, 'color:white');
|
|
648
750
|
if (this.is_fullscreen) this.exitFullscreen(true);
|
|
649
751
|
}
|
|
650
752
|
}
|
|
@@ -660,11 +762,7 @@
|
|
|
660
762
|
const response = await fetch(`${this.api_url}/chat/conversations`, {
|
|
661
763
|
method: 'POST',
|
|
662
764
|
headers: this.getHeaders(true),
|
|
663
|
-
body: JSON.stringify({
|
|
664
|
-
user_id: this.user_id,
|
|
665
|
-
user_info: this.user_info,
|
|
666
|
-
client_id: this.client_id,
|
|
667
|
-
}),
|
|
765
|
+
body: JSON.stringify({ user_id: this.user_id, user_info: this.user_info, client_id: this.client_id }),
|
|
668
766
|
});
|
|
669
767
|
const data = await response.json();
|
|
670
768
|
return data.result?.conversation_id;
|
|
@@ -676,15 +774,12 @@
|
|
|
676
774
|
const existing = this.querySelector('#tp-suggestions');
|
|
677
775
|
if (existing) existing.remove();
|
|
678
776
|
if (!suggestions || suggestions.length === 0) return;
|
|
679
|
-
|
|
680
777
|
const lang = (this.user_info?.language || 'EN').toUpperCase();
|
|
681
778
|
const color = this.getThemeColor();
|
|
682
779
|
const container = this.querySelector('#tp-messages');
|
|
683
|
-
|
|
684
780
|
const wrap = document.createElement('div');
|
|
685
781
|
wrap.id = 'tp-suggestions';
|
|
686
782
|
wrap.style.cssText = 'padding:8px 12px 12px;display:flex;flex-direction:column;gap:6px;';
|
|
687
|
-
|
|
688
783
|
suggestions.forEach(s => {
|
|
689
784
|
const label = s[lang] || s['EN'];
|
|
690
785
|
const btn = document.createElement('button');
|
|
@@ -709,38 +804,29 @@
|
|
|
709
804
|
});
|
|
710
805
|
wrap.appendChild(btn);
|
|
711
806
|
});
|
|
712
|
-
|
|
713
807
|
container.appendChild(wrap);
|
|
714
808
|
container.scrollTop = container.scrollHeight;
|
|
715
809
|
}
|
|
716
810
|
|
|
717
|
-
// ─── Conversation List
|
|
811
|
+
// ─── Conversation List ─────────────────────────────────────────────────────
|
|
718
812
|
|
|
719
813
|
async showConversationList() {
|
|
720
814
|
this.view = 'list';
|
|
721
815
|
const m = this.getMessages();
|
|
722
|
-
|
|
723
816
|
if (this.polling_interval) {
|
|
724
817
|
clearInterval(this.polling_interval);
|
|
725
818
|
this.polling_interval = null;
|
|
726
819
|
}
|
|
727
820
|
|
|
728
821
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
729
|
-
if (subtitle) subtitle.
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
const input_bar = this.querySelector('#tp-chatbot-input-bar');
|
|
735
|
-
if (input_bar) input_bar.style.display = 'none';
|
|
736
|
-
|
|
737
|
-
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
738
|
-
if (agent_bar) agent_bar.style.display = 'none';
|
|
739
|
-
|
|
740
|
-
const close_bar = this.querySelector('#tp-close-bar');
|
|
741
|
-
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';
|
|
742
827
|
|
|
743
828
|
const container = this.querySelector('#tp-messages');
|
|
829
|
+
container.style.background = '#f5f5f7';
|
|
744
830
|
container.innerHTML = `<div style="padding:16px;text-align:center;color:#aaa;font-size:12px;">${m.loading}</div>`;
|
|
745
831
|
|
|
746
832
|
try {
|
|
@@ -751,35 +837,40 @@
|
|
|
751
837
|
const color = this.getThemeColor();
|
|
752
838
|
|
|
753
839
|
container.innerHTML = '';
|
|
840
|
+
container.style.background = 'white';
|
|
841
|
+
container.style.padding = '0';
|
|
754
842
|
|
|
843
|
+
// Header greeting
|
|
755
844
|
const header_el = document.createElement('div');
|
|
756
|
-
header_el.style.cssText = 'padding:16px;border-bottom:1px solid #
|
|
845
|
+
header_el.style.cssText = 'padding:16px;border-bottom:1px solid #f0f0f0;';
|
|
757
846
|
header_el.innerHTML = `
|
|
758
|
-
<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>
|
|
759
848
|
<div style="font-size:12px;color:#aaa;">${m.recent_conv}</div>
|
|
760
849
|
`;
|
|
761
850
|
container.appendChild(header_el);
|
|
762
851
|
|
|
763
852
|
if (conversations.length === 0) {
|
|
764
853
|
const empty = document.createElement('div');
|
|
765
|
-
empty.style.cssText = 'padding:
|
|
766
|
-
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}`;
|
|
767
856
|
container.appendChild(empty);
|
|
768
857
|
} else {
|
|
769
858
|
conversations.forEach(conv => {
|
|
770
859
|
const item = document.createElement('div');
|
|
771
860
|
item.className = 'tp-conv-item';
|
|
772
|
-
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;';
|
|
773
863
|
|
|
774
864
|
const is_closed = conv.status === 'closed';
|
|
775
865
|
const status_label = is_closed
|
|
776
|
-
?
|
|
866
|
+
? m.status_closed
|
|
777
867
|
: conv.status === 'agent'
|
|
778
|
-
?
|
|
868
|
+
? m.status_agent
|
|
779
869
|
: conv.status === 'waiting_agent'
|
|
780
|
-
?
|
|
781
|
-
:
|
|
782
|
-
|
|
870
|
+
? m.status_waiting
|
|
871
|
+
: m.status_bot;
|
|
872
|
+
|
|
873
|
+
const badge_color = is_closed ? color : conv.status === 'waiting_agent' ? '#f59e0b' : color;
|
|
783
874
|
const date = new Date(conv.updated_at).toLocaleDateString('fr-FR', {
|
|
784
875
|
day: '2-digit',
|
|
785
876
|
month: '2-digit',
|
|
@@ -788,20 +879,27 @@
|
|
|
788
879
|
});
|
|
789
880
|
|
|
790
881
|
item.innerHTML = `
|
|
791
|
-
<div
|
|
792
|
-
<
|
|
793
|
-
<
|
|
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>
|
|
794
885
|
</div>
|
|
795
|
-
<
|
|
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>
|
|
796
889
|
`;
|
|
797
890
|
item.addEventListener('click', () => this.openConversation(conv.conversation_id, is_closed));
|
|
798
891
|
container.appendChild(item);
|
|
799
892
|
});
|
|
800
893
|
}
|
|
801
894
|
|
|
895
|
+
// New conversation button
|
|
802
896
|
const new_btn_wrap = document.createElement('div');
|
|
803
897
|
new_btn_wrap.style.cssText = 'padding:16px;';
|
|
804
|
-
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
|
+
`;
|
|
805
903
|
new_btn_wrap.querySelector('button').addEventListener('click', () => this.startNewConversation());
|
|
806
904
|
container.appendChild(new_btn_wrap);
|
|
807
905
|
} catch (e) {
|
|
@@ -820,10 +918,10 @@
|
|
|
820
918
|
this.agent_requested = false;
|
|
821
919
|
|
|
822
920
|
const m = this.getMessages();
|
|
823
|
-
|
|
824
|
-
if (back_btn) back_btn.style.display = 'block';
|
|
825
|
-
|
|
921
|
+
this.querySelector('#tp-back-btn').style.display = 'block';
|
|
826
922
|
const container = this.querySelector('#tp-messages');
|
|
923
|
+
container.style.background = '#f5f5f7';
|
|
924
|
+
container.style.padding = '16px';
|
|
827
925
|
container.innerHTML = '';
|
|
828
926
|
|
|
829
927
|
const input_bar = this.querySelector('#tp-chatbot-input-bar');
|
|
@@ -839,10 +937,9 @@
|
|
|
839
937
|
|
|
840
938
|
conv.messages.forEach(msg => {
|
|
841
939
|
if (['user', 'assistant', 'agent', 'system'].includes(msg.role)) {
|
|
842
|
-
this.addMessage(msg.role, msg.content);
|
|
940
|
+
this.addMessage(msg.role, msg.content, msg.message_id || null);
|
|
843
941
|
}
|
|
844
942
|
});
|
|
845
|
-
|
|
846
943
|
this.last_message_count = conv.messages.length;
|
|
847
944
|
|
|
848
945
|
if (conv.status === 'closed') {
|
|
@@ -859,14 +956,14 @@
|
|
|
859
956
|
if (conv.status === 'agent') {
|
|
860
957
|
this.agent_mode = true;
|
|
861
958
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
862
|
-
if (subtitle) subtitle.
|
|
959
|
+
if (subtitle) subtitle.innerHTML = `${icon('user', 12, 'margin-right:4px')} ${m.human_agent}`;
|
|
863
960
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
864
961
|
if (input_bar) input_bar.style.display = 'flex';
|
|
865
962
|
} else {
|
|
866
963
|
if (input_bar) input_bar.style.display = 'flex';
|
|
867
964
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
868
965
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
869
|
-
if (subtitle) subtitle.
|
|
966
|
+
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
870
967
|
}
|
|
871
968
|
|
|
872
969
|
this.updateUILanguage();
|
|
@@ -886,52 +983,40 @@
|
|
|
886
983
|
this.conversation_id = null;
|
|
887
984
|
|
|
888
985
|
const m = this.getMessages();
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
if (back_btn) back_btn.style.display = 'block';
|
|
892
|
-
|
|
893
|
-
const close_bar = this.querySelector('#tp-close-bar');
|
|
894
|
-
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';
|
|
895
988
|
|
|
896
989
|
const container = this.querySelector('#tp-messages');
|
|
990
|
+
container.style.background = '#f5f5f7';
|
|
991
|
+
container.style.padding = '16px';
|
|
897
992
|
container.innerHTML = '';
|
|
898
993
|
|
|
899
994
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
900
|
-
if (subtitle) subtitle.
|
|
901
|
-
|
|
902
|
-
const input_bar = this.querySelector('#tp-chatbot-input-bar');
|
|
903
|
-
if (input_bar) input_bar.style.display = 'flex';
|
|
904
|
-
|
|
905
|
-
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
906
|
-
if (agent_bar) agent_bar.style.display = 'none';
|
|
995
|
+
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
907
996
|
|
|
997
|
+
this.querySelector('#tp-chatbot-input-bar').style.display = 'flex';
|
|
998
|
+
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
908
999
|
this.updateUILanguage();
|
|
1000
|
+
|
|
909
1001
|
this.conversation_id = await this.createConversation();
|
|
910
1002
|
this.addMessage('assistant', this.getWelcomeMessage(this.user_info?.first_name || ''));
|
|
911
1003
|
this.last_message_count = 1;
|
|
912
1004
|
|
|
913
1005
|
const theme = getClientTheme(this.client_id);
|
|
914
|
-
if (theme.suggestions && theme.suggestions.length > 0)
|
|
915
|
-
this.showSuggestions(theme.suggestions);
|
|
916
|
-
}
|
|
1006
|
+
if (theme.suggestions && theme.suggestions.length > 0) this.showSuggestions(theme.suggestions);
|
|
917
1007
|
|
|
918
1008
|
this.polling_interval = setInterval(() => this.pollMessages(), 3000);
|
|
919
1009
|
}
|
|
920
1010
|
|
|
921
|
-
// ─── Guest form
|
|
1011
|
+
// ─── Guest form ────────────────────────────────────────────────────────────
|
|
922
1012
|
|
|
923
1013
|
showGuestForm() {
|
|
924
1014
|
const container = this.querySelector('#tp-messages');
|
|
925
1015
|
if (!container) return;
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
if (input_bar) input_bar.style.display = 'none';
|
|
929
|
-
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
930
|
-
if (agent_bar) agent_bar.style.display = 'none';
|
|
931
|
-
|
|
1016
|
+
this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
|
|
1017
|
+
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
932
1018
|
const color = this.getThemeColor();
|
|
933
1019
|
const dark = this.shadeColor(color, -20);
|
|
934
|
-
|
|
935
1020
|
const form_el = document.createElement('div');
|
|
936
1021
|
form_el.id = 'tp-guest-form';
|
|
937
1022
|
form_el.innerHTML = `
|
|
@@ -940,9 +1025,7 @@
|
|
|
940
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;" />
|
|
941
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;" />
|
|
942
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;" />
|
|
943
|
-
<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;">
|
|
944
|
-
Start conversation →
|
|
945
|
-
</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>
|
|
946
1029
|
<p id="tp-guest-error" style="margin:0;font-size:12px;color:#c0392b;display:none;"></p>
|
|
947
1030
|
</div>
|
|
948
1031
|
`;
|
|
@@ -955,7 +1038,6 @@
|
|
|
955
1038
|
const company_name = this.querySelector('#tp-guest-company')?.value.trim();
|
|
956
1039
|
const email = this.querySelector('#tp-guest-email')?.value.trim();
|
|
957
1040
|
const error_el = this.querySelector('#tp-guest-error');
|
|
958
|
-
|
|
959
1041
|
if (!first_name || !company_name || !email) {
|
|
960
1042
|
error_el.textContent = 'Please fill in all fields.';
|
|
961
1043
|
error_el.style.display = 'block';
|
|
@@ -976,57 +1058,86 @@
|
|
|
976
1058
|
this.view = 'chat';
|
|
977
1059
|
this.conversation_id = await this.createConversation();
|
|
978
1060
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
const
|
|
984
|
-
|
|
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';
|
|
985
1068
|
|
|
986
1069
|
this.updateUILanguage();
|
|
987
1070
|
this.addMessage('assistant', this.getWelcomeMessage(first_name));
|
|
988
1071
|
this.last_message_count = 1;
|
|
989
1072
|
|
|
990
1073
|
const theme = getClientTheme(this.client_id);
|
|
991
|
-
if (theme.suggestions && theme.suggestions.length > 0)
|
|
992
|
-
this.showSuggestions(theme.suggestions);
|
|
993
|
-
}
|
|
1074
|
+
if (theme.suggestions && theme.suggestions.length > 0) this.showSuggestions(theme.suggestions);
|
|
994
1075
|
|
|
995
1076
|
this.polling_interval = setInterval(() => this.pollMessages(), 3000);
|
|
996
1077
|
}
|
|
997
1078
|
|
|
998
|
-
// ─── Messages
|
|
1079
|
+
// ─── Messages ──────────────────────────────────────────────────────────────
|
|
999
1080
|
|
|
1000
|
-
addMessage(role, content) {
|
|
1001
|
-
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() });
|
|
1002
1083
|
const container = this.querySelector('#tp-messages');
|
|
1003
1084
|
|
|
1004
1085
|
const role_label =
|
|
1005
1086
|
role === 'user'
|
|
1006
|
-
?
|
|
1087
|
+
? `${icon('user', 11, 'margin-right:3px')} ${this.user_info?.first_name || 'You'}`
|
|
1007
1088
|
: role === 'agent'
|
|
1008
|
-
? '
|
|
1089
|
+
? `${icon('agent', 11, 'margin-right:3px')} Agent`
|
|
1009
1090
|
: role === 'system'
|
|
1010
|
-
? '
|
|
1011
|
-
: '
|
|
1091
|
+
? 'Info'
|
|
1092
|
+
: `${icon('bot', 11, 'margin-right:3px')} Maria`;
|
|
1012
1093
|
|
|
1013
1094
|
const msg_el = document.createElement('div');
|
|
1014
1095
|
msg_el.className = `tp-chatbot-message ${role}`;
|
|
1015
1096
|
const rendered_content =
|
|
1016
1097
|
role === 'assistant' || role === 'agent' ? (typeof marked !== 'undefined' ? marked.parse(content) : content) : content;
|
|
1017
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
|
+
|
|
1018
1107
|
msg_el.innerHTML = `
|
|
1019
1108
|
<div class="tp-chatbot-bubble-msg">
|
|
1020
1109
|
<div class="tp-chatbot-role">${role_label}</div>
|
|
1021
1110
|
<div class="tp-chatbot-content tp-chatbot-markdown">${rendered_content}</div>
|
|
1111
|
+
${feedback_html}
|
|
1022
1112
|
</div>
|
|
1023
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
|
+
|
|
1024
1137
|
container.appendChild(msg_el);
|
|
1025
1138
|
container.scrollTop = container.scrollHeight;
|
|
1026
1139
|
|
|
1027
|
-
if ((role === 'assistant' || role === 'agent') && this.sound_enabled)
|
|
1028
|
-
playSound();
|
|
1029
|
-
}
|
|
1140
|
+
if ((role === 'assistant' || role === 'agent') && this.sound_enabled) playSound();
|
|
1030
1141
|
}
|
|
1031
1142
|
|
|
1032
1143
|
showTyping() {
|
|
@@ -1040,8 +1151,8 @@
|
|
|
1040
1151
|
}
|
|
1041
1152
|
|
|
1042
1153
|
hideTyping() {
|
|
1043
|
-
const
|
|
1044
|
-
if (
|
|
1154
|
+
const t = this.querySelector('#tp-typing');
|
|
1155
|
+
if (t) t.remove();
|
|
1045
1156
|
}
|
|
1046
1157
|
|
|
1047
1158
|
showClosed(existing_csat = null) {
|
|
@@ -1053,14 +1164,11 @@
|
|
|
1053
1164
|
|
|
1054
1165
|
const m = this.getMessages();
|
|
1055
1166
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
1056
|
-
if (subtitle) subtitle.
|
|
1167
|
+
if (subtitle) subtitle.innerHTML = `${icon('check', 12, 'margin-right:4px')} ${m.terminated}`;
|
|
1057
1168
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
if (agent_bar) agent_bar.style.display = 'none';
|
|
1062
|
-
const close_bar = this.querySelector('#tp-close-bar');
|
|
1063
|
-
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';
|
|
1064
1172
|
const sugg_el = this.querySelector('#tp-suggestions');
|
|
1065
1173
|
if (sugg_el) sugg_el.remove();
|
|
1066
1174
|
|
|
@@ -1078,17 +1186,21 @@
|
|
|
1078
1186
|
? `<div style="font-size:13px;color:#888;margin-bottom:14px;">${existing_csat === 'positive' ? m.csat_positive : m.csat_negative}</div>`
|
|
1079
1187
|
: `<div style="font-size:12px;color:#888;margin-bottom:10px;">${m.csat_question}</div>
|
|
1080
1188
|
<div style="display:flex;gap:10px;justify-content:center;margin-bottom:14px;">
|
|
1081
|
-
<button id="tp-csat-positive" style="
|
|
1082
|
-
<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>
|
|
1083
1191
|
</div>`;
|
|
1084
1192
|
|
|
1085
1193
|
const banner = document.createElement('div');
|
|
1086
1194
|
banner.id = 'tp-closed-banner';
|
|
1087
1195
|
banner.innerHTML = `
|
|
1088
|
-
<div style="margin:16px;padding:16px;background:#
|
|
1089
|
-
<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>
|
|
1090
1200
|
<div id="tp-csat-block">${csat_html}</div>
|
|
1091
|
-
<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>
|
|
1092
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>
|
|
1093
1205
|
</div>
|
|
1094
1206
|
`;
|
|
@@ -1118,7 +1230,7 @@
|
|
|
1118
1230
|
this.querySelector('#tp-back-to-list').addEventListener('click', () => this.showConversationList());
|
|
1119
1231
|
}
|
|
1120
1232
|
|
|
1121
|
-
// ─── Send / Poll
|
|
1233
|
+
// ─── Send / Poll ───────────────────────────────────────────────────────────
|
|
1122
1234
|
|
|
1123
1235
|
async sendMessage() {
|
|
1124
1236
|
if (this.is_closed || !this.conversation_id) return;
|
|
@@ -1133,8 +1245,7 @@
|
|
|
1133
1245
|
const user_messages = this.messages.filter(msg => msg.role === 'user').length;
|
|
1134
1246
|
if (user_messages >= 30) {
|
|
1135
1247
|
this.addMessage('system', m.limit_reached);
|
|
1136
|
-
|
|
1137
|
-
if (input_bar) input_bar.style.display = 'none';
|
|
1248
|
+
this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
|
|
1138
1249
|
return;
|
|
1139
1250
|
}
|
|
1140
1251
|
|
|
@@ -1149,7 +1260,6 @@
|
|
|
1149
1260
|
headers: this.getHeaders(true),
|
|
1150
1261
|
body: JSON.stringify({ query, user_id: this.user_id, user_info: this.user_info }),
|
|
1151
1262
|
});
|
|
1152
|
-
|
|
1153
1263
|
const data = await response.json();
|
|
1154
1264
|
this.hideTyping();
|
|
1155
1265
|
|
|
@@ -1161,19 +1271,24 @@
|
|
|
1161
1271
|
|
|
1162
1272
|
this.agent_mode = data.result.agent_mode;
|
|
1163
1273
|
const source = data.result.source || null;
|
|
1164
|
-
|
|
1165
1274
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
1166
|
-
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}`;
|
|
1167
1279
|
|
|
1168
1280
|
if (data.result.reply) {
|
|
1169
|
-
this.addMessage(this.agent_mode ? 'agent' : 'assistant', data.result.reply);
|
|
1170
|
-
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;
|
|
1171
1283
|
}
|
|
1172
1284
|
|
|
1173
1285
|
const user_msg_count = this.messages.filter(msg => msg.role === 'user').length;
|
|
1174
1286
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1287
|
+
const suggest_agent = data.result.suggest_agent || false;
|
|
1288
|
+
|
|
1175
1289
|
if (agent_bar && !this.agent_requested && !this.agent_mode) {
|
|
1176
1290
|
if (
|
|
1291
|
+
suggest_agent ||
|
|
1177
1292
|
source === 'fallback' ||
|
|
1178
1293
|
source === 'clarification' ||
|
|
1179
1294
|
source === 'no_match' ||
|
|
@@ -1183,9 +1298,14 @@
|
|
|
1183
1298
|
agent_bar.style.display = 'block';
|
|
1184
1299
|
}
|
|
1185
1300
|
}
|
|
1301
|
+
|
|
1302
|
+
if (suggest_agent && !this.agent_requested) {
|
|
1303
|
+
this.addMessage('system', m.suggest_agent_escalation);
|
|
1304
|
+
this.last_message_count += 1;
|
|
1305
|
+
}
|
|
1186
1306
|
} catch {
|
|
1187
1307
|
this.hideTyping();
|
|
1188
|
-
this.addMessage('assistant',
|
|
1308
|
+
this.addMessage('assistant', this.getMessages().error_occurred);
|
|
1189
1309
|
}
|
|
1190
1310
|
|
|
1191
1311
|
this.is_loading = false;
|
|
@@ -1208,17 +1328,11 @@
|
|
|
1208
1328
|
const status = data.result?.status;
|
|
1209
1329
|
|
|
1210
1330
|
let msg = '';
|
|
1211
|
-
if (status === 'outside_hours')
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
msg = m.waiting_agent;
|
|
1217
|
-
} else if (status === 'waiting_agent_already') {
|
|
1218
|
-
msg = m.waiting_agent_already;
|
|
1219
|
-
} else if (status === 'agent') {
|
|
1220
|
-
msg = m.agent_already;
|
|
1221
|
-
}
|
|
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;
|
|
1222
1336
|
|
|
1223
1337
|
if (msg) {
|
|
1224
1338
|
this.addMessage('system', msg);
|
|
@@ -1231,6 +1345,7 @@
|
|
|
1231
1345
|
}
|
|
1232
1346
|
} catch {
|
|
1233
1347
|
this.agent_requested = false;
|
|
1348
|
+
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1234
1349
|
if (agent_bar) agent_bar.style.display = 'block';
|
|
1235
1350
|
}
|
|
1236
1351
|
}
|
|
@@ -1255,7 +1370,6 @@
|
|
|
1255
1370
|
const url = `${this.api_url}/chat/conversations/${this.conversation_id}?user_id=${encodeURIComponent(this.user_id)}`;
|
|
1256
1371
|
const response = await fetch(url, { headers: this.getHeaders() });
|
|
1257
1372
|
if (!response.ok) return;
|
|
1258
|
-
|
|
1259
1373
|
const data = await response.json();
|
|
1260
1374
|
const conv = data.result?.conversation;
|
|
1261
1375
|
if (!conv) return;
|
|
@@ -1276,7 +1390,7 @@
|
|
|
1276
1390
|
const typing = document.createElement('div');
|
|
1277
1391
|
typing.className = 'tp-chatbot-message agent';
|
|
1278
1392
|
typing.id = 'tp-agent-typing';
|
|
1279
|
-
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>`;
|
|
1280
1394
|
container.appendChild(typing);
|
|
1281
1395
|
container.scrollTop = container.scrollHeight;
|
|
1282
1396
|
}
|
|
@@ -1291,9 +1405,9 @@
|
|
|
1291
1405
|
if (msg.role === 'agent') {
|
|
1292
1406
|
const typing_el = this.querySelector('#tp-agent-typing');
|
|
1293
1407
|
if (typing_el) typing_el.remove();
|
|
1294
|
-
this.addMessage('agent', msg.content);
|
|
1408
|
+
this.addMessage('agent', msg.content, msg.message_id || null);
|
|
1295
1409
|
} else if (msg.role === 'assistant') {
|
|
1296
|
-
this.addMessage('assistant', msg.content);
|
|
1410
|
+
this.addMessage('assistant', msg.content, msg.message_id || null);
|
|
1297
1411
|
} else if (msg.role === 'system') {
|
|
1298
1412
|
this.addMessage('system', msg.content);
|
|
1299
1413
|
}
|
|
@@ -1304,7 +1418,7 @@
|
|
|
1304
1418
|
if (server_status === 'agent' && !this.agent_mode) {
|
|
1305
1419
|
this.agent_mode = true;
|
|
1306
1420
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
1307
|
-
if (subtitle) subtitle.
|
|
1421
|
+
if (subtitle) subtitle.innerHTML = `${icon('user', 12, 'margin-right:4px')} ${m.human_agent}`;
|
|
1308
1422
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1309
1423
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
1310
1424
|
this.addMessage('system', m.agent_taken);
|
|
@@ -1313,7 +1427,7 @@
|
|
|
1313
1427
|
this.agent_mode = false;
|
|
1314
1428
|
this.agent_requested = false;
|
|
1315
1429
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
1316
|
-
if (subtitle) subtitle.
|
|
1430
|
+
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
1317
1431
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1318
1432
|
if (agent_bar) agent_bar.style.display = 'block';
|
|
1319
1433
|
this.addMessage('system', m.agent_released);
|