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
*.log

View File

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

View File

@@ -20,7 +20,7 @@ export interface ChatStore {
const _defaultAPIEndpoint = "https://api.openai.com/v1/chat/completions";
const newChatStore = (
apiKey = "",
systemMessageContent = "你是一个猫娘,你要模仿猫娘的语气说话",
systemMessageContent = "你是一个有用的人工智能助理",
apiEndpoint = _defaultAPIEndpoint,
streamMode = true
): ChatStore => {
@@ -74,24 +74,31 @@ export function App() {
return JSON.parse(val) as ChatStore;
};
const chatStore = getChatStoreByIndex(selectedChatIndex);
const [chatStore, _setChatStore] = useState(
getChatStoreByIndex(selectedChatIndex)
);
const setChatStore = (cs: ChatStore) => {
console.log("saved chat", selectedChatIndex, chatStore);
localStorage.setItem(
`${STORAGE_NAME}-${selectedChatIndex}`,
JSON.stringify(chatStore)
JSON.stringify(cs)
);
_setChatStore(cs);
};
useEffect(() => {
_setChatStore(getChatStoreByIndex(selectedChatIndex));
}, [selectedChatIndex]);
return (
<div className="flex text-sm h-screen bg-slate-200">
<div className="flex flex-col h-full p-4 border-r-indigo-500 border-2">
<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 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(
@@ -138,6 +145,7 @@ export function App() {
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),
@@ -145,15 +153,15 @@ export function App() {
if (newAllChatStoreIndexes.length === 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
const next = newAllChatStoreIndexes[0];

View File

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

View File

@@ -1,3 +1,23 @@
@tailwind base;
@tailwind components;
@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.pathname +
`?sys=${encodeURIComponent(
props.chatStore.systemMessageContent
`?key=${encodeURIComponent(
props.chatStore.apiKey
)}&api=${encodeURIComponent(props.chatStore.apiEndpoint)}&mode=${
props.chatStore.streamMode ? "stream" : "fetch"
}`;
}&sys=${encodeURIComponent(props.chatStore.systemMessageContent)}`;
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="m-2 p-2 bg-white rounded-lg h-fit">
@@ -170,6 +170,8 @@ export default (props: {
)
return;
props.chatStore.history = [];
props.chatStore.postBeginIndex = 0;
props.chatStore.totalTokens = 0;
props.setChatStore({ ...props.chatStore });
}}
>