From 95a06ff076c637343cc7dc6643fe14244f2c58ce Mon Sep 17 00:00:00 2001 From: xjm Date: Mon, 28 Apr 2025 20:34:52 +0800 Subject: [PATCH] refactor: replace handleStartChat with event-based form validation and chat start --- .../base/chat/chat-with-history/context.tsx | 5 ++-- .../base/chat/chat-with-history/hooks.tsx | 26 +++++++++++++------ .../base/chat/chat-with-history/index.tsx | 10 +++---- .../chat-with-history/inputs-form/index.tsx | 9 ++++--- web/app/components/base/chat/constants.ts | 2 ++ .../base/chat/embedded-chatbot/context.tsx | 5 ++-- .../base/chat/embedded-chatbot/hooks.tsx | 26 +++++++++++++------ .../base/chat/embedded-chatbot/index.tsx | 4 +-- .../embedded-chatbot/inputs-form/index.tsx | 10 ++++--- web/app/components/base/chat/types.ts | 2 ++ 10 files changed, 66 insertions(+), 33 deletions(-) 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 7dd7a78691..1d4525e460 100644 --- a/web/app/components/base/chat/chat-with-history/context.tsx +++ b/web/app/components/base/chat/chat-with-history/context.tsx @@ -5,6 +5,7 @@ import { createContext, useContext } from 'use-context-selector' import type { Callback, ChatConfig, + ChatEventMap, ChatItemInTree, Feedback, } from '../types' @@ -16,6 +17,7 @@ import type { ConversationItem, } from '@/models/share' import { noop } from 'lodash-es' +import type { UseMittReturn } from '@/hooks/use-mitt' export type ChatWithHistoryContextValue = { appInfoError?: any @@ -34,7 +36,6 @@ export type ChatWithHistoryContextValue = { handleNewConversationInputsChange: (v: Record) => void inputsForms: any[] handleNewConversation: () => void - handleStartChat: (callback?: any) => void handleChangeConversation: (conversationId: string) => void handlePinConversation: (conversationId: string) => void handleUnpinConversation: (conversationId: string) => void @@ -57,6 +58,7 @@ export type ChatWithHistoryContextValue = { setIsResponding: (state: boolean) => void, currentConversationInputs: Record | null, setCurrentConversationInputs: (v: Record) => void, + eventEmitter?: UseMittReturn } export const ChatWithHistoryContext = createContext({ @@ -69,7 +71,6 @@ export const ChatWithHistoryContext = createContext handleNewConversationInputsChange: noop, inputsForms: [], handleNewConversation: noop, - handleStartChat: noop, handleChangeConversation: noop, handlePinConversation: noop, handleUnpinConversation: noop, 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 91ceaffd1e..c61769db00 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -12,10 +12,11 @@ import produce from 'immer' import type { Callback, ChatConfig, + ChatEventMap, ChatItem, Feedback, } from '../types' -import { CONVERSATION_ID_INFO } from '../constants' +import { CHAT_FORM_VALIDATE, CHAT_START_EVENT, CONVERSATION_ID_INFO } from '../constants' import { buildChatItemTree, getProcessedSystemVariablesFromUrlParams } from '../utils' import { addFileInfos, sortAgentSorts } from '../../../tools/utils' import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' @@ -43,6 +44,7 @@ import { useAppFavicon } from '@/hooks/use-app-favicon' import { InputVarType } from '@/app/components/workflow/types' import { TransferMethod } from '@/types/app' import { noop } from 'lodash-es' +import { useMitt } from '@/hooks/use-mitt' function getFormattedChatList(messages: any[]) { const newChatList: ChatItem[] = [] @@ -70,9 +72,14 @@ function getFormattedChatList(messages: any[]) { return newChatList } +/** + * only use init context provider + * @returns + */ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo]) const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo) + const eventEmitter = useMitt() useAppFavicon({ enable: !installedAppInfo, @@ -326,12 +333,15 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { return true }, [inputsForms, notify, t]) - const handleStartChat = useCallback((callback: any) => { - if (checkInputsRequired()) { - setShowNewConversationItemInList(true) - callback?.() - } - }, [setShowNewConversationItemInList, checkInputsRequired]) + useEffect(() => { + eventEmitter.useSubscribe(CHAT_FORM_VALIDATE, () => { + if (checkInputsRequired()) { + setShowNewConversationItemInList(true) + eventEmitter.emit(CHAT_START_EVENT) + } + }) + }, [eventEmitter, checkInputsRequired]) + const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: noop }) const handleChangeConversation = useCallback((conversationId: string) => { currentChatInstanceRef.current.handleStop() @@ -470,7 +480,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { handleNewConversationInputsChange, inputsForms, handleNewConversation, - handleStartChat, handleChangeConversation, handlePinConversation, handleUnpinConversation, @@ -491,5 +500,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { setIsResponding, currentConversationInputs, setCurrentConversationInputs, + eventEmitter, } } 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 dfd7bd21a7..4035c751ea 100644 --- a/web/app/components/base/chat/chat-with-history/index.tsx +++ b/web/app/components/base/chat/chat-with-history/index.tsx @@ -20,7 +20,7 @@ import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { checkOrSetAccessToken } from '@/app/components/share/utils' import AppUnavailable from '@/app/components/base/app-unavailable' import cn from '@/utils/classnames' - +import { EventEmitterContextProvider } from '@/context/event-emitter' type ChatWithHistoryProps = { className?: string } @@ -138,7 +138,6 @@ const ChatWithHistoryWrap: FC = ({ handleNewConversationInputsChange, inputsForms, handleNewConversation, - handleStartChat, handleChangeConversation, handlePinConversation, handleUnpinConversation, @@ -159,6 +158,7 @@ const ChatWithHistoryWrap: FC = ({ setIsResponding, currentConversationInputs, setCurrentConversationInputs, + eventEmitter, } = useChatWithHistory(installedAppInfo) return ( @@ -179,7 +179,6 @@ const ChatWithHistoryWrap: FC = ({ handleNewConversationInputsChange, inputsForms, handleNewConversation, - handleStartChat, handleChangeConversation, handlePinConversation, handleUnpinConversation, @@ -202,6 +201,7 @@ const ChatWithHistoryWrap: FC = ({ setIsResponding, currentConversationInputs, setCurrentConversationInputs, + eventEmitter, }}> @@ -242,12 +242,12 @@ const ChatWithHistoryWrapWithCheckToken: FC = ({ if (appUnavailable) return - return ( + return - ) + } export default ChatWithHistoryWrapWithCheckToken diff --git a/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx b/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx index 30ec11c729..c510100a99 100644 --- a/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx +++ b/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx @@ -6,6 +6,7 @@ import Divider from '@/app/components/base/divider' import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content' import { useChatWithHistoryContext } from '../context' import cn from '@/utils/classnames' +import { CHAT_FORM_VALIDATE, CHAT_START_EVENT } from '../../constants' type Props = { collapsed: boolean @@ -20,10 +21,12 @@ const InputsFormNode = ({ const { isMobile, currentConversationId, - handleStartChat, themeBuilder, + eventEmitter, } = useChatWithHistoryContext() - + eventEmitter?.useSubscribe(CHAT_START_EVENT, () => { + setCollapsed(true) + }) return (
handleStartChat(() => setCollapsed(true))} + onClick={() => eventEmitter?.emit(CHAT_FORM_VALIDATE)} style={ themeBuilder?.theme ? { diff --git a/web/app/components/base/chat/constants.ts b/web/app/components/base/chat/constants.ts index 309f0f04a7..639341b617 100644 --- a/web/app/components/base/chat/constants.ts +++ b/web/app/components/base/chat/constants.ts @@ -1,2 +1,4 @@ export const CONVERSATION_ID_INFO = 'conversationIdInfo' export const UUID_NIL = '00000000-0000-0000-0000-000000000000' +export const CHAT_START_EVENT = Symbol('chatStartEvent') +export const CHAT_FORM_VALIDATE = Symbol('chatStartFormValidate') diff --git a/web/app/components/base/chat/embedded-chatbot/context.tsx b/web/app/components/base/chat/embedded-chatbot/context.tsx index fb00dbd64d..17b6327bbc 100644 --- a/web/app/components/base/chat/embedded-chatbot/context.tsx +++ b/web/app/components/base/chat/embedded-chatbot/context.tsx @@ -4,6 +4,7 @@ import type { RefObject } from 'react' import { createContext, useContext } from 'use-context-selector' import type { ChatConfig, + ChatEventMap, ChatItem, Feedback, } from '../types' @@ -15,6 +16,7 @@ import type { ConversationItem, } from '@/models/share' import { noop } from 'lodash-es' +import type { UseMittReturn } from '@/hooks/use-mitt' export type EmbeddedChatbotContextValue = { appInfoError?: any @@ -33,7 +35,6 @@ export type EmbeddedChatbotContextValue = { handleNewConversationInputsChange: (v: Record) => void inputsForms: any[] handleNewConversation: () => void - handleStartChat: (callback?: any) => void handleChangeConversation: (conversationId: string) => void handleNewConversationCompleted: (newConversationId: string) => void chatShouldReloadKey: string @@ -50,6 +51,7 @@ export type EmbeddedChatbotContextValue = { setIsResponding: (state: boolean) => void, currentConversationInputs: Record | null, setCurrentConversationInputs: (v: Record) => void, + eventEmitter?: UseMittReturn } export const EmbeddedChatbotContext = createContext({ @@ -62,7 +64,6 @@ export const EmbeddedChatbotContext = createContext handleNewConversationInputsChange: noop, inputsForms: [], handleNewConversation: noop, - handleStartChat: noop, handleChangeConversation: noop, handleNewConversationCompleted: noop, chatShouldReloadKey: '', diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index 7efbc95dee..edbaf75cef 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -11,10 +11,11 @@ import { useLocalStorageState } from 'ahooks' import produce from 'immer' import type { ChatConfig, + ChatEventMap, ChatItem, Feedback, } from '../types' -import { CONVERSATION_ID_INFO } from '../constants' +import { CHAT_FORM_VALIDATE, CHAT_START_EVENT, CONVERSATION_ID_INFO } from '../constants' import { buildChatItemTree, getProcessedInputsFromUrlParams, getProcessedSystemVariablesFromUrlParams } from '../utils' import { getProcessedFilesFromResponse } from '../../file-uploader/utils' import { @@ -36,6 +37,7 @@ import { InputVarType } from '@/app/components/workflow/types' import { TransferMethod } from '@/types/app' import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils' import { noop } from 'lodash-es' +import { useMitt } from '@/hooks/use-mitt' function getFormattedChatList(messages: any[]) { const newChatList: ChatItem[] = [] @@ -63,9 +65,14 @@ function getFormattedChatList(messages: any[]) { return newChatList } +/** + * only use init context provider + * @returns + */ export const useEmbeddedChatbot = () => { const isInstalledApp = false const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR('appInfo', fetchAppInfo) + const eventEmitter = useMitt() const appData = useMemo(() => { return appInfo @@ -328,12 +335,15 @@ export const useEmbeddedChatbot = () => { return true }, [inputsForms, notify, t]) - const handleStartChat = useCallback((callback?: any) => { - if (checkInputsRequired()) { - setShowNewConversationItemInList(true) - callback?.() - } - }, [setShowNewConversationItemInList, checkInputsRequired]) + useEffect(() => { + eventEmitter.useSubscribe(CHAT_FORM_VALIDATE, () => { + if (checkInputsRequired()) { + setShowNewConversationItemInList(true) + eventEmitter.emit(CHAT_START_EVENT) + } + }) + }, [eventEmitter, checkInputsRequired]) + const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: noop }) const handleChangeConversation = useCallback((conversationId: string) => { currentChatInstanceRef.current.handleStop() @@ -388,7 +398,6 @@ export const useEmbeddedChatbot = () => { handleNewConversationInputsChange, inputsForms, handleNewConversation, - handleStartChat, handleChangeConversation, handleNewConversationCompleted, newConversationId, @@ -397,6 +406,7 @@ export const useEmbeddedChatbot = () => { currentChatInstanceRef, clearChatList, setClearChatList, + eventEmitter, isResponding, setIsResponding, currentConversationInputs, diff --git a/web/app/components/base/chat/embedded-chatbot/index.tsx b/web/app/components/base/chat/embedded-chatbot/index.tsx index 96dff67bf4..08ef11f09c 100644 --- a/web/app/components/base/chat/embedded-chatbot/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/index.tsx @@ -150,7 +150,6 @@ const EmbeddedChatbotWrapper = () => { handleNewConversationInputsChange, inputsForms, handleNewConversation, - handleStartChat, handleChangeConversation, handleNewConversationCompleted, chatShouldReloadKey, @@ -165,6 +164,7 @@ const EmbeddedChatbotWrapper = () => { setIsResponding, currentConversationInputs, setCurrentConversationInputs, + eventEmitter, } = useEmbeddedChatbot() return { handleNewConversationInputsChange, inputsForms, handleNewConversation, - handleStartChat, handleChangeConversation, handleNewConversationCompleted, chatShouldReloadKey, @@ -201,6 +200,7 @@ const EmbeddedChatbotWrapper = () => { setIsResponding, currentConversationInputs, setCurrentConversationInputs, + eventEmitter, }}> diff --git a/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx b/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx index 4ac4aaa16b..55df4e3736 100644 --- a/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx @@ -7,6 +7,8 @@ import InputsFormContent from '@/app/components/base/chat/embedded-chatbot/input import { useEmbeddedChatbotContext } from '../context' import cn from '@/utils/classnames' +import { CHAT_FORM_VALIDATE, CHAT_START_EVENT } from '../../constants' + type Props = { collapsed: boolean setCollapsed: (collapsed: boolean) => void @@ -21,9 +23,11 @@ const InputsFormNode = ({ isMobile, currentConversationId, themeBuilder, - handleStartChat, + eventEmitter, } = useEmbeddedChatbotContext() - + eventEmitter?.useSubscribe(CHAT_START_EVENT, () => { + setCollapsed(false) + }) return (
handleStartChat(() => setCollapsed(true))} + onClick={() => eventEmitter?.emit(CHAT_FORM_VALIDATE)} style={ themeBuilder?.theme ? { diff --git a/web/app/components/base/chat/types.ts b/web/app/components/base/chat/types.ts index 95e52f084e..69438c4d00 100644 --- a/web/app/components/base/chat/types.ts +++ b/web/app/components/base/chat/types.ts @@ -83,3 +83,5 @@ export type Callback = { export type Feedback = { rating: 'like' | 'dislike' | null } + +export type ChatEventMap = Record