refactor: add create-from-pipeline page and associated components for document processing
parent
9aef4b6d6b
commit
b18519b824
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CreateFromPipeline from '@/app/components/datasets/documents/create-from-pipeline'
|
||||||
|
|
||||||
|
const CreateFromPipelinePage = async () => {
|
||||||
|
return (
|
||||||
|
<CreateFromPipeline />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateFromPipelinePage
|
||||||
@ -1,96 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef } from 'react'
|
|
||||||
import useSWRInfinite from 'swr/infinite'
|
|
||||||
import { debounce } from 'lodash-es'
|
|
||||||
import NewDatasetCard from './NewDatasetCard'
|
|
||||||
import DatasetCard from '@/app/components/datasets/list/dataset-card'
|
|
||||||
import type { DataSetListResponse, FetchDatasetsParams } from '@/models/datasets'
|
|
||||||
import { fetchDatasets } from '@/service/datasets'
|
|
||||||
import { useAppContext } from '@/context/app-context'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
const getKey = (
|
|
||||||
pageIndex: number,
|
|
||||||
previousPageData: DataSetListResponse,
|
|
||||||
tags: string[],
|
|
||||||
keyword: string,
|
|
||||||
includeAll: boolean,
|
|
||||||
) => {
|
|
||||||
if (!pageIndex || previousPageData.has_more) {
|
|
||||||
const params: FetchDatasetsParams = {
|
|
||||||
url: 'datasets',
|
|
||||||
params: {
|
|
||||||
page: pageIndex + 1,
|
|
||||||
limit: 30,
|
|
||||||
include_all: includeAll,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if (tags.length)
|
|
||||||
params.params.tag_ids = tags
|
|
||||||
if (keyword)
|
|
||||||
params.params.keyword = keyword
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
containerRef: React.RefObject<HTMLDivElement>
|
|
||||||
tags: string[]
|
|
||||||
keywords: string
|
|
||||||
includeAll: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const Datasets = ({
|
|
||||||
containerRef,
|
|
||||||
tags,
|
|
||||||
keywords,
|
|
||||||
includeAll,
|
|
||||||
}: Props) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
|
||||||
const { data, isLoading, setSize, mutate } = useSWRInfinite(
|
|
||||||
(pageIndex: number, previousPageData: DataSetListResponse) => getKey(pageIndex, previousPageData, tags, keywords, includeAll),
|
|
||||||
fetchDatasets,
|
|
||||||
{ revalidateFirstPage: false, revalidateAll: true },
|
|
||||||
)
|
|
||||||
const loadingStateRef = useRef(false)
|
|
||||||
const anchorRef = useRef<HTMLAnchorElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadingStateRef.current = isLoading
|
|
||||||
}, [isLoading, t])
|
|
||||||
|
|
||||||
const onScroll = useCallback(
|
|
||||||
debounce(() => {
|
|
||||||
if (!loadingStateRef.current && containerRef.current && anchorRef.current) {
|
|
||||||
const { scrollTop, clientHeight } = containerRef.current
|
|
||||||
const anchorOffset = anchorRef.current.offsetTop
|
|
||||||
if (anchorOffset - scrollTop - clientHeight < 100)
|
|
||||||
setSize(size => size + 1)
|
|
||||||
}
|
|
||||||
}, 50),
|
|
||||||
[setSize],
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const currentContainer = containerRef.current
|
|
||||||
currentContainer?.addEventListener('scroll', onScroll)
|
|
||||||
return () => {
|
|
||||||
currentContainer?.removeEventListener('scroll', onScroll)
|
|
||||||
onScroll.cancel()
|
|
||||||
}
|
|
||||||
}, [onScroll])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav className='grid shrink-0 grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
|
|
||||||
{isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} />}
|
|
||||||
{data?.map(({ data: datasets }) => datasets.map(dataset => (
|
|
||||||
<DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />),
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Datasets
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { basePath } from '@/utils/var'
|
|
||||||
import {
|
|
||||||
RiAddLine,
|
|
||||||
RiArrowRightLine,
|
|
||||||
} from '@remixicon/react'
|
|
||||||
|
|
||||||
const CreateAppCard = (
|
|
||||||
{
|
|
||||||
ref,
|
|
||||||
..._
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='bg-background-default-dimm flex min-h-[160px] flex-col rounded-xl border-[0.5px]
|
|
||||||
border-components-panel-border transition-all duration-200 ease-in-out'
|
|
||||||
>
|
|
||||||
<a ref={ref} className='group flex grow cursor-pointer items-start p-4' href={`${basePath}/datasets/create`}>
|
|
||||||
<div className='flex items-center gap-3'>
|
|
||||||
<div className='flex h-10 w-10 items-center justify-center rounded-lg border border-dashed border-divider-regular bg-background-default-lighter
|
|
||||||
p-2 group-hover:border-solid group-hover:border-effects-highlight group-hover:bg-background-default-dodge'
|
|
||||||
>
|
|
||||||
<RiAddLine className='h-4 w-4 text-text-tertiary group-hover:text-text-accent'/>
|
|
||||||
</div>
|
|
||||||
<div className='system-md-semibold text-text-secondary group-hover:text-text-accent'>{t('dataset.createDataset')}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<div className='system-xs-regular p-4 pt-0 text-text-tertiary'>{t('dataset.createDatasetIntro')}</div>
|
|
||||||
<a className='group flex cursor-pointer items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle p-4' href={`${basePath}/datasets/connect`}>
|
|
||||||
<div className='system-xs-medium text-text-tertiary group-hover:text-text-accent'>{t('dataset.connectDataset')}</div>
|
|
||||||
<RiArrowRightLine className='h-3.5 w-3.5 text-text-tertiary group-hover:text-text-accent' />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateAppCard.displayName = 'CreateAppCard'
|
|
||||||
|
|
||||||
export default CreateAppCard
|
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { AddDocumentsStep } from './types'
|
||||||
|
|
||||||
|
export const useAddDocumentsSteps = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
label: t('datasetPipeline.addDocuments.steps.chooseDatasource'),
|
||||||
|
value: AddDocumentsStep.dataSource,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('datasetPipeline.addDocuments.steps.ProcessDocuments'),
|
||||||
|
value: AddDocumentsStep.processDocuments,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('datasetPipeline.addDocuments.steps.ProcessingDocuments'),
|
||||||
|
value: AddDocumentsStep.processingDocuments,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return steps
|
||||||
|
}
|
||||||
@ -0,0 +1,217 @@
|
|||||||
|
'use client'
|
||||||
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
|
// import StepIndicator from './step-indicator'
|
||||||
|
// import { useTestRunSteps } from './hooks'
|
||||||
|
// import DataSourceOptions from './data-source-options'
|
||||||
|
import type { CrawlResultItem, FileItem } from '@/models/datasets'
|
||||||
|
import { DataSourceType } from '@/models/datasets'
|
||||||
|
// import LocalFile from './data-source/local-file'
|
||||||
|
import produce from 'immer'
|
||||||
|
import { useProviderContextSelector } from '@/context/provider-context'
|
||||||
|
import { DataSourceProvider, type NotionPage } from '@/models/common'
|
||||||
|
// import Notion from './data-source/notion'
|
||||||
|
import VectorSpaceFull from '@/app/components/billing/vector-space-full'
|
||||||
|
// import Firecrawl from './data-source/website/firecrawl'
|
||||||
|
// import JinaReader from './data-source/website/jina-reader'
|
||||||
|
// import WaterCrawl from './data-source/website/water-crawl'
|
||||||
|
// import Actions from './data-source/actions'
|
||||||
|
// import DocumentProcessing from './document-processing'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type { Datasource } from '@/app/components/rag-pipeline/components/panel/test-run/types'
|
||||||
|
import LocalFile from '@/app/components/rag-pipeline/components/panel/test-run/data-source/local-file'
|
||||||
|
import Notion from '@/app/components/rag-pipeline/components/panel/test-run/data-source/notion'
|
||||||
|
import FireCrawl from '@/app/components/rag-pipeline/components/panel/test-run/data-source/website/firecrawl'
|
||||||
|
import JinaReader from '@/app/components/rag-pipeline/components/panel/test-run/data-source/website/jina-reader'
|
||||||
|
import WaterCrawl from '@/app/components/rag-pipeline/components/panel/test-run/data-source/website/water-crawl'
|
||||||
|
import Actions from '@/app/components/rag-pipeline/components/panel/test-run/data-source/actions'
|
||||||
|
import DocumentProcessing from '@/app/components/rag-pipeline/components/panel/test-run/document-processing'
|
||||||
|
import LeftHeader from './left-header'
|
||||||
|
// import { usePipelineRun } from '../../../hooks'
|
||||||
|
// import type { Datasource } from './types'
|
||||||
|
|
||||||
|
const TestRunPanel = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [currentStep, setCurrentStep] = useState(1)
|
||||||
|
const [datasource, setDatasource] = useState<Datasource>()
|
||||||
|
const [fileList, setFiles] = useState<FileItem[]>([])
|
||||||
|
const [notionPages, setNotionPages] = useState<NotionPage[]>([])
|
||||||
|
const [websitePages, setWebsitePages] = useState<CrawlResultItem[]>([])
|
||||||
|
const [websiteCrawlJobId, setWebsiteCrawlJobId] = useState('')
|
||||||
|
|
||||||
|
const plan = useProviderContextSelector(state => state.plan)
|
||||||
|
const enableBilling = useProviderContextSelector(state => state.enableBilling)
|
||||||
|
|
||||||
|
// const steps = useTestRunSteps()
|
||||||
|
|
||||||
|
const allFileLoaded = (fileList.length > 0 && fileList.every(file => file.file.id))
|
||||||
|
const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace
|
||||||
|
const isShowVectorSpaceFull = allFileLoaded && isVectorSpaceFull && enableBilling
|
||||||
|
const notSupportBatchUpload = enableBilling && plan.type === 'sandbox'
|
||||||
|
const nextDisabled = useMemo(() => {
|
||||||
|
if (!fileList.length)
|
||||||
|
return true
|
||||||
|
if (fileList.some(file => !file.file.id))
|
||||||
|
return true
|
||||||
|
return isShowVectorSpaceFull
|
||||||
|
}, [fileList, isShowVectorSpaceFull])
|
||||||
|
|
||||||
|
const nextBtnDisabled = useMemo(() => {
|
||||||
|
if (!datasource) return true
|
||||||
|
if (datasource.type === DataSourceType.FILE)
|
||||||
|
return nextDisabled
|
||||||
|
if (datasource.type === DataSourceType.NOTION)
|
||||||
|
return isShowVectorSpaceFull || !notionPages.length
|
||||||
|
if (datasource.type === DataSourceProvider.fireCrawl
|
||||||
|
|| datasource.type === DataSourceProvider.jinaReader
|
||||||
|
|| datasource.type === DataSourceProvider.waterCrawl)
|
||||||
|
return isShowVectorSpaceFull || !websitePages.length
|
||||||
|
return false
|
||||||
|
}, [datasource, nextDisabled, isShowVectorSpaceFull, notionPages.length, websitePages.length])
|
||||||
|
|
||||||
|
const updateFile = (fileItem: FileItem, progress: number, list: FileItem[]) => {
|
||||||
|
const newList = produce(list, (draft) => {
|
||||||
|
const targetIndex = draft.findIndex(file => file.fileID === fileItem.fileID)
|
||||||
|
draft[targetIndex] = {
|
||||||
|
...draft[targetIndex],
|
||||||
|
progress,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setFiles(newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFileList = (preparedFiles: FileItem[]) => {
|
||||||
|
setFiles(preparedFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateNotionPages = (value: NotionPage[]) => {
|
||||||
|
setNotionPages(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNextStep = useCallback(() => {
|
||||||
|
setCurrentStep(preStep => preStep + 1)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleBackStep = useCallback(() => {
|
||||||
|
setCurrentStep(preStep => preStep - 1)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// const { handleRun } = usePipelineRun()
|
||||||
|
|
||||||
|
const handleProcess = useCallback((data: Record<string, any>) => {
|
||||||
|
if (!datasource)
|
||||||
|
return
|
||||||
|
const datasourceInfo: Record<string, any> = {}
|
||||||
|
let datasource_type = ''
|
||||||
|
if (datasource.type === DataSourceType.FILE) {
|
||||||
|
datasource_type = 'local_file'
|
||||||
|
datasourceInfo.fileId = fileList.map(file => file.fileID)
|
||||||
|
}
|
||||||
|
if (datasource.type === DataSourceType.NOTION) {
|
||||||
|
datasource_type = 'online_document'
|
||||||
|
datasourceInfo.workspaceId = notionPages[0].workspace_id
|
||||||
|
datasourceInfo.page = notionPages.map((page) => {
|
||||||
|
const { workspace_id, ...rest } = page
|
||||||
|
return rest
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (datasource.type === DataSourceProvider.fireCrawl
|
||||||
|
|| datasource.type === DataSourceProvider.jinaReader
|
||||||
|
|| datasource.type === DataSourceProvider.waterCrawl) {
|
||||||
|
datasource_type = 'website_crawl'
|
||||||
|
datasourceInfo.jobId = websiteCrawlJobId
|
||||||
|
datasourceInfo.result = websitePages
|
||||||
|
}
|
||||||
|
// handleRun({
|
||||||
|
// inputs: data,
|
||||||
|
// datasource_type,
|
||||||
|
// datasource_info: datasourceInfo,
|
||||||
|
// })
|
||||||
|
}, [datasource, fileList, notionPages, websiteCrawlJobId, websitePages])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='relative flex h-[calc(100vh-56px)] min-w-[1512px] rounded-t-2xl border-t border-effects-highlight bg-background-default-subtle'
|
||||||
|
>
|
||||||
|
<div className='flex flex-1 flex-col px-14'>
|
||||||
|
<LeftHeader
|
||||||
|
title={t('datasetPipeline.addDocuments.title')}
|
||||||
|
currentStep={currentStep}
|
||||||
|
/>
|
||||||
|
<div className='grow overflow-y-auto'>
|
||||||
|
{
|
||||||
|
currentStep === 1 && (
|
||||||
|
<>
|
||||||
|
<div className='flex flex-col gap-y-4 px-4 py-2'>
|
||||||
|
{/* <DataSourceOptions
|
||||||
|
datasourceNodeId={datasource?.nodeId || ''}
|
||||||
|
onSelect={setDatasource}
|
||||||
|
/> */}
|
||||||
|
{datasource?.type === DataSourceType.FILE && (
|
||||||
|
<LocalFile
|
||||||
|
files={fileList}
|
||||||
|
updateFile={updateFile}
|
||||||
|
updateFileList={updateFileList}
|
||||||
|
notSupportBatchUpload={notSupportBatchUpload}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{datasource?.type === DataSourceType.NOTION && (
|
||||||
|
<Notion
|
||||||
|
nodeId={datasource?.nodeId || ''}
|
||||||
|
notionPages={notionPages}
|
||||||
|
updateNotionPages={updateNotionPages}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{datasource?.type === DataSourceProvider.fireCrawl && (
|
||||||
|
<FireCrawl
|
||||||
|
nodeId={datasource?.nodeId || ''}
|
||||||
|
variables={datasource?.variables}
|
||||||
|
checkedCrawlResult={websitePages}
|
||||||
|
onCheckedCrawlResultChange={setWebsitePages}
|
||||||
|
onJobIdChange={setWebsiteCrawlJobId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{datasource?.type === DataSourceProvider.jinaReader && (
|
||||||
|
<JinaReader
|
||||||
|
nodeId={datasource?.nodeId || ''}
|
||||||
|
variables={datasource?.variables}
|
||||||
|
checkedCrawlResult={websitePages}
|
||||||
|
onCheckedCrawlResultChange={setWebsitePages}
|
||||||
|
onJobIdChange={setWebsiteCrawlJobId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{datasource?.type === DataSourceProvider.waterCrawl && (
|
||||||
|
<WaterCrawl
|
||||||
|
nodeId={datasource?.nodeId || ''}
|
||||||
|
variables={datasource?.variables}
|
||||||
|
checkedCrawlResult={websitePages}
|
||||||
|
onCheckedCrawlResultChange={setWebsitePages}
|
||||||
|
onJobIdChange={setWebsiteCrawlJobId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isShowVectorSpaceFull && (
|
||||||
|
<VectorSpaceFull />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Actions disabled={nextBtnDisabled} handleNextStep={handleNextStep} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
currentStep === 2 && (
|
||||||
|
<DocumentProcessing
|
||||||
|
dataSourceNodeId={datasource?.nodeId || ''}
|
||||||
|
onProcess={handleProcess}
|
||||||
|
onBack={handleBackStep}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Preview */}
|
||||||
|
<div className='flex h-full flex-1 shrink-0 flex-col pl-2 pt-2'>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TestRunPanel
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { RiArrowLeftLine } from '@remixicon/react'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import { useParams } from 'next/navigation'
|
||||||
|
import Effect from '@/app/components/base/effect'
|
||||||
|
import { useAddDocumentsSteps } from './hooks'
|
||||||
|
import StepIndicator from './step-indicator'
|
||||||
|
|
||||||
|
type LeftHeaderProps = {
|
||||||
|
title: string
|
||||||
|
currentStep: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const LeftHeader = ({
|
||||||
|
title,
|
||||||
|
currentStep,
|
||||||
|
}: LeftHeaderProps) => {
|
||||||
|
const { datasetId } = useParams()
|
||||||
|
const steps = useAddDocumentsSteps()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative flex flex-col gap-y-0.5 pb-2 pt-4'>
|
||||||
|
<div className='flex items-center gap-x-2'>
|
||||||
|
<span className='system-2xs-semibold-uppercase bg-pipeline-add-documents-title-bg bg-clip-text text-transparent'>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
<span className='system-2xs-regular text-divider-regular'>/</span>
|
||||||
|
<StepIndicator steps={steps} currentStep={currentStep} />
|
||||||
|
</div>
|
||||||
|
<div className='system-md-semibold text-text-primary'>
|
||||||
|
{steps[currentStep - 1]?.label}
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
className='absolute -left-11 top-3.5'
|
||||||
|
href={`/datasets/${datasetId}/documents`}
|
||||||
|
>
|
||||||
|
<Button variant='secondary-accent' className='size-9 rounded-full p-0'>
|
||||||
|
<RiArrowLeftLine className='size-5 ' />
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
<Effect className='left-8 top-[-34px] opacity-20' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(LeftHeader)
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type Step = {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type StepIndicatorProps = {
|
||||||
|
currentStep: number
|
||||||
|
steps: Step[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepIndicator = ({
|
||||||
|
currentStep,
|
||||||
|
steps,
|
||||||
|
}: StepIndicatorProps) => {
|
||||||
|
return (
|
||||||
|
<div className='flex gap-x-1'>
|
||||||
|
{steps.map((step, index) => {
|
||||||
|
const isActive = index === currentStep - 1
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={step.value}
|
||||||
|
className={cn('h-1 w-1 rounded-lg bg-divider-solid', isActive && 'w-2 bg-state-accent-solid')}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(StepIndicator)
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export enum AddDocumentsStep {
|
||||||
|
dataSource = 'dataSource',
|
||||||
|
processDocuments = 'processDocuments',
|
||||||
|
processingDocuments = 'processingDocuments',
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue