Closed Dinnnng closed 4 days ago
Hey @Dinnnng! 👋 I'm here to help you with your bug report. I'm a bot and I can assist you with solving bugs, answering questions, and even becoming a contributor.
To resolve the issue where the 'Reset Conversation' button in your embedded chatbot causes incomplete tracing information and incorrect status display, you need to ensure that the context values and state updates are properly handled when the conversation is reset.
Focus on the handleNewConversation
and handleNewConversationCompleted
functions. These functions are likely responsible for resetting the conversation and updating the relevant state.
Here is a possible approach to ensure that the context values are correctly updated:
Ensure handleNewConversation
properly resets the state:
Ensure handleNewConversationCompleted
updates the state correctly:
Here is an example of how you might implement these functions based on the useEmbeddedChatbot
hook:
import { useCallback, useState, useMemo, useRef } from 'react';
import useSWR from 'swr';
import { useLocalStorageState } from 'ahooks';
import produce from 'immer';
import { fetchConversations, fetchChatList, generationConversationName } from '@/service/share';
import { CONVERSATION_ID_INFO } from '../constants';
import { useToastContext } from '@/app/components/base/toast';
export const useEmbeddedChatbot = () => {
const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState<Record<string, string>>(CONVERSATION_ID_INFO, {
defaultValue: {},
});
const [newConversationId, setNewConversationId] = useState('');
const [showNewConversationItemInList, setShowNewConversationItemInList] = useState(false);
const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: () => {} });
const currentConversationId = useMemo(() => conversationIdInfo?.[appId || ''] || '', [appId, conversationIdInfo]);
const handleConversationIdInfoChange = useCallback((changeConversationId: string) => {
if (appId) {
setConversationIdInfo({
...conversationIdInfo,
[appId || '']: changeConversationId,
});
}
}, [appId, conversationIdInfo, setConversationIdInfo]);
const handleNewConversation = useCallback(() => {
currentChatInstanceRef.current.handleStop();
setNewConversationId('');
if (showNewConversationItemInList) {
handleChangeConversation('');
} else if (currentConversationId) {
handleConversationIdInfoChange('');
setShowNewConversationItemInList(true);
}
}, [handleChangeConversation, currentConversationId, handleConversationIdInfoChange, setShowNewConversationItemInList, showNewConversationItemInList]);
const handleNewConversationCompleted = useCallback((newConversationId: string) => {
setNewConversationId(newConversationId);
handleConversationIdInfoChange(newConversationId);
setShowNewConversationItemInList(false);
mutateAppConversationData();
}, [mutateAppConversationData, handleConversationIdInfoChange]);
return {
currentConversationId,
handleNewConversation,
handleNewConversationCompleted,
currentChatInstanceRef,
};
};
Additionally, ensure that the handleNewConversation
function is correctly integrated into the Chatbot
component to handle the reset action:
import {
useEffect,
useState,
} from 'react';
import cn from 'classnames';
import { useAsyncEffect } from 'ahooks';
import {
EmbeddedChatbotContext,
useEmbeddedChatbotContext,
} from './context';
import { useEmbeddedChatbot } from './hooks';
import { isDify } from './utils';
import { useThemeContext } from './theme/theme-context';
import { checkOrSetAccessToken } from '@/app/components/share/utils';
import AppUnavailable from '@/app/components/base/app-unavailable';
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints';
import Loading from '@/app/components/base/loading';
import LogoHeader from '@/app/components/base/logo/logo-embeded-chat-header';
import Header from '@/app/components/base/chat/embedded-chatbot/header';
import ConfigPanel from '@/app/components/base/chat/embedded-chatbot/config-panel';
import ChatWrapper from '@/app/components/base/chat/embedded-chatbot/chat-wrapper';
const Chatbot = () => {
const {
isMobile,
appInfoError,
appInfoLoading,
appData,
appPrevChatList,
showConfigPanelBeforeChat,
appChatListDataLoading,
handleNewConversation,
themeBuilder,
} = useEmbeddedChatbotContext();
const chatReady = (!showConfigPanelBeforeChat || !!appPrevChatList.length);
const customConfig = appData?.custom_config;
const site = appData?.site;
const difyIcon = <LogoHeader />;
useEffect(() => {
themeBuilder?.buildTheme(site?.chat_color_theme, site?.chat_color_theme_inverted);
if (site) {
if (customConfig)
document.title = `${site.title}`;
else
document.title = `${site.title} - Powered by Dify`;
}
}, [site, customConfig]);
if (appInfoLoading) {
return (
<Loading type='app' />
);
}
if (appInfoError) {
return (
<AppUnavailable />
);
}
return (
<div>
<Header
isMobile={isMobile}
title={site?.title || ''}
customerIcon={isDify() ? difyIcon : ''}
theme={themeBuilder?.theme}
onCreateNewChat={handleNewConversation}
/>
<div className='flex bg-white overflow-hidden'>
<div className={cn('h-[100vh] grow flex flex-col overflow-y-auto', isMobile && '!h-[calc(100vh_-_3rem)]')}>
{showConfigPanelBeforeChat && !appChatListDataLoading && !appPrevChatList.length && (
<div className={cn('flex w-full items-center justify-center h-full tablet:px-4', isMobile && 'px-4')}>
<ConfigPanel />
</div>
)}
{appChatListDataLoading && chatReady && (
<Loading type='app' />
)}
{chatReady && !appChatListDataLoading && (
<ChatWrapper />
)}
</div>
</div>
</div>
);
};
const EmbeddedChatbotWrapper = () => {
const media = useBreakpoints();
const isMobile = media === MediaType.mobile;
const themeBuilder = useThemeContext();
const {
appInfoError,
appInfoLoading,
appData,
appParams,
appMeta,
appChatListDataLoading,
currentConversationId,
currentConversationItem,
appPrevChatList,
pinnedConversationList,
conversationList,
showConfigPanelBeforeChat,
newConversationInputs,
handleNewConversationInputsChange,
inputsForms,
handleNewConversation,
handleStartChat,
handleChangeConversation,
handleNewConversationCompleted,
chatShouldReloadKey,
isInstalledApp,
appId,
handleFeedback,
currentChatInstanceRef,
} = useEmbeddedChatbot();
return <EmbeddedChatbotContext.Provider value={{
appInfoError,
appInfoLoading,
appData,
appParams,
appMeta,
appChatListDataLoading,
currentConversationId,
currentConversationItem,
appPrevChatList,
pinnedConversationList,
conversationList,
showConfigPanelBeforeChat,
newConversationInputs,
handleNewConversationInputsChange,
inputsForms,
handleNewConversation,
handleStartChat,
handleChangeConversation,
handleNewConversationCompleted,
chatShouldReloadKey,
isMobile,
isInstalledApp,
appId,
handleFeedback,
currentChatInstanceRef,
themeBuilder,
}}>
<Chatbot />
</EmbeddedChatbotContext.Provider>;
};
const EmbeddedChatbot = () => {
const [initialized, setInitialized] = useState(false);
const [appUnavailable, setAppUnavailable] = useState<boolean>(false);
const [isUnknownReason, setIsUnknownReason] = useState<boolean>(false);
useAsyncEffect(async () => {
if (!initialized) {
try {
await checkOrSetAccessToken();
}
catch (e: any) {
if (e.status === 404) {
setAppUnavailable(true);
}
else {
setIsUnknownReason(true);
setAppUnavailable(true);
}
}
setInitialized(true);
}
}, []);
if (!initialized)
return null;
if (appUnavailable)
return <AppUnavailable isUnknownReason={isUnknownReason} />;
return <EmbeddedChatbotWrapper />;
};
export default EmbeddedChatbot;
Make sure to replace the placeholder logic with actual state management code as per your application's requirements. This should help in maintaining complete tracing information and correct status display when the 'Reset Conversation' button is used [1][2][3].
To continue talking to Dosu, mention @dosu.
Self Checks
Dify version
0.6.11
Cloud or Self Hosted
Cloud, Self Hosted (Docker), Self Hosted (Source)
Steps to reproduce
When running a workflow on the embedded chatbot and clicking the 'Reset Conversation' icon, it results in incomplete workflow information for the previously reset conversation in the user's backend.
The current issue has been identified as follows:
The frontend and backend of Flask utilize the SSE (Server-Sent Events) protocol.
When the frontend request is aborted, the backend thread running the workflow is also terminated (blocked), causing the workflow information from the previous conversation to be unrecorded in the database.
This leads to strange logs in the user's dashboard, showing the workflow stopping at arbitrary nodes without proceeding further. and the status is still showing 'running'
Moreover, the workflow task thread completes its execution, but this completion is not reflected in the user's backend. This situation is unfavorable for both billing purposes and the user experience, as it does not accurately represent the workflow's progress.
`
api/core/app/apps/advanced_chat/app_generator.py
`
✔️ Expected Behavior
Normal tracing information, as well as the display of interrupted or reset statuses.
❌ Actual Behavior