django-lucy-assist 1.2.3__py3-none-any.whl → 1.2.5__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 (26) hide show
  1. {django_lucy_assist-1.2.3.dist-info → django_lucy_assist-1.2.5.dist-info}/METADATA +1 -1
  2. django_lucy_assist-1.2.5.dist-info/RECORD +45 -0
  3. lucy_assist/__init__.py +1 -1
  4. lucy_assist/admin.py +6 -39
  5. lucy_assist/conf.py +4 -0
  6. lucy_assist/constantes.py +79 -29
  7. lucy_assist/migrations/0001_initial.py +3 -1
  8. lucy_assist/models/configuration.py +0 -116
  9. lucy_assist/services/context_service.py +234 -0
  10. lucy_assist/services/crud_service.py +97 -13
  11. lucy_assist/services/gitlab_service.py +79 -6
  12. lucy_assist/services/mistral_service.py +31 -11
  13. lucy_assist/services/tool_executor_service.py +91 -0
  14. lucy_assist/services/tools_definition.py +65 -0
  15. lucy_assist/services/view_discovery_service.py +47 -13
  16. lucy_assist/static/lucy_assist/css/lucy-assist.css +48 -1
  17. lucy_assist/static/lucy_assist/js/lucy-assist.js +11 -0
  18. lucy_assist/templates/lucy_assist/chatbot_sidebar.html +11 -1
  19. django_lucy_assist-1.2.3.dist-info/RECORD +0 -50
  20. lucy_assist/migrations/0002_configurationlucyassist_prompt_complementaire.py +0 -18
  21. lucy_assist/migrations/0003_configurationlucyassist_crud_views_mapping.py +0 -18
  22. lucy_assist/migrations/0004_configurationlucyassist_system_prompt.py +0 -22
  23. lucy_assist/migrations/0005_configurationlucyassist_project_context.py +0 -22
  24. lucy_assist/migrations/0006_remove_configurationlucyassist_prompt_complementaire.py +0 -17
  25. {django_lucy_assist-1.2.3.dist-info → django_lucy_assist-1.2.5.dist-info}/WHEEL +0 -0
  26. {django_lucy_assist-1.2.3.dist-info → django_lucy_assist-1.2.5.dist-info}/top_level.txt +0 -0
@@ -452,3 +452,237 @@ class ContextService:
452
452
 
453
453
  except Exception:
454
454
  return None
