Clemylia commited on
Commit
df39147
·
verified ·
1 Parent(s): a7f250d

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +53 -218
src/streamlit_app.py CHANGED
@@ -1,150 +1,33 @@
 
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 = 1000
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**."
127
-
128
- num_digits = len(re.findall(r'\d', key_str))
129
- if num_digits < 5:
130
- return False, f"La clé doit contenir au moins **5 chiffres** (actuel : {num_digits})."
131
-
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}**.")
@@ -155,15 +38,17 @@ def load_tiny_charlotte():
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,7 +57,7 @@ def run_inference(api_key, prompt, model, tokenizer, device):
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
@@ -182,16 +67,11 @@ def run_inference(api_key, prompt, model, tokenizer, device):
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]
@@ -202,21 +82,21 @@ def run_inference(api_key, prompt, model, tokenizer, device):
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; }
@@ -227,36 +107,26 @@ st.markdown(
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
-
243
  with st.form("new_key_form"):
244
  st.caption("Contraintes : Doit commencer par 'Tn-charlotte', contenir ≥ 5 chiffres et ≥ 7 lettres.")
245
- new_key = st.text_input(
246
- "Chaîne personnalisée",
247
- value="Tn-charlotte_Ma_Cle_12345ABCDEFG"
248
- )
249
  submitted = st.form_submit_button("✨ Créer la Clé")
250
-
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:
@@ -265,16 +135,12 @@ def generate_api_key_ui():
265
  st.error(f"🚫 **Erreur de validation :** {message}")
266
 
267
  def manage_api_keys_ui():
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({
@@ -282,108 +148,81 @@ def manage_api_keys_ui():
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)
336
- test_submitted = st.form_submit_button("🤖 Appeler l'API")
337
-
338
  if test_submitted:
339
  if not prompt:
340
  st.warning("Veuillez entrer un prompt.")
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
-
351
  st.markdown("### Réponse de Tiny-Charlotte :")
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 = {
@@ -410,17 +249,15 @@ except requests.exceptions.RequestException as e:
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,12 +265,10 @@ def main_app():
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
 
 
1
+ # Fichier : charlotte_apy.py (Interface utilisateur Streamlit)
2
  import streamlit as st
 
3
  import datetime
4
+ # Import de la logique partagée
5
+ import core_logic
6
+ # Import Streamlit spécifique
7
+ import torch
8
+ from transformers import AutoModelForCausalLM, AutoTokenizer
9
 
10
+ # Récupération des constantes depuis core_logic
11
+ DB_NAME = core_logic.DB_NAME
12
+ MODEL_NAME = core_logic.MODEL_NAME
13
+ MAX_QUOTA = core_logic.MAX_QUOTA
14
+ MAX_TOKENS_PER_RESPONSE = core_logic.MAX_TOKENS_PER_RESPONSE
15
 
16
+ # Initialisation de la BDD (assuré par core_logic mais on peut le rappeler)
17
+ core_logic.init_db()
 
 
18
 
19
  # ----------------------------------------------------
20
+ # A. CHARGEMENT DU MODÈLE POUR STREAMLIT
21
  # ----------------------------------------------------
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  @st.cache_resource
24
  def load_tiny_charlotte():
25
+ """Charge le modèle tiny-charlotte pour l'interface Streamlit."""
26
  try:
27
+ st.sidebar.info(f"⏳ Chargement du modèle {MODEL_NAME} pour Streamlit...")
28
 
29
  tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
 
30
  device = "cuda" if torch.cuda.is_available() else "cpu"
 
31
  model = AutoModelForCausalLM.from_pretrained(MODEL_NAME).to(device)
32
 
33
  st.sidebar.success(f"✅ Modèle Tiny-Charlotte chargé sur **{device}**.")
 
38
  return None, None, None
39
 
40
  # ----------------------------------------------------
41
+ # B. LOGIQUE D'INFÉRENCE ADAPTÉE À STREAMLIT
42
  # ----------------------------------------------------
43
+ # Nous devons recréer run_inference pour gérer l'affichage st.success/st.error
44
+ # en utilisant les fonctions de la BDD de core_logic.
45
 
46
+ def run_inference_streamlit(api_key, prompt, model, tokenizer, device):
47
  """
48
+ Exécute l'inférence pour Streamlit, gère le quota via core_logic.
49
  """
50
  today = datetime.date.today().isoformat()
51
+ key_data = core_logic.get_key_data(api_key)
52
 
53
  if not key_data:
54
  return "Erreur: Clé d'API non valide ou non trouvée.", 0
 
57
  if key_data['date_last_use'] != today:
58
  key_data['quota_remaining'] = key_data['max_quota']
59
  key_data['date_last_use'] = today
60
+ core_logic.update_key_quota_in_db(api_key, key_data['quota_remaining'], today)
61
  st.success(f"🎉 Quota réinitialisé automatiquement pour la clé **{api_key}** : {key_data['max_quota']} tokens disponibles aujourd'hui.")
62
 
63
  # 2. Vérification du Quota
 
67
  if model is None or tokenizer is None:
68
  return "Erreur interne: Le modèle n'est pas prêt.", 0
69
 
70
+ # 3. Exécution de la Génération (identique à l'API)
71
  try:
72
  input_ids = tokenizer.encode(prompt, return_tensors='pt').to(device)
73
+ # ... (génération)
74
+ output = model.generate(input_ids, max_length=input_ids.shape[1] + MAX_TOKENS_PER_RESPONSE, do_sample=True, top_k=50, top_p=0.95, num_return_sequences=1, pad_token_id=tokenizer.eos_token_id)
 
 
 
 
 
75
 
76
  response_text = tokenizer.decode(output[0], skip_special_tokens=True)
77
  tokens_generated = output.shape[1] - input_ids.shape[1]
 
82
 
83
  # 4. Mise à Jour du Quota DANS LA BASE DE DONNÉES
84
  new_remaining = key_data['quota_remaining'] - tokens_generated
85
+ core_logic.update_key_quota_in_db(api_key, new_remaining, today)
86
 
87
  return response_text, tokens_generated
88
 
89
+
90
  # ----------------------------------------------------
91
+ # C. INTERFACES UTILISATEUR STREAMLIT (Utilisation de core_logic)
92
  # ----------------------------------------------------
93
 
 
94
  st.set_page_config(page_title="Charlotte-APY 💖", layout="wide", initial_sidebar_state="expanded")
95
 
96
  st.markdown(
97
+ # (CSS pour le style Rose & Manga)
98
  """
99
  <style>
 
100
  .stApp { background-color: #FFF0F5; color: #333333; }
101
  h1 { color: #FF69B4; text-shadow: 2px 2px 4px #F08080; font-family: 'Comic Sans MS', cursive, sans-serif; text-align: center; }
102
  .stButton>button { background-color: #FFB6C1; color: #8B0000; border: 2px solid #FF69B4; border-radius: 10px; font-weight: bold; transition: all 0.2s; }
 
107
  """,
108
  unsafe_allow_html=True
109
  )
110
+ st.title("💖 Charlotte-APY : Portail d'Administration Tiny-Charlotte 🎀")
111
 
112
 
113
  def generate_api_key_ui():
 
114
  st.subheader("🔑 Créer une Nouvelle Clé d'API")
115
+ current_keys = core_logic.get_all_keys()
 
 
116
  if len(current_keys) >= 6:
117
  st.warning("❌ **Limite Atteinte !** Vous gérez déjà le maximum de 6 clés d'API.")
118
  return
 
119
  with st.form("new_key_form"):
120
  st.caption("Contraintes : Doit commencer par 'Tn-charlotte', contenir ≥ 5 chiffres et ≥ 7 lettres.")
121
+ new_key = st.text_input("Chaîne personnalisée", value="Tn-charlotte_Ma_Cle_12345ABCDEFG")
 
 
 
122
  submitted = st.form_submit_button("✨ Créer la Clé")
 
123
  if submitted:
124
+ is_valid, message = core_logic.validate_key(new_key)
125
+ if core_logic.get_key_data(new_key) is not None:
 
126
  is_valid = False
127
  message = "Cette clé d'API existe déjà (base de données)."
 
128
  if is_valid:
129
+ if core_logic.add_key_to_db(new_key):
130
  st.success(f"✅ Clé d'API **{new_key}** créée ! Quota : {MAX_QUOTA} tokens/jour.")
131
  st.rerun()
132
  else:
 
135
  st.error(f"🚫 **Erreur de validation :** {message}")
136
 
137
  def manage_api_keys_ui():
 
138
  st.subheader("🗂️ Gérer Vos Clés d'API")
139
+ keys_data_dict = core_logic.get_all_keys()
 
140
  keys_list = list(keys_data_dict.keys())
 
141
  if not keys_list:
142
  st.info("Vous n'avez pas encore de clés d'API.")
143
  return
 
144
  keys_data_list = []
145
  for key, data in keys_data_dict.items():
146
  keys_data_list.append({
 
148
  "Tokens Restants": f"{data['quota_remaining']} / {data['max_quota']}",
149
  "Dernière Utilisation (Réinitialisation)": data['date_last_use']
150
  })
 
151
  st.dataframe(keys_data_list, use_container_width=True, hide_index=True)
 
152
  st.markdown("---")
153
 
 
154
  st.write("### 🗑️ Supprimer une Clé")
155
  key_to_delete = st.selectbox("Sélectionnez la clé à supprimer :", [""] + keys_list, key="delete_select")
 
156
  if st.button("💔 Supprimer la Clé Sélectionnée", disabled=(key_to_delete == "")):
157
  if key_to_delete:
158
+ core_logic.delete_key_from_db(key_to_delete)
159
  st.success(f"🗑️ Clé **{key_to_delete}** supprimée avec succès.")
160
  st.rerun()
161
 
162
  def admin_quota_ui():
 
163
  st.subheader("⚙️ Administration : Réinitialisation du Quota")
164
+ keys_list = list(core_logic.get_all_keys().keys())
 
 
165
  if not keys_list:
166
  st.info("Aucune clé d'API à administrer.")
167
  return
 
168
  key_to_reset = st.selectbox("Sélectionnez la clé à réinitialiser :", [""] + keys_list, key="reset_select")
 
169
  if st.button("🔄 Réinitialiser le Quota à 600"):
170
  if key_to_reset:
171
+ core_logic.reset_key_quota_in_db(key_to_reset)
172
  st.success(f"✅ Quota pour la clé **{key_to_reset}** réinitialisé à {MAX_QUOTA} tokens.")
173
  st.rerun()
174
  else:
175
  st.warning("Veuillez sélectionner une clé.")
176
 
 
177
  def test_api_ui(model, tokenizer, device):
178
+ st.subheader("🧪 Tester l'API Tiny-Charlotte (Via Streamlit)")
179
+ keys_list = list(core_logic.get_all_keys().keys())
 
 
 
 
180
  if not keys_list:
181
  st.warning("Créez une clé d'API avant de pouvoir tester l'inférence.")
182
  return
 
183
  selected_key = st.selectbox("Sélectionnez votre clé d'API :", keys_list, key="inference_select")
 
184
  with st.form("inference_form"):
185
  prompt = st.text_area("Votre Requête pour Tiny-Charlotte", height=100)
186
+ test_submitted = st.form_submit_button("🤖 Appeler le Modèle")
 
187
  if test_submitted:
188
  if not prompt:
189
  st.warning("Veuillez entrer un prompt.")
190
  return
 
191
  if model is None:
192
  st.error("Le modèle n'a pas pu être chargé.")
193
  return
194
+ with st.spinner("Appel du modèle en cours..."):
195
+ response, tokens_used = run_inference_streamlit(selected_key, prompt, model, tokenizer, device)
 
 
 
196
  st.markdown("### Réponse de Tiny-Charlotte :")
197
  st.info(response)
198
+ updated_data = core_logic.get_key_data(selected_key)
 
 
 
199
  if updated_data:
200
  remaining = updated_data['quota_remaining']
 
201
  if tokens_used > 0:
202
  st.success(f"Tokens utilisés : **{tokens_used}**. Tokens restants pour cette clé aujourd'hui : **{remaining}** / {MAX_QUOTA}.")
203
  elif remaining < MAX_TOKENS_PER_RESPONSE:
204
+ st.error(f"Tokens restants : **{remaining}** / {MAX_QUOTA}. Pas assez de tokens pour une réponse complète ({MAX_TOKENS_PER_RESPONSE}).")
205
  else:
206
+ st.warning("Aucun token utilisé.")
207
  else:
208
  st.error("Impossible de récupérer les données de la clé après l'inférence.")
209
 
210
  def api_documentation_ui():
211
+ st.subheader("📖 Documentation de l'API de Production")
 
 
212
  st.markdown(f"""
213
  L'authentification se fait via la clé d'API dans l'en-tête `Authorization: Bearer <votre_clé>`.
214
 
215
+ * **Endpoint réel :** `{st.secrets.get('HUGGINGFACE_SPACE_URL', 'https://huggingface.co/spaces/Clemylia/Charlotte-APY')}/v1/inference`
216
  * **Limite :** {MAX_QUOTA} tokens par clé par jour.
217
  * **Réponse max :** {MAX_TOKENS_PER_RESPONSE} tokens.
 
 
218
  """)
219
 
220
  code_example = """
221
  import requests
222
  import json
223
 
224
+ # URL réelle de l'API de votre Space
225
+ API_URL = "https://huggingface.co/spaces/Clemylia/Charlotte-APY/v1/inference"
226
  YOUR_API_KEY = "Tn-charlotte_Ma_Cle_12345ABCDEFG"
227
 
228
  payload = {
 
249
  # --- DISPOSITION PRINCIPALE DE L'APPLICATION ---
250
  def main_app():
251
 
 
252
  model, tokenizer, device = load_tiny_charlotte()
253
 
 
254
  col1, col2 = st.columns([1, 1])
255
 
256
  with col1:
257
  generate_api_key_ui()
258
 
259
  with col2:
260
+ admin_quota_ui()
261
 
262
  st.markdown("---")
263
 
 
265
 
266
  st.markdown("---")
267
 
 
268
  test_api_ui(model, tokenizer, device)
269
 
270
  st.markdown("---")
271
 
 
272
  api_documentation_ui()
273
 
274