mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
refactor model menu items into component
This commit is contained in:
parent
0bec306e59
commit
62bb6ea8f8
6 changed files with 123 additions and 20 deletions
|
|
@ -16,7 +16,7 @@ const App = () => {
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
<Nav />
|
<Nav />
|
||||||
<div className="flex h-full w-full flex-1 flex-col bg-gray-50 md:pl-[260px]">
|
<div className="flex h-full w-full flex-1 flex-col bg-gray-50 md:pl-[260px]">
|
||||||
<div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden dark:bg-gray-800">
|
<div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden dark:bg-gray-800/90">
|
||||||
<MobileNav />
|
<MobileNav />
|
||||||
{messages.length === 0 ? (
|
{messages.length === 0 ? (
|
||||||
<Landing title={title} />
|
<Landing title={title} />
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,12 @@ export default function Message({
|
||||||
let icon = `${sender}:`;
|
let icon = `${sender}:`;
|
||||||
let backgroundColor = bgColors[sender];
|
let backgroundColor = bgColors[sender];
|
||||||
|
|
||||||
if (notUser && backgroundColor) {
|
if (notUser) {
|
||||||
props.className =
|
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 = (
|
icon = (
|
||||||
<div
|
<div
|
||||||
style={{ backgroundColor }}
|
style={{ backgroundColor }}
|
||||||
|
|
|
||||||
29
src/components/main/ModelItem.jsx
Normal file
29
src/components/main/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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { setModel, setDisabled, setCustomGpt } from '~/store/submitSlice';
|
import { setModel, setDisabled, setCustomGpt } from '~/store/submitSlice';
|
||||||
|
// import { setModels } from '~/store/modelSlice';
|
||||||
|
import ModelItem from './ModelItem';
|
||||||
import GPTIcon from '../svg/GPTIcon';
|
import GPTIcon from '../svg/GPTIcon';
|
||||||
import BingIcon from '../svg/BingIcon';
|
import BingIcon from '../svg/BingIcon';
|
||||||
import { Button } from '../ui/Button.tsx';
|
import { Button } from '../ui/Button.tsx';
|
||||||
|
|
@ -13,14 +15,14 @@ import {
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuRadioGroup,
|
DropdownMenuRadioGroup,
|
||||||
DropdownMenuRadioItem,
|
// DropdownMenuRadioItem,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger
|
DropdownMenuTrigger
|
||||||
} from '../ui/DropdownMenu.tsx';
|
} from '../ui/DropdownMenu.tsx';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTrigger,
|
// DialogTrigger,
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
|
|
@ -32,9 +34,11 @@ import {
|
||||||
export default function ModelMenu() {
|
export default function ModelMenu() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { model } = useSelector((state) => state.submit);
|
const { model } = useSelector((state) => state.submit);
|
||||||
|
const { models } = useSelector((state) => state.models);
|
||||||
const [chatGptLabel, setChatGptLabel] = useState('');
|
const [chatGptLabel, setChatGptLabel] = useState('');
|
||||||
const [promptPrefix, setPromptPrefix] = useState('');
|
const [promptPrefix, setPromptPrefix] = useState('');
|
||||||
const [required, setRequired] = useState(false);
|
const [required, setRequired] = useState(false);
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const lastSelectedModel = JSON.parse(localStorage.getItem('model'));
|
const lastSelectedModel = JSON.parse(localStorage.getItem('model'));
|
||||||
|
|
@ -61,6 +65,7 @@ export default function ModelMenu() {
|
||||||
if (chatGptLabel.length === 0) {
|
if (chatGptLabel.length === 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setRequired(true);
|
setRequired(true);
|
||||||
|
inputRef.current.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
|
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
|
||||||
|
|
@ -72,10 +77,10 @@ export default function ModelMenu() {
|
||||||
'text-gray-500',
|
'text-gray-500',
|
||||||
'hover:bg-gray-100',
|
'hover:bg-gray-100',
|
||||||
'disabled:hover:bg-transparent',
|
'disabled:hover:bg-transparent',
|
||||||
'dark:hover:bg-opacity-40',
|
'dark:hover:bg-opacity-20',
|
||||||
'dark:hover:bg-gray-900',
|
'dark:hover:bg-gray-900',
|
||||||
'dark:hover:text-gray-400',
|
'dark:hover:text-gray-400',
|
||||||
'dark:data-[state=open]:bg-transparent',
|
// 'dark:data-[state=open]:bg-transparent',
|
||||||
'dark:disabled:hover:bg-transparent'
|
'dark:disabled:hover:bg-transparent'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -87,7 +92,7 @@ export default function ModelMenu() {
|
||||||
'dark:hover:bg-opacity-50',
|
'dark:hover:bg-opacity-50',
|
||||||
'dark:hover:bg-green-900',
|
'dark:hover:bg-green-900',
|
||||||
'dark:hover:text-gray-100',
|
'dark:hover:text-gray-100',
|
||||||
// 'dark:data-[state=open]:bg-green-100',
|
// 'dark:data-[state=open]:bg-gray-700',
|
||||||
'dark:disabled:hover:bg-transparent'
|
'dark:disabled:hover:bg-transparent'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -105,7 +110,7 @@ export default function ModelMenu() {
|
||||||
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
|
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
|
||||||
className={`absolute bottom-0.5 rounded-md border-0 p-1 pl-2 outline-none ${colorProps.join(
|
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`}
|
)} 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}
|
{icon}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -117,26 +122,53 @@ export default function ModelMenu() {
|
||||||
value={model}
|
value={model}
|
||||||
onValueChange={onChange}
|
onValueChange={onChange}
|
||||||
>
|
>
|
||||||
<DropdownMenuRadioItem value="chatgpt" className="dark:font-semibold">
|
{models.map((modelItem, i) => (
|
||||||
|
<ModelItem
|
||||||
|
key={i}
|
||||||
|
modelName={modelItem.name}
|
||||||
|
value={modelItem.value}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{/* <DropdownMenuRadioItem
|
||||||
|
value="chatgpt"
|
||||||
|
className="dark:font-semibold dark:hover:bg-gray-800"
|
||||||
|
>
|
||||||
ChatGPT <sup>$</sup>
|
ChatGPT <sup>$</sup>
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<DropdownMenuRadioItem value="chatgptCustom" className="dark:font-semibold">
|
<DropdownMenuRadioItem
|
||||||
|
value="chatgptCustom"
|
||||||
|
className=" dark:hover:bg-gray-800"
|
||||||
|
>
|
||||||
CustomGPT <sup>$</sup>
|
CustomGPT <sup>$</sup>
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DropdownMenuRadioItem value="bingai" className="dark:font-semibold">BingAI</DropdownMenuRadioItem>
|
<DropdownMenuRadioItem
|
||||||
<DropdownMenuRadioItem value="chatgptBrowser" className="dark:font-semibold">ChatGPT</DropdownMenuRadioItem>
|
value="bingai"
|
||||||
|
className=" dark:hover:bg-gray-800"
|
||||||
|
>
|
||||||
|
BingAI
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem
|
||||||
|
value="chatgptBrowser"
|
||||||
|
className=" dark:hover:bg-gray-800"
|
||||||
|
>
|
||||||
|
ChatGPT
|
||||||
|
</DropdownMenuRadioItem> */}
|
||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<DialogContent>
|
<DialogContent className="dark:bg-gray-800">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Customize ChatGPT</DialogTitle>
|
<DialogTitle>Customize ChatGPT</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Note: important instructions are often better placed in your message rather than
|
Note: important instructions are often better placed in your message rather than
|
||||||
the prefix.{' '}
|
the prefix.{' '}
|
||||||
<a href="https://platform.openai.com/docs/guides/chat/instructing-chat-models">
|
<a
|
||||||
|
href="https://platform.openai.com/docs/guides/chat/instructing-chat-models"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
<u>More info here</u>
|
<u>More info here</u>
|
||||||
</a>
|
</a>
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
|
@ -152,10 +184,11 @@ export default function ModelMenu() {
|
||||||
<Input
|
<Input
|
||||||
id="chatGptLabel"
|
id="chatGptLabel"
|
||||||
value={chatGptLabel}
|
value={chatGptLabel}
|
||||||
|
ref={inputRef}
|
||||||
onChange={(e) => setChatGptLabel(e.target.value)}
|
onChange={(e) => setChatGptLabel(e.target.value)}
|
||||||
placeholder="Set a custom name for ChatGPT"
|
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
|
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
|
||||||
focus:ring-opacity-20 focus:invalid:border-red-400 focus:invalid:ring-red-400 focus:invalid:ring-opacity-20 dark:invalid:border-red-600 dark:invalid:text-red-300 dark:focus:invalid:ring-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}
|
{...requiredProp}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -171,14 +204,14 @@ export default function ModelMenu() {
|
||||||
value={promptPrefix}
|
value={promptPrefix}
|
||||||
onChange={(e) => setPromptPrefix(e.target.value)}
|
onChange={(e) => setPromptPrefix(e.target.value)}
|
||||||
placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'"
|
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"
|
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>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<DialogClose>Cancel</DialogClose>
|
<DialogClose>Cancel</DialogClose>
|
||||||
<Button
|
<Button
|
||||||
style={{backgroundColor: 'rgb(16, 163, 127)'}}
|
style={{ backgroundColor: 'rgb(16, 163, 127)' }}
|
||||||
className="inline-flex h-10 items-center justify-center rounded-md border-none py-2 px-4 text-sm font-semibold text-white transition-colors"
|
className="inline-flex h-10 items-center justify-center rounded-md border-none py-2 px-4 text-sm font-semibold text-white transition-colors"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import convoReducer from './convoSlice.js';
|
import convoReducer from './convoSlice.js';
|
||||||
import messageReducer from './messageSlice.js'
|
import messageReducer from './messageSlice.js'
|
||||||
|
import modelReducer from './modelSlice.js'
|
||||||
import submitReducer from './submitSlice.js'
|
import submitReducer from './submitSlice.js'
|
||||||
import textReducer from './textSlice.js'
|
import textReducer from './textSlice.js'
|
||||||
|
|
||||||
|
|
@ -9,6 +10,7 @@ export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
convo: convoReducer,
|
convo: convoReducer,
|
||||||
messages: messageReducer,
|
messages: messageReducer,
|
||||||
|
models: modelReducer,
|
||||||
text: textReducer,
|
text: textReducer,
|
||||||
submit: submitReducer,
|
submit: submitReducer,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
36
src/store/modelSlice.js
Normal file
36
src/store/modelSlice.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
name: 'ChatGPT',
|
||||||
|
value: 'chatgpt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'CustomGPT',
|
||||||
|
value: 'chatgptCustom'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'BingAI',
|
||||||
|
value: 'bingai'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ChatGPT',
|
||||||
|
value: 'chatgptBrowser'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentSlice = createSlice({
|
||||||
|
name: 'models',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setModels: (state, action) => {
|
||||||
|
state.models = [...state.models, ...action.payload];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setModels } = currentSlice.actions;
|
||||||
|
|
||||||
|
export default currentSlice.reducer;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue