diff --git a/frontend/src/app/chat/page.tsx b/frontend/src/app/chat/page.tsx index 333faf22..20ee66ca 100644 --- a/frontend/src/app/chat/page.tsx +++ b/frontend/src/app/chat/page.tsx @@ -2,6 +2,7 @@ import { Loader2, Zap } from "lucide-react"; import { useEffect, useRef, useState } from "react"; +import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom"; import { ProtectedRoute } from "@/components/protected-route"; import { Button } from "@/components/ui/button"; import { type EndpointType, useChat } from "@/contexts/chat-context"; @@ -71,8 +72,10 @@ function ChatPage() { x: number; y: number; } | null>(null); - const messagesEndRef = useRef(null); const chatInputRef = useRef(null); + + const { scrollToBottom } = useStickToBottomContext(); + const lastLoadedConversationRef = useRef(null); const { addTask } = useTask(); const { selectedFilter, parsedFilterData, setSelectedFilter } = @@ -82,7 +85,6 @@ function ChatPage() { const apiEndpoint = endpoint === "chat" ? "/api/chat" : "/api/langflow"; const { streamingMessage, - isLoading: isStreamingLoading, sendMessage: sendStreamingMessage, abortStream, } = useChatStreaming({ @@ -90,7 +92,7 @@ function ChatPage() { onComplete: (message, responseId) => { setMessages((prev) => [...prev, message]); setLoading(false); - + if (responseId) { cancelNudges(); setPreviousResponseIds((prev) => ({ @@ -119,10 +121,6 @@ function ChatPage() { }, }); - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }; - const getCursorPosition = (textarea: HTMLTextAreaElement) => { // Create a hidden div with the same styles as the textarea const div = document.createElement("div"); @@ -357,21 +355,10 @@ function ChatPage() { } }; - useEffect(() => { - // Only auto-scroll if not in the middle of user interaction - if (!isUserInteracting) { - const timer = setTimeout(() => { - scrollToBottom(); - }, 50); // Small delay to avoid conflicts with click events - - return () => clearTimeout(timer); - } - }, [messages, streamingMessage, isUserInteracting]); - // Reset selected index when search term changes useEffect(() => { setSelectedFilterIndex(0); - }, [filterSearchTerm]); + }, []); // Auto-focus the input on component mount useEffect(() => { @@ -408,7 +395,7 @@ function ChatPage() { window.removeEventListener("newConversation", handleNewConversation); window.removeEventListener("focusInput", handleFocusInput); }; - }, [abortStream]); + }, [abortStream, setLoading]); // Load conversation only when user explicitly selects a conversation useEffect(() => { @@ -709,7 +696,7 @@ function ChatPage() { handleFileUploadError as EventListener, ); }; - }, [endpoint, setPreviousResponseIds]); + }, [endpoint, setPreviousResponseIds, setLoading]); const { data: nudges = [], cancel: cancelNudges } = useGetNudgesQuery( previousResponseIds[endpoint], @@ -749,6 +736,10 @@ function ChatPage() { limit: parsedFilterData?.limit ?? 10, scoreThreshold: parsedFilterData?.scoreThreshold ?? 0, }); + scrollToBottom({ + animation: "smooth", + duration: 1000, + }); }; const handleSendMessage = async (inputMessage: string) => { @@ -765,6 +756,11 @@ function ChatPage() { setLoading(true); setIsFilterHighlighted(false); + scrollToBottom({ + animation: "smooth", + duration: 1000, + }); + if (asyncMode) { await handleSSEStream(userMessage); } else { @@ -1113,66 +1109,57 @@ function ChatPage() { } }; - return ( -
- {/* Debug header - only show in debug mode */} - {isDebugMode && ( -
-
-
- {/* Async Mode Toggle */} -
- - -
- {/* Endpoint Toggle */} -
- - + return (<> + {/* Debug header - only show in debug mode */} + {isDebugMode && ( +
+
+
+ {/* Async Mode Toggle */} +
+ + +
+ {/* Endpoint Toggle */} +
+ + +
-
- )} + )} -
-
- {/* Messages Area */} -
+ +
{messages.length === 0 && !streamingMessage ? (
@@ -1190,7 +1177,12 @@ function ChatPage() { ) : ( <> {messages.map((message, index) => ( -
+
{message.role === "user" && ( )} @@ -1220,55 +1212,58 @@ function ChatPage() { isStreaming /> )} -
)} + {!streamingMessage && ( + + )}
-
-
+ - {/* Suggestion chips - always show unless streaming */} - {!streamingMessage && ( - - )} - - {/* Input Area - Fixed at bottom */} - setTextareaHeight(height)} - onFilterSelect={handleFilterSelect} - onAtClick={onAtClick} - onFilePickerChange={handleFilePickerChange} - onFilePickerClick={handleFilePickerClick} - setSelectedFilter={setSelectedFilter} - setIsFilterHighlighted={setIsFilterHighlighted} - setIsFilterDropdownOpen={setIsFilterDropdownOpen} - /> -
+ {/* Input Area - Fixed at bottom */} + setTextareaHeight(height)} + onFilterSelect={handleFilterSelect} + onAtClick={onAtClick} + onFilePickerChange={handleFilePickerChange} + onFilePickerClick={handleFilePickerClick} + setSelectedFilter={setSelectedFilter} + setIsFilterHighlighted={setIsFilterHighlighted} + setIsFilterDropdownOpen={setIsFilterDropdownOpen} + /> ); } export default function ProtectedChatPage() { return ( +
+ +
); }