diff --git a/frontend/src/components/Jupyter.tsx b/frontend/src/components/Jupyter.tsx index edc56e0ea3..296f98a0dc 100644 --- a/frontend/src/components/Jupyter.tsx +++ b/frontend/src/components/Jupyter.tsx @@ -1,10 +1,14 @@ -import React, { useEffect, useRef } from "react"; +import React, { useRef, useState } from "react"; import { useSelector } from "react-redux"; import SyntaxHighlighter from "react-syntax-highlighter"; import Markdown from "react-markdown"; import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs"; +import { VscArrowDown } from "react-icons/vsc"; +import { useTranslation } from "react-i18next"; import { RootState } from "#/store"; import { Cell } from "#/state/jupyterSlice"; +import { useScrollToBottom } from "#/hooks/useScrollToBottom"; +import { I18nKey } from "#/i18n/declaration"; interface IJupyterCell { cell: Cell; @@ -75,27 +79,49 @@ function JupyterCell({ cell }: IJupyterCell): JSX.Element { } function Jupyter(): JSX.Element { + const { t } = useTranslation(); + const { cells } = useSelector((state: RootState) => state.jupyter); const jupyterRef = useRef(null); - function scrollDomToBottom() { - const dom = jupyterRef.current; - if (dom) { - requestAnimationFrame(() => { - dom.scrollTo(0, dom.scrollHeight); - }); - } - } + const [hitBottom, setHitBottom] = useState(true); + const { setAutoScroll, scrollDomToBottom } = useScrollToBottom(jupyterRef); - useEffect(() => { - scrollDomToBottom(); - }); + const onChatBodyScroll = (e: HTMLElement) => { + const bottomHeight = e.scrollTop + e.clientHeight; + + const isHitBottom = bottomHeight >= e.scrollHeight - 10; + + setHitBottom(isHitBottom); + setAutoScroll(isHitBottom); + }; return ( -
- {cells.map((cell, index) => ( - - ))} +
+
onChatBodyScroll(e.currentTarget)} + > + {cells.map((cell, index) => ( + + ))} +
+ {!hitBottom && ( +
+ +
+ )}
); } diff --git a/frontend/src/hooks/useScrollToBottom.ts b/frontend/src/hooks/useScrollToBottom.ts new file mode 100644 index 0000000000..7ff71f1bab --- /dev/null +++ b/frontend/src/hooks/useScrollToBottom.ts @@ -0,0 +1,30 @@ +import { RefObject, useEffect, useState } from "react"; + +export function useScrollToBottom(scrollRef: RefObject) { + // for auto-scroll + + const [autoScroll, setAutoScroll] = useState(true); + function scrollDomToBottom() { + const dom = scrollRef.current; + if (dom) { + requestAnimationFrame(() => { + setAutoScroll(true); + dom.scrollTo({ top: dom.scrollHeight, behavior: "auto" }); + }); + } + } + + // auto scroll + useEffect(() => { + if (autoScroll) { + scrollDomToBottom(); + } + }); + + return { + scrollRef, + autoScroll, + setAutoScroll, + scrollDomToBottom, + }; +} diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json index 3483c97612..cc8fcf79df 100644 --- a/frontend/src/i18n/translation.json +++ b/frontend/src/i18n/translation.json @@ -353,6 +353,10 @@ "fr": "Assistant", "tr": "Gönder" }, + "CHAT_INTERFACE$TO_BOTTOM": { + "en": "To Bottom", + "zh-CN": "回到底部" + }, "SETTINGS$MODEL_TOOLTIP": { "en": "Select the language model to use.", "zh-CN": "选择要使用的语言模型",