Clemylia commited on
Commit
70dd812
·
verified ·
1 Parent(s): 3127aa6

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +258 -140
src/streamlit_app.py CHANGED
@@ -1,85 +1,126 @@
1
  import streamlit as st
2
  import re
3
  import datetime
 
 
4
  from transformers import AutoModelForCausalLM, AutoTokenizer
5
- import torch
6
 
7
- # --- 1. Configuration et Design (Rose & Blanc/Manga) ---
8
-
9
- st.set_page_config(
10
- page_title="Charlotte-APY 💖",
11
- layout="wide",
12
- initial_sidebar_state="expanded"
13
- )
14
-
15
- # Style personnalisé pour l'ambiance Manga/Rose/Blanc
16
- st.markdown(
17
- """
18
- <style>
19
- /* Généralités */
20
- .stApp {
21
- background-color: #FFF0F5; /* Rose Pâle (Blush) */
22
- color: #333333; /* Texte Gris Foncé */
23
- }
24
-
25
- /* Titre (Tonalité Manga) */
26
- h1 {
27
- color: #FF69B4; /* Rose Vif */
28
- text-shadow: 2px 2px 4px #F08080; /* Ombre douce */
29
- font-family: 'Comic Sans MS', cursive, sans-serif; /* Police ludique */
30
- text-align: center;
31
- }
32
-
33
- /* Boutons */
34
- .stButton>button {
35
- background-color: #FFB6C1; /* Rose Clair */
36
- color: #8B0000; /* Rouge Fonce pour le texte */
37
- border: 2px solid #FF69B4;
38
- border-radius: 10px;
39
- transition: all 0.2s;
40
- font-weight: bold;
41
- }
42
- .stButton>button:hover {
43
- background-color: #FF69B4; /* Rose Vif au survol */
44
- color: white;
45
- }
46
 
47
- /* Conteneurs et zones de texte */
48
- .stTextInput>div>div>input, .stTextArea>div>div>textarea {
49
- background-color: white;
50
- border: 1px solid #FFB6C1;
51
- border-radius: 5px;
52
- }
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- /* En-têtes secondaires */
55
- h2, h3 {
56
- color: #FF1493; /* Rose Profond */
57
- border-bottom: 2px solid #FFC0CB; /* Ligne sous les titres */
58
- padding-bottom: 5px;
59
- }
60
- </style>
61
- """,
62
- unsafe_allow_html=True
63
- )
64
-
65
- st.title("💖 Charlotte-APY : Votre Portail d'API Tiny-Charlotte 🎀")
66
-
67
- # --- 2. Initialisation de l'État de Session ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- if 'api_keys' not in st.session_state:
70
- # Format : {'clé_api': {'quota_remaining': 600, 'max_quota': 600, 'date_last_use': '2025-11-17'}}
71
- st.session_state['api_keys'] = {}
72
 
73
- # --- 3. LOGIQUE DE VALIDATION DE CLÉ ---
 
 
74
 
75
  def validate_key(key_str):
76
- """
77
- Vérifie si la clé d'API respecte les contraintes :
78
- 1. Commence par 'Tn-charlotte'.
79
- 2. Contient au moins 5 chiffres.
80
- 3. Contient au moins 7 lettres (a-z ou A-Z).
81
- 4. Est unique.
82
- """
83
 
84
  if not key_str.startswith("Tn-charlotte"):
85
  return False, "La clé doit obligatoirement commencer par **Tn-charlotte**."
@@ -91,30 +132,20 @@ def validate_key(key_str):
91
  num_letters = len(re.findall(r'[a-zA-Z]', key_str))
92
  if num_letters < 7:
93
  return False, f"La clé doit contenir au moins **7 lettres** (actuel : {num_letters})."
94
-
95
- if key_str in st.session_state.api_keys:
96
- return False, "Cette clé d'API existe déjà."
97
 
98
  return True, "Clé valide !"
99
 
100
- # --- 4. CHARGEMENT DU MODÈLE ---
101
-
102
  @st.cache_resource
103
  def load_tiny_charlotte():
