Skip to content

Commit bd35dda

Browse files
Sg312emir-karabeg
authored andcommitted
Fix thinking scroll
1 parent b2b06c3 commit bd35dda

File tree

2 files changed

+127
-14
lines changed
  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components

2 files changed

+127
-14
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/thinking-block.tsx

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,12 @@ export function ThinkingBlock({
5454
}: ThinkingBlockProps) {
5555
const [isExpanded, setIsExpanded] = useState(false)
5656
const [duration, setDuration] = useState(0)
57+
const [userHasScrolledAway, setUserHasScrolledAway] = useState(false)
5758
const userCollapsedRef = useRef<boolean>(false)
5859
const scrollContainerRef = useRef<HTMLDivElement>(null)
5960
const startTimeRef = useRef<number>(Date.now())
61+
const lastScrollTopRef = useRef(0)
62+
const programmaticScrollRef = useRef(false)
6063

6164
/**
6265
* Auto-expands block when streaming with content
@@ -67,6 +70,7 @@ export function ThinkingBlock({
6770
if (!isStreaming || hasFollowingContent) {
6871
setIsExpanded(false)
6972
userCollapsedRef.current = false
73+
setUserHasScrolledAway(false)
7074
return
7175
}
7276

@@ -80,6 +84,7 @@ export function ThinkingBlock({
8084
if (isStreaming && !hasFollowingContent) {
8185
startTimeRef.current = Date.now()
8286
setDuration(0)
87+
setUserHasScrolledAway(false)
8388
}
8489
}, [isStreaming, hasFollowingContent])
8590

@@ -95,22 +100,65 @@ export function ThinkingBlock({
95100
return () => clearInterval(interval)
96101
}, [isStreaming, hasFollowingContent])
97102

98-
// Auto-scroll to bottom during streaming using interval (same as copilot chat)
103+
// Handle scroll events to detect user scrolling away
99104
useEffect(() => {
100-
if (!isStreaming || !isExpanded) return
105+
const container = scrollContainerRef.current
106+
if (!container || !isExpanded) return
107+
108+
const handleScroll = () => {
109+
if (programmaticScrollRef.current) return
110+
111+
const { scrollTop, scrollHeight, clientHeight } = container
112+
const distanceFromBottom = scrollHeight - scrollTop - clientHeight
113+
const isNearBottom = distanceFromBottom <= 20
114+
115+
const delta = scrollTop - lastScrollTopRef.current
116+
const movedUp = delta < -2
117+
118+
if (movedUp && !isNearBottom) {
119+
setUserHasScrolledAway(true)
120+
}
121+
122+
// Re-stick if user scrolls back to bottom
123+
if (userHasScrolledAway && isNearBottom) {
124+
setUserHasScrolledAway(false)
125+
}
126+
127+
lastScrollTopRef.current = scrollTop
128+
}
129+
130+
container.addEventListener('scroll', handleScroll, { passive: true })
131+
lastScrollTopRef.current = container.scrollTop
132+
133+
return () => container.removeEventListener('scroll', handleScroll)
134+
}, [isExpanded, userHasScrolledAway])
135+
136+
// Smart auto-scroll: only scroll if user hasn't scrolled away
137+
useEffect(() => {
138+
if (!isStreaming || !isExpanded || userHasScrolledAway) return
101139

102140
const intervalId = window.setInterval(() => {
103141
const container = scrollContainerRef.current
104142
if (!container) return
105143

106-
container.scrollTo({
107-
top: container.scrollHeight,
108-
behavior: 'smooth',
109-
})
144+
const { scrollTop, scrollHeight, clientHeight } = container
145+
const distanceFromBottom = scrollHeight - scrollTop - clientHeight
146+
const isNearBottom = distanceFromBottom <= 50
147+
148+
if (isNearBottom) {
149+
programmaticScrollRef.current = true
150+
container.scrollTo({
151+
top: container.scrollHeight,
152+
behavior: 'smooth',
153+
})
154+
window.setTimeout(() => {
155+
programmaticScrollRef.current = false
156+
}, 150)
157+
}
110158
}, SCROLL_INTERVAL)
111159

112160
return () => window.clearInterval(intervalId)
113-
}, [isStreaming, isExpanded])
161+
}, [isStreaming, isExpanded, userHasScrolledAway])
114162

115163
/**
116164
* Formats duration in milliseconds to seconds
@@ -137,6 +185,14 @@ export function ThinkingBlock({
137185
if (!isThinkingDone) {
138186
return (
139187
<div className='mt-1 mb-0'>
188+
{/* Define shimmer keyframes */}
189+
<style>{`
190+
@keyframes thinking-shimmer {
191+
0% { background-position: 150% 0; }
192+
50% { background-position: 0% 0; }
193+
100% { background-position: -150% 0; }
194+
}
195+
`}</style>
140196
<button
141197
onClick={() => {
142198
setIsExpanded((v) => {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -855,8 +855,11 @@ function SubAgentContent({
855855
toolName?: string
856856
}) {
857857
const [isExpanded, setIsExpanded] = useState(false)
858+
const [userHasScrolledAway, setUserHasScrolledAway] = useState(false)
858859
const userCollapsedRef = useRef<boolean>(false)
859860
const scrollContainerRef = useRef<HTMLDivElement>(null)
861+
const lastScrollTopRef = useRef(0)
862+
const programmaticScrollRef = useRef(false)
860863

861864
// Check if there are any tool calls (which means thinking should close)
862865
const hasToolCalls = useMemo(() => {
@@ -869,6 +872,7 @@ function SubAgentContent({
869872
if (!isStreaming || hasToolCalls) {
870873
setIsExpanded(false)
871874
userCollapsedRef.current = false
875+
setUserHasScrolledAway(false)
872876
return
873877
}
874878

@@ -877,22 +881,65 @@ function SubAgentContent({
877881
}
878882
}, [isStreaming, blocks, hasToolCalls])
879883

880-
// Auto-scroll to bottom during streaming using interval (same as copilot chat)
884+
// Handle scroll events to detect user scrolling away
881885
useEffect(() => {
882-
if (!isStreaming || !isExpanded) return
886+
const container = scrollContainerRef.current
887+
if (!container || !isExpanded) return
888+
889+
const handleScroll = () => {
890+
if (programmaticScrollRef.current) return
891+
892+
const { scrollTop, scrollHeight, clientHeight } = container
893+
const distanceFromBottom = scrollHeight - scrollTop - clientHeight
894+
const isNearBottom = distanceFromBottom <= 20
895+
896+
const delta = scrollTop - lastScrollTopRef.current
897+
const movedUp = delta < -2
898+
899+
if (movedUp && !isNearBottom) {
900+
setUserHasScrolledAway(true)
901+
}
902+
903+
// Re-stick if user scrolls back to bottom
904+
if (userHasScrolledAway && isNearBottom) {
905+
setUserHasScrolledAway(false)
906+
}
907+
908+
lastScrollTopRef.current = scrollTop
909+
}
910+
911+
container.addEventListener('scroll', handleScroll, { passive: true })
912+
lastScrollTopRef.current = container.scrollTop
913+
914+
return () => container.removeEventListener('scroll', handleScroll)
915+
}, [isExpanded, userHasScrolledAway])
916+
917+
// Smart auto-scroll: only scroll if user hasn't scrolled away
918+
useEffect(() => {
919+
if (!isStreaming || !isExpanded || userHasScrolledAway) return
883920

884921
const intervalId = window.setInterval(() => {
885922
const container = scrollContainerRef.current
886923
if (!container) return
887924

888-
container.scrollTo({
889-
top: container.scrollHeight,
890-
behavior: 'smooth',
891-
})
925+
const { scrollTop, scrollHeight, clientHeight } = container
926+
const distanceFromBottom = scrollHeight - scrollTop - clientHeight
927+
const isNearBottom = distanceFromBottom <= 50
928+
929+
if (isNearBottom) {
930+
programmaticScrollRef.current = true
931+
container.scrollTo({
932+
top: container.scrollHeight,
933+
behavior: 'smooth',
934+
})
935+
window.setTimeout(() => {
936+
programmaticScrollRef.current = false
937+
}, 150)
938+
}
892939
}, SUBAGENT_SCROLL_INTERVAL)
893940

894941
return () => window.clearInterval(intervalId)
895-
}, [isStreaming, isExpanded])
942+
}, [isStreaming, isExpanded, userHasScrolledAway])
896943

897944
if (!blocks || blocks.length === 0) return null
898945

@@ -903,6 +950,16 @@ function SubAgentContent({
903950

904951
return (
905952
<div className='mt-1 mb-0'>
953+
{/* Define shimmer keyframes */}
954+
{!isThinkingDone && (
955+
<style>{`
956+
@keyframes thinking-shimmer {
957+
0% { background-position: 150% 0; }
958+
50% { background-position: 0% 0; }
959+
100% { background-position: -150% 0; }
960+
}
961+
`}</style>
962+
)}
906963
<button
907964
onClick={() => {
908965
setIsExpanded((v) => {

0 commit comments

Comments
 (0)