django-lucy-assist 0.1.0__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.
Files changed (44) hide show
  1. django_lucy_assist-0.1.0.dist-info/METADATA +206 -0
  2. django_lucy_assist-0.1.0.dist-info/RECORD +44 -0
  3. django_lucy_assist-0.1.0.dist-info/WHEEL +5 -0
  4. django_lucy_assist-0.1.0.dist-info/top_level.txt +1 -0
  5. lucy_assist/__init__.py +11 -0
  6. lucy_assist/admin.py +22 -0
  7. lucy_assist/apps.py +10 -0
  8. lucy_assist/conf.py +103 -0
  9. lucy_assist/constantes.py +120 -0
  10. lucy_assist/context_processors.py +65 -0
  11. lucy_assist/migrations/0001_initial.py +92 -0
  12. lucy_assist/migrations/__init__.py +0 -0
  13. lucy_assist/models/__init__.py +14 -0
  14. lucy_assist/models/base.py +54 -0
  15. lucy_assist/models/configuration.py +175 -0
  16. lucy_assist/models/conversation.py +54 -0
  17. lucy_assist/models/message.py +45 -0
  18. lucy_assist/models/project_context_cache.py +213 -0
  19. lucy_assist/services/__init__.py +21 -0
  20. lucy_assist/services/bug_notification_service.py +183 -0
  21. lucy_assist/services/claude_service.py +417 -0
  22. lucy_assist/services/context_service.py +350 -0
  23. lucy_assist/services/crud_service.py +364 -0
  24. lucy_assist/services/gitlab_service.py +248 -0
  25. lucy_assist/services/project_context_service.py +412 -0
  26. lucy_assist/services/tool_executor_service.py +343 -0
  27. lucy_assist/services/tools_definition.py +229 -0
  28. lucy_assist/signals.py +25 -0
  29. lucy_assist/static/lucy_assist/css/lucy-assist.css +160 -0
  30. lucy_assist/static/lucy_assist/image/icon-lucy.png +0 -0
  31. lucy_assist/static/lucy_assist/js/lucy-assist.js +824 -0
  32. lucy_assist/templates/lucy_assist/chatbot_sidebar.html +419 -0
  33. lucy_assist/templates/lucy_assist/partials/documentation_content.html +107 -0
  34. lucy_assist/tests/__init__.py +0 -0
  35. lucy_assist/tests/factories/__init__.py +15 -0
  36. lucy_assist/tests/factories/lucy_assist_factories.py +109 -0
  37. lucy_assist/tests/test_lucy_assist.py +186 -0
  38. lucy_assist/urls.py +36 -0
  39. lucy_assist/utils/__init__.py +7 -0
  40. lucy_assist/utils/log_utils.py +59 -0
  41. lucy_assist/utils/message_utils.py +130 -0
  42. lucy_assist/utils/token_utils.py +87 -0
  43. lucy_assist/views/__init__.py +13 -0
  44. lucy_assist/views/api_views.py +595 -0
