operation.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import type { FC } from 'react'
  2. import {
  3. memo,
  4. useMemo,
  5. useState,
  6. } from 'react'
  7. import { useTranslation } from 'react-i18next'
  8. import type { ChatItem } from '../../types'
  9. import { useChatContext } from '../context'
  10. import cn from '@/utils/classnames'
  11. import CopyBtn from '@/app/components/base/copy-btn'
  12. import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
  13. import AudioBtn from '@/app/components/base/audio-btn'
  14. import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn'
  15. import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal'
  16. import {
  17. ThumbsDown,
  18. ThumbsUp,
  19. } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
  20. import TooltipPlus from '@/app/components/base/tooltip-plus'
  21. import Log from '@/app/components/base/chat/chat/log'
  22. type OperationProps = {
  23. item: ChatItem
  24. question: string
  25. index: number
  26. showPromptLog?: boolean
  27. maxSize: number
  28. contentWidth: number
  29. hasWorkflowProcess: boolean
  30. }
  31. const Operation: FC<OperationProps> = ({
  32. item,
  33. question,
  34. index,
  35. showPromptLog,
  36. maxSize,
  37. contentWidth,
  38. hasWorkflowProcess,
  39. }) => {
  40. const { t } = useTranslation()
  41. const {
  42. config,
  43. onAnnotationAdded,
  44. onAnnotationEdited,
  45. onAnnotationRemoved,
  46. onFeedback,
  47. } = useChatContext()
  48. const [isShowReplyModal, setIsShowReplyModal] = useState(false)
  49. const {
  50. id,
  51. isOpeningStatement,
  52. content: messageContent,
  53. annotation,
  54. feedback,
  55. agent_thoughts,
  56. } = item
  57. const hasAnnotation = !!annotation?.id
  58. const [localFeedback, setLocalFeedback] = useState(feedback)
  59. const content = useMemo(() => {
  60. if (agent_thoughts?.length)
  61. return agent_thoughts.reduce((acc, cur) => acc + cur.thought, '')
  62. return messageContent
  63. }, [agent_thoughts, messageContent])
  64. const handleFeedback = async (rating: 'like' | 'dislike' | null) => {
  65. if (!config?.supportFeedback || !onFeedback)
  66. return
  67. await onFeedback?.(id, { rating })
  68. setLocalFeedback({ rating })
  69. }
  70. const operationWidth = useMemo(() => {
  71. let width = 0
  72. if (!isOpeningStatement)
  73. width += 28
  74. if (!isOpeningStatement && showPromptLog)
  75. width += 102 + 8
  76. if (!isOpeningStatement && config?.text_to_speech?.enabled)
  77. width += 33
  78. if (!isOpeningStatement && config?.supportAnnotation && config?.annotation_reply?.enabled)
  79. width += 56 + 8
  80. if (config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement)
  81. width += 60 + 8
  82. if (config?.supportFeedback && localFeedback?.rating && onFeedback && !isOpeningStatement)
  83. width += 28 + 8
  84. return width
  85. }, [isOpeningStatement, showPromptLog, config?.text_to_speech?.enabled, config?.supportAnnotation, config?.annotation_reply?.enabled, config?.supportFeedback, localFeedback?.rating, onFeedback])
  86. const positionRight = useMemo(() => operationWidth < maxSize, [operationWidth, maxSize])
  87. return (
  88. <>
  89. <div
  90. className={cn(
  91. 'absolute flex justify-end gap-1',
  92. hasWorkflowProcess && '-top-3.5 -right-3.5',
  93. !positionRight && '-top-3.5 -right-3.5',
  94. !hasWorkflowProcess && positionRight && '!top-[9px]',
  95. )}
  96. style={(!hasWorkflowProcess && positionRight) ? { left: contentWidth + 8 } : {}}
  97. >
  98. {!isOpeningStatement && (
  99. <CopyBtn
  100. value={content}
  101. className='hidden group-hover:block'
  102. />
  103. )}
  104. {!isOpeningStatement && (showPromptLog || config?.text_to_speech?.enabled) && (
  105. <div className='hidden group-hover:flex items-center w-max h-[28px] p-0.5 rounded-lg bg-white border-[0.5px] border-gray-100 shadow-md shrink-0'>
  106. {showPromptLog && (
  107. <>
  108. <Log logItem={item} />
  109. <div className='mx-1 w-[1px] h-[14px] bg-gray-200' />
  110. </>
  111. )}
  112. {(config?.text_to_speech?.enabled) && (
  113. <>
  114. <AudioBtn
  115. id={id}
  116. value={content}
  117. noCache={false}
  118. className='hidden group-hover:block'
  119. />
  120. </>
  121. )}
  122. </div>
  123. )}
  124. {(!isOpeningStatement && config?.supportAnnotation && config.annotation_reply?.enabled) && (
  125. <AnnotationCtrlBtn
  126. appId={config?.appId || ''}
  127. messageId={id}
  128. annotationId={annotation?.id || ''}
  129. className='hidden group-hover:block ml-1 shrink-0'
  130. cached={hasAnnotation}
  131. query={question}
  132. answer={content}
  133. onAdded={(id, authorName) => onAnnotationAdded?.(id, authorName, question, content, index)}
  134. onEdit={() => setIsShowReplyModal(true)}
  135. onRemoved={() => onAnnotationRemoved?.(index)}
  136. />
  137. )}
  138. {
  139. !positionRight && annotation?.id && (
  140. <div
  141. className='relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-[#444CE7] shadow-md group-hover:hidden'
  142. >
  143. <div className='p-1 rounded-lg bg-[#EEF4FF] '>
  144. <MessageFast className='w-4 h-4' />
  145. </div>
  146. </div>
  147. )
  148. }
  149. {
  150. config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement && (
  151. <div className='hidden group-hover:flex ml-1 shrink-0 items-center px-0.5 bg-white border-[0.5px] border-gray-100 shadow-md text-gray-500 rounded-lg'>
  152. <TooltipPlus popupContent={t('appDebug.operation.agree')}>
  153. <div
  154. className='flex items-center justify-center mr-0.5 w-6 h-6 rounded-md hover:bg-black/5 hover:text-gray-800 cursor-pointer'
  155. onClick={() => handleFeedback('like')}
  156. >
  157. <ThumbsUp className='w-4 h-4' />
  158. </div>
  159. </TooltipPlus>
  160. <TooltipPlus popupContent={t('appDebug.operation.disagree')}>
  161. <div
  162. className='flex items-center justify-center w-6 h-6 rounded-md hover:bg-black/5 hover:text-gray-800 cursor-pointer'
  163. onClick={() => handleFeedback('dislike')}
  164. >
  165. <ThumbsDown className='w-4 h-4' />
  166. </div>
  167. </TooltipPlus>
  168. </div>
  169. )
  170. }
  171. {
  172. config?.supportFeedback && localFeedback?.rating && onFeedback && !isOpeningStatement && (
  173. <TooltipPlus popupContent={localFeedback.rating === 'like' ? t('appDebug.operation.cancelAgree') : t('appDebug.operation.cancelDisagree')}>
  174. <div
  175. className={`
  176. flex items-center justify-center w-7 h-7 rounded-[10px] border-[2px] border-white cursor-pointer
  177. ${localFeedback.rating === 'like' && 'bg-blue-50 text-blue-600'}
  178. ${localFeedback.rating === 'dislike' && 'bg-red-100 text-red-600'}
  179. `}
  180. onClick={() => handleFeedback(null)}
  181. >
  182. {
  183. localFeedback.rating === 'like' && (
  184. <ThumbsUp className='w-4 h-4' />
  185. )
  186. }
  187. {
  188. localFeedback.rating === 'dislike' && (
  189. <ThumbsDown className='w-4 h-4' />
  190. )
  191. }
  192. </div>
  193. </TooltipPlus>
  194. )
  195. }
  196. </div>
  197. <EditReplyModal
  198. isShow={isShowReplyModal}
  199. onHide={() => setIsShowReplyModal(false)}
  200. query={question}
  201. answer={content}
  202. onEdited={(editedQuery, editedAnswer) => onAnnotationEdited?.(editedQuery, editedAnswer, index)}
  203. onAdded={(annotationId, authorName, editedQuery, editedAnswer) => onAnnotationAdded?.(annotationId, authorName, editedQuery, editedAnswer, index)}
  204. appId={config?.appId || ''}
  205. messageId={id}
  206. annotationId={annotation?.id || ''}
  207. createdAt={annotation?.created_at}
  208. onRemove={() => onAnnotationRemoved?.(index)}
  209. />
  210. </>
  211. )
  212. }
  213. export default memo(Operation)