@developpement/tp-chatbot-widget 0.0.1 → 0.0.4
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.js +135 -19
package/package.json
CHANGED
package/src/chatbot.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
const CLIENT_THEMES = {
|
|
5
|
+
flix: { primary: '#7b1fa2', name: 'Flix Corporate' },
|
|
6
|
+
sncf: { primary: '#1a6b5a', name: 'SNCF' },
|
|
7
|
+
};
|
|
8
|
+
const DEFAULT_THEME = { primary: '#7b1fa2', name: 'Support' };
|
|
9
|
+
|
|
10
|
+
function getClientTheme(client_id) {
|
|
11
|
+
return CLIENT_THEMES[client_id] || DEFAULT_THEME;
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
function extractUserInfo(access_token) {
|
|
5
15
|
if (!access_token) return null;
|
|
6
16
|
try {
|
|
@@ -18,6 +28,22 @@
|
|
|
18
28
|
}
|
|
19
29
|
}
|
|
20
30
|
|
|
31
|
+
function playSound() {
|
|
32
|
+
try {
|
|
33
|
+
const ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
34
|
+
const oscillator = ctx.createOscillator();
|
|
35
|
+
const gain = ctx.createGain();
|
|
36
|
+
oscillator.connect(gain);
|
|
37
|
+
gain.connect(ctx.destination);
|
|
38
|
+
oscillator.frequency.value = 880;
|
|
39
|
+
oscillator.type = 'sine';
|
|
40
|
+
gain.gain.setValueAtTime(0.2, ctx.currentTime);
|
|
41
|
+
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.4);
|
|
42
|
+
oscillator.start(ctx.currentTime);
|
|
43
|
+
oscillator.stop(ctx.currentTime + 0.4);
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
|
|
21
47
|
class TpChatbot extends HTMLElement {
|
|
22
48
|
constructor() {
|
|
23
49
|
super();
|
|
@@ -32,10 +58,37 @@
|
|
|
32
58
|
this.polling_interval = null;
|
|
33
59
|
this.user_info = null;
|
|
34
60
|
this.is_closed = false;
|
|
35
|
-
this.view = 'list';
|
|
61
|
+
this.view = 'list';
|
|
62
|
+
this._initialized = false;
|
|
63
|
+
this.sound_enabled = true;
|
|
64
|
+
this.client_id = 'flix';
|
|
65
|
+
this.access_token = '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static get observedAttributes() {
|
|
69
|
+
return ['access-token', 'client-id'];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
attributeChangedCallback(name, old_val, new_val) {
|
|
73
|
+
if (name === 'client-id' && new_val && new_val !== old_val) {
|
|
74
|
+
this.client_id = new_val;
|
|
75
|
+
if (this._initialized) this.applyTheme();
|
|
76
|
+
}
|
|
77
|
+
if (name === 'access-token' && new_val && new_val !== old_val && this._initialized) {
|
|
78
|
+
this.access_token = new_val;
|
|
79
|
+
this.user_info = extractUserInfo(new_val);
|
|
80
|
+
this.user_id = this.user_info?.user_id;
|
|
81
|
+
this.applyTheme();
|
|
82
|
+
const guest_form = this.querySelector('#tp-guest-form');
|
|
83
|
+
if (guest_form) {
|
|
84
|
+
guest_form.remove();
|
|
85
|
+
this.showConversationList();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
36
88
|
}
|
|
37
89
|
|
|
38
90
|
connectedCallback() {
|
|
91
|
+
this.client_id = this.getAttribute('client-id') || 'flix';
|
|
39
92
|
this.access_token = this.getAttribute('access-token') || '';
|
|
40
93
|
this.api_url = this.getAttribute('api-url') || 'https://api.tst.travelplanet.click/chatbot/v1';
|
|
41
94
|
this.user_info = null;
|
|
@@ -47,15 +100,27 @@
|
|
|
47
100
|
this.view = 'list';
|
|
48
101
|
|
|
49
102
|
this.render();
|
|
103
|
+
this.applyTheme(); // ← couleur appliquée immédiatement, avant le token
|
|
50
104
|
this.attachEvents();
|
|
105
|
+
this._initialized = true;
|
|
51
106
|
|
|
52
107
|
if (this.access_token) {
|
|
53
108
|
this.user_info = extractUserInfo(this.access_token);
|
|
54
|
-
this.user_id = this.user_info
|
|
55
|
-
this.applyTheme();
|
|
109
|
+
this.user_id = this.user_info?.user_id;
|
|
56
110
|
this.showConversationList();
|
|
57
111
|
} else {
|
|
58
|
-
|
|
112
|
+
// Attend 500ms que le framework passe le token
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
const token = this.getAttribute('access-token');
|
|
115
|
+
if (token) {
|
|
116
|
+
this.access_token = token;
|
|
117
|
+
this.user_info = extractUserInfo(token);
|
|
118
|
+
this.user_id = this.user_info?.user_id;
|
|
119
|
+
this.showConversationList();
|
|
120
|
+
} else {
|
|
121
|
+
this.showGuestForm();
|
|
122
|
+
}
|
|
123
|
+
}, 500);
|
|
59
124
|
}
|
|
60
125
|
}
|
|
61
126
|
|
|
@@ -66,8 +131,7 @@
|
|
|
66
131
|
// ─── Theme ────────────────────────────────────────────────────────────────
|
|
67
132
|
|
|
68
133
|
getThemeColor() {
|
|
69
|
-
|
|
70
|
-
return site_id.startsWith('I') ? '#97d700' : '#7b1fa2';
|
|
134
|
+
return getClientTheme(this.client_id).primary;
|
|
71
135
|
}
|
|
72
136
|
|
|
73
137
|
shadeColor(hex, percent) {
|
|
@@ -98,7 +162,7 @@
|
|
|
98
162
|
.tp-chatbot-agent-btn:hover { background: ${color} !important; color: white !important; }
|
|
99
163
|
.tp-chatbot-typing span { background: ${color} !important; }
|
|
100
164
|
#tp-new-conversation { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
|
|
101
|
-
.tp-conv-item:hover { background:
|
|
165
|
+
.tp-conv-item:hover { background: ${color}11 !important; }
|
|
102
166
|
.tp-conv-new-btn { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
|
|
103
167
|
`;
|
|
104
168
|
document.head.appendChild(style);
|
|
@@ -107,15 +171,17 @@
|
|
|
107
171
|
// ─── Render ───────────────────────────────────────────────────────────────
|
|
108
172
|
|
|
109
173
|
render() {
|
|
174
|
+
const theme = getClientTheme(this.client_id);
|
|
110
175
|
this.innerHTML = `
|
|
111
176
|
<div class="tp-chatbot-host">
|
|
112
177
|
<div class="tp-chatbot-window" id="tp-window">
|
|
113
178
|
<div class="tp-chatbot-header" id="tp-header">
|
|
114
179
|
<div class="tp-chatbot-avatar">M</div>
|
|
115
180
|
<div style="flex:1">
|
|
116
|
-
<div class="tp-chatbot-title"
|
|
181
|
+
<div class="tp-chatbot-title">${theme.name} Support</div>
|
|
117
182
|
<div class="tp-chatbot-subtitle" id="tp-subtitle">🤖 Assistant virtuel</div>
|
|
118
183
|
</div>
|
|
184
|
+
<button id="tp-sound-btn" style="background:none;border:none;color:white;font-size:16px;cursor:pointer;padding:4px 8px;" title="Activer/désactiver le son">🔔</button>
|
|
119
185
|
<button id="tp-back-btn" style="display:none;background:none;border:none;color:white;font-size:18px;cursor:pointer;padding:4px 8px;">←</button>
|
|
120
186
|
</div>
|
|
121
187
|
<div class="tp-chatbot-messages" id="tp-messages"></div>
|
|
@@ -126,6 +192,11 @@
|
|
|
126
192
|
<textarea class="tp-chatbot-input" id="tp-input" placeholder="Écrivez votre message..." rows="1"></textarea>
|
|
127
193
|
<button class="tp-chatbot-send" id="tp-send">➤</button>
|
|
128
194
|
</div>
|
|
195
|
+
<div id="tp-close-bar" style="padding:8px 12px;border-top:1px solid #ede8f5;text-align:center;display:none;">
|
|
196
|
+
<button id="tp-close-btn" style="background:none;border:1px solid #e5e7eb;border-radius:8px;padding:6px 16px;font-size:12px;color:#9ca3af;cursor:pointer;font-weight:600;">
|
|
197
|
+
✅ Terminer la conversation
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
129
200
|
</div>
|
|
130
201
|
<div class="tp-chatbot-bubble" id="tp-bubble">💬</div>
|
|
131
202
|
</div>
|
|
@@ -143,6 +214,17 @@
|
|
|
143
214
|
this.sendMessage();
|
|
144
215
|
}
|
|
145
216
|
});
|
|
217
|
+
this.querySelector('#tp-sound-btn').addEventListener('click', () => {
|
|
218
|
+
this.sound_enabled = !this.sound_enabled;
|
|
219
|
+
const btn = this.querySelector('#tp-sound-btn');
|
|
220
|
+
btn.textContent = this.sound_enabled ? '🔔' : '🔕';
|
|
221
|
+
btn.title = this.sound_enabled ? 'Désactiver le son' : 'Activer le son';
|
|
222
|
+
});
|
|
223
|
+
this.querySelector('#tp-close-btn').addEventListener('click', () => {
|
|
224
|
+
if (window.confirm('Voulez-vous terminer cette conversation ?')) {
|
|
225
|
+
this.closeByUser();
|
|
226
|
+
}
|
|
227
|
+
});
|
|
146
228
|
}
|
|
147
229
|
|
|
148
230
|
toggleChat() {
|
|
@@ -169,7 +251,11 @@
|
|
|
169
251
|
const response = await fetch(`${this.api_url}/chat/conversations`, {
|
|
170
252
|
method: 'POST',
|
|
171
253
|
headers: this.getHeaders(true),
|
|
172
|
-
body: JSON.stringify({
|
|
254
|
+
body: JSON.stringify({
|
|
255
|
+
user_id: this.user_id,
|
|
256
|
+
user_info: this.user_info,
|
|
257
|
+
client_id: this.client_id,
|
|
258
|
+
}),
|
|
173
259
|
});
|
|
174
260
|
const data = await response.json();
|
|
175
261
|
return data.result?.conversation_id;
|
|
@@ -197,6 +283,9 @@
|
|
|
197
283
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
198
284
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
199
285
|
|
|
286
|
+
const close_bar = this.querySelector('#tp-close-bar');
|
|
287
|
+
if (close_bar) close_bar.style.display = 'none';
|
|
288
|
+
|
|
200
289
|
const container = this.querySelector('#tp-messages');
|
|
201
290
|
container.innerHTML = '<div style="padding:16px;text-align:center;color:#aaa;font-size:12px;">Chargement...</div>';
|
|
202
291
|
|
|
@@ -211,18 +300,14 @@
|
|
|
211
300
|
|
|
212
301
|
container.innerHTML = '';
|
|
213
302
|
|
|
214
|
-
// Header list
|
|
215
303
|
const header_el = document.createElement('div');
|
|
216
304
|
header_el.style.cssText = 'padding:16px;border-bottom:1px solid #ede8f5;';
|
|
217
305
|
header_el.innerHTML = `
|
|
218
|
-
<div style="font-size:13px;font-weight:700;color:#1a1a2e;margin-bottom:4px;">
|
|
219
|
-
Bonjour ${this.user_info?.first_name || ''} 👋
|
|
220
|
-
</div>
|
|
306
|
+
<div style="font-size:13px;font-weight:700;color:#1a1a2e;margin-bottom:4px;">Bonjour ${this.user_info?.first_name || ''} 👋</div>
|
|
221
307
|
<div style="font-size:12px;color:#aaa;">Vos conversations récentes</div>
|
|
222
308
|
`;
|
|
223
309
|
container.appendChild(header_el);
|
|
224
310
|
|
|
225
|
-
// Conversation items
|
|
226
311
|
if (conversations.length === 0) {
|
|
227
312
|
const empty = document.createElement('div');
|
|
228
313
|
empty.style.cssText = 'padding:24px 16px;text-align:center;color:#aaa;font-size:13px;';
|
|
@@ -264,7 +349,6 @@
|
|
|
264
349
|
});
|
|
265
350
|
}
|
|
266
351
|
|
|
267
|
-
// New conversation button
|
|
268
352
|
const new_btn_wrap = document.createElement('div');
|
|
269
353
|
new_btn_wrap.style.cssText = 'padding:16px;';
|
|
270
354
|
new_btn_wrap.innerHTML = `
|
|
@@ -297,6 +381,7 @@
|
|
|
297
381
|
|
|
298
382
|
const input_bar = this.querySelector('#tp-chatbot-input-bar');
|
|
299
383
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
384
|
+
const close_bar = this.querySelector('#tp-close-bar');
|
|
300
385
|
|
|
301
386
|
try {
|
|
302
387
|
const url = `${this.api_url}/chat/conversations/${conversation_id}?user_id=${encodeURIComponent(this.user_id)}`;
|
|
@@ -318,6 +403,12 @@
|
|
|
318
403
|
return;
|
|
319
404
|
}
|
|
320
405
|
|
|
406
|
+
if (conv.status === 'bot' || conv.status === 'waiting_agent') {
|
|
407
|
+
if (close_bar) close_bar.style.display = 'block';
|
|
408
|
+
} else {
|
|
409
|
+
if (close_bar) close_bar.style.display = 'none';
|
|
410
|
+
}
|
|
411
|
+
|
|
321
412
|
if (conv.status === 'agent') {
|
|
322
413
|
this.agent_mode = true;
|
|
323
414
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
@@ -349,6 +440,9 @@
|
|
|
349
440
|
const back_btn = this.querySelector('#tp-back-btn');
|
|
350
441
|
if (back_btn) back_btn.style.display = 'block';
|
|
351
442
|
|
|
443
|
+
const close_bar = this.querySelector('#tp-close-bar');
|
|
444
|
+
if (close_bar) close_bar.style.display = 'block';
|
|
445
|
+
|
|
352
446
|
const container = this.querySelector('#tp-messages');
|
|
353
447
|
container.innerHTML = '';
|
|
354
448
|
|
|
@@ -421,7 +515,6 @@
|
|
|
421
515
|
|
|
422
516
|
this.user_info = { user_id: `guest-${email}`, first_name, last_name: '', company_name, email, language: 'FR', site_id: '' };
|
|
423
517
|
this.user_id = this.user_info.user_id;
|
|
424
|
-
this.applyTheme();
|
|
425
518
|
|
|
426
519
|
const form_el = this.querySelector('#tp-guest-form');
|
|
427
520
|
if (form_el) form_el.remove();
|
|
@@ -434,6 +527,9 @@
|
|
|
434
527
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
435
528
|
if (agent_bar) agent_bar.style.display = 'block';
|
|
436
529
|
|
|
530
|
+
const close_bar = this.querySelector('#tp-close-bar');
|
|
531
|
+
if (close_bar) close_bar.style.display = 'block';
|
|
532
|
+
|
|
437
533
|
this.addMessage('assistant', `Bonjour ${first_name} ! Comment puis-je vous aider aujourd'hui ?`);
|
|
438
534
|
this.polling_interval = setInterval(() => this.pollMessages(), 3000);
|
|
439
535
|
}
|
|
@@ -466,6 +562,10 @@
|
|
|
466
562
|
`;
|
|
467
563
|
container.appendChild(msg_el);
|
|
468
564
|
container.scrollTop = container.scrollHeight;
|
|
565
|
+
|
|
566
|
+
if ((role === 'assistant' || role === 'agent') && this.sound_enabled) {
|
|
567
|
+
playSound();
|
|
568
|
+
}
|
|
469
569
|
}
|
|
470
570
|
|
|
471
571
|
showTyping() {
|
|
@@ -485,7 +585,6 @@
|
|
|
485
585
|
|
|
486
586
|
showClosed() {
|
|
487
587
|
this.is_closed = true;
|
|
488
|
-
|
|
489
588
|
if (this.polling_interval) {
|
|
490
589
|
clearInterval(this.polling_interval);
|
|
491
590
|
this.polling_interval = null;
|
|
@@ -500,6 +599,9 @@
|
|
|
500
599
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
501
600
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
502
601
|
|
|
602
|
+
const close_bar = this.querySelector('#tp-close-bar');
|
|
603
|
+
if (close_bar) close_bar.style.display = 'none';
|
|
604
|
+
|
|
503
605
|
const container = this.querySelector('#tp-messages');
|
|
504
606
|
if (this.querySelector('#tp-closed-banner')) return;
|
|
505
607
|
|
|
@@ -510,11 +612,11 @@
|
|
|
510
612
|
banner.id = 'tp-closed-banner';
|
|
511
613
|
banner.innerHTML = `
|
|
512
614
|
<div style="margin:16px;padding:16px;background:#fef2f2;border:1px solid #fecaca;border-radius:10px;text-align:center;">
|
|
513
|
-
<div style="font-size:13px;color:#ef4444;font-weight:600;margin-bottom:12px;">✅ Cette conversation a été clôturée
|
|
615
|
+
<div style="font-size:13px;color:#ef4444;font-weight:600;margin-bottom:12px;">✅ Cette conversation a été clôturée.</div>
|
|
514
616
|
<button id="tp-new-conversation" style="padding:10px 20px;background:linear-gradient(135deg,${color},${dark});color:white;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;margin-bottom:8px;width:100%;">
|
|
515
617
|
💬 Nouvelle conversation
|
|
516
618
|
</button>
|
|
517
|
-
<button id="tp-back-to-list" style="padding:8px 20px;background:none;color
|
|
619
|
+
<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%;">
|
|
518
620
|
← Voir toutes les conversations
|
|
519
621
|
</button>
|
|
520
622
|
</div>
|
|
@@ -608,6 +710,20 @@
|
|
|
608
710
|
}
|
|
609
711
|
}
|
|
610
712
|
|
|
713
|
+
async closeByUser() {
|
|
714
|
+
if (!this.conversation_id || this.is_closed) return;
|
|
715
|
+
try {
|
|
716
|
+
const response = await fetch(`${this.api_url}/chat/conversations/${this.conversation_id}/close`, {
|
|
717
|
+
method: 'POST',
|
|
718
|
+
headers: this.getHeaders(true),
|
|
719
|
+
body: JSON.stringify({ user_id: this.user_id }),
|
|
720
|
+
});
|
|
721
|
+
if (response.ok) this.showClosed();
|
|
722
|
+
} catch (e) {
|
|
723
|
+
console.error('closeByUser error:', e);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
611
727
|
async pollMessages() {
|
|
612
728
|
if (!this.conversation_id || this.is_closed || this.view !== 'chat') return;
|
|
613
729
|
try {
|