Charlotte-APY / src /streamlit_app.py
Clemylia's picture
Update src/streamlit_app.py
582bc08 verified
# 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(
"""
<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()