Feat: Principio Memoria de Hilo Conversacional
This commit is contained in:
@@ -14,6 +14,7 @@ const MAX_CHARS = 200;
|
||||
// Constantes para la clave del localStorage
|
||||
const CHAT_HISTORY_KEY = 'chatbot-history';
|
||||
const CHAT_CONTEXT_KEY = 'chatbot-active-article';
|
||||
const CHAT_SUMMARY_KEY = 'chatbot-summary';
|
||||
|
||||
const Chatbot: React.FC = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@@ -51,6 +52,23 @@ const Chatbot: React.FC = () => {
|
||||
return null;
|
||||
});
|
||||
|
||||
const [conversationSummary, setConversationSummary] = useState<string>(() => {
|
||||
try {
|
||||
return localStorage.getItem(CHAT_SUMMARY_KEY) || "";
|
||||
} catch (error) {
|
||||
console.error("No se pudo cargar el resumen de la conversación:", error);
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(CHAT_SUMMARY_KEY, conversationSummary);
|
||||
} catch (error) {
|
||||
console.error("No se pudo guardar el resumen de la conversación:", error);
|
||||
}
|
||||
}, [conversationSummary]);
|
||||
|
||||
// Añadimos un useEffect para guardar los mensajes.
|
||||
useEffect(() => {
|
||||
try {
|
||||
@@ -102,14 +120,14 @@ const Chatbot: React.FC = () => {
|
||||
const messageToSend = inputValue;
|
||||
setInputValue('');
|
||||
|
||||
// Inicia el estado de carga, pero aún no el de streaming
|
||||
setIsLoading(true);
|
||||
setIsStreaming(false);
|
||||
|
||||
try {
|
||||
const requestBody = {
|
||||
message: messageToSend,
|
||||
contextUrl: activeArticle ? activeArticle.url : null
|
||||
contextUrl: activeArticle ? activeArticle.url : null,
|
||||
conversationSummary: conversationSummary
|
||||
};
|
||||
|
||||
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/chat/stream-message`, {
|
||||
@@ -133,22 +151,41 @@ const Chatbot: React.FC = () => {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) {
|
||||
// ... (La lógica de `if (done)` no cambia)
|
||||
try {
|
||||
const responseArray = JSON.parse(fullReplyRaw);
|
||||
let intent = 'Homepage';
|
||||
let messageChunks = [];
|
||||
if (Array.isArray(responseArray) && responseArray.length > 0) {
|
||||
if (typeof responseArray[0] === 'string' && responseArray[0].startsWith('INTENT::')) {
|
||||
intent = responseArray[0].split('::')[1];
|
||||
messageChunks = responseArray.slice(1);
|
||||
} else {
|
||||
messageChunks = responseArray;
|
||||
}
|
||||
const messageChunks: string[] = [];
|
||||
let finalSummary = conversationSummary;
|
||||
|
||||
if (Array.isArray(responseArray)) {
|
||||
responseArray.forEach((item: string) => {
|
||||
if (typeof item === 'string') {
|
||||
if (item.startsWith('INTENT::')) {
|
||||
intent = item.split('::')[1];
|
||||
} else if (item.startsWith('SUMMARY::')) {
|
||||
finalSummary = item.split('::')[1];
|
||||
} else {
|
||||
messageChunks.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setConversationSummary(finalSummary);
|
||||
const finalCleanText = messageChunks.join('');
|
||||
|
||||
// 1. Aseguramos que el último mensaje tenga el texto final y 100% limpio.
|
||||
setMessages(prev => {
|
||||
const updatedMessages = [...prev];
|
||||
if (updatedMessages.length > 0) {
|
||||
updatedMessages[updatedMessages.length - 1].text = finalCleanText;
|
||||
}
|
||||
return updatedMessages;
|
||||
});
|
||||
|
||||
const linkRegex = /\[(.*?)\]\((https?:\/\/[^\s]+)\)/;
|
||||
const match = finalCleanText.match(linkRegex);
|
||||
|
||||
if (match && match[1] && match[2]) {
|
||||
setActiveArticle({ title: match[1], url: match[2] });
|
||||
} else if (intent === 'Database' || intent === 'Homepage') {
|
||||
@@ -164,26 +201,37 @@ const Chatbot: React.FC = () => {
|
||||
const chunk = decoder.decode(value);
|
||||
fullReplyRaw += chunk;
|
||||
|
||||
// --- LÓGICA DE VISUALIZACIÓN EN TIEMPO REAL ---
|
||||
let cleanTextForDisplay = '';
|
||||
try {
|
||||
const parsedArray = JSON.parse(fullReplyRaw.replace(/,$/, '') + ']');
|
||||
const displayChunks = parsedArray[0] && parsedArray[0].startsWith('INTENT::')
|
||||
? parsedArray.slice(1)
|
||||
: parsedArray;
|
||||
|
||||
// Filtramos CUALQUIER item que sea una pista interna.
|
||||
const displayChunks = Array.isArray(parsedArray)
|
||||
? parsedArray.filter((item: string) =>
|
||||
typeof item === 'string' && !item.startsWith('INTENT::') && !item.startsWith('SUMMARY::')
|
||||
)
|
||||
: [];
|
||||
|
||||
cleanTextForDisplay = displayChunks.join('');
|
||||
} catch (e) {
|
||||
cleanTextForDisplay = fullReplyRaw.replace(/^\["INTENT::.*?","|\["|"]$|","/g, '');
|
||||
// El fallback también debe filtrar ambas pistas.
|
||||
cleanTextForDisplay = fullReplyRaw
|
||||
.replace(/\"INTENT::.*?\",?/g, '')
|
||||
.replace(/\"SUMMARY::.*?\",?/g, '')
|
||||
.replace(/^\["|"]$|","/g, '');
|
||||
}
|
||||
|
||||
if (isFirstChunk) {
|
||||
// En el primer chunk, activamos el flag de streaming
|
||||
setIsStreaming(true);
|
||||
setMessages(prev => [...prev, { text: cleanTextForDisplay, sender: 'bot' }]);
|
||||
isFirstChunk = false;
|
||||
} else {
|
||||
setMessages(prev => {
|
||||
const updatedMessages = [...prev];
|
||||
updatedMessages[updatedMessages.length - 1].text = cleanTextForDisplay;
|
||||
if (updatedMessages.length > 0) {
|
||||
updatedMessages[updatedMessages.length - 1].text = cleanTextForDisplay;
|
||||
}
|
||||
return updatedMessages;
|
||||
});
|
||||
}
|
||||
@@ -197,7 +245,6 @@ const Chatbot: React.FC = () => {
|
||||
const errorText = error instanceof Error ? error.message : 'Lo siento, no pude conectarme.';
|
||||
setMessages(prev => [...prev, { text: errorText, sender: 'bot' }]);
|
||||
} finally {
|
||||
// Al final, reseteamos ambos estados
|
||||
setIsLoading(false);
|
||||
setIsStreaming(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user