From 2ffeded3d3a52aa1fb4cdcdbc45c7b4db8081d54 Mon Sep 17 00:00:00 2001 From: Ladebeze66 Date: Tue, 11 Feb 2025 22:27:45 +0100 Subject: [PATCH] chatbotv2 --- app/api/proxy/route.js | 31 ++++++++ app/components/ChatBot.js | 79 +++++++++++++++---- app/components/ContentSectionCompetences.tsx | 42 ++++++++-- app/utils/askAI.js | 5 ++ llm-api/__pycache__/api.cpython-313.pyc | Bin 714 -> 1496 bytes 5 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 app/api/proxy/route.js diff --git a/app/api/proxy/route.js b/app/api/proxy/route.js new file mode 100644 index 0000000..fc0be07 --- /dev/null +++ b/app/api/proxy/route.js @@ -0,0 +1,31 @@ +export async function GET(req) { + const { searchParams } = new URL(req.url); + const question = searchParams.get("q"); + + if (!question) { + return new Response(JSON.stringify({ error: "Question manquante" }), { status: 400 }); + } + + const apiUrl = `https://llmapi.fernandgrascalvet.com/ask?q=${encodeURIComponent(question)}`; + + try { + const response = await fetch(apiUrl, { + headers: { + "Content-Type": "application/json", + }, + }); + + const data = await response.json(); + return new Response(JSON.stringify(data), { + status: response.status, + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + }); + } catch (error) { + return new Response(JSON.stringify({ error: "Erreur de communication avec l'API" }), { + status: 500, + }); + } +} diff --git a/app/components/ChatBot.js b/app/components/ChatBot.js index 34acfea..1525d04 100644 --- a/app/components/ChatBot.js +++ b/app/components/ChatBot.js @@ -1,27 +1,76 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { askAI } from "../utils/askAI"; export default function ChatBot() { const [question, setQuestion] = useState(""); - const [response, setResponse] = useState(""); + const [messages, setMessages] = useState([]); + + // Afficher le message d'introduction une seule fois au chargement du chatbot + useEffect(() => { + const introMessage = { + sender: "bot", + text: "Bonjour ! Je suis GrasBot, une intelligence artificielle locale basée sur Mistral 7B et hébergée sur un serveur Windows. Je suis là pour répondre à vos questions. Posez-moi votre question ! 😊" + }; + setMessages([introMessage]); + }, []); const handleAsk = async () => { - if (!question) return; - const aiResponse = await askAI(question); - setResponse(aiResponse); + if (!question.trim()) return; // Évite d'envoyer un message vide + + const userMessage = { sender: "user", text: question }; + setMessages((prevMessages) => [...prevMessages, userMessage]); // Ajoute le message utilisateur + + setQuestion(""); // Réinitialise le champ après l'envoi + + try { + const botResponse = await askAI(question); + const botMessage = { sender: "bot", text: botResponse }; + setMessages((prevMessages) => [...prevMessages, botMessage]); // Ajoute la réponse du bot + } catch (error) { + setMessages((prevMessages) => [ + ...prevMessages, + { sender: "bot", text: "❌ Erreur de réponse. Réessayez plus tard." } + ]); + } }; return ( -
-

Chat avec l'IA

- setQuestion(e.target.value)} - /> - - {response &&

Réponse : {response}

} +
+ {/* En-tête du chatbot */} +
+ 💬 GrasBot + +
+ + {/* Zone d'affichage des messages */} +
+ {messages.map((msg, index) => ( +
+ {msg.text} +
+ ))} +
+ + {/* Zone d'entrée utilisateur */} +
+ setQuestion(e.target.value)} + /> + +
); } diff --git a/app/components/ContentSectionCompetences.tsx b/app/components/ContentSectionCompetences.tsx index 19e62a2..a5106e4 100644 --- a/app/components/ContentSectionCompetences.tsx +++ b/app/components/ContentSectionCompetences.tsx @@ -6,6 +6,7 @@ import CarouselCompetences from "./CarouselCompetences"; import ReactMarkdown from "react-markdown"; import rehypeRaw from "rehype-raw"; import ModalGlossaire from "./ModalGlossaire"; +import ChatBot from "./ChatBot"; // ✅ Import du ChatBot // ✅ Définition des types pour TypeScript interface ImageData { @@ -46,16 +47,16 @@ export default function ContentSectionCompetences({ console.log("🔍 [ContentSectionCompetences] Chargement du composant..."); const [selectedMot, setSelectedMot] = useState(null); - const [loading, setLoading] = useState(competenceData === null); // ✅ Initialiser correctement loading + const [isChatbotOpen, setIsChatbotOpen] = useState(false); // ✅ État pour afficher/masquer le chatbot + const [loading, setLoading] = useState(competenceData === null); const apiUrl = getApiUrl(); useEffect(() => { if (competenceData) { - setLoading(false); // ✅ Mise à jour de loading une seule fois après chargement + setLoading(false); } }, [competenceData]); - // ✅ Affichage d'un message de chargement if (loading) { return
⏳ Chargement des détails de la compétence...
; } @@ -67,7 +68,6 @@ export default function ContentSectionCompetences({ const { name, content, picture } = competenceData; - // ✅ Transformation des images de Strapi en format attendu par le carrousel const images = picture?.map((img) => ({ url: `${apiUrl}${img.formats?.large?.url || img.url}`, @@ -76,11 +76,17 @@ export default function ContentSectionCompetences({ console.log("✅ [ContentSectionCompetences] Images préparées :", images); - // ✅ Transformation des mots-clés du glossaire function transformMarkdownWithKeywords(text: string) { if (!glossaireData.length) return text; let modifiedText = text; + + // ✅ Ajout de la mise en surbrillance pour "IA locale" + modifiedText = modifiedText.replace( + /\bIA locale\b/g, + `IA locale` + ); + glossaireData.forEach(({ mot_clef, variantes }) => { const regexVariants = variantes .map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) @@ -98,7 +104,7 @@ export default function ContentSectionCompetences({ const contentWithLinks = transformMarkdownWithKeywords(content); - // ✅ Gestion des clics sur les mots-clés + // ✅ Gestion des clics sur les mots-clés pour le glossaire useEffect(() => { function handleKeywordClick(event: MouseEvent) { const target = event.target as HTMLElement; @@ -115,14 +121,36 @@ export default function ContentSectionCompetences({ return () => document.body.removeEventListener("click", handleKeywordClick); }, [glossaireData]); + // ✅ Gestion du clic sur "IA locale" pour ouvrir le chatbot + useEffect(() => { + function handleChatbotClick(event: MouseEvent) { + const target = event.target as HTMLElement; + if (target.dataset.chatbot === "true") { + setIsChatbotOpen(true); + } + } + + document.body.addEventListener("click", handleChatbotClick); + return () => document.body.removeEventListener("click", handleChatbotClick); + }, []); + return (
-

{name}

+

+ {name} +

{contentWithLinks}
{selectedMot && setSelectedMot(null)} />} + + {/* 🔥 Chatbot affiché uniquement si isChatbotOpen est vrai */} + {isChatbotOpen && ( +
+ +
+ )}
); } diff --git a/app/utils/askAI.js b/app/utils/askAI.js index e69de29..7214a06 100644 --- a/app/utils/askAI.js +++ b/app/utils/askAI.js @@ -0,0 +1,5 @@ +export async function askAI(question) { + const response = await fetch(`/api/proxy?q=${encodeURIComponent(question)}`); + const data = await response.json(); + return data.response; +} diff --git a/llm-api/__pycache__/api.cpython-313.pyc b/llm-api/__pycache__/api.cpython-313.pyc index 45388e9496daf57d1751e028ccceefa4bb4fef2e..ffddb41bfaf422ecc23bb07b786cea79f0c68fdc 100644 GIT binary patch literal 1496 zcmY*Z%WoS+7@vLE>o|Vop*)mIWt@aYMzPbX2@z?ilr&07ng@7effS2qYfqdF>s@zd z9ok40h6vFjq=E#ZNIm3OiwhSHoc#~P3R0sf5&=%VxiS|nh;KI8CXBT6&G*gsn)&@^ zThVA3!T9x;AB#T+5c*3f{s7$@t-Bz;Lpsu>TWC?D(xOadEOhynLKRP{RLx0)XyP=4 z26Tl6b(LzD5e?-6neNk#b3zYHNGGZu%!PF=Cy%RoXaH@iud=ik5U4iplUzYOq}ZUK7z2VlX3g zhrsfE(=Iq4Gpq}preEd1ZLoUHbA~J>r%-V{$M5ce#A6-tt(pFYw zU9lBi%_2RZ2h)nJu7`Y|j7Mf)JCTEEJ>pxks;<2yXX3toCVk4;fe$7!oo9A`rHAyk ziPLkWqM{EylvOTE!)Q|3l#=0RpRc@Q>;QA$c& z-DZx>LzFOpj*-e$C^HARI99zjinj^2M}Qar5H4469)c0}!Z<}H)hE>{Kn zd9(tw++c(WxEYTutgK~L=dw4}7FJh8IS*aNRY2Y4vDKxendO-ccv2hL%o2rDA+8jO zn+#HcEfrVKH7})E;8DNZ4VT=7f(64hXbfDK@WbBV7Z;~;rF$tGJW_&XIm;@g;5Fvp zOPBBQ2oP$+=NP>JfgK{l=Pc4$}F64s6cqc-h-*BNRsqB8h(!A&r$my!RVe@RgOE~tj3zE-0_K2jWolkv%4BSmeeo$ iKJ9zZyZ2gC?vX|w4>b{pr&>#hmV)HYm%>gHko7;ol8ds|EBH|f9CCUs6Am=cK0v!>|tjCWIEd~Y2WFHozdO;wA1Bi=3M!jTcVEDksAS8W5 zNce`3_(ygY9<~OTFI)@)68*JZwR7S&s9fYXZ}7OmE!=P0Y1?gofko2ZCk|d=D5|+Ql2iJH(phKQOT~ YO3zUK%mAc5Gczy=egp{=i32qQ0M_JcHvj+t