ソースを参照

Merge pull request #5386 from ConnectAI-E/feature/safeLocalStorage

fix: safaLocalStorage
Dogtiti 1 年間 前
コミット
35f77f45a2

+ 5 - 2
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: () => <LoadingIcon />,
 });
@@ -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;
     }

+ 2 - 2
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<any, IErrorBoundaryState> {
     try {
       useSyncStore.getState().export();
     } finally {
-      localStorage.clear();
-      location.reload();
+      useChatStore.getState().clearAllData();
     }
   }
 

+ 3 - 12
app/components/mask.tsx

@@ -426,16 +426,7 @@ export function MaskPage() {
   const maskStore = useMaskStore();
   const chatStore = useChatStore();
 
-  const [filterLang, setFilterLang] = useState<Lang | undefined>(
-    () => 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);
                 }
               }}
             >

+ 5 - 8
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() {

+ 9 - 1
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;

+ 9 - 1
app/store/mask.ts

@@ -23,9 +23,12 @@ export type Mask = {
 
 export const DEFAULT_MASK_STATE = {
   masks: {} as Record<string, Mask>,
+  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,

+ 60 - 0
app/utils.ts

@@ -318,3 +318,63 @@ export function adapter(config: Record<string, unknown>) {
     : 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.",
+        );
+      }
+    },
+  };
+}

+ 3 - 0
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<string | null> {