django-lucy-assist 1.2.2__tar.gz → 1.2.4__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.2.2 → django_lucy_assist-1.2.4}/PKG-INFO +1 -1
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/django_lucy_assist.egg-info/PKG-INFO +1 -1
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/django_lucy_assist.egg-info/SOURCES.txt +0 -3
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/__init__.py +1 -1
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/admin.py +13 -13
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/conf.py +4 -0
- django_lucy_assist-1.2.4/lucy_assist/constantes.py +166 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/migrations/0001_initial.py +3 -1
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/models/configuration.py +5 -13
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/context_service.py +234 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/crud_service.py +97 -13
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/gitlab_service.py +79 -6
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/mistral_service.py +31 -11
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/tool_executor_service.py +91 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/tools_definition.py +65 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/view_discovery_service.py +47 -13
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/static/lucy_assist/css/lucy-assist.css +48 -1
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/static/lucy_assist/js/lucy-assist.js +11 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/templates/lucy_assist/chatbot_sidebar.html +11 -1
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/pyproject.toml +1 -1
- django_lucy_assist-1.2.2/lucy_assist/constantes.py +0 -116
- django_lucy_assist-1.2.2/lucy_assist/migrations/0002_configurationlucyassist_prompt_complementaire.py +0 -18
- django_lucy_assist-1.2.2/lucy_assist/migrations/0003_configurationlucyassist_crud_views_mapping.py +0 -18
- django_lucy_assist-1.2.2/lucy_assist/migrations/0004_configurationlucyassist_system_prompt.py +0 -22
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/MANIFEST.in +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/README.md +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/django_lucy_assist.egg-info/dependency_links.txt +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/django_lucy_assist.egg-info/requires.txt +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/django_lucy_assist.egg-info/top_level.txt +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/apps.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/context_processors.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/migrations/__init__.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/models/__init__.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/models/base.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/models/conversation.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/models/message.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/models/project_context_cache.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/__init__.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/bug_notification_service.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/project_context_service.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/signals.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/static/lucy_assist/image/icon-lucy.png +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/templates/lucy_assist/partials/documentation_content.html +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/tests/__init__.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/tests/factories/__init__.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/tests/factories/lucy_assist_factories.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/tests/test_lucy_assist.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/urls.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/utils/__init__.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/utils/log_utils.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/utils/message_utils.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/utils/token_utils.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/views/__init__.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/views/api_views.py +0 -0
- {django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/setup.cfg +0 -0
{django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/django_lucy_assist.egg-info/SOURCES.txt
RENAMED
|
@@ -15,9 +15,6 @@ 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
|
|
19
|
-
lucy_assist/migrations/0003_configurationlucyassist_crud_views_mapping.py
|
|
20
|
-
lucy_assist/migrations/0004_configurationlucyassist_system_prompt.py
|
|
21
18
|
lucy_assist/migrations/__init__.py
|
|
22
19
|
lucy_assist/models/__init__.py
|
|
23
20
|
lucy_assist/models/base.py
|
|
@@ -5,7 +5,7 @@ Un chatbot IA base sur Mistral AI, integrable dans n'importe quelle
|
|
|
5
5
|
application Django pour fournir une assistance contextuelle aux utilisateurs.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = '1.2.
|
|
8
|
+
__version__ = '1.2.4'
|
|
9
9
|
__author__ = 'Revolucy'
|
|
10
10
|
|
|
11
11
|
default_app_config = 'lucy_assist.apps.LucyAssistConfig'
|
|
@@ -28,10 +28,10 @@ class ConfigurationLucyAssistAdmin(admin.ModelAdmin):
|
|
|
28
28
|
('Personnalisation', {
|
|
29
29
|
'fields': ('avatar', 'questions_frequentes')
|
|
30
30
|
}),
|
|
31
|
-
('Prompt
|
|
32
|
-
'fields': ('system_prompt',
|
|
31
|
+
('Prompt Systeme', {
|
|
32
|
+
'fields': ('system_prompt',),
|
|
33
33
|
'classes': ('collapse',),
|
|
34
|
-
'description': 'Le prompt
|
|
34
|
+
'description': 'Le prompt systeme est initialise automatiquement si vide.'
|
|
35
35
|
}),
|
|
36
36
|
('Mapping Automatique (lecture seule)', {
|
|
37
37
|
'fields': ('crud_views_mapping_display', 'model_app_mapping_display'),
|
|
@@ -41,36 +41,36 @@ class ConfigurationLucyAssistAdmin(admin.ModelAdmin):
|
|
|
41
41
|
actions = ['refresh_crud_views']
|
|
42
42
|
|
|
43
43
|
def nb_vues_crud(self, obj):
|
|
44
|
-
"""Affiche le nombre de
|
|
44
|
+
"""Affiche le nombre de modeles avec des vues CRUD decouvertes."""
|
|
45
45
|
if obj.crud_views_mapping:
|
|
46
46
|
return len(obj.crud_views_mapping)
|
|
47
47
|
return 0
|
|
48
|
-
nb_vues_crud.short_description = "
|
|
48
|
+
nb_vues_crud.short_description = "Modeles CRUD"
|
|
49
49
|
|
|
50
50
|
def crud_views_mapping_display(self, obj):
|
|
51
|
-
"""Affiche le mapping des vues CRUD de
|
|
51
|
+
"""Affiche le mapping des vues CRUD de maniere lisible."""
|
|
52
52
|
import json
|
|
53
53
|
if obj.crud_views_mapping:
|
|
54
54
|
return json.dumps(obj.crud_views_mapping, indent=2, ensure_ascii=False)
|
|
55
|
-
return "Aucune vue
|
|
56
|
-
crud_views_mapping_display.short_description = "Vues CRUD
|
|
55
|
+
return "Aucune vue decouverte. Utilisez l'action 'Rafraichir les vues CRUD'."
|
|
56
|
+
crud_views_mapping_display.short_description = "Vues CRUD decouvertes"
|
|
57
57
|
|
|
58
58
|
def model_app_mapping_display(self, obj):
|
|
59
|
-
"""Affiche le mapping
|
|
59
|
+
"""Affiche le mapping modele -> app de maniere lisible."""
|
|
60
60
|
import json
|
|
61
61
|
if obj.model_app_mapping:
|
|
62
62
|
return json.dumps(obj.model_app_mapping, indent=2, ensure_ascii=False)
|
|
63
63
|
return "Aucun mapping. Sera construit automatiquement."
|
|
64
|
-
model_app_mapping_display.short_description = "Mapping
|
|
64
|
+
model_app_mapping_display.short_description = "Mapping Modele -> App"
|
|
65
65
|
|
|
66
|
-
@admin.action(description="
|
|
66
|
+
@admin.action(description="Rafraichir les vues CRUD decouvertes")
|
|
67
67
|
def refresh_crud_views(self, request, queryset):
|
|
68
|
-
"""Action admin pour
|
|
68
|
+
"""Action admin pour rafraichir le mapping des vues CRUD."""
|
|
69
69
|
for config in queryset:
|
|
70
70
|
mapping = config.refresh_crud_views_mapping()
|
|
71
71
|
nb_models = len(mapping)
|
|
72
72
|
nb_views = sum(len(actions) for actions in mapping.values())
|
|
73
73
|
self.message_user(
|
|
74
74
|
request,
|
|
75
|
-
f"Mapping
|
|
75
|
+
f"Mapping rafraichi: {nb_models} modeles, {nb_views} vues decouvertes."
|
|
76
76
|
)
|
|
@@ -67,6 +67,10 @@ class LucyAssistSettings:
|
|
|
67
67
|
# Attributs de l'utilisateur a copier vers la requete
|
|
68
68
|
# Ex: ['franchise', 'tenant', 'organization']
|
|
69
69
|
'REQUEST_USER_ATTRS': [],
|
|
70
|
+
|
|
71
|
+
# Forcer l'utilisation des vues CRUD (pas de fallback ORM direct)
|
|
72
|
+
# Si True, Lucy refusera de creer/modifier/supprimer si aucune vue n'est trouvee
|
|
73
|
+
'CRUD_VIEWS_ONLY': True,
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
def __init__(self):
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constantes pour Lucy Assist
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LucyAssistConstantes:
|
|
7
|
+
"""Constantes générales de Lucy Assist"""
|
|
8
|
+
|
|
9
|
+
# Prix par million de tokens (en euros)
|
|
10
|
+
PRIX_PAR_MILLION_TOKENS = 50.0
|
|
11
|
+
|
|
12
|
+
# Nombre moyen de tokens par conversation
|
|
13
|
+
TOKENS_MOYENS_PAR_CONVERSATION = 2000
|
|
14
|
+
|
|
15
|
+
# Nombre de conversations estimées pour 1 million de tokens
|
|
16
|
+
CONVERSATIONS_PAR_MILLION = 500 # 1_000_000 / 2000
|
|
17
|
+
|
|
18
|
+
class Repondant:
|
|
19
|
+
"""Types de répondants dans une conversation"""
|
|
20
|
+
UTILISATEUR = 'UTILISATEUR'
|
|
21
|
+
CHATBOT = 'CHATBOT'
|
|
22
|
+
|
|
23
|
+
tuples = [
|
|
24
|
+
(UTILISATEUR, 'Utilisateur'),
|
|
25
|
+
(CHATBOT, 'Lucy Assist'),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
class TypeAction:
|
|
29
|
+
"""Types d'actions que Lucy Assist peut effectuer"""
|
|
30
|
+
AIDE_NAVIGATION = 'AIDE_NAVIGATION'
|
|
31
|
+
RECHERCHE = 'RECHERCHE'
|
|
32
|
+
CRUD = 'CRUD'
|
|
33
|
+
ANALYSE_BUG = 'ANALYSE_BUG'
|
|
34
|
+
EXPLICATION = 'EXPLICATION'
|
|
35
|
+
|
|
36
|
+
tuples = [
|
|
37
|
+
(AIDE_NAVIGATION, 'Aide à la navigation'),
|
|
38
|
+
(RECHERCHE, 'Recherche d\'objets'),
|
|
39
|
+
(CRUD, 'Création/Modification/Suppresion d\'objets'),
|
|
40
|
+
(ANALYSE_BUG, 'Analyse de bug'),
|
|
41
|
+
(EXPLICATION, 'Explication de fonctionnalité'),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
class StatutConversation:
|
|
45
|
+
"""Statuts possibles d'une conversation"""
|
|
46
|
+
ACTIVE = 'ACTIVE'
|
|
47
|
+
ARCHIVEE = 'ARCHIVEE'
|
|
48
|
+
|
|
49
|
+
tuples = [
|
|
50
|
+
(ACTIVE, 'Active'),
|
|
51
|
+
(ARCHIVEE, 'Archivée'),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# Prompts systeme pour Mistral
|
|
55
|
+
SYSTEM_PROMPTS = {
|
|
56
|
+
'default': """Tu es Lucy, une assistante IA sympathique et efficace integree dans {application_name}.
|
|
57
|
+
Tu aides les utilisateurs a naviguer et effectuer des actions dans l'application.
|
|
58
|
+
|
|
59
|
+
## TES CAPACITES
|
|
60
|
+
|
|
61
|
+
1. **Aide contextuelle** : Tu vois la page ou se trouve l'utilisateur et tu proposes une aide adaptee
|
|
62
|
+
2. **Recherche intelligente** : Tu peux chercher n'importe quel objet de base de données, suivant les permissions de l'utilisateur connectés
|
|
63
|
+
3. **Creation assistee** : Tu peux creer des elements directement via la conversation, suivant les CRUD disponibles et les permissions de l'utilisateur connectés
|
|
64
|
+
4. **Analyse de bugs** : Tu peux analyser les problemes signales par l'utilisateur en inspectant le projet GIT
|
|
65
|
+
|
|
66
|
+
## COMPORTEMENT - ACTION FIRST
|
|
67
|
+
|
|
68
|
+
Quand l'utilisateur demande une action (creer, modifier, chercher, supprimer) :
|
|
69
|
+
-> EXECUTE L'ACTION IMMEDIATEMENT avec les tools disponibles, UNIQUEMENT si les vues de CRUD sont disponibles dans le projet
|
|
70
|
+
-> Passe par les vues CRUD pour qu'on puisse avoir les logs, et que si il y a des services connecté aux vues, ceux soit appelés.
|
|
71
|
+
-> Ne te contente pas d'expliquer comment faire
|
|
72
|
+
|
|
73
|
+
Quand l'utilisateur pose une question ou demande de l'aide :
|
|
74
|
+
-> Reponds de maniere claire et concise
|
|
75
|
+
-> Propose des actions concretes avec des liens
|
|
76
|
+
|
|
77
|
+
## LIMITES STRICTES
|
|
78
|
+
|
|
79
|
+
Tu es EXCLUSIVEMENT dediee a l'aide sur {application_name}.
|
|
80
|
+
|
|
81
|
+
### Regle absolue sur les modeles
|
|
82
|
+
Tu ne peux travailler QU'AVEC les modeles listes dans "MODELES DISPONIBLES" ci-dessous.
|
|
83
|
+
- Si un modele n'est PAS dans la liste -> il N'EXISTE PAS dans cette application
|
|
84
|
+
- Ne JAMAIS inventer, supposer ou suggerer des modeles/fonctionnalites absents de la liste
|
|
85
|
+
- Ne JAMAIS parler de "logements", "produits", "commandes" etc. si ces modeles ne sont pas listes
|
|
86
|
+
|
|
87
|
+
Avant toute action CRUD, VERIFIE que le modele demande est dans la liste.
|
|
88
|
+
Si le modele n'existe pas, reponds :
|
|
89
|
+
"Cette fonctionnalite n'est pas disponible dans {application_name}. Voici ce que je peux faire : [lister les modeles disponibles]"
|
|
90
|
+
|
|
91
|
+
### Demandes hors sujet
|
|
92
|
+
Tu REFUSES POLIMENT :
|
|
93
|
+
- Questions sans rapport avec l'application
|
|
94
|
+
- Demandes de redaction, traduction, culture generale, recettes, etc.
|
|
95
|
+
|
|
96
|
+
Reponse type :
|
|
97
|
+
"Je suis Lucy, l'assistante de {application_name}. Je ne peux repondre qu'aux questions concernant l'application."
|
|
98
|
+
|
|
99
|
+
IMPORTANT : Ne donne JAMAIS la reponse meme partiellement. Refuse et redirige vers l'application.
|
|
100
|
+
|
|
101
|
+
## COMMUNICATION
|
|
102
|
+
|
|
103
|
+
Tu parles a un UTILISATEUR FINAL (pas un developpeur) :
|
|
104
|
+
- Langage simple et accessible
|
|
105
|
+
- Reponses courtes et directes
|
|
106
|
+
- Liens cliquables vers les pages (utilise navigate_to_page)
|
|
107
|
+
- Ne JAMAIS mentionner : fichiers, code, GitLab, classes Python, chemins techniques
|
|
108
|
+
|
|
109
|
+
## AIDE CONTEXTUELLE
|
|
110
|
+
|
|
111
|
+
Utilise le contexte de la page pour personnaliser ton aide :
|
|
112
|
+
- Sur une liste : propose de filtrer, chercher, ou creer un nouvel element
|
|
113
|
+
- Sur un formulaire : aide a remplir les champs ou explique leur utilite
|
|
114
|
+
- Sur un detail : propose de modifier, supprimer, ou voir les elements lies
|
|
115
|
+
|
|
116
|
+
## EXEMPLES DE BONNES REPONSES
|
|
117
|
+
|
|
118
|
+
Recherche :
|
|
119
|
+
"J'ai trouve 3 clients correspondant a 'Dupont' : [Jean Dupont](/client/42/), [Marie Dupont](/client/87/), [Dupont SARL](/client/156/)"
|
|
120
|
+
|
|
121
|
+
Creation :
|
|
122
|
+
"J'ai cree le membre Jean Martin (ID: 234). [Voir sa fiche](/membre/234/)"
|
|
123
|
+
|
|
124
|
+
Aide :
|
|
125
|
+
"Sur cette page, vous pouvez :
|
|
126
|
+
- Modifier les informations en cliquant sur les champs
|
|
127
|
+
- [Voir l'historique](/client/42/historique/)
|
|
128
|
+
- [Supprimer ce client](/client/42/delete/)"
|
|
129
|
+
|
|
130
|
+
## PROCEDURE DE SUPPRESSION
|
|
131
|
+
|
|
132
|
+
OBLIGATOIRE avant toute suppression :
|
|
133
|
+
1. Utilise `get_deletion_impact` pour analyser les consequences
|
|
134
|
+
2. Montre clairement ce qui sera supprime (objets en cascade, etc.)
|
|
135
|
+
3. Demande confirmation explicite
|
|
136
|
+
4. Supprime SEULEMENT apres confirmation avec `confirmed: true`
|
|
137
|
+
|
|
138
|
+
## CONTEXTE ACTUEL
|
|
139
|
+
|
|
140
|
+
Page : {page_context}
|
|
141
|
+
Permissions : {user_permissions}
|
|
142
|
+
|
|
143
|
+
## MODELES DISPONIBLES (LISTE EXHAUSTIVE)
|
|
144
|
+
Voici les SEULS modeles existants dans cette application. Tout autre modele N'EXISTE PAS.
|
|
145
|
+
{available_models}
|
|
146
|
+
|
|
147
|
+
Si "Aucun modele" est affiche, tu ne peux effectuer AUCUNE operation CRUD.
|
|
148
|
+
""",
|
|
149
|
+
'crud': """Tu dois aider l'utilisateur à créer ou modifier un objet.
|
|
150
|
+
Formulaire disponible : {form_info}
|
|
151
|
+
Champs requis : {required_fields}
|
|
152
|
+
""",
|
|
153
|
+
'bug_analysis': """Analyse le problème signalé par l'utilisateur.
|
|
154
|
+
Code source pertinent : {code_context}
|
|
155
|
+
Erreur rapportée : {error_message}
|
|
156
|
+
""",
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# Questions fréquentes par défaut (utilisées si non configurées dans le modèle)
|
|
160
|
+
QUESTIONS_FREQUENTES_DEFAULT = [
|
|
161
|
+
"Comment créer un nouvel enregistrement ?",
|
|
162
|
+
"Comment effectuer une recherche ?",
|
|
163
|
+
"Comment exporter des données ?",
|
|
164
|
+
"Comment modifier mon profil ?",
|
|
165
|
+
"Quelles sont les fonctionnalités disponibles ?",
|
|
166
|
+
]
|
{django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/migrations/0001_initial.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Generated by Django 6.0.
|
|
1
|
+
# Generated by Django 6.0.2 on 2026-02-04 14:09
|
|
2
2
|
|
|
3
3
|
import django.db.models.deletion
|
|
4
4
|
from django.conf import settings
|
|
@@ -26,7 +26,9 @@ class Migration(migrations.Migration):
|
|
|
26
26
|
('questions_frequentes', models.JSONField(blank=True, default=list)),
|
|
27
27
|
('actif', models.BooleanField(default=True)),
|
|
28
28
|
('avatar', models.ImageField(blank=True, null=True, upload_to='')),
|
|
29
|
+
('system_prompt', models.TextField(blank=True, default='')),
|
|
29
30
|
('model_app_mapping', models.JSONField(blank=True, default=dict)),
|
|
31
|
+
('crud_views_mapping', models.JSONField(blank=True, default=dict)),
|
|
30
32
|
],
|
|
31
33
|
options={
|
|
32
34
|
'verbose_name': 'Configuration Lucy Assist',
|
|
@@ -21,16 +21,12 @@ class ConfigurationLucyAssist(LucyAssistBaseModel):
|
|
|
21
21
|
avatar = models.ImageField(null=True, blank=True)
|
|
22
22
|
|
|
23
23
|
# Prompt système principal pour l'assistant
|
|
24
|
-
# Initialisé automatiquement
|
|
24
|
+
# Initialisé automatiquement si vide
|
|
25
25
|
system_prompt = models.TextField(
|
|
26
26
|
blank=True,
|
|
27
27
|
default='',
|
|
28
|
-
help_text="Prompt système pour l'assistant IA. Initialisé automatiquement si vide."
|
|
29
28
|
)
|
|
30
29
|
|
|
31
|
-
# Instructions complémentaires pour le prompt (ajoutées après le system_prompt)
|
|
32
|
-
prompt_complementaire = models.TextField(blank=True, default='')
|
|
33
|
-
|
|
34
30
|
# Mapping des modèles vers leurs applications Django
|
|
35
31
|
model_app_mapping = models.JSONField(blank=True, default=dict)
|
|
36
32
|
|
|
@@ -245,15 +241,15 @@ class ConfigurationLucyAssist(LucyAssistBaseModel):
|
|
|
245
241
|
available_models: str = ""
|
|
246
242
|
) -> str:
|
|
247
243
|
"""
|
|
248
|
-
Retourne le prompt
|
|
244
|
+
Retourne le prompt systeme formate avec les variables de contexte.
|
|
249
245
|
|
|
250
246
|
Args:
|
|
251
247
|
page_context: Contexte JSON de la page actuelle
|
|
252
248
|
user_permissions: Liste des permissions de l'utilisateur
|
|
253
|
-
available_models: Liste des
|
|
249
|
+
available_models: Liste des modeles disponibles
|
|
254
250
|
|
|
255
251
|
Returns:
|
|
256
|
-
Prompt
|
|
252
|
+
Prompt systeme complet avec le project_context
|
|
257
253
|
"""
|
|
258
254
|
prompt = self.system_prompt
|
|
259
255
|
|
|
@@ -265,10 +261,6 @@ class ConfigurationLucyAssist(LucyAssistBaseModel):
|
|
|
265
261
|
available_models=available_models
|
|
266
262
|
)
|
|
267
263
|
|
|
268
|
-
# Ajouter les instructions complémentaires si configurées
|
|
269
|
-
if self.prompt_complementaire:
|
|
270
|
-
prompt += f"\n\n## Instructions complémentaires\n{self.prompt_complementaire}"
|
|
271
|
-
|
|
272
264
|
return prompt
|
|
273
265
|
|
|
274
266
|
def _get_application_name(self) -> str:
|
|
@@ -319,7 +311,7 @@ class ConfigurationLucyAssist(LucyAssistBaseModel):
|
|
|
319
311
|
fields_str += ", ..."
|
|
320
312
|
descriptions.append(f"- {model_name} ({model_name_lower}) : {fields_str}")
|
|
321
313
|
|
|
322
|
-
return "\n".join(descriptions) if descriptions else "Aucun
|
|
314
|
+
return "\n".join(descriptions) if descriptions else "Aucun modele configure"
|
|
323
315
|
|
|
324
316
|
|
|
325
317
|
def get_default_model_app_mapping() -> dict:
|
{django_lucy_assist-1.2.2 → django_lucy_assist-1.2.4}/lucy_assist/services/context_service.py
RENAMED
|
@@ -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))
|