index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. 'use client'
  2. import React, { useMemo, useState } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import cn from 'classnames'
  5. import FilePreview from '../file-preview'
  6. import FileUploader from '../file-uploader'
  7. import NotionPagePreview from '../notion-page-preview'
  8. import EmptyDatasetCreationModal from '../empty-dataset-creation-modal'
  9. import Website from '../website'
  10. import WebsitePreview from '../website/preview'
  11. import s from './index.module.css'
  12. import type { CrawlOptions, CrawlResultItem, FileItem } from '@/models/datasets'
  13. import type { NotionPage } from '@/models/common'
  14. import { DataSourceType } from '@/models/datasets'
  15. import Button from '@/app/components/base/button'
  16. import { NotionPageSelector } from '@/app/components/base/notion-page-selector'
  17. import { useDatasetDetailContext } from '@/context/dataset-detail'
  18. import { useProviderContext } from '@/context/provider-context'
  19. import VectorSpaceFull from '@/app/components/billing/vector-space-full'
  20. type IStepOneProps = {
  21. datasetId?: string
  22. dataSourceType?: DataSourceType
  23. dataSourceTypeDisable: Boolean
  24. hasConnection: boolean
  25. onSetting: () => void
  26. files: FileItem[]
  27. updateFileList: (files: FileItem[]) => void
  28. updateFile: (fileItem: FileItem, progress: number, list: FileItem[]) => void
  29. notionPages?: NotionPage[]
  30. updateNotionPages: (value: NotionPage[]) => void
  31. onStepChange: () => void
  32. changeType: (type: DataSourceType) => void
  33. websitePages?: CrawlResultItem[]
  34. updateWebsitePages: (value: CrawlResultItem[]) => void
  35. onFireCrawlJobIdChange: (jobId: string) => void
  36. crawlOptions: CrawlOptions
  37. onCrawlOptionsChange: (payload: CrawlOptions) => void
  38. }
  39. type NotionConnectorProps = {
  40. onSetting: () => void
  41. }
  42. export const NotionConnector = ({ onSetting }: NotionConnectorProps) => {
  43. const { t } = useTranslation()
  44. return (
  45. <div className={s.notionConnectionTip}>
  46. <span className={s.notionIcon} />
  47. <div className={s.title}>{t('datasetCreation.stepOne.notionSyncTitle')}</div>
  48. <div className={s.tip}>{t('datasetCreation.stepOne.notionSyncTip')}</div>
  49. <Button className='h-8' variant='primary' onClick={onSetting}>{t('datasetCreation.stepOne.connect')}</Button>
  50. </div>
  51. )
  52. }
  53. const StepOne = ({
  54. datasetId,
  55. dataSourceType: inCreatePageDataSourceType,
  56. dataSourceTypeDisable,
  57. changeType,
  58. hasConnection,
  59. onSetting,
  60. onStepChange,
  61. files,
  62. updateFileList,
  63. updateFile,
  64. notionPages = [],
  65. updateNotionPages,
  66. websitePages = [],
  67. updateWebsitePages,
  68. onFireCrawlJobIdChange,
  69. crawlOptions,
  70. onCrawlOptionsChange,
  71. }: IStepOneProps) => {
  72. const { dataset } = useDatasetDetailContext()
  73. const [showModal, setShowModal] = useState(false)
  74. const [currentFile, setCurrentFile] = useState<File | undefined>()
  75. const [currentNotionPage, setCurrentNotionPage] = useState<NotionPage | undefined>()
  76. const [currentWebsite, setCurrentWebsite] = useState<CrawlResultItem | undefined>()
  77. const { t } = useTranslation()
  78. const modalShowHandle = () => setShowModal(true)
  79. const modalCloseHandle = () => setShowModal(false)
  80. const updateCurrentFile = (file: File) => {
  81. setCurrentFile(file)
  82. }
  83. const hideFilePreview = () => {
  84. setCurrentFile(undefined)
  85. }
  86. const updateCurrentPage = (page: NotionPage) => {
  87. setCurrentNotionPage(page)
  88. }
  89. const hideNotionPagePreview = () => {
  90. setCurrentNotionPage(undefined)
  91. }
  92. const hideWebsitePreview = () => {
  93. setCurrentWebsite(undefined)
  94. }
  95. const shouldShowDataSourceTypeList = !datasetId || (datasetId && !dataset?.data_source_type)
  96. const isInCreatePage = shouldShowDataSourceTypeList
  97. const dataSourceType = isInCreatePage ? inCreatePageDataSourceType : dataset?.data_source_type
  98. const { plan, enableBilling } = useProviderContext()
  99. const allFileLoaded = (files.length > 0 && files.every(file => file.file.id))
  100. const hasNotin = notionPages.length > 0
  101. const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace
  102. const isShowVectorSpaceFull = (allFileLoaded || hasNotin) && isVectorSpaceFull && enableBilling
  103. const notSupportBatchUpload = enableBilling && plan.type === 'sandbox'
  104. const nextDisabled = useMemo(() => {
  105. if (!files.length)
  106. return true
  107. if (files.some(file => !file.file.id))
  108. return true
  109. if (isShowVectorSpaceFull)
  110. return true
  111. return false
  112. }, [files])
  113. return (
  114. <div className='flex w-full h-full'>
  115. <div className='grow overflow-y-auto relative'>
  116. {
  117. shouldShowDataSourceTypeList && (
  118. <div className={s.stepHeader}>{t('datasetCreation.steps.one')}</div>
  119. )
  120. }
  121. <div className={s.form}>
  122. {
  123. shouldShowDataSourceTypeList && (
  124. <div className='flex items-center mb-8 flex-wrap gap-y-4'>
  125. <div
  126. className={cn(
  127. s.dataSourceItem,
  128. dataSourceType === DataSourceType.FILE && s.active,
  129. dataSourceTypeDisable && dataSourceType !== DataSourceType.FILE && s.disabled,
  130. )}
  131. onClick={() => {
  132. if (dataSourceTypeDisable)
  133. return
  134. changeType(DataSourceType.FILE)
  135. hideFilePreview()
  136. hideNotionPagePreview()
  137. }}
  138. >
  139. <span className={cn(s.datasetIcon)} />
  140. {t('datasetCreation.stepOne.dataSourceType.file')}
  141. </div>
  142. <div
  143. className={cn(
  144. s.dataSourceItem,
  145. dataSourceType === DataSourceType.NOTION && s.active,
  146. dataSourceTypeDisable && dataSourceType !== DataSourceType.NOTION && s.disabled,
  147. )}
  148. onClick={() => {
  149. if (dataSourceTypeDisable)
  150. return
  151. changeType(DataSourceType.NOTION)
  152. hideFilePreview()
  153. hideNotionPagePreview()
  154. }}
  155. >
  156. <span className={cn(s.datasetIcon, s.notion)} />
  157. {t('datasetCreation.stepOne.dataSourceType.notion')}
  158. </div>
  159. <div
  160. className={cn(
  161. s.dataSourceItem,
  162. dataSourceType === DataSourceType.WEB && s.active,
  163. dataSourceTypeDisable && dataSourceType !== DataSourceType.WEB && s.disabled,
  164. )}
  165. onClick={() => changeType(DataSourceType.WEB)}
  166. >
  167. <span className={cn(s.datasetIcon, s.web)} />
  168. {t('datasetCreation.stepOne.dataSourceType.web')}
  169. </div>
  170. </div>
  171. )
  172. }
  173. {dataSourceType === DataSourceType.FILE && (
  174. <>
  175. <FileUploader
  176. fileList={files}
  177. titleClassName={!shouldShowDataSourceTypeList ? 'mt-[30px] !mb-[44px] !text-lg !font-semibold !text-gray-900' : undefined}
  178. prepareFileList={updateFileList}
  179. onFileListUpdate={updateFileList}
  180. onFileUpdate={updateFile}
  181. onPreview={updateCurrentFile}
  182. notSupportBatchUpload={notSupportBatchUpload}
  183. />
  184. {isShowVectorSpaceFull && (
  185. <div className='max-w-[640px] mb-4'>
  186. <VectorSpaceFull />
  187. </div>
  188. )}
  189. <Button disabled={nextDisabled} className={s.submitButton} variant='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
  190. </>
  191. )}
  192. {dataSourceType === DataSourceType.NOTION && (
  193. <>
  194. {!hasConnection && <NotionConnector onSetting={onSetting} />}
  195. {hasConnection && (
  196. <>
  197. <div className='mb-8 w-[640px]'>
  198. <NotionPageSelector
  199. value={notionPages.map(page => page.page_id)}
  200. onSelect={updateNotionPages}
  201. onPreview={updateCurrentPage}
  202. />
  203. </div>
  204. {isShowVectorSpaceFull && (
  205. <div className='max-w-[640px] mb-4'>
  206. <VectorSpaceFull />
  207. </div>
  208. )}
  209. <Button disabled={isShowVectorSpaceFull || !notionPages.length} className={s.submitButton} variant='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
  210. </>
  211. )}
  212. </>
  213. )}
  214. {dataSourceType === DataSourceType.WEB && (
  215. <>
  216. <div className={cn('mb-8 w-[640px]', !shouldShowDataSourceTypeList && 'mt-12')}>
  217. <Website
  218. onPreview={setCurrentWebsite}
  219. checkedCrawlResult={websitePages}
  220. onCheckedCrawlResultChange={updateWebsitePages}
  221. onJobIdChange={onFireCrawlJobIdChange}
  222. crawlOptions={crawlOptions}
  223. onCrawlOptionsChange={onCrawlOptionsChange}
  224. />
  225. </div>
  226. {isShowVectorSpaceFull && (
  227. <div className='max-w-[640px] mb-4'>
  228. <VectorSpaceFull />
  229. </div>
  230. )}
  231. <Button disabled={isShowVectorSpaceFull || !websitePages.length} className={s.submitButton} variant='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
  232. </>
  233. )}
  234. {!datasetId && (
  235. <>
  236. <div className={s.dividerLine} />
  237. <div onClick={modalShowHandle} className={s.OtherCreationOption}>{t('datasetCreation.stepOne.emptyDatasetCreation')}</div>
  238. </>
  239. )}
  240. </div>
  241. <EmptyDatasetCreationModal show={showModal} onHide={modalCloseHandle} />
  242. </div>
  243. {currentFile && <FilePreview file={currentFile} hidePreview={hideFilePreview} />}
  244. {currentNotionPage && <NotionPagePreview currentPage={currentNotionPage} hidePreview={hideNotionPagePreview} />}
  245. {currentWebsite && <WebsitePreview payload={currentWebsite} hidePreview={hideWebsitePreview} />}
  246. </div>
  247. )
  248. }
  249. export default StepOne