chat-wrapper.tsx 5.3 KB

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