104
- """
105
- Charge le modèle tiny-charlotte et le tokenizer en utilisant le cache Streamlit.
106
- """
107
- model_name = "Clemylia/Tiny-charlotte"
108
-
109
  try:
110
- st.sidebar.info(f"⏳ Chargement du modèle {model_name}...")
111
 
112
- tokenizer = AutoTokenizer.from_pretrained(model_name)
113
 
114
- # Utiliser un dispositif si possible (CUDA si disponible, sinon CPU)
115
  device = "cuda" if torch.cuda.is_available() else "cpu"
116
 
117
- model = AutoModelForCausalLM.from_pretrained(model_name).to(device)
118
 
119
  st.sidebar.success(f"✅ Modèle Tiny-Charlotte chargé sur **{device}**.")
120
  return model, tokenizer, device
@@ -123,15 +154,16 @@ def load_tiny_charlotte():
123
  st.sidebar.error(f"❌ Erreur de chargement du modèle : {e}")
124
  return None, None, None
125
 
126
- # --- 5. INFÉRENCE ET GESTION DU QUOTA ---
 
 
127
 
128
  def run_inference(api_key, prompt, model, tokenizer, device):
129
  """
130
- Exécute l'inférence, vérifie/réinitialise le quota journalier (600 tokens)
131
- et limite la génération à 128 tokens.
132
  """
133
  today = datetime.date.today().isoformat()
134
- key_data = st.session_state.api_keys.get(api_key)
135
 
136
  if not key_data:
137
  return "Erreur: Clé d'API non valide ou non trouvée.", 0
@@ -140,57 +172,71 @@ def run_inference(api_key, prompt, model, tokenizer, device):
140
  if key_data['date_last_use'] != today:
141
  key_data['quota_remaining'] = key_data['max_quota']
142
  key_data['date_last_use'] = today
143
- st.session_state.api_keys[api_key] = key_data
144
- st.success(f"🎉 Quota réinitialisé pour la clé **{api_key}** : 600 tokens disponibles.")
145
 
146
  # 2. Vérification du Quota
147
- MAX_TOKENS_PER_RESPONSE = 100
148
-
149
  if key_data['quota_remaining'] < MAX_TOKENS_PER_RESPONSE:
150
- return f"🚫 **Quota journalier atteint** ({key_data['quota_remaining']} / 600). Veuillez réessayer demain.", 0
151
 
152
  if model is None or tokenizer is None:
153
  return "Erreur interne: Le modèle n'est pas prêt.", 0
154
 
155
  # 3. Exécution de la Génération
156
  try:
157
- # Encodage du prompt et transfert vers le dispositif (CPU/CUDA)
158
  input_ids = tokenizer.encode(prompt, return_tensors='pt').to(device)
159
 
160
- # Génération de la réponse
161
  output = model.generate(
162
  input_ids,
163
  max_length=input_ids.shape[1] + MAX_TOKENS_PER_RESPONSE,
164
- do_sample=True,
165
- top_k=50,
166
- top_p=0.95,
167
- num_return_sequences=1,
168
- pad_token_id=tokenizer.eos_token_id # Important pour la génération
169
  )
170
 
171
- # Décodage
172
  response_text = tokenizer.decode(output[0], skip_special_tokens=True)
173
-
174
- # Calcul des tokens utilisés (Tokens générés - Tokens du prompt)
175
  tokens_generated = output.shape[1] - input_ids.shape[1]
176
 
177
  except Exception as e:
178
  st.error(f"Erreur lors de la génération: {e}")
179
  return "Erreur d'inférence. Problème avec le modèle.", 0
180
 
181
- # 4. Mise à Jour du Quota
182
- key_data['quota_remaining'] -= tokens_generated
183
- st.session_state.api_keys[api_key] = key_data
184
 
185
  return response_text, tokens_generated
186
 
187
- # --- 6. INTERFACES UTILISATEUR ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
  def generate_api_key_ui():
190
  """Interface de création de clé d'API."""
191
  st.subheader("🔑 Créer une Nouvelle Clé d'API")
