mirror of
https://github.com/langgenius/dify.git
synced 2026-09-02 22:36:15 +08:00
644 lines
23 KiB
TypeScript
644 lines
23 KiB
TypeScript
'use client'
|
|
|
|
import type { KnowledgeFsSpaceCreatePayload } from '@dify/contracts/api/console/knowledge-fs/types.gen'
|
|
import type { NewKnowledgeSourceDraft } from './routes'
|
|
import type { CrawlPreviewPage } from './source-models'
|
|
import type {
|
|
DataSourceAuth,
|
|
DataSourceCredential,
|
|
} from '@/app/components/header/account-setting/data-source-page-new/types'
|
|
import { Button } from '@langgenius/dify-ui/button'
|
|
import { cn } from '@langgenius/dify-ui/cn'
|
|
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { buildIntegrationPath } from '@/app/components/integrations/routes'
|
|
import { consoleClient } from '@/service/client'
|
|
import { useGetDataSourceListAuth } from '@/service/use-datasource'
|
|
import { useDataSourceList } from '@/service/use-pipeline'
|
|
import { CrawlPreviewPageSelection } from './crawl-selection-form'
|
|
import { CreateConnectedSourceSetup } from './create-connected-source-setup'
|
|
import { WebsiteDatasourceParameterForm } from './datasource-parameter-form'
|
|
import {
|
|
datasourceIncludeSubpages,
|
|
datasourceParameterDefaults,
|
|
datasourceParameterSchemas,
|
|
invalidDatasourceParameters,
|
|
missingRequiredDatasourceParameters,
|
|
websiteDatasourceParameterSchemas,
|
|
withDatasourceParameterDefaults,
|
|
} from './datasource-parameter-model'
|
|
import {
|
|
discoverSourceProviderOptions,
|
|
sourceProviderOptionForDraft,
|
|
} from './source-provider-options'
|
|
import {
|
|
SourceNameField,
|
|
SourceProviderCredentialRequiredCard,
|
|
SourceProviderIcon,
|
|
SourceProviderNotInstalledCard,
|
|
SourceProviderRadioGroup,
|
|
SourceSyncPolicyField,
|
|
SourceTypeSelector,
|
|
} from './source-setup-fields'
|
|
|
|
const CRAWL_PREVIEW_SKELETONS = [
|
|
{ id: 'short', sourceWidth: 'w-22.5', titleWidth: 'w-37.5' },
|
|
{ id: 'medium', sourceWidth: 'w-26', titleWidth: 'w-42.5' },
|
|
{ id: 'long', sourceWidth: 'w-29.5', titleWidth: 'w-47.5' },
|
|
{ id: 'longest', sourceWidth: 'w-33', titleWidth: 'w-52.5' },
|
|
] as const
|
|
const CRAWL_POLL_INTERVAL_MS = 1500
|
|
|
|
type LocalCrawlState = 'error' | 'idle' | 'running' | 'stopped' | 'success'
|
|
type InitialSource = NonNullable<KnowledgeFsSpaceCreatePayload['initial_source']>
|
|
|
|
function datasourceAuthForProvider(
|
|
authProviders: DataSourceAuth[],
|
|
pluginId: string,
|
|
provider: string,
|
|
) {
|
|
return authProviders.find(
|
|
(candidate) => candidate.plugin_id === pluginId && candidate.provider === provider,
|
|
)
|
|
}
|
|
|
|
function preferredCredential(auth?: DataSourceAuth): DataSourceCredential | undefined {
|
|
return (
|
|
auth?.credentials_list.find((credential) => credential.is_default) ?? auth?.credentials_list[0]
|
|
)
|
|
}
|
|
|
|
function providerIntegrationPath(packageId?: string) {
|
|
const base = buildIntegrationPath('data-source')
|
|
if (!packageId) return base
|
|
const query = new URLSearchParams({ 'package-ids': JSON.stringify([packageId]) })
|
|
return `${base}?${query.toString()}`
|
|
}
|
|
|
|
function websiteSourceUri(parameters: Record<string, boolean | number | string>, fallback: string) {
|
|
const url = parameters.url
|
|
if (typeof url === 'string') {
|
|
try {
|
|
const parsed = new URL(url)
|
|
if (['http:', 'https:'].includes(parsed.protocol) && !parsed.username && !parsed.password)
|
|
return parsed.toString()
|
|
} catch {
|
|
// Non-URL website datasources use a stable synthetic URI.
|
|
}
|
|
}
|
|
return `datasource://${encodeURIComponent(fallback)}`
|
|
}
|
|
|
|
export function CreateSourceSetup({
|
|
disabled,
|
|
draft,
|
|
onDraftChange,
|
|
onInitialSourceChange,
|
|
onSourceTypeChange,
|
|
}: {
|
|
disabled: boolean
|
|
draft: NewKnowledgeSourceDraft
|
|
onDraftChange: (draft: NewKnowledgeSourceDraft) => void
|
|
onInitialSourceChange: (source?: InitialSource) => void
|
|
onSourceTypeChange: (sourceType: NewKnowledgeSourceDraft['sourceType']) => void
|
|
}) {
|
|
const { t } = useTranslation('dataset')
|
|
const datasourcePluginsQuery = useDataSourceList(true)
|
|
const datasourceAuthQuery = useGetDataSourceListAuth()
|
|
const [crawlState, setCrawlState] = useState<LocalCrawlState>('idle')
|
|
const [stoppingPreview, setStoppingPreview] = useState(false)
|
|
const [previewPages, setPreviewPages] = useState<CrawlPreviewPage[]>([])
|
|
const [selectedPageIds, setSelectedPageIds] = useState<Set<string>>(() => new Set())
|
|
const crawlAttemptRef = useRef(0)
|
|
const previewJobIdRef = useRef<string | undefined>(undefined)
|
|
const sourceType = draft.sourceType
|
|
const providerOptions = useMemo(
|
|
() => discoverSourceProviderOptions(sourceType, datasourcePluginsQuery.data ?? []),
|
|
[datasourcePluginsQuery.data, sourceType],
|
|
)
|
|
const providerOption = sourceProviderOptionForDraft(providerOptions, draft)
|
|
const installedProviderOption = providerOption?.installed ? providerOption : undefined
|
|
const datasourceAuth = installedProviderOption
|
|
? datasourceAuthForProvider(
|
|
datasourceAuthQuery.data?.result ?? [],
|
|
installedProviderOption.plugin.plugin_id,
|
|
installedProviderOption.plugin.provider,
|
|
)
|
|
: undefined
|
|
const credential = preferredCredential(datasourceAuth)
|
|
const parameterSchemas = useMemo(
|
|
() =>
|
|
draft.sourceType === 'websiteCrawl' && installedProviderOption
|
|
? websiteDatasourceParameterSchemas(installedProviderOption.datasource)
|
|
: [],
|
|
[draft.sourceType, installedProviderOption],
|
|
)
|
|
const parameters = useMemo(() => {
|
|
const current = withDatasourceParameterDefaults(parameterSchemas, draft.parameters)
|
|
if (
|
|
draft.sourceType === 'websiteCrawl' &&
|
|
draft.rootUrl &&
|
|
parameterSchemas.some((parameter) => parameter.name === 'url') &&
|
|
current.url === undefined
|
|
)
|
|
current.url = draft.rootUrl
|
|
return current
|
|
}, [draft, parameterSchemas])
|
|
const parametersValid =
|
|
!missingRequiredDatasourceParameters(parameterSchemas, parameters).length &&
|
|
!invalidDatasourceParameters(parameterSchemas, parameters).length
|
|
const selectionPages = previewPages
|
|
const previewReady = Boolean(
|
|
draft.sourceType === 'websiteCrawl' &&
|
|
credential &&
|
|
installedProviderOption &&
|
|
parametersValid &&
|
|
draft.sourceName.trim(),
|
|
)
|
|
const sourceUri = installedProviderOption
|
|
? websiteSourceUri(parameters, installedProviderOption.key)
|
|
: ''
|
|
const selectionRootUrl =
|
|
typeof parameters.url === 'string' && sourceUri.startsWith('http') ? sourceUri : undefined
|
|
const cancelPreviewBestEffort = () => {
|
|
crawlAttemptRef.current += 1
|
|
const jobId = previewJobIdRef.current
|
|
previewJobIdRef.current = undefined
|
|
if (jobId)
|
|
void consoleClient.knowledgeFs.sourceProviderPreview.jobs.byJobId
|
|
.delete({
|
|
params: { job_id: jobId },
|
|
})
|
|
.catch(() => {})
|
|
}
|
|
const stopPreview = async () => {
|
|
if (stoppingPreview) return
|
|
crawlAttemptRef.current += 1
|
|
const jobId = previewJobIdRef.current
|
|
if (!jobId) {
|
|
setCrawlState('stopped')
|
|
return
|
|
}
|
|
setStoppingPreview(true)
|
|
try {
|
|
const response = await consoleClient.knowledgeFs.sourceProviderPreview.jobs.byJobId.delete({
|
|
params: { job_id: jobId },
|
|
})
|
|
if (response.status === 'completed' && response.result) {
|
|
if (previewJobIdRef.current === jobId) previewJobIdRef.current = undefined
|
|
setPreviewPages(
|
|
(response.result.pages ?? []).map((page) => ({
|
|
description: page.description ?? undefined,
|
|
pageId: page.source_url,
|
|
sourceUrl: page.source_url,
|
|
title: page.title ?? page.source_url,
|
|
})),
|
|
)
|
|
setCrawlState('success')
|
|
} else if (response.status === 'canceled') {
|
|
if (previewJobIdRef.current === jobId) previewJobIdRef.current = undefined
|
|
setCrawlState('stopped')
|
|
} else if (response.status === 'failed') {
|
|
if (previewJobIdRef.current === jobId) previewJobIdRef.current = undefined
|
|
setCrawlState('error')
|
|
} else {
|
|
setCrawlState('running')
|
|
}
|
|
} catch {
|
|
// Keep the job handle and running state so Stop can be retried and unmount
|
|
// cleanup can still revoke a task whose cancellation response was lost.
|
|
setCrawlState('running')
|
|
} finally {
|
|
setStoppingPreview(false)
|
|
}
|
|
}
|
|
const resetPreview = () => {
|
|
cancelPreviewBestEffort()
|
|
setCrawlState('idle')
|
|
setPreviewPages([])
|
|
setSelectedPageIds(new Set())
|
|
onInitialSourceChange(undefined)
|
|
}
|
|
const updateDraft = (nextDraft: NewKnowledgeSourceDraft) => {
|
|
onDraftChange(nextDraft)
|
|
resetPreview()
|
|
}
|
|
const updateDraftWithoutReset = (nextDraft: NewKnowledgeSourceDraft) => {
|
|
onDraftChange(nextDraft)
|
|
}
|
|
const selectProvider = (providerKey: string) => {
|
|
const nextProvider = providerOptions.find((option) => option.key === providerKey)
|
|
if (!nextProvider) return
|
|
updateDraft({
|
|
...draft,
|
|
parameters: nextProvider.installed
|
|
? datasourceParameterDefaults(
|
|
draft.sourceType === 'websiteCrawl'
|
|
? websiteDatasourceParameterSchemas(nextProvider.datasource)
|
|
: datasourceParameterSchemas(nextProvider.datasource),
|
|
)
|
|
: {},
|
|
provider: nextProvider.label,
|
|
providerKey: nextProvider.key,
|
|
sourceName: '',
|
|
...(draft.sourceType === 'websiteCrawl' ? { rootUrl: '' } : {}),
|
|
...(draft.sourceType === 'onlineDrive' &&
|
|
nextProvider.label === 'Amazon S3' &&
|
|
draft.syncPolicy === 'provider'
|
|
? { syncPolicy: 'daily' as const }
|
|
: {}),
|
|
})
|
|
}
|
|
|
|
useEffect(
|
|
() => () => {
|
|
crawlAttemptRef.current += 1
|
|
const jobId = previewJobIdRef.current
|
|
if (jobId)
|
|
void consoleClient.knowledgeFs.sourceProviderPreview.jobs.byJobId
|
|
.delete({
|
|
params: { job_id: jobId },
|
|
})
|
|
.catch(() => {})
|
|
},
|
|
[],
|
|
)
|
|
|
|
const startPreview = async () => {
|
|
if (
|
|
draft.sourceType !== 'websiteCrawl' ||
|
|
!previewReady ||
|
|
!credential ||
|
|
!installedProviderOption
|
|
)
|
|
return
|
|
const attempt = crawlAttemptRef.current + 1
|
|
crawlAttemptRef.current = attempt
|
|
setPreviewPages([])
|
|
setSelectedPageIds(new Set())
|
|
setCrawlState('running')
|
|
try {
|
|
const job = await consoleClient.knowledgeFs.sourceProviderPreview.jobs.post({
|
|
body: {
|
|
credentialId: credential.id,
|
|
datasource: installedProviderOption.datasource.identity.name,
|
|
kind: 'website_crawl',
|
|
parameters,
|
|
pluginId: installedProviderOption.plugin.plugin_id,
|
|
provider: installedProviderOption.plugin.provider,
|
|
providerDisplayName: installedProviderOption.label,
|
|
},
|
|
})
|
|
if (crawlAttemptRef.current !== attempt) {
|
|
void consoleClient.knowledgeFs.sourceProviderPreview.jobs.byJobId
|
|
.delete({
|
|
params: { job_id: job.job_id },
|
|
})
|
|
.catch(() => {})
|
|
return
|
|
}
|
|
previewJobIdRef.current = job.job_id
|
|
let response = await consoleClient.knowledgeFs.sourceProviderPreview.jobs.byJobId.get({
|
|
params: { job_id: job.job_id },
|
|
})
|
|
while (
|
|
crawlAttemptRef.current === attempt &&
|
|
!['completed', 'failed', 'canceled'].includes(response.status)
|
|
) {
|
|
await new Promise((resolve) => globalThis.setTimeout(resolve, CRAWL_POLL_INTERVAL_MS))
|
|
if (crawlAttemptRef.current !== attempt) return
|
|
response = await consoleClient.knowledgeFs.sourceProviderPreview.jobs.byJobId.get({
|
|
params: { job_id: job.job_id },
|
|
})
|
|
}
|
|
if (crawlAttemptRef.current !== attempt) return
|
|
previewJobIdRef.current = undefined
|
|
if (response.status !== 'completed' || !response.result) {
|
|
setCrawlState(response.status === 'canceled' ? 'stopped' : 'error')
|
|
return
|
|
}
|
|
setPreviewPages(
|
|
(response.result.pages ?? []).map((page) => ({
|
|
description: page.description ?? undefined,
|
|
pageId: page.source_url,
|
|
sourceUrl: page.source_url,
|
|
title: page.title ?? page.source_url,
|
|
})),
|
|
)
|
|
setCrawlState('success')
|
|
} catch {
|
|
if (crawlAttemptRef.current === attempt) {
|
|
const jobId = previewJobIdRef.current
|
|
previewJobIdRef.current = undefined
|
|
if (jobId)
|
|
void consoleClient.knowledgeFs.sourceProviderPreview.jobs.byJobId
|
|
.delete({
|
|
params: { job_id: jobId },
|
|
})
|
|
.catch(() => {})
|
|
setCrawlState('error')
|
|
}
|
|
}
|
|
}
|
|
|
|
const updateSelectedPageIds = (pageIds: Set<string>) => {
|
|
setSelectedPageIds(pageIds)
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (draft.sourceType !== 'websiteCrawl') {
|
|
if (!installedProviderOption || !credential) onInitialSourceChange(undefined)
|
|
return
|
|
}
|
|
if (
|
|
crawlState !== 'success' ||
|
|
!selectionPages.length ||
|
|
!installedProviderOption ||
|
|
!credential
|
|
) {
|
|
onInitialSourceChange(undefined)
|
|
return
|
|
}
|
|
const selectedPages = selectionPages.filter((page) => selectedPageIds.has(page.pageId))
|
|
if (!selectedPages.length) {
|
|
onInitialSourceChange(undefined)
|
|
return
|
|
}
|
|
onInitialSourceChange({
|
|
crawl_options: {
|
|
include_subpages: datasourceIncludeSubpages(parameters),
|
|
limit: typeof parameters.limit === 'number' ? parameters.limit : 200,
|
|
},
|
|
credentialId: credential.id,
|
|
datasource: installedProviderOption.datasource.identity.name,
|
|
kind: 'website_crawl',
|
|
name: draft.sourceName.trim(),
|
|
pluginId: installedProviderOption.plugin.plugin_id,
|
|
provider: installedProviderOption.plugin.provider,
|
|
providerDisplayName: installedProviderOption.label,
|
|
parameters,
|
|
root_url: sourceUri,
|
|
selection: selectedPages.map((page) => ({
|
|
source_url: page.sourceUrl,
|
|
...(page.title ? { title: page.title } : {}),
|
|
})),
|
|
...(draft.syncPolicy === 'custom' && draft.customIntervalSeconds
|
|
? { custom_interval_seconds: draft.customIntervalSeconds }
|
|
: {}),
|
|
sync_policy: draft.syncPolicy,
|
|
})
|
|
}, [
|
|
crawlState,
|
|
credential,
|
|
draft,
|
|
installedProviderOption,
|
|
onInitialSourceChange,
|
|
parameters,
|
|
selectedPageIds,
|
|
selectionPages,
|
|
sourceUri,
|
|
])
|
|
|
|
return (
|
|
<div className="mx-4 -mt-1 mb-3.75 flex flex-col gap-4">
|
|
<SourceTypeSelector
|
|
appearance="embedded"
|
|
disabled={disabled}
|
|
value={sourceType}
|
|
onChange={(value) => {
|
|
onSourceTypeChange(value)
|
|
}}
|
|
/>
|
|
|
|
<Fieldset disabled={disabled}>
|
|
<FieldsetLegend className="sr-only">
|
|
{t(($) => $['newKnowledge.providerLabel'])}
|
|
</FieldsetLegend>
|
|
<div className="mb-1.5 flex items-center justify-between gap-3">
|
|
<span className="system-xs-medium text-text-secondary">
|
|
{t(($) => $['newKnowledge.providerLabel'])}
|
|
</span>
|
|
<Button
|
|
type="button"
|
|
variant="ghost-accent"
|
|
size="small"
|
|
disabled={disabled}
|
|
className="gap-0.5 px-2.75"
|
|
onClick={() =>
|
|
globalThis.open(buildIntegrationPath('data-source'), '_blank', 'noopener,noreferrer')
|
|
}
|
|
>
|
|
{t(($) => $['newKnowledge.moreProviders'])}
|
|
<span aria-hidden className="i-ri-arrow-right-up-line size-3.5" />
|
|
</Button>
|
|
</div>
|
|
<SourceProviderRadioGroup
|
|
value={providerOption?.key ?? ''}
|
|
disabled={disabled}
|
|
layout={sourceType === 'websiteCrawl' ? 'grid-four' : 'grid-three'}
|
|
options={providerOptions.map((option) => ({
|
|
icon: <SourceProviderIcon fallbackIcon={option.fallbackIcon} />,
|
|
label: option.label,
|
|
value: option.key,
|
|
}))}
|
|
surface="default"
|
|
onChange={selectProvider}
|
|
/>
|
|
</Fieldset>
|
|
|
|
{datasourcePluginsQuery.isPending || datasourceAuthQuery.isPending ? (
|
|
<div className="flex min-h-44 items-center justify-center">
|
|
<span aria-hidden className="i-ri-loader-4-line size-5 animate-spin text-text-tertiary" />
|
|
</div>
|
|
) : datasourcePluginsQuery.error || datasourceAuthQuery.error ? (
|
|
<div className="rounded-xl bg-background-section p-4">
|
|
<p className="system-sm-semibold text-text-primary">
|
|
{t(($) => $['newKnowledge.providerLoadFailed'])}
|
|
</p>
|
|
<Button
|
|
className="mt-3"
|
|
onClick={() =>
|
|
void Promise.all([datasourcePluginsQuery.refetch(), datasourceAuthQuery.refetch()])
|
|
}
|
|
>
|
|
{t(($) => $['newKnowledge.retryProviderLoad'])}
|
|
</Button>
|
|
</div>
|
|
) : providerOption && !providerOption.installed ? (
|
|
<SourceProviderNotInstalledCard
|
|
icon={<SourceProviderIcon fallbackIcon={providerOption.fallbackIcon} />}
|
|
provider={providerOption.label}
|
|
onInstall={() =>
|
|
globalThis.open(
|
|
providerIntegrationPath(providerOption.packageId),
|
|
'_blank',
|
|
'noopener,noreferrer',
|
|
)
|
|
}
|
|
/>
|
|
) : installedProviderOption && !credential ? (
|
|
<SourceProviderCredentialRequiredCard
|
|
disabled={disabled}
|
|
icon={<SourceProviderIcon fallbackIcon={installedProviderOption.fallbackIcon} />}
|
|
provider={installedProviderOption.label}
|
|
onConnect={() =>
|
|
globalThis.open(
|
|
providerIntegrationPath(installedProviderOption.packageId),
|
|
'_blank',
|
|
'noopener,noreferrer',
|
|
)
|
|
}
|
|
/>
|
|
) : draft.sourceType === 'websiteCrawl' && installedProviderOption && credential ? (
|
|
<div className="space-y-4">
|
|
<WebsiteDatasourceParameterForm
|
|
additionalPrimaryField={
|
|
<SourceNameField
|
|
disabled={disabled}
|
|
draft={draft}
|
|
preventSubmitOnEnter
|
|
onDraftChange={updateDraft}
|
|
/>
|
|
}
|
|
disabled={disabled || crawlState === 'running'}
|
|
parameters={parameters}
|
|
schemas={parameterSchemas}
|
|
onChange={(nextParameters) =>
|
|
updateDraft({
|
|
...draft,
|
|
parameters: nextParameters,
|
|
rootUrl: typeof nextParameters.url === 'string' ? nextParameters.url : '',
|
|
})
|
|
}
|
|
/>
|
|
{crawlState !== 'success' && (
|
|
<Button
|
|
type="button"
|
|
variant="primary"
|
|
className="w-full"
|
|
disabled={disabled || crawlState === 'running' || !previewReady}
|
|
onClick={() => void startPreview()}
|
|
>
|
|
{crawlState === 'running'
|
|
? t(($) => $['newKnowledge.crawling'])
|
|
: t(($) => $['newKnowledge.crawlAndPreview'])}
|
|
</Button>
|
|
)}
|
|
<section
|
|
aria-label={t(($) => $['newKnowledge.crawlPreview'])}
|
|
className={cn(
|
|
crawlState === 'running'
|
|
? 'flex h-60 flex-col gap-3.5 overflow-hidden rounded-xl border border-divider-deep bg-background-default-subtle p-4'
|
|
: crawlState === 'success'
|
|
? ''
|
|
: 'min-h-40 rounded-lg border border-dashed border-divider-regular px-4 py-4',
|
|
)}
|
|
>
|
|
{crawlState === 'idle' && (
|
|
<div className="flex min-h-32 flex-col items-center justify-center text-center">
|
|
<span className="flex size-10 items-center justify-center rounded-lg bg-background-section">
|
|
<span aria-hidden className="i-ri-global-line size-5 text-text-tertiary" />
|
|
</span>
|
|
<p className="mt-2 system-xs-semibold text-text-primary">
|
|
{t(($) => $['newKnowledge.pagesAppearTitle'])}
|
|
</p>
|
|
<p className="mt-2 system-xs-regular text-text-tertiary">
|
|
{t(($) => $['newKnowledge.pagesAppearDescription'])}
|
|
</p>
|
|
</div>
|
|
)}
|
|
{crawlState === 'running' && (
|
|
<div className="flex h-6 shrink-0 items-center gap-2">
|
|
<span
|
|
aria-hidden
|
|
className="i-ri-loader-4-line size-4 animate-spin text-text-accent"
|
|
/>
|
|
<p
|
|
role="status"
|
|
aria-live="polite"
|
|
className="min-w-0 flex-1 truncate system-xs-medium text-text-primary"
|
|
>
|
|
{t(($) => $['newKnowledge.crawlingPages'], {
|
|
count: previewPages.length,
|
|
host: installedProviderOption.label,
|
|
})}
|
|
</p>
|
|
<Button
|
|
type="button"
|
|
variant="ghost-accent"
|
|
size="small"
|
|
className="ml-auto shrink-0"
|
|
disabled={stoppingPreview}
|
|
loading={stoppingPreview}
|
|
onClick={() => void stopPreview()}
|
|
>
|
|
{t(($) => $['newKnowledge.stopCrawl'])}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
{crawlState === 'running' &&
|
|
CRAWL_PREVIEW_SKELETONS.map(({ id, sourceWidth, titleWidth }) => (
|
|
<div key={id} aria-hidden className="flex h-8 shrink-0 items-center gap-2.5 py-1">
|
|
<span className="size-4 animate-pulse rounded bg-util-colors-gray-gray-200 motion-reduce:animate-none" />
|
|
<span className="flex min-w-0 flex-1 flex-col gap-1.5">
|
|
<span
|
|
className={cn(
|
|
'block h-2.5 animate-pulse rounded bg-util-colors-gray-gray-200 motion-reduce:animate-none',
|
|
titleWidth,
|
|
)}
|
|
/>
|
|
<span
|
|
className={cn(
|
|
'block h-2 animate-pulse rounded bg-background-section-burn motion-reduce:animate-none',
|
|
sourceWidth,
|
|
)}
|
|
/>
|
|
</span>
|
|
</div>
|
|
))}
|
|
{crawlState === 'error' && (
|
|
<p role="alert" className="system-xs-regular text-text-destructive">
|
|
{t(($) => $['newKnowledge.crawlStartFailed'])}
|
|
</p>
|
|
)}
|
|
{crawlState === 'stopped' && (
|
|
<p role="status" className="system-xs-regular text-text-secondary">
|
|
{t(($) => $['newKnowledge.crawlStopped'])}
|
|
</p>
|
|
)}
|
|
{crawlState === 'success' && (
|
|
<CrawlPreviewPageSelection
|
|
disabled={disabled}
|
|
onRecrawl={() => void startPreview()}
|
|
onSelectionChange={updateSelectedPageIds}
|
|
pages={selectionPages}
|
|
rootUrl={selectionRootUrl}
|
|
selectedPageIds={selectedPageIds}
|
|
sourceLabel={installedProviderOption.label}
|
|
/>
|
|
)}
|
|
</section>
|
|
<SourceSyncPolicyField
|
|
className="w-full sm:w-75.25"
|
|
disabled={disabled}
|
|
draft={draft}
|
|
onDraftChange={updateDraftWithoutReset}
|
|
size="medium"
|
|
/>
|
|
</div>
|
|
) : draft.sourceType !== 'websiteCrawl' && installedProviderOption && credential ? (
|
|
<CreateConnectedSourceSetup
|
|
key={`${draft.sourceType}:${installedProviderOption.key}:${credential.id}`}
|
|
credential={credential}
|
|
disabled={disabled}
|
|
draft={draft}
|
|
providerOption={installedProviderOption}
|
|
onDraftChange={updateDraftWithoutReset}
|
|
onInitialSourceChange={onInitialSourceChange}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|