diff --git a/models/Conversation.js b/models/Conversation.js index 69743c5d37..1096a6dcc2 100644 --- a/models/Conversation.js +++ b/models/Conversation.js @@ -1,5 +1,5 @@ const mongoose = require('mongoose'); -const { getMessages } = require('./Message'); +const { getMessages, deleteMessages } = require('./Message'); const convoSchema = mongoose.Schema({ conversationId: { @@ -26,7 +26,7 @@ const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema); module.exports = { - saveConversation: async ({ conversationId, parentMessageId, title }) => { + saveConvo: async ({ conversationId, parentMessageId, title }) => { const messages = await getMessages({ conversationId }); const update = { parentMessageId, messages }; if (title) { @@ -39,5 +39,16 @@ module.exports = { { new: true, upsert: true } ).exec(); }, - getConversations: async () => await Conversation.find({}).exec(), + getConvos: async () => await Conversation.find({}).exec(), + deleteConvos: async (filter) => { + // const filter = {}; + + // if (!!conversationId) { + // filter = conversationId; + // } + + let deleteCount = await Conversation.deleteMany(filter).exec(); + deleteCount.messages = await deleteMessages(filter); + return deleteCount; + } }; diff --git a/server/index.js b/server/index.js index b8958cd16c..5fa4992680 100644 --- a/server/index.js +++ b/server/index.js @@ -1,8 +1,8 @@ const express = require('express'); const dbConnect = require('../models/dbConnect'); const { ask, titleConversation } = require('../app/chatgpt'); -const { saveMessage, getMessages, deleteAllMessages } = require('../models/Message'); -const { saveConversation, getConversations } = require('../models/Conversation'); +const { saveMessage, getMessages } = require('../models/Message'); +const { saveConvo, getConvos, deleteConvos } = require('../models/Conversation'); const crypto = require('crypto'); const path = require('path'); const cors = require('cors'); @@ -22,7 +22,7 @@ app.get('/', function (req, res) { }); app.get('/convos', async (req, res) => { - res.status(200).send(await getConversations()); + res.status(200).send(await getConvos()); }); app.get('/messages/:conversationId', async (req, res) => { @@ -31,10 +31,20 @@ app.get('/messages/:conversationId', async (req, res) => { }); app.post('/clear_convos', async (req, res) => { - const { conversationId } = req.body; + let filter = {}; + const { conversationId } = req.body.arg; console.log('conversationId', conversationId); - const filter = {}; - res.status(201).send(await deleteAllMessages(filter)); + if (!!conversationId) { + filter = { conversationId }; + } + + try { + const dbResponse = await deleteConvos(filter); + res.status(201).send(dbResponse); + } catch (error) { + console.error(error); + res.status(500).send(error); + } }); app.post('/ask', async (req, res) => { @@ -51,6 +61,8 @@ app.post('/ask', async (req, res) => { 'X-Accel-Buffering': 'no' }); + // res.write(`event: message\ndata: ${JSON.stringify('')}\n\n`); + let i = 0; const progressCallback = async (partial) => { // console.log('partial', partial); @@ -74,7 +86,7 @@ app.post('/ask', async (req, res) => { gptResponse.sender = 'GPT'; await saveMessage(gptResponse); - await saveConversation(gptResponse); + await saveConvo(gptResponse); res.write(`event: message\ndata: ${JSON.stringify(gptResponse)}\n\n`); res.end(); diff --git a/src/App.jsx b/src/App.jsx index 8a9153ac6d..2db12c6127 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,14 +4,14 @@ import Messages from './components/main/Messages'; import TextChat from './components/main/TextChat'; import Nav from './components/Nav'; import MobileNav from './components/Nav/MobileNav'; -import useSWR from 'swr'; - -const fetcher = (url) => fetch(url).then((res) => res.json()); -// const postRequest = async (url, { arg }) => await axios.post(url, { arg }); +import { swr } from './utils/fetchers'; +import useDidMountEffect from './hooks/useDidMountEffect'; const App = () => { - const messages = useSelector((state) => state.messages); - const { data, error, isLoading, mutate } = useSWR('http://localhost:3050/convos', fetcher); + const { messages } = useSelector((state) => state.messages); + const convo = useSelector((state) => state.convo); + const { data, error, isLoading, mutate } = swr('http://localhost:3050/convos'); + useDidMountEffect(() => mutate(), [convo]); return (
@@ -22,7 +22,10 @@ const App = () => { {/*
*/} - + {/*
*/}
diff --git a/src/components/Conversations/Conversation.jsx b/src/components/Conversations/Conversation.jsx index 81c4e0fcb3..7e9acd7473 100644 --- a/src/components/Conversations/Conversation.jsx +++ b/src/components/Conversations/Conversation.jsx @@ -4,32 +4,29 @@ import DeleteButton from './DeleteButton'; import { useSelector, useDispatch } from 'react-redux'; import { setConversation } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; -import useSWRMutation from 'swr/mutation'; - -const fetcher = (url) => fetch(url).then((res) => res.json()); +import manualSWR from '~/utils/fetchers'; export default function Conversation({ id, parentMessageId, title = 'New conversation' }) { const dispatch = useDispatch(); const conversationId = useSelector((state) => state.convo.conversationId); - - const { trigger, isMutating } = useSWRMutation( + const { trigger, isMutating } = manualSWR( `http://localhost:3050/messages/${id}`, - fetcher, - { - onSuccess: function (res) { - dispatch(setMessages(res)); - } - } + 'get', + (res) => dispatch(setMessages(res)) ); - const onConvoClick = (id, parentMessageId) => { + const clickHandler = () => { + if (conversationId === id) { + return; + } + dispatch(setConversation({ conversationId: id, parentMessageId })); trigger(); }; return ( onConvoClick(id, parentMessageId)} + onClick={() => clickHandler()} className="animate-flash group relative flex cursor-pointer items-center gap-3 break-all rounded-md bg-gray-800 py-3 px-3 pr-14 hover:bg-gray-800" > { + dispatch(setMessages([])); + dispatch(setConversation({ conversationId: null, parentMessageId: null })); + } + ); + + const clickHandler = () => trigger({ conversationId }); -export default function DeleteButton({ onClick, disabled }) { return ( -
+

+ ChatGPT Clone +

+
+
+

+ + + + + + + + + + + + Examples +

+
    + + + +
+
+
+

+ + Capabilities +

+
    +
  • + Remembers what user said earlier in the conversation +
  • +
  • + Allows user to provide follow-up corrections +
  • +
  • + Trained to decline inappropriate requests +
  • +
+
+
+

+ + + + + + Limitations +

+
    +
  • + May occasionally generate incorrect information +
  • +
  • + May occasionally produce harmful instructions or biased content +
  • +
  • + Limited knowledge of world and events after 2021 +
  • +
+
+
+
+ + ); +} diff --git a/src/components/main/Message.jsx b/src/components/main/Message.jsx index 62fbc60b93..0e6c803d4e 100644 --- a/src/components/main/Message.jsx +++ b/src/components/main/Message.jsx @@ -1,6 +1,8 @@ import React from 'react'; +import { useSelector } from 'react-redux'; -export default function Message({ sender, text }) { +export default function Message({ sender, text, last = false}) { + const { isSubmitting } = useSelector((state) => state.submit); const props = { className: 'group w-full border-b border-black/10 text-gray-800 dark:border-gray-900/50 dark:bg-gray-800 dark:text-gray-100' @@ -16,7 +18,10 @@ export default function Message({ sender, text }) {
{sender}:
- {text} + + {text} + {isSubmitting && last && sender === 'GPT' && } +
diff --git a/src/components/main/Messages.jsx b/src/components/main/Messages.jsx index e728a2f8b8..273f97c9bc 100644 --- a/src/components/main/Messages.jsx +++ b/src/components/main/Messages.jsx @@ -1,7 +1,13 @@ import React, { useEffect, useRef } from 'react'; import Message from './Message'; +import Landing from './Landing'; export default function Messages({ messages }) { + + if (messages.length === 0) { + return + }; + const messagesEndRef = useRef(null); const scrollToBottom = () => { @@ -23,6 +29,7 @@ export default function Messages({ messages }) { key={i} sender={message.sender} text={message.text} + last={i === messages.length - 1} /> ))}
diff --git a/src/components/main/SubmitButton.jsx b/src/components/main/SubmitButton.jsx index 82cd111864..d37c90270a 100644 --- a/src/components/main/SubmitButton.jsx +++ b/src/components/main/SubmitButton.jsx @@ -1,8 +1,30 @@ import React from 'react'; +import { useSelector } from 'react-redux'; +import useDidMountEffect from '~/hooks/useDidMountEffect'; -export default function SubmitButton({ onClick, disabled }) { +export default function SubmitButton({ submitMessage }) { + const { isSubmitting } = useSelector((state) => state.submit); + const clickHandler = (e) => { + e.preventDefault(); + submitMessage(); + }; + + if (isSubmitting) { + return ( + + ); + } return ( -