refactor: enhance document preview functionality and refactor form handling

pull/21398/head
twwu 11 months ago
parent 5ac1e3584d
commit 29d2f2339b

@ -1,7 +1,7 @@
'use client' 'use client'
import { useCallback, useMemo, useRef, useState } from 'react' import { useCallback, useMemo, useRef, useState } from 'react'
import DataSourceOptions from './data-source-options' import DataSourceOptions from './data-source-options'
import type { CrawlResultItem, CustomFile as File, FileIndexingEstimateResponse, FileItem } from '@/models/datasets' import type { CrawlResultItem, DocumentItem, CustomFile as File, FileIndexingEstimateResponse, FileItem } from '@/models/datasets'
import LocalFile from '@/app/components/rag-pipeline/components/panel/test-run/data-source/local-file' import LocalFile from '@/app/components/rag-pipeline/components/panel/test-run/data-source/local-file'
import produce from 'immer' import produce from 'immer'
import { useProviderContextSelector } from '@/context/provider-context' import { useProviderContextSelector } from '@/context/provider-context'
@ -48,6 +48,9 @@ const TestRunPanel = () => {
const isPreview = useRef(false) const isPreview = useRef(false)
const formRef = useRef<any>(null) const formRef = useRef<any>(null)
const previewFile = useRef<DocumentItem>(fileList[0].file as DocumentItem)
const previewNotionPage = useRef<NotionPage>(notionPages[0])
const previewWebsitePage = useRef<CrawlResultItem>(websitePages[0])
const { data: pipelineInfo, isFetching: isFetchingPipelineInfo } = usePublishedPipelineInfo(pipelineId || '') const { data: pipelineInfo, isFetching: isFetchingPipelineInfo } = usePublishedPipelineInfo(pipelineId || '')
@ -132,7 +135,7 @@ const TestRunPanel = () => {
return return
const datasourceInfoList: Record<string, any>[] = [] const datasourceInfoList: Record<string, any>[] = []
if (datasource.type === DatasourceType.localFile) { if (datasource.type === DatasourceType.localFile) {
const { id, name, type, size, extension, mime_type } = fileList[0].file const { id, name, type, size, extension, mime_type } = previewFile.current as File
const documentInfo = { const documentInfo = {
upload_file_id: id, upload_file_id: id,
name, name,
@ -144,7 +147,7 @@ const TestRunPanel = () => {
datasourceInfoList.push(documentInfo) datasourceInfoList.push(documentInfo)
} }
if (datasource.type === DatasourceType.onlineDocument) { if (datasource.type === DatasourceType.onlineDocument) {
const { workspace_id, ...rest } = notionPages[0] const { workspace_id, ...rest } = previewNotionPage.current
const documentInfo = { const documentInfo = {
workspace_id, workspace_id,
page: rest, page: rest,
@ -154,7 +157,7 @@ const TestRunPanel = () => {
if (datasource.type === DatasourceType.websiteCrawl) { if (datasource.type === DatasourceType.websiteCrawl) {
const documentInfo = { const documentInfo = {
job_id: websiteCrawlJobId, job_id: websiteCrawlJobId,
result: websitePages[0], result: previewWebsitePage.current,
} }
datasourceInfoList.push(documentInfo) datasourceInfoList.push(documentInfo)
} }
@ -167,10 +170,10 @@ const TestRunPanel = () => {
is_preview: true, is_preview: true,
}, { }, {
onSuccess: (res) => { onSuccess: (res) => {
setEstimateData(res as FileIndexingEstimateResponse) setEstimateData(res.data.outputs as FileIndexingEstimateResponse)
}, },
}) })
}, [datasource, fileList, notionPages, pipelineId, runPublishedPipeline, websiteCrawlJobId, websitePages]) }, [datasource, pipelineId, runPublishedPipeline, websiteCrawlJobId])
const handleProcess = useCallback(async (data: Record<string, any>) => { const handleProcess = useCallback(async (data: Record<string, any>) => {
if (!datasource) if (!datasource)
@ -231,14 +234,25 @@ const TestRunPanel = () => {
formRef.current?.submit() formRef.current?.submit()
}, []) }, [])
const onClickReset = useCallback(() => {
formRef.current?.reset()
}, [])
const handleSubmit = useCallback((data: Record<string, any>) => { const handleSubmit = useCallback((data: Record<string, any>) => {
isPreview.current ? handlePreviewChunks(data) : handleProcess(data) isPreview.current ? handlePreviewChunks(data) : handleProcess(data)
}, [handlePreviewChunks, handleProcess]) }, [handlePreviewChunks, handleProcess])
const handlePreviewFileChange = useCallback((file: DocumentItem) => {
previewFile.current = file
onClickPreview()
}, [onClickPreview])
const handlePreviewNotionPageChange = useCallback((page: NotionPage) => {
previewNotionPage.current = page
onClickPreview()
}, [onClickPreview])
const handlePreviewWebsiteChange = useCallback((website: CrawlResultItem) => {
previewWebsitePage.current = website
onClickPreview()
}, [onClickPreview])
if (isFetchingPipelineInfo) { if (isFetchingPipelineInfo) {
return ( return (
<Loading type='app' /> <Loading type='app' />
@ -312,7 +326,6 @@ const TestRunPanel = () => {
onProcess={onClickProcess} onProcess={onClickProcess}
onPreview={onClickPreview} onPreview={onClickPreview}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onReset={onClickReset}
onBack={handleBackStep} onBack={handleBackStep}
/> />
) )
@ -353,6 +366,9 @@ const TestRunPanel = () => {
isPending={isPending} isPending={isPending}
estimateData={estimateData} estimateData={estimateData}
onPreview={onClickPreview} onPreview={onClickPreview}
handlePreviewFileChange={handlePreviewFileChange}
handlePreviewNotionPageChange={handlePreviewNotionPageChange}
handlePreviewWebsitePageChange={handlePreviewWebsiteChange}
/> />
)} )}
</div> </div>

@ -26,6 +26,9 @@ type ChunkPreviewProps = {
isPending: boolean isPending: boolean
estimateData: FileIndexingEstimateResponse | undefined estimateData: FileIndexingEstimateResponse | undefined
onPreview: () => void onPreview: () => void
handlePreviewFileChange: (file: DocumentItem) => void
handlePreviewNotionPageChange: (page: NotionPage) => void
handlePreviewWebsitePageChange: (page: CrawlResultItem) => void
} }
const ChunkPreview = ({ const ChunkPreview = ({
@ -37,6 +40,9 @@ const ChunkPreview = ({
isPending, isPending,
estimateData, estimateData,
onPreview, onPreview,
handlePreviewFileChange,
handlePreviewNotionPageChange,
handlePreviewWebsitePageChange,
}: ChunkPreviewProps) => { }: ChunkPreviewProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const currentDocForm = useDatasetDetailContextWithSelector(s => s.dataset?.doc_form) const currentDocForm = useDatasetDetailContextWithSelector(s => s.dataset?.doc_form)
@ -58,6 +64,7 @@ const ChunkPreview = ({
files={files as Array<Required<CustomFile>>} files={files as Array<Required<CustomFile>>}
onChange={(selected) => { onChange={(selected) => {
setPreviewFile(selected) setPreviewFile(selected)
handlePreviewFileChange(selected)
}} }}
value={previewFile} value={previewFile}
/> />
@ -74,6 +81,7 @@ const ChunkPreview = ({
onChange={(selected) => { onChange={(selected) => {
const selectedPage = notionPages.find(page => page.page_id === selected.id) const selectedPage = notionPages.find(page => page.page_id === selected.id)
setPreviewNotionPage(selectedPage!) setPreviewNotionPage(selectedPage!)
handlePreviewNotionPageChange(selectedPage!)
}} }}
value={{ value={{
id: previewNotionPage?.page_id || '', id: previewNotionPage?.page_id || '',
@ -94,6 +102,7 @@ const ChunkPreview = ({
onChange={(selected) => { onChange={(selected) => {
const selectedPage = websitePages.find(page => page.source_url === selected.id) const selectedPage = websitePages.find(page => page.source_url === selected.id)
setPreviewWebsitePage(selectedPage!) setPreviewWebsitePage(selectedPage!)
handlePreviewWebsitePageChange(selectedPage!)
}} }}
value={ value={
{ {
@ -113,7 +122,7 @@ const ChunkPreview = ({
} }
</div> </div>
</PreviewHeader>} </PreviewHeader>}
className='relative flex h-full w-1/2 shrink-0 p-4 pr-0' className='relative flex h-full w-full shrink-0'
mainClassName='space-y-6' mainClassName='space-y-6'
> >
{currentDocForm === ChunkingMode.qa && estimateData?.qa_preview && ( {currentDocForm === ChunkingMode.qa && estimateData?.qa_preview && (
@ -169,7 +178,7 @@ const ChunkPreview = ({
) )
}) })
)} )}
{!isIdle && ( {isIdle && (
<div className='flex h-full w-full items-center justify-center'> <div className='flex h-full w-full items-center justify-center'>
<div className='flex flex-col items-center justify-center gap-3 pb-4'> <div className='flex flex-col items-center justify-center gap-3 pb-4'>
<RiSearchEyeLine className='size-10 text-text-empty-state-icon' /> <RiSearchEyeLine className='size-10 text-text-empty-state-icon' />

@ -2,22 +2,25 @@ import { useAppForm } from '@/app/components/base/form'
import BaseField from '@/app/components/base/form/form-scenarios/base/field' import BaseField from '@/app/components/base/form/form-scenarios/base/field'
import type { BaseConfiguration } from '@/app/components/base/form/form-scenarios/base/types' import type { BaseConfiguration } from '@/app/components/base/form/form-scenarios/base/types'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { useImperativeHandle } from 'react' import { useCallback, useImperativeHandle } from 'react'
import type { ZodSchema } from 'zod' import type { ZodSchema } from 'zod'
import Header from './header'
type OptionsProps = { type OptionsProps = {
initialData: Record<string, any> initialData: Record<string, any>
configurations: BaseConfiguration[] configurations: BaseConfiguration[]
schema: ZodSchema schema: ZodSchema
onSubmit: (data: Record<string, any>) => void onSubmit: (data: Record<string, any>) => void
onPreview: () => void
ref: React.RefObject<any> ref: React.RefObject<any>
} }
const Options = ({ const Form = ({
initialData, initialData,
configurations, configurations,
schema, schema,
onSubmit, onSubmit,
onPreview,
ref, ref,
}: OptionsProps) => { }: OptionsProps) => {
const form = useAppForm({ const form = useAppForm({
@ -48,24 +51,32 @@ const Options = ({
submit: () => { submit: () => {
form.handleSubmit() form.handleSubmit()
}, },
reset: () => {
form.reset()
},
isDirty: () => {
return form.state.isDirty
},
} }
}, [form]) }, [form])
const handleReset = useCallback(() => {
form.reset()
}, [form])
return ( return (
<form <form
className='w-full' className='flex w-full flex-col rounded-lg border border-components-panel-border bg-components-panel-bg'
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
form.handleSubmit() form.handleSubmit()
}} }}
> >
<form.Subscribe
selector={state => state.isDirty}
children={isDirty => (
<Header
onReset={handleReset}
resetDisabled={!isDirty}
onPreview={onPreview}
/>
)}
/>
<div className='flex flex-col gap-3 border-t border-divider-subtle px-4 py-3'> <div className='flex flex-col gap-3 border-t border-divider-subtle px-4 py-3'>
{configurations.map((config, index) => { {configurations.map((config, index) => {
const FieldComponent = BaseField({ const FieldComponent = BaseField({
@ -79,4 +90,4 @@ const Options = ({
) )
} }
export default Options export default Form

@ -5,13 +5,13 @@ import { RiSearchEyeLine } from '@remixicon/react'
type HeaderProps = { type HeaderProps = {
onReset: () => void onReset: () => void
disableReset?: boolean resetDisabled: boolean
onPreview?: () => void onPreview?: () => void
} }
const Header = ({ const Header = ({
onReset, onReset,
disableReset = true, resetDisabled,
onPreview, onPreview,
}: HeaderProps) => { }: HeaderProps) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -21,7 +21,7 @@ const Header = ({
<div className='system-sm-semibold-uppercase grow text-text-secondary'> <div className='system-sm-semibold-uppercase grow text-text-secondary'>
{t('datasetPipeline.addDocuments.stepTwo.chunkSettings')} {t('datasetPipeline.addDocuments.stepTwo.chunkSettings')}
</div> </div>
<Button variant='ghost' disabled={disableReset} onClick={onReset}> <Button variant='ghost' disabled={resetDisabled} onClick={onReset}>
{t('common.operation.reset')} {t('common.operation.reset')}
</Button> </Button>
<Button <Button

@ -1,15 +1,13 @@
import { generateZodSchema } from '@/app/components/base/form/form-scenarios/base/utils' import { generateZodSchema } from '@/app/components/base/form/form-scenarios/base/utils'
import { useConfigurations } from './hooks' import { useConfigurations } from './hooks'
import Options from './options' import Form from './form'
import Actions from './actions' import Actions from './actions'
import Header from './header'
type ProcessDocumentsProps = { type ProcessDocumentsProps = {
dataSourceNodeId: string dataSourceNodeId: string
ref: React.RefObject<any> ref: React.RefObject<any>
onProcess: () => void onProcess: () => void
onPreview: () => void onPreview: () => void
onReset: () => void
onSubmit: (data: Record<string, any>) => void onSubmit: (data: Record<string, any>) => void
onBack: () => void onBack: () => void
} }
@ -19,7 +17,6 @@ const ProcessDocuments = ({
onProcess, onProcess,
onPreview, onPreview,
onSubmit, onSubmit,
onReset,
onBack, onBack,
ref, ref,
}: ProcessDocumentsProps) => { }: ProcessDocumentsProps) => {
@ -28,20 +25,14 @@ const ProcessDocuments = ({
return ( return (
<div className='flex flex-col gap-y-4 pt-4'> <div className='flex flex-col gap-y-4 pt-4'>
<div className='flex flex-col rounded-lg border border-components-panel-border bg-components-panel-bg'> <Form
<Header ref={ref}
onReset={onReset} initialData={initialData}
disableReset={!ref.current?.isDirty()} configurations={configurations}
onPreview={onPreview} schema={schema}
/> onSubmit={onSubmit}
<Options onPreview={onPreview}
ref={ref} />
initialData={initialData}
configurations={configurations}
schema={schema}
onSubmit={onSubmit}
/>
</div>
<Actions onBack={onBack} onProcess={onProcess} /> <Actions onBack={onBack} onProcess={onProcess} />
</div> </div>
) )

Loading…
Cancel
Save