mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
complete customChatGpt model selection
This commit is contained in:
parent
0f9cfd0395
commit
214228542a
18 changed files with 742 additions and 63 deletions
15
README.md
15
README.md
|
|
@ -31,7 +31,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,7 +43,7 @@ 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
|
||||
- [ ] Customize prompt prefix/label (custom ChatGPT using official API)
|
||||
- [ ] AI model change handling (whether to pseudo-persist convos or start new convos within existing convo)
|
||||
- [ ] Server convo pagination (limit fetch and load more with 'show more' button)
|
||||
- [ ] Bing AI Styling (for suggested responses, convo end, etc.)
|
||||
|
|
@ -58,25 +58,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,13 @@ 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;
|
||||
return completion.data.choices[0].message.content.replace(/"/g, '');
|
||||
};
|
||||
|
||||
module.exports = titleConvo;
|
||||
|
|
|
|||
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",
|
||||
|
|
|
|||
|
|
@ -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,9 +111,13 @@ 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);
|
||||
await saveMessage(gptResponse);
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export default function Message({
|
|||
let icon = `${sender}:`;
|
||||
let backgroundColor = bgColors[sender];
|
||||
|
||||
if (notUser) {
|
||||
if (notUser && backgroundColor) {
|
||||
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]';
|
||||
icon = (
|
||||
|
|
@ -77,7 +77,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 ? (
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { setModel } from '~/store/submitSlice';
|
||||
import { setModel, setDisabled, setCustomGpt } from '~/store/submitSlice';
|
||||
import GPTIcon from '../svg/GPTIcon';
|
||||
import BingIcon from '../svg/BingIcon';
|
||||
|
||||
import { Button } from '../ui/Button.tsx';
|
||||
import { Input } from '../ui/Input.tsx';
|
||||
import { Label } from '../ui/Label.tsx';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
|
@ -15,25 +18,56 @@ import {
|
|||
DropdownMenuTrigger
|
||||
} from '../ui/DropdownMenu.tsx';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '../ui/Dialog.tsx';
|
||||
|
||||
export default function ModelMenu() {
|
||||
const dispatch = useDispatch();
|
||||
const { model } = useSelector((state) => state.submit);
|
||||
const onChange = (value) => {
|
||||
dispatch(setModel(value));
|
||||
};
|
||||
const [chatGptLabel, setChatGptLabel] = useState('');
|
||||
const [promptPrefix, setPromptPrefix] = useState('');
|
||||
const [required, setRequired] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const lastSelectedModel = JSON.parse(localStorage.getItem('model'));
|
||||
if (lastSelectedModel) {
|
||||
if (lastSelectedModel && lastSelectedModel !== 'chatgptCustom') {
|
||||
dispatch(setModel(lastSelectedModel));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('model', JSON.stringify(model));
|
||||
}, [model]);
|
||||
|
||||
const onChange = (value) => {
|
||||
if (value === 'chatgptCustom') {
|
||||
// dispatch(setDisabled(true));
|
||||
} else {
|
||||
dispatch(setModel(value));
|
||||
dispatch(setDisabled(false));
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = (e) => {
|
||||
if (chatGptLabel.length === 0) {
|
||||
e.preventDefault();
|
||||
setRequired(true);
|
||||
return;
|
||||
}
|
||||
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
|
||||
dispatch(setModel('chatgptCustom'));
|
||||
dispatch(setDisabled(false));
|
||||
};
|
||||
|
||||
const defaultColorProps = [
|
||||
'text-gray-500',
|
||||
'hover:bg-gray-100',
|
||||
|
|
@ -54,32 +88,100 @@ export default function ModelMenu() {
|
|||
'dark:disabled:hover:bg-transparent'
|
||||
];
|
||||
|
||||
const requiredProp = required ? { required: true } : {};
|
||||
|
||||
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
|
||||
const icon = model === 'bingai' ? <BingIcon button={true} /> : <GPTIcon button={true} /> ;
|
||||
const icon = model === 'bingai' ? <BingIcon button={true} /> : <GPTIcon button={true} />;
|
||||
|
||||
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>
|
||||
<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 md:bottom-1 md:left-2 md:pl-1 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 <sup>$</sup>
|
||||
</DropdownMenuRadioItem>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuRadioItem value="chatgptCustom">
|
||||
CustomGPT <sup>$</sup>
|
||||
</DropdownMenuRadioItem>
|
||||
</DialogTrigger>
|
||||
<DropdownMenuRadioItem value="bingai">BingAI</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="chatgptBrowser">ChatGPT</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DialogContent>
|
||||
<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">
|
||||
<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}
|
||||
onChange={(e) => setChatGptLabel(e.target.value)}
|
||||
placeholder="Set a custom name for ChatGPT"
|
||||
className="col-span-3 invalid:border-red-400 invalid:text-red-600 invalid:placeholder-red-600 invalid:placeholder-opacity-70
|
||||
focus:ring-opacity-20 focus:invalid:border-red-400 focus:invalid:ring-red-400 focus:invalid:ring-opacity-20"
|
||||
{...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 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:focus:ring-slate-400 dark:focus:ring-offset-slate-900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose>Cancel</DialogClose>
|
||||
<DialogClose
|
||||
onClick={submitHandler}
|
||||
className="inline-flex h-10 items-center justify-center rounded-md border-none bg-slate-900 py-2 px-4 text-sm font-semibold text-white transition-colors hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900"
|
||||
>
|
||||
Submit
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ 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 { isSubmitting, disabled, model, chatGptLabel, promptPrefix } = useSelector((state) => state.submit);
|
||||
const { text } = useSelector((state) => state.text);
|
||||
const { error } = convo;
|
||||
|
||||
|
|
@ -30,15 +30,20 @@ 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 (
|
||||
|
|
@ -116,7 +121,9 @@ export default function TextChat({ messages }) {
|
|||
convo,
|
||||
messageHandler,
|
||||
convoHandler,
|
||||
errorHandler
|
||||
errorHandler,
|
||||
chatGptLabel,
|
||||
promptPrefix
|
||||
};
|
||||
console.log('User Input:', message);
|
||||
handleSubmit(submission);
|
||||
|
|
@ -167,7 +174,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 +190,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 }
|
||||
|
|
@ -2,7 +2,10 @@ import { createSlice } from '@reduxjs/toolkit';
|
|||
|
||||
const initialState = {
|
||||
isSubmitting: false,
|
||||
model: 'bingai'
|
||||
disabled: false,
|
||||
model: 'chatgpt',
|
||||
promptPrefix: '',
|
||||
chatGptLabel: '',
|
||||
};
|
||||
|
||||
const currentSlice = createSlice({
|
||||
|
|
@ -12,12 +15,20 @@ 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) => {
|
||||
console.log('setCustomGpt', action.payload);
|
||||
state.promptPrefix = action.payload.promptPrefix;
|
||||
state.chatGptLabel = action.payload.chatGptLabel;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
export const { setSubmitState, setModel } = currentSlice.actions;
|
||||
export const { setSubmitState, setDisabled, setModel, setCustomGpt } = currentSlice.actions;
|
||||
|
||||
export default currentSlice.reducer;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue