This commit is contained in:
1 parent
0256ffab42
commit
fd064dde43
1 file changed
+95
-10
@@ -259,7 +259,6 @@ def parse_txt_catalog(content: str):
|
|||||||
for line in lines:
|
for line in lines:
|
||||||
line_trim = line.strip()
|
line_trim = line.strip()
|
||||||
|
|
||||||
# Extraction de la date comme dans la macro
|
|
||||||
if "Généré le" in line_trim:
|
if "Généré le" in line_trim:
|
||||||
match = re.search(r"Généré le (\d{2}/\d{2}/\d{4}) à (\d{2}:\d{2}:\d{2})", line_trim)
|
match = re.search(r"Généré le (\d{2}/\d{2}/\d{4}) à (\d{2}:\d{2}:\d{2})", line_trim)
|
||||||
if match:
|
if match:
|
||||||
@@ -288,7 +287,7 @@ def ask_llm(session_id, full_id, msg):
|
|||||||
if provider == "openrouter":
|
if provider == "openrouter":
|
||||||
res = requests.post("https://openrouter.ai/api/v1/chat/completions", headers={"Authorization": f"Bearer {os.getenv('OPENROUTER_API_KEY')}"}, json={"model": actual_model, "messages": [{"role": "user", "content": msg}]})
|
res = requests.post("https://openrouter.ai/api/v1/chat/completions", headers={"Authorization": f"Bearer {os.getenv('OPENROUTER_API_KEY')}"}, json={"model": actual_model, "messages": [{"role": "user", "content": msg}]})
|
||||||
if res.status_code == 200: return res.json()["choices"][0]["message"]["content"]
|
if res.status_code == 200: return res.json()["choices"][0]["message"]["content"]
|
||||||
return f"⚠️ Erreur API ({provider}) : {res.text}"
|
return f"⚠️️ Erreur API ({provider}) : {res.text}"
|
||||||
elif provider == "groq":
|
elif provider == "groq":
|
||||||
res = requests.post("https://api.groq.com/openai/v1/chat/completions", headers={"Authorization": f"Bearer {os.getenv('GROQ_API_KEY')}"}, json={"model": actual_model, "messages": [{"role": "user", "content": msg}]})
|
res = requests.post("https://api.groq.com/openai/v1/chat/completions", headers={"Authorization": f"Bearer {os.getenv('GROQ_API_KEY')}"}, json={"model": actual_model, "messages": [{"role": "user", "content": msg}]})
|
||||||
if res.status_code == 200: return res.json()["choices"][0]["message"]["content"]
|
if res.status_code == 200: return res.json()["choices"][0]["message"]["content"]
|
||||||
@@ -410,7 +409,6 @@ async def post(msg: str, session_id: str, session, writer: str = "", worker1: st
|
|||||||
if not session.get("authenticated"): return RedirectResponse('/login')
|
if not session.get("authenticated"): return RedirectResponse('/login')
|
||||||
t_start = time.time()
|
t_start = time.time()
|
||||||
|
|
||||||
# 1. Préparation
|
|
||||||
files_context, noms_fichiers = "", []
|
files_context, noms_fichiers = "", []
|
||||||
if fichiers:
|
if fichiers:
|
||||||
for f in fichiers:
|
for f in fichiers:
|
||||||
@@ -422,7 +420,6 @@ async def post(msg: str, session_id: str, session, writer: str = "", worker1: st
|
|||||||
base_prompt = f"Fichiers fournis :\n{files_context}\n\nConsigne de l'utilisateur : {msg}" if files_context else msg
|
base_prompt = f"Fichiers fournis :\n{files_context}\n\nConsigne de l'utilisateur : {msg}" if files_context else msg
|
||||||
workers = [w for w in [worker1, worker2, worker3, worker4, worker5] if w]
|
workers = [w for w in [worker1, worker2, worker3, worker4, worker5] if w]
|
||||||
|
|
||||||
# 2. Phase Rédacteur (Optionnel)
|
|
||||||
t_w0 = time.time()
|
t_w0 = time.time()
|
||||||
if writer:
|
if writer:
|
||||||
SESSION_PROGRESS[session_id] = "🧠 Étape 1 : Le Rédacteur prépare l'invite..."
|
SESSION_PROGRESS[session_id] = "🧠 Étape 1 : Le Rédacteur prépare l'invite..."
|
||||||
@@ -431,13 +428,11 @@ async def post(msg: str, session_id: str, session, writer: str = "", worker1: st
|
|||||||
prompt_to_work = base_prompt
|
prompt_to_work = base_prompt
|
||||||
t_w1 = time.time()
|
t_w1 = time.time()
|
||||||
|
|
||||||
# 3. Phase Travailleurs (Parallèle)
|
|
||||||
SESSION_PROGRESS[session_id] = f"⚡ Étape 2 : Travail en cours ({len(workers)} IA en parallèle)..."
|
SESSION_PROGRESS[session_id] = f"⚡ Étape 2 : Travail en cours ({len(workers)} IA en parallèle)..."
|
||||||
tasks = [async_ask_llm(session_id, w, prompt_to_work) for w in workers]
|
tasks = [async_ask_llm(session_id, w, prompt_to_work) for w in workers]
|
||||||
results = await asyncio.gather(*tasks)
|
results = await asyncio.gather(*tasks)
|
||||||
t_w2 = time.time()
|
t_w2 = time.time()
|
||||||
|
|
||||||
# 4. Phase Concaténeur
|
|
||||||
if synthesizer and len(workers) > 0:
|
if synthesizer and len(workers) > 0:
|
||||||
SESSION_PROGRESS[session_id] = "🏗️ Étape 3 : Synthèse et assemblage final..."
|
SESSION_PROGRESS[session_id] = "🏗️ Étape 3 : Synthèse et assemblage final..."
|
||||||
synth_input = "Tu es l'Architecte Final. Voici la même tâche effectuée par plusieurs intelligences artificielles :\n\n"
|
synth_input = "Tu es l'Architecte Final. Voici la même tâche effectuée par plusieurs intelligences artificielles :\n\n"
|
||||||
@@ -449,7 +444,6 @@ async def post(msg: str, session_id: str, session, writer: str = "", worker1: st
|
|||||||
t_w3 = time.time()
|
t_w3 = time.time()
|
||||||
SESSION_PROGRESS[session_id] = "✅ Pipeline Terminé"
|
SESSION_PROGRESS[session_id] = "✅ Pipeline Terminé"
|
||||||
|
|
||||||
# Rapport Chrono
|
|
||||||
rapport = (
|
rapport = (
|
||||||
f"\n\n---\n⏱️ **Rapport de Performance (Pipeline Distant)**\n"
|
f"\n\n---\n⏱️ **Rapport de Performance (Pipeline Distant)**\n"
|
||||||
f"- **Rédacteur** : {t_w1 - t_w0:.2f} s\n"
|
f"- **Rédacteur** : {t_w1 - t_w0:.2f} s\n"
|
||||||
@@ -474,7 +468,7 @@ def get(session):
|
|||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
txt_content = f"=== CATALOGUE COMPLET DES MODÈLES (Généré le {now.strftime('%d/%m/%Y à %H:%M:%S')}) ===\n\n"
|
txt_content = f"=== CATALOGUE COMPLET DES MODÈLES (Généré le {now.strftime('%d/%m/%Y à %H:%M:%S')}) ===\n\n"
|
||||||
|
|
||||||
google_banned = False # Sécurité pour ne pas figer si Google bloque l'IP
|
google_banned = False
|
||||||
|
|
||||||
for m in MODELS_DATA:
|
for m in MODELS_DATA:
|
||||||
name = m.get('name', 'Inconnu')
|
name = m.get('name', 'Inconnu')
|
||||||
@@ -523,7 +517,6 @@ def get(session):
|
|||||||
|
|
||||||
if not desc_fr: desc_fr = "Aucune description fournie."
|
if not desc_fr: desc_fr = "Aucune description fournie."
|
||||||
|
|
||||||
# SÉCURITÉ DOCKER : Le texte est découpé proprement pour éviter la coupure (SyntaxError)
|
|
||||||
txt_content += (
|
txt_content += (
|
||||||
f"Nom : {name}\n"
|
f"Nom : {name}\n"
|
||||||
f"ID : {m_id}\n"
|
f"ID : {m_id}\n"
|
||||||
@@ -533,10 +526,102 @@ def get(session):
|
|||||||
f"{'-'*60}\n"
|
f"{'-'*60}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Forcer le téléchargement du fichier TXT
|
|
||||||
filename = f"modeles_ia_{datetime.now().strftime('%Y%m%d_%H%M')}.txt"
|
filename = f"modeles_ia_{datetime.now().strftime('%Y%m%d_%H%M')}.txt"
|
||||||
return Response(txt_content, media_type="text/plain", headers={"Content-Disposition": f"attachment; filename={filename}"})
|
return Response(txt_content, media_type="text/plain", headers={"Content-Disposition": f"attachment; filename={filename}"})
|
||||||
|
|
||||||
|
@rt('/import_catalog')
|
||||||
|
async def post(catalog_file: UploadFile, session):
|
||||||
|
if not session.get("authenticated"): return RedirectResponse('/login')
|
||||||
|
filename = catalog_file.filename.lower()
|
||||||
|
new_models = []
|
||||||
|
ext_date, ext_time = "Inconnue", ""
|
||||||
|
|
||||||
|
if filename.endswith(".txt"):
|
||||||
|
content = (await catalog_file.read()).decode('utf-8').replace('\r\n', '\n')
|
||||||
|
new_models, ext_date, ext_time = parse_txt_catalog(content)
|
||||||
|
|
||||||
|
elif filename.endswith((".xlsx", ".xlsm")):
|
||||||
|
try:
|
||||||
|
import openpyxl
|
||||||
|
from io import BytesIO
|
||||||
|
content = await catalog_file.read()
|
||||||
|
wb = openpyxl.load_workbook(BytesIO(content), data_only=True)
|
||||||
|
ws = wb.active
|
||||||
|
|
||||||
|
val_b2 = ws["B2"].value
|
||||||
|
val_c2 = ws["C2"].value
|
||||||
|
|
||||||
|
if val_b2 and hasattr(val_b2, 'strftime'): ext_date = val_b2.strftime('%d/%m/%Y')
|
||||||
|
else: ext_date = str(val_b2) if val_b2 else "Inconnue"
|
||||||
|
|
||||||
|
if val_c2 and hasattr(val_c2, 'strftime'): ext_time = val_c2.strftime('%H:%M:%S')
|
||||||
|
else: ext_time = str(val_c2) if val_c2 else ""
|
||||||
|
|
||||||
|
for row in ws.iter_rows(min_row=5, values_only=True):
|
||||||
|
if row[0] and row[1]:
|
||||||
|
new_models.append({
|
||||||
|
'name': str(row[0]).strip(),
|
||||||
|
'id': str(row[1]).strip(),
|
||||||
|
'is_free': ("Gratuit" in str(row[2])) if row[2] else False,
|
||||||
|
'provider': str(row[3]).strip() if row[3] else "Général",
|
||||||
|
'description': str(row[4]).strip() if row[4] else ""
|
||||||
|
})
|
||||||
|
except ImportError:
|
||||||
|
return Div("❌ Erreur : 'openpyxl' n'est pas installé sur le serveur. Veuillez uploader le fichier .txt.", style="color:red;")
|
||||||
|
except Exception as e:
|
||||||
|
return Div(f"❌ Erreur Excel : {str(e)}", style="color:red;")
|
||||||
|
else:
|
||||||
|
return Div("❌ Format invalide (.txt ou .xlsm attendu)", style="color:red;")
|
||||||
|
|
||||||
|
if new_models:
|
||||||
|
save_catalog(new_models)
|
||||||
|
date_str = f"Extrait le {ext_date} à {ext_time}." if ext_date != "Inconnue" else ""
|
||||||
|
return Div(f"✅ Catalogue mis à jour ({len(new_models)} modèles). {date_str}", Script("setTimeout(()=>window.location.reload(), 2000);"), style="color:#10b981;")
|
||||||
|
else:
|
||||||
|
return Div("❌ Aucun modèle valide trouvé.", style="color:red;")
|
||||||
|
|
||||||
|
# --- HISTORY SYSTEM ---
|
||||||
|
@rt('/session/new')
|
||||||
|
def get(session):
|
||||||
|
if not session.get("authenticated"): return RedirectResponse('/login')
|
||||||
|
sid = create_new_session()
|
||||||
|
return Div(), Input(type="hidden", name="session_id", value=sid, id="current-session-id", hx_swap_oob="true"), render_history_list()
|
||||||
|
|
||||||
|
@rt('/session/load/{sid}')
|
||||||
|
def get(sid: str, session):
|
||||||
|
if not session.get("authenticated"): return RedirectResponse('/login')
|
||||||
|
messages = load_session_data(sid)
|
||||||
|
bubbles = [Div(P("Vous", cls="msg-user"), Div(NotStr(f"{msg['user']}{msg.get('info', '')}"), cls="bubble-user"), P("AETHAS38", cls="msg-ia"), Div(msg['ia'], cls="bubble-ia")) for msg in messages]
|
||||||
|
return tuple(bubbles) + (Input(type="hidden", name="session_id", value=sid, id="current-session-id", hx_swap_oob="true"),)
|
||||||
|
|
||||||
|
@rt('/history/pin/{sid}')
|
||||||
|
def post_pin(sid: str, session):
|
||||||
|
if not session.get("authenticated"): return RedirectResponse('/login')
|
||||||
|
idx = load_index()
|
||||||
|
if sid in idx:
|
||||||
|
idx[sid]['pinned'] = not idx[sid].get('pinned', False)
|
||||||
|
save_index(idx)
|
||||||
|
return render_history_list()
|
||||||
|
|
||||||
|
@rt('/history/rename/{sid}')
|
||||||
|
def post_rename(sid: str, title: str, session):
|
||||||
|
if not session.get("authenticated"): return RedirectResponse('/login')
|
||||||
|
idx = load_index()
|
||||||
|
if sid in idx:
|
||||||
|
idx[sid]['title'] = title
|
||||||
|
save_index(idx)
|
||||||
|
return render_history_list()
|
||||||
|
|
||||||
|
@rt('/history/delete/{sid}')
|
||||||
|
def post_delete(sid: str, session):
|
||||||
|
if not session.get("authenticated"): return RedirectResponse('/login')
|
||||||
|
idx = load_index()
|
||||||
|
if sid in idx:
|
||||||
|
del idx[sid]
|
||||||
|
save_index(idx)
|
||||||
|
if os.path.exists(f"sessions/{sid}.json"): os.remove(f"sessions/{sid}.json")
|
||||||
|
return render_history_list()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import uvicorn
|
import uvicorn
|
||||||
# Démarre le serveur Uvicorn en mode production, accroché à Docker de manière stable
|
# Démarre le serveur Uvicorn en mode production, accroché à Docker de manière stable
|
||||||
|
|||||||
Reference in new issue
Block a user