customGpts persist through localStorage

This commit is contained in:
Daniel Avila 2023-03-04 17:39:06 -05:00
parent 62bb6ea8f8
commit 9c3a78f96b
22 changed files with 405 additions and 220 deletions

View file

@ -5,6 +5,10 @@
## Updates ## Updates
<details open> <details open>
<summary><strong>2023-03-04</strong></summary>
<details>
<details>
<summary><strong>2023-03-01</strong></summary> <summary><strong>2023-03-01</strong></summary>
Official ChatGPT API is out! Removed davinci since the official API is extremely fast and 10x less expensive. Since user labeling and prompt prefixing is officially supported, I will add a View feature so you can set this within chat, which gives the UI an added use case. I've kept the BrowserClient, since it's free to use like the official site. Official ChatGPT API is out! Removed davinci since the official API is extremely fast and 10x less expensive. Since user labeling and prompt prefixing is officially supported, I will add a View feature so you can set this within chat, which gives the UI an added use case. I've kept the BrowserClient, since it's free to use like the official site.
@ -43,7 +47,7 @@ Here are my planned/recently finished features.
- [x] Markdown handling - [x] Markdown handling
- [x] Language Detection for code blocks - [x] Language Detection for code blocks
- [x] 'Copy to clipboard' button for code blocks - [x] 'Copy to clipboard' button for code blocks
- [ ] Customize prompt prefix/label (custom ChatGPT using official API) - [x] Customize prompt prefix/label (custom ChatGPT using official API)
- [ ] AI model change handling (whether to pseudo-persist convos or start new convos within existing convo) - [ ] AI model change handling (whether to pseudo-persist convos or start new convos within existing convo)
- [ ] Server convo pagination (limit fetch and load more with 'show more' button) - [ ] Server convo pagination (limit fetch and load more with 'show more' button)
- [ ] Bing AI Styling (for suggested responses, convo end, etc.) - [ ] Bing AI Styling (for suggested responses, convo end, etc.)

View file

