From 5729d38776e5c1cb19237e6aedb79dd53829a966 Mon Sep 17 00:00:00 2001 From: twwu Date: Thu, 7 Aug 2025 15:34:15 +0800 Subject: [PATCH] feat: add CredentialIcon component and integrate it into credential selector for improved avatar display --- .../datasets/common/credential-icon.tsx | 53 ++++++++++++++++ .../base/credential-selector/index.tsx | 11 +++- .../base/credential-selector/item.tsx | 9 ++- .../base/credential-selector/trigger.tsx | 29 +++++---- .../data-source/base/header.tsx | 10 +-- .../data-source/online-documents/index.tsx | 61 ++++++++++--------- .../data-source/store/index.ts | 11 ++-- .../data-source/store/provider.tsx | 2 +- .../data-source/store/slices/common.ts | 14 ++++- .../data-source/store/slices/local-file.ts | 2 +- .../store/slices/online-document.ts | 2 +- .../data-source/store/slices/online-drive.ts | 6 +- .../data-source/store/slices/website-crawl.ts | 2 +- .../datasets/documents/detail/index.tsx | 1 - .../data-source-page-new/card.tsx | 5 +- .../hooks/use-data-source-auth-update.ts | 17 +++++- web/service/use-datasource.ts | 40 ++++++++++-- 17 files changed, 198 insertions(+), 77 deletions(-) create mode 100644 web/app/components/datasets/common/credential-icon.tsx diff --git a/web/app/components/datasets/common/credential-icon.tsx b/web/app/components/datasets/common/credential-icon.tsx new file mode 100644 index 0000000000..fc4143681d --- /dev/null +++ b/web/app/components/datasets/common/credential-icon.tsx @@ -0,0 +1,53 @@ +import cn from '@/utils/classnames' +import React, { useMemo } from 'react' + +type CredentialIconProps = { + avatar_url?: string + name: string + size?: number + className?: string +} + +const ICON_BG_COLORS = [ + 'bg-components-icon-bg-orange-dark-solid', + 'bg-components-icon-bg-pink-solid', + 'bg-components-icon-bg-indigo-solid', + 'bg-components-icon-bg-teal-solid', +] + +export const CredentialIcon: React.FC = ({ + avatar_url, + name, + size = 20, + className = '', +}) => { + const firstLetter = useMemo(() => name.charAt(0).toUpperCase(), [name]) + const bgColor = useMemo(() => ICON_BG_COLORS[firstLetter.charCodeAt(0) % ICON_BG_COLORS.length], [firstLetter]) + + if (avatar_url && avatar_url !== 'default') { + return ( + {`${name} + ) + } + + return ( +
+ + {firstLetter} + +
+ ) +} diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx index e4b04268ce..0cd0270fc7 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react' +import React, { useCallback, useEffect, useMemo } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, @@ -24,7 +24,14 @@ const CredentialSelector = ({ }: CredentialSelectorProps) => { const [open, { toggle }] = useBoolean(false) - const currentCredential = credentials.find(cred => cred.id === currentCredentialId) as DataSourceCredential + const currentCredential = useMemo(() => { + return credentials.find(cred => cred.id === currentCredentialId) + }, [credentials, currentCredentialId]) + + useEffect(() => { + if (!currentCredential && credentials.length) + onCredentialChange(credentials[0].id) + }, [currentCredential, credentials]) const handleCredentialChange = useCallback((credentialId: string) => { onCredentialChange(credentialId) diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx index c833f27403..ab8de51fb1 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx @@ -1,3 +1,4 @@ +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' @@ -28,8 +29,12 @@ const Item = ({ className='flex cursor-pointer items-center gap-x-2 rounded-lg p-2 hover:bg-state-base-hover' onClick={handleCredentialChange} > - - + + {t('datasetPipeline.credentialSelector.name', { credentialName: name, pluginName, diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx index 0b21571645..9f70b2f56a 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx @@ -3,9 +3,10 @@ import type { DataSourceCredential } from '@/types/pipeline' import { useTranslation } from 'react-i18next' import { RiArrowDownSLine } from '@remixicon/react' import cn from '@/utils/classnames' +import { CredentialIcon } from '@/app/components/datasets/common/credential-icon' type TriggerProps = { - currentCredential: DataSourceCredential + currentCredential: DataSourceCredential | undefined pluginName: string isOpen: boolean } @@ -19,23 +20,29 @@ const Trigger = ({ const { avatar_url, - name, - } = currentCredential + name = '', + } = currentCredential || {} return ( -
- -
- +
+ +
+ {t('datasetPipeline.credentialSelector.name', { credentialName: name, pluginName, })} - +
) diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx index 0756e1eb37..cc1a589b6e 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx @@ -23,11 +23,11 @@ const Header = ({ return (
- ) diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.tsx index 7fb4be3c6e..357fb57f64 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.tsx @@ -13,8 +13,8 @@ import { useDataSourceStore, useDataSourceStoreWithSelector } from '../store' import { useShallow } from 'zustand/react/shallow' import { useModalContextSelector } from '@/context/modal-context' import Title from './title' -import { CredentialTypeEnum } from '@/app/components/plugins/plugin-auth' -import { noop } from 'lodash-es' +import { useGetDataSourceAuth } from '@/service/use-datasource' +import Loading from '@/app/components/base/loading' type OnlineDocumentsProps = { isInPipeline?: boolean @@ -34,12 +34,20 @@ const OnlineDocuments = ({ searchValue, selectedPagesId, currentWorkspaceId, + currentCredentialId, } = useDataSourceStoreWithSelector(useShallow(state => ({ documentsData: state.documentsData, searchValue: state.searchValue, selectedPagesId: state.selectedPagesId, currentWorkspaceId: state.currentWorkspaceId, + currentCredentialId: state.currentCredentialId, }))) + + const { data: dataSourceAuth } = useGetDataSourceAuth({ + pluginId: nodeData.plugin_id, + provider: nodeData.provider_name, + }) + const dataSourceStore = useDataSourceStore() const PagesMapAndSelectedPagesId: DataSourceNotionPageMap = useMemo(() => { @@ -137,29 +145,16 @@ const OnlineDocuments = ({ }) }, [setShowAccountSettingModal]) - if (!documentsData?.length) - return null - return (
@@ -172,18 +167,24 @@ const OnlineDocuments = ({ />
- + {documentsData?.length ? ( + + ) : ( +
+ +
+ )}
diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/index.ts b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/index.ts index 228d2039e3..8ec6a85764 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/index.ts +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/index.ts @@ -12,12 +12,11 @@ import { createWebsiteCrawlSlice } from './slices/website-crawl' import type { OnlineDriveSliceShape } from './slices/online-drive' import { createOnlineDriveSlice } from './slices/online-drive' -export type DataSourceShape = - CommonShape & - LocalFileSliceShape & - OnlineDocumentSliceShape & - WebsiteCrawlSliceShape & - OnlineDriveSliceShape +export type DataSourceShape = CommonShape + & LocalFileSliceShape + & OnlineDocumentSliceShape + & WebsiteCrawlSliceShape + & OnlineDriveSliceShape export const createDataSourceStore = () => { return createStore((...args) => ({ diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/provider.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/provider.tsx index 446d47704c..a1d27021c8 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/provider.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/provider.tsx @@ -14,7 +14,7 @@ type DataSourceProviderProps = { const DataSourceProvider = ({ children, }: DataSourceProviderProps) => { - const storeRef = useRef() + const storeRef = useRef(null) if (!storeRef.current) storeRef.current = createDataSourceStore() diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/common.ts b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/common.ts index 5d4a607905..4519e0eb01 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/common.ts +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/common.ts @@ -1,11 +1,19 @@ import type { StateCreator } from 'zustand' export type CommonShape = { - currentNodeIdRef: React.MutableRefObject + currentNodeIdRef: React.RefObject + currentCredentialId: string + setCurrentCredentialId: (credentialId: string) => void + currentCredentialIdRef: React.RefObject } -export const createCommonSlice: StateCreator = () => { +export const createCommonSlice: StateCreator = (set) => { return ({ - currentNodeIdRef: { current: undefined }, + currentNodeIdRef: { current: '' }, + currentCredentialId: '', + setCurrentCredentialId: (credentialId: string) => { + set({ currentCredentialId: credentialId }) + }, + currentCredentialIdRef: { current: '' }, }) } diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/local-file.ts b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/local-file.ts index 0f0e20694e..f13ef62264 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/local-file.ts +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/local-file.ts @@ -6,7 +6,7 @@ export type LocalFileSliceShape = { setLocalFileList: (fileList: FileItem[]) => void currentLocalFile: File | undefined setCurrentLocalFile: (file: File | undefined) => void - previewLocalFileRef: React.MutableRefObject + previewLocalFileRef: React.RefObject } export const createLocalFileSlice: StateCreator = (set, get) => { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-document.ts b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-document.ts index cdb7fbbebb..2b5eb1271b 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-document.ts +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-document.ts @@ -14,7 +14,7 @@ export type OnlineDocumentSliceShape = { setCurrentDocument: (document: NotionPage | undefined) => void selectedPagesId: Set setSelectedPagesId: (selectedPagesId: Set) => void - previewOnlineDocumentRef: React.MutableRefObject + previewOnlineDocumentRef: React.RefObject } export const createOnlineDocumentSlice: StateCreator = (set, get) => { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-drive.ts b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-drive.ts index 46d83599ab..2c9834e941 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-drive.ts +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-drive.ts @@ -12,9 +12,9 @@ export type OnlineDriveSliceShape = { setFileList: (fileList: OnlineDriveFile[]) => void bucket: string setBucket: (bucket: string) => void - startAfter: React.MutableRefObject - isTruncated: React.MutableRefObject - previewOnlineDriveFileRef: React.MutableRefObject + startAfter: React.RefObject + isTruncated: React.RefObject + previewOnlineDriveFileRef: React.RefObject } export const createOnlineDriveSlice: StateCreator = (set, get) => { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/website-crawl.ts b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/website-crawl.ts index 5286e5eef9..51e8e5ede4 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/website-crawl.ts +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/website-crawl.ts @@ -13,7 +13,7 @@ export type WebsiteCrawlSliceShape = { setStep: (step: CrawlStep) => void previewIndex: number setPreviewIndex: (index: number) => void - previewWebsitePageRef: React.MutableRefObject + previewWebsitePageRef: React.RefObject } export const createWebsiteCrawlSlice: StateCreator = (set, get) => { diff --git a/web/app/components/datasets/documents/detail/index.tsx b/web/app/components/datasets/documents/detail/index.tsx index fcecf2a12c..e3dc202d99 100644 --- a/web/app/components/datasets/documents/detail/index.tsx +++ b/web/app/components/datasets/documents/detail/index.tsx @@ -88,7 +88,6 @@ const DocumentDetail: FC = ({ datasetId, documentId }) => { documentId, params: { metadata: 'without' }, }) - console.log('🚀 ~ DocumentDetail ~ documentDetail:', documentDetail) const { data: documentMetadata } = useDocumentMetadata({ datasetId, diff --git a/web/app/components/header/account-setting/data-source-page-new/card.tsx b/web/app/components/header/account-setting/data-source-page-new/card.tsx index 74e188d350..7a8790e76d 100644 --- a/web/app/components/header/account-setting/data-source-page-new/card.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/card.tsx @@ -43,7 +43,10 @@ const Card = ({ category: AuthCategory.datasource, provider: `${item.plugin_id}/${item.name}`, } - const { handleAuthUpdate } = useDataSourceAuthUpdate() + const { handleAuthUpdate } = useDataSourceAuthUpdate({ + pluginId: item.plugin_id, + provider: item.name, + }) const { deleteCredentialId, doingAction, diff --git a/web/app/components/header/account-setting/data-source-page-new/hooks/use-data-source-auth-update.ts b/web/app/components/header/account-setting/data-source-page-new/hooks/use-data-source-auth-update.ts index beda9fdf24..d2b3797b76 100644 --- a/web/app/components/header/account-setting/data-source-page-new/hooks/use-data-source-auth-update.ts +++ b/web/app/components/header/account-setting/data-source-page-new/hooks/use-data-source-auth-update.ts @@ -1,17 +1,28 @@ import { useCallback } from 'react' -import { useInvalidDataSourceListAuth } from '@/service/use-datasource' +import { useInvalidDataSourceAuth, useInvalidDataSourceListAuth } from '@/service/use-datasource' import { useInvalidDefaultDataSourceListAuth } from '@/service/use-datasource' import { useInvalidDataSourceList } from '@/service/use-pipeline' -export const useDataSourceAuthUpdate = () => { +export const useDataSourceAuthUpdate = ({ + pluginId, + provider, +}: { + pluginId: string + provider: string +}) => { const invalidateDataSourceListAuth = useInvalidDataSourceListAuth() const invalidDefaultDataSourceListAuth = useInvalidDefaultDataSourceListAuth() const invalidateDataSourceList = useInvalidDataSourceList() + const invalidateDataSourceAuth = useInvalidDataSourceAuth({ + pluginId, + provider, + }) const handleAuthUpdate = useCallback(() => { invalidateDataSourceListAuth() invalidDefaultDataSourceListAuth() invalidateDataSourceList() - }, [invalidateDataSourceListAuth, invalidateDataSourceList]) + invalidateDataSourceAuth() + }, [invalidateDataSourceListAuth, invalidateDataSourceList, invalidateDataSourceAuth, invalidDefaultDataSourceListAuth]) return { handleAuthUpdate, diff --git a/web/service/use-datasource.ts b/web/service/use-datasource.ts index b923838f86..ede9bbfe27 100644 --- a/web/service/use-datasource.ts +++ b/web/service/use-datasource.ts @@ -4,7 +4,10 @@ import { } from '@tanstack/react-query' import { get } from './base' import { useInvalid } from './use-base' -import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types' +import type { + DataSourceAuth, + DataSourceCredential, +} from '@/app/components/header/account-setting/data-source-page-new/types' const NAME_SPACE = 'data-source-auth' @@ -34,6 +37,7 @@ export const useInvalidDefaultDataSourceListAuth = ( ) => { return useInvalid([NAME_SPACE, 'default-list']) } + export const useGetDataSourceOAuthUrl = ( provider: string, ) => { @@ -41,11 +45,35 @@ export const useGetDataSourceOAuthUrl = ( mutationKey: [NAME_SPACE, 'oauth-url', provider], mutationFn: (credentialId?: string) => { return get< - { - authorization_url: string - state: string - context_id: string - }>(`/oauth/plugin/${provider}/datasource/get-authorization-url?credential_id=${credentialId}`) + { + authorization_url: string + state: string + context_id: string + }>(`/oauth/plugin/${provider}/datasource/get-authorization-url?credential_id=${credentialId}`) }, }) } + +export const useGetDataSourceAuth = ({ + pluginId, + provider, +}: { + pluginId: string + provider: string +}) => { + return useQuery({ + queryKey: [NAME_SPACE, 'specific-data-source', pluginId, provider], + queryFn: () => get<{ result: DataSourceCredential[] }>(`/auth/plugin/datasource/${pluginId}/${provider}`), + retry: 0, + }) +} + +export const useInvalidDataSourceAuth = ({ + pluginId, + provider, +}: { + pluginId: string + provider: string +}) => { + return useInvalid([NAME_SPACE, 'specific-data-source', pluginId, provider]) +}