mirror of
https://github.com/NovaOSS/nova-betterchat.git
synced 2024-11-25 19:43:59 +01:00
parent
470aa40a84
commit
b813df5343
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const DownArrow = () => {
|
const DownArrow = (props: React.SVGProps<SVGSVGElement>) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
stroke='currentColor'
|
stroke='currentColor'
|
||||||
|
@ -13,6 +13,7 @@ const DownArrow = () => {
|
||||||
height='1em'
|
height='1em'
|
||||||
width='1em'
|
width='1em'
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<line x1='12' y1='5' x2='12' y2='19'></line>
|
<line x1='12' y1='5' x2='12' y2='19'></line>
|
||||||
<polyline points='19 12 12 19 5 12'></polyline>
|
<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 React from 'react';
|
||||||
|
import useStore from '@store/store';
|
||||||
|
|
||||||
import ChatContent from './ChatContent';
|
import ChatContent from './ChatContent';
|
||||||
import MobileBar from '../MobileBar';
|
import MobileBar from '../MobileBar';
|
||||||
import StopGeneratingButton from '@components/StopGeneratingButton/StopGeneratingButton';
|
import StopGeneratingButton from '@components/StopGeneratingButton/StopGeneratingButton';
|
||||||
|
|
||||||
const Chat = () => {
|
const Chat = () => {
|
||||||
|
const hideSideMenu = useStore((state) => state.hideSideMenu);
|
||||||
|
|
||||||
return (
|
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 />
|
<MobileBar />
|
||||||
<main className='relative h-full w-full transition-width flex flex-col overflow-hidden items-stretch flex-1'>
|
<main className='relative h-full w-full transition-width flex flex-col overflow-hidden items-stretch flex-1'>
|
||||||
<ChatContent />
|
<ChatContent />
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import useStore from '@store/store';
|
||||||
|
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import MessageContent from './MessageContent';
|
import MessageContent from './MessageContent';
|
||||||
|
@ -25,13 +26,21 @@ const Message = React.memo(
|
||||||
messageIndex: number;
|
messageIndex: number;
|
||||||
sticky?: boolean;
|
sticky?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
const hideSideMenu = useStore((state) => state.hideSideMenu);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group ${
|
className={`w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group ${
|
||||||
backgroundStyle[messageIndex % 2]
|
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} />
|
<Avatar role={role} />
|
||||||
<div className='w-[calc(100%-50px)] '>
|
<div className='w-[calc(100%-50px)] '>
|
||||||
<RoleSelector
|
<RoleSelector
|
||||||
|
|
|
@ -1,18 +1,33 @@
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import useStore from '@store/store';
|
||||||
|
|
||||||
import NewChat from './NewChat';
|
import NewChat from './NewChat';
|
||||||
import ChatHistoryList from './ChatHistoryList';
|
import ChatHistoryList from './ChatHistoryList';
|
||||||
import MenuOptions from './MenuOptions';
|
import MenuOptions from './MenuOptions';
|
||||||
|
|
||||||
import CrossIcon2 from '@icon/CrossIcon2';
|
import CrossIcon2 from '@icon/CrossIcon2';
|
||||||
|
import DownArrow from '@icon/DownArrow';
|
||||||
|
import MenuIcon from '@icon/MenuIcon';
|
||||||
|
|
||||||
const Menu = () => {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
id='menu'
|
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'>
|
<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'>
|
<nav className='flex h-full flex-1 flex-col space-y-1 p-2'>
|
||||||
<NewChat />
|
<NewChat />
|
||||||
|
@ -23,27 +38,39 @@ const Menu = () => {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id='menu-close'
|
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={() => {
|
onClick={() => {
|
||||||
document
|
setHideSideMenu(true);
|
||||||
.getElementById('menu')
|
|
||||||
?.classList.remove('max-md:translate-x-[0%]');
|
|
||||||
document.getElementById('menu-close')?.classList.add('hidden');
|
|
||||||
document.getElementById('menu-backdrop')?.classList.add('hidden');
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CrossIcon2 />
|
<CrossIcon2 />
|
||||||
</div>
|
</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>
|
||||||
<div
|
<div
|
||||||
id='menu-backdrop'
|
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={() => {
|
onClick={() => {
|
||||||
document
|
setHideSideMenu(true);
|
||||||
.getElementById('menu')
|
|
||||||
?.classList.remove('max-md:translate-x-[0%]');
|
|
||||||
document.getElementById('menu-close')?.classList.add('hidden');
|
|
||||||
document.getElementById('menu-backdrop')?.classList.add('hidden');
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -2,10 +2,12 @@ import React from 'react';
|
||||||
|
|
||||||
import useStore from '@store/store';
|
import useStore from '@store/store';
|
||||||
import PlusIcon from '@icon/PlusIcon';
|
import PlusIcon from '@icon/PlusIcon';
|
||||||
|
import MenuIcon from '@icon/MenuIcon';
|
||||||
import useAddChat from '@hooks/useAddChat';
|
import useAddChat from '@hooks/useAddChat';
|
||||||
|
|
||||||
const MobileBar = () => {
|
const MobileBar = () => {
|
||||||
const generating = useStore((state) => state.generating);
|
const generating = useStore((state) => state.generating);
|
||||||
|
const setHideSideMenu = useStore((state) => state.setHideSideMenu);
|
||||||
const chatTitle = useStore((state) =>
|
const chatTitle = useStore((state) =>
|
||||||
state.chats &&
|
state.chats &&
|
||||||
state.chats.length > 0 &&
|
state.chats.length > 0 &&
|
||||||
|
@ -23,30 +25,11 @@ const MobileBar = () => {
|
||||||
type='button'
|
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'
|
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={() => {
|
onClick={() => {
|
||||||
document
|
setHideSideMenu(false);
|
||||||
.getElementById('menu')
|
|
||||||
?.classList.add('max-md:translate-x-[0%]');
|
|
||||||
document.getElementById('menu-close')?.classList.remove('hidden');
|
|
||||||
document.getElementById('menu-backdrop')?.classList.remove('hidden');
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className='sr-only'>Open sidebar</span>
|
<span className='sr-only'>Open sidebar</span>
|
||||||
<svg
|
<MenuIcon />
|
||||||
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>
|
|
||||||
</button>
|
</button>
|
||||||
<h1 className='flex-1 text-center text-base font-normal'>{chatTitle}</h1>
|
<h1 className='flex-1 text-center text-base font-normal'>{chatTitle}</h1>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -10,18 +10,21 @@ export interface ConfigSlice {
|
||||||
hideMenuOptions: boolean;
|
hideMenuOptions: boolean;
|
||||||
defaultChatConfig: ConfigInterface;
|
defaultChatConfig: ConfigInterface;
|
||||||
defaultSystemMessage: string;
|
defaultSystemMessage: string;
|
||||||
|
hideSideMenu: boolean;
|
||||||
setOpenConfig: (openConfig: boolean) => void;
|
setOpenConfig: (openConfig: boolean) => void;
|
||||||
setTheme: (theme: Theme) => void;
|
setTheme: (theme: Theme) => void;
|
||||||
setAutoTitle: (autoTitle: boolean) => void;
|
setAutoTitle: (autoTitle: boolean) => void;
|
||||||
setDefaultChatConfig: (defaultChatConfig: ConfigInterface) => void;
|
setDefaultChatConfig: (defaultChatConfig: ConfigInterface) => void;
|
||||||
setDefaultSystemMessage: (defaultSystemMessage: string) => void;
|
setDefaultSystemMessage: (defaultSystemMessage: string) => void;
|
||||||
setHideMenuOptions: (hideMenuOptions: boolean) => void;
|
setHideMenuOptions: (hideMenuOptions: boolean) => void;
|
||||||
|
setHideSideMenu: (hideSideMenu: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createConfigSlice: StoreSlice<ConfigSlice> = (set, get) => ({
|
export const createConfigSlice: StoreSlice<ConfigSlice> = (set, get) => ({
|
||||||
openConfig: false,
|
openConfig: false,
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
hideMenuOptions: false,
|
hideMenuOptions: false,
|
||||||
|
hideSideMenu: false,
|
||||||
autoTitle: false,
|
autoTitle: false,
|
||||||
defaultChatConfig: _defaultChatConfig,
|
defaultChatConfig: _defaultChatConfig,
|
||||||
defaultSystemMessage: _defaultSystemMessage,
|
defaultSystemMessage: _defaultSystemMessage,
|
||||||
|
@ -61,4 +64,10 @@ export const createConfigSlice: StoreSlice<ConfigSlice> = (set, get) => ({
|
||||||
hideMenuOptions: hideMenuOptions,
|
hideMenuOptions: hideMenuOptions,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
setHideSideMenu: (hideSideMenu: boolean) => {
|
||||||
|
set((prev: ConfigSlice) => ({
|
||||||
|
...prev,
|
||||||
|
hideSideMenu: hideSideMenu,
|
||||||
|
}));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,6 +57,7 @@ const useStore = create<StoreState>()(
|
||||||
defaultSystemMessage: state.defaultSystemMessage,
|
defaultSystemMessage: state.defaultSystemMessage,
|
||||||
hideMenuOptions: state.hideMenuOptions,
|
hideMenuOptions: state.hideMenuOptions,
|
||||||
firstVisit: state.firstVisit,
|
firstVisit: state.firstVisit,
|
||||||
|
hideSideMenu: state.hideSideMenu,
|
||||||
}),
|
}),
|
||||||
version: 6,
|
version: 6,
|
||||||
migrate: (persistedState, version) => {
|
migrate: (persistedState, version) => {
|
||||||
|
|
Loading…
Reference in a new issue