chat-wrapper.tsx 5.2 KB

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