index.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useEffect, useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import EditItem, { EditItemType } from '../edit-annotation-modal/edit-item'
  6. import type { AnnotationItem, HitHistoryItem } from '../type'
  7. import s from './style.module.css'
  8. import HitHistoryNoData from './hit-history-no-data'
  9. import cn from '@/utils/classnames'
  10. import Pagination from '@/app/components/base/pagination'
  11. import Drawer from '@/app/components/base/drawer-plus'
  12. import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication'
  13. import Confirm from '@/app/components/base/confirm'
  14. import TabSlider from '@/app/components/base/tab-slider-plain'
  15. import { fetchHitHistoryList } from '@/service/annotation'
  16. import { APP_PAGE_LIMIT } from '@/config'
  17. import useTimestamp from '@/hooks/use-timestamp'
  18. type Props = {
  19. appId: string
  20. isShow: boolean
  21. onHide: () => void
  22. item: AnnotationItem
  23. onSave: (editedQuery: string, editedAnswer: string) => void
  24. onRemove: () => void
  25. }
  26. enum TabType {
  27. annotation = 'annotation',
  28. hitHistory = 'hitHistory',
  29. }
  30. const ViewAnnotationModal: FC<Props> = ({
  31. appId,
  32. isShow,
  33. onHide,
  34. item,
  35. onSave,
  36. onRemove,
  37. }) => {
  38. const { id, question, answer, created_at: createdAt } = item
  39. const [newQuestion, setNewQuery] = useState(question)
  40. const [newAnswer, setNewAnswer] = useState(answer)
  41. const { t } = useTranslation()
  42. const { formatTime } = useTimestamp()
  43. const [currPage, setCurrPage] = React.useState<number>(0)
  44. const [total, setTotal] = useState(0)
  45. const [hitHistoryList, setHitHistoryList] = useState<HitHistoryItem[]>([])
  46. const fetchHitHistory = async (page = 1) => {
  47. try {
  48. const { data, total }: any = await fetchHitHistoryList(appId, id, {
  49. page,
  50. limit: 10,
  51. })
  52. setHitHistoryList(data as HitHistoryItem[])
  53. setTotal(total)
  54. }
  55. catch (e) {
  56. }
  57. }
  58. useEffect(() => {
  59. fetchHitHistory(currPage + 1)
  60. }, [currPage])
  61. const tabs = [
  62. { value: TabType.annotation, text: t('appAnnotation.viewModal.annotatedResponse') },
  63. {
  64. value: TabType.hitHistory,
  65. text: (
  66. hitHistoryList.length > 0
  67. ? (
  68. <div className='flex items-center space-x-1'>
  69. <div>{t('appAnnotation.viewModal.hitHistory')}</div>
  70. <div className='flex px-1.5 item-center rounded-md border border-black/[8%] h-5 text-xs font-medium text-gray-500'>{total} {t(`appAnnotation.viewModal.hit${hitHistoryList.length > 1 ? 's' : ''}`)}</div>
  71. </div>
  72. )
  73. : t('appAnnotation.viewModal.hitHistory')
  74. ),
  75. },
  76. ]
  77. const [activeTab, setActiveTab] = useState(TabType.annotation)
  78. const handleSave = (type: EditItemType, editedContent: string) => {
  79. if (type === EditItemType.Query) {
  80. setNewQuery(editedContent)
  81. onSave(editedContent, newAnswer)
  82. }
  83. else {
  84. setNewAnswer(editedContent)
  85. onSave(newQuestion, editedContent)
  86. }
  87. }
  88. const [showModal, setShowModal] = useState(false)
  89. const annotationTab = (
  90. <>
  91. <EditItem
  92. type={EditItemType.Query}
  93. content={question}
  94. onSave={editedContent => handleSave(EditItemType.Query, editedContent)}
  95. />
  96. <EditItem
  97. type={EditItemType.Answer}
  98. content={answer}
  99. onSave={editedContent => handleSave(EditItemType.Answer, editedContent)}
  100. />
  101. </>
  102. )
  103. const hitHistoryTab = total === 0
  104. ? (<HitHistoryNoData />)
  105. : (
  106. <div>
  107. <table className={cn(s.table, 'w-full min-w-[440px] border-collapse border-0 text-sm')} >
  108. <thead className="h-8 leading-8 border-b border-gray-200 text-gray-500 font-bold">
  109. <tr className='uppercase'>
  110. <td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.query')}</td>
  111. <td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.match')}</td>
  112. <td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.response')}</td>
  113. <td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.source')}</td>
  114. <td className='whitespace-nowrap'>{t('appAnnotation.hitHistoryTable.score')}</td>
  115. <td className='whitespace-nowrap w-[160px]'>{t('appAnnotation.hitHistoryTable.time')}</td>
  116. </tr>
  117. </thead>
  118. <tbody className="text-gray-500">
  119. {hitHistoryList.map(item => (
  120. <tr
  121. key={item.id}
  122. className={'border-b border-gray-200 h-8 hover:bg-gray-50 cursor-pointer'}
  123. >
  124. <td
  125. className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
  126. title={item.question}
  127. >{item.question}</td>
  128. <td
  129. className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
  130. title={item.match}
  131. >{item.match}</td>
  132. <td
  133. className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[250px]'
  134. title={item.response}
  135. >{item.response}</td>
  136. <td>{item.source}</td>
  137. <td>{item.score ? item.score.toFixed(2) : '-'}</td>
  138. <td>{formatTime(item.created_at, t('appLog.dateTimeFormat') as string)}</td>
  139. </tr>
  140. ))}
  141. </tbody>
  142. </table>
  143. {(total && total > APP_PAGE_LIMIT)
  144. ? <Pagination
  145. current={currPage}
  146. onChange={setCurrPage}
  147. total={total}
  148. />
  149. : null}
  150. </div>
  151. )
  152. return (
  153. <div>
  154. <Drawer
  155. isShow={isShow}
  156. onHide={onHide}
  157. maxWidthClassName='!max-w-[800px]'
  158. // t('appAnnotation.editModal.title') as string
  159. title={
  160. <TabSlider
  161. className='shrink-0 relative top-[9px]'
  162. value={activeTab}
  163. onChange={v => setActiveTab(v as TabType)}
  164. options={tabs}
  165. noBorderBottom
  166. itemClassName='!pb-3.5'
  167. />
  168. }
  169. body={(
  170. <div>
  171. <div className='p-6 pb-4 space-y-6'>
  172. {activeTab === TabType.annotation ? annotationTab : hitHistoryTab}
  173. </div>
  174. <Confirm
  175. isShow={showModal}
  176. onCancel={() => setShowModal(false)}
  177. onConfirm={async () => {
  178. await onRemove()
  179. setShowModal(false)
  180. onHide()
  181. }}
  182. title={t('appDebug.feature.annotation.removeConfirm')}
  183. />
  184. </div>
  185. )}
  186. foot={id
  187. ? (
  188. <div className='px-4 flex h-16 items-center justify-between border-t border-black/5 bg-gray-50 rounded-bl-xl rounded-br-xl leading-[18px] text-[13px] font-medium text-gray-500'>
  189. <div
  190. className='flex items-center pl-3 space-x-2 cursor-pointer'
  191. onClick={() => setShowModal(true)}
  192. >
  193. <MessageCheckRemove />
  194. <div>{t('appAnnotation.editModal.removeThisCache')}</div>
  195. </div>
  196. <div>{t('appAnnotation.editModal.createdAt')}&nbsp;{formatTime(createdAt, t('appLog.dateTimeFormat') as string)}</div>
  197. </div>
  198. )
  199. : undefined}
  200. />
  201. </div>
  202. )
  203. }
  204. export default React.memo(ViewAnnotationModal)