192
 
193
- if len(st.session_state.api_keys) >= 6:
 
 
194
  st.warning("❌ **Limite Atteinte !** Vous gérez déjà le maximum de 6 clés d'API.")
195
  return
196
 
@@ -205,14 +251,16 @@ def generate_api_key_ui():
205
  if submitted:
206
  is_valid, message = validate_key(new_key)
207
 
 
 
 
 
208
  if is_valid:
209
- st.session_state.api_keys[new_key] = {
210
- 'quota_remaining': 600,
211
- 'max_quota': 600,
212
- 'date_last_use': datetime.date.today().isoformat()
213
- }
214
- st.success(f"✅ Clé d'API **{new_key}** créée ! Quota : 600 tokens/jour.")
215
- st.rerun()
216
  else:
217
  st.error(f"🚫 **Erreur de validation :** {message}")
218
 
@@ -220,47 +268,68 @@ def manage_api_keys_ui():
220
  """Interface de gestion et suppression des clés d'API."""
221
  st.subheader("🗂️ Gérer Vos Clés d'API")
222
 
223
- keys_list = list(st.session_state.api_keys.keys())
 
224
 
225
  if not keys_list:
226
  st.info("Vous n'avez pas encore de clés d'API.")
227
  return
228
 
229
- # Affichage des clés dans un tableau
230
- keys_data = []
231
- for key, data in st.session_state.api_keys.items():
232
- keys_data.append({
233
  "Clé d'API": key,
234
  "Tokens Restants": f"{data['quota_remaining']} / {data['max_quota']}",
235
  "Dernière Utilisation (Réinitialisation)": data['date_last_use']
236
  })
237
 
238
- st.dataframe(keys_data, use_container_width=True, hide_index=True)
239
 
240
  st.markdown("---")
241
 
242
  # Section de Suppression
243
  st.write("### 🗑️ Supprimer une Clé")
244
- key_to_delete = st.selectbox("Sélectionnez la clé à supprimer :", [""] + keys_list)
245
 
246
  if st.button("💔 Supprimer la Clé Sélectionnée", disabled=(key_to_delete == "")):
247
- if key_to_delete in st.session_state.api_keys:
248
- del st.session_state.api_keys[key_to_delete]
249
  st.success(f"🗑️ Clé **{key_to_delete}** supprimée avec succès.")
250
  st.rerun()
 
 
 
 
 
 
 
 
 
 
251
 
 
 
 
 
 
 
 
 
 
 
 
252
  def test_api_ui(model, tokenizer, device):
253
  """Interface pour tester l'API."""
254
 
255
  st.subheader("🧪 Tester l'API Tiny-Charlotte")
256
 
257
- keys_list = list(st.session_state.api_keys.keys())
258
 
259
  if not keys_list:
260
  st.warning("Créez une clé d'API avant de pouvoir tester l'inférence.")
261
  return
262
 
263
- selected_key = st.selectbox("Sélectionnez votre clé d'API :", keys_list)
264
 
265
  with st.form("inference_form"):
266
  prompt = st.text_area("Votre Requête pour Tiny-Charlotte", height=100)
@@ -272,10 +341,10 @@ def test_api_ui(model, tokenizer, device):
272
  return
273
 
274
  if model is None:
275
- st.error("Le modèle n'a pas pu être chargé. Vérifiez votre connexion ou la librairie transformers.")
276
  return
277
 
278
- with st.spinner("Appel de l'API Charlotte-APY en cours... (Vérification du quota et inférence)"):
279
 
280
  response, tokens_used = run_inference(selected_key, prompt, model, tokenizer, device)
281
 
@@ -283,30 +352,75 @@ def test_api_ui(model, tokenizer, device):
283
  st.info(response)
284
 
285
  # Affichage des métriques de quota
286
- if selected_key in st.session_state.api_keys:
287
- remaining = st.session_state.api_keys[selected_key]['quota_remaining']
 
 
288
 
289
  if tokens_used > 0:
