mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
customGpts persist through localStorage
This commit is contained in:
parent
62bb6ea8f8
commit
9c3a78f96b
22 changed files with 405 additions and 220 deletions
|
|
@ -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.)
|
||||||
|
|
|
||||||
|
|
@ -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
85
models/CustomGpt.js
Normal 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' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
@ -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, () => {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
56
server/routes/customGpts.js
Normal file
56
server/routes/customGpts.js
Normal 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;
|
||||||
|
|
@ -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 };
|
||||||
|
|
@ -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} />
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
() => {
|
() => {
|
||||||
|
|
|
||||||
20
src/components/main/MenuItems.jsx
Normal file
20
src/components/main/MenuItems.jsx
Normal 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}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
135
src/components/main/ModelDialog.jsx
Normal file
135
src/components/main/ModelDialog.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue