import gradio as gr import json import requests import tempfile from datetime import datetime, time import os import google.generativeai as genai from typing import Optional, Tuple # Configuration mise à jour avec les derniers modèles (juin 2025) UPDATED_MODELS = { "OpenAI": [ # Série GPT-4.1 (2025) "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", # Série o (reasoning models) "o3-pro", "o3", "o3-mini", "o4-mini", "o1", "o1-preview", "o1-pro", "o1-mini", # GPT-4.5 et autres "gpt-4.5-preview", "gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo", # Anciens modèles encore disponibles "gpt-4", "gpt-4-vision-preview", "gpt-3.5-turbo-16k" ], "Anthropic": [ # Claude 4 (mai 2025) "claude-opus-4-20250514", "claude-sonnet-4-20250514", # Claude 3.7 (février 2025) "claude-3-7-sonnet-20250219", "claude-3-7-sonnet-latest", # Claude 3.5 "claude-3-5-sonnet-20241022", "claude-3-5-sonnet-20240620", "claude-3-5-haiku-20241022", # Claude 3 "claude-3-opus-20240229", "claude-3-sonnet-20240229", "claude-3-haiku-20240307" ], "Google AI": [ # Gemini 2.5 (2025) - GRATUIT via Google AI Studio "gemini-2.5-pro 🆓", "gemini-2.5-flash 🆓", "gemini-2.5-flash-lite 🆓", "gemini-2.5-flash-preview-native-audio-dialog", "gemini-2.5-flash-preview-tts", "gemini-2.5-pro-preview-tts", # Gemini 2.0 - GRATUIT via Google AI Studio "gemini-2.0-flash 🆓", "gemini-2.0-flash-preview-image-generation", "gemini-2.0-flash-lite 🆓", "gemini-2.0-pro-experimental", # Gemini 1.5 "gemini-1.5-pro", "gemini-1.5-flash", "gemini-1.5-flash-8b", # Anciens modèles "gemini-pro", "gemini-pro-vision" ], "Cohere": [ "command-r", "command-r-plus", "command-r-08-2024", "command-r-plus-08-2024", "command", "command-light", "command-nightly", "aya-23-35b", "aya-23-8b" ], "Mistral AI": [ "mistral-medium-3", "mistral-small-3", "mistral-tiny-3", "magistral-medium", "magistral-small", "ministral-3b 🆓", "ministral-8b 🆓", "mistral-large", "mistral-medium", "mistral-small", "mistral-tiny", "mixtral-8x7b-instruct", "mixtral-8x22b-instruct" ], "Together AI": [ "meta-llama/Llama-4-Scout 🆓", "meta-llama/Llama-4-Maverick 🆓", "meta-llama/Llama-3.3-70B-Instruct 🆓", "meta-llama/Llama-3.3-Nemotron-Super-49B", "meta-llama/Llama-3.1-405B-Instruct", "meta-llama/Llama-3.1-70B-Instruct", "meta-llama/Llama-3.1-8B-Instruct 🆓", "Qwen/Qwen3-235B", "Qwen/Qwen3-32B 🆓", "Qwen/Qwen3-14B 🆓", "Qwen/Qwen3-8B 🆓", "Qwen/Qwen3-4B", "Qwen/QwQ-32B-Preview 🆓", "Qwen/Qwen2.5-Max", "deepseek-ai/DeepSeek-V3 🆓", "deepseek-ai/DeepSeek-R1 🆓", "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B 🆓" ], "Groq": [ "llama-3.3-70b-versatile 🆓", "llama-3.1-405b-reasoning", "llama-3.1-70b-versatile 🆓", "llama-3.1-8b-instant 🆓", "mixtral-8x7b-32768 🆓", "mixtral-8x22b-instruct", "gemma2-9b-it 🆓", "gemma-7b-it 🆓" ], "xAI": [ "grok-3", "grok-3-mini", "grok-3-reasoning-beta", "grok-2", "grok-2-mini", "grok-2-vision" ], "DeepSeek": [ "deepseek-r1 🆓", "deepseek-r1-distill-qwen-32b 🆓", "deepseek-r1-distill-qwen-14b 🆓", "deepseek-r1-distill-llama-70b 🆓", "deepseek-v3 🆓", "deepseek-v2.5", "deepseek-v2", "deepseek-coder-v2", "deepseek-coder-33b" ], "Perplexity AI": [ "sonar-reasoning", "sonar", "sonar-pro", "pplx-7b-online", "pplx-70b-online", "pplx-7b-chat", "pplx-70b-chat" ], "Amazon": ["nova-premier", "nova-pro", "nova-lite", "nova-micro"], "Reka AI": ["reka-flash-3", "reka-core", "reka-edge"] } # Informations détaillées sur les modèles gratuits FREE_MODELS_INFO = { "gemini-2.5-flash 🆓": { "provider": "Google AI Studio", "performance": "⭐⭐⭐⭐⭐ (Excellent, multimodal, très rapide)", "specialties": "Texte + images, vitesse, conversations longues", "limits": "1M tokens/min, 1500 req/jour", "conditions": "Compte Google gratuit. Données utilisées pour entraînement (hors UE/UK)", "signup": "https://ai.google.dev", "recommended": True, "default_available": True }, "deepseek-r1 🆓": { "provider": "DeepSeek + OpenRouter", "performance": "⭐⭐⭐⭐⭐ (Rivalise avec GPT-4o)", "specialties": "Raisonnement, mathématiques, programmation", "limits": "OpenRouter: 50 req/jour (1000 avec $10 crédit) | DeepSeek direct: Limites généreuses", "conditions": "Compte gratuit requis. Sur OpenRouter: données utilisées pour entraînement", "signup": "DeepSeek: https://platform.deepseek.com | OpenRouter: https://openrouter.ai", "recommended": True } } # Variables globales pour le quota DEFAULT_API_QUOTA = 1500 current_quota = DEFAULT_API_QUOTA quota_reset_time = None default_api_available = True def load_updated_models(): """Charge les modèles depuis le fichier JSON mis à jour ou utilise la configuration actualisée""" try: if os.path.exists('models_config.json'): with open('models_config.json', 'r', encoding='utf-8') as f: data = json.load(f) return data.get('models', UPDATED_MODELS), data.get('last_updated', 'Inconnu') config_url = "https://raw.githubusercontent.com/votre-username/votre-repo/main/models_config.json" response = requests.get(config_url, timeout=5) if response.status_code == 200: data = response.json() return data.get('models', UPDATED_MODELS), data.get('last_updated', 'Inconnu') except Exception as e: print(f"Erreur lors du chargement des modèles mis à jour: {e}") return UPDATED_MODELS, "Configuration mise à jour - Juin 2025" def get_default_api_key() -> Optional[str]: """Récupère la clé API par défaut depuis les secrets Hugging Face""" return os.getenv("GOOGLE_API_KEY") def check_quota_status() -> Tuple[bool, int, str]: """Vérifie le statut du quota de l'API par défaut""" global current_quota, quota_reset_time, default_api_available now = datetime.now() if quota_reset_time is None or now.date() > quota_reset_time.date(): current_quota = DEFAULT_API_QUOTA quota_reset_time = datetime.combine(now.date().replace(day=now.day + 1), time.min) default_api_available = True if current_quota <= 0: default_api_available = False hours_until_reset = (quota_reset_time - now).total_seconds() / 3600 status_msg = f"🚫 API par défaut épuisée. Réinitialisation dans {hours_until_reset:.1f}h" else: default_api_available = True status_msg = f"✅ API par défaut disponible" return default_api_available, current_quota, status_msg def update_models(company): """Met à jour la liste des modèles selon l'entreprise sélectionnée""" global default_api_available if company in CURRENT_MODELS: models = CURRENT_MODELS[company] default_value = None if default_api_available and company == "Google AI": default_value = "gemini-2.5-flash 🆓" elif models: default_value = models[0] if not default_api_available else None return gr.Dropdown( choices=models, value=default_value, label="🧠 Modèle LLM", interactive=True ) return gr.Dropdown(choices=[], label="🧠 Modèle LLM", interactive=True) def update_model_info(model): """Met à jour les informations du modèle sélectionné""" if not model: return "Sélectionnez un modèle pour voir ses informations." if model in FREE_MODELS_INFO: info = FREE_MODELS_INFO[model] default_api_info = "" if model == "gemini-2.5-flash 🆓" and default_api_available: default_api_info = f""" ### 🎁 **API PAR DÉFAUT DISPONIBLE** Cette app fournit une clé API Google gratuite pour ce modèle ! - **Quota partagé:** {current_quota}/{DEFAULT_API_QUOTA} requêtes restantes aujourd'hui - **Réinitialisation:** Chaque jour à minuit - **Alternative:** Vous pouvez utiliser votre propre clé API Google """ return f""" {default_api_info} ### 🆓 **{model}** - MODÈLE GRATUIT **🏢 Provider:** {info['provider']} **📊 Performance:** {info['performance']} **🎯 Spécialités:** {info['specialties']} **⏱️ Limites d'usage:** {info['limits']} **📋 Conditions:** {info['conditions']} **🔗 Inscription:** {info['signup']} {'🌟 **RECOMMANDÉ** - Excellent rapport qualité/gratuité' if info.get('recommended') else ''} """ elif "🆓" in model: return f""" ### 🆓 **{model}** - MODÈLE GRATUIT Ce modèle est disponible gratuitement sous certaines conditions. Consultez la documentation du provider pour les détails spécifiques. **⚠️ Important:** Les modèles gratuits nécessitent généralement : - Un compte gratuit chez le provider - Acceptation que vos données puissent être utilisées pour l'entraînement - Respect des limites d'usage quotidiennes/mensuelles """ else: return f""" ### 💰 **{model}** - MODÈLE PAYANT Ce modèle nécessite un abonnement ou un paiement à l'usage. Consultez la tarification du provider pour les coûts exacts. **💡 Conseil:** Vérifiez si le provider offre des crédits gratuits pour tester le modèle. """ def generate_selection_file(company, model, user_api_key, use_default_api): """Génère un fichier avec les choix effectués - SÉCURISÉ""" if not company or not model: return "Veuillez sélectionner une entreprise et un modèle", None, get_quota_display() try: is_free = "🆓" in model model_clean = model.replace(" 🆓", "") # SÉCURITÉ : Gestion des clés API api_config_section = "" api_status = "" if model == "gemini-2.5-flash 🆓" and use_default_api and default_api_available: # Utilisation de l'API par défaut - PAS DE CLÉ DANS LE FICHIER api_status = "🎁 API par défaut utilisée" api_config_section = """ 🔐 CONFIGURATION API - SÉCURISÉE =============================== Type d'API utilisée: API par défaut de l'application Clé API: [FOURNIE PAR L'APPLICATION - NON AFFICHÉE POUR SÉCURITÉ] ⚠️ POURQUOI LA CLÉ N'EST PAS AFFICHÉE ? - La clé API par défaut est stockée de manière sécurisée - Elle ne doit jamais être exposée aux utilisateurs - Cela protège le quota partagé contre les abus 🔑 POUR VOIR UNE CLÉ API DANS CETTE CONFIGURATION : 1. Créez votre compte gratuit sur https://ai.google.dev 2. Générez votre clé API personnelle 3. Saisissez-la dans le champ "Votre clé API" de l'interface 4. Régénérez cette configuration AVANTAGES DE VOTRE PROPRE CLÉ API : ✅ Quota personnel complet (1500 req/jour) ✅ Clé visible dans la configuration générée ✅ Contrôle total sur vos données ✅ Pas de partage avec d'autres utilisateurs """ elif user_api_key: # Utilisateur a fourni sa propre clé api_status = "🔑 Votre clé API sera utilisée" # Masquer partiellement la clé pour sécurité masked_key = user_api_key[:8] + "..." + user_api_key[-4:] if len(user_api_key) > 12 else "***" api_config_section = f""" 🔐 CONFIGURATION API - VOTRE CLÉ PERSONNELLE ========================================== Type d'API: Clé personnelle fournie Clé API: {user_api_key} Clé masquée: {masked_key} ✅ AVANTAGES DE VOTRE CLÉ PERSONNELLE : - Quota personnel complet - Contrôle total sur vos données - Pas de partage avec d'autres utilisateurs - Clé visible dans cette configuration """ else: api_status = "⚠️ Aucune clé API configurée" api_config_section = """ ⚠️ AUCUNE CLÉ API CONFIGURÉE ============================ Vous devez configurer une clé API pour utiliser ce modèle. OPTIONS DISPONIBLES : 1. Utiliser l'API par défaut (Gemini 2.5 Flash uniquement) 2. Créer votre compte et utiliser votre propre clé API POUR OBTENIR VOTRE CLÉ API : - Google AI: https://ai.google.dev - OpenAI: https://platform.openai.com - Anthropic: https://console.anthropic.com - DeepSeek: https://platform.deepseek.com """ # Création du contenu du fichier content = f"""Configuration LLM sélectionnée ===================================== Entreprise: {company} Modèle: {model_clean} Type: {'GRATUIT 🆓' if is_free else 'PAYANT 💰'} Statut API: {api_status} Date de génération: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} Dernière mise à jour des modèles: {LAST_UPDATE} {api_config_section} """ # Ajout d'informations sur l'API par défaut si applicable if model == "gemini-2.5-flash 🆓": content += f""" 🎁 INFORMATION API PAR DÉFAUT ============================ Cette application fournit une clé API Google gratuite pour Gemini 2.5 Flash. - Quota quotidien: {DEFAULT_API_QUOTA} requêtes (partagé) - Quota restant: {current_quota} requêtes - Réinitialisation: Chaque jour à minuit ⚠️ IMPORTANT - CONDITIONS D'USAGE : - API partagée entre tous les utilisateurs de l'app - Vos conversations peuvent être utilisées pour l'entraînement Google - Pour un usage privé, utilisez votre propre clé API Google - Quota limité - peut être épuisé rapidement 🔄 PASSER À VOTRE PROPRE API : 1. Aller sur https://ai.google.dev 2. Créer un compte Google gratuit 3. Générer votre clé API personnelle 4. Utiliser votre clé dans l'interface de l'app 5. Profiter de votre quota personnel de 1500 req/jour """ # Ajout d'exemples de code sécurisés if company == "Google AI": if use_default_api and not user_api_key: content += f""" Exemple d'utilisation (avec votre propre clé API): ------------------------------------------------ import google.generativeai as genai # ⚠️ REMPLACEZ PAR VOTRE VRAIE CLÉ API genai.configure(api_key="VOTRE_CLE_API_GOOGLE_ICI") model = genai.GenerativeModel('{model_clean}') response = model.generate_content("Votre prompt ici") print(response.text) # Avec LangChain from langchain_google_genai import ChatGoogleGenerativeAI llm = ChatGoogleGenerativeAI( model="{model_clean}", google_api_key="VOTRE_CLE_API_GOOGLE_ICI" ) 🔑 POUR OBTENIR VOTRE CLÉ API GOOGLE : 1. Aller sur https://ai.google.dev 2. Se connecter avec un compte Google 3. Cliquer sur "Get API Key" 4. Créer un nouveau projet si nécessaire 5. Copier la clé générée """ else: content += f""" Exemple d'utilisation avec votre clé API: --------------------------------------- import google.generativeai as genai # Votre clé API configurée genai.configure(api_key="{user_api_key if user_api_key else 'VOTRE_CLE_API_GOOGLE'}") model = genai.GenerativeModel('{model_clean}') response = model.generate_content("Votre prompt ici") print(response.text) """ # Création du fichier temporaire with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as f: f.write(content) temp_file_path = f.name return content, temp_file_path, get_quota_display() except Exception as e: error_msg = f"Erreur lors de la génération du fichier: {str(e)}" return error_msg, None, get_quota_display() def get_quota_display(): """Retourne l'affichage du quota actuel""" available, quota, status = check_quota_status() return f"📊 {status} | Quota: {quota}/{DEFAULT_API_QUOTA}" def refresh_models(): """Rafraîchit la liste des modèles""" global CURRENT_MODELS, LAST_UPDATE CURRENT_MODELS, LAST_UPDATE = load_updated_models() return f"Modèles rafraîchis - Dernière mise à jour: {LAST_UPDATE}" # Chargement des modèles au démarrage CURRENT_MODELS, LAST_UPDATE = load_updated_models() # CSS personnalisé pour un design moderne et classe custom_css = """ /* Design moderne et élégant */ .gradio-container { max-width: 1200px !important; margin: 0 auto !important; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important; } /* Titre centré avec style moderne */ .main-title { text-align: center !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important; background-clip: text !important; font-size: 2.5rem !important; font-weight: 700 !important; margin: 1rem 0 2rem 0 !important; letter-spacing: -0.02em !important; } /* Onglets modernes */ .tab-nav { border-bottom: 2px solid #e5e7eb !important; margin-bottom: 1.5rem !important; } .tab-nav button { background: none !important; border: none !important; padding: 0.75rem 1.5rem !important; font-weight: 600 !important; color: #6b7280 !important; border-bottom: 3px solid transparent !important; transition: all 0.3s ease !important; } .tab-nav button.selected { color: #4f46e5 !important; border-bottom-color: #4f46e5 !important; } .tab-nav button:hover { color: #4f46e5 !important; background: rgba(79, 70, 229, 0.05) !important; } /* Contenu des onglets */ .tab-content { width: 100% !important; padding: 1.5rem !important; background: #ffffff !important; border-radius: 12px !important; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important; border: 1px solid #e5e7eb !important; } /* Cartes d'information */ .info-card { background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%) !important; border: 1px solid #e2e8f0 !important; border-radius: 12px !important; padding: 1.5rem !important; margin: 1rem 0 !important; } /* Statut du quota */ .quota-status { background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important; color: white !important; padding: 0.75rem 1.5rem !important; border-radius: 8px !important; text-align: center !important; font-weight: 600 !important; margin-bottom: 1rem !important; } .quota-status.exhausted { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%) !important; } /* Boutons modernes */ .modern-button { background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%) !important; border: none !important; color: white !important; padding: 0.75rem 2rem !important; border-radius: 8px !important; font-weight: 600 !important; transition: all 0.3s ease !important; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important; } .modern-button:hover { transform: translateY(-2px) !important; box-shadow: 0 8px 15px -3px rgba(0, 0, 0, 0.2) !important; } /* Dropdowns et inputs */ .gradio-dropdown, .gradio-textbox { border-radius: 8px !important; border: 2px solid #e5e7eb !important; transition: border-color 0.3s ease !important; } .gradio-dropdown:focus, .gradio-textbox:focus { border-color: #4f46e5 !important; box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1) !important; } /* Badges gratuits */ .free-badge { background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important; color: white !important; padding: 0.25rem 0.5rem !important; border-radius: 4px !important; font-size: 0.75rem !important; font-weight: 600 !important; } /* Accordéons */ .gradio-accordion { border: 1px solid #e5e7eb !important; border-radius: 12px !important; overflow: hidden !important; margin: 1rem 0 !important; } .gradio-accordion summary { background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%) !important; padding: 1rem 1.5rem !important; font-weight: 600 !important; color: #374151 !important; } /* Responsive */ @media (max-width: 768px) { .gradio-container { max-width: 100% !important; padding: 0 1rem !important; } .main-title { font-size: 2rem !important; } .tab-content { padding: 1rem !important; } } """ # Interface Gradio avec design moderne with gr.Blocks( title="🤖 Sélecteur de LLM Premium - 2025", theme=gr.themes.Soft( primary_hue="indigo", secondary_hue="blue", neutral_hue="slate", font=gr.themes.GoogleFont("Inter") ), css=custom_css ) as app: # Titre centré moderne gr.HTML("""