index.tsx 6.0 KB

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