mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 04:26:30 +08:00
feat: implement dataset creation step one with preview functionality (#30507)
Co-authored-by: CodingOnStar <hanxujiang@dify.ai>
This commit is contained in:
parent
2cc89d30db
commit
64bfcbc4a9
@ -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
|
||||||
@ -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'
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export { default as usePreviewState } from './use-preview-state'
|
||||||
|
export type { PreviewActions, PreviewState, UsePreviewStateReturn } from './use-preview-state'
|
||||||
@ -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
|
||||||
1204
web/app/components/datasets/create/step-one/index.spec.tsx
Normal file
1204
web/app/components/datasets/create/step-one/index.spec.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user