mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00
Fixes all Nav Menu related errors and bugs (#331)
* chore(client): update lucide-react package to version 0.220.0 style(client): change color of MessageHeader component text to gray-500 style(client): change color of nav-close-button to gray-400 and nav-open-button to gray-500 feat(client): add Panel component to replace svg icons in Nav component * fix: forwardRef errors in Nav Menu * refactor(SearchBar.jsx): change clearSearch prop destructuring to props destructuring refactor(SearchBar.jsx): add ref prop to SearchBar component refactor(getIcon.jsx): remove unused imports refactor(getIcon.jsx): add nullish coalescing operator to user.name and user.avatar properties * fix (NavLinks): modals no longer close on nav menu close * style(ExportModel.jsx): remove unnecessary z-index property from a div element * style(ExportModel.jsx): remove trailing whitespace in input element * refactor(Message.jsx): remove unused cancelled variable fix(Message.jsx): fix error message length exceeding 512 characters refactor(MenuItem.jsx): remove unused MenuItem component
This commit is contained in:
parent
ee2b3e4fb2
commit
ec561fcd7f
16 changed files with 230 additions and 165 deletions
|
@ -53,7 +53,7 @@
|
|||
"filenamify": "^6.0.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.113.0",
|
||||
"lucide-react": "^0.220.0",
|
||||
"pino": "^8.12.1",
|
||||
"rc-input-number": "^7.4.2",
|
||||
"react": "^18.2.0",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import SubRow from './Content/SubRow';
|
||||
|
@ -31,7 +31,7 @@ export default function Message({
|
|||
siblingCount,
|
||||
setSiblingIdx
|
||||
}) {
|
||||
const { text, searchResult, isCreatedByUser, error, submitting, unfinished, cancelled } = message;
|
||||
const { text, searchResult, isCreatedByUser, error, submitting, unfinished } = message;
|
||||
const isSubmitting = useRecoilValue(store.isSubmitting);
|
||||
const setLatestMessage = useSetRecoilState(store.latestMessage);
|
||||
const [abortScroll, setAbort] = useState(false);
|
||||
|
@ -74,6 +74,7 @@ export default function Message({
|
|||
};
|
||||
|
||||
const getError = (text) => {
|
||||
const errorMessage = text.length > 512 ? text.slice(0, 512) + '...' : text;
|
||||
const match = text.match(/\{[^{}]*\}/);
|
||||
var json = match ? match[0] : '';
|
||||
if (isJson(json)) {
|
||||
|
@ -83,10 +84,10 @@ export default function Message({
|
|||
} else if (json.type === 'insufficient_quota') {
|
||||
return "We're sorry, but the default API key has reached its limit. To continue using this service, please set up your own API key. You can do this by clicking on the model logo in the top-left corner of the textbox.";
|
||||
} else {
|
||||
return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${text}`;
|
||||
return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${errorMessage}`;
|
||||
}
|
||||
} else {
|
||||
return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${text}`;
|
||||
return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${errorMessage}`;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ const MessageHeader = ({ isSearchView = false }) => {
|
|||
<>
|
||||
<div
|
||||
className={cn(
|
||||
'dark:text-gray-450 w-full gap-1 border-b border-black/10 bg-gray-50 text-sm text-gray-600 transition-all hover:bg-gray-100 hover:bg-opacity-30 dark:border-gray-900/50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:hover:bg-opacity-100 dark:text-gray-500',
|
||||
'dark:text-gray-450 w-full gap-1 border-b border-black/10 bg-gray-50 text-sm text-gray-500 transition-all hover:bg-gray-100 hover:bg-opacity-30 dark:border-gray-900/50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:hover:bg-opacity-100 dark:text-gray-500',
|
||||
endpoint === 'chatGPTBrowser' ? '' : 'cursor-pointer '
|
||||
)}
|
||||
onClick={() => (endpoint === 'chatGPTBrowser' ? null : setSaveAsDialogShow(true))}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useEffect } from 'react';
|
||||
import store from '~/store';
|
||||
import TrashIcon from '../svg/TrashIcon';
|
||||
import { Dialog, DialogTrigger } from '../ui/Dialog.tsx';
|
||||
import { Dialog } from '../ui/Dialog.tsx';
|
||||
import DialogTemplate from '../ui/DialogTemplate';
|
||||
import { useClearConversationsMutation } from '~/data-provider';
|
||||
|
||||
export default function ClearConvos() {
|
||||
const ClearConvos = ({ open, onOpenChange}) => {
|
||||
const { newConversation } = store.useConversation();
|
||||
const { refreshConversations } = store.useConversations();
|
||||
const clearConvosMutation = useClearConversationsMutation();
|
||||
|
@ -23,13 +22,7 @@ export default function ClearConvos() {
|
|||
}, [clearConvosMutation.isSuccess]);
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<button className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700">
|
||||
<TrashIcon />
|
||||
Clear conversations
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogTemplate
|
||||
title="Clear conversations"
|
||||
description="Are you sure you want to clear all conversations? This is irreversible."
|
||||
|
@ -41,4 +34,6 @@ export default function ClearConvos() {
|
|||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ClearConvos;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useState, useContext } from 'react';
|
||||
import { forwardRef, useContext } from 'react';
|
||||
import DarkModeIcon from '../svg/DarkModeIcon';
|
||||
import LightModeIcon from '../svg/LightModeIcon';
|
||||
import { ThemeContext } from '~/hooks/ThemeContext';
|
||||
|
||||
export default function DarkMode() {
|
||||
const DarkMode = forwardRef(() => {
|
||||
const { theme, setTheme } = useContext(ThemeContext);
|
||||
|
||||
const clickHandler = () => setTheme(theme === 'dark' ? 'light' : 'dark');
|
||||
|
@ -18,4 +18,6 @@ export default function DarkMode() {
|
|||
{mode}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default DarkMode;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue, useRecoilCallback } from 'recoil';
|
||||
import filenamify from 'filenamify';
|
||||
import exportFromJSON from 'export-from-json';
|
||||
import download from 'downloadjs';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate.jsx';
|
||||
import { Dialog, DialogClose, DialogButton } from '~/components/ui/Dialog.tsx';
|
||||
import { Dialog, DialogButton } from '~/components/ui/Dialog.tsx';
|
||||
import { Input } from '~/components/ui/Input.tsx';
|
||||
import { Label } from '~/components/ui/Label.tsx';
|
||||
import { Checkbox } from '~/components/ui/Checkbox.tsx';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useState, forwardRef } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Download } from 'lucide-react';
|
||||
import { cn } from '~/utils/';
|
||||
|
@ -7,7 +7,7 @@ import ExportModel from './ExportModel';
|
|||
|
||||
import store from '~/store';
|
||||
|
||||
export default function ExportConversation() {
|
||||
const ExportConversation = forwardRef(() => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const conversation = useRecoilValue(store.conversation) || {};
|
||||
|
@ -37,4 +37,6 @@ export default function ExportConversation() {
|
|||
<ExportModel open={open} onOpenChange={setOpen} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ExportConversation;
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import LogOutIcon from '../svg/LogOutIcon';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
|
||||
export default function Logout() {
|
||||
const Logout = forwardRef(() => {
|
||||
const { user, logout } = useAuthContext();
|
||||
|
||||
const handleLogout = () => {
|
||||
|
@ -20,4 +20,6 @@ export default function Logout() {
|
|||
<small>Log out</small>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default Logout;
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
import React from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
export default function NavLink({ svg, text, clickHandler }) {
|
||||
const props = {
|
||||
className:
|
||||
'flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10'
|
||||
};
|
||||
const NavLink = forwardRef((props, ref) => {
|
||||
const { svg, text, clickHandler, className = '' } = props;
|
||||
const defaultProps = {};
|
||||
|
||||
defaultProps.className = cn(
|
||||
'flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10',
|
||||
className
|
||||
);
|
||||
|
||||
if (clickHandler) {
|
||||
props.onClick = clickHandler;
|
||||
console.log('clickHandler: ', clickHandler);
|
||||
defaultProps.onClick = clickHandler;
|
||||
}
|
||||
|
||||
return (
|
||||
<a {...props}>
|
||||
<a {...defaultProps} ref={ref}>
|
||||
{svg()}
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default NavLink;
|
||||
|
|
|
@ -1,70 +1,111 @@
|
|||
import { Menu, Transition } from '@headlessui/react';
|
||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import { Fragment, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import SearchBar from './SearchBar';
|
||||
import TrashIcon from '../svg/TrashIcon';
|
||||
import { Download } from 'lucide-react';
|
||||
import NavLink from './NavLink';
|
||||
import ExportModel from './ExportConversation/ExportModel';
|
||||
import ClearConvos from './ClearConvos';
|
||||
import DarkMode from './DarkMode';
|
||||
import Logout from './Logout';
|
||||
import ExportConversation from './ExportConversation';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import { cn } from '~/utils/';
|
||||
import DotsIcon from '../svg/DotsIcon';
|
||||
|
||||
import store from '~/store';
|
||||
|
||||
export default function NavLinks({ clearSearch, isSearchEnabled }) {
|
||||
const { user, logout } = useAuthContext();
|
||||
const [showExports, setShowExports] = useState(false);
|
||||
const [showClearConvos, setShowClearConvos] = useState(false);
|
||||
const { user } = useAuthContext();
|
||||
|
||||
const conversation = useRecoilValue(store.conversation) || {};
|
||||
|
||||
const exportable =
|
||||
conversation?.conversationId &&
|
||||
conversation?.conversationId !== 'new' &&
|
||||
conversation?.conversationId !== 'search';
|
||||
|
||||
const clickHandler = () => {
|
||||
if (exportable) setShowExports(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu as="div" className="group relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Menu.Button
|
||||
className={cn(
|
||||
'group-ui-open:bg-gray-800 flex w-full items-center gap-2.5 rounded-md px-3 py-3 text-sm transition-colors duration-200 hover:bg-gray-800',
|
||||
open ? 'bg-gray-800' : ''
|
||||
)}
|
||||
>
|
||||
<div className="-ml-0.5 h-5 w-5 flex-shrink-0">
|
||||
<div className="relative flex">
|
||||
<img
|
||||
className="rounded-sm"
|
||||
src={
|
||||
user?.avatar || `https://avatars.dicebear.com/api/initials/${user?.name}.svg`
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
<>
|
||||
<Menu as="div" className="group relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Menu.Button
|
||||
className={cn(
|
||||
'group-ui-open:bg-gray-800 flex w-full items-center gap-2.5 rounded-md px-3 py-3 text-sm transition-colors duration-200 hover:bg-gray-800',
|
||||
open ? 'bg-gray-800' : ''
|
||||
)}
|
||||
>
|
||||
<div className="-ml-0.5 h-5 w-5 flex-shrink-0">
|
||||
<div className="relative flex">
|
||||
<img
|
||||
className="rounded-sm"
|
||||
src={
|
||||
user?.avatar || `https://avatars.dicebear.com/api/initials/${user?.name}.svg`
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-white">
|
||||
{user?.name || 'USER'}
|
||||
</div>
|
||||
<DotsIcon />
|
||||
</Menu.Button>
|
||||
<div className="grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-white">
|
||||
{user?.name || 'USER'}
|
||||
</div>
|
||||
<DotsIcon />
|
||||
</Menu.Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute bottom-full left-0 z-20 mb-2 w-full translate-y-0 overflow-hidden rounded-md bg-[#050509] py-1.5 opacity-100 outline-none">
|
||||
<Menu.Item>
|
||||
{({}) => <>{!!isSearchEnabled && <SearchBar clearSearch={clearSearch} />}</>}
|
||||
</Menu.Item>
|
||||
<Menu.Item>{({}) => <ExportConversation />}</Menu.Item>
|
||||
|
||||
<div className="my-1.5 h-px bg-white/20" role="none"></div>
|
||||
<Menu.Item>{({}) => <DarkMode />}</Menu.Item>
|
||||
<Menu.Item>{({}) => <ClearConvos />}</Menu.Item>
|
||||
|
||||
<div className="my-1.5 h-px bg-white/20" role="none"></div>
|
||||
<Menu.Item>
|
||||
<Logout />
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute bottom-full left-0 z-20 mb-2 w-full translate-y-0 overflow-hidden rounded-md bg-[#050509] py-1.5 opacity-100 outline-none">
|
||||
<Menu.Item>
|
||||
{!!isSearchEnabled && <SearchBar clearSearch={clearSearch} />}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<NavLink
|
||||
className={cn(
|
||||
'flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700',
|
||||
exportable ? 'cursor-pointer text-white' : 'cursor-not-allowed text-gray-400'
|
||||
)}
|
||||
svg={() => <Download size={16} />}
|
||||
text="Export conversation"
|
||||
clickHandler={clickHandler}
|
||||
/>
|
||||
</Menu.Item>
|
||||
<div className="my-1.5 h-px bg-white/20" role="none" />
|
||||
<Menu.Item>
|
||||
<DarkMode />
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<NavLink
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
|
||||
svg={() => <TrashIcon />}
|
||||
text="Clear conversations"
|
||||
clickHandler={() => setShowClearConvos(true)}
|
||||
/>
|
||||
</Menu.Item>
|
||||
<div className="my-1.5 h-px bg-white/20" role="none" />
|
||||
<Menu.Item>
|
||||
<Logout />
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
{showExports && <ExportModel open={showExports} onOpenChange={setShowExports} />}
|
||||
{showClearConvos && <ClearConvos open={showClearConvos} onOpenChange={setShowClearConvos} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { forwardRef } from 'react';
|
||||
import { Search } from 'lucide-react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import store from '~/store';
|
||||
|
||||
export default function SearchBar({ clearSearch }) {
|
||||
const SearchBar = forwardRef((props, ref) => {
|
||||
const { clearSearch } = props;
|
||||
const [searchQuery, setSearchQuery] = useRecoilState(store.searchQuery);
|
||||
|
||||
const handleKeyUp = (e) => {
|
||||
|
@ -19,7 +21,10 @@ export default function SearchBar({ clearSearch }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700">
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
|
||||
>
|
||||
{<Search className="h-4 w-4" />}
|
||||
<input
|
||||
type="text"
|
||||
|
@ -34,4 +39,6 @@ export default function SearchBar({ clearSearch }) {
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default SearchBar;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useState, useEffect, useRef, useContext } from 'react';
|
||||
import NewChat from './NewChat';
|
||||
import Panel from '../svg/Panel';
|
||||
import Spinner from '../svg/Spinner';
|
||||
import Pages from '../Conversations/Pages';
|
||||
import Conversations from '../Conversations';
|
||||
|
@ -198,62 +199,21 @@ export default function Nav({ navVisible, setNavVisible }) {
|
|||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={cn('nav-close-button -ml-0.5 -mt-2.5 inline-flex h-10 w-10 items-center justify-center rounded-md focus:outline-none focus:ring-white md:-ml-1 md:-mt-2.5', theme === 'dark' ? 'text-gray-500 hover:text-gray-400' : 'text-gray-900 hover:text-gray-600')}
|
||||
className={cn('nav-close-button -ml-0.5 -mt-2.5 inline-flex h-10 w-10 items-center justify-center rounded-md focus:outline-none focus:ring-white md:-ml-1 md:-mt-2.5', theme === 'dark' ? 'text-gray-500 hover:text-gray-400' : 'text-gray-400 hover:text-gray-500')}
|
||||
onClick={toggleNavVisible}
|
||||
>
|
||||
<span className="sr-only">Close sidebar</span>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="block h-6 w-6 md:hidden"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line x1="3" y1="6" x2="15" y2="18" />
|
||||
<line x1="3" y1="18" x2="15" y2="6" />
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="hidden h-[26px] w-[26px] md:block"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75"
|
||||
/>
|
||||
</svg>
|
||||
<Panel/>
|
||||
</button>
|
||||
</div>
|
||||
{!navVisible && (
|
||||
<button
|
||||
type="button"
|
||||
className="nav-open-button fixed left-2 top-0.5 z-10 inline-flex h-10 w-10 items-center justify-center rounded-md text-gray-900 hover:text-gray-600 focus:outline-none focus:ring-white dark:text-gray-500 dark:hover:text-gray-400"
|
||||
className="nav-open-button fixed left-2 top-0.5 z-10 inline-flex h-10 w-10 items-center justify-center rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-white dark:text-gray-500 dark:hover:text-gray-400"
|
||||
onClick={toggleNavVisible}
|
||||
>
|
||||
<span className="sr-only">Open sidebar</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9"
|
||||
/>
|
||||
</svg>
|
||||
<Panel open={true}/>
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
|
47
client/src/components/svg/Panel.jsx
Normal file
47
client/src/components/svg/Panel.jsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { cn } from '~/utils/';
|
||||
|
||||
export default function Panel({ open = false, className }) {
|
||||
const openPanel = (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={cn('lucide lucide-panel-right-close', className)}
|
||||
>
|
||||
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
|
||||
<line x1="15" x2="15" y1="3" y2="21" />
|
||||
<path d="m8 9 3 3-3 3" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const closePanel = (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={cn('lucide lucide-panel-left-close', className)}
|
||||
>
|
||||
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
|
||||
<path d="M9 3v18" />
|
||||
<path d="m16 15-3-3 3-3" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
if (open) {
|
||||
return openPanel;
|
||||
} else {
|
||||
return closePanel;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
|
@ -10,21 +9,22 @@ import {
|
|||
} from './Dialog.tsx';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
export default function DialogTemplate({
|
||||
title,
|
||||
description,
|
||||
main,
|
||||
buttons,
|
||||
leftButtons,
|
||||
selection,
|
||||
className
|
||||
}) {
|
||||
const DialogTemplate = forwardRef((props, ref) => {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
main,
|
||||
buttons,
|
||||
leftButtons,
|
||||
selection,
|
||||
className
|
||||
} = props;
|
||||
const { selectHandler, selectClasses, selectText } = selection || {};
|
||||
|
||||
const defaultSelect =
|
||||
'bg-gray-900 text-white transition-colors hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-900';
|
||||
return (
|
||||
<DialogContent className={cn('shadow-2xl dark:bg-gray-800', className || '')}>
|
||||
<DialogContent ref={ref} className={cn('shadow-2xl dark:bg-gray-800', className || '')}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-gray-800 dark:text-white">{title}</DialogTitle>
|
||||
<DialogDescription className="text-gray-600 dark:text-gray-300">
|
||||
|
@ -51,4 +51,6 @@ export default function DialogTemplate({
|
|||
</DialogFooter>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default DialogTemplate;
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { clsx } from 'clsx';
|
||||
import React from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import GPTIcon from '../components/svg/GPTIcon';
|
||||
import BingIcon from '../components/svg/BingIcon';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
|
@ -8,12 +5,13 @@ import { useAuthContext } from '~/hooks/AuthContext';
|
|||
const getIcon = (props) => {
|
||||
// { size = 30, isCreatedByUser, model, chatGptLabel, error, ...props }
|
||||
const { size = 30, isCreatedByUser, button, model } = props;
|
||||
const { user, logout } = useAuthContext();
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { user } = useAuthContext();
|
||||
|
||||
if (isCreatedByUser)
|
||||
return (
|
||||
<div
|
||||
title={user.name}
|
||||
title={user?.name || 'User'}
|
||||
style={{
|
||||
width: size,
|
||||
height: size
|
||||
|
@ -24,7 +22,7 @@ const getIcon = (props) => {
|
|||
className="rounded-sm"
|
||||
src={
|
||||
user?.avatar ||
|
||||
`https://api.dicebear.com/6.x/initials/svg?seed=${user?.name}&fontFamily=Verdana&fontSize=36`
|
||||
`https://api.dicebear.com/6.x/initials/svg?seed=${user?.name || 'User'}&fontFamily=Verdana&fontSize=36`
|
||||
}
|
||||
alt="avatar"
|
||||
/>
|
||||
|
|
13
package-lock.json
generated
13
package-lock.json
generated
|
@ -114,7 +114,7 @@
|
|||
"filenamify": "^6.0.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.113.0",
|
||||
"lucide-react": "^0.220.0",
|
||||
"pino": "^8.12.1",
|
||||
"rc-input-number": "^7.4.2",
|
||||
"react": "^18.2.0",
|
||||
|
@ -12104,8 +12104,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.113.0",
|
||||
"license": "ISC",
|
||||
"version": "0.220.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.220.0.tgz",
|
||||
"integrity": "sha512-bYtGUsLAWBvZu+BzAU/ziP1gzE4LwMEXLnlgSr1yUKEPPalLG77JLd5GdYebOVkpm+GtqRqnp6tEKDX7Bm8ZlQ==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
|
@ -22692,7 +22693,7 @@
|
|||
"filenamify": "^6.0.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.113.0",
|
||||
"lucide-react": "0.220.0",
|
||||
"path": "^0.12.7",
|
||||
"pino": "^8.12.1",
|
||||
"postcss": "^8.4.21",
|
||||
|
@ -26275,7 +26276,9 @@
|
|||
}
|
||||
},
|
||||
"lucide-react": {
|
||||
"version": "0.113.0",
|
||||
"version": "0.220.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.220.0.tgz",
|
||||
"integrity": "sha512-bYtGUsLAWBvZu+BzAU/ziP1gzE4LwMEXLnlgSr1yUKEPPalLG77JLd5GdYebOVkpm+GtqRqnp6tEKDX7Bm8ZlQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"magic-string": {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue