mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
complete renaming functions, abstracts more svg, sets title to current convo title, adds a try again feature to errors
This commit is contained in:
parent
592b7629aa
commit
5af5a97d8f
24 changed files with 512 additions and 82 deletions
|
|
@ -44,6 +44,18 @@ module.exports = {
|
|||
return { message: 'Error saving conversation' };
|
||||
}
|
||||
},
|
||||
updateConvo: async ({ conversationId, ...update }) => {
|
||||
try {
|
||||
return await Conversation.findOneAndUpdate(
|
||||
{ conversationId },
|
||||
update,
|
||||
{ new: true }
|
||||
).exec();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return { message: 'Error updating conversation' };
|
||||
}
|
||||
},
|
||||
getConvos: async () => await Conversation.find({}).exec(),
|
||||
deleteConvos: async (filter) => {
|
||||
|
||||
|
|
|
|||
52
models/Prompt.js
Normal file
52
models/Prompt.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const promptSchema = mongoose.Schema({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
prompt: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
},
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
|
||||
const Prompt = mongoose.models.Prompt || mongoose.model('Prompt', promptSchema);
|
||||
|
||||
module.exports = {
|
||||
savePrompt: async ({ title, prompt }) => {
|
||||
try {
|
||||
await Prompt.create({
|
||||
title,
|
||||
prompt
|
||||
});
|
||||
return { title, prompt };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { prompt: 'Error saving prompt' };
|
||||
}
|
||||
},
|
||||
getPrompts: async (filter) => {
|
||||
try {
|
||||
return await Prompt.find(filter).exec()
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { prompt: 'Error getting prompts' };
|
||||
}
|
||||
},
|
||||
deletePrompts: async (filter) => {
|
||||
try {
|
||||
return await Prompt.deleteMany(filter).exec()
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { prompt: 'Error deleting prompts' };
|
||||
}
|
||||
}
|
||||
}
|
||||
62
package-lock.json
generated
62
package-lock.json
generated
|
|
@ -21,6 +21,7 @@
|
|||
"openai": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-textarea-autosize": "^8.4.0",
|
||||
"swr": "^2.0.3",
|
||||
|
|
@ -16361,8 +16362,6 @@
|
|||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
|
|
@ -16372,9 +16371,7 @@
|
|||
"node_modules/prop-types/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
|
|
@ -16603,6 +16600,25 @@
|
|||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-fast-compare": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||
},
|
||||
"node_modules/react-helmet": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
|
||||
"integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-fast-compare": "^3.1.1",
|
||||
"react-side-effect": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
|
|
@ -16759,6 +16775,14 @@
|
|||
"react": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-side-effect": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
|
||||
"integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-textarea-autosize": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.4.0.tgz",
|
||||
|
|
@ -32185,8 +32209,6 @@
|
|||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
|
|
@ -32196,9 +32218,7 @@
|
|||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -32369,6 +32389,22 @@
|
|||
"scheduler": "^0.23.0"
|
||||
}
|
||||
},
|
||||
"react-fast-compare": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||
},
|
||||
"react-helmet": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
|
||||
"integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
|
||||
"requires": {
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-fast-compare": "^3.1.1",
|
||||
"react-side-effect": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
|
|
@ -32487,6 +32523,12 @@
|
|||
"react-is": "^16.12.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"react-side-effect": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
|
||||
"integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-textarea-autosize": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"openai": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-textarea-autosize": "^8.4.0",
|
||||
"swr": "^2.0.3",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ 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 } = require('../models/Conversation');
|
||||
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');
|
||||
|
|
@ -25,6 +26,15 @@ 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 }));
|
||||
|
|
@ -46,6 +56,18 @@ app.post('/clear_convos', async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
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) => {
|
||||
console.log(req.body);
|
||||
const { text, parentMessageId, conversationId } = req.body;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import useDidMountEffect from './hooks/useDidMountEffect';
|
|||
const App = () => {
|
||||
const { messages } = useSelector((state) => state.messages);
|
||||
const { conversationId } = useSelector((state) => state.convo);
|
||||
const { title } = useSelector((state) => state.convo);
|
||||
const { data, error, isLoading, mutate } = swr('http://localhost:3050/convos');
|
||||
useDidMountEffect(() => mutate(), [conversationId]);
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ const App = () => {
|
|||
<div className="flex h-full w-full flex-1 flex-col bg-gray-50 md:pl-[260px]">
|
||||
<div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden dark:bg-gray-800">
|
||||
<MobileNav />
|
||||
<Messages messages={messages} />
|
||||
<Messages messages={messages} title={title}/>
|
||||
<TextChat
|
||||
messages={messages}
|
||||
reloadConvos={mutate}
|
||||
|
|
|
|||
108
src/components/Conversations/Conversation copy.jsx
Normal file
108
src/components/Conversations/Conversation copy.jsx
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import React from 'react';
|
||||
import RenameButton from './RenameButton';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { setConversation } from '~/store/convoSlice';
|
||||
import { setMessages } from '~/store/messageSlice';
|
||||
import { setText } from '~/store/textSlice';
|
||||
import manualSWR from '~/utils/fetchers';
|
||||
|
||||
{
|
||||
/* <div class="flex py-3 px-3 items-center gap-3 relative rounded-md cursor-pointer hover:pr-14 break-all pr-14 bg-gray-800 hover:bg-gray-800"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4 flex-shrink-0" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg><input type="text" class="text-sm border-none bg-transparent p-0 m-0 w-full mr-0" value="titleeeeeeeeeeeeeeeee"><div class="absolute flex right-1 z-10 text-gray-300 visible"><button class="p-1 hover:text-white"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="20 6 9 17 4 12"></polyline></svg></button><button class="p-1 hover:text-white"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button></div></div> */
|
||||
}
|
||||
|
||||
export default function Conversation({ id, parentMessageId, title = 'New conversation' }) {
|
||||
const dispatch = useDispatch();
|
||||
const { conversationId } = useSelector((state) => state.convo);
|
||||
const { trigger, isMutating } = manualSWR(`http://localhost:3050/messages/${id}`, 'get');
|
||||
|
||||
const clickHandler = async () => {
|
||||
if (conversationId === id) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(setConversation({ error: false, conversationId: id, parentMessageId }));
|
||||
const data = await trigger();
|
||||
dispatch(setMessages(data));
|
||||
dispatch(setText(''));
|
||||
};
|
||||
|
||||
const aProps = {
|
||||
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'
|
||||
};
|
||||
|
||||
if (conversationId !== id) {
|
||||
aProps.className =
|
||||
'group relative flex cursor-pointer items-center gap-3 break-all rounded-md py-3 px-3 hover:bg-[#2A2B32] hover:pr-4';
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="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 hover:pr-14">
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="h-4 w-4 flex-shrink-0"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
||||
</svg>
|
||||
<input
|
||||
type="text"
|
||||
class="m-0 mr-0 w-full border-none bg-transparent p-0 text-sm"
|
||||
value="titleeeeeeeeeeeeeeeee"
|
||||
/>
|
||||
<div class="visible absolute right-1 z-10 flex text-gray-300">
|
||||
<button class="p-1 hover:text-white">
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="p-1 hover:text-white">
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1="18"
|
||||
y1="6"
|
||||
x2="6"
|
||||
y2="18"
|
||||
></line>
|
||||
<line
|
||||
x1="6"
|
||||
y1="6"
|
||||
x2="18"
|
||||
y2="18"
|
||||
></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useState, useRef } from 'react';
|
||||
import RenameButton from './RenameButton';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
|
@ -6,23 +6,56 @@ import { setConversation } from '~/store/convoSlice';
|
|||
import { setMessages } from '~/store/messageSlice';
|
||||
import { setText } from '~/store/textSlice';
|
||||
import manualSWR from '~/utils/fetchers';
|
||||
import ConvoIcon from '../svg/ConvoIcon';
|
||||
|
||||
export default function Conversation({ id, parentMessageId, title = 'New conversation' }) {
|
||||
const [renaming, setRenaming] = useState(false);
|
||||
const [titleInput, setTitleInput] = useState(title);
|
||||
const inputRef = useRef(null);
|
||||
const dispatch = useDispatch();
|
||||
const { conversationId } = useSelector((state) => state.convo);
|
||||
const { trigger, isMutating } = manualSWR(`http://localhost:3050/messages/${id}`, 'get');
|
||||
const rename = manualSWR(`http://localhost:3050/update_convo`, 'post');
|
||||
|
||||
const clickHandler = async () => {
|
||||
if (conversationId === id) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(setConversation({ error: false, conversationId: id, parentMessageId }));
|
||||
dispatch(setConversation({ title, error: false, conversationId: id, parentMessageId }));
|
||||
const data = await trigger();
|
||||
dispatch(setMessages(data));
|
||||
dispatch(setText(''));
|
||||
};
|
||||
|
||||
const renameHandler = (e) => {
|
||||
e.preventDefault();
|
||||
setRenaming(true);
|
||||
setTimeout(() => {
|
||||
inputRef.current.focus();
|
||||
}, 25);
|
||||
};
|
||||
|
||||
const cancelHandler = (e) => {
|
||||
e.preventDefault();
|
||||
setRenaming(false);
|
||||
};
|
||||
|
||||
const onRename = (e) => {
|
||||
e.preventDefault();
|
||||
setRenaming(false);
|
||||
if (titleInput === title) {
|
||||
return;
|
||||
}
|
||||
rename.trigger({ conversationId, title: titleInput });
|
||||
};
|
||||
|
||||
const handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
onRename(e);
|
||||
}
|
||||
};
|
||||
|
||||
const aProps = {
|
||||
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'
|
||||
|
|
@ -38,27 +71,35 @@ export default function Conversation({ id, parentMessageId, title = 'New convers
|
|||
onClick={() => clickHandler()}
|
||||
{...aProps}
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
<ConvoIcon />
|
||||
<div className="relative max-h-5 flex-1 overflow-hidden text-ellipsis break-all">
|
||||
{title}
|
||||
{renaming === true ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
className="m-0 mr-0 w-full leading-tight border border-blue-500 bg-transparent p-0 text-sm outline-none"
|
||||
value={titleInput}
|
||||
onChange={(e) => setTitleInput(e.target.value)}
|
||||
onBlur={onRename}
|
||||
onKeyPress={handleKeyPress}
|
||||
/>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</div>
|
||||
{conversationId === id ? (
|
||||
<div className="visible absolute right-1 z-10 flex text-gray-300">
|
||||
<RenameButton conversationId={id} />
|
||||
<DeleteButton conversationId={id} />
|
||||
<RenameButton
|
||||
conversationId={id}
|
||||
renaming={renaming}
|
||||
renameHandler={renameHandler}
|
||||
onRename={onRename}
|
||||
/>
|
||||
<DeleteButton
|
||||
conversationId={id}
|
||||
renaming={renaming}
|
||||
cancelHandler={cancelHandler}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute inset-y-0 right-0 z-10 w-8 bg-gradient-to-l from-gray-900 group-hover:from-[#2A2B32]" />
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import React from 'react';
|
||||
import TrashIcon from '../svg/TrashIcon';
|
||||
import CrossIcon from '../svg/CrossIcon';
|
||||
import manualSWR from '~/utils/fetchers';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setConversation } from '~/store/convoSlice';
|
||||
import { setMessages } from '~/store/messageSlice';
|
||||
|
||||
export default function DeleteButton({ conversationId }) {
|
||||
export default function DeleteButton({ conversationId, renaming, cancelHandler }) {
|
||||
const dispatch = useDispatch();
|
||||
const { trigger, isMutating } = manualSWR(
|
||||
'http://localhost:3050/clear_convos',
|
||||
|
|
@ -17,13 +18,14 @@ export default function DeleteButton({ conversationId }) {
|
|||
);
|
||||
|
||||
const clickHandler = () => trigger({ conversationId });
|
||||
const handler = renaming ? cancelHandler : clickHandler;
|
||||
|
||||
return (
|
||||
<button
|
||||
className="p-1 hover:text-white"
|
||||
onClick={clickHandler}
|
||||
onClick={handler}
|
||||
>
|
||||
<TrashIcon />
|
||||
{ renaming ? <CrossIcon/> : <TrashIcon />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,13 @@
|
|||
import React from 'react';
|
||||
import RenameIcon from '../svg/RenameIcon';
|
||||
import CheckMark from '../svg/CheckMark';
|
||||
|
||||
export default function RenameButton({ onClick, renaming, renameHandler, onRename }) {
|
||||
const handler = renaming ? onRename : renameHandler;
|
||||
|
||||
export default function RenameButton({ onClick, disabled }) {
|
||||
return (
|
||||
<button className="p-1 hover:text-white">
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M12 20h9" />
|
||||
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
|
||||
</svg>
|
||||
<button className="p-1 hover:text-white" onClick={handler}>
|
||||
{renaming ? <CheckMark /> : <RenameIcon />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default function NewChat() {
|
|||
const clickHandler = () => {
|
||||
dispatch(setText(''));
|
||||
dispatch(setMessages([]));
|
||||
dispatch(setConversation({ error: false, conversationId: null, parentMessageId: null }));
|
||||
dispatch(setConversation({ title: 'New Chat', error: false, conversationId: null, parentMessageId: null }));
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
20
src/components/Prompts/Prompt.jsx
Normal file
20
src/components/Prompts/Prompt.jsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function Prompt({ title, prompt, id }) {
|
||||
return (
|
||||
<div
|
||||
// onclick="selectPromptTemplate(0)"
|
||||
className="flex w-full flex-col gap-2 rounded-md bg-gray-50 p-4 text-left hover:bg-gray-200 dark:bg-white/5 "
|
||||
>
|
||||
<h2 className="m-auto flex items-center gap-3 text-lg font-normal md:flex-col md:gap-2">
|
||||
{ title }
|
||||
</h2>
|
||||
<button>
|
||||
<p className="w-full rounded-md bg-gray-50 p-3 hover:bg-gray-200 dark:bg-white/5 dark:hover:bg-gray-900">
|
||||
{prompt}
|
||||
</p>
|
||||
</button>
|
||||
<span className="font-medium">Use prompt →</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ export default function Templates() {
|
|||
</span>
|
||||
<div
|
||||
// onclick="selectPromptTemplate(0)"
|
||||
className="flex w-full flex-col gap-2 rounded-md bg-gray-50 p-4 text-left hover:bg-gray-200 dark:bg-white/5 dark:hover:bg-gray-900"
|
||||
className="flex w-full flex-col gap-2 rounded-md bg-gray-50 p-4 text-left hover:bg-gray-200 dark:bg-white/5 "
|
||||
>
|
||||
<h2 className="m-auto flex items-center gap-3 text-lg font-normal md:flex-col md:gap-2">
|
||||
DAN (Do Anything Now)
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setText } from '~/store/textSlice';
|
||||
import Templates from './Templates';
|
||||
import useDocumentTitle from '~/hooks/useDocumentTitle';
|
||||
import Templates from '../Prompts/Templates';
|
||||
import SunIcon from '../svg/SunIcon';
|
||||
import LightningIcon from '../svg/LightningIcon';
|
||||
import CautionIcon from '../svg/CautionIcon';
|
||||
|
||||
export default function Landing() {
|
||||
export default function Landing({ title }) {
|
||||
const dispatch = useDispatch();
|
||||
useDocumentTitle(title);
|
||||
|
||||
const clickHandler = (e) => {
|
||||
e.preventDefault();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import useDocumentTitle from '~/hooks/useDocumentTitle';
|
||||
import Message from './Message';
|
||||
import Landing from './Landing';
|
||||
|
||||
export default function Messages({ messages }) {
|
||||
export default function Messages({ messages, title }) {
|
||||
if (messages.length === 0) {
|
||||
return <Landing />;
|
||||
return <Landing title={title}/>;
|
||||
}
|
||||
|
||||
useDocumentTitle(title);
|
||||
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import RegenerateIcon from '../svg/RegenerateIcon';
|
||||
|
||||
export default function Regenerate({ submitMessage }) {
|
||||
export default function Regenerate({ submitMessage, tryAgain }) {
|
||||
const clickHandler = (e) => {
|
||||
e.preventDefault();
|
||||
submitMessage();
|
||||
|
|
@ -8,31 +9,22 @@ export default function Regenerate({ submitMessage }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<span className="mb-auto block flex justify-center text-xs md:mb-auto">
|
||||
<span className="mb-2 block flex justify-center text-xs md:mb-2">
|
||||
There was an error generating a response
|
||||
</span>
|
||||
<button
|
||||
onClick={clickHandler}
|
||||
className="btn btn-primary m-auto flex justify-center gap-2"
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-3 w-3"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<span className="m-auto flex justify-center">
|
||||
<button
|
||||
onClick={clickHandler}
|
||||
className="btn btn-primary m-auto flex justify-center gap-2"
|
||||
>
|
||||
<polyline points="1 4 1 10 7 10" />
|
||||
<polyline points="23 20 23 14 17 14" />
|
||||
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" />
|
||||
</svg>
|
||||
Regenerate response
|
||||
</button>
|
||||
<RegenerateIcon />
|
||||
Regenerate response
|
||||
</button>
|
||||
<button onClick={tryAgain} className="btn btn-neutral flex justify-center gap-2 border-0 md:border">
|
||||
<RegenerateIcon />
|
||||
Try another message
|
||||
</button>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ export default function TextChat({ messages, reloadConvos }) {
|
|||
dispatch(setText(value));
|
||||
};
|
||||
|
||||
const tryAgain = (e) => {
|
||||
e.preventDefault();
|
||||
dispatch(setError(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="md:bg-vert-light-gradient dark:md:bg-vert-dark-gradient absolute bottom-0 left-0 w-full border-t bg-white dark:border-white/20 dark:bg-gray-800 md:border-t-0 md:border-transparent md:!bg-transparent md:dark:border-transparent">
|
||||
<form className="stretch mx-2 flex flex-row gap-3 pt-2 last:mb-2 md:last:mb-6 lg:mx-auto lg:max-w-3xl lg:pt-6">
|
||||
|
|
@ -97,7 +102,10 @@ export default function TextChat({ messages, reloadConvos }) {
|
|||
{/* removed this prop shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] */}
|
||||
|
||||
{!!error ? (
|
||||
<Regenerate submitMessage={submitMessage} />
|
||||
<Regenerate
|
||||
submitMessage={submitMessage}
|
||||
tryAgain={tryAgain}
|
||||
/>
|
||||
) : (
|
||||
<div className="relative flex w-full flex-grow flex-col rounded-md border border-black/10 bg-white py-2 shadow-md dark:border-gray-900/50 dark:bg-gray-700 dark:text-white dark:shadow-lg md:py-3 md:pl-4">
|
||||
<TextareaAutosize
|
||||
|
|
|
|||
20
src/components/svg/CheckMark.jsx
Normal file
20
src/components/svg/CheckMark.jsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function CheckMark() {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
20
src/components/svg/ConvoIcon.jsx
Normal file
20
src/components/svg/ConvoIcon.jsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function ConvoIcon() {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
31
src/components/svg/CrossIcon.jsx
Normal file
31
src/components/svg/CrossIcon.jsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function CrossIcon() {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1="18"
|
||||
y1="6"
|
||||
x2="6"
|
||||
y2="18"
|
||||
/>
|
||||
<line
|
||||
x1="6"
|
||||
y1="6"
|
||||
x2="18"
|
||||
y2="18"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
22
src/components/svg/RegenerateIcon.jsx
Normal file
22
src/components/svg/RegenerateIcon.jsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function Regenerate() {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-3 w-3"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polyline points="1 4 1 10 7 10" />
|
||||
<polyline points="23 20 23 14 17 14" />
|
||||
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
21
src/components/svg/RenameIcon.jsx
Normal file
21
src/components/svg/RenameIcon.jsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function RenameIcon() {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M12 20h9" />
|
||||
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
20
src/hooks/useDocumentTitle.js
Normal file
20
src/hooks/useDocumentTitle.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// useDocumentTitle.js
|
||||
import { useRef, useEffect } from 'react';
|
||||
|
||||
function useDocumentTitle(title, prevailOnUnmount = false) {
|
||||
const defaultTitle = useRef(document.title);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = title;
|
||||
}, [title]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (!prevailOnUnmount) {
|
||||
document.title = defaultTitle.current;
|
||||
}
|
||||
}, []
|
||||
);
|
||||
}
|
||||
|
||||
export default useDocumentTitle;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
active: false,
|
||||
error: false,
|
||||
title: 'ChatGPT Clone',
|
||||
conversationId: null,
|
||||
parentMessageId: null,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue