feat: migrate state from v1 to v2

This commit is contained in:
Yidadaa 2023-04-27 02:00:22 +08:00
parent 401c1364be
commit 30040a0366
8 changed files with 85 additions and 27 deletions

View File

@ -1,7 +1,10 @@
import React from "react"; import React from "react";
import { IconButton } from "./button"; import { IconButton } from "./button";
import GithubIcon from "../icons/github.svg"; 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 { interface IErrorBoundaryState {
hasError: boolean; hasError: boolean;
@ -20,6 +23,25 @@ export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
this.setState({ hasError: true, error, info }); 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() { render() {
if (this.state.hasError) { if (this.state.hasError) {
// Render error message // Render error message
@ -31,6 +53,7 @@ export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
<code>{this.state.info?.componentStack}</code> <code>{this.state.info?.componentStack}</code>
</pre> </pre>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<a href={ISSUE_URL} className="report"> <a href={ISSUE_URL} className="report">
<IconButton <IconButton
text="Report This Error" text="Report This Error"
@ -38,6 +61,15 @@ export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
bordered bordered
/> />
</a> </a>
<IconButton
icon={<ResetIcon />}
text="Clear All Data"
onClick={() =>
confirm(Locale.Store.ConfirmClearAll) && this.clearAndSaveData()
}
bordered
/>
</div>
</div> </div>
); );
} }

View File

@ -24,6 +24,15 @@ export enum FileName {
Prompts = "prompts.json", 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 MAX_SIDEBAR_WIDTH = 500;
export const MIN_SIDEBAR_WIDTH = 230; export const MIN_SIDEBAR_WIDTH = 230;
export const NARROW_SIDEBAR_WIDTH = 100; export const NARROW_SIDEBAR_WIDTH = 100;

View File

@ -1,5 +1,6 @@
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
import { StoreKey } from "../constant";
export interface AccessControlStore { export interface AccessControlStore {
accessCode: string; accessCode: string;
@ -14,8 +15,6 @@ export interface AccessControlStore {
fetch: () => void; fetch: () => void;
} }
export const ACCESS_KEY = "access-control";
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
export const useAccessStore = create<AccessControlStore>()( export const useAccessStore = create<AccessControlStore>()(
@ -62,7 +61,7 @@ export const useAccessStore = create<AccessControlStore>()(
}, },
}), }),
{ {
name: ACCESS_KEY, name: StoreKey.Access,
version: 1, version: 1,
}, },
), ),

View File

@ -13,6 +13,7 @@ import Locale from "../locales";
import { showToast } from "../components/ui-lib"; import { showToast } from "../components/ui-lib";
import { DEFAULT_CONFIG, ModelConfig, ModelType, useAppConfig } from "./config"; import { DEFAULT_CONFIG, ModelConfig, ModelType, useAppConfig } from "./config";
import { createEmptyMask, Mask } from "./mask"; import { createEmptyMask, Mask } from "./mask";
import { StoreKey } from "../constant";
export type Message = ChatCompletionResponseMessage & { export type Message = ChatCompletionResponseMessage & {
date: string; date: string;
@ -109,8 +110,6 @@ function countMessages(msgs: Message[]) {
return msgs.reduce((pre, cur) => pre + cur.content.length, 0); return msgs.reduce((pre, cur) => pre + cur.content.length, 0);
} }
const LOCAL_KEY = "chat-next-web-store";
export const useChatStore = create<ChatStore>()( export const useChatStore = create<ChatStore>()(
persist( persist(
(set, get) => ({ (set, get) => ({
@ -489,16 +488,29 @@ export const useChatStore = create<ChatStore>()(
}, },
}), }),
{ {
name: LOCAL_KEY, name: StoreKey.Chat,
version: 2, version: 2,
migrate(persistedState, version) { 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) { 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;
}, },
}, },
), ),

View File

@ -1,5 +1,6 @@
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
import { StoreKey } from "../constant";
export enum SubmitKey { export enum SubmitKey {
Enter = "Enter", Enter = "Enter",
@ -112,8 +113,6 @@ export const ModalConfigValidator = {
}, },
}; };
const CONFIG_KEY = "app-config";
export const useAppConfig = create<ChatConfigStore>()( export const useAppConfig = create<ChatConfigStore>()(
persist( persist(
(set, get) => ({ (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;
},
}, },
), ),
); );

View File

@ -4,8 +4,7 @@ import { BUILTIN_MASKS } from "../masks";
import { getLang, Lang } from "../locales"; import { getLang, Lang } from "../locales";
import { DEFAULT_TOPIC, Message } from "./chat"; import { DEFAULT_TOPIC, Message } from "./chat";
import { ModelConfig, ModelType, useAppConfig } from "./config"; import { ModelConfig, ModelType, useAppConfig } from "./config";
import { StoreKey } from "../constant";
export const MASK_KEY = "mask-store";
export type Mask = { export type Mask = {
id: number; id: number;
@ -93,7 +92,7 @@ export const useMaskStore = create<MaskStore>()(
}, },
}), }),
{ {
name: MASK_KEY, name: StoreKey.Mask,
version: 2, version: 2,
}, },
), ),

View File

@ -2,6 +2,7 @@ import { create } from "zustand";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import { getLang } from "../locales"; import { getLang } from "../locales";
import { StoreKey } from "../constant";
export interface Prompt { export interface Prompt {
id?: number; id?: number;
@ -23,8 +24,6 @@ export interface PromptStore {
updateUserPrompts: (id: number, updater: (prompt: Prompt) => void) => void; updateUserPrompts: (id: number, updater: (prompt: Prompt) => void) => void;
} }
export const PROMPT_KEY = "prompt-store";
export const SearchService = { export const SearchService = {
ready: false, ready: false,
builtinEngine: new Fuse<Prompt>([], { keys: ["title"] }), builtinEngine: new Fuse<Prompt>([], { keys: ["title"] }),
@ -123,7 +122,7 @@ export const usePromptStore = create<PromptStore>()(
}, },
}), }),
{ {
name: PROMPT_KEY, name: StoreKey.Prompt,
version: 1, version: 1,
onRehydrateStorage(state) { onRehydrateStorage(state) {
const PROMPT_URL = "./prompts.json"; const PROMPT_URL = "./prompts.json";

View File

@ -1,6 +1,6 @@
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware"; 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"; import { requestUsage } from "../requests";
export interface UpdateStore { export interface UpdateStore {
@ -16,8 +16,6 @@ export interface UpdateStore {
updateUsage: (force?: boolean) => Promise<void>; updateUsage: (force?: boolean) => Promise<void>;
} }
export const UPDATE_KEY = "chat-update";
function queryMeta(key: string, defaultValue?: string): string { function queryMeta(key: string, defaultValue?: string): string {
let ret: string; let ret: string;
if (document) { if (document) {
@ -84,7 +82,7 @@ export const useUpdateStore = create<UpdateStore>()(
}, },
}), }),
{ {
name: UPDATE_KEY, name: StoreKey.Update,
version: 1, version: 1,
}, },
), ),