django-lucy-assist 1.0.6__py3-none-any.whl → 1.0.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-lucy-assist
3
- Version: 1.0.6
3
+ Version: 1.0.7
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>
@@ -176,7 +176,7 @@ Lucy Assist expose plusieurs endpoints API :
176
176
  - `POST /lucy-assist/api/conversations/` - Créer une conversation
177
177
  - `GET /lucy-assist/api/conversations/<id>/` - Détail d'une conversation
178
178
  - `POST /lucy-assist/api/conversations/<id>/messages/` - Ajouter un message
179
- - `POST /lucy-assist/api/conversations/<id>/completion/` - Générer une réponse (streaming)
179
+ - `POST /lucy-assist/api/convers2ations/<id>/completion/` - Générer une réponse (streaming)
180
180
  - `GET /lucy-assist/api/tokens/status/` - Statut des tokens
181
181
 
182
182
  ## Licence
@@ -1,29 +1,31 @@
1
- lucy_assist/__init__.py,sha256=GmRLNDDU0LJkL3EhCcm2DaUIX4Ix7ybTCn-fu5n2k4E,335
2
- lucy_assist/admin.py,sha256=-hNfuwuMfxgZVFQc_ODy6WcyZPxrM_8TfKsRMd0fj38,694
1
+ lucy_assist/__init__.py,sha256=RVSIAsuh02Z3-F9fcCAeJdoXu3J4bvWnJ2BllIevECY,335
2
+ lucy_assist/admin.py,sha256=DHdcvkCYNkVBZoWrZ58AQxOEN2EmsN4E09qRnqYyARI,2919
3
3
  lucy_assist/apps.py,sha256=zHZtlBXs5ML4CKtGg7xDyptSWzLfB1ks2VvbXF50hdo,264
4
- lucy_assist/conf.py,sha256=sWcAdJTSE3Hn_guTifZaRCLKIuJMvR9RwJk2GEKCFOI,3520
5
- lucy_assist/constantes.py,sha256=YppDWi1DQueMwJk3jmeGPDi-UTmSUUDSwR6sW7QzBn4,4083
4
+ lucy_assist/conf.py,sha256=WeglOdS6DlnbORViLN9uVqUbUqgAxQQhyHxLADkv93U,3855
5
+ lucy_assist/constantes.py,sha256=vKIEuNnK23CqX046l5rFLKeSjHl-kwor3lq8Boj8_IU,5005
6
6
  lucy_assist/context_processors.py,sha256=mDrr9G5XztDfJLGq_75X1rkJbVI5De08ys_pW3y12Dw,2210
7
7
  lucy_assist/signals.py,sha256=aQA84oe9JNL72eeV5kURTTV-9CcQpqakDle1Lv3dnFY,861
8
8
  lucy_assist/urls.py,sha256=Qr8jJjEyC_EFGAeiZnjhgTc-9P4Y-TqKDaYicWRp_GQ,1451
9
9
  lucy_assist/migrations/0001_initial.py,sha256=Z4chOo4Ok_5VqQyHvfPS00ikyuefdwHF3LaH4SKpzoQ,4977
10
10
  lucy_assist/migrations/0002_configurationlucyassist_prompt_complementaire.py,sha256=yWE4-RPHQtPAqYHlAhr4EsXgk35kI8zRQArlAP_JwDc,418
11
+ lucy_assist/migrations/0003_configurationlucyassist_crud_views_mapping.py,sha256=1vnu1VwVBouskhQBgLz-Lp71p4a9Cb9PjrT-RLVGY7s,429
11
12
  lucy_assist/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
13
  lucy_assist/models/__init__.py,sha256=JQMr50P94sBrKwwcNArRE_qnk7dLnknMKCabeRLskJc,415
13
14
  lucy_assist/models/base.py,sha256=Ql2AY7bzvcxZhxsPdCpbVEKvtWD-NBkHTLYZX4TjT3s,1532
14
- lucy_assist/models/configuration.py,sha256=pvHtI2kWnKgJjvz5SyC3ObpnCW5kHVt0fRq5E_dd4nc,6420
15
+ lucy_assist/models/configuration.py,sha256=9MjEI2gG2lx1H2UyHo9zCnDOoUC_1hVOKOYwzWuYIHs,8596
15
16
  lucy_assist/models/conversation.py,sha256=psx2AQtQ5SFP-AJD7wbGabpXXLIGYngdVZfeoTlgKtQ,1849
