diff --git a/package.json b/package.json
index 0ff2d66..838cda5 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"@types/ungap__structured-clone": "^0.3.1",
"@ungap/structured-clone": "^1.2.0",
"autoprefixer": "^10.4.16",
+ "idb": "^7.1.1",
"postcss": "^8.4.31",
"preact": "^10.18.1",
"preact-markdown": "^2.1.0",
diff --git a/src/CHATGPT_API_WEB_VERSION.ts b/src/CHATGPT_API_WEB_VERSION.ts
index 79ca382..a582d01 100644
--- a/src/CHATGPT_API_WEB_VERSION.ts
+++ b/src/CHATGPT_API_WEB_VERSION.ts
@@ -1,3 +1,3 @@
-const CHATGPT_API_WEB_VERSION = "v1.6.0";
+const CHATGPT_API_WEB_VERSION = "v2.0.0";
export default CHATGPT_API_WEB_VERSION;
diff --git a/src/app.tsx b/src/app.tsx
index c1870f1..0be193c 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -1,3 +1,4 @@
+import { IDBPDatabase, openDB } from "idb";
import { useEffect, useState } from "preact/hooks";
import "./global.css";
@@ -51,7 +52,7 @@ export interface ChatStore {
}
const _defaultAPIEndpoint = "https://api.openai.com/v1/chat/completions";
-const newChatStore = (
+export const newChatStore = (
apiKey = "",
systemMessageContent = "",
apiEndpoint = _defaultAPIEndpoint,
@@ -97,7 +98,7 @@ const newChatStore = (
};
};
-const STORAGE_NAME = "chatgpt-api-web";
+export const STORAGE_NAME = "chatgpt-api-web";
const STORAGE_NAME_SELECTED = `${STORAGE_NAME}-selected`;
const STORAGE_NAME_INDEXES = `${STORAGE_NAME}-indexes`;
const STORAGE_NAME_TOTALCOST = `${STORAGE_NAME}-totalcost`;
@@ -122,36 +123,45 @@ export function clearTotalCost() {
}
export function App() {
- // init indexes
- const initAllChatStoreIndexes: number[] = JSON.parse(
- localStorage.getItem(STORAGE_NAME_INDEXES) ?? "[0]"
- );
- const [allChatStoreIndexes, setAllChatStoreIndexes] = useState(
- initAllChatStoreIndexes
- );
- useEffect(() => {
- if (allChatStoreIndexes.length === 0) allChatStoreIndexes.push(0);
- console.log("saved all chat store indexes", allChatStoreIndexes);
- localStorage.setItem(
- STORAGE_NAME_INDEXES,
- JSON.stringify(allChatStoreIndexes)
- );
- }, [allChatStoreIndexes]);
-
// init selected index
const [selectedChatIndex, setSelectedChatIndex] = useState(
- parseInt(localStorage.getItem(STORAGE_NAME_SELECTED) ?? "0")
+ 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 getChatStoreByIndex = (index: number): ChatStore => {
- const key = `${STORAGE_NAME}-${index}`;
- const val = localStorage.getItem(key);
- if (val === null) return newChatStore();
- const ret = JSON.parse(val) as ChatStore;
+ const db = openDB(STORAGE_NAME, 1, {
+ upgrade(db) {
+ const store = db.createObjectStore(STORAGE_NAME, {
+ autoIncrement: true,
+ });
+
+ // copy from localStorage to indexedDB
+ const allChatStoreIndexes: number[] = JSON.parse(
+ localStorage.getItem(STORAGE_NAME_INDEXES) ?? "[]"
+ );
+ let keyCount = 0;
+ for (const i of allChatStoreIndexes) {
+ console.log("importing chatStore from localStorage", i);
+ const key = `${STORAGE_NAME}-${i}`;
+ const val = localStorage.getItem(key);
+ if (val === null) continue;
+ store.add(JSON.parse(val));
+ keyCount += 1;
+ }
+ setSelectedChatIndex(keyCount);
+ alert(
+ "v2.0.0 Update: Imported chat history from localStorage to indexedDB. 🎉"
+ );
+ },
+ });
+
+ const getChatStoreByIndex = async (index: number): Promise => {
+ const ret: ChatStore = await (await db).get(STORAGE_NAME, index);
+ if (ret === null || ret === undefined) return newChatStore();
// handle read from old version chatstore
if (ret.model === undefined) ret.model = "gpt-3.5-turbo";
if (ret.responseModelName === undefined) ret.responseModelName = "";
@@ -168,16 +178,8 @@ export function App() {
return ret;
};
- const [chatStore, _setChatStore] = useState(
- getChatStoreByIndex(selectedChatIndex)
- );
- const setChatStore = (chatStore: ChatStore) => {
- console.log("saved chat", selectedChatIndex, chatStore);
- localStorage.setItem(
- `${STORAGE_NAME}-${selectedChatIndex}`,
- JSON.stringify(chatStore)
- );
-
+ const [chatStore, _setChatStore] = useState(newChatStore());
+ const setChatStore = async (chatStore: ChatStore) => {
console.log("recalculate postBeginIndex");
const max = chatStore.maxTokens - chatStore.tokenMargin;
let sum = 0;
@@ -205,59 +207,75 @@ export function App() {
chatStore.totalTokens += msg.token;
}
+ console.log("saved chat", selectedChatIndex, chatStore);
+ (await db).put(STORAGE_NAME, chatStore, selectedChatIndex);
+
_setChatStore(chatStore);
};
useEffect(() => {
- _setChatStore(getChatStoreByIndex(selectedChatIndex));
+ const run = async () => {
+ _setChatStore(await getChatStoreByIndex(selectedChatIndex));
+ };
+ run();
}, [selectedChatIndex]);
- const handleNewChatStore = () => {
- const max = Math.max(...allChatStoreIndexes);
- const next = max + 1;
- console.log("save next chat", next);
- localStorage.setItem(
- `${STORAGE_NAME}-${next}`,
- JSON.stringify(
- newChatStore(
- chatStore.apiKey,
- chatStore.systemMessageContent,
- chatStore.apiEndpoint,
- chatStore.streamMode,
- chatStore.model,
- chatStore.temperature,
- !!chatStore.develop_mode,
- chatStore.whisper_api,
- chatStore.whisper_key,
- chatStore.tts_api,
- chatStore.tts_key,
- chatStore.tts_speed,
- chatStore.tts_speed_enabled
- )
+ // all chat store indexes
+ const [allChatStoreIndexes, setAllChatStoreIndexes] = useState(
+ []
+ );
+
+ const handleNewChatStore = async () => {
+ const newKey = await (
+ await db
+ ).add(
+ STORAGE_NAME,
+ newChatStore(
+ chatStore.apiKey,
+ chatStore.systemMessageContent,
+ chatStore.apiEndpoint,
+ chatStore.streamMode,
+ chatStore.model,
+ chatStore.temperature,
+ !!chatStore.develop_mode,
+ chatStore.whisper_api,
+ chatStore.whisper_key,
+ chatStore.tts_api,
+ chatStore.tts_key,
+ chatStore.tts_speed,
+ chatStore.tts_speed_enabled
)
);
- allChatStoreIndexes.push(next);
- setAllChatStoreIndexes([...allChatStoreIndexes]);
- setSelectedChatIndex(next);
+ setSelectedChatIndex(newKey as number);
+ setAllChatStoreIndexes(await (await db).getAllKeys(STORAGE_NAME));
};
// if there are any params in URL, create a new chatStore
useEffect(() => {
- const api = getDefaultParams("api", "");
- const key = getDefaultParams("key", "");
- const sys = getDefaultParams("sys", "");
- const mode = getDefaultParams("mode", "");
- const model = getDefaultParams("model", "");
- // 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)
- ) {
- handleNewChatStore();
- }
+ const run = async () => {
+ const api = getDefaultParams("api", "");
+ const key = getDefaultParams("key", "");
+ const sys = getDefaultParams("sys", "");
+ const mode = getDefaultParams("mode", "");
+ const model = getDefaultParams("model", "");
+ // 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)
+ ) {
+ handleNewChatStore();
+ }
+ await db;
+ const allidx = await (await db).getAllKeys(STORAGE_NAME);
+ if (allidx.length === 0) {
+ handleNewChatStore();
+ }
+ setAllChatStoreIndexes(await (await db).getAllKeys(STORAGE_NAME));
+ };
+ run();
}, []);
return (
@@ -271,7 +289,7 @@ export function App() {
{Tr("NEW")}
- {allChatStoreIndexes
+ {(allChatStoreIndexes as number[])
.slice()
.reverse()
.map((i) => {
@@ -295,43 +313,26 @@ export function App() {
+
)}
{chatStore.history.filter((msg) => !msg.example).length == 0 && (
-
+
{Tr("Saved prompt templates")}
))}
-
+
)}
{chatStore.history.length === 0 && (
diff --git a/yarn.lock b/yarn.lock
index 2562763..c695b4d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -773,6 +773,11 @@ hasown@^2.0.0:
dependencies:
function-bind "^1.1.2"
+idb@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.npmmirror.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b"
+ integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"