290
- st.success(f"Tokens utilisés : **{tokens_used}**. Tokens restants pour cette clé aujourd'hui : **{remaining}** / 600.")
291
- elif remaining < 128:
292
- # Si le quota est bas, c'est l'erreur de quota qui est apparue
293
- st.error(f"Tokens restants : **{remaining}** / 600. Vous n'avez pas assez de tokens pour une réponse complète (128).")
294
  else:
295
  st.warning("Aucun token utilisé (erreur de modèle ou quota atteint).")
 
 
296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
- # --- 7. DISPOSITION PRINCIPALE DE L'APPLICATION ---
299
 
 
300
  def main_app():
301
 
302
- # Le modèle est chargé une seule fois au début de l'exécution
303
  model, tokenizer, device = load_tiny_charlotte()
304
 
305
- # Colonnes pour la création de clé
306
  col1, col2 = st.columns([1, 1])
307
 
308
  with col1:
309
  generate_api_key_ui()
 
 
 
310
 
311
  st.markdown("---")
312
 
@@ -314,10 +428,14 @@ def main_app():
314
 
315
  st.markdown("---")
316
 
317
- # Le test de l'API dépend du modèle chargé
318
  test_api_ui(model, tokenizer, device)
 
 
 
 
 
319
 
320
 
321
- # Exécution de l'application
322
  if __name__ == "__main__":
323
  main_app()
 
1
  import streamlit as st
2
  import re
3
  import datetime
4
+ import sqlite3
5
+ import torch
6
  from transformers import AutoModelForCausalLM, AutoTokenizer
 
7
 
8
+ # --- 0. CONFIGURATION ET INITIALISATION DE LA BASE DE DONNÉES ---
9
+
10
+ DB_NAME = "charlotte_apy.db"
11
+ MODEL_NAME = "Clemylia/Tiny-charlotte"
12
+ MAX_QUOTA = 600
13
+ MAX_TOKENS_PER_RESPONSE = 100
14
+
15
+ # ----------------------------------------------------
16
+ # A. LOGIQUE DE BASE DE DONNÉES SQLite (Mise à jour)
17
+ # ----------------------------------------------------
18
+
19
+ def init_db():
20
+ """Initialise la base de données et la table api_keys."""
21
+ conn = sqlite3.connect(DB_NAME)
22
+ cursor = conn.cursor()
23
+ cursor.execute("""
24
+ CREATE TABLE IF NOT EXISTS api_keys (
25
+ key_id TEXT PRIMARY KEY,
26
+ quota_remaining INTEGER,
27
+ max_quota INTEGER,
28
+ date_last_use TEXT
29
+ )
30
+ """)
31
+ conn.commit()
32
+ conn.close()
33
+
34
+ def get_all_keys():
35
+ """Récupère toutes les clés d'API."""
36
+ conn = sqlite3.connect(DB_NAME)
37
+ cursor = conn.cursor()
38
+ cursor.execute("SELECT key_id, quota_remaining, max_quota, date_last_use FROM api_keys")
39
+ rows = cursor.fetchall()
40
+ conn.close()
 
 
 
 
 
 
41
 
42
+ keys = {}
43
+ for row in rows:
44
+ key_id, quota_remaining, max_quota, date_last_use = row
45
+ keys[key_id] = {
46
+ 'quota_remaining': quota_remaining,
47
+ 'max_quota': max_quota,
48
+ 'date_last_use': date_last_use
49
+ }
50
+ return keys
51
+
52
+ def get_key_data(key_id):
53
+ """Récupère les données d'une seule clé."""
54
+ conn = sqlite3.connect(DB_NAME)
55
+ cursor = conn.cursor()
56
+ cursor.execute("SELECT quota_remaining, max_quota, date_last_use FROM api_keys WHERE key_id = ?", (key_id,))
57
+ row = cursor.fetchone()
58
+ conn.close()
59
 