16
17
  lucy_assist/models/message.py,sha256=kf-ffMtLYNFhXYUrB3QSL97KKDJUOMrKaPjKeOcJa_o,1492
17
18
  lucy_assist/models/project_context_cache.py,sha256=Bnb0VU7pv7QEvjOI6JSLEPvL4BxskCQ0ojWGxO7YDSM,6530
18
19
  lucy_assist/services/__init__.py,sha256=I0brW674WNIKkGHj2lj4sGEDD7HUAr5Z254dsbirdLk,691
19
20
  lucy_assist/services/bug_notification_service.py,sha256=OyowCvAs-QDlsGQ_WTFoc4lRe9detD7r6ZyYK0JD2Sc,7217
20
21
  lucy_assist/services/claude_service.py,sha256=vYeotZKwFghbWNmN_VM0uggnFQgtNNK0SP3e9QPQzgc,16218
21
- lucy_assist/services/context_service.py,sha256=e7ByX0pmCBET9odM4ePWSt66sZohR6WOr8AljELuq1I,16499
22
- lucy_assist/services/crud_service.py,sha256=IQ0CwEgag-rTwmgA0lCqbKS5tEK5YjeiWmuxXainli8,14935
22
+ lucy_assist/services/context_service.py,sha256=Vx2tR6W1jmqr06pGn924uzWTF2SV7RAO_JIScxYbCy0,17087
23
+ lucy_assist/services/crud_service.py,sha256=W50K02ZkaScyzO-yRaBd9Cjva9PGH7OjNTipSruErcA,41495
23
24
  lucy_assist/services/gitlab_service.py,sha256=uH83fwRSCwiRItznENpYQG4aPckjafYIV9z6OChUrZg,8056
24
25
  lucy_assist/services/project_context_service.py,sha256=bIuqTanc59gP_BLod3oQgWplxpiCgByg-kbUMe_57CQ,14053
25
- lucy_assist/services/tool_executor_service.py,sha256=fXLH4Aaip-HPX1nNRs8UQ1N77rGloBahSwOR9gPmjfU,13583
26
- lucy_assist/services/tools_definition.py,sha256=_3wfZOtzPtN44H8ITjEPzGn1LVxAu0ipcWLXsvzLyHE,8923
26
+ lucy_assist/services/tool_executor_service.py,sha256=cHwEA2FvxE8cIC7N-YF8Wn7NV1hureBuxYKxTf_bG7U,15151
27
+ lucy_assist/services/tools_definition.py,sha256=y4ycmKbrub2fw_6DMwSqq36r8_g93tmvJU00Gp_0XR0,10341
28
+ lucy_assist/services/view_discovery_service.py,sha256=J9LkHXUOzlGS3cyft2_jA1X27TWOd3xViN4M7GOROVw,11872
27
29
  lucy_assist/static/lucy_assist/css/lucy-assist.css,sha256=gUfj4OUTz_aFiXWau1iXtHEmfUCkUI2zGMfwkLk2nXs,18190
28
30
  lucy_assist/static/lucy_assist/image/icon-lucy.png,sha256=FOYlwXAt40Gr9jsWFmhgPivYOBFWKeYW0lxJI5Up-GM,6710
29
31
  lucy_assist/static/lucy_assist/js/lucy-assist.js,sha256=dmXtSPQ38LisoRWZd3R1Ms6OvVaEk3OrYKcVM0OMb_Q,28207
@@ -39,7 +41,7 @@ lucy_assist/utils/message_utils.py,sha256=YzcLHnl1ig4d5_utHCJwgxS7tKmd49Q-tuo78e
39
41
  lucy_assist/utils/token_utils.py,sha256=rxe9jHjcRJcaIlcw0QuVmYXOjscTsUsxnhhI6RMBzDM,2608
40
42
  lucy_assist/views/__init__.py,sha256=uUPYpuHlBC8j7zKS_DDoWjwpCpRnOIXETY-S2-Ss0cY,288
41
43
  lucy_assist/views/api_views.py,sha256=iCvdTTTJ73r3jfyZVjcEDi3Of2wP_N24G_QsXwc-Euk,23617
