mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-11 17:29:14 +02:00
Modifications majeures: 1. Rebranding: Pricecious → PriceFlow - Mise à jour du titre, logo et noms de projet 2. Intégration OpenRouter - Ajout comme provider IA avec filtres (chat, vision, code, reasoning, free) - Récupération dynamique des modèles et coûts - Service et router dédiés (/api/openrouter/models) 3. Internationalisation (i18n) - Installation i18next + react-i18next - Application entièrement traduite en français - Fichier de traduction: fr.json 4. Configuration Docker - docker-compose.yml avec réseau nginx_default (external) - PostgreSQL sur port 5488:5432 - Browserless sur port 3012:3000 - init.sql pour initialisation de la base de données - .env.example avec variables documentées 5. Favicon - Documentation ajoutée dans index.html pour faciliter le remplacement
161 lines
5.3 KiB
Python
161 lines
5.3 KiB
Python
"""
|
|
Service pour interagir avec l'API OpenRouter.
|
|
Récupère la liste des modèles disponibles avec leurs coûts et catégories.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
import requests
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
OPENROUTER_API_URL = "https://openrouter.ai/api/v1"
|
|
|
|
|
|
class OpenRouterService:
|
|
@staticmethod
|
|
def get_models(api_key: str | None = None) -> list[dict[str, Any]]:
|
|
"""
|
|
Récupère la liste des modèles disponibles sur OpenRouter.
|
|
|
|
Args:
|
|
api_key: Clé API OpenRouter (optionnelle pour la liste des modèles)
|
|
|
|
Returns:
|
|
Liste des modèles avec leurs informations (id, nom, coûts, catégories)
|
|
"""
|
|
try:
|
|
headers = {"Content-Type": "application/json"}
|
|
if api_key:
|
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
|
|
response = requests.get(
|
|
f"{OPENROUTER_API_URL}/models",
|
|
headers=headers,
|
|
timeout=30
|
|
)
|
|
response.raise_for_status()
|
|
|
|
data = response.json()
|
|
models = data.get("data", [])
|
|
|
|
# Transformer et enrichir les données des modèles
|
|
processed_models = []
|
|
for model in models:
|
|
processed_model = {
|
|
"id": model.get("id", ""),
|
|
"name": model.get("name", model.get("id", "")),
|
|
"description": model.get("description", ""),
|
|
"context_length": model.get("context_length", 0),
|
|
"pricing": {
|
|
"prompt": float(model.get("pricing", {}).get("prompt", 0)),
|
|
"completion": float(model.get("pricing", {}).get("completion", 0)),
|
|
},
|
|
"top_provider": model.get("top_provider", {}),
|
|
"architecture": model.get("architecture", {}),
|
|
}
|
|
|
|
# Déterminer les catégories du modèle
|
|
categories = OpenRouterService._categorize_model(model)
|
|
processed_model["categories"] = categories
|
|
|
|
processed_models.append(processed_model)
|
|
|
|
# Trier par nom
|
|
processed_models.sort(key=lambda x: x["name"].lower())
|
|
|
|
logger.info(f"Récupéré {len(processed_models)} modèles OpenRouter")
|
|
return processed_models
|
|
|
|
except requests.RequestException as e:
|
|
logger.error(f"Erreur lors de la récupération des modèles OpenRouter: {e}")
|
|
return []
|
|
except Exception as e:
|
|
logger.error(f"Erreur inattendue: {e}")
|
|
return []
|
|
|
|
@staticmethod
|
|
def _categorize_model(model: dict[str, Any]) -> list[str]:
|
|
"""
|
|
Détermine les catégories d'un modèle basé sur ses capacités.
|
|
|
|
Categories possibles: chat, vision, code, reasoning, free
|
|
"""
|
|
categories = []
|
|
model_id = model.get("id", "").lower()
|
|
architecture = model.get("architecture", {})
|
|
|
|
# Tous les modèles supportent le chat de base
|
|
categories.append("chat")
|
|
|
|
# Détection Vision
|
|
modality = architecture.get("modality", "")
|
|
if "image" in modality or "vision" in model_id or "4o" in model_id:
|
|
categories.append("vision")
|
|
|
|
# Détection Code
|
|
code_keywords = ["code", "codestral", "deepseek-coder", "starcoder", "wizard-coder"]
|
|
if any(kw in model_id for kw in code_keywords):
|
|
categories.append("code")
|
|
|
|
# Détection Reasoning (modèles avec capacités de raisonnement avancé)
|
|
reasoning_keywords = ["o1", "o3", "reasoning", "think", "r1"]
|
|
if any(kw in model_id for kw in reasoning_keywords):
|
|
categories.append("reasoning")
|
|
|
|
# Modèles gratuits
|
|
pricing = model.get("pricing", {})
|
|
prompt_price = float(pricing.get("prompt", 1))
|
|
completion_price = float(pricing.get("completion", 1))
|
|
if prompt_price == 0 and completion_price == 0:
|
|
categories.append("free")
|
|
|
|
return categories
|
|
|
|
@staticmethod
|
|
def get_models_by_category(
|
|
api_key: str | None = None,
|
|
category: str | None = None
|
|
) -> list[dict[str, Any]]:
|
|
"""
|
|
Récupère les modèles filtrés par catégorie.
|
|
|
|
Args:
|
|
api_key: Clé API OpenRouter
|
|
category: Catégorie à filtrer (chat, vision, code, reasoning, free)
|
|
|
|
Returns:
|
|
Liste des modèles filtrés
|
|
"""
|
|
models = OpenRouterService.get_models(api_key)
|
|
|
|
if not category:
|
|
return models
|
|
|
|
return [m for m in models if category.lower() in m.get("categories", [])]
|
|
|
|
@staticmethod
|
|
def format_price(price_per_token: float) -> str:
|
|
"""
|
|
Formate le prix par token en format lisible.
|
|
|
|
Args:
|
|
price_per_token: Prix par token (généralement très petit)
|
|
|
|
Returns:
|
|
Prix formaté (ex: "$0.50 / 1M tokens")
|
|
"""
|
|
if price_per_token == 0:
|
|
return "Gratuit"
|
|
|
|
# Prix par million de tokens
|
|
price_per_million = price_per_token * 1_000_000
|
|
|
|
if price_per_million < 0.01:
|
|
return f"${price_per_million:.4f} / 1M"
|
|
elif price_per_million < 1:
|
|
return f"${price_per_million:.3f} / 1M"
|
|
else:
|
|
return f"${price_per_million:.2f} / 1M"
|