refactor: replace handleStartChat with event-based form validation and chat start

pull/19027/head
xjm 1 year ago
parent ff03460e13
commit 95a06ff076

@ -5,6 +5,7 @@ import { createContext, useContext } from 'use-context-selector'
import type { import type {
Callback, Callback,
ChatConfig, ChatConfig,
ChatEventMap,
ChatItemInTree, ChatItemInTree,
Feedback, Feedback,
} from '../types' } from '../types'
@ -16,6 +17,7 @@ import type {
ConversationItem, ConversationItem,
} from '@/models/share' } from '@/models/share'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import type { UseMittReturn } from '@/hooks/use-mitt'
export type ChatWithHistoryContextValue = { export type ChatWithHistoryContextValue = {
appInfoError?: any appInfoError?: any
@ -34,7 +36,6 @@ export type ChatWithHistoryContextValue = {
handleNewConversationInputsChange: (v: Record<string, any>) => void handleNewConversationInputsChange: (v: Record<string, any>) => void
inputsForms: any[] inputsForms: any[]
handleNewConversation: () => void handleNewConversation: () => void
handleStartChat: (callback?: any) => void
handleChangeConversation: (conversationId: string) => void handleChangeConversation: (conversationId: string) => void
handlePinConversation: (conversationId: string) => void handlePinConversation: (conversationId: string) => void
handleUnpinConversation: (conversationId: string) => void handleUnpinConversation: (conversationId: string) => void
@ -57,6 +58,7 @@ export type ChatWithHistoryContextValue = {
setIsResponding: (state: boolean) => void, setIsResponding: (state: boolean) => void,
currentConversationInputs: Record<string, any> | null, currentConversationInputs: Record<string, any> | null,
setCurrentConversationInputs: (v: Record<string, any>) => void, setCurrentConversationInputs: (v: Record<string, any>) => void,
eventEmitter?: UseMittReturn<ChatEventMap>
} }
export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
@ -69,7 +71,6 @@ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>
handleNewConversationInputsChange: noop, handleNewConversationInputsChange: noop,
inputsForms: [], inputsForms: [],
handleNewConversation: noop, handleNewConversation: noop,
handleStartChat: noop,
handleChangeConversation: noop, handleChangeConversation: noop,
handlePinConversation: noop, handlePinConversation: noop,
handleUnpinConversation: noop, handleUnpinConversation: noop,

@ -12,10 +12,11 @@ import produce from 'immer'
import type { import type {
Callback, Callback,
ChatConfig, ChatConfig,
ChatEventMap,
ChatItem, ChatItem,
Feedback, Feedback,
} from '../types' } 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 { buildChatItemTree, getProcessedSystemVariablesFromUrlParams } from '../utils'
import { addFileInfos, sortAgentSorts } from '../../../tools/utils' import { addFileInfos, sortAgentSorts } from '../../../tools/utils'
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/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 { InputVarType } from '@/app/components/workflow/types'
import { TransferMethod } from '@/types/app' import { TransferMethod } from '@/types/app'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import { useMitt } from '@/hooks/use-mitt'
function getFormattedChatList(messages: any[]) { function getFormattedChatList(messages: any[]) {
const newChatList: ChatItem[] = [] const newChatList: ChatItem[] = []
@ -70,9 +72,14 @@ function getFormattedChatList(messages: any[]) {
return newChatList return newChatList
} }
/**
* only use init context provider
* @returns
*/
export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo]) const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo])
const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo) const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo)
const eventEmitter = useMitt<ChatEventMap>()
useAppFavicon({ useAppFavicon({
enable: !installedAppInfo, enable: !installedAppInfo,
@ -326,12 +333,15 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
return true return true
}, [inputsForms, notify, t]) }, [inputsForms, notify, t])
const handleStartChat = useCallback((callback: any) => { useEffect(() => {
eventEmitter.useSubscribe(CHAT_FORM_VALIDATE, () => {
if (checkInputsRequired()) { if (checkInputsRequired()) {
setShowNewConversationItemInList(true) setShowNewConversationItemInList(true)
callback?.() eventEmitter.emit(CHAT_START_EVENT)
} }
}, [setShowNewConversationItemInList, checkInputsRequired]) })
}, [eventEmitter, checkInputsRequired])
const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: noop }) const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: noop })
const handleChangeConversation = useCallback((conversationId: string) => { const handleChangeConversation = useCallback((conversationId: string) => {
currentChatInstanceRef.current.handleStop() currentChatInstanceRef.current.handleStop()
@ -470,7 +480,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
handleNewConversationInputsChange, handleNewConversationInputsChange,
inputsForms, inputsForms,
handleNewConversation, handleNewConversation,
handleStartChat,
handleChangeConversation, handleChangeConversation,
handlePinConversation, handlePinConversation,
handleUnpinConversation, handleUnpinConversation,
@ -491,5 +500,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
setIsResponding, setIsResponding,
currentConversationInputs, currentConversationInputs,
setCurrentConversationInputs, setCurrentConversationInputs,
eventEmitter,
} }
} }