455
+
456
+ def get_page_help(self, url_path: str) -> Dict:
457
+ """
458
+ Retourne une aide contextuelle enrichie pour la page actuelle.
459
+
460
+ Args:
461
+ url_path: URL de la page
462
+
463
+ Returns:
464
+ Dict avec aide, actions suggerees, liens utiles
465
+ """
466
+ context = self.get_page_context(url_path)
467
+ action = context.get('action')
468
+ model_name = context.get('model_name', 'element')
469
+ app_name = context.get('app_name', '')
470
+
471
+ help_info = {
472
+ 'page_type': action,
473
+ 'current_model': model_name,
474
+ 'help_message': '',
475
+ 'suggested_actions': [],
476
+ 'useful_links': [],
477
+ 'tips': []
478
+ }
479
+
480
+ # Generer les URLs utiles
481
+ from django.urls import reverse
482
+ model_lower = model_name.lower() if model_name else ''
483
+ app_simple = app_name.split(':')[-1] if app_name else ''
484
+
485
+ def try_reverse(pattern, **kwargs):
486
+ try:
487
+ return reverse(pattern, kwargs=kwargs) if kwargs else reverse(pattern)
488
+ except Exception:
489
+ return None
490
+
491
+ # Aide selon le type de page
492
+ if action == 'list':
493
+ help_info['help_message'] = f"Vous consultez la liste des {model_name}s."
494
+ help_info['suggested_actions'] = [
495
+ "Rechercher un element specifique",
496
+ "Filtrer la liste",
497
+ "Creer un nouvel element"
498
+ ]
499
+ help_info['tips'] = [
500
+ "Utilisez la barre de recherche pour trouver rapidement",
501
+ "Cliquez sur une ligne pour voir les details"
502
+ ]
503
+ # Lien creation
504
+ create_url = try_reverse(f'{app_simple}:{model_lower}-form')
505
+ if create_url:
506
+ help_info['useful_links'].append({
507
+ 'label': f'Creer un nouveau {model_name}',
508
+ 'url': create_url
509
+ })
510
+
511
+ elif action == 'create_or_edit':
512
+ if context.get('object_id'):
513
+ help_info['help_message'] = f"Vous modifiez un {model_name} existant."
514
+ help_info['suggested_actions'] = [
515
+ "Modifier les informations",
516
+ "Enregistrer les modifications",
517
+ "Annuler et revenir a la liste"
518
+ ]
519
+ else:
520
+ help_info['help_message'] = f"Vous creez un nouveau {model_name}."
521
+ help_info['suggested_actions'] = [
522
+ "Remplir les champs obligatoires",
523
+ "Enregistrer le nouvel element"
524
+ ]
525
+ help_info['tips'] = [
526
+ "Les champs marques * sont obligatoires",
527
+ "Verifiez les informations avant d'enregistrer"
528
+ ]
529
+ # Lien liste
530
+ list_url = try_reverse(f'{app_simple}:{model_lower}-list')
531
+ if list_url:
532
+ help_info['useful_links'].append({
533
+ 'label': f'Retour a la liste des {model_name}s',
534
+ 'url': list_url
535
+ })
536
+
537
+ elif action == 'detail':
538
+ help_info['help_message'] = f"Vous consultez les details d'un {model_name}."
539
+ help_info['suggested_actions'] = [
540
+ "Modifier cet element",
541
+ "Supprimer cet element",
542
+ "Voir les elements lies"
543
+ ]
544
+ object_id = context.get('object_id')
545
+ if object_id:
546
+ edit_url = try_reverse(f'{app_simple}:{model_lower}-form', pk=object_id)
547
+ if edit_url:
548
+ help_info['useful_links'].append({
549
+ 'label': 'Modifier',
550
+ 'url': edit_url
551
+ })
552
+ list_url = try_reverse(f'{app_simple}:{model_lower}-list')
553
+ if list_url:
554
+ help_info['useful_links'].append({
555
+ 'label': f'Liste des {model_name}s',
556
+ 'url': list_url
557
+ })
558
+
559
+ elif action == 'delete':
560
+ help_info['help_message'] = f"Attention : vous allez supprimer ce {model_name}."
561
+ help_info['suggested_actions'] = [
562
+ "Confirmer la suppression",
563
+ "Annuler et revenir"
564
+ ]
565
+ help_info['tips'] = [
566
+ "Cette action est irreversible",
567
+ "Verifiez les elements lies qui seront aussi supprimes"
568
+ ]
569
+
570
+ else:
571
+ help_info['help_message'] = "Comment puis-je vous aider sur cette page ?"
572
+ help_info['suggested_actions'] = [
573
+ "Poser une question",
574
+ "Rechercher un element",
575
+ "Signaler un probleme"
576
+ ]
577
+
578
+ return help_info
579
+
580
+ def get_relevant_models(self, url_path: str) -> List[Dict]:
581
+ """
582
+ Retourne uniquement les modeles pertinents pour la page actuelle.
583
+ Optimise l'utilisation des tokens.
584
+
585
+ Args:
586
+ url_path: URL de la page
587
+
588
+ Returns:
589
+ Liste des modeles pertinents avec leurs champs
590
+ """
591
+ context = self.get_page_context(url_path)
592
+ app_name = context.get('app_name', '')
593
+ model_name = context.get('model_name')
594
+
595
+ relevant_models = []
596
+ apps_prefix = lucy_assist_settings.PROJECT_APPS_PREFIX
597
+ app_simple = app_name.split(':')[-1] if app_name else ''
598
+
599
+ # 1. Toujours inclure le modele de la page actuelle en premier
600
+ if model_name:
601
+ model_info = self._get_model_info(app_simple, model_name)
602
+ if model_info:
603
+ model_info['is_current'] = True
604
+ relevant_models.append(model_info)
605
+
606
+ # 2. Ajouter les modeles de la meme app (limiter a 5)
607
+ if app_simple:
608
+ try:
609
+ app_config = apps.get_app_config(app_simple)
610
+ for model in list(app_config.get_models())[:5]:
611
+ if model.__name__ != model_name:
612
+ model_info = self._get_model_info(app_simple, model.__name__)
613
+ if model_info:
614
+ model_info['is_current'] = False
615
+ relevant_models.append(model_info)
616
+ except LookupError:
617
+ pass
618
+
619
+ # 3. Si moins de 3 modeles, ajouter les modeles principaux du projet
620
+ if len(relevant_models) < 3:
621
+ for app_config in apps.get_app_configs():
622
+ if apps_prefix and not app_config.name.startswith(apps_prefix):
623
+ continue
624
+ if app_config.name.startswith('django.') or app_config.name == 'lucy_assist':
625
+ continue
626
+ if app_config.label == app_simple:
627
+ continue
628
+
629
+ for model in list(app_config.get_models())[:2]:
630
+ if len(relevant_models) >= 8:
631
+ break
632
+ model_info = self._get_model_info(app_config.label, model.__name__)
633
+ if model_info:
634
+ model_info['is_current'] = False
635
+ relevant_models.append(model_info)
636
+
637
+ if len(relevant_models) >= 8:
638
+ break
639
+
640
+ return relevant_models
641
+
642
+ def _get_model_info(self, app_label: str, model_name: str) -> Optional[Dict]:
643
+ """Retourne les informations d'un modele."""
644
+ try:
645
+ model = apps.get_model(app_label, model_name)
646
+ fields = []
647
+ for field in model._meta.get_fields():
648
+ if hasattr(field, 'name') and not field.name.startswith('_'):
649
+ if hasattr(field, 'auto_created') and field.auto_created:
650
+ continue
651
+ if field.name in ('id', 'pk', 'created_at', 'updated_at'):
652
+ continue
653
+ fields.append(field.name)
654
+
655
+ return {
656
+ 'app': app_label,
657
+ 'name': model_name,
658
+ 'name_lower': model_name.lower(),
659
+ 'fields': fields[:8],
660
+ 'verbose_name': str(model._meta.verbose_name) if hasattr(model._meta, 'verbose_name') else model_name
661
+ }
662
+ except LookupError:
663
+ return None
664
+
665
+ def get_all_model_names(self) -> List[str]:
666
+ """
667
+ Retourne la liste de TOUS les noms de modeles du projet.
668
+ Utilise pour informer Lucy de ce qui existe vs ce qui n'existe pas.
669
+
670
+ Returns:
671
+ Liste des noms de modeles (ex: ['Client', 'Reservation', 'Facture'])
672
+ """
673
+ model_names = []
674
+ apps_prefix = lucy_assist_settings.PROJECT_APPS_PREFIX
675
+
676
+ for app_config in apps.get_app_configs():
677
+ # Filtrer les apps si un préfixe est configuré
678
+ if apps_prefix and not app_config.name.startswith(apps_prefix):
679
+ continue
680
+
681
+ # Ignorer les apps Django internes et lucy_assist
682
+ if app_config.name.startswith('django.') or app_config.name == 'lucy_assist':
683
+ continue
684
+
685
+ for model in app_config.get_models():
686
+ model_names.append(model.__name__)
687
+
688
+ return sorted(set(model_names))
@@ -160,6 +160,8 @@ class CRUDService:
160
160
  """
161
161
  Appelle une vue du projet via RequestFactory.
162
162
 
163
+ Utilise la découverte dynamique des vues (cache mémoire 5 min).
164
+
163
165
  Args:
164
166
  model_name: Nom du modèle
165
167
  action: Action CRUD ('create', 'update', 'delete', 'list', 'detail')
@@ -169,13 +171,10 @@ class CRUDService:
169
171
  Returns:
170
172
  Dict avec 'success', 'response', 'messages' ou None si pas de vue
171
173
  """
