import React, { useState, useRef, useEffect } from 'react'; import ReactMarkdown from 'react-markdown'; import Avatar from './Avatar.jsx'; import '../App.css'; const ChatInterface = ({ messages = [], setMessages = () => {}, onMessageSent = () => {}, activeConversationId, saveBotResponse, toLogin, onNewChat = () => {}, refreshConversationList = () => {} }) => { const [inputMessage, setInputMessage] = useState(''); const [isLoading, setIsLoading] = useState(false); const messagesEndRef = useRef(null); const textareaRef = useRef(null); const [isStreaming, setIsStreaming] = useState(false); const [tokenLimitReached, setTokenLimitReached] = useState(false); const [hasInteractionStarted, setHasInteractionStarted] = useState(false); const [currentStreamId, setCurrentStreamId] = useState(null); // Pour optimiser les performances du streaming const accumulatedText = useRef(''); const updateThreshold = 1; const updateIntervalRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { scrollToBottom(); if (updateIntervalRef.current) { clearInterval(updateIntervalRef.current); updateIntervalRef.current = null; } if (isStreaming && currentStreamId) { updateIntervalRef.current = setInterval(() => { if (accumulatedText.current.length > 0) { setMessages(prev => { return prev.map(msg => msg.id === currentStreamId ? { ...msg, text: msg.text + accumulatedText.current } : msg ); }); accumulatedText.current = ''; } }, 100); } return () => { if (updateIntervalRef.current) { clearInterval(updateIntervalRef.current); } }; }, [isStreaming, currentStreamId, messages]); const sendMessage = async (message) => { try { setHasInteractionStarted(true); setIsLoading(true); const userMessageId = `user-${Date.now()}`; setMessages(prev => [...prev, { sender: 'user', text: message, id: userMessageId }]); const updatedConversationId = await onMessageSent(message); const streamMessageId = `bot-${Date.now()}`; setCurrentStreamId(streamMessageId); setMessages(prev => { const userMessageExists = prev.some(m => m.id === userMessageId); const updatedMessages = userMessageExists ? prev : [ ...prev, { sender: 'user', text: message, id: userMessageId } ]; return [...updatedMessages, { sender: 'bot', text: '', id: streamMessageId }]; }); setIsLoading(false); setIsStreaming(true); const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ message, conversation_id: activeConversationId || updatedConversationId, skip_save: false // Le backend gère la sauvegarde }), }); if (!response.ok) { const errorData = await response.json(); if (errorData.error === 'token_limit_exceeded') { setIsStreaming(false); setCurrentStreamId(null); setTokenLimitReached(true); setMessages(prev => [...prev.filter(m => m.id !== streamMessageId), { sender: 'bot', text: "⚠️ **Limite de taille de conversation atteinte**\n\nCette conversation est devenue trop longue. Pour continuer à discuter, veuillez créer une nouvelle conversation." }]); return; } throw new Error(`Chat API error ${response.status}`); } if (response.headers.get('content-type')?.includes('text/event-stream') && response.body) { const reader = response.body.getReader(); const decoder = new TextDecoder(); let fullText = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n\n'); for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(5)); if (data.type === 'start') { console.log("Début du streaming"); fullText = ''; accumulatedText.current = ''; } else if (data.type === 'end') { console.log("SSE End received"); setIsStreaming(false); setCurrentStreamId(null); setIsLoading(false); setMessages(prev => prev.map(msg => msg.id === streamMessageId ? { ...msg, sender: 'bot', text: fullText } : msg ) ); if (typeof refreshConversationList === 'function') { setTimeout(refreshConversationList, 100); } return; } if (data.content) { fullText += data.content; // Update immediately without accumulation setMessages(prev => { return prev.map(msg => msg.id === streamMessageId ? { ...msg, text: fullText } : msg ); }); // Ensure scroll requestAnimationFrame(scrollToBottom); } else if (data.type === 'error') { console.error("SSE Error received:", data.error); setIsStreaming(false); setCurrentStreamId(null); setIsLoading(false); setMessages(prev => prev.map(msg => msg.id === streamMessageId ? { sender: 'bot', text: "Désolé, une erreur s'est produite." } : msg ) ); } } catch (e) { console.error('Error parsing SSE data:', e, line); } } } } } else { console.log("Received non-streaming response."); const responseData = await response.json(); setIsStreaming(false); setCurrentStreamId(null); setIsLoading(false); setMessages(prev => prev.map(msg => msg.id === streamMessageId ? { sender: 'bot', text: responseData.response, id: streamMessageId } : msg ) ); if (typeof refreshConversationList === 'function') { setTimeout(refreshConversationList, 100); } } } catch (error) { console.error('Erreur lors de l\'envoi/réception du message:', error); setIsStreaming(false); setCurrentStreamId(null); setIsLoading(false); setMessages(prev => { const filteredMessages = prev.filter(m => m.id !== currentStreamId); return [...filteredMessages, { sender: 'bot', text: "Désolé, une erreur s'est produite. Veuillez réessayer." } ]; }); } }; const handleCreateNewConversation = () => { onNewChat(); setTokenLimitReached(false); setHasInteractionStarted(false); }; useEffect(() => { if (activeConversationId === null && messages.length === 0) { setHasInteractionStarted(false); } }, [activeConversationId, messages]); const handleSubmit = (e) => { e.preventDefault(); const txt = inputMessage.trim(); if (!txt) return; sendMessage(txt); setInputMessage(''); if (textareaRef.current) textareaRef.current.style.height = 'auto'; }; const isMarkdown = (text, sender) => { if (sender === 'bot') { return true; } return /(?:\*\*|__|##|\*|_|`|>|\d+\.\s|\-\s|\[.*\]\(.*\))/.test(text); }; return (
{messages.length === 0 && !hasInteractionStarted ? ( <>

Medic.ial

Bonjour ! Comment puis-je vous aider aujourd'hui ? 🧑‍⚕️