Spaces:
Paused
Paused
| import type { FC } from 'react' | |
| import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' | |
| import useSWR from 'swr' | |
| import { useRouter } from 'next/navigation' | |
| import { useTranslation } from 'react-i18next' | |
| import { omit } from 'lodash-es' | |
| import { ArrowRightIcon } from '@heroicons/react/24/solid' | |
| import { | |
| RiErrorWarningFill, | |
| } from '@remixicon/react' | |
| import s from './index.module.css' | |
| import cn from '@/utils/classnames' | |
| import { FieldInfo } from '@/app/components/datasets/documents/detail/metadata' | |
| import Button from '@/app/components/base/button' | |
| import type { FullDocumentDetail, IndexingStatusResponse, ProcessRuleResponse } from '@/models/datasets' | |
| import { fetchIndexingStatusBatch as doFetchIndexingStatus, fetchProcessRule } from '@/service/datasets' | |
| import { DataSourceType } from '@/models/datasets' | |
| import NotionIcon from '@/app/components/base/notion-icon' | |
| import PriorityLabel from '@/app/components/billing/priority-label' | |
| import { Plan } from '@/app/components/billing/type' | |
| import { ZapFast } from '@/app/components/base/icons/src/vender/solid/general' | |
| import UpgradeBtn from '@/app/components/billing/upgrade-btn' | |
| import { useProviderContext } from '@/context/provider-context' | |
| import Tooltip from '@/app/components/base/tooltip' | |
| import { sleep } from '@/utils' | |
| type Props = { | |
| datasetId: string | |
| batchId: string | |
| documents?: FullDocumentDetail[] | |
| indexingType?: string | |
| } | |
| const RuleDetail: FC<{ sourceData?: ProcessRuleResponse }> = ({ sourceData }) => { | |
| const { t } = useTranslation() | |
| const segmentationRuleMap = { | |
| mode: t('datasetDocuments.embedding.mode'), | |
| segmentLength: t('datasetDocuments.embedding.segmentLength'), | |
| textCleaning: t('datasetDocuments.embedding.textCleaning'), | |
| } | |
| const getRuleName = (key: string) => { | |
| if (key === 'remove_extra_spaces') | |
| return t('datasetCreation.stepTwo.removeExtraSpaces') | |
| if (key === 'remove_urls_emails') | |
| return t('datasetCreation.stepTwo.removeUrlEmails') | |
| if (key === 'remove_stopwords') | |
| return t('datasetCreation.stepTwo.removeStopwords') | |
| } | |
| const getValue = useCallback((field: string) => { | |
| let value: string | number | undefined = '-' | |
| switch (field) { | |
| case 'mode': | |
| value = sourceData?.mode === 'automatic' ? (t('datasetDocuments.embedding.automatic') as string) : (t('datasetDocuments.embedding.custom') as string) | |
| break | |
| case 'segmentLength': | |
| value = sourceData?.rules?.segmentation?.max_tokens | |
| break | |
| default: | |
| value = sourceData?.mode === 'automatic' | |
| ? (t('datasetDocuments.embedding.automatic') as string) | |
| // eslint-disable-next-line array-callback-return | |
| : sourceData?.rules?.pre_processing_rules?.map((rule) => { | |
| if (rule.enabled) | |
| return getRuleName(rule.id) | |
| }).filter(Boolean).join(';') | |
| break | |
| } | |
| return value | |
| }, [sourceData]) | |
| return <div className='flex flex-col pt-8 pb-10 first:mt-0'> | |
| {Object.keys(segmentationRuleMap).map((field) => { | |
| return <FieldInfo | |
| key={field} | |
| label={segmentationRuleMap[field as keyof typeof segmentationRuleMap]} | |
| displayedValue={String(getValue(field))} | |
| /> | |
| })} | |
| </div> | |
| } | |
| const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], indexingType }) => { | |
| const { t } = useTranslation() | |
| const { enableBilling, plan } = useProviderContext() | |
| const getFirstDocument = documents[0] | |
| const [indexingStatusBatchDetail, setIndexingStatusDetail] = useState<IndexingStatusResponse[]>([]) | |
| const fetchIndexingStatus = async () => { | |
| const status = await doFetchIndexingStatus({ datasetId, batchId }) | |
| setIndexingStatusDetail(status.data) | |
| return status.data | |
| } | |
| const [isStopQuery, setIsStopQuery] = useState(false) | |
| const isStopQueryRef = useRef(isStopQuery) | |
| useEffect(() => { | |
| isStopQueryRef.current = isStopQuery | |
| }, [isStopQuery]) | |
| const stopQueryStatus = () => { | |
| setIsStopQuery(true) | |
| } | |
| const startQueryStatus = async () => { | |
| if (isStopQueryRef.current) | |
| return | |
| try { | |
| const indexingStatusBatchDetail = await fetchIndexingStatus() | |
| const isCompleted = indexingStatusBatchDetail.every(indexingStatusDetail => ['completed', 'error', 'paused'].includes(indexingStatusDetail.indexing_status)) | |
| if (isCompleted) { | |
| stopQueryStatus() | |
| return | |
| } | |
| await sleep(2500) | |
| await startQueryStatus() | |
| } | |
| catch (e) { | |
| await sleep(2500) | |
| await startQueryStatus() | |
| } | |
| } | |
| useEffect(() => { | |
| startQueryStatus() | |
| return () => { | |
| stopQueryStatus() | |
| } | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, []) | |
| // get rule | |
| const { data: ruleDetail } = useSWR({ | |
| action: 'fetchProcessRule', | |
| params: { documentId: getFirstDocument.id }, | |
| }, apiParams => fetchProcessRule(omit(apiParams, 'action')), { | |
| revalidateOnFocus: false, | |
| }) | |
| const router = useRouter() | |
| const navToDocumentList = () => { | |
| router.push(`/datasets/${datasetId}/documents`) | |
| } | |
| const isEmbedding = useMemo(() => { | |
| return indexingStatusBatchDetail.some(indexingStatusDetail => ['indexing', 'splitting', 'parsing', 'cleaning'].includes(indexingStatusDetail?.indexing_status || '')) | |
| }, [indexingStatusBatchDetail]) | |
| const isEmbeddingCompleted = useMemo(() => { | |
| return indexingStatusBatchDetail.every(indexingStatusDetail => ['completed', 'error', 'paused'].includes(indexingStatusDetail?.indexing_status || '')) | |
| }, [indexingStatusBatchDetail]) | |
| const getSourceName = (id: string) => { | |
| const doc = documents.find(document => document.id === id) | |
| return doc?.name | |
| } | |
| const getFileType = (name?: string) => name?.split('.').pop() || 'txt' | |
| const getSourcePercent = (detail: IndexingStatusResponse) => { | |
| const completedCount = detail.completed_segments || 0 | |
| const totalCount = detail.total_segments || 0 | |
| if (totalCount === 0) | |
| return 0 | |
| const percent = Math.round(completedCount * 100 / totalCount) | |
| return percent > 100 ? 100 : percent | |
| } | |
| const getSourceType = (id: string) => { | |
| const doc = documents.find(document => document.id === id) | |
| return doc?.data_source_type as DataSourceType | |
| } | |
| const getIcon = (id: string) => { | |
| const doc = documents.find(document => document.id === id) | |
| return doc?.data_source_info.notion_page_icon | |
| } | |
| const isSourceEmbedding = (detail: IndexingStatusResponse) => ['indexing', 'splitting', 'parsing', 'cleaning', 'waiting'].includes(detail.indexing_status || '') | |
| return ( | |
| <> | |
| <div className='h-5 flex items-center mb-5'> | |
| <div className={s.embeddingStatus}> | |
| {isEmbedding && t('datasetDocuments.embedding.processing')} | |
| {isEmbeddingCompleted && t('datasetDocuments.embedding.completed')} | |
| </div> | |
| </div> | |
| { | |
| enableBilling && plan.type !== Plan.team && ( | |
| <div className='flex items-center mb-3 p-3 h-14 bg-white border-[0.5px] border-black/5 shadow-md rounded-xl'> | |
| <div className='shrink-0 flex items-center justify-center w-8 h-8 bg-[#FFF6ED] rounded-lg'> | |
| <ZapFast className='w-4 h-4 text-[#FB6514]' /> | |
| </div> | |
| <div className='grow mx-3 text-[13px] font-medium text-gray-700'> | |
| {t('billing.plansCommon.documentProcessingPriorityUpgrade')} | |
| </div> | |
| <UpgradeBtn loc='knowledge-speed-up' /> | |
| </div> | |
| ) | |
| } | |
| <div className={s.progressContainer}> | |
| {indexingStatusBatchDetail.map(indexingStatusDetail => ( | |
| <div key={indexingStatusDetail.id} className={cn( | |
| s.sourceItem, | |
| indexingStatusDetail.indexing_status === 'error' && s.error, | |
| indexingStatusDetail.indexing_status === 'completed' && s.success, | |
| )}> | |
| {isSourceEmbedding(indexingStatusDetail) && ( | |
| <div className={s.progressbar} style={{ width: `${getSourcePercent(indexingStatusDetail)}%` }} /> | |
| )} | |
| <div className={`${s.info} grow`}> | |
| {getSourceType(indexingStatusDetail.id) === DataSourceType.FILE && ( | |
| <div className={cn(s.fileIcon, s[getFileType(getSourceName(indexingStatusDetail.id))])} /> | |
| )} | |
| {getSourceType(indexingStatusDetail.id) === DataSourceType.NOTION && ( | |
| <NotionIcon | |
| className='shrink-0 mr-1' | |
| type='page' | |
| src={getIcon(indexingStatusDetail.id)} | |
| /> | |
| )} | |
| <div className={`${s.name} truncate`} title={getSourceName(indexingStatusDetail.id)}>{getSourceName(indexingStatusDetail.id)}</div> | |
| { | |
| enableBilling && ( | |
| <PriorityLabel /> | |
| ) | |
| } | |
| </div> | |
| <div className='shrink-0'> | |
| {isSourceEmbedding(indexingStatusDetail) && ( | |
| <div className={s.percent}>{`${getSourcePercent(indexingStatusDetail)}%`}</div> | |
| )} | |
| {indexingStatusDetail.indexing_status === 'error' && indexingStatusDetail.error && ( | |
| <Tooltip | |
| popupContent={( | |
| <div className='max-w-[400px]'> | |
| {indexingStatusDetail.error} | |
| </div> | |
| )} | |
| > | |
| <div className={cn(s.percent, s.error, 'flex items-center')}> | |
| Error | |
| <RiErrorWarningFill className='ml-1 w-4 h-4' /> | |
| </div> | |
| </Tooltip> | |
| )} | |
| {indexingStatusDetail.indexing_status === 'error' && !indexingStatusDetail.error && ( | |
| <div className={cn(s.percent, s.error, 'flex items-center')}> | |
| Error | |
| </div> | |
| )} | |
| {indexingStatusDetail.indexing_status === 'completed' && ( | |
| <div className={cn(s.percent, s.success)}>100%</div> | |
| )} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| <RuleDetail sourceData={ruleDetail} /> | |
| <div className='flex items-center gap-2 mt-10'> | |
| <Button className='w-fit' variant='primary' onClick={navToDocumentList}> | |
| <span>{t('datasetCreation.stepThree.navTo')}</span> | |
| <ArrowRightIcon className='h-4 w-4 ml-2 stroke-current stroke-1' /> | |
| </Button> | |
| </div> | |
| </> | |
| ) | |
| } | |
| export default EmbeddingProcess | |