Compare commits
10 Commits
71cdba59ee
...
2ed7f9d05a
| Author | SHA1 | Date | |
|---|---|---|---|
|
2ed7f9d05a
|
|||
|
e0b50ced12
|
|||
|
80508f9c6c
|
|||
|
14457cbb5f
|
|||
|
f59b63884d
|
|||
|
9b5730760a
|
|||
|
1372121e32
|
|||
|
09e9d18e71
|
|||
|
383d6de1d6
|
|||
|
e56389b4c2
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/dist.zip
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
@@ -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 @@
|
||||
|
||||

|
||||
|
||||
~~让喵娘统治世界吧((发病.webp~~
|
||||
|
||||
## 使用
|
||||
|
||||
以下任意方式都可:
|
||||
|
||||
34
src/app.tsx
34
src/app.tsx
@@ -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];
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
@tailwind base;
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user