index.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import type {
  2. FC,
  3. ReactNode,
  4. } from 'react'
  5. import { memo, useEffect, useRef, useState } from 'react'
  6. import { useTranslation } from 'react-i18next'
  7. import type {
  8. ChatConfig,
  9. ChatItem,
  10. } from '../../types'
  11. import Operation from './operation'
  12. import AgentContent from './agent-content'
  13. import BasicContent from './basic-content'
  14. import SuggestedQuestions from './suggested-questions'
  15. import More from './more'
  16. import WorkflowProcess from './workflow-process'
  17. import { AnswerTriangle } from '@/app/components/base/icons/src/vender/solid/general'
  18. import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
  19. import Citation from '@/app/components/base/chat/chat/citation'
  20. import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
  21. import type { Emoji } from '@/app/components/tools/types'
  22. import type { AppData } from '@/models/share'
  23. import AnswerIcon from '@/app/components/base/answer-icon'
  24. import cn from '@/utils/classnames'
  25. type AnswerProps = {
  26. item: ChatItem
  27. question: string
  28. index: number
  29. config?: ChatConfig
  30. answerIcon?: ReactNode
  31. responding?: boolean
  32. allToolIcons?: Record<string, string | Emoji>
  33. showPromptLog?: boolean
  34. chatAnswerContainerInner?: string
  35. hideProcessDetail?: boolean
  36. appData?: AppData
  37. noChatInput?: boolean
  38. }
  39. const Answer: FC<AnswerProps> = ({
  40. item,
  41. question,
  42. index,
  43. config,
  44. answerIcon,
  45. responding,
  46. allToolIcons,
  47. showPromptLog,
  48. chatAnswerContainerInner,
  49. hideProcessDetail,
  50. appData,
  51. noChatInput,
  52. }) => {
  53. const { t } = useTranslation()
  54. const {
  55. content,
  56. citation,
  57. agent_thoughts,
  58. more,
  59. annotation,
  60. workflowProcess,
  61. } = item
  62. const hasAgentThoughts = !!agent_thoughts?.length
  63. const [containerWidth, setContainerWidth] = useState(0)
  64. const [contentWidth, setContentWidth] = useState(0)
  65. const containerRef = useRef<HTMLDivElement>(null)
  66. const contentRef = useRef<HTMLDivElement>(null)
  67. const getContainerWidth = () => {
  68. if (containerRef.current)
  69. setContainerWidth(containerRef.current?.clientWidth + 16)
  70. }
  71. useEffect(() => {
  72. getContainerWidth()
  73. }, [])
  74. const getContentWidth = () => {
  75. if (contentRef.current)
  76. setContentWidth(contentRef.current?.clientWidth)
  77. }
  78. useEffect(() => {
  79. if (!responding)
  80. getContentWidth()
  81. }, [responding])
  82. // Recalculate contentWidth when content changes (e.g., SVG preview/source toggle)
  83. useEffect(() => {
  84. if (!containerRef.current)
  85. return
  86. const resizeObserver = new ResizeObserver(() => {
  87. getContentWidth()
  88. })
  89. resizeObserver.observe(containerRef.current)
  90. return () => {
  91. resizeObserver.disconnect()
  92. }
  93. }, [])
  94. return (
  95. <div className='flex mb-2 last:mb-0'>
  96. <div className='shrink-0 relative w-10 h-10'>
  97. {answerIcon || <AnswerIcon />}
  98. {responding && (
  99. <div className='absolute -top-[3px] -left-[3px] pl-[6px] flex items-center w-4 h-4 bg-white rounded-full shadow-xs border-[0.5px] border-gray-50'>
  100. <LoadingAnim type='avatar' />
  101. </div>
  102. )}
  103. </div>
  104. <div className='chat-answer-container group grow w-0 ml-4' ref={containerRef}>
  105. <div className={cn('group relative pr-10', chatAnswerContainerInner)}>
  106. <AnswerTriangle className='absolute -left-2 top-0 w-2 h-3 text-gray-100' />
  107. <div
  108. ref={contentRef}
  109. className={cn('relative inline-block px-4 py-3 max-w-full bg-gray-100 rounded-b-2xl rounded-tr-2xl text-sm text-gray-900', workflowProcess && 'w-full')}
  110. >
  111. {
  112. !responding && (
  113. <Operation
  114. hasWorkflowProcess={!!workflowProcess}
  115. maxSize={containerWidth - contentWidth - 4}
  116. contentWidth={contentWidth}
  117. item={item}
  118. question={question}
  119. index={index}
  120. showPromptLog={showPromptLog}
  121. noChatInput={noChatInput}
  122. />
  123. )
  124. }
  125. {/** Render the normal steps */}
  126. {
  127. workflowProcess && !hideProcessDetail && (
  128. <WorkflowProcess
  129. data={workflowProcess}
  130. item={item}
  131. hideInfo
  132. hideProcessDetail={hideProcessDetail}
  133. />
  134. )
  135. }
  136. {/** Hide workflow steps by it's settings in siteInfo */}
  137. {
  138. workflowProcess && hideProcessDetail && appData && appData.site.show_workflow_steps && (
  139. <WorkflowProcess
  140. data={workflowProcess}
  141. item={item}
  142. hideInfo
  143. hideProcessDetail={hideProcessDetail}
  144. />
  145. )
  146. }
  147. {
  148. responding && !content && !hasAgentThoughts && (
  149. <div className='flex items-center justify-center w-6 h-5'>
  150. <LoadingAnim type='text' />
  151. </div>
  152. )
  153. }
  154. {
  155. content && !hasAgentThoughts && (
  156. <BasicContent item={item} />
  157. )
  158. }
  159. {
  160. hasAgentThoughts && (
  161. <AgentContent
  162. item={item}
  163. responding={responding}
  164. allToolIcons={allToolIcons}
  165. />
  166. )
  167. }
  168. {
  169. annotation?.id && annotation.authorName && (
  170. <EditTitle
  171. className='mt-1'
  172. title={t('appAnnotation.editBy', { author: annotation.authorName })}
  173. />
  174. )
  175. }
  176. <SuggestedQuestions item={item} />
  177. {
  178. !!citation?.length && !responding && (
  179. <Citation data={citation} showHitInfo={config?.supportCitationHitInfo} />
  180. )
  181. }
  182. </div>
  183. </div>
  184. <More more={more} />
  185. </div>
  186. </div>
  187. )
  188. }
  189. export default memo(Answer)