django-lucy-assist 1.2.7__py3-none-any.whl → 1.2.8__py3-none-any.whl
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.
- {django_lucy_assist-1.2.7.dist-info → django_lucy_assist-1.2.8.dist-info}/METADATA +1 -1
- {django_lucy_assist-1.2.7.dist-info → django_lucy_assist-1.2.8.dist-info}/RECORD +7 -7
- lucy_assist/__init__.py +1 -1
- lucy_assist/static/lucy_assist/js/lucy-assist.js +282 -265
- lucy_assist/templates/lucy_assist/chatbot_sidebar.html +1 -1
- {django_lucy_assist-1.2.7.dist-info → django_lucy_assist-1.2.8.dist-info}/WHEEL +0 -0
- {django_lucy_assist-1.2.7.dist-info → django_lucy_assist-1.2.8.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
lucy_assist/__init__.py,sha256=
|
|
1
|
+
lucy_assist/__init__.py,sha256=wTA9Xnf5XEPL_0lrQlF26nBIrmbTFMCazE4-6A1hPYA,325
|
|
2
2
|
lucy_assist/admin.py,sha256=LnIXlYOIanAUoCPYpShAoYNzOr1XQukK2RSwlsWWXis,3086
|
|
3
3
|
lucy_assist/apps.py,sha256=zHZtlBXs5ML4CKtGg7xDyptSWzLfB1ks2VvbXF50hdo,264
|
|
4
4
|
lucy_assist/conf.py,sha256=Yyrhlyjj7RFf2jNYj9C__azQWR0tgJpU09ExfTpkiLM,4189
|
|
@@ -26,8 +26,8 @@ lucy_assist/services/tools_definition.py,sha256=cLRDx49eIGvElqT4avMtdsPzJO1mkcHj
|
|
|
26
26
|
lucy_assist/services/view_discovery_service.py,sha256=5FybECCoHUkDhUQhdZ_tJ4Lei6Muyo4L-zISL5ELRfs,13096
|
|
27
27
|
lucy_assist/static/lucy_assist/css/lucy-assist.css,sha256=tLCFhIg744wE7aED1wRC31CId-uLhL5QnscdNoOtQOA,24305
|
|
28
28
|
lucy_assist/static/lucy_assist/image/icon-lucy.png,sha256=FOYlwXAt40Gr9jsWFmhgPivYOBFWKeYW0lxJI5Up-GM,6710
|
|
29
|
-
lucy_assist/static/lucy_assist/js/lucy-assist.js,sha256=
|
|
30
|
-
lucy_assist/templates/lucy_assist/chatbot_sidebar.html,sha256=
|
|
29
|
+
lucy_assist/static/lucy_assist/js/lucy-assist.js,sha256=eYipYnKZ9_qDfBxRkjVvKDFb0Vzuvroj7bDdRYcG5Ds,31412
|
|
30
|
+
lucy_assist/templates/lucy_assist/chatbot_sidebar.html,sha256=67dlLfQj_Sc73hQxBYDcqUHVtOB_gQm9OtGp-_rMi9A,11428
|
|
31
31
|
lucy_assist/templates/lucy_assist/partials/documentation_content.html,sha256=8O1KIsr269s5BnQKhlxK5SIG7NKkF4fUCp3tB66xhfs,3784
|
|
32
32
|
lucy_assist/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
lucy_assist/tests/test_lucy_assist.py,sha256=KBvuED10_gOJWdaRS2zMigM3W5Kp00KhMEwaR8tvhUc,6949
|
|
@@ -39,7 +39,7 @@ lucy_assist/utils/message_utils.py,sha256=YzcLHnl1ig4d5_utHCJwgxS7tKmd49Q-tuo78e
|
|
|
39
39
|
lucy_assist/utils/token_utils.py,sha256=aBzyKVqpU67rAetO_Ee3QIfPtbNyjyWSe6qPfzIRJF8,2608
|
|
40
40
|
lucy_assist/views/__init__.py,sha256=uUPYpuHlBC8j7zKS_DDoWjwpCpRnOIXETY-S2-Ss0cY,288
|
|
41
41
|
lucy_assist/views/api_views.py,sha256=sJwKNC9-mi1zMbgyKZTVvT3pz4hMmw4jR0rrS7iJR-g,23619
|
|
42
|
-
django_lucy_assist-1.2.
|
|
43
|
-
django_lucy_assist-1.2.
|
|
44
|
-
django_lucy_assist-1.2.
|
|
45
|
-
django_lucy_assist-1.2.
|
|
42
|
+
django_lucy_assist-1.2.8.dist-info/METADATA,sha256=TKK8fLdPuQlp_U8ZQyPrMMGgj8kCr1yTBcNDKBL-Aac,5899
|
|
43
|
+
django_lucy_assist-1.2.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
44
|
+
django_lucy_assist-1.2.8.dist-info/top_level.txt,sha256=T-UCiwpn5yF3Oem3234TUpSVnEgbkrM2rGz9Tz5N-QA,12
|
|
45
|
+
django_lucy_assist-1.2.8.dist-info/RECORD,,
|
lucy_assist/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@ Un chatbot IA base sur Mistral AI, integrable dans n'importe quelle
|
|
|
5
5
|
application Django pour fournir une assistance contextuelle aux utilisateurs.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = '1.2.
|
|
8
|
+
__version__ = '1.2.8'
|
|
9
9
|
__author__ = 'Revolucy'
|
|
10
10
|
|
|
11
11
|
default_app_config = 'lucy_assist.apps.LucyAssistConfig'
|
|
@@ -36,11 +36,18 @@
|
|
|
36
36
|
let csrfToken = '';
|
|
37
37
|
|
|
38
38
|
// ========================================================================
|
|
39
|
-
// ELEMENTS DOM
|
|
39
|
+
// ELEMENTS DOM
|
|
40
40
|
// ========================================================================
|
|
41
|
-
const
|
|
41
|
+
const el = {};
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
function $(id) {
|
|
44
|
+
return document.getElementById(id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Attache un event listener si l'element existe */
|
|
48
|
+
function on(element, event, handler) {
|
|
49
|
+
if (element) element.addEventListener(event, handler);
|
|
50
|
+
}
|
|
44
51
|
|
|
45
52
|
// ========================================================================
|
|
46
53
|
// HELPERS
|
|
@@ -66,9 +73,9 @@
|
|
|
66
73
|
const now = new Date();
|
|
67
74
|
const diff = now - date;
|
|
68
75
|
if (diff < 60000) return "À l'instant";
|
|
69
|
-
if (diff < 3600000) return
|
|
76
|
+
if (diff < 3600000) return 'Il y a ' + Math.floor(diff / 60000) + ' min';
|
|
70
77
|
if (date.toDateString() === now.toDateString())
|
|
71
|
-
return
|
|
78
|
+
return "Aujourd'hui à " + date.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
|
|
72
79
|
if (diff < 7 * 86400000)
|
|
73
80
|
return date.toLocaleDateString('fr-FR', { weekday: 'long', hour: '2-digit', minute: '2-digit' });
|
|
74
81
|
return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' });
|
|
@@ -84,7 +91,7 @@
|
|
|
84
91
|
|
|
85
92
|
function formatMessage(content) {
|
|
86
93
|
if (!content) return '';
|
|
87
|
-
|
|
94
|
+
var f = escapeHtml(content);
|
|
88
95
|
f = f.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
89
96
|
f = f.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
|
90
97
|
f = f.replace(/`(.*?)`/g, '<code style="background:rgba(0,0,0,.1);padding:0 4px;border-radius:3px;">$1</code>');
|
|
@@ -95,8 +102,8 @@
|
|
|
95
102
|
|
|
96
103
|
function checkIfLucyDidNotUnderstand(response) {
|
|
97
104
|
if (!response) return false;
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
var lower = response.toLowerCase();
|
|
106
|
+
var patterns = [
|
|
100
107
|
'je ne comprends pas', "je n'ai pas compris", 'pourriez-vous reformuler',
|
|
101
108
|
'pouvez-vous reformuler', 'pourriez-vous préciser', 'pouvez-vous préciser',
|
|
102
109
|
"je ne suis pas sûr de comprendre", "je n'ai pas accès", "je n'ai pas les outils",
|
|
@@ -105,7 +112,7 @@
|
|
|
105
112
|
'impossible de réaliser', 'en dehors de mon périmètre', 'hors de mon champ',
|
|
106
113
|
'dépasse mes capacités', 'comment puis-je vous aider avec votre crm'
|
|
107
114
|
];
|
|
108
|
-
return patterns.some(p
|
|
115
|
+
return patterns.some(function (p) { return lower.includes(p); });
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
// ========================================================================
|
|
@@ -113,29 +120,29 @@
|
|
|
113
120
|
// ========================================================================
|
|
114
121
|
|
|
115
122
|
async function apiGet(endpoint) {
|
|
116
|
-
|
|
123
|
+
var r = await fetch(API_BASE + endpoint, {
|
|
117
124
|
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }
|
|
118
125
|
});
|
|
119
|
-
if (!r.ok)
|
|
126
|
+
if (!r.ok) { var e = await r.json(); e.status = r.status; throw e; }
|
|
120
127
|
return r.json();
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
async function apiPost(endpoint, data) {
|
|
124
|
-
|
|
131
|
+
var r = await fetch(API_BASE + endpoint, {
|
|
125
132
|
method: 'POST',
|
|
126
133
|
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
|
|
127
134
|
body: JSON.stringify(data)
|
|
128
135
|
});
|
|
129
|
-
if (!r.ok)
|
|
136
|
+
if (!r.ok) { var e = await r.json(); e.status = r.status; throw e; }
|
|
130
137
|
return r.json();
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
async function apiDelete(endpoint) {
|
|
134
|
-
|
|
141
|
+
var r = await fetch(API_BASE + endpoint, {
|
|
135
142
|
method: 'DELETE',
|
|
136
143
|
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }
|
|
137
144
|
});
|
|
138
|
-
if (!r.ok)
|
|
145
|
+
if (!r.ok) { var e = await r.json(); e.status = r.status; throw e; }
|
|
139
146
|
return r.json();
|
|
140
147
|
}
|
|
141
148
|
|
|
@@ -144,17 +151,19 @@
|
|
|
144
151
|
// ========================================================================
|
|
145
152
|
|
|
146
153
|
function scrollToBottom() {
|
|
147
|
-
|
|
148
|
-
if (c) c.scrollTop = c.scrollHeight;
|
|
154
|
+
if (el.messages) el.messages.scrollTop = el.messages.scrollHeight;
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
function updateTokensDisplay() {
|
|
152
|
-
|
|
153
|
-
|
|
158
|
+
if (el.tokens) el.tokens.textContent = formatTokens(state.tokensDisponibles);
|
|
159
|
+
if (el.btnBuyCredits) el.btnBuyCredits.style.display = state.tokensDisponibles < 100000 ? '' : 'none';
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
function updateExpandIcons() {
|
|
157
|
-
|
|
163
|
+
var expand = el.sidebar ? el.sidebar.querySelector('.lucy-icon-expand') : null;
|
|
164
|
+
var compress = el.sidebar ? el.sidebar.querySelector('.lucy-icon-compress') : null;
|
|
165
|
+
if (expand) expand.style.display = state.isExpanded ? 'none' : '';
|
|
166
|
+
if (compress) compress.style.display = state.isExpanded ? '' : 'none';
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
/** Affiche la bonne vue : chat / history / doc */
|
|
@@ -162,46 +171,40 @@
|
|
|
162
171
|
state.showHistory = view === 'history';
|
|
163
172
|
state.showDoc = view === 'doc';
|
|
164
173
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
if (el.viewHistory) el.viewHistory.style.display = state.showHistory ? '' : 'none';
|
|
175
|
+
if (el.viewDoc) el.viewDoc.style.display = state.showDoc ? '' : 'none';
|
|
176
|
+
if (el.viewChat) el.viewChat.style.display = (!state.showHistory && !state.showDoc) ? '' : 'none';
|
|
168
177
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
els.btnDoc.classList.toggle('active', state.showDoc);
|
|
178
|
+
if (el.btnHistory) el.btnHistory.classList.toggle('active', state.showHistory);
|
|
179
|
+
if (el.btnDoc) el.btnDoc.classList.toggle('active', state.showDoc);
|
|
172
180
|
}
|
|
173
181
|
|
|
174
182
|
/** Rend la liste des conversations dans l'historique */
|
|
175
183
|
function renderHistoryList() {
|
|
176
|
-
|
|
177
|
-
if (!container) return;
|
|
184
|
+
if (!el.historyList) return;
|
|
178
185
|
|
|
179
186
|
if (state.conversations.length === 0) {
|
|
180
|
-
|
|
187
|
+
el.historyList.innerHTML = '<p class="lucy-history-empty">Aucune conversation</p>';
|
|
181
188
|
return;
|
|
182
189
|
}
|
|
183
190
|
|
|
184
|
-
|
|
185
|
-
<div class="lucy-history-item
|
|
186
|
-
|
|
187
|
-
<div class="lucy-history-item-
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// Event delegation
|
|
197
|
-
container.querySelectorAll('[data-conv-id]').forEach(el => {
|
|
198
|
-
el.addEventListener('click', (e) => {
|
|
191
|
+
el.historyList.innerHTML = state.conversations.map(function (conv) {
|
|
192
|
+
return '<div class="lucy-history-item ' + (state.currentConversationId === conv.id ? 'active' : '') + '" data-conv-id="' + conv.id + '">'
|
|
193
|
+
+ '<div class="lucy-history-item-title">' + escapeHtml(conv.titre || 'Sans titre') + '</div>'
|
|
194
|
+
+ '<div class="lucy-history-item-meta"><span>' + formatDate(conv.created_date) + '</span>'
|
|
195
|
+
+ '<span style="margin-left:.5rem">🪙 ' + conv.total_tokens + ' tokens | ' + formatTokenCost(conv.total_tokens) + '</span></div>'
|
|
196
|
+
+ '<button class="lucy-btn-delete" data-delete-id="' + conv.id + '">🗑️ Supprimer</button>'
|
|
197
|
+
+ '</div>';
|
|
198
|
+
}).join('');
|
|
199
|
+
|
|
200
|
+
el.historyList.querySelectorAll('[data-conv-id]').forEach(function (item) {
|
|
201
|
+
item.addEventListener('click', function (e) {
|
|
199
202
|
if (e.target.closest('[data-delete-id]')) return;
|
|
200
|
-
loadConversation(parseInt(
|
|
203
|
+
loadConversation(parseInt(item.dataset.convId));
|
|
201
204
|
});
|
|
202
205
|
});
|
|
203
|
-
|
|
204
|
-
btn.addEventListener('click', (e)
|
|
206
|
+
el.historyList.querySelectorAll('[data-delete-id]').forEach(function (btn) {
|
|
207
|
+
btn.addEventListener('click', function (e) {
|
|
205
208
|
e.stopPropagation();
|
|
206
209
|
deleteConversation(parseInt(btn.dataset.deleteId));
|
|
207
210
|
});
|
|
@@ -210,84 +213,83 @@
|
|
|
210
213
|
|
|
211
214
|
/** Rend la zone de messages */
|
|
212
215
|
function renderMessages() {
|
|
213
|
-
|
|
214
|
-
if (!container) return;
|
|
216
|
+
if (!el.messages) return;
|
|
215
217
|
|
|
216
218
|
if (state.messages.length === 0) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
if (el.welcome) el.welcome.style.display = '';
|
|
220
|
+
if (el.feedbackContact) el.feedbackContact.style.display = 'none';
|
|
221
|
+
if (el.feedbackError) el.feedbackError.style.display = 'none';
|
|
222
|
+
// Supprimer les anciens messages
|
|
223
|
+
el.messages.querySelectorAll('.lucy-message').forEach(function (m) { m.remove(); });
|
|
221
224
|
return;
|
|
222
225
|
}
|
|
223
226
|
|
|
224
|
-
|
|
227
|
+
if (el.welcome) el.welcome.style.display = 'none';
|
|
225
228
|
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
// Supprimer les anciens message-divs
|
|
229
|
-
container.querySelectorAll('.lucy-message').forEach(el => el.remove());
|
|
229
|
+
// Supprimer les anciens messages
|
|
230
|
+
el.messages.querySelectorAll('.lucy-message').forEach(function (m) { m.remove(); });
|
|
230
231
|
|
|
231
|
-
|
|
232
|
-
|
|
232
|
+
var avatarUrl = el.messages.dataset.avatarUrl || '';
|
|
233
|
+
var lucyIcon = el.messages.dataset.lucyIcon || '';
|
|
233
234
|
|
|
234
|
-
state.messages.forEach(msg
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
state.messages.forEach(function (msg) {
|
|
236
|
+
var isUser = msg.repondant === 'UTILISATEUR';
|
|
237
|
+
var div = document.createElement('div');
|
|
237
238
|
div.className = 'lucy-message ' + (isUser ? 'user' : 'bot');
|
|
238
239
|
|
|
239
|
-
|
|
240
|
+
var avatarHtml;
|
|
240
241
|
if (isUser) {
|
|
241
242
|
avatarHtml = avatarUrl
|
|
242
|
-
?
|
|
243
|
-
:
|
|
243
|
+
? '<img src="' + avatarUrl + '" alt="Utilisateur">'
|
|
244
|
+
: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>';
|
|
244
245
|
} else {
|
|
245
246
|
avatarHtml = lucyIcon
|
|
246
|
-
?
|
|
247
|
+
? '<img src="' + lucyIcon + '" alt="Lucy">'
|
|
247
248
|
: '🤖';
|
|
248
249
|
}
|
|
249
250
|
|
|
250
|
-
|
|
251
|
-
?
|
|
252
|
-
:
|
|
253
|
-
|
|
254
|
-
div.innerHTML =
|
|
255
|
-
<div class="lucy-message-
|
|
256
|
-
<div class="lucy-message-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
container.appendChild(div);
|
|
251
|
+
var bubbleContent = msg.contenu
|
|
252
|
+
? '<span>' + formatMessage(msg.contenu) + '</span>'
|
|
253
|
+
: '<span class="lucy-loading-dots"><span></span><span></span><span></span></span>';
|
|
254
|
+
|
|
255
|
+
div.innerHTML = '<div class="lucy-message-avatar">' + avatarHtml + '</div>'
|
|
256
|
+
+ '<div class="lucy-message-content">'
|
|
257
|
+
+ '<div class="lucy-message-bubble">' + bubbleContent + '</div>'
|
|
258
|
+
+ '<div class="lucy-message-time">' + formatTime(msg.created_date) + '</div>'
|
|
259
|
+
+ '</div>';
|
|
260
|
+
|
|
261
|
+
el.messages.appendChild(div);
|
|
262
262
|
});
|
|
263
263
|
|
|
264
264
|
// Feedback bars
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
if (el.feedbackContact) el.feedbackContact.style.display = (state.lucyDidNotUnderstand && state.messages.length > 0) ? '' : 'none';
|
|
266
|
+
if (el.feedbackError) el.feedbackError.style.display = (state.hasError && !state.lucyDidNotUnderstand && state.messages.length > 0) ? '' : 'none';
|
|
267
267
|
|
|
268
268
|
requestAnimationFrame(scrollToBottom);
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
/** Rend les suggestions */
|
|
272
272
|
function renderSuggestions() {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
container.querySelectorAll('[data-suggestion]').forEach(btn => {
|
|
283
|
-
btn.addEventListener('click', () => sendMessage(btn.dataset.suggestion));
|
|
273
|
+
if (!el.suggestions) return;
|
|
274
|
+
el.suggestions.innerHTML = state.suggestions.map(function (s) {
|
|
275
|
+
return '<button class="lucy-suggestion-btn" data-suggestion="' + escapeHtml(s) + '">'
|
|
276
|
+
+ '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="flex-shrink:0;color:#f59e0b"><path stroke-linecap="round" stroke-linejoin="round" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>'
|
|
277
|
+
+ '<span>' + escapeHtml(s) + '</span></button>';
|
|
278
|
+
}).join('');
|
|
279
|
+
|
|
280
|
+
el.suggestions.querySelectorAll('[data-suggestion]').forEach(function (btn) {
|
|
281
|
+
btn.addEventListener('click', function () { sendMessage(btn.dataset.suggestion); });
|
|
284
282
|
});
|
|
285
283
|
}
|
|
286
284
|
|
|
287
285
|
function updateBuyModal() {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
286
|
+
var tokens = Math.floor((state.buyAmount / 10) * 1000000);
|
|
287
|
+
if (el.buyTokens) el.buyTokens.textContent = formatTokens(tokens);
|
|
288
|
+
if (el.buyConvs) el.buyConvs.textContent = Math.floor(tokens / 2000).toString();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function updateSendBtn() {
|
|
292
|
+
if (el.btnSend) el.btnSend.disabled = state.isLoading || !(el.input && el.input.value.trim());
|
|
291
293
|
}
|
|
292
294
|
|
|
293
295
|
// ========================================================================
|
|
@@ -297,20 +299,31 @@
|
|
|
297
299
|
function openSidebar() {
|
|
298
300
|
state.isOpen = true;
|
|
299
301
|
state.hasNewMessage = false;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
302
|
+
if (el.floatBtn) el.floatBtn.style.display = 'none';
|
|
303
|
+
if (el.sidebar) {
|
|
304
|
+
// 1) Rendre visible mais hors-ecran (classe hidden = translateX(100%))
|
|
305
|
+
el.sidebar.style.display = '';
|
|
306
|
+
// 2) Forcer un reflow pour que le navigateur prenne en compte le display
|
|
307
|
+
el.sidebar.offsetHeight; // eslint-disable-line no-unused-expressions
|
|
308
|
+
// 3) Retirer hidden pour declencher la transition CSS
|
|
309
|
+
el.sidebar.classList.remove('hidden');
|
|
310
|
+
}
|
|
303
311
|
saveState();
|
|
304
|
-
setTimeout(()
|
|
312
|
+
setTimeout(function () { if (el.input) el.input.focus(); }, 300);
|
|
305
313
|
}
|
|
306
314
|
|
|
307
315
|
function closeSidebar() {
|
|
308
316
|
state.isOpen = false;
|
|
309
317
|
state.showHistory = false;
|
|
310
318
|
state.showDoc = false;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
319
|
+
if (el.sidebar) {
|
|
320
|
+
el.sidebar.classList.add('hidden');
|
|
321
|
+
// Attendre la fin de la transition avant de masquer
|
|
322
|
+
setTimeout(function () {
|
|
323
|
+
if (!state.isOpen && el.sidebar) el.sidebar.style.display = 'none';
|
|
324
|
+
}, 350);
|
|
325
|
+
}
|
|
326
|
+
if (el.floatBtn) el.floatBtn.style.display = '';
|
|
314
327
|
showView('chat');
|
|
315
328
|
saveState();
|
|
316
329
|
}
|
|
@@ -321,36 +334,36 @@
|
|
|
321
334
|
|
|
322
335
|
function toggleExpanded() {
|
|
323
336
|
state.isExpanded = !state.isExpanded;
|
|
324
|
-
|
|
337
|
+
if (el.sidebar) el.sidebar.classList.toggle('expanded', state.isExpanded);
|
|
325
338
|
updateExpandIcons();
|
|
326
339
|
saveState();
|
|
327
340
|
}
|
|
328
341
|
|
|
329
342
|
async function loadTokenStatus() {
|
|
330
343
|
try {
|
|
331
|
-
|
|
344
|
+
var r = await apiGet('/tokens/status');
|
|
332
345
|
state.tokensDisponibles = r.tokens_disponibles || 0;
|
|
333
346
|
updateTokensDisplay();
|
|
334
|
-
} catch (e) { console.error('
|
|
347
|
+
} catch (e) { console.error('Lucy: erreur tokens', e); }
|
|
335
348
|
}
|
|
336
349
|
|
|
337
350
|
async function loadConversations() {
|
|
338
351
|
try {
|
|
339
|
-
|
|
352
|
+
var r = await apiGet('/conversations');
|
|
340
353
|
state.conversations = r.conversations || [];
|
|
341
354
|
renderHistoryList();
|
|
342
|
-
} catch (e) { console.error('
|
|
355
|
+
} catch (e) { console.error('Lucy: erreur conversations', e); }
|
|
343
356
|
}
|
|
344
357
|
|
|
345
358
|
async function loadSuggestions() {
|
|
346
359
|
try {
|
|
347
|
-
|
|
360
|
+
var r = await apiGet('/suggestions');
|
|
348
361
|
state.suggestions = r.suggestions || [];
|
|
349
362
|
} catch (e) {
|
|
350
363
|
state.suggestions = [
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
364
|
+
'Comment créer un nouveau membre ?',
|
|
365
|
+
'Comment effectuer un paiement ?',
|
|
366
|
+
'Où trouver la liste des adhésions ?'
|
|
354
367
|
];
|
|
355
368
|
}
|
|
356
369
|
renderSuggestions();
|
|
@@ -358,19 +371,19 @@
|
|
|
358
371
|
|
|
359
372
|
async function newConversation() {
|
|
360
373
|
try {
|
|
361
|
-
|
|
374
|
+
var r = await apiPost('/conversations', { page_contexte: window.location.pathname });
|
|
362
375
|
state.currentConversationId = r.id;
|
|
363
376
|
state.messages = [];
|
|
364
377
|
showView('chat');
|
|
365
378
|
renderMessages();
|
|
366
379
|
saveState();
|
|
367
380
|
await loadConversations();
|
|
368
|
-
} catch (e) { console.error('
|
|
381
|
+
} catch (e) { console.error('Lucy: erreur new conversation', e); }
|
|
369
382
|
}
|
|
370
383
|
|
|
371
384
|
async function loadConversation(id) {
|
|
372
385
|
try {
|
|
373
|
-
|
|
386
|
+
var r = await apiGet('/conversations/' + id);
|
|
374
387
|
state.currentConversationId = id;
|
|
375
388
|
state.messages = r.messages || [];
|
|
376
389
|
showView('chat');
|
|
@@ -378,7 +391,7 @@
|
|
|
378
391
|
saveState();
|
|
379
392
|
setTimeout(scrollToBottom, 100);
|
|
380
393
|
} catch (e) {
|
|
381
|
-
console.error('
|
|
394
|
+
console.error('Lucy: erreur load conversation', e);
|
|
382
395
|
state.currentConversationId = null;
|
|
383
396
|
state.messages = [];
|
|
384
397
|
renderMessages();
|
|
@@ -389,18 +402,18 @@
|
|
|
389
402
|
async function deleteConversation(id) {
|
|
390
403
|
if (!confirm('Êtes-vous sûr de vouloir supprimer cette conversation ?')) return;
|
|
391
404
|
try {
|
|
392
|
-
await apiDelete(
|
|
405
|
+
await apiDelete('/conversations/' + id);
|
|
393
406
|
await loadConversations();
|
|
394
407
|
if (state.currentConversationId === id) {
|
|
395
408
|
state.currentConversationId = null;
|
|
396
409
|
state.messages = [];
|
|
397
410
|
renderMessages();
|
|
398
411
|
}
|
|
399
|
-
} catch (e) { console.error('
|
|
412
|
+
} catch (e) { console.error('Lucy: erreur suppression', e); }
|
|
400
413
|
}
|
|
401
414
|
|
|
402
415
|
async function sendMessage(message) {
|
|
403
|
-
if (state.isLoading || !message.trim()) return;
|
|
416
|
+
if (state.isLoading || !message || !message.trim()) return;
|
|
404
417
|
|
|
405
418
|
state.lucyDidNotUnderstand = false;
|
|
406
419
|
|
|
@@ -415,20 +428,22 @@
|
|
|
415
428
|
tokens_utilises: 0
|
|
416
429
|
});
|
|
417
430
|
state.currentMessage = '';
|
|
418
|
-
|
|
419
|
-
|
|
431
|
+
if (el.input) {
|
|
432
|
+
el.input.value = '';
|
|
433
|
+
el.input.style.height = 'auto';
|
|
434
|
+
}
|
|
420
435
|
updateSendBtn();
|
|
421
436
|
renderMessages();
|
|
422
437
|
|
|
423
438
|
try {
|
|
424
439
|
state.isLoading = true;
|
|
425
|
-
|
|
426
|
-
|
|
440
|
+
if (el.input) el.input.disabled = true;
|
|
441
|
+
if (el.btnSend) el.btnSend.disabled = true;
|
|
427
442
|
|
|
428
|
-
await apiPost(
|
|
443
|
+
await apiPost('/conversations/' + state.currentConversationId + '/messages', { contenu: message });
|
|
429
444
|
await streamChatCompletion();
|
|
430
445
|
} catch (e) {
|
|
431
|
-
console.error('
|
|
446
|
+
console.error('Lucy: erreur envoi', e);
|
|
432
447
|
if (e.status === 402) {
|
|
433
448
|
showModal('buy');
|
|
434
449
|
} else {
|
|
@@ -437,15 +452,15 @@
|
|
|
437
452
|
}
|
|
438
453
|
} finally {
|
|
439
454
|
state.isLoading = false;
|
|
440
|
-
|
|
455
|
+
if (el.input) el.input.disabled = false;
|
|
441
456
|
updateSendBtn();
|
|
442
|
-
|
|
457
|
+
if (el.input) el.input.focus();
|
|
443
458
|
}
|
|
444
459
|
}
|
|
445
460
|
|
|
446
461
|
async function streamChatCompletion() {
|
|
447
|
-
|
|
448
|
-
|
|
462
|
+
var url = API_BASE + '/conversations/' + state.currentConversationId + '/chat';
|
|
463
|
+
var botIdx = state.messages.length;
|
|
449
464
|
|
|
450
465
|
state.messages.push({
|
|
451
466
|
id: Date.now() + 1,
|
|
@@ -457,27 +472,30 @@
|
|
|
457
472
|
renderMessages();
|
|
458
473
|
|
|
459
474
|
try {
|
|
460
|
-
|
|
475
|
+
var response = await fetch(url, {
|
|
461
476
|
method: 'POST',
|
|
462
477
|
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
|
|
463
478
|
body: JSON.stringify({ page_contexte: window.location.pathname })
|
|
464
479
|
});
|
|
465
480
|
|
|
466
|
-
if (!response.ok)
|
|
481
|
+
if (!response.ok) { var err = await response.json(); err.status = response.status; throw err; }
|
|
467
482
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
483
|
+
var reader = response.body.getReader();
|
|
484
|
+
var decoder = new TextDecoder();
|
|
485
|
+
var fullContent = '';
|
|
471
486
|
|
|
472
487
|
while (true) {
|
|
473
|
-
|
|
474
|
-
if (done) break;
|
|
488
|
+
var result = await reader.read();
|
|
489
|
+
if (result.done) break;
|
|
490
|
+
|
|
491
|
+
var text = decoder.decode(result.value);
|
|
492
|
+
var lines = text.split('\n');
|
|
475
493
|
|
|
476
|
-
|
|
477
|
-
|
|
494
|
+
for (var i = 0; i < lines.length; i++) {
|
|
495
|
+
var line = lines[i];
|
|
478
496
|
if (!line.startsWith('data: ')) continue;
|
|
479
497
|
try {
|
|
480
|
-
|
|
498
|
+
var data = JSON.parse(line.slice(6));
|
|
481
499
|
if (data.type === 'content') {
|
|
482
500
|
fullContent += data.content;
|
|
483
501
|
state.messages[botIdx].contenu = fullContent;
|
|
@@ -489,22 +507,24 @@
|
|
|
489
507
|
} else if (data.type === 'error') {
|
|
490
508
|
throw new Error(data.error);
|
|
491
509
|
}
|
|
492
|
-
} catch (
|
|
510
|
+
} catch (parseErr) { if (parseErr.message) throw parseErr; }
|
|
493
511
|
}
|
|
494
512
|
}
|
|
495
513
|
|
|
496
|
-
if (!state.messages[botIdx]
|
|
514
|
+
if (!state.messages[botIdx] || !state.messages[botIdx].contenu) {
|
|
497
515
|
state.messages.splice(botIdx, 1);
|
|
498
516
|
}
|
|
499
517
|
|
|
500
|
-
state.lucyDidNotUnderstand = checkIfLucyDidNotUnderstand(
|
|
518
|
+
state.lucyDidNotUnderstand = checkIfLucyDidNotUnderstand(
|
|
519
|
+
(state.messages[botIdx] && state.messages[botIdx].contenu) || ''
|
|
520
|
+
);
|
|
501
521
|
if (!state.lucyDidNotUnderstand) state.hasError = false;
|
|
502
522
|
|
|
503
523
|
renderMessages();
|
|
504
524
|
await loadConversations();
|
|
505
525
|
} catch (e) {
|
|
506
526
|
state.hasError = true;
|
|
507
|
-
if (botIdx < state.messages.length && !state.messages[botIdx]
|
|
527
|
+
if (botIdx < state.messages.length && state.messages[botIdx] && !state.messages[botIdx].contenu) {
|
|
508
528
|
state.messages.splice(botIdx, 1);
|
|
509
529
|
}
|
|
510
530
|
renderMessages();
|
|
@@ -525,17 +545,17 @@
|
|
|
525
545
|
state.lucyDidNotUnderstand = false;
|
|
526
546
|
hideModal('feedback');
|
|
527
547
|
renderMessages();
|
|
528
|
-
} catch (e) { console.error('
|
|
548
|
+
} catch (e) { console.error('Lucy: erreur feedback', e); }
|
|
529
549
|
finally { state.feedbackSending = false; }
|
|
530
550
|
}
|
|
531
551
|
|
|
532
552
|
async function buyCredits() {
|
|
533
553
|
if (state.buyAmount < 10) return;
|
|
534
554
|
try {
|
|
535
|
-
|
|
555
|
+
var r = await apiPost('/tokens/buy', { montant_ht: state.buyAmount });
|
|
536
556
|
window.open(r.url_souscription, '_blank');
|
|
537
557
|
hideModal('buy');
|
|
538
|
-
} catch (e) { console.error('
|
|
558
|
+
} catch (e) { console.error('Lucy: erreur achat', e); }
|
|
539
559
|
}
|
|
540
560
|
|
|
541
561
|
// ========================================================================
|
|
@@ -543,19 +563,19 @@
|
|
|
543
563
|
// ========================================================================
|
|
544
564
|
|
|
545
565
|
function showModal(name) {
|
|
546
|
-
if (name === 'buy') {
|
|
547
|
-
|
|
566
|
+
if (name === 'buy' && el.modalBuy) {
|
|
567
|
+
el.modalBuy.style.display = '';
|
|
548
568
|
updateBuyModal();
|
|
549
|
-
} else if (name === 'feedback') {
|
|
550
|
-
|
|
569
|
+
} else if (name === 'feedback' && el.modalFeedback) {
|
|
570
|
+
el.modalFeedback.style.display = '';
|
|
551
571
|
state.feedbackDescription = '';
|
|
552
|
-
|
|
572
|
+
if (el.feedbackDesc) el.feedbackDesc.value = '';
|
|
553
573
|
}
|
|
554
574
|
}
|
|
555
575
|
|
|
556
576
|
function hideModal(name) {
|
|
557
|
-
if (name === 'buy')
|
|
558
|
-
else if (name === 'feedback')
|
|
577
|
+
if (name === 'buy' && el.modalBuy) el.modalBuy.style.display = 'none';
|
|
578
|
+
else if (name === 'feedback' && el.modalFeedback) el.modalFeedback.style.display = 'none';
|
|
559
579
|
}
|
|
560
580
|
|
|
561
581
|
// ========================================================================
|
|
@@ -564,9 +584,9 @@
|
|
|
564
584
|
|
|
565
585
|
function checkFirstLaunch() {
|
|
566
586
|
if (!localStorage.getItem('lucy_assist_guide_seen') && state.conversations.length === 0) {
|
|
567
|
-
setTimeout(()
|
|
568
|
-
if (state.conversations.length === 0) {
|
|
569
|
-
|
|
587
|
+
setTimeout(function () {
|
|
588
|
+
if (state.conversations.length === 0 && el.guideOverlay) {
|
|
589
|
+
el.guideOverlay.style.display = '';
|
|
570
590
|
openSidebar();
|
|
571
591
|
}
|
|
572
592
|
}, 2000);
|
|
@@ -575,12 +595,12 @@
|
|
|
575
595
|
|
|
576
596
|
function guideNext() {
|
|
577
597
|
state.guideStep = 2;
|
|
578
|
-
|
|
579
|
-
|
|
598
|
+
if (el.guideStep1) el.guideStep1.style.display = 'none';
|
|
599
|
+
if (el.guideStep2) el.guideStep2.style.display = '';
|
|
580
600
|
}
|
|
581
601
|
|
|
582
602
|
function guideFinish() {
|
|
583
|
-
|
|
603
|
+
if (el.guideOverlay) el.guideOverlay.style.display = 'none';
|
|
584
604
|
localStorage.setItem('lucy_assist_guide_seen', 'true');
|
|
585
605
|
}
|
|
586
606
|
|
|
@@ -589,18 +609,20 @@
|
|
|
589
609
|
// ========================================================================
|
|
590
610
|
|
|
591
611
|
function saveState() {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
612
|
+
try {
|
|
613
|
+
sessionStorage.setItem('lucy_assist_state', JSON.stringify({
|
|
614
|
+
isOpen: state.isOpen,
|
|
615
|
+
isExpanded: state.isExpanded,
|
|
616
|
+
currentConversationId: state.currentConversationId,
|
|
617
|
+
showHistory: state.showHistory,
|
|
618
|
+
showDoc: state.showDoc
|
|
619
|
+
}));
|
|
620
|
+
} catch (e) {}
|
|
599
621
|
}
|
|
600
622
|
|
|
601
623
|
function restoreState() {
|
|
602
624
|
try {
|
|
603
|
-
|
|
625
|
+
var s = JSON.parse(sessionStorage.getItem('lucy_assist_state'));
|
|
604
626
|
if (!s) return;
|
|
605
627
|
state.isOpen = s.isOpen || false;
|
|
606
628
|
state.isExpanded = s.isExpanded || false;
|
|
@@ -610,75 +632,73 @@
|
|
|
610
632
|
} catch (e) {}
|
|
611
633
|
}
|
|
612
634
|
|
|
613
|
-
// ========================================================================
|
|
614
|
-
// SEND BUTTON STATE
|
|
615
|
-
// ========================================================================
|
|
616
|
-
|
|
617
|
-
function updateSendBtn() {
|
|
618
|
-
els.btnSend.disabled = state.isLoading || !els.input.value.trim();
|
|
619
|
-
}
|
|
620
|
-
|
|
621
635
|
// ========================================================================
|
|
622
636
|
// INIT
|
|
623
637
|
// ========================================================================
|
|
624
638
|
|
|
625
639
|
function init() {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
640
|
+
console.log('Lucy Assist: init');
|
|
641
|
+
|
|
642
|
+
// Cache des elements - tous optionnels
|
|
643
|
+
el.floatBtn = $('lucy-float-btn');
|
|
644
|
+
el.sidebar = $('lucy-sidebar');
|
|
645
|
+
el.tokens = $('lucy-tokens');
|
|
646
|
+
el.btnBuyCredits = $('lucy-btn-buy-credits');
|
|
647
|
+
el.btnExpand = $('lucy-btn-expand');
|
|
648
|
+
el.btnHistory = $('lucy-btn-history');
|
|
649
|
+
el.btnNew = $('lucy-btn-new');
|
|
650
|
+
el.btnDoc = $('lucy-btn-doc');
|
|
651
|
+
el.btnClose = $('lucy-btn-close');
|
|
652
|
+
el.btnSend = $('lucy-btn-send');
|
|
653
|
+
el.input = $('lucy-input');
|
|
654
|
+
el.messages = $('lucy-messages');
|
|
655
|
+
el.welcome = $('lucy-welcome');
|
|
656
|
+
el.suggestions = $('lucy-suggestions');
|
|
657
|
+
el.viewHistory = $('lucy-view-history');
|
|
658
|
+
el.viewDoc = $('lucy-view-doc');
|
|
659
|
+
el.viewChat = $('lucy-view-chat');
|
|
660
|
+
el.historyList = $('lucy-history-list');
|
|
661
|
+
el.feedbackContact = $('lucy-feedback-contact');
|
|
662
|
+
el.feedbackError = $('lucy-feedback-error');
|
|
663
|
+
el.modalBuy = $('lucy-modal-buy');
|
|
664
|
+
el.modalFeedback = $('lucy-modal-feedback');
|
|
665
|
+
el.buyAmount = $('lucy-buy-amount');
|
|
666
|
+
el.buyTokens = $('lucy-buy-tokens');
|
|
667
|
+
el.buyConvs = $('lucy-buy-convs');
|
|
668
|
+
el.buyCancel = $('lucy-buy-cancel');
|
|
669
|
+
el.buyConfirm = $('lucy-buy-confirm');
|
|
670
|
+
el.feedbackDesc = $('lucy-feedback-desc');
|
|
671
|
+
el.feedbackCancel = $('lucy-feedback-cancel');
|
|
672
|
+
el.feedbackConfirm = $('lucy-feedback-confirm');
|
|
673
|
+
el.guideOverlay = $('lucy-guide-overlay');
|
|
674
|
+
el.guideStep1 = $('lucy-guide-step1');
|
|
675
|
+
el.guideStep2 = $('lucy-guide-step2');
|
|
676
|
+
|
|
677
|
+
// Verifier que le root existe
|
|
678
|
+
if (!el.floatBtn && !el.sidebar) {
|
|
679
|
+
console.warn('Lucy Assist: elements DOM introuvables, abort.');
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
console.log('Lucy Assist: elements trouves, floatBtn=' + !!el.floatBtn + ', sidebar=' + !!el.sidebar);
|
|
667
684
|
|
|
668
685
|
// CSRF
|
|
669
|
-
|
|
686
|
+
var csrfEl = document.querySelector('[name=csrfmiddlewaretoken]');
|
|
687
|
+
csrfToken = (csrfEl && csrfEl.value) || getCookie('csrftoken') || '';
|
|
670
688
|
|
|
671
689
|
// Restaurer l'etat
|
|
672
690
|
restoreState();
|
|
673
691
|
|
|
674
692
|
// Appliquer l'etat initial
|
|
675
693
|
if (state.isOpen) {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
694
|
+
if (el.floatBtn) el.floatBtn.style.display = 'none';
|
|
695
|
+
if (el.sidebar) {
|
|
696
|
+
el.sidebar.style.display = '';
|
|
697
|
+
el.sidebar.classList.remove('hidden');
|
|
698
|
+
}
|
|
679
699
|
}
|
|
680
|
-
if (state.isExpanded) {
|
|
681
|
-
|
|
700
|
+
if (state.isExpanded && el.sidebar) {
|
|
701
|
+
el.sidebar.classList.add('expanded');
|
|
682
702
|
}
|
|
683
703
|
updateExpandIcons();
|
|
684
704
|
|
|
@@ -686,79 +706,76 @@
|
|
|
686
706
|
else if (state.showDoc) showView('doc');
|
|
687
707
|
else showView('chat');
|
|
688
708
|
|
|
689
|
-
// ---- EVENT LISTENERS ----
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
els.input.addEventListener('input', () => {
|
|
703
|
-
state.currentMessage = els.input.value;
|
|
704
|
-
els.input.style.height = 'auto';
|
|
705
|
-
els.input.style.height = Math.min(els.input.scrollHeight, 128) + 'px';
|
|
709
|
+
// ---- EVENT LISTENERS (tous avec null-safety via on()) ----
|
|
710
|
+
on(el.floatBtn, 'click', toggleSidebar);
|
|
711
|
+
on(el.btnExpand, 'click', toggleExpanded);
|
|
712
|
+
on(el.btnHistory, 'click', function () { showView(state.showHistory ? 'chat' : 'history'); });
|
|
713
|
+
on(el.btnNew, 'click', newConversation);
|
|
714
|
+
on(el.btnDoc, 'click', function () { showView(state.showDoc ? 'chat' : 'doc'); });
|
|
715
|
+
on(el.btnClose, 'click', closeSidebar);
|
|
716
|
+
|
|
717
|
+
function onInputChange() {
|
|
718
|
+
if (!el.input) return;
|
|
719
|
+
state.currentMessage = el.input.value;
|
|
720
|
+
el.input.style.height = 'auto';
|
|
721
|
+
el.input.style.height = Math.min(el.input.scrollHeight, 128) + 'px';
|
|
706
722
|
updateSendBtn();
|
|
707
|
-
}
|
|
708
|
-
|
|
723
|
+
}
|
|
724
|
+
on(el.input, 'input', onInputChange);
|
|
725
|
+
on(el.input, 'keyup', onInputChange);
|
|
726
|
+
on(el.input, 'change', onInputChange);
|
|
727
|
+
on(el.input, 'keydown', function (e) {
|
|
709
728
|
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
|
710
729
|
e.preventDefault();
|
|
711
|
-
sendMessage(
|
|
730
|
+
sendMessage(el.input.value);
|
|
712
731
|
}
|
|
732
|
+
// Mettre a jour le bouton sur la prochaine frame
|
|
733
|
+
setTimeout(updateSendBtn, 0);
|
|
713
734
|
});
|
|
714
735
|
|
|
715
|
-
|
|
716
|
-
els.btnSend.addEventListener('click', () => sendMessage(els.input.value));
|
|
736
|
+
on(el.btnSend, 'click', function () { sendMessage(el.input ? el.input.value : ''); });
|
|
717
737
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
els.btnFeedbackError.addEventListener('click', () => showModal('feedback'));
|
|
738
|
+
on($('lucy-btn-feedback-contact'), 'click', function () { showModal('feedback'); });
|
|
739
|
+
on($('lucy-btn-feedback-error'), 'click', function () { showModal('feedback'); });
|
|
721
740
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
state.buyAmount = parseInt(els.buyAmount.value) || 10;
|
|
741
|
+
on(el.btnBuyCredits, 'click', function () { showModal('buy'); });
|
|
742
|
+
on(el.buyCancel, 'click', function () { hideModal('buy'); });
|
|
743
|
+
on(el.buyConfirm, 'click', buyCredits);
|
|
744
|
+
on(el.buyAmount, 'input', function () {
|
|
745
|
+
state.buyAmount = parseInt(el.buyAmount.value) || 10;
|
|
728
746
|
updateBuyModal();
|
|
729
747
|
});
|
|
730
748
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
state.feedbackDescription = els.feedbackDesc.value;
|
|
749
|
+
on(el.feedbackCancel, 'click', function () { hideModal('feedback'); });
|
|
750
|
+
on(el.feedbackConfirm, 'click', sendFeedback);
|
|
751
|
+
on(el.feedbackDesc, 'input', function () {
|
|
752
|
+
state.feedbackDescription = el.feedbackDesc.value;
|
|
736
753
|
});
|
|
737
754
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
els.guideFinish.addEventListener('click', guideFinish);
|
|
755
|
+
on($('lucy-guide-skip'), 'click', guideFinish);
|
|
756
|
+
on($('lucy-guide-next'), 'click', guideNext);
|
|
757
|
+
on($('lucy-guide-finish'), 'click', guideFinish);
|
|
742
758
|
|
|
743
759
|
// Raccourcis clavier globaux
|
|
744
|
-
document.addEventListener('keydown', (e)
|
|
760
|
+
document.addEventListener('keydown', function (e) {
|
|
745
761
|
if (e.ctrlKey && e.key === 'k') { e.preventDefault(); toggleSidebar(); }
|
|
746
762
|
if (e.key === 'Escape' && state.isOpen) closeSidebar();
|
|
747
763
|
});
|
|
748
764
|
|
|
749
|
-
// Sauvegarder avant de quitter
|
|
750
765
|
window.addEventListener('beforeunload', saveState);
|
|
751
766
|
|
|
752
767
|
// Charger les donnees
|
|
753
768
|
loadTokenStatus();
|
|
754
|
-
loadConversations().then(()
|
|
769
|
+
loadConversations().then(function () {
|
|
755
770
|
if (state.currentConversationId) loadConversation(state.currentConversationId);
|
|
756
771
|
});
|
|
757
772
|
loadSuggestions();
|
|
758
773
|
checkFirstLaunch();
|
|
774
|
+
|
|
775
|
+
console.log('Lucy Assist: init complete');
|
|
759
776
|
}
|
|
760
777
|
|
|
761
|
-
// Lancer
|
|
778
|
+
// Lancer quand le DOM est pret
|
|
762
779
|
if (document.readyState === 'loading') {
|
|
763
780
|
document.addEventListener('DOMContentLoaded', init);
|
|
764
781
|
} else {
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
<div class="lucy-input-area">
|
|
92
92
|
<div class="lucy-input-wrapper">
|
|
93
93
|
<textarea id="lucy-input" placeholder="Tapez votre message..." class="lucy-input" rows="1"></textarea>
|
|
94
|
-
<button id="lucy-btn-send" class="lucy-send-btn"
|
|
94
|
+
<button id="lucy-btn-send" class="lucy-send-btn">
|
|
95
95
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/></svg>
|
|
96
96
|
</button>
|
|
97
97
|
</div>
|
|
File without changes
|
|
File without changes
|