mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	Merge pull request #5386 from ConnectAI-E/feature/safeLocalStorage
fix: safaLocalStorage
This commit is contained in:
		@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
                }
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
 
 | 
			
		||||
@@ -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() {
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
								
								app/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								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.",
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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> {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user