Spaces:
Sleeping
Sleeping
| # 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 | |
| # ---------------------------------------------------- | |
| 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( | |
| """ | |
| <style> | |
| .stApp { background-color: #FFF0F5; color: #333333; } | |
| h1 { color: #FF69B4; text-shadow: 2px 2px 4px #F08080; font-family: 'Comic Sans MS', cursive, sans-serif; text-align: center; } | |
| .stButton>button { background-color: #FFB6C1; color: #8B0000; border: 2px solid #FF69B4; border-radius: 10px; font-weight: bold; transition: all 0.2s; } | |
| .stButton>button:hover { background-color: #FF69B4; color: white; } | |
| .stTextInput>div>div>input, .stTextArea>div>div>textarea { background-color: white; border: 1px solid #FFB6C1; border-radius: 5px; } | |
| h2, h3 { color: #FF1493; border-bottom: 2px solid #FFC0CB; padding-bottom: 5px; } | |
| </style> | |
| """, | |
| 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 <votre_clé>` | |
| * **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() |