mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-07-28 17:53:21 +08:00
feat: implement stores and basic chat logic
This commit is contained in:
13
components/ChatView/MessageView.tsx
Normal file
13
components/ChatView/MessageView.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Message } from "../../types";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
message: Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Message = (props: Props) => {
|
||||||
|
const message = props.message;
|
||||||
|
|
||||||
|
return <div>{message.content}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Message;
|
32
components/ChatView/Sidebar.tsx
Normal file
32
components/ChatView/Sidebar.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { useChatStore, useUserStore } from "../../store";
|
||||||
|
import { User } from "../../types";
|
||||||
|
|
||||||
|
const Sidebar = () => {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
|
const handleAssistantClick = (user: User) => {
|
||||||
|
for (const chat of chatStore.chatList) {
|
||||||
|
if (chat.userId === user.id) {
|
||||||
|
chatStore.setCurrentChat(chat);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chatStore.createChat(user);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full border-r p-2">
|
||||||
|
<h2>Assistant list</h2>
|
||||||
|
<div>
|
||||||
|
{userStore.assistantList.map((assistant) => (
|
||||||
|
<p onClick={() => handleAssistantClick(assistant)} key={assistant.id}>
|
||||||
|
{assistant.name}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
59
components/ChatView/Textarea.tsx
Normal file
59
components/ChatView/Textarea.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import { useChatStore, useMessageStore, useUserStore } from "../../store";
|
||||||
|
import { UserRole } from "../../types";
|
||||||
|
import { generateUUID } from "../../utils";
|
||||||
|
import Icon from "../Icon";
|
||||||
|
|
||||||
|
const Textarea = () => {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
const messageStore = useMessageStore();
|
||||||
|
const [value, setValue] = useState<string>("");
|
||||||
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setValue(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSend = async () => {
|
||||||
|
if (!chatStore.currentChat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentChat = chatStore.currentChat;
|
||||||
|
messageStore.addMessage({
|
||||||
|
id: generateUUID(),
|
||||||
|
chatId: currentChat.id,
|
||||||
|
creatorId: userStore.currentUser.id,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
content: value,
|
||||||
|
});
|
||||||
|
setValue("");
|
||||||
|
textareaRef.current!.value = "";
|
||||||
|
|
||||||
|
const messageList = messageStore.getState().messageList.filter((message) => message.chatId === currentChat.id);
|
||||||
|
const { data } = await axios.post<string>("/api/chat", {
|
||||||
|
messages: messageList.map((message) => ({
|
||||||
|
role: message.creatorId === userStore.currentUser.id ? UserRole.User : UserRole.Assistant,
|
||||||
|
content: message.content,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
messageStore.addMessage({
|
||||||
|
id: generateUUID(),
|
||||||
|
chatId: currentChat.id,
|
||||||
|
creatorId: currentChat.userId,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
content: data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full h-auto border-t relative">
|
||||||
|
<textarea ref={textareaRef} className="w-full h-full outline-none pt-2 px-2 resize-none" onChange={handleChange} rows={1} />
|
||||||
|
<Icon.Send className="absolute bottom-2 right-2" onClick={handleSend} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Textarea;
|
28
components/ChatView/index.tsx
Normal file
28
components/ChatView/index.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useChatStore, useMessageStore, useUserStore } from "../../store";
|
||||||
|
import MessageView from "./MessageView";
|
||||||
|
import Sidebar from "./Sidebar";
|
||||||
|
import Textarea from "./Textarea";
|
||||||
|
|
||||||
|
const ChatView = () => {
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const messageStore = useMessageStore();
|
||||||
|
const currentChat = chatStore.currentChat;
|
||||||
|
const chatTitle = currentChat ? userStore.getAssistantById(currentChat.userId)?.name : "No chat";
|
||||||
|
const messageList = messageStore.messageList.filter((message) => message.chatId === currentChat?.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-full lg:max-w-3xl border rounded-md grid grid-cols-[192px_1fr]">
|
||||||
|
<Sidebar />
|
||||||
|
<main className="w-full">
|
||||||
|
<p className="w-full text-center py-2 border-b">{chatTitle}</p>
|
||||||
|
<div className="py-2">
|
||||||
|
{messageList.length === 0 ? <p>no message</p> : messageList.map((message) => <MessageView key={message.id} message={message} />)}
|
||||||
|
</div>
|
||||||
|
<Textarea />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatView;
|
3
components/Icon.tsx
Normal file
3
components/Icon.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import * as Icon from "lucide-react";
|
||||||
|
|
||||||
|
export default Icon;
|
@ -8,15 +8,22 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/analytics": "^0.1.11",
|
"@vercel/analytics": "^0.1.11",
|
||||||
|
"axios": "^1.3.4",
|
||||||
|
"csstype": "^3.1.1",
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.7.0",
|
||||||
|
"lucide-react": "^0.125.0",
|
||||||
"next": "^13.2.4",
|
"next": "^13.2.4",
|
||||||
"openai": "^3.0.0",
|
"openai": "^3.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hot-toast": "^2.4.0",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
|
"zustand": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"eslint": "8.20.0",
|
"eslint": "8.20.0",
|
||||||
"eslint-config-next": "12.2.3",
|
"eslint-config-next": "12.2.3",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AppProps } from "next/app";
|
import { AppProps } from "next/app";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Analytics } from "@vercel/analytics/react";
|
import { Analytics } from "@vercel/analytics/react";
|
||||||
import "./styles/globals.css";
|
import "../styles/global.css";
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import openai, { ChatCompletionResponse } from "./openai-api";
|
import openai from "../../utils/openai-api";
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse<ChatCompletionResponse>) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const completionResponse = await openai.createChatCompletion({
|
const completionResponse = await openai.createChatCompletion({
|
||||||
model: "gpt-3.5-turbo",
|
model: "gpt-3.5-turbo",
|
||||||
messages: req.body.messages,
|
messages: req.body.messages,
|
||||||
max_tokens: 2000,
|
max_tokens: 2000,
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
top_p: 1.0,
|
|
||||||
frequency_penalty: 0.0,
|
frequency_penalty: 0.0,
|
||||||
presence_penalty: 0.0,
|
presence_penalty: 0.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).json({ message: completionResponse.data.choices[0].message! });
|
res.status(200).json(completionResponse.data.choices[0].message?.content || "");
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO(steven): Implement a generic getChatPrompt function that takes in a
|
// TODO(steven): Implement a generic getChatPrompt function that takes in a
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import React, { useEffect } from "react";
|
import React from "react";
|
||||||
|
import ChatView from "../components/ChatView";
|
||||||
|
|
||||||
const ChatPage: NextPage = () => {
|
const ChatPage: NextPage = () => {
|
||||||
useEffect(() => {
|
|
||||||
// todo
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Head>
|
<Head>
|
||||||
@ -15,7 +12,9 @@ const ChatPage: NextPage = () => {
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<main className="flex flex-col items-center justify-center m-20">WIP</main>
|
<main className="w-full min-h-screen flex flex-col items-center justify-center">
|
||||||
|
<ChatView />
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -12,12 +12,12 @@ const HomePage: NextPage = () => {
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<main className="flex min-h-screen flex-col items-center justify-center bg-black">
|
<main className="w-full min-h-screen flex flex-col items-center justify-center">
|
||||||
<div className="flex flex-col items-center justify-center gap-12 px-4 py-16">
|
<div className="flex flex-col items-center justify-center px-4 py-16">
|
||||||
<h1 className="text-5xl font-extrabold tracking-tight text-white sm:text-6xl">ChatDBA</h1>
|
<h1 className="text-5xl font-extrabold sm:text-6xl">ChatDBA</h1>
|
||||||
<div className="grid grid-cols-1 gap-4">
|
<div className="grid grid-cols-1 mt-8">
|
||||||
<Link className="flex max-w-xs flex-col rounded-xl bg-gray-800 p-4 px-6 text-white hover:opacity-80" href="/chat">
|
<Link className="flex max-w-xs flex-col rounded-xl bg-gray-800 p-4 px-6 text-white hover:opacity-80" href="/chat">
|
||||||
<h3 className="text-2xl font-medium">Chat →</h3>
|
<h3 className="text-xl font-medium">Chat →</h3>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
92
pnpm-lock.yaml
generated
92
pnpm-lock.yaml
generated
@ -3,30 +3,44 @@ lockfileVersion: 5.4
|
|||||||
specifiers:
|
specifiers:
|
||||||
'@types/node': ^18.11.18
|
'@types/node': ^18.11.18
|
||||||
'@types/react': ^18.0.26
|
'@types/react': ^18.0.26
|
||||||
|
'@types/uuid': ^9.0.1
|
||||||
'@vercel/analytics': ^0.1.11
|
'@vercel/analytics': ^0.1.11
|
||||||
autoprefixer: ^10.4.13
|
autoprefixer: ^10.4.13
|
||||||
|
axios: ^1.3.4
|
||||||
|
csstype: ^3.1.1
|
||||||
eslint: 8.20.0
|
eslint: 8.20.0
|
||||||
eslint-config-next: 12.2.3
|
eslint-config-next: 12.2.3
|
||||||
highlight.js: ^11.7.0
|
highlight.js: ^11.7.0
|
||||||
|
lucide-react: ^0.125.0
|
||||||
next: ^13.2.4
|
next: ^13.2.4
|
||||||
openai: ^3.0.0
|
openai: ^3.0.0
|
||||||
postcss: ^8.4.20
|
postcss: ^8.4.20
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
react-dom: ^18.2.0
|
react-dom: ^18.2.0
|
||||||
|
react-hot-toast: ^2.4.0
|
||||||
tailwindcss: ^3.2.4
|
tailwindcss: ^3.2.4
|
||||||
typescript: ^4.9.4
|
typescript: ^4.9.4
|
||||||
|
uuid: ^9.0.0
|
||||||
|
zustand: ^4.3.6
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vercel/analytics': 0.1.11_react@18.2.0
|
'@vercel/analytics': 0.1.11_react@18.2.0
|
||||||
|
axios: 1.3.4
|
||||||
|
csstype: 3.1.1
|
||||||
highlight.js: 11.7.0
|
highlight.js: 11.7.0
|
||||||
|
lucide-react: 0.125.0_react@18.2.0
|
||||||
next: 13.2.4_biqbaboplfbrettd7655fr4n2y
|
next: 13.2.4_biqbaboplfbrettd7655fr4n2y
|
||||||
openai: 3.2.1
|
openai: 3.2.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
react-hot-toast: 2.4.0_owo25xnefcwdq3zjgtohz6dbju
|
||||||
|
uuid: 9.0.0
|
||||||
|
zustand: 4.3.6_react@18.2.0
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node': 18.15.3
|
'@types/node': 18.15.3
|
||||||
'@types/react': 18.0.28
|
'@types/react': 18.0.28
|
||||||
|
'@types/uuid': 9.0.1
|
||||||
autoprefixer: 10.4.14_postcss@8.4.21
|
autoprefixer: 10.4.14_postcss@8.4.21
|
||||||
eslint: 8.20.0
|
eslint: 8.20.0
|
||||||
eslint-config-next: 12.2.3_bqegqxcnsisudkhpmmezgt6uoa
|
eslint-config-next: 12.2.3_bqegqxcnsisudkhpmmezgt6uoa
|
||||||
@ -257,6 +271,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
|
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/uuid/9.0.1:
|
||||||
|
resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/parser/5.55.0_bqegqxcnsisudkhpmmezgt6uoa:
|
/@typescript-eslint/parser/5.55.0_bqegqxcnsisudkhpmmezgt6uoa:
|
||||||
resolution: {integrity: sha512-ppvmeF7hvdhUUZWSd2EEWfzcFkjJzgNQzVST22nzg958CR+sphy8A6K7LXQZd6V75m1VKjp+J4g/PCEfSCmzhw==}
|
resolution: {integrity: sha512-ppvmeF7hvdhUUZWSd2EEWfzcFkjJzgNQzVST22nzg958CR+sphy8A6K7LXQZd6V75m1VKjp+J4g/PCEfSCmzhw==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
@ -498,6 +516,16 @@ packages:
|
|||||||
- debug
|
- debug
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/axios/1.3.4:
|
||||||
|
resolution: {integrity: sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==}
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.2
|
||||||
|
form-data: 4.0.0
|
||||||
|
proxy-from-env: 1.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
dev: false
|
||||||
|
|
||||||
/axobject-query/3.1.1:
|
/axobject-query/3.1.1:
|
||||||
resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==}
|
resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -624,7 +652,6 @@ packages:
|
|||||||
|
|
||||||
/csstype/3.1.1:
|
/csstype/3.1.1:
|
||||||
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
|
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/damerau-levenshtein/1.0.8:
|
/damerau-levenshtein/1.0.8:
|
||||||
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
||||||
@ -1306,6 +1333,14 @@ packages:
|
|||||||
slash: 3.0.0
|
slash: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/goober/2.1.12_csstype@3.1.1:
|
||||||
|
resolution: {integrity: sha512-yXHAvO08FU1JgTXX6Zn6sYCUFfB/OJSX8HHjDSgerZHZmFKAb08cykp5LBw5QnmyMcZyPRMqkdyHUSSzge788Q==}
|
||||||
|
peerDependencies:
|
||||||
|
csstype: ^3.0.10
|
||||||
|
dependencies:
|
||||||
|
csstype: 3.1.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/gopd/1.0.1:
|
/gopd/1.0.1:
|
||||||
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
|
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1623,6 +1658,14 @@ packages:
|
|||||||
yallist: 4.0.0
|
yallist: 4.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/lucide-react/0.125.0_react@18.2.0:
|
||||||
|
resolution: {integrity: sha512-tadphtB6TPytEitR9vX75hqu9PQT/uz5RcvXMq976nC190eukAM9+cHMgBxfvfEGDXwIhIT9aFxTUGdAjxw9uQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.5.1 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/merge2/1.4.1:
|
/merge2/1.4.1:
|
||||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@ -1965,6 +2008,10 @@ packages:
|
|||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/proxy-from-env/1.1.0:
|
||||||
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/punycode/2.3.0:
|
/punycode/2.3.0:
|
||||||
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -1989,6 +2036,20 @@ packages:
|
|||||||
scheduler: 0.23.0
|
scheduler: 0.23.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-hot-toast/2.4.0_owo25xnefcwdq3zjgtohz6dbju:
|
||||||
|
resolution: {integrity: sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16'
|
||||||
|
react-dom: '>=16'
|
||||||
|
dependencies:
|
||||||
|
goober: 2.1.12_csstype@3.1.1
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- csstype
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-is/16.13.1:
|
/react-is/16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -2343,10 +2404,23 @@ packages:
|
|||||||
punycode: 2.3.0
|
punycode: 2.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/use-sync-external-store/1.2.0_react@18.2.0:
|
||||||
|
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/util-deprecate/1.0.2:
|
/util-deprecate/1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/uuid/9.0.0:
|
||||||
|
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/v8-compile-cache/2.3.0:
|
/v8-compile-cache/2.3.0:
|
||||||
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
|
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -2412,3 +2486,19 @@ packages:
|
|||||||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/zustand/4.3.6_react@18.2.0:
|
||||||
|
resolution: {integrity: sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==}
|
||||||
|
engines: {node: '>=12.7.0'}
|
||||||
|
peerDependencies:
|
||||||
|
immer: '>=9.0'
|
||||||
|
react: '>=16.8'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
immer:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
use-sync-external-store: 1.2.0_react@18.2.0
|
||||||
|
dev: false
|
||||||
|
31
store/chat.ts
Normal file
31
store/chat.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { Chat, User } from "../types";
|
||||||
|
import { generateUUID } from "../utils";
|
||||||
|
|
||||||
|
const defaultChat: Chat = {
|
||||||
|
id: generateUUID(),
|
||||||
|
userId: "assistant-chatgpt",
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ChatState {
|
||||||
|
chatList: Chat[];
|
||||||
|
currentChat: Chat;
|
||||||
|
createChat: (user: User) => void;
|
||||||
|
setCurrentChat: (chat: Chat) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useChatStore = create<ChatState>((set) => ({
|
||||||
|
chatList: [],
|
||||||
|
currentChat: defaultChat,
|
||||||
|
createChat: (user: User) => {
|
||||||
|
const chat = {
|
||||||
|
id: generateUUID(),
|
||||||
|
userId: user.id,
|
||||||
|
};
|
||||||
|
set((state) => ({
|
||||||
|
chatList: [...state.chatList, chat],
|
||||||
|
currentChat: chat,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
setCurrentChat: (chat: Chat) => set(() => ({ currentChat: chat })),
|
||||||
|
}));
|
3
store/index.ts
Normal file
3
store/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./user";
|
||||||
|
export * from "./chat";
|
||||||
|
export * from "./message";
|
14
store/message.ts
Normal file
14
store/message.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { Message } from "../types";
|
||||||
|
|
||||||
|
interface MessageState {
|
||||||
|
messageList: Message[];
|
||||||
|
getState: () => MessageState;
|
||||||
|
addMessage: (message: Message) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMessageStore = create<MessageState>((set, get) => ({
|
||||||
|
messageList: [],
|
||||||
|
getState: () => get(),
|
||||||
|
addMessage: (message: Message) => set((state) => ({ messageList: [...state.messageList, message] })),
|
||||||
|
}));
|
36
store/user.ts
Normal file
36
store/user.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { Id, User, UserRole } from "../types";
|
||||||
|
|
||||||
|
const assistantList: User[] = [
|
||||||
|
{
|
||||||
|
id: "assistant-chatgpt",
|
||||||
|
name: "Origin ChatGPT",
|
||||||
|
description: "",
|
||||||
|
avatar: "",
|
||||||
|
role: UserRole.Assistant,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const localUser: User = {
|
||||||
|
id: "local-user",
|
||||||
|
name: "Local user",
|
||||||
|
description: "",
|
||||||
|
avatar: "",
|
||||||
|
role: UserRole.User,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UserState {
|
||||||
|
// We can think assistants are special users.
|
||||||
|
assistantList: User[];
|
||||||
|
currentUser: User;
|
||||||
|
getAssistantById: (id: string) => User | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserStore = create<UserState>((set) => ({
|
||||||
|
assistantList: assistantList,
|
||||||
|
currentUser: localUser,
|
||||||
|
getAssistantById: (id: Id) => {
|
||||||
|
const user = assistantList.find((user) => user.id === id);
|
||||||
|
return user || undefined;
|
||||||
|
},
|
||||||
|
}));
|
6
types/chat.ts
Normal file
6
types/chat.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Id } from "./common";
|
||||||
|
|
||||||
|
export interface Chat {
|
||||||
|
id: string;
|
||||||
|
userId: Id;
|
||||||
|
}
|
2
types/common.ts
Normal file
2
types/common.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export type Id = string;
|
||||||
|
export type Timestamp = number;
|
4
types/index.ts
Normal file
4
types/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./common";
|
||||||
|
export * from "./user";
|
||||||
|
export * from "./chat";
|
||||||
|
export * from "./message";
|
9
types/message.ts
Normal file
9
types/message.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Id, Timestamp } from "./";
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
id: Id;
|
||||||
|
chatId: string;
|
||||||
|
creatorId: Id;
|
||||||
|
createdAt: Timestamp;
|
||||||
|
content: string;
|
||||||
|
}
|
13
types/user.ts
Normal file
13
types/user.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export enum UserRole {
|
||||||
|
System = "system",
|
||||||
|
User = "user",
|
||||||
|
Assistant = "assistant",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
avatar: string;
|
||||||
|
role: UserRole;
|
||||||
|
}
|
5
utils/index.ts
Normal file
5
utils/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
export const generateUUID = () => {
|
||||||
|
return uuidv4();
|
||||||
|
};
|
@ -7,6 +7,7 @@ export interface ChatCompletionResponse {
|
|||||||
const configuration = new Configuration({
|
const configuration = new Configuration({
|
||||||
apiKey: process.env.OPENAI_API_KEY,
|
apiKey: process.env.OPENAI_API_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
const openai = new OpenAIApi(configuration);
|
const openai = new OpenAIApi(configuration);
|
||||||
|
|
||||||
export default openai;
|
export default openai;
|
Reference in New Issue
Block a user