django-lucy-assist 1.2.6__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.6.dist-info → django_lucy_assist-1.2.8.dist-info}/METADATA +1 -1
- {django_lucy_assist-1.2.6.dist-info → django_lucy_assist-1.2.8.dist-info}/RECORD +8 -8
- lucy_assist/__init__.py +1 -1
- lucy_assist/static/lucy_assist/js/lucy-assist.js +708 -759
- lucy_assist/templates/lucy_assist/chatbot_sidebar.html +120 -366
- lucy_assist/templates/lucy_assist/partials/documentation_content.html +8 -16
- {django_lucy_assist-1.2.6.dist-info → django_lucy_assist-1.2.8.dist-info}/WHEEL +0 -0
- {django_lucy_assist-1.2.6.dist-info → django_lucy_assist-1.2.8.dist-info}/top_level.txt +0 -0
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lucy Assist -
|
|
3
|
-
*
|
|
4
|
-
* Ce fichier contient toute la logique client du chatbot Lucy Assist.
|
|
5
|
-
* Il utilise Alpine.js pour la réactivité et communique avec le backend Django via des API REST.
|
|
2
|
+
* Lucy Assist - Chatbot en vanilla JavaScript
|
|
3
|
+
* Zero dependance externe - pas d'Alpine.js, pas de Font Awesome
|
|
6
4
|
*/
|
|
5
|
+
(function () {
|
|
6
|
+
'use strict';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
// ========================================================================
|
|
9
|
+
// ETAT
|
|
10
|
+
// ========================================================================
|
|
11
|
+
const state = {
|
|
11
12
|
isOpen: false,
|
|
12
13
|
isExpanded: false,
|
|
13
14
|
isLoading: false,
|
|
14
15
|
showHistory: false,
|
|
15
16
|
showDoc: false,
|
|
16
|
-
showBuyCredits: false,
|
|
17
|
-
showFeedback: false,
|
|
18
|
-
showGuide: false,
|
|
19
|
-
guideStep: 1,
|
|
20
17
|
hasNewMessage: false,
|
|
21
18
|
hasError: false,
|
|
22
19
|
lucyDidNotUnderstand: false,
|
|
23
|
-
|
|
24
|
-
// Données
|
|
25
20
|
currentMessage: '',
|
|
26
21
|
messages: [],
|
|
27
22
|
conversations: [],
|
|
@@ -31,805 +26,759 @@ function lucyAssist() {
|
|
|
31
26
|
buyAmount: 10,
|
|
32
27
|
feedbackDescription: '',
|
|
33
28
|
feedbackSending: false,
|
|
29
|
+
guideStep: 1,
|
|
30
|
+
};
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* Initialisation du composant
|
|
41
|
-
*/
|
|
42
|
-
init() {
|
|
43
|
-
// Récupérer le token CSRF
|
|
44
|
-
this.csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value ||
|
|
45
|
-
this.getCookie('csrftoken');
|
|
46
|
-
|
|
47
|
-
// Restaurer l'état depuis sessionStorage
|
|
48
|
-
this.restoreState();
|
|
49
|
-
|
|
50
|
-
// Charger les données initiales
|
|
51
|
-
this.loadTokenStatus();
|
|
52
|
-
this.loadConversations().then(() => {
|
|
53
|
-
// Recharger la conversation en cours si elle existe
|
|
54
|
-
if (this.currentConversationId) {
|
|
55
|
-
this.loadConversation(this.currentConversationId);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
this.loadSuggestions();
|
|
59
|
-
|
|
60
|
-
// Vérifier si c'est le premier lancement
|
|
61
|
-
this.checkFirstLaunch();
|
|
62
|
-
|
|
63
|
-
// Sauvegarder l'état quand isOpen change
|
|
64
|
-
this.$watch('isOpen', (value) => {
|
|
65
|
-
this.saveState();
|
|
66
|
-
if (value) {
|
|
67
|
-
this.$nextTick(() => {
|
|
68
|
-
this.$refs.messageInput?.focus();
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
});
|
|
32
|
+
// ========================================================================
|
|
33
|
+
// CONFIG
|
|
34
|
+
// ========================================================================
|
|
35
|
+
const API_BASE = '/lucy-assist/api';
|
|
36
|
+
let csrfToken = '';
|
|
72
37
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
38
|
+
// ========================================================================
|
|
39
|
+
// ELEMENTS DOM
|
|
40
|
+
// ========================================================================
|
|
41
|
+
const el = {};
|
|
77
42
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Sauvegarde l'état dans sessionStorage
|
|
86
|
-
*/
|
|
87
|
-
saveState() {
|
|
88
|
-
const state = {
|
|
89
|
-
isOpen: this.isOpen,
|
|
90
|
-
isExpanded: this.isExpanded,
|
|
91
|
-
currentConversationId: this.currentConversationId,
|
|
92
|
-
showHistory: this.showHistory,
|
|
93
|
-
showDoc: this.showDoc
|
|
94
|
-
};
|
|
95
|
-
sessionStorage.setItem('lucy_assist_state', JSON.stringify(state));
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Restaure l'état depuis sessionStorage
|
|
100
|
-
*/
|
|
101
|
-
restoreState() {
|
|
102
|
-
try {
|
|
103
|
-
const savedState = sessionStorage.getItem('lucy_assist_state');
|
|
104
|
-
if (savedState) {
|
|
105
|
-
const state = JSON.parse(savedState);
|
|
106
|
-
this.isOpen = state.isOpen || false;
|
|
107
|
-
this.isExpanded = state.isExpanded || false;
|
|
108
|
-
this.currentConversationId = state.currentConversationId || null;
|
|
109
|
-
this.showHistory = state.showHistory || false;
|
|
110
|
-
this.showDoc = state.showDoc || false;
|
|
111
|
-
}
|
|
112
|
-
} catch (e) {
|
|
113
|
-
console.error('Erreur restauration état Lucy Assist:', e);
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Gestion des raccourcis clavier
|
|
119
|
-
*/
|
|
120
|
-
handleKeydown(event) {
|
|
121
|
-
// Ctrl+K pour ouvrir
|
|
122
|
-
if (event.ctrlKey && event.key === 'k') {
|
|
123
|
-
event.preventDefault();
|
|
124
|
-
this.toggleSidebar();
|
|
125
|
-
}
|
|
43
|
+
function $(id) {
|
|
44
|
+
return document.getElementById(id);
|
|
45
|
+
}
|
|
126
46
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Toggle du sidebar
|
|
135
|
-
*/
|
|
136
|
-
toggleSidebar() {
|
|
137
|
-
this.isOpen = !this.isOpen;
|
|
138
|
-
if (this.isOpen) {
|
|
139
|
-
this.hasNewMessage = false;
|
|
140
|
-
}
|
|
141
|
-
this.saveState();
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Fermeture du sidebar
|
|
146
|
-
*/
|
|
147
|
-
closeSidebar() {
|
|
148
|
-
this.isOpen = false;
|
|
149
|
-
this.saveState();
|
|
150
|
-
this.showHistory = false;
|
|
151
|
-
this.showDoc = false;
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Toggle du mode agrandi (80% de la fenetre)
|
|
156
|
-
*/
|
|
157
|
-
toggleExpanded() {
|
|
158
|
-
this.isExpanded = !this.isExpanded;
|
|
159
|
-
this.saveState();
|
|
160
|
-
},
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Vérifie si c'est le premier lancement
|
|
164
|
-
*/
|
|
165
|
-
checkFirstLaunch() {
|
|
166
|
-
const hasSeenGuide = localStorage.getItem('lucy_assist_guide_seen');
|
|
167
|
-
if (!hasSeenGuide && this.conversations.length === 0) {
|
|
168
|
-
// Afficher le guide au premier chargement si pas de conversations
|
|
169
|
-
setTimeout(() => {
|
|
170
|
-
if (this.conversations.length === 0) {
|
|
171
|
-
this.showGuide = true;
|
|
172
|
-
this.isOpen = true;
|
|
173
|
-
}
|
|
174
|
-
}, 2000);
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Navigation dans le guide
|
|
180
|
-
*/
|
|
181
|
-
nextGuideStep() {
|
|
182
|
-
this.guideStep++;
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
skipGuide() {
|
|
186
|
-
this.finishGuide();
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
finishGuide() {
|
|
190
|
-
this.showGuide = false;
|
|
191
|
-
localStorage.setItem('lucy_assist_guide_seen', 'true');
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Charge le statut des tokens
|
|
196
|
-
*/
|
|
197
|
-
async loadTokenStatus() {
|
|
198
|
-
try {
|
|
199
|
-
const response = await this.apiGet('/tokens/status');
|
|
200
|
-
this.tokensDisponibles = response.tokens_disponibles || 0;
|
|
201
|
-
} catch (error) {
|
|
202
|
-
console.error('Erreur chargement tokens:', error);
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Charge la liste des conversations
|
|
208
|
-
*/
|
|
209
|
-
async loadConversations() {
|
|
210
|
-
try {
|
|
211
|
-
const response = await this.apiGet('/conversations');
|
|
212
|
-
this.conversations = response.conversations || [];
|
|
213
|
-
} catch (error) {
|
|
214
|
-
console.error('Erreur chargement conversations:', error);
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Charge les suggestions
|
|
220
|
-
*/
|
|
221
|
-
async loadSuggestions() {
|
|
222
|
-
try {
|
|
223
|
-
const response = await this.apiGet('/suggestions');
|
|
224
|
-
this.suggestions = response.suggestions || [];
|
|
225
|
-
} catch (error) {
|
|
226
|
-
console.error('Erreur chargement suggestions:', error);
|
|
227
|
-
// Suggestions par défaut
|
|
228
|
-
this.suggestions = [
|
|
229
|
-
"Comment créer un nouveau membre ?",
|
|
230
|
-
"Comment effectuer un paiement ?",
|
|
231
|
-
"Où trouver la liste des adhésions ?"
|
|
232
|
-
];
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Crée une nouvelle conversation
|
|
238
|
-
*/
|
|
239
|
-
async newConversation() {
|
|
240
|
-
try {
|
|
241
|
-
const response = await this.apiPost('/conversations', {
|
|
242
|
-
page_contexte: window.location.pathname
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
this.currentConversationId = response.id;
|
|
246
|
-
this.messages = [];
|
|
247
|
-
this.showHistory = false;
|
|
248
|
-
this.showDoc = false;
|
|
249
|
-
this.saveState();
|
|
250
|
-
|
|
251
|
-
// Recharger les conversations
|
|
252
|
-
await this.loadConversations();
|
|
253
|
-
|
|
254
|
-
} catch (error) {
|
|
255
|
-
console.error('Erreur création conversation:', error);
|
|
256
|
-
this.showToast('Erreur lors de la création de la conversation', 'error');
|
|
257
|
-
}
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Charge une conversation existante
|
|
262
|
-
*/
|
|
263
|
-
async loadConversation(conversationId) {
|
|
264
|
-
try {
|
|
265
|
-
const response = await this.apiGet(`/conversations/${conversationId}`);
|
|
266
|
-
|
|
267
|
-
this.currentConversationId = conversationId;
|
|
268
|
-
this.messages = response.messages || [];
|
|
269
|
-
this.showHistory = false;
|
|
270
|
-
this.saveState();
|
|
271
|
-
|
|
272
|
-
// Scroll vers le bas avec un petit délai pour laisser le DOM se mettre à jour
|
|
273
|
-
this.$nextTick(() => {
|
|
274
|
-
setTimeout(() => {
|
|
275
|
-
this.scrollToBottom();
|
|
276
|
-
}, 100);
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
} catch (error) {
|
|
280
|
-
console.error('Erreur chargement conversation:', error);
|
|
281
|
-
// Si la conversation n'existe plus, réinitialiser
|
|
282
|
-
this.currentConversationId = null;
|
|
283
|
-
this.messages = [];
|
|
284
|
-
this.saveState();
|
|
285
|
-
this.showToast('Erreur lors du chargement de la conversation', 'error');
|
|
286
|
-
}
|
|
287
|
-
},
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Supprime une conversation
|
|
291
|
-
*/
|
|
292
|
-
async deleteConversation(conversationId) {
|
|
293
|
-
if (!confirm('Êtes-vous sûr de vouloir supprimer cette conversation ?')) {
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
47
|
+
/** Attache un event listener si l'element existe */
|
|
48
|
+
function on(element, event, handler) {
|
|
49
|
+
if (element) element.addEventListener(event, handler);
|
|
50
|
+
}
|
|
296
51
|
|
|
297
|
-
|
|
298
|
-
|
|
52
|
+
// ========================================================================
|
|
53
|
+
// HELPERS
|
|
54
|
+
// ========================================================================
|
|
299
55
|
|
|
300
|
-
|
|
301
|
-
|
|
56
|
+
function getCookie(name) {
|
|
57
|
+
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
|
58
|
+
return match ? decodeURIComponent(match[2]) : null;
|
|
59
|
+
}
|
|
302
60
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
61
|
+
function formatTokens(tokens) {
|
|
62
|
+
if (tokens >= 1000000) return (tokens / 1000000).toFixed(1) + 'M';
|
|
63
|
+
if (tokens >= 1000) return (tokens / 1000).toFixed(0) + 'K';
|
|
64
|
+
return tokens.toString();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function formatTokenCost(tokens) {
|
|
68
|
+
return ((tokens / 1000000) * 10).toFixed(4) + '€';
|
|
69
|
+
}
|
|
308
70
|
|
|
309
|
-
|
|
71
|
+
function formatDate(dateString) {
|
|
72
|
+
const date = new Date(dateString);
|
|
73
|
+
const now = new Date();
|
|
74
|
+
const diff = now - date;
|
|
75
|
+
if (diff < 60000) return "À l'instant";
|
|
76
|
+
if (diff < 3600000) return 'Il y a ' + Math.floor(diff / 60000) + ' min';
|
|
77
|
+
if (date.toDateString() === now.toDateString())
|
|
78
|
+
return "Aujourd'hui à " + date.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
|
|
79
|
+
if (diff < 7 * 86400000)
|
|
80
|
+
return date.toLocaleDateString('fr-FR', { weekday: 'long', hour: '2-digit', minute: '2-digit' });
|
|
81
|
+
return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' });
|
|
82
|
+
}
|
|
310
83
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
*/
|
|
330
|
-
async sendMessage(message) {
|
|
331
|
-
if (this.isLoading) return;
|
|
332
|
-
|
|
333
|
-
// Réinitialiser le flag "Lucy n'a pas compris" pour le nouveau message
|
|
334
|
-
this.lucyDidNotUnderstand = false;
|
|
335
|
-
|
|
336
|
-
// Créer une conversation si nécessaire
|
|
337
|
-
if (!this.currentConversationId) {
|
|
338
|
-
await this.newConversation();
|
|
339
|
-
}
|
|
84
|
+
function formatTime(dateString) {
|
|
85
|
+
return new Date(dateString).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function escapeHtml(s) {
|
|
89
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function formatMessage(content) {
|
|
93
|
+
if (!content) return '';
|
|
94
|
+
var f = escapeHtml(content);
|
|
95
|
+
f = f.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
96
|
+
f = f.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
|
97
|
+
f = f.replace(/`(.*?)`/g, '<code style="background:rgba(0,0,0,.1);padding:0 4px;border-radius:3px;">$1</code>');
|
|
98
|
+
f = f.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" style="color:var(--lucy-primary,#6366f1);text-decoration:underline;">$1</a>');
|
|
99
|
+
f = f.replace(/\n/g, '<br>');
|
|
100
|
+
return f;
|
|
101
|
+
}
|
|
340
102
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
103
|
+
function checkIfLucyDidNotUnderstand(response) {
|
|
104
|
+
if (!response) return false;
|
|
105
|
+
var lower = response.toLowerCase();
|
|
106
|
+
var patterns = [
|
|
107
|
+
'je ne comprends pas', "je n'ai pas compris", 'pourriez-vous reformuler',
|
|
108
|
+
'pouvez-vous reformuler', 'pourriez-vous préciser', 'pouvez-vous préciser',
|
|
109
|
+
"je ne suis pas sûr de comprendre", "je n'ai pas accès", "je n'ai pas les outils",
|
|
110
|
+
'je ne peux pas', 'je ne suis pas en mesure', 'fonctionnalité non disponible',
|
|
111
|
+
'je ne dispose pas', 'mes outils actuels ne permettent pas', "je n'arrive pas à",
|
|
112
|
+
'impossible de réaliser', 'en dehors de mon périmètre', 'hors de mon champ',
|
|
113
|
+
'dépasse mes capacités', 'comment puis-je vous aider avec votre crm'
|
|
114
|
+
];
|
|
115
|
+
return patterns.some(function (p) { return lower.includes(p); });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ========================================================================
|
|
119
|
+
// API
|
|
120
|
+
// ========================================================================
|
|
121
|
+
|
|
122
|
+
async function apiGet(endpoint) {
|
|
123
|
+
var r = await fetch(API_BASE + endpoint, {
|
|
124
|
+
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }
|
|
125
|
+
});
|
|
126
|
+
if (!r.ok) { var e = await r.json(); e.status = r.status; throw e; }
|
|
127
|
+
return r.json();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function apiPost(endpoint, data) {
|
|
131
|
+
var r = await fetch(API_BASE + endpoint, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
|
|
134
|
+
body: JSON.stringify(data)
|
|
135
|
+
});
|
|
136
|
+
if (!r.ok) { var e = await r.json(); e.status = r.status; throw e; }
|
|
137
|
+
return r.json();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function apiDelete(endpoint) {
|
|
141
|
+
var r = await fetch(API_BASE + endpoint, {
|
|
142
|
+
method: 'DELETE',
|
|
143
|
+
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }
|
|
144
|
+
});
|
|
145
|
+
if (!r.ok) { var e = await r.json(); e.status = r.status; throw e; }
|
|
146
|
+
return r.json();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ========================================================================
|
|
150
|
+
// RENDU
|
|
151
|
+
// ========================================================================
|
|
152
|
+
|
|
153
|
+
function scrollToBottom() {
|
|
154
|
+
if (el.messages) el.messages.scrollTop = el.messages.scrollHeight;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function updateTokensDisplay() {
|
|
158
|
+
if (el.tokens) el.tokens.textContent = formatTokens(state.tokensDisponibles);
|
|
159
|
+
if (el.btnBuyCredits) el.btnBuyCredits.style.display = state.tokensDisponibles < 100000 ? '' : 'none';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function updateExpandIcons() {
|
|
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';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Affiche la bonne vue : chat / history / doc */
|
|
170
|
+
function showView(view) {
|
|
171
|
+
state.showHistory = view === 'history';
|
|
172
|
+
state.showDoc = view === 'doc';
|
|
173
|
+
|
|
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';
|
|
177
|
+
|
|
178
|
+
if (el.btnHistory) el.btnHistory.classList.toggle('active', state.showHistory);
|
|
179
|
+
if (el.btnDoc) el.btnDoc.classList.toggle('active', state.showDoc);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Rend la liste des conversations dans l'historique */
|
|
183
|
+
function renderHistoryList() {
|
|
184
|
+
if (!el.historyList) return;
|
|
185
|
+
|
|
186
|
+
if (state.conversations.length === 0) {
|
|
187
|
+
el.historyList.innerHTML = '<p class="lucy-history-empty">Aucune conversation</p>';
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
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) {
|
|
202
|
+
if (e.target.closest('[data-delete-id]')) return;
|
|
203
|
+
loadConversation(parseInt(item.dataset.convId));
|
|
355
204
|
});
|
|
205
|
+
});
|
|
206
|
+
el.historyList.querySelectorAll('[data-delete-id]').forEach(function (btn) {
|
|
207
|
+
btn.addEventListener('click', function (e) {
|
|
208
|
+
e.stopPropagation();
|
|
209
|
+
deleteConversation(parseInt(btn.dataset.deleteId));
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
356
213
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
214
|
+
/** Rend la zone de messages */
|
|
215
|
+
function renderMessages() {
|
|
216
|
+
if (!el.messages) return;
|
|
217
|
+
|
|
218
|
+
if (state.messages.length === 0) {
|
|
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(); });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
360
226
|
|
|
361
|
-
|
|
362
|
-
await this.apiPost(`/conversations/${this.currentConversationId}/messages`, {
|
|
363
|
-
contenu: message
|
|
364
|
-
});
|
|
227
|
+
if (el.welcome) el.welcome.style.display = 'none';
|
|
365
228
|
|
|
366
|
-
|
|
367
|
-
|
|
229
|
+
// Supprimer les anciens messages
|
|
230
|
+
el.messages.querySelectorAll('.lucy-message').forEach(function (m) { m.remove(); });
|
|
368
231
|
|
|
369
|
-
|
|
370
|
-
|
|
232
|
+
var avatarUrl = el.messages.dataset.avatarUrl || '';
|
|
233
|
+
var lucyIcon = el.messages.dataset.lucyIcon || '';
|
|
371
234
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// Marquer qu'il y a eu une erreur pour proposer le feedback
|
|
377
|
-
this.hasError = true;
|
|
378
|
-
this.showToast('Erreur lors de l\'envoi du message', 'error');
|
|
379
|
-
}
|
|
235
|
+
state.messages.forEach(function (msg) {
|
|
236
|
+
var isUser = msg.repondant === 'UTILISATEUR';
|
|
237
|
+
var div = document.createElement('div');
|
|
238
|
+
div.className = 'lucy-message ' + (isUser ? 'user' : 'bot');
|
|
380
239
|
|
|
381
|
-
|
|
382
|
-
|
|
240
|
+
var avatarHtml;
|
|
241
|
+
if (isUser) {
|
|
242
|
+
avatarHtml = avatarUrl
|
|
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>';
|
|
245
|
+
} else {
|
|
246
|
+
avatarHtml = lucyIcon
|
|
247
|
+
? '<img src="' + lucyIcon + '" alt="Lucy">'
|
|
248
|
+
: '🤖';
|
|
383
249
|
}
|
|
384
|
-
},
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Stream la réponse de Claude
|
|
388
|
-
*/
|
|
389
|
-
async streamChatCompletion() {
|
|
390
|
-
const url = `${this.apiBaseUrl}/conversations/${this.currentConversationId}/chat`;
|
|
391
|
-
|
|
392
|
-
// Index du message bot dans le tableau
|
|
393
|
-
const botMessageIndex = this.messages.length;
|
|
394
|
-
|
|
395
|
-
// Créer un placeholder pour la réponse
|
|
396
|
-
this.messages.push({
|
|
397
|
-
id: Date.now() + 1,
|
|
398
|
-
repondant: 'CHATBOT',
|
|
399
|
-
contenu: '',
|
|
400
|
-
created_date: new Date().toISOString(),
|
|
401
|
-
tokens_utilises: 0
|
|
402
|
-
});
|
|
403
250
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
headers: {
|
|
408
|
-
'Content-Type': 'application/json',
|
|
409
|
-
'X-CSRFToken': this.csrfToken
|
|
410
|
-
},
|
|
411
|
-
body: JSON.stringify({
|
|
412
|
-
page_contexte: window.location.pathname
|
|
413
|
-
})
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
if (!response.ok) {
|
|
417
|
-
const errorData = await response.json();
|
|
418
|
-
throw { status: response.status, ...errorData };
|
|
419
|
-
}
|
|
251
|
+
var bubbleContent = msg.contenu
|
|
252
|
+
? '<span>' + formatMessage(msg.contenu) + '</span>'
|
|
253
|
+
: '<span class="lucy-loading-dots"><span></span><span></span><span></span></span>';
|
|
420
254
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const { done, value } = await reader.read();
|
|
427
|
-
if (done) break;
|
|
428
|
-
|
|
429
|
-
const text = decoder.decode(value);
|
|
430
|
-
const lines = text.split('\n');
|
|
431
|
-
|
|
432
|
-
for (const line of lines) {
|
|
433
|
-
if (line.startsWith('data: ')) {
|
|
434
|
-
try {
|
|
435
|
-
const data = JSON.parse(line.slice(6));
|
|
436
|
-
|
|
437
|
-
if (data.type === 'content') {
|
|
438
|
-
fullContent += data.content;
|
|
439
|
-
// Forcer la réactivité Alpine en réassignant l'objet
|
|
440
|
-
this.messages[botMessageIndex] = {
|
|
441
|
-
...this.messages[botMessageIndex],
|
|
442
|
-
contenu: fullContent
|
|
443
|
-
};
|
|
444
|
-
this.$nextTick(() => this.scrollToBottom());
|
|
445
|
-
} else if (data.type === 'done') {
|
|
446
|
-
// Mise à jour finale avec l'ID et les tokens
|
|
447
|
-
this.messages[botMessageIndex] = {
|
|
448
|
-
...this.messages[botMessageIndex],
|
|
449
|
-
id: data.message_id,
|
|
450
|
-
tokens_utilises: data.tokens_utilises
|
|
451
|
-
};
|
|
452
|
-
// Mettre à jour les tokens
|
|
453
|
-
await this.loadTokenStatus();
|
|
454
|
-
} else if (data.type === 'error') {
|
|
455
|
-
throw new Error(data.error);
|
|
456
|
-
}
|
|
457
|
-
} catch (e) {
|
|
458
|
-
if (e.message) throw e;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
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>';
|
|
463
260
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
this.messages.splice(botMessageIndex, 1);
|
|
467
|
-
}
|
|
261
|
+
el.messages.appendChild(div);
|
|
262
|
+
});
|
|
468
263
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
264
|
+
// Feedback bars
|
|
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';
|
|
472
267
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
268
|
+
requestAnimationFrame(scrollToBottom);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Rend les suggestions */
|
|
272
|
+
function renderSuggestions() {
|
|
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); });
|
|
282
|
+
});
|
|
283
|
+
}
|
|
477
284
|
|
|
478
|
-
|
|
479
|
-
|
|
285
|
+
function updateBuyModal() {
|
|
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
|
+
}
|
|
480
290
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
291
|
+
function updateSendBtn() {
|
|
292
|
+
if (el.btnSend) el.btnSend.disabled = state.isLoading || !(el.input && el.input.value.trim());
|
|
293
|
+
}
|
|
484
294
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
'je ne comprends pas',
|
|
505
|
-
'je n\'ai pas compris',
|
|
506
|
-
'je n\'arrive pas à comprendre',
|
|
507
|
-
'pourriez-vous reformuler',
|
|
508
|
-
'pouvez-vous reformuler',
|
|
509
|
-
'pourriez-vous préciser',
|
|
510
|
-
'pouvez-vous préciser',
|
|
511
|
-
'je ne suis pas sûr de comprendre',
|
|
512
|
-
'je ne suis pas certain de comprendre',
|
|
513
|
-
'votre message n\'est pas clair',
|
|
514
|
-
'erreur de frappe',
|
|
515
|
-
'erreur de saisie',
|
|
516
|
-
'message incompréhensible',
|
|
517
|
-
'je n\'ai pas pu interpréter',
|
|
518
|
-
'que souhaitez-vous faire',
|
|
519
|
-
'que voulez-vous faire',
|
|
520
|
-
// Ne peut pas aider / pas accès
|
|
521
|
-
'je n\'ai pas accès',
|
|
522
|
-
'je n\'ai pas les outils',
|
|
523
|
-
'je n\'ai pas la possibilité',
|
|
524
|
-
'je ne peux pas',
|
|
525
|
-
'je ne suis pas en mesure',
|
|
526
|
-
'pas accès aux outils',
|
|
527
|
-
'outils nécessaires',
|
|
528
|
-
'fonctionnalité non disponible',
|
|
529
|
-
'cette fonctionnalité n\'est pas',
|
|
530
|
-
'je ne dispose pas',
|
|
531
|
-
'mes outils actuels ne permettent pas',
|
|
532
|
-
'mes outils ne permettent pas',
|
|
533
|
-
'je n\'arrive pas à',
|
|
534
|
-
'impossible de réaliser',
|
|
535
|
-
'je ne trouve pas',
|
|
536
|
-
'modèle non disponible',
|
|
537
|
-
'n\'existe pas dans',
|
|
538
|
-
// Questions de clarification multiples
|
|
539
|
-
'pouvez-vous me dire',
|
|
540
|
-
'pourriez-vous me préciser',
|
|
541
|
-
'solutions possibles',
|
|
542
|
-
// Hors du périmètre / redirection vers autres outils
|
|
543
|
-
'je suis spécialisée',
|
|
544
|
-
'je vous recommande de consulter',
|
|
545
|
-
'je vous conseille de',
|
|
546
|
-
'en dehors de mon périmètre',
|
|
547
|
-
'hors de mon champ',
|
|
548
|
-
'ne fait pas partie de mes compétences',
|
|
549
|
-
'dépasse mes capacités',
|
|
550
|
-
'assistants culinaires',
|
|
551
|
-
'chatgpt',
|
|
552
|
-
'google assistant',
|
|
553
|
-
'sites spécialisés',
|
|
554
|
-
'comment puis-je vous aider avec votre crm',
|
|
555
|
-
'y a-t-il quelque chose que vous souhaitez faire'
|
|
556
|
-
];
|
|
295
|
+
// ========================================================================
|
|
296
|
+
// ACTIONS
|
|
297
|
+
// ========================================================================
|
|
298
|
+
|
|
299
|
+
function openSidebar() {
|
|
300
|
+
state.isOpen = true;
|
|
301
|
+
state.hasNewMessage = false;
|
|
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
|
+
}
|
|
311
|
+
saveState();
|
|
312
|
+
setTimeout(function () { if (el.input) el.input.focus(); }, 300);
|
|
313
|
+
}
|
|
557
314
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
this.showFeedback = false;
|
|
574
|
-
this.feedbackDescription = '';
|
|
575
|
-
},
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* Envoie le feedback à Revolucy
|
|
579
|
-
*/
|
|
580
|
-
async sendFeedback() {
|
|
581
|
-
if (!this.currentConversationId) {
|
|
582
|
-
this.showToast('Aucune conversation à signaler', 'warning');
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
315
|
+
function closeSidebar() {
|
|
316
|
+
state.isOpen = false;
|
|
317
|
+
state.showHistory = false;
|
|
318
|
+
state.showDoc = false;
|
|
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 = '';
|
|
327
|
+
showView('chat');
|
|
328
|
+
saveState();
|
|
329
|
+
}
|
|
585
330
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const response = await this.apiPost('/feedback', {
|
|
590
|
-
conversation_id: this.currentConversationId,
|
|
591
|
-
description: this.feedbackDescription,
|
|
592
|
-
page_url: window.location.href
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
this.showToast(response.message || 'Feedback envoyé avec succès !', 'success');
|
|
596
|
-
this.closeFeedback();
|
|
597
|
-
this.hasError = false;
|
|
598
|
-
this.lucyDidNotUnderstand = false;
|
|
599
|
-
|
|
600
|
-
} catch (error) {
|
|
601
|
-
console.error('Erreur envoi feedback:', error);
|
|
602
|
-
this.showToast('Erreur lors de l\'envoi du feedback', 'error');
|
|
603
|
-
} finally {
|
|
604
|
-
this.feedbackSending = false;
|
|
605
|
-
}
|
|
606
|
-
},
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* Achat de crédits
|
|
610
|
-
*/
|
|
611
|
-
async buyCredits() {
|
|
612
|
-
if (this.buyAmount < 10) {
|
|
613
|
-
this.showToast('Le montant minimum est de 10 EUR', 'warning');
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
331
|
+
function toggleSidebar() {
|
|
332
|
+
state.isOpen ? closeSidebar() : openSidebar();
|
|
333
|
+
}
|
|
616
334
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
335
|
+
function toggleExpanded() {
|
|
336
|
+
state.isExpanded = !state.isExpanded;
|
|
337
|
+
if (el.sidebar) el.sidebar.classList.toggle('expanded', state.isExpanded);
|
|
338
|
+
updateExpandIcons();
|
|
339
|
+
saveState();
|
|
340
|
+
}
|
|
621
341
|
|
|
622
|
-
|
|
623
|
-
|
|
342
|
+
async function loadTokenStatus() {
|
|
343
|
+
try {
|
|
344
|
+
var r = await apiGet('/tokens/status');
|
|
345
|
+
state.tokensDisponibles = r.tokens_disponibles || 0;
|
|
346
|
+
updateTokensDisplay();
|
|
347
|
+
} catch (e) { console.error('Lucy: erreur tokens', e); }
|
|
348
|
+
}
|
|
624
349
|
|
|
625
|
-
|
|
626
|
-
|
|
350
|
+
async function loadConversations() {
|
|
351
|
+
try {
|
|
352
|
+
var r = await apiGet('/conversations');
|
|
353
|
+
state.conversations = r.conversations || [];
|
|
354
|
+
renderHistoryList();
|
|
355
|
+
} catch (e) { console.error('Lucy: erreur conversations', e); }
|
|
356
|
+
}
|
|
627
357
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* Formate un nombre de tokens
|
|
643
|
-
*/
|
|
644
|
-
formatTokens(tokens) {
|
|
645
|
-
if (tokens >= 1000000) {
|
|
646
|
-
return (tokens / 1000000).toFixed(1) + 'M';
|
|
647
|
-
} else if (tokens >= 1000) {
|
|
648
|
-
return (tokens / 1000).toFixed(0) + 'K';
|
|
649
|
-
}
|
|
650
|
-
return tokens.toString();
|
|
651
|
-
},
|
|
652
|
-
|
|
653
|
-
/**
|
|
654
|
-
* Calcule et formate le coût en euros pour un nombre de tokens
|
|
655
|
-
* Prix par défaut: 10€ par million de tokens
|
|
656
|
-
*/
|
|
657
|
-
formatTokenCost(tokens) {
|
|
658
|
-
const prixParMillion = 10; // 10€ par million de tokens
|
|
659
|
-
const cout = (tokens / 1000000) * prixParMillion;
|
|
660
|
-
return cout.toFixed(4) + '€';
|
|
661
|
-
},
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Formate une date
|
|
665
|
-
*/
|
|
666
|
-
formatDate(dateString) {
|
|
667
|
-
const date = new Date(dateString);
|
|
668
|
-
const now = new Date();
|
|
669
|
-
const diff = now - date;
|
|
670
|
-
|
|
671
|
-
// Moins d'une minute
|
|
672
|
-
if (diff < 60000) {
|
|
673
|
-
return 'À l\'instant';
|
|
674
|
-
}
|
|
358
|
+
async function loadSuggestions() {
|
|
359
|
+
try {
|
|
360
|
+
var r = await apiGet('/suggestions');
|
|
361
|
+
state.suggestions = r.suggestions || [];
|
|
362
|
+
} catch (e) {
|
|
363
|
+
state.suggestions = [
|
|
364
|
+
'Comment créer un nouveau membre ?',
|
|
365
|
+
'Comment effectuer un paiement ?',
|
|
366
|
+
'Où trouver la liste des adhésions ?'
|
|
367
|
+
];
|
|
368
|
+
}
|
|
369
|
+
renderSuggestions();
|
|
370
|
+
}
|
|
675
371
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
372
|
+
async function newConversation() {
|
|
373
|
+
try {
|
|
374
|
+
var r = await apiPost('/conversations', { page_contexte: window.location.pathname });
|
|
375
|
+
state.currentConversationId = r.id;
|
|
376
|
+
state.messages = [];
|
|
377
|
+
showView('chat');
|
|
378
|
+
renderMessages();
|
|
379
|
+
saveState();
|
|
380
|
+
await loadConversations();
|
|
381
|
+
} catch (e) { console.error('Lucy: erreur new conversation', e); }
|
|
382
|
+
}
|
|
681
383
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
384
|
+
async function loadConversation(id) {
|
|
385
|
+
try {
|
|
386
|
+
var r = await apiGet('/conversations/' + id);
|
|
387
|
+
state.currentConversationId = id;
|
|
388
|
+
state.messages = r.messages || [];
|
|
389
|
+
showView('chat');
|
|
390
|
+
renderMessages();
|
|
391
|
+
saveState();
|
|
392
|
+
setTimeout(scrollToBottom, 100);
|
|
393
|
+
} catch (e) {
|
|
394
|
+
console.error('Lucy: erreur load conversation', e);
|
|
395
|
+
state.currentConversationId = null;
|
|
396
|
+
state.messages = [];
|
|
397
|
+
renderMessages();
|
|
398
|
+
saveState();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
686
401
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
402
|
+
async function deleteConversation(id) {
|
|
403
|
+
if (!confirm('Êtes-vous sûr de vouloir supprimer cette conversation ?')) return;
|
|
404
|
+
try {
|
|
405
|
+
await apiDelete('/conversations/' + id);
|
|
406
|
+
await loadConversations();
|
|
407
|
+
if (state.currentConversationId === id) {
|
|
408
|
+
state.currentConversationId = null;
|
|
409
|
+
state.messages = [];
|
|
410
|
+
renderMessages();
|
|
690
411
|
}
|
|
412
|
+
} catch (e) { console.error('Lucy: erreur suppression', e); }
|
|
413
|
+
}
|
|
691
414
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
.replace(/\n/g, '<br>');
|
|
727
|
-
|
|
728
|
-
return formatted;
|
|
729
|
-
},
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* Scroll vers le bas de la zone de messages
|
|
733
|
-
*/
|
|
734
|
-
scrollToBottom() {
|
|
735
|
-
const container = this.$refs.messagesContainer;
|
|
736
|
-
if (container) {
|
|
737
|
-
container.scrollTop = container.scrollHeight;
|
|
738
|
-
}
|
|
739
|
-
},
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* Affiche un toast de notification
|
|
743
|
-
*/
|
|
744
|
-
showToast(message, type = 'info') {
|
|
745
|
-
// Utiliser le système de toast Alpine.js existant si disponible
|
|
746
|
-
if (window.Alpine && window.toastData) {
|
|
747
|
-
window.toastData.show(message, type);
|
|
415
|
+
async function sendMessage(message) {
|
|
416
|
+
if (state.isLoading || !message || !message.trim()) return;
|
|
417
|
+
|
|
418
|
+
state.lucyDidNotUnderstand = false;
|
|
419
|
+
|
|
420
|
+
if (!state.currentConversationId) await newConversation();
|
|
421
|
+
|
|
422
|
+
// Message utilisateur
|
|
423
|
+
state.messages.push({
|
|
424
|
+
id: Date.now(),
|
|
425
|
+
repondant: 'UTILISATEUR',
|
|
426
|
+
contenu: message,
|
|
427
|
+
created_date: new Date().toISOString(),
|
|
428
|
+
tokens_utilises: 0
|
|
429
|
+
});
|
|
430
|
+
state.currentMessage = '';
|
|
431
|
+
if (el.input) {
|
|
432
|
+
el.input.value = '';
|
|
433
|
+
el.input.style.height = 'auto';
|
|
434
|
+
}
|
|
435
|
+
updateSendBtn();
|
|
436
|
+
renderMessages();
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
state.isLoading = true;
|
|
440
|
+
if (el.input) el.input.disabled = true;
|
|
441
|
+
if (el.btnSend) el.btnSend.disabled = true;
|
|
442
|
+
|
|
443
|
+
await apiPost('/conversations/' + state.currentConversationId + '/messages', { contenu: message });
|
|
444
|
+
await streamChatCompletion();
|
|
445
|
+
} catch (e) {
|
|
446
|
+
console.error('Lucy: erreur envoi', e);
|
|
447
|
+
if (e.status === 402) {
|
|
448
|
+
showModal('buy');
|
|
748
449
|
} else {
|
|
749
|
-
|
|
750
|
-
|
|
450
|
+
state.hasError = true;
|
|
451
|
+
renderMessages();
|
|
751
452
|
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
453
|
+
} finally {
|
|
454
|
+
state.isLoading = false;
|
|
455
|
+
if (el.input) el.input.disabled = false;
|
|
456
|
+
updateSendBtn();
|
|
457
|
+
if (el.input) el.input.focus();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function streamChatCompletion() {
|
|
462
|
+
var url = API_BASE + '/conversations/' + state.currentConversationId + '/chat';
|
|
463
|
+
var botIdx = state.messages.length;
|
|
464
|
+
|
|
465
|
+
state.messages.push({
|
|
466
|
+
id: Date.now() + 1,
|
|
467
|
+
repondant: 'CHATBOT',
|
|
468
|
+
contenu: '',
|
|
469
|
+
created_date: new Date().toISOString(),
|
|
470
|
+
tokens_utilises: 0
|
|
471
|
+
});
|
|
472
|
+
renderMessages();
|
|
473
|
+
|
|
474
|
+
try {
|
|
475
|
+
var response = await fetch(url, {
|
|
476
|
+
method: 'POST',
|
|
477
|
+
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
|
|
478
|
+
body: JSON.stringify({ page_contexte: window.location.pathname })
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
if (!response.ok) { var err = await response.json(); err.status = response.status; throw err; }
|
|
482
|
+
|
|
483
|
+
var reader = response.body.getReader();
|
|
484
|
+
var decoder = new TextDecoder();
|
|
485
|
+
var fullContent = '';
|
|
486
|
+
|
|
487
|
+
while (true) {
|
|
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');
|
|
493
|
+
|
|
494
|
+
for (var i = 0; i < lines.length; i++) {
|
|
495
|
+
var line = lines[i];
|
|
496
|
+
if (!line.startsWith('data: ')) continue;
|
|
497
|
+
try {
|
|
498
|
+
var data = JSON.parse(line.slice(6));
|
|
499
|
+
if (data.type === 'content') {
|
|
500
|
+
fullContent += data.content;
|
|
501
|
+
state.messages[botIdx].contenu = fullContent;
|
|
502
|
+
renderMessages();
|
|
503
|
+
} else if (data.type === 'done') {
|
|
504
|
+
state.messages[botIdx].id = data.message_id;
|
|
505
|
+
state.messages[botIdx].tokens_utilises = data.tokens_utilises;
|
|
506
|
+
await loadTokenStatus();
|
|
507
|
+
} else if (data.type === 'error') {
|
|
508
|
+
throw new Error(data.error);
|
|
509
|
+
}
|
|
510
|
+
} catch (parseErr) { if (parseErr.message) throw parseErr; }
|
|
767
511
|
}
|
|
768
512
|
}
|
|
769
|
-
return cookieValue;
|
|
770
|
-
},
|
|
771
|
-
|
|
772
|
-
// Méthodes utilitaires pour les appels API
|
|
773
|
-
async apiGet(endpoint) {
|
|
774
|
-
const response = await fetch(`${this.apiBaseUrl}${endpoint}`, {
|
|
775
|
-
method: 'GET',
|
|
776
|
-
headers: {
|
|
777
|
-
'Content-Type': 'application/json',
|
|
778
|
-
'X-CSRFToken': this.csrfToken
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
513
|
|
|
782
|
-
if (!
|
|
783
|
-
|
|
784
|
-
throw { status: response.status, ...error };
|
|
514
|
+
if (!state.messages[botIdx] || !state.messages[botIdx].contenu) {
|
|
515
|
+
state.messages.splice(botIdx, 1);
|
|
785
516
|
}
|
|
786
517
|
|
|
787
|
-
|
|
788
|
-
|
|
518
|
+
state.lucyDidNotUnderstand = checkIfLucyDidNotUnderstand(
|
|
519
|
+
(state.messages[botIdx] && state.messages[botIdx].contenu) || ''
|
|
520
|
+
);
|
|
521
|
+
if (!state.lucyDidNotUnderstand) state.hasError = false;
|
|
522
|
+
|
|
523
|
+
renderMessages();
|
|
524
|
+
await loadConversations();
|
|
525
|
+
} catch (e) {
|
|
526
|
+
state.hasError = true;
|
|
527
|
+
if (botIdx < state.messages.length && state.messages[botIdx] && !state.messages[botIdx].contenu) {
|
|
528
|
+
state.messages.splice(botIdx, 1);
|
|
529
|
+
}
|
|
530
|
+
renderMessages();
|
|
531
|
+
throw e;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
789
534
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
535
|
+
async function sendFeedback() {
|
|
536
|
+
if (!state.currentConversationId) return;
|
|
537
|
+
state.feedbackSending = true;
|
|
538
|
+
try {
|
|
539
|
+
await apiPost('/feedback', {
|
|
540
|
+
conversation_id: state.currentConversationId,
|
|
541
|
+
description: state.feedbackDescription,
|
|
542
|
+
page_url: window.location.href
|
|
798
543
|
});
|
|
544
|
+
state.hasError = false;
|
|
545
|
+
state.lucyDidNotUnderstand = false;
|
|
546
|
+
hideModal('feedback');
|
|
547
|
+
renderMessages();
|
|
548
|
+
} catch (e) { console.error('Lucy: erreur feedback', e); }
|
|
549
|
+
finally { state.feedbackSending = false; }
|
|
550
|
+
}
|
|
799
551
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
552
|
+
async function buyCredits() {
|
|
553
|
+
if (state.buyAmount < 10) return;
|
|
554
|
+
try {
|
|
555
|
+
var r = await apiPost('/tokens/buy', { montant_ht: state.buyAmount });
|
|
556
|
+
window.open(r.url_souscription, '_blank');
|
|
557
|
+
hideModal('buy');
|
|
558
|
+
} catch (e) { console.error('Lucy: erreur achat', e); }
|
|
559
|
+
}
|
|
804
560
|
|
|
805
|
-
|
|
806
|
-
|
|
561
|
+
// ========================================================================
|
|
562
|
+
// MODALS
|
|
563
|
+
// ========================================================================
|
|
564
|
+
|
|
565
|
+
function showModal(name) {
|
|
566
|
+
if (name === 'buy' && el.modalBuy) {
|
|
567
|
+
el.modalBuy.style.display = '';
|
|
568
|
+
updateBuyModal();
|
|
569
|
+
} else if (name === 'feedback' && el.modalFeedback) {
|
|
570
|
+
el.modalFeedback.style.display = '';
|
|
571
|
+
state.feedbackDescription = '';
|
|
572
|
+
if (el.feedbackDesc) el.feedbackDesc.value = '';
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function hideModal(name) {
|
|
577
|
+
if (name === 'buy' && el.modalBuy) el.modalBuy.style.display = 'none';
|
|
578
|
+
else if (name === 'feedback' && el.modalFeedback) el.modalFeedback.style.display = 'none';
|
|
579
|
+
}
|
|
807
580
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
581
|
+
// ========================================================================
|
|
582
|
+
// GUIDE
|
|
583
|
+
// ========================================================================
|
|
584
|
+
|
|
585
|
+
function checkFirstLaunch() {
|
|
586
|
+
if (!localStorage.getItem('lucy_assist_guide_seen') && state.conversations.length === 0) {
|
|
587
|
+
setTimeout(function () {
|
|
588
|
+
if (state.conversations.length === 0 && el.guideOverlay) {
|
|
589
|
+
el.guideOverlay.style.display = '';
|
|
590
|
+
openSidebar();
|
|
814
591
|
}
|
|
815
|
-
});
|
|
592
|
+
}, 2000);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
816
595
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
596
|
+
function guideNext() {
|
|
597
|
+
state.guideStep = 2;
|
|
598
|
+
if (el.guideStep1) el.guideStep1.style.display = 'none';
|
|
599
|
+
if (el.guideStep2) el.guideStep2.style.display = '';
|
|
600
|
+
}
|
|
821
601
|
|
|
822
|
-
|
|
602
|
+
function guideFinish() {
|
|
603
|
+
if (el.guideOverlay) el.guideOverlay.style.display = 'none';
|
|
604
|
+
localStorage.setItem('lucy_assist_guide_seen', 'true');
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// ========================================================================
|
|
608
|
+
// STATE PERSISTENCE
|
|
609
|
+
// ========================================================================
|
|
610
|
+
|
|
611
|
+
function saveState() {
|
|
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) {}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function restoreState() {
|
|
624
|
+
try {
|
|
625
|
+
var s = JSON.parse(sessionStorage.getItem('lucy_assist_state'));
|
|
626
|
+
if (!s) return;
|
|
627
|
+
state.isOpen = s.isOpen || false;
|
|
628
|
+
state.isExpanded = s.isExpanded || false;
|
|
629
|
+
state.currentConversationId = s.currentConversationId || null;
|
|
630
|
+
state.showHistory = s.showHistory || false;
|
|
631
|
+
state.showDoc = s.showDoc || false;
|
|
632
|
+
} catch (e) {}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// ========================================================================
|
|
636
|
+
// INIT
|
|
637
|
+
// ========================================================================
|
|
638
|
+
|
|
639
|
+
function init() {
|
|
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;
|
|
823
681
|
}
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
682
|
|
|
827
|
-
|
|
828
|
-
|
|
683
|
+
console.log('Lucy Assist: elements trouves, floatBtn=' + !!el.floatBtn + ', sidebar=' + !!el.sidebar);
|
|
684
|
+
|
|
685
|
+
// CSRF
|
|
686
|
+
var csrfEl = document.querySelector('[name=csrfmiddlewaretoken]');
|
|
687
|
+
csrfToken = (csrfEl && csrfEl.value) || getCookie('csrftoken') || '';
|
|
688
|
+
|
|
689
|
+
// Restaurer l'etat
|
|
690
|
+
restoreState();
|
|
691
|
+
|
|
692
|
+
// Appliquer l'etat initial
|
|
693
|
+
if (state.isOpen) {
|
|
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
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (state.isExpanded && el.sidebar) {
|
|
701
|
+
el.sidebar.classList.add('expanded');
|
|
702
|
+
}
|
|
703
|
+
updateExpandIcons();
|
|
704
|
+
|
|
705
|
+
if (state.showHistory) showView('history');
|
|
706
|
+
else if (state.showDoc) showView('doc');
|
|
707
|
+
else showView('chat');
|
|
708
|
+
|
|
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';
|
|
722
|
+
updateSendBtn();
|
|
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) {
|
|
728
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
|
729
|
+
e.preventDefault();
|
|
730
|
+
sendMessage(el.input.value);
|
|
731
|
+
}
|
|
732
|
+
// Mettre a jour le bouton sur la prochaine frame
|
|
733
|
+
setTimeout(updateSendBtn, 0);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
on(el.btnSend, 'click', function () { sendMessage(el.input ? el.input.value : ''); });
|
|
737
|
+
|
|
738
|
+
on($('lucy-btn-feedback-contact'), 'click', function () { showModal('feedback'); });
|
|
739
|
+
on($('lucy-btn-feedback-error'), 'click', function () { showModal('feedback'); });
|
|
740
|
+
|
|
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;
|
|
746
|
+
updateBuyModal();
|
|
747
|
+
});
|
|
748
|
+
|
|
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;
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
on($('lucy-guide-skip'), 'click', guideFinish);
|
|
756
|
+
on($('lucy-guide-next'), 'click', guideNext);
|
|
757
|
+
on($('lucy-guide-finish'), 'click', guideFinish);
|
|
758
|
+
|
|
759
|
+
// Raccourcis clavier globaux
|
|
760
|
+
document.addEventListener('keydown', function (e) {
|
|
761
|
+
if (e.ctrlKey && e.key === 'k') { e.preventDefault(); toggleSidebar(); }
|
|
762
|
+
if (e.key === 'Escape' && state.isOpen) closeSidebar();
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
window.addEventListener('beforeunload', saveState);
|
|
766
|
+
|
|
767
|
+
// Charger les donnees
|
|
768
|
+
loadTokenStatus();
|
|
769
|
+
loadConversations().then(function () {
|
|
770
|
+
if (state.currentConversationId) loadConversation(state.currentConversationId);
|
|
771
|
+
});
|
|
772
|
+
loadSuggestions();
|
|
773
|
+
checkFirstLaunch();
|
|
774
|
+
|
|
775
|
+
console.log('Lucy Assist: init complete');
|
|
776
|
+
}
|
|
829
777
|
|
|
830
|
-
//
|
|
831
|
-
document.
|
|
832
|
-
|
|
833
|
-
|
|
778
|
+
// Lancer quand le DOM est pret
|
|
779
|
+
if (document.readyState === 'loading') {
|
|
780
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
781
|
+
} else {
|
|
782
|
+
init();
|
|
834
783
|
}
|
|
835
|
-
});
|
|
784
|
+
})();
|