django-lucy-assist 1.0.4__tar.gz → 1.0.6__tar.gz
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.0.4 → django_lucy_assist-1.0.6}/PKG-INFO +1 -9
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/README.md +0 -8
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/PKG-INFO +1 -9
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/SOURCES.txt +1 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/__init__.py +1 -1
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/conf.py +4 -4
- django_lucy_assist-1.0.6/lucy_assist/migrations/0002_configurationlucyassist_prompt_complementaire.py +18 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/context_service.py +111 -21
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/crud_service.py +103 -27
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/tools_definition.py +6 -7
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/pyproject.toml +1 -1
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/MANIFEST.in +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/dependency_links.txt +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/requires.txt +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/top_level.txt +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/admin.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/apps.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/constantes.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/context_processors.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/migrations/0001_initial.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/migrations/__init__.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/__init__.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/base.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/configuration.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/conversation.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/message.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/project_context_cache.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/__init__.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/bug_notification_service.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/claude_service.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/gitlab_service.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/project_context_service.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/tool_executor_service.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/signals.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/static/lucy_assist/css/lucy-assist.css +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/static/lucy_assist/image/icon-lucy.png +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/static/lucy_assist/js/lucy-assist.js +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/templates/lucy_assist/chatbot_sidebar.html +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/templates/lucy_assist/partials/documentation_content.html +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/tests/__init__.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/tests/factories/__init__.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/tests/factories/lucy_assist_factories.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/tests/test_lucy_assist.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/urls.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/utils/__init__.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/utils/log_utils.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/utils/message_utils.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/utils/token_utils.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/views/__init__.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/views/api_views.py +0 -0
- {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-lucy-assist
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: Assistant IA intelligent Revolucy pour outil métier
|
|
5
5
|
Author-email: Revolucy <hello@revolucy.fr>
|
|
6
6
|
Maintainer-email: Maxence <hello@revolucy.fr>
|
|
@@ -183,14 +183,6 @@ Lucy Assist expose plusieurs endpoints API :
|
|
|
183
183
|
|
|
184
184
|
[Revolucy](https://www.revolucy.fr)
|
|
185
185
|
|
|
186
|
-
## Versionning
|
|
187
|
-
|
|
188
|
-
- V1.0.0 | Création du module Lucy
|
|
189
|
-
- V1.0.1 | Correction de bugs
|
|
190
|
-
- V1.0.2 | Correction de bugs
|
|
191
|
-
- V1.0.3 | Correction de bugs
|
|
192
|
-
- V1.0.4 | Ajout Prompt Custom dans configuration
|
|
193
|
-
|
|
194
186
|
## Déploiement Pypi
|
|
195
187
|
|
|
196
188
|
1. `docker-compose exec django pip install build twine`
|
|
@@ -140,14 +140,6 @@ Lucy Assist expose plusieurs endpoints API :
|
|
|
140
140
|
|
|
141
141
|
[Revolucy](https://www.revolucy.fr)
|
|
142
142
|
|
|
143
|
-
## Versionning
|
|
144
|
-
|
|
145
|
-
- V1.0.0 | Création du module Lucy
|
|
146
|
-
- V1.0.1 | Correction de bugs
|
|
147
|
-
- V1.0.2 | Correction de bugs
|
|
148
|
-
- V1.0.3 | Correction de bugs
|
|
149
|
-
- V1.0.4 | Ajout Prompt Custom dans configuration
|
|
150
|
-
|
|
151
143
|
## Déploiement Pypi
|
|
152
144
|
|
|
153
145
|
1. `docker-compose exec django pip install build twine`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-lucy-assist
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: Assistant IA intelligent Revolucy pour outil métier
|
|
5
5
|
Author-email: Revolucy <hello@revolucy.fr>
|
|
6
6
|
Maintainer-email: Maxence <hello@revolucy.fr>
|
|
@@ -183,14 +183,6 @@ Lucy Assist expose plusieurs endpoints API :
|
|
|
183
183
|
|
|
184
184
|
[Revolucy](https://www.revolucy.fr)
|
|
185
185
|
|
|
186
|
-
## Versionning
|
|
187
|
-
|
|
188
|
-
- V1.0.0 | Création du module Lucy
|
|
189
|
-
- V1.0.1 | Correction de bugs
|
|
190
|
-
- V1.0.2 | Correction de bugs
|
|
191
|
-
- V1.0.3 | Correction de bugs
|
|
192
|
-
- V1.0.4 | Ajout Prompt Custom dans configuration
|
|
193
|
-
|
|
194
186
|
## Déploiement Pypi
|
|
195
187
|
|
|
196
188
|
1. `docker-compose exec django pip install build twine`
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/SOURCES.txt
RENAMED
|
@@ -15,6 +15,7 @@ lucy_assist/context_processors.py
|
|
|
15
15
|
lucy_assist/signals.py
|
|
16
16
|
lucy_assist/urls.py
|
|
17
17
|
lucy_assist/migrations/0001_initial.py
|
|
18
|
+
lucy_assist/migrations/0002_configurationlucyassist_prompt_complementaire.py
|
|
18
19
|
lucy_assist/migrations/__init__.py
|
|
19
20
|
lucy_assist/models/__init__.py
|
|
20
21
|
lucy_assist/models/base.py
|
|
@@ -5,7 +5,7 @@ Un chatbot IA basé sur Claude d'Anthropic, intégrable dans n'importe quelle
|
|
|
5
5
|
application Django pour fournir une assistance contextuelle aux utilisateurs.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = '1.0.
|
|
8
|
+
__version__ = '1.0.6'
|
|
9
9
|
__author__ = 'Revolucy'
|
|
10
10
|
|
|
11
11
|
default_app_config = 'lucy_assist.apps.LucyAssistConfig'
|
|
@@ -47,13 +47,13 @@ class LucyAssistSettings:
|
|
|
47
47
|
# Nombre moyen de tokens par conversation
|
|
48
48
|
'TOKENS_MOYENS_PAR_CONVERSATION': 2000,
|
|
49
49
|
|
|
50
|
-
# Questions fréquentes par défaut
|
|
50
|
+
# Questions fréquentes par défaut (génériques)
|
|
51
51
|
'QUESTIONS_FREQUENTES_DEFAULT': [
|
|
52
|
-
"Comment créer un nouveau
|
|
53
|
-
"Comment effectuer
|
|
52
|
+
"Comment créer un nouveau client ?",
|
|
53
|
+
"Comment effectuer une recherche ?",
|
|
54
54
|
"Comment exporter des données ?",
|
|
55
55
|
"Comment modifier mon profil ?",
|
|
56
|
-
"Où trouver la liste des
|
|
56
|
+
"Où trouver la liste des réservations ?",
|
|
57
57
|
],
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 6.0.1 on 2026-01-27 09:51
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('lucy_assist', '0001_initial'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name='configurationlucyassist',
|
|
15
|
+
name='prompt_complementaire',
|
|
16
|
+
field=models.TextField(blank=True, default=''),
|
|
17
|
+
),
|
|
18
|
+
]
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/context_service.py
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Service de détection et construction du contexte de page.
|
|
3
3
|
"""
|
|
4
|
-
import logging
|
|
5
4
|
import re
|
|
5
|
+
from datetime import datetime
|
|
6
6
|
from typing import Dict, List, Optional
|
|
7
7
|
from urllib.parse import urlparse
|
|
8
8
|
|
|
@@ -10,14 +10,44 @@ from django.urls import resolve, Resolver404
|
|
|
10
10
|
from django.apps import apps
|
|
11
11
|
|
|
12
12
|
from lucy_assist.utils.log_utils import LogUtils
|
|
13
|
+
from lucy_assist.conf import lucy_assist_settings
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class ContextService:
|
|
16
17
|
"""Service pour construire le contexte de la page courante."""
|
|
17
18
|
|
|
19
|
+
# Patterns pour détecter les dates dans les requêtes
|
|
20
|
+
DATE_PATTERNS = [
|
|
21
|
+
(r'(\d{1,2})/(\d{1,2})/(\d{4})', '%d/%m/%Y'), # 18/10/2025
|
|
22
|
+
(r'(\d{1,2})-(\d{1,2})-(\d{4})', '%d-%m-%Y'), # 18-10-2025
|
|
23
|
+
(r'(\d{4})/(\d{1,2})/(\d{1,2})', '%Y/%m/%d'), # 2025/10/18
|
|
24
|
+
(r'(\d{4})-(\d{1,2})-(\d{1,2})', '%Y-%m-%d'), # 2025-10-18
|
|
25
|
+
]
|
|
26
|
+
|
|
18
27
|
def __init__(self, user):
|
|
19
28
|
self.user = user
|
|
20
29
|
|
|
30
|
+
def _extract_date_from_query(self, query: str) -> Optional[datetime]:
|
|
31
|
+
"""
|
|
32
|
+
Extrait une date d'une requête texte.
|
|
33
|
+
Supporte les formats: 18/10/2025, 18-10-2025, 2025/10/18, 2025-10-18
|
|
34
|
+
"""
|
|
35
|
+
for pattern, date_format in self.DATE_PATTERNS:
|
|
36
|
+
match = re.search(pattern, query)
|
|
37
|
+
if match:
|
|
38
|
+
try:
|
|
39
|
+
date_str = match.group(0)
|
|
40
|
+
return datetime.strptime(date_str, date_format)
|
|
41
|
+
except ValueError:
|
|
42
|
+
continue
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def _remove_date_from_query(self, query: str) -> str:
|
|
46
|
+
"""Supprime la date de la requête pour garder les autres mots-clés."""
|
|
47
|
+
for pattern, _ in self.DATE_PATTERNS:
|
|
48
|
+
query = re.sub(pattern, '', query)
|
|
49
|
+
return query.strip()
|
|
50
|
+
|
|
21
51
|
def get_page_context(self, url_path: str) -> Dict:
|
|
22
52
|
"""
|
|
23
53
|
Construit le contexte complet d'une page.
|
|
@@ -269,44 +299,104 @@ class ContextService:
|
|
|
269
299
|
except LookupError:
|
|
270
300
|
continue
|
|
271
301
|
else:
|
|
272
|
-
#
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
302
|
+
# Découvrir dynamiquement les modèles du projet
|
|
303
|
+
apps_prefix = lucy_assist_settings.PROJECT_APPS_PREFIX
|
|
304
|
+
|
|
305
|
+
for app_config in apps.get_app_configs():
|
|
306
|
+
# Filtrer par préfixe si configuré
|
|
307
|
+
if apps_prefix and not app_config.name.startswith(apps_prefix):
|
|
308
|
+
continue
|
|
309
|
+
|
|
310
|
+
# Exclure les apps système et lucy_assist
|
|
311
|
+
if app_config.name.startswith('django.') or app_config.name == 'lucy_assist':
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
# Ajouter les modèles de cette app (limiter à 3 par app)
|
|
315
|
+
app_models = list(app_config.get_models())[:3]
|
|
316
|
+
models_to_search.extend(app_models)
|
|
317
|
+
|
|
318
|
+
# Limiter le nombre total de modèles à rechercher
|
|
319
|
+
if len(models_to_search) >= 10:
|
|
320
|
+
break
|
|
321
|
+
|
|
322
|
+
# Log des modèles découverts pour debug
|
|
323
|
+
LogUtils.info(f"[search_objects] Recherche '{query}' dans {len(models_to_search)} modèles: {[m.__name__ for m in models_to_search]}")
|
|
324
|
+
|
|
325
|
+
# Extraire une date de la requête si présente
|
|
326
|
+
search_date = self._extract_date_from_query(query)
|
|
327
|
+
text_query = self._remove_date_from_query(query) if search_date else query
|
|
328
|
+
|
|
329
|
+
if search_date:
|
|
330
|
+
LogUtils.info(f"[search_objects] Date détectée: {search_date.date()}")
|
|
282
331
|
|
|
283
332
|
# Rechercher dans chaque modèle
|
|
284
333
|
for model in models_to_search:
|
|
285
334
|
try:
|
|
286
|
-
# Trouver les champs texte pour la recherche
|
|
335
|
+
# Trouver les champs texte et date pour la recherche
|
|
287
336
|
search_fields = []
|
|
337
|
+
date_fields = []
|
|
288
338
|
for field in model._meta.get_fields():
|
|
289
339
|
if hasattr(field, 'get_internal_type'):
|
|
290
|
-
|
|
340
|
+
field_type = field.get_internal_type()
|
|
341
|
+
if field_type in ['CharField', 'TextField']:
|
|
291
342
|
search_fields.append(field.name)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
continue
|
|
343
|
+
elif field_type in ['DateField', 'DateTimeField']:
|
|
344
|
+
date_fields.append(field.name)
|
|
295
345
|
|
|
296
346
|
# Construire la requête
|
|
297
347
|
from django.db.models import Q
|
|
298
|
-
q_objects =
|
|
299
|
-
|
|
300
|
-
|
|
348
|
+
q_objects = None
|
|
349
|
+
has_criteria = False
|
|
350
|
+
|
|
351
|
+
# Si on a une date, chercher dans les champs date
|
|
352
|
+
if search_date and date_fields:
|
|
353
|
+
date_q = Q()
|
|
354
|
+
for field_name in date_fields:
|
|
355
|
+
date_q |= Q(**{f'{field_name}__date': search_date.date()})
|
|
356
|
+
q_objects = date_q
|
|
357
|
+
has_criteria = True
|
|
358
|
+
LogUtils.info(f"[search_objects] {model.__name__}: recherche date dans {date_fields}")
|
|
359
|
+
|
|
360
|
+
# Si on a du texte, chercher dans les champs texte
|
|
361
|
+
if text_query.strip() and search_fields:
|
|
362
|
+
query_words = text_query.strip().split()
|
|
363
|
+
text_q = Q()
|
|
364
|
+
|
|
365
|
+
if len(query_words) > 1:
|
|
366
|
+
# Recherche multi-mots: chaque mot doit matcher au moins un champ
|
|
367
|
+
for word in query_words:
|
|
368
|
+
word_q = Q()
|
|
369
|
+
for field_name in search_fields[:5]:
|
|
370
|
+
word_q |= Q(**{f'{field_name}__icontains': word})
|
|
371
|
+
text_q &= word_q
|
|
372
|
+
else:
|
|
373
|
+
# Recherche simple
|
|
374
|
+
for field_name in search_fields[:5]:
|
|
375
|
+
text_q |= Q(**{f'{field_name}__icontains': text_query})
|
|
376
|
+
|
|
377
|
+
if q_objects is not None:
|
|
378
|
+
q_objects &= text_q
|
|
379
|
+
else:
|
|
380
|
+
q_objects = text_q
|
|
381
|
+
has_criteria = True
|
|
382
|
+
|
|
383
|
+
# Si pas de critère de recherche valide, skip
|
|
384
|
+
if not has_criteria:
|
|
385
|
+
if not search_fields and not date_fields:
|
|
386
|
+
LogUtils.info(f"[search_objects] {model.__name__}: aucun champ recherchable")
|
|
387
|
+
continue
|
|
301
388
|
|
|
302
389
|
# Filtrer par permissions si possible
|
|
303
390
|
# Note: certains modèles utilisent des managers customs qui
|
|
304
391
|
# dépendent de ThreadLocal/middleware pour l'utilisateur courant
|
|
305
392
|
try:
|
|
306
393
|
queryset = model.objects.all()
|
|
394
|
+
count = queryset.count()
|
|
395
|
+
LogUtils.info(f"[search_objects] {model.__name__}: {count} objets en base")
|
|
307
396
|
objects = queryset.filter(q_objects)[:limit]
|
|
308
|
-
except AttributeError:
|
|
397
|
+
except AttributeError as e:
|
|
309
398
|
# Si le manager a besoin d'un utilisateur non disponible, skip ce modèle
|
|
399
|
+
LogUtils.info(f"[search_objects] {model.__name__}: skip (AttributeError: {e})")
|
|
310
400
|
continue
|
|
311
401
|
|
|
312
402
|
for obj in objects:
|
|
@@ -322,7 +412,7 @@ class ContextService:
|
|
|
322
412
|
return results
|
|
323
413
|
|
|
324
414
|
except Exception as e:
|
|
325
|
-
LogUtils.info(f"Erreur recherche dans {model.__name__}: {e}")
|
|
415
|
+
LogUtils.info(f"[search_objects] Erreur recherche dans {model.__name__}: {e}")
|
|
326
416
|
continue
|
|
327
417
|
|
|
328
418
|
return results
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Service pour exécuter des opérations CRUD via Lucy Assist.
|
|
3
3
|
"""
|
|
4
|
-
import logging
|
|
5
4
|
from typing import Dict, List, Optional, Any
|
|
6
5
|
|
|
7
6
|
from django.apps import apps
|
|
@@ -9,6 +8,7 @@ from django.db import transaction
|
|
|
9
8
|
from django.forms import modelform_factory
|
|
10
9
|
|
|
11
10
|
from lucy_assist.utils.log_utils import LogUtils
|
|
11
|
+
from lucy_assist.conf import lucy_assist_settings
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class CRUDService:
|
|
@@ -17,6 +17,44 @@ class CRUDService:
|
|
|
17
17
|
def __init__(self, user):
|
|
18
18
|
self.user = user
|
|
19
19
|
|
|
20
|
+
def _serialize_value(self, value):
|
|
21
|
+
"""
|
|
22
|
+
Convertit une valeur en un format sérialisable JSON.
|
|
23
|
+
"""
|
|
24
|
+
from datetime import datetime, date, time
|
|
25
|
+
from decimal import Decimal
|
|
26
|
+
import uuid
|
|
27
|
+
|
|
28
|
+
if value is None:
|
|
29
|
+
return None
|
|
30
|
+
elif isinstance(value, (datetime,)):
|
|
31
|
+
return value.isoformat()
|
|
32
|
+
elif isinstance(value, (date,)):
|
|
33
|
+
return value.isoformat()
|
|
34
|
+
elif isinstance(value, (time,)):
|
|
35
|
+
return value.isoformat()
|
|
36
|
+
elif isinstance(value, Decimal):
|
|
37
|
+
return float(value)
|
|
38
|
+
elif isinstance(value, uuid.UUID):
|
|
39
|
+
return str(value)
|
|
40
|
+
elif hasattr(value, 'pk'):
|
|
41
|
+
# ForeignKey ou relation
|
|
42
|
+
return {'id': value.pk, 'str': str(value)}
|
|
43
|
+
elif hasattr(value, 'all'):
|
|
44
|
+
# ManyToMany ou reverse FK - retourner juste le count
|
|
45
|
+
return {'count': value.count()}
|
|
46
|
+
elif isinstance(value, bytes):
|
|
47
|
+
return value.decode('utf-8', errors='replace')
|
|
48
|
+
else:
|
|
49
|
+
# Essayer de retourner directement, sinon convertir en string
|
|
50
|
+
try:
|
|
51
|
+
# Types simples (str, int, float, bool, list, dict)
|
|
52
|
+
import json
|
|
53
|
+
json.dumps(value)
|
|
54
|
+
return value
|
|
55
|
+
except (TypeError, ValueError):
|
|
56
|
+
return str(value)
|
|
57
|
+
|
|
20
58
|
def can_perform_action(self, app_name: str, model_name: str, action: str) -> bool:
|
|
21
59
|
"""
|
|
22
60
|
Vérifie si l'utilisateur peut effectuer l'action.
|
|
@@ -37,35 +75,67 @@ class CRUDService:
|
|
|
37
75
|
|
|
38
76
|
def get_model(self, app_name: str, model_name: str):
|
|
39
77
|
"""Récupère une classe de modèle Django."""
|
|
78
|
+
# Essayer d'abord avec app_name fourni
|
|
40
79
|
try:
|
|
41
|
-
|
|
80
|
+
model = apps.get_model(app_name, model_name)
|
|
81
|
+
LogUtils.info(f"[CRUD] Modèle trouvé: {app_name}.{model_name}")
|
|
82
|
+
return model
|
|
42
83
|
except LookupError:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
# Essayer avec le préfixe PROJECT_APPS_PREFIX
|
|
87
|
+
apps_prefix = lucy_assist_settings.PROJECT_APPS_PREFIX or ''
|
|
88
|
+
if apps_prefix and not app_name.startswith(apps_prefix):
|
|
89
|
+
try:
|
|
90
|
+
full_app_name = f"{apps_prefix}{app_name}"
|
|
91
|
+
model = apps.get_model(full_app_name, model_name)
|
|
92
|
+
LogUtils.info(f"[CRUD] Modèle trouvé avec préfixe: {full_app_name}.{model_name}")
|
|
93
|
+
return model
|
|
94
|
+
except LookupError:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
# Chercher dans toutes les apps du projet
|
|
98
|
+
for app_config in apps.get_app_configs():
|
|
99
|
+
# Filtrer par préfixe si configuré
|
|
100
|
+
if apps_prefix and not app_config.name.startswith(apps_prefix):
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
model = app_config.get_model(model_name)
|
|
105
|
+
LogUtils.info(f"[CRUD] Modèle trouvé par recherche: {app_config.label}.{model_name}")
|
|
106
|
+
return model
|
|
107
|
+
except LookupError:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
LogUtils.warning(f"[CRUD] Modèle non trouvé: {app_name}.{model_name}")
|
|
111
|
+
return None
|
|
50
112
|
|
|
51
113
|
def get_form_class(self, model):
|
|
52
114
|
"""Récupère ou crée une classe de formulaire pour le modèle."""
|
|
53
|
-
# Essayer de trouver un formulaire existant
|
|
54
115
|
app_label = model._meta.app_label
|
|
116
|
+
model_name = model.__name__
|
|
55
117
|
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
118
|
+
# Liste des chemins d'import possibles pour les formulaires
|
|
119
|
+
apps_prefix = lucy_assist_settings.PROJECT_APPS_PREFIX or ''
|
|
120
|
+
possible_paths = [
|
|
121
|
+
f'{apps_prefix}{app_label}.forms', # apps.client.forms
|
|
122
|
+
f'{app_label}.forms', # client.forms
|
|
123
|
+
f'apps.{app_label}.forms', # apps.client.forms (legacy)
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
# Essayer de trouver un formulaire existant
|
|
127
|
+
for path in possible_paths:
|
|
128
|
+
try:
|
|
129
|
+
forms_module = __import__(path, fromlist=[f'{model_name}Form'])
|
|
130
|
+
form_class = getattr(forms_module, f'{model_name}Form', None)
|
|
131
|
+
if form_class:
|
|
132
|
+
LogUtils.info(f"[CRUD] Formulaire trouvé: {path}.{model_name}Form")
|
|
133
|
+
return form_class
|
|
134
|
+
except (ImportError, AttributeError, ModuleNotFoundError):
|
|
135
|
+
continue
|
|
67
136
|
|
|
68
137
|
# Créer un formulaire automatique
|
|
138
|
+
LogUtils.info(f"[CRUD] Formulaire auto-généré pour {model_name}")
|
|
69
139
|
return modelform_factory(model, fields='__all__')
|
|
70
140
|
|
|
71
141
|
def get_required_fields(self, model) -> List[Dict]:
|
|
@@ -332,16 +402,21 @@ class CRUDService:
|
|
|
332
402
|
Returns:
|
|
333
403
|
Dict avec les données de l'objet ou None
|
|
334
404
|
"""
|
|
405
|
+
LogUtils.info(f"[CRUD] get_object: {app_name}.{model_name} #{object_id}")
|
|
406
|
+
|
|
335
407
|
# Vérifier les permissions
|
|
336
408
|
if not self.can_perform_action(app_name, model_name, 'view'):
|
|
409
|
+
LogUtils.info(f"[CRUD] get_object: permission refusée pour {model_name}")
|
|
337
410
|
return None
|
|
338
411
|
|
|
339
412
|
model = self.get_model(app_name, model_name)
|
|
340
413
|
if not model:
|
|
414
|
+
LogUtils.warning(f"[CRUD] get_object: modèle {model_name} non trouvé")
|
|
341
415
|
return None
|
|
342
416
|
|
|
343
417
|
try:
|
|
344
|
-
|
|
418
|
+
# Utiliser objects.all() pour éviter les problèmes avec les managers customs
|
|
419
|
+
obj = model.objects.all().get(pk=object_id)
|
|
345
420
|
|
|
346
421
|
# Construire un dict avec les données
|
|
347
422
|
data = {'id': obj.pk, 'str': str(obj)}
|
|
@@ -350,15 +425,16 @@ class CRUDService:
|
|
|
350
425
|
if hasattr(field, 'verbose_name'):
|
|
351
426
|
try:
|
|
352
427
|
value = getattr(obj, field.name)
|
|
353
|
-
|
|
354
|
-
if hasattr(value, 'pk'):
|
|
355
|
-
data[field.name] = {'id': value.pk, 'str': str(value)}
|
|
356
|
-
else:
|
|
357
|
-
data[field.name] = value
|
|
428
|
+
data[field.name] = self._serialize_value(value)
|
|
358
429
|
except Exception:
|
|
359
430
|
pass
|
|
360
431
|
|
|
432
|
+
LogUtils.info(f"[CRUD] get_object: {model_name} #{object_id} récupéré avec succès")
|
|
361
433
|
return data
|
|
362
434
|
|
|
363
435
|
except model.DoesNotExist:
|
|
436
|
+
LogUtils.info(f"[CRUD] get_object: {model_name} #{object_id} non trouvé")
|
|
437
|
+
return None
|
|
438
|
+
except Exception as e:
|
|
439
|
+
LogUtils.error(f"[CRUD] get_object: erreur pour {model_name} #{object_id}: {e}")
|
|
364
440
|
return None
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/tools_definition.py
RENAMED
|
@@ -10,8 +10,7 @@ LUCY_ASSIST_TOOLS = [
|
|
|
10
10
|
{
|
|
11
11
|
"name": "create_object",
|
|
12
12
|
"description": """Crée un nouvel objet dans la base de données.
|
|
13
|
-
Utilise ce tool quand l'utilisateur demande de créer un
|
|
14
|
-
un paiement, une adhésion ou tout autre objet métier.
|
|
13
|
+
Utilise ce tool quand l'utilisateur demande de créer un objet métier (client, réservation, etc.).
|
|
15
14
|
IMPORTANT: Utilise ce tool en priorité quand l'utilisateur demande une création.
|
|
16
15
|
Ne demande pas de confirmation, exécute directement l'action.""",
|
|
17
16
|
"input_schema": {
|
|
@@ -19,11 +18,11 @@ LUCY_ASSIST_TOOLS = [
|
|
|
19
18
|
"properties": {
|
|
20
19
|
"app_name": {
|
|
21
20
|
"type": "string",
|
|
22
|
-
"description": "Nom de l'application Django (ex:
|
|
21
|
+
"description": "Nom de l'application Django (ex: client, reservation, franchise)"
|
|
23
22
|
},
|
|
24
23
|
"model_name": {
|
|
25
24
|
"type": "string",
|
|
26
|
-
"description": "Nom du modèle Django (ex:
|
|
25
|
+
"description": "Nom du modèle Django (ex: Client, Reservation, Franchise)"
|
|
27
26
|
},
|
|
28
27
|
"data": {
|
|
29
28
|
"type": "object",
|
|
@@ -37,7 +36,7 @@ LUCY_ASSIST_TOOLS = [
|
|
|
37
36
|
{
|
|
38
37
|
"name": "update_object",
|
|
39
38
|
"description": """Met à jour un objet existant dans la base de données.
|
|
40
|
-
Utilise ce tool quand l'utilisateur demande de modifier un
|
|
39
|
+
Utilise ce tool quand l'utilisateur demande de modifier un objet existant.
|
|
41
40
|
IMPORTANT: Exécute directement l'action sans demander de confirmation.""",
|
|
42
41
|
"input_schema": {
|
|
43
42
|
"type": "object",
|
|
@@ -90,7 +89,7 @@ LUCY_ASSIST_TOOLS = [
|
|
|
90
89
|
{
|
|
91
90
|
"name": "search_objects",
|
|
92
91
|
"description": """Recherche des objets dans la base de données.
|
|
93
|
-
Utilise ce tool pour trouver des
|
|
92
|
+
Utilise ce tool pour trouver des clients, réservations, ou tout autre objet métier.""",
|
|
94
93
|
"input_schema": {
|
|
95
94
|
"type": "object",
|
|
96
95
|
"properties": {
|
|
@@ -206,7 +205,7 @@ LUCY_ASSIST_TOOLS = [
|
|
|
206
205
|
},
|
|
207
206
|
"model_name": {
|
|
208
207
|
"type": "string",
|
|
209
|
-
"description": "Nom du modèle Django concerné (si identifiable, ex:
|
|
208
|
+
"description": "Nom du modèle Django concerné (si identifiable, ex: Client, Reservation)"
|
|
210
209
|
},
|
|
211
210
|
"action_type": {
|
|
212
211
|
"type": "string",
|
|
File without changes
|
|
File without changes
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/requires.txt
RENAMED
|
File without changes
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/project_context_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/claude_service.py
RENAMED
|
File without changes
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/gitlab_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/tool_executor_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/tests/factories/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|