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 {
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<string, any>) => 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<string, any> | null,
setCurrentConversationInputs: (v: Record<string, any>) => void,
eventEmitter?: UseMittReturn<ChatEventMap>
}
export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
@ -69,7 +71,6 @@ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>
handleNewConversationInputsChange: noop,
inputsForms: [],
handleNewConversation: noop,
handleStartChat: noop,
handleChangeConversation: noop,
handlePinConversation: noop,
handleUnpinConversation: noop,

@ -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<ChatEventMap>()
useAppFavicon({
enable: !installedAppInfo,
@ -326,12 +333,15 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
return true
}, [inputsForms, notify, t])
const handleStartChat = useCallback((callback: any) => {
useEffect(() => {
eventEmitter.useSubscribe(CHAT_FORM_VALIDATE, () => {
if (checkInputsRequired()) {
setShowNewConversationItemInList(true)
callback?.()
eventEmitter.emit(CHAT_START_EVENT)
}
}, [setShowNewConversationItemInList, checkInputsRequired])
})
}, [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,
}
}

@ -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<ChatWithHistoryWrapProps> = ({
handleNewConversationInputsChange,
inputsForms,
handleNewConversation,
handleStartChat,
handleChangeConversation,
handlePinConversation,
handleUnpinConversation,
@ -159,6 +158,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
eventEmitter,
} = useChatWithHistory(installedAppInfo)
return (
@ -179,7 +179,6 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
handleNewConversationInputsChange,
inputsForms,
handleNewConversation,
handleStartChat,
handleChangeConversation,
handlePinConversation,
handleUnpinConversation,
@ -202,6 +201,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
eventEmitter,
}}>
<ChatWithHistory className={className} />
</ChatWithHistoryContext.Provider>
@ -242,12 +242,12 @@ const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
if (appUnavailable)
return <AppUnavailable isUnknownReason={isUnknownReason} />
return (
return <EventEmitterContextProvider>
<ChatWithHistoryWrap
installedAppInfo={installedAppInfo}
className={className}
/>
)
</EventEmitterContextProvider>
}
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 { 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 (
<div className={cn('flex flex-col items-center px-4 pt-6', isMobile && 'pt-4')}>
<div className={cn(
@ -54,7 +57,7 @@ const InputsFormNode = ({
<Button
variant='primary'
className='w-full'
onClick={() => handleStartChat(() => setCollapsed(true))}
onClick={() => eventEmitter?.emit(CHAT_FORM_VALIDATE)}
style={
themeBuilder?.theme
? {

@ -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')

@ -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<string, any>) => 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<string, any> | null,
setCurrentConversationInputs: (v: Record<string, any>) => void,
eventEmitter?: UseMittReturn<ChatEventMap>
}
export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({
@ -62,7 +64,6 @@ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>
handleNewConversationInputsChange: noop,
inputsForms: [],
handleNewConversation: noop,
handleStartChat: noop,
handleChangeConversation: noop,
handleNewConversationCompleted: noop,
chatShouldReloadKey: '',

@ -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<ChatEventMap>()
const appData = useMemo(() => {
return appInfo
@ -328,12 +335,15 @@ export const useEmbeddedChatbot = () => {
return true
}, [inputsForms, notify, t])
const handleStartChat = useCallback((callback?: any) => {
useEffect(() => {
eventEmitter.useSubscribe(CHAT_FORM_VALIDATE, () => {
if (checkInputsRequired()) {
setShowNewConversationItemInList(true)
callback?.()
eventEmitter.emit(CHAT_START_EVENT)
}
}, [setShowNewConversationItemInList, checkInputsRequired])
})
}, [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,

@ -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 <EmbeddedChatbotContext.Provider value={{
@ -184,7 +184,6 @@ const EmbeddedChatbotWrapper = () => {
handleNewConversationInputsChange,
inputsForms,
handleNewConversation,
handleStartChat,
handleChangeConversation,
handleNewConversationCompleted,
chatShouldReloadKey,
@ -201,6 +200,7 @@ const EmbeddedChatbotWrapper = () => {
setIsResponding,
currentConversationInputs,
setCurrentConversationInputs,
eventEmitter,
}}>
<Chatbot />
</EmbeddedChatbotContext.Provider>

@ -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 (
<div className={cn('mb-6 flex flex-col items-center px-4 pt-6', isMobile && 'mb-4 pt-4')}>
<div className={cn(
@ -54,7 +58,7 @@ const InputsFormNode = ({
<Button
variant='primary'
className='w-full'
onClick={() => handleStartChat(() => setCollapsed(true))}
onClick={() => eventEmitter?.emit(CHAT_FORM_VALIDATE)}
style={
themeBuilder?.theme
? {

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

Loading…
Cancel
Save