import { useCallback, useEffect, useMemo } from 'react' import Chat from '../chat' import type { ChatConfig, ChatItem, ChatItemInTree, OnSend, } from '../types' import { useChat } from '../chat/hooks' import { getLastAnswer, isValidGeneratedAnswer } from '../utils' import { useChatWithHistoryContext } from './context' import Header from './header' import ConfigPanel from './config-panel' import { fetchSuggestedQuestions, getUrl, stopChatMessageResponding, } from '@/service/share' import AnswerIcon from '@/app/components/base/answer-icon' const ChatWrapper = () => { const { appParams, appPrevChatTree, currentConversationId, currentConversationItem, inputsForms, newConversationInputs, handleNewConversationCompleted, isMobile, isInstalledApp, appId, appMeta, handleFeedback, currentChatInstanceRef, appData, themeBuilder, } = useChatWithHistoryContext() const appConfig = useMemo(() => { const config = appParams || {} return { ...config, file_upload: { ...(config as any).file_upload, fileUploadConfig: (config as any).system_parameters, }, supportFeedback: true, opening_statement: currentConversationId ? currentConversationItem?.introduction : (config as any).opening_statement, } as ChatConfig }, [appParams, currentConversationItem?.introduction, currentConversationId]) const { chatList, setTargetMessageId, handleSend, handleStop, isResponding, suggestedQuestions, } = useChat( appConfig, { inputs: (currentConversationId ? currentConversationItem?.inputs : newConversationInputs) as any, inputsForm: inputsForms, }, appPrevChatTree, taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId), ) useEffect(() => { if (currentChatInstanceRef.current) currentChatInstanceRef.current.handleStop = handleStop // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const doSend: OnSend = useCallback((message, files, isRegenerate = false, parentAnswer: ChatItem | null = null) => { const data: any = { query: message, files, inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs, conversation_id: currentConversationId, parent_message_id: (isRegenerate ? parentAnswer?.id : getLastAnswer(chatList)?.id) || null, } handleSend( getUrl('chat-messages', isInstalledApp, appId || ''), data, { onGetSuggestedQuestions: responseItemId => fetchSuggestedQuestions(responseItemId, isInstalledApp, appId), onConversationComplete: currentConversationId ? undefined : handleNewConversationCompleted, isPublicAPI: !isInstalledApp, }, ) }, [ chatList, handleNewConversationCompleted, handleSend, currentConversationId, currentConversationItem, newConversationInputs, isInstalledApp, appId, ]) const doRegenerate = useCallback((chatItem: ChatItemInTree) => { const question = chatList.find(item => item.id === chatItem.parentMessageId)! const parentAnswer = chatList.find(item => item.id === question.parentMessageId) doSend(question.content, question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null) }, [chatList, doSend]) const chatNode = useMemo(() => { if (inputsForms.length) { return ( <> <Header isMobile={isMobile} title={currentConversationItem?.name || ''} /> { !currentConversationId && ( <div className={`mx-auto w-full max-w-[720px] ${isMobile && 'px-4'}`}> <div className='mb-6' /> <ConfigPanel /> <div className='my-6 h-[1px]' style={{ background: 'linear-gradient(90deg, rgba(242, 244, 247, 0.00) 0%, #F2F4F7 49.17%, rgba(242, 244, 247, 0.00) 100%)' }} /> </div> ) } </> ) } return ( <Header isMobile={isMobile} title={currentConversationItem?.name || ''} /> ) }, [ currentConversationId, inputsForms, currentConversationItem, isMobile, ]) const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon) ? <AnswerIcon iconType={appData.site.icon_type} icon={appData.site.icon} background={appData.site.icon_background} imageUrl={appData.site.icon_url} /> : null return ( <div className='h-full bg-chatbot-bg overflow-hidden' > <Chat appData={appData} config={appConfig} chatList={chatList} isResponding={isResponding} chatContainerInnerClassName={`mx-auto pt-6 w-full max-w-[720px] ${isMobile && 'px-4'}`} chatFooterClassName='pb-4' chatFooterInnerClassName={`mx-auto w-full max-w-[720px] ${isMobile && 'px-4'}`} onSend={doSend} inputs={currentConversationId ? currentConversationItem?.inputs as any : newConversationInputs} inputsForm={inputsForms} onRegenerate={doRegenerate} onStopResponding={handleStop} chatNode={chatNode} allToolIcons={appMeta?.tool_icons || {}} onFeedback={handleFeedback} suggestedQuestions={suggestedQuestions} answerIcon={answerIcon} hideProcessDetail themeBuilder={themeBuilder} switchSibling={siblingMessageId => setTargetMessageId(siblingMessageId)} /> </div> ) } export default ChatWrapper