mirror of
https://github.com/langgenius/dify.git
synced 2026-09-05 08:48:10 +08:00
fix(knowledge-fs): correct source provider branding
This commit is contained in:
parent
d8dde7fb5e
commit
077cdf5a54
@ -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:",
|
||||
|
||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
@ -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(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
|
||||
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(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
|
||||
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',
|
||||
|
||||
@ -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) => (
|
||||
<RadioItem<string>
|
||||
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',
|
||||
)}
|
||||
>
|
||||
<span aria-hidden className={`${option.icon} size-4 shrink-0`} />
|
||||
<span className="truncate">{option.label}</span>
|
||||
</RadioItem>
|
||||
))}
|
||||
{providers.map((option) => {
|
||||
const presentation = sourceProviderPresentation(option.label, sourceType)
|
||||
return (
|
||||
<RadioItem<string>
|
||||
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',
|
||||
)}
|
||||
>
|
||||
<SourceProviderIcon fallbackIcon={presentation?.fallbackIcon ?? option.icon} />
|
||||
<span className="truncate">{option.label}</span>
|
||||
</RadioItem>
|
||||
)
|
||||
})}
|
||||
</RadioGroup>
|
||||
</Fieldset>
|
||||
|
||||
|
||||
@ -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: <span aria-hidden className={`${option.fallbackIcon} size-4 shrink-0`} />,
|
||||
icon: <SourceProviderIcon fallbackIcon={option.fallbackIcon} />,
|
||||
label: option.label,
|
||||
value: option.key,
|
||||
}))}
|
||||
@ -463,7 +464,7 @@ export function CreateSourceSetup({
|
||||
</div>
|
||||
) : providerOption && !providerOption.installed ? (
|
||||
<SourceProviderNotInstalledCard
|
||||
icon={<span aria-hidden className={`${providerOption.fallbackIcon} size-4.5`} />}
|
||||
icon={<SourceProviderIcon fallbackIcon={providerOption.fallbackIcon} />}
|
||||
provider={providerOption.label}
|
||||
onInstall={() =>
|
||||
globalThis.open(
|
||||
@ -482,7 +483,7 @@ export function CreateSourceSetup({
|
||||
provider: installedProviderOption.label,
|
||||
})}
|
||||
disabled={disabled}
|
||||
icon={<span aria-hidden className={`${installedProviderOption.fallbackIcon} size-4.5`} />}
|
||||
icon={<SourceProviderIcon fallbackIcon={installedProviderOption.fallbackIcon} />}
|
||||
title={t(($) => $['newKnowledge.providerNotConfigured'], {
|
||||
provider: installedProviderOption.label,
|
||||
})}
|
||||
|
||||
@ -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<RecommendedProvider, 'fallbackIcon' | 'label'> | 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,
|
||||
|
||||
@ -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<SourceAction>()
|
||||
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 <Result,>(
|
||||
action: SourceAction,
|
||||
@ -560,7 +552,7 @@ function SourceRow({
|
||||
</td>
|
||||
<td className="min-w-0 py-2 pr-3">
|
||||
<div className="flex min-w-0 items-center gap-2.5">
|
||||
<span aria-hidden className={cn('size-4.5 shrink-0 text-text-tertiary', sourceIcon)} />
|
||||
<SourceProviderIcon fallbackIcon={sourceIcon} />
|
||||
<div className="min-w-0">
|
||||
<p className="truncate system-xs-medium text-text-primary">{source.name}</p>
|
||||
</div>
|
||||
|
||||
@ -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:*",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user