chat-wrapper.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { useCallback, useEffect, useMemo } from 'react'
  2. import Chat from '../chat'
  3. import type {
  4. ChatConfig,
  5. ChatItem,
  6. OnSend,
  7. } from '../types'
  8. import { useChat } from '../chat/hooks'
  9. import { useEmbeddedChatbotContext } from './context'
  10. import ConfigPanel from './config-panel'
  11. import { isDify } from './utils'
  12. import cn from '@/utils/classnames'
  13. import {
  14. fetchSuggestedQuestions,
  15. getUrl,
  16. stopChatMessageResponding,
  17. } from '@/service/share'
  18. import LogoAvatar from '@/app/components/base/logo/logo-embedded-chat-avatar'
  19. import AnswerIcon from '@/app/components/base/answer-icon'
  20. const ChatWrapper = () => {
  21. const {
  22. appData,
  23. appParams,
  24. appPrevChatList,
  25. currentConversationId,
  26. currentConversationItem,
  27. inputsForms,
  28. newConversationInputs,
  29. handleNewConversationCompleted,
  30. isMobile,
  31. isInstalledApp,
  32. appId,
  33. appMeta,
  34. handleFeedback,
  35. currentChatInstanceRef,
  36. themeBuilder,
  37. } = useEmbeddedChatbotContext()
  38. const appConfig = useMemo(() => {
  39. const config = appParams || {}
  40. return {
  41. ...config,
  42. supportFeedback: true,
  43. opening_statement: currentConversationId ? currentConversationItem?.introduction : (config as any).opening_statement,
  44. } as ChatConfig
  45. }, [appParams, currentConversationItem?.introduction, currentConversationId])
  46. const {
  47. chatListRef,
  48. chatList,
  49. handleSend,
  50. handleStop,
  51. isResponding,
  52. suggestedQuestions,
  53. handleUpdateChatList,
  54. } = useChat(
  55. appConfig,
  56. {
  57. inputs: (currentConversationId ? currentConversationItem?.inputs : newConversationInputs) as any,
  58. promptVariables: inputsForms,
  59. },
  60. appPrevChatList,
  61. taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId),
  62. )
  63. useEffect(() => {
  64. if (currentChatInstanceRef.current)
  65. currentChatInstanceRef.current.handleStop = handleStop
  66. }, [])
  67. const doSend: OnSend = useCallback((message, files, last_answer) => {
  68. const lastAnswer = chatListRef.current.at(-1)
  69. const data: any = {
  70. query: message,
  71. inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs,
  72. conversation_id: currentConversationId,
  73. parent_message_id: last_answer?.id || (lastAnswer
  74. ? lastAnswer.isOpeningStatement
  75. ? null
  76. : lastAnswer.id
  77. : null),
  78. }
  79. if (appConfig?.file_upload?.image.enabled && files?.length)
  80. data.files = files
  81. handleSend(
  82. getUrl('chat-messages', isInstalledApp, appId || ''),
  83. data,
  84. {
  85. onGetSuggestedQuestions: responseItemId => fetchSuggestedQuestions(responseItemId, isInstalledApp, appId),
  86. onConversationComplete: currentConversationId ? undefined : handleNewConversationCompleted,
  87. isPublicAPI: !isInstalledApp,
  88. },
  89. )
  90. }, [
  91. chatListRef,
  92. appConfig,
  93. currentConversationId,
  94. currentConversationItem,
  95. handleSend,
  96. newConversationInputs,
  97. handleNewConversationCompleted,
  98. isInstalledApp,
  99. appId,
  100. ])
  101. const doRegenerate = useCallback((chatItem: ChatItem) => {
  102. const index = chatList.findIndex(item => item.id === chatItem.id)
  103. if (index === -1)
  104. return
  105. const prevMessages = chatList.slice(0, index)
  106. const question = prevMessages.pop()
  107. const lastAnswer = prevMessages.at(-1)
  108. if (!question)
  109. return
  110. handleUpdateChatList(prevMessages)
  111. doSend(question.content, question.message_files, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer)
  112. }, [chatList, handleUpdateChatList, doSend])
  113. const chatNode = useMemo(() => {
  114. if (inputsForms.length) {
  115. return (
  116. <>
  117. {!currentConversationId && (
  118. <div className={cn('mx-auto w-full max-w-full tablet:px-4', isMobile && 'px-4')}>
  119. <div className='mb-6' />
  120. <ConfigPanel />
  121. <div
  122. className='my-6 h-[1px]'
  123. style={{ background: 'linear-gradient(90deg, rgba(242, 244, 247, 0.00) 0%, #F2F4F7 49.17%, rgba(242, 244, 247, 0.00) 100%)' }}
  124. />
  125. </div>
  126. )}
  127. </>
  128. )
  129. }
  130. return null
  131. }, [currentConversationId, inputsForms, isMobile])
  132. const answerIcon = isDify()
  133. ? <LogoAvatar className='relative shrink-0' />
  134. : (appData?.site && appData.site.use_icon_as_answer_icon)
  135. ? <AnswerIcon
  136. iconType={appData.site.icon_type}
  137. icon={appData.site.icon}
  138. background={appData.site.icon_background}
  139. imageUrl={appData.site.icon_url}
  140. />
  141. : null
  142. return (
  143. <Chat
  144. appData={appData}
  145. config={appConfig}
  146. chatList={chatList}
  147. isResponding={isResponding}
  148. chatContainerInnerClassName={cn('mx-auto w-full max-w-full tablet:px-4', isMobile && 'px-4')}
  149. chatFooterClassName='pb-4'
  150. chatFooterInnerClassName={cn('mx-auto w-full max-w-full tablet:px-4', isMobile && 'px-4')}
  151. onSend={doSend}
  152. onRegenerate={doRegenerate}
  153. onStopResponding={handleStop}
  154. chatNode={chatNode}
  155. allToolIcons={appMeta?.tool_icons || {}}
  156. onFeedback={handleFeedback}
  157. suggestedQuestions={suggestedQuestions}
  158. answerIcon={answerIcon}
  159. hideProcessDetail
  160. themeBuilder={themeBuilder}
  161. />
  162. )
  163. }
  164. export default ChatWrapper