File size: 11,130 Bytes
df39147
79f73d6
a5f51e3
df39147
 
 
79f73d6
582bc08
df39147
 
70dd812
df39147
 
70dd812
 
df39147
70dd812
 
a5f51e3
 
df39147
a5f51e3
582bc08
a5f51e3
582bc08
a5f51e3
582bc08
a5f51e3
 
 
 
 
 
 
 
70dd812
df39147
70dd812
a5f51e3
df39147
a5f51e3
df39147
a5f51e3
 
df39147
a5f51e3
 
 
 
582bc08
a5f51e3
 
 
df39147
70dd812
a5f51e3
582bc08
a5f51e3
70dd812
a5f51e3
 
 
 
 
 
df39147
a5f51e3
 
 
 
 
 
 
 
582bc08
70dd812
df39147
a5f51e3
 
 
70dd812
582bc08
70dd812
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
df39147
70dd812
a5f51e3
 
 
df39147
70dd812
a5f51e3
 
 
 
df39147
a5f51e3
 
df39147
 
70dd812
 
a5f51e3
df39147
70dd812
 
 
 
a5f51e3
 
 
 
 
df39147
70dd812
a5f51e3
 
 
70dd812
 
 
a5f51e3
 
 
 
70dd812
a5f51e3
 
 
70dd812
a5f51e3
70dd812
df39147
a5f51e3
 
70dd812
 
 
df39147
70dd812
 
 
 
 
 
df39147
70dd812
 
 
 
 
a5f51e3
df39147
 
a5f51e3
 
 
70dd812
a5f51e3
 
df39147
a5f51e3
 
 
 
 
70dd812
a5f51e3
df39147
 
a5f51e3
 
df39147
70dd812
 
a5f51e3
70dd812
 
df39147
a5f51e3
df39147
70dd812
 
a5f51e3
70dd812
df39147
70dd812
582bc08
 
 
 
 
 
 
70dd812
 
 
 
582bc08
70dd812
 
 
582bc08
 
70dd812
 
582bc08
70dd812
582bc08
70dd812
582bc08
70dd812
582bc08
 
70dd812
 
 
 
 
 
 
 
582bc08
70dd812
 
a5f51e3
 
70dd812
a5f51e3
 
 
 
 
 
 
 
70dd812
 
df39147
a5f51e3
 
 
 
 
 
 
 
70dd812
 
 
 
a5f51e3
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# 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()