@ -20,7 +20,7 @@ import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { checkOrSetAccessToken } from '@/app/components/share/utils' import { checkOrSetAccessToken } from '@/app/components/share/utils'
import AppUnavailable from '@/app/components/base/app-unavailable' import AppUnavailable from '@/app/components/base/app-unavailable'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { EventEmitterContextProvider } from '@/context/event-emitter'
type ChatWithHistoryProps = { type ChatWithHistoryProps = {
className?: string className?: string
} }
@ -138,7 +138,6 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
handleNewConversationInputsChange, handleNewConversationInputsChange,
inputsForms, inputsForms,
handleNewConversation, handleNewConversation,
handleStartChat,
handleChangeConversation, handleChangeConversation,
handlePinConversation, handlePinConversation,
handleUnpinConversation, handleUnpinConversation,
@ -159,6 +158,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
setIsResponding, setIsResponding,
currentConversationInputs, currentConversationInputs,
setCurrentConversationInputs, setCurrentConversationInputs,
eventEmitter,
} = useChatWithHistory(installedAppInfo) } = useChatWithHistory(installedAppInfo)
return ( return (
@ -179,7 +179,6 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
handleNewConversationInputsChange, handleNewConversationInputsChange,
inputsForms, inputsForms,
handleNewConversation, handleNewConversation,
handleStartChat,
handleChangeConversation, handleChangeConversation,
handlePinConversation, handlePinConversation,
handleUnpinConversation, handleUnpinConversation,
@ -202,6 +201,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
setIsResponding, setIsResponding,
currentConversationInputs, currentConversationInputs,
setCurrentConversationInputs, setCurrentConversationInputs,
eventEmitter,
}}> }}>
<ChatWithHistory className={className} /> <ChatWithHistory className={className} />
</ChatWithHistoryContext.Provider> </ChatWithHistoryContext.Provider>
@ -242,12 +242,12 @@ const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
if (appUnavailable) if (appUnavailable)
return <AppUnavailable isUnknownReason={isUnknownReason} /> return <AppUnavailable isUnknownReason={isUnknownReason} />
return ( return <EventEmitterContextProvider>
<ChatWithHistoryWrap <ChatWithHistoryWrap
installedAppInfo={installedAppInfo} installedAppInfo={installedAppInfo}
className={className} className={className}
/> />
) </EventEmitterContextProvider>
} }
export default ChatWithHistoryWrapWithCheckToken export default ChatWithHistoryWrapWithCheckToken

