浏览代码

feat: add CredentialIcon component and integrate it into credential selector for improved avatar display

tags/2.0.0-beta.1
twwu 2 个月前
父节点
当前提交
5729d38776
共有 17 个文件被更改,包括 198 次插入77 次删除
  1. 53
    0
      web/app/components/datasets/common/credential-icon.tsx
  2. 9
    2
      web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx
  3. 7
    2
      web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx
  4. 18
    11
      web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx
  5. 5
    5
      web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx
  6. 31
    30
      web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.tsx
  7. 5
    6
      web/app/components/datasets/documents/create-from-pipeline/data-source/store/index.ts
  8. 1
    1
      web/app/components/datasets/documents/create-from-pipeline/data-source/store/provider.tsx
  9. 11
    3
      web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/common.ts
  10. 1
    1
      web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/local-file.ts
  11. 1
    1
      web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-document.ts
  12. 3
    3
      web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-drive.ts
  13. 1
    1
      web/app/components/datasets/documents/create-from-pipeline/data-source/store/slices/website-crawl.ts
  14. 0
    1
      web/app/components/datasets/documents/detail/index.tsx
  15. 4
    1
      web/app/components/header/account-setting/data-source-page-new/card.tsx
  16. 14
    3
      web/app/components/header/account-setting/data-source-page-new/hooks/use-data-source-auth-update.ts
  17. 34
    6
      web/service/use-datasource.ts

+ 53
- 0
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<CredentialIconProps> = ({
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 (
<img
src={avatar_url}
alt={`${name} logo`}
width={size}
height={size}
className={cn('shrink-0 rounded-md border border-divider-regular object-contain', className)}
/>
)
}

return (
<div
className={cn(
'flex shrink-0 items-center justify-center rounded-md border border-divider-regular',
bgColor,
className,
)}
style={{ width: `${size}px`, height: `${size}px` }}
>
<span className='bg-gradient-to-b from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text text-[13px] font-semibold leading-[1.2] text-transparent opacity-90'>
{firstLetter}
</span>
</div>
)
}

+ 9
- 2
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)

