mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-05-19 04:00:16 +09:00
Merge 520c2850b4
into 3809375694
This commit is contained in:
commit
0fa2c3a24b
@ -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<HTMLDivElement>,
|
||||
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 && (
|
||||
<ChatAction
|
||||
onClick={props.scrollToBottom}
|
||||
onClick={props.scrollChatToBottom}
|
||||
text={Locale.Chat.InputActions.ToBottom}
|
||||
icon={<BottomIcon />}
|
||||
/>
|
||||
@ -997,37 +955,12 @@ function _Chat() {
|
||||
|
||||
const [showExport, setShowExport] = useState(false);
|
||||
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [userInput, setUserInput] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { submitKey, shouldSubmit } = useSubmitHandler();
|
||||
const scrollRef = useRef<HTMLDivElement>(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}
|
||||
|
@ -1699,6 +1699,23 @@ export function Settings() {
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
title={Locale.Settings.AutoScroll.Title}
|
||||
subTitle={Locale.Settings.AutoScroll.SubTitle}
|
||||
>
|
||||
<input
|
||||
aria-label={Locale.Settings.AutoScroll.Title}
|
||||
type="checkbox"
|
||||
checked={config.enableAutoScroll}
|
||||
data-testid="enable-auto-scroll-checkbox"
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) =>
|
||||
(config.enableAutoScroll = e.currentTarget.checked),
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<SyncItems />
|
||||
|
@ -199,6 +199,11 @@ const ar: PartialLocaleType = {
|
||||
Title: "توليد العنوان تلقائيًا",
|
||||
SubTitle: "توليد عنوان مناسب بناءً على محتوى الدردشة",
|
||||
},
|
||||
AutoScroll: {
|
||||
Title: "تفعيل التمرير التلقائي",
|
||||
SubTitle:
|
||||
"التمرير التلقائي للدردشة إلى الأسفل عند التركيز على منطقة النص أو إرسال الرسالة",
|
||||
},
|
||||
Sync: {
|
||||
CloudState: "بيانات السحابة",
|
||||
NotSyncYet: "لم يتم التزامن بعد",
|
||||
|
@ -200,6 +200,11 @@ const bn: PartialLocaleType = {
|
||||
Title: "স্বয়ংক্রিয় শিরোনাম জেনারেশন",
|
||||
SubTitle: "চ্যাট কনটেন্টের ভিত্তিতে উপযুক্ত শিরোনাম তৈরি করুন",
|
||||
},
|
||||
AutoScroll: {
|
||||
Title: "অটো স্ক্রল সক্ষম করুন",
|
||||
SubTitle:
|
||||
"টেক্সট এরিয়া ফোকাস বা মেসেজ সাবমিটে স্বয়ংক্রিয়ভাবে চ্যাট নিচে স্ক্রল করুন",
|
||||
},
|
||||
Sync: {
|
||||
CloudState: "ক্লাউড ডেটা",
|
||||
NotSyncYet: "এখনো সিঙ্ক করা হয়নি",
|
||||
|
@ -220,6 +220,10 @@ const cn = {
|
||||
Title: "自动生成标题",
|
||||
SubTitle: "根据对话内容生成合适的标题",
|
||||
},
|
||||
AutoScroll: {
|
||||
Title: "启用自动滚动",
|
||||
SubTitle: "在文本区域聚焦或提交消息时自动滚动聊天到底部",
|
||||
},
|
||||
Sync: {
|
||||
CloudState: "云端数据",
|
||||
NotSyncYet: "还没有进行过同步",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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é",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -200,6 +200,11 @@ const jp: PartialLocaleType = {
|
||||
Title: "自動タイトル生成",
|
||||
SubTitle: "チャット内容に基づいて適切なタイトルを生成",
|
||||
},
|
||||
AutoScroll: {
|
||||
Title: "自動スクロールを有効にする",
|
||||
SubTitle:
|
||||
"テキストエリアにフォーカスするか、メッセージを送信するとチャットが自動で下にスクロールされます",
|
||||
},
|
||||
Sync: {
|
||||
CloudState: "クラウドデータ",
|
||||
NotSyncYet: "まだ同期されていません",
|
||||
|
@ -199,6 +199,11 @@ const ko: PartialLocaleType = {
|
||||
Title: "제목 자동 생성",
|
||||
SubTitle: "대화 내용에 따라 적절한 제목 생성",
|
||||
},
|
||||
AutoScroll: {
|
||||
Title: "자동 스크롤 활성화",
|
||||
SubTitle:
|
||||
"텍스트 영역에 포커스하거나 메시지를 전송하면 채팅이 자동으로 아래로 스크롤됩니다",
|
||||
},
|
||||
Sync: {
|
||||
CloudState: "클라우드 데이터",
|
||||
NotSyncYet: "아직 동기화되지 않았습니다.",
|
||||
|
@ -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å",
|
||||
|
@ -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",
|
||||
|
@ -202,6 +202,11 @@ const ru: PartialLocaleType = {
|
||||
Title: "Автоматическое создание заголовка",
|
||||
SubTitle: "Создание подходящего заголовка на основе содержания беседы",
|
||||
},
|
||||
AutoScroll: {
|
||||
Title: "Включить автопрокрутку",
|
||||
SubTitle:
|
||||
"Автоматически прокручивать чат вниз при фокусе на текстовом поле или отправке сообщения",
|
||||
},
|
||||
Sync: {
|
||||
CloudState: "Облачные данные",
|
||||
NotSyncYet: "Синхронизация еще не проводилась",
|
||||
|
@ -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é",
|
||||
|
@ -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",
|
||||
|
@ -207,6 +207,10 @@ const tw = {
|
||||
Title: "自動產生標題",
|
||||
SubTitle: "根據對話內容產生合適的標題",
|
||||
},
|
||||
AutoScroll: {
|
||||
Title: "啟用自動捲動",
|
||||
SubTitle: "在文字區域聚焦或送出訊息時,自動將聊天捲動至底部",
|
||||
},
|
||||
Sync: {
|
||||
CloudState: "雲端資料",
|
||||
NotSyncYet: "還沒有進行過同步",
|
||||
|
@ -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ộ",
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user