172
- view_info = self.config.get_crud_view_for_model(model_name, action)
173
-
174
- # Essayer aussi avec des variations du nom de modèle
175
- if not view_info:
176
- from lucy_assist.services.view_discovery_service import ViewDiscoveryService
177
- service = ViewDiscoveryService()
178
- view_info = service.get_view_info(model_name, action)
174
+ # Utiliser la découverte dynamique des vues (cache mémoire)
175
+ from lucy_assist.services.view_discovery_service import ViewDiscoveryService
176
+ service = ViewDiscoveryService()
177
+ view_info = service.get_view_info(model_name, action)
179
178
 
180
179
  if not view_info:
181
180
  LogUtils.info(f"[CRUD] Pas de vue trouvée pour {model_name}.{action}")
@@ -187,6 +186,36 @@ class CRUDService:
187
186
 
188
187
  LogUtils.info(f"[CRUD] Appel vue {url_name} ({method}) pour {model_name}.{action}")
189
188
 
189
+ # Vérifier que l'utilisateur est authentifié
190
+ if not self.user or not self.user.is_authenticated:
191
+ LogUtils.warning(f"[CRUD] Utilisateur non authentifié pour {model_name}.{action}")
192
+ return {
193
+ 'success': False,
194
+ 'error': "Vous devez être connecté pour effectuer cette action.",
195
+ 'permission_denied': True,
196
+ 'messages': []
197
+ }
198
+
199
+ # Vérifier les permissions Django standard (optionnel mais rapide)
200
+ action_to_perm = {
201
+ 'create': 'add',
202
+ 'update': 'change',
203
+ 'delete': 'delete',
204
+ 'detail': 'view',
205
+ 'list': 'view',
206
+ }
207
+ perm_action = action_to_perm.get(action)
208
+ if perm_action and not self.user.is_superuser:
209
+ # Trouver l'app_label du modèle
210
+ model = self.get_model('', model_name)
211
+ if model:
212
+ app_label = model._meta.app_label
213
+ perm_codename = f"{app_label}.{perm_action}_{model_name.lower()}"
214
+ if not self.user.has_perm(perm_codename):
215
+ LogUtils.warning(f"[CRUD] Permission Django manquante: {perm_codename}")
216
+ # On ne bloque pas ici car la vue peut avoir des permissions custom
217
+ # mais on log pour le debug
218
+
190
219
  try:
