diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index dad1933ace97..77d351f309f8 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -67,6 +67,7 @@ import {
isVisionModel,
isDalle3,
showPlugins,
+ safeLocalStorage,
} from "../utils";
import { uploadImage as uploadImageRemote } from "@/app/utils/chat";
@@ -109,6 +110,8 @@ import { getClientConfig } from "../config/client";
import { useAllModels } from "../utils/hooks";
import { MultimodalContent } from "../client/api";
+const localStorage = safeLocalStorage();
+
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
loading: () => ,
});
@@ -941,7 +944,7 @@ function _Chat() {
.onUserInput(userInput, attachImages)
.then(() => setIsLoading(false));
setAttachImages([]);
- localStorage.setItem(LAST_INPUT_KEY, userInput);
+ chatStore.setLastInput(userInput);
setUserInput("");
setPromptHints([]);
if (!isMobileScreen) inputRef.current?.focus();
@@ -1007,7 +1010,7 @@ function _Chat() {
userInput.length <= 0 &&
!(e.metaKey || e.altKey || e.ctrlKey)
) {
- setUserInput(localStorage.getItem(LAST_INPUT_KEY) ?? "");
+ setUserInput(chatStore.lastInput ?? "");
e.preventDefault();
return;
}
diff --git a/app/components/error.tsx b/app/components/error.tsx
index c90997d11667..4fcf759c147c 100644
--- a/app/components/error.tsx
+++ b/app/components/error.tsx
@@ -8,6 +8,7 @@ import { ISSUE_URL } from "../constant";
import Locale from "../locales";
import { showConfirm } from "./ui-lib";
import { useSyncStore } from "../store/sync";
+import { useChatStore } from "../store/chat";
interface IErrorBoundaryState {
hasError: boolean;
@@ -30,8 +31,7 @@ export class ErrorBoundary extends React.Component {
try {
useSyncStore.getState().export();
} finally {
- localStorage.clear();
- location.reload();
+ useChatStore.getState().clearAllData();
}
}
diff --git a/app/components/mask.tsx b/app/components/mask.tsx
index 62503c37a858..ee6c7da97b83 100644
--- a/app/components/mask.tsx
+++ b/app/components/mask.tsx
@@ -426,16 +426,7 @@ export function MaskPage() {
const maskStore = useMaskStore();
const chatStore = useChatStore();
- const [filterLang, setFilterLang] = useState(
- () => localStorage.getItem("Mask-language") as Lang | undefined,
- );
- useEffect(() => {
- if (filterLang) {
- localStorage.setItem("Mask-language", filterLang);
- } else {
- localStorage.removeItem("Mask-language");
- }
- }, [filterLang]);
+ const filterLang = maskStore.language;
const allMasks = maskStore
.getAll()
@@ -542,9 +533,9 @@ export function MaskPage() {
onChange={(e) => {
const value = e.currentTarget.value;
if (value === Locale.Settings.Lang.All) {
- setFilterLang(undefined);
+ maskStore.setLanguage(undefined);
} else {
- setFilterLang(value as Lang);
+ maskStore.setLanguage(value as Lang);
}
}}
>
diff --git a/app/locales/index.ts b/app/locales/index.ts
index acdb3e878a1a..ff7e3a262574 100644
--- a/app/locales/index.ts
+++ b/app/locales/index.ts
@@ -18,10 +18,13 @@ import ar from "./ar";
import bn from "./bn";
import sk from "./sk";
import { merge } from "../utils/merge";
+import { safeLocalStorage } from "@/app/utils";
import type { LocaleType } from "./cn";
export type { LocaleType, PartialLocaleType } from "./cn";
+const localStorage = safeLocalStorage();
+
const ALL_LANGS = {
cn,
en,
@@ -82,17 +85,11 @@ merge(fallbackLang, targetLang);
export default fallbackLang as LocaleType;
function getItem(key: string) {
- try {
- return localStorage.getItem(key);
- } catch {
- return null;
- }
+ return localStorage.getItem(key);
}
function setItem(key: string, value: string) {
- try {
- localStorage.setItem(key, value);
- } catch {}
+ localStorage.setItem(key, value);
}
function getLanguage() {
diff --git a/app/store/chat.ts b/app/store/chat.ts
index 96cc402523b9..58c105e7ef71 100644
--- a/app/store/chat.ts
+++ b/app/store/chat.ts
@@ -26,9 +26,11 @@ import { nanoid } from "nanoid";
import { createPersistStore } from "../utils/store";
import { collectModelsWithDefaultModel } from "../utils/model";
import { useAccessStore } from "./access";
-import { isDalle3 } from "../utils";
+import { isDalle3, safeLocalStorage } from "../utils";
import { indexedDBStorage } from "@/app/utils/indexedDB-storage";
+const localStorage = safeLocalStorage();
+
export type ChatMessageTool = {
id: string;
index?: number;
@@ -179,6 +181,7 @@ function fillTemplateWith(input: string, modelConfig: ModelConfig) {
const DEFAULT_CHAT_STATE = {
sessions: [createEmptySession()],
currentSessionIndex: 0,
+ lastInput: "",
};
export const useChatStore = createPersistStore(
@@ -701,6 +704,11 @@ export const useChatStore = createPersistStore(
localStorage.clear();
location.reload();
},
+ setLastInput(lastInput: string) {
+ set({
+ lastInput,
+ });
+ },
};
return methods;
diff --git a/app/store/mask.ts b/app/store/mask.ts
index 083121b65ccb..0c74a892e56e 100644
--- a/app/store/mask.ts
+++ b/app/store/mask.ts
@@ -23,9 +23,12 @@ export type Mask = {
export const DEFAULT_MASK_STATE = {
masks: {} as Record,
+ language: undefined as Lang | undefined,
};
-export type MaskState = typeof DEFAULT_MASK_STATE;
+export type MaskState = typeof DEFAULT_MASK_STATE & {
+ language?: Lang | undefined;
+};
export const DEFAULT_MASK_AVATAR = "gpt-bot";
export const createEmptyMask = () =>
@@ -102,6 +105,11 @@ export const useMaskStore = createPersistStore(
search(text: string) {
return Object.values(get().masks);
},
+ setLanguage(language: Lang | undefined) {
+ set({
+ language,
+ });
+ },
}),
{
name: StoreKey.Mask,
diff --git a/app/utils.ts b/app/utils.ts
index 60041ba060f5..bf7450929136 100644
--- a/app/utils.ts
+++ b/app/utils.ts
@@ -318,3 +318,63 @@ export function adapter(config: Record) {
: path;
return fetch(fetchUrl as string, { ...rest, responseType: "text" });
}
+
+export function safeLocalStorage(): {
+ getItem: (key: string) => string | null;
+ setItem: (key: string, value: string) => void;
+ removeItem: (key: string) => void;
+ clear: () => void;
+} {
+ let storage: Storage | null;
+
+ try {
+ if (typeof window !== "undefined" && window.localStorage) {
+ storage = window.localStorage;
+ } else {
+ storage = null;
+ }
+ } catch (e) {
+ console.error("localStorage is not available:", e);
+ storage = null;
+ }
+
+ return {
+ getItem(key: string): string | null {
+ if (storage) {
+ return storage.getItem(key);
+ } else {
+ console.warn(
+ `Attempted to get item "${key}" from localStorage, but localStorage is not available.`,
+ );
+ return null;
+ }
+ },
+ setItem(key: string, value: string): void {
+ if (storage) {
+ storage.setItem(key, value);
+ } else {
+ console.warn(
+ `Attempted to set item "${key}" in localStorage, but localStorage is not available.`,
+ );
+ }
+ },
+ removeItem(key: string): void {
+ if (storage) {
+ storage.removeItem(key);
+ } else {
+ console.warn(
+ `Attempted to remove item "${key}" from localStorage, but localStorage is not available.`,
+ );
+ }
+ },
+ clear(): void {
+ if (storage) {
+ storage.clear();
+ } else {
+ console.warn(
+ "Attempted to clear localStorage, but localStorage is not available.",
+ );
+ }
+ },
+ };
+}
diff --git a/app/utils/indexedDB-storage.ts b/app/utils/indexedDB-storage.ts
index da3094550135..51417e9f3d93 100644
--- a/app/utils/indexedDB-storage.ts
+++ b/app/utils/indexedDB-storage.ts
@@ -1,5 +1,8 @@
import { StateStorage } from "zustand/middleware";
import { get, set, del, clear } from "idb-keyval";
+import { safeLocalStorage } from "@/app/utils";
+
+const localStorage = safeLocalStorage();
class IndexedDBStorage implements StateStorage {
public async getItem(name: string): Promise {