@developpement/tp-chatbot-widget 0.0.11 → 0.0.13

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/src/chatbot.js CHANGED
@@ -1,9 +1,6 @@
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
4
  const ICONS = {
8
5
  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
6
  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>`,
@@ -15,12 +12,13 @@
15
12
  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
13
  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
14
  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
15
  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
16
  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
17
  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
18
  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
19
  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>`,
20
+ history: `<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 0 7-7 7 7 0 0 0-5 2.1L3 7"/><path d="M3 3v4h4"/><path d="M10 7v4l2.5 2.5"/></svg>`,
21
+ lock: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="9" width="12" height="9" rx="2"/><path d="M7 9V6a3 3 0 0 1 6 0v3"/></svg>`,
24
22
  };
25
23
 
26
24
  function icon(name, size = 18, extra_style = '') {
@@ -32,7 +30,114 @@
32
30
  );
33
31
  }
34
32
 
35
- // ─── Clients ─────────────────────────────────────────────────────────────────
33
+ function injectCSS() {
34
+ if (document.getElementById('tp-chatbot-base-css')) return;
35
+ const style = document.createElement('style');
36
+ style.id = 'tp-chatbot-base-css';
37
+ style.textContent = `
38
+ tp-chatbot * { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
39
+ .tp-chatbot-host { position: fixed; bottom: 24px; z-index: 9999; display: flex; flex-direction: column; align-items: flex-end; width: 380px; }
40
+ .tp-chatbot-window { position: absolute; bottom: 72px; left: 0; right: auto; width: 380px; height: 580px; background: #fff; border-radius: 20px; box-shadow: 0 8px 40px rgba(0,0,0,0.14); display: none; flex-direction: column; overflow: hidden; opacity: 0; transform: scale(0.92) translateY(12px); pointer-events: none; z-index: 9999; transition: opacity 0.22s ease, transform 0.22s ease, visibility 0.22s ease; }
41
+ .tp-chatbot-window.open { opacity: 1; visibility: visible; transform: scale(1) translateY(0); pointer-events: all; }
42
+ .tp-chatbot-bubble { width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.18s, box-shadow 0.18s; margin-left: auto; flex-shrink: 0; }
43
+ .tp-chatbot-bubble:hover { transform: scale(1.07); }
44
+ .tp-header { padding: 14px 16px 0; color: white; }
45
+ .tp-header-top { display: flex; align-items: center; gap: 10px; padding-bottom: 12px; }
46
+ .tp-header-avatar { width: 38px; height: 38px; border-radius: 50%; background: rgba(255,255,255,0.25); display: flex; align-items: center; justify-content: center; font-size: 15px; font-weight: 700; color: white; position: relative; flex-shrink: 0; }
47
+ .tp-header-dot { position: absolute; bottom: 1px; right: 1px; width: 9px; height: 9px; border-radius: 50%; background: #22c55e; border: 2px solid white; }
48
+ .tp-header-info { flex: 1; min-width: 0; }
49
+ .tp-header-title { font-size: 14px; font-weight: 700; color: white; line-height: 1.2; }
50
+ .tp-header-subtitle { font-size: 11px; color: rgba(255,255,255,0.8); display: flex; align-items: center; gap: 4px; margin-top: 2px; }
51
+ .tp-header-btn { background: rgba(255,255,255,0.18); border: none; border-radius: 8px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: white; transition: background 0.15s; flex-shrink: 0; }
52
+ .tp-header-btn:hover { background: rgba(255,255,255,0.32); }
53
+ .tp-header-controls { display: flex; align-items: center; gap: 6px; }
54
+ .tp-tabs { display: flex; gap: 0; padding: 0 16px; border-bottom: 1px solid rgba(255,255,255,0.2); margin-top: 2px; justify-content: center; }
55
+ .tp-tab { padding: 8px 14px; font-size: 12px; font-weight: 600; color: rgba(255,255,255,0.65); border: none; background: none; cursor: pointer; border-bottom: 2px solid transparent; transition: color 0.15s, border-color 0.15s; display: flex; align-items: center; gap: 5px; margin-bottom: -1px; }
56
+ .tp-tab.active { color: white; border-bottom-color: white; }
57
+ .tp-body { flex: 1; overflow-y: auto; background: #f7f7f8; display: flex; flex-direction: column; }
58
+ .tp-body::-webkit-scrollbar { width: 4px; }
59
+ .tp-body::-webkit-scrollbar-thumb { background: #ddd; border-radius: 2px; }
60
+ .tp-messages-wrap { flex: 1; overflow-y: auto; padding: 16px 14px; display: flex; flex-direction: column; gap: 10px; background: #f7f7f8; }
61
+ .tp-messages-wrap::-webkit-scrollbar { width: 4px; }
62
+ .tp-messages-wrap::-webkit-scrollbar-thumb { background: #ddd; border-radius: 2px; }
63
+ .tp-msg { display: flex; flex-direction: column; max-width: 82%; }
64
+ .tp-msg.user { align-self: flex-end; align-items: flex-end; }
65
+ .tp-msg.assistant, .tp-msg.agent, .tp-msg.system { align-self: flex-start; align-items: flex-start; }
66
+ .tp-msg-role { font-size: 10px; color: #aaa; margin-bottom: 3px; display: flex; align-items: center; gap: 3px; }
67
+ .tp-msg.user .tp-msg-role { color: #aaa; }
68
+ .tp-bubble-msg { padding: 10px 13px; border-radius: 16px; font-size: 13px; line-height: 1.55; word-break: break-word; }
69
+ .tp-msg.user .tp-bubble-msg { color: white; border-bottom-right-radius: 4px; }
70
+ .tp-msg.assistant .tp-bubble-msg, .tp-msg.agent .tp-bubble-msg { background: white; color: #1a1a2e; border-bottom-left-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,0.07); }
71
+ .tp-msg.system .tp-bubble-msg { background: #e8f5e9; color: #2e7d32; font-size: 12px; border-radius: 10px; padding: 8px 12px; }
72
+ .tp-msg-time { font-size: 10px; color: #bbb; margin-top: 3px; }
73
+ .tp-md p { margin-bottom: 6px; }
74
+ .tp-md p:last-child { margin-bottom: 0; }
75
+ .tp-md ul, .tp-md ol { padding-left: 18px; margin-bottom: 6px; }
76
+ .tp-md li { margin-bottom: 2px; }
77
+ .tp-md strong { font-weight: 600; }
78
+ .tp-md code { background: #f0f0f0; border-radius: 4px; padding: 1px 5px; font-size: 12px; }
79
+ .tp-md pre { background: #f0f0f0; border-radius: 8px; padding: 10px; overflow-x: auto; margin-bottom: 6px; }
80
+ .tp-feedback-bar { display: flex; gap: 6px; margin-top: 7px; }
81
+ .tp-feedback-btn { background: none; border: 1px solid #e5e5e5; border-radius: 8px; padding: 4px 8px; cursor: pointer; color: #aaa; display: flex; align-items: center; transition: all 0.15s; font-size: 11px; }
82
+ .tp-feedback-btn:hover { border-color: #bbb; color: #666; }
83
+ .tp-feedback-btn.voted-positive { border-color: #22c55e; color: #22c55e; background: #f0fdf4; }
84
+ .tp-feedback-btn.voted-negative { border-color: #ef4444; color: #ef4444; background: #fef2f2; }
85
+ .tp-typing { display: flex; align-items: center; gap: 4px; padding: 4px 2px; }
86
+ .tp-typing span { width: 7px; height: 7px; border-radius: 50%; background: #ccc; animation: tp-bounce 1.2s infinite ease-in-out; }
87
+ .tp-typing span:nth-child(2) { animation-delay: 0.18s; }
88
+ .tp-typing span:nth-child(3) { animation-delay: 0.36s; }
89
+ @keyframes tp-bounce { 0%,60%,100%{transform:translateY(0)} 30%{transform:translateY(-5px)} }
90
+ .tp-agent-bar { padding: 8px 14px; border-top: 1px solid #f0f0f0; background: white; }
91
+ .tp-agent-btn { width: 100%; padding: 9px 14px; border-radius: 10px; border: 1.5px solid; background: white; font-size: 12px; font-weight: 600; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 7px; transition: all 0.15s; font-family: inherit; }
92
+ .tp-agent-btn:hover { color: white !important; }
93
+ .tp-close-bar { padding: 8px 14px; border-top: 1px solid #f0f0f0; background: white; text-align: center; }
94
+ .tp-close-btn { background: none; border: none; font-size: 12px; color: #aaa; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; font-family: inherit; padding: 4px 8px; border-radius: 8px; transition: background 0.15s; }
95
+ .tp-close-btn:hover { background: #f5f5f5; color: #888; }
96
+ .tp-input-bar { display: flex; align-items: flex-end; gap: 8px; padding: 10px 12px; border-top: 1px solid #f0f0f0; background: white; }
97
+ .tp-input { flex: 1; padding: 9px 12px; border: 1.5px solid #e5e5e5; border-radius: 12px; font-size: 13px; font-family: inherit; resize: none; outline: none; max-height: 100px; line-height: 1.4; transition: border-color 0.15s; background: #fafafa; }
98
+ .tp-input:focus { border-color: inherit; background: white; }
99
+ .tp-send { width: 36px; height: 36px; border-radius: 10px; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: transform 0.15s; }
100
+ .tp-send:hover { transform: scale(1.06); }
101
+ .tp-conv-list { display: flex; flex-direction: column; flex: 1; }
102
+ .tp-conv-item { padding: 13px 16px; border-bottom: 1px solid #f2f2f2; cursor: pointer; transition: background 0.13s; display: flex; justify-content: space-between; align-items: center; background: white; }
103
+ .tp-conv-item:hover { background: #fafafa; }
104
+ .tp-conv-item-left { display: flex; flex-direction: column; gap: 3px; }
105
+ .tp-conv-item-date { font-size: 12px; font-weight: 600; color: #1a1a2e; }
106
+ .tp-conv-item-count { font-size: 11px; color: #bbb; }
107
+ .tp-badge { font-size: 10px; font-weight: 700; padding: 3px 9px; border-radius: 20px; display: inline-flex; align-items: center; gap: 3px; }
108
+ .tp-new-conv-btn { width: 100%; padding: 11px; color: white; border: none; border-radius: 12px; font-size: 13px; font-weight: 700; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; font-family: inherit; transition: opacity 0.15s; }
109
+ .tp-new-conv-btn:hover { opacity: 0.9; }
110
+ .tp-guest-wrap { padding: 16px; display: flex; flex-direction: column; gap: 10px; flex: 1; }
111
+ .tp-guest-intro { background: white; border-radius: 14px; padding: 14px; box-shadow: 0 1px 6px rgba(0,0,0,0.07); }
112
+ .tp-guest-intro-name { font-size: 13px; font-weight: 700; color: #1a1a2e; margin-bottom: 4px; display: flex; align-items: center; gap: 6px; }
113
+ .tp-guest-intro-sub { font-size: 12px; color: #888; line-height: 1.5; }
114
+ .tp-guest-field-label { font-size: 11px; font-weight: 600; color: #666; margin-bottom: 4px; display: block; }
115
+ .tp-guest-input { width: 100%; padding: 10px 13px; border: 1.5px solid #e8e8e8; border-radius: 10px; font-size: 13px; font-family: inherit; outline: none; background: white; transition: border-color 0.15s; }
116
+ .tp-guest-input:focus { border-color: inherit; }
117
+ .tp-guest-submit { width: 100%; padding: 11px; color: white; border: none; border-radius: 12px; font-size: 13px; font-weight: 700; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; font-family: inherit; margin-top: 2px; transition: opacity 0.15s; }
118
+ .tp-guest-submit:hover { opacity: 0.9; }
119
+ .tp-guest-footer { text-align: center; font-size: 10px; color: #ccc; display: flex; align-items: center; justify-content: center; gap: 4px; }
120
+ .tp-guest-error { font-size: 12px; color: #ef4444; display: none; text-align: center; }
121
+ .tp-closed-banner { margin: 12px; padding: 16px; background: white; border: 1px solid #f0f0f0; border-radius: 16px; text-align: center; box-shadow: 0 1px 6px rgba(0,0,0,0.06); }
122
+ .tp-closed-title { font-size: 13px; font-weight: 700; color: #1a1a2e; margin-bottom: 12px; display: flex; align-items: center; justify-content: center; gap: 6px; }
123
+ .tp-closed-date { font-size: 11px; color: #aaa; margin-top: -8px; margin-bottom: 12px; }
124
+ .tp-csat-q { font-size: 12px; color: #888; margin-bottom: 10px; }
125
+ .tp-csat-btns { display: flex; gap: 10px; justify-content: center; margin-bottom: 14px; }
126
+ .tp-csat-btn { flex: 1; padding: 10px; border-radius: 10px; border: 1.5px solid; background: white; cursor: pointer; font-size: 13px; font-weight: 600; display: flex; align-items: center; justify-content: center; gap: 6px; transition: all 0.15s; font-family: inherit; }
127
+ .tp-csat-btn.positive { border-color: #22c55e; color: #22c55e; }
128
+ .tp-csat-btn.positive:hover { background: #22c55e; color: white; }
129
+ .tp-csat-btn.negative { border-color: #ef4444; color: #ef4444; }
130
+ .tp-csat-btn.negative:hover { background: #ef4444; color: white; }
131
+ .tp-csat-thanks { font-size: 13px; color: #888; margin-bottom: 14px; }
132
+ .tp-closed-new-btn { width: 100%; padding: 11px; 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; font-family: inherit; margin-bottom: 8px; transition: opacity 0.15s; }
133
+ .tp-closed-new-btn:hover { opacity: 0.9; }
134
+ .tp-closed-hist-btn { width: 100%; padding: 9px; background: none; border: 1px solid #e8e8e8; border-radius: 10px; font-size: 12px; font-weight: 600; cursor: pointer; font-family: inherit; color: #888; transition: background 0.15s; }
135
+ .tp-closed-hist-btn:hover { background: #f5f5f5; }
136
+ .tp-suggestions { display: flex; flex-direction: column; gap: 6px; padding: 4px 0 6px; }
137
+ .tp-suggestion-btn { padding: 8px 14px; border-radius: 20px; border: 1.5px solid; background: white; font-size: 12px; font-weight: 600; cursor: pointer; text-align: left; font-family: inherit; transition: all 0.12s; }
138
+ `;
139
+ document.head.appendChild(style);
140
+ }
36
141
 
37
142
  const CLIENT_THEMES = {
38
143
  flix: {
@@ -116,8 +221,8 @@
116
221
  };
117
222
 
118
223
  const DEFAULT_THEME = { primary: '#7b1fa2', name: 'Support', position: 'right', suggestions: [] };
119
- const WINDOW_WIDTH = '380px';
120
- const WINDOW_HEIGHT = '580px';
224
+ const WINDOW_WIDTH = '340px';
225
+ const WINDOW_HEIGHT = '480px';
121
226
 
122
227
  function getClientTheme(client_id) {
123
228
  return CLIENT_THEMES[client_id] || DEFAULT_THEME;
@@ -143,8 +248,8 @@
143
248
  function playSound() {
144
249
  try {
145
250
  const ctx = new (window.AudioContext || window.webkitAudioContext)();
146
- const osc = ctx.createOscillator();
147
- const gain = ctx.createGain();
251
+ const osc = ctx.createOscillator(),
252
+ gain = ctx.createGain();
148
253
  osc.connect(gain);
149
254
  gain.connect(ctx.destination);
150
255
  osc.frequency.value = 880;
@@ -156,7 +261,9 @@
156
261
  } catch {}
157
262
  }
158
263
 
159
- // ─── Component ───────────────────────────────────────────────────────────────
264
+ function formatTime(iso) {
265
+ return new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
266
+ }
160
267
 
161
268
  class TpChatbot extends HTMLElement {
162
269
  constructor() {
@@ -179,6 +286,7 @@
179
286
  this.access_token = '';
180
287
  this.is_fullscreen = false;
181
288
  this.is_minimized = false;
289
+ this.active_tab = 'new';
182
290
  }
183
291
 
184
292
  static get observedAttributes() {
@@ -195,9 +303,9 @@
195
303
  this.user_info = extractUserInfo(new_val);
196
304
  this.user_id = this.user_info?.user_id;
197
305
  this.applyTheme();
198
- const guest_form = this.querySelector('#tp-guest-form');
199
- if (guest_form) {
200
- guest_form.remove();
306
+ const guest_wrap = this.querySelector('#tp-guest-wrap');
307
+ if (guest_wrap) {
308
+ guest_wrap.remove();
201
309
  this.showConversationList();
202
310
  }
203
311
  }
@@ -209,7 +317,6 @@
209
317
  if (!response.ok) return;
210
318
  const data = await response.json();
211
319
  const t = data.result?.result;
212
- console.log('[fetchTheme] open_by_default:', t.open_by_default, typeof t.open_by_default);
213
320
  if (!t) return;
214
321
  if (CLIENT_THEMES[this.client_id]) {
215
322
  if (t.primary) CLIENT_THEMES[this.client_id].primary = t.primary;
@@ -225,7 +332,7 @@
225
332
  };
226
333
  }
227
334
  } catch (e) {
228
- console.warn('[tp-chatbot] fetchTheme failed, using defaults:', e.message);
335
+ console.warn('[tp-chatbot] fetchTheme failed:', e.message);
229
336
  }
230
337
  }
231
338
 
@@ -242,6 +349,9 @@
242
349
  this.view = 'list';
243
350
  this.is_fullscreen = false;
244
351
  this.is_minimized = false;
352
+ this.active_tab = 'new';
353
+
354
+ injectCSS();
245
355
 
246
356
  this.fetchTheme().then(() => {
247
357
  this.render();
@@ -250,11 +360,7 @@
250
360
  this._initialized = true;
251
361
 
252
362
  const theme = getClientTheme(this.client_id);
253
- console.log('[widget] theme after fetch:', theme);
254
-
255
- if (theme.open_by_default) {
256
- setTimeout(() => this.toggleChat(), 300);
257
- }
363
+ if (theme.open_by_default) setTimeout(() => this.toggleChat(), 300);
258
364
 
259
365
  if (this.access_token) {
260
366
  this.user_info = extractUserInfo(this.access_token);
@@ -280,8 +386,6 @@
280
386
  if (this.polling_interval) clearInterval(this.polling_interval);
281
387
  }
282
388
 
283
- // ─── i18n ──────────────────────────────────────────────────────────────────
284
-
285
389
  getMessages() {
286
390
  const lang = (this.user_info?.language || 'EN').toUpperCase();
287
391
  const msgs = {
@@ -307,6 +411,8 @@
307
411
  close_btn: 'Terminer la conversation',
308
412
  speak_agent: 'Parler à un agent',
309
413
  write_msg: 'Écrivez votre message...',
414
+ tab_new: 'Nouveau',
415
+ tab_history: 'Historique',
310
416
  limit_reached: 'Vous avez atteint la limite de 30 messages. Veuillez contacter un agent ou écrire à support@travelplanet.com.',
311
417
  too_many: 'Trop de messages. Veuillez patienter avant de réessayer.',
312
418
  error_occurred: 'Une erreur est survenue. Veuillez réessayer.',
@@ -321,6 +427,17 @@
321
427
  status_waiting: 'En attente',
322
428
  status_bot: 'Bot',
323
429
  messages: 'message',
430
+ guest_title: 'Bonjour ! Je suis Maria 👋',
431
+ guest_sub: "Identifiez-vous pour démarrer. Réponse en moins d'une minute.",
432
+ guest_firstname: 'Prénom *',
433
+ guest_company: 'Entreprise *',
434
+ guest_email: 'Email *',
435
+ guest_submit: 'Démarrer la conversation',
436
+ guest_secured: 'Données sécurisées · Propulsé par Makitizy',
437
+ guest_fill: 'Veuillez remplir tous les champs.',
438
+ guest_email_invalid: 'Veuillez entrer un email valide.',
439
+ helpful: 'Utile',
440
+ not_helpful: 'Pas utile',
324
441
  },
325
442
  EN: {
326
443
  closed: 'Conversation closed',
@@ -344,6 +461,8 @@
344
461
  close_btn: 'End conversation',
345
462
  speak_agent: 'Talk to an agent',
346
463
  write_msg: 'Write your message...',
464
+ tab_new: 'New',
465
+ tab_history: 'History',
347
466
  limit_reached: 'You have reached the 30 message limit. Please contact an agent or write to support@travelplanet.com.',
348
467
  too_many: 'Too many messages. Please wait before retrying.',
349
468
  error_occurred: 'An error occurred. Please try again.',
@@ -358,6 +477,17 @@
358
477
  status_waiting: 'Waiting',
359
478
  status_bot: 'Bot',
360
479
  messages: 'message',
480
+ guest_title: "Hello! I'm Maria 👋",
481
+ guest_sub: 'Identify yourself to start. Answer in less than a minute.',
482
+ guest_firstname: 'First name *',
483
+ guest_company: 'Company *',
484
+ guest_email: 'Email *',
485
+ guest_submit: 'Start conversation',
486
+ guest_secured: 'Secured data · Powered by Makitizy',
487
+ guest_fill: 'Please fill in all fields.',
488
+ guest_email_invalid: 'Please enter a valid email.',
489
+ helpful: 'Helpful',
490
+ not_helpful: 'Not helpful',
361
491
  },
362
492
  DE: {
363
493
  closed: 'Gespräch beendet',
@@ -366,8 +496,8 @@
366
496
  csat_negative: 'Danke, wir werden uns verbessern.',
367
497
  new_conv: 'Neues Gespräch',
368
498
  back_to_list: 'Zurück zur Liste',
369
- agent_taken: 'Ein Agent hat Ihr Gespräch übernommen. Sie können ihm jetzt direkt schreiben.',
370
- agent_released: 'Unser virtueller Assistent ist zurück. Wie kann ich Ihnen helfen?',
499
+ agent_taken: 'Ein Agent hat Ihr Gespräch übernommen.',
500
+ agent_released: 'Unser virtueller Assistent ist zurück.',
371
501
  close_confirm: 'Möchten Sie das Gespräch beenden?',
372
502
  terminated: 'Gespräch beendet',
373
503
  virtual: 'Virtueller Assistent',
@@ -381,13 +511,15 @@
381
511
  close_btn: 'Gespräch beenden',
382
512
  speak_agent: 'Mit einem Agenten sprechen',
383
513
  write_msg: 'Schreiben Sie Ihre Nachricht...',
514
+ tab_new: 'Neu',
515
+ tab_history: 'Verlauf',
384
516
  limit_reached: 'Sie haben das Limit von 30 Nachrichten erreicht.',
385
517
  too_many: 'Zu viele Nachrichten. Bitte warten Sie.',
386
- error_occurred: 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.',
518
+ error_occurred: 'Ein Fehler ist aufgetreten.',
387
519
  outside_hours: next => `Unser Support ist derzeit geschlossen. Nächster Termin: ${next}.`,
388
- no_agents: 'Derzeit kein Agent verfügbar. Bitte versuchen Sie es später.',
389
- waiting_agent: 'Ihre Anfrage wurde übermittelt. Ein Agent übernimmt gleich.',
390
- waiting_agent_already: 'Sie sind bereits in der Warteschlange. Bitte warten.',
520
+ no_agents: 'Derzeit kein Agent verfügbar.',
521
+ waiting_agent: 'Ihre Anfrage wurde übermittelt.',
522
+ waiting_agent_already: 'Sie sind bereits in der Warteschlange.',
391
523
  agent_already: 'Ein Agent betreut bereits Ihr Gespräch.',
392
524
  suggest_agent_escalation: 'Ich kann keine passende Antwort finden. Möchten Sie mit einem Agenten sprechen?',
393
525
  status_closed: 'Geschlossen',
@@ -395,6 +527,17 @@
395
527
  status_waiting: 'Wartend',
396
528
  status_bot: 'Bot',
397
529
  messages: 'Nachricht',
530
+ guest_title: 'Hallo! Ich bin Maria 👋',
531
+ guest_sub: 'Identifizieren Sie sich, um zu starten.',
532
+ guest_firstname: 'Vorname *',
533
+ guest_company: 'Unternehmen *',
534
+ guest_email: 'E-Mail *',
535
+ guest_submit: 'Gespräch starten',
536
+ guest_secured: 'Gesicherte Daten · Powered by Makitizy',
537
+ guest_fill: 'Bitte füllen Sie alle Felder aus.',
538
+ guest_email_invalid: 'Bitte geben Sie eine gültige E-Mail ein.',
539
+ helpful: 'Hilfreich',
540
+ not_helpful: 'Nicht hilfreich',
398
541
  },
399
542
  ES: {
400
543
  closed: 'Conversación cerrada',
@@ -403,14 +546,14 @@
403
546
  csat_negative: 'Gracias, mejoraremos.',
404
547
  new_conv: 'Nueva conversación',
405
548
  back_to_list: 'Volver a las conversaciones',
406
- agent_taken: 'Un agente ha tomado su conversación. Ahora puede escribirle directamente.',
407
- agent_released: 'Nuestro asistente virtual ha vuelto. ¿En qué puedo ayudarle?',
549
+ agent_taken: 'Un agente ha tomado su conversación.',
550
+ agent_released: 'Nuestro asistente virtual ha vuelto.',
408
551
  close_confirm: '¿Desea finalizar esta conversación?',
409
552
  terminated: 'Conversación terminada',
410
553
  virtual: 'Asistente virtual',
411
554
  human_agent: 'Agente humano',
412
555
  loading: 'Cargando...',
413
- no_conv: 'No hay conversaciones por el momento.',
556
+ no_conv: 'No hay conversaciones.',
414
557
  new_conv_btn: 'Nueva conversación',
415
558
  error_load: 'Error de carga.',
416
559
  recent_conv: 'Sus conversaciones recientes',
@@ -418,20 +561,33 @@
418
561
  close_btn: 'Finalizar conversación',
419
562
  speak_agent: 'Hablar con un agente',
420
563
  write_msg: 'Escriba su mensaje...',
564
+ tab_new: 'Nuevo',
565
+ tab_history: 'Historial',
421
566
  limit_reached: 'Ha alcanzado el límite de 30 mensajes.',
422
- too_many: 'Demasiados mensajes. Por favor espere.',
423
- error_occurred: 'Se produjo un error. Por favor, inténtelo de nuevo.',
567
+ too_many: 'Demasiados mensajes.',
568
+ error_occurred: 'Se produjo un error.',
424
569
  outside_hours: next => `Nuestro soporte está cerrado. Próximo horario: ${next}.`,
425
- no_agents: 'No hay agentes disponibles. Inténtelo más tarde.',
426
- waiting_agent: 'Su solicitud ha sido enviada. Un agente le atenderá pronto.',
427
- waiting_agent_already: 'Ya está en la cola de espera. Por favor, espere.',
570
+ no_agents: 'No hay agentes disponibles.',
571
+ waiting_agent: 'Su solicitud ha sido enviada.',
572
+ waiting_agent_already: 'Ya está en la cola.',
428
573
  agent_already: 'Un agente ya está gestionando su conversación.',
429
- suggest_agent_escalation: 'No encuentro una respuesta adecuada. ¿Desea hablar con un agente?',
574
+ suggest_agent_escalation: '¿Desea hablar con un agente?',
430
575
  status_closed: 'Cerrado',
431
576
  status_agent: 'Agente',
432
577
  status_waiting: 'Esperando',
433
578
  status_bot: 'Bot',
434
579
  messages: 'mensaje',
580
+ guest_title: '¡Hola! Soy Maria 👋',
581
+ guest_sub: 'Identifíquese para comenzar.',
582
+ guest_firstname: 'Nombre *',
583
+ guest_company: 'Empresa *',
584
+ guest_email: 'Email *',
585
+ guest_submit: 'Iniciar conversación',
586
+ guest_secured: 'Datos seguros · Powered by Makitizy',
587
+ guest_fill: 'Complete todos los campos.',
588
+ guest_email_invalid: 'Email inválido.',
589
+ helpful: 'Útil',
590
+ not_helpful: 'No útil',
435
591
  },
436
592
  IT: {
437
593
  closed: 'Conversazione chiusa',
@@ -441,13 +597,13 @@
441
597
  new_conv: 'Nuova conversazione',
442
598
  back_to_list: 'Torna alle conversazioni',
443
599
  agent_taken: 'Un agente ha preso in carico la sua conversazione.',
444
- agent_released: 'Il nostro assistente virtuale è tornato. Come posso aiutarla?',
600
+ agent_released: 'Il nostro assistente virtuale è tornato.',
445
601
  close_confirm: 'Vuole terminare questa conversazione?',
446
602
  terminated: 'Conversazione terminata',
447
603
  virtual: 'Assistente virtuale',
448
604
  human_agent: 'Agente umano',
449
605
  loading: 'Caricamento...',
450
- no_conv: 'Nessuna conversazione per il momento.',
606
+ no_conv: 'Nessuna conversazione.',
451
607
  new_conv_btn: 'Nuova conversazione',
452
608
  error_load: 'Errore di caricamento.',
453
609
  recent_conv: 'Le sue conversazioni recenti',
@@ -455,20 +611,33 @@
455
611
  close_btn: 'Termina conversazione',
456
612
  speak_agent: 'Parla con un agente',
457
613
  write_msg: 'Scrivi il tuo messaggio...',
614
+ tab_new: 'Nuovo',
615
+ tab_history: 'Cronologia',
458
616
  limit_reached: 'Hai raggiunto il limite di 30 messaggi.',
459
- too_many: 'Troppi messaggi. Attendere prego.',
460
- error_occurred: 'Si è verificato un errore. Riprova.',
617
+ too_many: 'Troppi messaggi.',
618
+ error_occurred: 'Si è verificato un errore.',
461
619
  outside_hours: next => `Il supporto è chiuso. Prossima disponibilità: ${next}.`,
462
- no_agents: 'Nessun agente disponibile. Riprova più tardi.',
463
- waiting_agent: 'Richiesta inviata. Un agente prenderà in carico la conversazione.',
464
- waiting_agent_already: 'È già in coda. Attenda.',
620
+ no_agents: 'Nessun agente disponibile.',
621
+ waiting_agent: 'Richiesta inviata.',
622
+ waiting_agent_already: 'È già in coda.',
465
623
  agent_already: 'Un agente sta già gestendo la conversazione.',
466
- suggest_agent_escalation: 'Non riesco a trovare una risposta adeguata. Desidera parlare con un agente?',
624
+ suggest_agent_escalation: 'Desidera parlare con un agente?',
467
625
  status_closed: 'Chiuso',
468
626
  status_agent: 'Agente',
469
627
  status_waiting: 'Attesa',
470
628
  status_bot: 'Bot',
471
629
  messages: 'messaggio',
630
+ guest_title: 'Ciao! Sono Maria 👋',
631
+ guest_sub: 'Si identifichi per iniziare.',
632
+ guest_firstname: 'Nome *',
633
+ guest_company: 'Azienda *',
634
+ guest_email: 'Email *',
635
+ guest_submit: 'Inizia conversazione',
636
+ guest_secured: 'Dati sicuri · Powered by Makitizy',
637
+ guest_fill: 'Compila tutti i campi.',
638
+ guest_email_invalid: 'Email non valida.',
639
+ helpful: 'Utile',
640
+ not_helpful: 'Non utile',
472
641
  },
473
642
  NL: {
474
643
  closed: 'Gesprek gesloten',
@@ -478,13 +647,13 @@
478
647
  new_conv: 'Nieuw gesprek',
479
648
  back_to_list: 'Terug naar gesprekken',
480
649
  agent_taken: 'Een agent heeft uw gesprek overgenomen.',
481
- agent_released: 'Onze virtuele assistent is terug. Hoe kan ik u helpen?',
650
+ agent_released: 'Onze virtuele assistent is terug.',
482
651
  close_confirm: 'Wilt u dit gesprek beëindigen?',
483
652
  terminated: 'Gesprek beëindigd',
484
653
  virtual: 'Virtuele assistent',
485
654
  human_agent: 'Menselijke agent',
486
655
  loading: 'Laden...',
487
- no_conv: 'Geen gesprekken op dit moment.',
656
+ no_conv: 'Geen gesprekken.',
488
657
  new_conv_btn: 'Nieuw gesprek',
489
658
  error_load: 'Laadout.',
490
659
  recent_conv: 'Uw recente gesprekken',
@@ -492,20 +661,33 @@
492
661
  close_btn: 'Gesprek beëindigen',
493
662
  speak_agent: 'Praat met een agent',
494
663
  write_msg: 'Schrijf uw bericht...',
664
+ tab_new: 'Nieuw',
665
+ tab_history: 'Geschiedenis',
495
666
  limit_reached: 'U heeft de limiet van 30 berichten bereikt.',
496
- too_many: 'Te veel berichten. Wacht even.',
497
- error_occurred: 'Er is een fout opgetreden. Probeer het opnieuw.',
498
- outside_hours: next => `Onze support is gesloten. Volgende beschikbare tijd: ${next}.`,
499
- no_agents: 'Geen agent beschikbaar. Probeer het later opnieuw.',
500
- waiting_agent: 'Uw verzoek is verzonden. Een agent neemt het gesprek over.',
501
- waiting_agent_already: 'U staat al in de wachtrij. Even geduld.',
667
+ too_many: 'Te veel berichten.',
668
+ error_occurred: 'Er is een fout opgetreden.',
669
+ outside_hours: next => `Support is gesloten. Volgende tijd: ${next}.`,
670
+ no_agents: 'Geen agent beschikbaar.',
671
+ waiting_agent: 'Uw verzoek is verzonden.',
672
+ waiting_agent_already: 'U staat al in de wachtrij.',
502
673
  agent_already: 'Een agent beheert uw gesprek al.',
503
- suggest_agent_escalation: 'Ik kan geen passend antwoord vinden. Wilt u met een agent spreken?',
674
+ suggest_agent_escalation: 'Wilt u met een agent spreken?',
504
675
  status_closed: 'Gesloten',
505
676
  status_agent: 'Agent',
506
677
  status_waiting: 'Wachten',
507
678
  status_bot: 'Bot',
508
679
  messages: 'bericht',
680
+ guest_title: 'Hallo! Ik ben Maria 👋',
681
+ guest_sub: 'Identificeer uzelf om te starten.',
682
+ guest_firstname: 'Voornaam *',
683
+ guest_company: 'Bedrijf *',
684
+ guest_email: 'E-mail *',
685
+ guest_submit: 'Gesprek starten',
686
+ guest_secured: 'Beveiligde gegevens · Powered by Makitizy',
687
+ guest_fill: 'Vul alle velden in.',
688
+ guest_email_invalid: 'Ongeldig e-mailadres.',
689
+ helpful: 'Nuttig',
690
+ not_helpful: 'Niet nuttig',
509
691
  },
510
692
  };
511
693
  return msgs[lang] || msgs['EN'];
@@ -525,8 +707,6 @@
525
707
  return msgs[lang] || msgs['EN'];
526
708
  }
527
709
 
528
- // ─── Theme ─────────────────────────────────────────────────────────────────
529
-
530
710
  getThemeColor() {
531
711
  return getClientTheme(this.client_id).primary;
532
712
  }
@@ -547,30 +727,28 @@
547
727
  const style = document.createElement('style');
548
728
  style.id = 'tp-chatbot-theme';
549
729
  style.textContent = `
550
- .tp-chatbot-bubble { background: linear-gradient(135deg, ${color}, ${dark}) !important; box-shadow: 0 4px 16px ${color}66 !important; }
551
- .tp-chatbot-header { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
552
- .tp-chatbot-message.user .tp-chatbot-bubble-msg { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
553
- .tp-chatbot-avatar { color: ${color} !important; }
554
- .tp-chatbot-role { color: ${color} !important; }
555
- .tp-chatbot-message.user .tp-chatbot-role { color: rgba(255,255,255,0.8) !important; }
556
- .tp-chatbot-input:focus { border-color: ${color} !important; }
557
- .tp-chatbot-send { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
558
- .tp-chatbot-agent-btn { border-color: ${color} !important; color: ${color} !important; }
559
- .tp-chatbot-agent-btn:hover { background: ${color} !important; color: white !important; }
560
- .tp-chatbot-typing span { background: ${color} !important; }
561
- #tp-new-conversation { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
562
- .tp-conv-item:hover { background: ${color}11 !important; }
730
+ .tp-chatbot-bubble { background: linear-gradient(135deg, ${color}, ${dark}) !important; box-shadow: 0 4px 20px ${color}55 !important; }
731
+ .tp-header { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
732
+ .tp-msg.user .tp-bubble-msg { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
733
+ .tp-input:focus { border-color: ${color} !important; }
734
+ .tp-send { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
735
+ .tp-agent-btn { border-color: ${color} !important; color: ${color} !important; }
736
+ .tp-agent-btn:hover { background: ${color} !important; color: white !important; }
737
+ .tp-typing span { background: ${color} !important; }
738
+ .tp-new-conv-btn { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
563
739
  .tp-conv-new-btn { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
564
- .tp-badge-closed { background: ${color}22 !important; color: ${dark} !important; }
565
- .tp-attach-btn { color: ${color} !important; }
566
- .tp-attach-btn:hover { background: ${color}18 !important; }
740
+ .tp-conv-item:hover { background: ${color}0d !important; }
741
+ .tp-guest-input:focus { border-color: ${color} !important; }
742
+ .tp-guest-submit { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
743
+ .tp-tab.active { border-bottom-color: white !important; color: white !important; }
744
+ .tp-closed-new-btn { background: linear-gradient(135deg, ${color}, ${dark}) !important; }
745
+ .tp-suggestion-btn { border-color: ${color} !important; color: ${color} !important; }
746
+ .tp-suggestion-btn:hover { background: ${color} !important; color: white !important; }
567
747
  `;
568
748
  document.head.appendChild(style);
569
-
570
749
  const position = getClientTheme(this.client_id).position || 'right';
571
750
  const host = this.querySelector('.tp-chatbot-host');
572
751
  if (host) {
573
- host.style.width = WINDOW_WIDTH;
574
752
  host.style.left = position === 'left' ? '24px' : 'auto';
575
753
  host.style.right = position === 'left' ? 'auto' : '24px';
576
754
  }
@@ -579,7 +757,7 @@
579
757
  bubble.style.marginLeft = position === 'left' ? '0' : 'auto';
580
758
  bubble.style.marginRight = position === 'left' ? 'auto' : '0';
581
759
  }
582
- const win = this.querySelector('.tp-chatbot-window');
760
+ const win = this.querySelector('#tp-window');
583
761
  if (win) {
584
762
  win.style.left = '0';
585
763
  win.style.right = 'auto';
@@ -587,47 +765,55 @@
587
765
  }
588
766
  }
589
767
 
590
- // ─── Render ────────────────────────────────────────────────────────────────
591
-
592
768
  render() {
593
769
  const theme = getClientTheme(this.client_id);
770
+ const m = this.getMessages();
594
771
  this.innerHTML = `
595
772
  <div class="tp-chatbot-host">
596
773
  <div class="tp-chatbot-window" id="tp-window">
597
- <div class="tp-chatbot-header" id="tp-header">
598
- <div class="tp-chatbot-avatar">M</div>
599
- <div style="flex:1;min-width:0;">
600
- <div class="tp-chatbot-title">${theme.name} Support</div>
601
- <div class="tp-chatbot-subtitle" id="tp-subtitle">${icon('bot', 12, 'margin-right:4px')} Assistant</div>
774
+ <div class="tp-header" id="tp-header">
775
+ <div class="tp-header-top">
776
+ <div class="tp-header-avatar">M<div class="tp-header-dot"></div></div>
777
+ <div class="tp-header-info">
778
+ <div class="tp-header-title">${theme.name} Support</div>
779
+ <div class="tp-header-subtitle" id="tp-subtitle">${icon('bot', 11)} ${m.virtual}</div>
780
+ </div>
781
+ <div class="tp-header-controls">
782
+ <button id="tp-sound-btn" class="tp-header-btn" title="Sound">${icon('bell', 14)}</button>
783
+ <button id="tp-minimize-btn" class="tp-header-btn" title="Minimize">${icon('minimize', 14)}</button>
784
+ <button id="tp-maximize-btn" class="tp-header-btn" title="Fullscreen">${icon('restore', 14)}</button>
785
+ <button id="tp-back-btn" class="tp-header-btn" style="display:none;" title="Back">${icon('back', 14)}</button>
786
+ <button id="tp-close-win-btn" class="tp-header-btn" title="Close">${icon('close', 14)}</button>
787
+ </div>
788
+ </div>
789
+ <div class="tp-tabs" id="tp-tabs">
790
+ <button class="tp-tab active" data-tab="new" id="tp-tab-new">${icon('msg_plus', 12)} ${m.tab_new}</button>
791
+ <button class="tp-tab" data-tab="history" id="tp-tab-history">${icon('history', 12)} ${m.tab_history}</button>
602
792
  </div>
603
- <button id="tp-sound-btn" class="tp-header-btn" title="Sound">${icon('bell', 16)}</button>
604
- <button id="tp-minimize-btn" class="tp-header-btn" title="Minimize">${icon('minimize', 16)}</button>
605
- <button id="tp-maximize-btn" class="tp-header-btn" title="Fullscreen">${icon('restore', 16)}</button>
606
- <button id="tp-back-btn" class="tp-header-btn" style="display:none;" title="Back">${icon('back', 16)}</button>
607
793
  </div>
608
- <div class="tp-chatbot-messages" id="tp-messages"></div>
609
- <div class="tp-chatbot-agent-bar" id="tp-agent-bar" style="display:none;">
610
- <button class="tp-chatbot-agent-btn" id="tp-agent-btn">
611
- <span style="display:inline-flex;align-items:center;gap:6px;">${icon('agent', 16)} Talk to an agent</span>
794
+ <div class="tp-body" id="tp-body">
795
+ <div class="tp-messages-wrap" id="tp-messages"></div>
796
+ </div>
797
+ <div class="tp-agent-bar" id="tp-agent-bar" style="display:none;">
798
+ <button class="tp-agent-btn" id="tp-agent-btn">
799
+ <span style="display:inline-flex;align-items:center;gap:6px;">${icon('agent', 15)} ${m.speak_agent}</span>
612
800
  </button>
613
801
  </div>
614
- <div class="tp-chatbot-input-bar" id="tp-chatbot-input-bar" style="display:none;">
615
- <textarea class="tp-chatbot-input" id="tp-input" placeholder="Write your message..." rows="1"></textarea>
616
- <button class="tp-chatbot-send" id="tp-send">${icon('send', 16, 'color:white')}</button>
802
+ <div class="tp-input-bar" id="tp-input-bar" style="display:none;">
803
+ <textarea class="tp-input" id="tp-input" placeholder="${m.write_msg}" rows="1"></textarea>
804
+ <button class="tp-send" id="tp-send">${icon('send', 15, 'color:white')}</button>
617
805
  </div>
618
- <div id="tp-close-bar" style="padding:8px 12px;border-top:1px solid #ede8f5;text-align:center;display:none;">
619
- <button id="tp-close-btn" class="tp-close-btn">
620
- <span style="display:inline-flex;align-items:center;gap:6px;">${icon('check', 14)} End conversation</span>
806
+ <div class="tp-close-bar" id="tp-close-bar" style="display:none;">
807
+ <button class="tp-close-btn" id="tp-close-btn">
808
+ <span style="display:inline-flex;align-items:center;gap:5px;">${icon('check', 12)} ${m.close_btn}</span>
621
809
  </button>
622
810
  </div>
623
811
  </div>
624
- <div class="tp-chatbot-bubble" id="tp-bubble">${icon('msg', 24, 'color:white')}</div>
812
+ <div class="tp-chatbot-bubble" id="tp-bubble">${icon('msg', 26, 'color:white')}</div>
625
813
  </div>
626
814
  `;
627
815
  }
628
816
 
629
- // ─── Window controls ───────────────────────────────────────────────────────
630
-
631
817
  minimize() {
632
818
  const win = this.querySelector('#tp-window');
633
819
  if (this.is_fullscreen) this.exitFullscreen(false);
@@ -637,8 +823,8 @@
637
823
  }
638
824
 
639
825
  enterFullscreen() {
640
- const win = this.querySelector('#tp-window');
641
- const host = this.querySelector('.tp-chatbot-host');
826
+ const win = this.querySelector('#tp-window'),
827
+ host = this.querySelector('.tp-chatbot-host');
642
828
  this.is_fullscreen = true;
643
829
  this.is_minimized = false;
644
830
  win.style.position = 'fixed';
@@ -650,19 +836,19 @@
650
836
  win.style.zIndex = '99999';
651
837
  host.style.width = '100vw';
652
838
  const btn = this.querySelector('#tp-maximize-btn');
653
- if (btn) btn.innerHTML = icon('restore', 16);
839
+ if (btn) btn.innerHTML = icon('restore', 14);
654
840
  }
655
841
 
656
842
  exitFullscreen(restore_height = true) {
657
- const win = this.querySelector('#tp-window');
658
- const position = getClientTheme(this.client_id).position || 'right';
659
- const host = this.querySelector('.tp-chatbot-host');
843
+ const win = this.querySelector('#tp-window'),
844
+ position = getClientTheme(this.client_id).position || 'right',
845
+ host = this.querySelector('.tp-chatbot-host');
660
846
  this.is_fullscreen = false;
661
847
  win.style.position = 'absolute';
662
848
  win.style.top = '';
663
849
  win.style.left = '0';
664
850
  win.style.width = WINDOW_WIDTH;
665
- win.style.borderRadius = '16px';
851
+ win.style.borderRadius = '20px';
666
852
  win.style.zIndex = '';
667
853
  host.style.width = WINDOW_WIDTH;
668
854
  host.style.left = position === 'left' ? '24px' : 'auto';
@@ -670,33 +856,33 @@
670
856
  if (restore_height) win.style.height = WINDOW_HEIGHT;
671
857
  win.style.overflow = 'hidden';
672
858
  const btn = this.querySelector('#tp-maximize-btn');
673
- if (btn) btn.innerHTML = icon('restore', 16);
859
+ if (btn) btn.innerHTML = icon('restore', 14);
674
860
  }
675
861
 
676
- // ─── Events ────────────────────────────────────────────────────────────────
677
-
678
862
  attachEvents() {
679
863
  this.querySelector('#tp-bubble').addEventListener('click', () => this.toggleChat());
864
+ this.querySelector('#tp-close-win-btn').addEventListener('click', () => {
865
+ if (this.is_open) this.toggleChat();
866
+ });
680
867
  this.querySelector('#tp-send').addEventListener('click', () => this.sendMessage());
681
868
  this.querySelector('#tp-agent-btn').addEventListener('click', () => this.requestAgent());
682
- this.querySelector('#tp-back-btn').addEventListener('click', () => this.showConversationList());
683
-
869
+ this.querySelector('#tp-back-btn').addEventListener('click', () => {
870
+ this.setTab('new');
871
+ this.showConversationList();
872
+ });
684
873
  this.querySelector('#tp-input').addEventListener('keydown', e => {
685
874
  if (e.key === 'Enter' && !e.shiftKey) {
686
875
  e.preventDefault();
687
876
  this.sendMessage();
688
877
  }
689
878
  });
690
-
691
879
  this.querySelector('#tp-sound-btn').addEventListener('click', () => {
692
880
  this.sound_enabled = !this.sound_enabled;
693
- this.querySelector('#tp-sound-btn').innerHTML = icon(this.sound_enabled ? 'bell' : 'bell_off', 16);
881
+ this.querySelector('#tp-sound-btn').innerHTML = icon(this.sound_enabled ? 'bell' : 'bell_off', 14);
694
882
  });
695
-
696
883
  this.querySelector('#tp-close-btn').addEventListener('click', () => {
697
884
  if (window.confirm(this.getMessages().close_confirm)) this.closeByUser();
698
885
  });
699
-
700
886
  this.querySelector('#tp-minimize-btn').addEventListener('click', () => {
701
887
  if (this.is_minimized) {
702
888
  this.is_minimized = false;
@@ -707,7 +893,6 @@
707
893
  this.minimize();
708
894
  }
709
895
  });
710
-
711
896
  this.querySelector('#tp-maximize-btn').addEventListener('click', () => {
712
897
  if (this.is_fullscreen) {
713
898
  this.exitFullscreen(true);
@@ -721,40 +906,58 @@
721
906
  this.enterFullscreen();
722
907
  }
723
908
  });
909
+ this.querySelector('#tp-tab-new').addEventListener('click', () => {
910
+ this.setTab('new');
911
+ this.showConversationList();
912
+ });
913
+ this.querySelector('#tp-tab-history').addEventListener('click', () => {
914
+ this.setTab('history');
915
+ this.showConversationList();
916
+ });
917
+ }
918
+
919
+ setTab(tab) {
920
+ this.active_tab = tab;
921
+ this.querySelectorAll('.tp-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));
922
+ }
923
+
924
+ showTabs(visible) {
925
+ const tabs = this.querySelector('#tp-tabs');
926
+ if (tabs) tabs.style.display = visible ? 'flex' : 'none';
724
927
  }
725
928
 
726
929
  updateUILanguage() {
727
930
  const m = this.getMessages();
728
931
  const agent_btn = this.querySelector('#tp-agent-btn');
729
932
  if (agent_btn)
730
- agent_btn.innerHTML = `<span style="display:inline-flex;align-items:center;gap:6px;">${icon('agent', 16)} ${m.speak_agent}</span>`;
933
+ agent_btn.innerHTML = `<span style="display:inline-flex;align-items:center;gap:6px;">${icon('agent', 15)} ${m.speak_agent}</span>`;
731
934
  const close_btn = this.querySelector('#tp-close-btn');
732
935
  if (close_btn)
733
- close_btn.innerHTML = `<span style="display:inline-flex;align-items:center;gap:6px;">${icon('check', 14)} ${m.close_btn}</span>`;
936
+ close_btn.innerHTML = `<span style="display:inline-flex;align-items:center;gap:5px;">${icon('check', 12)} ${m.close_btn}</span>`;
734
937
  const input = this.querySelector('#tp-input');
735
938
  if (input) input.placeholder = m.write_msg;
736
- const subtitle = this.querySelector('#tp-subtitle');
737
- if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
738
939
  }
739
940
 
740
941
  toggleChat() {
741
- this.is_open = !this.is_open;
742
- const window_el = this.querySelector('#tp-window');
743
- const bubble = this.querySelector('#tp-bubble');
744
- if (this.is_open) {
745
- window_el.classList.add('open');
746
- bubble.innerHTML = icon('close', 20, 'color:white');
747
- if (this.is_minimized) {
748
- this.is_minimized = false;
749
- window_el.style.height = WINDOW_HEIGHT;
750
- window_el.style.overflow = 'hidden';
751
- }
752
- } else {
753
- window_el.classList.remove('open');
754
- bubble.innerHTML = icon('msg', 24, 'color:white');
755
- if (this.is_fullscreen) this.exitFullscreen(true);
756
- }
942
+ this.is_open = !this.is_open;
943
+ const window_el = this.querySelector('#tp-window');
944
+ const bubble = this.querySelector('#tp-bubble');
945
+ if (this.is_open) {
946
+ window_el.style.display = 'flex';
947
+ setTimeout(() => window_el.classList.add('open'), 10);
948
+ bubble.innerHTML = icon('close', 22, 'color:white');
949
+ if (this.is_minimized) {
950
+ this.is_minimized = false;
951
+ window_el.style.height = WINDOW_HEIGHT;
952
+ window_el.style.overflow = 'hidden';
757
953
  }
954
+ } else {
955
+ window_el.classList.remove('open');
956
+ setTimeout(() => { window_el.style.display = 'none'; }, 250);
957
+ bubble.innerHTML = icon('msg', 22, 'color:white');
958
+ if (this.is_fullscreen) this.exitFullscreen(true);
959
+ }
960
+ }
758
961
 
759
962
  getHeaders(content_type = false) {
760
963
  const headers = {};
@@ -773,31 +976,20 @@
773
976
  return data.result?.conversation_id;
774
977
  }
775
978
 
776
- // ─── Suggestions ──────────────────────────────────────────────────────────
777
-
778
979
  showSuggestions(suggestions) {
779
980
  const existing = this.querySelector('#tp-suggestions');
780
981
  if (existing) existing.remove();
781
982
  if (!suggestions || suggestions.length === 0) return;
782
983
  const lang = (this.user_info?.language || 'EN').toUpperCase();
783
- const color = this.getThemeColor();
784
984
  const container = this.querySelector('#tp-messages');
785
985
  const wrap = document.createElement('div');
786
986
  wrap.id = 'tp-suggestions';
787
- wrap.style.cssText = 'padding:8px 12px 12px;display:flex;flex-direction:column;gap:6px;';
987
+ wrap.className = 'tp-suggestions';
788
988
  suggestions.forEach(s => {
789
989
  const label = s[lang] || s['EN'];
790
990
  const btn = document.createElement('button');
791
- btn.style.cssText = `padding:8px 14px;border-radius:20px;border:1.5px solid ${color};background:white;color:${color};font-size:12px;font-weight:600;cursor:pointer;text-align:left;font-family:inherit;transition:all 0.12s;`;
991
+ btn.className = 'tp-suggestion-btn';
792
992
  btn.textContent = `→ ${label}`;
793
- btn.addEventListener('mouseenter', () => {
794
- btn.style.background = color;
795
- btn.style.color = 'white';
796
- });
797
- btn.addEventListener('mouseleave', () => {
798
- btn.style.background = 'white';
799
- btn.style.color = color;
800
- });
801
993
  btn.addEventListener('click', () => {
802
994
  const sugg_el = this.querySelector('#tp-suggestions');
803
995
  if (sugg_el) sugg_el.remove();
@@ -813,8 +1005,6 @@
813
1005
  container.scrollTop = container.scrollHeight;
814
1006
  }
815
1007
 
816
- // ─── Conversation List ─────────────────────────────────────────────────────
817
-
818
1008
  async showConversationList() {
819
1009
  this.view = 'list';
820
1010
  const m = this.getMessages();
@@ -822,94 +1012,107 @@
822
1012
  clearInterval(this.polling_interval);
823
1013
  this.polling_interval = null;
824
1014
  }
825
-
826
1015
  const subtitle = this.querySelector('#tp-subtitle');
827
- if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
1016
+ if (subtitle) subtitle.innerHTML = `${icon('bot', 11)} ${m.virtual}`;
828
1017
  this.querySelector('#tp-back-btn').style.display = 'none';
829
- this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
1018
+ this.querySelector('#tp-input-bar').style.display = 'none';
830
1019
  this.querySelector('#tp-agent-bar').style.display = 'none';
831
1020
  this.querySelector('#tp-close-bar').style.display = 'none';
832
-
1021
+ this.showTabs(true);
833
1022
  const container = this.querySelector('#tp-messages');
834
- container.style.background = '#f5f5f7';
835
- container.innerHTML = `<div style="padding:16px;text-align:center;color:#aaa;font-size:12px;">${m.loading}</div>`;
836
-
1023
+ container.innerHTML = `<div style="padding:24px 16px;text-align:center;color:#ccc;font-size:12px;">${m.loading}</div>`;
837
1024
  try {
838
1025
  const url = `${this.api_url}/chat/conversations?user_id=${encodeURIComponent(this.user_id)}&limit=5`;
839
1026
  const response = await fetch(url, { headers: this.getHeaders() });
840
1027
  const data = await response.json();
841
1028
  const conversations = data.result?.conversations || [];
842
1029
  const color = this.getThemeColor();
843
-
844
1030
  container.innerHTML = '';
845
- container.style.background = 'white';
846
- container.style.padding = '0';
847
-
848
- // Header greeting
849
- const header_el = document.createElement('div');
850
- header_el.style.cssText = 'padding:16px;border-bottom:1px solid #f0f0f0;';
851
- header_el.innerHTML = `
852
- <div style="font-size:14px;font-weight:700;color:#1a1a2e;margin-bottom:2px;">${m.hello} ${this.user_info?.first_name || ''} 👋</div>
853
- <div style="font-size:12px;color:#aaa;">${m.recent_conv}</div>
854
- `;
855
- container.appendChild(header_el);
856
-
857
- if (conversations.length === 0) {
858
- const empty = document.createElement('div');
859
- empty.style.cssText = 'padding:32px 16px;text-align:center;color:#bbb;font-size:13px;';
860
- empty.innerHTML = `<div style="margin-bottom:8px;opacity:0.4;">${icon('msg', 32)}</div>${m.no_conv}`;
861
- container.appendChild(empty);
862
- } else {
863
- conversations.forEach(conv => {
864
- const item = document.createElement('div');
865
- item.className = 'tp-conv-item';
866
- item.style.cssText =
867
- 'padding:14px 16px;border-bottom:1px solid #f5f5f5;cursor:pointer;transition:background 0.15s;display:flex;justify-content:space-between;align-items:center;';
868
-
869
- const is_closed = conv.status === 'closed';
1031
+ if (this.active_tab === 'new') {
1032
+ const greeting = document.createElement('div');
1033
+ greeting.style.cssText = 'padding:20px 16px 8px;';
1034
+ greeting.innerHTML = `<div style="background:white;border-radius:16px;padding:16px;box-shadow:0 1px 8px rgba(0,0,0,0.07);margin-bottom:14px;"><div style="font-size:14px;font-weight:700;color:#1a1a2e;margin-bottom:4px;">${m.hello} ${this.user_info?.first_name || ''} 👋</div><div style="font-size:12px;color:#aaa;line-height:1.5;">${m.recent_conv}</div></div>`;
1035
+ container.appendChild(greeting);
1036
+ if (conversations.length === 0) {
1037
+ const empty = document.createElement('div');
1038
+ empty.style.cssText =
1039
+ 'padding:20px 16px;text-align:center;color:#ccc;font-size:13px;display:flex;flex-direction:column;align-items:center;gap:10px;';
1040
+ empty.innerHTML = `<div style="opacity:0.35;">${icon('msg', 36)}</div>${m.no_conv}`;
1041
+ container.appendChild(empty);
1042
+ } else {
1043
+ const last_conv = conversations[0];
1044
+ const is_closed = last_conv.status === 'closed';
1045
+ const badge_color = last_conv.status === 'waiting_agent' ? '#f59e0b' : color;
870
1046
  const status_label = is_closed
871
1047
  ? m.status_closed
872
- : conv.status === 'agent'
1048
+ : last_conv.status === 'agent'
873
1049
  ? m.status_agent
874
- : conv.status === 'waiting_agent'
1050
+ : last_conv.status === 'waiting_agent'
875
1051
  ? m.status_waiting
876
1052
  : m.status_bot;
877
-
878
- const badge_color = is_closed ? color : conv.status === 'waiting_agent' ? '#f59e0b' : color;
879
- const date = new Date(conv.updated_at).toLocaleDateString('fr-FR', {
1053
+ const date = new Date(last_conv.updated_at).toLocaleDateString('fr-FR', {
880
1054
  day: '2-digit',
881
1055
  month: '2-digit',
882
1056
  hour: '2-digit',
883
1057
  minute: '2-digit',
884
1058
  });
885
-
886
- item.innerHTML = `
887
- <div>
888
- <div style="font-size:13px;font-weight:600;color:${is_closed ? '#888' : '#1a1a2e'};margin-bottom:3px;">${date}</div>
889
- <div style="font-size:11px;color:#bbb;">${conv.message_count} ${m.messages}${conv.message_count > 1 ? 's' : ''}</div>
890
- </div>
891
- <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};">
892
- ${is_closed ? icon('check', 11) : ''} ${status_label}
893
- </span>
894
- `;
895
- item.addEventListener('click', () => this.openConversation(conv.conversation_id, is_closed));
1059
+ const item = document.createElement('div');
1060
+ item.style.cssText =
1061
+ 'margin:0 16px 14px;background:white;border-radius:14px;padding:14px 16px;box-shadow:0 1px 6px rgba(0,0,0,0.07);cursor:pointer;transition:box-shadow 0.15s;display:flex;justify-content:space-between;align-items:center;';
1062
+ item.innerHTML = `<div><div style="font-size:12px;font-weight:600;color:${is_closed ? '#aaa' : '#1a1a2e'};margin-bottom:3px;">${date}</div><div style="font-size:11px;color:#ccc;">${last_conv.message_count} ${m.messages}${last_conv.message_count > 1 ? 's' : ''}</div></div><span style="font-size:10px;font-weight:700;padding:3px 10px;border-radius:20px;background:${badge_color}18;color:${badge_color};">${status_label}</span>`;
1063
+ item.addEventListener('mouseenter', () => {
1064
+ item.style.boxShadow = '0 3px 12px rgba(0,0,0,0.12)';
1065
+ });
1066
+ item.addEventListener('mouseleave', () => {
1067
+ item.style.boxShadow = '0 1px 6px rgba(0,0,0,0.07)';
1068
+ });
1069
+ item.addEventListener('click', () => this.openConversation(last_conv.conversation_id, is_closed));
896
1070
  container.appendChild(item);
897
- });
1071
+ }
1072
+ const btn_wrap = document.createElement('div');
1073
+ btn_wrap.style.cssText = 'padding:4px 16px 16px;';
1074
+ btn_wrap.innerHTML = `<button class="tp-new-conv-btn">${icon('msg_plus', 15, 'color:white')} ${m.new_conv_btn}</button>`;
1075
+ btn_wrap.querySelector('button').addEventListener('click', () => this.startNewConversation());
1076
+ container.appendChild(btn_wrap);
1077
+ } else {
1078
+ const greeting = document.createElement('div');
1079
+ greeting.style.cssText = 'padding:16px 16px 8px;';
1080
+ greeting.innerHTML = `<div style="font-size:12px;font-weight:600;color:#aaa;text-transform:uppercase;letter-spacing:0.5px;">${m.recent_conv}</div>`;
1081
+ container.appendChild(greeting);
1082
+ if (conversations.length === 0) {
1083
+ const empty = document.createElement('div');
1084
+ empty.style.cssText =
1085
+ 'padding:32px 16px;text-align:center;color:#ccc;font-size:13px;display:flex;flex-direction:column;align-items:center;gap:10px;';
1086
+ empty.innerHTML = `<div style="opacity:0.35;">${icon('history', 36)}</div>${m.no_conv}`;
1087
+ container.appendChild(empty);
1088
+ } else {
1089
+ conversations.forEach(conv => {
1090
+ const is_closed = conv.status === 'closed';
1091
+ const badge_color = conv.status === 'waiting_agent' ? '#f59e0b' : color;
1092
+ const status_label = is_closed
1093
+ ? m.status_closed
1094
+ : conv.status === 'agent'
1095
+ ? m.status_agent
1096
+ : conv.status === 'waiting_agent'
1097
+ ? m.status_waiting
1098
+ : m.status_bot;
1099
+ const date = new Date(conv.updated_at).toLocaleDateString('fr-FR', {
1100
+ day: '2-digit',
1101
+ month: '2-digit',
1102
+ hour: '2-digit',
1103
+ minute: '2-digit',
1104
+ });
1105
+ const item = document.createElement('div');
1106
+ item.className = 'tp-conv-item';
1107
+ item.innerHTML = `<div class="tp-conv-item-left"><div class="tp-conv-item-date" style="color:${is_closed ? '#aaa' : '#1a1a2e'}">${date}</div><div class="tp-conv-item-count">${conv.message_count} ${m.messages}${conv.message_count > 1 ? 's' : ''}</div></div><span class="tp-badge" style="background:${badge_color}18;color:${badge_color};">${is_closed ? icon('check', 10) : ''} ${status_label}</span>`;
1108
+ item.addEventListener('click', () => this.openConversation(conv.conversation_id, is_closed));
1109
+ container.appendChild(item);
1110
+ });
1111
+ }
898
1112
  }
899
-
900
- // New conversation button
901
- const new_btn_wrap = document.createElement('div');
902
- new_btn_wrap.style.cssText = 'padding:16px;';
903
- new_btn_wrap.innerHTML = `
904
- <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;">
905
- ${icon('msg_plus', 16, 'color:white')} ${m.new_conv_btn}
906
- </button>
907
- `;
908
- new_btn_wrap.querySelector('button').addEventListener('click', () => this.startNewConversation());
909
- container.appendChild(new_btn_wrap);
910
1113
  } catch (e) {
911
1114
  console.error('showConversationList error:', e);
912
- container.innerHTML = `<div style="padding:16px;text-align:center;color:#ef4444;font-size:13px;">${m.error_load}</div>`;
1115
+ container.innerHTML = `<div style="padding:24px 16px;text-align:center;color:#ef4444;font-size:13px;">${this.getMessages().error_load}</div>`;
913
1116
  }
914
1117
  }
915
1118
 
@@ -921,56 +1124,42 @@
921
1124
  this.is_closed = is_closed;
922
1125
  this.agent_mode = false;
923
1126
  this.agent_requested = false;
924
-
925
- const m = this.getMessages();
926
- this.querySelector('#tp-back-btn').style.display = 'block';
1127
+ this.querySelector('#tp-back-btn').style.display = 'flex';
1128
+ this.showTabs(false);
927
1129
  const container = this.querySelector('#tp-messages');
928
- container.style.background = '#f5f5f7';
929
- container.style.padding = '16px';
930
1130
  container.innerHTML = '';
931
-
932
- const input_bar = this.querySelector('#tp-chatbot-input-bar');
933
- const agent_bar = this.querySelector('#tp-agent-bar');
934
- const close_bar = this.querySelector('#tp-close-bar');
935
-
936
1131
  try {
937
1132
  const url = `${this.api_url}/chat/conversations/${conversation_id}?user_id=${encodeURIComponent(this.user_id)}`;
938
1133
  const response = await fetch(url, { headers: this.getHeaders() });
939
1134
  const data = await response.json();
940
1135
  const conv = data.result?.conversation;
941
1136
  if (!conv) return;
942
-
943
1137
  conv.messages.forEach(msg => {
944
- if (['user', 'assistant', 'agent', 'system'].includes(msg.role)) {
945
- this.addMessage(msg.role, msg.content, msg.message_id || null);
946
- }
1138
+ if (['user', 'assistant', 'agent', 'system'].includes(msg.role)) this.addMessage(msg.role, msg.content, msg.message_id || null);
947
1139
  });
948
1140
  this.last_message_count = conv.messages.length;
949
-
950
1141
  if (conv.status === 'closed') {
951
1142
  this.showClosed(conv.csat || null);
952
1143
  return;
953
1144
  }
954
-
1145
+ const input_bar = this.querySelector('#tp-input-bar'),
1146
+ agent_bar = this.querySelector('#tp-agent-bar'),
1147
+ close_bar = this.querySelector('#tp-close-bar');
955
1148
  if (conv.status === 'bot' || conv.status === 'waiting_agent') {
956
1149
  if (close_bar) close_bar.style.display = 'block';
957
1150
  } else {
958
1151
  if (close_bar) close_bar.style.display = 'none';
959
1152
  }
960
-
961
1153
  if (conv.status === 'agent') {
962
1154
  this.agent_mode = true;
963
- const subtitle = this.querySelector('#tp-subtitle');
964
- if (subtitle) subtitle.innerHTML = `${icon('user', 12, 'margin-right:4px')} ${m.human_agent}`;
1155
+ this._updateSubtitle('agent');
965
1156
  if (agent_bar) agent_bar.style.display = 'none';
966
1157
  if (input_bar) input_bar.style.display = 'flex';
967
1158
  } else {
968
1159
  if (input_bar) input_bar.style.display = 'flex';
969
1160
  if (agent_bar) agent_bar.style.display = 'none';
970
- const subtitle = this.querySelector('#tp-subtitle');
971
- if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
1161
+ this._updateSubtitle('bot');
972
1162
  }
973
-
974
1163
  this.updateUILanguage();
975
1164
  this.polling_interval = setInterval(() => this.pollMessages(), 3000);
976
1165
  } catch (e) {
@@ -978,6 +1167,7 @@
978
1167
  }
979
1168
  }
980
1169
 
1170
+ // ─── FIXED: conv créée au premier sendMessage, pas à l'ouverture ──────────
981
1171
  async startNewConversation() {
982
1172
  this.view = 'chat';
983
1173
  this.is_closed = false;
@@ -985,71 +1175,78 @@
985
1175
  this.agent_requested = false;
986
1176
  this.messages = [];
987
1177
  this.last_message_count = 0;
988
- this.conversation_id = null;
989
-
990
- const m = this.getMessages();
991
- this.querySelector('#tp-back-btn').style.display = 'block';
992
- this.querySelector('#tp-close-bar').style.display = 'block';
1178
+ this.conversation_id = null; // pas encore créée
993
1179
 
1180
+ this.querySelector('#tp-back-btn').style.display = 'flex';
1181
+ this.querySelector('#tp-close-bar').style.display = 'none'; // cachée tant que pas de conv
1182
+ this.showTabs(false);
994
1183
  const container = this.querySelector('#tp-messages');
995
- container.style.background = '#f5f5f7';
996
- container.style.padding = '16px';
997
1184
  container.innerHTML = '';
998
-
999
- const subtitle = this.querySelector('#tp-subtitle');
1000
- if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
1001
-
1002
- this.querySelector('#tp-chatbot-input-bar').style.display = 'flex';
1185
+ this._updateSubtitle('bot');
1186
+ this.querySelector('#tp-input-bar').style.display = 'flex';
1003
1187
  this.querySelector('#tp-agent-bar').style.display = 'none';
1004
1188
  this.updateUILanguage();
1005
-
1006
- this.conversation_id = await this.createConversation();
1007
1189
  this.addMessage('assistant', this.getWelcomeMessage(this.user_info?.first_name || ''));
1008
- this.last_message_count = 1;
1009
-
1010
1190
  const theme = getClientTheme(this.client_id);
1011
1191
  if (theme.suggestions && theme.suggestions.length > 0) this.showSuggestions(theme.suggestions);
1012
-
1013
- this.polling_interval = setInterval(() => this.pollMessages(), 3000);
1192
+ // pas de polling — rien à poller tant que la conv n'est pas créée
1014
1193
  }
1015
1194
 
1016
- // ─── Guest form ────────────────────────────────────────────────────────────
1195
+ _updateSubtitle(mode) {
1196
+ const m = this.getMessages(),
1197
+ subtitle = this.querySelector('#tp-subtitle');
1198
+ if (!subtitle) return;
1199
+ if (mode === 'agent') subtitle.innerHTML = `${icon('user', 11)} ${m.human_agent}`;
1200
+ else if (mode === 'closed') subtitle.innerHTML = `${icon('check', 11)} ${m.terminated}`;
1201
+ else subtitle.innerHTML = `${icon('bot', 11)} ${m.virtual}`;
1202
+ }
1017
1203
 
1018
1204
  showGuestForm() {
1019
1205
  const container = this.querySelector('#tp-messages');
1020
1206
  if (!container) return;
1021
- this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
1207
+ this.querySelector('#tp-input-bar').style.display = 'none';
1022
1208
  this.querySelector('#tp-agent-bar').style.display = 'none';
1023
- const color = this.getThemeColor();
1024
- const dark = this.shadeColor(color, -20);
1025
- const form_el = document.createElement('div');
1026
- form_el.id = 'tp-guest-form';
1027
- form_el.innerHTML = `
1028
- <div style="padding:16px;display:flex;flex-direction:column;gap:12px;">
1029
- <p style="margin:0;font-size:13px;color:#1a1a2e;font-weight:600;">Before we start, please identify yourself:</p>
1030
- <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;" />
1031
- <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;" />
1032
- <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;" />
1033
- <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>
1034
- <p id="tp-guest-error" style="margin:0;font-size:12px;color:#c0392b;display:none;"></p>
1209
+ this.showTabs(false);
1210
+ const m = this.getMessages();
1211
+ container.innerHTML = '';
1212
+ const wrap = document.createElement('div');
1213
+ wrap.id = 'tp-guest-wrap';
1214
+ wrap.className = 'tp-guest-wrap';
1215
+ wrap.innerHTML = `
1216
+ <div class="tp-guest-intro">
1217
+ <div class="tp-guest-intro-name">${m.guest_title}</div>
1218
+ <div class="tp-guest-intro-sub">${m.guest_sub}</div>
1035
1219
  </div>
1220
+ <div><label class="tp-guest-field-label">${m.guest_firstname}</label><input id="tp-guest-firstname" type="text" class="tp-guest-input" placeholder="Jean" /></div>
1221
+ <div><label class="tp-guest-field-label">${m.guest_company}</label><input id="tp-guest-company" type="text" class="tp-guest-input" placeholder="Acme Corp" /></div>
1222
+ <div><label class="tp-guest-field-label">${m.guest_email}</label><input id="tp-guest-email" type="email" class="tp-guest-input" placeholder="jean@acme.com" /></div>
1223
+ <p id="tp-guest-error" class="tp-guest-error"></p>
1224
+ <button id="tp-guest-submit" class="tp-guest-submit">${icon('msg_plus', 15, 'color:white')} ${m.guest_submit}</button>
1225
+ <div class="tp-guest-footer">${icon('lock', 11)} ${m.guest_secured}</div>
1036
1226
  `;
1037
- container.appendChild(form_el);
1227
+ container.appendChild(wrap);
1038
1228
  this.querySelector('#tp-guest-submit').addEventListener('click', () => this.submitGuestForm());
1229
+ ['#tp-guest-firstname', '#tp-guest-company', '#tp-guest-email'].forEach(sel => {
1230
+ this.querySelector(sel)?.addEventListener('keydown', e => {
1231
+ if (e.key === 'Enter') this.submitGuestForm();
1232
+ });
1233
+ });
1039
1234
  }
1040
1235
 
1236
+ // ─── FIXED: conv créée au premier sendMessage, pas après le formulaire ────
1041
1237
  async submitGuestForm() {
1042
1238
  const first_name = this.querySelector('#tp-guest-firstname')?.value.trim();
1043
1239
  const company_name = this.querySelector('#tp-guest-company')?.value.trim();
1044
1240
  const email = this.querySelector('#tp-guest-email')?.value.trim();
1045
1241
  const error_el = this.querySelector('#tp-guest-error');
1242
+ const m = this.getMessages();
1046
1243
  if (!first_name || !company_name || !email) {
1047
- error_el.textContent = 'Please fill in all fields.';
1244
+ error_el.textContent = m.guest_fill;
1048
1245
  error_el.style.display = 'block';
1049
1246
  return;
1050
1247
  }
1051
1248
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
1052
- error_el.textContent = 'Please enter a valid email.';
1249
+ error_el.textContent = m.guest_email_invalid;
1053
1250
  error_el.style.display = 'block';
1054
1251
  return;
1055
1252
  }
@@ -1057,66 +1254,49 @@
1057
1254
  this.user_info = { user_id: `guest-${email}`, first_name, last_name: '', company_name, email, language: 'EN', site_id: '' };
1058
1255
  this.user_id = this.user_info.user_id;
1059
1256
 
1060
- const form_el = this.querySelector('#tp-guest-form');
1061
- if (form_el) form_el.remove();
1257
+ const wrap = this.querySelector('#tp-guest-wrap');
1258
+ if (wrap) wrap.remove();
1062
1259
 
1063
1260
  this.view = 'chat';
1064
- this.conversation_id = await this.createConversation();
1065
-
1066
- this.querySelector('#tp-chatbot-input-bar').style.display = 'flex';
1261
+ this.conversation_id = null; // pas encore créée — sera créée au premier sendMessage
1262
+ this.querySelector('#tp-input-bar').style.display = 'flex';
1067
1263
  this.querySelector('#tp-agent-bar').style.display = 'none';
1068
- this.querySelector('#tp-close-bar').style.display = 'block';
1069
-
1070
- const container = this.querySelector('#tp-messages');
1071
- container.style.background = '#f5f5f7';
1072
- container.style.padding = '16px';
1073
-
1264
+ this.querySelector('#tp-close-bar').style.display = 'none'; // cachée tant que pas de conv
1265
+ this.querySelector('#tp-back-btn').style.display = 'flex';
1266
+ this.showTabs(false);
1074
1267
  this.updateUILanguage();
1075
1268
  this.addMessage('assistant', this.getWelcomeMessage(first_name));
1076
- this.last_message_count = 1;
1077
-
1078
1269
  const theme = getClientTheme(this.client_id);
1079
1270
  if (theme.suggestions && theme.suggestions.length > 0) this.showSuggestions(theme.suggestions);
1080
-
1081
- this.polling_interval = setInterval(() => this.pollMessages(), 3000);
1271
+ // pas de polling — rien à poller tant que la conv n'est pas créée
1082
1272
  }
1083
1273
 
1084
- // ─── Messages ──────────────────────────────────────────────────────────────
1085
-
1086
- addMessage(role, content, message_id = null) {
1087
- this.messages.push({ role, content, message_id, created_at: new Date().toISOString() });
1274
+ addMessage(role, content, message_id = null, source = null) {
1275
+ this.messages.push({ role, content, message_id, source, created_at: new Date().toISOString() });
1088
1276
  const container = this.querySelector('#tp-messages');
1089
-
1277
+ const m = this.getMessages();
1090
1278
  const role_label =
1091
1279
  role === 'user'
1092
- ? `${icon('user', 11, 'margin-right:3px')} ${this.user_info?.first_name || 'You'}`
1280
+ ? `${icon('user', 11)} ${this.user_info?.first_name || 'You'}`
1093
1281
  : role === 'agent'
1094
- ? `${icon('agent', 11, 'margin-right:3px')} Agent`
1282
+ ? `${icon('agent', 11)} Agent`
1095
1283
  : role === 'system'
1096
1284
  ? 'Info'
1097
- : `${icon('bot', 11, 'margin-right:3px')} Maria`;
1098
-
1285
+ : `${icon('bot', 11)} Maria`;
1099
1286
  const msg_el = document.createElement('div');
1100
- msg_el.className = `tp-chatbot-message ${role}`;
1287
+ msg_el.className = `tp-msg ${role}`;
1101
1288
  const rendered_content =
1102
1289
  role === 'assistant' || role === 'agent' ? (typeof marked !== 'undefined' ? marked.parse(content) : content) : content;
1103
-
1290
+ const now = formatTime(new Date().toISOString());
1104
1291
  const feedback_html =
1105
1292
  role === 'assistant' && message_id
1106
- ? `<div class="tp-feedback-bar" data-message-id="${message_id}">
1107
- <button class="tp-feedback-btn" data-rating="positive" title="Helpful">${icon('helpful', 14)}</button>
1108
- <button class="tp-feedback-btn" data-rating="negative" title="Not helpful">${icon('not_helpful', 14)}</button>
1109
- </div>`
1293
+ ? `<div class="tp-feedback-bar" data-message-id="${message_id}"><button class="tp-feedback-btn" data-rating="positive" title="${m.helpful}">${icon('helpful', 13)} <span style="font-size:11px;">${m.helpful}</span></button><button class="tp-feedback-btn" data-rating="negative" title="${m.not_helpful}">${icon('not_helpful', 13)} <span style="font-size:11px;">${m.not_helpful}</span></button></div>`
1110
1294
  : '';
1111
-
1112
- msg_el.innerHTML = `
1113
- <div class="tp-chatbot-bubble-msg">
1114
- <div class="tp-chatbot-role">${role_label}</div>
1115
- <div class="tp-chatbot-content tp-chatbot-markdown">${rendered_content}</div>
1116
- ${feedback_html}
1117
- </div>
1118
- `;
1119
-
1295
+ if (role === 'system') {
1296
+ msg_el.innerHTML = `<div class="tp-bubble-msg">${rendered_content}</div>`;
1297
+ } else {
1298
+ msg_el.innerHTML = `<div class="tp-msg-role">${role_label}</div><div class="tp-bubble-msg${role === 'assistant' || role === 'agent' ? ' tp-md' : ''}">${rendered_content}${feedback_html}</div><div class="tp-msg-time">${role === 'user' ? `${this.user_info?.first_name || 'You'} · ${now}` : `Maria · ${now}`}</div>`;
1299
+ }
1120
1300
  if (role === 'assistant' && message_id) {
1121
1301
  const bar = msg_el.querySelector('.tp-feedback-bar');
1122
1302
  bar.querySelectorAll('.tp-feedback-btn').forEach(btn => {
@@ -1125,9 +1305,11 @@
1125
1305
  bar.dataset.voted = '1';
1126
1306
  const rating = btn.dataset.rating;
1127
1307
  bar.querySelectorAll('.tp-feedback-btn').forEach(b => {
1128
- b.style.opacity = b === btn ? '1' : '0.3';
1308
+ b.classList.remove('voted-positive', 'voted-negative');
1309
+ b.style.opacity = b === btn ? '1' : '0.35';
1129
1310
  b.style.cursor = 'default';
1130
1311
  });
1312
+ btn.classList.add(rating === 'positive' ? 'voted-positive' : 'voted-negative');
1131
1313
  try {
1132
1314
  await fetch(`${this.api_url}/chat/conversations/${this.conversation_id}/feedback`, {
1133
1315
  method: 'POST',
@@ -1138,19 +1320,17 @@
1138
1320
  });
1139
1321
  });
1140
1322
  }
1141
-
1142
1323
  container.appendChild(msg_el);
1143
1324
  container.scrollTop = container.scrollHeight;
1144
-
1145
1325
  if ((role === 'assistant' || role === 'agent') && this.sound_enabled) playSound();
1146
1326
  }
1147
1327
 
1148
1328
  showTyping() {
1149
1329
  const container = this.querySelector('#tp-messages');
1150
1330
  const typing = document.createElement('div');
1151
- typing.className = 'tp-chatbot-message assistant';
1331
+ typing.className = 'tp-msg assistant';
1152
1332
  typing.id = 'tp-typing';
1153
- typing.innerHTML = `<div class="tp-chatbot-bubble-msg"><div class="tp-chatbot-typing"><span></span><span></span><span></span></div></div>`;
1333
+ typing.innerHTML = `<div class="tp-msg-role">${icon('bot', 11)} Maria</div><div class="tp-bubble-msg"><div class="tp-typing"><span></span><span></span><span></span></div></div>`;
1154
1334
  container.appendChild(typing);
1155
1335
  container.scrollTop = container.scrollHeight;
1156
1336
  }
@@ -1166,56 +1346,33 @@
1166
1346
  clearInterval(this.polling_interval);
1167
1347
  this.polling_interval = null;
1168
1348
  }
1169
-
1170
1349
  const m = this.getMessages();
1171
- const subtitle = this.querySelector('#tp-subtitle');
1172
- if (subtitle) subtitle.innerHTML = `${icon('check', 12, 'margin-right:4px')} ${m.terminated}`;
1173
-
1174
- this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
1350
+ this._updateSubtitle('closed');
1351
+ this.querySelector('#tp-input-bar').style.display = 'none';
1175
1352
  this.querySelector('#tp-agent-bar').style.display = 'none';
1176
1353
  this.querySelector('#tp-close-bar').style.display = 'none';
1177
1354
  const sugg_el = this.querySelector('#tp-suggestions');
1178
1355
  if (sugg_el) sugg_el.remove();
1179
-
1180
1356
  const container = this.querySelector('#tp-messages');
1181
- const existing_banner = this.querySelector('#tp-closed-banner');
1357
+ const existing_banner = container.querySelector('#tp-closed-banner');
1182
1358
  if (existing_banner) {
1183
1359
  if (existing_banner.querySelector('#tp-csat-block')) return;
1184
1360
  existing_banner.remove();
1185
1361
  }
1186
-
1187
- const color = this.getThemeColor();
1188
- const dark = this.shadeColor(color, -20);
1189
-
1362
+ const now = new Date().toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
1190
1363
  const csat_html = existing_csat
1191
- ? `<div style="font-size:13px;color:#888;margin-bottom:14px;">${existing_csat === 'positive' ? m.csat_positive : m.csat_negative}</div>`
1192
- : `<div style="font-size:12px;color:#888;margin-bottom:10px;">${m.csat_question}</div>
1193
- <div style="display:flex;gap:10px;justify-content:center;margin-bottom:14px;">
1194
- <button id="tp-csat-positive" class="tp-csat-btn" style="border-color:#22c55e;color:#22c55e;">${icon('helpful', 20)}</button>
1195
- <button id="tp-csat-negative" class="tp-csat-btn" style="border-color:#ef4444;color:#ef4444;">${icon('not_helpful', 20)}</button>
1196
- </div>`;
1197
-
1364
+ ? `<div class="tp-csat-thanks">${existing_csat === 'positive' ? m.csat_positive : m.csat_negative}</div>`
1365
+ : `<div class="tp-csat-q">${m.csat_question}</div><div class="tp-csat-btns"><button id="tp-csat-positive" class="tp-csat-btn positive">${icon('helpful', 16)} ${m.helpful}</button><button id="tp-csat-negative" class="tp-csat-btn negative">${icon('not_helpful', 16)} ${m.not_helpful}</button></div>`;
1198
1366
  const banner = document.createElement('div');
1199
1367
  banner.id = 'tp-closed-banner';
1200
- banner.innerHTML = `
1201
- <div style="margin:16px;padding:16px;background:#f9f9f9;border:1px solid #ede8f5;border-radius:12px;text-align:center;">
1202
- <div style="display:inline-flex;align-items:center;gap:6px;font-size:13px;color:#1a1a2e;font-weight:600;margin-bottom:10px;">
1203
- ${icon('check', 14)} ${m.closed}
1204
- </div>
1205
- <div id="tp-csat-block">${csat_html}</div>
1206
- <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;">
1207
- ${icon('msg_plus', 15, 'color:white')} ${m.new_conv}
1208
- </button>
1209
- <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>
1210
- </div>
1211
- `;
1368
+ banner.className = 'tp-closed-banner';
1369
+ banner.innerHTML = `<div class="tp-closed-title">${icon('check', 14)} ${m.closed}</div><div class="tp-closed-date">${now}</div><div id="tp-csat-block">${csat_html}</div><button class="tp-closed-new-btn" id="tp-closed-new-btn">${icon('msg_plus', 14, 'color:white')} ${m.new_conv}</button><button class="tp-closed-hist-btn" id="tp-closed-hist-btn">${m.back_to_list}</button>`;
1212
1370
  container.appendChild(banner);
1213
1371
  container.scrollTop = container.scrollHeight;
1214
-
1215
1372
  if (!existing_csat) {
1216
1373
  const submit_csat = async rating => {
1217
- const csat_block = this.querySelector('#tp-csat-block');
1218
- if (!csat_block) return;
1374
+ const block = this.querySelector('#tp-csat-block');
1375
+ if (!block) return;
1219
1376
  try {
1220
1377
  await fetch(`${this.api_url}/chat/conversations/${this.conversation_id}/csat`, {
1221
1378
  method: 'POST',
@@ -1225,20 +1382,21 @@
1225
1382
  } catch (e) {
1226
1383
  console.error('CSAT error:', e);
1227
1384
  }
1228
- csat_block.innerHTML = `<div style="font-size:13px;color:#888;margin-bottom:14px;">${rating === 'positive' ? m.csat_positive : m.csat_negative}</div>`;
1385
+ block.innerHTML = `<div class="tp-csat-thanks">${rating === 'positive' ? m.csat_positive : m.csat_negative}</div>`;
1229
1386
  };
1230
- this.querySelector('#tp-csat-positive').addEventListener('click', () => submit_csat('positive'));
1231
- this.querySelector('#tp-csat-negative').addEventListener('click', () => submit_csat('negative'));
1387
+ this.querySelector('#tp-csat-positive')?.addEventListener('click', () => submit_csat('positive'));
1388
+ this.querySelector('#tp-csat-negative')?.addEventListener('click', () => submit_csat('negative'));
1232
1389
  }
1233
-
1234
- this.querySelector('#tp-new-conversation').addEventListener('click', () => this.startNewConversation());
1235
- this.querySelector('#tp-back-to-list').addEventListener('click', () => this.showConversationList());
1390
+ this.querySelector('#tp-closed-new-btn').addEventListener('click', () => this.startNewConversation());
1391
+ this.querySelector('#tp-closed-hist-btn').addEventListener('click', () => {
1392
+ this.setTab('history');
1393
+ this.showConversationList();
1394
+ });
1236
1395
  }
1237
1396
 
1238
- // ─── Send / Poll ───────────────────────────────────────────────────────────
1239
-
1397
+ // ─── FIXED: createConversation lazy au premier sendMessage ────────────────
1240
1398
  async sendMessage() {
1241
- if (this.is_closed || !this.conversation_id) return;
1399
+ if (this.is_closed) return;
1242
1400
  const m = this.getMessages();
1243
1401
  const input = this.querySelector('#tp-input');
1244
1402
  const query = input.value.trim();
@@ -1250,10 +1408,22 @@
1250
1408
  const user_messages = this.messages.filter(msg => msg.role === 'user').length;
1251
1409
  if (user_messages >= 30) {
1252
1410
  this.addMessage('system', m.limit_reached);
1253
- this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
1411
+ this.querySelector('#tp-input-bar').style.display = 'none';
1254
1412
  return;
1255
1413
  }
1256
1414
 
1415
+ // Créer la conv au premier message si pas encore créée
1416
+ if (!this.conversation_id) {
1417
+ try {
1418
+ this.conversation_id = await this.createConversation();
1419
+ this.querySelector('#tp-close-bar').style.display = 'block';
1420
+ this.polling_interval = setInterval(() => this.pollMessages(), 3000);
1421
+ } catch (e) {
1422
+ this.addMessage('system', m.error_occurred);
1423
+ return;
1424
+ }
1425
+ }
1426
+
1257
1427
  input.value = '';
1258
1428
  this.is_loading = true;
1259
1429
  this.addMessage('user', query);
@@ -1276,14 +1446,10 @@
1276
1446
 
1277
1447
  this.agent_mode = data.result.agent_mode;
1278
1448
  const source = data.result.source || null;
1279
- const subtitle = this.querySelector('#tp-subtitle');
1280
- if (subtitle)
1281
- subtitle.innerHTML = this.agent_mode
1282
- ? `${icon('user', 12, 'margin-right:4px')} ${m.human_agent}`
1283
- : `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
1449
+ this._updateSubtitle(this.agent_mode ? 'agent' : 'bot');
1284
1450
 
1285
1451
  if (data.result.reply) {
1286
- this.addMessage(this.agent_mode ? 'agent' : 'assistant', data.result.reply, data.result.message_id || null);
1452
+ this.addMessage(this.agent_mode ? 'agent' : 'assistant', data.result.reply, data.result.message_id || null, source);
1287
1453
  this.last_message_count += 2;
1288
1454
  }
1289
1455
 
@@ -1292,18 +1458,11 @@
1292
1458
  const suggest_agent = data.result.suggest_agent || false;
1293
1459
 
1294
1460
  if (agent_bar && !this.agent_requested && !this.agent_mode) {
1295
- if (
1296
- suggest_agent ||
1297
- source === 'fallback' ||
1298
- source === 'clarification' ||
1299
- source === 'no_match' ||
1300
- source === 'out_of_scope' ||
1301
- user_msg_count >= 5
1302
- ) {
1303
- agent_bar.style.display = 'block';
1304
- }
1461
+ const semantic_trigger = source === 'no_match' || source === 'out_of_scope';
1462
+ const clarif_count = this.messages.filter(msg => msg.role === 'assistant' && msg.source === 'clarification').length;
1463
+ const repeated_clarif = source === 'clarification' && clarif_count >= 3;
1464
+ if (semantic_trigger || repeated_clarif || (suggest_agent && user_msg_count >= 5)) agent_bar.style.display = 'block';
1305
1465
  }
1306
-
1307
1466
  if (suggest_agent && !this.agent_requested) {
1308
1467
  this.addMessage('system', m.suggest_agent_escalation);
1309
1468
  this.last_message_count += 1;
@@ -1312,7 +1471,6 @@
1312
1471
  this.hideTyping();
1313
1472
  this.addMessage('assistant', this.getMessages().error_occurred);
1314
1473
  }
1315
-
1316
1474
  this.is_loading = false;
1317
1475
  }
1318
1476
 
@@ -1321,7 +1479,6 @@
1321
1479
  this.agent_requested = true;
1322
1480
  const agent_bar = this.querySelector('#tp-agent-bar');
1323
1481
  if (agent_bar) agent_bar.style.display = 'none';
1324
-
1325
1482
  try {
1326
1483
  const response = await fetch(`${this.api_url}/chat/conversations/${this.conversation_id}/request-agent`, {
1327
1484
  method: 'POST',
@@ -1329,21 +1486,18 @@
1329
1486
  body: JSON.stringify({ user_id: this.user_id }),
1330
1487
  });
1331
1488
  const data = await response.json();
1332
- const m = this.getMessages();
1333
- const status = data.result?.status;
1334
-
1489
+ const m = this.getMessages(),
1490
+ status = data.result?.status;
1335
1491
  let msg = '';
1336
1492
  if (status === 'outside_hours') msg = m.outside_hours(data.result.next_opening);
1337
1493
  else if (status === 'no_agents') msg = m.no_agents;
1338
1494
  else if (status === 'waiting_agent') msg = m.waiting_agent;
1339
1495
  else if (status === 'waiting_agent_already') msg = m.waiting_agent_already;
1340
1496
  else if (status === 'agent') msg = m.agent_already;
1341
-
1342
1497
  if (msg) {
1343
1498
  this.addMessage('system', msg);
1344
1499
  this.last_message_count += 1;
1345
1500
  }
1346
-
1347
1501
  if (status === 'no_agents' || status === 'outside_hours' || status === 'waiting_agent_already') {
1348
1502
  this.agent_requested = false;
1349
1503
  if (agent_bar) agent_bar.style.display = 'block';
@@ -1378,24 +1532,21 @@
1378
1532
  const data = await response.json();
1379
1533
  const conv = data.result?.conversation;
1380
1534
  if (!conv) return;
1381
-
1382
- const server_messages = conv.messages || [];
1383
- const server_status = conv.status;
1384
- const is_typing = conv.is_typing || false;
1535
+ const server_messages = conv.messages || [],
1536
+ server_status = conv.status,
1537
+ is_typing = conv.is_typing || false;
1385
1538
  const m = this.getMessages();
1386
-
1387
1539
  if (server_status === 'closed' && !this.is_closed) {
1388
1540
  this.showClosed();
1389
1541
  return;
1390
1542
  }
1391
-
1392
1543
  if (is_typing && server_status === 'agent') {
1393
1544
  if (!this.querySelector('#tp-agent-typing')) {
1394
1545
  const container = this.querySelector('#tp-messages');
1395
1546
  const typing = document.createElement('div');
1396
- typing.className = 'tp-chatbot-message agent';
1547
+ typing.className = 'tp-msg agent';
1397
1548
  typing.id = 'tp-agent-typing';
1398
- 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>`;
1549
+ typing.innerHTML = `<div class="tp-msg-role">${icon('agent', 11)} Agent</div><div class="tp-bubble-msg"><div class="tp-typing"><span></span><span></span><span></span></div></div>`;
1399
1550
  container.appendChild(typing);
1400
1551
  container.scrollTop = container.scrollHeight;
1401
1552
  }
@@ -1403,13 +1554,12 @@
1403
1554
  const typing_el = this.querySelector('#tp-agent-typing');
1404
1555
  if (typing_el) typing_el.remove();
1405
1556
  }
1406
-
1407
1557
  if (server_messages.length > this.last_message_count) {
1408
1558
  const new_messages = server_messages.slice(this.last_message_count);
1409
1559
  new_messages.forEach(msg => {
1410
1560
  if (msg.role === 'agent') {
1411
- const typing_el = this.querySelector('#tp-agent-typing');
1412
- if (typing_el) typing_el.remove();
1561
+ const t = this.querySelector('#tp-agent-typing');
1562
+ if (t) t.remove();
1413
1563
  this.addMessage('agent', msg.content, msg.message_id || null);
1414
1564
  } else if (msg.role === 'assistant') {
1415
1565
  this.addMessage('assistant', msg.content, msg.message_id || null);
@@ -1419,22 +1569,23 @@
1419
1569
  });
1420
1570
  this.last_message_count = server_messages.length;
1421
1571
  }
1422
-
1423
1572
  if (server_status === 'agent' && !this.agent_mode) {
1424
1573
  this.agent_mode = true;
1425
- const subtitle = this.querySelector('#tp-subtitle');
1426
- if (subtitle) subtitle.innerHTML = `${icon('user', 12, 'margin-right:4px')} ${m.human_agent}`;
1574
+ this._updateSubtitle('agent');
1427
1575
  const agent_bar = this.querySelector('#tp-agent-bar');
1428
1576
  if (agent_bar) agent_bar.style.display = 'none';
1577
+ const close_bar = this.querySelector('#tp-close-bar');
1578
+ if (close_bar) close_bar.style.display = 'none';
1429
1579
  this.addMessage('system', m.agent_taken);
1430
1580
  this.last_message_count = server_messages.length;
1431
1581
  } else if (server_status === 'bot' && this.agent_mode) {
1432
1582
  this.agent_mode = false;
1433
1583
  this.agent_requested = false;
1434
- const subtitle = this.querySelector('#tp-subtitle');
1435
- if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
1584
+ this._updateSubtitle('bot');
1436
1585
  const agent_bar = this.querySelector('#tp-agent-bar');
1437
1586
  if (agent_bar) agent_bar.style.display = 'block';
1587
+ const close_bar = this.querySelector('#tp-close-bar');
1588
+ if (close_bar) close_bar.style.display = 'block';
1438
1589
  this.addMessage('system', m.agent_released);
1439
1590
  this.last_message_count = server_messages.length;
1440
1591
  }