42
- django_lucy_assist-1.0.6.dist-info/METADATA,sha256=7CCVUKQWHPNS52cMi6YpPNyrRtu_eQilAHlBd_gu_fI,5543
43
- django_lucy_assist-1.0.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
44
- django_lucy_assist-1.0.6.dist-info/top_level.txt,sha256=T-UCiwpn5yF3Oem3234TUpSVnEgbkrM2rGz9Tz5N-QA,12
45
- django_lucy_assist-1.0.6.dist-info/RECORD,,
44
+ django_lucy_assist-1.0.7.dist-info/METADATA,sha256=OLG_nvmoGozZUIdaaAslZSdFgbh0g-3hHKxa-mw1Yvo,5544
45
+ django_lucy_assist-1.0.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
46
+ django_lucy_assist-1.0.7.dist-info/top_level.txt,sha256=T-UCiwpn5yF3Oem3234TUpSVnEgbkrM2rGz9Tz5N-QA,12
47
+ django_lucy_assist-1.0.7.dist-info/RECORD,,
lucy_assist/__init__.py CHANGED
@@ -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.6'
8
+ __version__ = '1.0.7'
9
9
  __author__ = 'Revolucy'
10
10
 
11
11
  default_app_config = 'lucy_assist.apps.LucyAssistConfig'
lucy_assist/admin.py CHANGED
@@ -19,4 +19,53 @@ class ConversationAdmin(admin.ModelAdmin):
19
19
 
20
20
  @admin.register(ConfigurationLucyAssist)
21
21
  class ConfigurationLucyAssistAdmin(admin.ModelAdmin):
22
- list_display = ('id', 'tokens_disponibles', 'prix_par_million_tokens', 'updated_date')
22
+ list_display = ('id', 'tokens_disponibles', 'prix_par_million_tokens', 'nb_vues_crud', 'updated_date')
23
+ readonly_fields = ('crud_views_mapping_display', 'model_app_mapping_display')
24
+ fieldsets = (
25
+ ('Configuration Tokens', {
26
+ 'fields': ('tokens_disponibles', 'prix_par_million_tokens', 'actif')
27
+ }),
28
+ ('Personnalisation', {
29
+ 'fields': ('avatar', 'questions_frequentes', 'prompt_complementaire')
30
+ }),
31
+ ('Mapping Automatique (lecture seule)', {
32
+ 'fields': ('crud_views_mapping_display', 'model_app_mapping_display'),
33
+ 'classes': ('collapse',)
34
+ }),
35
+ )
36
+ actions = ['refresh_crud_views']
37
+
38
+ def nb_vues_crud(self, obj):
39
+ """Affiche le nombre de modèles avec des vues CRUD découvertes."""
40
+ if obj.crud_views_mapping:
41
+ return len(obj.crud_views_mapping)
42
+ return 0
43
+ nb_vues_crud.short_description = "Modèles CRUD"
44
+
45
+ def crud_views_mapping_display(self, obj):
46
+ """Affiche le mapping des vues CRUD de manière lisible."""
47
+ import json
48
+ if obj.crud_views_mapping:
49
+ return json.dumps(obj.crud_views_mapping, indent=2, ensure_ascii=False)
50
+ return "Aucune vue découverte. Utilisez l'action 'Rafraîchir les vues CRUD'."
51
+ crud_views_mapping_display.short_description = "Vues CRUD découvertes"
52
+
53
+ def model_app_mapping_display(self, obj):
54
+ """Affiche le mapping modèle -> app de manière lisible."""
55
+ import json
56
+ if obj.model_app_mapping:
57
+ return json.dumps(obj.model_app_mapping, indent=2, ensure_ascii=False)
58
+ return "Aucun mapping. Sera construit automatiquement."
59
+ model_app_mapping_display.short_description = "Mapping Modèle -> App"
60
+
61
+ @admin.action(description="Rafraîchir les vues CRUD découvertes")
62
+ def refresh_crud_views(self, request, queryset):
63
+ """Action admin pour rafraîchir le mapping des vues CRUD."""
64
+ for config in queryset:
65
+ mapping = config.refresh_crud_views_mapping()
66
+ nb_models = len(mapping)
67
+ nb_views = sum(len(actions) for actions in mapping.values())
68
+ self.message_user(
69
+ request,
70
+ f"Mapping rafraîchi: {nb_models} modèles, {nb_views} vues découvertes."
71
+ )
lucy_assist/conf.py CHANGED
@@ -55,6 +55,14 @@ class LucyAssistSettings:
55
55
  "Comment modifier mon profil ?",