@ -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 InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content'
import { useChatWithHistoryContext } from '../context' import { useChatWithHistoryContext } from '../context'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { CHAT_FORM_VALIDATE, CHAT_START_EVENT } from '../../constants'
type Props = { type Props = {
collapsed: boolean collapsed: boolean
@ -20,10 +21,12 @@ const InputsFormNode = ({
const { const {
isMobile, isMobile,
currentConversationId, currentConversationId,
handleStartChat,
themeBuilder, themeBuilder,
eventEmitter,
} = useChatWithHistoryContext() } = useChatWithHistoryContext()
eventEmitter?.useSubscribe(CHAT_START_EVENT, () => {
setCollapsed(true)
})
return ( return (
<div className={cn('flex flex-col items-center px-4 pt-6', isMobile && 'pt-4')}> <div className={cn('flex flex-col items-center px-4 pt-6', isMobile && 'pt-4')}>
<div className={cn( <div className={cn(
@ -54,7 +57,7 @@ const InputsFormNode = ({
<Button <Button
variant='primary' variant='primary'
className='w-full' className='w-full'
onClick={() => handleStartChat(() => setCollapsed(true))} onClick={() => eventEmitter?.emit(CHAT_FORM_VALIDATE)}
style={ style={
themeBuilder?.theme themeBuilder?.theme
? { ? {

@ -1,2 +1,4 @@
export const CONVERSATION_ID_INFO = 'conversationIdInfo' export const CONVERSATION_ID_INFO = 'conversationIdInfo'
export const UUID_NIL = '00000000-0000-0000-0000-000000000000' export const UUID_NIL = '00000000-0000-0000-0000-000000000000'
export const CHAT_START_EVENT = Symbol('chatStartEvent')
export const CHAT_FORM_VALIDATE = Symbol('chatStartFormValidate')

@ -4,6 +4,7 @@ import type { RefObject } from 'react'
import { createContext, useContext } from 'use-context-selector' import { createContext, useContext } from 'use-context-selector'
import type { import type {
ChatConfig, ChatConfig,
ChatEventMap,
ChatItem, ChatItem,
Feedback, Feedback,
} from '../types' } from '../types'
@ -15,6 +16,7 @@ import type {
ConversationItem, ConversationItem,
} from '@/models/share' } from '@/models/share'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import type { UseMittReturn } from '@/hooks/use-mitt'
export type EmbeddedChatbotContextValue = { export type EmbeddedChatbotContextValue = {
appInfoError?: any appInfoError?: any
@ -33,7 +35,6 @@ export type EmbeddedChatbotContextValue = {
handleNewConversationInputsChange: (v: Record<string, any>) => void handleNewConversationInputsChange: (v: Record<string, any>) => void
inputsForms: any[] inputsForms: any[]
handleNewConversation: () => void handleNewConversation: () => void
handleStartChat: (callback?: any) => void
handleChangeConversation: (conversationId: string) => void handleChangeConversation: (conversationId: string) => void
handleNewConversationCompleted: (newConversationId: string) => void handleNewConversationCompleted: (newConversationId: string) => void
chatShouldReloadKey: string chatShouldReloadKey: string
@ -50,6 +51,7 @@ export type EmbeddedChatbotContextValue = {
setIsResponding: (state: boolean) => void, setIsResponding: (state: boolean) => void,
currentConversationInputs: Record<string, any> | null, currentConversationInputs: Record<string, any> | null,
setCurrentConversationInputs: (v: Record<string, any>) => void, setCurrentConversationInputs: (v: Record<string, any>) => void,
eventEmitter?: UseMittReturn<ChatEventMap>
} }
export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({
@ -62,7 +64,6 @@ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>
handleNewConversationInputsChange: noop, handleNewConversationInputsChange: noop,
inputsForms: [], inputsForms: [],
handleNewConversation: noop, handleNewConversation: noop,
handleStartChat: noop,
handleChangeConversation: noop, handleChangeConversation: noop,
handleNewConversationCompleted: noop, handleNewConversationCompleted: noop,
chatShouldReloadKey: '', chatShouldReloadKey: '',

@ -11,10 +11,11 @@ import { useLocalStorageState } from 'ahooks'
import produce from 'immer' import produce from 'immer'
import type { import type {
ChatConfig, ChatConfig,
ChatEventMap,
ChatItem, ChatItem,
Feedback, Feedback,
} from '../types' } 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 { buildChatItemTree, getProcessedInputsFromUrlParams, getProcessedSystemVariablesFromUrlParams } from '../utils'
import { getProcessedFilesFromResponse } from '../../file-uploader/utils' import { getProcessedFilesFromResponse } from '../../file-uploader/utils'
import { import {
@ -36,6 +37,7 @@ import { InputVarType } from '@/app/components/workflow/types'
import { TransferMethod } from '@/types/app' import { TransferMethod } from '@/types/app'
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils' import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import { useMitt } from '@/hooks/use-mitt'
function getFormattedChatList(messages: any[]) { function getFormattedChatList(messages: any[]) {
const newChatList: ChatItem[] = [] const newChatList: ChatItem[] = []
@ -63,9 +65,14 @@ function getFormattedChatList(messages: any[]) {
return newChatList return newChatList
} }
/**
* only use init context provider
* @returns
*/
export const useEmbeddedChatbot = () => { export const useEmbeddedChatbot = () => {
const isInstalledApp = false const isInstalledApp = false
const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR('appInfo', fetchAppInfo) const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR('appInfo', fetchAppInfo)
const eventEmitter = useMitt<ChatEventMap>()
const appData = useMemo(() => { const appData = useMemo(() => {
return appInfo return appInfo
@ -328,12 +335,15 @@ export const useEmbeddedChatbot = () => {
return true return true
}, [inputsForms, notify, t]) }, [inputsForms, notify, t])
const handleStartChat = useCallback((callback?: any) => { useEffect(() => {
eventEmitter.useSubscribe(CHAT_FORM_VALIDATE, () => {
if (checkInputsRequired()) { if (checkInputsRequired()) {
setShowNewConversationItemInList(true) setShowNewConversationItemInList(true)
callback?.() eventEmitter.emit(CHAT_START_EVENT)
} }
}, [setShowNewConversationItemInList, checkInputsRequired]) })
}, [eventEmitter, checkInputsRequired])
const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: noop }) const currentChatInstanceRef = useRef<{ handleStop: () => void }>({ handleStop: noop })
const handleChangeConversation = useCallback((conversationId: string) => { const handleChangeConversation = useCallback((conversationId: string) => {
currentChatInstanceRef.current.handleStop() currentChatInstanceRef.current.handleStop()
@ -388,7 +398,6 @@ export const useEmbeddedChatbot = () => {
handleNewConversationInputsChange, handleNewConversationInputsChange,
inputsForms, inputsForms,
handleNewConversation, handleNewConversation,
handleStartChat,
handleChangeConversation, handleChangeConversation,
handleNewConversationCompleted, handleNewConversationCompleted,
newConversationId, newConversationId,
@ -397,6 +406,7 @@ export const useEmbeddedChatbot = () => {
currentChatInstanceRef, currentChatInstanceRef,
clearChatList, clearChatList,
setClearChatList, setClearChatList,
eventEmitter,
isResponding, isResponding,
setIsResponding, setIsResponding,
currentConversationInputs, currentConversationInputs,

@ -150,7 +150,6 @@ const EmbeddedChatbotWrapper = () => {
handleNewConversationInputsChange, handleNewConversationInputsChange,
inputsForms, inputsForms,
handleNewConversation, handleNewConversation,
handleStartChat,
handleChangeConversation, handleChangeConversation,
handleNewConversationCompleted, handleNewConversationCompleted,
chatShouldReloadKey, chatShouldReloadKey,
@ -165,6 +164,7 @@ const EmbeddedChatbotWrapper = () => {
setIsResponding, setIsResponding,
currentConversationInputs, currentConversationInputs,
setCurrentConversationInputs, setCurrentConversationInputs,
eventEmitter,
} = useEmbeddedChatbot() } = useEmbeddedChatbot()
return <EmbeddedChatbotContext.Provider value={{ return <EmbeddedChatbotContext.Provider value={{
@ -184,7 +184,6 @@ const EmbeddedChatbotWrapper = () => {
handleNewConversationInputsChange, handleNewConversationInputsChange,
inputsForms, inputsForms,
handleNewConversation, handleNewConversation,
handleStartChat,
handleChangeConversation, handleChangeConversation,
handleNewConversationCompleted, handleNewConversationCompleted,
chatShouldReloadKey, chatShouldReloadKey,
@ -201,6 +200,7 @@ const EmbeddedChatbotWrapper = () => {
setIsResponding, setIsResponding,
currentConversationInputs, currentConversationInputs,
setCurrentConversationInputs, setCurrentConversationInputs,
eventEmitter,
}}> }}>
<Chatbot /> <Chatbot />
</EmbeddedChatbotContext.Provider> </EmbeddedChatbotContext.Provider>

@ -7,6 +7,8 @@ import InputsFormContent from '@/app/components/base/chat/embedded-chatbot/input
import { useEmbeddedChatbotContext } from '../context' import { useEmbeddedChatbotContext } from '../context'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { CHAT_FORM_VALIDATE, CHAT_START_EVENT } from '../../constants'
type Props = { type Props = {
collapsed: boolean collapsed: boolean
setCollapsed: (collapsed: boolean) => void setCollapsed: (collapsed: boolean) => void
@ -21,9 +23,11 @@ const InputsFormNode = ({
isMobile, isMobile,
currentConversationId, currentConversationId,
themeBuilder, themeBuilder,
handleStartChat, eventEmitter,
} = useEmbeddedChatbotContext() } = useEmbeddedChatbotContext()
eventEmitter?.useSubscribe(CHAT_START_EVENT, () => {
setCollapsed(false)
})
return ( return (
<div className={cn('mb-6 flex flex-col items-center px-4 pt-6', isMobile && 'mb-4 pt-4')}> <div className={cn('mb-6 flex flex-col items-center px-4 pt-6', isMobile && 'mb-4 pt-4')}>
<div className={cn( <div className={cn(
@ -54,7 +58,7 @@ const InputsFormNode = ({
<Button <Button
variant='primary' variant='primary'
className='w-full' className='w-full'
onClick={() => handleStartChat(() => setCollapsed(true))} onClick={() => eventEmitter?.emit(CHAT_FORM_VALIDATE)}
style={ style={
themeBuilder?.theme themeBuilder?.theme
? { ? {

@ -83,3 +83,5 @@ export type Callback = {
export type Feedback = { export type Feedback = {
rating: 'like' | 'dislike' | null rating: 'like' | 'dislike' | null
} }
export type ChatEventMap = Record<symbol, void>

Loading…
Cancel
Save