2025-12-11 11:25:26 +01:00
import React , { useState , useRef , useEffect } from 'react' ;
import { ChatIcon , CloseIcon , SendIcon } from './Icons' ;
import { ChatMessage } from '../types' ;
interface AIChatProps {
onSendMessage : ( text : string ) => Promise < string >;
isOpen : boolean ;
setIsOpen : ( open : boolean ) => void ;
}
const ModelMessage : React.FC < { text : string } > = ({ text }) => {
const elements : React.ReactNode [] = [];
let listItems : React.ReactNode [] = [];
const flushList = () => {
if ( listItems . length > 0 ) {
elements . push (
< ul key = { `ul- ${ elements . length } ` } className = "list-disc list-inside space-y-1 my-2 pl-2" >
{ listItems }
</ ul >
);
listItems = [];
}
};
2026-02-10 15:15:44 +01:00
2025-12-11 11:25:26 +01:00
const parseBold = ( content : string , keyPrefix : string ) => {
2026-02-10 15:15:44 +01:00
const parts = content . split ( /(\*\*.*?\*\*)/g );
return parts . map (( part , i ) => {
if ( part . startsWith ( '**' ) && part . endsWith ( '**' )) {
return < strong key = { ` ${ keyPrefix } - ${ i } ` }>{ part . slice ( 2 , - 2 )}</ strong >;
}
return part ;
});
2025-12-11 11:25:26 +01:00
}
text . split ( '\n' ). forEach (( line , index ) => {
const trimmedLine = line . trim ();
if ( trimmedLine . startsWith ( '* ' ) || trimmedLine . startsWith ( '- ' )) {
const content = trimmedLine . substring ( 2 );
listItems . push (< li key = { index }>{ parseBold ( content , `li- ${ index } ` )}</ li >);
} else {
flushList ();
if ( line . trim () !== '' ) {
2026-02-10 15:15:44 +01:00
elements . push (
< p key = { `p- ${ index } ` } className = "my-1" >
{ parseBold ( line , `p- ${ index } ` )}
</ p >
);
2025-12-11 11:25:26 +01:00
}
}
});
flushList (); // Flush any remaining list items at the end
return <>{ elements . length > 0 ? elements : < p >{ text }</ p >}</>;
};
2025-12-11 14:03:33 +01:00
const AIChat : React.FC < AIChatProps > = ({ onSendMessage , isOpen , setIsOpen }) => {
2025-12-11 11:25:26 +01:00
const [ messages , setMessages ] = useState < ChatMessage [] >([
2026-02-10 15:15:44 +01:00
{ role : 'model' , text : '¡Hola! Soy tu Estratega y KAM (Key Account Manager) experto. He analizado tus datos actuales de ventas y publicidad. ¿En qué puedo ayudarte a optimizar tu negocio hoy?' , timestamp : new Date () }
2025-12-11 11:25:26 +01:00
]);
const [ input , setInput ] = useState ( '' );
const [ isTyping , setIsTyping ] = useState ( false );
const messagesEndRef = useRef < HTMLDivElement >( null );
const scrollToBottom = () => {
messagesEndRef . current ? . scrollIntoView ({ behavior : 'smooth' });
};
2025-12-11 14:03:33 +01:00
useEffect ( scrollToBottom , [ messages , isOpen ]);
2025-12-11 11:25:26 +01:00
const handleSend = async () => {
2025-12-11 14:03:33 +01:00
if ( ! input . trim ()) return ;
2025-12-11 11:25:26 +01:00
const userMsg : ChatMessage = { role : 'user' , text : input , timestamp : new Date () };
setMessages ( prev => [... prev , userMsg ]);
setInput ( '' );
setIsTyping ( true );
const responseText = await onSendMessage ( userMsg . text );
2026-02-10 15:15:44 +01:00
2025-12-11 11:25:26 +01:00
setIsTyping ( false );
setMessages ( prev => [... prev , { role : 'model' , text : responseText , timestamp : new Date () }]);
};
const handleKeyDown = ( e : React.KeyboardEvent ) => {
if ( e . key === 'Enter' && ! e . shiftKey ) {
e . preventDefault ();
handleSend ();
}
};
return (
<>
{ /* Trigger Button */ }
< button
onClick = {() => setIsOpen ( ! isOpen )}
className = { `fixed bottom-6 right-6 z-50 p-4 rounded-full shadow-2xl transition-all duration-300 hover:scale-110
${ isOpen ? 'bg-slate-700 rotate-90 opacity-0 pointer-events-none' : 'bg-primary text-white rotate-0 opacity-100' } ` }
>
< ChatIcon />
</ button >
{ /* Chat Window */ }
2026-02-10 15:15:44 +01:00
< div
2025-12-11 11:25:26 +01:00
className = { `fixed z-50 bg-slate-900 border border-border shadow-2xl transition-all duration-300 flex flex-col overflow-hidden
2026-02-10 15:15:44 +01:00
${ isOpen
? 'bottom-6 right-6 w-96 h-[600px] rounded-2xl opacity-100 translate-y-0'
: 'bottom-6 right-6 w-96 h-0 opacity-0 translate-y-10 pointer-events-none' } ` }
2025-12-11 11:25:26 +01:00
>
{ /* Header */ }
< div className = "bg-primary/10 p-4 border-b border-border flex justify-between items-center backdrop-blur" >
< div className = "flex items-center gap-2" >
2025-12-11 14:03:33 +01:00
< div className = "w-2 h-2 rounded-full bg-green-400 animate-pulse" ></ div >
2026-02-10 15:15:44 +01:00
< h3 className = "font-bold text-white tracking-wide" > KAM Strategy Expert </ h3 >
2025-12-11 11:25:26 +01:00
</ div >
2025-12-11 14:03:33 +01:00
< button onClick = {() => setIsOpen ( false )} className = "text-slate-400 hover:text-white transition-colors" >
< CloseIcon />
</ button >
</ div >
{ /* Messages */ }
< div className = "flex-1 overflow-y-auto p-4 space-y-4 bg-slate-950/50" >
{ messages . map (( msg , idx ) => (
< div key = { idx } className = { `flex ${ msg . role === 'user' ? 'justify-end' : 'justify-start' } ` }>
2026-02-10 15:15:44 +01:00
< div
2025-12-11 14:03:33 +01:00
className = { `max-w-[85%] rounded-2xl p-3 text-sm leading-relaxed shadow-sm
2026-02-10 15:15:44 +01:00
${ msg . role === 'user'
? 'bg-primary text-white rounded-br-none'
2025-12-11 14:03:33 +01:00
: 'bg-slate-800 text-slate-200 border border-border rounded-bl-none' } ` }
>
{ msg . role === 'model' ? < ModelMessage text = { msg . text } /> : msg . text }
</ div >
</ div >
))}
{ isTyping && (
2026-02-10 15:15:44 +01:00
< div className = "flex justify-start" >
< div className = "bg-slate-800 border border-border rounded-2xl rounded-bl-none p-4 flex gap-1 items-center" >
< div className = "w-2 h-2 bg-slate-500 rounded-full animate-bounce" ></ div >
< div className = "w-2 h-2 bg-slate-500 rounded-full animate-bounce delay-100" ></ div >
< div className = "w-2 h-2 bg-slate-500 rounded-full animate-bounce delay-200" ></ div >
</ div >
</ div >
2025-12-11 14:03:33 +01:00
)}
< div ref = { messagesEndRef } />
</ div >
{ /* Input */ }
< div className = "p-4 bg-slate-900 border-t border-border" >
< div className = "relative" >
< input
type = "text"
value = { input }
onChange = {( e ) => setInput ( e . target . value )}
onKeyDown = { handleKeyDown }
2026-02-10 15:15:44 +01:00
placeholder = "Pregunta sobre tendencias, recomendaciones de publicidad..."
2025-12-11 14:03:33 +01:00
className = "w-full bg-slate-950 border border-slate-700 text-slate-200 rounded-full py-3 pl-4 pr-12 focus:outline-none focus:border-primary transition-colors placeholder-slate-500 text-sm"
/>
2026-02-10 15:15:44 +01:00
< button
2025-12-11 14:03:33 +01:00
onClick = { handleSend }
disabled = { ! input . trim () || isTyping }
className = "absolute right-2 top-1/2 -translate-y-1/2 p-2 bg-primary text-white rounded-full hover:bg-indigo-400 disabled:opacity-50 transition-colors"
2025-12-11 11:25:26 +01:00
>
2025-12-11 14:03:33 +01:00
< SendIcon />
2025-12-11 11:25:26 +01:00
</ button >
</ div >
</ div >
</ div >
</>
);
};
2025-12-11 14:03:33 +01:00
export default AIChat ;