llama-stack-mirror/llama_stack/ui/hooks/use-auto-scroll.ts
Francisco Arceo f3d5459647
feat(UI): adding MVP playground UI (#2828)
# What does this PR do?
I've been tinkering a little with a simple chat playground in the UI, so
I'm opening the PR with what's kind of a WIP.

If you look at the first commit, that includes the big part of the
changes. The rest of the files changed come from adding installing the
`shadcn` components.

Note this is missing a lot; e.g.,
- sessions
- document upload
- audio (the shadcn components install these by default from
https://shadcn-chatbot-kit.vercel.app/docs/components/chat)

I still need to wire up a lot more to make it actually fully functional
but it does basic chat using the LS Typescript Client.

Basic demo: 

<img width="1329" height="1430" alt="Image"
src="https://github.com/user-attachments/assets/917a2096-36d4-4925-b83b-f1f2cda98698"
/>

<img width="1319" height="1424" alt="Image"
src="https://github.com/user-attachments/assets/fab1583b-1c72-4bf3-baf2-405aee13c6bb"
/>


<!-- If resolving an issue, uncomment and update the line below -->
<!-- Closes #[issue-number] -->

## Test Plan
<!-- Describe the tests you ran to verify your changes with result
summaries. *Provide clear instructions so the plan can be easily
re-executed.* -->

---------

Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
2025-07-30 19:44:16 -07:00

73 lines
2 KiB
TypeScript

import { useEffect, useRef, useState } from "react"
// How many pixels from the bottom of the container to enable auto-scroll
const ACTIVATION_THRESHOLD = 50
// Minimum pixels of scroll-up movement required to disable auto-scroll
const MIN_SCROLL_UP_THRESHOLD = 10
export function useAutoScroll(dependencies: React.DependencyList) {
const containerRef = useRef<HTMLDivElement | null>(null)
const previousScrollTop = useRef<number | null>(null)
const [shouldAutoScroll, setShouldAutoScroll] = useState(true)
const scrollToBottom = () => {
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight
}
}
const handleScroll = () => {
if (containerRef.current) {
const { scrollTop, scrollHeight, clientHeight } = containerRef.current
const distanceFromBottom = Math.abs(
scrollHeight - scrollTop - clientHeight
)
const isScrollingUp = previousScrollTop.current
? scrollTop < previousScrollTop.current
: false
const scrollUpDistance = previousScrollTop.current
? previousScrollTop.current - scrollTop
: 0
const isDeliberateScrollUp =
isScrollingUp && scrollUpDistance > MIN_SCROLL_UP_THRESHOLD
if (isDeliberateScrollUp) {
setShouldAutoScroll(false)
} else {
const isScrolledToBottom = distanceFromBottom < ACTIVATION_THRESHOLD
setShouldAutoScroll(isScrolledToBottom)
}
previousScrollTop.current = scrollTop
}
}
const handleTouchStart = () => {
setShouldAutoScroll(false)
}
useEffect(() => {
if (containerRef.current) {
previousScrollTop.current = containerRef.current.scrollTop
}
}, [])
useEffect(() => {
if (shouldAutoScroll) {
scrollToBottom()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dependencies)
return {
containerRef,
scrollToBottom,
handleScroll,
shouldAutoScroll,
handleTouchStart,
}
}