mirror of
https://github.com/NovaOSS/nova-betterchat.git
synced 2024-11-25 17:33:59 +01:00
parent
470aa40a84
commit
b813df5343
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const DownArrow = () => {
|
||||
const DownArrow = (props: React.SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<svg
|
||||
stroke='currentColor'
|
||||
|
@ -13,6 +13,7 @@ const DownArrow = () => {
|
|||
height='1em'
|
||||
width='1em'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<line x1='12' y1='5' x2='12' y2='19'></line>
|
||||
<polyline points='19 12 12 19 5 12'></polyline>
|
||||
|
|
25
src/assets/icons/MenuIcon.tsx
Normal file
25
src/assets/icons/MenuIcon.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
|
||||
const MenuIcon = (props: React.SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<svg
|
||||
stroke='currentColor'
|
||||
fill='none'
|
||||
strokeWidth='1.5'
|
||||
viewBox='0 0 24 24'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
className='h-6 w-6'
|
||||
height='1em'
|
||||
width='1em'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<line x1='3' y1='12' x2='21' y2='12'></line>
|
||||
<line x1='3' y1='6' x2='21' y2='6'></line>
|
||||
<line x1='3' y1='18' x2='21' y2='18'></line>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuIcon;
|
|
@ -1,12 +1,19 @@
|
|||
import React from 'react';
|
||||
import useStore from '@store/store';
|
||||
|
||||
import ChatContent from './ChatContent';
|
||||
import MobileBar from '../MobileBar';
|
||||
import StopGeneratingButton from '@components/StopGeneratingButton/StopGeneratingButton';
|
||||
|
||||
const Chat = () => {
|
||||
const hideSideMenu = useStore((state) => state.hideSideMenu);
|
||||
|
||||
return (
|
||||
<div className='flex h-full flex-1 flex-col md:pl-[260px]'>
|
||||
<div
|
||||
className={`flex h-full flex-1 flex-col ${
|
||||
hideSideMenu ? 'md:pl-0' : 'md:pl-[260px]'
|
||||
}`}
|
||||
>
|
||||
<MobileBar />
|
||||
<main className='relative h-full w-full transition-width flex flex-col overflow-hidden items-stretch flex-1'>
|
||||
<ChatContent />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import useStore from '@store/store';
|
||||
|
||||
import Avatar from './Avatar';
|
||||
import MessageContent from './MessageContent';
|
||||
|
@ -25,13 +26,21 @@ const Message = React.memo(
|
|||
messageIndex: number;
|
||||
sticky?: boolean;
|
||||
}) => {
|
||||
const hideSideMenu = useStore((state) => state.hideSideMenu);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group ${
|
||||
backgroundStyle[messageIndex % 2]
|
||||
}`}
|
||||
>
|
||||
<div className='text-base gap-4 md:gap-6 m-auto md:max-w-2xl lg:max-w-2xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0'>
|
||||
<div
|
||||
className={`text-base gap-4 md:gap-6 m-auto p-4 md:py-6 flex lg:px-0 transition-all ease-in-out ${
|
||||
hideSideMenu
|
||||
? 'md:max-w-5xl lg:max-w-5xl xl:max-w-6xl'
|
||||
: 'md:max-w-3xl lg:max-w-3xl xl:max-w-4xl'
|
||||
}`}
|
||||
>
|
||||
<Avatar role={role} />
|
||||
<div className='w-[calc(100%-50px)] '>
|
||||
<RoleSelector
|
||||
|
|
|
@ -1,18 +1,33 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import useStore from '@store/store';
|
||||
|
||||
import NewChat from './NewChat';
|
||||
import ChatHistoryList from './ChatHistoryList';
|
||||
import MenuOptions from './MenuOptions';
|
||||
|
||||
import CrossIcon2 from '@icon/CrossIcon2';
|
||||
import DownArrow from '@icon/DownArrow';
|
||||
import MenuIcon from '@icon/MenuIcon';
|
||||
|
||||
const Menu = () => {
|
||||
const hideSideMenu = useStore((state) => state.hideSideMenu);
|
||||
const setHideSideMenu = useStore((state) => state.setHideSideMenu);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.innerWidth < 768) setHideSideMenu(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id='menu'
|
||||
className='dark bg-gray-900 md:fixed md:inset-y-0 md:flex md:w-[260px] md:flex-col max-md:translate-x-[-100%] max-md:fixed max-md:transition-transform max-md:z-[999] max-md:top-0 max-md:left-0 max-md:h-full max-md:w-3/4'
|
||||
className={`group dark bg-gray-900 fixed md:inset-y-0 md:flex md:w-[260px] md:flex-col transition-transform z-[999] top-0 left-0 h-full max-md:w-3/4 ${
|
||||
hideSideMenu ? 'translate-x-[-100%]' : 'translate-x-[0%]'
|
||||
}`}
|
||||
>
|
||||
<div className='flex h-full min-h-0 flex-col '>
|
||||
<div className='flex h-full min-h-0 flex-col'>
|
||||
<div className='scrollbar-trigger flex h-full w-full flex-1 items-start border-white/20'>
|
||||
<nav className='flex h-full flex-1 flex-col space-y-1 p-2'>
|
||||
<NewChat />
|
||||
|
@ -23,27 +38,39 @@ const Menu = () => {
|
|||
</div>
|
||||
<div
|
||||
id='menu-close'
|
||||
className='hidden md:hidden absolute z-[999] right-0 translate-x-full top-10 bg-gray-900 p-2 cursor-pointer hover:bg-black text-white'
|
||||
className={`${
|
||||
hideSideMenu ? 'hidden' : ''
|
||||
} md:hidden absolute z-[999] right-0 translate-x-full top-10 bg-gray-900 p-2 cursor-pointer hover:bg-black text-white`}
|
||||
onClick={() => {
|
||||
document
|
||||
.getElementById('menu')
|
||||
?.classList.remove('max-md:translate-x-[0%]');
|
||||
document.getElementById('menu-close')?.classList.add('hidden');
|
||||
document.getElementById('menu-backdrop')?.classList.add('hidden');
|
||||
setHideSideMenu(true);
|
||||
}}
|
||||
>
|
||||
<CrossIcon2 />
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
hideSideMenu ? 'opacity-100' : 'opacity-0'
|
||||
} group md:group-hover:opacity-100 max-md:hidden transition-opacity absolute z-[999] right-0 translate-x-full top-10 bg-gray-900 p-2 cursor-pointer hover:bg-black text-white ${
|
||||
hideSideMenu ? '' : 'rotate-90'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setHideSideMenu(!hideSideMenu);
|
||||
}}
|
||||
>
|
||||
{hideSideMenu ? (
|
||||
<MenuIcon className='h-4 w-4' />
|
||||
) : (
|
||||
<DownArrow className='h-4 w-4' />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id='menu-backdrop'
|
||||
className='hidden md:hidden fixed top-0 left-0 h-full w-full z-[60] bg-gray-900/70'
|
||||
className={`${
|
||||
hideSideMenu ? 'hidden' : ''
|
||||
} md:hidden fixed top-0 left-0 h-full w-full z-[60] bg-gray-900/70`}
|
||||
onClick={() => {
|
||||
document
|
||||
.getElementById('menu')
|
||||
?.classList.remove('max-md:translate-x-[0%]');
|
||||
document.getElementById('menu-close')?.classList.add('hidden');
|
||||
document.getElementById('menu-backdrop')?.classList.add('hidden');
|
||||
setHideSideMenu(true);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -2,10 +2,12 @@ import React from 'react';
|
|||
|
||||
import useStore from '@store/store';
|
||||
import PlusIcon from '@icon/PlusIcon';
|
||||
import MenuIcon from '@icon/MenuIcon';
|
||||
import useAddChat from '@hooks/useAddChat';
|
||||
|
||||
const MobileBar = () => {
|
||||
const generating = useStore((state) => state.generating);
|
||||
const setHideSideMenu = useStore((state) => state.setHideSideMenu);
|
||||
const chatTitle = useStore((state) =>
|
||||
state.chats &&
|
||||
state.chats.length > 0 &&
|
||||
|
@ -23,30 +25,11 @@ const MobileBar = () => {
|
|||
type='button'
|
||||
className='-ml-0.5 -mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white dark:hover:text-white'
|
||||
onClick={() => {
|
||||
document
|
||||
.getElementById('menu')
|
||||
?.classList.add('max-md:translate-x-[0%]');
|
||||
document.getElementById('menu-close')?.classList.remove('hidden');
|
||||
document.getElementById('menu-backdrop')?.classList.remove('hidden');
|
||||
setHideSideMenu(false);
|
||||
}}
|
||||
>
|
||||
<span className='sr-only'>Open sidebar</span>
|
||||
<svg
|
||||
stroke='currentColor'
|
||||
fill='none'
|
||||
strokeWidth='1.5'
|
||||
viewBox='0 0 24 24'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
className='h-6 w-6'
|
||||
height='1em'
|
||||
width='1em'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<line x1='3' y1='12' x2='21' y2='12'></line>
|
||||
<line x1='3' y1='6' x2='21' y2='6'></line>
|
||||
<line x1='3' y1='18' x2='21' y2='18'></line>
|
||||
</svg>
|
||||
<MenuIcon />
|
||||
</button>
|
||||
<h1 className='flex-1 text-center text-base font-normal'>{chatTitle}</h1>
|
||||
<button
|
||||
|
|
|
@ -10,18 +10,21 @@ export interface ConfigSlice {
|
|||
hideMenuOptions: boolean;
|
||||
defaultChatConfig: ConfigInterface;
|
||||
defaultSystemMessage: string;
|
||||
hideSideMenu: boolean;
|
||||
setOpenConfig: (openConfig: boolean) => void;
|
||||
setTheme: (theme: Theme) => void;
|
||||
setAutoTitle: (autoTitle: boolean) => void;
|
||||
setDefaultChatConfig: (defaultChatConfig: ConfigInterface) => void;
|
||||
setDefaultSystemMessage: (defaultSystemMessage: string) => void;
|
||||
setHideMenuOptions: (hideMenuOptions: boolean) => void;
|
||||
setHideSideMenu: (hideSideMenu: boolean) => void;
|
||||
}
|
||||
|
||||
export const createConfigSlice: StoreSlice<ConfigSlice> = (set, get) => ({
|
||||
openConfig: false,
|
||||
theme: 'dark',
|
||||
hideMenuOptions: false,
|
||||
hideSideMenu: false,
|
||||
autoTitle: false,
|
||||
defaultChatConfig: _defaultChatConfig,
|
||||
defaultSystemMessage: _defaultSystemMessage,
|
||||
|
@ -61,4 +64,10 @@ export const createConfigSlice: StoreSlice<ConfigSlice> = (set, get) => ({
|
|||
hideMenuOptions: hideMenuOptions,
|
||||
}));
|
||||
},
|
||||
setHideSideMenu: (hideSideMenu: boolean) => {
|
||||
set((prev: ConfigSlice) => ({
|
||||
...prev,
|
||||
hideSideMenu: hideSideMenu,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
|
|
@ -57,6 +57,7 @@ const useStore = create<StoreState>()(
|
|||
defaultSystemMessage: state.defaultSystemMessage,
|
||||
hideMenuOptions: state.hideMenuOptions,
|
||||
firstVisit: state.firstVisit,
|
||||
hideSideMenu: state.hideSideMenu,
|
||||
}),
|
||||
version: 6,
|
||||
migrate: (persistedState, version) => {
|
||||
|
|
Loading…
Reference in a new issue