chat-wrapper.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import {
  2. forwardRef,
  3. memo,
  4. useCallback,
  5. useImperativeHandle,
  6. useMemo,
  7. } from 'react'
  8. import { useNodes } from 'reactflow'
  9. import { BlockEnum } from '../../types'
  10. import {
  11. useStore,
  12. useWorkflowStore,
  13. } from '../../store'
  14. import type { StartNodeType } from '../../nodes/start/types'
  15. import Empty from './empty'
  16. import UserInput from './user-input'
  17. import ConversationVariableModal from './conversation-variable-modal'
  18. import { useChat } from './hooks'
  19. import type { ChatWrapperRefType } from './index'
  20. import Chat from '@/app/components/base/chat/chat'
  21. import type { ChatItem, OnSend } from '@/app/components/base/chat/types'
  22. import { useFeaturesStore } from '@/app/components/base/features/hooks'
  23. import {
  24. fetchSuggestedQuestions,
  25. stopChatMessageResponding,
  26. } from '@/service/debug'
  27. import { useStore as useAppStore } from '@/app/components/app/store'
  28. type ChatWrapperProps = {
  29. showConversationVariableModal: boolean
  30. onConversationModalHide: () => void
  31. showInputsFieldsPanel: boolean
  32. }
  33. const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({ showConversationVariableModal, onConversationModalHide, showInputsFieldsPanel }, ref) => {
  34. const nodes = useNodes<StartNodeType>()
  35. const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
  36. const startVariables = startNode?.data.variables
  37. const appDetail = useAppStore(s => s.appDetail)
  38. const workflowStore = useWorkflowStore()
  39. const featuresStore = useFeaturesStore()
  40. const inputs = useStore(s => s.inputs)
  41. const features = featuresStore!.getState().features
  42. const config = useMemo(() => {
  43. return {
  44. opening_statement: features.opening?.opening_statement || '',
  45. suggested_questions: features.opening?.suggested_questions || [],
  46. suggested_questions_after_answer: features.suggested,
  47. text_to_speech: features.text2speech,
  48. speech_to_text: features.speech2text,
  49. retriever_resource: features.citation,
  50. sensitive_word_avoidance: features.moderation,
  51. file_upload: features.file,
  52. }
  53. }, [features])
  54. const {
  55. conversationId,
  56. chatList,
  57. chatListRef,
  58. handleUpdateChatList,
  59. handleStop,
  60. isResponding,
  61. suggestedQuestions,
  62. handleSend,
  63. handleRestart,
  64. } = useChat(
  65. config,
  66. {
  67. inputs,
  68. promptVariables: (startVariables as any) || [],
  69. },
  70. [],
  71. taskId => stopChatMessageResponding(appDetail!.id, taskId),
  72. )
  73. const doSend = useCallback<OnSend>((query, files, last_answer) => {
  74. const lastAnswer = chatListRef.current.at(-1)
  75. handleSend(
  76. {
  77. query,
  78. files,
  79. inputs: workflowStore.getState().inputs,
  80. conversation_id: conversationId,
  81. parent_message_id: last_answer?.id || (lastAnswer
  82. ? lastAnswer.isOpeningStatement
  83. ? null
  84. : lastAnswer.id
  85. : null),
  86. },
  87. {
  88. onGetSuggestedQuestions: (messageId, getAbortController) => fetchSuggestedQuestions(appDetail!.id, messageId, getAbortController),
  89. },
  90. )
  91. }, [chatListRef, conversationId, handleSend, workflowStore, appDetail])
  92. const doRegenerate = useCallback((chatItem: ChatItem) => {
  93. const index = chatList.findIndex(item => item.id === chatItem.id)
  94. if (index === -1)
  95. return
  96. const prevMessages = chatList.slice(0, index)
  97. const question = prevMessages.pop()
  98. const lastAnswer = prevMessages.at(-1)
  99. if (!question)
  100. return
  101. handleUpdateChatList(prevMessages)
  102. doSend(question.content, question.message_files, (!lastAnswer || lastAnswer.isOpeningStatement) ? undefined : lastAnswer)
  103. }, [chatList, handleUpdateChatList, doSend])
  104. useImperativeHandle(ref, () => {
  105. return {
  106. handleRestart,
  107. }
  108. }, [handleRestart])
  109. return (
  110. <>
  111. <Chat
  112. config={{
  113. ...config,
  114. supportCitationHitInfo: true,
  115. } as any}
  116. chatList={chatList}
  117. isResponding={isResponding}
  118. chatContainerClassName='px-3'
  119. chatContainerInnerClassName='pt-6 w-full max-w-full mx-auto'
  120. chatFooterClassName='px-4 rounded-bl-2xl'
  121. chatFooterInnerClassName='pb-4 w-full max-w-full mx-auto'
  122. onSend={doSend}
  123. onRegenerate={doRegenerate}
  124. onStopResponding={handleStop}
  125. chatNode={(
  126. <>
  127. {showInputsFieldsPanel && <UserInput />}
  128. {
  129. !chatList.length && (
  130. <Empty />
  131. )
  132. }
  133. </>
  134. )}
  135. noSpacing
  136. suggestedQuestions={suggestedQuestions}
  137. showPromptLog
  138. chatAnswerContainerInner='!pr-2'
  139. />
  140. {showConversationVariableModal && (
  141. <ConversationVariableModal
  142. conversationID={conversationId}
  143. onHide={onConversationModalHide}
  144. />
  145. )}
  146. </>
  147. )
  148. })
  149. ChatWrapper.displayName = 'ChatWrapper'
  150. export default memo(ChatWrapper)