@ -24,6 +24,12 @@ const convoSchema = mongoose.Schema({
invocationId: { invocationId: {
type: String type: String
}, },
chatGptLabel: {
type: String
},
promptPrefix: {
type: String
},
model: { model: {
type: String type: String
}, },

85
models/CustomGpt.js Normal file
View file

@ -0,0 +1,85 @@
const mongoose = require('mongoose');
const customGptSchema = mongoose.Schema({
chatGptLabel: {
type: String,
required: true
},
promptPrefix: {
type: String
},
value: {
type: String,
required: true
},
created: {
type: Date,
default: Date.now
}
});
const CustomGpt = mongoose.models.CustomGpt || mongoose.model('CustomGpt', customGptSchema);
const createCustomGpt = async ({ chatGptLabel, promptPrefix, value }) => {
try {
await CustomGpt.create({
chatGptLabel,
promptPrefix,
value
});
return { chatGptLabel, promptPrefix, value };
} catch (error) {
console.error(error);
return { customGpt: 'Error saving customGpt' };
}
};
module.exports = {
getCustomGpts: async (filter) => {
try {
return await CustomGpt.find(filter).exec();
} catch (error) {
console.error(error);
return { customGpt: 'Error getting customGpts' };
}
},
// updateCustomGpt: async ({ _id, ...update }) => {
// try {
// console.log('updateCustomGpt', _id, update);
// return await CustomGpt.findOneAndUpdate({ _id }, update, {
// new: true,
// upsert: true
// }).exec();
// } catch (error) {
// console.log(error);
// return { message: 'Error updating customGpt' };
// }
// },
updateCustomGpt: async ({ value, ...update }) => {
try {
console.log('updateCustomGpt', value, update);
const customGpt = await CustomGpt.findOne({ value }).exec();
if (!customGpt) {
return await createCustomGpt({ value, ...update });
} else {
return await CustomGpt.findOneAndUpdate({ value }, update, {
new: true,
upsert: true
}).exec();
}
} catch (error) {
console.log(error);
return { message: 'Error updating customGpt' };
}
},
deleteCustomGpts: async (filter) => {
try {
return await CustomGpt.deleteMany(filter).exec();
} catch (error) {
console.error(error);
return { customGpt: 'Error deleting customGpts' };
}
}
};

View file

@ -1,8 +1,12 @@
const { saveMessage, deleteMessages } = require('./Message'); const { saveMessage, deleteMessages } = require('./Message');
const { getCustomGpts, updateCustomGpt, deleteCustomGpts } = require('./CustomGpt');
const { saveConvo } = require('./Conversation'); const { saveConvo } = require('./Conversation');
module.exports = { module.exports = {
saveMessage, saveMessage,
deleteMessages, deleteMessages,
saveConvo, saveConvo,
getCustomGpts,
updateCustomGpt,
deleteCustomGpts
}; };

View file

@ -20,6 +20,7 @@ app.get('/', function (req, res) {
app.use('/ask', routes.ask); app.use('/ask', routes.ask);
app.use('/messages', routes.messages); app.use('/messages', routes.messages);
app.use('/convos', routes.convos); app.use('/convos', routes.convos);
app.use('/customGpts', routes.customGpts);
app.use('/prompts', routes.prompts); app.use('/prompts', routes.prompts);
app.listen(port, () => { app.listen(port, () => {

View file

@ -120,6 +120,15 @@ router.post('/', async (req, res) => {
gptResponse.sender = model === 'chatgptCustom' ? chatGptLabel : model; gptResponse.sender = model === 'chatgptCustom' ? chatGptLabel : model;
gptResponse.final = true; gptResponse.final = true;
gptResponse.text = await detectCode(gptResponse.text); gptResponse.text = await detectCode(gptResponse.text);
if (chatGptLabel?.length > 0 && model === 'chatgptCustom') {
gptResponse.chatGptLabel = chatGptLabel;
}
if (promptPrefix?.length > 0 && model === 'chatgptCustom') {
gptResponse.promptPrefix = promptPrefix;
}
await saveMessage(gptResponse); await saveMessage(gptResponse);
await saveConvo(gptResponse); await saveConvo(gptResponse);
sendMessage(res, gptResponse); sendMessage(res, gptResponse);

View file

@ -0,0 +1,56 @@
const express = require('express');
const router = express.Router();
const { getCustomGpts, updateCustomGpt, deleteCustomGpts } = require('../../models');
router.get('/', async (req, res) => {
const models = (await getCustomGpts()).map(model => {
model = model.toObject();
model._id = model._id.toString();
return model;
});
// console.log(models);
res.status(200).send(models);
});
router.post('/delete/:_id', async (req, res) => {
const { _id } = req.params;
let filter = {};
if (_id) {
filter = { _id };
}
try {
const dbResponse = await deleteCustomGpts(filter);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
// router.post('/create', async (req, res) => {
// const payload = req.body.arg;
// try {
// const dbResponse = await createCustomGpt(payload);
// res.status(201).send(dbResponse);
// } catch (error) {
// console.error(error);
// res.status(500).send(error);
// }
// });
router.post('/', async (req, res) => {
const update = req.body.arg;
try {
const dbResponse = await updateCustomGpt(update);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
module.exports = router;

View file

@ -1,6 +1,7 @@
const ask = require('./ask'); const ask = require('./ask');
const messages = require('./messages'); const messages = require('./messages');
const convos = require('./convos'); const convos = require('./convos');
const customGpts = require('./customGpts');
const prompts = require('./prompts'); const prompts = require('./prompts');
module.exports = { ask, messages, convos, prompts }; module.exports = { ask, messages, convos, customGpts, prompts };

View file

@ -12,11 +12,13 @@ const App = () => {
const { title } = useSelector((state) => state.convo); const { title } = useSelector((state) => state.convo);
useDocumentTitle(title); useDocumentTitle(title);
// bg-color: #343541 instead of bg-gray-800
return ( return (
<div className="flex h-screen"> <div className="flex h-screen">
<Nav /> <Nav />
<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/90"> <div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden bg-white dark:bg-gray-800/90">
<MobileNav /> <MobileNav />
{messages.length === 0 ? ( {messages.length === 0 ? (
<Landing title={title} /> <Landing title={title} />

View file

@ -13,13 +13,14 @@ export default function Conversation({
parentMessageId, parentMessageId,
conversationId, conversationId,
title = 'New conversation', title = 'New conversation',
bingData bingData,
chatGptLabel = null,
promptPrefix = null,
}) { }) {
const [renaming, setRenaming] = useState(false); const [renaming, setRenaming] = useState(false);
const [titleInput, setTitleInput] = useState(title); const [titleInput, setTitleInput] = useState(title);
const inputRef = useRef(null); const inputRef = useRef(null);
const dispatch = useDispatch(); const dispatch = useDispatch();
// const { trigger, isMutating } = manualSWR(`http://localhost:3050/messages/${id}`, 'get');
const { trigger } = manualSWR(`http://localhost:3050/messages/${id}`, 'get'); const { trigger } = manualSWR(`http://localhost:3050/messages/${id}`, 'get');
const rename = manualSWR(`http://localhost:3050/convos/update`, 'post'); const rename = manualSWR(`http://localhost:3050/convos/update`, 'post');
@ -28,13 +29,13 @@ export default function Conversation({
return; return;
} }
const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix };
if (bingData) { if (bingData) {
const { conversationSignature, clientId, invocationId } = bingData; const { conversationSignature, clientId, invocationId } = bingData;
dispatch( dispatch(
setConversation({ setConversation({
title, ...convo,
error: false,
conversationId: id,
parentMessageId: null, parentMessageId: null,
conversationSignature, conversationSignature,
clientId, clientId,
@ -44,9 +45,7 @@ export default function Conversation({
} else { } else {
dispatch( dispatch(
setConversation({ setConversation({
title, ...convo,
error: false,
conversationId: id,
parentMessageId, parentMessageId,
conversationSignature: null, conversationSignature: null,
clientId: null, clientId: null,

View file

@ -23,6 +23,8 @@ export default function Conversations({ conversations, conversationId }) {
parentMessageId={convo.parentMessageId} parentMessageId={convo.parentMessageId}
title={convo.title} title={convo.title}
conversationId={conversationId} conversationId={conversationId}
chatGptLabel={convo.chatGptLabel}
promptPrefix={convo.promptPrefix}
bingData={bingData} bingData={bingData}
/> />
); );

View file

@ -42,7 +42,7 @@ export default function Message({
const props = { const props = {
className: className:
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group dark:bg-gray-800' 'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 bg-white dark:text-gray-100 group dark:bg-gray-800'
}; };
const bgColors = { const bgColors = {

View file

@ -10,7 +10,7 @@ export default function ClearConvos() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { mutate } = useSWRConfig() const { mutate } = useSWRConfig()
const { trigger, isMutating } = manualSWR( const { trigger } = manualSWR(
'http://localhost:3050/convos/clear', 'http://localhost:3050/convos/clear',
'post', 'post',
() => { () => {

View file

@ -0,0 +1,20 @@
import React from 'react';
// import { setModel, setDisabled } from '~/store/submitSlice';
// import { swr } from '~/utils/fetchers';
// import { setModels } from '~/store/modelSlice';
import ModelItem from './ModelItem';
export default function MenuItems({ models }) {
return (
<>
{models.map((modelItem, i) => (
<ModelItem
key={i}
// id={modelItem._id}
modelName={modelItem.name}
value={modelItem.value}
/>
))}
</>
);
}

View file

@ -0,0 +1,135 @@
// import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useRef } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { useDispatch } from 'react-redux';
import { setModel, setDisabled, setCustomGpt } from '~/store/submitSlice';
import manualSWR from '~/utils/fetchers';
import { Button } from '../ui/Button.tsx';
import { Input } from '../ui/Input.tsx';
import { Label } from '../ui/Label.tsx';
import {
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '../ui/Dialog.tsx';
export default function ModelDialog({ mutate }) {
const dispatch = useDispatch();
const [chatGptLabel, setChatGptLabel] = useState('');
const [promptPrefix, setPromptPrefix] = useState('');
const [saveText, setSaveText] = useState('Save');
const [required, setRequired] = useState(false);
const inputRef = useRef(null);
const updateCustomGpt = manualSWR('http://localhost:3050/customGpts/', 'post');
const submitHandler = (e) => {
if (chatGptLabel.length === 0) {
e.preventDefault();
setRequired(true);
inputRef.current.focus();
return;
}
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
dispatch(setModel('chatgptCustom'));
dispatch(setDisabled(false));
};
const saveHandler = (e) => {
e.preventDefault();
const value = chatGptLabel.toLowerCase();
if (chatGptLabel.length === 0) {
setRequired(true);
inputRef.current.focus();
return;
}
updateCustomGpt.trigger({ value, chatGptLabel, promptPrefix });
mutate();
setSaveText('Saved!');
setTimeout(() => {
setSaveText('Save');
}, 2500);
// dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
// dispatch(setModel('chatgptCustom'));
// dispatch(setDisabled(false));
};
const requiredProp = required ? { required: true } : {};
return (
<DialogContent className="dark:bg-gray-800">
<DialogHeader>
<DialogTitle>Customize ChatGPT</DialogTitle>
<DialogDescription>
Note: important instructions are often better placed in your message rather than the
prefix.{' '}
<a
href="https://platform.openai.com/docs/guides/chat/instructing-chat-models"
target="_blank"
rel="noopener noreferrer"
>
<u>More info here</u>
</a>
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label
htmlFor="chatGptLabel"
className="text-right"
>
Custom Name
</Label>
<Input
id="chatGptLabel"
value={chatGptLabel}
ref={inputRef}
onChange={(e) => setChatGptLabel(e.target.value)}
placeholder="Set a custom name for ChatGPT"
className="col-span-3 shadow-[0_0_10px_rgba(0,0,0,0.10)] invalid:border-red-400 invalid:text-red-600 invalid:placeholder-red-600
invalid:placeholder-opacity-70 invalid:ring-opacity-20 focus:ring-opacity-20 focus:invalid:border-red-400 focus:invalid:ring-red-400 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:invalid:border-red-600 dark:invalid:text-red-300 dark:invalid:placeholder-opacity-80 dark:focus:invalid:ring-red-600 dark:focus:invalid:ring-opacity-50"
{...requiredProp}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label
htmlFor="promptPrefix"
className="text-right"
>
Prompt Prefix
</Label>
<TextareaAutosize
id="promptPrefix"
value={promptPrefix}
onChange={(e) => setPromptPrefix(e.target.value)}
placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'"
className="col-span-3 flex h-20 w-full resize-none rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900"
/>
</div>
</div>
<DialogFooter>
<DialogClose>Cancel</DialogClose>
<Button
style={{ backgroundColor: 'rgb(16, 163, 127)' }}
onClick={saveHandler}
className="inline-flex h-10 items-center justify-center rounded-md border-none py-2 px-4 text-sm font-semibold text-white transition-colors"
>
{saveText}
</Button>
<DialogClose
onClick={submitHandler}
className="inline-flex h-10 items-center justify-center rounded-md border-none bg-slate-900 py-2 px-4 text-sm font-semibold text-white transition-colors hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900"
>
Submit
</DialogClose>
</DialogFooter>
</DialogContent>
);
}

View file

@ -1,50 +1,60 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useEffect } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { setModel, setDisabled, setCustomGpt } from '~/store/submitSlice'; import { setModel, setDisabled } from '~/store/submitSlice';
// import { setModels } from '~/store/modelSlice'; import { setConversation } from '~/store/convoSlice';
import ModelItem from './ModelItem'; import ModelDialog from './ModelDialog';
import MenuItems from './MenuItems';
// import useDidMountEffect from '~/hooks/useDidMountEffect';
// import { swr } from '~/utils/fetchers';
import manualSWR from '~/utils/fetchers';
// import { setMessages } from '~/store/messageSlice';
import { setModels } from '~/store/modelSlice';
// import ModelItem from './ModelItem';
import GPTIcon from '../svg/GPTIcon'; import GPTIcon from '../svg/GPTIcon';
import BingIcon from '../svg/BingIcon'; import BingIcon from '../svg/BingIcon';
import { Button } from '../ui/Button.tsx'; import { Button } from '../ui/Button.tsx';
import { Input } from '../ui/Input.tsx';
import { Label } from '../ui/Label.tsx';
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
// DropdownMenuRadioItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger DropdownMenuTrigger
} from '../ui/DropdownMenu.tsx'; } from '../ui/DropdownMenu.tsx';
import { import { Dialog } from '../ui/Dialog.tsx';
Dialog,
// DialogTrigger,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '../ui/Dialog.tsx';
export default function ModelMenu() { export default function ModelMenu() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { model } = useSelector((state) => state.submit); const { model } = useSelector((state) => state.submit);
const { models } = useSelector((state) => state.models); const { models } = useSelector((state) => state.models);
const [chatGptLabel, setChatGptLabel] = useState(''); const { trigger } = manualSWR('http://localhost:3050/customGpts', 'get', (res) => {
const [promptPrefix, setPromptPrefix] = useState(''); console.log('models data (response)', res);
const [required, setRequired] = useState(false); if (models.length + res.length === models.length) {
const inputRef = useRef(null); return;
}
const fetchedModels = res.map((modelItem) => ({
...modelItem,
name: modelItem.chatGptLabel
}));
dispatch(setModels(fetchedModels));
});
// useDidMountEffect(() => mutate(), [chatGptLabel]);
useEffect(() => { useEffect(() => {
const lastSelectedModel = JSON.parse(localStorage.getItem('model')); const lastSelectedModel = JSON.parse(localStorage.getItem('model'));
if (lastSelectedModel && lastSelectedModel !== 'chatgptCustom') { if (lastSelectedModel && lastSelectedModel !== 'chatgptCustom') {
dispatch(setModel(lastSelectedModel)); dispatch(setModel(lastSelectedModel));
} }
const cachedModels = JSON.parse(localStorage.getItem('models'));
if (cachedModels) {
dispatch(setModels(cachedModels));
}
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
@ -52,25 +62,28 @@ export default function ModelMenu() {
localStorage.setItem('model', JSON.stringify(model)); localStorage.setItem('model', JSON.stringify(model));
}, [model]); }, [model]);
useEffect(() => {
localStorage.setItem('models', JSON.stringify(models.slice(4)));
}, [models]);
const onChange = (value) => { const onChange = (value) => {
if (value === 'chatgptCustom') { if (!value) {
// dispatch(setDisabled(true)); return;
} else if (value === 'chatgptCustom') {
// dispatch(setMessages([]));
} else { } else {
dispatch(setModel(value)); dispatch(setModel(value));
dispatch(setDisabled(false)); dispatch(setDisabled(false));
} }
}; // Set new conversation
dispatch(
const submitHandler = (e) => { setConversation({
if (chatGptLabel.length === 0) { title: 'New Chat',
e.preventDefault(); error: false,
setRequired(true); conversationId: null,
inputRef.current.focus(); parentMessageId: null
return; })
} );
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
dispatch(setModel('chatgptCustom'));
dispatch(setDisabled(false));
}; };
const defaultColorProps = [ const defaultColorProps = [
@ -80,7 +93,6 @@ export default function ModelMenu() {
'dark:hover:bg-opacity-20', 'dark:hover:bg-opacity-20',
'dark:hover:bg-gray-900', 'dark:hover:bg-gray-900',
'dark:hover:text-gray-400', 'dark:hover:text-gray-400',
// 'dark:data-[state=open]:bg-transparent',
'dark:disabled:hover:bg-transparent' 'dark:disabled:hover:bg-transparent'
]; ];
@ -92,12 +104,9 @@ export default function ModelMenu() {
'dark:hover:bg-opacity-50', 'dark:hover:bg-opacity-50',
'dark:hover:bg-green-900', 'dark:hover:bg-green-900',
'dark:hover:text-gray-100', 'dark:hover:text-gray-100',
// 'dark:data-[state=open]:bg-gray-700',
'dark:disabled:hover:bg-transparent' 'dark:disabled:hover:bg-transparent'
]; ];
const requiredProp = required ? { required: true } : {};
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps; const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
const icon = model === 'bingai' ? <BingIcon button={true} /> : <GPTIcon button={true} />; const icon = model === 'bingai' ? <BingIcon button={true} /> : <GPTIcon button={true} />;
@ -121,109 +130,13 @@ export default function ModelMenu() {
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
value={model} value={model}
onValueChange={onChange} onValueChange={onChange}
className="overflow-y-auto"
> >
{models.map((modelItem, i) => ( <MenuItems models={models} />
<ModelItem
key={i}
modelName={modelItem.name}
value={modelItem.value}
/>
))}
{/* <DropdownMenuRadioItem
value="chatgpt"
className="dark:font-semibold dark:hover:bg-gray-800"
>
ChatGPT <sup>$</sup>
</DropdownMenuRadioItem>
<DialogTrigger asChild>
<DropdownMenuRadioItem
value="chatgptCustom"
className=" dark:hover:bg-gray-800"
>
CustomGPT <sup>$</sup>
</DropdownMenuRadioItem>
</DialogTrigger>
<DropdownMenuRadioItem
value="bingai"
className=" dark:hover:bg-gray-800"
>
BingAI
</DropdownMenuRadioItem>
<DropdownMenuRadioItem
value="chatgptBrowser"
className=" dark:hover:bg-gray-800"
>
ChatGPT
</DropdownMenuRadioItem> */}
</DropdownMenuRadioGroup> </DropdownMenuRadioGroup>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<DialogContent className="dark:bg-gray-800"> <ModelDialog mutate={trigger} />
<DialogHeader>
<DialogTitle>Customize ChatGPT</DialogTitle>
<DialogDescription>
Note: important instructions are often better placed in your message rather than
the prefix.{' '}
<a
href="https://platform.openai.com/docs/guides/chat/instructing-chat-models"
target="_blank"
rel="noopener noreferrer"
>
<u>More info here</u>
</a>
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label
htmlFor="chatGptLabel"
className="text-right"
>
Custom Name
</Label>
<Input
id="chatGptLabel"
value={chatGptLabel}
ref={inputRef}
onChange={(e) => setChatGptLabel(e.target.value)}
placeholder="Set a custom name for ChatGPT"
className="col-span-3 shadow-[0_0_10px_rgba(0,0,0,0.10)] invalid:border-red-400 invalid:text-red-600 invalid:placeholder-red-600
invalid:placeholder-opacity-70 invalid:ring-opacity-20 focus:ring-opacity-20 focus:invalid:border-red-400 focus:invalid:ring-red-400 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:invalid:border-red-600 dark:invalid:text-red-300 dark:invalid:placeholder-opacity-80 dark:focus:invalid:ring-red-600 dark:focus:invalid:ring-opacity-50"
{...requiredProp}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label
htmlFor="promptPrefix"
className="text-right"
>
Prompt Prefix
</Label>
<TextareaAutosize
id="promptPrefix"
value={promptPrefix}
onChange={(e) => setPromptPrefix(e.target.value)}
placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'"
className="col-span-3 flex h-20 w-full resize-none rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900"
/>
</div>
</div>
<DialogFooter>
<DialogClose>Cancel</DialogClose>
<Button
style={{ backgroundColor: 'rgb(16, 163, 127)' }}
className="inline-flex h-10 items-center justify-center rounded-md border-none py-2 px-4 text-sm font-semibold text-white transition-colors"
>
Save
</Button>
<DialogClose
onClick={submitHandler}
className="inline-flex h-10 items-center justify-center rounded-md border-none bg-slate-900 py-2 px-4 text-sm font-semibold text-white transition-colors hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900"
>
Submit
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog> </Dialog>
); );
} }

View file

@ -59,7 +59,9 @@ export default function TextChat({ messages }) {
parentMessageId: id, parentMessageId: id,
conversationSignature: null, conversationSignature: null,
clientId: null, clientId: null,
invocationId: null invocationId: null,
chatGptLabel: model === 'chatgptCustom' ? chatGptLabel : null,
promptPrefix: model === 'chatgptCustom' ? promptPrefix : null,
}) })
); );
} else if ( } else if (

View file

@ -1,4 +1,4 @@
import { createSlice, current } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
const initialState = { const initialState = {
error: false, error: false,
@ -8,6 +8,8 @@ const initialState = {
conversationSignature: null, conversationSignature: null,
clientId: null, clientId: null,
invocationId: null, invocationId: null,
chatGptLabel: null,
promptPrefix: null,
convosLoading: false convosLoading: false
}; };

View file

@ -3,18 +3,22 @@ import { createSlice } from '@reduxjs/toolkit';
const initialState = { const initialState = {
models: [ models: [
{ {
_id: '0',
name: 'ChatGPT', name: 'ChatGPT',
value: 'chatgpt' value: 'chatgpt'
}, },
{ {
_id: '1',
name: 'CustomGPT', name: 'CustomGPT',
value: 'chatgptCustom' value: 'chatgptCustom'
}, },
{ {
_id: '2',
name: 'BingAI', name: 'BingAI',
value: 'bingai' value: 'bingai'
}, },
{ {
_id: '3',
name: 'ChatGPT', name: 'ChatGPT',
value: 'chatgptBrowser' value: 'chatgptBrowser'
} }
@ -26,7 +30,8 @@ const currentSlice = createSlice({
initialState, initialState,
reducers: { reducers: {
setModels: (state, action) => { setModels: (state, action) => {
state.models = [...state.models, ...action.payload]; console.log('setModels', action.payload);
state.models = [...initialState.models, ...action.payload];
} }
} }
}); });

View file

@ -1419,10 +1419,6 @@ html {
.border-white\/20 { .border-white\/20 {
border-color:hsla(0,0%,100%,.2) border-color:hsla(0,0%,100%,.2)
} }
.border-indigo-400 {
--tw-border-opacity:1;
border-color:rgba(129,140,248,var(--tw-border-opacity))
}
.border-gray-100 { .border-gray-100 {
--tw-border-opacity:1; --tw-border-opacity:1;
border-color:rgba(236,236,241,var(--tw-border-opacity)) border-color:rgba(236,236,241,var(--tw-border-opacity))
@ -1431,24 +1427,9 @@ html {
--tw-border-opacity:1; --tw-border-opacity:1;
border-color:rgba(217,217,227,var(--tw-border-opacity)) border-color:rgba(217,217,227,var(--tw-border-opacity))
} }
.border-transparent {
border-color:transparent
}
.border-black\/20 { .border-black\/20 {
border-color:rgba(0,0,0,.2) border-color:rgba(0,0,0,.2)
} }
.border-green-500 {
--tw-border-opacity:1;
border-color:rgba(25,195,125,var(--tw-border-opacity))
}
.border-orange-500 {
--tw-border-opacity:1;
border-color:rgba(224,108,43,var(--tw-border-opacity))
}
.border-red-500 {
--tw-border-opacity:1;
border-color:rgba(239,68,68,var(--tw-border-opacity))
}
.border-gray-500 { .border-gray-500 {
--tw-border-opacity:1; --tw-border-opacity:1;
border-color:rgba(142,142,160,var(--tw-border-opacity)) border-color:rgba(142,142,160,var(--tw-border-opacity))
@ -1457,22 +1438,10 @@ html {
--tw-bg-opacity:1; --tw-bg-opacity:1;
background-color:rgba(217,217,227,var(--tw-bg-opacity)) background-color:rgba(217,217,227,var(--tw-bg-opacity))
} }
.bg-orange-500 {
--tw-bg-opacity:1;
background-color:rgba(224,108,43,var(--tw-bg-opacity))
}
.bg-red-500 {
--tw-bg-opacity:1;
background-color:rgba(239,68,68,var(--tw-bg-opacity))
}
.bg-\[\#5436DA\] { .bg-\[\#5436DA\] {
--tw-bg-opacity:1; --tw-bg-opacity:1;
background-color:rgba(84,54,218,var(--tw-bg-opacity)) background-color:rgba(84,54,218,var(--tw-bg-opacity))
} }
.bg-yellow-200 {
--tw-bg-opacity:1;
background-color:rgba(250,230,158,var(--tw-bg-opacity))
}
.bg-white { .bg-white {
--tw-bg-opacity:1; --tw-bg-opacity:1;
background-color:rgba(255,255,255,var(--tw-bg-opacity)) background-color:rgba(255,255,255,var(--tw-bg-opacity))
@ -1489,28 +1458,9 @@ html {
--tw-bg-opacity:1; --tw-bg-opacity:1;
background-color:rgba(247,247,248,var(--tw-bg-opacity)) background-color:rgba(247,247,248,var(--tw-bg-opacity))
} }
.bg-gray-100 {
--tw-bg-opacity:1;
background-color:rgba(236,236,241,var(--tw-bg-opacity))
}
.bg-transparent {
background-color:transparent
}
.bg-gray-500\/90 { .bg-gray-500\/90 {
background-color:hsla(240,9%,59%,.9) background-color:hsla(240,9%,59%,.9)
} }
.bg-red-100 {
--tw-bg-opacity:1;
background-color:rgba(254,226,226,var(--tw-bg-opacity))
}
.bg-yellow-100 {
--tw-bg-opacity:1;
background-color:rgba(254,249,195,var(--tw-bg-opacity))
}
.bg-green-100 {
--tw-bg-opacity:1;
background-color:rgba(210,244,211,var(--tw-bg-opacity))
}
.bg-gray-900 { .bg-gray-900 {
--tw-bg-opacity:1; --tw-bg-opacity:1;
background-color:rgba(32,33,35,var(--tw-bg-opacity)) background-color:rgba(32,33,35,var(--tw-bg-opacity))
@ -1539,13 +1489,6 @@ html {
--tw-bg-opacity:1!important; --tw-bg-opacity:1!important;
background-color:rgba(217,217,227,var(--tw-bg-opacity))!important background-color:rgba(217,217,227,var(--tw-bg-opacity))!important
} }
.bg-green-500 {
--tw-bg-opacity:1;
background-color:rgba(25,195,125,var(--tw-bg-opacity))
}
.bg-orange-500\/10 {
background-color:rgba(224,108,43,.1)
}
.bg-red-500\/10 { .bg-red-500\/10 {
background-color:rgba(239,68,68,.1) background-color:rgba(239,68,68,.1)
} }
@ -1557,10 +1500,6 @@ html {
--tw-bg-opacity:1; --tw-bg-opacity:1;
background-color:rgba(172,172,190,var(--tw-bg-opacity)) background-color:rgba(172,172,190,var(--tw-bg-opacity))
} }
.bg-green-600 {
--tw-bg-opacity:1;
background-color:rgba(16,163,127,var(--tw-bg-opacity))
}
.bg-opacity-75 { .bg-opacity-75 {
--tw-bg-opacity:0.75 --tw-bg-opacity:0.75
} }

View file

@ -16,7 +16,7 @@ export const swr = (path, successCallback) => {
options.onSuccess = successCallback; options.onSuccess = successCallback;
} }
return useSWR(path, fetcher); return useSWR(path, fetcher, options);
} }
export default function manualSWR(path, type, successCallback) { export default function manualSWR(path, type, successCallback) {
@ -27,4 +27,4 @@ export default function manualSWR(path, type, successCallback) {
} }
const fetchFunction = type === 'get' ? fetcher : postRequest; const fetchFunction = type === 'get' ? fetcher : postRequest;
return useSWRMutation(path, fetchFunction, options); return useSWRMutation(path, fetchFunction, options);
}; }

View file

@ -1,6 +1,6 @@
const path = require('path'); const path = require('path');
require('dotenv').config(); require('dotenv').config();
const HtmlWebpackPlugin = require('html-webpack-plugin'); // const HtmlWebpackPlugin = require('html-webpack-plugin');
/*We are basically telling webpack to take index.js from entry. Then check for all file extensions in resolve. /*We are basically telling webpack to take index.js from entry. Then check for all file extensions in resolve.
After that apply all the rules in module.rules and produce the output and place it in main.js in the public folder.*/ After that apply all the rules in module.rules and produce the output and place it in main.js in the public folder.*/