chat-wrapper.tsx 5.3 KB

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