diff --git a/www/app/Chat.tsx b/www/app/Chat.tsx index 8cb7be5..6d5f2c0 100644 --- a/www/app/Chat.tsx +++ b/www/app/Chat.tsx @@ -21,6 +21,8 @@ import { type Message } from '@/utils/types'; import { localStorageProvider } from '@/utils/swrCache'; import useAutoScroll from '@/hooks/autoscroll'; +import MessageList from '@/components/MessageList'; +import { MessageListRef } from '@/components/MessageList'; const Thoughts = dynamic(() => import('@/components/thoughts'), { ssr: false, @@ -130,6 +132,7 @@ export default function Chat({ const messageContainerRef = useRef>(null); const [, scrollToBottom] = useAutoScroll(messageContainerRef); + const messageListRef = useRef(null); const firstChat = useMemo(() => { return !initialConversations?.length || (initialConversations.length === 1 && !initialMessages?.length) || @@ -297,7 +300,7 @@ What\'s on your mind? Let\'s dive in. 🌱`, }, ]; await mutateMessages(newMessages, { revalidate: false }); - scrollToBottom(); + messageListRef.current?.scrollToBottom(); await new Promise((resolve) => setTimeout(resolve, 1000)); @@ -394,20 +397,20 @@ What\'s on your mind? Let\'s dive in. 🌱`, { revalidate: false } ); - scrollToBottom(); + messageListRef.current?.scrollToBottom(); } responseReader.releaseLock(); responseReader = null; await mutateMessages(); - scrollToBottom(); + messageListRef.current?.scrollToBottom(); setCanSend(true); } catch (error) { console.error('Chat error:', error); setCanSend(true); await mutateMessages(); - scrollToBottom(); + messageListRef.current?.scrollToBottom(); } finally { // Cleanup if (thoughtReader) { @@ -478,45 +481,18 @@ What\'s on your mind? Let\'s dive in. 🌱`, )}
-
- {messages ? ( - [defaultMessage, ...messages].map((message, i) => ( - - setIsThoughtsOpen(isOpen, message.id) - } - onReactionAdded={handleReactionAdded} - /> - )) - ) : ( - - )} -
+
Promise; + setThoughtParent: (thought: string) => void; + setIsThoughtsOpen: (isOpen: boolean, messageId?: string | null | undefined) => void; + openThoughtMessageId: string | null; +} + +export interface MessageListRef { + scrollToBottom: () => void; +} + +const MessageList = forwardRef(({ + messages, + defaultMessage, + userId, + conversationId, + messagesLoading, + handleReactionAdded, + setThoughtParent, + setIsThoughtsOpen, + openThoughtMessageId, +}, ref) => { + const [isThoughtLoading, setIsThoughtLoading] = useState(false); + const [thoughtError, setThoughtError] = useState<{ messageId: string, error: string } | null>(null); + + const messageContainerRef = useRef(null); + const [, scrollToBottom] = useAutoScroll(messageContainerRef); + + // Expose scrollToBottom method to parent + useImperativeHandle(ref, () => ({ + scrollToBottom: () => { + scrollToBottom(); + }, + })); + + const handleGetThought = async (messageId: string) => { + if (!conversationId || !userId) return; + + // Find the last user message before this AI message + const allMessages = [defaultMessage, ...(messages || [])]; + const messageIndex = allMessages.findIndex(msg => msg.id === messageId); + + // Look backwards for the last user message + let lastUserMessageId = null; + for (let i = messageIndex; i >= 0; i--) { + if (allMessages[i].isUser) { + lastUserMessageId = allMessages[i].id; + break; + } + } + + try { + // Try with last user message first + if (lastUserMessageId) { + const thought = await getThought(conversationId, lastUserMessageId); + if (thought) { + setThoughtParent(thought); + setIsThoughtsOpen(true, messageId); + return; + } + } + + // If that didn't work, try with current AI message + const thought = await getThought(conversationId, messageId); + if (thought) { + setThoughtParent(thought); + setIsThoughtsOpen(true, messageId); + return; + } + + // If neither worked + setThoughtError({ messageId, error: 'No thought data available.' }); + console.log(messageId, 'No thought data available.'); + } catch (error) { + console.error('Failed to fetch thought:', error); + setThoughtError({ messageId, error: 'Failed to fetch thought.' }); + } finally { + setIsThoughtLoading(false); + } + }; + + return ( +
+ {messages ? ( + [defaultMessage, ...messages].map((message, i) => ( + setIsThoughtsOpen(isOpen, message.id)} + onReactionAdded={handleReactionAdded} + onGetThought={handleGetThought} + isThoughtLoading={isThoughtLoading && openThoughtMessageId === message.id} + thoughtError={thoughtError?.messageId === message.id ? thoughtError.error : null} + /> + )) + ) : ( + + )} +
+ ); +}); + +MessageList.displayName = 'MessageList'; + +export default MessageList; diff --git a/www/components/messagebox.tsx b/www/components/messagebox.tsx index e73257d..16671ad 100644 --- a/www/components/messagebox.tsx +++ b/www/components/messagebox.tsx @@ -8,12 +8,10 @@ import { FaLightbulb, FaThumbsDown, FaThumbsUp, - FaChevronDown, - FaChevronRight, } from 'react-icons/fa'; import { type Message } from '@/utils/types'; import Spinner from './spinner'; -import { getThought } from '@/app/actions/messages'; +// import { getThought } from '@/app/actions/messages'; export type Reaction = 'thumbs_up' | 'thumbs_down' | null; @@ -24,22 +22,24 @@ interface MessageBoxProps { message: Message; loading?: boolean; isThoughtOpen?: boolean; - setIsThoughtsOpen: (isOpen: boolean) => void; - setThought: (thought: string) => void; + isThoughtLoading?: boolean; + thoughtError?: string | null; + setIsThoughtsOpen: (isOpen: boolean, messageId?: string | null) => void; onReactionAdded: (messageId: string, reaction: Reaction) => Promise; + onGetThought: (messageId: string) => Promise; } export default function MessageBox({ isUser, userId, - // URL, message, loading = false, isThoughtOpen, setIsThoughtsOpen, conversationId, onReactionAdded, - setThought, + onGetThought, + thoughtError, }: MessageBoxProps) { const [isThoughtLoading, setIsThoughtLoading] = useState(false); const [pendingReaction, setPendingReaction] = useState(null); @@ -73,18 +73,12 @@ export default function MessageBox({ setIsThoughtsOpen(false); return; } + setIsThoughtLoading(true); setError(null); try { - const thought = await getThought(conversationId, messageId); - - if (thought) { - setIsThoughtsOpen(true); - setThought(thought); - } else { - setError('No thought found.'); - } + await onGetThought(messageId); } catch (err) { setError('Failed to fetch thought.'); console.error(err); @@ -117,54 +111,58 @@ export default function MessageBox({
{loading ? memoizedSkeleton : } {!loading && !isUser && shouldShowButtons && ( -
- - + - + +
+ {thoughtError && ( +
+ {thoughtError}
- + )}
)} {error &&

Error: {error}

}