From 8711a57d9252b6f4576794f686014c50ccb76c2b Mon Sep 17 00:00:00 2001 From: twwu Date: Thu, 31 Jul 2025 15:19:03 +0800 Subject: [PATCH] feat: Enhance NotionPageSelector and NotionPageSelectorModal with loading states and credential handling --- .../base/notion-page-selector/base.tsx | 65 ++++++++++++------- .../notion-page-selector-modal/index.tsx | 55 ++++++++++++---- .../page-selector/index.tsx | 10 +-- web/app/components/datasets/create/index.tsx | 4 +- web/service/knowledge/use-import.ts | 24 +++++-- 5 files changed, 110 insertions(+), 48 deletions(-) diff --git a/web/app/components/base/notion-page-selector/base.tsx b/web/app/components/base/notion-page-selector/base.tsx index cf5e7eebe4..4dd56d142e 100644 --- a/web/app/components/base/notion-page-selector/base.tsx +++ b/web/app/components/base/notion-page-selector/base.tsx @@ -6,9 +6,10 @@ import PageSelector from './page-selector' import type { DataSourceNotionPageMap, DataSourceNotionWorkspace, NotionPage } from '@/models/common' import { useModalContextSelector } from '@/context/modal-context' import NotionConnector from '../notion-connector' -import { usePreImportNotionPages } from '@/service/knowledge/use-import' +import { useInvalidPreImportNotionPages, usePreImportNotionPages } from '@/service/knowledge/use-import' import Header from '../../datasets/create/website/base/header' import type { DataSourceCredential } from '../../header/account-setting/data-source-page-new/types' +import Loading from '../loading' type NotionPageSelectorProps = { value?: string[] @@ -50,12 +51,16 @@ const NotionPageSelector = ({ setCurrentCredential(notionCredentials[0]) }, [notionCredentials]) - const { data } = usePreImportNotionPages({ datasetId, credentialId: currentCredential.credentialId || '' }) + const { + data: notionsPages, + isFetching: isFetchingNotionPages, + isError: isFetchingNotionPagesError, + } = usePreImportNotionPages({ datasetId, credentialId: currentCredential.credentialId || '' }) - const getPagesMapAndSelectedPagesId: [DataSourceNotionPageMap, Set, Set] = useMemo(() => { + const pagesMapAndSelectedPagesId: [DataSourceNotionPageMap, Set, Set] = useMemo(() => { const selectedPagesId = new Set() const boundPagesId = new Set() - const notionWorkspaces = data?.notion_info || [] + const notionWorkspaces = notionsPages?.notion_info || [] const pagesMap = notionWorkspaces.reduce((prev: DataSourceNotionPageMap, next: DataSourceNotionWorkspace) => { next.pages.forEach((page) => { if (page.is_bound) { @@ -71,41 +76,47 @@ const NotionPageSelector = ({ return prev }, {}) return [pagesMap, selectedPagesId, boundPagesId] - }, [data?.notion_info]) + }, [notionsPages?.notion_info]) - const defaultSelectedPagesId = [...Array.from(getPagesMapAndSelectedPagesId[1]), ...(value || [])] + const defaultSelectedPagesId = useMemo(() => { + return [...Array.from(pagesMapAndSelectedPagesId[1]), ...(value || [])] + }, [pagesMapAndSelectedPagesId, value]) const [selectedPagesId, setSelectedPagesId] = useState>(new Set(defaultSelectedPagesId)) useEffect(() => { setSelectedPagesId(new Set(defaultSelectedPagesId)) - }, [data]) + }, [defaultSelectedPagesId]) const handleSearchValueChange = useCallback((value: string) => { setSearchValue(value) }, []) + const invalidPreImportNotionPages = useInvalidPreImportNotionPages() + const handleSelectCredential = useCallback((credentialId: string) => { const credential = notionCredentials.find(item => item.credentialId === credentialId)! + invalidPreImportNotionPages({ datasetId, credentialId: credential.credentialId }) setCurrentCredential(credential) - }, []) + onSelect([]) // Clear selected pages when changing credential + }, [onSelect]) const handleSelectPages = useCallback((newSelectedPagesId: Set) => { - const selectedPages = Array.from(newSelectedPagesId).map(pageId => getPagesMapAndSelectedPagesId[0][pageId]) + const selectedPages = Array.from(newSelectedPagesId).map(pageId => pagesMapAndSelectedPagesId[0][pageId]) setSelectedPagesId(new Set(Array.from(newSelectedPagesId))) onSelect(selectedPages) - }, [getPagesMapAndSelectedPagesId, onSelect]) + }, [pagesMapAndSelectedPagesId, onSelect]) const handlePreviewPage = useCallback((previewPageId: string) => { if (onPreview) - onPreview(getPagesMapAndSelectedPagesId[0][previewPageId]) - }, [getPagesMapAndSelectedPagesId, onSelect, onPreview]) + onPreview(pagesMapAndSelectedPagesId[0][previewPageId]) + }, [pagesMapAndSelectedPagesId, onPreview]) const handleConfigureNotion = useCallback(() => { setShowAccountSettingModal({ payload: 'data-source' }) }, [setShowAccountSettingModal]) - if (!data) { + if (isFetchingNotionPagesError) { return (
- + {isFetchingNotionPages ? ( +
+ +
+ ) : ( + + )}
diff --git a/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.tsx b/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.tsx index 22d2a1634e..5d045f1b37 100644 --- a/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.tsx +++ b/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { XMarkIcon } from '@heroicons/react/24/outline' import NotionPageSelector from '../base' @@ -7,6 +7,9 @@ import type { NotionPage } from '@/models/common' import cn from '@/utils/classnames' import Modal from '@/app/components/base/modal' import { noop } from 'lodash-es' +import { useGetDefaultDataSourceListAuth } from '@/service/use-datasource' +import NotionConnector from '../../notion-connector' +import { useModalContextSelector } from '@/context/modal-context' type NotionPageSelectorModalProps = { isShow: boolean @@ -21,17 +24,39 @@ const NotionPageSelectorModal = ({ datasetId, }: NotionPageSelectorModalProps) => { const { t } = useTranslation() + const setShowAccountSettingModal = useModalContextSelector(state => state.setShowAccountSettingModal) const [selectedPages, setSelectedPages] = useState([]) - const handleClose = () => { + const { data: dataSourceList } = useGetDefaultDataSourceListAuth() + + const handleClose = useCallback(() => { onClose() - } - const handleSelectPage = (newSelectedPages: NotionPage[]) => { + }, [onClose]) + + const handleSelectPage = useCallback((newSelectedPages: NotionPage[]) => { setSelectedPages(newSelectedPages) - } - const handleSave = () => { + }, []) + + const handleSave = useCallback(() => { onSave(selectedPages) - } + }, [onSave]) + + const handleOpenSetting = useCallback(() => { + setShowAccountSettingModal({ payload: 'data-source' }) + }, [setShowAccountSettingModal]) + + const authedDataSourceList = dataSourceList?.result || [] + + const isNotionAuthed = useMemo(() => { + if (!authedDataSourceList) return false + const notionSource = authedDataSourceList.find(item => item.provider === 'notion_datasource') + if (!notionSource) return false + return notionSource.credentials_list.length > 0 + }, [authedDataSourceList]) + + const notionCredentialList = useMemo(() => { + return authedDataSourceList.find(item => item.provider === 'notion_datasource')?.credentials_list || [] + }, [authedDataSourceList]) return (
-
{t('common.dataSource.notion.selector.addPages')}
+
{t('common.dataSource.notion.selector.addPages')}
- + {!isNotionAuthed && } + {isNotionAuthed && ( + + )}
{t('common.operation.cancel')}
{t('common.operation.save')}
diff --git a/web/app/components/base/notion-page-selector/page-selector/index.tsx b/web/app/components/base/notion-page-selector/page-selector/index.tsx index 33d02da772..a61b45cbf6 100644 --- a/web/app/components/base/notion-page-selector/page-selector/index.tsx +++ b/web/app/components/base/notion-page-selector/page-selector/index.tsx @@ -1,4 +1,4 @@ -import { memo, useMemo, useState } from 'react' +import { memo, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { FixedSizeList as List, areEqual } from 'react-window' import type { ListChildComponentProps } from 'react-window' @@ -194,11 +194,10 @@ const PageSelector = ({ onPreview, }: PageSelectorProps) => { const { t } = useTranslation() - const [prevDataList, setPrevDataList] = useState(list) const [dataList, setDataList] = useState([]) const [localPreviewPageId, setLocalPreviewPageId] = useState('') - if (prevDataList !== list) { - setPrevDataList(list) + + useEffect(() => { setDataList(list.filter(item => item.parent_id === 'root' || !pagesMap[item.parent_id]).map((item) => { return { ...item, @@ -206,7 +205,8 @@ const PageSelector = ({ depth: 0, } })) - } + }, [list]) + const searchDataList = list.filter((item) => { return item.page_name.includes(searchValue) }).map((item) => { diff --git a/web/app/components/datasets/create/index.tsx b/web/app/components/datasets/create/index.tsx index 41ee1b0820..5e9d6ea522 100644 --- a/web/app/components/datasets/create/index.tsx +++ b/web/app/components/datasets/create/index.tsx @@ -10,7 +10,7 @@ import { TopBar } from './top-bar' import { DataSourceType } from '@/models/datasets' import type { CrawlOptions, CrawlResultItem, FileItem, createDocumentResponse } from '@/models/datasets' import { DataSourceProvider, type NotionPage } from '@/models/common' -import { useModalContext } from '@/context/modal-context' +import { useModalContextSelector } from '@/context/modal-context' import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useGetDefaultDataSourceListAuth } from '@/service/use-datasource' import produce from 'immer' @@ -33,7 +33,7 @@ const DEFAULT_CRAWL_OPTIONS: CrawlOptions = { const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => { const { t } = useTranslation() - const { setShowAccountSettingModal } = useModalContext() + const setShowAccountSettingModal = useModalContextSelector(state => state.setShowAccountSettingModal) const datasetDetail = useDatasetDetailContextWithSelector(state => state.dataset) const { data: embeddingsDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding) diff --git a/web/service/knowledge/use-import.ts b/web/service/knowledge/use-import.ts index f169b0c468..07579a065e 100644 --- a/web/service/knowledge/use-import.ts +++ b/web/service/knowledge/use-import.ts @@ -1,18 +1,20 @@ -import { useQuery } from '@tanstack/react-query' +import { useQuery, useQueryClient } from '@tanstack/react-query' import { get } from '../base' import type { DataSourceNotionWorkspace } from '@/models/common' type PreImportNotionPagesParams = { - credentialId: string datasetId: string + credentialId: string } +const PRE_IMPORT_NOTION_PAGES_QUERY_KEY = 'notion-pre-import-pages' + export const usePreImportNotionPages = ({ - credentialId, datasetId, + credentialId, }: PreImportNotionPagesParams) => { return useQuery({ - queryKey: ['notion-pre-import-pages', credentialId, datasetId], + queryKey: [PRE_IMPORT_NOTION_PAGES_QUERY_KEY, datasetId, credentialId], queryFn: async () => { return get<{ notion_info: DataSourceNotionWorkspace[] }>('/notion/pre-import/pages', { params: { @@ -24,3 +26,17 @@ export const usePreImportNotionPages = ({ retry: 0, }) } + +export const useInvalidPreImportNotionPages = () => { + const queryClient = useQueryClient() + return ({ + datasetId, + credentialId, + }: PreImportNotionPagesParams) => { + queryClient.invalidateQueries( + { + queryKey: [PRE_IMPORT_NOTION_PAGES_QUERY_KEY, datasetId, credentialId], + }, + ) + } +}