feat: passage des prompt en traduction anglaise. Mise en place du téléchargement d'un *.md des modèles. Traduction de la description des modèles en Français.
Build and Push Docker Image / build-and-push (push) Successful in 43s
Build and Push Docker Image / build-and-push (push) Successful in 43s
This commit is contained in:
1 parent
f840561123
commit
ecd9854527
2 files changed
+64
-17
No files matched your search
+36
-11
@@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import httpx
|
import httpx
|
||||||
|
import urllib.parse
|
||||||
from openai import AsyncOpenAI
|
from openai import AsyncOpenAI
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -14,6 +15,20 @@ def determine_domain(model_id: str) -> str:
|
|||||||
if "audio" in mid or "whisper" in mid: return "Audio"
|
if "audio" in mid or "whisper" in mid: return "Audio"
|
||||||
return "Texte Polyvalent"
|
return "Texte Polyvalent"
|
||||||
|
|
||||||
|
async def translate_en_to_fr(client: httpx.AsyncClient, text: str) -> str:
|
||||||
|
"""Traduit automatiquement l'anglais vers le français via l'API publique Google Translate."""
|
||||||
|
if not text: return "Aucune description fournie."
|
||||||
|
try:
|
||||||
|
short_text = text[:300].strip() # Limite stricte pour l'API gratuite
|
||||||
|
url = f"https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=fr&dt=t&q={urllib.parse.quote(short_text)}"
|
||||||
|
resp = await client.get(url, timeout=4.0)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
translated = "".join([s[0] for s in resp.json()[0]])
|
||||||
|
return translated + ("..." if len(text) > 300 else "")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return text[:200] + "..."
|
||||||
|
|
||||||
async def sync_finances(db: Session, settings: SystemSettings):
|
async def sync_finances(db: Session, settings: SystemSettings):
|
||||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
if settings.openrouter_management_key or settings.openrouter_api_key:
|
if settings.openrouter_management_key or settings.openrouter_api_key:
|
||||||
@@ -75,8 +90,8 @@ async def sync_providers_models(db: Session, settings: SystemSettings, sync_type
|
|||||||
pp = float(pricing.get("prompt") or 0.0) * 1000000
|
pp = float(pricing.get("prompt") or 0.0) * 1000000
|
||||||
pc = float(pricing.get("completion") or 0.0) * 1000000
|
pc = float(pricing.get("completion") or 0.0) * 1000000
|
||||||
is_free = (pp == 0.0 and pc == 0.0)
|
is_free = (pp == 0.0 and pc == 0.0)
|
||||||
desc = item.get("description", "Modèle OpenRouter.")[:200] + "..."
|
desc_en = item.get("description", "Generic AI Model.")
|
||||||
models_to_process[m_id] = {"provider": "openrouter", "name": item.get("name", "Inconnu"), "desc": desc, "domain": determine_domain(m_id), "is_free": is_free, "ctx": item.get("context_length", 0), "pp": pp, "pc": pc}
|
models_to_process[m_id] = {"provider": "openrouter", "name": item.get("name", "Inconnu"), "desc_en": desc_en, "domain": determine_domain(m_id), "is_free": is_free, "ctx": item.get("context_length", 0), "pp": pp, "pc": pc}
|
||||||
except: pass
|
except: pass
|
||||||
except Exception as e: print(f"Erreur OR Models: {e}")
|
except Exception as e: print(f"Erreur OR Models: {e}")
|
||||||
|
|
||||||
@@ -86,7 +101,7 @@ async def sync_providers_models(db: Session, settings: SystemSettings, sync_type
|
|||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
for item in resp.json().get("data", []):
|
for item in resp.json().get("data", []):
|
||||||
m_id = item["id"]
|
m_id = item["id"]
|
||||||
models_to_process[m_id] = {"provider": "groq", "name": m_id.capitalize(), "desc": "Modèle rapide LPU Groq.", "domain": determine_domain(m_id), "is_free": True, "ctx": 8192, "pp": 0.0, "pc": 0.0}
|
models_to_process[m_id] = {"provider": "groq", "name": m_id.capitalize(), "desc": "Modèle très rapide hébergé sur LPU Groq.", "domain": determine_domain(m_id), "is_free": True, "ctx": 8192, "pp": 0.0, "pc": 0.0}
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
if settings.deepseek_api_key:
|
if settings.deepseek_api_key:
|
||||||
@@ -95,7 +110,7 @@ async def sync_providers_models(db: Session, settings: SystemSettings, sync_type
|
|||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
for item in resp.json().get("data", []):
|
for item in resp.json().get("data", []):
|
||||||
m_id = item["id"]
|
m_id = item["id"]
|
||||||
models_to_process[m_id] = {"provider": "deepseek", "name": m_id.capitalize(), "desc": "Modèle officiel DeepSeek.", "domain": determine_domain(m_id), "is_free": False, "ctx": 64000, "pp": 0.14, "pc": 0.28}
|
models_to_process[m_id] = {"provider": "deepseek", "name": m_id.capitalize(), "desc": "Modèle officiel du fournisseur DeepSeek.", "domain": determine_domain(m_id), "is_free": False, "ctx": 64000, "pp": 0.14, "pc": 0.28}
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
if settings.mistral_api_key:
|
if settings.mistral_api_key:
|
||||||
@@ -104,7 +119,7 @@ async def sync_providers_models(db: Session, settings: SystemSettings, sync_type
|
|||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
for item in resp.json().get("data", []):
|
for item in resp.json().get("data", []):
|
||||||
m_id = item["id"]
|
m_id = item["id"]
|
||||||
models_to_process[m_id] = {"provider": "mistral", "name": m_id.capitalize(), "desc": "Modèle officiel Mistral AI.", "domain": determine_domain(m_id), "is_free": False, "ctx": 32000, "pp": 0.2, "pc": 0.6}
|
models_to_process[m_id] = {"provider": "mistral", "name": m_id.capitalize(), "desc": "Modèle officiel développé par Mistral AI.", "domain": determine_domain(m_id), "is_free": False, "ctx": 32000, "pp": 0.2, "pc": 0.6}
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
if settings.gemini_api_key:
|
if settings.gemini_api_key:
|
||||||
@@ -113,8 +128,7 @@ async def sync_providers_models(db: Session, settings: SystemSettings, sync_type
|
|||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
for item in resp.json().get("models", []):
|
for item in resp.json().get("models", []):
|
||||||
m_id = item["name"].replace("models/", "")
|
m_id = item["name"].replace("models/", "")
|
||||||
desc = item.get("description", "Modèle Google Gemini.")[:200] + "..."
|
models_to_process[m_id] = {"provider": "gemini", "name": item.get("displayName", m_id), "desc": "Modèle natif de l'écosystème Google Gemini.", "domain": determine_domain(m_id), "is_free": True, "ctx": item.get("inputTokenLimit", 32000), "pp": 0.0, "pc": 0.0}
|
||||||
models_to_process[m_id] = {"provider": "gemini", "name": item.get("displayName", m_id), "desc": desc, "domain": determine_domain(m_id), "is_free": True, "ctx": item.get("inputTokenLimit", 32000), "pp": 0.0, "pc": 0.0}
|
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
if settings.cloudflare_account_id and settings.cloudflare_api_token:
|
if settings.cloudflare_account_id and settings.cloudflare_api_token:
|
||||||
@@ -124,15 +138,26 @@ async def sync_providers_models(db: Session, settings: SystemSettings, sync_type
|
|||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
for item in resp.json().get("result", []):
|
for item in resp.json().get("result", []):
|
||||||
m_id = item.get("name")
|
m_id = item.get("name")
|
||||||
desc = item.get("description", "Modèle Cloudflare Workers AI.")[:200] + "..."
|
models_to_process[m_id] = {"provider": "cloudflare", "name": m_id.split("/")[-1], "desc": "Modèle Serverless Cloudflare Workers AI.", "domain": determine_domain(m_id), "is_free": True, "ctx": 4096, "pp": 0.0, "pc": 0.0}
|
||||||
models_to_process[m_id] = {"provider": "cloudflare", "name": m_id.split("/")[-1], "desc": desc, "domain": determine_domain(m_id), "is_free": True, "ctx": 4096, "pp": 0.0, "pc": 0.0}
|
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
for m_id, data in models_to_process.items():
|
# Phase de traduction asynchrone pour les modèles OpenRouter (les autres sont déjà en français natif)
|
||||||
|
sem = asyncio.Semaphore(15)
|
||||||
|
async def process_and_translate(m_id, data, client_session):
|
||||||
|
async with sem:
|
||||||
|
if "desc_en" in data:
|
||||||
|
data["desc"] = await translate_en_to_fr(client_session, data["desc_en"])
|
||||||
|
return m_id, data
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client_trans:
|
||||||
|
tasks = [process_and_translate(m_id, data, client_trans) for m_id, data in models_to_process.items()]
|
||||||
|
translated_results = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
for m_id, data in translated_results:
|
||||||
try:
|
try:
|
||||||
existing = db.query(AIModel).filter(AIModel.model_id == m_id).first()
|
existing = db.query(AIModel).filter(AIModel.model_id == m_id).first()
|
||||||
if existing:
|
if existing:
|
||||||
existing.pricing_prompt = data["pp"]; existing.pricing_completion = data["pc"]; existing.is_free = data["is_free"]; existing.last_updated = datetime.now(timezone.utc)
|
existing.pricing_prompt = data["pp"]; existing.pricing_completion = data["pc"]; existing.is_free = data["is_free"]; existing.description_fr = data["desc"]; existing.last_updated = datetime.now(timezone.utc)
|
||||||
else:
|
else:
|
||||||
db.add(AIModel(provider=data["provider"], model_id=m_id, name=data["name"], description_fr=data["desc"], domain=data["domain"], is_free=data["is_free"], context_length=data["ctx"], pricing_prompt=data["pp"], pricing_completion=data["pc"]))
|
db.add(AIModel(provider=data["provider"], model_id=m_id, name=data["name"], description_fr=data["desc"], domain=data["domain"], is_free=data["is_free"], context_length=data["ctx"], pricing_prompt=data["pp"], pricing_completion=data["pc"]))
|
||||||
added += 1
|
added += 1
|
||||||
|
|||||||
+28
-6
@@ -296,7 +296,7 @@
|
|||||||
<div class="p-4 md:p-6 w-full max-w-7xl mx-auto space-y-2 flex-shrink-0">
|
<div class="p-4 md:p-6 w-full max-w-7xl mx-auto space-y-2 flex-shrink-0">
|
||||||
<div class="grid grid-cols-9 border border-black bg-gray-900 rounded-lg overflow-hidden text-center text-xs font-bold shadow-lg">
|
<div class="grid grid-cols-9 border border-black bg-gray-900 rounded-lg overflow-hidden text-center text-xs font-bold shadow-lg">
|
||||||
|
|
||||||
<!-- 1. PAYANT / GRATUIT -->
|
<!-- 1. PAYANT / GRATUIT (Filtre par colonne local sans impacter workers) -->
|
||||||
<div class="border-r border-black p-1.5 bg-gray-800 flex flex-col justify-center">
|
<div class="border-r border-black p-1.5 bg-gray-800 flex flex-col justify-center">
|
||||||
<label class="block text-[9px] text-gray-400 mb-1">PRIX</label>
|
<label class="block text-[9px] text-gray-400 mb-1">PRIX</label>
|
||||||
<select v-model="colPricing" class="w-full bg-aethas-dark border border-gray-700 rounded text-gray-200 text-[10px] p-1 outline-none">
|
<select v-model="colPricing" class="w-full bg-aethas-dark border border-gray-700 rounded text-gray-200 text-[10px] p-1 outline-none">
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 2. TYPE (DOMAINE) -->
|
<!-- 2. TYPE (DOMAINE) (Filtre par colonne local sans impacter workers) -->
|
||||||
<div class="border-r border-black p-1.5 bg-gray-800 flex flex-col justify-center">
|
<div class="border-r border-black p-1.5 bg-gray-800 flex flex-col justify-center">
|
||||||
<label class="block text-[9px] text-gray-400 mb-1">TYPE</label>
|
<label class="block text-[9px] text-gray-400 mb-1">TYPE</label>
|
||||||
<select v-model="colDomain" class="w-full bg-aethas-dark border border-gray-700 rounded text-gray-200 text-[10px] p-1 outline-none">
|
<select v-model="colDomain" class="w-full bg-aethas-dark border border-gray-700 rounded text-gray-200 text-[10px] p-1 outline-none">
|
||||||
@@ -450,7 +450,7 @@
|
|||||||
const selectedPrompter = ref("gemini-3.5-flash-lite");
|
const selectedPrompter = ref("gemini-3.5-flash-lite");
|
||||||
const selectedConcatenator = ref("gemini-3.5-flash-lite");
|
const selectedConcatenator = ref("gemini-3.5-flash-lite");
|
||||||
|
|
||||||
const attachedFiles = ref([]); // Liste de max 10 fichiers
|
const attachedFiles = ref([]);
|
||||||
|
|
||||||
const pinnedProjects = computed(() => projects.value.filter(p => p.is_pinned));
|
const pinnedProjects = computed(() => projects.value.filter(p => p.is_pinned));
|
||||||
const recentProjects = computed(() => projects.value.filter(p => !p.is_pinned));
|
const recentProjects = computed(() => projects.value.filter(p => !p.is_pinned));
|
||||||
@@ -501,7 +501,31 @@
|
|||||||
if(res.ok) {
|
if(res.ok) {
|
||||||
await fetchModelsInfo();
|
await fetchModelsInfo();
|
||||||
await fetchFinances();
|
await fetchFinances();
|
||||||
alert("Synchronisation réussie !");
|
|
||||||
|
// GENERATION DU FICHIER MARKDOWN A L'EXTRACTION
|
||||||
|
let mdContent = `# Extraction des Modèles IA - AETHAS38\n\n`;
|
||||||
|
const d = new Date();
|
||||||
|
const dateStr = d.getFullYear() + String(d.getMonth()+1).padStart(2, '0') + String(d.getDate()).padStart(2, '0');
|
||||||
|
mdContent += `**Date d'extraction :** ${d.toLocaleDateString('fr-FR')} à ${d.toLocaleTimeString('fr-FR')}\n\n`;
|
||||||
|
|
||||||
|
const providers = [...new Set(modelInfo.models.map(m => m.provider))];
|
||||||
|
providers.forEach(prov => {
|
||||||
|
mdContent += `## Fournisseur : ${prov.toUpperCase()}\n\n`;
|
||||||
|
const provModels = modelInfo.models.filter(m => m.provider === prov);
|
||||||
|
provModels.forEach(m => {
|
||||||
|
const priceInfo = m.is_free ? "**GRATUIT**" : `In: $${m.pricing_prompt.toFixed(2)} / Out: $${m.pricing_completion.toFixed(2)}`;
|
||||||
|
const ctxInfo = `${(m.context_length / 1000).toFixed(0)}k`;
|
||||||
|
mdContent += `- **${m.name || m.model_id}** (\`${m.model_id}\`)\n`;
|
||||||
|
mdContent += ` - *Domaine :* ${m.domain}\n`;
|
||||||
|
mdContent += ` - *Prix (1M tokens) :* ${priceInfo}\n`;
|
||||||
|
mdContent += ` - *Contexte :* ${ctxInfo}\n`;
|
||||||
|
mdContent += ` - *Description :* ${m.description_fr.replace(/\n/g, ' ')}\n\n`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const filename = `${dateStr}-extraction-modèles.md`;
|
||||||
|
downloadFile(mdContent, filename);
|
||||||
|
alert("Synchronisation réussie ! Le fichier markdown a été généré et téléchargé.");
|
||||||
} else {
|
} else {
|
||||||
let errText = await res.text();
|
let errText = await res.text();
|
||||||
try { const errObj = JSON.parse(errText); errText = errObj.detail || errText; } catch(e){}
|
try { const errObj = JSON.parse(errText); errText = errObj.detail || errText; } catch(e){}
|
||||||
@@ -589,7 +613,6 @@
|
|||||||
isAiThinking.value = true;
|
isAiThinking.value = true;
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
|
|
||||||
// Préparation du payload avec copie des fichiers avant destruction
|
|
||||||
const payloadFiles = [...attachedFiles.value];
|
const payloadFiles = [...attachedFiles.value];
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -603,7 +626,6 @@
|
|||||||
files: payloadFiles
|
files: payloadFiles
|
||||||
};
|
};
|
||||||
|
|
||||||
// DESTRUCTION AUTOMATIQUE DES FICHIERS JOINTS APRÈS ENVOI
|
|
||||||
attachedFiles.value = [];
|
attachedFiles.value = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in new issue
Block a user