From 077cdf5a549e7ee14a092d29f04cdea35db44de9 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:28:01 +0800 Subject: [PATCH] fix(knowledge-fs): correct source provider branding --- package.json | 1 + pnpm-lock.yaml | 16 +++++ pnpm-workspace.yaml | 1 + web/app/styles/plugins/icons.ts | 2 +- .../__tests__/source-provider-options.spec.ts | 21 +++++++ .../new-rag/__tests__/sources-page.spec.tsx | 48 +++++++++++++++ .../new-rag/add-source-placeholder.tsx | 37 +++++++----- web/features/new-rag/create-source-setup.tsx | 7 ++- .../new-rag/source-provider-options.ts | 34 +++++++++-- web/features/new-rag/sources-page.tsx | 60 ++++++++----------- web/package.json | 1 + 11 files changed, 169 insertions(+), 59 deletions(-) create mode 100644 web/features/new-rag/__tests__/source-provider-options.spec.ts diff --git a/package.json b/package.json index 736d6afac10..2d7d24d7be3 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@eslint-react/eslint-plugin": "catalog:", "@eslint/markdown": "catalog:", "@iconify-json/heroicons": "catalog:", + "@iconify-json/logos": "catalog:", "@iconify-json/ri": "catalog:", "@tanstack/eslint-plugin-query": "catalog:", "@types/node": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2104938791..1c1fb02226a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,6 +57,9 @@ catalogs: '@iconify-json/heroicons': specifier: 1.2.3 version: 1.2.3 + '@iconify-json/logos': + specifier: 1.2.12 + version: 1.2.12 '@iconify-json/ri': specifier: 1.2.10 version: 1.2.10 @@ -671,6 +674,9 @@ importers: '@iconify-json/heroicons': specifier: 'catalog:' version: 1.2.3 + '@iconify-json/logos': + specifier: 'catalog:' + version: 1.2.12 '@iconify-json/ri': specifier: 'catalog:' version: 1.2.10 @@ -1501,6 +1507,9 @@ importers: '@iconify-json/heroicons': specifier: 'catalog:' version: 1.2.3 + '@iconify-json/logos': + specifier: 'catalog:' + version: 1.2.12 '@iconify-json/ri': specifier: 'catalog:' version: 1.2.10 @@ -2354,6 +2363,9 @@ packages: '@iconify-json/heroicons@1.2.3': resolution: {integrity: sha512-n+vmCEgTesRsOpp5AB5ILB6srsgsYK+bieoQBNlafvoEhjVXLq8nIGN4B0v/s4DUfa0dOrjwE/cKJgIKdJXOEg==} + '@iconify-json/logos@1.2.12': + resolution: {integrity: sha512-zUi/AoezU2F3L65nPVd2smiU6Y+ZI7RjdVPlGfeAeYbPbZ9kWn7Ucxj+KshmyQRBYwLtKoqAlUyoGgMqWG1T8g==} + '@iconify-json/ri@1.2.10': resolution: {integrity: sha512-WWMhoncVVM+Xmu9T5fgu2lhYRrKTEWhKk3Com0KiM111EeEsRLiASjpsFKnC/SrB6covhUp95r2mH8tGxhgd5Q==} @@ -10129,6 +10141,10 @@ snapshots: dependencies: '@iconify/types': 2.0.0 + '@iconify-json/logos@1.2.12': + dependencies: + '@iconify/types': 2.0.0 + '@iconify-json/ri@1.2.10': dependencies: '@iconify/types': 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3517b184dcb..3d40662ad6e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -67,6 +67,7 @@ catalog: '@hey-api/openapi-ts': 0.98.2 '@hono/node-server': 2.1.0 '@iconify-json/heroicons': 1.2.3 + '@iconify-json/logos': 1.2.12 '@iconify-json/ri': 1.2.10 '@lexical/code': 0.47.0 '@lexical/link': 0.47.0 diff --git a/web/app/styles/plugins/icons.ts b/web/app/styles/plugins/icons.ts index 1e73c28bf00..202331d51d2 100644 --- a/web/app/styles/plugins/icons.ts +++ b/web/app/styles/plugins/icons.ts @@ -9,7 +9,7 @@ import { getIconCollections, iconsPlugin } from '@egoist/tailwindcss-icons' export default iconsPlugin({ collections: { - ...getIconCollections(['heroicons', 'ri']), + ...getIconCollections(['heroicons', 'logos', 'ri']), 'custom-public': customPublicIcons, 'custom-vender': customVenderIcons, }, diff --git a/web/features/new-rag/__tests__/source-provider-options.spec.ts b/web/features/new-rag/__tests__/source-provider-options.spec.ts new file mode 100644 index 00000000000..77becf4736e --- /dev/null +++ b/web/features/new-rag/__tests__/source-provider-options.spec.ts @@ -0,0 +1,21 @@ +import { sourceProviderPresentation } from '../source-provider-options' + +describe('sourceProviderPresentation', () => { + it.each([ + ['Firecrawl', 'websiteCrawl', 'Firecrawl', 'i-custom-public-common-firecrawl'], + ['Jina Reader', 'websiteCrawl', 'Jina Reader', 'i-custom-public-llm-jina'], + ['WaterCrawl', 'websiteCrawl', 'WaterCrawl', 'i-custom-public-knowledge-watercrawl'], + ['OneDrive', 'onlineDrive', 'OneDrive', 'i-logos-microsoft-onedrive'], + ['aws_s3', 'onlineDrive', 'Amazon S3', 'i-logos-aws-s3'], + ['notion_datasource', 'onlineDocuments', 'Notion', 'i-custom-public-common-notion'], + ] as const)( + 'returns the canonical presentation for %s', + (provider, sourceType, label, fallbackIcon) => { + expect(sourceProviderPresentation(provider, sourceType)).toEqual({ fallbackIcon, label }) + }, + ) + + it('does not collapse a custom provider name that merely contains a built-in brand', () => { + expect(sourceProviderPresentation('Notion Backup', 'onlineDocuments')).toBeUndefined() + }) +}) diff --git a/web/features/new-rag/__tests__/sources-page.spec.tsx b/web/features/new-rag/__tests__/sources-page.spec.tsx index 51d3844863c..df61d86e8f4 100644 --- a/web/features/new-rag/__tests__/sources-page.spec.tsx +++ b/web/features/new-rag/__tests__/sources-page.spec.tsx @@ -1610,6 +1610,31 @@ describe('SourcesPage', () => { expect(within(row).queryByText('notion_datasource')).not.toBeInTheDocument() }) + it('preserves a custom provider name containing a built-in brand', () => { + sourcesQuery.data = { + pages: [ + { + items: [ + source({ + metadata: { + providerKind: 'online-document', + providerName: 'Notion Backup', + }, + name: 'Archived workspace', + type: 'connector', + }), + ], + }, + ], + } + + render() + + const row = screen.getByRole('row', { name: /Archived workspace/ }) + expect(within(row).getByText('Notion Backup')).toBeInTheDocument() + expect(within(row).queryByText('Notion')).not.toBeInTheDocument() + }) + it('uses provider kind to distinguish legacy Google Drive sources', () => { sourcesQuery.data = { pages: [ @@ -1635,6 +1660,29 @@ describe('SourcesPage', () => { expect(within(row).getByText('dataset.newKnowledge.onlineDrive')).toBeInTheDocument() }) + it('restores the OneDrive name, type, and brand icon', () => { + sourcesQuery.data = { + pages: [ + { + items: [ + source({ + metadata: { providerName: 'microsoft_onedrive' }, + name: 'Sales enablement', + type: 'connector', + }), + ], + }, + ], + } + + render() + + const row = screen.getByRole('row', { name: /Sales enablement/ }) + expect(within(row).getByText('OneDrive')).toBeInTheDocument() + expect(within(row).getByText('dataset.newKnowledge.onlineDrive')).toBeInTheDocument() + expect(row.querySelector('.i-logos-microsoft-onedrive')).toBeInTheDocument() + }) + it('does not report ordinary source updates as successful syncs', () => { const syncPolicy: SourceSyncPolicy = { createdAt: '2026-07-20T10:00:00Z', diff --git a/web/features/new-rag/add-source-placeholder.tsx b/web/features/new-rag/add-source-placeholder.tsx index 3dd21b5f1f4..c205976759c 100644 --- a/web/features/new-rag/add-source-placeholder.tsx +++ b/web/features/new-rag/add-source-placeholder.tsx @@ -39,6 +39,8 @@ import { NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH, NEW_KNOWLEDGE_SOURCE_URL_MAX_LENGTH, } from './routes' +import { sourceProviderPresentation } from './source-provider-options' +import { SourceProviderIcon } from './source-setup-fields' const connectedProviders = { onlineDocuments: [ @@ -48,8 +50,8 @@ const connectedProviders = { ], onlineDrive: [ { icon: 'i-custom-public-common-google-drive', label: 'Google Drive' }, - { icon: 'i-ri-cloud-line', label: 'OneDrive' }, - { icon: 'i-ri-box-3-line', label: 'Amazon S3' }, + { icon: 'i-logos-microsoft-onedrive', label: 'OneDrive' }, + { icon: 'i-logos-aws-s3', label: 'Amazon S3' }, ], } as const @@ -210,20 +212,23 @@ export function UnavailableConnectedSourceSetup({ className="grid grid-cols-1 gap-2 sm:grid-cols-3" onValueChange={selectProvider} > - {providers.map((option) => ( - - key={option.label} - value={option.label} - className={cn( - 'flex min-h-9 items-center justify-center gap-2 rounded-lg border border-divider-subtle px-3 system-xs-medium text-text-secondary outline-hidden', - 'hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid', - 'data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg data-checked:text-text-primary', - )} - > - - {option.label} - - ))} + {providers.map((option) => { + const presentation = sourceProviderPresentation(option.label, sourceType) + return ( + + key={option.label} + value={option.label} + className={cn( + 'flex min-h-9 items-center justify-center gap-2 rounded-lg border border-divider-subtle px-3 system-xs-medium text-text-secondary outline-hidden', + 'hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid', + 'data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg data-checked:text-text-primary', + )} + > + + {option.label} + + ) + })} diff --git a/web/features/new-rag/create-source-setup.tsx b/web/features/new-rag/create-source-setup.tsx index d75037ab992..abe01c1fab4 100644 --- a/web/features/new-rag/create-source-setup.tsx +++ b/web/features/new-rag/create-source-setup.tsx @@ -35,6 +35,7 @@ import { import { SourceConnectionRequiredCard, SourceNameField, + SourceProviderIcon, SourceProviderNotInstalledCard, SourceProviderRadioGroup, SourceSyncPolicyField, @@ -434,7 +435,7 @@ export function CreateSourceSetup({ disabled={disabled} layout={sourceType === 'websiteCrawl' ? 'grid-four' : 'grid-three'} options={providerOptions.map((option) => ({ - icon: , + icon: , label: option.label, value: option.key, }))} @@ -463,7 +464,7 @@ export function CreateSourceSetup({ ) : providerOption && !providerOption.installed ? ( } + icon={} provider={providerOption.label} onInstall={() => globalThis.open( @@ -482,7 +483,7 @@ export function CreateSourceSetup({ provider: installedProviderOption.label, })} disabled={disabled} - icon={} + icon={} title={t(($) => $['newKnowledge.providerNotConfigured'], { provider: installedProviderOption.label, })} diff --git a/web/features/new-rag/source-provider-options.ts b/web/features/new-rag/source-provider-options.ts index b3cacd512ec..9e8cd082fb9 100644 --- a/web/features/new-rag/source-provider-options.ts +++ b/web/features/new-rag/source-provider-options.ts @@ -38,7 +38,7 @@ export type SourceProviderOption = InstalledSourceProviderOption | UninstalledSo const recommendedProviders: RecommendedProvider[] = [ { - aliases: ['firecrawl'], + aliases: ['firecrawl', 'plugin-daemon-website', 'plugin-daemon-website-firecrawl'], fallbackIcon: 'i-custom-public-common-firecrawl', label: 'Firecrawl', packageId: 'langgenius/firecrawl_datasource', @@ -62,7 +62,7 @@ const recommendedProviders: RecommendedProvider[] = [ sourceType: 'websiteCrawl', }, { - aliases: ['notion'], + aliases: ['notion', 'notion_datasource'], fallbackIcon: 'i-custom-public-common-notion', label: 'Notion', packageId: 'langgenius/notion_datasource', @@ -95,15 +95,15 @@ const recommendedProviders: RecommendedProvider[] = [ }, { aliases: ['onedrive', 'microsoft onedrive'], - fallbackIcon: 'i-ri-cloud-line', + fallbackIcon: 'i-logos-microsoft-onedrive', label: 'OneDrive', packageId: 'langgenius/onedrive_datasource', providerType: 'online_drive', sourceType: 'onlineDrive', }, { - aliases: ['amazon s3', 'amazons3', 's3'], - fallbackIcon: 'i-ri-box-3-line', + aliases: ['amazon s3', 'amazons3', 'aws s3', 's3'], + fallbackIcon: 'i-logos-aws-s3', label: 'Amazon S3', packageId: 'langgenius/aws_s3_storage', providerType: 'online_drive', @@ -115,6 +115,30 @@ export function normalizeSourceProviderName(value: string) { return value.toLocaleLowerCase().replace(/[^a-z0-9]+/g, '') } +export function sourceProviderPresentation( + value: string, + sourceType?: NewKnowledgeSourceType, +): Pick | undefined { + const normalized = normalizeSourceProviderName(value) + if (!normalized) return undefined + + const candidates = sourceType + ? recommendedProviders.filter((provider) => provider.sourceType === sourceType) + : recommendedProviders + const provider = + (!sourceType + ? candidates.find((candidate) => normalizeSourceProviderName(candidate.label) === normalized) + : undefined) ?? + candidates.find((candidate) => + [candidate.label, candidate.packageId, ...candidate.aliases] + .map(normalizeSourceProviderName) + .includes(normalized), + ) + + if (!provider) return undefined + return { fallbackIcon: provider.fallbackIcon, label: provider.label } +} + function providerKey( sourceType: NewKnowledgeSourceType, plugin: DataSourceItem, diff --git a/web/features/new-rag/sources-page.tsx b/web/features/new-rag/sources-page.tsx index a2d94cefb11..10488152f43 100644 --- a/web/features/new-rag/sources-page.tsx +++ b/web/features/new-rag/sources-page.tsx @@ -52,6 +52,8 @@ import { sourceWorkflowFromApi, sourceWorkflowStatus, } from './source-models' +import { normalizeSourceProviderName, sourceProviderPresentation } from './source-provider-options' +import { SourceProviderIcon } from './source-setup-fields' import { useKnowledgeModelSetupGuard } from './use-knowledge-model-setup-guard' type SourceStatus = Source['status'] @@ -114,32 +116,32 @@ function metadataRecord(metadata: Source['metadata'], key: string) { : undefined } -function knownSourceProviderName(value: string, providerKind?: string) { - const normalized = value.toLocaleLowerCase().replace(/[^a-z0-9]+/g, '') - if (normalized.includes('firecrawl')) return 'Firecrawl' - if (normalized.includes('jinareader') || normalized === 'jina') return 'Jina Reader' - if (normalized.includes('watercrawl')) return 'WaterCrawl' - if (normalized.includes('notion')) return 'Notion' - if (normalized.includes('googledocs')) return 'Google Docs' - if (normalized.includes('googledrive')) - return providerKind === 'online-document' ? 'Google Docs' : 'Google Drive' - if (normalized.includes('confluence')) return 'Confluence' - if (normalized.includes('amazons3') || normalized.includes('awss3')) return 'Amazon S3' +function sourceProviderType(source: Source, providerKind?: string) { + if (source.type === 'web' || providerKind === 'website') return 'websiteCrawl' as const + if (providerKind === 'online-document') return 'onlineDocuments' as const + if (providerKind === 'online-drive') return 'onlineDrive' as const return undefined } -function sourceProviderName(source: Source) { +function sourceProviderDetails(source: Source) { const providerKind = metadataString(source.metadata, 'providerKind') + const providerType = sourceProviderType(source, providerKind) const explicitName = metadataString(source.metadata, 'providerName') - if (explicitName) return knownSourceProviderName(explicitName, providerKind) ?? explicitName + if (explicitName) { + const presentation = sourceProviderPresentation(explicitName, providerType) + return { + iconClass: presentation?.fallbackIcon, + name: presentation?.label ?? explicitName, + } + } const providerId = metadataString(source.metadata, 'providerId') - if (!providerId) return undefined - if (providerId === 'plugin-daemon-website') return 'Firecrawl' - if (providerId.toLocaleLowerCase().includes('fakecrawler')) return 'FakeCrawler' - const knownName = knownSourceProviderName(providerId, providerKind) - if (knownName) return knownName - return undefined + if (!providerId) return {} + const presentation = sourceProviderPresentation(providerId, providerType) + if (presentation) return { iconClass: presentation.fallbackIcon, name: presentation.label } + if (normalizeSourceProviderName(providerId).includes('fakecrawler')) + return { name: 'FakeCrawler' } + return {} } function sourceLastSyncAt(source: Source) { @@ -404,7 +406,8 @@ function SourceRow({ const [pendingAction, setPendingAction] = useState() const syncWorkflow = source.syncWorkflow - const providerName = sourceProviderName(source) + const provider = sourceProviderDetails(source) + const providerName = provider.name const providerKind = metadataString(source.metadata, 'providerKind') const sourceSyncPolicy = source.syncPolicy const syncPolicy = sourceSyncPolicy @@ -425,23 +428,12 @@ function SourceRow({ : source.type === 'connector' && (providerKind === 'online-drive' || providerName === 'Google Drive' || + providerName === 'OneDrive' || providerName === 'Amazon S3') ? t(($) => $['newKnowledge.onlineDrive']) : t(($) => $[`newKnowledge.sourceType.${source.type}`]) const sourceIcon = - source.type === 'web' - ? 'i-ri-global-line' - : providerName === 'Notion' - ? 'i-custom-public-common-notion' - : providerName === 'Amazon S3' - ? 'i-ri-folder-line' - : providerName === 'Google Drive' - ? 'i-custom-public-common-google-drive' - : providerName === 'Google Docs' - ? 'i-ri-file-text-fill text-[#4d8bf5]' - : providerName === 'Confluence' - ? 'i-custom-public-common-confluence' - : 'i-ri-links-line' + provider.iconClass ?? (source.type === 'web' ? 'i-ri-global-line' : 'i-ri-links-line') const runAction = async ( action: SourceAction, @@ -560,7 +552,7 @@ function SourceRow({
- +

{source.name}

diff --git a/web/package.json b/web/package.json index a416e30acfb..7672304fde7 100644 --- a/web/package.json +++ b/web/package.json @@ -150,6 +150,7 @@ "@dify/tsconfig": "workspace:*", "@egoist/tailwindcss-icons": "catalog:", "@iconify-json/heroicons": "catalog:", + "@iconify-json/logos": "catalog:", "@iconify-json/ri": "catalog:", "@langgenius/dev-proxy": "workspace:*", "@langgenius/dify-ui": "workspace:*",