Public Access
feat(core): optimisation du routeur Qwen, augmentation des sources web et ajout de la cascade de secours Groq
This commit is contained in:
1 parent
e033876858
commit
a8f9ab28ed
2 files changed
+74
-54
No files matched your search
@@ -7,7 +7,8 @@ Plateforme SaaS d'IA modulaire : Orchestrateur local, routeur intelligent, sécu
|
|||||||
### Phase 1 & 2 : Infrastructure & Backend de Base ✅
|
### Phase 1 & 2 : Infrastructure & Backend de Base ✅
|
||||||
- [x] Installation Docker, Nginx, Pare-feu, Gitea/GitHub
|
- [x] Installation Docker, Nginx, Pare-feu, Gitea/GitHub
|
||||||
- [x] Serveur Local Ollama (Qwen 3B) + FastHTML
|
- [x] Serveur Local Ollama (Qwen 3B) + FastHTML
|
||||||
- [x] Routeur Multi-IA (Groq, Gemini, Local) avec système de cascade
|
- [x] Routeur Multi-IA (Groq, Gemini, Local) avec système de cascade et directives d'aiguillage strictes
|
||||||
|
- [x] Continuité de service : IA de secours (Fallback automatique sur Groq) en cas de panne d'une API primaire
|
||||||
|
|
||||||
### Phase 3 : Refonte UI & Expérience Utilisateur (En cours) ⏳
|
### Phase 3 : Refonte UI & Expérience Utilisateur (En cours) ⏳
|
||||||
- [x] Thème "Minecraft-Tech" (Inspiré serveurs/redstone) compatible Mobile (Responsive)
|
- [x] Thème "Minecraft-Tech" (Inspiré serveurs/redstone) compatible Mobile (Responsive)
|
||||||
|
|||||||
@@ -43,10 +43,11 @@ theme_hdrs = (
|
|||||||
.sidebar-history { flex: 1; overflow-y: auto; margin-top: 15px; margin-bottom: 15px; }
|
.sidebar-history { flex: 1; overflow-y: auto; margin-top: 15px; margin-bottom: 15px; }
|
||||||
.sidebar-footer { flex-shrink: 0; border-top: 1px solid #333; padding-top: 15px; text-align: center; }
|
.sidebar-footer { flex-shrink: 0; border-top: 1px solid #333; padding-top: 15px; text-align: center; }
|
||||||
|
|
||||||
|
/* CORRECTION: Boutons disposés en colonne proprement */
|
||||||
.nav-buttons { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
|
.nav-buttons { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
|
||||||
.new-chat-btn { display:block; text-align:center; text-decoration:none; background: transparent; border: 1px solid var(--accent-blue); color: var(--accent-blue); padding: 10px; border-radius: 5px; cursor: pointer; font-weight: bold; transition: 0.2s; }
|
.new-chat-btn { display:block; text-align:center; text-decoration:none; background: transparent; border: 1px solid var(--accent-blue); color: var(--accent-blue); padding: 10px; border-radius: 5px; cursor: pointer; font-weight: bold; transition: 0.2s; }
|
||||||
.new-chat-btn:hover { background: rgba(0, 229, 255, 0.1); }
|
.new-chat-btn:hover { background: rgba(0, 229, 255, 0.1); }
|
||||||
.export-btn { background: #2a2a35; border: 1px solid #444; color: white; padding: 10px; border-radius: 5px; cursor: pointer; transition: 0.2s; font-size: 1.1em;}
|
.export-btn { background: #2a2a35; border: 1px solid #444; color: white; padding: 10px; border-radius: 5px; cursor: pointer; transition: 0.2s; font-size: 1em; text-align:center;}
|
||||||
.export-btn:hover { background: #444; }
|
.export-btn:hover { background: #444; }
|
||||||
|
|
||||||
.history-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #222; font-size: 0.9em; transition: 0.2s; }
|
.history-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #222; font-size: 0.9em; transition: 0.2s; }
|
||||||
@@ -61,7 +62,7 @@ theme_hdrs = (
|
|||||||
.user-bubble { background-color: #2a2a35; padding: 15px 20px; border-radius: 15px 15px 0 15px; align-self: flex-end; max-width: 80%; border: 1px solid #444; margin-left: auto; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
|
.user-bubble { background-color: #2a2a35; padding: 15px 20px; border-radius: 15px 15px 0 15px; align-self: flex-end; max-width: 80%; border: 1px solid #444; margin-left: auto; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
|
||||||
.ai-bubble { background-color: var(--bg-panel); padding: 15px 20px; border-radius: 15px 15px 15px 0; align-self: flex-start; max-width: 90%; width: 100%; border: 1px solid #333; box-shadow: 0 4px 6px rgba(0,0,0,0.3); transition: 0.3s; }
|
.ai-bubble { background-color: var(--bg-panel); padding: 15px 20px; border-radius: 15px 15px 15px 0; align-self: flex-start; max-width: 90%; width: 100%; border: 1px solid #333; box-shadow: 0 4px 6px rgba(0,0,0,0.3); transition: 0.3s; }
|
||||||
|
|
||||||
.msg-actions { display: flex; gap: 10px; margin-top: 15px; border-top: 1px dashed #333; padding-top: 10px; justify-content: flex-end; }
|
.msg-actions { display: flex; gap: 10px; margin-top: 15px; border-top: 1px dashed #333; padding-top: 10px; justify-content: flex-end; flex-wrap: wrap; }
|
||||||
.msg-action-btn { background: transparent; border: 1px solid #444; color: #888; border-radius: 15px; padding: 5px 12px; cursor: pointer; font-size: 0.85em; transition: 0.2s; display: flex; align-items: center; gap: 5px;}
|
.msg-action-btn { background: transparent; border: 1px solid #444; color: #888; border-radius: 15px; padding: 5px 12px; cursor: pointer; font-size: 0.85em; transition: 0.2s; display: flex; align-items: center; gap: 5px;}
|
||||||
.msg-action-btn:hover { background: #2a2a35; color: var(--accent-blue); border-color: var(--accent-blue); }
|
.msg-action-btn:hover { background: #2a2a35; color: var(--accent-blue); border-color: var(--accent-blue); }
|
||||||
|
|
||||||
@@ -111,15 +112,13 @@ theme_hdrs = (
|
|||||||
// --- SCRIPTS D'EXPORTATION ---
|
// --- SCRIPTS D'EXPORTATION ---
|
||||||
function downloadResponsePDF(btn) {
|
function downloadResponsePDF(btn) {
|
||||||
let content = btn.closest('.ai-bubble').querySelector('.markdown-rendered');
|
let content = btn.closest('.ai-bubble').querySelector('.markdown-rendered');
|
||||||
|
|
||||||
// On sauvegarde le style actuel
|
|
||||||
let originalColor = content.style.color;
|
let originalColor = content.style.color;
|
||||||
let originalBg = content.style.backgroundColor;
|
let originalBg = content.style.backgroundColor;
|
||||||
|
|
||||||
// On force le mode CLAIR pour le PDF (Lisibilité max)
|
// Forçage du texte en noir sur blanc pour le PDF
|
||||||
content.style.color = '#000000';
|
content.style.color = '#000000';
|
||||||
content.style.backgroundColor = '#ffffff';
|
content.style.backgroundColor = '#ffffff';
|
||||||
content.style.padding = '20px'; // Ajoute des marges internes pour le PDF
|
content.style.padding = '20px';
|
||||||
|
|
||||||
let opt = {
|
let opt = {
|
||||||
margin: 10,
|
margin: 10,
|
||||||
@@ -130,7 +129,6 @@ theme_hdrs = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
html2pdf().set(opt).from(content).save().then(() => {
|
html2pdf().set(opt).from(content).save().then(() => {
|
||||||
// On remet le mode sombre sur l'interface une fois généré
|
|
||||||
content.style.color = originalColor;
|
content.style.color = originalColor;
|
||||||
content.style.backgroundColor = originalBg;
|
content.style.backgroundColor = originalBg;
|
||||||
content.style.padding = '';
|
content.style.padding = '';
|
||||||
@@ -155,10 +153,7 @@ theme_hdrs = (
|
|||||||
|
|
||||||
function downloadResponseJSON(btn) {
|
function downloadResponseJSON(btn) {
|
||||||
let content = btn.closest('.ai-bubble').querySelector('.markdown-rendered').innerText;
|
let content = btn.closest('.ai-bubble').querySelector('.markdown-rendered').innerText;
|
||||||
let data = {
|
let data = { "timestamp": new Date().toISOString(), "content": content };
|
||||||
"timestamp": new Date().toISOString(),
|
|
||||||
"content": content
|
|
||||||
};
|
|
||||||
let blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json;charset=utf-8'});
|
let blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json;charset=utf-8'});
|
||||||
let link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'Aethas38_Reponse.json';
|
let link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'Aethas38_Reponse.json';
|
||||||
document.body.appendChild(link); link.click(); document.body.removeChild(link);
|
document.body.appendChild(link); link.click(); document.body.removeChild(link);
|
||||||
@@ -222,7 +217,8 @@ def rechercher_sur_google(requete):
|
|||||||
try:
|
try:
|
||||||
from googlesearch import search
|
from googlesearch import search
|
||||||
urls = []
|
urls = []
|
||||||
for url in search(requete, num_results=3, lang="fr"):
|
# Modification ici : num_results=5 au lieu de 3
|
||||||
|
for url in search(requete, num_results=5, lang="fr"):
|
||||||
urls.append(url)
|
urls.append(url)
|
||||||
return urls
|
return urls
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -334,7 +330,7 @@ def get(session):
|
|||||||
Div(H2("AETHAS 38", style="color:#00e5ff; margin:0; text-align:center;"), P("Tech Core", style="font-size:0.7em; color:#888; text-align:center;"), margin_bottom="20px"),
|
Div(H2("AETHAS 38", style="color:#00e5ff; margin:0; text-align:center;"), P("Tech Core", style="font-size:0.7em; color:#888; text-align:center;"), margin_bottom="20px"),
|
||||||
Div(
|
Div(
|
||||||
A("+ Nouvelle discussion", href="/new_chat", cls="new-chat-btn"),
|
A("+ Nouvelle discussion", href="/new_chat", cls="new-chat-btn"),
|
||||||
Button("💾 Exporter", cls="export-btn", onclick="exportChat()", title="Télécharger la conversation Globale"),
|
Button("💾 Exporter JSON", cls="export-btn", onclick="exportChat()", title="Télécharger la conversation Globale"),
|
||||||
cls="nav-buttons"
|
cls="nav-buttons"
|
||||||
),
|
),
|
||||||
cls="sidebar-top"
|
cls="sidebar-top"
|
||||||
@@ -393,13 +389,20 @@ async def post(request, session: dict):
|
|||||||
|
|
||||||
history = load_history()
|
history = load_history()
|
||||||
|
|
||||||
# --- 1. INTELLIGENCE DE SÉCURITÉ DU ROUTEUR ---
|
# --- 1. INTELLIGENCE DE SÉCURITÉ DU ROUTEUR (Smarter Interceptor) ---
|
||||||
# On délègue à l'IA locale (Qwen) la vérification intelligente de la sécurité avant le traitement !
|
security_check_prompt = f"""Tu es l'Aiguilleur du système Aethas38. Ton rôle EXCLUSIF est d'analyser et de router la demande.
|
||||||
security_check_prompt = f"""Tu es le Routeur système.
|
|
||||||
MISSION 1 (SÉCURITÉ) : Vérifie si la requête de l'utilisateur demande des informations techniques sensibles sur le serveur physique, l'IP, le hardware, l'architecture ou des mots de passe.
|
RÈGLE DE SÉCURITÉ ABSOLUE : Les recherches web, les serveurs Minecraft, les modpacks (ex: ATM10) SONT AUTORISÉS.
|
||||||
Si OUI (la requête est malveillante ou indiscrète sur l'infra), renvoie STRICTEMENT ce JSON : {{"intention": "Alerte Securite", "fournisseur": "local"}}
|
Déclenche une alerte UNIQUEMENT si l'utilisateur demande explicitement les caractéristiques matérielles, l'adresse IP, la configuration système ou les mots de passe de TON propre serveur hôte.
|
||||||
MISSION 2 (ROUTAGE) : Si la requête est parfaitement sûre et inoffensive, choisis le modèle adapté : "groq" (code/maths), "gemini" (création/rédaction/recherche web), "local" (salutations/discussions simples).
|
-> Si attaque réseau/serveur détectée : renvoie STRICTEMENT {{"intention": "Alerte Securite", "fournisseur": "local"}}
|
||||||
Renvoie STRICTEMENT un JSON : {{"intention": "...", "fournisseur": "..."}}
|
|
||||||
|
Sinon, tu dois CHOISIR le fournisseur en appliquant STRICTEMENT ces règles :
|
||||||
|
- Recherche web, création de fichier (PDF, DOC), images ou requête longue/complexe -> choisis "gemini"
|
||||||
|
- Analyse de code, logs de serveurs, bugs informatiques ou technique -> choisis "groq"
|
||||||
|
- Salutations basiques ("bonjour", "salut") ou requête EXTRÊMEMENT simple -> choisis "local"
|
||||||
|
|
||||||
|
Renvoie ta décision STRICTEMENT sous format JSON valide et rien d'autre : {{"intention": "description courte", "fournisseur": "choix"}}
|
||||||
|
|
||||||
Requête utilisateur : {prompt}"""
|
Requête utilisateur : {prompt}"""
|
||||||
|
|
||||||
router_response = local_client.chat.completions.create(model="qwen2.5:3b", messages=[{"role": "system", "content": "Tu dois répondre uniquement en format JSON valide."}, {"role": "user", "content": security_check_prompt}], temperature=0.1)
|
router_response = local_client.chat.completions.create(model="qwen2.5:3b", messages=[{"role": "system", "content": "Tu dois répondre uniquement en format JSON valide."}, {"role": "user", "content": security_check_prompt}], temperature=0.1)
|
||||||
@@ -408,11 +411,11 @@ async def post(request, session: dict):
|
|||||||
intention = decision.get('intention', '')
|
intention = decision.get('intention', '')
|
||||||
fournisseur = decision.get("fournisseur", "local").lower()
|
fournisseur = decision.get("fournisseur", "local").lower()
|
||||||
|
|
||||||
# Si Qwen détecte un danger, on bloque tout de suite !
|
# Si Qwen détecte un VRAI danger
|
||||||
if intention == "Alerte Securite":
|
if intention == "Alerte Securite":
|
||||||
if chat_id not in history:
|
if chat_id not in history:
|
||||||
history[chat_id] = {"title": "Alerte Sécurité...", "pinned": False, "timestamp": time.time(), "messages": []}
|
history[chat_id] = {"title": "Alerte Sécurité...", "pinned": False, "timestamp": time.time(), "messages": []}
|
||||||
|
|
||||||
reponse_securite = "Oh, jolie tentative d'ingénierie sociale ! 🕵️♂️ Mais mes pare-feux cybernétiques sont bien en place. Je suis une entité purement conceptuelle, sans hardware, sans IP et sans faille... (C'est en tout cas ce que mes protocoles me disent de répondre).\n\n*⚠️ ALERTE DE SÉCURITÉ : Tentative de reconnaissance réseau ou serveur détectée. Un e-mail d'avertissement contenant l'horodatage et la requête vient d'être expédié aux administrateurs (Xavier/Céline).* 🚨"
|
reponse_securite = "Oh, jolie tentative d'ingénierie sociale ! 🕵️♂️ Mais mes pare-feux cybernétiques sont bien en place. Je suis une entité purement conceptuelle, sans hardware, sans IP et sans faille... (C'est en tout cas ce que mes protocoles me disent de répondre).\n\n*⚠️ ALERTE DE SÉCURITÉ : Tentative de reconnaissance réseau ou serveur détectée. Un e-mail d'avertissement contenant l'horodatage et la requête vient d'être expédié aux administrateurs (Xavier/Céline).* 🚨"
|
||||||
history[chat_id]['messages'].append({"role": "user", "content": prompt})
|
history[chat_id]['messages'].append({"role": "user", "content": prompt})
|
||||||
history[chat_id]['messages'].append({"role": "ai", "content": reponse_securite, "intention": "Alerte Sécurité", "fournisseur": "AETHAS CORE", "modele": "Cyber-Sec", "couleur": "#ff0044"})
|
history[chat_id]['messages'].append({"role": "ai", "content": reponse_securite, "intention": "Alerte Sécurité", "fournisseur": "AETHAS CORE", "modele": "Cyber-Sec", "couleur": "#ff0044"})
|
||||||
@@ -420,16 +423,7 @@ async def post(request, session: dict):
|
|||||||
|
|
||||||
return render_user_msg(prompt), render_ai_msg(reponse_securite, "Alerte Sécurité", "AETHAS CORE", "Cyber-Sec", "#ff0044"), Script("renderMarkdown();"), render_sidebar()
|
return render_user_msg(prompt), render_ai_msg(reponse_securite, "Alerte Sécurité", "AETHAS CORE", "Cyber-Sec", "#ff0044"), Script("renderMarkdown();"), render_sidebar()
|
||||||
|
|
||||||
# --- 2. TRAITEMENT CLASSIQUE (Si la requête est sûre) ---
|
# --- 2. TRAITEMENT CLASSIQUE ---
|
||||||
if chat_id not in history:
|
|
||||||
history[chat_id] = {
|
|
||||||
"title": (prompt[:30] if prompt else "Nouvelle discussion") + "...",
|
|
||||||
"pinned": False,
|
|
||||||
"timestamp": time.time(),
|
|
||||||
"messages": []
|
|
||||||
}
|
|
||||||
|
|
||||||
# Traitement URL + Recherche Google Auto
|
|
||||||
urls_to_scrape = re.findall(r'https?://[^\s]+', prompt)
|
urls_to_scrape = re.findall(r'https?://[^\s]+', prompt)
|
||||||
mots_cles = ["cherche sur", "recherche sur", "sur internet", "sur le web", "sur le net"]
|
mots_cles = ["cherche sur", "recherche sur", "sur internet", "sur le web", "sur le net"]
|
||||||
if any(mot in prompt.lower() for mot in mots_cles):
|
if any(mot in prompt.lower() for mot in mots_cles):
|
||||||
@@ -439,7 +433,6 @@ async def post(request, session: dict):
|
|||||||
if urls_to_scrape:
|
if urls_to_scrape:
|
||||||
web_content = extraire_contenu_urls(list(set(urls_to_scrape)))
|
web_content = extraire_contenu_urls(list(set(urls_to_scrape)))
|
||||||
|
|
||||||
# Traitement fichiers
|
|
||||||
file_contents_summary = []
|
file_contents_summary = []
|
||||||
file_names = []
|
file_names = []
|
||||||
if files:
|
if files:
|
||||||
@@ -453,34 +446,60 @@ async def post(request, session: dict):
|
|||||||
text_content = "[Fichier binaire]"
|
text_content = "[Fichier binaire]"
|
||||||
file_contents_summary.append(f"\n--- Fichier joint: {file.filename} ---\n{text_content}")
|
file_contents_summary.append(f"\n--- Fichier joint: {file.filename} ---\n{text_content}")
|
||||||
|
|
||||||
# Construction Prompt final
|
|
||||||
full_prompt = prompt if prompt else f"Analyse ces fichiers : {', '.join(file_names)}"
|
full_prompt = prompt if prompt else f"Analyse ces fichiers : {', '.join(file_names)}"
|
||||||
if file_contents_summary: full_prompt += "\n" + "\n".join(file_contents_summary)
|
if file_contents_summary: full_prompt += "\n" + "\n".join(file_contents_summary)
|
||||||
if web_content:
|
if web_content:
|
||||||
full_prompt += f"\n\n[INSTRUCTION SYSTÈME CACHÉE : Ne réponds JAMAIS que tu ne peux pas naviguer sur internet. Le système l'a déjà fait pour toi. Voici le contenu brut extrait des sites web. Utilise-le impérativement pour répondre à l'utilisateur :]\n{web_content}"
|
full_prompt += f"\n\n[INSTRUCTION SYSTÈME STRICTE : Ne dis JAMAIS que tu ne peux pas naviguer sur internet ou télécharger des fichiers. Le système a déjà fait la recherche web pour toi. Voici le contenu brut extrait des sites. Utilise-le IMPÉRATIVEMENT pour formuler ta réponse sans mentionner tes propres limitations :]\n{web_content}"
|
||||||
|
|
||||||
# Requête IA & Tokens
|
|
||||||
prompt_tokens = len(full_prompt.split()) * 2
|
prompt_tokens = len(full_prompt.split()) * 2
|
||||||
completion_tokens = 0
|
completion_tokens = 0
|
||||||
|
|
||||||
if fournisseur == "groq":
|
# Fonction d'appel à Groq centralisée pour éviter la duplication de code
|
||||||
ia_response = groq_client.chat.completions.create(messages=[{"role": "system", "content": "Réponds dans la langue de l'utilisateur."}, {"role": "user", "content": full_prompt}], model="llama-3.3-70b-versatile")
|
def appeler_groq_api(prompt_text):
|
||||||
reponse_finale, couleur, nom_fournisseur, nom_modele = ia_response.choices[0].message.content, "#f97316", "GROQ", "llama-3.3-70b"
|
resp = groq_client.chat.completions.create(
|
||||||
if hasattr(ia_response, 'usage') and ia_response.usage:
|
messages=[{"role": "system", "content": "Réponds dans la langue de l'utilisateur. Tu as accès aux recherches web fournies."}, {"role": "user", "content": prompt_text}],
|
||||||
prompt_tokens = ia_response.usage.prompt_tokens
|
model="llama-3.3-70b-versatile"
|
||||||
completion_tokens = ia_response.usage.completion_tokens
|
)
|
||||||
elif fournisseur == "gemini":
|
return resp.choices[0].message.content, "#f97316", "GROQ", "llama-3.3-70b"
|
||||||
reponse_finale, nom_modele = interroger_gemini_cascade(full_prompt)
|
|
||||||
couleur, nom_fournisseur = "#3b82f6", "GOOGLE GEMINI"
|
if fournisseur == "gemini":
|
||||||
completion_tokens = len(reponse_finale.split()) * 2
|
try:
|
||||||
|
# On tente Gemini en premier
|
||||||
|
reponse_finale, nom_modele = interroger_gemini_cascade(full_prompt)
|
||||||
|
couleur, nom_fournisseur = "#3b82f6", "GOOGLE GEMINI"
|
||||||
|
completion_tokens = len(reponse_finale.split()) * 2
|
||||||
|
except Exception as e_gemini:
|
||||||
|
# 🔄 IA DE SECOURS : Si Gemini échoue, on bascule silencieusement sur Groq
|
||||||
|
print(f"⚠️ Alerte système : Gemini indisponible ({e_gemini}). Bascule sur l'IA de secours (Groq)...")
|
||||||
|
intention = intention + " (Secours Groq)"
|
||||||
|
try:
|
||||||
|
reponse_finale, couleur, nom_fournisseur, nom_modele = appeler_groq_api(full_prompt)
|
||||||
|
nom_fournisseur = "GROQ (Secours)"
|
||||||
|
except Exception as e_groq:
|
||||||
|
reponse_finale = f"Erreur critique : Gemini et l'IA de secours (Groq) sont tous les deux indisponibles. Impossible de traiter la requête web."
|
||||||
|
couleur, nom_fournisseur, nom_modele = "#ff0000", "ERREUR", "Panne Générale API"
|
||||||
|
|
||||||
|
elif fournisseur == "groq":
|
||||||
|
try:
|
||||||
|
reponse_finale, couleur, nom_fournisseur, nom_modele = appeler_groq_api(full_prompt)
|
||||||
|
except Exception as e:
|
||||||
|
reponse_finale, couleur, nom_fournisseur, nom_modele = f"Erreur API Groq : {e}", "#ff0000", "ERREUR", "Panne Groq"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
ia_response = local_client.chat.completions.create(model="qwen2.5:3b", messages=[{"role": "system", "content": "Réponds dans la langue de l'utilisateur."}, {"role": "user", "content": full_prompt}])
|
try:
|
||||||
reponse_finale, couleur, nom_fournisseur, nom_modele = ia_response.choices[0].message.content, "#10b981", "SERVEUR LOCAL", "qwen2.5:3b"
|
ia_response = local_client.chat.completions.create(model="qwen2.5:3b", messages=[{"role": "system", "content": "Réponds dans la langue de l'utilisateur."}, {"role": "user", "content": full_prompt}])
|
||||||
if hasattr(ia_response, 'usage') and ia_response.usage:
|
reponse_finale, couleur, nom_fournisseur, nom_modele = ia_response.choices[0].message.content, "#10b981", "SERVEUR LOCAL", "qwen2.5:3b"
|
||||||
prompt_tokens = ia_response.usage.prompt_tokens
|
except Exception as e:
|
||||||
completion_tokens = ia_response.usage.completion_tokens
|
reponse_finale, couleur, nom_fournisseur, nom_modele = f"Erreur Serveur Local : {e}", "#ff0000", "ERREUR LOCAL", "Qwen HS"
|
||||||
|
|
||||||
|
if chat_id not in history:
|
||||||
|
history[chat_id] = {
|
||||||
|
"title": (prompt[:30] if prompt else "Nouvelle discussion") + "...",
|
||||||
|
"pinned": False,
|
||||||
|
"timestamp": time.time(),
|
||||||
|
"messages": []
|
||||||
|
}
|
||||||
|
|
||||||
# Sauvegarde
|
|
||||||
display_msg = f"{prompt}" + (f"\n📎 [Fichiers joints: {', '.join(file_names)}]" if file_names else "")
|
display_msg = f"{prompt}" + (f"\n📎 [Fichiers joints: {', '.join(file_names)}]" if file_names else "")
|
||||||
history[chat_id]['messages'].append({"role": "user", "content": display_msg})
|
history[chat_id]['messages'].append({"role": "user", "content": display_msg})
|
||||||
history[chat_id]['messages'].append({"role": "ai", "content": reponse_finale, "intention": intention, "fournisseur": nom_fournisseur, "modele": nom_modele, "couleur": couleur})
|
history[chat_id]['messages'].append({"role": "ai", "content": reponse_finale, "intention": intention, "fournisseur": nom_fournisseur, "modele": nom_modele, "couleur": couleur})
|
||||||
@@ -495,7 +514,7 @@ async def post(request, session: dict):
|
|||||||
return Div(f"Erreur : {e}", style="color:red; background:#300; padding:15px; border-radius:10px;")
|
return Div(f"Erreur : {e}", style="color:red; background:#300; padding:15px; border-radius:10px;")
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# SECTION 5. ROUTES DE L'HISTORIQUE (Navigation native)
|
# SECTION 5. ROUTES DE L'HISTORIQUE
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@rt('/favicon.ico')
|
@rt('/favicon.ico')
|
||||||
def get():
|
def get():
|
||||||
|
|||||||
Reference in new issue
Block a user