2025-06-21 14:30:11 -03:00
"use client" ;
import React , { useState , useRef , useEffect } from 'react' ;
import {
Send , Plus , Menu , Search , Archive , Trash2 , Edit3 ,
MessageSquare , User , Bot , Copy , ThumbsUp , ThumbsDown ,
RotateCcw , Share , MoreHorizontal , Image , Video , FileText ,
Code , Table , Music , Mic , Settings , Zap , Brain , Sparkles ,
BarChart2 , Clock , Globe , Lock , Unlock , Book , Feather ,
Camera , Film , Headphones , Download , Upload , Link , Mail ,
AlertCircle , CheckCircle , HelpCircle , Info , Star , Heart ,
Award , Gift , Coffee , ShoppingCart , CreditCard , Key ,
Map , Navigation , Phone , Tag , Watch , Wifi ,
Cloud , Sun , Moon , Umbrella , Droplet , Wind
} from 'lucide-react' ;
const ChatPage = ( ) = > {
const [ messages , setMessages ] = useState ( [
{
id : 1 ,
type : 'assistant' ,
content : "Hello! I'm General Bots, a large language model by Pragmatismo. How can I help you today?" ,
timestamp : new Date ( ) . toISOString ( )
}
] ) ;
const [ input , setInput ] = useState ( '' ) ;
const [ isTyping , setIsTyping ] = useState ( false ) ;
const [ sidebarOpen , setSidebarOpen ] = useState ( true ) ;
const [ conversations , setConversations ] = useState ( [
{ id : 1 , title : 'Current Chat' , timestamp : new Date ( ) , active : true } ,
{ id : 2 , title : 'Previous Conversation' , timestamp : new Date ( Date . now ( ) - 86400000 ) , active : false } ,
{ id : 3 , title : 'Code Review Discussion' , timestamp : new Date ( Date . now ( ) - 172800000 ) , active : false } ,
{ id : 4 , title : 'Project Planning' , timestamp : new Date ( Date . now ( ) - 259200000 ) , active : false } ,
] ) ;
const [ activeMode , setActiveMode ] = useState ( 'assistant' ) ;
const messagesEndRef = useRef ( null ) ;
const textareaRef = useRef ( null ) ;
// Mode buttons
const modeButtons = [
{ id : 'deep-think' , icon : < Brain size = { 16 } / > , label : 'Deep Think' } ,
{ id : 'web' , icon : < Globe size = { 16 } / > , label : 'Web' } ,
{ id : 'creative' , icon : < Sparkles size = { 16 } / > , label : 'Creative' } ,
{ id : 'image' , icon : < Image size = { 16 } / > , label : 'Image' } ,
{ id : 'video' , icon : < Video size = { 16 } / > , label : 'Video' } ,
{ id : 'document' , icon : < FileText size = { 16 } / > , label : 'Document' } ,
{ id : 'code' , icon : < Code size = { 16 } / > , label : 'Code' } ,
{ id : 'data' , icon : < Table size = { 16 } / > , label : 'Data' } ,
{ id : 'audio' , icon : < Headphones size = { 16 } / > , label : 'Audio' } ,
{ id : 'analysis' , icon : < BarChart2 size = { 16 } / > , label : 'Analysis' } ,
{ id : 'research' , icon : < Book size = { 16 } / > , label : 'Research' } ,
{ id : 'writing' , icon : < Feather size = { 16 } / > , label : 'Writing' } ,
] ;
const scrollToBottom = ( ) = > {
messagesEndRef . current ? . scrollIntoView ( { behavior : 'smooth' } ) ;
} ;
useEffect ( ( ) = > {
scrollToBottom ( ) ;
} , [ messages ] ) ;
const handleSubmit = async ( ) = > {
if ( ! input . trim ( ) ) return ;
const userMessage = {
id : Date.now ( ) ,
type : 'user' ,
content : input.trim ( ) ,
timestamp : new Date ( ) . toISOString ( )
} ;
setMessages ( prev = > [ . . . prev , userMessage ] ) ;
setInput ( '' ) ;
setIsTyping ( true ) ;
// Simulate assistant response
setTimeout ( ( ) = > {
const assistantMessage = {
id : Date.now ( ) + 1 ,
type : 'assistant' ,
content : ` I understand you're asking about " ${ input . trim ( ) } ". This is a simulated response to demonstrate the chat interface. The actual implementation would connect to your chat provider and send real responses. ` ,
timestamp : new Date ( ) . toISOString ( )
} ;
setMessages ( prev = > [ . . . prev , assistantMessage ] ) ;
setIsTyping ( false ) ;
} , 1500 ) ;
} ;
const handleKeyDown = ( e ) = > {
if ( e . key === 'Enter' && ! e . shiftKey ) {
e . preventDefault ( ) ;
handleSubmit ( ) ;
}
} ;
const formatTimestamp = ( timestamp ) = > {
const date = new Date ( timestamp ) ;
const now = new Date ( ) ;
const diffInHours = ( now - date ) / ( 1000 * 60 * 60 ) ;
if ( diffInHours < 24 ) {
return date . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) ;
} else if ( diffInHours < 48 ) {
return 'Yesterday' ;
} else {
return date . toLocaleDateString ( ) ;
}
} ;
const newChat = ( ) = > {
const newConv = {
id : Date.now ( ) ,
title : 'New Chat' ,
timestamp : new Date ( ) ,
active : true
} ;
setConversations ( prev = > [ newConv , . . . prev . map ( c = > ( { . . . c , active : false } ) ) ] ) ;
setMessages ( [ {
id : Date.now ( ) ,
type : 'assistant' ,
content : "Hello! I'm General Bots, a large language model by Pragmatismo. How can I help you today?" ,
timestamp : new Date ( ) . toISOString ( )
} ] ) ;
} ;
const MessageActions = ( { message } ) = > (
< div className = "flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200" >
< button className = "p-1.5 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors" >
< Copy className = "w-3.5 h-3.5" / >
< / button >
< button className = "p-1.5 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors" >
< ThumbsUp className = "w-3.5 h-3.5" / >
< / button >
< button className = "p-1.5 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors" >
< ThumbsDown className = "w-3.5 h-3.5" / >
< / button >
< button className = "p-1.5 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors" >
< Share className = "w-3.5 h-3.5" / >
< / button >
< / div >
) ;
// Auto-resize textarea
useEffect ( ( ) = > {
const textarea = textareaRef . current ;
if ( textarea ) {
textarea . style . height = 'auto' ;
textarea . style . height = ` ${ Math . min ( textarea . scrollHeight , 120 ) } px ` ;
}
} , [ input ] ) ;
2025-03-30 16:42:51 -03:00
return (
2025-06-21 14:30:11 -03:00
< div className = "flex min-h-[calc(100vh-95px)] bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-white" >
{ /* Sidebar */ }
< div
className = { ` ${
sidebarOpen ? 'w-80' : 'w-0'
} transition - all duration - 300 ease - in - out bg - white dark :bg - gray - 800 border - r border - gray - 200 dark :border - gray - 700 flex flex - col overflow - hidden ` }
>
{ sidebarOpen && (
< >
{ /* Sidebar Header */ }
< div className = "p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0" >
< button
onClick = { newChat }
className = "flex items-center gap-3 w-full p-3 rounded-xl border-2 border-dashed border-gray-300 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-all duration-200 group"
>
< Plus className = "w-5 h-5 text-gray-600 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" / >
< span className = "font-medium text-gray-700 dark:text-gray-300 group-hover:text-blue-700 dark:group-hover:text-blue-300" >
New Chat
< / span >
< / button >
< / div >
{ /* Conversations List */ }
< div className = "flex-1 overflow-hidden" >
< div className = "h-full overflow-y-auto px-3 py-2 space-y-1" >
{ conversations . map ( conv = > (
< div
key = { conv . id }
className = { ` group relative flex items-center gap-3 p-3 rounded-xl cursor-pointer transition-all duration-200 ${
conv . active
? 'bg-blue-100 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-700'
: 'hover:bg-gray-100 dark:hover:bg-gray-700/50'
} ` }
onClick = { ( ) = > {
setConversations ( prev = > prev . map ( c = > ( { . . . c , active : c.id === conv . id } ) ) ) ;
} }
>
< MessageSquare className = { ` w-4 h-4 flex-shrink-0 ${
conv . active ? 'text-blue-600 dark:text-blue-400' : 'text-gray-500 dark:text-gray-400'
} ` } />
< div className = "flex-1 min-w-0" >
< div className = { ` font-medium truncate ${
conv . active ? 'text-blue-900 dark:text-blue-100' : 'text-gray-900 dark:text-gray-100'
} ` }>
{ conv . title }
< / div >
< div className = "text-xs text-gray-500 dark:text-gray-400 truncate" >
{ formatTimestamp ( conv . timestamp ) }
< / div >
< / div >
{ conv . active && (
< div className = "w-2 h-2 rounded-full bg-blue-500 flex-shrink-0" > < / div >
) }
< / div >
) ) }
< / div >
< / div >
{ /* Sidebar Footer */ }
< div className = "p-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0" >
< div className = "flex items-center gap-3 p-3 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-700/50 cursor-pointer transition-colors duration-200" >
< User className = "w-5 h-5 text-gray-600 dark:text-gray-400" / >
2025-06-21 19:06:13 -03:00
< User className = "w-5 h-5 text-gray-600 dark:text-gray-400" / >
< User className = "w-5 h-5 text-gray-600 dark:text-gray-400" / >
< User className = "w-5 h-5 text-gray-600 dark:text-gray-400" / >
< User className = "w-5 h-5 text-gray-600 dark:text-gray-400" / >
2025-06-21 14:30:11 -03:00
< / div >
< / div >
< / >
) }
< / div >
{ /* Main Chat Area */ }
< div className = "flex-1 flex flex-col min-w-0 bg-white dark:bg-gray-900" >
{ /* Header */ }
< div className = "flex-shrink-0 p-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900" >
< div className = "flex items-center justify-between" >
< div className = "flex items-center gap-4" >
< button
onClick = { ( ) = > setSidebarOpen ( ! sidebarOpen ) }
className = "p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors duration-200"
>
< Menu className = "w-5 h-5" / >
< / button >
< h1 className = "text-xl font-semibold text-gray-900 dark:text-white" >
{ conversations . find ( c = > c . active ) ? . title || 'New Chat' }
< / h1 >
< / div >
< button className = "p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors duration-200" >
< Search className = "w-5 h-5" / >
< / button >
< / div >
< / div >
{ /* Messages Container - This is the key fix */ }
< div className = "flex-1 overflow-hidden flex flex-col" >
< div className = "flex-1 overflow-y-auto px-4 py-6 space-y-6" >
{ messages . map ( message = > (
< div
key = { message . id }
className = { ` group flex ${ message . type === 'user' ? 'justify-end' : 'justify-start' } ` }
>
< div className = { ` max-w-[85%] md:max-w-[75%] ${
message . type === 'user'
? 'bg-blue-600 text-white'
: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white'
} rounded - 2 xl px - 4 py - 3 shadow - sm ` }>
< div className = "flex items-start gap-3" >
< div className = "flex-shrink-0 mt-0.5" >
{ message . type === 'user' ? (
< User className = "w-4 h-4" / >
) : (
< Bot className = "w-4 h-4" / >
) }
< / div >
< div className = "flex-1 min-w-0" >
< div className = "whitespace-pre-wrap break-words leading-relaxed" >
{ message . content }
< / div >
< div className = { ` mt-3 flex items-center justify-between text-xs ${
message . type === 'user'
? 'text-blue-100'
: 'text-gray-500 dark:text-gray-400'
} ` }>
< span > { formatTimestamp ( message . timestamp ) } < / span >
{ message . type === 'assistant' && < MessageActions message = { message } / > }
< / div >
< / div >
< / div >
< / div >
< / div >
) ) }
{ isTyping && (
< div className = "flex justify-start" >
< div className = "max-w-[85%] md:max-w-[75%] bg-gray-100 dark:bg-gray-800 rounded-2xl px-4 py-3 shadow-sm" >
< div className = "flex items-center gap-3" >
< Bot className = "w-4 h-4 text-gray-600 dark:text-gray-400" / >
< div className = "flex gap-1" >
< div className = "w-2 h-2 rounded-full bg-gray-400 animate-bounce" > < / div >
< div className = "w-2 h-2 rounded-full bg-gray-400 animate-bounce" style = { { animationDelay : '0.2s' } } > < / div >
< div className = "w-2 h-2 rounded-full bg-gray-400 animate-bounce" style = { { animationDelay : '0.4s' } } > < / div >
< / div >
< / div >
< / div >
< / div >
) }
< div ref = { messagesEndRef } / >
< / div >
< / div >
{ /* Mode Carousel */ }
< div className = "flex-shrink-0 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900" >
< div className = "overflow-x-auto px-4 py-3" >
< div className = "flex gap-2 min-w-max" >
{ modeButtons . map ( button = > (
< button
key = { button . id }
onClick = { ( ) = > setActiveMode ( button . id ) }
className = { ` flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 whitespace-nowrap ${
activeMode === button . id
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-700'
: 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
} ` }
>
{ button . icon }
< span > { button . label } < / span >
< / button >
) ) }
< / div >
< / div >
< / div >
{ /* Input Area */ }
< div className = "flex-shrink-0 p-4 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900" >
< div className = "relative max-w-4xl mx-auto" >
< textarea
ref = { textareaRef }
value = { input }
onChange = { ( e ) = > setInput ( e . target . value ) }
onKeyDown = { handleKeyDown }
placeholder = "Type your message..."
className = "w-full p-4 pr-14 rounded-2xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent resize-none transition-all duration-200"
rows = { 1 }
style = { { minHeight : '56px' , maxHeight : '120px' } }
/ >
< button
onClick = { handleSubmit }
disabled = { ! input . trim ( ) }
className = { ` absolute right-3 bottom-3 p-2.5 rounded-xl transition-all duration-200 ${
input . trim ( )
? 'bg-blue-600 hover:bg-blue-700 text-white shadow-lg hover:shadow-xl transform hover:scale-105'
: 'bg-gray-300 dark:bg-gray-600 text-gray-500 dark:text-gray-400 cursor-not-allowed'
} ` }
>
< Send className = "w-5 h-5" / >
< / button >
< / div >
< / div >
< / div >
< / div >
2025-03-30 16:42:51 -03:00
) ;
2025-06-21 14:30:11 -03:00
} ;
export default ChatPage ;