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 4a3e292f80..55d938d1fa 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 @@ -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 ( +
+
+ +
+ + +
+
+
+ ) + } 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 = (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} @@ -227,6 +260,7 @@ const ChatWrapper = () => { switchSibling={siblingMessageId => setTargetMessageId(siblingMessageId)} inputDisabled={inputDisabled} isMobile={isMobile} + sidebarCollapseState={sidebarCollapseState} /> ) 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 389658c42e..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 @@ -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 = () => {
{isSidebarCollapsed && ( - - - + +
+ + + +
+
)}
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 466e3cef2a..bff742fa9c 100644 --- a/web/app/components/base/chat/chat-with-history/index.tsx +++ b/web/app/components/base/chat/chat-with-history/index.tsx @@ -82,7 +82,7 @@ const ChatWithHistory: FC = ({ {isMobile && ( )} -
+
{isSidebarCollapsed && (
= ({
)} -
+
{!isMobile &&
} {appChatListDataLoading && ( @@ -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/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index 9e29d28433..a2371abe44 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -110,7 +110,7 @@ const Answer: 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/chat/index.tsx b/web/app/components/base/chat/chat/index.tsx index 3745e03653..d26e81005d 100644 --- a/web/app/components/base/chat/chat/index.tsx +++ b/web/app/components/base/chat/chat/index.tsx @@ -72,6 +72,7 @@ export type ChatProps = { noSpacing?: boolean inputDisabled?: boolean isMobile?: boolean + sidebarCollapseState?: boolean } const Chat: FC = ({ @@ -110,6 +111,7 @@ const Chat: FC = ({ noSpacing, inputDisabled, isMobile, + sidebarCollapseState, }) => { const { t } = useTranslation() const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({ @@ -193,6 +195,11 @@ const Chat: FC = ({ } }, []) + 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 = ({
{ @@ -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 +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} 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; } diff --git a/web/i18n/en-US/share-app.ts b/web/i18n/en-US/share-app.ts index b700225621..3db0e98f99 100644 --- a/web/i18n/en-US/share-app.ts +++ b/web/i18n/en-US/share-app.ts @@ -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', diff --git a/web/i18n/zh-Hans/share-app.ts b/web/i18n/zh-Hans/share-app.ts index 0f1f14e363..bfd17ef7a3 100644 --- a/web/i18n/zh-Hans/share-app.ts +++ b/web/i18n/zh-Hans/share-app.ts @@ -6,6 +6,7 @@ const translation = { }, chat: { newChat: '开启新对话', + newChatTip: '已在新对话中', chatSettingsTitle: '新对话设置', chatFormTip: '对话开始后,对话设置将无法修改。', pinnedTitle: '已置顶',