+ 7
- 2
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}
>
<img src={avatar_url} className='size-5 shrink-0 rounded-md border border-divider-regular object-contain' />
<span className='system-sm-medium grow text-text-secondary'>
<CredentialIcon
avatar_url={avatar_url}
name={name}
size={20}
/>
<span className='system-sm-medium grow truncate text-text-secondary'>
{t('datasetPipeline.credentialSelector.name', {
credentialName: name,
pluginName,

+ 18
- 11
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 (
<div className={cn(
'flex cursor-pointer items-center gap-x-2 rounded-md p-1 pr-2',
isOpen ? 'bg-state-base-hover' : 'hover:bg-state-base-hover',
)}>
<img src={avatar_url} className='size-5 shrink-0 rounded-md border border-divider-regular object-contain' />
<div className='flex grow items-center gap-x-1'>
<span className='system-md-semibold text-text-secondary'>
<div
className={cn(
'flex cursor-pointer items-center gap-x-2 rounded-md p-1 pr-2',
isOpen ? 'bg-state-base-hover' : 'hover:bg-state-base-hover',
)}
>
<CredentialIcon
avatar_url={avatar_url}
name={name}
size={20}
/>
<div className='flex items-center gap-x-1'>
<span className='system-md-semibold min-w-0 truncate text-text-secondary'>
{t('datasetPipeline.credentialSelector.name', {
credentialName: name,
pluginName,
})}
</span>
<RiArrowDownSLine className='size-4 text-text-secondary' />
<RiArrowDownSLine className='size-4 shrink-0 text-text-secondary' />
</div>
</div>
)

+ 5
- 5
web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx 查看文件

@@ -23,11 +23,11 @@ const Header = ({

return (
<div className='flex items-center gap-x-2'>
<div className='flex shrink-0 grow items-center gap-x-1'>
<div className='flex grow items-center gap-x-1'>
<CredentialSelector
{...rest}
/>
<Divider type='vertical' className='mx-1 h-3.5' />
<Divider type='vertical' className='mx-1 h-3.5 shrink-0' />
<Tooltip
popupContent={t('datasetPipeline.configurationTip', { pluginName: rest.pluginName })}
position='top'
@@ -35,7 +35,7 @@ const Header = ({
<Button
variant='ghost'
size='small'
className='size-6 px-1'
className='size-6 shrink-0 px-1'
>
<RiEqualizer2Line
className='h-4 w-4'
@@ -45,13 +45,13 @@ const Header = ({
</Tooltip>
</div>
<a
className='system-xs-medium flex items-center gap-x-1 overflow-hidden 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'
>
<RiBookOpenLine className='size-3.5 shrink-0' />
<span className='grow truncate' title={docTitle}>{docTitle}</span>
<span title={docTitle}>{docTitle}</span>
</a>
</div>
)

+ 31
- 30
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 (
<div className='flex flex-col gap-y-2'>
<Header
// todo: delete mock data
docTitle='How to use?'
docLink='https://docs.dify.ai'
onClickConfiguration={handleSetting}
pluginName={nodeData.datasource_label}
currentCredentialId={'12345678'}
onCredentialChange={noop}
credentials={[{
avatar_url: 'https://cloud.dify.ai/logo/logo.svg',
credential: {
credentials: '......',
},
id: '12345678',
is_default: true,
name: 'test123',
type: CredentialTypeEnum.API_KEY,
}]}
currentCredentialId={currentCredentialId}
onCredentialChange={dataSourceStore.getState().setCurrentCredentialId}
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'>
@@ -172,18 +167,24 @@ const OnlineDocuments = ({
/>
</div>
<div className='overflow-hidden rounded-b-xl'>
<PageSelector
checkedIds={selectedPagesId}
disabledValue={new Set()}
searchValue={searchValue}
list={currentWorkspace?.pages || []}
pagesMap={PagesMapAndSelectedPagesId}
onSelect={handleSelectPages}
canPreview={!isInPipeline}
onPreview={handlePreviewPage}
isMultipleChoice={!isInPipeline}
currentWorkspaceId={currentWorkspaceId}
/>
{documentsData?.length ? (
<PageSelector
checkedIds={selectedPagesId}
disabledValue={new Set()}
searchValue={searchValue}
list={currentWorkspace?.pages || []}
pagesMap={PagesMapAndSelectedPagesId}
onSelect={handleSelectPages}
canPreview={!isInPipeline}
onPreview={handlePreviewPage}
isMultipleChoice={!isInPipeline}
currentWorkspaceId={currentWorkspaceId}
/>
) : (
<div className='flex h-[296px] items-center justify-center'>
<Loading type='app' />
</div>
)}
</div>
</div>
</div>

+ 5
- 6
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<DataSourceShape>((...args) => ({

+ 1
- 1
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<DataSourceStoreApi>()
const storeRef = useRef<DataSourceStoreApi>(null)

if (!storeRef.current)
storeRef.current = createDataSourceStore()

+ 11
- 3
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<string | undefined>
currentNodeIdRef: React.RefObject<string>
currentCredentialId: string
setCurrentCredentialId: (credentialId: string) => void
currentCredentialIdRef: React.RefObject<string>
}

export const createCommonSlice: StateCreator<CommonShape> = () => {
export const createCommonSlice: StateCreator<CommonShape> = (set) => {
return ({
currentNodeIdRef: { current: undefined },
currentNodeIdRef: { current: '' },
currentCredentialId: '',
setCurrentCredentialId: (credentialId: string) => {
set({ currentCredentialId: credentialId })
},
currentCredentialIdRef: { current: '' },
})
}

+ 1
- 1
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<DocumentItem | undefined>
previewLocalFileRef: React.RefObject<DocumentItem | undefined>
}

export const createLocalFileSlice: StateCreator<LocalFileSliceShape> = (set, get) => {

+ 1
- 1
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<string>
setSelectedPagesId: (selectedPagesId: Set<string>) => void
previewOnlineDocumentRef: React.MutableRefObject<NotionPage | undefined>
previewOnlineDocumentRef: React.RefObject<NotionPage | undefined>
}

export const createOnlineDocumentSlice: StateCreator<OnlineDocumentSliceShape> = (set, get) => {

+ 3
- 3
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<string>
isTruncated: React.MutableRefObject<boolean>
previewOnlineDriveFileRef: React.MutableRefObject<OnlineDriveFile | undefined>
startAfter: React.RefObject<string>
isTruncated: React.RefObject<boolean>
previewOnlineDriveFileRef: React.RefObject<OnlineDriveFile | undefined>
}

export const createOnlineDriveSlice: StateCreator<OnlineDriveSliceShape> = (set, get) => {

+ 1
- 1
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<CrawlResultItem | undefined>
previewWebsitePageRef: React.RefObject<CrawlResultItem | undefined>
}

export const createWebsiteCrawlSlice: StateCreator<WebsiteCrawlSliceShape> = (set, get) => {

+ 0
- 1
web/app/components/datasets/documents/detail/index.tsx 查看文件

@@ -88,7 +88,6 @@ const DocumentDetail: FC<DocumentDetailProps> = ({ datasetId, documentId }) => {
documentId,
params: { metadata: 'without' },
})
console.log('🚀 ~ DocumentDetail ~ documentDetail:', documentDetail)

const { data: documentMetadata } = useDocumentMetadata({
datasetId,

+ 4
- 1
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,

+ 14
- 3
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,

+ 34
- 6
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])
}

正在加载...
取消
保存