import { IDBPDatabase, openDB } from "idb"; import { createContext, useContext, useEffect, useState } from "react"; import "@/global.css"; import { calculate_token_length } from "@/chatgpt"; import { getDefaultParams } from "@/utils/getDefaultParam"; import ChatBOX from "@/pages/Chatbox"; import { models } from "@/types/models"; import { DefaultModel } from "@/const"; import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate"; import { ChatStore } from "@/types/chatstore"; import { newChatStore } from "@/types/newChatstore"; import { STORAGE_NAME, STORAGE_NAME_SELECTED } from "@/const"; import { upgrade } from "@/indexedDB/upgrade"; import { getTotalCost } from "@/utils/totalCost"; import { STORAGE_NAME_TEMPLATE, STORAGE_NAME_TEMPLATE_API, STORAGE_NAME_TEMPLATE_API_IMAGE_GEN, STORAGE_NAME_TEMPLATE_API_TTS, STORAGE_NAME_TEMPLATE_API_WHISPER, STORAGE_NAME_TEMPLATE_TOOLS, } from "@/const"; import { ChatStoreMessage, TemplateChatStore, TemplateAPI, TemplateTools, } from "../types/chatstore"; interface AppContextType { db: Promise>; selectedChatIndex: number; setSelectedChatIndex: (i: number) => void; templates: TemplateChatStore[]; setTemplates: (t: TemplateChatStore[]) => void; templateAPIs: TemplateAPI[]; setTemplateAPIs: (t: TemplateAPI[]) => void; templateAPIsWhisper: TemplateAPI[]; setTemplateAPIsWhisper: (t: TemplateAPI[]) => void; templateAPIsTTS: TemplateAPI[]; setTemplateAPIsTTS: (t: TemplateAPI[]) => void; templateAPIsImageGen: TemplateAPI[]; setTemplateAPIsImageGen: (t: TemplateAPI[]) => void; templateTools: TemplateTools[]; setTemplateTools: (t: TemplateTools[]) => void; } interface AppChatStoreContextType { chatStore: ChatStore; setChatStore: (cs: ChatStore) => Promise; } export const AppContext = createContext(null as any); export const AppChatStoreContext = createContext( null as any ); import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarRail, SidebarInset, SidebarTrigger, } from "@/components/ui/sidebar"; import { Button } from "@/components/ui/button"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { useToast } from "@/hooks/use-toast"; import { ModeToggle } from "@/components/ModeToggle"; import Search from "@/components/Search"; import Navbar from "@/components/navbar"; export function App() { // init selected index const [selectedChatIndex, setSelectedChatIndex] = useState( parseInt(localStorage.getItem(STORAGE_NAME_SELECTED) ?? "1") ); console.log("selectedChatIndex", selectedChatIndex); useEffect(() => { console.log("set selected chat index", selectedChatIndex); localStorage.setItem(STORAGE_NAME_SELECTED, `${selectedChatIndex}`); }, [selectedChatIndex]); const db = openDB(STORAGE_NAME, 11, { upgrade, }); const { toast } = useToast(); const getChatStoreByIndex = async (index: number): Promise => { const ret: ChatStore = await (await db).get(STORAGE_NAME, index); if (ret === null || ret === undefined) { const newStore = newChatStore({}); toast({ title: "New chat created", description: `Current API Endpoint: ${newStore.apiEndpoint}`, }); return newStore; } // handle read from old version chatstore if (ret.maxGenTokens === undefined) ret.maxGenTokens = 2048; if (ret.maxGenTokens_enabled === undefined) ret.maxGenTokens_enabled = true; if (ret.model === undefined) ret.model = DefaultModel; if (ret.toolsString === undefined) ret.toolsString = ""; if (ret.chatgpt_api_web_version === undefined) // this is from old version becasue it is undefined, // so no higher than v1.3.0 ret.chatgpt_api_web_version = "v1.2.2"; for (const message of ret.history) { if (message.hide === undefined) message.hide = false; if (message.token === undefined) message.token = calculate_token_length(message.content); } if (ret.cost === undefined) ret.cost = 0; toast({ title: "Chat ready", description: `Current API Endpoint: ${ret.apiEndpoint}`, }); return ret; }; // all chat store indexes const [allChatStoreIndexes, setAllChatStoreIndexes] = useState( [] ); const handleNewChatStoreWithOldOne = async (chatStore: ChatStore) => { const newKey = await (await db).add(STORAGE_NAME, newChatStore(chatStore)); setSelectedChatIndex(newKey as number); setAllChatStoreIndexes(await (await db).getAllKeys(STORAGE_NAME)); toast({ title: "New chat created", description: `Current API Endpoint: ${chatStore.apiEndpoint}`, }); }; const handleNewChatStore = async () => { let currentChatStore = await getChatStoreByIndex(selectedChatIndex); return handleNewChatStoreWithOldOne(currentChatStore); }; const handleDEL = async () => { console.log("remove item", `${STORAGE_NAME}-${selectedChatIndex}`); (await db).delete(STORAGE_NAME, selectedChatIndex); const newAllChatStoreIndexes = await (await db).getAllKeys(STORAGE_NAME); if (newAllChatStoreIndexes.length === 0) { await handleNewChatStore(); return; } // find nex selected chat index const next = newAllChatStoreIndexes[newAllChatStoreIndexes.length - 1]; console.log("next is", next); setSelectedChatIndex(next as number); setAllChatStoreIndexes(newAllChatStoreIndexes); toast({ title: "Chat history deleted", description: `Chat history ${selectedChatIndex} has been deleted.`, }); }; const handleCLS = async () => { if (!confirm("Are you sure you want to delete **ALL** chat history?")) return; await (await db).clear(STORAGE_NAME); setAllChatStoreIndexes([]); setSelectedChatIndex(1); window.location.reload(); }; // if there are any params in URL, create a new chatStore useEffect(() => { const run = async () => { const chatStore = await getChatStoreByIndex(selectedChatIndex); const api = getDefaultParams("api", ""); const key = getDefaultParams("key", ""); const sys = getDefaultParams("sys", ""); const mode = getDefaultParams("mode", ""); const model = getDefaultParams("model", ""); const max = getDefaultParams("max", 0); console.log("max is", max, "chatStore.max is", chatStore.maxTokens); // only create new chatStore if the params in URL are NOT // equal to the current selected chatStore if ( (api && api !== chatStore.apiEndpoint) || (key && key !== chatStore.apiKey) || (sys && sys !== chatStore.systemMessageContent) || (mode && mode !== (chatStore.streamMode ? "stream" : "fetch")) || (model && model !== chatStore.model) || (max !== 0 && max !== chatStore.maxTokens) ) { console.log("create new chatStore because of params in URL"); handleNewChatStoreWithOldOne(chatStore); } await db; const allidx = await (await db).getAllKeys(STORAGE_NAME); if (allidx.length === 0) { handleNewChatStore(); } setAllChatStoreIndexes(await (await db).getAllKeys(STORAGE_NAME)); }; run(); }, []); const [templates, _setTemplates] = useState( JSON.parse( localStorage.getItem(STORAGE_NAME_TEMPLATE) || "[]" ) as TemplateChatStore[] ); const [templateAPIs, _setTemplateAPIs] = useState( JSON.parse( localStorage.getItem(STORAGE_NAME_TEMPLATE_API) || "[]" ) as TemplateAPI[] ); const [templateAPIsWhisper, _setTemplateAPIsWhisper] = useState( JSON.parse( localStorage.getItem(STORAGE_NAME_TEMPLATE_API_WHISPER) || "[]" ) as TemplateAPI[] ); const [templateAPIsTTS, _setTemplateAPIsTTS] = useState( JSON.parse( localStorage.getItem(STORAGE_NAME_TEMPLATE_API_TTS) || "[]" ) as TemplateAPI[] ); const [templateAPIsImageGen, _setTemplateAPIsImageGen] = useState( JSON.parse( localStorage.getItem(STORAGE_NAME_TEMPLATE_API_IMAGE_GEN) || "[]" ) as TemplateAPI[] ); const [templateTools, _setTemplateTools] = useState( JSON.parse( localStorage.getItem(STORAGE_NAME_TEMPLATE_TOOLS) || "[]" ) as TemplateTools[] ); const setTemplates = (templates: TemplateChatStore[]) => { localStorage.setItem(STORAGE_NAME_TEMPLATE, JSON.stringify(templates)); _setTemplates(templates); }; const setTemplateAPIs = (templateAPIs: TemplateAPI[]) => { localStorage.setItem( STORAGE_NAME_TEMPLATE_API, JSON.stringify(templateAPIs) ); _setTemplateAPIs(templateAPIs); }; const setTemplateAPIsWhisper = (templateAPIWhisper: TemplateAPI[]) => { localStorage.setItem( STORAGE_NAME_TEMPLATE_API_WHISPER, JSON.stringify(templateAPIWhisper) ); _setTemplateAPIsWhisper(templateAPIWhisper); }; const setTemplateAPIsTTS = (templateAPITTS: TemplateAPI[]) => { localStorage.setItem( STORAGE_NAME_TEMPLATE_API_TTS, JSON.stringify(templateAPITTS) ); _setTemplateAPIsTTS(templateAPITTS); }; const setTemplateAPIsImageGen = (templateAPIImageGen: TemplateAPI[]) => { localStorage.setItem( STORAGE_NAME_TEMPLATE_API_IMAGE_GEN, JSON.stringify(templateAPIImageGen) ); _setTemplateAPIsImageGen(templateAPIImageGen); }; const setTemplateTools = (templateTools: TemplateTools[]) => { localStorage.setItem( STORAGE_NAME_TEMPLATE_TOOLS, JSON.stringify(templateTools) ); _setTemplateTools(templateTools); }; console.log("[PERFORMANCE!] reading localStorage"); return ( Conversation {(allChatStoreIndexes as number[]) .slice() .reverse() .map((i) => { // reverse return ( setSelectedChatIndex(i)} > {i} ); })}
Are you absolutely sure? This action cannot be undone. This will permanently delete the chat history. Cancel Delete
); } const AppChatStoreProvider = ({ children, selectedChatIndex, getChatStoreByIndex, }: { children: React.ReactNode; selectedChatIndex: number; getChatStoreByIndex: (index: number) => Promise; }) => { console.log("[Render] AppChatStoreProvider"); const ctx = useContext(AppContext); const [chatStore, _setChatStore] = useState(newChatStore({})); const setChatStore = async (chatStore: ChatStore) => { console.log("recalculate postBeginIndex"); const max = chatStore.maxTokens - chatStore.tokenMargin; let sum = 0; chatStore.postBeginIndex = chatStore.history.filter( ({ hide }) => !hide ).length; for (const msg of chatStore.history .filter(({ hide }) => !hide) .slice() .reverse()) { if (sum + msg.token > max) break; sum += msg.token; chatStore.postBeginIndex -= 1; } chatStore.postBeginIndex = chatStore.postBeginIndex < 0 ? 0 : chatStore.postBeginIndex; // manually estimate token chatStore.totalTokens = calculate_token_length( chatStore.systemMessageContent ); for (const msg of chatStore.history .filter(({ hide }) => !hide) .slice(chatStore.postBeginIndex)) { chatStore.totalTokens += msg.token; } console.log("saved chat", selectedChatIndex, chatStore); (await ctx.db).put(STORAGE_NAME, chatStore, selectedChatIndex); // update total tokens chatStore.totalTokens = calculate_token_length( chatStore.systemMessageContent ); for (const msg of chatStore.history .filter(({ hide }) => !hide) .slice(chatStore.postBeginIndex)) { chatStore.totalTokens += msg.token; } _setChatStore(chatStore); }; useEffect(() => { const run = async () => { _setChatStore(await getChatStoreByIndex(selectedChatIndex)); }; run(); }, [selectedChatIndex]); return ( {children} ); };