UNPKG

remix-nlux

Version:

Remix IDE NLUX integration. Remix IDE is the leading IDE for building and deploying smart contracts on Ethereum. NLUX is a JavaScript and React library for building conversational AI experiences.

121 lines (104 loc) 4.75 kB
import {throttle} from '../../utils/throttle'; import {AutoScrollController} from './type'; import {createConversationScrollHandler} from './utils/conversationScrollHandler'; export const createAutoScrollController = ( newConversationContainer: HTMLElement, autoScroll: boolean, ): AutoScrollController => { let shouldScrollWhenGenerating: boolean = autoScroll; let conversationContainer: HTMLElement | undefined = newConversationContainer; let scrollingStickToConversationEnd: boolean = true; let activeChatSegment: {uid: string; container: HTMLElement} | undefined = undefined; const scrollHandler = throttle(createConversationScrollHandler(( { scrolledToBottom, scrollDirection, }) => { if (scrollingStickToConversationEnd) { // When the user is already at the bottom of the conversation, we stick to the bottom // and only unstick when the user scrolls up. if (scrollDirection === 'up') { scrollingStickToConversationEnd = false; } } else { // When the user is not at the bottom of the conversation, we only stick to the bottom // when the user scrolls down and reaches the bottom. if (scrollDirection === 'down' && scrolledToBottom) { scrollingStickToConversationEnd = true; } } }), 50); const initConversationContainer = (newConversationContainer: HTMLElement) => { newConversationContainer.addEventListener('scroll', scrollHandler); }; const resetConversationContainer = (oldConversationContainer?: HTMLElement) => { oldConversationContainer?.removeEventListener('scroll', scrollHandler); }; const handleDoneWithSegment = (segmentId: string) => { if (activeChatSegment?.uid === segmentId) { resizeObserver?.disconnect(); mutationObserver?.disconnect(); activeChatSegment = undefined; resizeObserver = undefined; mutationObserver = undefined; } }; let resizeObserver: ResizeObserver | undefined; let mutationObserver: MutationObserver | undefined; const scrollToBottom = () => { // Using a very large number to make sure the scroll position is set to the bottom. // The alternative is to use scrollHeight, but that would result in an extra reflow - that can // be avoided by using a very large number. // Downside: if the scrollHeight is larger than the max value (50000 pixels), this will not work. conversationContainer?.scrollTo({ top: 50000, behavior: 'instant', }); }; const handleActiveChatSegmentResized = () => { if (conversationContainer && shouldScrollWhenGenerating && scrollingStickToConversationEnd) { scrollToBottom(); } }; const handleActiveChatSegmentDomChanged = () => { handleActiveChatSegmentResized(); }; initConversationContainer(conversationContainer); return { updateConversationContainer: (newConversationContainer: HTMLElement) => { resetConversationContainer(conversationContainer); initConversationContainer(newConversationContainer); conversationContainer = newConversationContainer; }, updateProps: ({autoScroll}) => { shouldScrollWhenGenerating = autoScroll; }, handleNewChatSegmentAdded: (segmentId, segmentContainer) => { if (activeChatSegment) { resizeObserver?.disconnect(); mutationObserver?.disconnect(); } activeChatSegment = {uid: segmentId, container: segmentContainer}; resizeObserver = new ResizeObserver(handleActiveChatSegmentResized); resizeObserver.observe(segmentContainer); mutationObserver = new MutationObserver(handleActiveChatSegmentDomChanged); mutationObserver.observe(segmentContainer, { childList: true, subtree: true, characterData: true, }); if (shouldScrollWhenGenerating) { scrollToBottom(); } }, handleChatSegmentRemoved: (segmentId) => handleDoneWithSegment(segmentId), handleChatSegmentComplete: (segmentId) => handleDoneWithSegment(segmentId), destroy: () => { if (activeChatSegment) { handleDoneWithSegment(activeChatSegment.uid); activeChatSegment = undefined; } resetConversationContainer(conversationContainer); conversationContainer = undefined; }, }; };