chatbotv2

This commit is contained in:
Ladebeze66 2025-02-11 22:27:45 +01:00
parent f5cf69f568
commit 2ffeded3d3
5 changed files with 135 additions and 22 deletions

31
app/api/proxy/route.js Normal file
View File

@ -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,
});
}
}

View File

@ -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 (
<div>
<h2>Chat avec l'IA</h2>
<input
type="text"
placeholder="Posez une question..."
value={question}
onChange={(e) => setQuestion(e.target.value)}
/>
<button onClick={handleAsk}>Envoyer</button>
{response && <p>Réponse : {response}</p>}
<div className="flex flex-col w-96 bg-white shadow-lg rounded-lg border border-gray-300">
{/* En-tête du chatbot */}
<div className="bg-blue-600 text-white p-3 rounded-t-lg flex justify-between items-center">
<span className="font-bold">💬 GrasBot</span>
<button className="text-white hover:text-red-400 text-xl" onClick={() => setMessages([])}></button>
</div>
{/* Zone d'affichage des messages */}
<div className="h-64 overflow-y-auto p-4 space-y-2">
{messages.map((msg, index) => (
<div
key={index}
className={`p-2 rounded-lg text-white ${msg.sender === "user" ? "bg-blue-500 ml-auto" : "bg-gray-500 mr-auto"}`}
style={{ maxWidth: "80%" }}
>
{msg.text}
</div>
))}
</div>
{/* Zone d'entrée utilisateur */}
<div className="flex p-3 border-t border-gray-300">
<input
type="text"
className="flex-1 p-2 border border-gray-300 rounded-l-lg focus:outline-none"
placeholder="Posez votre question..."
value={question}
onChange={(e) => setQuestion(e.target.value)}
/>
<button
className="bg-blue-500 text-white px-4 py-2 rounded-r-lg hover:bg-blue-700"
onClick={handleAsk}
>
</button>
</div>
</div>
);
}

View File

@ -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<GlossaireItem | null>(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 <div className="text-center text-gray-500"> Chargement des détails de la compétence...</div>;
}
@ -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,
`<span class="chatbot-keyword" data-chatbot="true" style="color: red; cursor: pointer;">IA locale</span>`
);
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 (
<div className="max-w-3xl mx-auto p-6">
<h1 className={titleClass || "bg-white/60 rounded-md p-1 text-2xl mb-6 font-orbitron-16-bold text-blue-700"}>{name}</h1>
<h1 className={titleClass || "bg-white/60 rounded-md p-1 text-2xl mb-6 font-orbitron-16-bold text-blue-700"}>
{name}
</h1>
<CarouselCompetences images={images} className="w-full h-64" />
<div className={contentClass || "bg-white/70 rounded-md p-4 mt-6 text-lg font-orbitron-16-bold text-black-700"}>
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{contentWithLinks}</ReactMarkdown>
</div>
{selectedMot && <ModalGlossaire mot={selectedMot} onClose={() => setSelectedMot(null)} />}
{/* 🔥 Chatbot affiché uniquement si isChatbotOpen est vrai */}
{isChatbotOpen && (
<div className="fixed bottom-10 right-10 p-4 w-96">
<ChatBot />
</div>
)}
</div>
);
}

View File

@ -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;
}