diff --git a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx index 3b32367512..17133b29de 100644 --- a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx +++ b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx @@ -20,6 +20,7 @@ import { 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 = () => { @@ -41,6 +42,9 @@ const ChatWrapper = () => { appData, themeBuilder, sidebarCollapseState, + clearChatList, + setClearChatList, + setIsResponding, } = useChatWithHistoryContext() const appConfig = useMemo(() => { const config = appParams || {} @@ -60,7 +64,7 @@ const ChatWrapper = () => { setTargetMessageId, handleSend, handleStop, - isResponding, + isResponding: respondingState, suggestedQuestions, } = useChat( appConfig, @@ -70,6 +74,8 @@ const ChatWrapper = () => { }, appPrevChatTree, taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId), + clearChatList, + setClearChatList, ) const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputsRef?.current const inputDisabled = useMemo(() => { @@ -110,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, @@ -146,10 +156,10 @@ const ChatWrapper = () => { }, [chatList, doSend]) const messageList = useMemo(() => { - if (currentConversationId || isResponding) + if (currentConversationId) return chatList return chatList.filter(item => !item.isOpeningStatement) - }, [chatList, currentConversationId, isResponding]) + }, [chatList, currentConversationId]) const [collapsed, setCollapsed] = useState(!!currentConversationId) @@ -168,7 +178,7 @@ const ChatWrapper = () => { const welcome = useMemo(() => { const welcomeMessage = chatList.find(item => item.isOpeningStatement) - if (isResponding) + if (respondingState) return null if (currentConversationId) return null @@ -188,7 +198,7 @@ const ChatWrapper = () => { imageUrl={appData?.site.icon_url} />
- {welcomeMessage.content} +
@@ -204,10 +214,12 @@ const ChatWrapper = () => { background={appData?.site.icon_background} imageUrl={appData?.site.icon_url} /> -
{welcomeMessage.content}
+
+ +
) - }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, isResponding]) + }, [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) ? { 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} diff --git a/web/app/components/base/chat/chat-with-history/context.tsx b/web/app/components/base/chat/chat-with-history/context.tsx index 73e3d1398d..ed8c27e841 100644 --- a/web/app/components/base/chat/chat-with-history/context.tsx +++ b/web/app/components/base/chat/chat-with-history/context.tsx @@ -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({ @@ -77,5 +81,9 @@ export const ChatWithHistoryContext = createContext currentChatInstanceRef: { current: { handleStop: () => {} } }, sidebarCollapseState: false, handleSidebarCollapse: () => {}, + clearChatList: false, + setClearChatList: () => {}, + isResponding: false, + setIsResponding: () => {}, }) export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext) diff --git a/web/app/components/base/chat/chat-with-history/header/index.tsx b/web/app/components/base/chat/chat-with-history/header/index.tsx index f0e4d085b9..22a2b65f9c 100644 --- a/web/app/components/base/chat/chat-with-history/header/index.tsx +++ b/web/app/components/base/chat/chat-with-history/header/index.tsx @@ -33,6 +33,7 @@ const Header = () => { handleNewConversation, sidebarCollapseState, handleSidebarCollapse, + isResponding, } = useChatWithHistoryContext() const { t } = useTranslation() const isSidebarCollapsed = sidebarCollapseState @@ -111,7 +112,12 @@ const Header = () => { popupContent={t('share.chat.newChatTip')} >
- +
diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx index dab7a7fd14..7b6780761a 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -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, } } diff --git a/web/app/components/base/chat/chat-with-history/index.tsx b/web/app/components/base/chat/chat-with-history/index.tsx index ec7121e404..bff742fa9c 100644 --- a/web/app/components/base/chat/chat-with-history/index.tsx +++ b/web/app/components/base/chat/chat-with-history/index.tsx @@ -153,6 +153,10 @@ const ChatWithHistoryWrap: FC = ({ currentChatInstanceRef, sidebarCollapseState, handleSidebarCollapse, + clearChatList, + setClearChatList, + isResponding, + setIsResponding, } = useChatWithHistory(installedAppInfo) return ( @@ -190,6 +194,10 @@ const ChatWithHistoryWrap: FC = ({ themeBuilder, sidebarCollapseState, handleSidebarCollapse, + clearChatList, + setClearChatList, + isResponding, + setIsResponding, }}> diff --git a/web/app/components/base/chat/chat-with-history/sidebar/index.tsx b/web/app/components/base/chat/chat-with-history/sidebar/index.tsx index a1fe28d4a0..9c29647e41 100644 --- a/web/app/components/base/chat/chat-with-history/sidebar/index.tsx +++ b/web/app/components/base/chat/chat-with-history/sidebar/index.tsx @@ -41,6 +41,7 @@ const Sidebar = ({ isPanel }: Props) => { sidebarCollapseState, handleSidebarCollapse, isMobile, + isResponding, } = useChatWithHistoryContext() const isSidebarCollapsed = sidebarCollapseState @@ -105,7 +106,7 @@ const Sidebar = ({ isPanel }: Props) => { )}
- diff --git a/web/app/components/base/chat/chat/answer/operation.tsx b/web/app/components/base/chat/chat/answer/operation.tsx index 60565c5cfb..70db75dca3 100644 --- a/web/app/components/base/chat/chat/answer/operation.tsx +++ b/web/app/components/base/chat/chat/answer/operation.tsx @@ -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 = ({ {!noChatInput && ( onRegenerate?.(item)}> - + )} {(config?.supportAnnotation && config.annotation_reply?.enabled) && ( diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index 473dc42a0b..eb48f9515b 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -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, diff --git a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx index 67a2b37a86..483f7dcd0e 100644 --- a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx +++ b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx @@ -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 ( +
+
+ +
+ + +
+
+
+ ) + } return (
{ background={appData?.site.icon_background} imageUrl={appData?.site.icon_url} /> -
{welcomeMessage.content}
+
+ +
) - }, [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() ? @@ -203,10 +236,10 @@ const ChatWrapper = () => { appData={appData} config={appConfig} chatList={messageList} - isResponding={isResponding} + 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} diff --git a/web/app/components/base/chat/embedded-chatbot/context.tsx b/web/app/components/base/chat/embedded-chatbot/context.tsx index b84fced04b..4f344bd841 100644 --- a/web/app/components/base/chat/embedded-chatbot/context.tsx +++ b/web/app/components/base/chat/embedded-chatbot/context.tsx @@ -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({ @@ -62,5 +66,9 @@ export const EmbeddedChatbotContext = createContext isInstalledApp: false, handleFeedback: () => {}, currentChatInstanceRef: { current: { handleStop: () => {} } }, + clearChatList: false, + setClearChatList: () => {}, + isResponding: false, + setIsResponding: () => {}, }) export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext) diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index 7934d6c8d3..2ee0f57aa2 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -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, } } diff --git a/web/app/components/base/chat/embedded-chatbot/index.tsx b/web/app/components/base/chat/embedded-chatbot/index.tsx index a01637d869..3c3bb88e2e 100644 --- a/web/app/components/base/chat/embedded-chatbot/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/index.tsx @@ -156,6 +156,10 @@ const EmbeddedChatbotWrapper = () => { appId, handleFeedback, currentChatInstanceRef, + clearChatList, + setClearChatList, + isResponding, + setIsResponding, } = useEmbeddedChatbot() return { handleFeedback, currentChatInstanceRef, themeBuilder, + clearChatList, + setClearChatList, + isResponding, + setIsResponding, }}> diff --git a/web/app/styles/markdown.scss b/web/app/styles/markdown.scss index faffdff3d2..12ddeb1622 100644 --- a/web/app/styles/markdown.scss +++ b/web/app/styles/markdown.scss @@ -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; }