| import { useCallback, useEffect, useMemo } from 'react' | |
| import Chat from '../chat' | |
| import type { | |
| ChatConfig, | |
| ChatItem, | |
| OnSend, | |
| } from '../types' | |
| import { useChat } from '../chat/hooks' | |
| import { getLastAnswer } from '../utils' | |
| import { useEmbeddedChatbotContext } from './context' | |
| import ConfigPanel from './config-panel' | |
| import { isDify } from './utils' | |
| import cn from '@/utils/classnames' | |
| import { | |
| fetchSuggestedQuestions, | |
| getUrl, | |
| stopChatMessageResponding, | |
| } from '@/service/share' | |
| import LogoAvatar from '@/app/components/base/logo/logo-embedded-chat-avatar' | |
| import AnswerIcon from '@/app/components/base/answer-icon' | |
| const ChatWrapper = () => { | |
| const { | |
| appData, | |
| appParams, | |
| appPrevChatList, | |
| currentConversationId, | |
| currentConversationItem, | |
| inputsForms, | |
| newConversationInputs, | |
| handleNewConversationCompleted, | |
| isMobile, | |
| isInstalledApp, | |
| appId, | |
| appMeta, | |
| handleFeedback, | |
| currentChatInstanceRef, | |
| themeBuilder, | |
| } = useEmbeddedChatbotContext() | |
| 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 { | |
| chatListRef, | |
| chatList, | |
| handleSend, | |
| handleStop, | |
| isResponding, | |
| suggestedQuestions, | |
| handleUpdateChatList, | |
| } = useChat( | |
| appConfig, | |
| { | |
| inputs: (currentConversationId ? currentConversationItem?.inputs : newConversationInputs) as any, | |
| inputsForm: inputsForms, | |
| }, | |
| appPrevChatList, | |
| taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId), | |
| ) | |
| useEffect(() => { | |
| if (currentChatInstanceRef.current) | |
| currentChatInstanceRef.current.handleStop = handleStop | |
| }, []) | |
| const doSend: OnSend = useCallback((message, files, last_answer) => { | |
| const data: any = { | |
| query: message, | |
| files, | |
| inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs, | |
| conversation_id: currentConversationId, | |
| parent_message_id: last_answer?.id || getLastAnswer(chatListRef.current)?.id || null, | |
| } | |
| handleSend( | |
| getUrl('chat-messages', isInstalledApp, appId || ''), | |
| data, | |
| { | |
| onGetSuggestedQuestions: responseItemId => fetchSuggestedQuestions(responseItemId, isInstalledApp, appId), | |
| onConversationComplete: currentConversationId ? undefined : handleNewConversationCompleted, | |
| isPublicAPI: !isInstalledApp, | |
| }, | |
| ) | |
| }, [ | |
| chatListRef, | |
| appConfig, | |
| currentConversationId, | |
| currentConversationItem, | |
| handleSend, | |
| newConversationInputs, | |
| handleNewConversationCompleted, | |
| isInstalledApp, | |
| appId, | |
| ]) | |
| const doRegenerate = useCallback((chatItem: ChatItem) => { | |
| const index = chatList.findIndex(item => item.id === chatItem.id) | |
| if (index === -1) | |
| return | |
| const prevMessages = chatList.slice(0, index) | |
| const question = prevMessages.pop() | |
| const lastAnswer = getLastAnswer(prevMessages) | |
| if (!question) | |
| return | |
| handleUpdateChatList(prevMessages) | |
| doSend(question.content, question.message_files, lastAnswer) | |
| }, [chatList, handleUpdateChatList, doSend]) | |
| const chatNode = useMemo(() => { | |
| if (inputsForms.length) { | |
| return ( | |
| <> | |
| {!currentConversationId && ( | |
| <div className={cn('mx-auto w-full max-w-full tablet:px-4', 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 null | |
| }, [currentConversationId, inputsForms, isMobile]) | |
| const answerIcon = isDify() | |
| ? <LogoAvatar className='relative shrink-0' /> | |
| : (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 ( | |
| <Chat | |
| appData={appData} | |
| config={appConfig} | |
| chatList={chatList} | |
| isResponding={isResponding} | |
| chatContainerInnerClassName={cn('mx-auto w-full max-w-full tablet:px-4', isMobile && 'px-4')} | |
| chatFooterClassName='pb-4' | |
| chatFooterInnerClassName={cn('mx-auto w-full max-w-full tablet:px-4', 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} | |
| /> | |
| ) | |
| } | |
| export default ChatWrapper | |