From 30040a0366222cd63b12b2e66fa96bb43a66737e Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 27 Apr 2023 02:00:22 +0800 Subject: [PATCH] feat: migrate state from v1 to v2 --- app/components/error.tsx | 42 +++++++++++++++++++++++++++++++++++----- app/constant.ts | 9 +++++++++ app/store/access.ts | 5 ++--- app/store/chat.ts | 24 +++++++++++++++++------ app/store/config.ts | 16 ++++++++++++--- app/store/mask.ts | 5 ++--- app/store/prompt.ts | 5 ++--- app/store/update.ts | 6 ++---- 8 files changed, 85 insertions(+), 27 deletions(-) diff --git a/app/components/error.tsx b/app/components/error.tsx index aa80939b..1e30e6b6 100644 --- a/app/components/error.tsx +++ b/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 { this.setState({ hasError: true, error, info }); } + clearAndSaveData() { + const snapshot: Record = {}; + 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 { {this.state.info?.componentStack} - +
+ + } + bordered + /> + } + icon={} + text="Clear All Data" + onClick={() => + confirm(Locale.Store.ConfirmClearAll) && this.clearAndSaveData() + } bordered /> - +
); } diff --git a/app/constant.ts b/app/constant.ts index 87d3a94d..e0ec03d0 100644 --- a/app/constant.ts +++ b/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; diff --git a/app/store/access.ts b/app/store/access.ts index aed13168..e72052b4 100644 --- a/app/store/access.ts +++ b/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()( @@ -62,7 +61,7 @@ export const useAccessStore = create()( }, }), { - name: ACCESS_KEY, + name: StoreKey.Access, version: 1, }, ), diff --git a/app/store/chat.ts b/app/store/chat.ts index 6482988b..54fd3fad 100644 --- a/app/store/chat.ts +++ b/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()( persist( (set, get) => ({ @@ -489,16 +488,29 @@ export const useChatStore = create()( }, }), { - 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; }, }, ), diff --git a/app/store/config.ts b/app/store/config.ts index 06a95835..6ffed01d 100644 --- a/app/store/config.ts +++ b/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()( persist( (set, get) => ({ @@ -130,7 +129,18 @@ export const useAppConfig = create()( }, }), { - 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; + }, }, ), ); diff --git a/app/store/mask.ts b/app/store/mask.ts index d4fd10aa..b35e160c 100644 --- a/app/store/mask.ts +++ b/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()( }, }), { - name: MASK_KEY, + name: StoreKey.Mask, version: 2, }, ), diff --git a/app/store/prompt.ts b/app/store/prompt.ts index 8d754ff5..e3a2eedd 100644 --- a/app/store/prompt.ts +++ b/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([], { keys: ["title"] }), @@ -123,7 +122,7 @@ export const usePromptStore = create()( }, }), { - name: PROMPT_KEY, + name: StoreKey.Prompt, version: 1, onRehydrateStorage(state) { const PROMPT_URL = "./prompts.json"; diff --git a/app/store/update.ts b/app/store/update.ts index 47b190b8..888741b8 100644 --- a/app/store/update.ts +++ b/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; } -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()( }, }), { - name: UPDATE_KEY, + name: StoreKey.Update, version: 1, }, ),