@developpement/tp-chatbot-widget 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/chatbot.css +2 -0
- package/src/chatbot.js +534 -385
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
|
-
|
|
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: flex; flex-direction: column; overflow: hidden; opacity: 0; transform: scale(0.92) translateY(12px); pointer-events: none; transition: opacity 0.22s ease, transform 0.22s ease; }
|
|
41
|
+
.tp-chatbot-window.open { opacity: 1; 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 = '
|
|
120
|
-
const WINDOW_HEIGHT = '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
199
|
-
if (
|
|
200
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
370
|
-
agent_released: 'Unser virtueller Assistent ist zurück.
|
|
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.
|
|
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.
|
|
389
|
-
waiting_agent: 'Ihre Anfrage wurde übermittelt.
|
|
390
|
-
waiting_agent_already: 'Sie sind bereits in der Warteschlange.
|
|
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.
|
|
407
|
-
agent_released: 'Nuestro asistente virtual ha vuelto.
|
|
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
|
|
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.
|
|
423
|
-
error_occurred: 'Se produjo un error.
|
|
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.
|
|
426
|
-
waiting_agent: 'Su solicitud ha sido enviada.
|
|
427
|
-
waiting_agent_already: 'Ya está en la cola
|
|
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: '
|
|
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.
|
|
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
|
|
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.
|
|
460
|
-
error_occurred: 'Si è verificato un errore.
|
|
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.
|
|
463
|
-
waiting_agent: 'Richiesta inviata.
|
|
464
|
-
waiting_agent_already: 'È già in coda.
|
|
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: '
|
|
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.
|
|
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
|
|
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.
|
|
497
|
-
error_occurred: 'Er is een fout opgetreden.
|
|
498
|
-
outside_hours: next => `
|
|
499
|
-
no_agents: 'Geen agent beschikbaar.
|
|
500
|
-
waiting_agent: 'Uw verzoek is verzonden.
|
|
501
|
-
waiting_agent_already: 'U staat al in de wachtrij.
|
|
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: '
|
|
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
|
|
551
|
-
.tp-
|
|
552
|
-
.tp-
|
|
553
|
-
.tp-
|
|
554
|
-
.tp-
|
|
555
|
-
.tp-
|
|
556
|
-
.tp-
|
|
557
|
-
.tp-
|
|
558
|
-
.tp-
|
|
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-
|
|
565
|
-
.tp-
|
|
566
|
-
.tp-
|
|
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('
|
|
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-
|
|
598
|
-
<div class="tp-
|
|
599
|
-
|
|
600
|
-
<div class="tp-
|
|
601
|
-
|
|
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-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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-
|
|
615
|
-
<textarea class="tp-
|
|
616
|
-
<button class="tp-
|
|
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="
|
|
619
|
-
<button
|
|
620
|
-
<span style="display:inline-flex;align-items:center;gap:
|
|
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',
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
659
|
-
|
|
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 = '
|
|
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',
|
|
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', () =>
|
|
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',
|
|
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,29 +906,45 @@
|
|
|
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',
|
|
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:
|
|
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
942
|
this.is_open = !this.is_open;
|
|
742
|
-
const window_el = this.querySelector('#tp-window')
|
|
743
|
-
|
|
943
|
+
const window_el = this.querySelector('#tp-window'),
|
|
944
|
+
bubble = this.querySelector('#tp-bubble');
|
|
744
945
|
if (this.is_open) {
|
|
745
946
|
window_el.classList.add('open');
|
|
746
|
-
bubble.innerHTML = icon('close',
|
|
947
|
+
bubble.innerHTML = icon('close', 22, 'color:white');
|
|
747
948
|
if (this.is_minimized) {
|
|
748
949
|
this.is_minimized = false;
|
|
749
950
|
window_el.style.height = WINDOW_HEIGHT;
|
|
@@ -751,7 +952,7 @@
|
|
|
751
952
|
}
|
|
752
953
|
} else {
|
|
753
954
|
window_el.classList.remove('open');
|
|
754
|
-
bubble.innerHTML = icon('msg',
|
|
955
|
+
bubble.innerHTML = icon('msg', 22, 'color:white');
|
|
755
956
|
if (this.is_fullscreen) this.exitFullscreen(true);
|
|
756
957
|
}
|
|
757
958
|
}
|
|
@@ -773,31 +974,20 @@
|
|
|
773
974
|
return data.result?.conversation_id;
|
|
774
975
|
}
|
|
775
976
|
|
|
776
|
-
// ─── Suggestions ──────────────────────────────────────────────────────────
|
|
777
|
-
|
|
778
977
|
showSuggestions(suggestions) {
|
|
779
978
|
const existing = this.querySelector('#tp-suggestions');
|
|
780
979
|
if (existing) existing.remove();
|
|
781
980
|
if (!suggestions || suggestions.length === 0) return;
|
|
782
981
|
const lang = (this.user_info?.language || 'EN').toUpperCase();
|
|
783
|
-
const color = this.getThemeColor();
|
|
784
982
|
const container = this.querySelector('#tp-messages');
|
|
785
983
|
const wrap = document.createElement('div');
|
|
786
984
|
wrap.id = 'tp-suggestions';
|
|
787
|
-
wrap.
|
|
985
|
+
wrap.className = 'tp-suggestions';
|
|
788
986
|
suggestions.forEach(s => {
|
|
789
987
|
const label = s[lang] || s['EN'];
|
|
790
988
|
const btn = document.createElement('button');
|
|
791
|
-
btn.
|
|
989
|
+
btn.className = 'tp-suggestion-btn';
|
|
792
990
|
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
991
|
btn.addEventListener('click', () => {
|
|
802
992
|
const sugg_el = this.querySelector('#tp-suggestions');
|
|
803
993
|
if (sugg_el) sugg_el.remove();
|
|
@@ -813,8 +1003,6 @@
|
|
|
813
1003
|
container.scrollTop = container.scrollHeight;
|
|
814
1004
|
}
|
|
815
1005
|
|
|
816
|
-
// ─── Conversation List ─────────────────────────────────────────────────────
|
|
817
|
-
|
|
818
1006
|
async showConversationList() {
|
|
819
1007
|
this.view = 'list';
|
|
820
1008
|
const m = this.getMessages();
|
|
@@ -822,94 +1010,107 @@
|
|
|
822
1010
|
clearInterval(this.polling_interval);
|
|
823
1011
|
this.polling_interval = null;
|
|
824
1012
|
}
|
|
825
|
-
|
|
826
1013
|
const subtitle = this.querySelector('#tp-subtitle');
|
|
827
|
-
if (subtitle) subtitle.innerHTML = `${icon('bot',
|
|
1014
|
+
if (subtitle) subtitle.innerHTML = `${icon('bot', 11)} ${m.virtual}`;
|
|
828
1015
|
this.querySelector('#tp-back-btn').style.display = 'none';
|
|
829
|
-
this.querySelector('#tp-
|
|
1016
|
+
this.querySelector('#tp-input-bar').style.display = 'none';
|
|
830
1017
|
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
831
1018
|
this.querySelector('#tp-close-bar').style.display = 'none';
|
|
832
|
-
|
|
1019
|
+
this.showTabs(true);
|
|
833
1020
|
const container = this.querySelector('#tp-messages');
|
|
834
|
-
container.
|
|
835
|
-
container.innerHTML = `<div style="padding:16px;text-align:center;color:#aaa;font-size:12px;">${m.loading}</div>`;
|
|
836
|
-
|
|
1021
|
+
container.innerHTML = `<div style="padding:24px 16px;text-align:center;color:#ccc;font-size:12px;">${m.loading}</div>`;
|
|
837
1022
|
try {
|
|
838
1023
|
const url = `${this.api_url}/chat/conversations?user_id=${encodeURIComponent(this.user_id)}&limit=5`;
|
|
839
1024
|
const response = await fetch(url, { headers: this.getHeaders() });
|
|
840
1025
|
const data = await response.json();
|
|
841
1026
|
const conversations = data.result?.conversations || [];
|
|
842
1027
|
const color = this.getThemeColor();
|
|
843
|
-
|
|
844
1028
|
container.innerHTML = '';
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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';
|
|
1029
|
+
if (this.active_tab === 'new') {
|
|
1030
|
+
const greeting = document.createElement('div');
|
|
1031
|
+
greeting.style.cssText = 'padding:20px 16px 8px;';
|
|
1032
|
+
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>`;
|
|
1033
|
+
container.appendChild(greeting);
|
|
1034
|
+
if (conversations.length === 0) {
|
|
1035
|
+
const empty = document.createElement('div');
|
|
1036
|
+
empty.style.cssText =
|
|
1037
|
+
'padding:20px 16px;text-align:center;color:#ccc;font-size:13px;display:flex;flex-direction:column;align-items:center;gap:10px;';
|
|
1038
|
+
empty.innerHTML = `<div style="opacity:0.35;">${icon('msg', 36)}</div>${m.no_conv}`;
|
|
1039
|
+
container.appendChild(empty);
|
|
1040
|
+
} else {
|
|
1041
|
+
const last_conv = conversations[0];
|
|
1042
|
+
const is_closed = last_conv.status === 'closed';
|
|
1043
|
+
const badge_color = last_conv.status === 'waiting_agent' ? '#f59e0b' : color;
|
|
870
1044
|
const status_label = is_closed
|
|
871
1045
|
? m.status_closed
|
|
872
|
-
:
|
|
1046
|
+
: last_conv.status === 'agent'
|
|
873
1047
|
? m.status_agent
|
|
874
|
-
:
|
|
1048
|
+
: last_conv.status === 'waiting_agent'
|
|
875
1049
|
? m.status_waiting
|
|
876
1050
|
: 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', {
|
|
1051
|
+
const date = new Date(last_conv.updated_at).toLocaleDateString('fr-FR', {
|
|
880
1052
|
day: '2-digit',
|
|
881
1053
|
month: '2-digit',
|
|
882
1054
|
hour: '2-digit',
|
|
883
1055
|
minute: '2-digit',
|
|
884
1056
|
});
|
|
885
|
-
|
|
886
|
-
item.
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
item.addEventListener('click', () => this.openConversation(
|
|
1057
|
+
const item = document.createElement('div');
|
|
1058
|
+
item.style.cssText =
|
|
1059
|
+
'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;';
|
|
1060
|
+
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>`;
|
|
1061
|
+
item.addEventListener('mouseenter', () => {
|
|
1062
|
+
item.style.boxShadow = '0 3px 12px rgba(0,0,0,0.12)';
|
|
1063
|
+
});
|
|
1064
|
+
item.addEventListener('mouseleave', () => {
|
|
1065
|
+
item.style.boxShadow = '0 1px 6px rgba(0,0,0,0.07)';
|
|
1066
|
+
});
|
|
1067
|
+
item.addEventListener('click', () => this.openConversation(last_conv.conversation_id, is_closed));
|
|
896
1068
|
container.appendChild(item);
|
|
897
|
-
}
|
|
1069
|
+
}
|
|
1070
|
+
const btn_wrap = document.createElement('div');
|
|
1071
|
+
btn_wrap.style.cssText = 'padding:4px 16px 16px;';
|
|
1072
|
+
btn_wrap.innerHTML = `<button class="tp-new-conv-btn">${icon('msg_plus', 15, 'color:white')} ${m.new_conv_btn}</button>`;
|
|
1073
|
+
btn_wrap.querySelector('button').addEventListener('click', () => this.startNewConversation());
|
|
1074
|
+
container.appendChild(btn_wrap);
|
|
1075
|
+
} else {
|
|
1076
|
+
const greeting = document.createElement('div');
|
|
1077
|
+
greeting.style.cssText = 'padding:16px 16px 8px;';
|
|
1078
|
+
greeting.innerHTML = `<div style="font-size:12px;font-weight:600;color:#aaa;text-transform:uppercase;letter-spacing:0.5px;">${m.recent_conv}</div>`;
|
|
1079
|
+
container.appendChild(greeting);
|
|
1080
|
+
if (conversations.length === 0) {
|
|
1081
|
+
const empty = document.createElement('div');
|
|
1082
|
+
empty.style.cssText =
|
|
1083
|
+
'padding:32px 16px;text-align:center;color:#ccc;font-size:13px;display:flex;flex-direction:column;align-items:center;gap:10px;';
|
|
1084
|
+
empty.innerHTML = `<div style="opacity:0.35;">${icon('history', 36)}</div>${m.no_conv}`;
|
|
1085
|
+
container.appendChild(empty);
|
|
1086
|
+
} else {
|
|
1087
|
+
conversations.forEach(conv => {
|
|
1088
|
+
const is_closed = conv.status === 'closed';
|
|
1089
|
+
const badge_color = conv.status === 'waiting_agent' ? '#f59e0b' : color;
|
|
1090
|
+
const status_label = is_closed
|
|
1091
|
+
? m.status_closed
|
|
1092
|
+
: conv.status === 'agent'
|
|
1093
|
+
? m.status_agent
|
|
1094
|
+
: conv.status === 'waiting_agent'
|
|
1095
|
+
? m.status_waiting
|
|
1096
|
+
: m.status_bot;
|
|
1097
|
+
const date = new Date(conv.updated_at).toLocaleDateString('fr-FR', {
|
|
1098
|
+
day: '2-digit',
|
|
1099
|
+
month: '2-digit',
|
|
1100
|
+
hour: '2-digit',
|
|
1101
|
+
minute: '2-digit',
|
|
1102
|
+
});
|
|
1103
|
+
const item = document.createElement('div');
|
|
1104
|
+
item.className = 'tp-conv-item';
|
|
1105
|
+
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>`;
|
|
1106
|
+
item.addEventListener('click', () => this.openConversation(conv.conversation_id, is_closed));
|
|
1107
|
+
container.appendChild(item);
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
898
1110
|
}
|
|
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
1111
|
} catch (e) {
|
|
911
1112
|
console.error('showConversationList error:', e);
|
|
912
|
-
container.innerHTML = `<div style="padding:16px;text-align:center;color:#ef4444;font-size:13px;">${
|
|
1113
|
+
container.innerHTML = `<div style="padding:24px 16px;text-align:center;color:#ef4444;font-size:13px;">${this.getMessages().error_load}</div>`;
|
|
913
1114
|
}
|
|
914
1115
|
}
|
|
915
1116
|
|
|
@@ -921,56 +1122,42 @@
|
|
|
921
1122
|
this.is_closed = is_closed;
|
|
922
1123
|
this.agent_mode = false;
|
|
923
1124
|
this.agent_requested = false;
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
this.querySelector('#tp-back-btn').style.display = 'block';
|
|
1125
|
+
this.querySelector('#tp-back-btn').style.display = 'flex';
|
|
1126
|
+
this.showTabs(false);
|
|
927
1127
|
const container = this.querySelector('#tp-messages');
|
|
928
|
-
container.style.background = '#f5f5f7';
|
|
929
|
-
container.style.padding = '16px';
|
|
930
1128
|
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
1129
|
try {
|
|
937
1130
|
const url = `${this.api_url}/chat/conversations/${conversation_id}?user_id=${encodeURIComponent(this.user_id)}`;
|
|
938
1131
|
const response = await fetch(url, { headers: this.getHeaders() });
|
|
939
1132
|
const data = await response.json();
|
|
940
1133
|
const conv = data.result?.conversation;
|
|
941
1134
|
if (!conv) return;
|
|
942
|
-
|
|
943
1135
|
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
|
-
}
|
|
1136
|
+
if (['user', 'assistant', 'agent', 'system'].includes(msg.role)) this.addMessage(msg.role, msg.content, msg.message_id || null);
|
|
947
1137
|
});
|
|
948
1138
|
this.last_message_count = conv.messages.length;
|
|
949
|
-
|
|
950
1139
|
if (conv.status === 'closed') {
|
|
951
1140
|
this.showClosed(conv.csat || null);
|
|
952
1141
|
return;
|
|
953
1142
|
}
|
|
954
|
-
|
|
1143
|
+
const input_bar = this.querySelector('#tp-input-bar'),
|
|
1144
|
+
agent_bar = this.querySelector('#tp-agent-bar'),
|
|
1145
|
+
close_bar = this.querySelector('#tp-close-bar');
|
|
955
1146
|
if (conv.status === 'bot' || conv.status === 'waiting_agent') {
|
|
956
1147
|
if (close_bar) close_bar.style.display = 'block';
|
|
957
1148
|
} else {
|
|
958
1149
|
if (close_bar) close_bar.style.display = 'none';
|
|
959
1150
|
}
|
|
960
|
-
|
|
961
1151
|
if (conv.status === 'agent') {
|
|
962
1152
|
this.agent_mode = true;
|
|
963
|
-
|
|
964
|
-
if (subtitle) subtitle.innerHTML = `${icon('user', 12, 'margin-right:4px')} ${m.human_agent}`;
|
|
1153
|
+
this._updateSubtitle('agent');
|
|
965
1154
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
966
1155
|
if (input_bar) input_bar.style.display = 'flex';
|
|
967
1156
|
} else {
|
|
968
1157
|
if (input_bar) input_bar.style.display = 'flex';
|
|
969
1158
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
970
|
-
|
|
971
|
-
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
1159
|
+
this._updateSubtitle('bot');
|
|
972
1160
|
}
|
|
973
|
-
|
|
974
1161
|
this.updateUILanguage();
|
|
975
1162
|
this.polling_interval = setInterval(() => this.pollMessages(), 3000);
|
|
976
1163
|
} catch (e) {
|
|
@@ -978,6 +1165,7 @@
|
|
|
978
1165
|
}
|
|
979
1166
|
}
|
|
980
1167
|
|
|
1168
|
+
// ─── FIXED: conv créée au premier sendMessage, pas à l'ouverture ──────────
|
|
981
1169
|
async startNewConversation() {
|
|
982
1170
|
this.view = 'chat';
|
|
983
1171
|
this.is_closed = false;
|
|
@@ -985,71 +1173,78 @@
|
|
|
985
1173
|
this.agent_requested = false;
|
|
986
1174
|
this.messages = [];
|
|
987
1175
|
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';
|
|
1176
|
+
this.conversation_id = null; // pas encore créée
|
|
993
1177
|
|
|
1178
|
+
this.querySelector('#tp-back-btn').style.display = 'flex';
|
|
1179
|
+
this.querySelector('#tp-close-bar').style.display = 'none'; // cachée tant que pas de conv
|
|
1180
|
+
this.showTabs(false);
|
|
994
1181
|
const container = this.querySelector('#tp-messages');
|
|
995
|
-
container.style.background = '#f5f5f7';
|
|
996
|
-
container.style.padding = '16px';
|
|
997
1182
|
container.innerHTML = '';
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
1001
|
-
|
|
1002
|
-
this.querySelector('#tp-chatbot-input-bar').style.display = 'flex';
|
|
1183
|
+
this._updateSubtitle('bot');
|
|
1184
|
+
this.querySelector('#tp-input-bar').style.display = 'flex';
|
|
1003
1185
|
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
1004
1186
|
this.updateUILanguage();
|
|
1005
|
-
|
|
1006
|
-
this.conversation_id = await this.createConversation();
|
|
1007
1187
|
this.addMessage('assistant', this.getWelcomeMessage(this.user_info?.first_name || ''));
|
|
1008
|
-
this.last_message_count = 1;
|
|
1009
|
-
|
|
1010
1188
|
const theme = getClientTheme(this.client_id);
|
|
1011
1189
|
if (theme.suggestions && theme.suggestions.length > 0) this.showSuggestions(theme.suggestions);
|
|
1012
|
-
|
|
1013
|
-
this.polling_interval = setInterval(() => this.pollMessages(), 3000);
|
|
1190
|
+
// pas de polling — rien à poller tant que la conv n'est pas créée
|
|
1014
1191
|
}
|
|
1015
1192
|
|
|
1016
|
-
|
|
1193
|
+
_updateSubtitle(mode) {
|
|
1194
|
+
const m = this.getMessages(),
|
|
1195
|
+
subtitle = this.querySelector('#tp-subtitle');
|
|
1196
|
+
if (!subtitle) return;
|
|
1197
|
+
if (mode === 'agent') subtitle.innerHTML = `${icon('user', 11)} ${m.human_agent}`;
|
|
1198
|
+
else if (mode === 'closed') subtitle.innerHTML = `${icon('check', 11)} ${m.terminated}`;
|
|
1199
|
+
else subtitle.innerHTML = `${icon('bot', 11)} ${m.virtual}`;
|
|
1200
|
+
}
|
|
1017
1201
|
|
|
1018
1202
|
showGuestForm() {
|
|
1019
1203
|
const container = this.querySelector('#tp-messages');
|
|
1020
1204
|
if (!container) return;
|
|
1021
|
-
this.querySelector('#tp-
|
|
1205
|
+
this.querySelector('#tp-input-bar').style.display = 'none';
|
|
1022
1206
|
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
1023
|
-
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
<
|
|
1032
|
-
<
|
|
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>
|
|
1207
|
+
this.showTabs(false);
|
|
1208
|
+
const m = this.getMessages();
|
|
1209
|
+
container.innerHTML = '';
|
|
1210
|
+
const wrap = document.createElement('div');
|
|
1211
|
+
wrap.id = 'tp-guest-wrap';
|
|
1212
|
+
wrap.className = 'tp-guest-wrap';
|
|
1213
|
+
wrap.innerHTML = `
|
|
1214
|
+
<div class="tp-guest-intro">
|
|
1215
|
+
<div class="tp-guest-intro-name">${m.guest_title}</div>
|
|
1216
|
+
<div class="tp-guest-intro-sub">${m.guest_sub}</div>
|
|
1035
1217
|
</div>
|
|
1218
|
+
<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>
|
|
1219
|
+
<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>
|
|
1220
|
+
<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>
|
|
1221
|
+
<p id="tp-guest-error" class="tp-guest-error"></p>
|
|
1222
|
+
<button id="tp-guest-submit" class="tp-guest-submit">${icon('msg_plus', 15, 'color:white')} ${m.guest_submit}</button>
|
|
1223
|
+
<div class="tp-guest-footer">${icon('lock', 11)} ${m.guest_secured}</div>
|
|
1036
1224
|
`;
|
|
1037
|
-
container.appendChild(
|
|
1225
|
+
container.appendChild(wrap);
|
|
1038
1226
|
this.querySelector('#tp-guest-submit').addEventListener('click', () => this.submitGuestForm());
|
|
1227
|
+
['#tp-guest-firstname', '#tp-guest-company', '#tp-guest-email'].forEach(sel => {
|
|
1228
|
+
this.querySelector(sel)?.addEventListener('keydown', e => {
|
|
1229
|
+
if (e.key === 'Enter') this.submitGuestForm();
|
|
1230
|
+
});
|
|
1231
|
+
});
|
|
1039
1232
|
}
|
|
1040
1233
|
|
|
1234
|
+
// ─── FIXED: conv créée au premier sendMessage, pas après le formulaire ────
|
|
1041
1235
|
async submitGuestForm() {
|
|
1042
1236
|
const first_name = this.querySelector('#tp-guest-firstname')?.value.trim();
|
|
1043
1237
|
const company_name = this.querySelector('#tp-guest-company')?.value.trim();
|
|
1044
1238
|
const email = this.querySelector('#tp-guest-email')?.value.trim();
|
|
1045
1239
|
const error_el = this.querySelector('#tp-guest-error');
|
|
1240
|
+
const m = this.getMessages();
|
|
1046
1241
|
if (!first_name || !company_name || !email) {
|
|
1047
|
-
error_el.textContent =
|
|
1242
|
+
error_el.textContent = m.guest_fill;
|
|
1048
1243
|
error_el.style.display = 'block';
|
|
1049
1244
|
return;
|
|
1050
1245
|
}
|
|
1051
1246
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
1052
|
-
error_el.textContent =
|
|
1247
|
+
error_el.textContent = m.guest_email_invalid;
|
|
1053
1248
|
error_el.style.display = 'block';
|
|
1054
1249
|
return;
|
|
1055
1250
|
}
|
|
@@ -1057,66 +1252,49 @@
|
|
|
1057
1252
|
this.user_info = { user_id: `guest-${email}`, first_name, last_name: '', company_name, email, language: 'EN', site_id: '' };
|
|
1058
1253
|
this.user_id = this.user_info.user_id;
|
|
1059
1254
|
|
|
1060
|
-
const
|
|
1061
|
-
if (
|
|
1255
|
+
const wrap = this.querySelector('#tp-guest-wrap');
|
|
1256
|
+
if (wrap) wrap.remove();
|
|
1062
1257
|
|
|
1063
1258
|
this.view = 'chat';
|
|
1064
|
-
this.conversation_id =
|
|
1065
|
-
|
|
1066
|
-
this.querySelector('#tp-chatbot-input-bar').style.display = 'flex';
|
|
1259
|
+
this.conversation_id = null; // pas encore créée — sera créée au premier sendMessage
|
|
1260
|
+
this.querySelector('#tp-input-bar').style.display = 'flex';
|
|
1067
1261
|
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
1068
|
-
this.querySelector('#tp-close-bar').style.display = '
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
container.style.background = '#f5f5f7';
|
|
1072
|
-
container.style.padding = '16px';
|
|
1073
|
-
|
|
1262
|
+
this.querySelector('#tp-close-bar').style.display = 'none'; // cachée tant que pas de conv
|
|
1263
|
+
this.querySelector('#tp-back-btn').style.display = 'flex';
|
|
1264
|
+
this.showTabs(false);
|
|
1074
1265
|
this.updateUILanguage();
|
|
1075
1266
|
this.addMessage('assistant', this.getWelcomeMessage(first_name));
|
|
1076
|
-
this.last_message_count = 1;
|
|
1077
|
-
|
|
1078
1267
|
const theme = getClientTheme(this.client_id);
|
|
1079
1268
|
if (theme.suggestions && theme.suggestions.length > 0) this.showSuggestions(theme.suggestions);
|
|
1080
|
-
|
|
1081
|
-
this.polling_interval = setInterval(() => this.pollMessages(), 3000);
|
|
1269
|
+
// pas de polling — rien à poller tant que la conv n'est pas créée
|
|
1082
1270
|
}
|
|
1083
1271
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
addMessage(role, content, message_id = null) {
|
|
1087
|
-
this.messages.push({ role, content, message_id, created_at: new Date().toISOString() });
|
|
1272
|
+
addMessage(role, content, message_id = null, source = null) {
|
|
1273
|
+
this.messages.push({ role, content, message_id, source, created_at: new Date().toISOString() });
|
|
1088
1274
|
const container = this.querySelector('#tp-messages');
|
|
1089
|
-
|
|
1275
|
+
const m = this.getMessages();
|
|
1090
1276
|
const role_label =
|
|
1091
1277
|
role === 'user'
|
|
1092
|
-
? `${icon('user', 11
|
|
1278
|
+
? `${icon('user', 11)} ${this.user_info?.first_name || 'You'}`
|
|
1093
1279
|
: role === 'agent'
|
|
1094
|
-
? `${icon('agent', 11
|
|
1280
|
+
? `${icon('agent', 11)} Agent`
|
|
1095
1281
|
: role === 'system'
|
|
1096
1282
|
? 'Info'
|
|
1097
|
-
: `${icon('bot', 11
|
|
1098
|
-
|
|
1283
|
+
: `${icon('bot', 11)} Maria`;
|
|
1099
1284
|
const msg_el = document.createElement('div');
|
|
1100
|
-
msg_el.className = `tp-
|
|
1285
|
+
msg_el.className = `tp-msg ${role}`;
|
|
1101
1286
|
const rendered_content =
|
|
1102
1287
|
role === 'assistant' || role === 'agent' ? (typeof marked !== 'undefined' ? marked.parse(content) : content) : content;
|
|
1103
|
-
|
|
1288
|
+
const now = formatTime(new Date().toISOString());
|
|
1104
1289
|
const feedback_html =
|
|
1105
1290
|
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>`
|
|
1291
|
+
? `<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
1292
|
: '';
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
${feedback_html}
|
|
1117
|
-
</div>
|
|
1118
|
-
`;
|
|
1119
|
-
|
|
1293
|
+
if (role === 'system') {
|
|
1294
|
+
msg_el.innerHTML = `<div class="tp-bubble-msg">${rendered_content}</div>`;
|
|
1295
|
+
} else {
|
|
1296
|
+
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>`;
|
|
1297
|
+
}
|
|
1120
1298
|
if (role === 'assistant' && message_id) {
|
|
1121
1299
|
const bar = msg_el.querySelector('.tp-feedback-bar');
|
|
1122
1300
|
bar.querySelectorAll('.tp-feedback-btn').forEach(btn => {
|
|
@@ -1125,9 +1303,11 @@
|
|
|
1125
1303
|
bar.dataset.voted = '1';
|
|
1126
1304
|
const rating = btn.dataset.rating;
|
|
1127
1305
|
bar.querySelectorAll('.tp-feedback-btn').forEach(b => {
|
|
1128
|
-
b.
|
|
1306
|
+
b.classList.remove('voted-positive', 'voted-negative');
|
|
1307
|
+
b.style.opacity = b === btn ? '1' : '0.35';
|
|
1129
1308
|
b.style.cursor = 'default';
|
|
1130
1309
|
});
|
|
1310
|
+
btn.classList.add(rating === 'positive' ? 'voted-positive' : 'voted-negative');
|
|
1131
1311
|
try {
|
|
1132
1312
|
await fetch(`${this.api_url}/chat/conversations/${this.conversation_id}/feedback`, {
|
|
1133
1313
|
method: 'POST',
|
|
@@ -1138,19 +1318,17 @@
|
|
|
1138
1318
|
});
|
|
1139
1319
|
});
|
|
1140
1320
|
}
|
|
1141
|
-
|
|
1142
1321
|
container.appendChild(msg_el);
|
|
1143
1322
|
container.scrollTop = container.scrollHeight;
|
|
1144
|
-
|
|
1145
1323
|
if ((role === 'assistant' || role === 'agent') && this.sound_enabled) playSound();
|
|
1146
1324
|
}
|
|
1147
1325
|
|
|
1148
1326
|
showTyping() {
|
|
1149
1327
|
const container = this.querySelector('#tp-messages');
|
|
1150
1328
|
const typing = document.createElement('div');
|
|
1151
|
-
typing.className = 'tp-
|
|
1329
|
+
typing.className = 'tp-msg assistant';
|
|
1152
1330
|
typing.id = 'tp-typing';
|
|
1153
|
-
typing.innerHTML = `<div class="tp-
|
|
1331
|
+
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
1332
|
container.appendChild(typing);
|
|
1155
1333
|
container.scrollTop = container.scrollHeight;
|
|
1156
1334
|
}
|
|
@@ -1166,56 +1344,33 @@
|
|
|
1166
1344
|
clearInterval(this.polling_interval);
|
|
1167
1345
|
this.polling_interval = null;
|
|
1168
1346
|
}
|
|
1169
|
-
|
|
1170
1347
|
const m = this.getMessages();
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
this.querySelector('#tp-chatbot-input-bar').style.display = 'none';
|
|
1348
|
+
this._updateSubtitle('closed');
|
|
1349
|
+
this.querySelector('#tp-input-bar').style.display = 'none';
|
|
1175
1350
|
this.querySelector('#tp-agent-bar').style.display = 'none';
|
|
1176
1351
|
this.querySelector('#tp-close-bar').style.display = 'none';
|
|
1177
1352
|
const sugg_el = this.querySelector('#tp-suggestions');
|
|
1178
1353
|
if (sugg_el) sugg_el.remove();
|
|
1179
|
-
|
|
1180
1354
|
const container = this.querySelector('#tp-messages');
|
|
1181
|
-
const existing_banner =
|
|
1355
|
+
const existing_banner = container.querySelector('#tp-closed-banner');
|
|
1182
1356
|
if (existing_banner) {
|
|
1183
1357
|
if (existing_banner.querySelector('#tp-csat-block')) return;
|
|
1184
1358
|
existing_banner.remove();
|
|
1185
1359
|
}
|
|
1186
|
-
|
|
1187
|
-
const color = this.getThemeColor();
|
|
1188
|
-
const dark = this.shadeColor(color, -20);
|
|
1189
|
-
|
|
1360
|
+
const now = new Date().toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
|
|
1190
1361
|
const csat_html = existing_csat
|
|
1191
|
-
? `<div
|
|
1192
|
-
: `<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
|
-
|
|
1362
|
+
? `<div class="tp-csat-thanks">${existing_csat === 'positive' ? m.csat_positive : m.csat_negative}</div>`
|
|
1363
|
+
: `<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
1364
|
const banner = document.createElement('div');
|
|
1199
1365
|
banner.id = 'tp-closed-banner';
|
|
1200
|
-
banner.
|
|
1201
|
-
|
|
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
|
-
`;
|
|
1366
|
+
banner.className = 'tp-closed-banner';
|
|
1367
|
+
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
1368
|
container.appendChild(banner);
|
|
1213
1369
|
container.scrollTop = container.scrollHeight;
|
|
1214
|
-
|
|
1215
1370
|
if (!existing_csat) {
|
|
1216
1371
|
const submit_csat = async rating => {
|
|
1217
|
-
const
|
|
1218
|
-
if (!
|
|
1372
|
+
const block = this.querySelector('#tp-csat-block');
|
|
1373
|
+
if (!block) return;
|
|
1219
1374
|
try {
|
|
1220
1375
|
await fetch(`${this.api_url}/chat/conversations/${this.conversation_id}/csat`, {
|
|
1221
1376
|
method: 'POST',
|
|
@@ -1225,20 +1380,21 @@
|
|
|
1225
1380
|
} catch (e) {
|
|
1226
1381
|
console.error('CSAT error:', e);
|
|
1227
1382
|
}
|
|
1228
|
-
|
|
1383
|
+
block.innerHTML = `<div class="tp-csat-thanks">${rating === 'positive' ? m.csat_positive : m.csat_negative}</div>`;
|
|
1229
1384
|
};
|
|
1230
|
-
this.querySelector('#tp-csat-positive')
|
|
1231
|
-
this.querySelector('#tp-csat-negative')
|
|
1385
|
+
this.querySelector('#tp-csat-positive')?.addEventListener('click', () => submit_csat('positive'));
|
|
1386
|
+
this.querySelector('#tp-csat-negative')?.addEventListener('click', () => submit_csat('negative'));
|
|
1232
1387
|
}
|
|
1233
|
-
|
|
1234
|
-
this.querySelector('#tp-
|
|
1235
|
-
|
|
1388
|
+
this.querySelector('#tp-closed-new-btn').addEventListener('click', () => this.startNewConversation());
|
|
1389
|
+
this.querySelector('#tp-closed-hist-btn').addEventListener('click', () => {
|
|
1390
|
+
this.setTab('history');
|
|
1391
|
+
this.showConversationList();
|
|
1392
|
+
});
|
|
1236
1393
|
}
|
|
1237
1394
|
|
|
1238
|
-
// ───
|
|
1239
|
-
|
|
1395
|
+
// ─── FIXED: createConversation lazy au premier sendMessage ────────────────
|
|
1240
1396
|
async sendMessage() {
|
|
1241
|
-
if (this.is_closed
|
|
1397
|
+
if (this.is_closed) return;
|
|
1242
1398
|
const m = this.getMessages();
|
|
1243
1399
|
const input = this.querySelector('#tp-input');
|
|
1244
1400
|
const query = input.value.trim();
|
|
@@ -1250,10 +1406,22 @@
|
|
|
1250
1406
|
const user_messages = this.messages.filter(msg => msg.role === 'user').length;
|
|
1251
1407
|
if (user_messages >= 30) {
|
|
1252
1408
|
this.addMessage('system', m.limit_reached);
|
|
1253
|
-
this.querySelector('#tp-
|
|
1409
|
+
this.querySelector('#tp-input-bar').style.display = 'none';
|
|
1254
1410
|
return;
|
|
1255
1411
|
}
|
|
1256
1412
|
|
|
1413
|
+
// Créer la conv au premier message si pas encore créée
|
|
1414
|
+
if (!this.conversation_id) {
|
|
1415
|
+
try {
|
|
1416
|
+
this.conversation_id = await this.createConversation();
|
|
1417
|
+
this.querySelector('#tp-close-bar').style.display = 'block';
|
|
1418
|
+
this.polling_interval = setInterval(() => this.pollMessages(), 3000);
|
|
1419
|
+
} catch (e) {
|
|
1420
|
+
this.addMessage('system', m.error_occurred);
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1257
1425
|
input.value = '';
|
|
1258
1426
|
this.is_loading = true;
|
|
1259
1427
|
this.addMessage('user', query);
|
|
@@ -1276,14 +1444,10 @@
|
|
|
1276
1444
|
|
|
1277
1445
|
this.agent_mode = data.result.agent_mode;
|
|
1278
1446
|
const source = data.result.source || null;
|
|
1279
|
-
|
|
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}`;
|
|
1447
|
+
this._updateSubtitle(this.agent_mode ? 'agent' : 'bot');
|
|
1284
1448
|
|
|
1285
1449
|
if (data.result.reply) {
|
|
1286
|
-
this.addMessage(this.agent_mode ? 'agent' : 'assistant', data.result.reply, data.result.message_id || null);
|
|
1450
|
+
this.addMessage(this.agent_mode ? 'agent' : 'assistant', data.result.reply, data.result.message_id || null, source);
|
|
1287
1451
|
this.last_message_count += 2;
|
|
1288
1452
|
}
|
|
1289
1453
|
|
|
@@ -1292,18 +1456,11 @@
|
|
|
1292
1456
|
const suggest_agent = data.result.suggest_agent || false;
|
|
1293
1457
|
|
|
1294
1458
|
if (agent_bar && !this.agent_requested && !this.agent_mode) {
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
source === 'no_match' ||
|
|
1300
|
-
source === 'out_of_scope' ||
|
|
1301
|
-
user_msg_count >= 5
|
|
1302
|
-
) {
|
|
1303
|
-
agent_bar.style.display = 'block';
|
|
1304
|
-
}
|
|
1459
|
+
const semantic_trigger = source === 'no_match' || source === 'out_of_scope';
|
|
1460
|
+
const clarif_count = this.messages.filter(msg => msg.role === 'assistant' && msg.source === 'clarification').length;
|
|
1461
|
+
const repeated_clarif = source === 'clarification' && clarif_count >= 3;
|
|
1462
|
+
if (semantic_trigger || repeated_clarif || (suggest_agent && user_msg_count >= 5)) agent_bar.style.display = 'block';
|
|
1305
1463
|
}
|
|
1306
|
-
|
|
1307
1464
|
if (suggest_agent && !this.agent_requested) {
|
|
1308
1465
|
this.addMessage('system', m.suggest_agent_escalation);
|
|
1309
1466
|
this.last_message_count += 1;
|
|
@@ -1312,7 +1469,6 @@
|
|
|
1312
1469
|
this.hideTyping();
|
|
1313
1470
|
this.addMessage('assistant', this.getMessages().error_occurred);
|
|
1314
1471
|
}
|
|
1315
|
-
|
|
1316
1472
|
this.is_loading = false;
|
|
1317
1473
|
}
|
|
1318
1474
|
|
|
@@ -1321,7 +1477,6 @@
|
|
|
1321
1477
|
this.agent_requested = true;
|
|
1322
1478
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1323
1479
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
1324
|
-
|
|
1325
1480
|
try {
|
|
1326
1481
|
const response = await fetch(`${this.api_url}/chat/conversations/${this.conversation_id}/request-agent`, {
|
|
1327
1482
|
method: 'POST',
|
|
@@ -1329,21 +1484,18 @@
|
|
|
1329
1484
|
body: JSON.stringify({ user_id: this.user_id }),
|
|
1330
1485
|
});
|
|
1331
1486
|
const data = await response.json();
|
|
1332
|
-
const m = this.getMessages()
|
|
1333
|
-
|
|
1334
|
-
|
|
1487
|
+
const m = this.getMessages(),
|
|
1488
|
+
status = data.result?.status;
|
|
1335
1489
|
let msg = '';
|
|
1336
1490
|
if (status === 'outside_hours') msg = m.outside_hours(data.result.next_opening);
|
|
1337
1491
|
else if (status === 'no_agents') msg = m.no_agents;
|
|
1338
1492
|
else if (status === 'waiting_agent') msg = m.waiting_agent;
|
|
1339
1493
|
else if (status === 'waiting_agent_already') msg = m.waiting_agent_already;
|
|
1340
1494
|
else if (status === 'agent') msg = m.agent_already;
|
|
1341
|
-
|
|
1342
1495
|
if (msg) {
|
|
1343
1496
|
this.addMessage('system', msg);
|
|
1344
1497
|
this.last_message_count += 1;
|
|
1345
1498
|
}
|
|
1346
|
-
|
|
1347
1499
|
if (status === 'no_agents' || status === 'outside_hours' || status === 'waiting_agent_already') {
|
|
1348
1500
|
this.agent_requested = false;
|
|
1349
1501
|
if (agent_bar) agent_bar.style.display = 'block';
|
|
@@ -1378,24 +1530,21 @@
|
|
|
1378
1530
|
const data = await response.json();
|
|
1379
1531
|
const conv = data.result?.conversation;
|
|
1380
1532
|
if (!conv) return;
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
const is_typing = conv.is_typing || false;
|
|
1533
|
+
const server_messages = conv.messages || [],
|
|
1534
|
+
server_status = conv.status,
|
|
1535
|
+
is_typing = conv.is_typing || false;
|
|
1385
1536
|
const m = this.getMessages();
|
|
1386
|
-
|
|
1387
1537
|
if (server_status === 'closed' && !this.is_closed) {
|
|
1388
1538
|
this.showClosed();
|
|
1389
1539
|
return;
|
|
1390
1540
|
}
|
|
1391
|
-
|
|
1392
1541
|
if (is_typing && server_status === 'agent') {
|
|
1393
1542
|
if (!this.querySelector('#tp-agent-typing')) {
|
|
1394
1543
|
const container = this.querySelector('#tp-messages');
|
|
1395
1544
|
const typing = document.createElement('div');
|
|
1396
|
-
typing.className = 'tp-
|
|
1545
|
+
typing.className = 'tp-msg agent';
|
|
1397
1546
|
typing.id = 'tp-agent-typing';
|
|
1398
|
-
typing.innerHTML = `<div class="tp-
|
|
1547
|
+
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
1548
|
container.appendChild(typing);
|
|
1400
1549
|
container.scrollTop = container.scrollHeight;
|
|
1401
1550
|
}
|
|
@@ -1403,13 +1552,12 @@
|
|
|
1403
1552
|
const typing_el = this.querySelector('#tp-agent-typing');
|
|
1404
1553
|
if (typing_el) typing_el.remove();
|
|
1405
1554
|
}
|
|
1406
|
-
|
|
1407
1555
|
if (server_messages.length > this.last_message_count) {
|
|
1408
1556
|
const new_messages = server_messages.slice(this.last_message_count);
|
|
1409
1557
|
new_messages.forEach(msg => {
|
|
1410
1558
|
if (msg.role === 'agent') {
|
|
1411
|
-
const
|
|
1412
|
-
if (
|
|
1559
|
+
const t = this.querySelector('#tp-agent-typing');
|
|
1560
|
+
if (t) t.remove();
|
|
1413
1561
|
this.addMessage('agent', msg.content, msg.message_id || null);
|
|
1414
1562
|
} else if (msg.role === 'assistant') {
|
|
1415
1563
|
this.addMessage('assistant', msg.content, msg.message_id || null);
|
|
@@ -1419,22 +1567,23 @@
|
|
|
1419
1567
|
});
|
|
1420
1568
|
this.last_message_count = server_messages.length;
|
|
1421
1569
|
}
|
|
1422
|
-
|
|
1423
1570
|
if (server_status === 'agent' && !this.agent_mode) {
|
|
1424
1571
|
this.agent_mode = true;
|
|
1425
|
-
|
|
1426
|
-
if (subtitle) subtitle.innerHTML = `${icon('user', 12, 'margin-right:4px')} ${m.human_agent}`;
|
|
1572
|
+
this._updateSubtitle('agent');
|
|
1427
1573
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1428
1574
|
if (agent_bar) agent_bar.style.display = 'none';
|
|
1575
|
+
const close_bar = this.querySelector('#tp-close-bar');
|
|
1576
|
+
if (close_bar) close_bar.style.display = 'none';
|
|
1429
1577
|
this.addMessage('system', m.agent_taken);
|
|
1430
1578
|
this.last_message_count = server_messages.length;
|
|
1431
1579
|
} else if (server_status === 'bot' && this.agent_mode) {
|
|
1432
1580
|
this.agent_mode = false;
|
|
1433
1581
|
this.agent_requested = false;
|
|
1434
|
-
|
|
1435
|
-
if (subtitle) subtitle.innerHTML = `${icon('bot', 12, 'margin-right:4px')} ${m.virtual}`;
|
|
1582
|
+
this._updateSubtitle('bot');
|
|
1436
1583
|
const agent_bar = this.querySelector('#tp-agent-bar');
|
|
1437
1584
|
if (agent_bar) agent_bar.style.display = 'block';
|
|
1585
|
+
const close_bar = this.querySelector('#tp-close-bar');
|
|
1586
|
+
if (close_bar) close_bar.style.display = 'block';
|
|
1438
1587
|
this.addMessage('system', m.agent_released);
|
|
1439
1588
|
this.last_message_count = server_messages.length;
|
|
1440
1589
|
}
|