60
+ if row:
61
+ return {'quota_remaining': row[0], 'max_quota': row[1], 'date_last_use': row[2]}
62
+ return None
63
+
64
+ def add_key_to_db(key_id, max_quota=MAX_QUOTA):
65
+ """Ajoute une nouvelle clé à la base de données."""
66
+ conn = sqlite3.connect(DB_NAME)
67
+ cursor = conn.cursor()
68
+ today = datetime.date.today().isoformat()
69
+ try:
70
+ cursor.execute("""
71
+ INSERT INTO api_keys (key_id, quota_remaining, max_quota, date_last_use)
72
+ VALUES (?, ?, ?, ?)
73
+ """, (key_id, max_quota, max_quota, today))
74
+ conn.commit()
75
+ conn.close()
76
+ return True
77
+ except sqlite3.IntegrityError:
78
+ conn.close()
79
+ return False
80
+
81
+ def delete_key_from_db(key_id):
82
+ """Supprime une clé de la base de données."""
83
+ conn = sqlite3.connect(DB_NAME)
84
+ cursor = conn.cursor()
85
+ cursor.execute("DELETE FROM api_keys WHERE key_id = ?", (key_id,))
86
+ conn.commit()
87
+ conn.close()
88
+
89
+ def update_key_quota_in_db(key_id, new_remaining_quota, new_date_last_use):
90
+ """Met à jour le quota et la date d'utilisation."""
91
+ conn = sqlite3.connect(DB_NAME)
92
+ cursor = conn.cursor()
93
+ cursor.execute("""
94
+ UPDATE api_keys
95
+ SET quota_remaining = ?, date_last_use = ?
96
+ WHERE key_id = ?
97
+ """, (new_remaining_quota, new_date_last_use, key_id))
98
+ conn.commit()
99
+ conn.close()
100
+
101
+ # Nouvelle fonction d'administration : réinitialiser le quota
102
+ def reset_key_quota_in_db(key_id):
103
+ """Réinitialise le quota d'une clé au maximum et met à jour la date."""
104
+ conn = sqlite3.connect(DB_NAME)
105
+ cursor = conn.cursor()
106
+ today = datetime.date.today().isoformat()
107
+ cursor.execute("""
108
+ UPDATE api_keys
109
+ SET quota_remaining = max_quota, date_last_use = ?
110
+ WHERE key_id = ?
111
+ """, (today, key_id))
112
+ conn.commit()
113
+ conn.close()
114
 
115
+ # Initialisation au démarrage
116
+ init_db()
 
117
 
118
+ # ----------------------------------------------------
119
+ # B. LOGIQUE DE VALIDATION ET CHARGEMENT DU MODÈLE
120
+ # ----------------------------------------------------
121
 
122
  def validate_key(key_str):
123
+ """Vérifie si la clé respecte les contraintes de format."""
 
 
 
 
 
 
124
 
125
  if not key_str.startswith("Tn-charlotte"):
126
  return False, "La clé doit obligatoirement commencer par **Tn-charlotte**."
 
132
  num_letters = len(re.findall(r'[a-zA-Z]', key_str))
133
  if num_letters < 7:
134
  return False, f"La clé doit contenir au moins **7 lettres** (actuel : {num_letters})."
 
 
 
135
 
136
  return True, "Clé valide !"
137
 
 
 
138
  @st.cache_resource
139
  def load_tiny_charlotte():
140
+ """Charge le modèle tiny-charlotte et le tokenizer en utilisant le cache Streamlit."""
 
 
 
 
141
  try:
142
+ st.sidebar.info(f"⏳ Chargement du modèle {MODEL_NAME}...")
143
 
144
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
145
 
 
146
  device = "cuda" if torch.cuda.is_available() else "cpu"
147
 
148
+ model = AutoModelForCausalLM.from_pretrained(MODEL_NAME).to(device)
149
 
150
  st.sidebar.success(f"✅ Modèle Tiny-Charlotte chargé sur **{device}**.")
151
  return model, tokenizer, device
 
154
  st.sidebar.error(f"❌ Erreur de chargement du modèle : {e}")
155
  return None, None, None
156
 
157
+ # ----------------------------------------------------
158
+ # C. INFÉRENCE ET GESTION DU QUOTA
159
+ # ----------------------------------------------------
160
 
