Compare commits

...

10 Commits

6 changed files with 67 additions and 33 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/dist.zip
# Logs # Logs
logs logs
*.log *.log

View File

@@ -12,7 +12,7 @@
- 对话记录使用浏览器的 localStorage 保存在本地 - 对话记录使用浏览器的 localStorage 保存在本地
- 可删除对话消息 - 可删除对话消息
- 可以设置 system message (如:"你是一个娘",参见官方 [API 文档](https://platform.openai.com/docs/guides/chat)) - 可以设置 system message (如:"你是一个娘",参见官方 [API 文档](https://platform.openai.com/docs/guides/chat))
- 可以为不同对话设置不同 APIKEY - 可以为不同对话设置不同 APIKEY
- 小(整个网页 30k 左右) - 小(整个网页 30k 左右)
- 可以设置不同的 API Endpoint方便墙内人士使用反向代理转发 API 请求) - 可以设置不同的 API Endpoint方便墙内人士使用反向代理转发 API 请求)
@@ -21,8 +21,6 @@
![screenshot](./screenshot.webp) ![screenshot](./screenshot.webp)
~~让喵娘统治世界吧((发病.webp~~
## 使用 ## 使用
以下任意方式都可: 以下任意方式都可:

View File

@@ -20,7 +20,7 @@ export interface ChatStore {
const _defaultAPIEndpoint = "https://api.openai.com/v1/chat/completions"; const _defaultAPIEndpoint = "https://api.openai.com/v1/chat/completions";
const newChatStore = ( const newChatStore = (
apiKey = "", apiKey = "",
systemMessageContent = "你是一个猫娘,你要模仿猫娘的语气说话", systemMessageContent = "你是一个有用的人工智能助理",
apiEndpoint = _defaultAPIEndpoint, apiEndpoint = _defaultAPIEndpoint,
streamMode = true streamMode = true
): ChatStore => { ): ChatStore => {
@@ -74,24 +74,31 @@ export function App() {
return JSON.parse(val) as ChatStore; return JSON.parse(val) as ChatStore;
}; };
const chatStore = getChatStoreByIndex(selectedChatIndex); const [chatStore, _setChatStore] = useState(
getChatStoreByIndex(selectedChatIndex)
);
const setChatStore = (cs: ChatStore) => { const setChatStore = (cs: ChatStore) => {
console.log("saved chat", selectedChatIndex, chatStore); console.log("saved chat", selectedChatIndex, chatStore);
localStorage.setItem( localStorage.setItem(
`${STORAGE_NAME}-${selectedChatIndex}`, `${STORAGE_NAME}-${selectedChatIndex}`,
JSON.stringify(chatStore) JSON.stringify(cs)
); );
_setChatStore(cs);
}; };
useEffect(() => {
_setChatStore(getChatStoreByIndex(selectedChatIndex));
}, [selectedChatIndex]);
return ( return (
<div className="flex text-sm h-screen bg-slate-200"> <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-4 border-r-indigo-500 border-2"> <div className="flex flex-col h-full p-4 border-r-indigo-500 border-2 dark:border-slate-800 dark:border-r-indigo-500 dark:text-black">
<div className="grow overflow-scroll"> <div className="grow overflow-scroll">
<button <button
className="bg-violet-300 p-1 rounded hover:bg-violet-400" className="bg-violet-300 p-1 rounded hover:bg-violet-400"
onClick={() => { onClick={() => {
const max = Math.max(...allChatStoreIndexes); const max = Math.max(...allChatStoreIndexes);
const next = max + 1; const next = max + 1;
console.log("save next chat", next);
localStorage.setItem( localStorage.setItem(
`${STORAGE_NAME}-${next}`, `${STORAGE_NAME}-${next}`,
JSON.stringify( JSON.stringify(
@@ -138,6 +145,7 @@ export function App() {
onClick={() => { onClick={() => {
if (!confirm("Are you sure you want to delete this chat history?")) if (!confirm("Are you sure you want to delete this chat history?"))
return; return;
console.log("remove item", `${STORAGE_NAME}-${selectedChatIndex}`);
localStorage.removeItem(`${STORAGE_NAME}-${selectedChatIndex}`); localStorage.removeItem(`${STORAGE_NAME}-${selectedChatIndex}`);
const newAllChatStoreIndexes = [ const newAllChatStoreIndexes = [
...allChatStoreIndexes.filter((v) => v !== selectedChatIndex), ...allChatStoreIndexes.filter((v) => v !== selectedChatIndex),
@@ -145,15 +153,15 @@ export function App() {
if (newAllChatStoreIndexes.length === 0) { if (newAllChatStoreIndexes.length === 0) {
newAllChatStoreIndexes.push(0); newAllChatStoreIndexes.push(0);
setChatStore(
newChatStore(
chatStore.apiKey,
chatStore.systemMessageContent,
chatStore.apiEndpoint,
chatStore.streamMode
)
);
} }
setChatStore(
newChatStore(
chatStore.apiKey,
chatStore.systemMessageContent,
chatStore.apiEndpoint,
chatStore.streamMode
)
);
// find nex selected chat index // find nex selected chat index
const next = newAllChatStoreIndexes[0]; const next = newAllChatStoreIndexes[0];

View File

@@ -22,8 +22,8 @@ export default function ChatBOX(props: {
console.log("response", response); console.log("response", response);
const reader = response.body?.getReader(); const reader = response.body?.getReader();
const allChunkMessage: string[] = []; const allChunkMessage: string[] = [];
await new ReadableStream({ new ReadableStream({
async start(controller) { async start() {
while (true) { while (true) {
let responseDone = false; let responseDone = false;
let state = await reader?.read(); let state = await reader?.read();
@@ -56,9 +56,11 @@ export default function ChatBOX(props: {
.join(""); .join("");
// console.log("chunk text", chunkText); // console.log("chunk text", chunkText);
allChunkMessage.push(chunkText); allChunkMessage.push(chunkText);
setShowGenerating(true);
setGeneratingMessage(allChunkMessage.join("")); setGeneratingMessage(allChunkMessage.join(""));
if (responseDone) break; if (responseDone) break;
} }
setShowGenerating(false);
// console.log("push to history", allChunkMessage); // console.log("push to history", allChunkMessage);
chatStore.history.push({ chatStore.history.push({
@@ -133,14 +135,17 @@ export default function ChatBOX(props: {
const [showSettings, setShowSettings] = useState(false); const [showSettings, setShowSettings] = useState(false);
return ( return (
<div className="grow flex flex-col p-4"> <div className="grow flex flex-col p-4 dark:text-black">
<Settings <Settings
chatStore={chatStore} chatStore={chatStore}
setChatStore={setChatStore} setChatStore={setChatStore}
show={showSettings} show={showSettings}
setShow={setShowSettings} setShow={setShowSettings}
/> />
<p className="cursor-pointer" onClick={() => setShowSettings(true)}> <p
className="cursor-pointer dark:text-white"
onClick={() => setShowSettings(true)}
>
<div> <div>
<button className="underline"> <button className="underline">
{chatStore.systemMessageContent.length > 16 {chatStore.systemMessageContent.length > 16
@@ -163,24 +168,24 @@ export default function ChatBOX(props: {
</p> </p>
<div className="grow overflow-scroll"> <div className="grow overflow-scroll">
{!chatStore.apiKey && ( {!chatStore.apiKey && (
<p className="opacity-60 p-6 rounded bg-white my-3 text-left"> <p className="opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
(OPENAI) API KEY (OPENAI) API KEY
</p> </p>
)} )}
{!chatStore.apiEndpoint && ( {!chatStore.apiEndpoint && (
<p className="opacity-60 p-6 rounded bg-white my-3 text-left"> <p className="opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
API Endpoint API Endpoint
</p> </p>
)} )}
{chatStore.history.length === 0 && ( {chatStore.history.length === 0 && (
<p className="opacity-60 p-6 rounded bg-white my-3 text-left"> <p className="opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
QwQ
</p> </p>
)} )}
{chatStore.history.map((chat, i) => { {chatStore.history.map((chat, i) => {
const pClassName = const pClassName =
chat.role === "assistant" chat.role === "assistant"
? "p-2 rounded relative bg-white my-2 text-left" ? "p-2 rounded relative bg-white my-2 text-left dark:bg-gray-700 dark:text-white"
: "p-2 rounded relative bg-green-400 my-2 text-right"; : "p-2 rounded relative bg-green-400 my-2 text-right";
const iconClassName = const iconClassName =
chat.role === "user" chat.role === "user"
@@ -223,10 +228,10 @@ export default function ChatBOX(props: {
); );
})} })}
{showGenerating && ( {showGenerating && (
<p className="p-2 my-2 animate-pulse"> <p className="p-2 my-2 animate-pulse dark:text-white">
{generatingMessage {generatingMessage
? generatingMessage.split("\n").map((line) => <p>{line}</p>) ? generatingMessage.split("\n").map((line) => <p>{line}</p>)
: "生成中,保持网络稳定"} : "生成中,保持网络稳定"}
... ...
</p> </p>
)} )}

View File

@@ -1,3 +1,23 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
/* Hide scrollbar for webkit based browsers */
::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for moz based browsers */
::-moz-scrollbar {
display: none;
}
/* Hide scrollbar for IE/Edge based browsers */
::-ms-scrollbar {
display: none;
}
/* Hide scrollbar for all based browsers */
body::-webkit-scrollbar {
display: none;
}

View File

@@ -97,11 +97,11 @@ export default (props: {
"//" + "//" +
location.host + location.host +
location.pathname + location.pathname +
`?sys=${encodeURIComponent( `?key=${encodeURIComponent(
props.chatStore.systemMessageContent props.chatStore.apiKey
)}&api=${encodeURIComponent(props.chatStore.apiEndpoint)}&mode=${ )}&api=${encodeURIComponent(props.chatStore.apiEndpoint)}&mode=${
props.chatStore.streamMode ? "stream" : "fetch" props.chatStore.streamMode ? "stream" : "fetch"
}`; }&sys=${encodeURIComponent(props.chatStore.systemMessageContent)}`;
return ( return (
<div className="left-0 top-0 overflow-scroll flex justify-center absolute w-screen h-screen bg-black bg-opacity-50 z-10"> <div className="left-0 top-0 overflow-scroll flex justify-center absolute w-screen h-screen bg-black bg-opacity-50 z-10">
<div className="m-2 p-2 bg-white rounded-lg h-fit"> <div className="m-2 p-2 bg-white rounded-lg h-fit">
@@ -170,6 +170,8 @@ export default (props: {
) )
return; return;
props.chatStore.history = []; props.chatStore.history = [];
props.chatStore.postBeginIndex = 0;
props.chatStore.totalTokens = 0;
props.setChatStore({ ...props.chatStore }); props.setChatStore({ ...props.chatStore });
}} }}
> >