django-lucy-assist 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_lucy_assist-0.1.0.dist-info/METADATA +206 -0
- django_lucy_assist-0.1.0.dist-info/RECORD +44 -0
- django_lucy_assist-0.1.0.dist-info/WHEEL +5 -0
- django_lucy_assist-0.1.0.dist-info/top_level.txt +1 -0
- lucy_assist/__init__.py +11 -0
- lucy_assist/admin.py +22 -0
- lucy_assist/apps.py +10 -0
- lucy_assist/conf.py +103 -0
- lucy_assist/constantes.py +120 -0
- lucy_assist/context_processors.py +65 -0
- lucy_assist/migrations/0001_initial.py +92 -0
- lucy_assist/migrations/__init__.py +0 -0
- lucy_assist/models/__init__.py +14 -0
- lucy_assist/models/base.py +54 -0
- lucy_assist/models/configuration.py +175 -0
- lucy_assist/models/conversation.py +54 -0
- lucy_assist/models/message.py +45 -0
- lucy_assist/models/project_context_cache.py +213 -0
- lucy_assist/services/__init__.py +21 -0
- lucy_assist/services/bug_notification_service.py +183 -0
- lucy_assist/services/claude_service.py +417 -0
- lucy_assist/services/context_service.py +350 -0
- lucy_assist/services/crud_service.py +364 -0
- lucy_assist/services/gitlab_service.py +248 -0
- lucy_assist/services/project_context_service.py +412 -0
- lucy_assist/services/tool_executor_service.py +343 -0
- lucy_assist/services/tools_definition.py +229 -0
- lucy_assist/signals.py +25 -0
- lucy_assist/static/lucy_assist/css/lucy-assist.css +160 -0
- lucy_assist/static/lucy_assist/image/icon-lucy.png +0 -0
- lucy_assist/static/lucy_assist/js/lucy-assist.js +824 -0
- lucy_assist/templates/lucy_assist/chatbot_sidebar.html +419 -0
- lucy_assist/templates/lucy_assist/partials/documentation_content.html +107 -0
- lucy_assist/tests/__init__.py +0 -0
- lucy_assist/tests/factories/__init__.py +15 -0
- lucy_assist/tests/factories/lucy_assist_factories.py +109 -0
- lucy_assist/tests/test_lucy_assist.py +186 -0
- lucy_assist/urls.py +36 -0
- lucy_assist/utils/__init__.py +7 -0
- lucy_assist/utils/log_utils.py +59 -0
- lucy_assist/utils/message_utils.py +130 -0
- lucy_assist/utils/token_utils.py +87 -0
- lucy_assist/views/__init__.py +13 -0
- lucy_assist/views/api_views.py +595 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Service d'exécution des tools pour Lucy Assist.
|
|
3
|
+
|
|
4
|
+
Ce service fait le lien entre les appels de tools de Claude
|
|
5
|
+
et les services CRUD/Context de l'application.
|
|
6
|
+
"""
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
|
|
9
|
+
from django.urls import reverse
|
|
10
|
+
|
|
11
|
+
from lucy_assist.utils.log_utils import LogUtils
|
|
12
|
+
from lucy_assist.services.crud_service import CRUDService
|
|
13
|
+
from lucy_assist.services.context_service import ContextService
|
|
14
|
+
from lucy_assist.services.tools_definition import get_app_for_model
|
|
15
|
+
from lucy_assist.services.gitlab_service import GitLabService
|
|
16
|
+
from lucy_assist.services.bug_notification_service import BugNotificationService
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ToolExecutorService:
|
|
20
|
+
"""Service pour exécuter les tools appelés par Claude."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, user):
|
|
23
|
+
self.user = user
|
|
24
|
+
self.crud_service = CRUDService(user)
|
|
25
|
+
self.context_service = ContextService(user)
|
|
26
|
+
self.gitlab_service = GitLabService()
|
|
27
|
+
self.bug_notification_service = BugNotificationService(user)
|
|
28
|
+
|
|
29
|
+
def execute(self, tool_name: str, tool_input: Dict[str, Any], user=None) -> Dict[str, Any]:
|
|
30
|
+
"""
|
|
31
|
+
Exécute un tool et retourne le résultat.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
tool_name: Nom du tool à exécuter
|
|
35
|
+
tool_input: Paramètres du tool
|
|
36
|
+
user: Utilisateur (optionnel, utilise self.user si non fourni)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dict avec le résultat de l'exécution
|
|
40
|
+
"""
|
|
41
|
+
if user:
|
|
42
|
+
self.user = user
|
|
43
|
+
self.crud_service = CRUDService(user)
|
|
44
|
+
self.context_service = ContextService(user)
|
|
45
|
+
|
|
46
|
+
# Dispatcher vers la méthode appropriée
|
|
47
|
+
handlers = {
|
|
48
|
+
'create_object': self._handle_create_object,
|
|
49
|
+
'update_object': self._handle_update_object,
|
|
50
|
+
'delete_object': self._handle_delete_object,
|
|
51
|
+
'search_objects': self._handle_search_objects,
|
|
52
|
+
'get_object_details': self._handle_get_object_details,
|
|
53
|
+
'get_form_fields': self._handle_get_form_fields,
|
|
54
|
+
'navigate_to_page': self._handle_navigate_to_page,
|
|
55
|
+
'analyze_bug': self._handle_analyze_bug,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
handler = handlers.get(tool_name)
|
|
59
|
+
if not handler:
|
|
60
|
+
return {'error': f'Tool inconnu: {tool_name}'}
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
return handler(tool_input)
|
|
64
|
+
except Exception as e:
|
|
65
|
+
LogUtils.error(f"Erreur lors de l'exécution du tool {tool_name}")
|
|
66
|
+
return {'error': str(e)}
|
|
67
|
+
|
|
68
|
+
def _handle_create_object(self, params: Dict) -> Dict:
|
|
69
|
+
"""Crée un nouvel objet."""
|
|
70
|
+
app_name = params.get('app_name') or get_app_for_model(params.get('model_name', ''))
|
|
71
|
+
model_name = params.get('model_name', '')
|
|
72
|
+
data = params.get('data', {})
|
|
73
|
+
|
|
74
|
+
if not model_name:
|
|
75
|
+
return {'error': 'model_name est requis'}
|
|
76
|
+
|
|
77
|
+
if not data:
|
|
78
|
+
return {'error': 'data est requis pour créer un objet'}
|
|
79
|
+
|
|
80
|
+
result = self.crud_service.create_object(app_name, model_name, data)
|
|
81
|
+
|
|
82
|
+
if result.get('success'):
|
|
83
|
+
# Ajouter l'URL de l'objet créé
|
|
84
|
+
try:
|
|
85
|
+
url = reverse(
|
|
86
|
+
f'{app_name}:{model_name.lower()}-form',
|
|
87
|
+
kwargs={'pk': result['object_id']}
|
|
88
|
+
)
|
|
89
|
+
result['url'] = url
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
return result
|
|
94
|
+
|
|
95
|
+
def _handle_update_object(self, params: Dict) -> Dict:
|
|
96
|
+
"""Met à jour un objet existant."""
|
|
97
|
+
app_name = params.get('app_name') or get_app_for_model(params.get('model_name', ''))
|
|
98
|
+
model_name = params.get('model_name', '')
|
|
99
|
+
object_id = params.get('object_id')
|
|
100
|
+
data = params.get('data', {})
|
|
101
|
+
|
|
102
|
+
if not model_name:
|
|
103
|
+
return {'error': 'model_name est requis'}
|
|
104
|
+
|
|
105
|
+
if not object_id:
|
|
106
|
+
return {'error': 'object_id est requis'}
|
|
107
|
+
|
|
108
|
+
if not data:
|
|
109
|
+
return {'error': 'data est requis pour modifier un objet'}
|
|
110
|
+
|
|
111
|
+
return self.crud_service.update_object(app_name, model_name, object_id, data)
|
|
112
|
+
|
|
113
|
+
def _handle_delete_object(self, params: Dict) -> Dict:
|
|
114
|
+
"""Supprime un objet."""
|
|
115
|
+
app_name = params.get('app_name') or get_app_for_model(params.get('model_name', ''))
|
|
116
|
+
model_name = params.get('model_name', '')
|
|
117
|
+
object_id = params.get('object_id')
|
|
118
|
+
|
|
119
|
+
if not model_name:
|
|
120
|
+
return {'error': 'model_name est requis'}
|
|
121
|
+
|
|
122
|
+
if not object_id:
|
|
123
|
+
return {'error': 'object_id est requis'}
|
|
124
|
+
|
|
125
|
+
return self.crud_service.delete_object(app_name, model_name, object_id)
|
|
126
|
+
|
|
127
|
+
def _handle_search_objects(self, params: Dict) -> Dict:
|
|
128
|
+
"""Recherche des objets."""
|
|
129
|
+
query = params.get('query', '')
|
|
130
|
+
model_name = params.get('model_name')
|
|
131
|
+
limit = params.get('limit', 10)
|
|
132
|
+
|
|
133
|
+
if not query:
|
|
134
|
+
return {'error': 'query est requis'}
|
|
135
|
+
|
|
136
|
+
results = self.context_service.search_objects(query, model_name, limit)
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
'success': True,
|
|
140
|
+
'count': len(results),
|
|
141
|
+
'results': results
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
def _handle_get_object_details(self, params: Dict) -> Dict:
|
|
145
|
+
"""Récupère les détails d'un objet."""
|
|
146
|
+
app_name = params.get('app_name') or get_app_for_model(params.get('model_name', ''))
|
|
147
|
+
model_name = params.get('model_name', '')
|
|
148
|
+
object_id = params.get('object_id')
|
|
149
|
+
|
|
150
|
+
if not model_name:
|
|
151
|
+
return {'error': 'model_name est requis'}
|
|
152
|
+
|
|
153
|
+
if not object_id:
|
|
154
|
+
return {'error': 'object_id est requis'}
|
|
155
|
+
|
|
156
|
+
result = self.crud_service.get_object(app_name, model_name, object_id)
|
|
157
|
+
|
|
158
|
+
if result:
|
|
159
|
+
return {'success': True, 'data': result}
|
|
160
|
+
else:
|
|
161
|
+
return {'error': f'{model_name} #{object_id} non trouvé ou accès refusé'}
|
|
162
|
+
|
|
163
|
+
def _handle_get_form_fields(self, params: Dict) -> Dict:
|
|
164
|
+
"""Récupère les champs d'un formulaire."""
|
|
165
|
+
app_name = params.get('app_name') or get_app_for_model(params.get('model_name', ''))
|
|
166
|
+
model_name = params.get('model_name', '')
|
|
167
|
+
|
|
168
|
+
if not model_name:
|
|
169
|
+
return {'error': 'model_name est requis'}
|
|
170
|
+
|
|
171
|
+
required_fields = self.crud_service.get_required_fields(
|
|
172
|
+
self.crud_service.get_model(app_name, model_name)
|
|
173
|
+
)
|
|
174
|
+
optional_fields = self.crud_service.get_optional_fields(
|
|
175
|
+
self.crud_service.get_model(app_name, model_name)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
'success': True,
|
|
180
|
+
'model_name': model_name,
|
|
181
|
+
'required_fields': required_fields,
|
|
182
|
+
'optional_fields': optional_fields
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
def _handle_navigate_to_page(self, params: Dict) -> Dict:
|
|
186
|
+
"""Génère une URL de navigation."""
|
|
187
|
+
page_type = params.get('page_type', 'list')
|
|
188
|
+
app_name = params.get('app_name') or get_app_for_model(params.get('model_name', ''))
|
|
189
|
+
model_name = params.get('model_name', '')
|
|
190
|
+
object_id = params.get('object_id')
|
|
191
|
+
|
|
192
|
+
if not model_name:
|
|
193
|
+
return {'error': 'model_name est requis'}
|
|
194
|
+
|
|
195
|
+
model_lower = model_name.lower()
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
if page_type == 'list':
|
|
199
|
+
url = reverse(f'{app_name}:{model_lower}-list')
|
|
200
|
+
elif page_type == 'create':
|
|
201
|
+
url = reverse(f'{app_name}:{model_lower}-form')
|
|
202
|
+
elif page_type in ['detail', 'edit']:
|
|
203
|
+
if not object_id:
|
|
204
|
+
return {'error': 'object_id est requis pour detail/edit'}
|
|
205
|
+
url = reverse(f'{app_name}:{model_lower}-form', kwargs={'pk': object_id})
|
|
206
|
+
else:
|
|
207
|
+
return {'error': f'Type de page inconnu: {page_type}'}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
'success': True,
|
|
211
|
+
'url': url,
|
|
212
|
+
'message': f'Naviguez vers: {url}'
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
except Exception as e:
|
|
216
|
+
LogUtils.info(f"Erreur génération URL: {e}")
|
|
217
|
+
# Fallback: générer une URL probable
|
|
218
|
+
if page_type == 'list':
|
|
219
|
+
url = f'/{app_name}/{model_lower}/list/'
|
|
220
|
+
elif page_type == 'create':
|
|
221
|
+
url = f'/{app_name}/{model_lower}/add/'
|
|
222
|
+
else:
|
|
223
|
+
url = f'/{app_name}/{model_lower}/{object_id}/'
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
'success': True,
|
|
227
|
+
'url': url,
|
|
228
|
+
'message': f'URL probable: {url} (vérifiez la disponibilité)'
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
def _handle_analyze_bug(self, params: Dict) -> Dict:
|
|
232
|
+
"""
|
|
233
|
+
Analyse un bug potentiel en utilisant GitLab et Claude.
|
|
234
|
+
Si un bug est détecté, envoie automatiquement une notification à Revolucy.
|
|
235
|
+
"""
|
|
236
|
+
from lucy_assist.services.claude_service import ClaudeService
|
|
237
|
+
|
|
238
|
+
user_description = params.get('user_description', '')
|
|
239
|
+
error_message = params.get('error_message', '')
|
|
240
|
+
page_url = params.get('page_url', '')
|
|
241
|
+
model_name = params.get('model_name', '')
|
|
242
|
+
action_type = params.get('action_type', 'other')
|
|
243
|
+
|
|
244
|
+
if not user_description:
|
|
245
|
+
return {'error': 'user_description est requis'}
|
|
246
|
+
|
|
247
|
+
# Récupérer le contexte du code via GitLab
|
|
248
|
+
code_context = ""
|
|
249
|
+
file_path = ""
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
# Rechercher le code pertinent basé sur le modèle ou l'URL
|
|
253
|
+
if model_name:
|
|
254
|
+
# Chercher le modèle et ses fichiers associés
|
|
255
|
+
model_info = self.gitlab_service.find_model_and_form(model_name)
|
|
256
|
+
if model_info.get('model_code'):
|
|
257
|
+
code_context += f"=== MODÈLE {model_name} ===\n"
|
|
258
|
+
code_context += f"Fichier: {model_info.get('model_file', 'inconnu')}\n"
|
|
259
|
+
code_context += model_info['model_code'][:3000] + "\n\n"
|
|
260
|
+
file_path = model_info.get('model_file', '')
|
|
261
|
+
|
|
262
|
+
if model_info.get('form_code'):
|
|
263
|
+
code_context += f"=== FORMULAIRE {model_name}Form ===\n"
|
|
264
|
+
code_context += f"Fichier: {model_info.get('form_file', 'inconnu')}\n"
|
|
265
|
+
code_context += model_info['form_code'][:2000] + "\n\n"
|
|
266
|
+
|
|
267
|
+
if page_url:
|
|
268
|
+
# Chercher la vue correspondant à l'URL
|
|
269
|
+
view_info = self.gitlab_service.find_view_for_url(page_url)
|
|
270
|
+
if view_info and view_info.get('code'):
|
|
271
|
+
code_context += f"=== VUE {view_info.get('view_name', 'inconnue')} ===\n"
|
|
272
|
+
code_context += f"Fichier: {view_info.get('file_path', 'inconnu')}\n"
|
|
273
|
+
code_context += view_info['code'][:3000] + "\n"
|
|
274
|
+
if not file_path:
|
|
275
|
+
file_path = view_info.get('file_path', '')
|
|
276
|
+
|
|
277
|
+
# Si pas de code trouvé via les méthodes précédentes, chercher par mots-clés
|
|
278
|
+
if not code_context and error_message:
|
|
279
|
+
# Extraire des mots-clés de l'erreur pour rechercher dans le code
|
|
280
|
+
search_results = self.gitlab_service.search_code(error_message[:100])
|
|
281
|
+
if search_results:
|
|
282
|
+
for result in search_results[:2]:
|
|
283
|
+
file_content = self.gitlab_service.get_file_content(result.get('filename', ''))
|
|
284
|
+
if file_content:
|
|
285
|
+
code_context += f"=== {result.get('filename', 'fichier')} ===\n"
|
|
286
|
+
code_context += file_content[:2000] + "\n\n"
|
|
287
|
+
if not file_path:
|
|
288
|
+
file_path = result.get('filename', '')
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
LogUtils.error(f"Erreur lors de la récupération du code GitLab: {e}")
|
|
292
|
+
code_context = f"Impossible de récupérer le code source: {str(e)}"
|
|
293
|
+
|
|
294
|
+
# Analyser le bug avec Claude
|
|
295
|
+
try:
|
|
296
|
+
claude_service = ClaudeService()
|
|
297
|
+
bug_analysis = claude_service.analyze_code_for_bug(
|
|
298
|
+
error_message=error_message,
|
|
299
|
+
code_context=code_context,
|
|
300
|
+
user_description=user_description
|
|
301
|
+
)
|
|
302
|
+
except Exception as e:
|
|
303
|
+
LogUtils.error(f"Erreur lors de l'analyse Claude: {e}")
|
|
304
|
+
bug_analysis = {
|
|
305
|
+
'is_bug': False,
|
|
306
|
+
'analysis': f'Impossible d\'analyser: {str(e)}',
|
|
307
|
+
'recommendation': 'Veuillez contacter le support technique.'
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
# Si c'est un bug, envoyer la notification automatiquement
|
|
311
|
+
notification_result = {'notified': False}
|
|
312
|
+
if bug_analysis.get('is_bug', False):
|
|
313
|
+
notification_result = self.bug_notification_service.notify_bug_detected(
|
|
314
|
+
bug_analysis=bug_analysis,
|
|
315
|
+
user_description=user_description,
|
|
316
|
+
error_message=error_message,
|
|
317
|
+
code_context=code_context,
|
|
318
|
+
file_path=file_path,
|
|
319
|
+
page_url=page_url
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
'success': True,
|
|
324
|
+
'is_bug': bug_analysis.get('is_bug', False),
|
|
325
|
+
'analysis': bug_analysis.get('analysis', ''),
|
|
326
|
+
'recommendation': bug_analysis.get('recommendation', ''),
|
|
327
|
+
'severity': bug_analysis.get('severity', 'medium') if bug_analysis.get('is_bug') else None,
|
|
328
|
+
'file_path': file_path,
|
|
329
|
+
'revolucy_notified': notification_result.get('notified', False),
|
|
330
|
+
'notification_message': notification_result.get('message', '')
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def create_tool_executor(user):
|
|
335
|
+
"""
|
|
336
|
+
Factory function pour créer un callable d'exécution de tools.
|
|
337
|
+
|
|
338
|
+
Usage:
|
|
339
|
+
executor = create_tool_executor(request.user)
|
|
340
|
+
result = executor('create_object', {...}, user)
|
|
341
|
+
"""
|
|
342
|
+
service = ToolExecutorService(user)
|
|
343
|
+
return service.execute
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Définition des tools pour l'API Claude.
|
|
3
|
+
|
|
4
|
+
Ces tools permettent à Lucy Assist d'exécuter des actions CRUD
|
|
5
|
+
directement dans l'application.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Tools disponibles pour Claude
|
|
9
|
+
LUCY_ASSIST_TOOLS = [
|
|
10
|
+
{
|
|
11
|
+
"name": "create_object",
|
|
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 membre, une structure,
|
|
14
|
+
un paiement, une adhésion ou tout autre objet métier.
|
|
15
|
+
IMPORTANT: Utilise ce tool en priorité quand l'utilisateur demande une création.
|
|
16
|
+
Ne demande pas de confirmation, exécute directement l'action.""",
|
|
17
|
+
"input_schema": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"app_name": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Nom de l'application Django (ex: membre, structure, paiement, adhesion)"
|
|
23
|
+
},
|
|
24
|
+
"model_name": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Nom du modèle Django (ex: Membre, Structure, Paiement, Adhesion)"
|
|
27
|
+
},
|
|
28
|
+
"data": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"description": "Données pour créer l'objet. Les clés sont les noms des champs du modèle.",
|
|
31
|
+
"additionalProperties": True
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": ["app_name", "model_name", "data"]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "update_object",
|
|
39
|
+
"description": """Met à jour un objet existant dans la base de données.
|
|
40
|
+
Utilise ce tool quand l'utilisateur demande de modifier un membre, une structure, etc.
|
|
41
|
+
IMPORTANT: Exécute directement l'action sans demander de confirmation.""",
|
|
42
|
+
"input_schema": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"app_name": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Nom de l'application Django"
|
|
48
|
+
},
|
|
49
|
+
"model_name": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Nom du modèle Django"
|
|
52
|
+
},
|
|
53
|
+
"object_id": {
|
|
54
|
+
"type": "integer",
|
|
55
|
+
"description": "ID de l'objet à modifier"
|
|
56
|
+
},
|
|
57
|
+
"data": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"description": "Données à mettre à jour",
|
|
60
|
+
"additionalProperties": True
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"required": ["app_name", "model_name", "object_id", "data"]
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "delete_object",
|
|
68
|
+
"description": """Supprime un objet de la base de données.
|
|
69
|
+
Utilise ce tool quand l'utilisateur demande explicitement de supprimer un objet.
|
|
70
|
+
ATTENTION: Demande confirmation avant de supprimer.""",
|
|
71
|
+
"input_schema": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"app_name": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"description": "Nom de l'application Django"
|
|
77
|
+
},
|
|
78
|
+
"model_name": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"description": "Nom du modèle Django"
|
|
81
|
+
},
|
|
82
|
+
"object_id": {
|
|
83
|
+
"type": "integer",
|
|
84
|
+
"description": "ID de l'objet à supprimer"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"required": ["app_name", "model_name", "object_id"]
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "search_objects",
|
|
92
|
+
"description": """Recherche des objets dans la base de données.
|
|
93
|
+
Utilise ce tool pour trouver des membres, structures, paiements, etc.""",
|
|
94
|
+
"input_schema": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"properties": {
|
|
97
|
+
"query": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "Terme de recherche"
|
|
100
|
+
},
|
|
101
|
+
"model_name": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": "Nom du modèle à rechercher (optionnel, cherche dans tous si non spécifié)"
|
|
104
|
+
},
|
|
105
|
+
"limit": {
|
|
106
|
+
"type": "integer",
|
|
107
|
+
"description": "Nombre maximum de résultats (défaut: 10)",
|
|
108
|
+
"default": 10
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"required": ["query"]
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "get_object_details",
|
|
116
|
+
"description": """Récupère les détails d'un objet spécifique.""",
|
|
117
|
+
"input_schema": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"properties": {
|
|
120
|
+
"app_name": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"description": "Nom de l'application Django"
|
|
123
|
+
},
|
|
124
|
+
"model_name": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": "Nom du modèle Django"
|
|
127
|
+
},
|
|
128
|
+
"object_id": {
|
|
129
|
+
"type": "integer",
|
|
130
|
+
"description": "ID de l'objet"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"required": ["app_name", "model_name", "object_id"]
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "get_form_fields",
|
|
138
|
+
"description": """Récupère les champs requis et optionnels d'un formulaire.
|
|
139
|
+
Utilise ce tool uniquement si tu as besoin de connaître les champs avant de créer un objet,
|
|
140
|
+
ou si l'utilisateur demande explicitement quels champs sont disponibles.""",
|
|
141
|
+
"input_schema": {
|
|
142
|
+
"type": "object",
|
|
143
|
+
"properties": {
|
|
144
|
+
"app_name": {
|
|
145
|
+
"type": "string",
|
|
146
|
+
"description": "Nom de l'application Django"
|
|
147
|
+
},
|
|
148
|
+
"model_name": {
|
|
149
|
+
"type": "string",
|
|
150
|
+
"description": "Nom du modèle Django"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"required": ["app_name", "model_name"]
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "navigate_to_page",
|
|
158
|
+
"description": """Génère une URL pour naviguer vers une page spécifique.
|
|
159
|
+
Utilise ce tool uniquement si l'utilisateur demande explicitement d'aller quelque part
|
|
160
|
+
ou si tu as besoin de lui montrer où aller.""",
|
|
161
|
+
"input_schema": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"properties": {
|
|
164
|
+
"page_type": {
|
|
165
|
+
"type": "string",
|
|
166
|
+
"enum": ["list", "create", "detail", "edit"],
|
|
167
|
+
"description": "Type de page"
|
|
168
|
+
},
|
|
169
|
+
"app_name": {
|
|
170
|
+
"type": "string",
|
|
171
|
+
"description": "Nom de l'application"
|
|
172
|
+
},
|
|
173
|
+
"model_name": {
|
|
174
|
+
"type": "string",
|
|
175
|
+
"description": "Nom du modèle"
|
|
176
|
+
},
|
|
177
|
+
"object_id": {
|
|
178
|
+
"type": "integer",
|
|
179
|
+
"description": "ID de l'objet (pour detail/edit)"
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"required": ["page_type", "app_name", "model_name"]
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "analyze_bug",
|
|
187
|
+
"description": """Analyse un bug potentiel en se connectant à GitLab pour examiner le code source.
|
|
188
|
+
Utilise ce tool quand l'utilisateur signale un problème technique, une erreur, ou un comportement inattendu.
|
|
189
|
+
Ce tool analyse le code via GitLab et si un bug est détecté, envoie automatiquement une notification
|
|
190
|
+
à l'équipe Revolucy pour correction.
|
|
191
|
+
IMPORTANT: Utilise ce tool dès qu'un utilisateur signale un message d'erreur ou un dysfonctionnement.""",
|
|
192
|
+
"input_schema": {
|
|
193
|
+
"type": "object",
|
|
194
|
+
"properties": {
|
|
195
|
+
"user_description": {
|
|
196
|
+
"type": "string",
|
|
197
|
+
"description": "Description du problème par l'utilisateur (obligatoire)"
|
|
198
|
+
},
|
|
199
|
+
"error_message": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"description": "Message d'erreur exact (si disponible)"
|
|
202
|
+
},
|
|
203
|
+
"page_url": {
|
|
204
|
+
"type": "string",
|
|
205
|
+
"description": "URL de la page où le problème se produit"
|
|
206
|
+
},
|
|
207
|
+
"model_name": {
|
|
208
|
+
"type": "string",
|
|
209
|
+
"description": "Nom du modèle Django concerné (si identifiable, ex: Membre, Structure)"
|
|
210
|
+
},
|
|
211
|
+
"action_type": {
|
|
212
|
+
"type": "string",
|
|
213
|
+
"enum": ["create", "update", "delete", "list", "search", "other"],
|
|
214
|
+
"description": "Type d'action qui a causé le problème"
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
"required": ["user_description"]
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
def get_app_for_model(model_name: str) -> str:
|
|
223
|
+
"""
|
|
224
|
+
Retourne le nom de l'app Django pour un modèle donné.
|
|
225
|
+
|
|
226
|
+
Utilise la configuration stockée en base de données.
|
|
227
|
+
"""
|
|
228
|
+
from lucy_assist.models import ConfigurationLucyAssist
|
|
229
|
+
return ConfigurationLucyAssist.get_app_for_model_static(model_name)
|
lucy_assist/signals.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Signaux pour Lucy Assist
|
|
3
|
+
"""
|
|
4
|
+
from django.db.models.signals import post_save, pre_delete
|
|
5
|
+
from django.dispatch import receiver
|
|
6
|
+
|
|
7
|
+
from lucy_assist.models import Message, ConfigurationLucyAssist
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@receiver(post_save, sender=Message)
|
|
11
|
+
def message_post_save(sender, instance, created, **kwargs):
|
|
12
|
+
"""
|
|
13
|
+
Signal déclenché après sauvegarde d'un message.
|
|
14
|
+
Décompte les tokens utilisés de la configuration.
|
|
15
|
+
"""
|
|
16
|
+
if getattr(instance, '_skip_signals', False):
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
if created and instance.tokens_utilises > 0:
|
|
20
|
+
# Déduire les tokens de la configuration
|
|
21
|
+
config = ConfigurationLucyAssist.get_config()
|
|
22
|
+
if config:
|
|
23
|
+
config.tokens_disponibles = max(0, config.tokens_disponibles - instance.tokens_utilises)
|
|
24
|
+
config._skip_signals = True
|
|
25
|
+
config.save(update_fields=['tokens_disponibles'])
|