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 45388e9..ffddb41 100644
Binary files a/llm-api/__pycache__/api.cpython-313.pyc and b/llm-api/__pycache__/api.cpython-313.pyc differ