diff --git a/src/components/ApiMenu/ApiMenu.tsx b/src/components/ApiMenu/ApiMenu.tsx index a927944ee..aedc3c5d6 100644 --- a/src/components/ApiMenu/ApiMenu.tsx +++ b/src/components/ApiMenu/ApiMenu.tsx @@ -2,13 +2,11 @@ import React, { useEffect, useState } from 'react'; import { useTranslation, Trans } from 'react-i18next'; import useStore from '@store/store'; -import useHideOnOutsideClick from '@hooks/useHideOnOutsideClick'; - import PopupModal from '@components/PopupModal'; import { availableEndpoints, defaultAPIEndpoint } from '@constants/auth'; -import DownChevronArrow from '@icon/DownChevronArrow'; +import DropDown from '@components/DropDown'; const ApiMenu = ({ setIsModalOpen, @@ -144,44 +142,16 @@ const ApiEndpointSelector = ({ _apiEndpoint: string; _setApiEndpoint: React.Dispatch>; }) => { - const [dropDown, setDropDown, dropDownRef] = useHideOnOutsideClick(); - return (
- - + ({ value: e, label: e }))} + dropDownStyles={['w-32', 'w-full']} + onClick={(value) => { + _setApiEndpoint(value); + }} + />
); }; diff --git a/src/components/Chat/ChatContent/ChatContent.tsx b/src/components/Chat/ChatContent/ChatContent.tsx index 1671db444..2de426563 100644 --- a/src/components/Chat/ChatContent/ChatContent.tsx +++ b/src/components/Chat/ChatContent/ChatContent.tsx @@ -64,16 +64,19 @@ const ChatContent = () => { )} {messages?.map((message, index) => ( - (advancedMode || index !== 0 || message.role !== 'system') && ( - - - {!generating && advancedMode && } - - ) + + + {!generating && advancedMode && ( + + )} + ))} @@ -81,6 +84,7 @@ const ChatContent = () => { role={inputRole} content='' messageIndex={stickyIndex} + generating={generating} sticky /> {error !== '' && ( @@ -105,7 +109,7 @@ const ChatContent = () => { : 'md:max-w-3xl lg:max-w-3xl xl:max-w-4xl' }`} > - {useStore.getState().generating || ( + {generating || (
diff --git a/src/components/Chat/ChatContent/Message/Button/BaseButton.tsx b/src/components/Chat/ChatContent/Message/Button/BaseButton.tsx new file mode 100644 index 000000000..6cae8909f --- /dev/null +++ b/src/components/Chat/ChatContent/Message/Button/BaseButton.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +const BaseButton = ({ + onClick, + icon, + disabled = false, +}: { + onClick: React.MouseEventHandler; + icon: React.ReactElement; + disabled?: boolean; +}) => { + return ( +
+ +
+ ); +}; + +export default BaseButton; diff --git a/src/components/Chat/ChatContent/Message/Button/LeftButton.tsx b/src/components/Chat/ChatContent/Message/Button/LeftButton.tsx new file mode 100644 index 000000000..269a28fb5 --- /dev/null +++ b/src/components/Chat/ChatContent/Message/Button/LeftButton.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import DownChevronArrow from '@icon/DownChevronArrow'; + +import BaseButton from './BaseButton'; + +const LeftButton = ({ + disabled, + onClick, +}: { + disabled: boolean; + onClick: React.MouseEventHandler; +}) => { + return ( + } + disabled={disabled} + onClick={onClick} + /> + ); +}; + +export default LeftButton; diff --git a/src/components/Chat/ChatContent/Message/Button/RightButton.tsx b/src/components/Chat/ChatContent/Message/Button/RightButton.tsx new file mode 100644 index 000000000..064aabd75 --- /dev/null +++ b/src/components/Chat/ChatContent/Message/Button/RightButton.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import DownChevronArrow from '@icon/DownChevronArrow'; + +import BaseButton from './BaseButton'; + +const RightButton = ({ + disabled, + onClick, +}: { + disabled: boolean; + onClick: React.MouseEventHandler; +}) => { + return ( + + } + disabled={disabled} + onClick={onClick} + /> + ); +}; + +export default RightButton; diff --git a/src/components/Chat/ChatContent/Message/CommandPrompt/CommandPrompt.tsx b/src/components/Chat/ChatContent/Message/CommandPrompt/CommandPrompt.tsx index e61ba00f9..9328dbe71 100644 --- a/src/components/Chat/ChatContent/Message/CommandPrompt/CommandPrompt.tsx +++ b/src/components/Chat/ChatContent/Message/CommandPrompt/CommandPrompt.tsx @@ -40,7 +40,7 @@ const CommandPrompt = ({ }, [prompts]); return ( -
+
}> - -
- ); - } -); -export default RoleSelector; diff --git a/src/components/Chat/ChatContent/Message/View/Button/BaseButton.tsx b/src/components/Chat/ChatContent/Message/View/Button/BaseButton.tsx deleted file mode 100644 index cba121180..000000000 --- a/src/components/Chat/ChatContent/Message/View/Button/BaseButton.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -const BaseButton = ({ - onClick, - icon, - buttonProps, -}: { - onClick: React.MouseEventHandler; - icon: React.ReactElement; - buttonProps?: React.ButtonHTMLAttributes; -}) => { - return ( -
- -
- ); -}; - -export default BaseButton; diff --git a/src/components/Chat/ChatContent/Message/View/Button/CopyButton.tsx b/src/components/Chat/ChatContent/Message/View/Button/CopyButton.tsx index 6d2f1772f..884373b3e 100644 --- a/src/components/Chat/ChatContent/Message/View/Button/CopyButton.tsx +++ b/src/components/Chat/ChatContent/Message/View/Button/CopyButton.tsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import TickIcon from '@icon/TickIcon'; import CopyIcon from '@icon/CopyIcon'; -import BaseButton from './BaseButton'; +import BaseButton from '../../Button/BaseButton'; const CopyButton = ({ onClick, diff --git a/src/components/Chat/ChatContent/Message/View/Button/DeleteButton.tsx b/src/components/Chat/ChatContent/Message/View/Button/DeleteButton.tsx index 235e6d1a4..ad0d1be6f 100644 --- a/src/components/Chat/ChatContent/Message/View/Button/DeleteButton.tsx +++ b/src/components/Chat/ChatContent/Message/View/Button/DeleteButton.tsx @@ -2,7 +2,7 @@ import React, { memo } from 'react'; import DeleteIcon from '@icon/DeleteIcon'; -import BaseButton from './BaseButton'; +import BaseButton from '../../Button/BaseButton'; const DeleteButton = memo( ({ diff --git a/src/components/Chat/ChatContent/Message/View/Button/DownButton.tsx b/src/components/Chat/ChatContent/Message/View/Button/DownButton.tsx index b42d836d4..63476eb36 100644 --- a/src/components/Chat/ChatContent/Message/View/Button/DownButton.tsx +++ b/src/components/Chat/ChatContent/Message/View/Button/DownButton.tsx @@ -2,7 +2,7 @@ import React from 'react'; import DownChevronArrow from '@icon/DownChevronArrow'; -import BaseButton from './BaseButton'; +import BaseButton from '../../Button/BaseButton'; const DownButton = ({ onClick, diff --git a/src/components/Chat/ChatContent/Message/View/Button/EditButton.tsx b/src/components/Chat/ChatContent/Message/View/Button/EditButton.tsx index a1715e95f..b401c6ae1 100644 --- a/src/components/Chat/ChatContent/Message/View/Button/EditButton.tsx +++ b/src/components/Chat/ChatContent/Message/View/Button/EditButton.tsx @@ -2,7 +2,7 @@ import React, { memo } from 'react'; import EditIcon2 from '@icon/EditIcon2'; -import BaseButton from './BaseButton'; +import BaseButton from '../../Button/BaseButton'; const EditButton = memo( ({ diff --git a/src/components/Chat/ChatContent/Message/View/Button/MarkdownModeButton.tsx b/src/components/Chat/ChatContent/Message/View/Button/MarkdownModeButton.tsx index 60d6cbbb4..feb5b126f 100644 --- a/src/components/Chat/ChatContent/Message/View/Button/MarkdownModeButton.tsx +++ b/src/components/Chat/ChatContent/Message/View/Button/MarkdownModeButton.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import useStore from '@store/store'; -import BaseButton from './BaseButton'; +import BaseButton from '../../Button/BaseButton'; import MarkdownIcon from '@icon/MarkdownIcon'; import FileTextIcon from '@icon/FileTextIcon'; diff --git a/src/components/Chat/ChatContent/Message/View/Button/RefreshButton.tsx b/src/components/Chat/ChatContent/Message/View/Button/RefreshButton.tsx index a270cd4a5..3c319a104 100644 --- a/src/components/Chat/ChatContent/Message/View/Button/RefreshButton.tsx +++ b/src/components/Chat/ChatContent/Message/View/Button/RefreshButton.tsx @@ -2,7 +2,7 @@ import React from 'react'; import RefreshIcon from '@icon/RefreshIcon'; -import BaseButton from './BaseButton'; +import BaseButton from '../../Button/BaseButton'; const RefreshButton = ({ onClick, diff --git a/src/components/Chat/ChatContent/Message/View/Button/UpButton.tsx b/src/components/Chat/ChatContent/Message/View/Button/UpButton.tsx index e8470d97e..c38e64948 100644 --- a/src/components/Chat/ChatContent/Message/View/Button/UpButton.tsx +++ b/src/components/Chat/ChatContent/Message/View/Button/UpButton.tsx @@ -2,7 +2,7 @@ import React from 'react'; import DownChevronArrow from '@icon/DownChevronArrow'; -import BaseButton from './BaseButton'; +import BaseButton from '../../Button/BaseButton'; const UpButton = ({ onClick, diff --git a/src/components/Chat/ChatContent/Message/View/ContentView.tsx b/src/components/Chat/ChatContent/Message/View/ContentView.tsx index ad4c415a9..1ef6de8c3 100644 --- a/src/components/Chat/ChatContent/Message/View/ContentView.tsx +++ b/src/components/Chat/ChatContent/Message/View/ContentView.tsx @@ -5,6 +5,8 @@ import React, { useState, } from 'react'; +import { useTranslation } from 'react-i18next'; + import ReactMarkdown from 'react-markdown'; import { CodeProps, ReactMarkdownProps } from 'react-markdown/lib/ast-to-react'; @@ -23,6 +25,8 @@ import { ChatInterface } from '@type/chat'; import { codeLanguageSubset } from '@constants/chat'; +import PopupModal from '@components/PopupModal'; + import RefreshButton from './Button/RefreshButton'; import UpButton from './Button/UpButton'; import DownButton from './Button/DownButton'; @@ -48,6 +52,7 @@ const ContentView = memo( const { handleSubmit } = useSubmit(); const [isDelete, setIsDelete] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); const currentChatIndex = useStore((state) => state.currentChatIndex); const setChats = useStore((state) => state.setChats); @@ -57,6 +62,8 @@ const ContentView = memo( const inlineLatex = useStore((state) => state.inlineLatex); const markdownMode = useStore((state) => state.markdownMode); + const { t } = useTranslation(); + const handleDelete = () => { const updatedChats: ChatInterface[] = JSON.parse( JSON.stringify(useStore.getState().chats) @@ -89,14 +96,42 @@ const ContentView = memo( handleMove('down'); }; - const handleRefresh = () => { + const handleRefresh = (force: boolean = false) => { const updatedChats: ChatInterface[] = JSON.parse( JSON.stringify(useStore.getState().chats) ); const updatedMessages = updatedChats[currentChatIndex].messages; - updatedMessages.splice(updatedMessages.length - 1, 1); + + let subsequentMessages = updatedMessages.reduce( + (result: number[], _, index: number) => { + if (index > messageIndex) { + result.push(index); + } + return result; + }, + [] + ); + + const originalMessages = updatedChats[currentChatIndex].messages.map( + (message) => ({ ...message }) + ); + + if (subsequentMessages.length) { + if (force) { + for (let i of subsequentMessages.sort((a, b) => b - a)) { + updatedChats[currentChatIndex].messages.splice(i, 1); + } + } else { + setIsModalOpen(true); + return; + } + } + setChats(updatedChats); - handleSubmit(); + setIsModalOpen(false); + + // We specify that this is a regeneration and pass the original chat state in case of errors + handleSubmit({ regeneration: true, originalMessages }); }; const handleCopy = () => { @@ -140,8 +175,8 @@ const ContentView = memo( <> {!useStore.getState().generating && role === 'assistant' && - messageIndex === lastMessageIndex && ( - + messageIndex == lastMessageIndex && ( + handleRefresh()} /> )} {messageIndex !== 0 && } {messageIndex !== lastMessageIndex && ( @@ -173,6 +208,14 @@ const ContentView = memo( )}
+ {isModalOpen && ( + handleRefresh(true)} + /> + )} ); } diff --git a/src/components/DropDown/DropDown.tsx b/src/components/DropDown/DropDown.tsx new file mode 100644 index 000000000..919bc0577 --- /dev/null +++ b/src/components/DropDown/DropDown.tsx @@ -0,0 +1,77 @@ +import React, { ReactNode } from 'react'; +import useDropDown from '@hooks/useDropDown'; + +import DownChevronArrow from '@icon/DownChevronArrow'; + +type Selection = { value: string; label: string; }; + +const DropDown = ({ + selected, + selections, + onClick, + dropDownStyles, + icon = , +}: { + selected: string | ReactNode | React.ReactElement; + selections: Selection[]; + onClick: (value: string) => void; + icon?: React.ReactElement; + dropDownStyles?: string[]; +}) => { + const { + buttonRef, + dropDown, + setDropDown, + dropDownRef, + openDirection, + setCheckPosition, + } = useDropDown(); + + return ( +
+ +
} + id='dropdown' + className={`${ + dropDown ? '' : 'hidden' + } absolute z-10 bg-white text-gray-800 group opacity-90 border-b border-black/10 ${ + openDirection === 'down' ? 'top-full' : 'bottom-full' + } rounded-lg shadow-xl dark:border-gray-900/50 dark:text-gray-100 dark:bg-gray-800 ${ + dropDownStyles && dropDownStyles.join(' ') + }`} + > +
    + {selections.map((r) => ( +
  • { + onClick(r.value); + setDropDown(false); + }} + key={r.value} + > + {r.label} +
  • + ))} +
+
+
+ ); +}; + +export default DropDown; diff --git a/src/components/DropDown/index.ts b/src/components/DropDown/index.ts new file mode 100644 index 000000000..d76df1b0f --- /dev/null +++ b/src/components/DropDown/index.ts @@ -0,0 +1 @@ +export { default } from './DropDown'; diff --git a/src/components/Menu/ChatFolder.tsx b/src/components/Menu/ChatFolder.tsx index 42b144c29..c1ad17f65 100644 --- a/src/components/Menu/ChatFolder.tsx +++ b/src/components/Menu/ChatFolder.tsx @@ -230,7 +230,7 @@ const ChatFolder = ({ <>
} >