Преглед на файлове

feat: migrate state from v1 to v2

Yidadaa преди 1 година
родител
ревизия
30040a0366
променени са 8 файла, в които са добавени 85 реда и са изтрити 27 реда
  1. 37 5
      app/components/error.tsx
  2. 9 0
      app/constant.ts
  3. 2 3
      app/store/access.ts
  4. 18 6
      app/store/chat.ts
  5. 13 3
      app/store/config.ts
  6. 2 3
      app/store/mask.ts
  7. 2 3
      app/store/prompt.ts
  8. 2 4
      app/store/update.ts

+ 37 - 5
app/components/error.tsx

@@ -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>
       );
     }

+ 9 - 0
app/constant.ts

@@ -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;

+ 2 - 3
app/store/access.ts

@@ -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,
     },
   ),

+ 18 - 6
app/store/chat.ts

@@ -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;
       },
     },
   ),

+ 13 - 3
app/store/config.ts

@@ -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;
+      },
     },
   ),
 );

+ 2 - 3
app/store/mask.ts

@@ -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 - 3
app/store/prompt.ts

@@ -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";

+ 2 - 4
app/store/update.ts

@@ -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,
     },
   ),