index.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback, useEffect, useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import UrlInput from '../base/url-input'
  6. import OptionsWrap from '../base/options-wrap'
  7. import CrawledResult from '../base/crawled-result'
  8. import Crawling from '../base/crawling'
  9. import ErrorMessage from '../base/error-message'
  10. import Header from './header'
  11. import Options from './options'
  12. import cn from '@/utils/classnames'
  13. import { useModalContext } from '@/context/modal-context'
  14. import Toast from '@/app/components/base/toast'
  15. import { checkJinaReaderTaskStatus, createJinaReaderTask } from '@/service/datasets'
  16. import { sleep } from '@/utils'
  17. import type { CrawlOptions, CrawlResultItem } from '@/models/datasets'
  18. const ERROR_I18N_PREFIX = 'common.errorMsg'
  19. const I18N_PREFIX = 'datasetCreation.stepOne.website'
  20. type Props = {
  21. onPreview: (payload: CrawlResultItem) => void
  22. checkedCrawlResult: CrawlResultItem[]
  23. onCheckedCrawlResultChange: (payload: CrawlResultItem[]) => void
  24. onJobIdChange: (jobId: string) => void
  25. crawlOptions: CrawlOptions
  26. onCrawlOptionsChange: (payload: CrawlOptions) => void
  27. }
  28. enum Step {
  29. init = 'init',
  30. running = 'running',
  31. finished = 'finished',
  32. }
  33. const JinaReader: FC<Props> = ({
  34. onPreview,
  35. checkedCrawlResult,
  36. onCheckedCrawlResultChange,
  37. onJobIdChange,
  38. crawlOptions,
  39. onCrawlOptionsChange,
  40. }) => {
  41. const { t } = useTranslation()
  42. const [step, setStep] = useState<Step>(Step.init)
  43. const [controlFoldOptions, setControlFoldOptions] = useState<number>(0)
  44. useEffect(() => {
  45. if (step !== Step.init)
  46. setControlFoldOptions(Date.now())
  47. }, [step])
  48. const { setShowAccountSettingModal } = useModalContext()
  49. const handleSetting = useCallback(() => {
  50. setShowAccountSettingModal({
  51. payload: 'data-source',
  52. })
  53. }, [setShowAccountSettingModal])
  54. const checkValid = useCallback((url: string) => {
  55. let errorMsg = ''
  56. if (!url) {
  57. errorMsg = t(`${ERROR_I18N_PREFIX}.fieldRequired`, {
  58. field: 'url',
  59. })
  60. }
  61. if (!errorMsg && !((url.startsWith('http://') || url.startsWith('https://'))))
  62. errorMsg = t(`${ERROR_I18N_PREFIX}.urlError`)
  63. if (!errorMsg && (crawlOptions.limit === null || crawlOptions.limit === undefined || crawlOptions.limit === '')) {
  64. errorMsg = t(`${ERROR_I18N_PREFIX}.fieldRequired`, {
  65. field: t(`${I18N_PREFIX}.limit`),
  66. })
  67. }
  68. return {
  69. isValid: !errorMsg,
  70. errorMsg,
  71. }
  72. }, [crawlOptions, t])
  73. const isInit = step === Step.init
  74. const isCrawlFinished = step === Step.finished
  75. const isRunning = step === Step.running
  76. const [crawlResult, setCrawlResult] = useState<{
  77. current: number
  78. total: number
  79. data: CrawlResultItem[]
  80. time_consuming: number | string
  81. } | undefined>(undefined)
  82. const [crawlErrorMessage, setCrawlErrorMessage] = useState('')
  83. const showError = isCrawlFinished && crawlErrorMessage
  84. const waitForCrawlFinished = useCallback(async (jobId: string) => {
  85. try {
  86. const res = await checkJinaReaderTaskStatus(jobId) as any
  87. console.log('res', res)
  88. if (res.status === 'completed') {
  89. return {
  90. isError: false,
  91. data: {
  92. ...res,
  93. total: Math.min(res.total, parseFloat(crawlOptions.limit as string)),
  94. },
  95. }
  96. }
  97. if (res.status === 'failed' || !res.status) {
  98. return {
  99. isError: true,
  100. errorMessage: res.message,
  101. data: {
  102. data: [],
  103. },
  104. }
  105. }
  106. // update the progress
  107. setCrawlResult({
  108. ...res,
  109. total: Math.min(res.total, parseFloat(crawlOptions.limit as string)),
  110. })
  111. onCheckedCrawlResultChange(res.data || []) // default select the crawl result
  112. await sleep(2500)
  113. return await waitForCrawlFinished(jobId)
  114. }
  115. catch (e: any) {
  116. const errorBody = await e.json()
  117. return {
  118. isError: true,
  119. errorMessage: errorBody.message,
  120. data: {
  121. data: [],
  122. },
  123. }
  124. }
  125. }, [crawlOptions.limit])
  126. const handleRun = useCallback(async (url: string) => {
  127. const { isValid, errorMsg } = checkValid(url)
  128. if (!isValid) {
  129. Toast.notify({
  130. message: errorMsg!,
  131. type: 'error',
  132. })
  133. return
  134. }
  135. setStep(Step.running)
  136. try {
  137. const startTime = Date.now()
  138. const res = await createJinaReaderTask({
  139. url,
  140. options: crawlOptions,
  141. }) as any
  142. if (res.data) {
  143. const data = {
  144. current: 1,
  145. total: 1,
  146. data: [{
  147. title: res.data.title,
  148. markdown: res.data.content,
  149. description: res.data.description,
  150. source_url: res.data.url,
  151. }],
  152. time_consuming: (Date.now() - startTime) / 1000,
  153. }
  154. setCrawlResult(data)
  155. onCheckedCrawlResultChange(data.data || [])
  156. setCrawlErrorMessage('')
  157. }
  158. else if (res.job_id) {
  159. const jobId = res.job_id
  160. onJobIdChange(jobId)
  161. const { isError, data, errorMessage } = await waitForCrawlFinished(jobId)
  162. if (isError) {
  163. setCrawlErrorMessage(errorMessage || t(`${I18N_PREFIX}.unknownError`))
  164. }
  165. else {
  166. setCrawlResult(data)
  167. onCheckedCrawlResultChange(data.data || []) // default select the crawl result
  168. setCrawlErrorMessage('')
  169. }
  170. }
  171. }
  172. catch (e) {
  173. setCrawlErrorMessage(t(`${I18N_PREFIX}.unknownError`)!)
  174. console.log(e)
  175. }
  176. finally {
  177. setStep(Step.finished)
  178. }
  179. }, [checkValid, crawlOptions, onJobIdChange, t, waitForCrawlFinished])
  180. return (
  181. <div>
  182. <Header onSetting={handleSetting} />
  183. <div className={cn('mt-2 p-4 pb-0 rounded-xl border border-gray-200')}>
  184. <UrlInput onRun={handleRun} isRunning={isRunning} />
  185. <OptionsWrap
  186. className={cn('mt-4')}
  187. controlFoldOptions={controlFoldOptions}
  188. >
  189. <Options className='mt-2' payload={crawlOptions} onChange={onCrawlOptionsChange} />
  190. </OptionsWrap>
  191. {!isInit && (
  192. <div className='mt-3 relative left-[-16px] w-[calc(100%_+_32px)] rounded-b-xl'>
  193. {isRunning
  194. && <Crawling
  195. className='mt-2'
  196. crawledNum={crawlResult?.current || 0}
  197. totalNum={crawlResult?.total || parseFloat(crawlOptions.limit as string) || 0}
  198. />}
  199. {showError && (
  200. <ErrorMessage className='rounded-b-xl' title={t(`${I18N_PREFIX}.exceptionErrorTitle`)} errorMsg={crawlErrorMessage} />
  201. )}
  202. {isCrawlFinished && !showError
  203. && <CrawledResult
  204. className='mb-2'
  205. list={crawlResult?.data || []}
  206. checkedList={checkedCrawlResult}
  207. onSelectedChange={onCheckedCrawlResultChange}
  208. onPreview={onPreview}
  209. usedTime={parseFloat(crawlResult?.time_consuming as string) || 0}
  210. />
  211. }
  212. </div>
  213. )}
  214. </div>
  215. </div>
  216. )
  217. }
  218. export default React.memo(JinaReader)