mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat: migrate state from v1 to v2
This commit is contained in:
		@@ -1,7 +1,10 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { IconButton } from "./button";
 | 
			
		||||
import GithubIcon from "../icons/github.svg";
 | 
			
		||||
import { ISSUE_URL } from "../constant";
 | 
			
		||||
import ResetIcon from "../icons/reload.svg";
 | 
			
		||||
import { ISSUE_URL, StoreKey } from "../constant";
 | 
			
		||||
import Locale from "../locales";
 | 
			
		||||
import { downloadAs } from "../utils";
 | 
			
		||||
 | 
			
		||||
interface IErrorBoundaryState {
 | 
			
		||||
  hasError: boolean;
 | 
			
		||||
@@ -20,6 +23,25 @@ export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
 | 
			
		||||
    this.setState({ hasError: true, error, info });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearAndSaveData() {
 | 
			
		||||
    const snapshot: Record<string, any> = {};
 | 
			
		||||
    Object.values(StoreKey).forEach((key) => {
 | 
			
		||||
      snapshot[key] = localStorage.getItem(key);
 | 
			
		||||
 | 
			
		||||
      if (snapshot[key]) {
 | 
			
		||||
        try {
 | 
			
		||||
          snapshot[key] = JSON.parse(snapshot[key]);
 | 
			
		||||
        } catch {}
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      downloadAs(JSON.stringify(snapshot), "chatgpt-next-web-snapshot.json");
 | 
			
		||||
    } catch {}
 | 
			
		||||
 | 
			
		||||
    localStorage.clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    if (this.state.hasError) {
 | 
			
		||||
      // Render error message
 | 
			
		||||
@@ -31,13 +53,23 @@ export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
 | 
			
		||||
            <code>{this.state.info?.componentStack}</code>
 | 
			
		||||
          </pre>
 | 
			
		||||
 | 
			
		||||
          <a href={ISSUE_URL} className="report">
 | 
			
		||||
          <div style={{ display: "flex", justifyContent: "space-between" }}>
 | 
			
		||||
            <a href={ISSUE_URL} className="report">
 | 
			
		||||
              <IconButton
 | 
			
		||||
                text="Report This Error"
 | 
			
		||||
                icon={<GithubIcon />}
 | 
			
		||||
                bordered
 | 
			
		||||
              />
 | 
			
		||||
            </a>
 | 
			
		||||
            <IconButton
 | 
			
		||||
              text="Report This Error"
 | 
			
		||||
              icon={<GithubIcon />}
 | 
			
		||||
              icon={<ResetIcon />}
 | 
			
		||||
              text="Clear All Data"
 | 
			
		||||
              onClick={() =>
 | 
			
		||||
                confirm(Locale.Store.ConfirmClearAll) && this.clearAndSaveData()
 | 
			
		||||
              }
 | 
			
		||||
              bordered
 | 
			
		||||
            />
 | 
			
		||||
          </a>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,15 @@ export enum FileName {
 | 
			
		||||
  Prompts = "prompts.json",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum StoreKey {
 | 
			
		||||
  Chat = "chat-next-web-store",
 | 
			
		||||
  Access = "access-control",
 | 
			
		||||
  Config = "app-config",
 | 
			
		||||
  Mask = "mask-store",
 | 
			
		||||
  Prompt = "prompt-store",
 | 
			
		||||
  Update = "chat-update",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const MAX_SIDEBAR_WIDTH = 500;
 | 
			
		||||
export const MIN_SIDEBAR_WIDTH = 230;
 | 
			
		||||
export const NARROW_SIDEBAR_WIDTH = 100;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { create } from "zustand";
 | 
			
		||||
import { persist } from "zustand/middleware";
 | 
			
		||||
import { StoreKey } from "../constant";
 | 
			
		||||
 | 
			
		||||
export interface AccessControlStore {
 | 
			
		||||
  accessCode: string;
 | 
			
		||||
@@ -14,8 +15,6 @@ export interface AccessControlStore {
 | 
			
		||||
  fetch: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ACCESS_KEY = "access-control";
 | 
			
		||||
 | 
			
		||||
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
 | 
			
		||||
 | 
			
		||||
export const useAccessStore = create<AccessControlStore>()(
 | 
			
		||||
@@ -62,7 +61,7 @@ export const useAccessStore = create<AccessControlStore>()(
 | 
			
		||||
      },
 | 
			
		||||
    }),
 | 
			
		||||
    {
 | 
			
		||||
      name: ACCESS_KEY,
 | 
			
		||||
      name: StoreKey.Access,
 | 
			
		||||
      version: 1,
 | 
			
		||||
    },
 | 
			
		||||
  ),
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import Locale from "../locales";
 | 
			
		||||
import { showToast } from "../components/ui-lib";
 | 
			
		||||
import { DEFAULT_CONFIG, ModelConfig, ModelType, useAppConfig } from "./config";
 | 
			
		||||
import { createEmptyMask, Mask } from "./mask";
 | 
			
		||||
import { StoreKey } from "../constant";
 | 
			
		||||
 | 
			
		||||
export type Message = ChatCompletionResponseMessage & {
 | 
			
		||||
  date: string;
 | 
			
		||||
@@ -109,8 +110,6 @@ function countMessages(msgs: Message[]) {
 | 
			
		||||
  return msgs.reduce((pre, cur) => pre + cur.content.length, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const LOCAL_KEY = "chat-next-web-store";
 | 
			
		||||
 | 
			
		||||
export const useChatStore = create<ChatStore>()(
 | 
			
		||||
  persist(
 | 
			
		||||
    (set, get) => ({
 | 
			
		||||
@@ -489,16 +488,29 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
      },
 | 
			
		||||
    }),
 | 
			
		||||
    {
 | 
			
		||||
      name: LOCAL_KEY,
 | 
			
		||||
      name: StoreKey.Chat,
 | 
			
		||||
      version: 2,
 | 
			
		||||
      migrate(persistedState, version) {
 | 
			
		||||
        const state = persistedState as ChatStore;
 | 
			
		||||
        const state = persistedState as any;
 | 
			
		||||
        const newState = JSON.parse(JSON.stringify(state)) as ChatStore;
 | 
			
		||||
 | 
			
		||||
        if (version < 2) {
 | 
			
		||||
          state.sessions.forEach((s) => (s.mask = createEmptyMask()));
 | 
			
		||||
          newState.globalId = 0;
 | 
			
		||||
          newState.sessions = [];
 | 
			
		||||
 | 
			
		||||
          const oldSessions = state.sessions;
 | 
			
		||||
          for (const oldSession of oldSessions) {
 | 
			
		||||
            const newSession = createEmptySession();
 | 
			
		||||
            newSession.topic = oldSession.topic;
 | 
			
		||||
            newSession.messages = [...oldSession.messages];
 | 
			
		||||
            newSession.mask.modelConfig.sendMemory = true;
 | 
			
		||||
            newSession.mask.modelConfig.historyMessageCount = 4;
 | 
			
		||||
            newSession.mask.modelConfig.compressMessageLengthThreshold = 1000;
 | 
			
		||||
            newState.sessions.push(newSession);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return state;
 | 
			
		||||
        return newState;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { create } from "zustand";
 | 
			
		||||
import { persist } from "zustand/middleware";
 | 
			
		||||
import { StoreKey } from "../constant";
 | 
			
		||||
 | 
			
		||||
export enum SubmitKey {
 | 
			
		||||
  Enter = "Enter",
 | 
			
		||||
@@ -112,8 +113,6 @@ export const ModalConfigValidator = {
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const CONFIG_KEY = "app-config";
 | 
			
		||||
 | 
			
		||||
export const useAppConfig = create<ChatConfigStore>()(
 | 
			
		||||
  persist(
 | 
			
		||||
    (set, get) => ({
 | 
			
		||||
@@ -130,7 +129,18 @@ export const useAppConfig = create<ChatConfigStore>()(
 | 
			
		||||
      },
 | 
			
		||||
    }),
 | 
			
		||||
    {
 | 
			
		||||
      name: CONFIG_KEY,
 | 
			
		||||
      name: StoreKey.Config,
 | 
			
		||||
      version: 2,
 | 
			
		||||
      migrate(persistedState, version) {
 | 
			
		||||
        if (version === 2) return persistedState as any;
 | 
			
		||||
 | 
			
		||||
        const state = persistedState as ChatConfig;
 | 
			
		||||
        state.modelConfig.sendMemory = true;
 | 
			
		||||
        state.modelConfig.historyMessageCount = 4;
 | 
			
		||||
        state.modelConfig.compressMessageLengthThreshold = 1000;
 | 
			
		||||
 | 
			
		||||
        return state;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ),
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,7 @@ import { BUILTIN_MASKS } from "../masks";
 | 
			
		||||
import { getLang, Lang } from "../locales";
 | 
			
		||||
import { DEFAULT_TOPIC, Message } from "./chat";
 | 
			
		||||
import { ModelConfig, ModelType, useAppConfig } from "./config";
 | 
			
		||||
 | 
			
		||||
export const MASK_KEY = "mask-store";
 | 
			
		||||
import { StoreKey } from "../constant";
 | 
			
		||||
 | 
			
		||||
export type Mask = {
 | 
			
		||||
  id: number;
 | 
			
		||||
@@ -93,7 +92,7 @@ export const useMaskStore = create<MaskStore>()(
 | 
			
		||||
      },
 | 
			
		||||
    }),
 | 
			
		||||
    {
 | 
			
		||||
      name: MASK_KEY,
 | 
			
		||||
      name: StoreKey.Mask,
 | 
			
		||||
      version: 2,
 | 
			
		||||
    },
 | 
			
		||||
  ),
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ import { create } from "zustand";
 | 
			
		||||
import { persist } from "zustand/middleware";
 | 
			
		||||
import Fuse from "fuse.js";
 | 
			
		||||
import { getLang } from "../locales";
 | 
			
		||||
import { StoreKey } from "../constant";
 | 
			
		||||
 | 
			
		||||
export interface Prompt {
 | 
			
		||||
  id?: number;
 | 
			
		||||
@@ -23,8 +24,6 @@ export interface PromptStore {
 | 
			
		||||
  updateUserPrompts: (id: number, updater: (prompt: Prompt) => void) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const PROMPT_KEY = "prompt-store";
 | 
			
		||||
 | 
			
		||||
export const SearchService = {
 | 
			
		||||
  ready: false,
 | 
			
		||||
  builtinEngine: new Fuse<Prompt>([], { keys: ["title"] }),
 | 
			
		||||
@@ -123,7 +122,7 @@ export const usePromptStore = create<PromptStore>()(
 | 
			
		||||
      },
 | 
			
		||||
    }),
 | 
			
		||||
    {
 | 
			
		||||
      name: PROMPT_KEY,
 | 
			
		||||
      name: StoreKey.Prompt,
 | 
			
		||||
      version: 1,
 | 
			
		||||
      onRehydrateStorage(state) {
 | 
			
		||||
        const PROMPT_URL = "./prompts.json";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import { create } from "zustand";
 | 
			
		||||
import { persist } from "zustand/middleware";
 | 
			
		||||
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
 | 
			
		||||
import { FETCH_COMMIT_URL, FETCH_TAG_URL, StoreKey } from "../constant";
 | 
			
		||||
import { requestUsage } from "../requests";
 | 
			
		||||
 | 
			
		||||
export interface UpdateStore {
 | 
			
		||||
@@ -16,8 +16,6 @@ export interface UpdateStore {
 | 
			
		||||
  updateUsage: (force?: boolean) => Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const UPDATE_KEY = "chat-update";
 | 
			
		||||
 | 
			
		||||
function queryMeta(key: string, defaultValue?: string): string {
 | 
			
		||||
  let ret: string;
 | 
			
		||||
  if (document) {
 | 
			
		||||
@@ -84,7 +82,7 @@ export const useUpdateStore = create<UpdateStore>()(
 | 
			
		||||
      },
 | 
			
		||||
    }),
 | 
			
		||||
    {
 | 
			
		||||
      name: UPDATE_KEY,
 | 
			
		||||
      name: StoreKey.Update,
 | 
			
		||||
      version: 1,
 | 
			
		||||
    },
 | 
			
		||||
  ),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user