mirror of
https://github.com/Ladebeze66/devsite.git
synced 2025-12-13 12:46:49 +01:00
chatbotv2
This commit is contained in:
parent
f5cf69f568
commit
2ffeded3d3
31
app/api/proxy/route.js
Normal file
31
app/api/proxy/route.js
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user