# Fichier : charlotte_apy.py (Interface utilisateur Streamlit) import streamlit as st import datetime import core_logic import torch from transformers import AutoModelForCausalLM, AutoTokenizer # Récupération des constantes MAX_QUOTA = core_logic.MAX_QUOTA MAX_TOKENS_PER_RESPONSE = core_logic.MAX_TOKENS_PER_RESPONSE # Initialisation de la BDD (assuré par core_logic mais on peut le rappeler) core_logic.init_db() # ---------------------------------------------------- # A. CHARGEMENT DU MODÈLE POUR STREAMLIT # ---------------------------------------------------- @st.cache_resource def load_tiny_charlotte(): """Charge le modèle tiny-charlotte pour l'interface Streamlit.""" try: st.sidebar.info(f"⏳ Chargement du modèle {core_logic.MODEL_NAME} pour Streamlit...") tokenizer = AutoTokenizer.from_pretrained(core_logic.MODEL_NAME) device = "cuda" if torch.cuda.is_available() else "cpu" model = AutoModelForCausalLM.from_pretrained(core_logic.MODEL_NAME).to(device) st.sidebar.success(f"✅ Modèle Tiny-Charlotte chargé sur **{device}**.") return model, tokenizer, device except Exception as e: st.sidebar.error(f"❌ Erreur de chargement du modèle : {e}") return None, None, None # ---------------------------------------------------- # B. LOGIQUE D'INFÉRENCE ADAPTÉE À STREAMLIT # ---------------------------------------------------- def run_inference_streamlit(api_key, prompt, model, tokenizer, device): """ Exécute l'inférence pour Streamlit, gère le quota via core_logic. """ today = datetime.date.today().isoformat() key_data = core_logic.get_key_data(api_key) if not key_data: return "Erreur: Clé d'API non valide ou non trouvée.", 0 # Réinitialisation if key_data['date_last_use'] != today: key_data['quota_remaining'] = key_data['max_quota'] key_data['date_last_use'] = today core_logic.update_key_quota_in_db(api_key, key_data['quota_remaining'], today) st.success(f"🎉 Quota réinitialisé automatiquement pour la clé **{api_key}** : {key_data['max_quota']} tokens disponibles aujourd'hui.") # Vérification du Quota if key_data['quota_remaining'] < MAX_TOKENS_PER_RESPONSE: return f"🚫 **Quota journalier atteint** ({key_data['quota_remaining']} / {MAX_QUOTA}). Veuillez réessayer demain.", 0 if model is None or tokenizer is None: return "Erreur interne: Le modèle n'est pas prêt.", 0 try: input_ids = tokenizer.encode(prompt, return_tensors='pt').to(device) 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) response_text = tokenizer.decode(output[0], skip_special_tokens=True) tokens_generated = output.shape[1] - input_ids.shape[1] except Exception as e: st.error(f"Erreur lors de la génération: {e}") return "Erreur d'inférence. Problème avec le modèle.", 0 # Mise à Jour du Quota DANS LA BASE DE DONNÉES new_remaining = key_data['quota_remaining'] - tokens_generated core_logic.update_key_quota_in_db(api_key, new_remaining, today) return response_text, tokens_generated # ---------------------------------------------------- # C. INTERFACES UTILISATEUR STREAMLIT # ---------------------------------------------------- st.set_page_config(page_title="Charlotte-APY 💖", layout="wide", initial_sidebar_state="expanded") st.markdown( """ """, unsafe_allow_html=True ) st.title("💖 Charlotte-APY : Portail d'Administration Tiny-Charlotte 🎀") def generate_api_key_ui(): st.subheader("🔑 Créer une Nouvelle Clé d'API") current_keys = core_logic.get_all_keys() if len(current_keys) >= 6: st.warning("❌ **Limite Atteinte !** Vous gérez déjà le maximum de 6 clés d'API.") return with st.form("new_key_form"): st.caption("Contraintes : Doit commencer par 'Tn-charlotte', contenir ≥ 5 chiffres et ≥ 7 lettres.") new_key = st.text_input("Chaîne personnalisée", value="Tn-charlotte_Ma_Cle_12345ABCDEFG") submitted = st.form_submit_button("✨ Créer la Clé") if submitted: is_valid, message = core_logic.validate_key(new_key) if core_logic.get_key_data(new_key) is not None: is_valid = False message = "Cette clé d'API existe déjà (base de données)." if is_valid: if core_logic.add_key_to_db(new_key): st.success(f"✅ Clé d'API **{new_key}** créée ! Quota : {MAX_QUOTA} tokens/jour.") st.rerun() else: st.error("Échec de l'ajout à la base de données.") else: st.error(f"🚫 **Erreur de validation :** {message}") def manage_api_keys_ui(): st.subheader("🗂️ Gérer Vos Clés d'API") keys_data_dict = core_logic.get_all_keys() keys_list = list(keys_data_dict.keys()) if not keys_list: st.info("Vous n'avez pas encore de clés d'API.") return keys_data_list = [] for key, data in keys_data_dict.items(): keys_data_list.append({ "Clé d'API": key, "Tokens Restants": f"{data['quota_remaining']} / {data['max_quota']}", "Dernière Utilisation (Réinitialisation)": data['date_last_use'] }) st.dataframe(keys_data_list, use_container_width=True, hide_index=True) st.markdown("---") st.write("### 🗑️ Supprimer une Clé") key_to_delete = st.selectbox("Sélectionnez la clé à supprimer :", [""] + keys_list, key="delete_select") if st.button("💔 Supprimer la Clé Sélectionnée", disabled=(key_to_delete == "")): if key_to_delete: core_logic.delete_key_from_db(key_to_delete) st.success(f"🗑️ Clé **{key_to_delete}** supprimée avec succès.") st.rerun() def admin_quota_ui(): st.subheader("⚙️ Administration : Réinitialisation du Quota") keys_list = list(core_logic.get_all_keys().keys()) if not keys_list: st.info("Aucune clé d'API à administrer.") return key_to_reset = st.selectbox("Sélectionnez la clé à réinitialiser :", [""] + keys_list, key="reset_select") if st.button("🔄 Réinitialiser le Quota à 600"): if key_to_reset: core_logic.reset_key_quota_in_db(key_to_reset) st.success(f"✅ Quota pour la clé **{key_to_reset}** réinitialisé à {MAX_QUOTA} tokens.") st.rerun() else: st.warning("Veuillez sélectionner une clé.") def test_api_ui(model, tokenizer, device): st.subheader("🧪 Tester l'API Tiny-Charlotte (Via Streamlit)") keys_list = list(core_logic.get_all_keys().keys()) if not keys_list: st.warning("Créez une clé d'API avant de pouvoir tester l'inférence.") return selected_key = st.selectbox("Sélectionnez votre clé d'API :", keys_list, key="inference_select") with st.form("inference_form"): prompt = st.text_area("Votre Requête pour Tiny-Charlotte", height=100) test_submitted = st.form_submit_button("🤖 Appeler le Modèle") if test_submitted: if not prompt: st.warning("Veuillez entrer un prompt.") return if model is None: st.error("Le modèle n'a pas pu être chargé.") return with st.spinner("Appel du modèle en cours..."): response, tokens_used = run_inference_streamlit(selected_key, prompt, model, tokenizer, device) st.markdown("### Réponse de Tiny-Charlotte :") st.info(response) updated_data = core_logic.get_key_data(selected_key) if updated_data: remaining = updated_data['quota_remaining'] if tokens_used > 0: st.success(f"Tokens utilisés : **{tokens_used}**. Tokens restants pour cette clé aujourd'hui : **{remaining}** / {MAX_QUOTA}.") elif remaining < MAX_TOKENS_PER_RESPONSE: st.error(f"Tokens restants : **{remaining}** / {MAX_QUOTA}. Pas assez de tokens pour une réponse complète ({MAX_TOKENS_PER_RESPONSE}).") else: st.warning("Aucun token utilisé.") else: st.error("Impossible de récupérer les données de la clé après l'inférence.") def api_documentation_ui(): st.subheader("📖 Documentation de l'API de Production") # CORRECTION APPORTÉE ICI API_SPACE_URL = "https://huggingface.co/spaces/Clemylia/Charlotte-APY/v1/inference" st.markdown(f""" * **Endpoint réel :** `{API_SPACE_URL}` * **Méthode :** `POST` * **Authentification :** En-tête `Authorization: Bearer ` * **Limite :** {MAX_QUOTA} tokens par clé par jour. * **Réponse max :** {MAX_TOKENS_PER_RESPONSE} tokens. """) code_example = f""" import requests import json # URL réelle de l'API API_URL = "{API_SPACE_URL}" YOUR_API_KEY = "Tn-charlotte_Ma_Cle_12345ABCDEFG" payload = {{ "prompt": "Peux-tu me donner un conseil sur l'espoir ?", }} headers = {{ "Content-Type": "application/json", "Authorization": f"Bearer {{YOUR_API_KEY}}" }} try: response = requests.post(API_URL, headers=headers, data=json.dumps(payload)) response.raise_for_status() data = response.json() print("Réponse de Tiny-Charlotte :", data.get("generated_text")) except requests.exceptions.RequestException as e: print(f"Erreur lors de l'appel API: {{e}}") """ st.code(code_example, language="python") # --- DISPOSITION PRINCIPALE DE L'APPLICATION --- def main_app(): model, tokenizer, device = load_tiny_charlotte() col1, col2 = st.columns([1, 1]) with col1: generate_api_key_ui() with col2: admin_quota_ui() st.markdown("---") manage_api_keys_ui() st.markdown("---") test_api_ui(model, tokenizer, device) st.markdown("---") api_documentation_ui() if __name__ == "__main__": main_app()