191
220
  # Construire l'URL
192
221
  if requires_pk and object_id:
@@ -226,21 +255,49 @@ class CRUDService:
226
255
  self._cleanup_thread_local()
227
256
 
228
257
  # Analyser la réponse
229
- success = response.status_code in [200, 201, 302]
258
+ redirect_url = response.get('Location', '') if response.status_code == 302 else ''
230
259
  messages = self._extract_messages(request)
231
260
 
232
- # Détecter les erreurs dans la réponse
261
+ # Vérifier si c'est une redirection vers login (permission refusée)
262
+ is_permission_denied = False
263
+ if response.status_code == 302 and redirect_url:
264
+ login_patterns = ['/login', '/connexion', '/auth', 'accounts/login']
265
+ if any(pattern in redirect_url.lower() for pattern in login_patterns):
266
+ is_permission_denied = True
267
+ LogUtils.warning(f"[CRUD] Permission refusée - redirection vers {redirect_url}")
268
+
269
+ # Vérifier si c'est une erreur 403 (Forbidden)
270
+ if response.status_code == 403:
271
+ is_permission_denied = True
272
+ LogUtils.warning(f"[CRUD] Permission refusée - HTTP 403")
273
+
274
+ if is_permission_denied:
275
+ return {
276
+ 'success': False,
277
+ 'status_code': response.status_code,
278
+ 'error': "Vous n'avez pas les permissions nécessaires pour effectuer cette action.",
279
+ 'permission_denied': True,
280
+ 'messages': messages
281
+ }
282
+
283
+ # Déterminer le succès
284
+ success = response.status_code in [200, 201, 302]
285
+
286
+ # Détecter les erreurs dans la réponse HTML
233
287
  if hasattr(response, 'content'):
