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,186 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests pour Lucy Assist.
|
|
3
|
+
"""
|
|
4
|
+
from django.test import TestCase
|
|
5
|
+
from django.urls import reverse
|
|
6
|
+
|
|
7
|
+
from lucy_assist.models import Conversation, Message, ConfigurationLucyAssist
|
|
8
|
+
from lucy_assist.tests.factories import (
|
|
9
|
+
ConfigurationLucyAssistFactory,
|
|
10
|
+
ConversationFactory,
|
|
11
|
+
MessageUtilisateurFactory,
|
|
12
|
+
MessageChatbotFactory,
|
|
13
|
+
)
|
|
14
|
+
from apps.utilisateur.tests.factories import UtilisateurAdminUnafFactory
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestConfigurationLucyAssist(TestCase):
|
|
18
|
+
"""Tests pour le modèle ConfigurationLucyAssist."""
|
|
19
|
+
|
|
20
|
+
def setUp(self):
|
|
21
|
+
self.user = UtilisateurAdminUnafFactory()
|
|
22
|
+
self.client.force_login(self.user)
|
|
23
|
+
|
|
24
|
+
# -----------------------------------------------------------------
|
|
25
|
+
# CONFIGURATION SINGLETON
|
|
26
|
+
# -----------------------------------------------------------------
|
|
27
|
+
def test_configuration_singleton_ok(self):
|
|
28
|
+
"""Vérifie que la configuration est un singleton."""
|
|
29
|
+
config1 = ConfigurationLucyAssist.get_config()
|
|
30
|
+
config2 = ConfigurationLucyAssist.get_config()
|
|
31
|
+
|
|
32
|
+
self.assertEqual(config1.pk, config2.pk)
|
|
33
|
+
self.assertEqual(config1.pk, 1)
|
|
34
|
+
|
|
35
|
+
def test_ajouter_tokens_ok(self):
|
|
36
|
+
"""Vérifie l'ajout de tokens."""
|
|
37
|
+
config = ConfigurationLucyAssist.get_config()
|
|
38
|
+
initial_tokens = config.tokens_disponibles
|
|
39
|
+
|
|
40
|
+
tokens_ajoutes = config.ajouter_tokens(10) # 10€
|
|
41
|
+
|
|
42
|
+
self.assertEqual(tokens_ajoutes, 1_000_000)
|
|
43
|
+
self.assertEqual(config.tokens_disponibles, initial_tokens + 1_000_000)
|
|
44
|
+
|
|
45
|
+
def test_get_questions_frequentes_default_ok(self):
|
|
46
|
+
"""Vérifie les questions fréquentes par défaut."""
|
|
47
|
+
config = ConfigurationLucyAssist.get_config()
|
|
48
|
+
config.questions_frequentes = []
|
|
49
|
+
config.save()
|
|
50
|
+
|
|
51
|
+
questions = config.get_questions_frequentes()
|
|
52
|
+
|
|
53
|
+
self.assertIsInstance(questions, list)
|
|
54
|
+
self.assertGreater(len(questions), 0)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestConversation(TestCase):
|
|
58
|
+
"""Tests pour le modèle Conversation."""
|
|
59
|
+
|
|
60
|
+
def setUp(self):
|
|
61
|
+
self.user = UtilisateurAdminUnafFactory()
|
|
62
|
+
self.client.force_login(self.user)
|
|
63
|
+
ConfigurationLucyAssistFactory()
|
|
64
|
+
|
|
65
|
+
# -----------------------------------------------------------------
|
|
66
|
+
# CRÉATION
|
|
67
|
+
# -----------------------------------------------------------------
|
|
68
|
+
def test_create_conversation_ok(self):
|
|
69
|
+
"""Vérifie la création d'une conversation."""
|
|
70
|
+
conversation = ConversationFactory(utilisateur=self.user)
|
|
71
|
+
|
|
72
|
+
self.assertIsNotNone(conversation.pk)
|
|
73
|
+
self.assertEqual(conversation.utilisateur, self.user)
|
|
74
|
+
self.assertTrue(conversation.is_active)
|
|
75
|
+
|
|
76
|
+
def test_generer_titre_ok(self):
|
|
77
|
+
"""Vérifie la génération automatique du titre."""
|
|
78
|
+
conversation = ConversationFactory(utilisateur=self.user, titre=None)
|
|
79
|
+
MessageUtilisateurFactory(
|
|
80
|
+
conversation=conversation,
|
|
81
|
+
contenu="Comment créer un membre ?"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
conversation.generer_titre()
|
|
85
|
+
|
|
86
|
+
self.assertIsNotNone(conversation.titre)
|
|
87
|
+
self.assertIn("Comment créer", conversation.titre)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TestMessage(TestCase):
|
|
91
|
+
"""Tests pour le modèle Message."""
|
|
92
|
+
|
|
93
|
+
def setUp(self):
|
|
94
|
+
self.user = UtilisateurAdminUnafFactory()
|
|
95
|
+
self.client.force_login(self.user)
|
|
96
|
+
ConfigurationLucyAssistFactory()
|
|
97
|
+
|
|
98
|
+
# -----------------------------------------------------------------
|
|
99
|
+
# CRÉATION
|
|
100
|
+
# -----------------------------------------------------------------
|
|
101
|
+
def test_create_message_utilisateur_ok(self):
|
|
102
|
+
"""Vérifie la création d'un message utilisateur."""
|
|
103
|
+
conversation = ConversationFactory(utilisateur=self.user)
|
|
104
|
+
message = MessageUtilisateurFactory(conversation=conversation)
|
|
105
|
+
|
|
106
|
+
self.assertIsNotNone(message.pk)
|
|
107
|
+
self.assertTrue(message.est_utilisateur)
|
|
108
|
+
self.assertFalse(message.est_chatbot)
|
|
109
|
+
|
|
110
|
+
def test_create_message_chatbot_ok(self):
|
|
111
|
+
"""Vérifie la création d'un message chatbot."""
|
|
112
|
+
conversation = ConversationFactory(utilisateur=self.user)
|
|
113
|
+
message = MessageChatbotFactory(conversation=conversation)
|
|
114
|
+
|
|
115
|
+
self.assertIsNotNone(message.pk)
|
|
116
|
+
self.assertTrue(message.est_chatbot)
|
|
117
|
+
self.assertFalse(message.est_utilisateur)
|
|
118
|
+
self.assertGreater(message.tokens_utilises, 0)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestAPIViews(TestCase):
|
|
122
|
+
"""Tests pour les vues API Lucy Assist."""
|
|
123
|
+
|
|
124
|
+
def setUp(self):
|
|
125
|
+
self.user = UtilisateurAdminUnafFactory()
|
|
126
|
+
self.client.force_login(self.user)
|
|
127
|
+
self.config = ConfigurationLucyAssistFactory(tokens_disponibles=1_000_000)
|
|
128
|
+
|
|
129
|
+
# -----------------------------------------------------------------
|
|
130
|
+
# LISTE CONVERSATIONS
|
|
131
|
+
# -----------------------------------------------------------------
|
|
132
|
+
def test_conversation_list_ok(self):
|
|
133
|
+
"""Vérifie que la liste des conversations est accessible."""
|
|
134
|
+
ConversationFactory(utilisateur=self.user)
|
|
135
|
+
ConversationFactory(utilisateur=self.user)
|
|
136
|
+
|
|
137
|
+
url = reverse("lucy_assist:api-conversations")
|
|
138
|
+
response = self.client.get(url)
|
|
139
|
+
|
|
140
|
+
self.assertEqual(response.status_code, 200)
|
|
141
|
+
data = response.json()
|
|
142
|
+
self.assertIn('conversations', data)
|
|
143
|
+
self.assertEqual(len(data['conversations']), 2)
|
|
144
|
+
|
|
145
|
+
# -----------------------------------------------------------------
|
|
146
|
+
# CRÉATION CONVERSATION
|
|
147
|
+
# -----------------------------------------------------------------
|
|
148
|
+
def test_conversation_create_ok(self):
|
|
149
|
+
"""Vérifie la création d'une conversation via API."""
|
|
150
|
+
initial_count = Conversation.objects.count()
|
|
151
|
+
|
|
152
|
+
url = reverse("lucy_assist:api-conversations")
|
|
153
|
+
response = self.client.post(
|
|
154
|
+
url,
|
|
155
|
+
data={'page_contexte': '/membre/list'},
|
|
156
|
+
content_type='application/json'
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
self.assertEqual(response.status_code, 201)
|
|
160
|
+
self.assertEqual(Conversation.objects.count(), initial_count + 1)
|
|
161
|
+
|
|
162
|
+
# -----------------------------------------------------------------
|
|
163
|
+
# STATUT TOKENS
|
|
164
|
+
# -----------------------------------------------------------------
|
|
165
|
+
def test_token_status_ok(self):
|
|
166
|
+
"""Vérifie le statut des tokens."""
|
|
167
|
+
url = reverse("lucy_assist:api-token-status")
|
|
168
|
+
response = self.client.get(url)
|
|
169
|
+
|
|
170
|
+
self.assertEqual(response.status_code, 200)
|
|
171
|
+
data = response.json()
|
|
172
|
+
self.assertIn('tokens_disponibles', data)
|
|
173
|
+
self.assertEqual(data['tokens_disponibles'], 1_000_000)
|
|
174
|
+
|
|
175
|
+
# -----------------------------------------------------------------
|
|
176
|
+
# SUGGESTIONS
|
|
177
|
+
# -----------------------------------------------------------------
|
|
178
|
+
def test_suggestions_ok(self):
|
|
179
|
+
"""Vérifie les suggestions de questions."""
|
|
180
|
+
url = reverse("lucy_assist:api-suggestions")
|
|
181
|
+
response = self.client.get(url)
|
|
182
|
+
|
|
183
|
+
self.assertEqual(response.status_code, 200)
|
|
184
|
+
data = response.json()
|
|
185
|
+
self.assertIn('suggestions', data)
|
|
186
|
+
self.assertIsInstance(data['suggestions'], list)
|
lucy_assist/urls.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
URLs pour Lucy Assist.
|
|
3
|
+
|
|
4
|
+
Ce module définit toutes les routes de l'application Lucy Assist,
|
|
5
|
+
incluant les API REST et les vues de documentation.
|
|
6
|
+
"""
|
|
7
|
+
from django.urls import path
|
|
8
|
+
|
|
9
|
+
from lucy_assist import views
|
|
10
|
+
|
|
11
|
+
app_name = "lucy_assist"
|
|
12
|
+
|
|
13
|
+
urlpatterns = [
|
|
14
|
+
# API - Conversations
|
|
15
|
+
path("api/conversations", views.ConversationListCreateView.as_view(), name="api-conversations"),
|
|
16
|
+
path("api/conversations/<int:pk>", views.ConversationDetailView.as_view(), name="api-conversation-detail"),
|
|
17
|
+
|
|
18
|
+
# API - Messages
|
|
19
|
+
path("api/conversations/<int:conversation_id>/messages", views.MessageCreateView.as_view(), name="api-messages"),
|
|
20
|
+
path("api/conversations/<int:conversation_id>/chat", views.ChatCompletionView.as_view(), name="api-chat"),
|
|
21
|
+
|
|
22
|
+
# API - Tokens
|
|
23
|
+
path("api/tokens/status", views.TokenStatusView.as_view(), name="api-token-status"),
|
|
24
|
+
path("api/tokens/buy", views.AcheterTokensView.as_view(), name="api-token-buy"),
|
|
25
|
+
|
|
26
|
+
# API - Utilitaires
|
|
27
|
+
path("api/suggestions", views.SuggestionsView.as_view(), name="api-suggestions"),
|
|
28
|
+
path("api/context", views.PageContextView.as_view(), name="api-context"),
|
|
29
|
+
|
|
30
|
+
# API - Cache (admin only)
|
|
31
|
+
path("api/cache/stats", views.CacheStatsView.as_view(), name="api-cache-stats"),
|
|
32
|
+
path("api/cache/invalidate", views.CacheInvalidateView.as_view(), name="api-cache-invalidate"),
|
|
33
|
+
|
|
34
|
+
# API - Feedback
|
|
35
|
+
path("api/feedback", views.FeedbackCreateView.as_view(), name="api-feedback"),
|
|
36
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilitaire de logging pour Lucy Assist.
|
|
3
|
+
Version simplifiée et autonome.
|
|
4
|
+
"""
|
|
5
|
+
import inspect
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
from django.conf import settings
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("lucy_assist")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LogUtils:
|
|
15
|
+
"""
|
|
16
|
+
Classe générique de gestion des logs pour Lucy Assist.
|
|
17
|
+
Format: lucy_assist [$className.$methodName] - $message
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def _get_caller_info():
|
|
22
|
+
"""Récupère les informations sur l'appelant."""
|
|
23
|
+
try:
|
|
24
|
+
frame = inspect.stack()[2]
|
|
25
|
+
class_name = frame[0].f_locals.get("self", None)
|
|
26
|
+
if class_name:
|
|
27
|
+
class_name = class_name.__class__.__name__
|
|
28
|
+
else:
|
|
29
|
+
# Méthode statique ou fonction
|
|
30
|
+
class_name = frame[0].f_globals.get('__name__', 'unknown').split('.')[-1]
|
|
31
|
+
|
|
32
|
+
method_name = frame[3]
|
|
33
|
+
return f"{class_name}.{method_name}"
|
|
34
|
+
except Exception:
|
|
35
|
+
return "unknown"
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def info(msg=None):
|
|
39
|
+
"""Log un message de niveau INFO."""
|
|
40
|
+
caller = LogUtils._get_caller_info()
|
|
41
|
+
logger.info(f"lucy_assist [{caller}] - {msg}")
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def warning(msg=None):
|
|
45
|
+
"""Log un message de niveau WARNING."""
|
|
46
|
+
caller = LogUtils._get_caller_info()
|
|
47
|
+
logger.warning(f"lucy_assist [{caller}] - {msg}")
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def debug(msg=None):
|
|
51
|
+
"""Log un message de niveau DEBUG."""
|
|
52
|
+
caller = LogUtils._get_caller_info()
|
|
53
|
+
logger.debug(f"lucy_assist [{caller}] - {msg}")
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def error(msg=None):
|
|
57
|
+
"""Log un message de niveau ERROR."""
|
|
58
|
+
caller = LogUtils._get_caller_info()
|
|
59
|
+
logger.error(f"lucy_assist [{caller}] - {msg}")
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilitaires pour le formatage des messages Lucy Assist.
|
|
3
|
+
Sans impact sur la base de données.
|
|
4
|
+
"""
|
|
5
|
+
import re
|
|
6
|
+
from typing import List, Dict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MessageUtils:
|
|
10
|
+
"""Utilitaires de formatage et transformation des messages."""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def tronquer_texte(texte: str, max_length: int = 50, suffix: str = "...") -> str:
|
|
14
|
+
"""
|
|
15
|
+
Tronque un texte à une longueur maximale.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
texte: Texte à tronquer
|
|
19
|
+
max_length: Longueur maximale
|
|
20
|
+
suffix: Suffixe à ajouter si tronqué
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Texte tronqué
|
|
24
|
+
"""
|
|
25
|
+
if not texte:
|
|
26
|
+
return ""
|
|
27
|
+
if len(texte) <= max_length:
|
|
28
|
+
return texte
|
|
29
|
+
return texte[:max_length - len(suffix)] + suffix
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def formater_messages_pour_claude(messages: List) -> List[Dict]:
|
|
33
|
+
"""
|
|
34
|
+
Formate les messages pour l'API Claude.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
messages: Liste de messages (objets Message)
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Liste de dicts formatés pour Claude
|
|
41
|
+
"""
|
|
42
|
+
from lucy_assist.constantes import LucyAssistConstantes
|
|
43
|
+
|
|
44
|
+
formatted = []
|
|
45
|
+
for msg in messages:
|
|
46
|
+
role = "user" if msg.repondant == LucyAssistConstantes.Repondant.UTILISATEUR else "assistant"
|
|
47
|
+
formatted.append({
|
|
48
|
+
"role": role,
|
|
49
|
+
"content": msg.contenu
|
|
50
|
+
})
|
|
51
|
+
return formatted
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def extraire_json_de_reponse(texte: str) -> dict:
|
|
55
|
+
"""
|
|
56
|
+
Extrait un objet JSON d'une réponse texte.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
texte: Texte contenant potentiellement du JSON
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Dict extrait ou dict vide
|
|
63
|
+
"""
|
|
64
|
+
import json
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# Chercher un bloc JSON dans le texte
|
|
68
|
+
json_match = re.search(r'\{[^{}]*\}', texte, re.DOTALL)
|
|
69
|
+
if json_match:
|
|
70
|
+
return json.loads(json_match.group())
|
|
71
|
+
except (json.JSONDecodeError, AttributeError):
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def nettoyer_message(message: str) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Nettoie un message utilisateur.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
message: Message brut
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Message nettoyé
|
|
86
|
+
"""
|
|
87
|
+
if not message:
|
|
88
|
+
return ""
|
|
89
|
+
|
|
90
|
+
# Supprimer les espaces multiples
|
|
91
|
+
message = re.sub(r'\s+', ' ', message)
|
|
92
|
+
|
|
93
|
+
# Trim
|
|
94
|
+
message = message.strip()
|
|
95
|
+
|
|
96
|
+
return message
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def markdown_to_html_basic(texte: str) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Conversion basique de Markdown vers HTML.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
texte: Texte en markdown
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Texte en HTML
|
|
108
|
+
"""
|
|
109
|
+
if not texte:
|
|
110
|
+
return ""
|
|
111
|
+
|
|
112
|
+
# Échapper le HTML
|
|
113
|
+
texte = texte.replace('&', '&').replace('<', '<').replace('>', '>')
|
|
114
|
+
|
|
115
|
+
# Gras
|
|
116
|
+
texte = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', texte)
|
|
117
|
+
|
|
118
|
+
# Italique
|
|
119
|
+
texte = re.sub(r'\*(.*?)\*', r'<em>\1</em>', texte)
|
|
120
|
+
|
|
121
|
+
# Code inline
|
|
122
|
+
texte = re.sub(r'`(.*?)`', r'<code>\1</code>', texte)
|
|
123
|
+
|
|
124
|
+
# Liens
|
|
125
|
+
texte = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2" target="_blank">\1</a>', texte)
|
|
126
|
+
|
|
127
|
+
# Sauts de ligne
|
|
128
|
+
texte = texte.replace('\n', '<br>')
|
|
129
|
+
|
|
130
|
+
return texte
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilitaires pour la gestion des tokens Lucy Assist.
|
|
3
|
+
Sans impact sur la base de données.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TokenUtils:
|
|
8
|
+
"""Utilitaires de calcul et formatage des tokens."""
|
|
9
|
+
|
|
10
|
+
PRIX_PAR_MILLION = 10.0
|
|
11
|
+
TOKENS_PAR_CONVERSATION = 2000
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def calculer_tokens_pour_montant(montant_euros: float, prix_par_million: float = None) -> int:
|
|
15
|
+
"""
|
|
16
|
+
Calcule le nombre de tokens pour un montant en euros.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
montant_euros: Montant en euros
|
|
20
|
+
prix_par_million: Prix par million de tokens (défaut: 10€)
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Nombre de tokens
|
|
24
|
+
"""
|
|
25
|
+
prix = prix_par_million or TokenUtils.PRIX_PAR_MILLION
|
|
26
|
+
return int((montant_euros / prix) * 1_000_000)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def calculer_montant_pour_tokens(tokens: int, prix_par_million: float = None) -> float:
|
|
30
|
+
"""
|
|
31
|
+
Calcule le montant en euros pour un nombre de tokens.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
tokens: Nombre de tokens
|
|
35
|
+
prix_par_million: Prix par million de tokens (défaut: 10€)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Montant en euros
|
|
39
|
+
"""
|
|
40
|
+
prix = prix_par_million or TokenUtils.PRIX_PAR_MILLION
|
|
41
|
+
return (tokens / 1_000_000) * prix
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def estimer_conversations(tokens: int, tokens_par_conversation: int = None) -> int:
|
|
45
|
+
"""
|
|
46
|
+
Estime le nombre de conversations possibles.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
tokens: Nombre de tokens disponibles
|
|
50
|
+
tokens_par_conversation: Tokens moyens par conversation (défaut: 2000)
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Nombre de conversations estimées
|
|
54
|
+
"""
|
|
55
|
+
tpc = tokens_par_conversation or TokenUtils.TOKENS_PAR_CONVERSATION
|
|
56
|
+
return int(tokens / tpc) if tpc > 0 else 0
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def formater_tokens(tokens: int) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Formate un nombre de tokens pour l'affichage.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
tokens: Nombre de tokens
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
String formatée (ex: "1.5M", "500K", "1234")
|
|
68
|
+
"""
|
|
69
|
+
if tokens >= 1_000_000:
|
|
70
|
+
return f"{tokens / 1_000_000:.1f}M"
|
|
71
|
+
elif tokens >= 1_000:
|
|
72
|
+
return f"{tokens / 1_000:.0f}K"
|
|
73
|
+
return str(tokens)
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def tokens_suffisants(tokens_disponibles: int, tokens_requis: int = 2000) -> bool:
|
|
77
|
+
"""
|
|
78
|
+
Vérifie si suffisamment de tokens sont disponibles.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
tokens_disponibles: Tokens actuellement disponibles
|
|
82
|
+
tokens_requis: Tokens minimum requis
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
True si suffisant
|
|
86
|
+
"""
|
|
87
|
+
return tokens_disponibles >= tokens_requis
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .api_views import (
|
|
2
|
+
ConversationListCreateView,
|
|
3
|
+
ConversationDetailView,
|
|
4
|
+
MessageCreateView,
|
|
5
|
+
ChatCompletionView,
|
|
6
|
+
TokenStatusView,
|
|
7
|
+
AcheterTokensView,
|
|
8
|
+
SuggestionsView,
|
|
9
|
+
PageContextView,
|
|
10
|
+
CacheStatsView,
|
|
11
|
+
CacheInvalidateView,
|
|
12
|
+
FeedbackCreateView,
|
|
13
|
+
)
|