161
  def run_inference(api_key, prompt, model, tokenizer, device):
162
  """
163
+ Exécute l'inférence avec le modèle, gère le quota via SQLite.
 
164
  """
165
  today = datetime.date.today().isoformat()
166
+ key_data = get_key_data(api_key)
167
 
168
  if not key_data:
169
  return "Erreur: Clé d'API non valide ou non trouvée.", 0
 
172
  if key_data['date_last_use'] != today:
173
  key_data['quota_remaining'] = key_data['max_quota']
174
  key_data['date_last_use'] = today
175
+ update_key_quota_in_db(api_key, key_data['quota_remaining'], today)
176
+ st.success(f"🎉 Quota réinitialisé automatiquement pour la clé **{api_key}** : {key_data['max_quota']} tokens disponibles aujourd'hui.")
177
 
178
  # 2. Vérification du Quota
 
 
179
  if key_data['quota_remaining'] < MAX_TOKENS_PER_RESPONSE:
180
+ return f"🚫 **Quota journalier atteint** ({key_data['quota_remaining']} / {MAX_QUOTA}). Veuillez réessayer demain.", 0
181
 
182
  if model is None or tokenizer is None:
183
  return "Erreur interne: Le modèle n'est pas prêt.", 0
184
 
185
  # 3. Exécution de la Génération
186
  try:
 
187
  input_ids = tokenizer.encode(prompt, return_tensors='pt').to(device)
188
 
 
189
  output = model.generate(
190
  input_ids,
191
  max_length=input_ids.shape[1] + MAX_TOKENS_PER_RESPONSE,
192
+ do_sample=True, top_k=50, top_p=0.95, num_return_sequences=1,
193
+ pad_token_id=tokenizer.eos_token_id
 
 
 
194
  )
195
 
 
196
  response_text = tokenizer.decode(output[0], skip_special_tokens=True)
 
 
197
  tokens_generated = output.shape[1] - input_ids.shape[1]
198
 
199
  except Exception as e:
200
  st.error(f"Erreur lors de la génération: {e}")
201
  return "Erreur d'inférence. Problème avec le modèle.", 0
202
 
203
+ # 4. Mise à Jour du Quota DANS LA BASE DE DONNÉES
204
+ new_remaining = key_data['quota_remaining'] - tokens_generated
205
+ update_key_quota_in_db(api_key, new_remaining, today)
206
 
207
  return response_text, tokens_generated
208
 
209
+ # ----------------------------------------------------
210
+ # D. INTERFACE UTILISATEUR STREAMLIT
211
+ # ----------------------------------------------------
212
+
213
+ # Configuration Streamlit et Style
214
+ st.set_page_config(page_title="Charlotte-APY 💖", layout="wide", initial_sidebar_state="expanded")
215
+
216
+ st.markdown(
217
+ """
218
+ <style>
219
+ /* ... (CSS pour le style Rose & Manga) ... */
220
+ .stApp { background-color: #FFF0F5; color: #333333; }
221
+ h1 { color: #FF69B4; text-shadow: 2px 2px 4px #F08080; font-family: 'Comic Sans MS', cursive, sans-serif; text-align: center; }
222
+ .stButton>button { background-color: #FFB6C1; color: #8B0000; border: 2px solid #FF69B4; border-radius: 10px; font-weight: bold; transition: all 0.2s; }
223
+ .stButton>button:hover { background-color: #FF69B4; color: white; }
224
+ .stTextInput>div>div>input, .stTextArea>div>div>textarea { background-color: white; border: 1px solid #FFB6C1; border-radius: 5px; }
225
+ h2, h3 { color: #FF1493; border-bottom: 2px solid #FFC0CB; padding-bottom: 5px; }
226
+ </style>
227
+ """,
228
+ unsafe_allow_html=True
229
+ )
230
+ st.title("💖 Charlotte-APY : Votre Portail d'API Tiny-Charlotte 🎀")
231
+
232
 
233
  def generate_api_key_ui():
