MAJ MAIN.PY avec les différent modèles dispo sur openrouter
This commit is contained in:
1 parent
8f20caafe1
commit
fb193d6b63
1 file changed
+124
-17
@@ -5,23 +5,93 @@ from dotenv import load_dotenv
|
|||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
# En-têtes et CSS pour le style AETHAS38
|
# --- 1. GESTION DES MODELES (Mise en cache au démarrage) ---
|
||||||
|
MODELS_DATA = []
|
||||||
|
|
||||||
|
def init_models():
|
||||||
|
"""Télécharge la liste complète au lancement du serveur et la stocke en mémoire."""
|
||||||
|
global MODELS_DATA
|
||||||
|
try:
|
||||||
|
res = requests.get("https://openrouter.ai/api/v1/models", timeout=5)
|
||||||
|
if res.status_code == 200:
|
||||||
|
MODELS_DATA = res.json().get("data", [])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not MODELS_DATA: # Sécurité si le réseau coupe au démarrage
|
||||||
|
MODELS_DATA = [
|
||||||
|
{"id": "meta-llama/llama-3.1-8b-instruct:free", "name": "Llama 3.1 8B", "pricing": {"prompt": "0", "completion": "0"}},
|
||||||
|
{"id": "anthropic/claude-3.5-sonnet", "name": "Claude 3.5 Sonnet", "pricing": {"prompt": "1", "completion": "1"}}
|
||||||
|
]
|
||||||
|
|
||||||
|
init_models()
|
||||||
|
|
||||||
|
def check_if_free(model):
|
||||||
|
"""Vérifie si le modèle est gratuit en analysant ses tarifs OpenRouter."""
|
||||||
|
if "free" in model.get("id", "").lower():
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
pricing = model.get("pricing", {})
|
||||||
|
prompt = float(pricing.get("prompt", -1))
|
||||||
|
completion = float(pricing.get("completion", -1))
|
||||||
|
# Si le prix de lecture et d'écriture est à 0, c'est 100% gratuit
|
||||||
|
if prompt == 0.0 and completion == 0.0:
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_model_options(filter_type="all"):
|
||||||
|
"""Génère la liste déroulante des modèles avec leur étiquette, selon le filtre."""
|
||||||
|
options = []
|
||||||
|
for m in sorted(MODELS_DATA, key=lambda x: x['name']):
|
||||||
|
is_free = check_if_free(m)
|
||||||
|
|
||||||
|
# Application du filtre (Tous / Gratuits / Payants)
|
||||||
|
if filter_type == "free" and not is_free: continue
|
||||||
|
if filter_type == "paid" and is_free: continue
|
||||||
|
|
||||||
|
# Ajout de l'étiquette
|
||||||
|
tag = "Gratuit" if is_free else "Payant"
|
||||||
|
label = f"{m['name']} ({tag})"
|
||||||
|
|
||||||
|
options.append(Option(label, value=m['id']))
|
||||||
|
|
||||||
|
return Select(*options, name="model_id", cls="model-select", style="width: 100%;")
|
||||||
|
|
||||||
|
|
||||||
|
# --- 2. GESTION DU BUDGET ---
|
||||||
|
def get_budget():
|
||||||
|
mgmt_key = os.getenv("OPENROUTER_MANAGEMENT_KEY")
|
||||||
|
if not mgmt_key: return "Clé manquante"
|
||||||
|
try:
|
||||||
|
res = requests.get("https://openrouter.ai/api/v1/credits", headers={"Authorization": f"Bearer {mgmt_key}"}, timeout=5)
|
||||||
|
if res.status_code == 200:
|
||||||
|
data = res.json().get("data", {})
|
||||||
|
return f"{data.get('total_credits', 0) - data.get('total_usage', 0):.4f} $"
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return "Erreur lecture"
|
||||||
|
|
||||||
|
|
||||||
|
# --- 3. INTERFACE UTILISATEUR (UI) ---
|
||||||
theme_hdrs = [
|
theme_hdrs = [
|
||||||
Script(src="https://cdn.tailwindcss.com"),
|
Script(src="https://cdn.tailwindcss.com"),
|
||||||
Style("""
|
Style("""
|
||||||
body { background-color: #0f172a; color: #f8fafc; font-family: system-ui, sans-serif; }
|
body { background-color: #0f172a; color: #f8fafc; font-family: system-ui, sans-serif; }
|
||||||
.sidebar { background-color: #1e293b; border-right: 1px solid #333; height: 100vh; padding: 20px; }
|
.sidebar { background-color: #1e293b; border-right: 1px solid #333; height: 100vh; padding: 20px; display: flex; flex-direction: column;}
|
||||||
.main-content { padding: 20px; height: 100vh; display: flex; flex-direction: column; }
|
.main-content { padding: 20px; height: 100vh; display: flex; flex-direction: column; }
|
||||||
.chat-container { flex-grow: 1; overflow-y: auto; margin-bottom: 20px; border: 1px solid #333; padding: 15px; border-radius: 8px; background: #151e2e; }
|
.chat-container { flex-grow: 1; overflow-y: auto; margin-bottom: 20px; border: 1px solid #333; padding: 15px; border-radius: 8px; background: #151e2e; }
|
||||||
.input-row { display: flex; gap: 10px; align-items: flex-end; }
|
.input-row { display: flex; gap: 10px; align-items: flex-end; width: 100%; }
|
||||||
.input-box { flex-grow: 1; padding: 12px; border-radius: 5px; background: #2a2a35; border: 1px solid #444; color: white; outline: none; }
|
.input-box { flex-grow: 1; padding: 12px; border-radius: 5px; background: #2a2a35; border: 1px solid #444; color: white; outline: none; }
|
||||||
|
.model-select { padding: 12px; border-radius: 5px; background: #2a2a35; border: 1px solid #444; color: #00e5ff; outline: none; font-weight: bold; cursor: pointer;}
|
||||||
.send-btn { padding: 12px 24px; border-radius: 5px; background: #00e5ff; color: #0f172a; font-weight: bold; cursor: pointer; border: none; transition: 0.2s; }
|
.send-btn { padding: 12px 24px; border-radius: 5px; background: #00e5ff; color: #0f172a; font-weight: bold; cursor: pointer; border: none; transition: 0.2s; }
|
||||||
.send-btn:hover { background: #38bdf8; }
|
.send-btn:hover { background: #38bdf8; }
|
||||||
/* Style des messages */
|
|
||||||
.msg-user { color: #00e5ff; font-size: 12px; font-weight: bold; margin-bottom: 2px; margin-top: 15px;}
|
.msg-user { color: #00e5ff; font-size: 12px; font-weight: bold; margin-bottom: 2px; margin-top: 15px;}
|
||||||
.msg-ia { color: #a855f7; font-size: 12px; font-weight: bold; margin-bottom: 2px; margin-top: 15px;}
|
.msg-ia { color: #a855f7; font-size: 12px; font-weight: bold; margin-bottom: 2px; margin-top: 15px;}
|
||||||
.bubble-user { background: #1e293b; padding: 10px; border-radius: 5px; display: inline-block; color: white; }
|
.bubble-user { background: #1e293b; padding: 10px; border-radius: 5px; display: inline-block; color: white; }
|
||||||
.bubble-ia { background: #2a2a35; padding: 10px; border-radius: 5px; display: inline-block; color: #e2e8f0; border-left: 3px solid #a855f7; }
|
.bubble-ia { background: #2a2a35; padding: 10px; border-radius: 5px; display: inline-block; color: #e2e8f0; border-left: 3px solid #a855f7; white-space: pre-wrap; }
|
||||||
|
.budget-box { background: #151e2e; padding: 15px; border-radius: 8px; border: 1px solid #333; margin-top: auto; }
|
||||||
""")
|
""")
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -34,14 +104,39 @@ def get():
|
|||||||
Div(
|
Div(
|
||||||
H2("AETHAS 38", style="color:#00e5ff; font-weight:bold; font-size:24px;"),
|
H2("AETHAS 38", style="color:#00e5ff; font-weight:bold; font-size:24px;"),
|
||||||
P("Tech Core - Multi IA", style="color:#a855f7; margin-bottom: 20px;"),
|
P("Tech Core - Multi IA", style="color:#a855f7; margin-bottom: 20px;"),
|
||||||
|
|
||||||
|
Div(
|
||||||
|
P("BUDGET OPENROUTER", style="color:#94a3b8; font-size:10px; font-weight:bold; margin-bottom:5px;"),
|
||||||
|
P(get_budget(), id="budget-display", style="color:#10b981; font-size:18px; font-weight:bold;"),
|
||||||
|
cls="budget-box"
|
||||||
|
),
|
||||||
cls="sidebar"
|
cls="sidebar"
|
||||||
),
|
),
|
||||||
Div(
|
Div(
|
||||||
Div(id="chat-history", cls="chat-container"),
|
Div(id="chat-history", cls="chat-container"),
|
||||||
Form(
|
Form(
|
||||||
Input(type="text", name="msg", placeholder="Demandez moi de coder quelque chose...", cls="input-box", required=True),
|
# Ligne 1 : Filtre et Liste dynamique des modèles
|
||||||
Button("Envoyer", type="submit", cls="send-btn"),
|
Div(
|
||||||
cls="input-row",
|
Select(
|
||||||
|
Option("Tous les modèles", value="all"),
|
||||||
|
Option("Gratuits uniquement", value="free"),
|
||||||
|
Option("Payants uniquement", value="paid"),
|
||||||
|
name="filter_type",
|
||||||
|
cls="model-select",
|
||||||
|
hx_get="/filter_models", # Demande la mise à jour à HTMX...
|
||||||
|
hx_target="#model-select-wrapper", # ...pour l'injecter dans cet élément
|
||||||
|
hx_swap="innerHTML",
|
||||||
|
style="width: 250px;"
|
||||||
|
),
|
||||||
|
Div(get_model_options("all"), id="model-select-wrapper", style="flex-grow: 1;"),
|
||||||
|
cls="flex gap-2 mb-2 w-full"
|
||||||
|
),
|
||||||
|
# Ligne 2 : Saisie texte et Bouton d'envoi
|
||||||
|
Div(
|
||||||
|
Input(type="text", name="msg", placeholder="Demandez moi de coder quelque chose...", cls="input-box", required=True),
|
||||||
|
Button("Envoyer", type="submit", cls="send-btn"),
|
||||||
|
cls="input-row"
|
||||||
|
),
|
||||||
hx_post="/chat",
|
hx_post="/chat",
|
||||||
hx_target="#chat-history",
|
hx_target="#chat-history",
|
||||||
hx_swap="beforeend"
|
hx_swap="beforeend"
|
||||||
@@ -52,38 +147,50 @@ def get():
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- 4. ROUTES DYNAMIQUES (HTMX) ---
|
||||||
|
|
||||||
|
@rt('/filter_models')
|
||||||
|
def get(filter_type: str):
|
||||||
|
"""Route appelée automatiquement quand on change le filtre (Gratuit/Payant)"""
|
||||||
|
return get_model_options(filter_type)
|
||||||
|
|
||||||
@rt('/chat')
|
@rt('/chat')
|
||||||
def post(msg: str):
|
def post(msg: str, model_id: str):
|
||||||
# 1. Préparation de la requête vers l'API OpenRouter
|
|
||||||
api_key = os.getenv("OPENROUTER_API_KEY")
|
api_key = os.getenv("OPENROUTER_API_KEY")
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {api_key}",
|
"Authorization": f"Bearer {api_key}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Payload avec un modèle gratuit d'OpenRouter
|
|
||||||
payload = {
|
payload = {
|
||||||
"model": "meta-llama/llama-3-8b-instruct:free",
|
"model": model_id,
|
||||||
"messages": [{"role": "user", "content": msg}]
|
"messages": [{"role": "user", "content": msg}]
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2. Envoi de la requête
|
|
||||||
try:
|
try:
|
||||||
response = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload)
|
response = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
ia_reponse = response.json()["choices"][0]["message"]["content"]
|
ia_reponse = response.json()["choices"][0]["message"]["content"]
|
||||||
else:
|
else:
|
||||||
ia_reponse = f"Erreur API ({response.status_code}) : Vérifiez votre clé dans le fichier .env"
|
ia_reponse = f"Erreur API ({response.status_code}) : {response.text}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ia_reponse = f"Erreur réseau : {e}"
|
ia_reponse = f"Erreur réseau : {e}"
|
||||||
|
|
||||||
# 3. Injection de la réponse dans l'interface Web
|
nom_modele = model_id.split('/')[-1]
|
||||||
return Div(
|
|
||||||
|
# 1. On prépare la bulle de chat
|
||||||
|
chat_bubble = Div(
|
||||||
P("Vous", cls="msg-user"),
|
P("Vous", cls="msg-user"),
|
||||||
Div(msg, cls="bubble-user"),
|
Div(msg, cls="bubble-user"),
|
||||||
P("AETHAS38 (Llama 3)", cls="msg-ia"),
|
P(f"AETHAS38 ({nom_modele})", cls="msg-ia"),
|
||||||
Div(ia_reponse, cls="bubble-ia")
|
Div(ia_reponse, cls="bubble-ia")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 2. On recalcule le budget financier et on lui ordonne de s'actualiser avec hx_swap_oob
|
||||||
|
updated_budget = P(get_budget(), id="budget-display", style="color:#10b981; font-size:18px; font-weight:bold;", hx_swap_oob="true")
|
||||||
|
|
||||||
|
return chat_bubble, updated_budget
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
serve(port=5001)
|
serve(port=5001)
|
||||||
Reference in new issue
Block a user