From edd83c0908a3e2218f224bd3e6983c5fde628278 Mon Sep 17 00:00:00 2001 From: gq97a6 Date: Fri, 25 Apr 2025 03:50:04 +0200 Subject: [PATCH 1/3] Add auto scroll feature locales. --- app/locales/ar.ts | 5 +++++ app/locales/bn.ts | 5 +++++ app/locales/cn.ts | 4 ++++ app/locales/cs.ts | 5 +++++ app/locales/da.ts | 5 +++++ app/locales/de.ts | 5 +++++ app/locales/en.ts | 7 ++++++- app/locales/es.ts | 5 +++++ app/locales/fr.ts | 5 +++++ app/locales/id.ts | 5 +++++ app/locales/it.ts | 5 +++++ app/locales/jp.ts | 5 +++++ app/locales/ko.ts | 5 +++++ app/locales/no.ts | 5 +++++ app/locales/pt.ts | 5 +++++ app/locales/ru.ts | 5 +++++ app/locales/sk.ts | 5 +++++ app/locales/tr.ts | 5 +++++ app/locales/tw.ts | 4 ++++ app/locales/vi.ts | 5 +++++ 20 files changed, 99 insertions(+), 1 deletion(-) diff --git a/app/locales/ar.ts b/app/locales/ar.ts index e2fad3494..75e34d6aa 100644 --- a/app/locales/ar.ts +++ b/app/locales/ar.ts @@ -199,6 +199,11 @@ const ar: PartialLocaleType = { Title: "توليد العنوان تلقائيًا", SubTitle: "توليد عنوان مناسب بناءً على محتوى الدردشة", }, + AutoScroll: { + Title: "تفعيل التمرير التلقائي", + SubTitle: + "التمرير التلقائي للدردشة إلى الأسفل عند التركيز على منطقة النص أو إرسال الرسالة", + }, Sync: { CloudState: "بيانات السحابة", NotSyncYet: "لم يتم التزامن بعد", diff --git a/app/locales/bn.ts b/app/locales/bn.ts index f52f101ce..1f05c4d76 100644 --- a/app/locales/bn.ts +++ b/app/locales/bn.ts @@ -200,6 +200,11 @@ const bn: PartialLocaleType = { Title: "স্বয়ংক্রিয় শিরোনাম জেনারেশন", SubTitle: "চ্যাট কনটেন্টের ভিত্তিতে উপযুক্ত শিরোনাম তৈরি করুন", }, + AutoScroll: { + Title: "অটো স্ক্রল সক্ষম করুন", + SubTitle: + "টেক্সট এরিয়া ফোকাস বা মেসেজ সাবমিটে স্বয়ংক্রিয়ভাবে চ্যাট নিচে স্ক্রল করুন", + }, Sync: { CloudState: "ক্লাউড ডেটা", NotSyncYet: "এখনো সিঙ্ক করা হয়নি", diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 81b609cde..4d5d55a59 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -220,6 +220,10 @@ const cn = { Title: "自动生成标题", SubTitle: "根据对话内容生成合适的标题", }, + AutoScroll: { + Title: "启用自动滚动", + SubTitle: "在文本区域聚焦或提交消息时自动滚动聊天到底部", + }, Sync: { CloudState: "云端数据", NotSyncYet: "还没有进行过同步", diff --git a/app/locales/cs.ts b/app/locales/cs.ts index d62a20367..5835a553a 100644 --- a/app/locales/cs.ts +++ b/app/locales/cs.ts @@ -200,6 +200,11 @@ const cs: PartialLocaleType = { Title: "Automatické generování názvu", SubTitle: "Generovat vhodný název na základě obsahu konverzace", }, + AutoScroll: { + Title: "Povolit automatické posouvání", + SubTitle: + "Automaticky posunout chat dolů při zaměření na textové pole nebo odeslání zprávy", + }, Sync: { CloudState: "Data na cloudu", NotSyncYet: "Ještě nebylo synchronizováno", diff --git a/app/locales/da.ts b/app/locales/da.ts index 7090b062b..3d48563ce 100644 --- a/app/locales/da.ts +++ b/app/locales/da.ts @@ -219,6 +219,11 @@ const da: PartialLocaleType = { Title: "Lav titel automatisk", SubTitle: "Foreslå en titel ud fra chatten", }, + AutoScroll: { + Title: "Aktivér automatisk rulning", + SubTitle: + "Rul automatisk chatten til bunden ved fokus på tekstfelt eller afsendelse af besked", + }, Sync: { CloudState: "Seneste opdatering", NotSyncYet: "Endnu ikke synkroniseret", diff --git a/app/locales/de.ts b/app/locales/de.ts index 3490190a8..8c6e65252 100644 --- a/app/locales/de.ts +++ b/app/locales/de.ts @@ -205,6 +205,11 @@ const de: PartialLocaleType = { SubTitle: "Basierend auf dem Chat-Inhalt einen passenden Titel generieren", }, + AutoScroll: { + Title: "Automatisches Scrollen aktivieren", + SubTitle: + "Chat automatisch nach unten scrollen bei Fokus auf Texteingabe oder Nachrichtensenden", + }, Sync: { CloudState: "Cloud-Daten", NotSyncYet: "Noch nicht synchronisiert", diff --git a/app/locales/en.ts b/app/locales/en.ts index 8fecf8bf7..222f09926 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -222,6 +222,11 @@ const en: LocaleType = { Title: "Auto Generate Title", SubTitle: "Generate a suitable title based on the conversation content", }, + AutoScroll: { + Title: "Enable Auto Scroll", + SubTitle: + "Automatically scroll chat to bottom on text area focus or message submit", + }, Sync: { CloudState: "Last Update", NotSyncYet: "Not sync yet", @@ -756,7 +761,7 @@ const en: LocaleType = { }, Artifacts: { Title: "Enable Artifacts", - SubTitle: "Can render HTML page when enable artifacts.", + SubTitle: "Can render HTML page when enable artifacts", }, CodeFold: { Title: "Enable CodeFold", diff --git a/app/locales/es.ts b/app/locales/es.ts index 03af9b439..0a11d2527 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -208,6 +208,11 @@ const es: PartialLocaleType = { Title: "Generar título automáticamente", SubTitle: "Generar un título adecuado basado en el contenido del chat", }, + AutoScroll: { + Title: "Habilitar desplazamiento automático", + SubTitle: + "Desplazar el chat automáticamente hacia abajo al enfocar el área de texto o enviar un mensaje", + }, Sync: { CloudState: "Datos en la nube", NotSyncYet: "Aún no se ha sincronizado", diff --git a/app/locales/fr.ts b/app/locales/fr.ts index d25c60eb6..16f3b74a9 100644 --- a/app/locales/fr.ts +++ b/app/locales/fr.ts @@ -207,6 +207,11 @@ const fr: PartialLocaleType = { SubTitle: "Générer un titre approprié en fonction du contenu de la discussion", }, + AutoScroll: { + Title: "Activer le défilement automatique", + SubTitle: + "Faire défiler automatiquement le chat vers le bas lors du focus sur la zone de texte ou de l'envoi d'un message", + }, Sync: { CloudState: "Données cloud", NotSyncYet: "Pas encore synchronisé", diff --git a/app/locales/id.ts b/app/locales/id.ts index af96fd272..b286c8736 100644 --- a/app/locales/id.ts +++ b/app/locales/id.ts @@ -201,6 +201,11 @@ const id: PartialLocaleType = { Title: "Otomatis Membuat Judul", SubTitle: "Membuat judul yang sesuai berdasarkan konten obrolan", }, + AutoScroll: { + Title: "Aktifkan Gulir Otomatis", + SubTitle: + "Secara otomatis gulir obrolan ke bawah saat area teks difokuskan atau pesan dikirim", + }, Sync: { CloudState: "Data Cloud", NotSyncYet: "Belum disinkronkan", diff --git a/app/locales/it.ts b/app/locales/it.ts index 59bc1eb15..66c3416ba 100644 --- a/app/locales/it.ts +++ b/app/locales/it.ts @@ -209,6 +209,11 @@ const it: PartialLocaleType = { SubTitle: "Genera un titolo appropriato in base al contenuto della conversazione", }, + AutoScroll: { + Title: "Abilita lo scorrimento automatico", + SubTitle: + "Scorri automaticamente la chat in basso quando si seleziona l'area di testo o si invia un messaggio", + }, Sync: { CloudState: "Dati cloud", NotSyncYet: "Non è ancora avvenuta alcuna sincronizzazione", diff --git a/app/locales/jp.ts b/app/locales/jp.ts index e7c81e186..cfd5585e7 100644 --- a/app/locales/jp.ts +++ b/app/locales/jp.ts @@ -200,6 +200,11 @@ const jp: PartialLocaleType = { Title: "自動タイトル生成", SubTitle: "チャット内容に基づいて適切なタイトルを生成", }, + AutoScroll: { + Title: "自動スクロールを有効にする", + SubTitle: + "テキストエリアにフォーカスするか、メッセージを送信するとチャットが自動で下にスクロールされます", + }, Sync: { CloudState: "クラウドデータ", NotSyncYet: "まだ同期されていません", diff --git a/app/locales/ko.ts b/app/locales/ko.ts index f2c433b76..a15aa4aa9 100644 --- a/app/locales/ko.ts +++ b/app/locales/ko.ts @@ -199,6 +199,11 @@ const ko: PartialLocaleType = { Title: "제목 자동 생성", SubTitle: "대화 내용에 따라 적절한 제목 생성", }, + AutoScroll: { + Title: "자동 스크롤 활성화", + SubTitle: + "텍스트 영역에 포커스하거나 메시지를 전송하면 채팅이 자동으로 아래로 스크롤됩니다", + }, Sync: { CloudState: "클라우드 데이터", NotSyncYet: "아직 동기화되지 않았습니다.", diff --git a/app/locales/no.ts b/app/locales/no.ts index f056ef12f..015cba54e 100644 --- a/app/locales/no.ts +++ b/app/locales/no.ts @@ -206,6 +206,11 @@ const no: PartialLocaleType = { Title: "Automatisk generere tittel", SubTitle: "Generer en passende tittel basert på samtaleinnholdet", }, + AutoScroll: { + Title: "Aktiver automatisk rulling", + SubTitle: + "Rull automatisk chatten til bunnen ved fokus på tekstfelt eller sending av melding", + }, Sync: { CloudState: "Skydatasynkronisering", NotSyncYet: "Har ikke blitt synkronisert ennå", diff --git a/app/locales/pt.ts b/app/locales/pt.ts index 152f50228..2cbd59dd3 100644 --- a/app/locales/pt.ts +++ b/app/locales/pt.ts @@ -199,6 +199,11 @@ const pt: PartialLocaleType = { Title: "Gerar Título Automaticamente", SubTitle: "Gerar um título adequado baseado no conteúdo da conversa", }, + AutoScroll: { + Title: "Ativar rolagem automática", + SubTitle: + "Rolar automaticamente o chat para baixo ao focar na área de texto ou enviar uma mensagem", + }, Sync: { CloudState: "Última Atualização", NotSyncYet: "Ainda não sincronizado", diff --git a/app/locales/ru.ts b/app/locales/ru.ts index 4294a3b34..e23c173ef 100644 --- a/app/locales/ru.ts +++ b/app/locales/ru.ts @@ -202,6 +202,11 @@ const ru: PartialLocaleType = { Title: "Автоматическое создание заголовка", SubTitle: "Создание подходящего заголовка на основе содержания беседы", }, + AutoScroll: { + Title: "Включить автопрокрутку", + SubTitle: + "Автоматически прокручивать чат вниз при фокусе на текстовом поле или отправке сообщения", + }, Sync: { CloudState: "Облачные данные", NotSyncYet: "Синхронизация еще не проводилась", diff --git a/app/locales/sk.ts b/app/locales/sk.ts index 36454de75..799c44999 100644 --- a/app/locales/sk.ts +++ b/app/locales/sk.ts @@ -200,6 +200,11 @@ const sk: PartialLocaleType = { Title: "Automaticky generovať názov", SubTitle: "Generovať vhodný názov na základe obsahu konverzácie", }, + AutoScroll: { + Title: "Povoliť automatické posúvanie", + SubTitle: + "Automaticky posunúť chat nadol pri zameraní na textové pole alebo odoslaní správy", + }, Sync: { CloudState: "Posledná aktualizácia", NotSyncYet: "Zatiaľ nesynchronizované", diff --git a/app/locales/tr.ts b/app/locales/tr.ts index 2082488a5..91b1cd814 100644 --- a/app/locales/tr.ts +++ b/app/locales/tr.ts @@ -200,6 +200,11 @@ const tr: PartialLocaleType = { Title: "Başlığı Otomatik Oluştur", SubTitle: "Sohbet içeriğine göre uygun başlık oluştur", }, + AutoScroll: { + Title: "Otomatik Kaydırmayı Etkinleştir", + SubTitle: + "Metin alanına odaklanıldığında veya mesaj gönderildiğinde sohbeti otomatik olarak aşağı kaydır", + }, Sync: { CloudState: "Bulut Verisi", NotSyncYet: "Henüz senkronize edilmedi", diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 83dd547b8..e4f580c54 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -207,6 +207,10 @@ const tw = { Title: "自動產生標題", SubTitle: "根據對話內容產生合適的標題", }, + AutoScroll: { + Title: "啟用自動捲動", + SubTitle: "在文字區域聚焦或送出訊息時,自動將聊天捲動至底部", + }, Sync: { CloudState: "雲端資料", NotSyncYet: "還沒有進行過同步", diff --git a/app/locales/vi.ts b/app/locales/vi.ts index c53baf35d..ac6f26525 100644 --- a/app/locales/vi.ts +++ b/app/locales/vi.ts @@ -200,6 +200,11 @@ const vi: PartialLocaleType = { Title: "Tự động tạo tiêu đề", SubTitle: "Tạo tiêu đề phù hợp dựa trên nội dung cuộc trò chuyện", }, + AutoScroll: { + Title: "Bật Tự động Cuộn", + SubTitle: + "Tự động cuộn cuộc trò chuyện xuống dưới khi tập trung vào vùng văn bản hoặc gửi tin nhắn", + }, Sync: { CloudState: "Dữ liệu đám mây", NotSyncYet: "Chưa thực hiện đồng bộ", From 98e323af4ce96e6e6efe457fe3e480e58cce4679 Mon Sep 17 00:00:00 2001 From: gq97a6 Date: Fri, 25 Apr 2025 03:50:45 +0200 Subject: [PATCH 2/3] Add auto scroll configuration to settings. --- app/components/settings.tsx | 17 +++++++++++++++++ app/store/config.ts | 2 ++ 2 files changed, 19 insertions(+) diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 68ebcf084..a43bedebd 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -1699,6 +1699,23 @@ export function Settings() { } > + + + updateConfig( + (config) => + (config.enableAutoScroll = e.currentTarget.checked), + ) + } + > + diff --git a/app/store/config.ts b/app/store/config.ts index 45e21b026..061fed971 100644 --- a/app/store/config.ts +++ b/app/store/config.ts @@ -55,6 +55,8 @@ export const DEFAULT_CONFIG = { enableCodeFold: true, // code fold config + enableAutoScroll: true, // auto scroll config + disablePromptHint: false, dontShowMaskSplashScreen: false, // dont show splash screen when create chat From 520c2850b40a24f3c4b5fc4d7c14408d0a63ccbc Mon Sep 17 00:00:00 2001 From: gq97a6 Date: Fri, 25 Apr 2025 03:58:01 +0200 Subject: [PATCH 3/3] Refactor auto scroll functionality. --- app/components/chat.tsx | 125 +++++++++++++--------------------------- 1 file changed, 39 insertions(+), 86 deletions(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 6691403e6..fb3170bda 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -1,7 +1,6 @@ import { useDebouncedCallback } from "use-debounce"; import React, { Fragment, - RefObject, useCallback, useEffect, useMemo, @@ -450,53 +449,12 @@ export function ChatAction(props: { ); } -function useScrollToBottom( - scrollRef: RefObject, - detach: boolean = false, - messages: ChatMessage[], -) { - // for auto-scroll - const [autoScroll, setAutoScroll] = useState(true); - const scrollDomToBottom = useCallback(() => { - const dom = scrollRef.current; - if (dom) { - requestAnimationFrame(() => { - setAutoScroll(true); - dom.scrollTo(0, dom.scrollHeight); - }); - } - }, [scrollRef]); - - // auto scroll - useEffect(() => { - if (autoScroll && !detach) { - scrollDomToBottom(); - } - }); - - // auto scroll when messages length changes - const lastMessagesLength = useRef(messages.length); - useEffect(() => { - if (messages.length > lastMessagesLength.current && !detach) { - scrollDomToBottom(); - } - lastMessagesLength.current = messages.length; - }, [messages.length, detach, scrollDomToBottom]); - - return { - scrollRef, - autoScroll, - setAutoScroll, - scrollDomToBottom, - }; -} - export function ChatActions(props: { uploadImage: () => void; setAttachImages: (images: string[]) => void; setUploading: (uploading: boolean) => void; showPromptModal: () => void; - scrollToBottom: () => void; + scrollChatToBottom: () => void; showPromptHints: () => void; hitBottom: boolean; uploading: boolean; @@ -608,7 +566,7 @@ export function ChatActions(props: { )} {!props.hitBottom && ( } /> @@ -997,37 +955,12 @@ function _Chat() { const [showExport, setShowExport] = useState(false); + const scrollRef = useRef(null); const inputRef = useRef(null); const [userInput, setUserInput] = useState(""); const [isLoading, setIsLoading] = useState(false); const { submitKey, shouldSubmit } = useSubmitHandler(); - const scrollRef = useRef(null); - const isScrolledToBottom = scrollRef?.current - ? Math.abs( - scrollRef.current.scrollHeight - - (scrollRef.current.scrollTop + scrollRef.current.clientHeight), - ) <= 1 - : false; - const isAttachWithTop = useMemo(() => { - const lastMessage = scrollRef.current?.lastElementChild as HTMLElement; - // if scrolllRef is not ready or no message, return false - if (!scrollRef?.current || !lastMessage) return false; - const topDistance = - lastMessage!.getBoundingClientRect().top - - scrollRef.current.getBoundingClientRect().top; - // leave some space for user question - return topDistance < 100; - }, [scrollRef?.current?.scrollHeight]); - const isTyping = userInput !== ""; - - // if user is typing, should auto scroll to bottom - // if user is not typing, should auto scroll to bottom only if already at bottom - const { setAutoScroll, scrollDomToBottom } = useScrollToBottom( - scrollRef, - (isScrolledToBottom || isAttachWithTop) && !isTyping, - session.messages, - ); const [hitBottom, setHitBottom] = useState(true); const isMobileScreen = useMobileScreen(); const navigate = useNavigate(); @@ -1104,6 +1037,7 @@ function _Chat() { const doSubmit = (userInput: string) => { if (userInput.trim() === "" && isEmpty(attachImages)) return; + const matchCommand = chatCommands.match(userInput); if (matchCommand.matched) { setUserInput(""); @@ -1111,16 +1045,19 @@ function _Chat() { matchCommand.invoke(); return; } + setIsLoading(true); - chatStore - .onUserInput(userInput, attachImages) - .then(() => setIsLoading(false)); + + chatStore.onUserInput(userInput, attachImages).then(() => { + setIsLoading(false); + autoScrollChatToBottom(); + }); + setAttachImages([]); chatStore.setLastInput(userInput); setUserInput(""); setPromptHints([]); - if (!isMobileScreen) inputRef.current?.focus(); - setAutoScroll(true); + autoScrollChatToBottom(); }; const onPromptSelect = (prompt: RenderPrompt) => { @@ -1420,14 +1357,33 @@ function _Chat() { } setHitBottom(isHitBottom); - setAutoScroll(isHitBottom); }; - function scrollToBottom() { - setMsgRenderIndex(renderMessages.length - CHAT_PAGE_SIZE); - scrollDomToBottom(); + function scrollChatToBottom() { + const dom = scrollRef.current; + if (dom) { + setMsgRenderIndex(renderMessages.length - CHAT_PAGE_SIZE); + requestAnimationFrame(() => { + dom.scrollTo(0, dom.scrollHeight); + }); + } } + // scroll if auto-scroll is enabled in the settings + function autoScrollChatToBottom() { + if (config.enableAutoScroll) scrollChatToBottom(); + } + + // scroll to the bottom on mount + useEffect(() => { + scrollChatToBottom(); + }, []); + + // keep scroll the chat as it gets longer, but only if the chat is already scrolled to the bottom (sticky bottom) + useEffect(() => { + if (hitBottom) scrollChatToBottom(); + }); + // clear context index = context length + index in messages const clearContextIndex = (session.clearContextIndex ?? -1) >= 0 @@ -1775,10 +1731,7 @@ function _Chat() { ref={scrollRef} onScroll={(e) => onChatBodyScroll(e.currentTarget)} onMouseDown={() => inputRef.current?.blur()} - onTouchStart={() => { - inputRef.current?.blur(); - setAutoScroll(false); - }} + onTouchStart={() => inputRef.current?.blur()} > {messages // TODO @@ -2050,7 +2003,7 @@ function _Chat() { setAttachImages={setAttachImages} setUploading={setUploading} showPromptModal={() => setShowPromptModal(true)} - scrollToBottom={scrollToBottom} + scrollChatToBottom={scrollChatToBottom} hitBottom={hitBottom} uploading={uploading} showPromptHints={() => { @@ -2083,8 +2036,8 @@ function _Chat() { onInput={(e) => onInput(e.currentTarget.value)} value={userInput} onKeyDown={onInputKeyDown} - onFocus={scrollToBottom} - onClick={scrollToBottom} + onFocus={autoScrollChatToBottom} + onClick={autoScrollChatToBottom} onPaste={handlePaste} rows={inputRows} autoFocus={autoFocus}