234
  """Interface de création de clé d'API."""
235
  st.subheader("🔑 Créer une Nouvelle Clé d'API")
236
 
237
+ current_keys = get_all_keys()
238
+
239
+ if len(current_keys) >= 6:
240
  st.warning("❌ **Limite Atteinte !** Vous gérez déjà le maximum de 6 clés d'API.")
241
  return
242
 
 
251
  if submitted:
252
  is_valid, message = validate_key(new_key)
253
 
254
+ if get_key_data(new_key) is not None:
255
+ is_valid = False
256
+ message = "Cette clé d'API existe déjà (base de données)."
257
+
258
  if is_valid:
259
+ if add_key_to_db(new_key):
260
+ st.success(f"✅ Clé d'API **{new_key}** créée ! Quota : {MAX_QUOTA} tokens/jour.")
261
+ st.rerun()
262
+ else:
263
+ st.error("Échec de l'ajout à la base de données.")
 
 
264
  else:
265
  st.error(f"🚫 **Erreur de validation :** {message}")
266
 
 
268
  """Interface de gestion et suppression des clés d'API."""
269
  st.subheader("🗂️ Gérer Vos Clés d'API")
270
 
271
+ keys_data_dict = get_all_keys()
272
+ keys_list = list(keys_data_dict.keys())
273
 
274
  if not keys_list:
275
  st.info("Vous n'avez pas encore de clés d'API.")
276
  return
277
 
278
+ keys_data_list = []
279
+ for key, data in keys_data_dict.items():
280
+ keys_data_list.append({
 
281
  "Clé d'API": key,
282
  "Tokens Restants": f"{data['quota_remaining']} / {data['max_quota']}",
283
  "Dernière Utilisation (Réinitialisation)": data['date_last_use']
284
  })
285
 
286
+ st.dataframe(keys_data_list, use_container_width=True, hide_index=True)
287
 
288
  st.markdown("---")
289
 
290
  # Section de Suppression
291
  st.write("### 🗑️ Supprimer une Clé")
292
+ key_to_delete = st.selectbox("Sélectionnez la clé à supprimer :", [""] + keys_list, key="delete_select")
293
 
294
  if st.button("💔 Supprimer la Clé Sélectionnée", disabled=(key_to_delete == "")):
295
+ if key_to_delete:
296
+ delete_key_from_db(key_to_delete)
297
  st.success(f"🗑️ Clé **{key_to_delete}** supprimée avec succès.")
298
  st.rerun()
299
+
300
+ def admin_quota_ui():
301
+ """Interface d'administration pour la réinitialisation manuelle du quota."""
302
+ st.subheader("⚙️ Administration : Réinitialisation du Quota")
303
+
304
+ keys_list = list(get_all_keys().keys())
305
+
306
+ if not keys_list:
307
+ st.info("Aucune clé d'API à administrer.")
308
+ return
309
 
310
+ key_to_reset = st.selectbox("Sélectionnez la clé à réinitialiser :", [""] + keys_list, key="reset_select")
311
+
312
+ if st.button("🔄 Réinitialiser le Quota à 600"):
313
+ if key_to_reset:
314
+ reset_key_quota_in_db(key_to_reset)
315
+ st.success(f"✅ Quota pour la clé **{key_to_reset}** réinitialisé à {MAX_QUOTA} tokens.")
316
+ st.rerun()
317
+ else:
318
+ st.warning("Veuillez sélectionner une clé.")
319
+
320
+
321
  def test_api_ui(model, tokenizer, device):
322
  """Interface pour tester l'API."""
323
 
324
  st.subheader("🧪 Tester l'API Tiny-Charlotte")
325
 
326
+ keys_list = list(get_all_keys().keys())
327
 
328
  if not keys_list:
329
  st.warning("Créez une clé d'API avant de pouvoir tester l'inférence.")
330
  return
331
 
332
+ selected_key = st.selectbox("Sélectionnez votre clé d'API :", keys_list, key="inference_select")
333
 
334
  with st.form("inference_form"):
