index.tsx 8.9 KB

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