diff --git a/client/src/components/Chat/Input/ChatForm.tsx b/client/src/components/Chat/Input/ChatForm.tsx index 842957fcb1..4cdd0f0c2c 100644 --- a/client/src/components/Chat/Input/ChatForm.tsx +++ b/client/src/components/Chat/Input/ChatForm.tsx @@ -1,6 +1,6 @@ import { useForm } from 'react-hook-form'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { memo, useCallback, useRef, useMemo } from 'react'; +import { memo, useCallback, useRef, useMemo, useState, useEffect } from 'react'; import { supportsFiles, mergeFileConfig, @@ -8,6 +8,7 @@ import { fileConfig as defaultFileConfig, } from 'librechat-data-provider'; import { useChatContext, useAssistantsMapContext } from '~/Providers'; +import { useAutoSave } from '~/hooks/Input/useAutoSave'; import { useRequiresKey, useTextarea } from '~/hooks'; import { TextareaAutosize } from '~/components/ui'; import { useGetFileConfig } from '~/data-provider'; @@ -56,6 +57,14 @@ const ChatForm = ({ index = 0 }) => { handleStopGenerating, } = useChatContext(); + const { clearDraft } = useAutoSave({ + conversationId: useMemo(() => conversation?.conversationId, [conversation]), + textAreaRef, + setValue: methods.setValue, + files, + setFiles, + }); + const assistantMap = useAssistantsMapContext(); const submitMessage = useCallback( @@ -65,8 +74,9 @@ const ChatForm = ({ index = 0 }) => { } ask({ text: data.text }); methods.reset(); + clearDraft(); }, - [ask, methods], + [ask, methods, clearDraft], ); const { endpoint: _endpoint, endpointType } = conversation ?? { endpoint: null }; diff --git a/client/src/components/Chat/Input/Files/FileRow.tsx b/client/src/components/Chat/Input/Files/FileRow.tsx index b33282abb0..5e467c17b2 100644 --- a/client/src/components/Chat/Input/Files/FileRow.tsx +++ b/client/src/components/Chat/Input/Files/FileRow.tsx @@ -83,7 +83,7 @@ export default function FileRow({ return ( +
+ +
diff --git a/client/src/components/Nav/SettingsTabs/Messages/SaveDraft.tsx b/client/src/components/Nav/SettingsTabs/Messages/SaveDraft.tsx new file mode 100644 index 0000000000..989dc33d5b --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Messages/SaveDraft.tsx @@ -0,0 +1,33 @@ +import { useRecoilState } from 'recoil'; +import { Switch } from '~/components/ui/Switch'; +import useLocalize from '~/hooks/useLocalize'; +import store from '~/store'; + +export default function SaveDraft({ + onCheckedChange, +}: { + onCheckedChange?: (value: boolean) => void; +}) { + const [saveDrafts, setSaveDrafts] = useRecoilState(store.saveDrafts); + const localize = useLocalize(); + + const handleCheckedChange = (value: boolean) => { + setSaveDrafts(value); + if (onCheckedChange) { + onCheckedChange(value); + } + }; + + return ( +
+
{localize('com_nav_save_drafts')}
+ +
+ ); +} diff --git a/client/src/hooks/Input/useAutoSave.ts b/client/src/hooks/Input/useAutoSave.ts new file mode 100644 index 0000000000..b1949b24b9 --- /dev/null +++ b/client/src/hooks/Input/useAutoSave.ts @@ -0,0 +1,213 @@ +import debounce from 'lodash/debounce'; +import { UseFormSetValue } from 'react-hook-form'; +import { SetterOrUpdater, useRecoilValue } from 'recoil'; +import { useState, useEffect, useMemo, useCallback } from 'react'; +import { LocalStorageKeys, TFile } from 'librechat-data-provider'; +import { useGetFiles } from '~/data-provider'; +import { ExtendedFile } from '~/common'; +import store from '~/store'; + +export const useAutoSave = ({ + conversationId, + textAreaRef, + files, + setFiles, + setValue, +}: { + conversationId?: string | null; + textAreaRef: React.RefObject; + files: Map; + setFiles: SetterOrUpdater>; + setValue: UseFormSetValue<{ text: string }>; +}) => { + // setting for auto-save + const saveDrafts = useRecoilValue(store.saveDrafts); + + const [currentConversationId, setCurrentConversationId] = useState(null); + const fileIds = useMemo(() => Array.from(files.keys()), [files]); + const { data: fileList } = useGetFiles(); + + const encodeBase64 = (plainText: string): string => { + try { + const textBytes = new TextEncoder().encode(plainText); + return btoa(String.fromCharCode(...textBytes)); + } catch (e) { + return ''; + } + }; + + const decodeBase64 = (base64String: string): string => { + try { + const bytes = atob(base64String); + const uint8Array = new Uint8Array(bytes.length); + for (let i = 0; i < bytes.length; i++) { + uint8Array[i] = bytes.charCodeAt(i); + } + return new TextDecoder().decode(uint8Array); + } catch (e) { + return ''; + } + }; + + const restoreFiles = useCallback( + (id: string) => { + const filesDraft = JSON.parse( + localStorage.getItem(`${LocalStorageKeys.FILES_DRAFT}${id}`) || '[]', + ) as string[]; + + if (filesDraft.length === 0) { + setFiles(new Map()); + return; + } + + // Retrieve files stored in localStorage from files in fileList and set them to `setFiles` + // If a file is found with `temp_file_id`, use `temp_file_id` as a key in `setFiles` + filesDraft.forEach((fileId) => { + const fileData = fileList?.find((f) => f.file_id === fileId); + const tempFileData = fileList?.find((f) => f.temp_file_id === fileId); + const { fileToRecover, fileIdToRecover } = fileData + ? { fileToRecover: fileData, fileIdToRecover: fileId } + : { fileToRecover: tempFileData, fileIdToRecover: tempFileData?.temp_file_id || fileId }; + + if (fileToRecover) { + setFiles((currentFiles) => { + const updatedFiles = new Map(currentFiles); + updatedFiles.set(fileIdToRecover, { + ...fileToRecover, + progress: 1, + attached: true, + size: fileToRecover.bytes, + }); + return updatedFiles; + }); + } + }); + }, + [fileList, setFiles], + ); + + const restoreText = useCallback( + (id: string) => { + const savedDraft = localStorage.getItem(`${LocalStorageKeys.TEXT_DRAFT}${id}`) || ''; + setValue('text', decodeBase64(savedDraft)); + }, + [setValue], + ); + + const saveText = useCallback( + (id: string) => { + if (!textAreaRef?.current) { + return; + } + // Save the draft of the current conversation before switching + if (textAreaRef.current.value === '') { + localStorage.removeItem(`${LocalStorageKeys.TEXT_DRAFT}${id}`); + } else { + localStorage.setItem( + `${LocalStorageKeys.TEXT_DRAFT}${id}`, + encodeBase64(textAreaRef.current.value), + ); + } + }, + [textAreaRef], + ); + + useEffect(() => { + // This useEffect is responsible for setting up and cleaning up the auto-save functionality + // for the text area input. It saves the text to localStorage with a debounce to prevent + // excessive writes. + if (!saveDrafts || !conversationId) { + return; + } + + const handleInput = debounce(() => { + if (textAreaRef.current && textAreaRef.current.value) { + localStorage.setItem( + `${LocalStorageKeys.TEXT_DRAFT}${conversationId}`, + encodeBase64(textAreaRef.current.value), + ); + } else { + localStorage.removeItem(`${LocalStorageKeys.TEXT_DRAFT}${conversationId}`); + } + }, 1000); + + const textArea = textAreaRef.current; + if (textArea) { + textArea.addEventListener('input', handleInput); + } + + return () => { + if (textArea) { + textArea.removeEventListener('input', handleInput); + } + handleInput.cancel(); + }; + }, [conversationId, saveDrafts, textAreaRef]); + + useEffect(() => { + // This useEffect is responsible for saving the current conversation's draft and + // restoring the new conversation's draft when switching between conversations. + // It handles both text and file drafts, ensuring that the user's input is preserved + // across different conversations. + + if (!saveDrafts || !conversationId) { + return; + } + if (conversationId === currentConversationId) { + return; + } + + // clear attachment files when switching conversation + setFiles(new Map()); + + try { + if (currentConversationId) { + saveText(currentConversationId); + } + + restoreText(conversationId); + restoreFiles(conversationId); + } catch (e) { + console.error(e); + } + + setCurrentConversationId(conversationId); + }, [ + conversationId, + currentConversationId, + restoreFiles, + restoreText, + saveDrafts, + saveText, + setFiles, + ]); + + useEffect(() => { + // This useEffect is responsible for saving or removing the current conversation's file drafts + // in localStorage whenever the file attachments change. + // It ensures that the file drafts are kept up-to-date and can be restored + // when the conversation is revisited. + + if (!saveDrafts || !conversationId || currentConversationId !== conversationId) { + return; + } + + if (fileIds.length === 0) { + localStorage.removeItem(`${LocalStorageKeys.FILES_DRAFT}${conversationId}`); + } else { + localStorage.setItem( + `${LocalStorageKeys.FILES_DRAFT}${conversationId}`, + JSON.stringify(fileIds), + ); + } + }, [files, conversationId, saveDrafts, currentConversationId, fileIds]); + + const clearDraft = useCallback(() => { + if (conversationId) { + localStorage.removeItem(`${LocalStorageKeys.TEXT_DRAFT}${conversationId}`); + localStorage.removeItem(`${LocalStorageKeys.FILES_DRAFT}${conversationId}`); + } + }, [conversationId]); + + return { clearDraft }; +}; diff --git a/client/src/hooks/useNewConvo.ts b/client/src/hooks/useNewConvo.ts index 58cae55f58..cca7700e9c 100644 --- a/client/src/hooks/useNewConvo.ts +++ b/client/src/hooks/useNewConvo.ts @@ -48,6 +48,7 @@ const useNewConvo = (index = 0) => { const timeoutIdRef = useRef(); const assistantsListMap = useAssistantListMap(); const { pauseGlobalAudio } = usePauseGlobalAudio(index); + const saveDrafts = useRecoilValue(store.saveDrafts); const { mutateAsync } = useDeleteFilesMutation({ onSuccess: () => { @@ -211,14 +212,22 @@ const useNewConvo = (index = 0) => { setFiles(new Map()); localStorage.setItem(LocalStorageKeys.FILES_TO_DELETE, JSON.stringify({})); - if (filesToDelete.length > 0) { + if (!saveDrafts && filesToDelete.length > 0) { mutateAsync({ files: filesToDelete }); } } switchToConversation(conversation, preset, modelsData, buildDefault, keepLatestMessage); }, - [pauseGlobalAudio, switchToConversation, mutateAsync, setFiles, files, startupConfig], + [ + pauseGlobalAudio, + startupConfig, + saveDrafts, + switchToConversation, + files, + setFiles, + mutateAsync, + ], ); return { diff --git a/client/src/localization/languages/Ar.ts b/client/src/localization/languages/Ar.ts index a755fa7ab8..c0d09e07fb 100644 --- a/client/src/localization/languages/Ar.ts +++ b/client/src/localization/languages/Ar.ts @@ -544,6 +544,7 @@ export default { com_nav_my_files: 'ملفاتي', com_nav_enter_to_send: 'اضغط على مفتاح الإدخال لإرسال الرسائل', com_nav_user_name_display: 'عرض اسم المستخدم في الرسائل', + com_nav_save_drafts: 'حفظ المستخدمين', com_nav_show_code: 'إظهار الشفرة دائمًا عند استخدام مفسر الشفرة', com_nav_send_message: 'إرسال رسالة', com_nav_setting_beta: 'ميزات تجريبية', @@ -2559,6 +2560,10 @@ export const comparisons = { english: 'Display username in messages', translated: 'عرض اسم المستخدم في الرسائل', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: 'حفظ المستخدمين محليًا', + }, com_nav_show_code: { english: 'Always show code when using code interpreter', translated: 'إظهار الشفرة دائمًا عند استخدام مفسر الشفرة', diff --git a/client/src/localization/languages/Br.ts b/client/src/localization/languages/Br.ts index e571d2ec1e..f427e0afd4 100644 --- a/client/src/localization/languages/Br.ts +++ b/client/src/localization/languages/Br.ts @@ -440,6 +440,7 @@ export default { com_nav_theme_dark: 'Escuro', com_nav_theme_light: 'Claro', com_nav_user_name_display: 'Exibir nome de usuário nas mensagens', + com_nav_save_drafts: 'Salvar rascunhos localmente', com_nav_show_code: 'Sempre mostrar código ao usar o interpretador de código', com_nav_clear_all_chats: 'Limpar todas as conversas', com_nav_confirm_clear: 'Confirmar Limpeza', @@ -2053,6 +2054,10 @@ export const comparisons = { english: 'Display username in messages', translated: 'Exibir nome de usuário nas mensagens', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: 'Salvar rascunhos localmente', + }, com_nav_show_code: { english: 'Always show code when using code interpreter', translated: 'Sempre mostrar código ao usar o interpretador de código', diff --git a/client/src/localization/languages/De.ts b/client/src/localization/languages/De.ts index a548fcb3d0..e76da69026 100644 --- a/client/src/localization/languages/De.ts +++ b/client/src/localization/languages/De.ts @@ -454,6 +454,7 @@ export default { com_nav_theme_dark: 'Dunkel', com_nav_theme_light: 'Hell', com_nav_user_name_display: 'Benutzernamen in Nachrichten anzeigen', + com_nav_save_drafts: 'Entwurf lokal speichern', com_nav_show_code: 'Code immer anzeigen, wenn Code-Interpreter verwendet wird', com_nav_clear_all_chats: 'Alle Chats löschen', com_nav_confirm_clear: 'Bestätige Löschen', @@ -2215,6 +2216,10 @@ export const comparisons = { english: 'Display username in messages', translated: 'Benutzernamen in Nachrichten anzeigen', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: 'Entwurf lokal speichern', + }, com_nav_show_code: { english: 'Always show code when using code interpreter', translated: 'Code immer anzeigen, wenn Code-Interpreter verwendet wird', diff --git a/client/src/localization/languages/Eng.ts b/client/src/localization/languages/Eng.ts index 38c8423ae0..d3b7eda9ad 100644 --- a/client/src/localization/languages/Eng.ts +++ b/client/src/localization/languages/Eng.ts @@ -541,6 +541,7 @@ export default { com_nav_theme_light: 'Light', com_nav_enter_to_send: 'Press Enter to send messages', com_nav_user_name_display: 'Display username in messages', + com_nav_save_drafts: 'Save drafts locally', com_nav_show_code: 'Always show code when using code interpreter', com_nav_clear_all_chats: 'Clear all chats', com_nav_confirm_clear: 'Confirm Clear', diff --git a/client/src/localization/languages/Es.ts b/client/src/localization/languages/Es.ts index b708d1efa4..b184aed81c 100644 --- a/client/src/localization/languages/Es.ts +++ b/client/src/localization/languages/Es.ts @@ -446,6 +446,7 @@ export default { com_nav_theme_dark: 'Oscuro', com_nav_theme_light: 'Claro', com_nav_user_name_display: 'Mostrar nombre de usuario en los mensajes', + com_nav_save_drafts: 'Guardar borradores localmente', com_nav_show_code: 'Mostrar siempre el código cuando se use el intérprete de código', com_nav_clear_all_chats: 'Borrar todos los chats', com_nav_confirm_clear: 'Confirmar borrado', @@ -2188,6 +2189,10 @@ export const comparisons = { english: 'Display username in messages', translated: 'Mostrar nombre de usuario en los mensajes', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: 'Guardar borradores localmente', + }, com_nav_show_code: { english: 'Always show code when using code interpreter', translated: 'Mostrar siempre el código cuando se use el intérprete de código', diff --git a/client/src/localization/languages/Fr.ts b/client/src/localization/languages/Fr.ts index 1eb2b6c8ea..6c3059c77e 100644 --- a/client/src/localization/languages/Fr.ts +++ b/client/src/localization/languages/Fr.ts @@ -340,6 +340,7 @@ export default { com_nav_theme_dark: 'Sombre', com_nav_theme_light: 'Clair', com_nav_user_name_display: 'Afficher le nom d\'utilisateur dans les messages', + com_nav_save_drafts: 'Enregistrer les brouillons localement', com_nav_clear_all_chats: 'Effacer toutes les conversations', com_nav_confirm_clear: 'Confirmer l\'effacement', com_nav_close_sidebar: 'Fermer la barre latérale', @@ -1784,6 +1785,10 @@ export const comparisons = { english: 'Display username in messages', translated: 'Afficher le nom d\'utilisateur dans les messages', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: 'Enregistrer les brouillons localement', + }, com_nav_clear_all_chats: { english: 'Clear all chats', translated: 'Effacer toutes les conversations', diff --git a/client/src/localization/languages/He.ts b/client/src/localization/languages/He.ts index 138b79e2a9..61ee3f58ce 100644 --- a/client/src/localization/languages/He.ts +++ b/client/src/localization/languages/He.ts @@ -367,6 +367,7 @@ export default { com_nav_theme_dark: 'כהה', com_nav_theme_light: 'אור', com_nav_user_name_display: 'הצג שם משתמש בהודעות', + com_nav_save_drafts: 'שמיר את האפצה באותו מחשב', com_nav_clear_all_chats: 'נקה את כל השיחות', com_nav_confirm_clear: 'אשר נקה', com_nav_close_sidebar: 'סגור סרגל צד', @@ -1757,6 +1758,10 @@ export const comparisons = { english: 'Display username in messages', translated: 'הצג שם משתמש בהודעות', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: 'שמיר את האפצה באותו מחשב', + }, com_nav_clear_all_chats: { english: 'Clear all chats', translated: 'נקה את כל השיחות', diff --git a/client/src/localization/languages/Id.ts b/client/src/localization/languages/Id.ts index 41219cca39..fa68087114 100644 --- a/client/src/localization/languages/Id.ts +++ b/client/src/localization/languages/Id.ts @@ -326,6 +326,7 @@ export default { com_nav_theme_dark: 'Gelap', com_nav_theme_light: 'Terang', com_nav_user_name_display: 'Tampilkan nama pengguna dalam pesan', + com_nav_save_drafts: 'Simpan draft', com_nav_clear_all_chats: 'Hapus semua obrolan', com_nav_confirm_clear: 'Konfirmasi Hapus', com_nav_close_sidebar: 'Tutup sidebar', @@ -1555,6 +1556,10 @@ export const comparisons = { english: 'Display username in messages', translated: 'Tampilkan nama pengguna dalam pesan', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: 'Simpan draft', + }, com_nav_clear_all_chats: { english: 'Clear all chats', translated: 'Hapus semua obrolan', diff --git a/client/src/localization/languages/It.ts b/client/src/localization/languages/It.ts index 6c24a60f68..4fc228b29d 100644 --- a/client/src/localization/languages/It.ts +++ b/client/src/localization/languages/It.ts @@ -500,6 +500,7 @@ export default { com_nav_theme_light: 'Chiaro', com_nav_enter_to_send: 'Premi Invio per inviare messaggi', com_nav_user_name_display: 'Mostra nome utente nei messaggi', + com_nav_save_drafts: 'Salva bozze localmente', com_nav_show_code: 'Mostra sempre il codice quando si usa l\'interprete di codice', com_nav_clear_all_chats: 'Cancella tutte le chat', com_nav_confirm_clear: 'Conferma cancellazione', @@ -2367,6 +2368,10 @@ export const comparisons = { english: 'Display username in messages', translated: 'Mostra nome utente nei messaggi', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: 'Salva bozze localmente', + }, com_nav_show_code: { english: 'Always show code when using code interpreter', translated: 'Mostra sempre il codice quando si usa l\'interprete di codice', diff --git a/client/src/localization/languages/Jp.ts b/client/src/localization/languages/Jp.ts index bd73c54194..138a3bcedd 100644 --- a/client/src/localization/languages/Jp.ts +++ b/client/src/localization/languages/Jp.ts @@ -447,6 +447,7 @@ export default { com_nav_theme_light: 'ライト', com_nav_enter_to_send: 'Enterキーでメッセージを送信する', com_nav_user_name_display: 'メッセージにユーザー名を表示する', + com_nav_save_drafts: 'ローカルにドラフトを保存する', com_nav_show_code: 'Code Interpreter を使用する際は常にコードを表示する', com_nav_clear_all_chats: 'すべてのチャットを削除する', com_nav_confirm_clear: '削除を確定', @@ -2210,6 +2211,10 @@ export const comparisons = { english: 'Display username in messages', translated: 'メッセージにユーザー名を表示する', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: 'ローカルにドラフトを保存する', + }, com_nav_show_code: { english: 'Always show code when using code interpreter', translated: 'Code Interpreter を使用する際は常にコードを表示する', diff --git a/client/src/localization/languages/Ko.ts b/client/src/localization/languages/Ko.ts index 0c2bc6a95c..b4845897dc 100644 --- a/client/src/localization/languages/Ko.ts +++ b/client/src/localization/languages/Ko.ts @@ -538,6 +538,7 @@ export default { com_nav_my_files: '내 파일', com_nav_enter_to_send: '엔터키를 눌러 메시지 보내기', com_nav_user_name_display: '메시지에서 사용자 이름 표시', + com_nav_save_drafts: '초안을 로컬에 저장', com_nav_show_code: '코드 인터프리터 사용 시 항상 코드 표시', com_nav_setting_beta: '베타 기능', com_nav_setting_account: '계정', @@ -2555,6 +2556,10 @@ export const comparisons = { english: 'Display username in messages', translated: '메시지에서 사용자 이름 표시', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: '초안을 로컬에 저장', + }, com_nav_show_code: { english: 'Always show code when using code interpreter', translated: '코드 인터프리터 사용 시 항상 코드 표시', diff --git a/client/src/localization/languages/Ru.ts b/client/src/localization/languages/Ru.ts index 862802c89a..fd2ab5a413 100644 --- a/client/src/localization/languages/Ru.ts +++ b/client/src/localization/languages/Ru.ts @@ -325,6 +325,7 @@ export default { com_nav_theme_dark: 'Темная', com_nav_theme_light: 'Светлая', com_nav_user_name_display: 'Отображать имя пользователя в сообщениях', + com_nav_save_drafts: 'Сохранить черновики локально', com_nav_language: 'Локализация', com_nav_setting_account: 'Аккаунт', com_nav_profile_picture: 'Изображение профиля', @@ -1753,6 +1754,10 @@ export const comparisons = { english: 'Display username in messages', translated: 'Отображать имя пользователя в сообщениях', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: 'Сохранить черновики локально', + }, com_nav_language: { english: 'Language', translated: 'Локализация', diff --git a/client/src/localization/languages/Zh.ts b/client/src/localization/languages/Zh.ts index 0093c761b2..a56c509363 100644 --- a/client/src/localization/languages/Zh.ts +++ b/client/src/localization/languages/Zh.ts @@ -409,6 +409,7 @@ export default { com_nav_theme_dark: '暗色主题', com_nav_theme_light: '亮色主题', com_nav_user_name_display: '在消息中显示用户名', + com_nav_save_drafts: '保存草稿本地', com_nav_show_code: '使用代码解释器时始终显示代码', com_nav_clear_all_chats: '清空所有对话', com_nav_confirm_clear: '确认清空', @@ -2110,6 +2111,10 @@ export const comparisons = { english: 'Display username in messages', translated: '在消息中显示用户名', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: '保存草稿本地', + }, com_nav_show_code: { english: 'Always show code when using code interpreter', translated: '使用代码解释器时始终显示代码', diff --git a/client/src/localization/languages/ZhTraditional.ts b/client/src/localization/languages/ZhTraditional.ts index 5150436922..8abb0cae60 100644 --- a/client/src/localization/languages/ZhTraditional.ts +++ b/client/src/localization/languages/ZhTraditional.ts @@ -519,6 +519,7 @@ export default { com_nav_my_files: '我的檔案', com_nav_enter_to_send: '按 Enter 鍵傳送訊息', com_nav_user_name_display: '在訊息中顯示使用者名稱', + com_nav_save_drafts: '保存草稿本地', com_nav_show_code: '一律顯示使用程式碼解譯器時的程式碼', com_nav_setting_beta: '測試功能', com_nav_setting_account: '帳號', @@ -2528,6 +2529,10 @@ export const comparisons = { english: 'Display username in messages', translated: '在訊息中顯示使用者名稱', }, + com_nav_save_drafts: { + english: 'Save drafts locally', + translated: '保存草稿本地', + }, com_nav_show_code: { english: 'Always show code when using code interpreter', translated: '一律顯示使用程式碼解譯器時的程式碼', diff --git a/client/src/store/settings.ts b/client/src/store/settings.ts index e50387ebff..d9ed29fb52 100644 --- a/client/src/store/settings.ts +++ b/client/src/store/settings.ts @@ -72,6 +72,7 @@ const localStorageAtoms = { splitAtTarget: atomWithLocalStorage('splitAtTarget', false), rememberForkOption: atomWithLocalStorage('rememberForkOption', true), playbackRate: atomWithLocalStorage('playbackRate', null), + saveDrafts: atomWithLocalStorage('saveDrafts', false), }; export default { ...staticAtoms, ...localStorageAtoms }; diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 24a0441a77..cd90a83826 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -820,6 +820,10 @@ export enum LocalStorageKeys { REMEMBER_FORK_OPTION = 'rememberForkOption', /** Key for remembering the split at target fork option modifier */ FORK_SPLIT_AT_TARGET = 'splitAtTarget', + /** Key for saving text drafts */ + TEXT_DRAFT = 'textDraft_', + /** Key for saving file drafts */ + FILES_DRAFT = 'filesDraft_', } export enum ForkOptions {