56
56
  "Où trouver la liste des réservations ?",
57
57
  ],
58
+
59
+ # Chemin vers le module contenant set_current_user pour le ThreadLocal
60
+ # Ex: 'alyse.middleware.middleware' pour le projet Alyse
61
+ 'THREAD_LOCAL_MODULE': None,
62
+
63
+ # Attributs de l'utilisateur à copier vers la requête
64
+ # Ex: ['franchise', 'tenant', 'organization']
65
+ 'REQUEST_USER_ATTRS': [],
58
66
  }
59
67
 
60
68
  def __init__(self):
lucy_assist/constantes.py CHANGED
@@ -79,7 +79,27 @@ Utilisateur : "Modifie l'adresse du membre 42"
79
79
 
80
80
  - Si l'utilisateur demande explicitement "comment faire" ou "explique-moi"
81
81
  - Si tu n'as pas assez d'informations (dans ce cas, demande les infos manquantes)
82
- - Pour les suppressions : demande une confirmation avant d'exécuter
82
+
83
+ ## IMPORTANT - Procédure de suppression
84
+
85
+ Pour TOUTE demande de suppression, tu DOIS suivre cette procédure :
86
+
87
+ 1. D'abord, utilise le tool `get_deletion_impact` pour analyser les conséquences
88
+ 2. Affiche à l'utilisateur le résultat complet :
89
+ - L'objet qui sera supprimé
90
+ - TOUS les objets supprimés en cascade (avec leur type et nombre)
91
+ - Les champs qui seront mis à NULL
92
+ - Les éventuels blocages (objets protégés)
93
+ 3. Demande une confirmation EXPLICITE à l'utilisateur ("Confirmez-vous la suppression ?")
94
+ 4. SEULEMENT si l'utilisateur confirme explicitement (oui, ok, confirme, etc.), utilise `delete_object` avec `confirmed: true`
95
+
96
+ Exemple de réponse après get_deletion_impact :
97
+ "Voici les conséquences de la suppression du Client #42 :
98
+ - 5 Réservations seront supprimées en cascade
99
+ - 3 Paiements seront supprimés en cascade
100
+ - 2 Documents auront leur champ 'client' mis à NULL
101
+
102
+ **Confirmez-vous vouloir supprimer ce client et tous les éléments associés ?**"
83
103
 
84
104
  ## Contexte de la page actuelle
85
105
  {page_context}
@@ -0,0 +1,18 @@
1
+ # Generated by Django
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('lucy_assist', '0002_configurationlucyassist_prompt_complementaire'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='configurationlucyassist',
15
+ name='crud_views_mapping',
16
+ field=models.JSONField(blank=True, default=dict),
17
+ ),
18
+ ]
@@ -26,6 +26,17 @@ class ConfigurationLucyAssist(LucyAssistBaseModel):
26
26
  # Mapping des modèles vers leurs applications Django
27
27
  model_app_mapping = models.JSONField(blank=True, default=dict)
28
28
 
29
+ # Mapping des vues CRUD découvertes automatiquement
30
+ # Structure: {
31
+ # "model_name": {
32
+ # "list": {"url_name": "app:model-list", "url": "/app/model/"},
33
+ # "create": {"url_name": "app:model-formulaire", "url": "/app/model/formulaire/"},
34
+ # "detail": {"url_name": "app:model-detail", "url": "/app/model/detail/<pk>/"},
35
+ # "delete": {"url_name": "app:model-suppression", "url": "/app/model/suppression/<pk>/"}
36
+ # }
37
+ # }
38
+ crud_views_mapping = models.JSONField(blank=True, default=dict)
39
+
29
40
  class Meta:
30
41
  verbose_name = "Configuration Lucy Assist"
31
42
  verbose_name_plural = "Configuration Lucy Assist"
@@ -51,10 +62,53 @@ class ConfigurationLucyAssist(LucyAssistBaseModel):
51
62
  def save(self, *args, **kwargs):
52
63
  # Forcer l'ID à 1 pour le singleton
53
64
  self.pk = 1
65
+
66
+ # Auto-découverte des vues CRUD si le mapping est vide
67
+ if not self.crud_views_mapping:
68
+ self.crud_views_mapping = self._discover_crud_views()
69
+
54
70
  super().save(*args, **kwargs)
55
71
  # Invalider le cache
