diff --git a/.vscode/settings.json b/.vscode/settings.json index da8e97b76..1c0b59339 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,5 @@ "files.trimTrailingWhitespace": true, "javascript.suggest.enabled": false, "javascript.updateImportsOnFileMove.enabled": "never", - "javascript.validate.enable": false + "javascript.validate.enable": true } diff --git a/package.json b/package.json index 39d477b8c..3525b27ba 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "webpack-dev-server .", "build": "Webpack . --watch", - "server": "node server/index.js", + "server": "npx nodemon server/index.js", "test": "test" }, "repository": { diff --git a/server/index.js b/server/index.js index 4dcc5cfa0..9ad7b3ec8 100644 --- a/server/index.js +++ b/server/index.js @@ -1,141 +1,27 @@ const express = require('express'); const dbConnect = require('../models/dbConnect'); -const { ask, titleConversation } = require('../app/chatgpt'); -const { saveMessage, getMessages, deleteMessages } = require('../models/Message'); -const { saveConvo, getConvos, deleteConvos, updateConvo } = require('../models/Conversation'); -const { savePrompt, getPrompts, deletePrompts } = require('../models/Prompt'); -const crypto = require('crypto'); const path = require('path'); const cors = require('cors'); +const routes = require('./routes'); const app = express(); const port = 3050; +const projectPath = path.join(__dirname, '..'); +dbConnect().then(() => console.log('Connected to MongoDB')); + app.use(cors()); app.use(express.json()); - -const projectPath = path.join(__dirname, '..'); app.use(express.static(path.join(projectPath, 'public'))); -dbConnect().then((connection) => console.log('Connected to MongoDB')); - app.get('/', function (req, res) { console.log(path.join(projectPath, 'public', 'index.html')); res.sendFile(path.join(projectPath, 'public', 'index.html')); }); -app.get('/convos', async (req, res) => { - res.status(200).send(await getConvos()); -}); - -app.get('/prompts', async (req, res) => { - let filter = {}; - // const { search } = req.body.arg; - // if (!!search) { - // filter = { conversationId }; - // } - res.status(200).send(await getPrompts(filter)); -}); - -app.get('/messages/:conversationId', async (req, res) => { - const { conversationId } = req.params; - res.status(200).send(await getMessages({ conversationId })); -}); - -app.post('/clear_convos', async (req, res) => { - let filter = {}; - const { conversationId } = req.body.arg; - 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('/update_convo', async (req, res) => { - const update = req.body.arg; - - try { - const dbResponse = await updateConvo(update); - res.status(201).send(dbResponse); - } catch (error) { - console.error(error); - res.status(500).send(error); - } -}); - -app.post('/ask', async (req, res) => { - const { text, parentMessageId, conversationId } = req.body; - if (!text.trim().includes(' ') && text.length < 5) { - res.status(500).write('Prompt empty or too short'); - res.end(); - return; - } - - const userMessageId = crypto.randomUUID(); - let userMessage = { id: userMessageId, sender: 'User', text }; - - console.log('initial ask log', userMessage); - - res.writeHead(200, { - Connection: 'keep-alive', - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache, no-transform', - 'Access-Control-Allow-Origin': '*', - 'X-Accel-Buffering': 'no' - }); - - try { - let i = 0; - const progressCallback = async (partial) => { - if (i === 0) { - userMessage.parentMessageId = parentMessageId ? parentMessageId : partial.id; - userMessage.conversationId = conversationId ? conversationId : partial.conversationId; - await saveMessage(userMessage); - res.write( - `event: message\ndata: ${JSON.stringify({ ...partial, initial: true })}\n\n` - ); - i++; - } - const data = JSON.stringify({ ...partial, message: true }); - res.write(`event: message\ndata: ${data}\n\n`); - }; - - let gptResponse = await ask(text, progressCallback, { parentMessageId, conversationId }); - if (!!parentMessageId) { - gptResponse = { ...gptResponse, parentMessageId }; - } else { - gptResponse.title = await titleConversation(text, gptResponse.text); - } - - if ( - (gptResponse.text.includes('2023') && !gptResponse.text.trim().includes(' ')) || - gptResponse.text.toLowerCase().includes('no response') || - gptResponse.text.toLowerCase().includes('no answer') - ) { - res.status(500).write('event: error\ndata: Prompt empty or too short'); - res.end(); - return; - } - - gptResponse.sender = 'GPT'; - await saveMessage(gptResponse); - await saveConvo(gptResponse); - - res.write(`event: message\ndata: ${JSON.stringify(gptResponse)}\n\n`); - res.end(); - } catch (error) { - console.log(error); - await deleteMessages({ id: userMessageId }); - res.status(500).write('event: error\ndata: ' + error.message); - res.end(); - } -}); +app.use('/ask', routes.ask); +app.use('/messages', routes.messages); +app.use('/convos', routes.convos); +app.use('/prompts', routes.prompts); app.listen(port, () => { console.log(`Server listening at http://localhost:${port}`); -}); +}); \ No newline at end of file diff --git a/server/routes/ask.js b/server/routes/ask.js new file mode 100644 index 000000000..5ee288e58 --- /dev/null +++ b/server/routes/ask.js @@ -0,0 +1,76 @@ +const express = require('express'); +const crypto = require('crypto'); +const router = express.Router(); +const { ask, titleConversation } = require('../../app/chatgpt'); +const { saveMessage, deleteMessages } = require('../../models/Message'); +const { saveConvo } = require('../../models/Conversation'); + +router.post('/', async (req, res) => { + const { text, parentMessageId, conversationId } = req.body; + if (!text.trim().includes(' ') && text.length < 5) { + res.status(500).write('Prompt empty or too short'); + res.end(); + return; + } + + const userMessageId = crypto.randomUUID(); + let userMessage = { id: userMessageId, sender: 'User', text }; + + console.log('initial ask log', userMessage); + + res.writeHead(200, { + Connection: 'keep-alive', + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache, no-transform', + 'Access-Control-Allow-Origin': '*', + 'X-Accel-Buffering': 'no' + }); + + try { + let i = 0; + const progressCallback = async (partial) => { + if (i === 0) { + userMessage.parentMessageId = parentMessageId ? parentMessageId : partial.id; + userMessage.conversationId = conversationId ? conversationId : partial.conversationId; + await saveMessage(userMessage); + res.write( + `event: message\ndata: ${JSON.stringify({ ...partial, initial: true })}\n\n` + ); + i++; + } + const data = JSON.stringify({ ...partial, message: true }); + res.write(`event: message\ndata: ${data}\n\n`); + }; + + let gptResponse = await ask(text, progressCallback, { parentMessageId, conversationId }); + if (!!parentMessageId) { + gptResponse = { ...gptResponse, parentMessageId }; + } else { + gptResponse.title = await titleConversation(text, gptResponse.text); + } + + if ( + (gptResponse.text.includes('2023') && !gptResponse.text.trim().includes(' ')) || + gptResponse.text.toLowerCase().includes('no response') || + gptResponse.text.toLowerCase().includes('no answer') + ) { + res.status(500).write('event: error\ndata: Prompt empty or too short'); + res.end(); + return; + } + + gptResponse.sender = 'GPT'; + await saveMessage(gptResponse); + await saveConvo(gptResponse); + + res.write(`event: message\ndata: ${JSON.stringify(gptResponse)}\n\n`); + res.end(); + } catch (error) { + console.log(error); + await deleteMessages({ id: userMessageId }); + res.status(500).write('event: error\ndata: ' + error.message); + res.end(); + } +}); + +module.exports = router; diff --git a/server/routes/convos.js b/server/routes/convos.js new file mode 100644 index 000000000..6c1538be6 --- /dev/null +++ b/server/routes/convos.js @@ -0,0 +1,37 @@ +const express = require('express'); +const router = express.Router(); +const { getConvos, deleteConvos, updateConvo } = require('../../models/Conversation'); + +router.get('/', async (req, res) => { + res.status(200).send(await getConvos()); +}); + +router.post('/clear', async (req, res) => { + let filter = {}; + const { conversationId } = req.body.arg; + 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); + } +}); + +router.post('/update', async (req, res) => { + const update = req.body.arg; + + try { + const dbResponse = await updateConvo(update); + res.status(201).send(dbResponse); + } catch (error) { + console.error(error); + res.status(500).send(error); + } +}); + +module.exports = router; diff --git a/server/routes/index.js b/server/routes/index.js new file mode 100644 index 000000000..9e1bd103d --- /dev/null +++ b/server/routes/index.js @@ -0,0 +1,6 @@ +const ask = require('./ask'); +const messages = require('./messages'); +const convos = require('./convos'); +const prompts = require('./prompts'); + +module.exports = { ask, messages, convos, prompts }; \ No newline at end of file diff --git a/server/routes/messages.js b/server/routes/messages.js new file mode 100644 index 000000000..be34a062e --- /dev/null +++ b/server/routes/messages.js @@ -0,0 +1,10 @@ +const express = require('express'); +const router = express.Router(); +const { getMessages } = require('../../models/Message'); + +router.get('/:conversationId', async (req, res) => { + const { conversationId } = req.params; + res.status(200).send(await getMessages({ conversationId })); +}); + +module.exports = router; diff --git a/server/routes/prompts.js b/server/routes/prompts.js new file mode 100644 index 000000000..84ff935f0 --- /dev/null +++ b/server/routes/prompts.js @@ -0,0 +1,14 @@ +const express = require('express'); +const router = express.Router(); +const { savePrompt, getPrompts, deletePrompts } = require('../../models/Prompt'); + +router.get('/', async (req, res) => { + let filter = {}; + // const { search } = req.body.arg; + // if (!!search) { + // filter = { conversationId }; + // } + res.status(200).send(await getPrompts(filter)); +}); + +module.exports = router; diff --git a/src/components/Conversations/Conversation.jsx b/src/components/Conversations/Conversation.jsx index fc83042be..fda1d0c24 100644 --- a/src/components/Conversations/Conversation.jsx +++ b/src/components/Conversations/Conversation.jsx @@ -14,7 +14,7 @@ export default function Conversation({ id, parentMessageId, conversationId, titl const inputRef = useRef(null); const dispatch = useDispatch(); const { trigger, isMutating } = manualSWR(`http://localhost:3050/messages/${id}`, 'get'); - const rename = manualSWR(`http://localhost:3050/update_convo`, 'post'); + const rename = manualSWR(`http://localhost:3050/convos/update`, 'post'); const clickHandler = async () => { if (conversationId === id) { diff --git a/src/components/Conversations/DeleteButton.jsx b/src/components/Conversations/DeleteButton.jsx index 3b5ab389a..3f0722cd3 100644 --- a/src/components/Conversations/DeleteButton.jsx +++ b/src/components/Conversations/DeleteButton.jsx @@ -9,7 +9,7 @@ import { setMessages } from '~/store/messageSlice'; export default function DeleteButton({ conversationId, renaming, cancelHandler }) { const dispatch = useDispatch(); const { trigger, isMutating } = manualSWR( - 'http://localhost:3050/clear_convos', + 'http://localhost:3050/convos/clear', 'post', () => { dispatch(setMessages([])); diff --git a/src/components/Nav/ClearConvos.jsx b/src/components/Nav/ClearConvos.jsx index e083f442c..9908b877c 100644 --- a/src/components/Nav/ClearConvos.jsx +++ b/src/components/Nav/ClearConvos.jsx @@ -9,7 +9,7 @@ export default function ClearConvos() { const dispatch = useDispatch(); const { trigger, isMutating } = manualSWR( - 'http://localhost:3050/clear_convos', + 'http://localhost:3050/convos/clear', 'post', () => { dispatch(setMessages([]));