dify/web/app/components/base/notion-page-selector/base.tsx
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

221 lines
7.4 KiB
TypeScript

import type { DataSourceCredential } from '../../header/account-setting/data-source-page-new/types'
import type { NotionCredential } from './credential-selector'
import type {
DataSourceNotionPageMap,
DataSourceNotionWorkspace,
NotionPage,
} from '@/models/common'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
import { useIntegrationsSetting } from '@/app/components/header/account-setting/use-integrations-setting'
import {
useInvalidPreImportNotionPages,
usePreImportNotionPages,
} from '@/service/knowledge/use-import'
import Header from '../../datasets/create/website/base/header'
import Loading from '../loading'
import NotionConnector from '../notion-connector'
import WorkspaceSelector from './credential-selector'
import PageSelector from './page-selector'
import SearchInput from './search-input'
type NotionPageSelectorProps = {
value?: string[]
onSelect: (selectedPages: NotionPage[]) => void
canPreview?: boolean
previewPageId?: string
onPreview?: (selectedPage: NotionPage) => void
datasetId?: string
credentialList: DataSourceCredential[]
onSelectCredential?: (credentialId: string) => void
}
const NotionPageSelector = ({
value,
onSelect,
canPreview,
previewPageId,
onPreview,
datasetId = '',
credentialList,
onSelectCredential,
}: NotionPageSelectorProps) => {
const { t } = useTranslation()
const [searchValue, setSearchValue] = useState('')
const openIntegrationsSetting = useIntegrationsSetting()
const invalidPreImportNotionPages = useInvalidPreImportNotionPages()
const notionCredentials = useMemo((): NotionCredential[] => {
return credentialList.map((item) => {
return {
credentialId: item.id,
credentialName: item.name,
workspaceIcon: item.credential.workspace_icon,
workspaceName: item.credential.workspace_name,
}
})
}, [credentialList])
const [selectedCredentialId, setSelectedCredentialId] = useState(
() => notionCredentials[0]?.credentialId ?? '',
)
const currentCredential = useMemo(() => {
return (
notionCredentials.find((item) => item.credentialId === selectedCredentialId) ??
notionCredentials[0] ??
null
)
}, [notionCredentials, selectedCredentialId])
const currentCredentialId = currentCredential?.credentialId ?? ''
useEffect(() => {
onSelectCredential?.(currentCredentialId)
}, [currentCredentialId, onSelectCredential])
useEffect(() => {
if (!notionCredentials.length) {
onSelect([])
return
}
if (!selectedCredentialId || selectedCredentialId === currentCredentialId) return
invalidPreImportNotionPages({ datasetId, credentialId: currentCredentialId })
onSelect([])
}, [
currentCredentialId,
datasetId,
invalidPreImportNotionPages,
notionCredentials.length,
onSelect,
selectedCredentialId,
])
const {
data: notionsPages,
isFetching: isFetchingNotionPages,
isError: isFetchingNotionPagesError,
} = usePreImportNotionPages({ datasetId, credentialId: currentCredentialId })
const pagesMapAndSelectedPagesId: [DataSourceNotionPageMap, Set<string>, Set<string>] =
useMemo(() => {
const selectedPagesId = new Set<string>()
const boundPagesId = new Set<string>()
const notionWorkspaces = notionsPages?.notion_info || []
const pagesMap = notionWorkspaces.reduce(
(prev: DataSourceNotionPageMap, cur: DataSourceNotionWorkspace) => {
cur.pages.forEach((page) => {
if (page.is_bound) {
selectedPagesId.add(page.page_id)
boundPagesId.add(page.page_id)
}
prev[page.page_id] = {
...page,
workspace_id: cur.workspace_id,
}
})
return prev
},
{},
)
return [pagesMap, selectedPagesId, boundPagesId]
}, [notionsPages?.notion_info])
const defaultSelectedPagesId = useMemo(() => {
return [...Array.from(pagesMapAndSelectedPagesId[1]), ...(value || [])]
}, [pagesMapAndSelectedPagesId, value])
const selectedPagesId = useMemo(() => new Set(defaultSelectedPagesId), [defaultSelectedPagesId])
const handleSearchValueChange = useCallback((value: string) => {
setSearchValue(value)
}, [])
const handleSelectCredential = useCallback(
(credentialId: string) => {
if (credentialId === currentCredentialId) return
invalidPreImportNotionPages({ datasetId, credentialId })
setSelectedCredentialId(credentialId)
onSelect([]) // Clear selected pages when changing credential
},
[currentCredentialId, datasetId, invalidPreImportNotionPages, onSelect],
)
const handleSelectPages = useCallback(
(newSelectedPagesId: Set<string>) => {
const selectedPages = Array.from(newSelectedPagesId).map(
(pageId) => pagesMapAndSelectedPagesId[0][pageId]!,
)
onSelect(selectedPages)
},
[pagesMapAndSelectedPagesId, onSelect],
)
const handlePreviewPage = useCallback(
(previewPageId: string) => {
if (onPreview) onPreview(pagesMapAndSelectedPagesId[0][previewPageId]!)
},
[pagesMapAndSelectedPagesId, onPreview],
)
const handleConfigureNotion = useCallback(() => {
openIntegrationsSetting({ payload: ACCOUNT_SETTING_TAB.DATA_SOURCE })
}, [openIntegrationsSetting])
if (isFetchingNotionPagesError) {
return <NotionConnector onSetting={handleConfigureNotion} />
}
return (
<div className="flex flex-col gap-y-2" data-testid="notion-page-selector-base">
<Header
onClickConfiguration={handleConfigureNotion}
title={t(($) => $['dataSource.notion.selector.headerTitle'], { ns: 'common' })}
buttonText={t(($) => $['dataSource.notion.selector.configure'], { ns: 'common' })}
docTitle={t(($) => $['dataSource.notion.selector.docs'], { ns: 'common' })}
docLink="https://www.notion.so/docs"
/>
<div className="rounded-xl border border-components-panel-border bg-background-default-subtle">
<div className="flex h-12 items-center gap-x-2 rounded-t-xl border-b border-b-divider-regular bg-components-panel-bg p-2">
<div className="flex grow items-center gap-x-1">
<WorkspaceSelector
value={currentCredentialId}
items={notionCredentials}
onSelect={handleSelectCredential}
/>
</div>
<SearchInput value={searchValue} onChange={handleSearchValueChange} />
</div>
<div className="overflow-hidden rounded-b-xl">
{isFetchingNotionPages ? (
<div
className="flex h-[296px] items-center justify-center"
data-testid="notion-page-selector-loading"
>
<Loading />
</div>
) : (
<PageSelector
key={currentCredentialId || 'default'}
value={selectedPagesId}
disabledValue={pagesMapAndSelectedPagesId[2]}
searchValue={searchValue}
list={notionsPages!.notion_info?.[0]!.pages || []}
pagesMap={pagesMapAndSelectedPagesId[0]}
onSelect={handleSelectPages}
canPreview={canPreview}
previewPageId={previewPageId}
onPreview={handlePreviewPage}
/>
)}
</div>
</div>
</div>
)
}
export default NotionPageSelector