mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50: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' };
|
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(),
|
getConvos: async () => await Conversation.find({}).exec(),
|
||||||
deleteConvos: async (filter) => {
|
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",
|
"openai": "^3.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
"react-redux": "^8.0.5",
|
"react-redux": "^8.0.5",
|
||||||
"react-textarea-autosize": "^8.4.0",
|
"react-textarea-autosize": "^8.4.0",
|
||||||
"swr": "^2.0.3",
|
"swr": "^2.0.3",
|
||||||
|
|
@ -16361,8 +16362,6 @@
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
|
|
@ -16372,9 +16371,7 @@
|
||||||
"node_modules/prop-types/node_modules/react-is": {
|
"node_modules/prop-types/node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"optional": true,
|
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/proxy-addr": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
|
|
@ -16603,6 +16600,25 @@
|
||||||
"react": "^18.2.0"
|
"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": {
|
"node_modules/react-is": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
"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"
|
"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": {
|
"node_modules/react-textarea-autosize": {
|
||||||
"version": "8.4.0",
|
"version": "8.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.4.0.tgz",
|
||||||
|
|
@ -32185,8 +32209,6 @@
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
|
|
@ -32196,9 +32218,7 @@
|
||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"optional": true,
|
|
||||||
"peer": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -32369,6 +32389,22 @@
|
||||||
"scheduler": "^0.23.0"
|
"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": {
|
"react-is": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
"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-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": {
|
"react-textarea-autosize": {
|
||||||
"version": "8.4.0",
|
"version": "8.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.4.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
"openai": "^3.1.0",
|
"openai": "^3.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
"react-redux": "^8.0.5",
|
"react-redux": "^8.0.5",
|
||||||
"react-textarea-autosize": "^8.4.0",
|
"react-textarea-autosize": "^8.4.0",
|
||||||
"swr": "^2.0.3",
|
"swr": "^2.0.3",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ const express = require('express');
|
||||||
const dbConnect = require('../models/dbConnect');
|
const dbConnect = require('../models/dbConnect');
|
||||||
const { ask, titleConversation } = require('../app/chatgpt');
|
const { ask, titleConversation } = require('../app/chatgpt');
|
||||||
const { saveMessage, getMessages, deleteMessages } = require('../models/Message');
|
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 crypto = require('crypto');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
|
|
@ -25,6 +26,15 @@ app.get('/convos', async (req, res) => {
|
||||||
res.status(200).send(await getConvos());
|
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) => {
|
app.get('/messages/:conversationId', async (req, res) => {
|
||||||
const { conversationId } = req.params;
|
const { conversationId } = req.params;
|
||||||
res.status(200).send(await getMessages({ conversationId }));
|
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) => {
|
app.post('/ask', async (req, res) => {
|
||||||
console.log(req.body);
|
console.log(req.body);
|
||||||
const { text, parentMessageId, conversationId } = req.body;
|
const { text, parentMessageId, conversationId } = req.body;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import useDidMountEffect from './hooks/useDidMountEffect';
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const { messages } = useSelector((state) => state.messages);
|
const { messages } = useSelector((state) => state.messages);
|
||||||
const { conversationId } = useSelector((state) => state.convo);
|
const { conversationId } = useSelector((state) => state.convo);
|
||||||
|
const { title } = useSelector((state) => state.convo);
|
||||||
const { data, error, isLoading, mutate } = swr('http://localhost:3050/convos');
|
const { data, error, isLoading, mutate } = swr('http://localhost:3050/convos');
|
||||||
useDidMountEffect(() => mutate(), [conversationId]);
|
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="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">
|
<div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden dark:bg-gray-800">
|
||||||
<MobileNav />
|
<MobileNav />
|
||||||
<Messages messages={messages} />
|
<Messages messages={messages} title={title}/>
|
||||||
<TextChat
|
<TextChat
|
||||||
messages={messages}
|
messages={messages}
|
||||||
reloadConvos={mutate}
|
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 RenameButton from './RenameButton';
|
||||||
import DeleteButton from './DeleteButton';
|
import DeleteButton from './DeleteButton';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
|
@ -6,23 +6,56 @@ import { setConversation } from '~/store/convoSlice';
|
||||||
import { setMessages } from '~/store/messageSlice';
|
import { setMessages } from '~/store/messageSlice';
|
||||||
import { setText } from '~/store/textSlice';
|
import { setText } from '~/store/textSlice';
|
||||||
import manualSWR from '~/utils/fetchers';
|
import manualSWR from '~/utils/fetchers';
|
||||||
|
import ConvoIcon from '../svg/ConvoIcon';
|
||||||
|
|
||||||
export default function Conversation({ id, parentMessageId, title = 'New conversation' }) {
|
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 dispatch = useDispatch();
|
||||||
const { conversationId } = useSelector((state) => state.convo);
|
const { conversationId } = useSelector((state) => state.convo);
|
||||||
const { trigger, isMutating } = manualSWR(`http://localhost:3050/messages/${id}`, 'get');
|
const { trigger, isMutating } = manualSWR(`http://localhost:3050/messages/${id}`, 'get');
|
||||||
|
const rename = manualSWR(`http://localhost:3050/update_convo`, 'post');
|
||||||
|
|
||||||
const clickHandler = async () => {
|
const clickHandler = async () => {
|
||||||
if (conversationId === id) {
|
if (conversationId === id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setConversation({ error: false, conversationId: id, parentMessageId }));
|
dispatch(setConversation({ title, error: false, conversationId: id, parentMessageId }));
|
||||||
const data = await trigger();
|
const data = await trigger();
|
||||||
dispatch(setMessages(data));
|
dispatch(setMessages(data));
|
||||||
dispatch(setText(''));
|
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 = {
|
const aProps = {
|
||||||
className:
|
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'
|
'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()}
|
onClick={() => clickHandler()}
|
||||||
{...aProps}
|
{...aProps}
|
||||||
>
|
>
|
||||||
<svg
|
<ConvoIcon />
|
||||||
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>
|
|
||||||
<div className="relative max-h-5 flex-1 overflow-hidden text-ellipsis break-all">
|
<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>
|
</div>
|
||||||
{conversationId === id ? (
|
{conversationId === id ? (
|
||||||
<div className="visible absolute right-1 z-10 flex text-gray-300">
|
<div className="visible absolute right-1 z-10 flex text-gray-300">
|
||||||
<RenameButton conversationId={id} />
|
<RenameButton
|
||||||
<DeleteButton conversationId={id} />
|
conversationId={id}
|
||||||
|
renaming={renaming}
|
||||||
|
renameHandler={renameHandler}
|
||||||
|
onRename={onRename}
|
||||||
|
/>
|
||||||
|
<DeleteButton
|
||||||
|
conversationId={id}
|
||||||
|
renaming={renaming}
|
||||||
|
cancelHandler={cancelHandler}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute inset-y-0 right-0 z-10 w-8 bg-gradient-to-l from-gray-900 group-hover:from-[#2A2B32]" />
|
<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 React from 'react';
|
||||||
import TrashIcon from '../svg/TrashIcon';
|
import TrashIcon from '../svg/TrashIcon';
|
||||||
|
import CrossIcon from '../svg/CrossIcon';
|
||||||
import manualSWR from '~/utils/fetchers';
|
import manualSWR from '~/utils/fetchers';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { setConversation } from '~/store/convoSlice';
|
import { setConversation } from '~/store/convoSlice';
|
||||||
import { setMessages } from '~/store/messageSlice';
|
import { setMessages } from '~/store/messageSlice';
|
||||||
|
|
||||||
export default function DeleteButton({ conversationId }) {
|
export default function DeleteButton({ conversationId, renaming, cancelHandler }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { trigger, isMutating } = manualSWR(
|
const { trigger, isMutating } = manualSWR(
|
||||||
'http://localhost:3050/clear_convos',
|
'http://localhost:3050/clear_convos',
|
||||||
|
|
@ -17,13 +18,14 @@ export default function DeleteButton({ conversationId }) {
|
||||||
);
|
);
|
||||||
|
|
||||||
const clickHandler = () => trigger({ conversationId });
|
const clickHandler = () => trigger({ conversationId });
|
||||||
|
const handler = renaming ? cancelHandler : clickHandler;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="p-1 hover:text-white"
|
className="p-1 hover:text-white"
|
||||||
onClick={clickHandler}
|
onClick={handler}
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
{ renaming ? <CrossIcon/> : <TrashIcon />}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,13 @@
|
||||||
import React from 'react';
|
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 (
|
return (
|
||||||
<button className="p-1 hover:text-white">
|
<button className="p-1 hover:text-white" onClick={handler}>
|
||||||
<svg
|
{renaming ? <CheckMark /> : <RenameIcon />}
|
||||||
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>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export default function NewChat() {
|
||||||
const clickHandler = () => {
|
const clickHandler = () => {
|
||||||
dispatch(setText(''));
|
dispatch(setText(''));
|
||||||
dispatch(setMessages([]));
|
dispatch(setMessages([]));
|
||||||
dispatch(setConversation({ error: false, conversationId: null, parentMessageId: null }));
|
dispatch(setConversation({ title: 'New Chat', error: false, conversationId: null, parentMessageId: null }));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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>
|
</span>
|
||||||
<div
|
<div
|
||||||
// onclick="selectPromptTemplate(0)"
|
// 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">
|
<h2 className="m-auto flex items-center gap-3 text-lg font-normal md:flex-col md:gap-2">
|
||||||
DAN (Do Anything Now)
|
DAN (Do Anything Now)
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { setText } from '~/store/textSlice';
|
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 SunIcon from '../svg/SunIcon';
|
||||||
import LightningIcon from '../svg/LightningIcon';
|
import LightningIcon from '../svg/LightningIcon';
|
||||||
import CautionIcon from '../svg/CautionIcon';
|
import CautionIcon from '../svg/CautionIcon';
|
||||||
|
|
||||||
export default function Landing() {
|
export default function Landing({ title }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
useDocumentTitle(title);
|
||||||
|
|
||||||
const clickHandler = (e) => {
|
const clickHandler = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import useDocumentTitle from '~/hooks/useDocumentTitle';
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
import Landing from './Landing';
|
import Landing from './Landing';
|
||||||
|
|
||||||
export default function Messages({ messages }) {
|
export default function Messages({ messages, title }) {
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
return <Landing />;
|
return <Landing title={title}/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useDocumentTitle(title);
|
||||||
|
|
||||||
const messagesEndRef = useRef(null);
|
const messagesEndRef = useRef(null);
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import RegenerateIcon from '../svg/RegenerateIcon';
|
||||||
|
|
||||||
export default function Regenerate({ submitMessage }) {
|
export default function Regenerate({ submitMessage, tryAgain }) {
|
||||||
const clickHandler = (e) => {
|
const clickHandler = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
submitMessage();
|
submitMessage();
|
||||||
|
|
@ -8,31 +9,22 @@ export default function Regenerate({ submitMessage }) {
|
||||||
|
|
||||||
return (
|
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
|
There was an error generating a response
|
||||||
</span>
|
</span>
|
||||||
|
<span className="m-auto flex justify-center">
|
||||||
<button
|
<button
|
||||||
onClick={clickHandler}
|
onClick={clickHandler}
|
||||||
className="btn btn-primary m-auto flex justify-center gap-2"
|
className="btn btn-primary m-auto flex justify-center gap-2"
|
||||||
>
|
>
|
||||||
<svg
|
<RegenerateIcon />
|
||||||
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>
|
|
||||||
Regenerate response
|
Regenerate response
|
||||||
</button>
|
</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));
|
dispatch(setText(value));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tryAgain = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch(setError(false));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
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">
|
<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">
|
<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)] */}
|
{/* 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 ? (
|
{!!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">
|
<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
|
<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';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
active: false,
|
|
||||||
error: false,
|
error: false,
|
||||||
|
title: 'ChatGPT Clone',
|
||||||
conversationId: null,
|
conversationId: null,
|
||||||
parentMessageId: null,
|
parentMessageId: null,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue