mirror of
https://github.com/langgenius/dify.git
synced 2026-04-05 16:59:21 +08:00
chore(web): new lint setup (#30020)
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import type { CredentialSelectorProps } from './index'
|
||||
import type { DataSourceCredential } from '@/types/pipeline'
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import CredentialSelector from './index'
|
||||
import type { CredentialSelectorProps } from './index'
|
||||
import type { DataSourceCredential } from '@/types/pipeline'
|
||||
|
||||
// Mock CredentialTypeEnum to avoid deep import chain issues
|
||||
enum MockCredentialTypeEnum {
|
||||
@@ -80,8 +80,7 @@ const createMockCredentials = (count: number = 3): DataSourceCredential[] =>
|
||||
name: `Credential ${i + 1}`,
|
||||
avatar_url: `https://example.com/avatar-${i + 1}.png`,
|
||||
is_default: i === 0,
|
||||
}),
|
||||
)
|
||||
}))
|
||||
|
||||
const createDefaultProps = (overrides?: Partial<CredentialSelectorProps>): CredentialSelectorProps => ({
|
||||
currentCredentialId: 'cred-1',
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { DataSourceCredential } from '@/types/pipeline'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import type { DataSourceCredential } from '@/types/pipeline'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import Trigger from './trigger'
|
||||
import List from './list'
|
||||
import Trigger from './trigger'
|
||||
|
||||
export type CredentialSelectorProps = {
|
||||
currentCredentialId: string
|
||||
@@ -40,18 +40,18 @@ const CredentialSelector = ({
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={toggle}
|
||||
placement='bottom-start'
|
||||
placement="bottom-start"
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={toggle} className='grow overflow-hidden'>
|
||||
<PortalToFollowElemTrigger onClick={toggle} className="grow overflow-hidden">
|
||||
<Trigger
|
||||
currentCredential={currentCredential}
|
||||
isOpen={open}
|
||||
/>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-10'>
|
||||
<PortalToFollowElemContent className="z-10">
|
||||
<List
|
||||
currentCredentialId={currentCredentialId}
|
||||
credentials={credentials}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CredentialIcon } from '@/app/components/datasets/common/credential-icon'
|
||||
import type { DataSourceCredential } from '@/types/pipeline'
|
||||
import { RiCheckLine } from '@remixicon/react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { CredentialIcon } from '@/app/components/datasets/common/credential-icon'
|
||||
|
||||
type ItemProps = {
|
||||
credential: DataSourceCredential
|
||||
@@ -22,7 +22,7 @@ const Item = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-x-2 rounded-lg p-2 hover:bg-state-base-hover'
|
||||
className="flex cursor-pointer items-center gap-x-2 rounded-lg p-2 hover:bg-state-base-hover"
|
||||
onClick={handleCredentialChange}
|
||||
>
|
||||
<CredentialIcon
|
||||
@@ -30,12 +30,12 @@ const Item = ({
|
||||
name={name}
|
||||
size={20}
|
||||
/>
|
||||
<span className='system-sm-medium grow truncate text-text-secondary'>
|
||||
<span className="system-sm-medium grow truncate text-text-secondary">
|
||||
{name}
|
||||
</span>
|
||||
{
|
||||
isSelected && (
|
||||
<RiCheckLine className='size-4 shrink-0 text-text-accent' />
|
||||
<RiCheckLine className="size-4 shrink-0 text-text-accent" />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ const List = ({
|
||||
onCredentialChange,
|
||||
}: ListProps) => {
|
||||
return (
|
||||
<div className='flex w-[280px] flex-col gap-y-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]'>
|
||||
<div className="flex w-[280px] flex-col gap-y-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]">
|
||||
{
|
||||
credentials.map((credential) => {
|
||||
const isSelected = credential.id === currentCredentialId
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import type { DataSourceCredential } from '@/types/pipeline'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import React from 'react'
|
||||
import { CredentialIcon } from '@/app/components/datasets/common/credential-icon'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type TriggerProps = {
|
||||
currentCredential: DataSourceCredential | undefined
|
||||
@@ -30,11 +30,11 @@ const Trigger = ({
|
||||
name={name}
|
||||
size={20}
|
||||
/>
|
||||
<div className='flex grow items-center gap-x-1 overflow-hidden'>
|
||||
<span className='system-md-semibold grow truncate text-text-secondary'>
|
||||
<div className="flex grow items-center gap-x-1 overflow-hidden">
|
||||
<span className="system-md-semibold grow truncate text-text-secondary">
|
||||
{name}
|
||||
</span>
|
||||
<RiArrowDownSLine className='size-4 shrink-0 text-text-secondary' />
|
||||
<RiArrowDownSLine className="size-4 shrink-0 text-text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DataSourceCredential } from '@/types/pipeline'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import Header from './header'
|
||||
import type { DataSourceCredential } from '@/types/pipeline'
|
||||
|
||||
// Mock CredentialTypeEnum to avoid deep import chain issues
|
||||
enum MockCredentialTypeEnum {
|
||||
@@ -74,8 +74,7 @@ const createMockCredentials = (count: number = 3): DataSourceCredential[] =>
|
||||
name: `Credential ${i + 1}`,
|
||||
avatar_url: `https://example.com/avatar-${i + 1}.png`,
|
||||
is_default: i === 0,
|
||||
}),
|
||||
)
|
||||
}))
|
||||
|
||||
type HeaderProps = React.ComponentProps<typeof Header>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiBookOpenLine, RiEqualizer2Line } from '@remixicon/react'
|
||||
import type { CredentialSelectorProps } from './credential-selector'
|
||||
import CredentialSelector from './credential-selector'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { RiBookOpenLine, RiEqualizer2Line } from '@remixicon/react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import CredentialSelector from './credential-selector'
|
||||
|
||||
type HeaderProps = {
|
||||
docTitle: string
|
||||
@@ -24,35 +24,35 @@ const Header = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex items-center justify-between gap-x-2'>
|
||||
<div className='flex items-center gap-x-1 overflow-hidden'>
|
||||
<div className="flex items-center justify-between gap-x-2">
|
||||
<div className="flex items-center gap-x-1 overflow-hidden">
|
||||
<CredentialSelector
|
||||
{...rest}
|
||||
/>
|
||||
<Divider type='vertical' className='mx-1 h-3.5 shrink-0' />
|
||||
<Divider type="vertical" className="mx-1 h-3.5 shrink-0" />
|
||||
<Tooltip
|
||||
popupContent={t('datasetPipeline.configurationTip', { pluginName })}
|
||||
position='top'
|
||||
position="top"
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
className='size-6 shrink-0 px-1'
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="size-6 shrink-0 px-1"
|
||||
>
|
||||
<RiEqualizer2Line
|
||||
className='h-4 w-4'
|
||||
className="h-4 w-4"
|
||||
onClick={onClickConfiguration}
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<a
|
||||
className='system-xs-medium flex shrink-0 items-center gap-x-1 text-text-accent'
|
||||
className="system-xs-medium flex shrink-0 items-center gap-x-1 text-text-accent"
|
||||
href={docLink}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<RiBookOpenLine className='size-3.5 shrink-0' />
|
||||
<RiBookOpenLine className="size-3.5 shrink-0" />
|
||||
<span title={docTitle}>{docTitle}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
'use client'
|
||||
import type { CustomFile as File, FileItem } from '@/models/datasets'
|
||||
import { RiDeleteBinLine, RiErrorWarningFill, RiUploadCloud2Line } from '@remixicon/react'
|
||||
import { produce } from 'immer'
|
||||
import dynamic from 'next/dynamic'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { RiDeleteBinLine, RiErrorWarningFill, RiUploadCloud2Line } from '@remixicon/react'
|
||||
import DocumentFileIcon from '@/app/components/datasets/common/document-file-icon'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import type { CustomFile as File, FileItem } from '@/models/datasets'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import { upload } from '@/service/base'
|
||||
import { getFileUploadErrorMessage } from '@/app/components/base/file-uploader/utils'
|
||||
import I18n from '@/context/i18n'
|
||||
import { LanguagesSupported } from '@/i18n-config/language'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import DocumentFileIcon from '@/app/components/datasets/common/document-file-icon'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
import { Theme } from '@/types/app'
|
||||
import I18n from '@/context/i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import { LanguagesSupported } from '@/i18n-config/language'
|
||||
import { upload } from '@/service/base'
|
||||
import { useFileUploadConfig } from '@/service/use-common'
|
||||
import { Theme } from '@/types/app'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useDataSourceStore, useDataSourceStoreWithSelector } from '../store'
|
||||
import { produce } from 'immer'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const SimplePieChart = dynamic(() => import('@/app/components/base/simple-pie-chart'), { ssr: false })
|
||||
|
||||
@@ -273,13 +273,13 @@ const LocalFile = ({
|
||||
}, [handleDrop])
|
||||
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<div className="flex flex-col">
|
||||
{!hideUpload && (
|
||||
<input
|
||||
ref={fileUploader}
|
||||
id='fileUploader'
|
||||
className='hidden'
|
||||
type='file'
|
||||
id="fileUploader"
|
||||
className="hidden"
|
||||
type="file"
|
||||
multiple={supportBatchUpload}
|
||||
accept={ACCEPTS.join(',')}
|
||||
onChange={fileChangeHandle}
|
||||
@@ -291,28 +291,31 @@ const LocalFile = ({
|
||||
className={cn(
|
||||
'relative box-border flex min-h-20 flex-col items-center justify-center gap-1 rounded-xl border border-dashed border-components-dropzone-border bg-components-dropzone-bg px-4 py-3 text-xs leading-4 text-text-tertiary',
|
||||
dragging && 'border-components-dropzone-border-accent bg-components-dropzone-bg-accent',
|
||||
)}>
|
||||
<div className='flex min-h-5 items-center justify-center text-sm leading-4 text-text-secondary'>
|
||||
<RiUploadCloud2Line className='mr-2 size-5' />
|
||||
)}
|
||||
>
|
||||
<div className="flex min-h-5 items-center justify-center text-sm leading-4 text-text-secondary">
|
||||
<RiUploadCloud2Line className="mr-2 size-5" />
|
||||
|
||||
<span>
|
||||
{supportBatchUpload ? t('datasetCreation.stepOne.uploader.button') : t('datasetCreation.stepOne.uploader.buttonSingleFile')}
|
||||
{allowedExtensions.length > 0 && (
|
||||
<label className='ml-1 cursor-pointer text-text-accent' onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
|
||||
<label className="ml-1 cursor-pointer text-text-accent" onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div>{t('datasetCreation.stepOne.uploader.tip', {
|
||||
size: fileUploadConfig.file_size_limit,
|
||||
supportTypes: supportTypesShowNames,
|
||||
batchCount: fileUploadConfig.batch_count_limit,
|
||||
totalCount: fileUploadConfig.file_upload_limit,
|
||||
})}</div>
|
||||
{dragging && <div ref={dragRef} className='absolute left-0 top-0 h-full w-full' />}
|
||||
<div>
|
||||
{t('datasetCreation.stepOne.uploader.tip', {
|
||||
size: fileUploadConfig.file_size_limit,
|
||||
supportTypes: supportTypesShowNames,
|
||||
batchCount: fileUploadConfig.batch_count_limit,
|
||||
totalCount: fileUploadConfig.file_upload_limit,
|
||||
})}
|
||||
</div>
|
||||
{dragging && <div ref={dragRef} className="absolute left-0 top-0 h-full w-full" />}
|
||||
</div>
|
||||
)}
|
||||
{localFileList.length > 0 && (
|
||||
<div className='mt-1 flex flex-col gap-y-1'>
|
||||
<div className="mt-1 flex flex-col gap-y-1">
|
||||
{localFileList.map((fileItem, index) => {
|
||||
const isUploading = fileItem.progress >= 0 && fileItem.progress < 100
|
||||
const isError = fileItem.progress === -2
|
||||
@@ -325,38 +328,41 @@ const LocalFile = ({
|
||||
isError && 'border-state-destructive-border bg-state-destructive-hover',
|
||||
)}
|
||||
>
|
||||
<div className='flex w-12 shrink-0 items-center justify-center'>
|
||||
<div className="flex w-12 shrink-0 items-center justify-center">
|
||||
<DocumentFileIcon
|
||||
size='lg'
|
||||
className='shrink-0'
|
||||
size="lg"
|
||||
className="shrink-0"
|
||||
name={fileItem.file.name}
|
||||
extension={getFileType(fileItem.file)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex shrink grow flex-col gap-0.5'>
|
||||
<div className='flex w-full'>
|
||||
<div className='w-0 grow truncate text-xs text-text-secondary'>{fileItem.file.name}</div>
|
||||
<div className="flex shrink grow flex-col gap-0.5">
|
||||
<div className="flex w-full">
|
||||
<div className="w-0 grow truncate text-xs text-text-secondary">{fileItem.file.name}</div>
|
||||
</div>
|
||||
<div className='w-full truncate text-2xs leading-3 text-text-tertiary'>
|
||||
<span className='uppercase'>{getFileType(fileItem.file)}</span>
|
||||
<span className='px-1 text-text-quaternary'>·</span>
|
||||
<div className="w-full truncate text-2xs leading-3 text-text-tertiary">
|
||||
<span className="uppercase">{getFileType(fileItem.file)}</span>
|
||||
<span className="px-1 text-text-quaternary">·</span>
|
||||
<span>{getFileSize(fileItem.file.size)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex w-16 shrink-0 items-center justify-end gap-1 pr-3'>
|
||||
<div className="flex w-16 shrink-0 items-center justify-end gap-1 pr-3">
|
||||
{isUploading && (
|
||||
<SimplePieChart percentage={fileItem.progress} stroke={chartColor} fill={chartColor} animationDuration={0} />
|
||||
)}
|
||||
{
|
||||
isError && (
|
||||
<RiErrorWarningFill className='size-4 text-text-destructive' />
|
||||
<RiErrorWarningFill className="size-4 text-text-destructive" />
|
||||
)
|
||||
}
|
||||
<span className='flex h-6 w-6 cursor-pointer items-center justify-center' onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
removeFile(fileItem.fileID)
|
||||
}}>
|
||||
<RiDeleteBinLine className='size-4 text-text-tertiary' />
|
||||
<span
|
||||
className="flex h-6 w-6 cursor-pointer items-center justify-center"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
removeFile(fileItem.fileID)
|
||||
}}
|
||||
>
|
||||
<RiDeleteBinLine className="size-4 text-text-tertiary" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import type { DataSourceNotionWorkspace, NotionPage } from '@/models/common'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import OnlineDocuments from './index'
|
||||
import type { DataSourceNotionWorkspace, NotionPage } from '@/models/common'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { VarKindType } from '@/app/components/workflow/nodes/_base/types'
|
||||
import OnlineDocuments from './index'
|
||||
|
||||
// ==========================================
|
||||
// Mock Modules
|
||||
@@ -99,7 +99,7 @@ vi.mock('../base/header', () => ({
|
||||
|
||||
// Mock SearchInput component
|
||||
vi.mock('@/app/components/base/notion-page-selector/search-input', () => ({
|
||||
default: ({ value, onChange }: { value: string; onChange: (v: string) => void }) => (
|
||||
default: ({ value, onChange }: { value: string, onChange: (v: string) => void }) => (
|
||||
<div data-testid="search-input">
|
||||
<input
|
||||
data-testid="search-input-field"
|
||||
@@ -179,7 +179,7 @@ const createMockWorkspace = (overrides?: Partial<DataSourceNotionWorkspace>): Da
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const createMockCredential = (overrides?: Partial<{ id: string; name: string }>) => ({
|
||||
const createMockCredential = (overrides?: Partial<{ id: string, name: string }>) => ({
|
||||
id: 'cred-1',
|
||||
name: 'Test Credential',
|
||||
avatar_url: 'https://example.com/avatar.png',
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
import SearchInput from '@/app/components/base/notion-page-selector/search-input'
|
||||
import PageSelector from './page-selector'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import type { DataSourceNotionPageMap, DataSourceNotionWorkspace } from '@/models/common'
|
||||
import Header from '../base/header'
|
||||
import type { DataSourceNodeCompletedResponse, DataSourceNodeErrorResponse } from '@/types/pipeline'
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import SearchInput from '@/app/components/base/notion-page-selector/search-input'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { DatasourceType } from '@/models/pipeline'
|
||||
import { ssePost } from '@/service/base'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { DataSourceNodeCompletedResponse, DataSourceNodeErrorResponse } from '@/types/pipeline'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { useDataSourceStore, useDataSourceStoreWithSelector } from '../store'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import Title from './title'
|
||||
import { useGetDataSourceAuth } from '@/service/use-datasource'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import Header from '../base/header'
|
||||
import { useDataSourceStore, useDataSourceStoreWithSelector } from '../store'
|
||||
import PageSelector from './page-selector'
|
||||
import Title from './title'
|
||||
|
||||
type OnlineDocumentsProps = {
|
||||
nodeId: string
|
||||
@@ -106,7 +106,8 @@ const OnlineDocuments = ({
|
||||
}, [dataSourceStore, datasourceNodeRunURL, nodeData.datasource_parameters])
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentCredentialId) return
|
||||
if (!currentCredentialId)
|
||||
return
|
||||
getOnlineDocuments()
|
||||
}, [currentCredentialId])
|
||||
|
||||
@@ -134,9 +135,9 @@ const OnlineDocuments = ({
|
||||
}, [setShowAccountSettingModal])
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-y-2'>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<Header
|
||||
docTitle='Docs'
|
||||
docTitle="Docs"
|
||||
docLink={docLink('/guides/knowledge-base/knowledge-pipeline/authorize-data-source')}
|
||||
onClickConfiguration={handleSetting}
|
||||
pluginName={nodeData.datasource_label}
|
||||
@@ -144,9 +145,9 @@ const OnlineDocuments = ({
|
||||
onCredentialChange={onCredentialChange}
|
||||
credentials={dataSourceAuth?.result || []}
|
||||
/>
|
||||
<div className='rounded-xl border border-components-panel-border bg-background-default-subtle'>
|
||||
<div className='flex items-center gap-x-2 rounded-t-xl border-b border-b-divider-regular bg-components-panel-bg p-1 pl-3'>
|
||||
<div className='flex grow items-center'>
|
||||
<div className="rounded-xl border border-components-panel-border bg-background-default-subtle">
|
||||
<div className="flex items-center gap-x-2 rounded-t-xl border-b border-b-divider-regular bg-components-panel-bg p-1 pl-3">
|
||||
<div className="flex grow items-center">
|
||||
<Title name={nodeData.datasource_label} />
|
||||
</div>
|
||||
<SearchInput
|
||||
@@ -154,25 +155,27 @@ const OnlineDocuments = ({
|
||||
onChange={handleSearchValueChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='overflow-hidden rounded-b-xl'>
|
||||
{documentsData?.length ? (
|
||||
<PageSelector
|
||||
checkedIds={selectedPagesId}
|
||||
disabledValue={new Set()}
|
||||
searchValue={searchValue}
|
||||
list={documentsData[0].pages || []}
|
||||
pagesMap={PagesMapAndSelectedPagesId}
|
||||
onSelect={handleSelectPages}
|
||||
canPreview={!isInPipeline}
|
||||
onPreview={handlePreviewPage}
|
||||
isMultipleChoice={supportBatchUpload}
|
||||
currentCredentialId={currentCredentialId}
|
||||
/>
|
||||
) : (
|
||||
<div className='flex h-[296px] items-center justify-center'>
|
||||
<Loading type='app' />
|
||||
</div>
|
||||
)}
|
||||
<div className="overflow-hidden rounded-b-xl">
|
||||
{documentsData?.length
|
||||
? (
|
||||
<PageSelector
|
||||
checkedIds={selectedPagesId}
|
||||
disabledValue={new Set()}
|
||||
searchValue={searchValue}
|
||||
list={documentsData[0].pages || []}
|
||||
pagesMap={PagesMapAndSelectedPagesId}
|
||||
onSelect={handleSelectPages}
|
||||
canPreview={!isInPipeline}
|
||||
onPreview={handlePreviewPage}
|
||||
isMultipleChoice={supportBatchUpload}
|
||||
currentCredentialId={currentCredentialId}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className="flex h-[296px] items-center justify-center">
|
||||
<Loading type="app" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { NotionPageTreeItem, NotionPageTreeMap } from './index'
|
||||
import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import PageSelector from './index'
|
||||
import type { NotionPageTreeItem, NotionPageTreeMap } from './index'
|
||||
import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common'
|
||||
import { recursivePushInParentDescendants } from './utils'
|
||||
|
||||
// ==========================================
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FixedSizeList as List } from 'react-window'
|
||||
import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common'
|
||||
import Item from './item'
|
||||
import { recursivePushInParentDescendants } from './utils'
|
||||
|
||||
@@ -153,7 +153,7 @@ const PageSelector = ({
|
||||
|
||||
if (!currentDataList.length) {
|
||||
return (
|
||||
<div className='flex h-[296px] items-center justify-center text-[13px] text-text-tertiary'>
|
||||
<div className="flex h-[296px] items-center justify-center text-[13px] text-text-tertiary">
|
||||
{t('common.dataSource.notion.selector.noSearchResult')}
|
||||
</div>
|
||||
)
|
||||
@@ -161,11 +161,11 @@ const PageSelector = ({
|
||||
|
||||
return (
|
||||
<List
|
||||
className='py-2'
|
||||
className="py-2"
|
||||
height={296}
|
||||
itemCount={currentDataList.length}
|
||||
itemSize={28}
|
||||
width='100%'
|
||||
width="100%"
|
||||
itemKey={(index, data) => data.dataList[index].page_id}
|
||||
itemData={{
|
||||
dataList: currentDataList,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { ListChildComponentProps } from 'react-window'
|
||||
import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common'
|
||||
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { areEqual } from 'react-window'
|
||||
import type { ListChildComponentProps } from 'react-window'
|
||||
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import NotionIcon from '@/app/components/base/notion-icon'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common'
|
||||
import Radio from '@/app/components/base/radio/ui'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type NotionPageTreeItem = {
|
||||
children: Set<string>
|
||||
@@ -63,14 +63,14 @@ const Item = ({ index, style, data }: ListChildComponentProps<{
|
||||
if (hasChild) {
|
||||
return (
|
||||
<div
|
||||
className='mr-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-md hover:bg-components-button-ghost-bg-hover'
|
||||
className="mr-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-md hover:bg-components-button-ghost-bg-hover"
|
||||
style={{ marginLeft: current.depth * 8 }}
|
||||
onClick={() => handleToggle(index)}
|
||||
>
|
||||
{
|
||||
current.expand
|
||||
? <RiArrowDownSLine className='h-4 w-4 text-text-tertiary' />
|
||||
: <RiArrowRightSLine className='h-4 w-4 text-text-tertiary' />
|
||||
? <RiArrowDownSLine className="h-4 w-4 text-text-tertiary" />
|
||||
: <RiArrowRightSLine className="h-4 w-4 text-text-tertiary" />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
@@ -81,42 +81,44 @@ const Item = ({ index, style, data }: ListChildComponentProps<{
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className='mr-1 h-5 w-5 shrink-0' style={{ marginLeft: current.depth * 8 }} />
|
||||
<div className="mr-1 h-5 w-5 shrink-0" style={{ marginLeft: current.depth * 8 }} />
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('group flex cursor-pointer items-center rounded-md pl-2 pr-[2px] hover:bg-state-base-hover',
|
||||
previewPageId === current.page_id && 'bg-state-base-hover')}
|
||||
className={cn('group flex cursor-pointer items-center rounded-md pl-2 pr-[2px] hover:bg-state-base-hover', previewPageId === current.page_id && 'bg-state-base-hover')}
|
||||
style={{ ...style, top: style.top as number + 8, left: 8, right: 8, width: 'calc(100% - 16px)' }}
|
||||
>
|
||||
{isMultipleChoice ? (
|
||||
<Checkbox
|
||||
className='mr-2 shrink-0'
|
||||
checked={checkedIds.has(current.page_id)}
|
||||
disabled={disabled}
|
||||
onCheck={() => {
|
||||
handleCheck(index)
|
||||
}}
|
||||
/>) : (
|
||||
<Radio
|
||||
className='mr-2 shrink-0'
|
||||
isChecked={checkedIds.has(current.page_id)}
|
||||
disabled={disabled}
|
||||
onCheck={() => {
|
||||
handleCheck(index)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isMultipleChoice
|
||||
? (
|
||||
<Checkbox
|
||||
className="mr-2 shrink-0"
|
||||
checked={checkedIds.has(current.page_id)}
|
||||
disabled={disabled}
|
||||
onCheck={() => {
|
||||
handleCheck(index)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Radio
|
||||
className="mr-2 shrink-0"
|
||||
isChecked={checkedIds.has(current.page_id)}
|
||||
disabled={disabled}
|
||||
onCheck={() => {
|
||||
handleCheck(index)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!searchValue && renderArrow()}
|
||||
<NotionIcon
|
||||
className='mr-1 shrink-0'
|
||||
type='page'
|
||||
className="mr-1 shrink-0"
|
||||
type="page"
|
||||
src={current.page_icon}
|
||||
/>
|
||||
<div
|
||||
className='grow truncate text-[13px] font-medium leading-4 text-text-secondary'
|
||||
className="grow truncate text-[13px] font-medium leading-4 text-text-secondary"
|
||||
title={current.page_name}
|
||||
>
|
||||
{current.page_name}
|
||||
@@ -124,10 +126,11 @@ const Item = ({ index, style, data }: ListChildComponentProps<{
|
||||
{
|
||||
canPreview && (
|
||||
<div
|
||||
className='ml-1 hidden h-6 shrink-0 cursor-pointer items-center rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-2 text-xs
|
||||
className="ml-1 hidden h-6 shrink-0 cursor-pointer items-center rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-2 text-xs
|
||||
font-medium leading-4 text-components-button-secondary-text shadow-xs shadow-shadow-shadow-3 backdrop-blur-[10px]
|
||||
hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover group-hover:flex'
|
||||
onClick={() => handlePreview(index)}>
|
||||
hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover group-hover:flex"
|
||||
onClick={() => handlePreview(index)}
|
||||
>
|
||||
{t('common.dataSource.notion.selector.preview')}
|
||||
</div>
|
||||
)
|
||||
@@ -135,7 +138,7 @@ const Item = ({ index, style, data }: ListChildComponentProps<{
|
||||
{
|
||||
searchValue && (
|
||||
<div
|
||||
className='ml-1 max-w-[120px] shrink-0 truncate text-xs text-text-quaternary'
|
||||
className="ml-1 max-w-[120px] shrink-0 truncate text-xs text-text-quaternary"
|
||||
title={breadCrumbs.join(' / ')}
|
||||
>
|
||||
{breadCrumbs.join(' / ')}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DataSourceNotionPageMap } from '@/models/common'
|
||||
import type { NotionPageTreeItem, NotionPageTreeMap } from './index'
|
||||
import type { DataSourceNotionPageMap } from '@/models/common'
|
||||
|
||||
export const recursivePushInParentDescendants = (
|
||||
pagesMap: DataSourceNotionPageMap,
|
||||
|
||||
@@ -11,7 +11,7 @@ const Title = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='system-sm-medium px-[5px] py-1 text-text-secondary'>
|
||||
<div className="system-sm-medium px-[5px] py-1 text-text-secondary">
|
||||
{t('datasetPipeline.onlineDocument.pageSelectorTitle', { name })}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import Connect from './index'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
|
||||
// ==========================================
|
||||
// Mock Modules
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { Icon3Dots } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import { useToolIcon } from '@/app/components/workflow/hooks'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
|
||||
type ConnectProps = {
|
||||
@@ -19,27 +19,27 @@ const Connect = ({
|
||||
const toolIcon = useToolIcon(nodeData)
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-start gap-y-2 rounded-xl bg-workflow-process-bg p-6'>
|
||||
<div className='flex size-12 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg p-1 shadow-lg shadow-shadow-shadow-5'>
|
||||
<div className="flex flex-col items-start gap-y-2 rounded-xl bg-workflow-process-bg p-6">
|
||||
<div className="flex size-12 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg p-1 shadow-lg shadow-shadow-shadow-5">
|
||||
<BlockIcon
|
||||
type={BlockEnum.DataSource}
|
||||
toolIcon={toolIcon}
|
||||
size='md'
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1'>
|
||||
<div className='flex flex-col gap-y-1 pb-3 pt-1'>
|
||||
<div className='system-md-semibold text-text-secondary'>
|
||||
<span className='relative'>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<div className="flex flex-col gap-y-1 pb-3 pt-1">
|
||||
<div className="system-md-semibold text-text-secondary">
|
||||
<span className="relative">
|
||||
{t('datasetPipeline.onlineDrive.notConnected', { name: nodeData.title })}
|
||||
<Icon3Dots className='absolute -right-2.5 -top-1.5 size-4 text-text-secondary' />
|
||||
<Icon3Dots className="absolute -right-2.5 -top-1.5 size-4 text-text-secondary" />
|
||||
</span>
|
||||
</div>
|
||||
<div className='system-sm-regular text-text-tertiary'>
|
||||
<div className="system-sm-regular text-text-tertiary">
|
||||
{t('datasetPipeline.onlineDrive.notConnectedTip', { name: nodeData.title })}
|
||||
</div>
|
||||
</div>
|
||||
<Button className='w-fit' variant='primary' onClick={onSetting}>
|
||||
<Button className="w-fit" variant="primary" onClick={onSetting}>
|
||||
{t('datasetCreation.stepOne.connect')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BucketsGray } from '@/app/components/base/icons/src/public/knowledge/online-drive'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type BucketProps = {
|
||||
@@ -33,16 +33,16 @@ const Bucket = ({
|
||||
popupContent={t('datasetPipeline.onlineDrive.breadcrumbs.allBuckets')}
|
||||
>
|
||||
<button
|
||||
type='button'
|
||||
className='flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover'
|
||||
type="button"
|
||||
className="flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover"
|
||||
onClick={handleBackToBucketList}
|
||||
>
|
||||
<BucketsGray />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span className='system-xs-regular text-divider-deep'>/</span>
|
||||
<span className="system-xs-regular text-divider-deep">/</span>
|
||||
<button
|
||||
type='button'
|
||||
type="button"
|
||||
className={cn(
|
||||
'max-w-full shrink truncate rounded-md px-[5px] py-1',
|
||||
isActive ? 'system-sm-medium text-text-secondary' : 'system-sm-regular text-text-tertiary',
|
||||
@@ -54,7 +54,7 @@ const Bucket = ({
|
||||
>
|
||||
{bucketName}
|
||||
</button>
|
||||
{showSeparator && <span className='system-xs-regular shrink-0 text-divider-deep'>/</span>}
|
||||
{showSeparator && <span className="system-xs-regular shrink-0 text-divider-deep">/</span>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type DriveProps = {
|
||||
breadcrumbs: string[]
|
||||
@@ -16,7 +16,7 @@ const Drive = ({
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type='button'
|
||||
type="button"
|
||||
className={cn(
|
||||
'max-w-full shrink truncate rounded-md px-[5px] py-1',
|
||||
breadcrumbs.length > 0 && 'system-sm-regular text-text-tertiary hover:bg-state-base-hover',
|
||||
@@ -27,7 +27,7 @@ const Drive = ({
|
||||
>
|
||||
{t('datasetPipeline.onlineDrive.breadcrumbs.allFiles')}
|
||||
</button>
|
||||
{breadcrumbs.length > 0 && <span className='system-xs-regular text-divider-deep'>/</span>}
|
||||
{breadcrumbs.length > 0 && <span className="system-xs-regular text-divider-deep">/</span>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -528,10 +528,13 @@ describe('Dropdown', () => {
|
||||
fireEvent.click(screen.getByText('folder'))
|
||||
|
||||
// Rerender with different callback
|
||||
rerender(<Dropdown {...createDefaultProps({
|
||||
breadcrumbs: ['folder'],
|
||||
onBreadcrumbClick: mockOnBreadcrumbClick2,
|
||||
})} />)
|
||||
rerender(
|
||||
<Dropdown {...createDefaultProps({
|
||||
breadcrumbs: ['folder'],
|
||||
onBreadcrumbClick: mockOnBreadcrumbClick2,
|
||||
})}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Open and click with second callback
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { RiMoreFill } from '@remixicon/react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { RiMoreFill } from '@remixicon/react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Menu from './menu'
|
||||
|
||||
@@ -34,7 +34,7 @@ const Dropdown = ({
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
placement="bottom-start"
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: -13,
|
||||
@@ -42,23 +42,23 @@ const Dropdown = ({
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<button
|
||||
type='button'
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex size-6 items-center justify-center rounded-md',
|
||||
open ? 'bg-state-base-hover' : 'hover:bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<RiMoreFill className='size-4 text-text-tertiary' />
|
||||
<RiMoreFill className="size-4 text-text-tertiary" />
|
||||
</button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
<PortalToFollowElemContent className="z-[11]">
|
||||
<Menu
|
||||
breadcrumbs={breadcrumbs}
|
||||
startIndex={startIndex}
|
||||
onBreadcrumbClick={handleBreadCrumbClick}
|
||||
/>
|
||||
</PortalToFollowElemContent>
|
||||
<span className='system-xs-regular text-divider-deep'>/</span>
|
||||
<span className="system-xs-regular text-divider-deep">/</span>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const Item = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className='system-md-regular rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
|
||||
className="system-md-regular rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover"
|
||||
onClick={handleClick}
|
||||
>
|
||||
{name}
|
||||
|
||||
@@ -13,7 +13,7 @@ const Menu = ({
|
||||
onBreadcrumbClick,
|
||||
}: MenuProps) => {
|
||||
return (
|
||||
<div className='flex w-[136px] flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]'>
|
||||
<div className="flex w-[136px] flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]">
|
||||
{breadcrumbs.map((breadcrumb, index) => {
|
||||
return (
|
||||
<Item
|
||||
|
||||
@@ -2,9 +2,9 @@ import React, { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDataSourceStore, useDataSourceStoreWithSelector } from '../../../../store'
|
||||
import Bucket from './bucket'
|
||||
import BreadcrumbItem from './item'
|
||||
import Dropdown from './dropdown'
|
||||
import Drive from './drive'
|
||||
import Dropdown from './dropdown'
|
||||
import BreadcrumbItem from './item'
|
||||
|
||||
type BreadcrumbsProps = {
|
||||
breadcrumbs: string[]
|
||||
@@ -80,9 +80,9 @@ const Breadcrumbs = ({
|
||||
}, [dataSourceStore])
|
||||
|
||||
return (
|
||||
<div className='flex grow items-center overflow-hidden'>
|
||||
<div className="flex grow items-center overflow-hidden">
|
||||
{showSearchResult && (
|
||||
<div className='system-sm-medium text-test-secondary px-[5px]'>
|
||||
<div className="system-sm-medium text-test-secondary px-[5px]">
|
||||
{t('datasetPipeline.onlineDrive.breadcrumbs.searchResult', {
|
||||
searchResultsLength,
|
||||
folderName: breadcrumbs.length > 0 ? breadcrumbs[breadcrumbs.length - 1] : bucket,
|
||||
@@ -90,12 +90,12 @@ const Breadcrumbs = ({
|
||||
</div>
|
||||
)}
|
||||
{!showSearchResult && showBucketListTitle && (
|
||||
<div className='system-sm-medium text-test-secondary px-[5px]'>
|
||||
<div className="system-sm-medium text-test-secondary px-[5px]">
|
||||
{t('datasetPipeline.onlineDrive.breadcrumbs.allBuckets')}
|
||||
</div>
|
||||
)}
|
||||
{!showSearchResult && !showBucketListTitle && (
|
||||
<div className='flex w-full items-center gap-x-0.5 overflow-hidden'>
|
||||
<div className="flex w-full items-center gap-x-0.5 overflow-hidden">
|
||||
{hasBucket && bucket && (
|
||||
<Bucket
|
||||
bucketName={bucket}
|
||||
|
||||
@@ -26,7 +26,7 @@ const BreadcrumbItem = ({
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type='button'
|
||||
type="button"
|
||||
className={cn(
|
||||
'max-w-full shrink truncate rounded-md px-[5px] py-1',
|
||||
isActive ? 'system-sm-medium text-text-secondary' : 'system-sm-regular text-text-tertiary',
|
||||
@@ -38,7 +38,7 @@ const BreadcrumbItem = ({
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
{showSeparator && <span className='system-xs-regular shrink-0 text-divider-deep'>/</span>}
|
||||
{showSeparator && <span className="system-xs-regular shrink-0 text-divider-deep">/</span>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import Breadcrumbs from './breadcrumbs'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Breadcrumbs from './breadcrumbs'
|
||||
|
||||
type HeaderProps = {
|
||||
breadcrumbs: string[]
|
||||
@@ -27,7 +27,7 @@ const Header = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-x-2 bg-components-panel-bg p-1 pl-3'>
|
||||
<div className="flex items-center gap-x-2 bg-components-panel-bg p-1 pl-3">
|
||||
<Breadcrumbs
|
||||
breadcrumbs={breadcrumbs}
|
||||
keywords={keywords}
|
||||
@@ -42,7 +42,7 @@ const Header = ({
|
||||
placeholder={t('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder')}
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
wrapperClassName='w-[200px] h-8 shrink-0'
|
||||
wrapperClassName="w-[200px] h-8 shrink-0"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import FileList from './index'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import { OnlineDriveFileType } from '@/models/pipeline'
|
||||
import FileList from './index'
|
||||
|
||||
// ==========================================
|
||||
// Mock Modules
|
||||
@@ -544,8 +544,7 @@ describe('FileList', () => {
|
||||
it('should handle large number of files', () => {
|
||||
// Arrange
|
||||
const fileList = Array.from({ length: 50 }, (_, i) =>
|
||||
createMockOnlineDriveFile({ id: `file-${i}`, name: `file-${i}.txt` }),
|
||||
)
|
||||
createMockOnlineDriveFile({ id: `file-${i}`, name: `file-${i}.txt` }))
|
||||
const props = createDefaultProps({ fileList })
|
||||
|
||||
// Act
|
||||
@@ -598,8 +597,7 @@ describe('FileList', () => {
|
||||
])('should handle $description correctly', ({ isLoading, fileCount }) => {
|
||||
// Arrange
|
||||
const fileList = Array.from({ length: fileCount }, (_, i) =>
|
||||
createMockOnlineDriveFile({ id: `file-${i}`, name: `file-${i}.txt` }),
|
||||
)
|
||||
createMockOnlineDriveFile({ id: `file-${i}`, name: `file-${i}.txt` }))
|
||||
const props = createDefaultProps({ isLoading, fileList })
|
||||
|
||||
// Act
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useState } from 'react'
|
||||
import Header from './header'
|
||||
import List from './list'
|
||||
import { useState } from 'react'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
|
||||
type FileListProps = {
|
||||
fileList: OnlineDriveFile[]
|
||||
@@ -56,7 +56,7 @@ const FileList = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex h-[400px] flex-col overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg shadow-xs shadow-shadow-shadow-3'>
|
||||
<div className="flex h-[400px] flex-col overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg shadow-xs shadow-shadow-shadow-3">
|
||||
<Header
|
||||
breadcrumbs={breadcrumbs}
|
||||
inputValue={inputValue}
|
||||
|
||||
@@ -5,8 +5,8 @@ const EmptyFolder = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex size-full items-center justify-center rounded-[10px] bg-background-section px-1 py-1.5'>
|
||||
<span className='system-xs-regular text-text-tertiary'>{t('datasetPipeline.onlineDrive.emptyFolder')}</span>
|
||||
<div className="flex size-full items-center justify-center rounded-[10px] bg-background-section px-1 py-1.5">
|
||||
<span className="system-xs-regular text-text-tertiary">{t('datasetPipeline.onlineDrive.emptyFolder')}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Button from '@/app/components/base/button'
|
||||
import { SearchMenu } from '@/app/components/base/icons/src/vender/knowledge'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { SearchMenu } from '@/app/components/base/icons/src/vender/knowledge'
|
||||
|
||||
type EmptySearchResultProps = {
|
||||
onResetKeywords: () => void
|
||||
@@ -15,18 +15,18 @@ const EmptySearchResult = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex size-full flex-col items-center justify-center gap-y-2 rounded-[10px] bg-background-section p-6'>
|
||||
<SearchMenu className='size-8 text-text-tertiary' />
|
||||
<div className='system-sm-regular text-text-secondary'>
|
||||
<div className="flex size-full flex-col items-center justify-center gap-y-2 rounded-[10px] bg-background-section p-6">
|
||||
<SearchMenu className="size-8 text-text-tertiary" />
|
||||
<div className="system-sm-regular text-text-secondary">
|
||||
{t('datasetPipeline.onlineDrive.emptySearchResult')}
|
||||
</div>
|
||||
<Button
|
||||
variant='secondary-accent'
|
||||
size='small'
|
||||
variant="secondary-accent"
|
||||
size="small"
|
||||
onClick={onResetKeywords}
|
||||
className='px-1.5'
|
||||
className="px-1.5"
|
||||
>
|
||||
<span className='px-[3px]'>{t('datasetPipeline.onlineDrive.resetKeywords')}</span>
|
||||
<span className="px-[3px]">{t('datasetPipeline.onlineDrive.resetKeywords')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { OnlineDriveFileType } from '@/models/pipeline'
|
||||
import { BucketsBlue, Folder } from '@/app/components/base/icons/src/public/knowledge/online-drive'
|
||||
import FileTypeIcon from '@/app/components/base/file-uploader/file-type-icon'
|
||||
import { getFileType } from './utils'
|
||||
import { BucketsBlue, Folder } from '@/app/components/base/icons/src/public/knowledge/online-drive'
|
||||
import { OnlineDriveFileType } from '@/models/pipeline'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { getFileType } from './utils'
|
||||
|
||||
type FileIconProps = {
|
||||
type: OnlineDriveFileType
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Mock } from 'vitest'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import List from './index'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import { OnlineDriveFileType } from '@/models/pipeline'
|
||||
import List from './index'
|
||||
|
||||
// ==========================================
|
||||
// Mock Modules
|
||||
@@ -1086,35 +1086,34 @@ describe('List', () => {
|
||||
{ isLoading: false, fileCount: 0, keywords: '', expectedState: 'empty-folder' },
|
||||
{ isLoading: false, fileCount: 0, keywords: 'search', expectedState: 'empty-search' },
|
||||
{ isLoading: false, fileCount: 5, keywords: '', expectedState: 'file-list' },
|
||||
])('should render $expectedState when isLoading=$isLoading, fileCount=$fileCount, keywords=$keywords',
|
||||
({ isLoading, fileCount, keywords, expectedState }) => {
|
||||
// Arrange
|
||||
const fileList = createMockFileList(fileCount)
|
||||
const props = createDefaultProps({ fileList, isLoading, keywords })
|
||||
])('should render $expectedState when isLoading=$isLoading, fileCount=$fileCount, keywords=$keywords', ({ isLoading, fileCount, keywords, expectedState }) => {
|
||||
// Arrange
|
||||
const fileList = createMockFileList(fileCount)
|
||||
const props = createDefaultProps({ fileList, isLoading, keywords })
|
||||
|
||||
// Act
|
||||
render(<List {...props} />)
|
||||
// Act
|
||||
render(<List {...props} />)
|
||||
|
||||
// Assert
|
||||
switch (expectedState) {
|
||||
case 'all-loading':
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
break
|
||||
case 'partial-loading':
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('item-file-1')).toBeInTheDocument()
|
||||
break
|
||||
case 'empty-folder':
|
||||
expect(screen.getByTestId('empty-folder')).toBeInTheDocument()
|
||||
break
|
||||
case 'empty-search':
|
||||
expect(screen.getByTestId('empty-search-result')).toBeInTheDocument()
|
||||
break
|
||||
case 'file-list':
|
||||
expect(screen.getByTestId('item-file-1')).toBeInTheDocument()
|
||||
break
|
||||
}
|
||||
})
|
||||
// Assert
|
||||
switch (expectedState) {
|
||||
case 'all-loading':
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
break
|
||||
case 'partial-loading':
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('item-file-1')).toBeInTheDocument()
|
||||
break
|
||||
case 'empty-folder':
|
||||
expect(screen.getByTestId('empty-folder')).toBeInTheDocument()
|
||||
break
|
||||
case 'empty-search':
|
||||
expect(screen.getByTestId('empty-search-result')).toBeInTheDocument()
|
||||
break
|
||||
case 'file-list':
|
||||
expect(screen.getByTestId('item-file-1')).toBeInTheDocument()
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ selectedCount: 0, expectedSelected: [] },
|
||||
@@ -1298,7 +1297,7 @@ describe('EmptySearchResult', () => {
|
||||
// ==========================================
|
||||
describe('FileIcon', () => {
|
||||
// Get real component for testing
|
||||
type FileIconProps = { type: OnlineDriveFileType; fileName: string; size?: 'sm' | 'md' | 'lg' | 'xl'; className?: string }
|
||||
type FileIconProps = { type: OnlineDriveFileType, fileName: string, size?: 'sm' | 'md' | 'lg' | 'xl', className?: string }
|
||||
let ActualFileIcon: React.ComponentType<FileIconProps>
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -1757,7 +1756,7 @@ describe('utils', () => {
|
||||
let FileAppearanceTypeEnum: Record<string, string>
|
||||
|
||||
beforeAll(async () => {
|
||||
const utils = await vi.importActual<{ getFileExtension: typeof getFileExtension; getFileType: typeof getFileType }>('./utils')
|
||||
const utils = await vi.importActual<{ getFileExtension: typeof getFileExtension, getFileType: typeof getFileType }>('./utils')
|
||||
const types = await vi.importActual<{ FileAppearanceTypeEnum: typeof FileAppearanceTypeEnum }>('@/app/components/base/file-uploader/types')
|
||||
getFileExtension = utils.getFileExtension
|
||||
getFileType = utils.getFileType
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import Item from './item'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useDataSourceStore } from '../../../store'
|
||||
import EmptyFolder from './empty-folder'
|
||||
import EmptySearchResult from './empty-search-result'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import { useDataSourceStore } from '../../../store'
|
||||
import Item from './item'
|
||||
|
||||
type FileListProps = {
|
||||
fileList: OnlineDriveFile[]
|
||||
@@ -54,10 +54,10 @@ const List = ({
|
||||
const isSearchResultEmpty = !isLoading && fileList.length === 0 && keywords.length > 0
|
||||
|
||||
return (
|
||||
<div className='grow overflow-hidden p-1 pt-0'>
|
||||
<div className="grow overflow-hidden p-1 pt-0">
|
||||
{
|
||||
isAllLoading && (
|
||||
<Loading type='app' />
|
||||
<Loading type="app" />
|
||||
)
|
||||
}
|
||||
{
|
||||
@@ -71,7 +71,7 @@ const List = ({
|
||||
)
|
||||
}
|
||||
{fileList.length > 0 && (
|
||||
<div className='flex h-full flex-col gap-y-px overflow-y-auto rounded-[10px] bg-background-section px-1 py-1.5'>
|
||||
<div className="flex h-full flex-col gap-y-px overflow-y-auto rounded-[10px] bg-background-section px-1 py-1.5">
|
||||
{
|
||||
fileList.map((file) => {
|
||||
const isSelected = selectedFileIds.includes(file.id)
|
||||
@@ -90,16 +90,16 @@ const List = ({
|
||||
{
|
||||
isPartialLoading && (
|
||||
<div
|
||||
className='flex items-center justify-center py-2'
|
||||
role='status'
|
||||
aria-live='polite'
|
||||
className="flex items-center justify-center py-2"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label={t('appApi.loading')}
|
||||
>
|
||||
<RiLoader2Line className='animation-spin size-4 text-text-tertiary' />
|
||||
<RiLoader2Line className="animation-spin size-4 text-text-tertiary" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div ref={anchorRef} className='h-0' />
|
||||
<div ref={anchorRef} className="h-0" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Radio from '@/app/components/base/radio/ui'
|
||||
import type { Placement } from '@floating-ui/react'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import React, { useCallback } from 'react'
|
||||
import FileIcon from './file-icon'
|
||||
import { formatFileSize } from '@/utils/format'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Radio from '@/app/components/base/radio/ui'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import type { Placement } from '@floating-ui/react'
|
||||
import { formatFileSize } from '@/utils/format'
|
||||
import FileIcon from './file-icon'
|
||||
|
||||
type ItemProps = {
|
||||
file: OnlineDriveFile
|
||||
@@ -33,11 +33,13 @@ const Item = ({
|
||||
const isFolder = type === 'folder'
|
||||
|
||||
const Wrapper = disabled ? Tooltip : React.Fragment
|
||||
const wrapperProps = disabled ? {
|
||||
popupContent: t('datasetPipeline.onlineDrive.notSupportedFileType'),
|
||||
position: 'top-end' as Placement,
|
||||
offset: { mainAxis: 4, crossAxis: -104 },
|
||||
} : {}
|
||||
const wrapperProps = disabled
|
||||
? {
|
||||
popupContent: t('datasetPipeline.onlineDrive.notSupportedFileType'),
|
||||
position: 'top-end' as Placement,
|
||||
offset: { mainAxis: 4, crossAxis: -104 },
|
||||
}
|
||||
: {}
|
||||
|
||||
const handleSelect = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
@@ -46,7 +48,8 @@ const Item = ({
|
||||
|
||||
const handleClickItem = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
if (disabled) return
|
||||
if (disabled)
|
||||
return
|
||||
if (isBucket || isFolder) {
|
||||
onOpen(file)
|
||||
return
|
||||
@@ -56,12 +59,12 @@ const Item = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-2 rounded-md px-2 py-[3px] hover:bg-state-base-hover'
|
||||
className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-[3px] hover:bg-state-base-hover"
|
||||
onClick={handleClickItem}
|
||||
>
|
||||
{!isBucket && isMultipleChoice && (
|
||||
<Checkbox
|
||||
className='shrink-0'
|
||||
className="shrink-0"
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
checked={isSelected}
|
||||
@@ -70,7 +73,7 @@ const Item = ({
|
||||
)}
|
||||
{!isBucket && !isMultipleChoice && (
|
||||
<Radio
|
||||
className='shrink-0'
|
||||
className="shrink-0"
|
||||
disabled={disabled}
|
||||
isChecked={isSelected}
|
||||
onCheck={handleSelect}
|
||||
@@ -83,16 +86,17 @@ const Item = ({
|
||||
className={cn(
|
||||
'flex grow items-center gap-x-1 overflow-hidden py-0.5',
|
||||
disabled && 'opacity-30',
|
||||
)}>
|
||||
<FileIcon type={type} fileName={name} className='shrink-0 transform-gpu' />
|
||||
)}
|
||||
>
|
||||
<FileIcon type={type} fileName={name} className="shrink-0 transform-gpu" />
|
||||
<span
|
||||
className='system-sm-medium grow truncate text-text-secondary'
|
||||
className="system-sm-medium grow truncate text-text-secondary"
|
||||
title={name}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
{!isFolder && typeof size === 'number' && (
|
||||
<span className='system-xs-regular shrink-0 text-text-tertiary'>{formatFileSize(size)}</span>
|
||||
<span className="system-xs-regular shrink-0 text-text-tertiary">{formatFileSize(size)}</span>
|
||||
)}
|
||||
</div>
|
||||
</Wrapper>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { RiBookOpenLine, RiEqualizer2Line } from '@remixicon/react'
|
||||
import React from 'react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { RiBookOpenLine, RiEqualizer2Line } from '@remixicon/react'
|
||||
|
||||
type HeaderProps = {
|
||||
onClickConfiguration?: () => void
|
||||
@@ -15,31 +15,31 @@ const Header = ({
|
||||
docLink,
|
||||
}: HeaderProps) => {
|
||||
return (
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<div className='flex shrink-0 grow items-center gap-x-1'>
|
||||
<div className='w-20 bg-black'>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="flex shrink-0 grow items-center gap-x-1">
|
||||
<div className="w-20 bg-black">
|
||||
{/* placeholder */}
|
||||
</div>
|
||||
<Divider type='vertical' className='mx-1 h-3.5' />
|
||||
<Divider type="vertical" className="mx-1 h-3.5" />
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
className='px-1'
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="px-1"
|
||||
>
|
||||
<RiEqualizer2Line
|
||||
className='size-4'
|
||||
className="size-4"
|
||||
onClick={onClickConfiguration}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
<a
|
||||
className='system-xs-medium flex items-center gap-x-1 overflow-hidden text-text-accent'
|
||||
className="system-xs-medium flex items-center gap-x-1 overflow-hidden text-text-accent"
|
||||
href={docLink}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<RiBookOpenLine className='size-3.5 shrink-0' />
|
||||
<span className='grow truncate' title={docTitle}>{docTitle}</span>
|
||||
<RiBookOpenLine className="size-3.5 shrink-0" />
|
||||
<span className="grow truncate" title={docTitle}>{docTitle}</span>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import type { OnlineDriveData } from '@/types/pipeline'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import OnlineDrive from './index'
|
||||
import Header from './header'
|
||||
import { convertOnlineDriveData, isBucketListInitiation, isFile } from './utils'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import { DatasourceType, OnlineDriveFileType } from '@/models/pipeline'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import type { OnlineDriveData } from '@/types/pipeline'
|
||||
import { DatasourceType, OnlineDriveFileType } from '@/models/pipeline'
|
||||
import Header from './header'
|
||||
import OnlineDrive from './index'
|
||||
import { convertOnlineDriveData, isBucketListInitiation, isFile } from './utils'
|
||||
|
||||
// ==========================================
|
||||
// Mock Modules
|
||||
@@ -198,7 +198,7 @@ const createMockOnlineDriveFile = (overrides?: Partial<OnlineDriveFile>): Online
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const createMockCredential = (overrides?: Partial<{ id: string; name: string }>) => ({
|
||||
const createMockCredential = (overrides?: Partial<{ id: string, name: string }>) => ({
|
||||
id: 'cred-1',
|
||||
name: 'Test Credential',
|
||||
avatar_url: 'https://example.com/avatar.png',
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import Header from '../base/header'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import FileList from './file-list'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import { DatasourceType, OnlineDriveFileType } from '@/models/pipeline'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { ssePost } from '@/service/base'
|
||||
import type { DataSourceNodeCompletedResponse, DataSourceNodeErrorResponse } from '@/types/pipeline'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useDataSourceStore, useDataSourceStoreWithSelector } from '../store'
|
||||
import { convertOnlineDriveData } from './utils'
|
||||
import { produce } from 'immer'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { useGetDataSourceAuth } from '@/service/use-datasource'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { DatasourceType, OnlineDriveFileType } from '@/models/pipeline'
|
||||
import { ssePost } from '@/service/base'
|
||||
import { useGetDataSourceAuth } from '@/service/use-datasource'
|
||||
import Header from '../base/header'
|
||||
import { useDataSourceStore, useDataSourceStoreWithSelector } from '../store'
|
||||
import FileList from './file-list'
|
||||
import { convertOnlineDriveData } from './utils'
|
||||
|
||||
type OnlineDriveProps = {
|
||||
nodeId: string
|
||||
@@ -69,7 +69,8 @@ const OnlineDrive = ({
|
||||
: `/rag/pipelines/${pipelineId}/workflows/draft/datasource/nodes/${nodeId}/run`
|
||||
|
||||
const getOnlineDriveFiles = useCallback(async () => {
|
||||
if (isLoadingRef.current) return
|
||||
if (isLoadingRef.current)
|
||||
return
|
||||
const { nextPageParameters, prefix, bucket, onlineDriveFileList, currentCredentialId } = dataSourceStore.getState()
|
||||
setIsLoading(true)
|
||||
isLoadingRef.current = true
|
||||
@@ -116,7 +117,8 @@ const OnlineDrive = ({
|
||||
}, [dataSourceStore, datasourceNodeRunURL, breadcrumbs])
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentCredentialId) return
|
||||
if (!currentCredentialId)
|
||||
return
|
||||
if (isInitialMount) {
|
||||
// Only fetch files on initial mount if fileList is empty
|
||||
if (onlineDriveFileList.length === 0)
|
||||
@@ -147,14 +149,16 @@ const OnlineDrive = ({
|
||||
|
||||
const handleSelectFile = useCallback((file: OnlineDriveFile) => {
|
||||
const { selectedFileIds, setSelectedFileIds } = dataSourceStore.getState()
|
||||
if (file.type === OnlineDriveFileType.bucket) return
|
||||
if (file.type === OnlineDriveFileType.bucket)
|
||||
return
|
||||
const newSelectedFileList = produce(selectedFileIds, (draft) => {
|
||||
if (draft.includes(file.id)) {
|
||||
const index = draft.indexOf(file.id)
|
||||
draft.splice(index, 1)
|
||||
}
|
||||
else {
|
||||
if (!supportBatchUpload && draft.length >= 1) return
|
||||
if (!supportBatchUpload && draft.length >= 1)
|
||||
return
|
||||
draft.push(file.id)
|
||||
}
|
||||
})
|
||||
@@ -163,7 +167,8 @@ const OnlineDrive = ({
|
||||
|
||||
const handleOpenFolder = useCallback((file: OnlineDriveFile) => {
|
||||
const { breadcrumbs, prefix, setBreadcrumbs, setPrefix, setBucket, setOnlineDriveFileList, setSelectedFileIds } = dataSourceStore.getState()
|
||||
if (file.type === OnlineDriveFileType.file) return
|
||||
if (file.type === OnlineDriveFileType.file)
|
||||
return
|
||||
setOnlineDriveFileList([])
|
||||
if (file.type === OnlineDriveFileType.bucket) {
|
||||
setBucket(file.name)
|
||||
@@ -188,9 +193,9 @@ const OnlineDrive = ({
|
||||
}, [setShowAccountSettingModal])
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-y-2'>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<Header
|
||||
docTitle='Docs'
|
||||
docTitle="Docs"
|
||||
docLink={docLink('/guides/knowledge-base/knowledge-pipeline/authorize-data-source')}
|
||||
onClickConfiguration={handleSetting}
|
||||
pluginName={nodeData.datasource_label}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { type OnlineDriveFile, OnlineDriveFileType } from '@/models/pipeline'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import type { OnlineDriveData } from '@/types/pipeline'
|
||||
import { OnlineDriveFileType } from '@/models/pipeline'
|
||||
|
||||
export const isFile = (type: 'file' | 'folder'): boolean => {
|
||||
return type === 'file'
|
||||
}
|
||||
|
||||
export const isBucketListInitiation = (data: OnlineDriveData[], prefix: string[], bucket: string): boolean => {
|
||||
if (bucket || prefix.length > 0) return false
|
||||
if (bucket || prefix.length > 0)
|
||||
return false
|
||||
const hasBucket = data.every(item => !!item.bucket)
|
||||
return hasBucket && (data.length > 1 || (data.length === 1 && !!data[0].bucket && data[0].files.length === 0))
|
||||
}
|
||||
|
||||
export const convertOnlineDriveData = (data: OnlineDriveData[], prefix: string[], bucket: string): {
|
||||
fileList: OnlineDriveFile[],
|
||||
isTruncated: boolean,
|
||||
fileList: OnlineDriveFile[]
|
||||
isTruncated: boolean
|
||||
nextPageParameters: Record<string, any>
|
||||
hasBucket: boolean
|
||||
} => {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import type { CommonShape } from './slices/common'
|
||||
import type { LocalFileSliceShape } from './slices/local-file'
|
||||
import type { OnlineDocumentSliceShape } from './slices/online-document'
|
||||
import type { OnlineDriveSliceShape } from './slices/online-drive'
|
||||
import type { WebsiteCrawlSliceShape } from './slices/website-crawl'
|
||||
import { useContext } from 'react'
|
||||
import { createStore, useStore } from 'zustand'
|
||||
import { DataSourceContext } from './provider'
|
||||
import type { CommonShape } from './slices/common'
|
||||
import { createCommonSlice } from './slices/common'
|
||||
import type { LocalFileSliceShape } from './slices/local-file'
|
||||
import { createLocalFileSlice } from './slices/local-file'
|
||||
import type { OnlineDocumentSliceShape } from './slices/online-document'
|
||||
import { createOnlineDocumentSlice } from './slices/online-document'
|
||||
import type { WebsiteCrawlSliceShape } from './slices/website-crawl'
|
||||
import { createWebsiteCrawlSlice } from './slices/website-crawl'
|
||||
import type { OnlineDriveSliceShape } from './slices/online-drive'
|
||||
import { createOnlineDriveSlice } from './slices/online-drive'
|
||||
import { createWebsiteCrawlSlice } from './slices/website-crawl'
|
||||
|
||||
export type DataSourceShape = CommonShape
|
||||
& LocalFileSliceShape
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type CheckboxWithLabelProps = {
|
||||
className?: string
|
||||
@@ -28,9 +28,9 @@ const CheckboxWithLabel = ({
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[200px]'>{tooltip}</div>
|
||||
<div className="w-[200px]">{tooltip}</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4'
|
||||
triggerClassName="ml-0.5 w-4 h-4"
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
import React, { useCallback } from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Button from '@/app/components/base/button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Radio from '@/app/components/base/radio/ui'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type CrawledResultItemProps = {
|
||||
payload: CrawlResultItemType
|
||||
@@ -36,31 +36,34 @@ const CrawledResultItem = ({
|
||||
<div className={cn(
|
||||
'relative flex cursor-pointer gap-x-2 rounded-lg p-2',
|
||||
isPreview ? 'bg-state-base-active' : 'group hover:bg-state-base-hover',
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
{
|
||||
isMultipleChoice ? (
|
||||
<Checkbox
|
||||
className='shrink-0'
|
||||
checked={isChecked}
|
||||
onCheck={handleCheckChange}
|
||||
/>
|
||||
) : (
|
||||
<Radio
|
||||
className='shrink-0'
|
||||
isChecked={isChecked}
|
||||
onCheck={handleCheckChange}
|
||||
/>
|
||||
)
|
||||
isMultipleChoice
|
||||
? (
|
||||
<Checkbox
|
||||
className="shrink-0"
|
||||
checked={isChecked}
|
||||
onCheck={handleCheckChange}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Radio
|
||||
className="shrink-0"
|
||||
isChecked={isChecked}
|
||||
onCheck={handleCheckChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<div className='flex min-w-0 grow flex-col gap-y-0.5'>
|
||||
<div className="flex min-w-0 grow flex-col gap-y-0.5">
|
||||
<div
|
||||
className='system-sm-medium truncate text-text-secondary'
|
||||
className="system-sm-medium truncate text-text-secondary"
|
||||
title={payload.title}
|
||||
>
|
||||
{payload.title}
|
||||
</div>
|
||||
<div
|
||||
className='system-xs-regular truncate text-text-tertiary'
|
||||
className="system-xs-regular truncate text-text-tertiary"
|
||||
title={payload.source_url}
|
||||
>
|
||||
{payload.source_url}
|
||||
@@ -68,9 +71,9 @@ const CrawledResultItem = ({
|
||||
</div>
|
||||
{showPreview && (
|
||||
<Button
|
||||
size='small'
|
||||
size="small"
|
||||
onClick={onPreview}
|
||||
className='system-xs-medium-uppercase right-2 top-2 hidden px-1.5 group-hover:absolute group-hover:block'
|
||||
className="system-xs-medium-uppercase right-2 top-2 hidden px-1.5 group-hover:absolute group-hover:block"
|
||||
>
|
||||
{t('datasetCreation.stepOne.website.preview')}
|
||||
</Button>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
import type { CrawlResultItem } from '@/models/datasets'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import type { CrawlResultItem } from '@/models/datasets'
|
||||
import CheckboxWithLabel from './checkbox-with-label'
|
||||
import CrawledResultItem from './crawled-result-item'
|
||||
|
||||
@@ -56,28 +56,30 @@ const CrawledResult = ({
|
||||
}, [checkedList, onSelectedChange, isMultipleChoice])
|
||||
|
||||
const handlePreview = useCallback((index: number) => {
|
||||
if (!onPreview) return
|
||||
if (!onPreview)
|
||||
return
|
||||
onPreview(list[index], index)
|
||||
}, [list, onPreview])
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-2', className)}>
|
||||
<div className='system-sm-medium pt-2 text-text-primary'>
|
||||
<div className="system-sm-medium pt-2 text-text-primary">
|
||||
{t(`${I18N_PREFIX}.scrapTimeInfo`, {
|
||||
total: list.length,
|
||||
time: usedTime.toFixed(1),
|
||||
})}
|
||||
</div>
|
||||
<div className='overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg'>
|
||||
<div className="overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg">
|
||||
{isMultipleChoice && (
|
||||
<div className='flex items-center px-4 py-2'>
|
||||
<div className="flex items-center px-4 py-2">
|
||||
<CheckboxWithLabel
|
||||
isChecked={isCheckAll}
|
||||
onChange={handleCheckedAll} label={isCheckAll ? t(`${I18N_PREFIX}.resetAll`) : t(`${I18N_PREFIX}.selectAll`)}
|
||||
onChange={handleCheckedAll}
|
||||
label={isCheckAll ? t(`${I18N_PREFIX}.resetAll`) : t(`${I18N_PREFIX}.selectAll`)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex flex-col gap-y-px border-t border-divider-subtle bg-background-default-subtle p-2'>
|
||||
<div className="flex flex-col gap-y-px border-t border-divider-subtle bg-background-default-subtle p-2">
|
||||
{list.map((item, index) => (
|
||||
<CrawledResultItem
|
||||
key={item.source_url}
|
||||
|
||||
@@ -29,15 +29,15 @@ const Item = React.memo(({
|
||||
secondLineWidth,
|
||||
}: ItemProps) => {
|
||||
return (
|
||||
<div className='flex gap-x-2 px-2 py-[5px]'>
|
||||
<div className='py-0.5'>
|
||||
<Block className='size-4 rounded-[4px]' />
|
||||
<div className="flex gap-x-2 px-2 py-[5px]">
|
||||
<div className="py-0.5">
|
||||
<Block className="size-4 rounded-[4px]" />
|
||||
</div>
|
||||
<div className='flex grow flex-col'>
|
||||
<div className='flex h-5 w-full items-center'>
|
||||
<div className="flex grow flex-col">
|
||||
<div className="flex h-5 w-full items-center">
|
||||
<Block className={cn('h-2.5 rounded-sm', firstLineWidth)} />
|
||||
</div>
|
||||
<div className='flex h-[18px] w-full items-center'>
|
||||
<div className="flex h-[18px] w-full items-center">
|
||||
<Block className={cn('h-1.5 rounded-sm', secondLineWidth)} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,15 +65,19 @@ const Crawling = ({
|
||||
|
||||
return (
|
||||
<div className={cn('mt-2 flex flex-col gap-y-2 pt-2', className)}>
|
||||
<div className='system-sm-medium text-text-primary'>
|
||||
{t('datasetCreation.stepOne.website.totalPageScraped')} {crawledNum}/{totalNum}
|
||||
<div className="system-sm-medium text-text-primary">
|
||||
{t('datasetCreation.stepOne.website.totalPageScraped')}
|
||||
{' '}
|
||||
{crawledNum}
|
||||
/
|
||||
{totalNum}
|
||||
</div>
|
||||
<div className='overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg'>
|
||||
<div className='flex items-center gap-x-2 px-4 py-2'>
|
||||
<Block className='size-4 rounded-[4px]' />
|
||||
<Block className='h-2.5 w-14 rounded-sm' />
|
||||
<div className="overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg">
|
||||
<div className="flex items-center gap-x-2 px-4 py-2">
|
||||
<Block className="size-4 rounded-[4px]" />
|
||||
<Block className="h-2.5 w-14 rounded-sm" />
|
||||
</div>
|
||||
<div className='flex flex-col gap-px border-t border-divider-subtle bg-background-default-subtle p-2'>
|
||||
<div className="flex flex-col gap-px border-t border-divider-subtle bg-background-default-subtle p-2">
|
||||
{itemsConfig.map((item, index) => (
|
||||
<Item
|
||||
key={index}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RiErrorWarningFill } from '@remixicon/react'
|
||||
import React from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { RiErrorWarningFill } from '@remixicon/react'
|
||||
|
||||
type ErrorMessageProps = {
|
||||
className?: string
|
||||
@@ -18,14 +18,15 @@ const ErrorMessage = ({
|
||||
<div className={cn(
|
||||
'flex gap-x-0.5 rounded-xl border-[0.5px] border-components-panel-border bg-opacity-40 bg-toast-error-bg p-2 shadow-xs shadow-shadow-shadow-3',
|
||||
className,
|
||||
)}>
|
||||
<div className='flex size-6 items-center justify-center'>
|
||||
<RiErrorWarningFill className='h-4 w-4 text-text-destructive' />
|
||||
)}
|
||||
>
|
||||
<div className="flex size-6 items-center justify-center">
|
||||
<RiErrorWarningFill className="h-4 w-4 text-text-destructive" />
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-0.5 py-1'>
|
||||
<div className='system-xs-medium text-text-primary'>{title}</div>
|
||||
<div className="flex flex-col gap-y-0.5 py-1">
|
||||
<div className="system-xs-medium text-text-primary">{title}</div>
|
||||
{errorMsg && (
|
||||
<div className='system-xs-regular text-text-secondary'>{errorMsg}</div>
|
||||
<div className="system-xs-regular text-text-secondary">{errorMsg}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import CheckboxWithLabel from './checkbox-with-label'
|
||||
import CrawledResultItem from './crawled-result-item'
|
||||
import CrawledResult from './crawled-result'
|
||||
import CrawledResultItem from './crawled-result-item'
|
||||
import Crawling from './crawling'
|
||||
import ErrorMessage from './error-message'
|
||||
import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets'
|
||||
|
||||
// ==========================================
|
||||
// Test Data Builders
|
||||
@@ -24,8 +24,7 @@ const createMockCrawlResultItems = (count = 3): CrawlResultItemType[] => {
|
||||
createMockCrawlResultItem({
|
||||
source_url: `https://example.com/page${i + 1}`,
|
||||
title: `Page ${i + 1}`,
|
||||
}),
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { MockInstance } from 'vitest'
|
||||
import type { RAGPipelineVariables } from '@/models/pipeline'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import Options from './index'
|
||||
import { CrawlStep } from '@/models/datasets'
|
||||
import type { RAGPipelineVariables } from '@/models/pipeline'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { CrawlStep } from '@/models/datasets'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import Options from './index'
|
||||
|
||||
// ==========================================
|
||||
// Mock Modules
|
||||
@@ -88,8 +88,7 @@ const createMockVariables = (count = 1): RAGPipelineVariables => {
|
||||
createMockVariable({
|
||||
variable: `variable_${i}`,
|
||||
label: `Label ${i}`,
|
||||
}),
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
const createMockConfiguration = (overrides?: Partial<any>): any => ({
|
||||
@@ -796,8 +795,7 @@ describe('Options', () => {
|
||||
it('should handle many configurations', () => {
|
||||
// Arrange
|
||||
const manyConfigs = Array.from({ length: 10 }, (_, i) =>
|
||||
createMockConfiguration({ variable: `field_${i}`, label: `Field ${i}` }),
|
||||
)
|
||||
createMockConfiguration({ variable: `field_${i}`, label: `Field ${i}` }))
|
||||
mockUseConfigurations.mockReturnValue(manyConfigs)
|
||||
const props = createDefaultProps()
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useAppForm } from '@/app/components/base/form'
|
||||
import BaseField from '@/app/components/base/form/form-scenarios/base/field'
|
||||
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import type { RAGPipelineVariables } from '@/models/pipeline'
|
||||
import { RiPlayLargeLine } from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { RAGPipelineVariables } from '@/models/pipeline'
|
||||
import { useConfigurations, useInitialData } from '@/app/components/rag-pipeline/hooks/use-input-fields'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useAppForm } from '@/app/components/base/form'
|
||||
import BaseField from '@/app/components/base/form/form-scenarios/base/field'
|
||||
import { generateZodSchema } from '@/app/components/base/form/form-scenarios/base/utils'
|
||||
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useConfigurations, useInitialData } from '@/app/components/rag-pipeline/hooks/use-input-fields'
|
||||
import { CrawlStep } from '@/models/datasets'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
const I18N_PREFIX = 'datasetCreation.stepOne.website'
|
||||
|
||||
@@ -76,37 +76,37 @@ const Options = ({
|
||||
|
||||
return (
|
||||
<form
|
||||
className='w-full'
|
||||
className="w-full"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
form.handleSubmit()
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center gap-x-1 px-4 py-2'>
|
||||
<div className="flex items-center gap-x-1 px-4 py-2">
|
||||
<div
|
||||
className='flex grow cursor-pointer select-none items-center gap-x-0.5'
|
||||
className="flex grow cursor-pointer select-none items-center gap-x-0.5"
|
||||
onClick={foldToggle}
|
||||
>
|
||||
<span className='system-sm-semibold-uppercase text-text-secondary'>
|
||||
<span className="system-sm-semibold-uppercase text-text-secondary">
|
||||
{t(`${I18N_PREFIX}.options`)}
|
||||
</span>
|
||||
<ArrowDownRoundFill className={cn('h-4 w-4 shrink-0 text-text-quaternary', fold && '-rotate-90')} />
|
||||
</div>
|
||||
<Button
|
||||
variant='primary'
|
||||
variant="primary"
|
||||
onClick={form.handleSubmit}
|
||||
disabled={runDisabled || isRunning}
|
||||
loading={isRunning}
|
||||
className='shrink-0 gap-x-0.5'
|
||||
spinnerClassName='!ml-0'
|
||||
className="shrink-0 gap-x-0.5"
|
||||
spinnerClassName="!ml-0"
|
||||
>
|
||||
<RiPlayLargeLine className='size-4' />
|
||||
<span className='px-0.5'>{!isRunning ? t(`${I18N_PREFIX}.run`) : t(`${I18N_PREFIX}.running`)}</span>
|
||||
<RiPlayLargeLine className="size-4" />
|
||||
<span className="px-0.5">{!isRunning ? t(`${I18N_PREFIX}.run`) : t(`${I18N_PREFIX}.running`)}</span>
|
||||
</Button>
|
||||
</div>
|
||||
{!fold && (
|
||||
<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) => {
|
||||
const FieldComponent = BaseField({
|
||||
initialData,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import type { CrawlResultItem } from '@/models/datasets'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import WebsiteCrawl from './index'
|
||||
import type { CrawlResultItem } from '@/models/datasets'
|
||||
import { CrawlStep } from '@/models/datasets'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { CrawlStep } from '@/models/datasets'
|
||||
import WebsiteCrawl from './index'
|
||||
|
||||
// ==========================================
|
||||
// Mock Modules
|
||||
@@ -63,7 +63,7 @@ vi.mock('@/service/use-pipeline', () => ({
|
||||
|
||||
// Mock store
|
||||
const mockStoreState = {
|
||||
crawlResult: undefined as { data: CrawlResultItem[]; time_consuming: number | string } | undefined,
|
||||
crawlResult: undefined as { data: CrawlResultItem[], time_consuming: number | string } | undefined,
|
||||
step: CrawlStep.init,
|
||||
websitePages: [] as CrawlResultItem[],
|
||||
previewIndex: -1,
|
||||
@@ -188,7 +188,7 @@ const createMockCrawlResultItem = (overrides?: Partial<CrawlResultItem>): CrawlR
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const createMockCredential = (overrides?: Partial<{ id: string; name: string }>) => ({
|
||||
const createMockCredential = (overrides?: Partial<{ id: string, name: string }>) => ({
|
||||
id: 'cred-1',
|
||||
name: 'Test Credential',
|
||||
avatar_url: 'https://example.com/avatar.png',
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
'use client'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import type { CrawlResultItem } from '@/models/datasets'
|
||||
import { CrawlStep } from '@/models/datasets'
|
||||
import Header from '../base/header'
|
||||
import Options from './base/options'
|
||||
import Crawling from './base/crawling'
|
||||
import ErrorMessage from './base/error-message'
|
||||
import CrawledResult from './base/crawled-result'
|
||||
import {
|
||||
useDraftPipelinePreProcessingParams,
|
||||
usePublishedPipelinePreProcessingParams,
|
||||
} from '@/service/use-pipeline'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { DatasourceType } from '@/models/pipeline'
|
||||
import { ssePost } from '@/service/base'
|
||||
import type {
|
||||
DataSourceNodeCompletedResponse,
|
||||
DataSourceNodeErrorResponse,
|
||||
DataSourceNodeProcessingResponse,
|
||||
} from '@/types/pipeline'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { useDataSourceStore, useDataSourceStoreWithSelector } from '../store'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { useGetDataSourceAuth } from '@/service/use-datasource'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { CrawlStep } from '@/models/datasets'
|
||||
import { DatasourceType } from '@/models/pipeline'
|
||||
import { ssePost } from '@/service/base'
|
||||
import { useGetDataSourceAuth } from '@/service/use-datasource'
|
||||
import {
|
||||
useDraftPipelinePreProcessingParams,
|
||||
usePublishedPipelinePreProcessingParams,
|
||||
} from '@/service/use-pipeline'
|
||||
import Header from '../base/header'
|
||||
import { useDataSourceStore, useDataSourceStoreWithSelector } from '../store'
|
||||
import CrawledResult from './base/crawled-result'
|
||||
import Crawling from './base/crawling'
|
||||
import ErrorMessage from './base/error-message'
|
||||
import Options from './base/options'
|
||||
|
||||
const I18N_PREFIX = 'datasetCreation.stepOne.website'
|
||||
|
||||
@@ -154,9 +154,9 @@ const WebsiteCrawl = ({
|
||||
}, [onCredentialChange])
|
||||
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<div className="flex flex-col">
|
||||
<Header
|
||||
docTitle='Docs'
|
||||
docTitle="Docs"
|
||||
docLink={docLink('/guides/knowledge-base/knowledge-pipeline/authorize-data-source')}
|
||||
onClickConfiguration={handleSetting}
|
||||
pluginName={nodeData.datasource_label}
|
||||
@@ -164,7 +164,7 @@ const WebsiteCrawl = ({
|
||||
onCredentialChange={handleCredentialChange}
|
||||
credentials={dataSourceAuth?.result || []}
|
||||
/>
|
||||
<div className='mt-2 rounded-xl border border-components-panel-border bg-background-default-subtle'>
|
||||
<div className="mt-2 rounded-xl border border-components-panel-border bg-background-default-subtle">
|
||||
<Options
|
||||
variables={paramsConfig?.variables || []}
|
||||
step={step}
|
||||
@@ -173,7 +173,7 @@ const WebsiteCrawl = ({
|
||||
/>
|
||||
</div>
|
||||
{!isInit && (
|
||||
<div className='relative flex flex-col'>
|
||||
<div className="relative flex flex-col">
|
||||
{isRunning && (
|
||||
<Crawling
|
||||
crawledNum={crawledNum}
|
||||
@@ -182,14 +182,14 @@ const WebsiteCrawl = ({
|
||||
)}
|
||||
{showError && (
|
||||
<ErrorMessage
|
||||
className='mt-2'
|
||||
className="mt-2"
|
||||
title={t(`${I18N_PREFIX}.exceptionErrorTitle`)}
|
||||
errorMsg={crawlErrorMessage}
|
||||
/>
|
||||
)}
|
||||
{isCrawlFinished && !showError && (
|
||||
<CrawledResult
|
||||
className='mt-2'
|
||||
className="mt-2"
|
||||
list={crawlResult?.data || []}
|
||||
checkedList={checkedCrawlResult}
|
||||
onSelectedChange={handleCheckedCrawlResultChange}
|
||||
|
||||
Reference in New Issue
Block a user