234
288
  content = response.content.decode('utf-8', errors='replace')
235
289
  # Chercher des indicateurs d'erreur dans le HTML
236
290
  if 'class="error' in content or 'class="invalid' in content:
237
291
  success = False
292
+ # Détecter les messages d'erreur de permission dans le contenu
293
+ if 'permission' in content.lower() and ('denied' in content.lower() or 'refusé' in content.lower()):
294
+ success = False
238
295
 
239
296
  return {
240
297
  'success': success,
241
298
  'status_code': response.status_code,
242
299
  'messages': messages,
243
- 'redirect_url': response.get('Location') if response.status_code == 302 else None
300
+ 'redirect_url': redirect_url if response.status_code == 302 else None
244
301
  }
245
302
 
246
303
  except NoReverseMatch as e:
@@ -480,7 +537,16 @@ class CRUDService:
480
537
  'via_view': True
481
538
  }
482
539
 
483
- # Fallback: création directe via le modèle
540
+ # Si CRUD_VIEWS_ONLY est activé, refuser l'opération
541
+ if lucy_assist_settings.get('CRUD_VIEWS_ONLY', True):
542
+ LogUtils.info(f"[CRUD] Aucune vue trouvée pour {model_name}.create et CRUD_VIEWS_ONLY=True")
543
+ return {
544
+ 'success': False,
545
+ 'errors': [f"Aucune vue de création trouvée pour {model_name}. L'opération CRUD n'est pas disponible pour ce modèle."],
546
+ 'no_view_found': True
547
+ }
548
+
549
+ # Fallback: création directe via le modèle (si autorisé)
484
550
  LogUtils.info(f"[CRUD] Fallback création directe pour {model_name}")
485
551
  return self._create_object_direct(app_name, model_name, data)
486
552
 
@@ -594,7 +660,16 @@ class CRUDService:
594
660
  'via_view': True
595
661
  }
596
662
 
597
- # Fallback: mise à jour directe via le modèle
663
+ # Si CRUD_VIEWS_ONLY est activé, refuser l'opération
664
+ if lucy_assist_settings.get('CRUD_VIEWS_ONLY', True):
665
+ LogUtils.info(f"[CRUD] Aucune vue trouvée pour {model_name}.update et CRUD_VIEWS_ONLY=True")
666
+ return {
667
+ 'success': False,
668
+ 'errors': [f"Aucune vue de modification trouvée pour {model_name}. L'opération CRUD n'est pas disponible pour ce modèle."],
669
+ 'no_view_found': True
670
+ }
671
+
672
+ # Fallback: mise à jour directe via le modèle (si autorisé)
598
673
  LogUtils.info(f"[CRUD] Fallback mise à jour directe pour {model_name}")
599
674
  return self._update_object_direct(app_name, model_name, object_id, data)
600
675
 
@@ -715,7 +790,16 @@ class CRUDService:
715
790
  'via_view': True
716
791
  }
717
792
 
718
- # Fallback: suppression directe via le modèle
793
+ # Si CRUD_VIEWS_ONLY est activé, refuser l'opération
794
+ if lucy_assist_settings.get('CRUD_VIEWS_ONLY', True):
795
+ LogUtils.info(f"[CRUD] Aucune vue trouvée pour {model_name}.delete et CRUD_VIEWS_ONLY=True")
796
+ return {
797
+ 'success': False,
798
+ 'errors': [f"Aucune vue de suppression trouvée pour {model_name}. L'opération CRUD n'est pas disponible pour ce modèle."],
799
+ 'no_view_found': True
800
+ }
801
+
802
+ # Fallback: suppression directe via le modèle (si autorisé)
719
803
  LogUtils.info(f"[CRUD] Fallback suppression directe pour {model_name}")
720
804
  return self._delete_object_direct(app_name, model_name, object_id)
721
805
 
@@ -35,6 +35,26 @@ class GitLabService:
35
35
  """Construit l'URL de l'API."""
