Files
tweakcn/hooks/use-scroll-start-end.ts
Sahaj Jain 5882f60c32 Enhance ai chat (#218)
* update package locks to avoid conflict

* Refactor AI Generation with AI SDK v5 (#210)

* refactor(wip): Whole Chat implementation

* fix: Deps

* chore: Update types and create tool to handle theme generation

* chore: Add hook and component to handle feedback text

* chore: Check for themeStyles in asssiatant metadata

* feat: Improve messages styles and feedback

* chore: Update react-hook-form deps

* chore: Improve Messages display

* chore: Show a banner when theme is generating

* chore: Optimize the Messages syncing

* feat: Add debug button to MessageActions in dev mode

* chore: messages

* chore: Improve System prompt + tools + utils

* fix: correctly display User messages with only images

* feat: Handle errors in UI and adapt to AI sdk

* feat: Use tool output instead of custom data parts

* chore: System prompt and schema context

* chore: Gemini 2.5 pro as the default model

* refactor: Change name to Chat Context and apply generated theme automatically

* chore: Update ai packages to latest version

* feat: Stop ongoing request before starting a new chat

* feat: Refactor logic for Scroll start/end sentinels

* feat: Improve UI streaming with AI Elements and Stream Text utils

* chore: Generate theme types and logic

* feat: Allow customize the speed of streaming text

* refactor: Theme generation utils

* fix: Avoid duplicated Mention references

* styles: Improve Chat error banner

* feat: Enhance Prompt core implementation

* feat: Implement enhance prompt in components

* chore: Add TODOS comments

* chore: Remove Openai provider

* chore: Update AI adk packages

* fix: Free request constant

---------

Co-authored-by: Sahaj Jain <82111591+jnsahaj@users.noreply.github.com>

* update lockfile

* fix import

* fix types

* chore: Trigger theme generation before transitioning to editor page

* refactor: Move AI sdk specific code into Lib and update imports

* chore: Provide model ID to record usage util

* refactor: Multi model provider convention

* fix types

* refactor: Use Zustand persist middleware versioning

* feat: Improve prompts and context

* feat: Optimize streaming JSONContent into text area

* chore: Improve Theme Generation prompt

* feat: Improve prompt

* feat: Add subscription checks for enhancing prompt and Posthog tracking

---------

Co-authored-by: Luis Llanes <137589205+llanesluis@users.noreply.github.com>
Co-authored-by: Luis Llanes <luisllaboj@gmail.com>
2025-10-01 16:31:10 +05:30

79 lines
2.7 KiB
TypeScript

import { useEffect, useMemo, useRef, useState } from "react";
/**
* Observes two sentinel elements (start/end) inside a scrollable container
* and reports whether the scroll position is at the start or at the end.
*
* Props:
* - (optional) containerRef: Ref to the scroll container element to be used as the observer root.
* When provided, it takes precedence over observerOptions.root.
* - (optional) observerOptions: Standard IntersectionObserver options. If no containerRef is provided,
* you may provide observerOptions.root (an Element) or omit it to default to the viewport.
*
* Root precedence: containerRef.current -> observerOptions.root -> null (viewport).
*
* Returns:
* - isScrollStart: boolean indicating the start sentinel is visible in the root
* - isScrollEnd: boolean indicating the end sentinel is visible in the root
* - scrollStartRef, scrollEndRef: attach inside the scrollable content near the start/end edges
*/
type IntersectionObserverInitWithoutRoot = Omit<IntersectionObserverInit, "root"> & {
root?: never;
};
type UseScrollStartEndProps =
| {
containerRef: React.RefObject<HTMLDivElement | null> | null;
observerOptions?: IntersectionObserverInitWithoutRoot;
}
| {
containerRef?: null | undefined;
observerOptions?: IntersectionObserverInit;
};
const defaultObserverOptions: IntersectionObserverInit = {
root: null,
threshold: 0,
rootMargin: "0px",
};
export function useScrollStartEnd({
containerRef = null,
observerOptions = defaultObserverOptions,
}: UseScrollStartEndProps = {}) {
const [isScrollStart, setIsScrollStart] = useState(false);
const [isScrollEnd, setIsScrollEnd] = useState(false);
const scrollStartRef = useRef<HTMLDivElement>(null);
const scrollEndRef = useRef<HTMLDivElement>(null);
const intersectionObserverOptions = useMemo<IntersectionObserverInit>(() => {
return {
...defaultObserverOptions,
...observerOptions,
root: containerRef?.current ?? observerOptions.root ?? null,
};
}, [observerOptions.root, observerOptions.threshold, observerOptions.rootMargin]);
useEffect(() => {
const startMarker = scrollStartRef.current;
const endMarker = scrollEndRef.current;
if (!startMarker || !endMarker) return;
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.target === startMarker) setIsScrollStart(entry.isIntersecting);
if (entry.target === endMarker) setIsScrollEnd(entry.isIntersecting);
}
}, intersectionObserverOptions);
observer.observe(startMarker);
observer.observe(endMarker);
return () => observer.disconnect();
}, [intersectionObserverOptions]);
return { isScrollStart, isScrollEnd, scrollStartRef, scrollEndRef };
}