mirror of
https://github.com/NovaOSS/nova-betterchat.git
synced 2024-11-25 19:33:59 +01:00
parent
2b0280a479
commit
5b642f043f
|
@ -132,6 +132,7 @@ const ApiEndpointSelector = ({
|
||||||
<button
|
<button
|
||||||
className='btn btn-neutral btn-small flex justify-between w-full'
|
className='btn btn-neutral btn-small flex justify-between w-full'
|
||||||
type='button'
|
type='button'
|
||||||
|
aria-label='expand api menu'
|
||||||
onClick={() => setDropDown((prev) => !prev)}
|
onClick={() => setDropDown((prev) => !prev)}
|
||||||
>
|
>
|
||||||
<span className='truncate'>{_apiEndpoint}</span>
|
<span className='truncate'>{_apiEndpoint}</span>
|
||||||
|
|
|
@ -44,7 +44,11 @@ const CloneChat = React.memo(() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className='btn btn-neutral flex gap-1' onClick={cloneChat}>
|
<button
|
||||||
|
className='btn btn-neutral flex gap-1'
|
||||||
|
aria-label={t('cloneChat') as string}
|
||||||
|
onClick={cloneChat}
|
||||||
|
>
|
||||||
{cloned ? (
|
{cloned ? (
|
||||||
<>
|
<>
|
||||||
<TickIcon /> {t('cloned')}
|
<TickIcon /> {t('cloned')}
|
||||||
|
|
|
@ -24,6 +24,7 @@ const DownloadChat = React.memo(
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className='btn btn-neutral'
|
className='btn btn-neutral'
|
||||||
|
aria-label={t('downloadChat') as string}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
}}
|
}}
|
||||||
|
@ -39,6 +40,7 @@ const DownloadChat = React.memo(
|
||||||
<div className='p-6 border-b border-gray-200 dark:border-gray-600 flex gap-4'>
|
<div className='p-6 border-b border-gray-200 dark:border-gray-600 flex gap-4'>
|
||||||
<button
|
<button
|
||||||
className='btn btn-neutral gap-2'
|
className='btn btn-neutral gap-2'
|
||||||
|
aria-label='image'
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (saveRef && saveRef.current) {
|
if (saveRef && saveRef.current) {
|
||||||
const imgData = await htmlToImg(saveRef.current);
|
const imgData = await htmlToImg(saveRef.current);
|
||||||
|
@ -82,6 +84,7 @@ const DownloadChat = React.memo(
|
||||||
</button> */}
|
</button> */}
|
||||||
<button
|
<button
|
||||||
className='btn btn-neutral gap-2'
|
className='btn btn-neutral gap-2'
|
||||||
|
aria-label='markdown'
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (saveRef && saveRef.current) {
|
if (saveRef && saveRef.current) {
|
||||||
const chats = useStore.getState().chats;
|
const chats = useStore.getState().chats;
|
||||||
|
@ -106,6 +109,7 @@ const DownloadChat = React.memo(
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className='btn btn-neutral gap-2'
|
className='btn btn-neutral gap-2'
|
||||||
|
aria-label='json'
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const chats = useStore.getState().chats;
|
const chats = useStore.getState().chats;
|
||||||
if (chats) {
|
if (chats) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ const CodeBar = React.memo(
|
||||||
<span className=''>{lang}</span>
|
<span className=''>{lang}</span>
|
||||||
<button
|
<button
|
||||||
className='flex ml-auto gap-2'
|
className='flex ml-auto gap-2'
|
||||||
|
aria-label='copy codeblock'
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const codeString = codeRef.current?.textContent;
|
const codeString = codeRef.current?.textContent;
|
||||||
if (codeString)
|
if (codeString)
|
||||||
|
|
|
@ -20,7 +20,6 @@ const CommandPrompt = ({
|
||||||
|
|
||||||
const [dropDown, setDropDown, dropDownRef] = useHideOnOutsideClick();
|
const [dropDown, setDropDown, dropDownRef] = useHideOnOutsideClick();
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dropDown && inputRef.current) {
|
if (dropDown && inputRef.current) {
|
||||||
// When dropdown is visible, focus the input
|
// When dropdown is visible, focus the input
|
||||||
|
@ -44,6 +43,7 @@ const CommandPrompt = ({
|
||||||
<div className='relative max-wd-sm' ref={dropDownRef}>
|
<div className='relative max-wd-sm' ref={dropDownRef}>
|
||||||
<button
|
<button
|
||||||
className='btn btn-neutral btn-small'
|
className='btn btn-neutral btn-small'
|
||||||
|
aria-label='prompt library'
|
||||||
onClick={() => setDropDown(!dropDown)}
|
onClick={() => setDropDown(!dropDown)}
|
||||||
>
|
>
|
||||||
/
|
/
|
||||||
|
|
|
@ -46,7 +46,11 @@ const NewMessageButton = React.memo(
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-0 w-0 relative' key={messageIndex}>
|
<div
|
||||||
|
className='h-0 w-0 relative'
|
||||||
|
key={messageIndex}
|
||||||
|
aria-label='insert message'
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className='absolute top-0 right-0 translate-x-1/2 translate-y-[-50%] text-gray-600 dark:text-white cursor-pointer bg-gray-200 dark:bg-gray-600/80 rounded-full p-1 text-sm hover:bg-gray-300 dark:hover:bg-gray-800/80 transition-bg duration-200'
|
className='absolute top-0 right-0 translate-x-1/2 translate-y-[-50%] text-gray-600 dark:text-white cursor-pointer bg-gray-200 dark:bg-gray-600/80 rounded-full p-1 text-sm hover:bg-gray-300 dark:hover:bg-gray-800/80 transition-bg duration-200'
|
||||||
onClick={addMessage}
|
onClick={addMessage}
|
||||||
|
|
|
@ -28,6 +28,7 @@ const RoleSelector = React.memo(
|
||||||
<div className='prose dark:prose-invert relative'>
|
<div className='prose dark:prose-invert relative'>
|
||||||
<button
|
<button
|
||||||
className='btn btn-neutral btn-small flex gap-1'
|
className='btn btn-neutral btn-small flex gap-1'
|
||||||
|
aria-label={t(role) as string}
|
||||||
type='button'
|
type='button'
|
||||||
onClick={() => setDropDown((prev) => !prev)}
|
onClick={() => setDropDown((prev) => !prev)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,15 +3,18 @@ import React from 'react';
|
||||||
const BaseButton = ({
|
const BaseButton = ({
|
||||||
onClick,
|
onClick,
|
||||||
icon,
|
icon,
|
||||||
|
buttonProps,
|
||||||
}: {
|
}: {
|
||||||
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
icon: React.ReactElement;
|
icon: React.ReactElement;
|
||||||
|
buttonProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className='text-gray-400 flex self-end lg:self-center justify-center gap-3 md:gap-4 visible'>
|
<div className='text-gray-400 flex self-end lg:self-center justify-center gap-3 md:gap-4 visible'>
|
||||||
<button
|
<button
|
||||||
className='p-1 rounded-md hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible'
|
className='p-1 rounded-md hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible'
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
{...buttonProps}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -15,6 +15,7 @@ const CopyButton = ({
|
||||||
return (
|
return (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
icon={isCopied ? <TickIcon /> : <CopyIcon />}
|
icon={isCopied ? <TickIcon /> : <CopyIcon />}
|
||||||
|
buttonProps={{ 'aria-label': 'copy message' }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
onClick(e);
|
onClick(e);
|
||||||
setIsCopied(true);
|
setIsCopied(true);
|
||||||
|
|
|
@ -11,7 +11,11 @@ const DeleteButton = memo(
|
||||||
setIsDelete: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsDelete: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<BaseButton icon={<DeleteIcon />} onClick={() => setIsDelete(true)} />
|
<BaseButton
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
buttonProps={{ 'aria-label': 'delete message' }}
|
||||||
|
onClick={() => setIsDelete(true)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,13 @@ const DownButton = ({
|
||||||
}: {
|
}: {
|
||||||
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
}) => {
|
}) => {
|
||||||
return <BaseButton icon={<DownChevronArrow />} onClick={onClick} />;
|
return (
|
||||||
|
<BaseButton
|
||||||
|
icon={<DownChevronArrow />}
|
||||||
|
buttonProps={{ 'aria-label': 'shift message down' }}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DownButton;
|
export default DownButton;
|
||||||
|
|
|
@ -10,7 +10,13 @@ const EditButton = memo(
|
||||||
}: {
|
}: {
|
||||||
setIsEdit: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsEdit: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
}) => {
|
}) => {
|
||||||
return <BaseButton icon={<EditIcon2 />} onClick={() => setIsEdit(true)} />;
|
return (
|
||||||
|
<BaseButton
|
||||||
|
icon={<EditIcon2 />}
|
||||||
|
buttonProps={{ 'aria-label': 'edit message' }}
|
||||||
|
onClick={() => setIsEdit(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ const MarkdownModeButton = () => {
|
||||||
return (
|
return (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
icon={markdownMode ? <MarkdownIcon /> : <FileTextIcon />}
|
icon={markdownMode ? <MarkdownIcon /> : <FileTextIcon />}
|
||||||
|
buttonProps={{ 'aria-label': 'toggle markdown mode' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setMarkdownMode(!markdownMode);
|
setMarkdownMode(!markdownMode);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -9,7 +9,13 @@ const RefreshButton = ({
|
||||||
}: {
|
}: {
|
||||||
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
}) => {
|
}) => {
|
||||||
return <BaseButton icon={<RefreshIcon />} onClick={onClick} />;
|
return (
|
||||||
|
<BaseButton
|
||||||
|
icon={<RefreshIcon />}
|
||||||
|
buttonProps={{ 'aria-label': 'regenerate message' }}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RefreshButton;
|
export default RefreshButton;
|
||||||
|
|
|
@ -12,6 +12,7 @@ const UpButton = ({
|
||||||
return (
|
return (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
icon={<DownChevronArrow className='rotate-180' />}
|
icon={<DownChevronArrow className='rotate-180' />}
|
||||||
|
buttonProps={{ 'aria-label': 'shift message up' }}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -158,11 +158,16 @@ const ContentView = memo(
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className='p-1 hover:text-white'
|
className='p-1 hover:text-white'
|
||||||
|
aria-label='cancel'
|
||||||
onClick={() => setIsDelete(false)}
|
onClick={() => setIsDelete(false)}
|
||||||
>
|
>
|
||||||
<CrossIcon />
|
<CrossIcon />
|
||||||
</button>
|
</button>
|
||||||
<button className='p-1 hover:text-white' onClick={handleDelete}>
|
<button
|
||||||
|
className='p-1 hover:text-white'
|
||||||
|
aria-label='confirm'
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
<TickIcon />
|
<TickIcon />
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -189,6 +189,7 @@ const EditViewButtons = memo(
|
||||||
generating ? 'cursor-not-allowed opacity-40' : ''
|
generating ? 'cursor-not-allowed opacity-40' : ''
|
||||||
}`}
|
}`}
|
||||||
onClick={handleSaveAndSubmit}
|
onClick={handleSaveAndSubmit}
|
||||||
|
aria-label={t('saveAndSubmit') as string}
|
||||||
>
|
>
|
||||||
<div className='flex items-center justify-center gap-2'>
|
<div className='flex items-center justify-center gap-2'>
|
||||||
{t('saveAndSubmit')}
|
{t('saveAndSubmit')}
|
||||||
|
@ -205,6 +206,7 @@ const EditViewButtons = memo(
|
||||||
: 'btn-primary'
|
: 'btn-primary'
|
||||||
}`}
|
}`}
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
|
aria-label={t('save') as string}
|
||||||
>
|
>
|
||||||
<div className='flex items-center justify-center gap-2'>
|
<div className='flex items-center justify-center gap-2'>
|
||||||
{t('save')}
|
{t('save')}
|
||||||
|
@ -217,6 +219,7 @@ const EditViewButtons = memo(
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
!generating && setIsModalOpen(true);
|
!generating && setIsModalOpen(true);
|
||||||
}}
|
}}
|
||||||
|
aria-label={t('saveAndSubmit') as string}
|
||||||
>
|
>
|
||||||
<div className='flex items-center justify-center gap-2'>
|
<div className='flex items-center justify-center gap-2'>
|
||||||
{t('saveAndSubmit')}
|
{t('saveAndSubmit')}
|
||||||
|
@ -228,6 +231,7 @@ const EditViewButtons = memo(
|
||||||
<button
|
<button
|
||||||
className='btn relative btn-neutral'
|
className='btn relative btn-neutral'
|
||||||
onClick={() => setIsEdit(false)}
|
onClick={() => setIsEdit(false)}
|
||||||
|
aria-label={t('cancel') as string}
|
||||||
>
|
>
|
||||||
<div className='flex items-center justify-center gap-2'>
|
<div className='flex items-center justify-center gap-2'>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
|
|
|
@ -12,6 +12,7 @@ const ScrollToBottomButton = React.memo(() => {
|
||||||
className={`cursor-pointer absolute right-6 bottom-[60px] md:bottom-[60px] z-10 rounded-full border border-gray-200 bg-gray-50 text-gray-600 dark:border-white/10 dark:bg-white/10 dark:text-gray-200 ${
|
className={`cursor-pointer absolute right-6 bottom-[60px] md:bottom-[60px] z-10 rounded-full border border-gray-200 bg-gray-50 text-gray-600 dark:border-white/10 dark:bg-white/10 dark:text-gray-200 ${
|
||||||
atBottom ? 'hidden' : ''
|
atBottom ? 'hidden' : ''
|
||||||
}`}
|
}`}
|
||||||
|
aria-label='scroll to bottom'
|
||||||
onClick={scrollToBottom}
|
onClick={scrollToBottom}
|
||||||
>
|
>
|
||||||
<DownArrow />
|
<DownArrow />
|
||||||
|
|
|
@ -24,7 +24,10 @@ const TextField = () => {
|
||||||
className='m-0 w-full resize-none border-0 bg-transparent p-0 pl-2 pr-7 focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-0'
|
className='m-0 w-full resize-none border-0 bg-transparent p-0 pl-2 pr-7 focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-0'
|
||||||
style={{ maxHeight: '200px', height: '24px', overflowY: 'hidden' }}
|
style={{ maxHeight: '200px', height: '24px', overflowY: 'hidden' }}
|
||||||
></textarea>
|
></textarea>
|
||||||
<button className='absolute p-1 rounded-md text-gray-500 bottom-1.5 right-1 md:bottom-2.5 md:right-2 hover:bg-gray-100 dark:hover:text-gray-400 dark:hover:bg-gray-900 disabled:hover:bg-transparent dark:disabled:hover:bg-transparent'>
|
<button
|
||||||
|
className='absolute p-1 rounded-md text-gray-500 bottom-1.5 right-1 md:bottom-2.5 md:right-2 hover:bg-gray-100 dark:hover:text-gray-400 dark:hover:bg-gray-900 disabled:hover:bg-transparent dark:disabled:hover:bg-transparent'
|
||||||
|
aria-label='submit'
|
||||||
|
>
|
||||||
<SendIcon />
|
<SendIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,7 +20,11 @@ const ChatConfigMenu = () => {
|
||||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button className='btn btn-neutral' onClick={() => setIsModalOpen(true)}>
|
<button
|
||||||
|
className='btn btn-neutral'
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
aria-label={t('defaultChatConfig') as string}
|
||||||
|
>
|
||||||
{t('defaultChatConfig')}
|
{t('defaultChatConfig')}
|
||||||
</button>
|
</button>
|
||||||
{isModalOpen && <ChatConfigPopup setIsModalOpen={setIsModalOpen} />}
|
{isModalOpen && <ChatConfigPopup setIsModalOpen={setIsModalOpen} />}
|
||||||
|
|
|
@ -86,6 +86,7 @@ export const ModelSelector = ({
|
||||||
className='btn btn-neutral btn-small flex gap-1'
|
className='btn btn-neutral btn-small flex gap-1'
|
||||||
type='button'
|
type='button'
|
||||||
onClick={() => setDropDown((prev) => !prev)}
|
onClick={() => setDropDown((prev) => !prev)}
|
||||||
|
aria-label='model'
|
||||||
>
|
>
|
||||||
{_model}
|
{_model}
|
||||||
<DownChevronArrow />
|
<DownChevronArrow />
|
||||||
|
|
|
@ -52,11 +52,19 @@ const GoogleSyncButton = ({ loginHandler }: { loginHandler?: () => void }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex gap-4 flex-wrap justify-center'>
|
<div className='flex gap-4 flex-wrap justify-center'>
|
||||||
<button className='btn btn-primary' onClick={() => login()}>
|
<button
|
||||||
|
className='btn btn-primary'
|
||||||
|
onClick={() => login()}
|
||||||
|
aria-label={t('button.sync') as string}
|
||||||
|
>
|
||||||
{t('button.sync')}
|
{t('button.sync')}
|
||||||
</button>
|
</button>
|
||||||
{cloudSync && (
|
{cloudSync && (
|
||||||
<button className='btn btn-neutral' onClick={logout}>
|
<button
|
||||||
|
className='btn btn-neutral'
|
||||||
|
onClick={logout}
|
||||||
|
aria-label={t('button.stop') as string}
|
||||||
|
>
|
||||||
{t('button.stop')}
|
{t('button.stop')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -26,6 +26,7 @@ const ExportChat = () => {
|
||||||
};
|
};
|
||||||
downloadFile(fileData, getToday());
|
downloadFile(fileData, getToday());
|
||||||
}}
|
}}
|
||||||
|
aria-label={t('export') as string}
|
||||||
>
|
>
|
||||||
{t('export')}
|
{t('export')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -152,6 +152,7 @@ const ImportChat = () => {
|
||||||
<button
|
<button
|
||||||
className='btn btn-small btn-primary mt-3'
|
className='btn btn-small btn-primary mt-3'
|
||||||
onClick={handleFileUpload}
|
onClick={handleFileUpload}
|
||||||
|
aria-label={t('import') as string}
|
||||||
>
|
>
|
||||||
{t('import')}
|
{t('import')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -68,6 +68,7 @@ const ImportChatOpenAI = ({
|
||||||
<button
|
<button
|
||||||
className='btn btn-small btn-primary mt-3'
|
className='btn btn-small btn-primary mt-3'
|
||||||
onClick={handleFileUpload}
|
onClick={handleFileUpload}
|
||||||
|
aria-label={t('import') as string}
|
||||||
>
|
>
|
||||||
{t('import')}
|
{t('import')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -14,6 +14,7 @@ const LanguageSelector = () => {
|
||||||
className='btn btn-neutral btn-small w-36 flex justify-between'
|
className='btn btn-neutral btn-small w-36 flex justify-between'
|
||||||
type='button'
|
type='button'
|
||||||
onClick={() => setDropDown((prev) => !prev)}
|
onClick={() => setDropDown((prev) => !prev)}
|
||||||
|
aria-label='language selector'
|
||||||
>
|
>
|
||||||
{languageCodeToName[i18n.language as keyof typeof languageCodeToName] ??
|
{languageCodeToName[i18n.language as keyof typeof languageCodeToName] ??
|
||||||
i18n.language}
|
i18n.language}
|
||||||
|
|
|
@ -211,10 +211,18 @@ const ChatFolder = ({
|
||||||
>
|
>
|
||||||
{isDelete || isEdit ? (
|
{isDelete || isEdit ? (
|
||||||
<>
|
<>
|
||||||
<button className='p-1 hover:text-white' onClick={handleTick}>
|
<button
|
||||||
|
className='p-1 hover:text-white'
|
||||||
|
onClick={handleTick}
|
||||||
|
aria-label='confirm'
|
||||||
|
>
|
||||||
<TickIcon />
|
<TickIcon />
|
||||||
</button>
|
</button>
|
||||||
<button className='p-1 hover:text-white' onClick={handleCross}>
|
<button
|
||||||
|
className='p-1 hover:text-white'
|
||||||
|
onClick={handleCross}
|
||||||
|
aria-label='cancel'
|
||||||
|
>
|
||||||
<CrossIcon />
|
<CrossIcon />
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
|
@ -229,6 +237,7 @@ const ChatFolder = ({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowPalette((prev) => !prev);
|
setShowPalette((prev) => !prev);
|
||||||
}}
|
}}
|
||||||
|
aria-label='folder color'
|
||||||
>
|
>
|
||||||
<ColorPaletteIcon />
|
<ColorPaletteIcon />
|
||||||
</button>
|
</button>
|
||||||
|
@ -243,12 +252,14 @@ const ChatFolder = ({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateColor(c);
|
updateColor(c);
|
||||||
}}
|
}}
|
||||||
|
aria-label={c}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateColor();
|
updateColor();
|
||||||
}}
|
}}
|
||||||
|
aria-label='default color'
|
||||||
>
|
>
|
||||||
<RefreshIcon />
|
<RefreshIcon />
|
||||||
</button>
|
</button>
|
||||||
|
@ -260,16 +271,22 @@ const ChatFolder = ({
|
||||||
<button
|
<button
|
||||||
className='p-1 hover:text-white md:hidden group-hover/folder:md:inline'
|
className='p-1 hover:text-white md:hidden group-hover/folder:md:inline'
|
||||||
onClick={() => setIsEdit(true)}
|
onClick={() => setIsEdit(true)}
|
||||||
|
aria-label='edit folder title'
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className='p-1 hover:text-white md:hidden group-hover/folder:md:inline'
|
className='p-1 hover:text-white md:hidden group-hover/folder:md:inline'
|
||||||
onClick={() => setIsDelete(true)}
|
onClick={() => setIsDelete(true)}
|
||||||
|
aria-label='delete folder'
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</button>
|
</button>
|
||||||
<button className='p-1 hover:text-white' onClick={toggleExpanded}>
|
<button
|
||||||
|
className='p-1 hover:text-white'
|
||||||
|
onClick={toggleExpanded}
|
||||||
|
aria-label='expand folder'
|
||||||
|
>
|
||||||
<DownChevronArrow
|
<DownChevronArrow
|
||||||
className={`${
|
className={`${
|
||||||
isExpanded ? 'rotate-180' : ''
|
isExpanded ? 'rotate-180' : ''
|
||||||
|
|
|
@ -131,10 +131,18 @@ const ChatHistory = React.memo(
|
||||||
<div className='absolute flex right-1 z-10 text-gray-300 visible'>
|
<div className='absolute flex right-1 z-10 text-gray-300 visible'>
|
||||||
{isDelete || isEdit ? (
|
{isDelete || isEdit ? (
|
||||||
<>
|
<>
|
||||||
<button className='p-1 hover:text-white' onClick={handleTick}>
|
<button
|
||||||
|
className='p-1 hover:text-white'
|
||||||
|
onClick={handleTick}
|
||||||
|
aria-label='confirm'
|
||||||
|
>
|
||||||
<TickIcon />
|
<TickIcon />
|
||||||
</button>
|
</button>
|
||||||
<button className='p-1 hover:text-white' onClick={handleCross}>
|
<button
|
||||||
|
className='p-1 hover:text-white'
|
||||||
|
onClick={handleCross}
|
||||||
|
aria-label='cancel'
|
||||||
|
>
|
||||||
<CrossIcon />
|
<CrossIcon />
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
|
@ -143,12 +151,14 @@ const ChatHistory = React.memo(
|
||||||
<button
|
<button
|
||||||
className='p-1 hover:text-white'
|
className='p-1 hover:text-white'
|
||||||
onClick={() => setIsEdit(true)}
|
onClick={() => setIsEdit(true)}
|
||||||
|
aria-label='edit chat title'
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className='p-1 hover:text-white'
|
className='p-1 hover:text-white'
|
||||||
onClick={() => setIsDelete(true)}
|
onClick={() => setIsDelete(true)}
|
||||||
|
aria-label='delete chat'
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -21,10 +21,12 @@ const ClearConversation = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button className='btn btn-neutral'
|
<button
|
||||||
|
className='btn btn-neutral'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
}}
|
}}
|
||||||
|
aria-label={t('clearConversation') as string}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
{t('clearConversation')}
|
{t('clearConversation')}
|
||||||
|
|
|
@ -29,6 +29,7 @@ const ThemeSwitcher = () => {
|
||||||
<button
|
<button
|
||||||
className='items-center gap-3 btn btn-neutral'
|
className='items-center gap-3 btn btn-neutral'
|
||||||
onClick={switchTheme}
|
onClick={switchTheme}
|
||||||
|
aria-label='toggle dark/light mode'
|
||||||
>
|
>
|
||||||
{theme === 'dark' ? <SunIcon /> : <MoonIcon />}
|
{theme === 'dark' ? <SunIcon /> : <MoonIcon />}
|
||||||
{t(getOppositeTheme(theme) + 'Mode')}
|
{t(getOppositeTheme(theme) + 'Mode')}
|
||||||
|
|
|
@ -27,6 +27,7 @@ const MobileBar = () => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setHideSideMenu(false);
|
setHideSideMenu(false);
|
||||||
}}
|
}}
|
||||||
|
aria-label='open sidebar'
|
||||||
>
|
>
|
||||||
<span className='sr-only'>Open sidebar</span>
|
<span className='sr-only'>Open sidebar</span>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
|
@ -44,6 +45,7 @@ const MobileBar = () => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!generating) addChat();
|
if (!generating) addChat();
|
||||||
}}
|
}}
|
||||||
|
aria-label='new chat'
|
||||||
>
|
>
|
||||||
<PlusIcon className='h-6 w-6' />
|
<PlusIcon className='h-6 w-6' />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -49,6 +49,7 @@ const PopupModal = ({
|
||||||
type='button'
|
type='button'
|
||||||
className='text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white'
|
className='text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white'
|
||||||
onClick={_handleClose}
|
onClick={_handleClose}
|
||||||
|
aria-label='close modal'
|
||||||
>
|
>
|
||||||
<CrossIcon2 />
|
<CrossIcon2 />
|
||||||
</button>
|
</button>
|
||||||
|
@ -70,6 +71,7 @@ const PopupModal = ({
|
||||||
type='button'
|
type='button'
|
||||||
className='btn btn-primary'
|
className='btn btn-primary'
|
||||||
onClick={handleConfirm}
|
onClick={handleConfirm}
|
||||||
|
aria-label='confirm'
|
||||||
>
|
>
|
||||||
{t('confirm')}
|
{t('confirm')}
|
||||||
</button>
|
</button>
|
||||||
|
@ -79,6 +81,7 @@ const PopupModal = ({
|
||||||
type='button'
|
type='button'
|
||||||
className='btn btn-neutral'
|
className='btn btn-neutral'
|
||||||
onClick={_handleClose}
|
onClick={_handleClose}
|
||||||
|
aria-label='cancel'
|
||||||
>
|
>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -17,6 +17,7 @@ const ExportPrompt = () => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
exportPrompts(prompts);
|
exportPrompts(prompts);
|
||||||
}}
|
}}
|
||||||
|
aria-label={t('export') as string}
|
||||||
>
|
>
|
||||||
{t('export')}
|
{t('export')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -63,6 +63,7 @@ const ImportPrompt = () => {
|
||||||
<button
|
<button
|
||||||
className='btn btn-small btn-primary mt-3'
|
className='btn btn-small btn-primary mt-3'
|
||||||
onClick={handleFileUpload}
|
onClick={handleFileUpload}
|
||||||
|
aria-label={t('import') as string}
|
||||||
>
|
>
|
||||||
{t('import')}
|
{t('import')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -15,7 +15,11 @@ const PromptLibraryMenu = () => {
|
||||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button className='btn btn-neutral' onClick={() => setIsModalOpen(true)}>
|
<button
|
||||||
|
className='btn btn-neutral'
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
aria-label={t('promptLibrary') as string}
|
||||||
|
>
|
||||||
{t('promptLibrary')}
|
{t('promptLibrary')}
|
||||||
</button>
|
</button>
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
|
|
|
@ -45,6 +45,7 @@ const ShareGPT = React.memo(() => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
}}
|
}}
|
||||||
|
aria-label={t('postOnShareGPT.title') as string}
|
||||||
>
|
>
|
||||||
{t('postOnShareGPT.title')}
|
{t('postOnShareGPT.title')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -10,7 +10,10 @@ const StopGeneratingButton = () => {
|
||||||
className='absolute bottom-6 left-0 right-0 m-auto flex md:w-full md:m-auto gap-0 md:gap-2 justify-center'
|
className='absolute bottom-6 left-0 right-0 m-auto flex md:w-full md:m-auto gap-0 md:gap-2 justify-center'
|
||||||
onClick={() => setGenerating(false)}
|
onClick={() => setGenerating(false)}
|
||||||
>
|
>
|
||||||
<button className='btn relative btn-neutral border-0 md:border'>
|
<button
|
||||||
|
className='btn relative btn-neutral border-0 md:border'
|
||||||
|
aria-label='stop generating'
|
||||||
|
>
|
||||||
<div className='flex w-full items-center justify-center gap-2'>
|
<div className='flex w-full items-center justify-center gap-2'>
|
||||||
<svg
|
<svg
|
||||||
stroke='currentColor'
|
stroke='currentColor'
|
||||||
|
|
Loading…
Reference in a new issue