@@ -0,0 +1,419 @@
1
+ {% load static %}
2
+
3
+ {# Composant Lucy Assist - Sidebar Chatbot #}
4
+ {# Ce template est autonome et inclut ses propres ressources CSS/JS #}
5
+
6
+ {% if lucy_assist_enabled %}
7
+ {# CSS Lucy Assist #}
8
+ <link href="{% static 'lucy_assist/css/lucy-assist.css' %}" rel="stylesheet">
9
+
10
+ {# Script Lucy Assist - doit être chargé avant d'utiliser le composant Alpine #}
11
+ <script src="{% static 'lucy_assist/js/lucy-assist.js' %}"></script>
12
+
13
+ <div x-data="lucyAssist()" x-init="init()" @keydown.window="handleKeydown($event)">
14
+ {# Bouton flottant - masqué quand le chat est ouvert #}
15
+ <button
16
+ x-show="!isOpen"
17
+ @click="toggleSidebar()"
18
+ class="fixed right-4 bottom-4 z-50 btn btn-circle btn-lg btn-primary shadow-lg hover:scale-110 transition-transform"
19
+ :class="{ 'animate-pulse': hasNewMessage }"
20
+ title="Ouvrir Lucy (Ctrl+K)"
21
+ >
22
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
23
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
24
+ </svg>
25
+ </button>
26
+
27
+ {# Sidebar #}
28
+ <div
29
+ x-show="isOpen"
30
+ x-transition:enter="transition ease-out duration-300"
31
+ x-transition:enter-start="translate-x-full"
32
+ x-transition:enter-end="translate-x-0"
33
+ x-transition:leave="transition ease-in duration-200"
34
+ x-transition:leave-start="translate-x-0"
35
+ x-transition:leave-end="translate-x-full"
36
+ class="fixed right-0 top-0 h-full w-96 bg-base-100 shadow-2xl z-999 flex flex-col border-l border-base-300"
37
+ >
38
+ {# Header #}
39
+ <div class="navbar bg-primary text-primary-content px-4">
40
+ <div class="flex-1">
41
+ <span class="text-lg font-bold">
42
+ Lucy Assist
43
+ </span>
44
+ </div>
45
+ <div class="flex-none gap-1">
46
+ {# Bouton Historique #}
47
+ <button
48
+ @click="showHistory = !showHistory; showDoc = false"
49
+ class="btn btn-ghost btn-sm btn-circle"
50
+ :class="{ 'bg-primary-focus': showHistory }"
51
+ title="Historique des conversations"
52
+ >
53
+ <i class="fa-solid fa-clock-rotate-left"></i>
54
+ </button>
55
+
56
+ {# Bouton Nouvelle conversation #}
57
+ <button
58
+ @click="newConversation()"
59
+ class="btn btn-ghost btn-sm btn-circle"
60
+ title="Nouvelle conversation"
61
+ >
62
+ <i class="fa-solid fa-plus"></i>
63
+ </button>
64
+
65
+ {# Bouton Documentation #}
66
+ <button
67
+ @click="showDoc = !showDoc; showHistory = false"
68
+ class="btn btn-ghost btn-sm btn-circle"
69
+ :class="{ 'bg-primary-focus': showDoc }"
70
+ title="Documentation"
71
+ >
72
+ <i class="fa-solid fa-book"></i>
73
+ </button>
74
+
75
+ {# Bouton Fermer #}
76
+ <button
77
+ @click="closeSidebar()"
78
+ class="btn btn-ghost btn-sm btn-circle"
79
+ title="Fermer (Echap)"
80
+ >
81
+ <i class="fa-solid fa-xmark"></i>
82
+ </button>
83
+ </div>
84
+ </div>
85
+
86
+ {# Indicateur de crédits #}
87
+ <div class="px-4 py-2 bg-base-200 text-sm flex items-center justify-between">
88
+ <span class="text-base-content/70">
89
+ <i class="fa-solid fa-coins mr-1"></i>
90
+ <span x-text="formatTokens(tokensDisponibles)"></span> crédits
91
+ </span>
92
+ <button
93
+ x-show="tokensDisponibles < 100000"
94
+ @click="showBuyCredits = true"
95
+ class="btn btn-xs btn-warning"
96
+ >
97
+ <i class="fa-solid fa-cart-plus mr-1"></i>
98
+ Acheter
99
+ </button>
100
+ </div>
101
+
102
+ {# Contenu principal #}
103
+ <div class="flex-1 overflow-hidden flex flex-col">
104
+ {# Vue Historique #}
105
+ <template x-if="showHistory">
106
+ <div class="flex-1 overflow-y-auto p-4">
107
+ <h3 class="font-semibold mb-3">Historique des conversations</h3>
108
+ <template x-if="conversations.length === 0">
109
+ <p class="text-base-content/60 text-sm">Aucune conversation</p>
110
+ </template>
111
+ <div class="space-y-2">
112
+ <template x-for="conv in conversations" :key="conv.id">
113
+ <div
114
+ @click="loadConversation(conv.id)"
115
+ class="p-3 rounded-lg bg-base-200 hover:bg-base-300 cursor-pointer transition-colors"
116
+ :class="{ 'ring-2 ring-primary': currentConversationId === conv.id }"
117
+ >
118
+ <div class="font-medium text-sm truncate" x-text="conv.titre"></div>
119
+ <div class="text-xs text-base-content/60 mt-1">
120
+ <span x-text="formatDate(conv.created_date)"></span>
121
+ <span class="ml-2">
122
+ <i class="fa-solid fa-coins"></i>
123
+ <span x-text="conv.total_tokens"></span> tokens | <span x-text="formatTokenCost(conv.total_tokens)"></span>
124
+ </span>
125
+ </div>
126
+ <button
127
+ @click.stop="deleteConversation(conv.id)"
128
+ class="btn btn-ghost btn-xs text-error mt-1"
129
+ >
130
+ <i class="fa-solid fa-trash"></i>
131
+ </button>
132
+ </div>
133
+ </template>
134
+ </div>
135
+ </div>
136
+ </template>
137
+
138
+ {# Vue Documentation #}
139
+ <template x-if="showDoc">
140
+ <div class="flex-1 overflow-y-auto p-4">
141
+ {% include "lucy_assist/partials/documentation_content.html" %}
142
+ </div>
143
+ </template>
144
+
145
+ {# Vue Chat #}
146
+ <template x-if="!showHistory && !showDoc">
147
+ <div class="flex-1 flex flex-col overflow-hidden">
148
+ {# Zone de messages #}
149
+ <div
150
+ x-ref="messagesContainer"
151
+ class="flex-1 overflow-y-auto p-4 space-y-4"
152
+ >
153
+ {# Message de bienvenue / Suggestions #}
154
+ <template x-if="messages.length === 0">
155
+ <div class="text-center py-8">
156
+ <div class="text-4xl mb-4">
157
+ <img class="mx-auto w-20" src="{% static 'lucy_assist/image/icon-lucy.png' %}" alt="Lucy">
158
+ </div>
159
+ <h3 class="font-semibold text-lg mb-2">Bonjour ! Je suis Lucy</h3>
160
+ <p class="text-base-content/70 text-sm mb-4">
161
+ Comment puis-je vous aider aujourd'hui ?
162
+ </p>
163
+
164
+ {# Suggestions #}
165
+ <div class="space-y-2">
166
+ <template x-for="suggestion in suggestions" :key="suggestion">
167
+ <button
168
+ @click="sendMessage(suggestion)"
169
+ class="btn btn-sm btn-outline btn-block justify-start text-left"
170
+ >
171
+ <i class="fa-solid fa-lightbulb mr-2 text-warning"></i>
172
+ <span x-text="suggestion" class="truncate"></span>
173
+ </button>
174
+ </template>
175
+ </div>
176
+ </div>
177
+ </template>
178
+
179
+ {# Messages #}
180
+ <template x-for="(msg, index) in messages" :key="msg.id || index">
181
+ <div
182
+ class="chat"
183
+ :class="msg.repondant === 'UTILISATEUR' ? 'chat-end' : 'chat-start'"
184
+ >
185
+ <div class="chat-image avatar">
186
+ <div class="w-8 rounded-full flex items-center justify-center overflow-hidden">
187
+ <template x-if="msg.repondant === 'UTILISATEUR'">
188
+ {% if lucy_assist_config.avatar_url %}
189
+ <img src="{{ lucy_assist_config.avatar_url }}" alt="Utilisateur" class="w-full h-full object-cover">
190
+ {% else %}
191
+ <i class="fa-solid fa-user" style="font-size: 0.875rem;"></i>
192
+ {% endif %}
193
+ </template>
194
+ <template x-if="msg.repondant !== 'UTILISATEUR'">
195
+ <img src="{% static 'lucy_assist/image/icon-lucy.png' %}" alt="Lucy" class="w-full h-full object-cover">
196
+ </template>
197
+ </div>
198
+ </div>
199
+ <div
200
+ class="chat-bubble"
201
+ :class="msg.repondant === 'UTILISATEUR' ? 'chat-bubble-primary' : 'chat-bubble-neutral'"
202
+ x-html="msg.contenu ? formatMessage(msg.contenu) : '<span class=\'loading loading-dots loading-sm\'></span>'"
203
+ >
204
+ </div>
205
+ <div class="chat-footer opacity-50 text-xs">
206
+ <span x-text="formatTime(msg.created_date)"></span>
207
+ </div>
208
+ </div>
209
+ </template>
210
+ </div>
211
+
212
+ {# Bouton Feedback - Lucy n'a pas compris #}
213
+ <template x-if="lucyDidNotUnderstand && messages.length > 0">
214
+ <div class="px-4 py-2 bg-info/10 border-t border-info/30">
215
+ <button
216
+ @click="openFeedback()"
217
+ class="btn btn-sm btn-info btn-outline w-full"
218
+ >
219
+ <i class="fa-solid fa-headset mr-2"></i>
220
+ Contacter le chef de projet Revolucy
221
+ </button>
222
+ </div>
223
+ </template>
224
+
225
+ {# Bouton Feedback - Erreur technique #}
226
+ <template x-if="hasError && !lucyDidNotUnderstand && messages.length > 0">
227
+ <div class="px-4 py-2 bg-warning/10 border-t border-warning/30">
228
+ <button
229
+ @click="openFeedback()"
230
+ class="btn btn-sm btn-warning btn-outline w-full"
231
+ >
232
+ <i class="fa-solid fa-comment-medical mr-2"></i>
233
+ Souhaites-tu que j'informe Revolucy ?
234
+ </button>
235
+ </div>
236
+ </template>
237
+
238
+ {# Zone de saisie #}
239
+ <div class="p-4 border-t border-base-300 bg-base-100">
240
+ <div class="flex gap-2 items-end">
241
+ <textarea
242
+ x-model="currentMessage"
243
+ x-ref="messageInput"
244
+ :disabled="isLoading"
245
+ @keydown.ctrl.enter.prevent="sendCurrentMessage()"
246
+ @keydown.meta.enter.prevent="sendCurrentMessage()"
247
+ placeholder="Tapez votre message..."
248
+ class="textarea textarea-bordered flex-1 textarea-sm min-h-[2.5rem] max-h-32 resize-none"
249
+ rows="1"
250
+ @input="$el.style.height = 'auto'; $el.style.height = Math.min($el.scrollHeight, 128) + 'px'"
251
+ ></textarea>
252
+ <button
253
+ type="button"
254
+ @click="sendCurrentMessage()"
255
+ :disabled="isLoading || !currentMessage.trim()"
256
+ class="btn btn-primary btn-sm"
257
+ >
258
+ <i class="fa-solid fa-paper-plane"></i>
259
+ </button>
260
+ </div>
261
+ <p class="text-xs text-base-content/50 mt-2 text-center">
262
+ Ctrl+Entrée pour envoyer
263
+ </p>
264
+ </div>
265
+ </div>
266
+ </template>
267
+ </div>
268
+ </div>
269
+
270
+ {# Modal Achat de crédits #}
271
+ <div
272
+ x-show="showBuyCredits"
273
+ class="modal modal-open"
274
+ @keydown.escape="showBuyCredits = false"
275
+ >
276
+ <div class="modal-box">
277
+ <h3 class="font-bold text-lg mb-4">
278
+ <i class="fa-solid fa-cart-plus mr-2"></i>
279
+ Acheter des crédits
280
+ </h3>
281
+
282
+ <div class="form-control mb-4">
283
+ <label class="label">
284
+ <span class="label-text">Montant (EUR HT)</span>
285
+ </label>
286
+ <input
287
+ type="number"
288
+ x-model.number="buyAmount"
289
+ min="10"
290
+ step="10"
291
+ class="input input-bordered"
292
+ />
293
+ </div>
294
+
295
+ <div class="bg-base-200 rounded-lg p-4 mb-4">
296
+ <div class="flex justify-between mb-2">
297
+ <span>Tokens à recevoir</span>
298
+ <span class="font-bold" x-text="formatTokens(calculateTokens(buyAmount))"></span>
299
+ </div>
300
+ <div class="flex justify-between text-sm text-base-content/70">
301
+ <span>Conversations estimées</span>
302
+ <span x-text="Math.floor(calculateTokens(buyAmount) / 2000)"></span>
303
+ </div>
304
+ </div>
305
+
306
+ <div class="alert alert-info text-sm mb-4">
307
+ <i class="fa-solid fa-info-circle"></i>
308
+ <span>10 EUR = 1 million de tokens = ~500 conversations</span>
309
+ </div>
310
+
311
+ <div class="modal-action">
312
+ <button @click="showBuyCredits = false" class="btn btn-ghost">
313
+ Annuler
314
+ </button>
315
+ <button @click="buyCredits()" class="btn btn-primary" :disabled="buyAmount < 10">
316
+ <i class="fa-solid fa-credit-card mr-2"></i>
317
+ Acheter
318
+ </button>
319
+ </div>
320
+ </div>
321
+ <div class="modal-backdrop" @click="showBuyCredits = false"></div>
322
+ </div>
323
+
324
+ {# Modal Feedback #}
325
+ <div
326
+ x-show="showFeedback"
327
+ class="modal modal-open"
328
+ @keydown.escape="closeFeedback()"
329
+ >
330
+ <div class="modal-box">
331
+ <h3 class="font-bold text-lg mb-4">
332
+ <i class="fa-solid fa-comment-medical mr-2 text-warning"></i>
333
+ Signaler un problème
334
+ </h3>
335
+
336
+ <div class="alert alert-info text-sm mb-4">
337
+ <i class="fa-solid fa-info-circle"></i>
338
+ <span>Votre conversation sera envoyée à l'équipe Revolucy pour analyse et amélioration de Lucy.</span>
339
+ </div>
340
+
341
+ <div class="form-control mb-4">
342
+ <label class="label">
343
+ <span class="label-text">Description du problème (optionnel)</span>
344
+ </label>
345
+ <textarea
346
+ x-model="feedbackDescription"
347
+ class="textarea textarea-bordered h-24" style="width: 100%;margin-top: 10px;"
348
+ placeholder="Décrivez ce qui n'a pas fonctionné ou ce que vous attendiez..."
349
+ ></textarea>
350
+ </div>
351
+
352
+ <div class="modal-action">
353
+ <button @click="closeFeedback()" class="btn btn-ghost" :disabled="feedbackSending">
354
+ Annuler
355
+ </button>
356
+ <button
357
+ @click="sendFeedback()"
358
+ class="btn btn-warning"
359
+ :disabled="feedbackSending"
360
+ >
361
+ <span x-show="!feedbackSending">
362
+ <i class="fa-solid fa-paper-plane mr-2"></i>
363
+ Envoyer le feedback
364
+ </span>
365
+ <span x-show="feedbackSending" class="loading loading-spinner loading-sm"></span>
366
+ </button>
367
+ </div>
368
+ </div>
369
+ <div class="modal-backdrop" @click="closeFeedback()"></div>
370
+ </div>
371
+
372
+ {# Guide interactif (premier lancement) #}
373
+ <template x-if="showGuide">
374
+ <div class="fixed inset-0 z-50 bg-black/50">
375
+ <div
376
+ x-show="guideStep === 1"
377
+ class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-base-100 rounded-xl p-6 max-w-md shadow-2xl"
378
+ >
379
+ <h3 class="text-xl font-bold mb-4">
380
+ <i class="fa-solid fa-hand-wave text-warning mr-2"></i>
381
+ Bienvenue sur Lucy Assist !
382
+ </h3>
383
+ <p class="mb-4">
384
+ Je suis votre assistant intelligent. Je peux vous aider à :
385
+ </p>
386
+ <ul class="list-disc list-inside mb-4 space-y-1 text-sm">
387
+ <li>Naviguer dans l'application</li>
388
+ <li>Rechercher des informations</li>
389
+ <li>Créer et modifier des éléments</li>
390
+ <li>Comprendre les fonctionnalités</li>
391
+ </ul>
392
+ <div class="flex justify-end gap-2">
393
+ <button @click="skipGuide()" class="btn btn-ghost btn-sm">
394
+ Passer
395
+ </button>
396
+ <button @click="nextGuideStep()" class="btn btn-primary btn-sm">
397
+ Suivant
398
+ </button>
399
+ </div>
400
+ </div>
401
+
402
+ <div
403
+ x-show="guideStep === 2"
404
+ class="absolute bottom-20 right-4 bg-base-100 rounded-xl p-4 max-w-xs shadow-2xl"
405
+ >
406
+ <div class="absolute -bottom-2 right-8 w-4 h-4 bg-base-100 transform rotate-45"></div>
407
+ <p class="text-sm mb-3">
408
+ Cliquez sur ce bouton ou utilisez <kbd class="kbd kbd-sm">Ctrl+K</kbd> pour ouvrir Lucy Assist à tout moment.
409
+ </p>
410
+ <div class="flex justify-end">
411
+ <button @click="finishGuide()" class="btn btn-primary btn-sm">
412
+ Compris !
413
+ </button>
414
+ </div>
415
+ </div>
416
+ </div>
417
+ </template>
418
+ </div>
419
+ {% endif %}
@@ -0,0 +1,107 @@
1
+ {# Contenu de la documentation Lucy Assist #}
2
+
3
+ <div class="prose prose-sm max-w-none">
4
+ <h3 class="text-lg font-bold mb-4">
5
+ <i class="fa-solid fa-book mr-2 text-primary"></i>
6
+ Guide Lucy
7
+ </h3>
8
+
9
+ <div class="collapse collapse-arrow bg-base-200 mb-2">
10
+ <input type="checkbox" checked />
11
+ <div class="collapse-title font-medium">
12
+ <i class="fa-solid fa-compass mr-2"></i>
13
+ Aide contextuelle
14
+ </div>
15
+ <div class="collapse-content text-sm">
16
+ <p>Lucy détecte automatiquement la page où vous vous trouvez pour vous proposer une aide ciblée.</p>
17
+ </div>
18
+ </div>
19
+
20
+ <div class="collapse collapse-arrow bg-base-200 mb-2">
21
+ <input type="checkbox" />
22
+ <div class="collapse-title font-medium">
23
+ <i class="fa-solid fa-search mr-2"></i>
24
+ Recherche intelligente
25
+ </div>
26
+ <div class="collapse-content text-sm">
27
+ <p>Trouvez rapidement n'importe quel élément en posant simplement la question.</p>
28
+ <p class="mt-2"><strong>Exemple :</strong> "Trouve-moi le client Dupont"</p>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="collapse collapse-arrow bg-base-200 mb-2">
33
+ <input type="checkbox" />
34
+ <div class="collapse-title font-medium">
35
+ <i class="fa-solid fa-plus-circle mr-2"></i>
36
+ Création assistée
37
+ </div>
38
+ <div class="collapse-content text-sm">
39
+ <p>Créez de nouveaux éléments en discutant avec Lucy qui vous guide étape par étape.</p>
40
+ <p class="mt-2"><strong>Exemple :</strong> "Crée un nouveau membre nommé Jean Martin"</p>
41
+ </div>
42
+ </div>
43
+
44
+ <div class="collapse collapse-arrow bg-base-200 mb-2">
45
+ <input type="checkbox" />
46
+ <div class="collapse-title font-medium">
47
+ <i class="fa-solid fa-bug mr-2"></i>
48
+ Analyse de bugs
49
+ </div>
50
+ <div class="collapse-content text-sm">
51
+ <p>Signalez un problème et Lucy analysera pour détecter d'éventuels bugs.</p>
52
+ <p class="mt-2"><strong>Exemple :</strong> "Je n'arrive pas à enregistrer le formulaire, j'ai une erreur"</p>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="collapse collapse-arrow bg-base-200 mb-2">
57
+ <input type="checkbox" />
58
+ <div class="collapse-title font-medium">
59
+ <i class="fa-solid fa-keyboard mr-2"></i>
60
+ Raccourcis clavier
61
+ </div>
62
+ <div class="collapse-content text-sm">
63
+ <ul class="list-none space-y-2">
64
+ <li>
65
+ <kbd class="kbd kbd-sm">Ctrl + K</kbd>
66
+ <span class="ml-2">Ouvrir Lucy</span>
67
+ </li>
68
+ <li>
69
+ <kbd class="kbd kbd-sm">Echap</kbd>
70
+ <span class="ml-2">Fermer Lucy</span>
71
+ </li>
72
+ <li>
73
+ <kbd class="kbd kbd-sm">Entrée</kbd>
74
+ <span class="ml-2">Envoyer le message</span>
75
+ </li>
76
+ </ul>
77
+ </div>
78
+ </div>
79
+
80
+ <div class="divider"></div>
81
+
82
+ <h4 class="font-bold mb-2">
83
+ <i class="fa-solid fa-coins mr-2 text-warning"></i>
84
+ Tarification
85
+ </h4>
86
+
87
+ <div class="stats stats-vertical shadow w-full bg-base-200">
88
+ <div class="stat">
89
+ <div class="stat-title">Prix par million de tokens</div>
90
+ <div class="stat-value text-primary">10 EUR</div>
91
+ </div>
92
+ <div class="stat">
93
+ <div class="stat-title">Tokens moyens par conversation</div>
94
+ <div class="stat-value text-secondary">2 000</div>
95
+ </div>
96
+ <div class="stat">
97
+ <div class="stat-title">Conversations pour 10 EUR</div>
98
+ <div class="stat-value text-accent">~500</div>
99
+ <div class="stat-desc">Estimation basée sur l'usage moyen</div>
100
+ </div>
101
+ </div>
102
+
103
+ <div class="alert alert-info mt-4 text-xs">
104
+ <i class="fa-solid fa-info-circle"></i>
105
+ <span>Le nombre de tokens utilisés dépend de la complexité de vos questions et des réponses générées.</span>
106
+ </div>
107
+ </div>
File without changes
@@ -0,0 +1,15 @@
1
+ from .lucy_assist_factories import (
2
+ ConfigurationLucyAssistFactory,
3
+ ConversationFactory,
4
+ MessageUtilisateurFactory,
5
+ MessageChatbotFactory,
6
+ ConversationAvecMessagesFactory,
7
+ )
8
+
9
+ __all__ = [
10
+ 'ConfigurationLucyAssistFactory',
11
+ 'ConversationFactory',
12
+ 'MessageUtilisateurFactory',
13
+ 'MessageChatbotFactory',
14
+ 'ConversationAvecMessagesFactory',
15
+ ]
@@ -0,0 +1,109 @@
1
+ """
2
+ Factories pour les tests Lucy Assist.
3
+ Utilise factory_boy avec faker pour générer des données réalistes.
4
+ """
5
+ import factory
6
+ from factory.django import DjangoModelFactory
7
+ from faker import Faker
8
+
9
+ from lucy_assist.models import Conversation, Message, ConfigurationLucyAssist
10
+ from lucy_assist.constantes import LucyAssistConstantes
11
+
12
+ fake = Faker('fr_FR')
13
+
14
+
15
+ class ConfigurationLucyAssistFactory(DjangoModelFactory):
16
+ """Factory pour créer une configuration Lucy Assist."""
17
+
18
+ class Meta:
19
+ model = ConfigurationLucyAssist
20
+ django_get_or_create = ('id',)
21
+
22
+ id = 1 # Singleton
23
+ tokens_disponibles = factory.LazyFunction(lambda: fake.random_int(min=100000, max=10000000))
24
+ prix_par_million_tokens = 10.0
25
+ questions_frequentes = factory.LazyFunction(lambda: [
26
+ "Comment créer un nouveau membre ?",
27
+ "Comment effectuer un paiement ?",
28
+ "Comment exporter des données ?",
29
+ ])
30
+ actif = True
31
+
32
+
33
+ class ConversationFactory(DjangoModelFactory):
34
+ """Factory pour créer des conversations."""
35
+
36
+ class Meta:
37
+ model = Conversation
38
+
39
+ utilisateur = factory.SubFactory('apps.utilisateur.tests.factories.UtilisateurFactory')
40
+ titre = factory.LazyFunction(lambda: fake.sentence(nb_words=5))
41
+ page_contexte = factory.LazyFunction(lambda: f"/{fake.word()}/{fake.word()}/list")
42
+ is_active = True
43
+
44
+
45
+ class MessageUtilisateurFactory(DjangoModelFactory):
46
+ """Factory pour créer des messages utilisateur."""
47
+
48
+ class Meta:
49
+ model = Message
50
+
51
+ conversation = factory.SubFactory(ConversationFactory)
52
+ repondant = LucyAssistConstantes.Repondant.UTILISATEUR
53
+ contenu = factory.LazyFunction(lambda: fake.text(max_nb_chars=200))
54
+ tokens_utilises = 0
55
+ type_action = None
56
+ metadata = factory.LazyFunction(dict)
57
+
58
+
59
+ class MessageChatbotFactory(DjangoModelFactory):
60
+ """Factory pour créer des messages chatbot."""
61
+
62
+ class Meta:
63
+ model = Message
64
+
65
+ conversation = factory.SubFactory(ConversationFactory)
66
+ repondant = LucyAssistConstantes.Repondant.CHATBOT
67
+ contenu = factory.LazyFunction(lambda: fake.text(max_nb_chars=500))
68
+ tokens_utilises = factory.LazyFunction(lambda: fake.random_int(min=100, max=2000))
69
+ type_action = factory.LazyFunction(
70
+ lambda: fake.random_element([
71
+ LucyAssistConstantes.TypeAction.AIDE_NAVIGATION,
72
+ LucyAssistConstantes.TypeAction.RECHERCHE,
73
+ LucyAssistConstantes.TypeAction.EXPLICATION,
74
+ ])
75
+ )
76
+ metadata = factory.LazyFunction(dict)
77
+
78
+
79
+ class ConversationAvecMessagesFactory(ConversationFactory):
80
+ """Factory pour créer une conversation avec plusieurs messages."""
81
+
82
+ @factory.post_generation
83
+ def messages(self, create, extracted, **kwargs):
84
+ if not create:
85
+ return
86
+
87
+ if extracted:
88
+ # Si des messages sont passés, les utiliser
89
+ for message in extracted:
90
+ message.conversation = self
91
+ message.save()
92
+ else:
93
+ # Créer une conversation type avec échanges
94
+ MessageUtilisateurFactory(
95
+ conversation=self,
96
+ contenu="Comment créer un nouveau membre ?"
97
+ )
98
+ MessageChatbotFactory(
99
+ conversation=self,
100
+ contenu="Pour créer un nouveau membre, suivez ces étapes..."
101
+ )
102
+ MessageUtilisateurFactory(
103
+ conversation=self,
104
+ contenu="Merci, et comment lui ajouter une adhésion ?"
105
+ )
106
+ MessageChatbotFactory(
107
+ conversation=self,
108
+ contenu="Pour ajouter une adhésion, rendez-vous sur..."
109
+ )