Skip to content

Commit eaa019d

Browse files
committed
Improve auto-scroll following of token stream
1 parent 9a92db6 commit eaa019d

File tree

1 file changed

+27
-3
lines changed
  • genai-cookbook/packages/recipes/src/multiturn-chat

1 file changed

+27
-3
lines changed

genai-cookbook/packages/recipes/src/multiturn-chat/ui.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,30 +60,54 @@ export default function Recipe({ endpoint, model, pathname }: RecipeProps) {
6060
const [followStream, setFollowStream] = useState(true)
6161
const viewportRef = useRef<HTMLDivElement>(null)
6262
const bottomRef = useRef<HTMLDivElement>(null)
63+
const isAutoScrolling = useRef(false)
6364

6465
// Whenever a new message arrives, keep the latest tokens in view unless
6566
// we've intentionally scrolled upward to review earlier context.
6667
useEffect(() => {
6768
if (!followStream) return
69+
70+
// Mark that we're about to programmatically scroll
71+
isAutoScrolling.current = true
6872
bottomRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })
73+
74+
// Clear the flag after scroll animation starts (~100ms is enough)
75+
const timer = setTimeout(() => {
76+
isAutoScrolling.current = false
77+
}, 100)
78+
79+
return () => clearTimeout(timer)
6980
}, [messages, followStream])
7081

7182
return (
7283
<>
7384
<Box style={{ flex: 1, minHeight: 0 }}>
7485
<ScrollArea
75-
// Mantine exposes scroll info through a forwarded ref so we can detect manual scrolling.
86+
// Mantine exposes scroll info through a forwarded ref
87+
// so we can detect manual scrolling.
7688
h="100%"
7789
type="auto"
7890
viewportRef={viewportRef}
7991
onScrollPositionChange={() => {
8092
const el = viewportRef.current
8193
if (!el) return
94+
8295
const distanceToBottom =
8396
el.scrollHeight - el.scrollTop - el.clientHeight
84-
const nearBottomThreshold = 8 // px
97+
const nearBottomThreshold = 20 // px
8598
const nearBottom = distanceToBottom <= nearBottomThreshold
8699

100+
// If user has clearly scrolled away (>50px from bottom),
101+
// they want to stop following - even during auto-scroll
102+
if (isAutoScrolling.current && distanceToBottom > 50) {
103+
isAutoScrolling.current = false
104+
setFollowStream(false)
105+
return
106+
}
107+
108+
// Don't interfere with programmatic auto-scroll when close to bottom
109+
if (isAutoScrolling.current) return
110+
87111
// Pause auto-follow when the user scrolls up to read older content.
88112
setFollowStream(nearBottom)
89113
}}
@@ -104,7 +128,7 @@ export default function Recipe({ endpoint, model, pathname }: RecipeProps) {
104128
}
105129

106130
// ============================================================================
107-
// Message panel types and components
131+
// Message panel
108132
// ============================================================================
109133

110134
/*

0 commit comments

Comments
 (0)