chat-wrapper.tsx 5.2 KB

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