mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 20:00:15 +01:00
Merge pull request #2 from danny-avila/customChatGPT
feature: customize chat gpt
This commit is contained in:
commit
c8d8484d86
38 changed files with 1815 additions and 138 deletions
27
README.md
27
README.md
|
|
@ -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 ##
|
||||
|
||||

|
||||
- 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
37
app/chatgpt-custom.js
Normal 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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
73
models/CustomGpt.js
Normal 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' };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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
105
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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, () => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
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 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 };
|
||||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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(''));
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
|
|
|
|||
16
src/components/Models/MenuItems.jsx
Normal file
16
src/components/Models/MenuItems.jsx
Normal 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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
134
src/components/Models/ModelDialog.jsx
Normal file
134
src/components/Models/ModelDialog.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
29
src/components/Models/ModelItem.jsx
Normal file
29
src/components/Models/ModelItem.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
146
src/components/Models/ModelMenu.jsx
Normal file
146
src/components/Models/ModelMenu.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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',
|
||||
() => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
156
src/components/ui/AlertDialog.tsx
Normal file
156
src/components/ui/AlertDialog.tsx
Normal 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,
|
||||
}
|
||||
144
src/components/ui/Dialog.tsx
Normal file
144
src/components/ui/Dialog.tsx
Normal 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,
|
||||
}
|
||||
24
src/components/ui/Input.tsx
Normal file
24
src/components/ui/Input.tsx
Normal 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 }
|
||||
21
src/components/ui/Label.tsx
Normal file
21
src/components/ui/Label.tsx
Normal 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 }
|
||||
26
src/components/ui/Textarea.tsx
Normal file
26
src/components/ui/Textarea.tsx
Normal 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 }
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
53
src/store/modelSlice.js
Normal 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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
615
src/style.css
615
src/style.css
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue