chunk-content.tsx 5.1 KB

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