chore(web): new lint setup (#30020)

Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
Stephen Zhou
2025-12-23 16:58:55 +08:00
committed by GitHub
parent 9701a2994b
commit f2842da397
3356 changed files with 85046 additions and 81278 deletions

View File

@@ -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',

View File

@@ -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}

View File

@@ -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>

View File

@@ -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

View File

@@ -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>
)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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',

View File

@@ -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>

View File

@@ -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'
// ==========================================

View File

@@ -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,

View File

@@ -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(' / ')}

View File

@@ -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,

View File

@@ -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>
)

View File

@@ -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

View File

@@ -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>

View File

@@ -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>}
</>
)
}

View File

@@ -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>}
</>
)
}

View File

@@ -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'))

View File

@@ -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>
)
}

View File

@@ -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}

View File

@@ -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

View File

@@ -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}

View File

@@ -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>}
</>
)
}

View File

@@ -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>
)

View File

@@ -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

View File

@@ -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}

View File

@@ -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>
)
}

View File

@@ -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>
)

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
)

View File

@@ -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',

View File

@@ -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}

View File

@@ -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
} => {

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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}

View File

@@ -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>

View File

@@ -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}`,
}),
)
}))
}
// ==========================================

View File

@@ -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()

View File

@@ -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,

View File

@@ -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',

View File

@@ -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}