36
36
  return f"{self.base_url}/api/v4{endpoint}"
37
37
 
38
+ def get_default_branch(self) -> str:
39
+ """
40
+ Recupere la branche par defaut du projet.
41
+
42
+ Returns:
43
+ Nom de la branche par defaut ou 'main' si erreur
44
+ """
45
+ if not self.token or not self.project_id:
46
+ return 'main'
47
+
48
+ try:
49
+ url = self._api_url(f"/projects/{self.project_id}")
50
+ response = requests.get(url, headers=self.headers, timeout=10)
51
+ response.raise_for_status()
52
+ project_info = response.json()
53
+ return project_info.get('default_branch', 'main')
54
+ except requests.RequestException as e:
55
+ LogUtils.error(f"Erreur lors de la recuperation de la branche par defaut: {e}")
56
+ return 'main'
57
+
38
58
  def search_code(
39
59
  self,
40
60
  query: str,
@@ -76,14 +96,14 @@ class GitLabService:
76
96
  def get_file_content(
77
97
  self,
78
98
  file_path: str,
79
- ref: str = 'main'
99
+ ref: str = None
80
100
  ) -> Optional[str]:
81
101
  """
82
- Récupère le contenu d'un fichier.
102
+ Recupere le contenu d'un fichier.
83
103
 
84
104
  Args:
85
105
  file_path: Chemin du fichier dans le repo
86
- ref: Branche ou tag
106
+ ref: Branche ou tag (None = branche par defaut)
87
107
 
88
108
  Returns:
89
109
  Contenu du fichier ou None
@@ -91,6 +111,10 @@ class GitLabService:
91
111
  if not self.token or not self.project_id:
92
112
  return None
93
113
 
114
+ # Utiliser la branche par defaut si non specifiee
115
+ if ref is None:
116
+ ref = self.get_default_branch()
117
+
94
118
  try:
95
119
  encoded_path = quote(file_path, safe='')
96
120
  url = self._api_url(
@@ -104,16 +128,16 @@ class GitLabService:
104
128
  return response.text
105
129
 
106
130
  except requests.RequestException as e:
107
- LogUtils.error(f"Erreur lors de la récupération du fichier {file_path}: {e}")
131
+ LogUtils.error(f"Erreur lors de la recuperation du fichier {file_path}: {e}")
108
132
  return None
109
133
 
110
134
  def get_file_blame(
111
135
  self,
112
136
  file_path: str,
113
- ref: str = 'main'
137
+ ref: str = None
114
138
  ) -> List[Dict]:
115
139
  """
116
- Récupère le blame d'un fichier (qui a modifié quoi).
140
+ Recupere le blame d'un fichier (qui a modifie quoi).
117
141
 
118
142
  Returns:
119
143
  Liste des blocs de blame
@@ -121,6 +145,10 @@ class GitLabService:
121
145
  if not self.token or not self.project_id:
122
146
  return []
123
147
 
148
+ # Utiliser la branche par defaut si non specifiee
149
+ if ref is None:
150
+ ref = self.get_default_branch()
151
+
124
152
  try:
125
153
  encoded_path = quote(file_path, safe='')
126
154
  url = self._api_url(
@@ -218,6 +246,51 @@ class GitLabService:
218
246
 
219
247
  return result
220
248
 
249
+ def get_repository_tree(
250
+ self,
251
+ path: str = '',
252
+ ref: str = None,
253
+ recursive: bool = True,
254
+ per_page: int = 100
255
+ ) -> List[Dict]:
256
+ """
257
+ Liste les fichiers et dossiers du repository.
258
+
259
+ Args:
260
+ path: Chemin du dossier (vide = racine)
261
+ ref: Branche ou tag (None = branche par defaut)
262
+ recursive: Inclure les sous-dossiers
263
+ per_page: Nombre de resultats par page
264
+
265
+ Returns:
266
+ Liste des fichiers/dossiers avec 'name', 'path', 'type'
267
+ """
268
+ if not self.token or not self.project_id:
269
+ return []
270
+
271
+ # Utiliser la branche par defaut si non specifiee
272
+ if ref is None:
273
+ ref = self.get_default_branch()
274
+
275
+ try:
276
+ url = self._api_url(f"/projects/{self.project_id}/repository/tree")
277
+ params = {
278
+ 'ref': ref,
279
+ 'recursive': str(recursive).lower(),
280
+ 'per_page': per_page
281
+ }
282
+ if path:
283
+ params['path'] = path
284
+
285
+ response = requests.get(url, headers=self.headers, params=params, timeout=15)
286
+ response.raise_for_status()
287
+
288
+ return response.json()
289
+
290
+ except requests.RequestException as e:
291
+ LogUtils.error(f"Erreur lors de la recuperation de l'arborescence: {e}")
292
+ return []
293
+
221
294
  def get_recent_commits(self, per_page: int = 10) -> List[Dict]:
222
295
  """
223
296
  Récupère les commits récents.
@@ -60,6 +60,8 @@ class MistralService:
60
60
  Utilise le prompt stocke en base de donnees et le cache
61
61
  pour reduire la redondance des informations sur le projet.
62
62
  """
63
+ from lucy_assist.services.context_service import ContextService
64
+
63
65
  # Recuperer les permissions utilisateur (compressees)
64
66
  user_permissions = []
65
67
  if hasattr(user, 'get_all_permissions'):
@@ -69,25 +71,43 @@ class MistralService:
69
71
 
70
72
  # Recuperer le contexte projet optimise depuis le cache
71
73
  page_url = page_context.get('page_url', page_context.get('url', ''))
72
- optimized_context = self.project_context_service.get_optimized_context(
73
- page_url=page_url,
74
- user_question=user_question
75
- )
76
74
 
77
- # Fusionner le contexte de page avec le contexte projet cache
75
+ # Utiliser ContextService pour obtenir les modeles pertinents (optimisation tokens)
76
+ context_service = ContextService(user)
77
+ relevant_models = context_service.get_relevant_models(page_url)
78
+
79
+ # Formater les modeles pertinents (avec details)
80
+ models_description = []
81
+ for model in relevant_models:
82
+ prefix = "[PAGE ACTUELLE] " if model.get('is_current') else ""
83
+ fields_str = ", ".join(model.get('fields', []))
84
+ models_description.append(
85
+ f"{prefix}{model['name']} ({model['name_lower']}): {fields_str}"
86
+ )
87
+
88
+ # Ajouter la liste complete de TOUS les modeles disponibles (juste les noms)
89
+ all_model_names = context_service.get_all_model_names()
90
+ if all_model_names:
91
+ models_description.append("")
92
+ models_description.append("Liste complete des modeles existants: " + ", ".join(all_model_names))
93
+
94
+ available_models = "\n".join(models_description) if models_description else "Aucun modele"
95
+
96
+ # Enrichir le contexte de page avec l'aide contextuelle
97
+ page_help = context_service.get_page_help(page_url)
78
98
  enriched_context = {
79
- 'page': page_context,
80
- 'projet': optimized_context.get('relevant_info', {}),
81
- 'cache_stats': optimized_context.get('stats', {})
99
+ 'url': page_url,
100
+ 'type_page': page_help.get('page_type'),
101
+ 'modele_actuel': page_help.get('current_model'),
102
+ 'aide': page_help.get('help_message'),
103
+ 'actions_suggerees': page_help.get('suggested_actions', []),
104
+ 'liens_utiles': page_help.get('useful_links', [])
82
105
  }
83
106
 
84
107
  # Recuperer la configuration avec le prompt systeme
85
108
  from lucy_assist.models import ConfigurationLucyAssist
86
109
  config = ConfigurationLucyAssist.get_config()
87
110
 
88
- # Generer la description des modeles disponibles
89
- available_models = config.get_available_models_description()
90
-
91
111
  # Construire le prompt depuis la configuration
92
112
  prompt = config.get_system_prompt(
93
113
  page_context=json.dumps(enriched_context, ensure_ascii=False, indent=2),