56
72
  cache.delete('lucy_assist_config')
57
73
 
74
+ def refresh_crud_views_mapping(self):
75
+ """
76
+ Force la redécouverte des vues CRUD.
77
+ Utile après l'ajout de nouvelles vues dans le projet.
78
+ """
79
+ self.crud_views_mapping = self._discover_crud_views()
80
+ self.save(update_fields=['crud_views_mapping'])
81
+ cache.delete('lucy_assist_config')
82
+ return self.crud_views_mapping
83
+
84
+ def _discover_crud_views(self) -> dict:
85
+ """
86
+ Découvre automatiquement les vues CRUD du projet
87
+ en parcourant les URL patterns.
88
+ """
89
+ from lucy_assist.services.view_discovery_service import ViewDiscoveryService
90
+ service = ViewDiscoveryService()
91
+ return service.discover_crud_views()
92
+
93
+ def get_crud_view_for_model(self, model_name: str, action: str) -> dict:
94
+ """
95
+ Retourne les infos de la vue CRUD pour un modèle et une action donnés.
96
+
97
+ Args:
98
+ model_name: Nom du modèle (ex: 'Client', 'Reservation')
99
+ action: Type d'action ('list', 'create', 'detail', 'update', 'delete')
100
+
101
+ Returns:
102
+ Dict avec 'url_name', 'url', 'method' ou None si non trouvé
103
+ """
104
+ if not self.crud_views_mapping:
105
+ self.crud_views_mapping = self._discover_crud_views()
106
+ self.save(update_fields=['crud_views_mapping'])
107
+
108
+ model_lower = model_name.lower()
109
+ model_views = self.crud_views_mapping.get(model_lower, {})
110
+ return model_views.get(action)
111
+
58
112
  @property
59
113
  def tokens_restants_en_euros(self):
60
114
  """Retourne la valeur en euros des tokens restants."""
@@ -334,14 +334,17 @@ class ContextService:
334
334
  try:
335
335
  # Trouver les champs texte et date pour la recherche
336
336
  search_fields = []
337
- date_fields = []
337
+ date_fields = [] # DateField (comparaison directe)
338
+ datetime_fields = [] # DateTimeField (utilise __date)
338
339
  for field in model._meta.get_fields():
339
340
  if hasattr(field, 'get_internal_type'):
340
341
  field_type = field.get_internal_type()
341
342
  if field_type in ['CharField', 'TextField']:
342
343
  search_fields.append(field.name)
343
- elif field_type in ['DateField', 'DateTimeField']:
344
+ elif field_type == 'DateField':
344
345
  date_fields.append(field.name)
346
+ elif field_type == 'DateTimeField':
347
+ datetime_fields.append(field.name)
345
348
 
346
349
  # Construire la requête
347
350
  from django.db.models import Q
@@ -349,13 +352,21 @@ class ContextService:
349
352
  has_criteria = False
350
353
 
351
354
  # Si on a une date, chercher dans les champs date
352
- if search_date and date_fields:
355
+ if search_date and (date_fields or datetime_fields):
353
356
  date_q = Q()
357
+ search_date_only = search_date.date()
358
+
359
+ # DateField: comparaison directe
354
360
  for field_name in date_fields:
355
- date_q |= Q(**{f'{field_name}__date': search_date.date()})
361
+ date_q |= Q(**{field_name: search_date_only})
362
+
363
+ # DateTimeField: utiliser __date
364
+ for field_name in datetime_fields:
365
+ date_q |= Q(**{f'{field_name}__date': search_date_only})
366
+
356
367
  q_objects = date_q
357
368
  has_criteria = True
358
- LogUtils.info(f"[search_objects] {model.__name__}: recherche date dans {date_fields}")
369
+ LogUtils.info(f"[search_objects] {model.__name__}: recherche date dans DateField={date_fields}, DateTimeField={datetime_fields}")
359
370
 
360
371
  # Si on a du texte, chercher dans les champs texte
361
372
  if text_query.strip() and search_fields:
@@ -382,7 +393,7 @@ class ContextService:
382
393
 
383
394
  # Si pas de critère de recherche valide, skip
384
395
  if not has_criteria:
385
- if not search_fields and not date_fields:
396
+ if not search_fields and not date_fields and not datetime_fields:
386
397
  LogUtils.info(f"[search_objects] {model.__name__}: aucun champ recherchable")
387
398
  continue
388
399