import React, { useRef, useEffect, useCallback } from 'react'
import { IoSearchOutline, IoDocumentTextOutline } from 'react-icons/io5'
import Message from '../Message'
import { MessageUI } from '@/lib/types/database'
import { ToolResult } from '@/lib/services/tool-executor'
interface ContextInfo {
filesFound: number
fileNames: string[]
}
interface ChatMessagesProps {
// Messages and display
allDisplayMessages: (MessageUI & { _isStreaming?: boolean })[]
isStreaming: boolean
currentChat: any
bookId?: string
// Context and tool results
contextInfo: ContextInfo | null
toolResults: ToolResult[]
// Handlers
onViewDiff?: (filePath: string, oldContent: string, newContent: string) => void
getAvatarUrl: () => string | null
getInitials: () => string
onFileClick: (fileId: string, fileName: string) => Promise<void>
onDeleteMessage?: (messageId: string) => Promise<void>
}
export default function ChatMessages({
allDisplayMessages,
isStreaming,
currentChat,
bookId,
contextInfo,
toolResults,
onViewDiff,
getAvatarUrl,
getInitials,
onFileClick,
onDeleteMessage
}: ChatMessagesProps) {
const messagesEndRef = useRef<HTMLDivElement>(null)
const scrollToBottom = useCallback(() => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' })
}
}, [])
// Debounced scroll to bottom for better performance
const debouncedScrollToBottom = useCallback(() => {
const timeoutId = setTimeout(() => {
requestAnimationFrame(() => {
scrollToBottom()
})
}, 50) // Small delay to batch multiple updates
return () => clearTimeout(timeoutId)
}, [scrollToBottom])
useEffect(() => {
const cleanup = debouncedScrollToBottom()
return cleanup
}, [allDisplayMessages.length, debouncedScrollToBottom])
const renderEmptyState = () => (
<div className="flex items-center justify-center h-full text-slate-400">
<div className="text-center">
<div className="text-2xl mb-2">๐ฌ</div>
<h3 className="text-sm font-medium mb-0.5">
{currentChat ? 'Welcome to Bookwiz Chat' : 'Ready to start chatting?'}
</h3>
<div className="text-xs space-y-0.5">
<p>
{currentChat
? 'Ask me anything about writing, editing, or creative feedback!'
: 'Type a message below to start a new conversation'
}
</p>
{bookId && (
<div className="mt-2 p-3 bg-gradient-to-br from-slate-800/50 to-slate-900/50 rounded-lg border border-slate-700/50 text-[10px]">
<p className="font-medium text-teal-400 mb-1">Smart Mode is active! Try asking:</p>
<ul className="space-y-1 text-slate-300">
<li className="flex items-center gap-1.5">
<span className="w-1 h-1 bg-teal-500 rounded-full"></span>
"Tell me about [character name]"
</li>
<li className="flex items-center gap-1.5">
<span className="w-1 h-1 bg-teal-500 rounded-full"></span>
"What happens in @chapter-1?"
</li>
<li className="flex items-center gap-1.5">
<span className="w-1 h-1 bg-teal-500 rounded-full"></span>
"How does the magic system work?"
</li>
<li className="flex items-center gap-1.5">
<span className="w-1 h-1 bg-teal-500 rounded-full"></span>
"Improve the dialogue in chapter 2"
</li>
<li className="flex items-center gap-1.5 text-blue-300 font-medium">
<span className="w-1 h-1 bg-blue-500 rounded-full"></span>
"Improve the main idea file with more details"
</li>
<li className="flex items-center gap-1.5 text-blue-300 font-medium">
<span className="w-1 h-1 bg-blue-500 rounded-full"></span>
"Add more backstory to Kris's character file"
</li>
<li className="flex items-center gap-1.5 text-blue-300 font-medium">
<span className="w-1 h-1 bg-blue-500 rounded-full"></span>
"Edit the outline to include new plot points"
</li>
</ul>
</div>
)}
</div>
</div>
</div>
)
return (
<>
{/* Context Info Display */}
{contextInfo && (
<div className="px-3 py-1.5 bg-gradient-to-r from-blue-500/10 to-purple-500/10 border-b border-slate-700/50">
<div className="flex items-center gap-1.5 text-[10px] text-slate-300">
<IoSearchOutline className="w-2.5 h-2.5" />
<span>
Found {contextInfo.filesFound} relevant file{contextInfo.filesFound !== 1 ? 's' : ''}
{contextInfo.fileNames.length > 0 && (
<>: {contextInfo.fileNames.slice(0, 3).join(', ')}
{contextInfo.fileNames.length > 3 && ' and more...'}
</>
)}
</span>
</div>
</div>
)}
{/* Tool Results Display */}
{toolResults.length > 0 && (
<div className="px-3 py-1.5 bg-gradient-to-r from-emerald-500/10 to-teal-500/10 border-b border-slate-700/50">
<div className="flex flex-wrap gap-1">
{toolResults.map((result, index) => (
<div key={index} className="flex items-center gap-1 text-[10px] bg-slate-800/50 text-slate-300 px-1.5 py-0.5 rounded-md border border-slate-700/50">
{result.name === 'search_files' && '๐'}
{result.name === 'read_file' && '๐'}
{result.name === 'update_file' && 'โ๏ธ'}
{result.name === 'create_file' && '๐'}
{result.name === 'delete_file' && '๐๏ธ'}
{result.name === 'list_files' && '๐'}
<span>{result.name}</span>
</div>
))}
</div>
</div>
)}
{/* Scrollable Messages Area */}
<div className="flex-1 overflow-y-auto px-3 py-2 space-y-3 chat-scroll min-h-0">
{allDisplayMessages.length === 0 ? renderEmptyState() : (
allDisplayMessages.map((message: any, index: number) => (
<Message
key={message.id}
message={message}
index={index}
isStreaming={isStreaming}
isLastMessage={index === allDisplayMessages.length - 1}
onViewDiff={onViewDiff ? () => onViewDiff('', '', '') : undefined}
getAvatarUrl={getAvatarUrl}
getInitials={getInitials}
onFileClick={onFileClick}
onDeleteMessage={onDeleteMessage}
/>
))
)}
<div ref={messagesEndRef} />
</div>
</>
)
}