feat: implement dataset creation step one with preview functionality (#30507)

Co-authored-by: CodingOnStar <hanxujiang@dify.ai>
This commit is contained in:
Coding On Star 2026-01-06 18:59:18 +08:00 committed by GitHub
parent 2cc89d30db
commit 64bfcbc4a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1596 additions and 216 deletions

View File

@ -0,0 +1,97 @@
'use client'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { ENABLE_WEBSITE_FIRECRAWL, ENABLE_WEBSITE_JINAREADER, ENABLE_WEBSITE_WATERCRAWL } from '@/config'
import { DataSourceType } from '@/models/datasets'
import { cn } from '@/utils/classnames'
import s from '../index.module.css'
type DataSourceTypeSelectorProps = {
currentType: DataSourceType
disabled: boolean
onChange: (type: DataSourceType) => void
onClearPreviews: (type: DataSourceType) => void
}
type DataSourceLabelKey
= | 'stepOne.dataSourceType.file'
| 'stepOne.dataSourceType.notion'
| 'stepOne.dataSourceType.web'
type DataSourceOption = {
type: DataSourceType
iconClass?: string
labelKey: DataSourceLabelKey
}
const DATA_SOURCE_OPTIONS: DataSourceOption[] = [
{
type: DataSourceType.FILE,
labelKey: 'stepOne.dataSourceType.file',
},
{
type: DataSourceType.NOTION,
iconClass: s.notion,
labelKey: 'stepOne.dataSourceType.notion',
},
{
type: DataSourceType.WEB,
iconClass: s.web,
labelKey: 'stepOne.dataSourceType.web',
},
]
/**
* Data source type selector component for choosing between file, notion, and web sources.
*/
function DataSourceTypeSelector({
currentType,
disabled,
onChange,
onClearPreviews,
}: DataSourceTypeSelectorProps) {
const { t } = useTranslation()
const isWebEnabled = ENABLE_WEBSITE_FIRECRAWL || ENABLE_WEBSITE_JINAREADER || ENABLE_WEBSITE_WATERCRAWL
const handleTypeChange = useCallback((type: DataSourceType) => {
if (disabled)
return
onChange(type)
onClearPreviews(type)
}, [disabled, onChange, onClearPreviews])
const visibleOptions = useMemo(() => DATA_SOURCE_OPTIONS.filter((option) => {
if (option.type === DataSourceType.WEB)
return isWebEnabled
return true
}), [isWebEnabled])
return (
<div className="mb-8 grid grid-cols-3 gap-4">
{visibleOptions.map(option => (
<div
key={option.type}
className={cn(
s.dataSourceItem,
'system-sm-medium',
currentType === option.type && s.active,
disabled && currentType !== option.type && s.disabled,
)}
onClick={() => handleTypeChange(option.type)}
>
<span className={cn(s.datasetIcon, option.iconClass)} />
<span
title={t(option.labelKey, { ns: 'datasetCreation' }) || undefined}
className="truncate"
>
{t(option.labelKey, { ns: 'datasetCreation' })}
</span>
</div>
))}
</div>
)
}
export default DataSourceTypeSelector

View File

@ -0,0 +1,3 @@
export { default as DataSourceTypeSelector } from './data-source-type-selector'
export { default as NextStepButton } from './next-step-button'
export { default as PreviewPanel } from './preview-panel'

View File

@ -0,0 +1,30 @@
'use client'
import { RiArrowRightLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
type NextStepButtonProps = {
disabled: boolean
onClick: () => void
}
/**
* Reusable next step button component for dataset creation flow.
*/
function NextStepButton({ disabled, onClick }: NextStepButtonProps) {
const { t } = useTranslation()
return (
<div className="flex max-w-[640px] justify-end gap-2">
<Button disabled={disabled} variant="primary" onClick={onClick}>
<span className="flex gap-0.5 px-[10px]">
<span className="px-0.5">{t('stepOne.button', { ns: 'datasetCreation' })}</span>
<RiArrowRightLine className="size-4" />
</span>
</Button>
</div>
)
}
export default NextStepButton

View File

@ -0,0 +1,62 @@
'use client'
import type { NotionPage } from '@/models/common'
import type { CrawlResultItem } from '@/models/datasets'
import { useTranslation } from 'react-i18next'
import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal'
import FilePreview from '../../file-preview'
import NotionPagePreview from '../../notion-page-preview'
import WebsitePreview from '../../website/preview'
type PreviewPanelProps = {
currentFile: File | undefined
currentNotionPage: NotionPage | undefined
currentWebsite: CrawlResultItem | undefined
notionCredentialId: string
isShowPlanUpgradeModal: boolean
hideFilePreview: () => void
hideNotionPagePreview: () => void
hideWebsitePreview: () => void
hidePlanUpgradeModal: () => void
}
/**
* Right panel component for displaying file, notion page, or website previews.
*/
function PreviewPanel({
currentFile,
currentNotionPage,
currentWebsite,
notionCredentialId,
isShowPlanUpgradeModal,
hideFilePreview,
hideNotionPagePreview,
hideWebsitePreview,
hidePlanUpgradeModal,
}: PreviewPanelProps) {
const { t } = useTranslation()
return (
<div className="h-full w-1/2 overflow-y-auto">
{currentFile && <FilePreview file={currentFile} hidePreview={hideFilePreview} />}
{currentNotionPage && (
<NotionPagePreview
currentPage={currentNotionPage}
hidePreview={hideNotionPagePreview}
notionCredentialId={notionCredentialId}
/>
)}
{currentWebsite && <WebsitePreview payload={currentWebsite} hidePreview={hideWebsitePreview} />}
{isShowPlanUpgradeModal && (
<PlanUpgradeModal
show
onClose={hidePlanUpgradeModal}
title={t('upgrade.uploadMultiplePages.title', { ns: 'billing' })!}
description={t('upgrade.uploadMultiplePages.description', { ns: 'billing' })!}
/>
)}
</div>
)
}
export default PreviewPanel

View File

@ -0,0 +1,2 @@
export { default as usePreviewState } from './use-preview-state'
export type { PreviewActions, PreviewState, UsePreviewStateReturn } from './use-preview-state'

View File

@ -0,0 +1,70 @@
'use client'
import type { NotionPage } from '@/models/common'
import type { CrawlResultItem } from '@/models/datasets'
import { useCallback, useState } from 'react'
export type PreviewState = {
currentFile: File | undefined
currentNotionPage: NotionPage | undefined
currentWebsite: CrawlResultItem | undefined
}
export type PreviewActions = {
showFilePreview: (file: File) => void
hideFilePreview: () => void
showNotionPagePreview: (page: NotionPage) => void
hideNotionPagePreview: () => void
showWebsitePreview: (website: CrawlResultItem) => void
hideWebsitePreview: () => void
}
export type UsePreviewStateReturn = PreviewState & PreviewActions
/**
* Custom hook for managing preview state across different data source types.
* Handles file, notion page, and website preview visibility.
*/
function usePreviewState(): UsePreviewStateReturn {
const [currentFile, setCurrentFile] = useState<File | undefined>()
const [currentNotionPage, setCurrentNotionPage] = useState<NotionPage | undefined>()
const [currentWebsite, setCurrentWebsite] = useState<CrawlResultItem | undefined>()
const showFilePreview = useCallback((file: File) => {
setCurrentFile(file)
}, [])
const hideFilePreview = useCallback(() => {
setCurrentFile(undefined)
}, [])
const showNotionPagePreview = useCallback((page: NotionPage) => {
setCurrentNotionPage(page)
}, [])
const hideNotionPagePreview = useCallback(() => {
setCurrentNotionPage(undefined)
}, [])
const showWebsitePreview = useCallback((website: CrawlResultItem) => {
setCurrentWebsite(website)
}, [])
const hideWebsitePreview = useCallback(() => {
setCurrentWebsite(undefined)
}, [])
return {
currentFile,
currentNotionPage,
currentWebsite,
showFilePreview,
hideFilePreview,
showNotionPagePreview,
hideNotionPagePreview,
showWebsitePreview,
hideWebsitePreview,
}
}
export default usePreviewState

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +1,25 @@
'use client' 'use client'
import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types' import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types'
import type { DataSourceProvider, NotionPage } from '@/models/common' import type { DataSourceProvider, NotionPage } from '@/models/common'
import type { CrawlOptions, CrawlResultItem, FileItem } from '@/models/datasets' import type { CrawlOptions, CrawlResultItem, FileItem } from '@/models/datasets'
import { RiArrowRightLine, RiFolder6Line } from '@remixicon/react' import { RiFolder6Line } from '@remixicon/react'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import * as React from 'react' import { useCallback, useMemo } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import NotionConnector from '@/app/components/base/notion-connector' import NotionConnector from '@/app/components/base/notion-connector'
import { NotionPageSelector } from '@/app/components/base/notion-page-selector' import { NotionPageSelector } from '@/app/components/base/notion-page-selector'
import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal'
import { Plan } from '@/app/components/billing/type' import { Plan } from '@/app/components/billing/type'
import VectorSpaceFull from '@/app/components/billing/vector-space-full' import VectorSpaceFull from '@/app/components/billing/vector-space-full'
import { ENABLE_WEBSITE_FIRECRAWL, ENABLE_WEBSITE_JINAREADER, ENABLE_WEBSITE_WATERCRAWL } from '@/config'
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { DataSourceType } from '@/models/datasets' import { DataSourceType } from '@/models/datasets'
import { cn } from '@/utils/classnames' import { cn } from '@/utils/classnames'
import EmptyDatasetCreationModal from '../empty-dataset-creation-modal' import EmptyDatasetCreationModal from '../empty-dataset-creation-modal'
import FilePreview from '../file-preview'
import FileUploader from '../file-uploader' import FileUploader from '../file-uploader'
import NotionPagePreview from '../notion-page-preview'
import Website from '../website' import Website from '../website'
import WebsitePreview from '../website/preview' import { DataSourceTypeSelector, NextStepButton, PreviewPanel } from './components'
import { usePreviewState } from './hooks'
import s from './index.module.css' import s from './index.module.css'
import UpgradeCard from './upgrade-card' import UpgradeCard from './upgrade-card'
@ -50,6 +46,24 @@ type IStepOneProps = {
authedDataSourceList: DataSourceAuth[] authedDataSourceList: DataSourceAuth[]
} }
// Helper function to check if notion is authenticated
function checkNotionAuth(authedDataSourceList: DataSourceAuth[]): boolean {
const notionSource = authedDataSourceList.find(item => item.provider === 'notion_datasource')
return Boolean(notionSource && notionSource.credentials_list.length > 0)
}
// Helper function to get notion credential list
function getNotionCredentialList(authedDataSourceList: DataSourceAuth[]) {
return authedDataSourceList.find(item => item.provider === 'notion_datasource')?.credentials_list || []
}
// Lookup table for checking multiple items by data source type
const MULTIPLE_ITEMS_CHECK: Record<DataSourceType, (props: { files: FileItem[], notionPages: NotionPage[], websitePages: CrawlResultItem[] }) => boolean> = {
[DataSourceType.FILE]: ({ files }) => files.length > 1,
[DataSourceType.NOTION]: ({ notionPages }) => notionPages.length > 1,
[DataSourceType.WEB]: ({ websitePages }) => websitePages.length > 1,
}
const StepOne = ({ const StepOne = ({
datasetId, datasetId,
dataSourceType: inCreatePageDataSourceType, dataSourceType: inCreatePageDataSourceType,
@ -72,76 +86,47 @@ const StepOne = ({
onCrawlOptionsChange, onCrawlOptionsChange,
authedDataSourceList, authedDataSourceList,
}: IStepOneProps) => { }: IStepOneProps) => {
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
const [showModal, setShowModal] = useState(false)
const [currentFile, setCurrentFile] = useState<File | undefined>()
const [currentNotionPage, setCurrentNotionPage] = useState<NotionPage | undefined>()
const [currentWebsite, setCurrentWebsite] = useState<CrawlResultItem | undefined>()
const { t } = useTranslation() const { t } = useTranslation()
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
const { plan, enableBilling } = useProviderContext()
const modalShowHandle = () => setShowModal(true) // Preview state management
const modalCloseHandle = () => setShowModal(false) const {
currentFile,
currentNotionPage,
currentWebsite,
showFilePreview,
hideFilePreview,
showNotionPagePreview,
hideNotionPagePreview,
showWebsitePreview,
hideWebsitePreview,
} = usePreviewState()
const updateCurrentFile = useCallback((file: File) => { // Empty dataset modal state
setCurrentFile(file) const [showModal, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false)
}, [])
const hideFilePreview = useCallback(() => { // Plan upgrade modal state
setCurrentFile(undefined) const [isShowPlanUpgradeModal, { setTrue: showPlanUpgradeModal, setFalse: hidePlanUpgradeModal }] = useBoolean(false)
}, [])
const updateCurrentPage = useCallback((page: NotionPage) => {
setCurrentNotionPage(page)
}, [])
const hideNotionPagePreview = useCallback(() => {
setCurrentNotionPage(undefined)
}, [])
const updateWebsite = useCallback((website: CrawlResultItem) => {
setCurrentWebsite(website)
}, [])
const hideWebsitePreview = useCallback(() => {
setCurrentWebsite(undefined)
}, [])
// Computed values
const shouldShowDataSourceTypeList = !datasetId || (datasetId && !dataset?.data_source_type) const shouldShowDataSourceTypeList = !datasetId || (datasetId && !dataset?.data_source_type)
const isInCreatePage = shouldShowDataSourceTypeList const isInCreatePage = shouldShowDataSourceTypeList
const dataSourceType = isInCreatePage ? inCreatePageDataSourceType : dataset?.data_source_type // Default to FILE type when no type is provided from either source
const { plan, enableBilling } = useProviderContext() const dataSourceType = isInCreatePage
const allFileLoaded = (files.length > 0 && files.every(file => file.file.id)) ? (inCreatePageDataSourceType ?? DataSourceType.FILE)
const hasNotin = notionPages.length > 0 : (dataset?.data_source_type ?? DataSourceType.FILE)
const allFileLoaded = files.length > 0 && files.every(file => file.file.id)
const hasNotion = notionPages.length > 0
const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace
const isShowVectorSpaceFull = (allFileLoaded || hasNotin) && isVectorSpaceFull && enableBilling const isShowVectorSpaceFull = (allFileLoaded || hasNotion) && isVectorSpaceFull && enableBilling
const supportBatchUpload = !enableBilling || plan.type !== Plan.sandbox const supportBatchUpload = !enableBilling || plan.type !== Plan.sandbox
const notSupportBatchUpload = !supportBatchUpload
const [isShowPlanUpgradeModal, { const isNotionAuthed = useMemo(() => checkNotionAuth(authedDataSourceList), [authedDataSourceList])
setTrue: showPlanUpgradeModal, const notionCredentialList = useMemo(() => getNotionCredentialList(authedDataSourceList), [authedDataSourceList])
setFalse: hidePlanUpgradeModal,
}] = useBoolean(false)
const onStepChange = useCallback(() => {
if (notSupportBatchUpload) {
let isMultiple = false
if (dataSourceType === DataSourceType.FILE && files.length > 1)
isMultiple = true
if (dataSourceType === DataSourceType.NOTION && notionPages.length > 1) const fileNextDisabled = useMemo(() => {
isMultiple = true
if (dataSourceType === DataSourceType.WEB && websitePages.length > 1)
isMultiple = true
if (isMultiple) {
showPlanUpgradeModal()
return
}
}
doOnStepChange()
}, [dataSourceType, doOnStepChange, files.length, notSupportBatchUpload, notionPages.length, showPlanUpgradeModal, websitePages.length])
const nextDisabled = useMemo(() => {
if (!files.length) if (!files.length)
return true return true
if (files.some(file => !file.file.id)) if (files.some(file => !file.file.id))
@ -149,109 +134,50 @@ const StepOne = ({
return isShowVectorSpaceFull return isShowVectorSpaceFull
}, [files, isShowVectorSpaceFull]) }, [files, isShowVectorSpaceFull])
const isNotionAuthed = useMemo(() => { // Clear previews when switching data source type
if (!authedDataSourceList) const handleClearPreviews = useCallback((newType: DataSourceType) => {
return false if (newType !== DataSourceType.FILE)
const notionSource = authedDataSourceList.find(item => item.provider === 'notion_datasource') hideFilePreview()
if (!notionSource) if (newType !== DataSourceType.NOTION)
return false hideNotionPagePreview()
return notionSource.credentials_list.length > 0 if (newType !== DataSourceType.WEB)
}, [authedDataSourceList]) hideWebsitePreview()
}, [hideFilePreview, hideNotionPagePreview, hideWebsitePreview])
const notionCredentialList = useMemo(() => { // Handle step change with batch upload check
return authedDataSourceList.find(item => item.provider === 'notion_datasource')?.credentials_list || [] const onStepChange = useCallback(() => {
}, [authedDataSourceList]) if (!supportBatchUpload && dataSourceType) {
const checkFn = MULTIPLE_ITEMS_CHECK[dataSourceType]
if (checkFn?.({ files, notionPages, websitePages })) {
showPlanUpgradeModal()
return
}
}
doOnStepChange()
}, [dataSourceType, doOnStepChange, files, supportBatchUpload, notionPages, showPlanUpgradeModal, websitePages])
return ( return (
<div className="h-full w-full overflow-x-auto"> <div className="h-full w-full overflow-x-auto">
<div className="flex h-full w-full min-w-[1440px]"> <div className="flex h-full w-full min-w-[1440px]">
{/* Left Panel - Form */}
<div className="relative h-full w-1/2 overflow-y-auto"> <div className="relative h-full w-1/2 overflow-y-auto">
<div className="flex justify-end"> <div className="flex justify-end">
<div className={cn(s.form)}> <div className={cn(s.form)}>
{ {shouldShowDataSourceTypeList && (
shouldShowDataSourceTypeList && ( <>
<div className={cn(s.stepHeader, 'system-md-semibold text-text-secondary')}> <div className={cn(s.stepHeader, 'system-md-semibold text-text-secondary')}>
{t('steps.one', { ns: 'datasetCreation' })} {t('steps.one', { ns: 'datasetCreation' })}
</div> </div>
) <DataSourceTypeSelector
} currentType={dataSourceType}
{ disabled={dataSourceTypeDisable}
shouldShowDataSourceTypeList && ( onChange={changeType}
<div className="mb-8 grid grid-cols-3 gap-4"> onClearPreviews={handleClearPreviews}
<div />
className={cn( </>
s.dataSourceItem, )}
'system-sm-medium',
dataSourceType === DataSourceType.FILE && s.active, {/* File Data Source */}
dataSourceTypeDisable && dataSourceType !== DataSourceType.FILE && s.disabled,
)}
onClick={() => {
if (dataSourceTypeDisable)
return
changeType(DataSourceType.FILE)
hideNotionPagePreview()
hideWebsitePreview()
}}
>
<span className={cn(s.datasetIcon)} />
<span
title={t('stepOne.dataSourceType.file', { ns: 'datasetCreation' })!}
className="truncate"
>
{t('stepOne.dataSourceType.file', { ns: 'datasetCreation' })}
</span>
</div>
<div
className={cn(
s.dataSourceItem,
'system-sm-medium',
dataSourceType === DataSourceType.NOTION && s.active,
dataSourceTypeDisable && dataSourceType !== DataSourceType.NOTION && s.disabled,
)}
onClick={() => {
if (dataSourceTypeDisable)
return
changeType(DataSourceType.NOTION)
hideFilePreview()
hideWebsitePreview()
}}
>
<span className={cn(s.datasetIcon, s.notion)} />
<span
title={t('stepOne.dataSourceType.notion', { ns: 'datasetCreation' })!}
className="truncate"
>
{t('stepOne.dataSourceType.notion', { ns: 'datasetCreation' })}
</span>
</div>
{(ENABLE_WEBSITE_FIRECRAWL || ENABLE_WEBSITE_JINAREADER || ENABLE_WEBSITE_WATERCRAWL) && (
<div
className={cn(
s.dataSourceItem,
'system-sm-medium',
dataSourceType === DataSourceType.WEB && s.active,
dataSourceTypeDisable && dataSourceType !== DataSourceType.WEB && s.disabled,
)}
onClick={() => {
if (dataSourceTypeDisable)
return
changeType(DataSourceType.WEB)
hideFilePreview()
hideNotionPagePreview()
}}
>
<span className={cn(s.datasetIcon, s.web)} />
<span
title={t('stepOne.dataSourceType.web', { ns: 'datasetCreation' })!}
className="truncate"
>
{t('stepOne.dataSourceType.web', { ns: 'datasetCreation' })}
</span>
</div>
)}
</div>
)
}
{dataSourceType === DataSourceType.FILE && ( {dataSourceType === DataSourceType.FILE && (
<> <>
<FileUploader <FileUploader
@ -260,7 +186,7 @@ const StepOne = ({
prepareFileList={updateFileList} prepareFileList={updateFileList}
onFileListUpdate={updateFileList} onFileListUpdate={updateFileList}
onFileUpdate={updateFile} onFileUpdate={updateFile}
onPreview={updateCurrentFile} onPreview={showFilePreview}
supportBatchUpload={supportBatchUpload} supportBatchUpload={supportBatchUpload}
/> />
{isShowVectorSpaceFull && ( {isShowVectorSpaceFull && (
@ -268,24 +194,17 @@ const StepOne = ({
<VectorSpaceFull /> <VectorSpaceFull />
</div> </div>
)} )}
<div className="flex max-w-[640px] justify-end gap-2"> <NextStepButton disabled={fileNextDisabled} onClick={onStepChange} />
<Button disabled={nextDisabled} variant="primary" onClick={onStepChange}> {enableBilling && plan.type === Plan.sandbox && files.length > 0 && (
<span className="flex gap-0.5 px-[10px]"> <div className="mt-5">
<span className="px-0.5">{t('stepOne.button', { ns: 'datasetCreation' })}</span> <div className="mb-4 h-px bg-divider-subtle" />
<RiArrowRightLine className="size-4" /> <UpgradeCard />
</span> </div>
</Button> )}
</div>
{
enableBilling && plan.type === Plan.sandbox && files.length > 0 && (
<div className="mt-5">
<div className="mb-4 h-px bg-divider-subtle"></div>
<UpgradeCard />
</div>
)
}
</> </>
)} )}
{/* Notion Data Source */}
{dataSourceType === DataSourceType.NOTION && ( {dataSourceType === DataSourceType.NOTION && (
<> <>
{!isNotionAuthed && <NotionConnector onSetting={onSetting} />} {!isNotionAuthed && <NotionConnector onSetting={onSetting} />}
@ -295,7 +214,7 @@ const StepOne = ({
<NotionPageSelector <NotionPageSelector
value={notionPages.map(page => page.page_id)} value={notionPages.map(page => page.page_id)}
onSelect={updateNotionPages} onSelect={updateNotionPages}
onPreview={updateCurrentPage} onPreview={showNotionPagePreview}
credentialList={notionCredentialList} credentialList={notionCredentialList}
onSelectCredential={updateNotionCredentialId} onSelectCredential={updateNotionCredentialId}
datasetId={datasetId} datasetId={datasetId}
@ -306,23 +225,21 @@ const StepOne = ({
<VectorSpaceFull /> <VectorSpaceFull />
</div> </div>
)} )}
<div className="flex max-w-[640px] justify-end gap-2"> <NextStepButton
<Button disabled={isShowVectorSpaceFull || !notionPages.length} variant="primary" onClick={onStepChange}> disabled={isShowVectorSpaceFull || !notionPages.length}
<span className="flex gap-0.5 px-[10px]"> onClick={onStepChange}
<span className="px-0.5">{t('stepOne.button', { ns: 'datasetCreation' })}</span> />
<RiArrowRightLine className="size-4" />
</span>
</Button>
</div>
</> </>
)} )}
</> </>
)} )}
{/* Web Data Source */}
{dataSourceType === DataSourceType.WEB && ( {dataSourceType === DataSourceType.WEB && (
<> <>
<div className={cn('mb-8 w-[640px]', !shouldShowDataSourceTypeList && 'mt-12')}> <div className={cn('mb-8 w-[640px]', !shouldShowDataSourceTypeList && 'mt-12')}>
<Website <Website
onPreview={updateWebsite} onPreview={showWebsitePreview}
checkedCrawlResult={websitePages} checkedCrawlResult={websitePages}
onCheckedCrawlResultChange={updateWebsitePages} onCheckedCrawlResultChange={updateWebsitePages}
onCrawlProviderChange={onWebsiteCrawlProviderChange} onCrawlProviderChange={onWebsiteCrawlProviderChange}
@ -337,48 +254,43 @@ const StepOne = ({
<VectorSpaceFull /> <VectorSpaceFull />
</div> </div>
)} )}
<div className="flex max-w-[640px] justify-end gap-2"> <NextStepButton
<Button disabled={isShowVectorSpaceFull || !websitePages.length} variant="primary" onClick={onStepChange}> disabled={isShowVectorSpaceFull || !websitePages.length}
<span className="flex gap-0.5 px-[10px]"> onClick={onStepChange}
<span className="px-0.5">{t('stepOne.button', { ns: 'datasetCreation' })}</span> />
<RiArrowRightLine className="size-4" />
</span>
</Button>
</div>
</> </>
)} )}
{/* Empty Dataset Creation Link */}
{!datasetId && ( {!datasetId && (
<> <>
<div className="my-8 h-px max-w-[640px] bg-divider-regular" /> <div className="my-8 h-px max-w-[640px] bg-divider-regular" />
<span className="inline-flex cursor-pointer items-center text-[13px] leading-4 text-text-accent" onClick={modalShowHandle}> <span
className="inline-flex cursor-pointer items-center text-[13px] leading-4 text-text-accent"
onClick={openModal}
>
<RiFolder6Line className="mr-1 size-4" /> <RiFolder6Line className="mr-1 size-4" />
{t('stepOne.emptyDatasetCreation', { ns: 'datasetCreation' })} {t('stepOne.emptyDatasetCreation', { ns: 'datasetCreation' })}
</span> </span>
</> </>
)} )}
</div> </div>
<EmptyDatasetCreationModal show={showModal} onHide={modalCloseHandle} /> <EmptyDatasetCreationModal show={showModal} onHide={closeModal} />
</div> </div>
</div> </div>
<div className="h-full w-1/2 overflow-y-auto">
{currentFile && <FilePreview file={currentFile} hidePreview={hideFilePreview} />} {/* Right Panel - Preview */}
{currentNotionPage && ( <PreviewPanel
<NotionPagePreview currentFile={currentFile}
currentPage={currentNotionPage} currentNotionPage={currentNotionPage}
hidePreview={hideNotionPagePreview} currentWebsite={currentWebsite}
notionCredentialId={notionCredentialId} notionCredentialId={notionCredentialId}
/> isShowPlanUpgradeModal={isShowPlanUpgradeModal}
)} hideFilePreview={hideFilePreview}
{currentWebsite && <WebsitePreview payload={currentWebsite} hidePreview={hideWebsitePreview} />} hideNotionPagePreview={hideNotionPagePreview}
{isShowPlanUpgradeModal && ( hideWebsitePreview={hideWebsitePreview}
<PlanUpgradeModal hidePlanUpgradeModal={hidePlanUpgradeModal}
show />
onClose={hidePlanUpgradeModal}
title={t('upgrade.uploadMultiplePages.title', { ns: 'billing' })!}
description={t('upgrade.uploadMultiplePages.description', { ns: 'billing' })!}
/>
)}
</div>
</div> </div>
</div> </div>
) )