| @@ -19,6 +19,8 @@ import { | |||
| } from '@/service/share' | |||
| import AppIcon from '@/app/components/base/app-icon' | |||
| import AnswerIcon from '@/app/components/base/answer-icon' | |||
| import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions' | |||
| import { Markdown } from '@/app/components/base/markdown' | |||
| import cn from '@/utils/classnames' | |||
| const ChatWrapper = () => { | |||
| @@ -39,6 +41,10 @@ const ChatWrapper = () => { | |||
| currentChatInstanceRef, | |||
| appData, | |||
| themeBuilder, | |||
| sidebarCollapseState, | |||
| clearChatList, | |||
| setClearChatList, | |||
| setIsResponding, | |||
| } = useChatWithHistoryContext() | |||
| const appConfig = useMemo(() => { | |||
| const config = appParams || {} | |||
| @@ -58,7 +64,7 @@ const ChatWrapper = () => { | |||
| setTargetMessageId, | |||
| handleSend, | |||
| handleStop, | |||
| isResponding, | |||
| isResponding: respondingState, | |||
| suggestedQuestions, | |||
| } = useChat( | |||
| appConfig, | |||
| @@ -68,6 +74,8 @@ const ChatWrapper = () => { | |||
| }, | |||
| appPrevChatTree, | |||
| taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId), | |||
| clearChatList, | |||
| setClearChatList, | |||
| ) | |||
| const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputsRef?.current | |||
| const inputDisabled = useMemo(() => { | |||
| @@ -108,6 +116,10 @@ const ChatWrapper = () => { | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, []) | |||
| useEffect(() => { | |||
| setIsResponding(respondingState) | |||
| }, [respondingState, setIsResponding]) | |||
| const doSend: OnSend = useCallback((message, files, isRegenerate = false, parentAnswer: ChatItem | null = null) => { | |||
| const data: any = { | |||
| query: message, | |||
| @@ -166,12 +178,33 @@ const ChatWrapper = () => { | |||
| const welcome = useMemo(() => { | |||
| const welcomeMessage = chatList.find(item => item.isOpeningStatement) | |||
| if (respondingState) | |||
| return null | |||
| if (currentConversationId) | |||
| return null | |||
| if (!welcomeMessage) | |||
| return null | |||
| if (!collapsed && inputsForms.length > 0) | |||
| return null | |||
| if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) { | |||
| return ( | |||
| <div className='h-[50vh] py-12 px-4 flex items-center justify-center'> | |||
| <div className='grow max-w-[720px] flex gap-4'> | |||
| <AppIcon | |||
| size='xl' | |||
| iconType={appData?.site.icon_type} | |||
| icon={appData?.site.icon} | |||
| background={appData?.site.icon_background} | |||
| imageUrl={appData?.site.icon_url} | |||
| /> | |||
| <div className='grow px-4 py-3 bg-chat-bubble-bg text-text-primary rounded-2xl body-lg-regular'> | |||
| <Markdown content={welcomeMessage.content} /> | |||
| <SuggestedQuestions item={welcomeMessage} /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ) | |||
| } | |||
| return ( | |||
| <div className={cn('h-[50vh] py-12 flex flex-col items-center justify-center gap-3')}> | |||
| <AppIcon | |||
| @@ -181,10 +214,10 @@ const ChatWrapper = () => { | |||
| background={appData?.site.icon_background} | |||
| imageUrl={appData?.site.icon_url} | |||
| /> | |||
| <div className='text-text-tertiary body-2xl-regular'>{welcomeMessage.content}</div> | |||
| <Markdown className='!text-text-tertiary !body-2xl-regular' content={welcomeMessage.content} /> | |||
| </div> | |||
| ) | |||
| }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length]) | |||
| }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState]) | |||
| const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon) | |||
| ? <AnswerIcon | |||
| @@ -203,10 +236,10 @@ const ChatWrapper = () => { | |||
| appData={appData} | |||
| config={appConfig} | |||
| chatList={messageList} | |||
| isResponding={isResponding} | |||
| chatContainerInnerClassName={`mx-auto pt-6 w-full max-w-[720px] ${isMobile && 'px-4'}`} | |||
| isResponding={respondingState} | |||
| chatContainerInnerClassName={`mx-auto pt-6 w-full max-w-[768px] ${isMobile && 'px-4'}`} | |||
| chatFooterClassName='pb-4' | |||
| chatFooterInnerClassName={`mx-auto w-full max-w-[720px] ${isMobile ? 'px-2' : 'px-4'}`} | |||
| chatFooterInnerClassName={`mx-auto w-full max-w-[768px] ${isMobile ? 'px-2' : 'px-4'}`} | |||
| onSend={doSend} | |||
| inputs={currentConversationId ? currentConversationItem?.inputs as any : newConversationInputs} | |||
| inputsForm={inputsForms} | |||
| @@ -227,6 +260,7 @@ const ChatWrapper = () => { | |||
| switchSibling={siblingMessageId => setTargetMessageId(siblingMessageId)} | |||
| inputDisabled={inputDisabled} | |||
| isMobile={isMobile} | |||
| sidebarCollapseState={sidebarCollapseState} | |||
| /> | |||
| </div> | |||
| ) | |||
| @@ -50,6 +50,10 @@ export type ChatWithHistoryContextValue = { | |||
| themeBuilder?: ThemeBuilder | |||
| sidebarCollapseState?: boolean | |||
| handleSidebarCollapse: (state: boolean) => void | |||
| clearChatList?: boolean | |||
| setClearChatList: (state: boolean) => void | |||
| isResponding?: boolean | |||
| setIsResponding: (state: boolean) => void, | |||
| } | |||
| export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({ | |||
| @@ -77,5 +81,9 @@ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue> | |||
| currentChatInstanceRef: { current: { handleStop: () => {} } }, | |||
| sidebarCollapseState: false, | |||
| handleSidebarCollapse: () => {}, | |||
| clearChatList: false, | |||
| setClearChatList: () => {}, | |||
| isResponding: false, | |||
| setIsResponding: () => {}, | |||
| }) | |||
| export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext) | |||
| @@ -9,7 +9,7 @@ import { | |||
| useChatWithHistoryContext, | |||
| } from '../context' | |||
| import Operation from './operation' | |||
| import ActionButton from '@/app/components/base/action-button' | |||
| import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' | |||
| import AppIcon from '@/app/components/base/app-icon' | |||
| import Tooltip from '@/app/components/base/tooltip' | |||
| import ViewFormDropdown from '@/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown' | |||
| @@ -33,6 +33,7 @@ const Header = () => { | |||
| handleNewConversation, | |||
| sidebarCollapseState, | |||
| handleSidebarCollapse, | |||
| isResponding, | |||
| } = useChatWithHistoryContext() | |||
| const { t } = useTranslation() | |||
| const isSidebarCollapsed = sidebarCollapseState | |||
| @@ -106,9 +107,21 @@ const Header = () => { | |||
| <div className='h-[14px] w-px bg-divider-regular'></div> | |||
| </div> | |||
| {isSidebarCollapsed && ( | |||
| <ActionButton size='l' onClick={handleNewConversation}> | |||
| <RiEditBoxLine className='w-[18px] h-[18px]' /> | |||
| </ActionButton> | |||
| <Tooltip | |||
| disabled={!!currentConversationId} | |||
| popupContent={t('share.chat.newChatTip')} | |||
| > | |||
| <div> | |||
| <ActionButton | |||
| size='l' | |||
| state={(!currentConversationId || isResponding) ? ActionButtonState.Disabled : ActionButtonState.Default} | |||
| disabled={!currentConversationId || isResponding} | |||
| onClick={handleNewConversation} | |||
| > | |||
| <RiEditBoxLine className='w-[18px] h-[18px]' /> | |||
| </ActionButton> | |||
| </div> | |||
| </Tooltip> | |||
| )} | |||
| </div> | |||
| <div className='flex items-center gap-1'> | |||
| @@ -150,6 +150,8 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { | |||
| const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations(isInstalledApp, appId, undefined, false, 100)) | |||
| const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey, isInstalledApp, appId)) | |||
| const [clearChatList, setClearChatList] = useState(false) | |||
| const [isResponding, setIsResponding] = useState(false) | |||
| const appPrevChatTree = useMemo( | |||
| () => (currentConversationId && appChatListData?.data.length) | |||
| ? buildChatItemTree(getFormattedChatList(appChatListData.data)) | |||
| @@ -310,20 +312,16 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { | |||
| currentChatInstanceRef.current.handleStop() | |||
| setNewConversationId('') | |||
| handleConversationIdInfoChange(conversationId) | |||
| }, [handleConversationIdInfoChange]) | |||
| if (conversationId) | |||
| setClearChatList(false) | |||
| }, [handleConversationIdInfoChange, setClearChatList]) | |||
| const handleNewConversation = useCallback(() => { | |||
| currentChatInstanceRef.current.handleStop() | |||
| setNewConversationId('') | |||
| if (showNewConversationItemInList) { | |||
| handleChangeConversation('') | |||
| } | |||
| else if (currentConversationId) { | |||
| handleConversationIdInfoChange('') | |||
| setShowNewConversationItemInList(true) | |||
| handleNewConversationInputsChange({}) | |||
| } | |||
| }, [handleChangeConversation, currentConversationId, handleConversationIdInfoChange, setShowNewConversationItemInList, showNewConversationItemInList, handleNewConversationInputsChange]) | |||
| setShowNewConversationItemInList(true) | |||
| handleChangeConversation('') | |||
| handleNewConversationInputsChange({}) | |||
| setClearChatList(true) | |||
| }, [handleChangeConversation, setShowNewConversationItemInList, handleNewConversationInputsChange, setClearChatList]) | |||
| const handleUpdateConversationList = useCallback(() => { | |||
| mutateAppConversationData() | |||
| mutateAppPinnedConversationData() | |||
| @@ -462,5 +460,9 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { | |||
| currentChatInstanceRef, | |||
| sidebarCollapseState, | |||
| handleSidebarCollapse, | |||
| clearChatList, | |||
| setClearChatList, | |||
| isResponding, | |||
| setIsResponding, | |||
| } | |||
| } | |||
| @@ -82,7 +82,7 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({ | |||
| {isMobile && ( | |||
| <HeaderInMobile /> | |||
| )} | |||
| <div className={cn('relative grow p-2')}> | |||
| <div className={cn('relative grow p-2', isMobile && 'h-[calc(100%_-_56px)] p-0')}> | |||
| {isSidebarCollapsed && ( | |||
| <div | |||
| className={cn( | |||
| @@ -95,7 +95,7 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({ | |||
| <Sidebar isPanel /> | |||
| </div> | |||
| )} | |||
| <div className='h-full flex flex-col bg-chatbot-bg rounded-2xl border-[0,5px] border-components-panel-border-subtle overflow-hidden'> | |||
| <div className={cn('h-full flex flex-col bg-chatbot-bg border-[0,5px] border-components-panel-border-subtle overflow-hidden', isMobile ? 'rounded-t-2xl' : 'rounded-2xl')}> | |||
| {!isMobile && <Header />} | |||
| {appChatListDataLoading && ( | |||
| <Loading type='app' /> | |||
| @@ -153,6 +153,10 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({ | |||
| currentChatInstanceRef, | |||
| sidebarCollapseState, | |||
| handleSidebarCollapse, | |||
| clearChatList, | |||
| setClearChatList, | |||
| isResponding, | |||
| setIsResponding, | |||
| } = useChatWithHistory(installedAppInfo) | |||
| return ( | |||
| @@ -190,6 +194,10 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({ | |||
| themeBuilder, | |||
| sidebarCollapseState, | |||
| handleSidebarCollapse, | |||
| clearChatList, | |||
| setClearChatList, | |||
| isResponding, | |||
| setIsResponding, | |||
| }}> | |||
| <ChatWithHistory className={className} /> | |||
| </ChatWithHistoryContext.Provider> | |||
| @@ -41,6 +41,7 @@ const Sidebar = ({ isPanel }: Props) => { | |||
| sidebarCollapseState, | |||
| handleSidebarCollapse, | |||
| isMobile, | |||
| isResponding, | |||
| } = useChatWithHistoryContext() | |||
| const isSidebarCollapsed = sidebarCollapseState | |||
| @@ -105,7 +106,7 @@ const Sidebar = ({ isPanel }: Props) => { | |||
| )} | |||
| </div> | |||
| <div className='shrink-0 px-3 py-4'> | |||
| <Button variant='secondary-accent' className='w-full justify-center' onClick={handleNewConversation}> | |||
| <Button variant='secondary-accent' disabled={isResponding} className='w-full justify-center' onClick={handleNewConversation}> | |||
| <RiEditBoxLine className='w-4 h-4 mr-1' /> | |||
| {t('share.chat.newChat')} | |||
| </Button> | |||
| @@ -110,7 +110,7 @@ const Answer: FC<AnswerProps> = ({ | |||
| </div> | |||
| )} | |||
| </div> | |||
| <div className='chat-answer-container group grow w-0 ml-4' ref={containerRef}> | |||
| <div className='chat-answer-container group grow w-0 ml-4 pb-4' ref={containerRef}> | |||
| <div className={cn('group relative pr-10', chatAnswerContainerInner)}> | |||
| <div | |||
| ref={contentRef} | |||
| @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next' | |||
| import { | |||
| RiClipboardLine, | |||
| RiEditLine, | |||
| RiReplay15Line, | |||
| RiResetLeftLine, | |||
| RiThumbDownLine, | |||
| RiThumbUpLine, | |||
| } from '@remixicon/react' | |||
| @@ -130,7 +130,7 @@ const Operation: FC<OperationProps> = ({ | |||
| </ActionButton> | |||
| {!noChatInput && ( | |||
| <ActionButton onClick={() => onRegenerate?.(item)}> | |||
| <RiReplay15Line className='w-4 h-4' /> | |||
| <RiResetLeftLine className='w-4 h-4' /> | |||
| </ActionButton> | |||
| )} | |||
| {(config?.supportAnnotation && config.annotation_reply?.enabled) && ( | |||
| @@ -51,6 +51,8 @@ export const useChat = ( | |||
| }, | |||
| prevChatTree?: ChatItemInTree[], | |||
| stopChat?: (taskId: string) => void, | |||
| clearChatList?: boolean, | |||
| clearChatListCallback?: (state: boolean) => void, | |||
| ) => { | |||
| const { t } = useTranslation() | |||
| const { formatTime } = useTimestamp() | |||
| @@ -90,7 +92,7 @@ export const useChat = ( | |||
| } | |||
| else { | |||
| ret.unshift({ | |||
| id: `${Date.now()}`, | |||
| id: 'opening-statement', | |||
| content: getIntroduction(config.opening_statement), | |||
| isAnswer: true, | |||
| isOpeningStatement: true, | |||
| @@ -163,12 +165,13 @@ export const useChat = ( | |||
| suggestedQuestionsAbortControllerRef.current.abort() | |||
| }, [stopChat, handleResponding]) | |||
| const handleRestart = useCallback(() => { | |||
| const handleRestart = useCallback((cb?: any) => { | |||
| conversationId.current = '' | |||
| taskIdRef.current = '' | |||
| handleStop() | |||
| setChatTree([]) | |||
| setSuggestQuestions([]) | |||
| cb?.() | |||
| }, [handleStop]) | |||
| const updateCurrentQAOnTree = useCallback(({ | |||
| @@ -682,6 +685,11 @@ export const useChat = ( | |||
| }) | |||
| }, [chatList, updateChatTreeNode]) | |||
| useEffect(() => { | |||
| if (clearChatList) | |||
| handleRestart(() => clearChatListCallback?.(false)) | |||
| }, [clearChatList, clearChatListCallback, handleRestart]) | |||
| return { | |||
| chatList, | |||
| setTargetMessageId, | |||
| @@ -72,6 +72,7 @@ export type ChatProps = { | |||
| noSpacing?: boolean | |||
| inputDisabled?: boolean | |||
| isMobile?: boolean | |||
| sidebarCollapseState?: boolean | |||
| } | |||
| const Chat: FC<ChatProps> = ({ | |||
| @@ -110,6 +111,7 @@ const Chat: FC<ChatProps> = ({ | |||
| noSpacing, | |||
| inputDisabled, | |||
| isMobile, | |||
| sidebarCollapseState, | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({ | |||
| @@ -193,6 +195,11 @@ const Chat: FC<ChatProps> = ({ | |||
| } | |||
| }, []) | |||
| useEffect(() => { | |||
| if (!sidebarCollapseState) | |||
| setTimeout(() => handleWindowResize(), 200) | |||
| }, [sidebarCollapseState]) | |||
| const hasTryToAsk = config?.suggested_questions_after_answer?.enabled && !!suggestedQuestions?.length && onSend | |||
| return ( | |||
| @@ -255,7 +262,7 @@ const Chat: FC<ChatProps> = ({ | |||
| </div> | |||
| </div> | |||
| <div | |||
| className={`absolute bottom-0 bg-chat-input-mask ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`} | |||
| className={`absolute bottom-0 bg-chat-input-mask flex justify-center ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`} | |||
| ref={chatFooterRef} | |||
| > | |||
| <div | |||
| @@ -21,6 +21,8 @@ import { | |||
| import AppIcon from '@/app/components/base/app-icon' | |||
| import LogoAvatar from '@/app/components/base/logo/logo-embedded-chat-avatar' | |||
| import AnswerIcon from '@/app/components/base/answer-icon' | |||
| import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions' | |||
| import { Markdown } from '@/app/components/base/markdown' | |||
| import cn from '@/utils/classnames' | |||
| const ChatWrapper = () => { | |||
| @@ -41,6 +43,9 @@ const ChatWrapper = () => { | |||
| handleFeedback, | |||
| currentChatInstanceRef, | |||
| themeBuilder, | |||
| clearChatList, | |||
| setClearChatList, | |||
| setIsResponding, | |||
| } = useEmbeddedChatbotContext() | |||
| const appConfig = useMemo(() => { | |||
| const config = appParams || {} | |||
| @@ -60,7 +65,7 @@ const ChatWrapper = () => { | |||
| setTargetMessageId, | |||
| handleSend, | |||
| handleStop, | |||
| isResponding, | |||
| isResponding: respondingState, | |||
| suggestedQuestions, | |||
| } = useChat( | |||
| appConfig, | |||
| @@ -70,6 +75,8 @@ const ChatWrapper = () => { | |||
| }, | |||
| appPrevChatList, | |||
| taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId), | |||
| clearChatList, | |||
| setClearChatList, | |||
| ) | |||
| const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputsRef?.current | |||
| const inputDisabled = useMemo(() => { | |||
| @@ -108,6 +115,9 @@ const ChatWrapper = () => { | |||
| if (currentChatInstanceRef.current) | |||
| currentChatInstanceRef.current.handleStop = handleStop | |||
| }, [currentChatInstanceRef, handleStop]) | |||
| useEffect(() => { | |||
| setIsResponding(respondingState) | |||
| }, [respondingState, setIsResponding]) | |||
| const doSend: OnSend = useCallback((message, files, isRegenerate = false, parentAnswer: ChatItem | null = null) => { | |||
| const data: any = { | |||
| @@ -167,12 +177,33 @@ const ChatWrapper = () => { | |||
| const welcome = useMemo(() => { | |||
| const welcomeMessage = chatList.find(item => item.isOpeningStatement) | |||
| if (respondingState) | |||
| return null | |||
| if (currentConversationId) | |||
| return null | |||
| if (!welcomeMessage) | |||
| return null | |||
| if (!collapsed && inputsForms.length > 0) | |||
| return null | |||
| if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) { | |||
| return ( | |||
| <div className='h-[50vh] py-12 px-4 flex items-center justify-center'> | |||
| <div className='grow max-w-[720px] flex gap-4'> | |||
| <AppIcon | |||
| size='xl' | |||
| iconType={appData?.site.icon_type} | |||
| icon={appData?.site.icon} | |||
| background={appData?.site.icon_background} | |||
| imageUrl={appData?.site.icon_url} | |||
| /> | |||
| <div className='grow px-4 py-3 bg-chat-bubble-bg text-text-primary rounded-2xl body-lg-regular'> | |||
| <Markdown content={welcomeMessage.content} /> | |||
| <SuggestedQuestions item={welcomeMessage} /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ) | |||
| } | |||
| return ( | |||
| <div className={cn('h-[50vh] py-12 flex flex-col items-center justify-center gap-3')}> | |||
| <AppIcon | |||
| @@ -182,10 +213,10 @@ const ChatWrapper = () => { | |||
| background={appData?.site.icon_background} | |||
| imageUrl={appData?.site.icon_url} | |||
| /> | |||
| <div className='text-text-tertiary body-2xl-regular'>{welcomeMessage.content}</div> | |||
| <Markdown className='!text-text-tertiary !body-2xl-regular' content={welcomeMessage.content} /> | |||
| </div> | |||
| ) | |||
| }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length]) | |||
| }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState]) | |||
| const answerIcon = isDify() | |||
| ? <LogoAvatar className='relative shrink-0' /> | |||
| @@ -203,10 +234,10 @@ const ChatWrapper = () => { | |||
| appData={appData} | |||
| config={appConfig} | |||
| chatList={messageList} | |||
| isResponding={isResponding} | |||
| chatContainerInnerClassName={cn('mx-auto w-full max-w-full tablet:px-4', isMobile && 'px-4')} | |||
| isResponding={respondingState} | |||
| chatContainerInnerClassName={cn('mx-auto w-full max-w-full pt-4 tablet:px-4', isMobile && 'px-4')} | |||
| chatFooterClassName={cn('pb-4', !isMobile && 'rounded-b-2xl')} | |||
| chatFooterInnerClassName={cn('mx-auto w-full max-w-full tablet:px-4', isMobile && 'px-2')} | |||
| chatFooterInnerClassName={cn('mx-auto w-full max-w-full px-4', isMobile && 'px-2')} | |||
| onSend={doSend} | |||
| inputs={currentConversationId ? currentConversationItem?.inputs as any : newConversationInputs} | |||
| inputsForm={inputsForms} | |||
| @@ -42,6 +42,10 @@ export type EmbeddedChatbotContextValue = { | |||
| handleFeedback: (messageId: string, feedback: Feedback) => void | |||
| currentChatInstanceRef: RefObject<{ handleStop: () => void }> | |||
| themeBuilder?: ThemeBuilder | |||
| clearChatList?: boolean | |||
| setClearChatList: (state: boolean) => void | |||
| isResponding?: boolean | |||
| setIsResponding: (state: boolean) => void, | |||
| } | |||
| export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({ | |||
| @@ -62,5 +66,9 @@ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue> | |||
| isInstalledApp: false, | |||
| handleFeedback: () => {}, | |||
| currentChatInstanceRef: { current: { handleStop: () => {} } }, | |||
| clearChatList: false, | |||
| setClearChatList: () => {}, | |||
| isResponding: false, | |||
| setIsResponding: () => {}, | |||
| }) | |||
| export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext) | |||
| @@ -103,6 +103,8 @@ export const useEmbeddedChatbot = () => { | |||
| const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations(isInstalledApp, appId, undefined, false, 100)) | |||
| const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey, isInstalledApp, appId)) | |||
| const [clearChatList, setClearChatList] = useState(false) | |||
| const [isResponding, setIsResponding] = useState(false) | |||
| const appPrevChatList = useMemo( | |||
| () => (currentConversationId && appChatListData?.data.length) | |||
| ? buildChatItemTree(getFormattedChatList(appChatListData.data)) | |||
| @@ -283,20 +285,16 @@ export const useEmbeddedChatbot = () => { | |||
| currentChatInstanceRef.current.handleStop() | |||
| setNewConversationId('') | |||
| handleConversationIdInfoChange(conversationId) | |||
| }, [handleConversationIdInfoChange]) | |||
| if (conversationId) | |||
| setClearChatList(false) | |||
| }, [handleConversationIdInfoChange, setClearChatList]) | |||
| const handleNewConversation = useCallback(() => { | |||
| currentChatInstanceRef.current.handleStop() | |||
| setNewConversationId('') | |||
| if (showNewConversationItemInList) { | |||
| handleChangeConversation('') | |||
| } | |||
| else if (currentConversationId) { | |||
| handleConversationIdInfoChange('') | |||
| setShowNewConversationItemInList(true) | |||
| handleNewConversationInputsChange({}) | |||
| } | |||
| }, [handleChangeConversation, currentConversationId, handleConversationIdInfoChange, setShowNewConversationItemInList, showNewConversationItemInList, handleNewConversationInputsChange]) | |||
| setShowNewConversationItemInList(true) | |||
| handleChangeConversation('') | |||
| handleNewConversationInputsChange({}) | |||
| setClearChatList(true) | |||
| }, [handleChangeConversation, setShowNewConversationItemInList, handleNewConversationInputsChange, setClearChatList]) | |||
| const handleNewConversationCompleted = useCallback((newConversationId: string) => { | |||
| setNewConversationId(newConversationId) | |||
| @@ -342,5 +340,9 @@ export const useEmbeddedChatbot = () => { | |||
| chatShouldReloadKey, | |||
| handleFeedback, | |||
| currentChatInstanceRef, | |||
| clearChatList, | |||
| setClearChatList, | |||
| isResponding, | |||
| setIsResponding, | |||
| } | |||
| } | |||
| @@ -156,6 +156,10 @@ const EmbeddedChatbotWrapper = () => { | |||
| appId, | |||
| handleFeedback, | |||
| currentChatInstanceRef, | |||
| clearChatList, | |||
| setClearChatList, | |||
| isResponding, | |||
| setIsResponding, | |||
| } = useEmbeddedChatbot() | |||
| return <EmbeddedChatbotContext.Provider value={{ | |||
| @@ -185,6 +189,10 @@ const EmbeddedChatbotWrapper = () => { | |||
| handleFeedback, | |||
| currentChatInstanceRef, | |||
| themeBuilder, | |||
| clearChatList, | |||
| setClearChatList, | |||
| isResponding, | |||
| setIsResponding, | |||
| }}> | |||
| <Chatbot /> | |||
| </EmbeddedChatbotContext.Provider> | |||
| @@ -213,7 +213,7 @@ | |||
| display: block; | |||
| width: max-content; | |||
| max-width: 100%; | |||
| overflow: hidden; | |||
| overflow: auto; | |||
| border: 1px solid var(--color-divider-regular); | |||
| border-radius: 8px; | |||
| } | |||
| @@ -6,6 +6,7 @@ const translation = { | |||
| }, | |||
| chat: { | |||
| newChat: 'Start New chat', | |||
| newChatTip: 'Already in a new chat', | |||
| chatSettingsTitle: 'New chat setup', | |||
| chatFormTip: 'Chat settings cannot be modified after the chat has started.', | |||
| pinnedTitle: 'Pinned', | |||
| @@ -6,6 +6,7 @@ const translation = { | |||
| }, | |||
| chat: { | |||
| newChat: '开启新对话', | |||
| newChatTip: '已在新对话中', | |||
| chatSettingsTitle: '新对话设置', | |||
| chatFormTip: '对话开始后,对话设置将无法修改。', | |||
| pinnedTitle: '已置顶', | |||