index.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { Fragment, useEffect, useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import cn from 'classnames'
  6. import {
  7. RiAddLine,
  8. } from '@remixicon/react'
  9. import { useContext } from 'use-context-selector'
  10. import {
  11. useCSVDownloader,
  12. } from 'react-papaparse'
  13. import { Menu, Transition } from '@headlessui/react'
  14. import Button from '../../../base/button'
  15. import AddAnnotationModal from '../add-annotation-modal'
  16. import type { AnnotationItemBasic } from '../type'
  17. import BatchAddModal from '../batch-add-annotation-modal'
  18. import s from './style.module.css'
  19. import CustomPopover from '@/app/components/base/popover'
  20. import { FileDownload02, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files'
  21. import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
  22. import I18n from '@/context/i18n'
  23. import { fetchExportAnnotationList } from '@/service/annotation'
  24. import { LanguagesSupported } from '@/i18n/language'
  25. const CSV_HEADER_QA_EN = ['Question', 'Answer']
  26. const CSV_HEADER_QA_CN = ['问题', '答案']
  27. type Props = {
  28. appId: string
  29. onAdd: (payload: AnnotationItemBasic) => void
  30. onAdded: () => void
  31. controlUpdateList: number
  32. }
  33. const HeaderOptions: FC<Props> = ({
  34. appId,
  35. onAdd,
  36. onAdded,
  37. controlUpdateList,
  38. }) => {
  39. const { t } = useTranslation()
  40. const { locale } = useContext(I18n)
  41. const { CSVDownloader, Type } = useCSVDownloader()
  42. const [list, setList] = useState<AnnotationItemBasic[]>([])
  43. const annotationUnavailable = list.length === 0
  44. const listTransformer = (list: AnnotationItemBasic[]) => list.map(
  45. (item: AnnotationItemBasic) => {
  46. const dataString = `{"messages": [{"role": "system", "content": ""}, {"role": "user", "content": ${JSON.stringify(item.question)}}, {"role": "assistant", "content": ${JSON.stringify(item.answer)}}]}`
  47. return dataString
  48. },
  49. )
  50. const JSONLOutput = () => {
  51. const a = document.createElement('a')
  52. const content = listTransformer(list).join('\n')
  53. const file = new Blob([content], { type: 'application/jsonl' })
  54. a.href = URL.createObjectURL(file)
  55. a.download = `annotations-${locale}.jsonl`
  56. a.click()
  57. }
  58. const fetchList = async () => {
  59. const { data }: any = await fetchExportAnnotationList(appId)
  60. setList(data as AnnotationItemBasic[])
  61. }
  62. useEffect(() => {
  63. fetchList()
  64. }, [])
  65. useEffect(() => {
  66. if (controlUpdateList)
  67. fetchList()
  68. }, [controlUpdateList])
  69. const [showBulkImportModal, setShowBulkImportModal] = useState(false)
  70. const Operations = () => {
  71. return (
  72. <div className="w-full py-1">
  73. <button className={s.actionItem} onClick={() => {
  74. setShowBulkImportModal(true)
  75. }}>
  76. <FilePlus02 className={s.actionItemIcon} />
  77. <span className={s.actionName}>{t('appAnnotation.table.header.bulkImport')}</span>
  78. </button>
  79. <Menu as="div" className="relative w-full h-full">
  80. <Menu.Button className={s.actionItem}>
  81. <FileDownload02 className={s.actionItemIcon} />
  82. <span className={s.actionName}>{t('appAnnotation.table.header.bulkExport')}</span>
  83. <ChevronRight className='shrink-0 w-[14px] h-[14px] text-gray-500' />
  84. </Menu.Button>
  85. <Transition
  86. as={Fragment}
  87. enter="transition ease-out duration-100"
  88. enterFrom="transform opacity-0 scale-95"
  89. enterTo="transform opacity-100 scale-100"
  90. leave="transition ease-in duration-75"
  91. leaveFrom="transform opacity-100 scale-100"
  92. leaveTo="transform opacity-0 scale-95"
  93. >
  94. <Menu.Items
  95. className={cn(
  96. `
  97. absolute top-[1px] py-1 min-w-[100px] z-10 bg-white border-[0.5px] border-gray-200
  98. divide-y divide-gray-100 origin-top-right rounded-xl
  99. `,
  100. s.popup,
  101. )}
  102. >
  103. <CSVDownloader
  104. type={Type.Link}
  105. filename={`annotations-${locale}`}
  106. bom={true}
  107. data={[
  108. locale !== LanguagesSupported[1] ? CSV_HEADER_QA_EN : CSV_HEADER_QA_CN,
  109. ...list.map(item => [item.question, item.answer]),
  110. ]}
  111. >
  112. <button disabled={annotationUnavailable} className={s.actionItem}>
  113. <span className={s.actionName}>CSV</span>
  114. </button>
  115. </CSVDownloader>
  116. <button disabled={annotationUnavailable} className={cn(s.actionItem, '!border-0')} onClick={JSONLOutput}>
  117. <span className={s.actionName}>JSONL</span>
  118. </button>
  119. </Menu.Items>
  120. </Transition>
  121. </Menu>
  122. </div>
  123. )
  124. }
  125. const [showAddModal, setShowAddModal] = React.useState(false)
  126. return (
  127. <div className='flex space-x-2'>
  128. <Button variant='primary' onClick={() => setShowAddModal(true)} className='flex items-center !h-8 !px-3 !text-[13px] space-x-2'>
  129. <RiAddLine className='w-4 h-4' />
  130. <div>{t('appAnnotation.table.header.addAnnotation')}</div>
  131. </Button>
  132. <CustomPopover
  133. htmlContent={<Operations />}
  134. position="br"
  135. trigger="click"
  136. btnElement={<div className={cn(s.actionIcon, s.commonIcon)} />}
  137. btnClassName={open =>
  138. cn(
  139. open ? 'border-gray-300 !bg-gray-100 !shadow-none' : 'border-gray-200',
  140. s.actionIconWrapper,
  141. )
  142. }
  143. className={'!w-[155px] h-fit !z-20'}
  144. popupClassName='!w-full !overflow-visible'
  145. manualClose
  146. />
  147. {showAddModal && (
  148. <AddAnnotationModal
  149. isShow={showAddModal}
  150. onHide={() => setShowAddModal(false)}
  151. onAdd={onAdd}
  152. />
  153. )}
  154. {
  155. showBulkImportModal && (
  156. <BatchAddModal
  157. appId={appId}
  158. isShow={showBulkImportModal}
  159. onCancel={() => setShowBulkImportModal(false)}
  160. onAdded={onAdded}
  161. />
  162. )
  163. }
  164. </div>
  165. )
  166. }
  167. export default React.memo(HeaderOptions)