chat-wrapper.tsx 5.1 KB

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