From 9288e849af070a5a3f9322e378c285175a9d9948 Mon Sep 17 00:00:00 2001 From: Xavier Date: Wed, 7 Oct 2026 11:00:49 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20int=C3=A9gration=20du=20moteur=20d'IA?= =?UTF-8?q?=20(ai.py)=20et=20g=C3=A9n=C3=A9ration=20de=20la=20premi=C3=A8r?= =?UTF-8?q?e=20r=C3=A9ponse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/ai.py | 53 +++++++++++++++++++++++++++++++++++++++++ backend/main.py | 33 ++++++++++++++++++++++++- frontend/dashboard.html | 19 ++++++++------- requirements.txt | 3 ++- 4 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 backend/ai.py diff --git a/backend/ai.py b/backend/ai.py new file mode 100644 index 0000000..96a851f --- /dev/null +++ b/backend/ai.py @@ -0,0 +1,53 @@ +from openai import OpenAI +from fastapi import HTTPException +from .models import SystemSettings + +def get_ai_response(messages: list, settings: SystemSettings) -> str: + """ + Route la conversation vers le premier fournisseur IA disponible configuré par l'admin. + Prend l'historique des messages et retourne le texte généré. + """ + if not settings: + raise HTTPException(status_code=500, detail="Configuration système introuvable.") + + # Formatage de l'historique pour l'API (OpenAI compatible) + formatted_messages = [{"role": msg.role, "content": msg.content} for msg in messages] + + # 1. Test OpenRouter (Idéal car donne accès à tout) + if settings.openrouter_api_key: + client = OpenAI( + base_url="https://openrouter.ai/api/v1", + api_key=settings.openrouter_api_key, + ) + model = "google/gemini-pro" # Fallback par défaut via OpenRouter + + # 2. Test DeepSeek + elif settings.deepseek_api_key: + client = OpenAI( + base_url="https://api.deepseek.com/v1", + api_key=settings.deepseek_api_key, + ) + model = "deepseek-chat" + + # 3. Test Groq + elif settings.groq_api_key: + client = OpenAI( + base_url="https://api.groq.com/openai/v1", + api_key=settings.groq_api_key, + ) + model = "llama3-8b-8192" + + else: + raise HTTPException(status_code=400, detail="Aucun moteur IA (OpenRouter, DeepSeek, Groq) n'est configuré avec une clé API.") + + try: + response = client.chat.completions.create( + model=model, + messages=formatted_messages, + # Identifiant unique de l'application pour OpenRouter + extra_headers={"HTTP-Referer": "https://aethas38.duckdns.org", "X-Title": "AETHAS38 Multi-IA"} if settings.openrouter_api_key else {} + ) + return response.choices[0].message.content + except Exception as e: + print(f"Erreur API IA : {str(e)}") + raise HTTPException(status_code=502, detail="Erreur de communication avec le fournisseur IA.") \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 9fc8c7f..6366b10 100644 --- a/backend/main.py +++ b/backend/main.py @@ -11,6 +11,8 @@ from .models import User, Project, Message from .auth import get_password_hash, generate_totp_secret, get_totp_uri, verify_password, verify_totp, create_access_token, verify_token from .schemas import AdminCreate, LoginRequest, ProjectCreate, ProjectResponse, ProjectRename, MessageCreate, MessageResponse from .models import User, Project, Message, SystemSettings +from .ai import get_ai_response +from .models import User, Project, Message, SystemSettings # Création des tables dans la base de données Base.metadata.create_all(bind=engine) @@ -245,4 +247,33 @@ def create_message(project_id: int, message: MessageCreate, db: Session = Depend db.add(new_message) db.commit() db.refresh(new_message) - return new_message \ No newline at end of file + return new_message + +@app.post("/api/projects/{project_id}/messages", response_model=List[MessageResponse]) +def create_message(project_id: int, message: MessageCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): + """Ajoute un message, interroge l'IA et retourne l'historique mis à jour.""" + project = db.query(Project).filter(Project.id == project_id, Project.user_id == current_user.id).first() + if not project: + raise HTTPException(status_code=404, detail="Projet introuvable") + + # 1. Sauvegarde du message de l'utilisateur + user_message = Message(role=message.role, content=message.content, project_id=project_id) + db.add(user_message) + db.commit() + + # 2. Récupération de l'historique complet pour le contexte + history = db.query(Message).filter(Message.project_id == project_id).order_by(Message.created_at.asc()).all() + + # 3. Récupération des clés API depuis la base + settings = db.query(SystemSettings).first() + + # 4. Interrogation de l'IA via notre routeur (ai.py) + ai_response_text = get_ai_response(history, settings) + + # 5. Sauvegarde de la réponse de l'IA + ai_message = Message(role="assistant", content=ai_response_text, project_id=project_id) + db.add(ai_message) + db.commit() + + # On retourne le nouvel historique contenant la question ET la réponse + return db.query(Message).filter(Message.project_id == project_id).order_by(Message.created_at.asc()).all() \ No newline at end of file diff --git a/frontend/dashboard.html b/frontend/dashboard.html index 36c8da3..dec9b78 100644 --- a/frontend/dashboard.html +++ b/frontend/dashboard.html @@ -264,22 +264,25 @@ nextTick(() => lucide.createIcons()); try { - await fetch(`/api/projects/${activeProject.value.id}/messages`, { + const response = await fetch(`/api/projects/${activeProject.value.id}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role: 'user', content: userText }) }); - // SIMULATION TEMPORAIRE : Attente de l'IA (2 secondes) - setTimeout(() => { - isAiThinking.value = false; - scrollToBottom(); - // La prochaine étape remplacera ce setTimeout par le véritable appel au moteur IA ! - }, 2000); - + if (response.ok) { + // L'API nous renvoie l'historique à jour avec la réponse de l'IA + messages.value = await response.json(); + } else { + const err = await response.json(); + alert("Erreur: " + err.detail); + } } catch (error) { console.error("Erreur envoi message", error); + } finally { isAiThinking.value = false; + scrollToBottom(); + nextTick(() => lucide.createIcons()); } }; diff --git a/requirements.txt b/requirements.txt index ab025ed..eb0636f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ python-dotenv python-multipart bcrypt email-validator -PyJWT \ No newline at end of file +PyJWT +openai \ No newline at end of file