chat-wrapper.tsx 5.2 KB

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