yangdx commited on
Commit
f6989e6
·
1 Parent(s): 29b36fa

Improve scrolling logic

Browse files
lightrag_webui/src/features/RetrievalTesting.tsx CHANGED
@@ -17,6 +17,10 @@ export default function RetrievalTesting() {
17
  )
18
  const [inputValue, setInputValue] = useState('')
19
  const [isLoading, setIsLoading] = useState(false)
 
 
 
 
20
  const messagesEndRef = useRef<HTMLDivElement>(null)
21
  const messagesContainerRef = useRef<HTMLDivElement>(null)
22
 
@@ -61,6 +65,11 @@ export default function RetrievalTesting() {
61
  // Add messages to chatbox
62
  setMessages([...prevMessages, userMessage, assistantMessage])
63
 
 
 
 
 
 
64
  // Force scroll to bottom after messages are rendered
65
  setTimeout(() => {
66
  scrollToBottom(true)
@@ -72,6 +81,17 @@ export default function RetrievalTesting() {
72
 
73
  // Create a function to update the assistant's message
74
  const updateAssistantMessage = (chunk: string, isError?: boolean) => {
 
 
 
 
 
 
 
 
 
 
 
75
  assistantMessage.content += chunk
76
  setMessages((prev) => {
77
  const newMessages = [...prev]
@@ -82,8 +102,20 @@ export default function RetrievalTesting() {
82
  }
83
  return newMessages
84
  })
85
- // Don't force scroll when updating with new chunks
86
- scrollToBottom(false)
 
 
 
 
 
 
 
 
 
 
 
 
87
  }
88
 
89
  // Prepare query parameters
@@ -128,6 +160,28 @@ export default function RetrievalTesting() {
128
  [inputValue, isLoading, messages, setMessages, t, scrollToBottom]
129
  )
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  const debouncedMessages = useDebounce(messages, 100)
132
  useEffect(() => scrollToBottom(false), [debouncedMessages, scrollToBottom])
133
 
 
17
  )
18
  const [inputValue, setInputValue] = useState('')
19
  const [isLoading, setIsLoading] = useState(false)
20
+ // Reference to track if we should follow scroll during streaming (using ref for synchronous updates)
21
+ const shouldFollowScrollRef = useRef(true)
22
+ // Reference to track if this is the first chunk of a streaming response
23
+ const isFirstChunkRef = useRef(true)
24
  const messagesEndRef = useRef<HTMLDivElement>(null)
25
  const messagesContainerRef = useRef<HTMLDivElement>(null)
26
 
 
65
  // Add messages to chatbox
66
  setMessages([...prevMessages, userMessage, assistantMessage])
67
 
68
+ // Reset first chunk flag for new streaming response
69
+ isFirstChunkRef.current = true
70
+ // Enable follow scroll for new query
71
+ shouldFollowScrollRef.current = true
72
+
73
  // Force scroll to bottom after messages are rendered
74
  setTimeout(() => {
75
  scrollToBottom(true)
 
81
 
82
  // Create a function to update the assistant's message
83
  const updateAssistantMessage = (chunk: string, isError?: boolean) => {
84
+ // Check if this is the first chunk of the streaming response
85
+ if (isFirstChunkRef.current) {
86
+ // Determine scroll behavior based on initial position
87
+ shouldFollowScrollRef.current = isNearBottom();
88
+ isFirstChunkRef.current = false;
89
+ }
90
+
91
+ // Save current scroll position before updating content
92
+ const container = messagesContainerRef.current;
93
+ const currentScrollPosition = container ? container.scrollTop : 0;
94
+
95
  assistantMessage.content += chunk
96
  setMessages((prev) => {
97
  const newMessages = [...prev]
 
102
  }
103
  return newMessages
104
  })
105
+
106
+ // After updating content, check if we should scroll
107
+ // Use consistent scrolling behavior throughout the streaming response
108
+ if (shouldFollowScrollRef.current) {
109
+ scrollToBottom(true);
110
+ } else if (container) {
111
+ // If user was not near bottom, restore their scroll position
112
+ // This needs to be in a setTimeout to work after React updates the DOM
113
+ setTimeout(() => {
114
+ if (container) {
115
+ container.scrollTop = currentScrollPosition;
116
+ }
117
+ }, 0);
118
+ }
119
  }
120
 
121
  // Prepare query parameters
 
160
  [inputValue, isLoading, messages, setMessages, t, scrollToBottom]
161
  )
162
 
163
+ // Add scroll event listener to detect when user manually scrolls
164
+ useEffect(() => {
165
+ const container = messagesContainerRef.current;
166
+ if (!container) return;
167
+
168
+ const handleScroll = () => {
169
+ const isNearBottomNow = isNearBottom();
170
+
171
+ // If user scrolls away from bottom while in auto-scroll mode, disable it
172
+ if (shouldFollowScrollRef.current && !isNearBottomNow) {
173
+ shouldFollowScrollRef.current = false;
174
+ }
175
+ // If user scrolls back to bottom while not in auto-scroll mode, re-enable it
176
+ else if (!shouldFollowScrollRef.current && isNearBottomNow) {
177
+ shouldFollowScrollRef.current = true;
178
+ }
179
+ };
180
+
181
+ container.addEventListener('scroll', handleScroll);
182
+ return () => container.removeEventListener('scroll', handleScroll);
183
+ }, [isNearBottom]); // Remove shouldFollowScroll from dependencies since we're using ref now
184
+
185
  const debouncedMessages = useDebounce(messages, 100)
186
  useEffect(() => scrollToBottom(false), [debouncedMessages, scrollToBottom])
187