From c2c35542d754e2abc740575bec5d7d29bb3abd11 Mon Sep 17 00:00:00 2001 From: Xavier Date: Thu, 27 Aug 2026 20:37:05 +0200 Subject: [PATCH] Mise a jour media: gestion dynamique HF et securisation payant OpenRouter --- modules/ai_media.py | 77 ++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/modules/ai_media.py b/modules/ai_media.py index 703e28b..c4ac7f2 100644 --- a/modules/ai_media.py +++ b/modules/ai_media.py @@ -5,6 +5,26 @@ import concurrent.futures import base64 from modules.logger import log_event +def obtenir_modele_image_hf_dynamique(api_key: str) -> str: + """Interroge le Hub HF pour trouver le modèle image gratuit le plus populaire ET actif (warm).""" + url = "https://huggingface.co/api/models" + params = { + "pipeline_tag": "text-to-image", + "sort": "downloads", + "direction": -1, + "inference": "warm", # GARANTIT que le modèle est gratuit et en ligne + "limit": 3 + } + headers = {"Authorization": f"Bearer {api_key}"} if api_key else {} + try: + resp = requests.get(url, headers=headers, params=params, timeout=5) + if resp.status_code == 200 and len(resp.json()) > 0: + return resp.json()[0]["id"] + except Exception: + pass + # Secours absolu si l'API de liste ne répond pas + return "runwayml/stable-diffusion-v1-5" + def generer_image_cloudflare(prompt: str) -> bytes: account_id = os.getenv("CLOUDFLARE_ACCOUNT_ID") api_token = os.getenv("CLOUDFLARE_API_TOKEN") @@ -36,36 +56,43 @@ def generer_image_openrouter(prompt: str) -> tuple: "HTTP-Referer": "https://aethas38.duckdns.org", "X-Title": "Aethas38" } - modele = "bytedance-seed/seedream-4.5" - payload = {"prompt": prompt, "model": modele} - try: - resp = requests.post(url, headers=headers, json=payload, timeout=120) - if resp.status_code == 200: - data = resp.json() - if "data" in data and len(data["data"]) > 0 and "b64_json" in data["data"][0]: - img_bytes = base64.b64decode(data["data"][0]["b64_json"]) - return img_bytes, modele - except Exception as e: - log_event("WARNING", "MEDIA", f"Échec OpenRouter Image : {e}") + # LISTE DE REPLI (Fallback) : Maîtrise des coûts tout en évitant les crashs + modeles_payants = ["bytedance-seed/seedream-4.5", "openai/dall-e-3"] + + for modele in modeles_payants: + payload = {"prompt": prompt, "model": modele} + try: + resp = requests.post(url, headers=headers, json=payload, timeout=120) + if resp.status_code == 200: + data = resp.json() + if "data" in data and len(data["data"]) > 0 and "b64_json" in data["data"][0]: + img_bytes = base64.b64decode(data["data"][0]["b64_json"]) + return img_bytes, modele + except Exception as e: + log_event("WARNING", "MEDIA", f"Échec OpenRouter ({modele}) : {e}") + continue # Passe au modèle de secours suivant + return None, None def generer_image_huggingface(prompt: str) -> tuple: api_key = os.getenv("HUGGINGFACE_API_KEY") - if not api_key: return None, "Clé API manquante" + if not api_key: return None, "Clé API manquante", None - url = "https://router.huggingface.co/hf-inference/models/black-forest-labs/FLUX.1-schnell" + # Sélection dynamique HF + Utilisation de la passerelle de secours réseau (router) + modele_cible = obtenir_modele_image_hf_dynamique(api_key) + url = f"https://router.huggingface.co/hf-inference/models/{modele_cible}" headers = {"Authorization": f"Bearer {api_key}"} try: resp = requests.post(url, headers=headers, json={"inputs": prompt}, timeout=120) if resp.status_code == 200: - return resp.content, "Succès" + return resp.content, "Succès", modele_cible else: - return None, f"Erreur HTTP {resp.status_code} : {resp.text}" + return None, f"Erreur HTTP {resp.status_code} : {resp.text}", modele_cible except Exception as e: log_event("WARNING", "MEDIA", f"Échec HF : {e}") - return None, f"Exception système : {str(e)}" + return None, f"Exception système : {str(e)}", "Inconnu" def generer_les_images(prompt: str, fichiers: list, premium: bool) -> tuple: trace = [] @@ -75,7 +102,6 @@ def generer_les_images(prompt: str, fichiers: list, premium: bool) -> tuple: trace.append(f"🟢 Lancement Text-to-Image (Premium: {premium})") - # Exécution en parallèle (OpenRouter n'est lancé QUE si premium est True) with concurrent.futures.ThreadPoolExecutor() as executor: future_or = executor.submit(generer_image_openrouter, prompt) if premium else None future_cf = executor.submit(generer_image_cloudflare, prompt) @@ -83,7 +109,7 @@ def generer_les_images(prompt: str, fichiers: list, premium: bool) -> tuple: img_or, nom_modele_or = future_or.result() if future_or else (None, None) img_cf = future_cf.result() - img_hf, msg_hf = future_hf.result() + img_hf, msg_hf, nom_modele_hf = future_hf.result() if future_hf else (None, None, None) dossier_media = "/DATA/AppData/MULTI-IA-AETHAS38/RAG/Media" os.makedirs(dossier_media, exist_ok=True) @@ -91,7 +117,7 @@ def generer_les_images(prompt: str, fichiers: list, premium: bool) -> tuple: reponse_md = f"**Prompt analysé :** {prompt}\n\n" fournisseurs_actifs = [] - # Traitement OpenRouter + # --- Rendu OpenRouter --- if future_or is None: trace.append("⚪ OR : Ignoré (Mode Gratuit)") elif img_or: @@ -100,11 +126,11 @@ def generer_les_images(prompt: str, fichiers: list, premium: bool) -> tuple: reponse_md += f"### 🌐 OpenRouter ({nom_modele_or})\n![OpenRouter](/Media/{nom_or})\n" reponse_md += f"**[📥 Télécharger l'image OR](/Media/{nom_or})**\n\n---\n\n" fournisseurs_actifs.append("OpenRouter") - trace.append("🟢 OR : Succès") + trace.append(f"🟢 OR ({nom_modele_or}) : Succès") else: trace.append("🔴 OR : Échec") - # Traitement Cloudflare + # --- Rendu Cloudflare --- if img_cf: nom_cf = f"img_cf_{int(time.time())}.png" with open(os.path.join(dossier_media, nom_cf), 'wb') as f: f.write(img_cf) @@ -115,18 +141,19 @@ def generer_les_images(prompt: str, fichiers: list, premium: bool) -> tuple: else: trace.append("🔴 CF : Échec") - # Traitement HuggingFace + # --- Rendu HuggingFace Dynamique --- if img_hf: nom_hf = f"img_hf_{int(time.time())}.png" with open(os.path.join(dossier_media, nom_hf), 'wb') as f: f.write(img_hf) - reponse_md += f"### 🤗 HuggingFace (FLUX.1)\n![HuggingFace](/Media/{nom_hf})\n" + nom_propre = nom_modele_hf.split('/')[-1] if nom_modele_hf else "Générateur" + reponse_md += f"### 🤗 HuggingFace ({nom_propre})\n![HuggingFace](/Media/{nom_hf})\n" reponse_md += f"**[📥 Télécharger l'image HF](/Media/{nom_hf})**\n\n" fournisseurs_actifs.append("HuggingFace") - trace.append("🟢 HF : Succès") + trace.append(f"🟢 HF ({nom_propre}) : Succès") else: erreur_courte = str(msg_hf)[:50] + "..." if len(str(msg_hf)) > 50 else str(msg_hf) trace.append(f"🔴 HF : Échec ({erreur_courte})") - reponse_md += f"### 🤗 HuggingFace (FLUX.1)\n*⚠️ Échec de la génération : {msg_hf}*\n\n" + reponse_md += f"### 🤗 HuggingFace\n*⚠️ Échec de la génération : {msg_hf}*\n\n" if not img_or and not img_cf and not img_hf: return "*Échec critique : Aucune API n'a pu générer d'image.*", "#ff4444", "Système", "Erreur", trace