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

|

|
||||||
|
|
||||||
~~让喵娘统治世界吧((发病.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 _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];
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user