chunk-content.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import React, { useEffect, useRef, useState } from 'react'
  2. import type { ComponentProps, FC } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import { ChunkingMode } from '@/models/datasets'
  5. import classNames from '@/utils/classnames'
  6. import { Markdown } from '@/app/components/base/markdown'
  7. type IContentProps = ComponentProps<'textarea'>
  8. const Textarea: FC<IContentProps> = React.memo(({
  9. value,
  10. placeholder,
  11. className,
  12. disabled,
  13. ...rest
  14. }) => {
  15. return (
  16. <textarea
  17. className={classNames(
  18. 'disabled:bg-transparent inset-0 outline-none border-none appearance-none resize-none w-full overflow-y-auto',
  19. className,
  20. )}
  21. placeholder={placeholder}
  22. value={value}
  23. disabled={disabled}
  24. {...rest}
  25. />
  26. )
  27. })
  28. Textarea.displayName = 'Textarea'
  29. type IAutoResizeTextAreaProps = ComponentProps<'textarea'> & {
  30. containerRef: React.RefObject<HTMLDivElement>
  31. labelRef: React.RefObject<HTMLDivElement>
  32. }
  33. const AutoResizeTextArea: FC<IAutoResizeTextAreaProps> = React.memo(({
  34. className,
  35. placeholder,
  36. value,
  37. disabled,
  38. containerRef,
  39. labelRef,
  40. ...rest
  41. }) => {
  42. const textareaRef = useRef<HTMLTextAreaElement>(null)
  43. const observerRef = useRef<ResizeObserver>()
  44. const [maxHeight, setMaxHeight] = useState(0)
  45. useEffect(() => {
  46. const textarea = textareaRef.current
  47. if (!textarea)
  48. return
  49. textarea.style.height = 'auto'
  50. const lineHeight = Number.parseInt(getComputedStyle(textarea).lineHeight)
  51. const textareaHeight = Math.max(textarea.scrollHeight, lineHeight)
  52. textarea.style.height = `${textareaHeight}px`
  53. }, [value])
  54. useEffect(() => {
  55. const container = containerRef.current
  56. const label = labelRef.current
  57. if (!container || !label)
  58. return
  59. const updateMaxHeight = () => {
  60. const containerHeight = container.clientHeight
  61. const labelHeight = label.clientHeight
  62. const padding = 32
  63. const space = 12
  64. const maxHeight = Math.floor((containerHeight - 2 * labelHeight - padding - space) / 2)
  65. setMaxHeight(maxHeight)
  66. }
  67. updateMaxHeight()
  68. observerRef.current = new ResizeObserver(updateMaxHeight)
  69. observerRef.current.observe(container)
  70. return () => {
  71. observerRef.current?.disconnect()
  72. }
  73. }, [])
  74. return (
  75. <textarea
  76. ref={textareaRef}
  77. className={classNames(
  78. 'disabled:bg-transparent inset-0 outline-none border-none appearance-none resize-none w-full',
  79. className,
  80. )}
  81. style={{
  82. maxHeight,
  83. }}
  84. placeholder={placeholder}
  85. value={value}
  86. disabled={disabled}
  87. {...rest}
  88. />
  89. )
  90. })
  91. AutoResizeTextArea.displayName = 'AutoResizeTextArea'
  92. type IQATextAreaProps = {
  93. question: string
  94. answer?: string
  95. onQuestionChange: (question: string) => void
  96. onAnswerChange?: (answer: string) => void
  97. isEditMode?: boolean
  98. }
  99. const QATextArea: FC<IQATextAreaProps> = React.memo(({
  100. question,
  101. answer,
  102. onQuestionChange,
  103. onAnswerChange,
  104. isEditMode = true,
  105. }) => {
  106. const { t } = useTranslation()
  107. const containerRef = useRef<HTMLDivElement>(null)
  108. const labelRef = useRef<HTMLDivElement>(null)
  109. return (
  110. <div ref={containerRef} className='h-full overflow-hidden'>
  111. <div ref={labelRef} className='text-text-tertiary text-xs font-medium mb-1'>QUESTION</div>
  112. <AutoResizeTextArea
  113. className='text-text-secondary text-sm tracking-[-0.07px] caret-[#295EFF]'
  114. value={question}
  115. placeholder={t('datasetDocuments.segment.questionPlaceholder') || ''}
  116. onChange={e => onQuestionChange(e.target.value)}
  117. disabled={!isEditMode}
  118. containerRef={containerRef}
  119. labelRef={labelRef}
  120. />
  121. <div className='text-text-tertiary text-xs font-medium mb-1 mt-6'>ANSWER</div>
  122. <AutoResizeTextArea
  123. className='text-text-secondary text-sm tracking-[-0.07px] caret-[#295EFF]'
  124. value={answer}
  125. placeholder={t('datasetDocuments.segment.answerPlaceholder') || ''}
  126. onChange={e => onAnswerChange?.(e.target.value)}
  127. disabled={!isEditMode}
  128. autoFocus
  129. containerRef={containerRef}
  130. labelRef={labelRef}
  131. />
  132. </div>
  133. )
  134. })
  135. QATextArea.displayName = 'QATextArea'
  136. type IChunkContentProps = {
  137. question: string
  138. answer?: string
  139. onQuestionChange: (question: string) => void
  140. onAnswerChange?: (answer: string) => void
  141. isEditMode?: boolean
  142. docForm: ChunkingMode
  143. }
  144. const ChunkContent: FC<IChunkContentProps> = ({
  145. question,
  146. answer,
  147. onQuestionChange,
  148. onAnswerChange,
  149. isEditMode,
  150. docForm,
  151. }) => {
  152. const { t } = useTranslation()
  153. if (docForm === ChunkingMode.qa) {
  154. return <QATextArea
  155. question={question}
  156. answer={answer}
  157. onQuestionChange={onQuestionChange}
  158. onAnswerChange={onAnswerChange}
  159. isEditMode={isEditMode}
  160. />
  161. }
  162. if (!isEditMode) {
  163. return (
  164. <Markdown
  165. className='h-full w-full !text-text-secondary'
  166. content={question}
  167. customDisallowedElements={['input']}
  168. />
  169. )
  170. }
  171. return (
  172. <Textarea
  173. className='h-full w-full pb-6 body-md-regular text-text-secondary tracking-[-0.07px] caret-[#295EFF]'
  174. value={question}
  175. placeholder={t('datasetDocuments.segment.contentPlaceholder') || ''}
  176. onChange={e => onQuestionChange(e.target.value)}
  177. disabled={!isEditMode}
  178. autoFocus
  179. />
  180. )
  181. }
  182. ChunkContent.displayName = 'ChunkContent'
  183. export default React.memo(ChunkContent)