335
  prompt = st.text_area("Votre Requête pour Tiny-Charlotte", height=100)
 
341
  return
342
 
343
  if model is None:
344
+ st.error("Le modèle n'a pas pu être chargé.")
345
  return
346
 
347
+ with st.spinner("Appel de l'API Charlotte-APY en cours..."):
348
 
349
  response, tokens_used = run_inference(selected_key, prompt, model, tokenizer, device)
350
 
 
352
  st.info(response)
353
 
354
  # Affichage des métriques de quota
355
+ updated_data = get_key_data(selected_key)
356
+
357
+ if updated_data:
358
+ remaining = updated_data['quota_remaining']
359
 
360
  if tokens_used > 0:
361
+ st.success(f"Tokens utilisés : **{tokens_used}**. Tokens restants pour cette clé aujourd'hui : **{remaining}** / {MAX_QUOTA}.")
362
+ elif remaining < MAX_TOKENS_PER_RESPONSE:
363
+ st.error(f"Tokens restants : **{remaining}** / {MAX_QUOTA}. Vous n'avez pas assez de tokens pour une réponse complète ({MAX_TOKENS_PER_RESPONSE}).")
 
364
  else:
365
  st.warning("Aucun token utilisé (erreur de modèle ou quota atteint).")
366
+ else:
367
+ st.error("Impossible de récupérer les données de la clé après l'inférence.")
368
 
369
+ def api_documentation_ui():
370
+ """Fournit la documentation pour l'utilisation externe de l'API (simulée)."""
371
+ st.subheader("📖 Documentation de l'API Tiny-Charlotte")
372
+
373
+ st.markdown(f"""
374
+ L'authentification se fait via la clé d'API dans l'en-tête `Authorization: Bearer <votre_clé>`.
375
+
376
+ * **Limite :** {MAX_QUOTA} tokens par clé par jour.
377
+ * **Réponse max :** {MAX_TOKENS_PER_RESPONSE} tokens.
378
+
379
+ ### 🌐 Endpoint (Simulé) : `POST https://api.charlotte-apy.com/v1/inference`
380
+ """)
381
+
382
+ code_example = """
383
+ import requests
384
+ import json
385
+
386
+ API_URL = "https://api.charlotte-apy.com/v1/inference"
387
+ YOUR_API_KEY = "Tn-charlotte_Ma_Cle_12345ABCDEFG"
388
+
389
+ payload = {
390
+ "prompt": "Peux-tu me donner un conseil sur l'espoir ?",
391
+ }
392
+
393
+ headers = {
394
+ "Content-Type": "application/json",
395
+ "Authorization": f"Bearer {YOUR_API_KEY}"
396
+ }
397
+
398
+ try:
399
+ response = requests.post(API_URL, headers=headers, data=json.dumps(payload))
400
+ response.raise_for_status()
401
+ data = response.json()
402
+ print("Réponse de Tiny-Charlotte :", data.get("generated_text"))
403
+
404
+ except requests.exceptions.RequestException as e:
405
+ print(f"Erreur lors de l'appel API: {e}")
406
+ """
407
+ st.code(code_example, language="python")
408
 
 
409
 
410
+ # --- DISPOSITION PRINCIPALE DE L'APPLICATION ---
411
  def main_app():
412
 
413
+ # 1. Chargement du Modèle (mis en cache)
414
  model, tokenizer, device = load_tiny_charlotte()
415
 
416
+ # 2. Gestion des Clés et Quota (Colonnes)
417
  col1, col2 = st.columns([1, 1])
418
 
419
  with col1:
420
  generate_api_key_ui()
421
+
422
+ with col2:
423
+ admin_quota_ui() # Nouvelle section d'administration
424
 
425
  st.markdown("---")
426
 
 
428
 
429
  st.markdown("---")
430
 
431
+ # 3. Test de l'API
432
  test_api_ui(model, tokenizer, device)
433
+
434
+ st.markdown("---")
435
+
436
+ # 4. Documentation
437
+ api_documentation_ui()
438
 
439
 
 
440
  if __name__ == "__main__":
441
  main_app()