mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-13 13:33:42 +08:00
merge上游更新,升级为2.0版本
This commit is contained in:
@@ -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,
|
||||
},
|
||||
),
|
||||
|
@@ -11,7 +11,9 @@ import { isMobileScreen, trimTopic } from "../utils";
|
||||
|
||||
import Locale from "../locales";
|
||||
import { showToast } from "../components/ui-lib";
|
||||
import { ModelType, useAppConfig } from "./config";
|
||||
import { DEFAULT_CONFIG, ModelConfig, ModelType, useAppConfig } from "./config";
|
||||
import { createEmptyMask, Mask } from "./mask";
|
||||
import { StoreKey } from "../constant";
|
||||
|
||||
export type Message = ChatCompletionResponseMessage & {
|
||||
date: string;
|
||||
@@ -41,50 +43,50 @@ export interface ChatStat {
|
||||
|
||||
export interface ChatSession {
|
||||
id: number;
|
||||
|
||||
topic: string;
|
||||
sendMemory: boolean;
|
||||
|
||||
memoryPrompt: string;
|
||||
context: Message[];
|
||||
messages: Message[];
|
||||
stat: ChatStat;
|
||||
lastUpdate: string;
|
||||
lastUpdate: number;
|
||||
lastSummarizeIndex: number;
|
||||
|
||||
mask: Mask;
|
||||
}
|
||||
|
||||
const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
|
||||
export const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
|
||||
export const BOT_HELLO: Message = createMessage({
|
||||
role: "assistant",
|
||||
content: Locale.Store.BotHello,
|
||||
});
|
||||
|
||||
function createEmptySession(): ChatSession {
|
||||
const createDate = new Date().toLocaleString();
|
||||
|
||||
return {
|
||||
id: Date.now(),
|
||||
id: Date.now() + Math.random(),
|
||||
topic: DEFAULT_TOPIC,
|
||||
sendMemory: true,
|
||||
memoryPrompt: "",
|
||||
context: [],
|
||||
messages: [],
|
||||
stat: {
|
||||
tokenCount: 0,
|
||||
wordCount: 0,
|
||||
charCount: 0,
|
||||
},
|
||||
lastUpdate: createDate,
|
||||
lastUpdate: Date.now(),
|
||||
lastSummarizeIndex: 0,
|
||||
mask: createEmptyMask(),
|
||||
};
|
||||
}
|
||||
|
||||
interface ChatStore {
|
||||
sessions: ChatSession[];
|
||||
currentSessionIndex: number;
|
||||
globalId: number;
|
||||
clearSessions: () => void;
|
||||
removeSession: (index: number) => void;
|
||||
moveSession: (from: number, to: number) => void;
|
||||
selectSession: (index: number) => void;
|
||||
newSession: () => void;
|
||||
newSession: (mask?: Mask) => void;
|
||||
deleteSession: (index?: number) => void;
|
||||
currentSession: () => ChatSession;
|
||||
onNewMessage: (message: Message) => void;
|
||||
@@ -108,13 +110,12 @@ 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) => ({
|
||||
sessions: [createEmptySession()],
|
||||
currentSessionIndex: 0,
|
||||
globalId: 0,
|
||||
|
||||
clearSessions() {
|
||||
set(() => ({
|
||||
@@ -179,10 +180,20 @@ export const useChatStore = create<ChatStore>()(
|
||||
});
|
||||
},
|
||||
|
||||
newSession() {
|
||||
newSession(mask) {
|
||||
const session = createEmptySession();
|
||||
|
||||
set(() => ({ globalId: get().globalId + 1 }));
|
||||
session.id = get().globalId;
|
||||
|
||||
if (mask) {
|
||||
session.mask = { ...mask };
|
||||
session.topic = mask.name;
|
||||
}
|
||||
|
||||
set((state) => ({
|
||||
currentSessionIndex: 0,
|
||||
sessions: [createEmptySession()].concat(state.sessions),
|
||||
sessions: [session].concat(state.sessions),
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -229,7 +240,7 @@ export const useChatStore = create<ChatStore>()(
|
||||
|
||||
onNewMessage(message) {
|
||||
get().updateCurrentSession((session) => {
|
||||
session.lastUpdate = new Date().toLocaleString();
|
||||
session.lastUpdate = Date.now();
|
||||
});
|
||||
get().updateStat(message);
|
||||
get().summarizeSession();
|
||||
@@ -308,7 +319,10 @@ export const useChatStore = create<ChatStore>()(
|
||||
|
||||
return {
|
||||
role: "system",
|
||||
content: Locale.Store.Prompt.History(session.memoryPrompt),
|
||||
content:
|
||||
session.memoryPrompt.length > 0
|
||||
? Locale.Store.Prompt.History(session.memoryPrompt)
|
||||
: "",
|
||||
date: "",
|
||||
} as Message;
|
||||
},
|
||||
@@ -319,11 +333,11 @@ export const useChatStore = create<ChatStore>()(
|
||||
const messages = session.messages.filter((msg) => !msg.isError);
|
||||
const n = messages.length;
|
||||
|
||||
const context = session.context.slice();
|
||||
const context = session.mask.context.slice();
|
||||
|
||||
// long term memory
|
||||
if (
|
||||
session.sendMemory &&
|
||||
session.mask.modelConfig.sendMemory &&
|
||||
session.memoryPrompt &&
|
||||
session.memoryPrompt.length > 0
|
||||
) {
|
||||
@@ -334,14 +348,14 @@ export const useChatStore = create<ChatStore>()(
|
||||
// get short term and unmemoried long term memory
|
||||
const shortTermMemoryMessageIndex = Math.max(
|
||||
0,
|
||||
n - config.historyMessageCount,
|
||||
n - config.modelConfig.historyMessageCount,
|
||||
);
|
||||
const longTermMemoryMessageIndex = session.lastSummarizeIndex;
|
||||
const oldestIndex = Math.max(
|
||||
shortTermMemoryMessageIndex,
|
||||
longTermMemoryMessageIndex,
|
||||
);
|
||||
const threshold = config.compressMessageLengthThreshold;
|
||||
const threshold = config.modelConfig.compressMessageLengthThreshold;
|
||||
|
||||
// get recent messages as many as possible
|
||||
const reversedRecentMessages = [];
|
||||
@@ -410,7 +424,7 @@ export const useChatStore = create<ChatStore>()(
|
||||
if (historyMsgLength > config?.modelConfig?.max_tokens ?? 4000) {
|
||||
const n = toBeSummarizedMsgs.length;
|
||||
toBeSummarizedMsgs = toBeSummarizedMsgs.slice(
|
||||
Math.max(0, n - config.historyMessageCount),
|
||||
Math.max(0, n - config.modelConfig.historyMessageCount),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -423,12 +437,13 @@ export const useChatStore = create<ChatStore>()(
|
||||
"[Chat History] ",
|
||||
toBeSummarizedMsgs,
|
||||
historyMsgLength,
|
||||
config.compressMessageLengthThreshold,
|
||||
config.modelConfig.compressMessageLengthThreshold,
|
||||
);
|
||||
|
||||
if (
|
||||
historyMsgLength > config.compressMessageLengthThreshold &&
|
||||
session.sendMemory
|
||||
historyMsgLength >
|
||||
config.modelConfig.compressMessageLengthThreshold &&
|
||||
session.mask.modelConfig.sendMemory
|
||||
) {
|
||||
requestChatStream(
|
||||
toBeSummarizedMsgs.concat({
|
||||
@@ -469,27 +484,34 @@ export const useChatStore = create<ChatStore>()(
|
||||
},
|
||||
|
||||
clearAllData() {
|
||||
if (confirm(Locale.Store.ConfirmClearAll)) {
|
||||
localStorage.clear();
|
||||
location.reload();
|
||||
}
|
||||
localStorage.clear();
|
||||
location.reload();
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: LOCAL_KEY,
|
||||
version: 1.2,
|
||||
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 === 1) {
|
||||
state.sessions.forEach((s) => (s.context = []));
|
||||
if (version < 2) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (version < 1.2) {
|
||||
state.sessions.forEach((s) => (s.sendMemory = true));
|
||||
}
|
||||
|
||||
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",
|
||||
@@ -15,9 +16,7 @@ export enum Theme {
|
||||
Light = "light",
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
historyMessageCount: 4,
|
||||
compressMessageLengthThreshold: 1000,
|
||||
export const DEFAULT_CONFIG = {
|
||||
sendBotMessages: true as boolean,
|
||||
submitKey: SubmitKey.CtrlEnter as SubmitKey,
|
||||
avatar: "1f603",
|
||||
@@ -29,11 +28,16 @@ const DEFAULT_CONFIG = {
|
||||
|
||||
disablePromptHint: false,
|
||||
|
||||
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
|
||||
|
||||
modelConfig: {
|
||||
model: "gpt-3.5-turbo" as ModelType,
|
||||
temperature: 1,
|
||||
max_tokens: 2000,
|
||||
presence_penalty: 0,
|
||||
sendMemory: true,
|
||||
historyMessageCount: 4,
|
||||
compressMessageLengthThreshold: 1000,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -107,12 +111,10 @@ export const ModalConfigValidator = {
|
||||
return limitNumber(x, -2, 2, 0);
|
||||
},
|
||||
temperature(x: number) {
|
||||
return limitNumber(x, 0, 2, 1);
|
||||
return limitNumber(x, 0, 1, 1);
|
||||
},
|
||||
};
|
||||
|
||||
const CONFIG_KEY = "app-config";
|
||||
|
||||
export const useAppConfig = create<ChatConfigStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
@@ -129,7 +131,19 @@ 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;
|
||||
state.dontShowMaskSplashScreen = false;
|
||||
|
||||
return state;
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
export * from "./app";
|
||||
export * from "./chat";
|
||||
export * from "./update";
|
||||
export * from "./access";
|
||||
export * from "./config";
|
||||
|
99
app/store/mask.ts
Normal file
99
app/store/mask.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { BUILTIN_MASKS } from "../masks";
|
||||
import { getLang, Lang } from "../locales";
|
||||
import { DEFAULT_TOPIC, Message } from "./chat";
|
||||
import { ModelConfig, ModelType, useAppConfig } from "./config";
|
||||
import { StoreKey } from "../constant";
|
||||
|
||||
export type Mask = {
|
||||
id: number;
|
||||
avatar: string;
|
||||
name: string;
|
||||
context: Message[];
|
||||
modelConfig: ModelConfig;
|
||||
lang: Lang;
|
||||
builtin: boolean;
|
||||
};
|
||||
|
||||
export const DEFAULT_MASK_STATE = {
|
||||
masks: {} as Record<number, Mask>,
|
||||
globalMaskId: 0,
|
||||
};
|
||||
|
||||
export type MaskState = typeof DEFAULT_MASK_STATE;
|
||||
type MaskStore = MaskState & {
|
||||
create: (mask?: Partial<Mask>) => Mask;
|
||||
update: (id: number, updater: (mask: Mask) => void) => void;
|
||||
delete: (id: number) => void;
|
||||
search: (text: string) => Mask[];
|
||||
get: (id?: number) => Mask | null;
|
||||
getAll: () => Mask[];
|
||||
};
|
||||
|
||||
export const DEFAULT_MASK_ID = 1145141919810;
|
||||
export const DEFAULT_MASK_AVATAR = "gpt-bot";
|
||||
export const createEmptyMask = () =>
|
||||
({
|
||||
id: DEFAULT_MASK_ID,
|
||||
avatar: DEFAULT_MASK_AVATAR,
|
||||
name: DEFAULT_TOPIC,
|
||||
context: [],
|
||||
modelConfig: { ...useAppConfig.getState().modelConfig },
|
||||
lang: getLang(),
|
||||
builtin: false,
|
||||
} as Mask);
|
||||
|
||||
export const useMaskStore = create<MaskStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
...DEFAULT_MASK_STATE,
|
||||
|
||||
create(mask) {
|
||||
set(() => ({ globalMaskId: get().globalMaskId + 1 }));
|
||||
const id = get().globalMaskId;
|
||||
const masks = get().masks;
|
||||
masks[id] = {
|
||||
...createEmptyMask(),
|
||||
...mask,
|
||||
id,
|
||||
};
|
||||
|
||||
set(() => ({ masks }));
|
||||
|
||||
return masks[id];
|
||||
},
|
||||
update(id, updater) {
|
||||
const masks = get().masks;
|
||||
const mask = masks[id];
|
||||
if (!mask) return;
|
||||
const updateMask = { ...mask };
|
||||
updater(updateMask);
|
||||
masks[id] = updateMask;
|
||||
set(() => ({ masks }));
|
||||
},
|
||||
delete(id) {
|
||||
const masks = get().masks;
|
||||
delete masks[id];
|
||||
set(() => ({ masks }));
|
||||
},
|
||||
|
||||
get(id) {
|
||||
return get().masks[id ?? 1145141919810];
|
||||
},
|
||||
getAll() {
|
||||
const userMasks = Object.values(get().masks).sort(
|
||||
(a, b) => b.id - a.id,
|
||||
);
|
||||
return userMasks.concat(BUILTIN_MASKS);
|
||||
},
|
||||
search(text) {
|
||||
return Object.values(get().masks);
|
||||
},
|
||||
}),
|
||||
{
|
||||
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