Merge pull request #2 from danny-avila/customChatGPT

feature: customize chat gpt
This commit is contained in:
Danny Avila 2023-03-04 21:47:48 -05:00 committed by GitHub
commit c8d8484d86
38 changed files with 1815 additions and 138 deletions

View file

@ -5,10 +5,19 @@
## Updates
<details open>
<summary><strong>2023-03-04</strong></summary>
Custom prompt prefixing and labeling is now supported through the official API. This nets some interesting results when you need ChatGPT for specific uses or entertainment. Select 'CustomGPT' in the model menu to configure this, and you can choose to save the configuration or reference it by conversation. Model selection will change by conversation.
</details>
<details>
<summary><strong>Previous Updates</strong></summary>
<details>
<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.
The Messages UI correctly mirrors code syntax highlighting. The exact replication of the cursor is not 1-to-1 yet, but pretty close. Later on in the project, I'll implement tests for code edge cases and explore the possibility of running code in-browser. Right now, unknown code defaults to javascript, but will detect language as close as possible.
</details>
<details>
<summary><strong>2023-02-21</strong></summary>
BingAI is integrated (although sadly limited by Microsoft with the 5 msg/convo limit, 50 msgs/day). I will need to handle the case when Bing refuses to give more answers on top of the other styling features I have in mind. Official ChatGPT use is back with the new BrowserClient. Brainstorming how to handle the UI when the Ai model changes, since conversations can't be persisted between them (or perhaps build a way to achieve this at some level).
@ -24,6 +33,7 @@ Official ChatGPT use is no longer possible though I recently used it with waylai
Currently, this project is only functional with the `text-davinci-003` model.
</details>
</details>
## Roadmap
@ -31,7 +41,7 @@ Currently, this project is only functional with the `text-davinci-003` model.
> This is a work in progress. I'm building this in public. You can follow the progress here or on my [Linkedin](https://www.linkedin.com/in/danny-avila).
> Here are my planned/recently finished features.
Here are my planned/recently finished features.
- [x] Rename, delete conversations
- [x] Persistent conversation
@ -43,8 +53,8 @@ Currently, this project is only functional with the `text-davinci-003` model.
- [x] Markdown handling
- [x] Language Detection for code blocks
- [x] 'Copy to clipboard' button for code blocks
- [ ] Set user/model label and prompt prefix view option
- [ ] AI model change handling (whether to pseudo-persist convos or start new convos within existing convo)
- [x] Customize prompt prefix/label (custom ChatGPT using official API)
- [x] AI model change handling (start new convos within existing convo)
- [ ] Server convo pagination (limit fetch and load more with 'show more' button)
- [ ] Bing AI Styling (for suggested responses, convo end, etc.)
- [ ] Prompt Templates
@ -58,25 +68,26 @@ Currently, this project is only functional with the `text-davinci-003` model.
- Response streaming identical to ChatGPT
- UI from original ChatGPT, including Dark mode
- AI model selection
- AI model selection, including OpenAI's official ChatGPT API
### Tech Stack
- Utilizes [node-chatgpt-api](https://github.com/waylaidwanderer/node-chatgpt-api)
- Response streaming identical to ChatGPT through server-sent events
- Use of Tailwind CSS (like the official site) and [shadcn/ui](https://github.com/shadcn/ui) components
- useSWR, Redux Toolkit, Express, MongoDB, [Keyv](https://www.npmjs.com/package/keyv)
- highlight.js, useSWR, Redux, Express, MongoDB, [Keyv](https://www.npmjs.com/package/keyv)
## Use Cases ##
![use case example](./public/use_case.png "GPT is down! Plus is too expensive!")
- ChatGPT is down ( and don't want to pay for ChatGPT Plus).
- One stop shop for all conversational AIs, with the added bonus of searching past conversations.
- Using the official API, you'd have to generate 7.5 million words to expense the same cost as ChatGPT Plus ($20).
- ChatGPT Free is down.
- ChatGPT/Google Bard/Bing AI conversations are lost in space or
cannot be searched past a certain timeframe.
- Quick one stop shop for all conversational AIs, with the added bonus of searching
## Origin ##
This project was originally created as a Minimum Viable Product (or MVP) for the [@HackReactor](https://github.com/hackreactor/) Bootcamp. It was built with OpenAI response streaming and most of the UI completed in under 20 hours. 20 hours in, I had most of the UI and basic functionality done. This was created without using any boilerplates or templates, including create-react-app and other toolchains. The purpose of the exercise was to learn setting up a full stack project from scratch. Please feel free to give feedback, suggestions, or fork the project for your own use.
This project was originally created as a Minimum Viable Product (or MVP) for the [@HackReactor](https://github.com/hackreactor/) Bootcamp. It was built with OpenAI response streaming and most of the UI completed in under 20 hours. During the end of that time, I had most of the UI and basic functionality done. This was created without using any boilerplates or templates, including create-react-app and other toolchains. I didn't follow any 'un-official chatgpt' video tutorials, and simply referenced the official site for the UI. The purpose of the exercise was to learn setting up a full stack project from scratch. Please feel free to give feedback, suggestions, or fork the project for your own use.
<!-- ## Solution ##
Serves and searches all conversations reliably. All AI convos under one house.

37
app/chatgpt-custom.js Normal file
View file

@ -0,0 +1,37 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const clientOptions = {
modelOptions: {
model: 'gpt-3.5-turbo'
},
debug: false
};
const customClient = async ({ text, progressCallback, convo, promptPrefix, chatGptLabel }) => {
const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default;
const store = {
store: new KeyvFile({ filename: './data/cache.json' })
};
clientOptions.chatGptLabel = chatGptLabel;
if (promptPrefix.length > 0) {
clientOptions.promptPrefix = promptPrefix;
}
const client = new ChatGPTClient(process.env.OPENAI_KEY, clientOptions, store);
let options = {
onProgress: async (partialRes) => await progressCallback(partialRes)
};
if (!!convo.parentMessageId && !!convo.conversationId) {
options = { ...options, ...convo };
}
const res = await client.sendMessage(text, options);
return res;
};
module.exports = customClient;

View file

@ -1,5 +1,6 @@
const { askClient } = require('./chatgpt-client');
const { browserClient } = require('./chatgpt-browser');
const customClient = require('./chatgpt-custom');
const { askBing } = require('./bingai');
const titleConvo = require('./titleConvo');
const detectCode = require('./detectCode');
@ -7,6 +8,7 @@ const detectCode = require('./detectCode');
module.exports = {
askClient,
browserClient,
customClient,
askBing,
titleConvo,
detectCode

View file

@ -11,13 +11,14 @@ const titleConvo = async ({ message, response, model }) => {
{
role: 'system',
content:
'You are a helpful title-generator with one job: titling in title case the conversation provided by a user. You do not reply with anything but a succinct title that summarizes the conversation in title case, ideally around 5 words or less. You do not refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title.'
'You are a title-generator with one job: titling the conversation provided by a user in title case.'
},
{ role: 'user', content: `Please title this conversation: User:"${message}" ${model}:"${response}" Title:` },
{ role: 'user', content: `In 5 words or less, summarize the conversation below with a title in title case. Don't refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${message}"\n\n${model}: "${response}"\n\nTitle: ` },
]
});
return completion.data.choices[0].message.content;
//eslint-disable-next-line
return completion.data.choices[0].message.content.replace(/["\.]/g, '');
};
module.exports = titleConvo;

View file

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

73
models/CustomGpt.js Normal file
View file

@ -0,0 +1,73 @@
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 ({ 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 { getCustomGpts, updateCustomGpt, deleteCustomGpts } = require('./CustomGpt');
const { saveConvo } = require('./Conversation');
module.exports = {
saveMessage,
deleteMessages,
saveConvo,
};
getCustomGpts,
updateCustomGpt,
deleteCustomGpts
};

105
package-lock.json generated
View file

@ -10,7 +10,10 @@
"license": "ISC",
"dependencies": {
"@keyv/mongo": "^2.1.8",
"@radix-ui/react-alert-dialog": "^1.0.2",
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.2",
"@radix-ui/react-label": "^2.0.0",
"@radix-ui/react-tabs": "^1.0.2",
"@reduxjs/toolkit": "^1.9.2",
"@vscode/vscode-languagedetection": "^1.0.22",
@ -3716,6 +3719,24 @@
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/react-alert-dialog": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.2.tgz",
"integrity": "sha512-0MtxV53FaEEBOKRgyLnEqHZKKDS5BldQ9oUBsKVXWI5FHbl2jp35qs+0aJET+K5hJDsc40kQUzP7g+wC7tqrqA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-dialog": "1.0.2",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-slot": "1.0.1"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-arrow": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.1.tgz",
@ -3767,6 +3788,32 @@
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.2.tgz",
"integrity": "sha512-EKxxp2WNSmUPkx4trtWNmZ4/vAYEg7JkAfa1HKBUnaubw9eHzf1Orr9B472lJYaYz327RHDrd4R95fsw7VR8DA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-dismissable-layer": "1.0.2",
"@radix-ui/react-focus-guards": "1.0.0",
"@radix-ui/react-focus-scope": "1.0.1",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-portal": "1.0.1",
"@radix-ui/react-presence": "1.0.0",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-slot": "1.0.1",
"@radix-ui/react-use-controllable-state": "1.0.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.5.5"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-direction": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz",
@ -3852,6 +3899,19 @@
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-label": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.0.tgz",
"integrity": "sha512-7qCcZ3j2VQspWjy+gKR4W+V/z0XueQjeiZnlPOtsyiP9HaS8bfSU7ECoI3bvvdYntQj7NElW7OAYsYRW4MQvCg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.1"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-menu": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.2.tgz",
@ -17716,6 +17776,20 @@
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-alert-dialog": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.2.tgz",
"integrity": "sha512-0MtxV53FaEEBOKRgyLnEqHZKKDS5BldQ9oUBsKVXWI5FHbl2jp35qs+0aJET+K5hJDsc40kQUzP7g+wC7tqrqA==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-dialog": "1.0.2",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-slot": "1.0.1"
}
},
"@radix-ui/react-arrow": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.1.tgz",
@ -17753,6 +17827,28 @@
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-dialog": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.2.tgz",
"integrity": "sha512-EKxxp2WNSmUPkx4trtWNmZ4/vAYEg7JkAfa1HKBUnaubw9eHzf1Orr9B472lJYaYz327RHDrd4R95fsw7VR8DA==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-dismissable-layer": "1.0.2",
"@radix-ui/react-focus-guards": "1.0.0",
"@radix-ui/react-focus-scope": "1.0.1",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-portal": "1.0.1",
"@radix-ui/react-presence": "1.0.0",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-slot": "1.0.1",
"@radix-ui/react-use-controllable-state": "1.0.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.5.5"
}
},
"@radix-ui/react-direction": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz",
@ -17817,6 +17913,15 @@
"@radix-ui/react-use-layout-effect": "1.0.0"
}
},
"@radix-ui/react-label": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.0.tgz",
"integrity": "sha512-7qCcZ3j2VQspWjy+gKR4W+V/z0XueQjeiZnlPOtsyiP9HaS8bfSU7ECoI3bvvdYntQj7NElW7OAYsYRW4MQvCg==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.1"
}
},
"@radix-ui/react-menu": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.2.tgz",

View file

@ -22,7 +22,10 @@
"homepage": "https://github.com/danny-avila/rpp2210-mvp#readme",
"dependencies": {
"@keyv/mongo": "^2.1.8",
"@radix-ui/react-alert-dialog": "^1.0.2",
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.2",
"@radix-ui/react-label": "^2.0.0",
"@radix-ui/react-tabs": "^1.0.2",
"@reduxjs/toolkit": "^1.9.2",
"@vscode/vscode-languagedetection": "^1.0.22",

View file

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

View file

@ -2,14 +2,21 @@ const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const askBing = require('./askBing');
const { titleConvo, askClient, browserClient, detectCode } = require('../../app/');
const {
titleConvo,
askClient,
browserClient,
customClient,
detectCode
} = require('../../app/');
const { saveMessage, deleteMessages, saveConvo } = require('../../models');
const { handleError, sendMessage } = require('./handlers');
router.use('/bing', askBing);
router.post('/', async (req, res) => {
const { model, text, parentMessageId, conversationId } = req.body;
const { model, text, parentMessageId, conversationId, chatGptLabel, promptPrefix } =
req.body;
if (!text.trim().includes(' ') && text.length < 5) {
return handleError(res, 'Prompt empty or too short');
}
@ -17,9 +24,24 @@ router.post('/', async (req, res) => {
const userMessageId = crypto.randomUUID();
let userMessage = { id: userMessageId, sender: 'User', text };
console.log('ask log', { model, ...userMessage, parentMessageId, conversationId });
console.log('ask log', {
model,
...userMessage,
parentMessageId,
conversationId,
chatGptLabel,
promptPrefix
});
const client = model === 'chatgpt' ? askClient : browserClient;
let client;
if (model === 'chatgpt') {
client = askClient;
} else if (model === 'chatgptCustom') {
client = customClient;
} else {
client = browserClient;
}
res.writeHead(200, {
Connection: 'keep-alive',
@ -61,7 +83,9 @@ router.post('/', async (req, res) => {
convo: {
parentMessageId,
conversationId
}
},
chatGptLabel,
promptPrefix
});
console.log('CLIENT RESPONSE', gptResponse);
@ -87,11 +111,24 @@ router.post('/', async (req, res) => {
}
if (!parentMessageId) {
gptResponse.title = await titleConvo({ model, message: text, response: JSON.stringify(gptResponse.text) });
gptResponse.title = await titleConvo({
model,
message: text,
response: JSON.stringify(gptResponse.text)
});
}
gptResponse.sender = model;
gptResponse.sender = model === 'chatgptCustom' ? chatGptLabel : model;
gptResponse.final = true;
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 saveConvo(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 messages = require('./messages');
const convos = require('./convos');
const customGpts = require('./customGpts');
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);
useDocumentTitle(title);
// bg-color: #343541 instead of bg-gray-800
return (
<div className="flex h-screen">
<Nav />
<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 bg-white dark:bg-gray-800/90">
<MobileNav />
{messages.length === 0 ? (
<Landing title={title} />

View file

@ -1,8 +1,9 @@
import React, { useState, useRef } from 'react';
import RenameButton from './RenameButton';
import DeleteButton from './DeleteButton';
import { useDispatch } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
import { setConversation } from '~/store/convoSlice';
import { setCustomGpt, setModel, setCustomModel } from '~/store/submitSlice';
import { setMessages } from '~/store/messageSlice';
import { setText } from '~/store/textSlice';
import manualSWR from '~/utils/fetchers';
@ -13,13 +14,15 @@ export default function Conversation({
parentMessageId,
conversationId,
title = 'New conversation',
bingData
bingData,
chatGptLabel = null,
promptPrefix = null
}) {
const [renaming, setRenaming] = useState(false);
const [titleInput, setTitleInput] = useState(title);
const { modelMap } = useSelector((state) => state.models);
const inputRef = useRef(null);
const dispatch = useDispatch();
// const { trigger, isMutating } = 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');
@ -28,13 +31,13 @@ export default function Conversation({
return;
}
const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix };
if (bingData) {
const { conversationSignature, clientId, invocationId } = bingData;
dispatch(
setConversation({
title,
error: false,
conversationId: id,
...convo,
parentMessageId: null,
conversationSignature,
clientId,
@ -44,9 +47,7 @@ export default function Conversation({
} else {
dispatch(
setConversation({
title,
error: false,
conversationId: id,
...convo,
parentMessageId,
conversationSignature: null,
clientId: null,
@ -55,7 +56,22 @@ export default function Conversation({
);
}
const data = await trigger();
if (chatGptLabel) {
dispatch(setModel('chatgptCustom'));
} else {
dispatch(setModel(data[1].sender));
}
if (modelMap[data[1].sender.toLowerCase()]) {
console.log('sender', data[1].sender);
dispatch(setCustomModel(data[1].sender.toLowerCase()));
} else {
dispatch(setCustomModel(null));
}
dispatch(setMessages(data));
dispatch(setCustomGpt(convo));
dispatch(setText(''));
};

View file

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

View file

@ -42,7 +42,7 @@ export default function Message({
const props = {
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 = {
@ -56,7 +56,10 @@ export default function Message({
if (notUser) {
props.className =
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654]';
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654]';
}
if (notUser && backgroundColor || sender === 'bingai') {
icon = (
<div
style={{ backgroundColor }}
@ -77,7 +80,9 @@ export default function Message({
onMouseOut={handleMouseOut}
>
<div className="m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
<strong className="relative flex w-[30px] flex-col items-end">{icon}</strong>
<strong className="relative flex w-[30px] flex-col items-end text-right">
{icon}
</strong>
<div className="relative flex w-[calc(100%-50px)] flex-col gap-1 whitespace-pre-wrap md:gap-3 lg:w-[calc(100%-115px)]">
<div className="flex flex-grow flex-col gap-3">
{error ? (

View file

@ -0,0 +1,16 @@
import React from 'react';
import ModelItem from './ModelItem';
export default function MenuItems({ models }) {
return (
<>
{models.map((modelItem, i) => (
<ModelItem
key={i}
modelName={modelItem.name}
value={modelItem.value}
/>
))}
</>
);
}

View file

@ -0,0 +1,134 @@
import React, { useState, useRef } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { useDispatch } from 'react-redux';
import { setModel, 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

@ -0,0 +1,29 @@
import React from 'react';
import { DropdownMenuRadioItem } from '../ui/DropdownMenu.tsx';
import { DialogTrigger } from '../ui/Dialog.tsx';
export default function ModelItem({ modelName, value }) {
if (value === 'chatgptCustom') {
return (
<DialogTrigger className="w-full">
<DropdownMenuRadioItem
value={value}
className="dark:font-semibold dark:hover:bg-gray-800"
>
{modelName}
<sup>$</sup>
</DropdownMenuRadioItem>
</DialogTrigger>
);
}
return (
<DropdownMenuRadioItem
value={value}
className="dark:font-semibold dark:hover:bg-gray-800"
>
{modelName}
{value === 'chatgpt' && <sup>$</sup>}
</DropdownMenuRadioItem>
);
}

View file

@ -0,0 +1,146 @@
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { setModel, setDisabled, setCustomGpt, setCustomModel } from '~/store/submitSlice';
import { setConversation } from '~/store/convoSlice';
import ModelDialog from './ModelDialog';
import MenuItems from './MenuItems';
import manualSWR from '~/utils/fetchers';
import { setModels } from '~/store/modelSlice';
import GPTIcon from '../svg/GPTIcon';
import BingIcon from '../svg/BingIcon';
import { Button } from '../ui/Button.tsx';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '../ui/DropdownMenu.tsx';
import { Dialog } from '../ui/Dialog.tsx';
export default function ModelMenu() {
const dispatch = useDispatch();
const { model, customModel } = useSelector((state) => state.submit);
const { models, modelMap, initial } = useSelector((state) => state.models);
const { trigger } = manualSWR('http://localhost:3050/customGpts', 'get', (res) => {
console.log('models data (response)', res);
if (models.length + res.length === models.length) {
return;
}
const fetchedModels = res.map((modelItem) => ({
...modelItem,
name: modelItem.chatGptLabel
}));
dispatch(setModels(fetchedModels));
});
useEffect(() => {
const lastSelected = JSON.parse(localStorage.getItem('model'));
if (lastSelected && lastSelected !== 'chatgptCustom' && initial[lastSelected]) {
dispatch(setModel(lastSelected));
}
const cachedModels = JSON.parse(localStorage.getItem('models'));
if (cachedModels) {
dispatch(setModels(cachedModels));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
localStorage.setItem('model', JSON.stringify(model));
}, [model]);
useEffect(() => {
localStorage.setItem('models', JSON.stringify(models.slice(4)));
}, [models]);
const onChange = (value) => {
if (!value) {
return;
} else if (value === 'chatgptCustom') {
// dispatch(setMessages([]));
} else if (initial[value]) {
dispatch(setModel(value));
dispatch(setDisabled(false));
setCustomModel(null);
} else if (!initial[value]) {
const chatGptLabel = modelMap[value]?.chatGptLabel;
const promptPrefix = modelMap[value]?.promptPrefix;
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
dispatch(setModel('chatgptCustom'));
setCustomModel(value);
} else if (!modelMap[value]) {
setCustomModel(null);
}
// Set new conversation
dispatch(
setConversation({
title: 'New Chat',
error: false,
conversationId: null,
parentMessageId: null
})
);
};
const defaultColorProps = [
'text-gray-500',
'hover:bg-gray-100',
'disabled:hover:bg-transparent',
'dark:hover:bg-opacity-20',
'dark:hover:bg-gray-900',
'dark:hover:text-gray-400',
'dark:disabled:hover:bg-transparent'
];
const chatgptColorProps = [
'text-green-700',
'dark:text-emerald-300',
'hover:bg-green-100',
'disabled:hover:bg-transparent',
'dark:hover:bg-opacity-50',
'dark:hover:bg-green-900',
'dark:hover:text-gray-100',
'dark:disabled:hover:bg-transparent'
];
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
const icon = model === 'bingai' ? <BingIcon button={true} /> : <GPTIcon button={true} />;
return (
<Dialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
className={`absolute bottom-0.5 rounded-md border-0 p-1 pl-2 outline-none ${colorProps.join(
' '
)} focus:ring-0 focus:ring-offset-0 disabled:bottom-0.5 dark:data-[state=open]:bg-gray-800 dark:data-[state=open]:bg-opacity-50 md:bottom-1 md:left-2 md:pl-1 md:disabled:bottom-1`}
>
{icon}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 dark:bg-gray-700">
<DropdownMenuLabel>Select a Model</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup
value={customModel ? customModel : model}
onValueChange={onChange}
className="overflow-y-auto"
>
<MenuItems models={models} />
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<ModelDialog mutate={trigger} />
</Dialog>
);
}

View file

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

View file

@ -1,85 +0,0 @@
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { setModel } from '~/store/submitSlice';
import GPTIcon from '../svg/GPTIcon';
import BingIcon from '../svg/BingIcon';
import { Button } from '../ui/Button.tsx';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '../ui/DropdownMenu.tsx';
export default function ModelMenu() {
const dispatch = useDispatch();
const { model } = useSelector((state) => state.submit);
const onChange = (value) => {
dispatch(setModel(value));
};
useEffect(() => {
const lastSelectedModel = JSON.parse(localStorage.getItem('model'));
if (lastSelectedModel) {
dispatch(setModel(lastSelectedModel));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
localStorage.setItem('model', JSON.stringify(model));
}, [model]);
const defaultColorProps = [
'text-gray-500',
'hover:bg-gray-100',
'disabled:hover:bg-transparent',
'dark:hover:bg-gray-900',
'dark:hover:text-gray-400',
'dark:disabled:hover:bg-transparent'
];
const chatgptColorProps = [
'text-green-700',
'dark:text-emerald-300',
'hover:bg-green-100',
'disabled:hover:bg-transparent',
'dark:hover:bg-opacity-50',
'dark:hover:bg-green-900',
'dark:hover:text-gray-100',
'dark:disabled:hover:bg-transparent'
];
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
const icon = model === 'bingai' ? <BingIcon button={true} /> : <GPTIcon button={true} /> ;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
className={`absolute bottom-0.5 rounded-md border-0 p-1 pl-2 outline-none ${colorProps.join(' ')} focus:ring-0 focus:ring-offset-0 disabled:bottom-0.5 md:pl-1 md:bottom-1 md:left-2 md:disabled:bottom-1`}
>
{icon}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Select a Model</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup
value={model}
onValueChange={onChange}
>
<DropdownMenuRadioItem value="chatgpt">ChatGPT</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="bingai">BingAI</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="chatgptBrowser">{'ChatGPT (free)'}</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}

View file

@ -2,7 +2,7 @@ import React from 'react';
import { useSelector } from 'react-redux';
export default function SubmitButton({ submitMessage }) {
const { isSubmitting } = useSelector((state) => state.submit);
const { isSubmitting, disabled } = useSelector((state) => state.submit);
const clickHandler = (e) => {
e.preventDefault();
submitMessage();
@ -22,6 +22,7 @@ export default function SubmitButton({ submitMessage }) {
return (
<button
onClick={clickHandler}
disabled={disabled}
className="absolute bottom-1.5 right-1 rounded-md p-1 text-gray-500 hover:bg-gray-100 disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent md:bottom-2.5 md:right-2"
>
<svg

View file

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import SubmitButton from './SubmitButton';
import Regenerate from './Regenerate';
import ModelMenu from './ModelMenu';
import ModelMenu from '../Models/ModelMenu';
import Footer from './Footer';
import TextareaAutosize from 'react-textarea-autosize';
import handleSubmit from '~/utils/handleSubmit';
@ -15,9 +15,13 @@ export default function TextChat({ messages }) {
const [errorMessage, setErrorMessage] = useState('');
const dispatch = useDispatch();
const convo = useSelector((state) => state.convo);
const { isSubmitting, model } = useSelector((state) => state.submit);
const { initial } = useSelector((state) => state.models);
const { isSubmitting, disabled, model, chatGptLabel, promptPrefix } = useSelector(
(state) => state.submit
);
const { text } = useSelector((state) => state.text);
const { error } = convo;
const isCustomModel = model === 'chatgptCustom' || !initial[model];
const submitMessage = () => {
if (error) {
@ -30,15 +34,16 @@ export default function TextChat({ messages }) {
dispatch(setSubmitState(true));
const message = text.trim();
const currentMsg = { sender: 'User', text: message, current: true };
const initialResponse = { sender: model, text: '' };
const sender = model === 'chatgptCustom' ? chatGptLabel : model;
const initialResponse = { sender, text: '' };
dispatch(setMessages([...messages, currentMsg, initialResponse]));
dispatch(setText(''));
const messageHandler = (data) => {
dispatch(setMessages([...messages, currentMsg, { sender: model, text: data }]));
dispatch(setMessages([...messages, currentMsg, { sender, text: data }]));
};
const convoHandler = (data) => {
dispatch(
setMessages([...messages, currentMsg, { sender: model, text: data.text || data.response }])
setMessages([...messages, currentMsg, { sender, text: data.text || data.response }])
);
if (
@ -54,7 +59,9 @@ export default function TextChat({ messages }) {
parentMessageId: id,
conversationSignature: null,
clientId: null,
invocationId: null
invocationId: null,
chatGptLabel: model === isCustomModel ? chatGptLabel : null,
promptPrefix: model === isCustomModel ? promptPrefix : null
})
);
} else if (
@ -116,7 +123,9 @@ export default function TextChat({ messages }) {
convo,
messageHandler,
convoHandler,
errorHandler
errorHandler,
chatGptLabel,
promptPrefix
};
console.log('User Input:', message);
handleSubmit(submission);
@ -167,7 +176,13 @@ export default function TextChat({ messages }) {
errorMessage={errorMessage}
/>
) : (
<div className="relative flex w-full flex-grow flex-col rounded-md border border-black/10 bg-white py-2 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 dark:bg-gray-700 dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4">
<div
className={`relative flex w-full flex-grow flex-col rounded-md border border-black/10 ${
disabled ? 'bg-gray-100' : 'bg-white'
} py-2 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
} dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4`}
>
<ModelMenu />
<TextareaAutosize
tabIndex="0"
@ -177,7 +192,8 @@ export default function TextChat({ messages }) {
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
onChange={changeHandler}
placeholder=""
placeholder={disabled ? 'Choose another model or customize GPT again' : ''}
disabled={disabled}
className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-9 pr-8 leading-6 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-8"
/>
<SubmitButton submitMessage={submitMessage} />

View file

@ -0,0 +1,156 @@
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "../../utils"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = ({
className,
children,
...props
}: AlertDialogPrimitive.AlertDialogPortalProps) => (
<AlertDialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
{children}
</div>
</AlertDialogPrimitive.Portal>
)
AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed z-50 grid w-full max-w-lg scale-100 gap-4 bg-white p-6 opacity-100 animate-in fade-in-90 slide-in-from-bottom-10 sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0 md:w-full",
"dark:bg-slate-900",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold text-slate-900",
"dark:text-slate-50",
className
)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-slate-500", "dark:text-slate-400", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md 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",
className
)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
"mt-2 inline-flex h-10 items-center justify-center rounded-md border border-slate-200 bg-transparent py-2 px-4 text-sm font-semibold text-slate-900 transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-700 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}

View file

@ -0,0 +1,144 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "../../utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = ({
className,
children,
...props
}: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
{children}
</div>
</DialogPrimitive.Portal>
)
DialogPortal.displayName = DialogPrimitive.Portal.displayName
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<DialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=open]:fade-in data-[state=closed]:fade-out",
className
)}
{...props}
ref={ref}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed z-50 grid w-full gap-4 rounded-b-lg bg-white p-6 animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
"dark:bg-slate-900",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold text-slate-900",
"dark:text-slate-50",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-slate-500", "dark:text-slate-400", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
const DialogClose = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Close>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Close
ref={ref}
className={cn(
"mt-2 inline-flex h-10 items-center justify-center rounded-md border border-slate-200 bg-transparent py-2 px-4 text-sm font-semibold text-slate-900 transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-700 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 sm:mt-0",
className
)}
{...props}
/>
))
DialogClose.displayName = DialogPrimitive.Title.displayName
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
DialogClose,
}

View file

@ -0,0 +1,24 @@
import * as React from "react"
import { cn } from "../../utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, ...props }, ref) => {
return (
<input
className={cn(
"flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View file

@ -0,0 +1,21 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cn } from "../../utils"
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(
"text-sm font-medium dark:text-gray-200 leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className
)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View file

@ -0,0 +1,26 @@
/* eslint-disable */
import * as React from "react"
import TextareaAutosize from 'react-textarea-autosize';
import { cn } from "../../utils"
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex h-20 w-full resize-none rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
export { Textarea }

View file

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

View file

@ -2,6 +2,7 @@ import { configureStore } from '@reduxjs/toolkit';
import convoReducer from './convoSlice.js';
import messageReducer from './messageSlice.js'
import modelReducer from './modelSlice.js'
import submitReducer from './submitSlice.js'
import textReducer from './textSlice.js'
@ -9,6 +10,7 @@ export const store = configureStore({
reducer: {
convo: convoReducer,
messages: messageReducer,
models: modelReducer,
text: textReducer,
submit: submitReducer,
},

53
src/store/modelSlice.js Normal file
View file

@ -0,0 +1,53 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
models: [
{
_id: '0',
name: 'ChatGPT',
value: 'chatgpt'
},
{
_id: '1',
name: 'CustomGPT',
value: 'chatgptCustom'
},
{
_id: '2',
name: 'BingAI',
value: 'bingai'
},
{
_id: '3',
name: 'ChatGPT',
value: 'chatgptBrowser'
}
],
modelMap: {},
initial: { chatgpt: true, chatgptCustom: true, bingai: true, chatgptBrowser: true }
};
const currentSlice = createSlice({
name: 'models',
initialState,
reducers: {
setModels: (state, action) => {
const models = [...initialState.models, ...action.payload];
state.models = models;
const modelMap = {};
models.slice(4).forEach((modelItem) => {
modelMap[modelItem.value] = {
chatGptLabel: modelItem.chatGptLabel,
promptPrefix: modelItem.promptPrefix
};
});
state.modelMap = modelMap;
}
}
});
export const { setModels } = currentSlice.actions;
export default currentSlice.reducer;

View file

@ -2,7 +2,11 @@ import { createSlice } from '@reduxjs/toolkit';
const initialState = {
isSubmitting: false,
model: 'bingai'
disabled: false,
model: 'chatgpt',
promptPrefix: '',
chatGptLabel: '',
customModel: null
};
const currentSlice = createSlice({
@ -12,12 +16,23 @@ const currentSlice = createSlice({
setSubmitState: (state, action) => {
state.isSubmitting = action.payload;
},
setDisabled: (state, action) => {
state.disabled = action.payload;
},
setModel: (state, action) => {
state.model = action.payload;
},
setCustomGpt: (state, action) => {
state.promptPrefix = action.payload.promptPrefix;
state.chatGptLabel = action.payload.chatGptLabel;
},
setCustomModel: (state, action) => {
state.customModel = action.payload;
}
}
});
export const { setSubmitState, setModel } = currentSlice.actions;
export const { setSubmitState, setDisabled, setModel, setCustomGpt, setCustomModel } =
currentSlice.actions;
export default currentSlice.reducer;

View file

@ -809,7 +809,7 @@ button {
}
::-webkit-scrollbar {
height: 1rem;
height: 0.85em;
width: 0.5rem;
}
@ -1237,3 +1237,616 @@ html {
vertical-align:baseline
} */
.form-input,
.form-multiselect,
.form-select,
.form-textarea {
--tw-shadow:0 0 transparent;
-webkit-appearance:none;
appearance:none;
background-color:#fff;
border-color:#8e8ea0;
border-radius:0;
border-width:1px;
font-size:1rem;
line-height:1.5rem;
padding:.5rem .75rem
}
.form-input:focus,
.form-multiselect:focus,
.form-select:focus,
.form-textarea:focus {
--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);
--tw-ring-offset-width:0px;
--tw-ring-offset-color:#fff;
--tw-ring-color:#2563eb;
--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
border-color:#2563eb;
box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);
outline:2px solid transparent;
outline-offset:2px
}
.form-input::-webkit-input-placeholder,
.form-textarea::-webkit-input-placeholder {
color:#8e8ea0;
opacity:1
}
.form-input::placeholder,
.form-textarea::placeholder {
color:#8e8ea0;
opacity:1
}
.form-input::-webkit-datetime-edit-fields-wrapper {
padding:0
}
.form-input::-webkit-date-and-time-value {
min-height:1.5em
}
.form-input::-webkit-datetime-edit,
.form-input::-webkit-datetime-edit-day-field,
.form-input::-webkit-datetime-edit-hour-field,
.form-input::-webkit-datetime-edit-meridiem-field,
.form-input::-webkit-datetime-edit-millisecond-field,
.form-input::-webkit-datetime-edit-minute-field,
.form-input::-webkit-datetime-edit-month-field,
.form-input::-webkit-datetime-edit-second-field,
.form-input::-webkit-datetime-edit-year-field {
padding-bottom:0;
padding-top:0
}
.grow {
flex-grow:1
}
.-translate-y-1\/2 {
--tw-translate-y:-50%
}
.-translate-y-1\/2,
.translate-y-\[calc\(100\%-71px\)\] {
-webkit-transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.translate-y-\[calc\(100\%-71px\)\] {
--tw-translate-y:calc(100% - 71px)
}
.-translate-x-full {
--tw-translate-x:-100%
}
.-translate-x-full,
.translate-y-4 {
-webkit-transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.translate-y-4 {
--tw-translate-y:1rem
}
.translate-y-0 {
--tw-translate-y:0px
}
.translate-x-0,
.translate-y-0 {
-webkit-transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.translate-x-0 {
--tw-translate-x:0px
}
.translate-y-1 {
--tw-translate-y:0.25rem
}
.-translate-x-1\/2,
.translate-y-1 {
-webkit-transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.-translate-x-1\/2 {
--tw-translate-x:-50%
}
.translate-x-1 {
--tw-translate-x:0.25rem
}
.-translate-y-full,
.translate-x-1 {
-webkit-transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.-translate-y-full {
--tw-translate-y:-100%
}
.translate-x-full {
--tw-translate-x:100%
}
.translate-x-5,
.translate-x-full {
-webkit-transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.translate-x-5 {
--tw-translate-x:1.25rem
}
.translate-x-4 {
--tw-translate-x:1rem
}
.translate-x-4,
.translate-y-1\/4 {
-webkit-transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.translate-y-1\/4 {
--tw-translate-y:25%
}
.-translate-x-3\/4 {
--tw-translate-x:-75%
}
.-translate-x-3\/4,
.translate-x-3\/4 {
-webkit-transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.translate-x-3\/4 {
--tw-translate-x:75%
}
.rotate-180 {
--tw-rotate:180deg
}
.-rotate-180,
.rotate-180 {
-webkit-transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.-rotate-180 {
--tw-rotate:-180deg
}
.transform {
-webkit-transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
}
.border-white\/10 {
border-color:hsla(0,0%,100%,.1)
}
.border-white {
--tw-border-opacity:1;
border-color:rgba(255,255,255,var(--tw-border-opacity))
}
.border-gray-300 {
--tw-border-opacity:1;
border-color:rgba(197,197,210,var(--tw-border-opacity))
}
.border-black\/10 {
border-color:rgba(0,0,0,.1)
}
.border-white\/20 {
border-color:hsla(0,0%,100%,.2)
}
.border-gray-100 {
--tw-border-opacity:1;
border-color:rgba(236,236,241,var(--tw-border-opacity))
}
.border-gray-200 {
--tw-border-opacity:1;
border-color:rgba(217,217,227,var(--tw-border-opacity))
}
.border-black\/20 {
border-color:rgba(0,0,0,.2)
}
.border-gray-500 {
--tw-border-opacity:1;
border-color:rgba(142,142,160,var(--tw-border-opacity))
}
.bg-gray-200 {
--tw-bg-opacity:1;
background-color:rgba(217,217,227,var(--tw-bg-opacity))
}
.bg-\[\#5436DA\] {
--tw-bg-opacity:1;
background-color:rgba(84,54,218,var(--tw-bg-opacity))
}
.bg-white {
--tw-bg-opacity:1;
background-color:rgba(255,255,255,var(--tw-bg-opacity))
}
.bg-black {
--tw-bg-opacity:1;
background-color:rgba(0,0,0,var(--tw-bg-opacity))
}
.bg-gray-800 {
--tw-bg-opacity:1;
background-color:rgba(52,53,65,var(--tw-bg-opacity))
}
.bg-gray-50 {
--tw-bg-opacity:1;
background-color:rgba(247,247,248,var(--tw-bg-opacity))
}
.bg-gray-500\/90 {
background-color:hsla(240,9%,59%,.9)
}
.bg-gray-900 {
--tw-bg-opacity:1;
background-color:rgba(32,33,35,var(--tw-bg-opacity))
}
.bg-gray-600 {
--tw-bg-opacity:1;
background-color:rgba(86,88,105,var(--tw-bg-opacity))
}
.bg-gray-500 {
--tw-bg-opacity:1;
background-color:rgba(142,142,160,var(--tw-bg-opacity))
}
.\!bg-white {
--tw-bg-opacity:1!important;
background-color:rgba(255,255,255,var(--tw-bg-opacity))!important
}
.bg-red-200 {
--tw-bg-opacity:1;
background-color:rgba(254,202,202,var(--tw-bg-opacity))
}
.\!bg-indigo-600 {
--tw-bg-opacity:1!important;
background-color:rgba(79,70,229,var(--tw-bg-opacity))!important
}
.\!bg-gray-200 {
--tw-bg-opacity:1!important;
background-color:rgba(217,217,227,var(--tw-bg-opacity))!important
}
.bg-red-500\/10 {
background-color:rgba(239,68,68,.1)
}
.bg-gray-300 {
--tw-bg-opacity:1;
background-color:rgba(197,197,210,var(--tw-bg-opacity))
}
.bg-gray-400 {
--tw-bg-opacity:1;
background-color:rgba(172,172,190,var(--tw-bg-opacity))
}
.bg-opacity-75 {
--tw-bg-opacity:0.75
}
.bg-gradient-to-l {
background-image:linear-gradient(to left,var(--tw-gradient-stops))
}
.from-gray-800 {
--tw-gradient-from:#343541;
--tw-gradient-to:rgba(52,53,65,0);
--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)
}
.from-gray-900 {
--tw-gradient-from:#202123;
--tw-gradient-to:rgba(32,33,35,0);
--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)
}
.text-red-500 {
--tw-text-opacity:1;
color:rgba(239,68,68,var(--tw-text-opacity))
}
.text-gray-400 {
--tw-text-opacity:1;
color:rgba(172,172,190,var(--tw-text-opacity))
}
.text-gray-500 {
--tw-text-opacity:1;
color:rgba(142,142,160,var(--tw-text-opacity))
}
.text-white {
--tw-text-opacity:1;
color:rgba(255,255,255,var(--tw-text-opacity))
}
.text-yellow-900 {
--tw-text-opacity:1;
color:rgba(146,114,1,var(--tw-text-opacity))
}
.text-green-700 {
--tw-text-opacity:1;
color:rgba(26,127,100,var(--tw-text-opacity))
}
.text-gray-800 {
--tw-text-opacity:1;
color:rgba(52,53,65,var(--tw-text-opacity))
}
.text-gray-700 {
--tw-text-opacity:1;
color:rgba(64,65,79,var(--tw-text-opacity))
}
.text-gray-200 {
--tw-text-opacity:1;
color:rgba(217,217,227,var(--tw-text-opacity))
}
.text-gray-100 {
--tw-text-opacity:1;
color:rgba(236,236,241,var(--tw-text-opacity))
}
.text-gray-300 {
--tw-text-opacity:1;
color:rgba(197,197,210,var(--tw-text-opacity))
}
.text-gray-900 {
--tw-text-opacity:1;
color:rgba(32,33,35,var(--tw-text-opacity))
}
.text-gray-600 {
--tw-text-opacity:1;
color:rgba(86,88,105,var(--tw-text-opacity))
}
.text-red-600 {
--tw-text-opacity:1;
color:rgba(220,38,38,var(--tw-text-opacity))
}
.text-yellow-700 {
--tw-text-opacity:1;
color:rgba(161,98,7,var(--tw-text-opacity))
}
.text-indigo-500 {
--tw-text-opacity:1;
color:rgba(99,102,241,var(--tw-text-opacity))
}
.text-red-800 {
--tw-text-opacity:1;
color:rgba(153,27,27,var(--tw-text-opacity))
}
.text-black\/50 {
color:rgba(0,0,0,.5)
}
.text-indigo-600 {
--tw-text-opacity:1;
color:rgba(79,70,229,var(--tw-text-opacity))
}
.text-yellow-400 {
--tw-text-opacity:1;
color:rgba(255,198,87,var(--tw-text-opacity))
}
.text-red-300 {
--tw-text-opacity:1;
color:rgba(252,165,165,var(--tw-text-opacity))
}
.text-green-600 {
--tw-text-opacity:1;
color:rgba(16,163,127,var(--tw-text-opacity))
}
.text-orange-500 {
--tw-text-opacity:1;
color:rgba(224,108,43,var(--tw-text-opacity))
}
.text-blue-500 {
--tw-text-opacity:1;
color:rgba(59,130,246,var(--tw-text-opacity))
}
.underline {
text-decoration-line:underline
}
.\!no-underline {
text-decoration-line:none!important
}
.placeholder-gray-500::-webkit-input-placeholder {
--tw-placeholder-opacity:1;
color:rgba(142,142,160,var(--tw-placeholder-opacity))
}
.placeholder-gray-500::placeholder {
--tw-placeholder-opacity:1;
color:rgba(142,142,160,var(--tw-placeholder-opacity))
}
.transition-transform {
transition-duration:.15s;
transition-property:-webkit-transform;
transition-property:transform;
transition-property:transform,-webkit-transform;
transition-timing-function:cubic-bezier(.4,0,.2,1)
}
.transition {
transition-duration:.15s;
transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,-webkit-transform,-webkit-filter,-webkit-backdrop-filter;
transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;
transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-transform,-webkit-filter,-webkit-backdrop-filter;
transition-timing-function:cubic-bezier(.4,0,.2,1)
}
.transition-opacity {
transition-duration:.15s;
transition-property:opacity;
transition-timing-function:cubic-bezier(.4,0,.2,1)
}
.transition-all {
transition-duration:.15s;
transition-property:all;
transition-timing-function:cubic-bezier(.4,0,.2,1)
}
.transition-colors {
transition-duration:.15s;
transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;
transition-timing-function:cubic-bezier(.4,0,.2,1)
}
.transition-width {
transition-duration:.15s;
transition-property:width;
transition-timing-function:cubic-bezier(.4,0,.2,1)
}
.duration-200 {
transition-duration:.2s
}
.duration-500 {
transition-duration:.5s
}
.duration-75 {
transition-duration:75ms
}
.duration-300 {
transition-duration:.3s
}
.duration-150 {
transition-duration:.15s
}
.duration-100 {
transition-duration:.1s
}
.ease-out {
transition-timing-function:cubic-bezier(0,0,.2,1)
}
.ease-in {
transition-timing-function:cubic-bezier(.4,0,1,1)
}
.ease-linear {
transition-timing-function:linear
}
.ease-in-out {
transition-timing-function:cubic-bezier(.4,0,.2,1)
}
.line-clamp-2 {
-webkit-line-clamp:2
}
.line-clamp-2,
.line-clamp-3 {
-webkit-box-orient:vertical;
display:-webkit-box;
overflow:hidden
}
.line-clamp-3 {
-webkit-line-clamp:3
}
body,
html {
height:100%
}
.dark body,
.dark html {
--tw-bg-opacity:1;
background-color:rgba(52,53,65,var(--tw-bg-opacity))
}
#__next,
#root {
height:100%
}
.markdown ol {
counter-reset:item
}
.markdown ul li {
display:block;
margin:0;
position:relative
}
.markdown ul li:before {
content:"•";
font-size:.875rem;
line-height:1.25rem;
margin-left:-1rem;
position:absolute
}
.markdown {
max-width:none
}
.markdown h1,
.markdown h2 {
font-weight:600
}
.markdown h2 {
margin-bottom:1rem;
margin-top:2rem
}
.markdown h3 {
font-weight:600
}
.markdown h3,
.markdown h4 {
margin-bottom:.5rem;
margin-top:1rem
}
.markdown h4 {
font-weight:400
}
.markdown h5 {
font-weight:600
}
.markdown blockquote {
--tw-border-opacity:1;
border-color:rgba(142,142,160,var(--tw-border-opacity));
border-left-width:2px;
line-height:1rem;
padding-left:1rem
}
.markdown ol,
.markdown ul {
display:flex;
flex-direction:column;
padding-left:1rem
}
.markdown ol li,
.markdown ol li>p,
.markdown ol ol,
.markdown ol ul,
.markdown ul li,
.markdown ul li>p,
.markdown ul ol,
.markdown ul ul {
margin:0
}
.markdown table {
--tw-border-spacing-x:0px;
--tw-border-spacing-y:0px;
border-collapse:separate;
border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y);
width:100%
}
.markdown th {
background-color:rgba(236,236,241,.2);
border-bottom-width:1px;
border-left-width:1px;
border-top-width:1px;
padding:.25rem .75rem
}
.markdown th:first-child {
border-top-left-radius:.375rem
}
.markdown th:last-child {
border-right-width:1px;
border-top-right-radius:.375rem
}
.markdown td {
border-bottom-width:1px;
border-left-width:1px;
padding:.25rem .75rem
}
.markdown td:last-child {
border-right-width:1px
}
.markdown tbody tr:last-child td:first-child {
border-bottom-left-radius:.375rem
}
.markdown tbody tr:last-child td:last-child {
border-bottom-right-radius:.375rem
}
.markdown a {
text-decoration-line:underline;
text-underline-offset:2px
}
.conversation-item-time:before {
content:attr(data-time)
}
.tooltip-label:before {
content:attr(data-content)
}
button.scroll-convo {
display:none
}
@-webkit-keyframes blink {
to {
visibility:hidden
}
}
@keyframes blink {
to {
visibility:hidden
}
}
.animate-flash {
-webkit-animation:flash 2s steps(60,start);
animation:flash 2s steps(60,start)
}
@-webkit-keyframes flash {
0% {
background-color:hsla(0,0%,100%,.4)
}
}
@keyframes flash {
0% {
background-color:hsla(0,0%,100%,.4)
}
}

View file

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

View file

@ -8,9 +8,11 @@ export default function handleSubmit({
convo,
messageHandler,
convoHandler,
errorHandler
errorHandler,
chatGptLabel,
promptPrefix
}) {
let payload = { model, text };
let payload = { model, text, chatGptLabel, promptPrefix };
if (convo.conversationId && convo.parentMessageId) {
payload = {
...payload,

View file

@ -1,6 +1,6 @@
const path = require('path');
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.
After that apply all the rules in module.rules and produce the output and place it in main.js in the public folder.*/