182 lines
5.8 KiB
TypeScript
182 lines
5.8 KiB
TypeScript
import { useEffect, useState } from "preact/hooks";
|
|
import "./global.css";
|
|
|
|
import { Message } from "./chatgpt";
|
|
import getDefaultParams from "./getDefaultParam";
|
|
import ChatBOX from "./chatbox";
|
|
|
|
export interface ChatStore {
|
|
systemMessageContent: string;
|
|
history: Message[];
|
|
postBeginIndex: number;
|
|
tokenMargin: number;
|
|
totalTokens: number;
|
|
maxTokens: number;
|
|
apiKey: string;
|
|
apiEndpoint: string;
|
|
streamMode: boolean;
|
|
}
|
|
|
|
const _defaultAPIEndpoint = "https://api.openai.com/v1/chat/completions";
|
|
const newChatStore = (
|
|
apiKey = "",
|
|
systemMessageContent = "你是一个有用的人工智能助理",
|
|
apiEndpoint = _defaultAPIEndpoint,
|
|
streamMode = true
|
|
): ChatStore => {
|
|
return {
|
|
systemMessageContent: getDefaultParams("sys", systemMessageContent),
|
|
history: [],
|
|
postBeginIndex: 0,
|
|
tokenMargin: 1024,
|
|
totalTokens: 0,
|
|
maxTokens: 4096,
|
|
apiKey: getDefaultParams("key", apiKey),
|
|
apiEndpoint: getDefaultParams("api", apiEndpoint),
|
|
streamMode: getDefaultParams("mode", streamMode),
|
|
};
|
|
};
|
|
|
|
const STORAGE_NAME = "chatgpt-api-web";
|
|
const STORAGE_NAME_SELECTED = `${STORAGE_NAME}-selected`;
|
|
const STORAGE_NAME_INDEXES = `${STORAGE_NAME}-indexes`;
|
|
|
|
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")
|
|
);
|
|
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();
|
|
return JSON.parse(val) as ChatStore;
|
|
};
|
|
|
|
const [chatStore, _setChatStore] = useState(
|
|
getChatStoreByIndex(selectedChatIndex)
|
|
);
|
|
const setChatStore = (cs: ChatStore) => {
|
|
console.log("saved chat", selectedChatIndex, chatStore);
|
|
localStorage.setItem(
|
|
`${STORAGE_NAME}-${selectedChatIndex}`,
|
|
JSON.stringify(cs)
|
|
);
|
|
_setChatStore(cs);
|
|
};
|
|
useEffect(() => {
|
|
_setChatStore(getChatStoreByIndex(selectedChatIndex));
|
|
}, [selectedChatIndex]);
|
|
|
|
return (
|
|
<div className="flex text-sm h-screen bg-slate-200 dark:bg-slate-800 dark:text-white">
|
|
<div className="flex flex-col h-full p-2 border-r-indigo-500 border-2 dark:border-slate-800 dark:border-r-indigo-500 dark:text-black">
|
|
<div className="grow overflow-scroll">
|
|
<button
|
|
className="bg-violet-300 p-1 rounded hover:bg-violet-400"
|
|
onClick={() => {
|
|
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
|
|
)
|
|
)
|
|
);
|
|
allChatStoreIndexes.push(next);
|
|
setAllChatStoreIndexes([...allChatStoreIndexes]);
|
|
setSelectedChatIndex(next);
|
|
}}
|
|
>
|
|
NEW
|
|
</button>
|
|
<ul>
|
|
{allChatStoreIndexes
|
|
.slice()
|
|
.reverse()
|
|
.map((i) => {
|
|
// reverse
|
|
return (
|
|
<li>
|
|
<button
|
|
className={`w-full my-1 p-1 rounded hover:bg-blue-300 ${
|
|
i === selectedChatIndex ? "bg-blue-500" : "bg-blue-200"
|
|
}`}
|
|
onClick={() => {
|
|
setSelectedChatIndex(i);
|
|
}}
|
|
>
|
|
{i}
|
|
</button>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
<button
|
|
className="rounded bg-rose-400 p-1 my-1 w-full"
|
|
onClick={() => {
|
|
if (!confirm("Are you sure you want to delete this chat history?"))
|
|
return;
|
|
console.log("remove item", `${STORAGE_NAME}-${selectedChatIndex}`);
|
|
localStorage.removeItem(`${STORAGE_NAME}-${selectedChatIndex}`);
|
|
const newAllChatStoreIndexes = [
|
|
...allChatStoreIndexes.filter((v) => v !== selectedChatIndex),
|
|
];
|
|
|
|
if (newAllChatStoreIndexes.length === 0) {
|
|
newAllChatStoreIndexes.push(0);
|
|
setChatStore(
|
|
newChatStore(
|
|
chatStore.apiKey,
|
|
chatStore.systemMessageContent,
|
|
chatStore.apiEndpoint,
|
|
chatStore.streamMode
|
|
)
|
|
);
|
|
}
|
|
|
|
// find nex selected chat index
|
|
const next =
|
|
newAllChatStoreIndexes[newAllChatStoreIndexes.length - 1];
|
|
console.log("next is", next);
|
|
setSelectedChatIndex(next);
|
|
|
|
setAllChatStoreIndexes([...newAllChatStoreIndexes]);
|
|
}}
|
|
>
|
|
DEL
|
|
</button>
|
|
</div>
|
|
<ChatBOX chatStore={chatStore} setChatStore={setChatStore} />
|
|
</div>
|
|
);
|
|
}
|