mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 02:43:49 +08:00
refactor(knowledge-fs): remove new rag edit guards
This commit is contained in:
parent
dbcc3a11e5
commit
e9180d38e5
@ -11,6 +11,6 @@ This feature owns the KnowledgeFS-backed knowledge list, creation flows, sources
|
||||
- Shared KnowledgeFS read contracts used by other features live under `service/knowledge-fs/`; feature-specific editing policy remains here.
|
||||
- Document query modules own server-state configuration. View components consume those options and coordinate only workflows that require one consistent page snapshot.
|
||||
- Document processing events live with `documents/tasks/`; retrieval event streams live with `retrieval/services/`.
|
||||
- Exit confirmation, creation, and processing overlays are feature compositions of Dify UI Dialog, AlertDialog, Drawer, and Popover primitives.
|
||||
- Creation and processing overlays are feature compositions of Dify UI Dialog, AlertDialog, Drawer, and Popover primitives.
|
||||
|
||||
Files in this directory remain feature-owned; direct consumers do not become their owners. Keep shared dataset APIs and permission policy in their existing owners rather than copying them into this feature.
|
||||
|
||||
@ -2423,9 +2423,8 @@ describe('CreateKnowledgePage', () => {
|
||||
expect(routerMock.replace).toHaveBeenCalledWith('/datasets?view=new')
|
||||
})
|
||||
|
||||
it('asks before discarding an unsaved draft', async () => {
|
||||
it('closes an unsaved draft without confirmation', async () => {
|
||||
const user = userEvent.setup()
|
||||
const historyBack = vi.spyOn(window.history, 'back').mockImplementation(() => undefined)
|
||||
renderPage()
|
||||
await user.type(
|
||||
screen.getByRole('textbox', { name: 'dataset.newKnowledge.name' }),
|
||||
@ -2434,24 +2433,11 @@ describe('CreateKnowledgePage', () => {
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.close' }))
|
||||
|
||||
expect(routerMock.back).not.toHaveBeenCalled()
|
||||
const confirmation = await screen.findByRole('alertdialog', {
|
||||
name: 'dataset.newKnowledge.discardDraftTitle',
|
||||
})
|
||||
expect(confirmation).toHaveTextContent('dataset.newKnowledge.discardDraftDescription')
|
||||
await user.click(
|
||||
within(confirmation).getByRole('button', {
|
||||
name: 'dataset.newKnowledge.discardDraftConfirm',
|
||||
}),
|
||||
)
|
||||
expect(historyBack).toHaveBeenCalledOnce()
|
||||
|
||||
act(() => window.dispatchEvent(new PopStateEvent('popstate')))
|
||||
|
||||
expect(routerMock.replace).toHaveBeenCalledWith('/datasets?view=new')
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('protects an unsaved draft from browser unload', async () => {
|
||||
it('does not protect an unsaved draft from browser unload', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderPage()
|
||||
await user.type(
|
||||
@ -2462,49 +2448,10 @@ describe('CreateKnowledgePage', () => {
|
||||
|
||||
act(() => window.dispatchEvent(event))
|
||||
|
||||
expect(event.defaultPrevented).toBe(true)
|
||||
expect(event.defaultPrevented).toBe(false)
|
||||
})
|
||||
|
||||
it('asks before leaving an unsaved draft with browser Back', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderPage()
|
||||
await user.type(
|
||||
screen.getByRole('textbox', { name: 'dataset.newKnowledge.name' }),
|
||||
'Draft knowledge',
|
||||
)
|
||||
|
||||
act(() => window.dispatchEvent(new PopStateEvent('popstate')))
|
||||
|
||||
const confirmation = await screen.findByRole('alertdialog', {
|
||||
name: 'dataset.newKnowledge.discardDraftTitle',
|
||||
})
|
||||
await user.click(
|
||||
within(confirmation).getByRole('button', {
|
||||
name: 'dataset.newKnowledge.discardDraftConfirm',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(routerMock.replace).toHaveBeenCalledWith('/datasets?view=new')
|
||||
})
|
||||
|
||||
it('does not warn after a draft is cleared before browser Back', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderPage()
|
||||
const nameInput = screen.getByRole('textbox', { name: 'dataset.newKnowledge.name' })
|
||||
await user.type(nameInput, 'Draft knowledge')
|
||||
await user.clear(nameInput)
|
||||
|
||||
act(() => window.dispatchEvent(new PopStateEvent('popstate')))
|
||||
|
||||
expect(
|
||||
screen.queryByRole('alertdialog', {
|
||||
name: 'dataset.newKnowledge.discardDraftTitle',
|
||||
}),
|
||||
).not.toBeInTheDocument()
|
||||
expect(routerMock.replace).toHaveBeenCalledWith('/datasets?view=new')
|
||||
})
|
||||
|
||||
it('warns before leaving a partially created knowledge space', async () => {
|
||||
it('leaves a partially created knowledge space without confirmation', async () => {
|
||||
const user = userEvent.setup()
|
||||
navigationMock.startMode = 'upload'
|
||||
serviceMock.upload.mockRejectedValueOnce(new Error('upload unavailable'))
|
||||
@ -2522,18 +2469,9 @@ describe('CreateKnowledgePage', () => {
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
expect(routerMock.back).not.toHaveBeenCalled()
|
||||
const confirmation = await screen.findByRole('alertdialog', {
|
||||
name: 'dataset.newKnowledge.leavePartialSetupTitle',
|
||||
})
|
||||
expect(confirmation).toHaveTextContent('dataset.newKnowledge.leavePartialSetupDescription')
|
||||
await user.click(
|
||||
within(confirmation).getByRole('button', {
|
||||
name: 'dataset.newKnowledge.leavePartialSetupConfirm',
|
||||
}),
|
||||
)
|
||||
expect(routerMock.replace).toHaveBeenCalledWith(
|
||||
'/datasets/new/e735c1dc-d2b8-4dc4-86dc-abaf2fb7d084/sources',
|
||||
)
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export type CreateKnowledgeExitReason = 'discard' | 'partial'
|
||||
|
||||
export function CreateKnowledgeExitDialog({
|
||||
onCancel,
|
||||
onConfirm,
|
||||
reason,
|
||||
}: {
|
||||
onCancel: () => void
|
||||
onConfirm: () => void
|
||||
reason: CreateKnowledgeExitReason | null
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const isPartial = reason === 'partial'
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
open={reason !== null}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) onCancel()
|
||||
}}
|
||||
>
|
||||
<AlertDialogContent className="w-120 overflow-hidden! rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0! text-left align-middle shadow-lg">
|
||||
<div className="flex flex-col items-start gap-2 self-stretch p-6 pb-4">
|
||||
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(($) =>
|
||||
isPartial
|
||||
? $['newKnowledge.leavePartialSetupTitle']
|
||||
: $['newKnowledge.discardDraftTitle'],
|
||||
)}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription
|
||||
render={<div />}
|
||||
className="system-md-regular text-text-secondary"
|
||||
>
|
||||
{t(($) =>
|
||||
isPartial
|
||||
? $['newKnowledge.leavePartialSetupDescription']
|
||||
: $['newKnowledge.discardDraftDescription'],
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions className="gap-2 p-6">
|
||||
<AlertDialogCancelButton variant="secondary">
|
||||
{tCommon(($) => $['operation.cancel'])}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton onClick={onConfirm}>
|
||||
{t(($) =>
|
||||
isPartial
|
||||
? $['newKnowledge.leavePartialSetupConfirm']
|
||||
: $['newKnowledge.discardDraftConfirm'],
|
||||
)}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
@ -10,7 +10,6 @@ import type {
|
||||
KnowledgeFsUploadPhase,
|
||||
KnowledgeFsUploadProgress,
|
||||
} from '../upload/knowledge-fs-upload'
|
||||
import type { CreateKnowledgeExitReason } from './components/exit-dialog'
|
||||
import type { QueuedUpload } from './upload-queue'
|
||||
import type { KnowledgeVisibility } from './workflow'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
@ -38,7 +37,7 @@ import {
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useEffect, useId, useRef, useState } from 'react'
|
||||
import { useCallback, useId, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { datasetDefaultPermissionKeysAtom } from '@/context/permission-state'
|
||||
import { knowledgeFsUploadEnabledAtom, rbacEnabledAtom } from '@/features/system-features/state'
|
||||
@ -62,7 +61,6 @@ import {
|
||||
} from '../upload/knowledge-fs-upload'
|
||||
import { useKnowledgeFileSizeLimit } from '../upload/use-file-size-limit'
|
||||
import { KnowledgeIllustration, StartMode } from './components/dialog-parts'
|
||||
import { CreateKnowledgeExitDialog } from './components/exit-dialog'
|
||||
import { CreateSourceSetup } from './source-setup'
|
||||
import { CreateUploadQueue } from './upload-queue'
|
||||
import {
|
||||
@ -122,14 +120,9 @@ export function CreateKnowledgePage() {
|
||||
() => new Map(),
|
||||
)
|
||||
const [uploadError, setUploadError] = useState(false)
|
||||
const [exitReason, setExitReason] = useState<CreateKnowledgeExitReason | null>(null)
|
||||
const idempotencyKeyRef = useRef<string | undefined>(undefined)
|
||||
const uploadsRef = useRef<QueuedUpload[]>([])
|
||||
const uploadProgressRef = useRef<KnowledgeFsUploadProgress>(new Map())
|
||||
const historyGuardArmedRef = useRef(false)
|
||||
const browserBackExitRef = useRef(false)
|
||||
const pendingNavigationRef = useRef<string | undefined>(undefined)
|
||||
const navigationFallbackRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
||||
const createMutation = useMutation({ mutationFn: createKnowledge })
|
||||
const submissionPending = createMutation.isPending || uploading || stagingCount > 0
|
||||
const validUploads = uploads.filter(({ issue }) => !issue)
|
||||
@ -144,106 +137,11 @@ export function CreateKnowledgePage() {
|
||||
!validUploads.length ||
|
||||
validUploads.some((upload) => upload.stagingFailed || !upload.stagedUploadId))
|
||||
const sourceSubmissionBlocked = startMode === 'source' && !initialSource
|
||||
const sourceDraftChanged =
|
||||
JSON.stringify(sourceDraft) !==
|
||||
JSON.stringify(createNewKnowledgeSourceDraft(sourceDraft.sourceType))
|
||||
const hasUnsavedChanges = Boolean(
|
||||
name ||
|
||||
description ||
|
||||
visibility !== defaultVisibility ||
|
||||
startMode !== initialStartMode ||
|
||||
sourceDraftChanged ||
|
||||
uploads.length ||
|
||||
createdKnowledge,
|
||||
)
|
||||
|
||||
const updateInitialSource = useCallback((source?: InitialSource) => {
|
||||
initialSourceRef.current = source
|
||||
setInitialSource(source)
|
||||
}, [])
|
||||
|
||||
const armHistoryGuard = useCallback(() => {
|
||||
globalThis.history.pushState(globalThis.history.state, '', globalThis.location.href)
|
||||
historyGuardArmedRef.current = true
|
||||
}, [])
|
||||
|
||||
const replaceAfterHistoryGuard = useCallback(
|
||||
(path: string) => {
|
||||
if (!historyGuardArmedRef.current) {
|
||||
router.replace(path)
|
||||
return
|
||||
}
|
||||
|
||||
pendingNavigationRef.current = path
|
||||
globalThis.history.back()
|
||||
globalThis.clearTimeout(navigationFallbackRef.current)
|
||||
navigationFallbackRef.current = globalThis.setTimeout(() => {
|
||||
if (pendingNavigationRef.current !== path) return
|
||||
pendingNavigationRef.current = undefined
|
||||
historyGuardArmedRef.current = false
|
||||
router.replace(path)
|
||||
}, 1000)
|
||||
},
|
||||
[router],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!hasUnsavedChanges ||
|
||||
historyGuardArmedRef.current ||
|
||||
browserBackExitRef.current ||
|
||||
pendingNavigationRef.current
|
||||
)
|
||||
return
|
||||
|
||||
armHistoryGuard()
|
||||
}, [armHistoryGuard, hasUnsavedChanges])
|
||||
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
if (!historyGuardArmedRef.current) return
|
||||
|
||||
historyGuardArmedRef.current = false
|
||||
const pendingNavigation = pendingNavigationRef.current
|
||||
if (pendingNavigation) {
|
||||
globalThis.clearTimeout(navigationFallbackRef.current)
|
||||
navigationFallbackRef.current = undefined
|
||||
pendingNavigationRef.current = undefined
|
||||
router.replace(pendingNavigation)
|
||||
return
|
||||
}
|
||||
if (!hasUnsavedChanges) {
|
||||
router.replace(newKnowledgeListPath)
|
||||
return
|
||||
}
|
||||
|
||||
browserBackExitRef.current = true
|
||||
setExitReason(createdKnowledge ? 'partial' : 'discard')
|
||||
}
|
||||
|
||||
globalThis.addEventListener('popstate', handlePopState)
|
||||
return () => globalThis.removeEventListener('popstate', handlePopState)
|
||||
}, [createdKnowledge, hasUnsavedChanges, router])
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
globalThis.clearTimeout(navigationFallbackRef.current)
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasUnsavedChanges) return
|
||||
|
||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
}
|
||||
|
||||
globalThis.addEventListener('beforeunload', handleBeforeUnload)
|
||||
return () => globalThis.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
}, [hasUnsavedChanges])
|
||||
|
||||
const resetUnsubmittedError = () => {
|
||||
if (!submissionLocked) createMutation.reset()
|
||||
setUploadError(false)
|
||||
@ -307,39 +205,15 @@ export function CreateKnowledgePage() {
|
||||
|
||||
const requestClose = () => {
|
||||
if (submissionPending) return
|
||||
if (createdKnowledge) {
|
||||
setExitReason('partial')
|
||||
return
|
||||
}
|
||||
if (hasUnsavedChanges) {
|
||||
setExitReason('discard')
|
||||
return
|
||||
}
|
||||
replaceAfterHistoryGuard(newKnowledgeListPath)
|
||||
}
|
||||
|
||||
const confirmExit = () => {
|
||||
const confirmedReason = exitReason
|
||||
setExitReason(null)
|
||||
for (const upload of uploadsRef.current) {
|
||||
if (upload.stagedUploadId)
|
||||
void discardKnowledgeFsStagedUpload(upload.stagedUploadId).catch(() => undefined)
|
||||
}
|
||||
if (confirmedReason === 'partial' && createdKnowledge) {
|
||||
browserBackExitRef.current = false
|
||||
replaceAfterHistoryGuard(newKnowledgeDetailPath(createdKnowledge.control_space_id))
|
||||
return
|
||||
}
|
||||
browserBackExitRef.current = false
|
||||
replaceAfterHistoryGuard(newKnowledgeListPath)
|
||||
}
|
||||
|
||||
const cancelExit = () => {
|
||||
setExitReason(null)
|
||||
if (!browserBackExitRef.current) return
|
||||
|
||||
browserBackExitRef.current = false
|
||||
armHistoryGuard()
|
||||
router.replace(
|
||||
createdKnowledge
|
||||
? newKnowledgeDetailPath(createdKnowledge.control_space_id)
|
||||
: newKnowledgeListPath,
|
||||
)
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
@ -401,7 +275,7 @@ export function CreateKnowledgePage() {
|
||||
}
|
||||
}
|
||||
|
||||
replaceAfterHistoryGuard(
|
||||
router.replace(
|
||||
startMode === 'upload'
|
||||
? newKnowledgeDocumentsPath(created.control_space_id)
|
||||
: `${newKnowledgeDetailPath(created.control_space_id)}${
|
||||
@ -693,18 +567,13 @@ export function CreateKnowledgePage() {
|
||||
</aside>
|
||||
</DialogPopup>
|
||||
</DialogPortal>
|
||||
<CreateKnowledgeExitDialog
|
||||
reason={exitReason}
|
||||
onCancel={cancelExit}
|
||||
onConfirm={confirmExit}
|
||||
/>
|
||||
<KnowledgeModelSetupDialog
|
||||
open={modelSetupDialogOpen}
|
||||
onOpenChange={setModelSetupDialogOpen}
|
||||
onConfigure={() => {
|
||||
setModelSetupDialogOpen(false)
|
||||
if (createdKnowledge)
|
||||
replaceAfterHistoryGuard(newKnowledgeSettingsPath(createdKnowledge.control_space_id))
|
||||
router.replace(newKnowledgeSettingsPath(createdKnowledge.control_space_id))
|
||||
}}
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
@ -1705,76 +1705,7 @@ describe('KnowledgeSettingsPage workflows', () => {
|
||||
expect(screen.getByRole('alert')).toHaveTextContent('datasetSettings.form.embeddingModel')
|
||||
})
|
||||
|
||||
it('asks before following a link while the form has unsaved changes', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderForm()
|
||||
const destination = document.createElement('a')
|
||||
destination.href = '/datasets/new/space-1/sources'
|
||||
destination.textContent = 'Go to sources'
|
||||
document.body.append(destination)
|
||||
|
||||
const nameInput = screen.getByRole('textbox', { name: 'datasetSettings.form.name' })
|
||||
await user.clear(nameInput)
|
||||
await user.type(nameInput, 'Unsaved camera specs')
|
||||
await user.click(destination)
|
||||
|
||||
const dialog = await screen.findByRole('alertdialog')
|
||||
expect(routerMock.push).not.toHaveBeenCalled()
|
||||
await user.click(
|
||||
within(dialog).getByRole('button', {
|
||||
name: 'dataset.newKnowledge.discardDraftConfirm',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/sources')
|
||||
destination.remove()
|
||||
})
|
||||
|
||||
it('protects a delayed retrieval save from immediate navigation', async () => {
|
||||
renderForm()
|
||||
const destination = document.createElement('a')
|
||||
destination.href = '/datasets/new/space-1/sources'
|
||||
destination.textContent = 'Go to sources'
|
||||
document.body.append(destination)
|
||||
|
||||
fireEvent.change(
|
||||
screen.getByRole('textbox', {
|
||||
name: 'dataset.newKnowledge.settings.topKLabel',
|
||||
}),
|
||||
{ target: { value: '8' } },
|
||||
)
|
||||
fireEvent.click(destination)
|
||||
|
||||
expect(await screen.findByRole('alertdialog')).toBeInTheDocument()
|
||||
expect(routerMock.push).not.toHaveBeenCalled()
|
||||
destination.remove()
|
||||
})
|
||||
|
||||
it('asks before browser back navigation while the form has unsaved changes', async () => {
|
||||
const user = userEvent.setup()
|
||||
const historyBack = vi.spyOn(globalThis.history, 'back').mockImplementation(() => {})
|
||||
try {
|
||||
renderForm()
|
||||
const nameInput = screen.getByRole('textbox', { name: 'datasetSettings.form.name' })
|
||||
await user.clear(nameInput)
|
||||
await user.type(nameInput, 'Unsaved camera specs')
|
||||
|
||||
act(() => globalThis.dispatchEvent(new PopStateEvent('popstate')))
|
||||
|
||||
const dialog = await screen.findByRole('alertdialog')
|
||||
expect(historyBack).not.toHaveBeenCalled()
|
||||
await user.click(
|
||||
within(dialog).getByRole('button', {
|
||||
name: 'dataset.newKnowledge.discardDraftConfirm',
|
||||
}),
|
||||
)
|
||||
expect(historyBack).toHaveBeenCalledOnce()
|
||||
} finally {
|
||||
historyBack.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('protects unsaved changes from browser unload', async () => {
|
||||
it('does not protect unsaved changes from browser unload', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderForm()
|
||||
|
||||
@ -1784,7 +1715,8 @@ describe('KnowledgeSettingsPage workflows', () => {
|
||||
const event = new Event('beforeunload', { cancelable: true })
|
||||
globalThis.dispatchEvent(event)
|
||||
|
||||
expect(event.defaultPrevented).toBe(true)
|
||||
expect(event.defaultPrevented).toBe(false)
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('normalizes split Knowledge FS provider identities for the model selector', () => {
|
||||
|
||||
@ -5,25 +5,25 @@ import { render } from '@/test/console/render'
|
||||
import { KnowledgeSettingsStateBoundary } from '../state/boundary'
|
||||
import { knowledgeSettingsSpaceIdAtom } from '../state/inputs'
|
||||
import {
|
||||
knowledgeSettingsHasUnsavedWorkAtom,
|
||||
startKnowledgeSettingsBasicDraftAtom,
|
||||
knowledgeSettingsHasPendingSaveAtom,
|
||||
setKnowledgeSettingsSavePendingAtom,
|
||||
} from '../state/workflow'
|
||||
|
||||
const globalProbeAtom = atom('global-before')
|
||||
|
||||
function WorkflowProbe({ name }: { name: string }) {
|
||||
const globalValue = useAtomValue(globalProbeAtom)
|
||||
const hasUnsavedWork = useAtomValue(knowledgeSettingsHasUnsavedWorkAtom)
|
||||
const hasPendingSave = useAtomValue(knowledgeSettingsHasPendingSaveAtom)
|
||||
const knowledgeSpaceId = useAtomValue(knowledgeSettingsSpaceIdAtom)
|
||||
const startDraft = useSetAtom(startKnowledgeSettingsBasicDraftAtom)
|
||||
const setSavePending = useSetAtom(setKnowledgeSettingsSavePendingAtom)
|
||||
|
||||
return (
|
||||
<section aria-label={name}>
|
||||
<span>{globalValue}</span>
|
||||
<span>{hasUnsavedWork ? 'dirty' : 'clean'}</span>
|
||||
<span>{hasPendingSave ? 'saving' : 'idle'}</span>
|
||||
<span>{knowledgeSpaceId}</span>
|
||||
<button onClick={startDraft} type="button">
|
||||
start draft
|
||||
<button onClick={() => setSavePending({ owner: 'probe', pending: true })} type="button">
|
||||
start saving
|
||||
</button>
|
||||
</section>
|
||||
)
|
||||
@ -59,9 +59,9 @@ describe('KnowledgeSettingsStateBoundary', () => {
|
||||
const first = within(screen.getByRole('region', { name: 'first settings instance' }))
|
||||
const second = within(screen.getByRole('region', { name: 'second settings instance' }))
|
||||
|
||||
await user.click(first.getByRole('button', { name: 'start draft' }))
|
||||
expect(first.getByText('dirty')).toBeInTheDocument()
|
||||
expect(second.getByText('clean')).toBeInTheDocument()
|
||||
await user.click(first.getByRole('button', { name: 'start saving' }))
|
||||
expect(first.getByText('saving')).toBeInTheDocument()
|
||||
expect(second.getByText('idle')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'update global' }))
|
||||
expect(first.getByText('global-after')).toBeInTheDocument()
|
||||
@ -81,13 +81,13 @@ describe('KnowledgeSettingsStateBoundary', () => {
|
||||
const rendered = render(tree('space-1'))
|
||||
const instance = within(screen.getByRole('region', { name: 'settings instance' }))
|
||||
|
||||
await user.click(instance.getByRole('button', { name: 'start draft' }))
|
||||
expect(instance.getByText('dirty')).toBeInTheDocument()
|
||||
await user.click(instance.getByRole('button', { name: 'start saving' }))
|
||||
expect(instance.getByText('saving')).toBeInTheDocument()
|
||||
|
||||
rendered.rerender(tree('space-2'))
|
||||
|
||||
const resetInstance = within(screen.getByRole('region', { name: 'settings instance' }))
|
||||
expect(resetInstance.getByText('clean')).toBeInTheDocument()
|
||||
expect(resetInstance.getByText('idle')).toBeInTheDocument()
|
||||
expect(resetInstance.getByText('space-2')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -31,11 +31,7 @@ import {
|
||||
knowledgeSettingsPermissionsAtom,
|
||||
knowledgeSettingsSpaceAtom,
|
||||
} from './state/queries'
|
||||
import {
|
||||
finishKnowledgeSettingsBasicDraftAtom,
|
||||
setKnowledgeSettingsSavePendingAtom,
|
||||
startKnowledgeSettingsBasicDraftAtom,
|
||||
} from './state/workflow'
|
||||
import { setKnowledgeSettingsSavePendingAtom } from './state/workflow'
|
||||
|
||||
const NAME_ERROR_ID = 'knowledge-name-error'
|
||||
const DESCRIPTION_ERROR_ID = 'knowledge-description-error'
|
||||
@ -114,8 +110,6 @@ export function BasicInformationSection() {
|
||||
const { t: tWorkflow } = useTranslation('workflow')
|
||||
const space = useAtomValue(knowledgeSettingsSpaceAtom)
|
||||
const permissions = useAtomValue(knowledgeSettingsPermissionsAtom)
|
||||
const startDraft = useSetAtom(startKnowledgeSettingsBasicDraftAtom)
|
||||
const finishDraft = useSetAtom(finishKnowledgeSettingsBasicDraftAtom)
|
||||
const setSavePending = useSetAtom(setKnowledgeSettingsSavePendingAtom)
|
||||
const invalidateSettings = useSetAtom(invalidateKnowledgeSettingsAtom)
|
||||
const membersQuery = useMembers()
|
||||
@ -195,12 +189,10 @@ export function BasicInformationSection() {
|
||||
draftRef.current = undefined
|
||||
draftBaseVersionRef.current = undefined
|
||||
setDraft(undefined)
|
||||
finishDraft()
|
||||
return
|
||||
}
|
||||
draftRef.current = next
|
||||
draftBaseVersionRef.current ??= serverVersion
|
||||
startDraft()
|
||||
setDraft(next)
|
||||
}
|
||||
|
||||
@ -209,7 +201,6 @@ export function BasicInformationSection() {
|
||||
draftBaseVersionRef.current = undefined
|
||||
setDraft(undefined)
|
||||
setNameTouched(false)
|
||||
finishDraft()
|
||||
}
|
||||
|
||||
const showSaveError = (error?: unknown) =>
|
||||
@ -276,7 +267,6 @@ export function BasicInformationSection() {
|
||||
toast.success(tCommon(($) => $['api.actionSuccess']))
|
||||
setIsRefreshing(true)
|
||||
draftBaseVersionRef.current = undefined
|
||||
finishDraft()
|
||||
void invalidateSettings().then(
|
||||
() => {
|
||||
draftRef.current = undefined
|
||||
|
||||
@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import { BasicInformationSection } from './basic-information'
|
||||
import { DeleteKnowledgeAction } from './delete-knowledge'
|
||||
import { ExternalAccessSection } from './external-access'
|
||||
import { KnowledgeSettingsNavigationGuard } from './navigation-guard'
|
||||
import { RetrievalSettingsSection } from './retrieval-settings'
|
||||
import { knowledgeSettingsSpaceAtom } from './state/queries'
|
||||
|
||||
@ -34,7 +33,6 @@ export function KnowledgeSettingsForm() {
|
||||
<RetrievalSettingsSection />
|
||||
<DeleteKnowledgeAction />
|
||||
</div>
|
||||
<KnowledgeSettingsNavigationGuard />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,177 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { knowledgeSettingsHasUnsavedWorkAtom } from './state/workflow'
|
||||
|
||||
export function KnowledgeSettingsNavigationGuard() {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const router = useRouter()
|
||||
const hasUnsavedWork = useAtomValue(knowledgeSettingsHasUnsavedWorkAtom)
|
||||
const [discardDialogOpen, setDiscardDialogOpen] = useState(false)
|
||||
const pendingNavigationRef = useRef<string | undefined>(undefined)
|
||||
const historyGuardArmedRef = useRef(false)
|
||||
const historyGuardReleaseRef = useRef(false)
|
||||
const browserBackPendingRef = useRef(false)
|
||||
const allowNavigationRef = useRef(false)
|
||||
|
||||
const armHistoryGuard = useCallback(() => {
|
||||
globalThis.history.pushState(globalThis.history.state, '', globalThis.location.href)
|
||||
historyGuardArmedRef.current = true
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (hasUnsavedWork) {
|
||||
if (
|
||||
!historyGuardArmedRef.current &&
|
||||
!browserBackPendingRef.current &&
|
||||
!allowNavigationRef.current
|
||||
)
|
||||
armHistoryGuard()
|
||||
return
|
||||
}
|
||||
if (!historyGuardArmedRef.current) return
|
||||
historyGuardReleaseRef.current = true
|
||||
historyGuardArmedRef.current = false
|
||||
globalThis.history.back()
|
||||
}, [armHistoryGuard, hasUnsavedWork])
|
||||
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
if (historyGuardReleaseRef.current) {
|
||||
historyGuardReleaseRef.current = false
|
||||
return
|
||||
}
|
||||
if (allowNavigationRef.current) {
|
||||
const destination = pendingNavigationRef.current
|
||||
pendingNavigationRef.current = undefined
|
||||
if (destination) router.push(destination)
|
||||
return
|
||||
}
|
||||
if (!historyGuardArmedRef.current) return
|
||||
|
||||
historyGuardArmedRef.current = false
|
||||
browserBackPendingRef.current = true
|
||||
setDiscardDialogOpen(true)
|
||||
}
|
||||
|
||||
globalThis.addEventListener('popstate', handlePopState)
|
||||
return () => globalThis.removeEventListener('popstate', handlePopState)
|
||||
}, [router])
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasUnsavedWork) return
|
||||
|
||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
}
|
||||
const handleDocumentClick = (event: MouseEvent) => {
|
||||
if (
|
||||
event.defaultPrevented ||
|
||||
event.button !== 0 ||
|
||||
event.metaKey ||
|
||||
event.ctrlKey ||
|
||||
event.shiftKey ||
|
||||
event.altKey
|
||||
)
|
||||
return
|
||||
const anchor = event
|
||||
.composedPath()
|
||||
.find((target): target is HTMLAnchorElement => target instanceof HTMLAnchorElement)
|
||||
if (
|
||||
!anchor ||
|
||||
anchor.hasAttribute('download') ||
|
||||
(anchor.target && anchor.target !== '_self')
|
||||
)
|
||||
return
|
||||
|
||||
const destination = new URL(anchor.href, globalThis.location.href)
|
||||
const current = new URL(globalThis.location.href)
|
||||
if (
|
||||
destination.origin !== current.origin ||
|
||||
(destination.pathname === current.pathname &&
|
||||
destination.search === current.search &&
|
||||
destination.hash === current.hash)
|
||||
)
|
||||
return
|
||||
|
||||
event.preventDefault()
|
||||
pendingNavigationRef.current = `${destination.pathname}${destination.search}${destination.hash}`
|
||||
setDiscardDialogOpen(true)
|
||||
}
|
||||
|
||||
globalThis.addEventListener('beforeunload', handleBeforeUnload)
|
||||
document.addEventListener('click', handleDocumentClick, true)
|
||||
return () => {
|
||||
globalThis.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
document.removeEventListener('click', handleDocumentClick, true)
|
||||
}
|
||||
}, [hasUnsavedWork])
|
||||
|
||||
const confirmDiscardAndNavigate = () => {
|
||||
const destination = pendingNavigationRef.current
|
||||
setDiscardDialogOpen(false)
|
||||
allowNavigationRef.current = true
|
||||
if (browserBackPendingRef.current) {
|
||||
browserBackPendingRef.current = false
|
||||
pendingNavigationRef.current = undefined
|
||||
globalThis.history.back()
|
||||
return
|
||||
}
|
||||
if (destination && historyGuardArmedRef.current) {
|
||||
historyGuardArmedRef.current = false
|
||||
globalThis.history.back()
|
||||
return
|
||||
}
|
||||
pendingNavigationRef.current = undefined
|
||||
if (destination) router.push(destination)
|
||||
}
|
||||
|
||||
const closeDiscardDialog = () => {
|
||||
setDiscardDialogOpen(false)
|
||||
pendingNavigationRef.current = undefined
|
||||
if (!browserBackPendingRef.current) return
|
||||
browserBackPendingRef.current = false
|
||||
if (hasUnsavedWork) armHistoryGuard()
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
open={discardDialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (open) setDiscardDialogOpen(true)
|
||||
else closeDiscardDialog()
|
||||
}}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<div className="px-6 pt-6">
|
||||
<AlertDialogTitle className="title-xl-semi-bold text-text-primary">
|
||||
{tCommon(($) => $['operation.confirmAction'])}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="mt-2 body-sm-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.discardDraftDescription'])}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>{tCommon(($) => $['operation.cancel'])}</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton tone="destructive" onClick={confirmDiscardAndNavigate}>
|
||||
{t(($) => $['newKnowledge.discardDraftConfirm'])}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
@ -48,11 +48,7 @@ import {
|
||||
knowledgeSettingsSettingsAtom,
|
||||
knowledgeSettingsSpaceAtom,
|
||||
} from './state/queries'
|
||||
import {
|
||||
finishKnowledgeSettingsRetrievalDraftAtom,
|
||||
setKnowledgeSettingsSavePendingAtom,
|
||||
startKnowledgeSettingsRetrievalDraftAtom,
|
||||
} from './state/workflow'
|
||||
import { setKnowledgeSettingsSavePendingAtom } from './state/workflow'
|
||||
|
||||
const REASONING_MODEL_LABEL_ID = 'knowledge-reasoning-model-label'
|
||||
const REASONING_MODEL_ERROR_ID = 'knowledge-reasoning-model-error'
|
||||
@ -69,8 +65,6 @@ export function RetrievalSettingsSection() {
|
||||
const space = useAtomValue(knowledgeSettingsSpaceAtom)
|
||||
const settings = useAtomValue(knowledgeSettingsSettingsAtom)
|
||||
const invalidateSettings = useSetAtom(invalidateKnowledgeSettingsAtom)
|
||||
const startDraftSession = useSetAtom(startKnowledgeSettingsRetrievalDraftAtom)
|
||||
const finishDraftSession = useSetAtom(finishKnowledgeSettingsRetrievalDraftAtom)
|
||||
const setSavePending = useSetAtom(setKnowledgeSettingsSavePendingAtom)
|
||||
const { data: reasoningModelList } = useModelList(ModelTypeEnum.textGeneration)
|
||||
const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)
|
||||
@ -136,7 +130,6 @@ export function RetrievalSettingsSection() {
|
||||
retrievalBaselineRef.current = retrievalFingerprint(current)
|
||||
if (!draft) settingsRevisionRef.current = settings.revision
|
||||
draftSessionActiveRef.current = true
|
||||
startDraftSession()
|
||||
setSavePending({ owner: 'retrieval', pending: true })
|
||||
}
|
||||
const next = { ...current, ...patch }
|
||||
@ -250,7 +243,6 @@ export function RetrievalSettingsSection() {
|
||||
liveDraftRef.current = latestDraft
|
||||
if (latestDraft) setDraft(latestDraft)
|
||||
draftSessionActiveRef.current = false
|
||||
finishDraftSession()
|
||||
setSavePending({ owner: 'retrieval', pending: false })
|
||||
}
|
||||
|
||||
@ -284,7 +276,6 @@ export function RetrievalSettingsSection() {
|
||||
const latestDraft = liveDraftRef.current
|
||||
if (latestDraft) setDraft(latestDraft)
|
||||
draftSessionActiveRef.current = false
|
||||
finishDraftSession()
|
||||
setSavePending({ owner: 'retrieval', pending: false })
|
||||
showSaveSuccess()
|
||||
})
|
||||
@ -306,7 +297,6 @@ export function RetrievalSettingsSection() {
|
||||
}
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps -- Migration completion is keyed by the observer state; queued saves use the latest ref snapshot.
|
||||
}, [
|
||||
finishDraftSession,
|
||||
invalidateSettings,
|
||||
migrationQuery.data,
|
||||
migrationQuery.isError,
|
||||
|
||||
@ -1,18 +1,8 @@
|
||||
import { atom } from 'jotai'
|
||||
|
||||
const basicDraftActiveAtom = atom(false)
|
||||
const retrievalDraftActiveAtom = atom(false)
|
||||
const pendingSaveOwnersAtom = atom<Set<string>>(new globalThis.Set<string>())
|
||||
|
||||
export const knowledgeSettingsScopedAtoms = [
|
||||
basicDraftActiveAtom,
|
||||
retrievalDraftActiveAtom,
|
||||
pendingSaveOwnersAtom,
|
||||
] as const
|
||||
|
||||
export const knowledgeSettingsHasUnsavedWorkAtom = atom(
|
||||
(get) => get(basicDraftActiveAtom) || get(retrievalDraftActiveAtom),
|
||||
)
|
||||
export const knowledgeSettingsScopedAtoms = [pendingSaveOwnersAtom] as const
|
||||
|
||||
export const knowledgeSettingsHasPendingSaveAtom = atom(
|
||||
(get) => get(pendingSaveOwnersAtom).size > 0,
|
||||
@ -29,19 +19,3 @@ export const setKnowledgeSettingsSavePendingAtom = atom(
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
export const startKnowledgeSettingsBasicDraftAtom = atom(null, (_get, set) => {
|
||||
set(basicDraftActiveAtom, true)
|
||||
})
|
||||
|
||||
export const finishKnowledgeSettingsBasicDraftAtom = atom(null, (_get, set) => {
|
||||
set(basicDraftActiveAtom, false)
|
||||
})
|
||||
|
||||
export const startKnowledgeSettingsRetrievalDraftAtom = atom(null, (_get, set) => {
|
||||
set(retrievalDraftActiveAtom, true)
|
||||
})
|
||||
|
||||
export const finishKnowledgeSettingsRetrievalDraftAtom = atom(null, (_get, set) => {
|
||||
set(retrievalDraftActiveAtom, false)
|
||||
})
|
||||
|
||||
@ -901,7 +901,6 @@ describe('AddSourcePage', () => {
|
||||
|
||||
it('clears the stored draft when source setup is canceled', async () => {
|
||||
const user = userEvent.setup()
|
||||
const historyBack = vi.spyOn(window.history, 'back').mockImplementation(() => undefined)
|
||||
const storageKey = newKnowledgeSourceDraftStorageKey('cancel-draft')
|
||||
globalThis.sessionStorage.setItem(
|
||||
storageKey,
|
||||
@ -920,19 +919,10 @@ describe('AddSourcePage', () => {
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }),
|
||||
)
|
||||
const confirmation = await screen.findByRole('alertdialog', {
|
||||
name: 'dataset.newKnowledge.discardSourceDraftTitle',
|
||||
})
|
||||
expect(globalThis.sessionStorage.getItem(storageKey)).not.toBeNull()
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardDraftConfirm' }),
|
||||
)
|
||||
|
||||
expect(globalThis.sessionStorage.getItem(storageKey)).toBeNull()
|
||||
expect(historyBack).toHaveBeenCalledOnce()
|
||||
act(() => window.dispatchEvent(new PopStateEvent('popstate')))
|
||||
expect(routerMock.replace).toHaveBeenCalledWith('/datasets/new/space-1/sources')
|
||||
expect(confirmation).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('creates the exact Firecrawl provider connection without leaking credentials', async () => {
|
||||
@ -964,8 +954,6 @@ describe('AddSourcePage', () => {
|
||||
params: { control_space_id: 'space-1' },
|
||||
}),
|
||||
)
|
||||
await screen.findByRole('status', { name: 'appApi.loading' })
|
||||
act(() => window.dispatchEvent(new PopStateEvent('popstate')))
|
||||
expect(queryClientMock.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ['source-connections'],
|
||||
})
|
||||
@ -1089,7 +1077,6 @@ describe('AddSourcePage', () => {
|
||||
params: { control_space_id: 'space-1' },
|
||||
}),
|
||||
)
|
||||
act(() => window.dispatchEvent(new PopStateEvent('popstate')))
|
||||
expect(
|
||||
await screen.findByRole('textbox', { name: /dataset\.newKnowledge\.rootUrl/ }),
|
||||
).toBeEnabled()
|
||||
@ -1224,9 +1211,8 @@ describe('AddSourcePage', () => {
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('releases the parent history guard before the crawl preview owns navigation', async () => {
|
||||
it('shows the crawl preview immediately after the provider connection becomes active', async () => {
|
||||
const user = userEvent.setup()
|
||||
const historyBack = vi.spyOn(window.history, 'back').mockImplementation(() => undefined)
|
||||
clientMock.createConnection.mockResolvedValue(connection('active'))
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
@ -1236,14 +1222,9 @@ describe('AddSourcePage', () => {
|
||||
await user.type(screen.getByLabelText(/Api Key/), 'secret-value')
|
||||
await user.click(screen.getByRole('button', { name: connectFirecrawlButtonName }))
|
||||
|
||||
await waitFor(() => expect(historyBack).toHaveBeenCalledOnce())
|
||||
expect(screen.queryByText(/dataset\.newKnowledge\.providerConnected/)).not.toBeInTheDocument()
|
||||
|
||||
act(() => window.dispatchEvent(new PopStateEvent('popstate')))
|
||||
await screen.findByText(/dataset\.newKnowledge\.providerConnected/)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
|
||||
expect(historyBack).toHaveBeenCalledOnce()
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/sources')
|
||||
})
|
||||
|
||||
@ -1285,9 +1266,6 @@ describe('AddSourcePage', () => {
|
||||
view.rerender(
|
||||
<AddSourcePage initialSourceDraft={initialSourceDraft} knowledgeSpaceId="space-1" />,
|
||||
)
|
||||
await screen.findByRole('status', { name: 'appApi.loading' })
|
||||
act(() => window.dispatchEvent(new PopStateEvent('popstate')))
|
||||
|
||||
expect(screen.getByRole('textbox', { name: /dataset\.newKnowledge\.rootUrl/ })).toHaveValue(
|
||||
'https://docs.dify.ai',
|
||||
)
|
||||
@ -1341,7 +1319,6 @@ describe('AddSourcePage', () => {
|
||||
await user.click(screen.getByRole('button', { name: connectFirecrawlButtonName }))
|
||||
|
||||
await waitFor(() => expect(clientMock.createConnection).toHaveBeenCalledOnce())
|
||||
act(() => window.dispatchEvent(new PopStateEvent('popstate')))
|
||||
expect(await screen.findByText(/dataset\.newKnowledge\.providerConnected/)).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('dataset.newKnowledge.connectionFailed:{"provider":"Firecrawl"}'),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { Source, SourceWorkflowRun } from '../../source-models'
|
||||
import { act, screen, waitFor, within } from '@testing-library/react'
|
||||
import { act, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { render } from '@/test/console/render'
|
||||
import { WebsiteCrawlPreview } from '../website-crawl-preview'
|
||||
@ -397,9 +397,6 @@ describe('WebsiteCrawlPreview', () => {
|
||||
})
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
|
||||
await waitFor(() => expect(clientMock.deleteSource).toHaveBeenCalledTimes(2))
|
||||
expect(clientMock.deleteSource).toHaveBeenNthCalledWith(1, {
|
||||
@ -436,9 +433,6 @@ describe('WebsiteCrawlPreview', () => {
|
||||
})
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
|
||||
await waitFor(() => expect(clientMock.getSource).toHaveBeenCalledOnce())
|
||||
expect(clientMock.deleteSource).toHaveBeenCalledOnce()
|
||||
@ -579,7 +573,7 @@ describe('WebsiteCrawlPreview', () => {
|
||||
expect(clientMock.createSource.mock.calls[0]?.[0].body.metadata.crawlOptions.limit).toBe(50)
|
||||
})
|
||||
|
||||
it('confirms dirty cancellation and protects an unfinished draft from page unload', async () => {
|
||||
it('cancels a dirty draft directly without protecting page unload', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<WebsiteCrawlPreview connection={connection} knowledgeSpaceId="space-1" />)
|
||||
|
||||
@ -589,23 +583,13 @@ describe('WebsiteCrawlPreview', () => {
|
||||
)
|
||||
const unload = new Event('beforeunload', { cancelable: true })
|
||||
window.dispatchEvent(unload)
|
||||
expect(unload.defaultPrevented).toBe(true)
|
||||
expect(unload.defaultPrevented).toBe(false)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
expect(
|
||||
screen.getByRole('alertdialog', { name: 'dataset.newKnowledge.discardSourceChanges' }),
|
||||
).toBeInTheDocument()
|
||||
expect(routerMock.push).not.toHaveBeenCalled()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.keepEditing' }))
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
await waitFor(() =>
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/sources'),
|
||||
)
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('cancels an active preview before discarding its provisional source', async () => {
|
||||
@ -617,9 +601,6 @@ describe('WebsiteCrawlPreview', () => {
|
||||
await waitFor(() => expect(clientMock.startPreview).toHaveBeenCalledOnce())
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
|
||||
await waitFor(() => expect(clientMock.cancel).toHaveBeenCalledOnce())
|
||||
expect(clientMock.cancel).toHaveBeenCalledWith({
|
||||
@ -645,12 +626,8 @@ describe('WebsiteCrawlPreview', () => {
|
||||
await waitFor(() => expect(clientMock.startPreview).toHaveBeenCalledOnce())
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.keepEditing' })).toBeDisabled()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }),
|
||||
).toHaveAttribute('aria-disabled', 'true')
|
||||
|
||||
startRequest.resolve(run('running'))
|
||||
@ -672,9 +649,6 @@ describe('WebsiteCrawlPreview', () => {
|
||||
await screen.findByRole('alert')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
|
||||
await waitFor(() => expect(clientMock.startPreview).toHaveBeenCalledTimes(2))
|
||||
expect(clientMock.startPreview.mock.calls[0]?.[0].headers).toEqual(
|
||||
@ -695,9 +669,6 @@ describe('WebsiteCrawlPreview', () => {
|
||||
await waitFor(() => expect(clientMock.createSource).toHaveBeenCalledOnce())
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
sourceRequest.resolve(source())
|
||||
|
||||
await waitFor(() =>
|
||||
@ -707,74 +678,6 @@ describe('WebsiteCrawlPreview', () => {
|
||||
expect(clientMock.cancel).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('cancels a fresh re-crawl that returns after navigation discard was confirmed', async () => {
|
||||
const recrawlRequest = deferred<SourceWorkflowRun>()
|
||||
clientMock.startPreview
|
||||
.mockResolvedValueOnce(run('running'))
|
||||
.mockReturnValueOnce(recrawlRequest.promise)
|
||||
clientMock.cancel
|
||||
.mockResolvedValueOnce(run('canceled'))
|
||||
.mockResolvedValueOnce(run('canceled', { id: 'run-2' }))
|
||||
render(
|
||||
<>
|
||||
<a href="/datasets/new/space-1/documents">Documents navigation</a>
|
||||
<WebsiteCrawlPreview connection={connection} knowledgeSpaceId="space-1" />
|
||||
</>,
|
||||
)
|
||||
const user = await fillValidForm()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.crawlAndPreview' }))
|
||||
await screen.findByText('Getting started')
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.reCrawl' }))
|
||||
await waitFor(() => expect(clientMock.startPreview).toHaveBeenCalledTimes(2))
|
||||
|
||||
await user.click(screen.getByRole('link', { name: 'Documents navigation' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
recrawlRequest.resolve(run('running', { id: 'run-2' }))
|
||||
|
||||
await waitFor(() => expect(clientMock.cancel).toHaveBeenCalledTimes(2))
|
||||
expect(clientMock.retry).not.toHaveBeenCalled()
|
||||
await waitFor(() =>
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/documents'),
|
||||
)
|
||||
})
|
||||
|
||||
it('reconciles a response-lost fresh re-crawl before leaving the preview', async () => {
|
||||
clientMock.startPreview
|
||||
.mockResolvedValueOnce(run('running'))
|
||||
.mockRejectedValueOnce(new Error('response lost'))
|
||||
.mockResolvedValueOnce(run('running', { id: 'run-2' }))
|
||||
clientMock.cancel
|
||||
.mockResolvedValueOnce(run('canceled'))
|
||||
.mockResolvedValueOnce(run('canceled', { id: 'run-2' }))
|
||||
render(
|
||||
<>
|
||||
<a href="/datasets/new/space-1/documents">Documents navigation</a>
|
||||
<WebsiteCrawlPreview connection={connection} knowledgeSpaceId="space-1" />
|
||||
</>,
|
||||
)
|
||||
const user = await fillValidForm()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.crawlAndPreview' }))
|
||||
await screen.findByText('Getting started')
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.reCrawl' }))
|
||||
await waitFor(() => expect(clientMock.startPreview).toHaveBeenCalledTimes(2))
|
||||
|
||||
await user.click(screen.getByRole('link', { name: 'Documents navigation' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
|
||||
await waitFor(() => expect(clientMock.startPreview).toHaveBeenCalledTimes(3))
|
||||
await waitFor(() => expect(clientMock.cancel).toHaveBeenCalledTimes(2))
|
||||
expect(clientMock.startPreview.mock.calls[1]?.[0].headers).toEqual(
|
||||
clientMock.startPreview.mock.calls[2]?.[0].headers,
|
||||
)
|
||||
await waitFor(() =>
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/documents'),
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps the started run available when discard cancellation must be retried', async () => {
|
||||
clientMock.getRun.mockReturnValue(new Promise<SourceWorkflowRun>(() => {}))
|
||||
clientMock.cancel
|
||||
@ -786,17 +689,10 @@ describe('WebsiteCrawlPreview', () => {
|
||||
await waitFor(() => expect(clientMock.startPreview).toHaveBeenCalledOnce())
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
expect(within(screen.getByRole('alertdialog')).getByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.crawlFailedDescription',
|
||||
)
|
||||
expect(screen.getByText('dataset.newKnowledge.crawlFailedDescription')).toBeInTheDocument()
|
||||
expect(routerMock.push).not.toHaveBeenCalled()
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
|
||||
await waitFor(() => expect(clientMock.cancel).toHaveBeenCalledTimes(2))
|
||||
expect(clientMock.cancel.mock.calls[0]?.[0].params.run_id).toBe('run-1')
|
||||
@ -806,16 +702,11 @@ describe('WebsiteCrawlPreview', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps the preview available when re-crawl cancellation fails', async () => {
|
||||
it('cancels the preview after a re-crawl cancellation is retried', async () => {
|
||||
clientMock.cancel
|
||||
.mockRejectedValueOnce(Object.assign(new Error('conflict'), { status: 409 }))
|
||||
.mockResolvedValueOnce(run('canceled'))
|
||||
render(
|
||||
<>
|
||||
<a href="/datasets/new/space-1/documents">Documents navigation</a>
|
||||
<WebsiteCrawlPreview connection={connection} knowledgeSpaceId="space-1" />
|
||||
</>,
|
||||
)
|
||||
render(<WebsiteCrawlPreview connection={connection} knowledgeSpaceId="space-1" />)
|
||||
const user = await fillValidForm()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.crawlAndPreview' }))
|
||||
await screen.findByText('Getting started')
|
||||
@ -824,66 +715,14 @@ describe('WebsiteCrawlPreview', () => {
|
||||
expect(clientMock.startPreview).toHaveBeenCalledOnce()
|
||||
expect(screen.getByText('Getting started')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('link', { name: 'Documents navigation' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
|
||||
await waitFor(() => expect(clientMock.cancel).toHaveBeenCalledTimes(2))
|
||||
await waitFor(() =>
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/documents'),
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/sources'),
|
||||
)
|
||||
})
|
||||
|
||||
it('guards sidebar links and browser history after a crawl preview is ready', async () => {
|
||||
render(
|
||||
<>
|
||||
<a href="/datasets/new/space-1/documents">Documents navigation</a>
|
||||
<WebsiteCrawlPreview connection={connection} knowledgeSpaceId="space-1" />
|
||||
</>,
|
||||
)
|
||||
const user = await fillValidForm()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.crawlAndPreview' }))
|
||||
await screen.findByText('Getting started')
|
||||
|
||||
act(() => window.history.back())
|
||||
expect(
|
||||
await screen.findByRole('alertdialog', {
|
||||
name: 'dataset.newKnowledge.discardSourceChanges',
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.keepEditing' }))
|
||||
|
||||
await user.click(screen.getByRole('link', { name: 'Documents navigation' }))
|
||||
expect(
|
||||
screen.getByRole('alertdialog', { name: 'dataset.newKnowledge.discardSourceChanges' }),
|
||||
).toBeInTheDocument()
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/documents'),
|
||||
)
|
||||
})
|
||||
|
||||
it('guards browser history before a crawl preview starts', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<WebsiteCrawlPreview connection={connection} knowledgeSpaceId="space-1" />)
|
||||
await user.type(
|
||||
screen.getByLabelText(/^dataset\.newKnowledge\.rootUrl/),
|
||||
'https://docs.dify.ai',
|
||||
)
|
||||
|
||||
act(() => window.history.back())
|
||||
|
||||
expect(
|
||||
await screen.findByRole('alertdialog', {
|
||||
name: 'dataset.newKnowledge.discardSourceChanges',
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('leaves through clean Cancel after a dirty form is cleared', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<WebsiteCrawlPreview connection={connection} knowledgeSpaceId="space-1" />)
|
||||
@ -899,48 +738,6 @@ describe('WebsiteCrawlPreview', () => {
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('leaves through a same-origin link after a dirty form is cleared', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<>
|
||||
<a href="/datasets/new/space-1/documents">Documents navigation</a>
|
||||
<WebsiteCrawlPreview connection={connection} knowledgeSpaceId="space-1" />
|
||||
</>,
|
||||
)
|
||||
const rootUrl = screen.getByLabelText(/^dataset\.newKnowledge\.rootUrl/)
|
||||
await user.type(rootUrl, 'x')
|
||||
await user.clear(rootUrl)
|
||||
|
||||
await user.click(screen.getByRole('link', { name: 'Documents navigation' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/documents'),
|
||||
)
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('clears a canceled sidebar destination before the form cancel action', async () => {
|
||||
render(
|
||||
<>
|
||||
<a href="/datasets/new/space-1/documents">Documents navigation</a>
|
||||
<WebsiteCrawlPreview connection={connection} knowledgeSpaceId="space-1" />
|
||||
</>,
|
||||
)
|
||||
const user = await fillValidForm()
|
||||
await user.click(screen.getByRole('link', { name: 'Documents navigation' }))
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.keepEditing' }))
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/sources'),
|
||||
)
|
||||
expect(routerMock.push).not.toHaveBeenCalledWith('/datasets/new/space-1/documents')
|
||||
})
|
||||
|
||||
it('shows pending feedback while a preview-ready crawl is being restarted', async () => {
|
||||
const cancelRequest = deferred<SourceWorkflowRun>()
|
||||
clientMock.cancel.mockReturnValue(cancelRequest.promise)
|
||||
@ -1453,13 +1250,8 @@ describe('WebsiteCrawlPreview', () => {
|
||||
await screen.findByRole('alert')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.discardSourceChangesConfirm' }),
|
||||
)
|
||||
|
||||
expect(within(screen.getByRole('alertdialog')).getByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.crawlFailedDescription',
|
||||
)
|
||||
expect(screen.getByText('dataset.newKnowledge.crawlFailedDescription')).toBeInTheDocument()
|
||||
expect(routerMock.push).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export function AddSourceExitDialog({
|
||||
discarding,
|
||||
error,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
open,
|
||||
}: {
|
||||
discarding: boolean
|
||||
error: boolean
|
||||
onCancel: () => void
|
||||
onConfirm: () => void
|
||||
open: boolean
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
open={open}
|
||||
onOpenChange={(nextOpen) => {
|
||||
if (!nextOpen && !discarding) onCancel()
|
||||
}}
|
||||
>
|
||||
<AlertDialogContent className="w-120 rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0! text-left align-middle shadow-lg">
|
||||
<div className="flex flex-col items-start gap-2 self-stretch p-6 pb-4">
|
||||
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['newKnowledge.discardSourceDraftTitle'])}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription
|
||||
render={<div />}
|
||||
className="system-md-regular text-text-secondary"
|
||||
>
|
||||
{t(($) => $['newKnowledge.discardSourceDraftDescription'])}
|
||||
</AlertDialogDescription>
|
||||
{error && (
|
||||
<p role="alert" className="system-sm-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.discardSourceDraftFailed'])}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<AlertDialogActions className="gap-2 p-6">
|
||||
<AlertDialogCancelButton variant="secondary" disabled={discarding}>
|
||||
{tCommon(($) => $['operation.cancel'])}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton loading={discarding} disabled={discarding} onClick={onConfirm}>
|
||||
{t(($) => $['newKnowledge.discardDraftConfirm'])}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
@ -513,7 +513,6 @@ function ResourceConfiguration({
|
||||
knowledgeSpaceId,
|
||||
onCompleted,
|
||||
onDraftChange,
|
||||
onDirtyChange,
|
||||
onExit,
|
||||
parameters,
|
||||
provider,
|
||||
@ -524,7 +523,6 @@ function ResourceConfiguration({
|
||||
knowledgeSpaceId: string
|
||||
onCompleted: () => void
|
||||
onDraftChange: (draft: NewKnowledgeSourceDraft) => void
|
||||
onDirtyChange: (dirty: boolean) => void
|
||||
onExit: () => void
|
||||
parameters: DatasourceParameters
|
||||
provider: SourceProvider
|
||||
@ -570,10 +568,6 @@ function ResourceConfiguration({
|
||||
[],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
onDirtyChange(selected.size > 0)
|
||||
}, [onDirtyChange, selected.size])
|
||||
|
||||
useEffect(() => {
|
||||
let active = true
|
||||
let source: Source | undefined
|
||||
@ -1165,7 +1159,6 @@ function ResourceConfiguration({
|
||||
setSubmitError(false)
|
||||
const completeSubmission = async () => {
|
||||
committedRef.current = true
|
||||
onDirtyChange(false)
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: consoleQuery.knowledgeFs.spaces.byControlSpaceId.sources.get.key(),
|
||||
@ -1431,7 +1424,6 @@ function AppliedResourceConfiguration({
|
||||
knowledgeSpaceId,
|
||||
onCompleted,
|
||||
onDraftChange,
|
||||
onDirtyChange,
|
||||
onExit,
|
||||
parameters,
|
||||
parametersValid,
|
||||
@ -1444,7 +1436,6 @@ function AppliedResourceConfiguration({
|
||||
knowledgeSpaceId: string
|
||||
onCompleted: () => void
|
||||
onDraftChange: (draft: NewKnowledgeSourceDraft) => void
|
||||
onDirtyChange: (dirty: boolean) => void
|
||||
onExit: () => void
|
||||
parameters: DatasourceParameters
|
||||
parametersValid: boolean
|
||||
@ -1492,7 +1483,6 @@ function AppliedResourceConfiguration({
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
onCompleted={onCompleted}
|
||||
onDraftChange={onDraftChange}
|
||||
onDirtyChange={onDirtyChange}
|
||||
onExit={onExit}
|
||||
parameters={appliedParameters}
|
||||
provider={provider}
|
||||
@ -1508,14 +1498,12 @@ export function ConnectedSourceWorkflow({
|
||||
knowledgeSpaceId,
|
||||
onCompleted,
|
||||
onDraftChange,
|
||||
onDirtyChange,
|
||||
onExit,
|
||||
}: {
|
||||
draft: ConnectedSourceDraft
|
||||
knowledgeSpaceId: string
|
||||
onCompleted: () => void
|
||||
onDraftChange: (draft: NewKnowledgeSourceDraft) => void
|
||||
onDirtyChange: (dirty: boolean) => void
|
||||
onExit: () => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
@ -1822,7 +1810,6 @@ export function ConnectedSourceWorkflow({
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
onCompleted={onCompleted}
|
||||
onDraftChange={onDraftChange}
|
||||
onDirtyChange={onDirtyChange}
|
||||
onExit={onExit}
|
||||
parameters={parameters}
|
||||
parametersValid={parametersValid}
|
||||
|
||||
@ -61,7 +61,6 @@ import {
|
||||
sourceConnectionListFromApi,
|
||||
sourceProviderListFromApi,
|
||||
} from '../source-models'
|
||||
import { AddSourceExitDialog } from './components/exit-dialog'
|
||||
import { ConnectedSourceWorkflow } from './connected-source-workflow'
|
||||
import { WebsiteCrawlPreview } from './website-crawl-preview'
|
||||
|
||||
@ -259,7 +258,6 @@ function ConnectionForm({
|
||||
datasourceProvider,
|
||||
knowledgeSpaceId,
|
||||
onConnected,
|
||||
onDraftChange,
|
||||
onReconcile,
|
||||
provider,
|
||||
providerName,
|
||||
@ -268,7 +266,6 @@ function ConnectionForm({
|
||||
datasourceProvider: WebsiteDatasourceProvider
|
||||
knowledgeSpaceId: string
|
||||
onConnected: (connection: Connection) => void
|
||||
onDraftChange: (dirty: boolean) => void
|
||||
onReconcile: () => Promise<Connection | undefined>
|
||||
provider: Provider
|
||||
providerName: string
|
||||
@ -287,16 +284,6 @@ function ConnectionForm({
|
||||
const visibleFields = configurableFields.filter(
|
||||
(field) => authKind === 'api-key' || (!field.secret && field.format === 'uri'),
|
||||
)
|
||||
const hasDraftChanges =
|
||||
authKind !== (supportedAuthKinds[0] ?? 'api-key') ||
|
||||
Object.values(configuration).some((value) => Boolean(value.trim())) ||
|
||||
Object.values(credentials).some((value) => Boolean(value.trim()))
|
||||
|
||||
useEffect(() => {
|
||||
onDraftChange(hasDraftChanges)
|
||||
return () => onDraftChange(false)
|
||||
}, [hasDraftChanges, onDraftChange])
|
||||
|
||||
const changeAuthKind = (nextAuthKind: ConnectionAuthKind) => {
|
||||
if (nextAuthKind !== authKind) setCredentials({})
|
||||
setAuthKind(nextAuthKind)
|
||||
@ -351,7 +338,6 @@ function ConnectionForm({
|
||||
}),
|
||||
)
|
||||
setCredentials({})
|
||||
onDraftChange(false)
|
||||
onConnected(createdConnection)
|
||||
} catch {
|
||||
setCredentials({})
|
||||
@ -534,7 +520,6 @@ function UnconfiguredProvider({
|
||||
knowledgeSpaceId,
|
||||
onConnected,
|
||||
onConfigureManagedProvider,
|
||||
onDraftChange,
|
||||
onReconcile,
|
||||
provider,
|
||||
providerOption,
|
||||
@ -545,7 +530,6 @@ function UnconfiguredProvider({
|
||||
knowledgeSpaceId: string
|
||||
onConnected: (connection: Connection) => void
|
||||
onConfigureManagedProvider: () => void
|
||||
onDraftChange: (dirty: boolean) => void
|
||||
onReconcile: () => Promise<Connection | undefined>
|
||||
provider: Provider
|
||||
providerOption: InstalledSourceProviderOption
|
||||
@ -575,7 +559,6 @@ function UnconfiguredProvider({
|
||||
datasourceProvider={datasourceProvider}
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
onConnected={onConnected}
|
||||
onDraftChange={onDraftChange}
|
||||
onReconcile={onReconcile}
|
||||
provider={provider}
|
||||
providerName={providerName}
|
||||
@ -775,25 +758,8 @@ function AddSourcePageContent({
|
||||
),
|
||||
)
|
||||
const [sourceDraft, setSourceDraft] = useState<NewKnowledgeSourceDraft>(initialDraftRef.current)
|
||||
const sourceDraftBaselineRef = useRef(
|
||||
JSON.stringify(
|
||||
initialSourceProvider && !initialSourceDraft
|
||||
? initialDraftRef.current
|
||||
: createNewKnowledgeSourceDraft(initialDraftRef.current.sourceType),
|
||||
),
|
||||
)
|
||||
const [sourceDraftResolved, setSourceDraftResolved] = useState(!sourceDraftKey)
|
||||
const [connectionDraftDirty, setConnectionDraftDirty] = useState(false)
|
||||
const [exitOpen, setExitOpen] = useState(false)
|
||||
const [discarding, setDiscarding] = useState(false)
|
||||
const [discardError, setDiscardError] = useState(false)
|
||||
const [websiteSetupLocked, setWebsiteSetupLocked] = useState(false)
|
||||
const [historyGuardReleaseVersion, setHistoryGuardReleaseVersion] = useState(0)
|
||||
const historyGuardArmedRef = useRef(false)
|
||||
const historyGuardReleaseRef = useRef(false)
|
||||
const browserBackExitRef = useRef(false)
|
||||
const pendingNavigationRef = useRef<string | undefined>(undefined)
|
||||
const exitDestinationRef = useRef(newKnowledgeDetailPath(knowledgeSpaceId))
|
||||
const sourceDraftsRef = useRef<
|
||||
Partial<Record<NewKnowledgeSourceDraft['sourceType'], NewKnowledgeSourceDraft>>
|
||||
>({ [sourceDraft.sourceType]: sourceDraft })
|
||||
@ -1039,168 +1005,10 @@ function AddSourcePageContent({
|
||||
supportsDirectConnection &&
|
||||
activeConnection,
|
||||
)
|
||||
const websitePreviewReady =
|
||||
sourceDraftResolved &&
|
||||
websiteReady &&
|
||||
!historyGuardArmedRef.current &&
|
||||
!historyGuardReleaseRef.current
|
||||
const hasUnsavedChanges =
|
||||
sourceDraftResolved &&
|
||||
!websiteReady &&
|
||||
(connectionDraftDirty || JSON.stringify(sourceDraft) !== sourceDraftBaselineRef.current)
|
||||
const armHistoryGuard = useCallback(() => {
|
||||
globalThis.history.pushState(globalThis.history.state, '', globalThis.location.href)
|
||||
historyGuardArmedRef.current = true
|
||||
}, [])
|
||||
const replaceAfterHistoryGuard = useCallback(
|
||||
(path: string) => {
|
||||
if (!historyGuardArmedRef.current) {
|
||||
router.replace(path)
|
||||
return
|
||||
}
|
||||
pendingNavigationRef.current = path
|
||||
globalThis.history.back()
|
||||
},
|
||||
[router],
|
||||
)
|
||||
const requestNavigation = useCallback(
|
||||
(path: string) => {
|
||||
if (discarding) return
|
||||
if (hasUnsavedChanges) {
|
||||
exitDestinationRef.current = path
|
||||
browserBackExitRef.current = false
|
||||
setDiscardError(false)
|
||||
setExitOpen(true)
|
||||
return
|
||||
}
|
||||
clearStoredSourceDraft()
|
||||
replaceAfterHistoryGuard(path)
|
||||
},
|
||||
[clearStoredSourceDraft, discarding, hasUnsavedChanges, replaceAfterHistoryGuard],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!hasUnsavedChanges ||
|
||||
historyGuardArmedRef.current ||
|
||||
browserBackExitRef.current ||
|
||||
pendingNavigationRef.current
|
||||
)
|
||||
return
|
||||
armHistoryGuard()
|
||||
}, [armHistoryGuard, hasUnsavedChanges])
|
||||
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
if (!historyGuardArmedRef.current) return
|
||||
historyGuardArmedRef.current = false
|
||||
if (historyGuardReleaseRef.current) {
|
||||
historyGuardReleaseRef.current = false
|
||||
setHistoryGuardReleaseVersion((version) => version + 1)
|
||||
return
|
||||
}
|
||||
const pendingNavigation = pendingNavigationRef.current
|
||||
if (pendingNavigation) {
|
||||
pendingNavigationRef.current = undefined
|
||||
router.replace(pendingNavigation)
|
||||
return
|
||||
}
|
||||
if (!hasUnsavedChanges) {
|
||||
clearStoredSourceDraft()
|
||||
router.replace(detailPath)
|
||||
return
|
||||
}
|
||||
browserBackExitRef.current = true
|
||||
exitDestinationRef.current = detailPath
|
||||
setDiscardError(false)
|
||||
setExitOpen(true)
|
||||
}
|
||||
|
||||
globalThis.addEventListener('popstate', handlePopState)
|
||||
return () => globalThis.removeEventListener('popstate', handlePopState)
|
||||
}, [clearStoredSourceDraft, detailPath, hasUnsavedChanges, router])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!websiteReady ||
|
||||
!historyGuardArmedRef.current ||
|
||||
historyGuardReleaseRef.current ||
|
||||
pendingNavigationRef.current
|
||||
)
|
||||
return
|
||||
historyGuardReleaseRef.current = true
|
||||
globalThis.history.back()
|
||||
}, [websiteReady])
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasUnsavedChanges) return
|
||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
}
|
||||
globalThis.addEventListener('beforeunload', handleBeforeUnload)
|
||||
return () => globalThis.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
}, [hasUnsavedChanges])
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasUnsavedChanges) return
|
||||
const handleDocumentClick = (event: MouseEvent) => {
|
||||
if (
|
||||
event.defaultPrevented ||
|
||||
event.button !== 0 ||
|
||||
event.metaKey ||
|
||||
event.ctrlKey ||
|
||||
event.shiftKey ||
|
||||
event.altKey
|
||||
)
|
||||
return
|
||||
const anchor = event
|
||||
.composedPath()
|
||||
.find((target): target is HTMLAnchorElement => target instanceof HTMLAnchorElement)
|
||||
if (
|
||||
!anchor ||
|
||||
anchor.hasAttribute('download') ||
|
||||
(anchor.target && anchor.target !== '_self')
|
||||
)
|
||||
return
|
||||
const destination = new URL(anchor.href, globalThis.location.href)
|
||||
if (destination.origin !== globalThis.location.origin) return
|
||||
const current = new URL(globalThis.location.href)
|
||||
if (destination.pathname === current.pathname && destination.search === current.search) return
|
||||
event.preventDefault()
|
||||
requestNavigation(`${destination.pathname}${destination.search}${destination.hash}`)
|
||||
}
|
||||
|
||||
document.addEventListener('click', handleDocumentClick, true)
|
||||
return () => document.removeEventListener('click', handleDocumentClick, true)
|
||||
}, [hasUnsavedChanges, requestNavigation])
|
||||
|
||||
const requestExit = () => requestNavigation(detailPath)
|
||||
const cancelExit = () => {
|
||||
setExitOpen(false)
|
||||
setDiscardError(false)
|
||||
if (!browserBackExitRef.current) return
|
||||
browserBackExitRef.current = false
|
||||
armHistoryGuard()
|
||||
}
|
||||
const confirmExit = () => {
|
||||
if (discarding) return
|
||||
setDiscarding(true)
|
||||
setDiscardError(false)
|
||||
try {
|
||||
clearStoredSourceDraft()
|
||||
sourceDraftBaselineRef.current = JSON.stringify(sourceDraft)
|
||||
setConnectionDraftDirty(false)
|
||||
setExitOpen(false)
|
||||
browserBackExitRef.current = false
|
||||
const destination = exitDestinationRef.current
|
||||
exitDestinationRef.current = detailPath
|
||||
replaceAfterHistoryGuard(destination)
|
||||
} catch {
|
||||
setDiscardError(true)
|
||||
} finally {
|
||||
setDiscarding(false)
|
||||
}
|
||||
const websitePreviewReady = sourceDraftResolved && websiteReady
|
||||
const requestExit = () => {
|
||||
clearStoredSourceDraft()
|
||||
router.replace(detailPath)
|
||||
}
|
||||
|
||||
if (
|
||||
@ -1308,7 +1116,7 @@ function AddSourcePageContent({
|
||||
</div>
|
||||
) : activeConnection && websitePreviewReady ? (
|
||||
<WebsiteCrawlPreview
|
||||
key={`${historyGuardReleaseVersion}:${datasourceProvider.key}:${activeConnection.id}`}
|
||||
key={`${datasourceProvider.key}:${activeConnection.id}`}
|
||||
connection={activeConnection}
|
||||
initialDraft={sourceDraft}
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
@ -1357,7 +1165,6 @@ function AddSourcePageContent({
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
}
|
||||
onDraftChange={setConnectionDraftDirty}
|
||||
onReconcile={reconcileConnection}
|
||||
provider={provider}
|
||||
providerOption={datasourceProvider}
|
||||
@ -1371,14 +1178,11 @@ function AddSourcePageContent({
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
onCompleted={() => {
|
||||
clearStoredSourceDraft()
|
||||
sourceDraftBaselineRef.current = JSON.stringify(sourceDraft)
|
||||
setConnectionDraftDirty(false)
|
||||
replaceAfterHistoryGuard(detailPath)
|
||||
router.replace(detailPath)
|
||||
}}
|
||||
onDraftChange={(draft) => {
|
||||
updateSourceDraft(draft)
|
||||
}}
|
||||
onDirtyChange={setConnectionDraftDirty}
|
||||
onExit={requestExit}
|
||||
/>
|
||||
)}
|
||||
@ -1409,13 +1213,6 @@ function AddSourcePageContent({
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
<AddSourceExitDialog
|
||||
discarding={discarding}
|
||||
error={discardError}
|
||||
onCancel={cancelExit}
|
||||
onConfirm={confirmExit}
|
||||
open={exitOpen}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -6,15 +6,6 @@ import type { InstalledSourceProviderOption } from '../setup/provider-options'
|
||||
import type { NewKnowledgeWebsiteSourceDraft } from '../setup/source-draft'
|
||||
import type { SyncPolicyValue } from '../setup/sync-policy-field'
|
||||
import type { CrawlPreviewPage as PreviewPage, Source, SourceWorkflowRun } from '../source-models'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
@ -31,7 +22,6 @@ import { newKnowledgeDetailPath } from '../../routes'
|
||||
import { WebsiteDatasourceParameterForm } from '../setup/datasource-parameter-form'
|
||||
import {
|
||||
datasourceIncludeSubpages,
|
||||
datasourceParameterDefaults,
|
||||
invalidDatasourceParameters,
|
||||
missingRequiredDatasourceParameters,
|
||||
websiteDatasourceParameterSchemas,
|
||||
@ -76,8 +66,6 @@ type PreviewDraft = {
|
||||
source?: Source
|
||||
}
|
||||
|
||||
type PendingNavigation = { type: 'back' } | { href: string; type: 'push' }
|
||||
|
||||
function previewPagesEqual(left: PreviewPage, right: PreviewPage) {
|
||||
return (
|
||||
left.pageId === right.pageId &&
|
||||
@ -459,10 +447,6 @@ export function WebsiteCrawlPreview({
|
||||
: websiteDatasourceParameterSchemas(),
|
||||
[providerOption],
|
||||
)
|
||||
const defaultParameters = useMemo(
|
||||
() => datasourceParameterDefaults(parameterSchemas),
|
||||
[parameterSchemas],
|
||||
)
|
||||
const [parameters, setParameters] = useState<DatasourceParameters>(() => {
|
||||
const initialParameters = withDatasourceParameterDefaults(
|
||||
parameterSchemas,
|
||||
@ -485,7 +469,6 @@ export function WebsiteCrawlPreview({
|
||||
return initialParameters
|
||||
})
|
||||
const [sourceName, setSourceName] = useState(initialDraft?.sourceName ?? '')
|
||||
const initialSyncPolicyRef = useRef(initialSyncPolicyValue(initialDraft))
|
||||
const syncPolicy = initialSyncPolicyValue(initialDraft)
|
||||
const [run, setRun] = useState<SourceWorkflowRun>()
|
||||
const [pages, setPages] = useState<PreviewPage[]>([])
|
||||
@ -495,7 +478,6 @@ export function WebsiteCrawlPreview({
|
||||
const [stopping, setStopping] = useState(false)
|
||||
const [pollPaused, setPollPaused] = useState(false)
|
||||
const [requestError, setRequestError] = useState<string>()
|
||||
const [cancelConfirmationOpen, setCancelConfirmationOpen] = useState(false)
|
||||
const [discarding, setDiscarding] = useState(false)
|
||||
const [discardError, setDiscardError] = useState(false)
|
||||
const [workflowUncertain, setWorkflowUncertain] = useState(false)
|
||||
@ -521,9 +503,6 @@ export function WebsiteCrawlPreview({
|
||||
const uncertainWorkflowRef = useRef<(() => Promise<SourceWorkflowRun | undefined>) | undefined>(
|
||||
undefined,
|
||||
)
|
||||
const pendingNavigationRef = useRef<PendingNavigation | undefined>(undefined)
|
||||
const historyGuardRef = useRef<string | undefined>(undefined)
|
||||
const historyGuardCompletionRef = useRef<(() => void) | undefined>(undefined)
|
||||
|
||||
const deletePreviewDraftSource = useCallback(
|
||||
async (draft: PreviewDraft) => {
|
||||
@ -655,12 +634,6 @@ export function WebsiteCrawlPreview({
|
||||
)
|
||||
const runId = run?.id
|
||||
const locked = starting || stopping || active || uncertainOperation || selectionInteractionLocked
|
||||
const dirty = Boolean(
|
||||
sourceName ||
|
||||
run ||
|
||||
JSON.stringify(parameters) !== JSON.stringify(defaultParameters) ||
|
||||
JSON.stringify(syncPolicy) !== JSON.stringify(initialSyncPolicyRef.current),
|
||||
)
|
||||
const host = normalizedURL?.host ?? providerName
|
||||
const completedCount = Math.max(run?.progressCompleted ?? 0, pages.length)
|
||||
const crawlingStatusText = t(($) => $['newKnowledge.crawlingPages'], {
|
||||
@ -704,110 +677,6 @@ export function WebsiteCrawlPreview({
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!dirty) return
|
||||
const preventUnsavedUnload = (event: BeforeUnloadEvent) => {
|
||||
if (submittedRef.current) return
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
}
|
||||
window.addEventListener('beforeunload', preventUnsavedUnload)
|
||||
return () => window.removeEventListener('beforeunload', preventUnsavedUnload)
|
||||
}, [dirty])
|
||||
|
||||
useEffect(() => {
|
||||
if ((!dirty && !historyGuardRef.current) || submittedRef.current) return
|
||||
const guardId = historyGuardRef.current ?? createRequestId()
|
||||
const currentState =
|
||||
window.history.state && typeof window.history.state === 'object' ? window.history.state : {}
|
||||
if (!historyGuardRef.current) {
|
||||
window.history.pushState(
|
||||
{ ...currentState, difyUnsavedSourceGuard: guardId },
|
||||
'',
|
||||
location.href,
|
||||
)
|
||||
historyGuardRef.current = guardId
|
||||
}
|
||||
|
||||
const handlePopState = () => {
|
||||
if (submittedRef.current) {
|
||||
historyGuardRef.current = undefined
|
||||
const complete = historyGuardCompletionRef.current
|
||||
historyGuardCompletionRef.current = undefined
|
||||
complete?.()
|
||||
return
|
||||
}
|
||||
if (!dirty) {
|
||||
window.removeEventListener('popstate', handlePopState)
|
||||
historyGuardRef.current = undefined
|
||||
window.history.back()
|
||||
return
|
||||
}
|
||||
window.history.pushState(
|
||||
{ ...currentState, difyUnsavedSourceGuard: guardId },
|
||||
'',
|
||||
location.href,
|
||||
)
|
||||
pendingNavigationRef.current = { type: 'back' }
|
||||
setDiscardError(false)
|
||||
setCancelConfirmationOpen(true)
|
||||
}
|
||||
const handleLinkClick = (event: MouseEvent) => {
|
||||
if (
|
||||
event.defaultPrevented ||
|
||||
event.button !== 0 ||
|
||||
event.metaKey ||
|
||||
event.ctrlKey ||
|
||||
event.shiftKey ||
|
||||
event.altKey
|
||||
)
|
||||
return
|
||||
const target = event.target
|
||||
if (!(target instanceof Element)) return
|
||||
const anchor = target.closest('a[href]')
|
||||
if (
|
||||
!(anchor instanceof HTMLAnchorElement) ||
|
||||
anchor.target === '_blank' ||
|
||||
anchor.hasAttribute('download')
|
||||
)
|
||||
return
|
||||
const destination = new URL(anchor.href, location.href)
|
||||
if (destination.origin !== location.origin || destination.href === location.href) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (!dirty) {
|
||||
submittedRef.current = true
|
||||
onDraftFinished?.()
|
||||
historyGuardCompletionRef.current = () =>
|
||||
router.push(`${destination.pathname}${destination.search}${destination.hash}`)
|
||||
window.history.back()
|
||||
return
|
||||
}
|
||||
pendingNavigationRef.current = {
|
||||
href: `${destination.pathname}${destination.search}${destination.hash}`,
|
||||
type: 'push',
|
||||
}
|
||||
setDiscardError(false)
|
||||
setCancelConfirmationOpen(true)
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', handlePopState)
|
||||
document.addEventListener('click', handleLinkClick, true)
|
||||
return () => {
|
||||
window.removeEventListener('popstate', handlePopState)
|
||||
document.removeEventListener('click', handleLinkClick, true)
|
||||
}
|
||||
}, [dirty, onDraftFinished, router])
|
||||
|
||||
const leaveHistoryGuard = useCallback((complete: () => void) => {
|
||||
if (!historyGuardRef.current) {
|
||||
complete()
|
||||
return
|
||||
}
|
||||
historyGuardCompletionRef.current = complete
|
||||
window.history.back()
|
||||
}, [])
|
||||
|
||||
const ensureProvisionalSource = useCallback(
|
||||
async (nextConfiguration: CrawlConfiguration) => {
|
||||
const nextConfigurationKey = configurationKey(nextConfiguration)
|
||||
@ -1253,18 +1122,6 @@ export function WebsiteCrawlPreview({
|
||||
(run ? ['timed_out', 'timeout'].includes(normalizedState(run.state)) : false)
|
||||
const isProviderError = errorCode?.toUpperCase().includes('PROVIDER')
|
||||
|
||||
const cancel = () => {
|
||||
if (dirty) {
|
||||
pendingNavigationRef.current = undefined
|
||||
setDiscardError(false)
|
||||
setCancelConfirmationOpen(true)
|
||||
return
|
||||
}
|
||||
submittedRef.current = true
|
||||
onDraftFinished?.()
|
||||
leaveHistoryGuard(() => router.push(newKnowledgeDetailPath(knowledgeSpaceId)))
|
||||
}
|
||||
|
||||
const discardAndCancel = async () => {
|
||||
if (discarding) return
|
||||
setDiscarding(true)
|
||||
@ -1314,28 +1171,10 @@ export function WebsiteCrawlPreview({
|
||||
retryPredecessorRef.current = undefined
|
||||
submittedRef.current = true
|
||||
onDraftFinished?.()
|
||||
setCancelConfirmationOpen(false)
|
||||
const pendingNavigation = pendingNavigationRef.current
|
||||
pendingNavigationRef.current = undefined
|
||||
leaveHistoryGuard(() => {
|
||||
if (pendingNavigation?.type === 'back') window.history.back()
|
||||
else
|
||||
router.push(
|
||||
pendingNavigation?.type === 'push'
|
||||
? pendingNavigation.href
|
||||
: newKnowledgeDetailPath(knowledgeSpaceId),
|
||||
)
|
||||
})
|
||||
router.push(newKnowledgeDetailPath(knowledgeSpaceId))
|
||||
}
|
||||
|
||||
const handleCancelConfirmationOpenChange = (open: boolean) => {
|
||||
if (discarding) return
|
||||
setCancelConfirmationOpen(open)
|
||||
if (!open) {
|
||||
pendingNavigationRef.current = undefined
|
||||
setDiscardError(false)
|
||||
}
|
||||
}
|
||||
const cancel = () => void discardAndCancel()
|
||||
|
||||
return (
|
||||
<section aria-label={t(($) => $['newKnowledge.crawlAndPreview'])}>
|
||||
@ -1439,7 +1278,7 @@ export function WebsiteCrawlPreview({
|
||||
)}
|
||||
{showSuccess && run && draftRef.current?.source && configuration && (
|
||||
<CrawlSelectionForm
|
||||
busy={starting || stopping}
|
||||
busy={starting || stopping || discarding}
|
||||
discardRequested={() => discardRequestedRef.current}
|
||||
initialSyncMode={
|
||||
initialDraft?.syncPolicy === 'daily' ? 'interval' : initialDraft?.syncPolicy
|
||||
@ -1450,14 +1289,10 @@ export function WebsiteCrawlPreview({
|
||||
onInteractionLockChange={setSelectionInteractionLocked}
|
||||
onRecrawl={handlePrimaryAction}
|
||||
onSubmissionUncertainChange={updateSelectionUncertain}
|
||||
onSubmitted={() =>
|
||||
new Promise<void>((resolve) => {
|
||||
pendingNavigationRef.current = undefined
|
||||
submittedRef.current = true
|
||||
onDraftFinished?.()
|
||||
leaveHistoryGuard(resolve)
|
||||
})
|
||||
}
|
||||
onSubmitted={() => {
|
||||
submittedRef.current = true
|
||||
onDraftFinished?.()
|
||||
}}
|
||||
onWorkflowPending={trackPendingWorkflow}
|
||||
onWorkflowRun={(nextRun) => {
|
||||
pendingCancelRunRef.current = nextRun
|
||||
@ -1531,7 +1366,7 @@ export function WebsiteCrawlPreview({
|
||||
{!showSuccess && syncPolicyField && <div className="mt-4">{syncPolicyField}</div>}
|
||||
{!showSuccess && (
|
||||
<div className="mt-5 flex justify-end gap-3 border-t border-divider-subtle pt-4.75">
|
||||
<Button type="button" onClick={cancel}>
|
||||
<Button type="button" disabled={discarding} loading={discarding} onClick={cancel}>
|
||||
{t(($) => $['newKnowledge.cancelAddSource'])}
|
||||
</Button>
|
||||
<span id="add-source-selection-requirement" className="sr-only">
|
||||
@ -1542,35 +1377,11 @@ export function WebsiteCrawlPreview({
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<AlertDialog open={cancelConfirmationOpen} onOpenChange={handleCancelConfirmationOpenChange}>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 p-6 pb-4">
|
||||
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['newKnowledge.discardSourceChanges'])}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.discardSourceChangesDescription'])}
|
||||
</AlertDialogDescription>
|
||||
{discardError && (
|
||||
<p role="alert" className="mt-3 system-sm-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.crawlFailedDescription'])}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton disabled={discarding}>
|
||||
{t(($) => $['newKnowledge.keepEditing'])}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
disabled={discarding}
|
||||
loading={discarding}
|
||||
onClick={() => void discardAndCancel()}
|
||||
>
|
||||
{t(($) => $['newKnowledge.discardSourceChangesConfirm'])}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
{discardError && requestError !== 'CANCEL_FAILED' && (
|
||||
<p role="alert" className="mt-3 system-sm-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.crawlFailedDescription'])}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "لم نتمكن من تحميل قاعدة المعرفة هذه. حاول ثانية.",
|
||||
"newKnowledge.detailErrorTitle": "حدث خطأ ما",
|
||||
"newKnowledge.disableSource": "تعطيل",
|
||||
"newKnowledge.discardDraftConfirm": "تجاهل المسودة",
|
||||
"newKnowledge.discardDraftDescription": "ستُفقد تغييراتك على مسودة قاعدة المعرفة هذه.",
|
||||
"newKnowledge.discardDraftTitle": "تجاهل مسودة قاعدة المعرفة هذه؟",
|
||||
"newKnowledge.discardSourceChanges": "هل تريد تجاهل تغييرات المصدر؟",
|
||||
"newKnowledge.discardSourceChangesConfirm": "تجاهل التغييرات",
|
||||
"newKnowledge.discardSourceChangesDescription": "ستفقد معاينة الزحف وتحديد الصفحات وإعدادات المزامنة.",
|
||||
"newKnowledge.discardSourceDraftDescription": "ستتم إزالة إعداد المصدر ومعاينة الزحف.",
|
||||
"newKnowledge.discardSourceDraftFailed": "تعذّر تجاهل مسودة المصدر. أعد المحاولة.",
|
||||
"newKnowledge.discardSourceDraftTitle": "هل تريد تجاهل مسودة المصدر هذه؟",
|
||||
"newKnowledge.documentActions": "إجراءات {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "إجراءات المستند غير متاحة لقاعدة المعرفة هذه حتى الآن.",
|
||||
"newKnowledge.documentChunksEmpty": "لا توجد مقاطع متاحة لهذه المراجعة بعد.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "إيقاف",
|
||||
"newKnowledge.invalidDatasourceParameter": "أدخل قيمة صالحة لـ {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "أدخل عنوان URL صالحًا يبدأ بـ http(s).",
|
||||
"newKnowledge.keepEditing": "متابعة التحرير",
|
||||
"newKnowledge.lastIndexed": "آخر فهرسة",
|
||||
"newKnowledge.lastReadyRevisionHint": "لا تؤدي المعالجة الفاشلة إلى مقاطعة الاسترداد مطلقًا، حيث تظل آخر مراجعة جاهزة متاحة في فهرس المحتوى.",
|
||||
"newKnowledge.lastSyncColumn": "آخر مزامنة",
|
||||
"newKnowledge.learnMore": "معرفة المزيد",
|
||||
"newKnowledge.leavePartialSetupConfirm": "مغادرة الإعداد",
|
||||
"newKnowledge.leavePartialSetupDescription": "تم إنشاء قاعدة المعرفة، لكن تعذّر تأكيد أذوناتها. عند المغادرة سيتم الاحتفاظ بقاعدة المعرفة؛ يمكنك مراجعة الوصول لاحقًا.",
|
||||
"newKnowledge.leavePartialSetupTitle": "مغادرة الإعداد غير المكتمل؟",
|
||||
"newKnowledge.legacy": "قديم",
|
||||
"newKnowledge.loadMore": "تحميل المزيد",
|
||||
"newKnowledge.loadMoreRevisions": "تحميل مراجعات أقدم",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Diese Wissensdatenbank konnte nicht geladen werden. Versuchen Sie es erneut.",
|
||||
"newKnowledge.detailErrorTitle": "Etwas ist schief gelaufen",
|
||||
"newKnowledge.disableSource": "Deaktivieren",
|
||||
"newKnowledge.discardDraftConfirm": "Entwurf verwerfen",
|
||||
"newKnowledge.discardDraftDescription": "Ihre Änderungen an diesem Wissensdatenbank-Entwurf gehen verloren.",
|
||||
"newKnowledge.discardDraftTitle": "Diesen Wissensdatenbank-Entwurf verwerfen?",
|
||||
"newKnowledge.discardSourceChanges": "Änderungen an der Quelle verwerfen?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Änderungen verwerfen",
|
||||
"newKnowledge.discardSourceChangesDescription": "Crawl-Vorschau, Seitenauswahl und Synchronisierungseinstellungen gehen verloren.",
|
||||
"newKnowledge.discardSourceDraftDescription": "Die Quellenkonfiguration und die Crawl-Vorschau werden entfernt.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Der Quellenentwurf konnte nicht verworfen werden. Versuche es erneut.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Diesen Quellenentwurf verwerfen?",
|
||||
"newKnowledge.documentActions": "Aktionen für {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Für diese Wissensdatenbank sind noch keine Dokumentaktionen verfügbar.",
|
||||
"newKnowledge.documentChunksEmpty": "Für diese Revision sind noch keine Abschnitte verfügbar.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Unterbrechen",
|
||||
"newKnowledge.invalidDatasourceParameter": "Gib einen gültigen Wert für {{parameter}} ein.",
|
||||
"newKnowledge.invalidRootUrl": "Gib eine gültige http(s)-URL ein.",
|
||||
"newKnowledge.keepEditing": "Weiter bearbeiten",
|
||||
"newKnowledge.lastIndexed": "Zuletzt indexiert",
|
||||
"newKnowledge.lastReadyRevisionHint": "Eine fehlgeschlagene Verarbeitung unterbricht nie den Abruf – die letzte fertige Revision bleibt im Inhaltsindex verfügbar.",
|
||||
"newKnowledge.lastSyncColumn": "Letzte Synchronisierung",
|
||||
"newKnowledge.learnMore": "Mehr erfahren",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Einrichtung verlassen",
|
||||
"newKnowledge.leavePartialSetupDescription": "Die Wissensdatenbank wurde erstellt, aber ihre Berechtigung konnte nicht bestätigt werden. Beim Verlassen bleibt die Wissensdatenbank erhalten; Sie können den Zugriff später überprüfen.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Unvollständige Einrichtung verlassen?",
|
||||
"newKnowledge.legacy": "Legacy",
|
||||
"newKnowledge.loadMore": "Mehr laden",
|
||||
"newKnowledge.loadMoreRevisions": "Ältere Revisionen laden",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "We couldn't load this knowledge base. Try again.",
|
||||
"newKnowledge.detailErrorTitle": "Something went wrong",
|
||||
"newKnowledge.disableSource": "Disable",
|
||||
"newKnowledge.discardDraftConfirm": "Discard draft",
|
||||
"newKnowledge.discardDraftDescription": "Your changes to this knowledge base draft will be lost.",
|
||||
"newKnowledge.discardDraftTitle": "Discard this knowledge base draft?",
|
||||
"newKnowledge.discardSourceChanges": "Discard source changes?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Discard changes",
|
||||
"newKnowledge.discardSourceChangesDescription": "Your crawl preview, page selection, and sync settings will be lost.",
|
||||
"newKnowledge.discardSourceDraftDescription": "The source configuration and crawl preview will be removed.",
|
||||
"newKnowledge.discardSourceDraftFailed": "We couldn’t discard the source draft. Try again.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Discard this source draft?",
|
||||
"newKnowledge.documentActions": "Actions for {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Document actions aren't available for this knowledge base yet.",
|
||||
"newKnowledge.documentChunksEmpty": "No chunks are available for this revision yet.",
|
||||
@ -301,14 +292,10 @@
|
||||
"newKnowledge.interruptTask": "Interrupt",
|
||||
"newKnowledge.invalidDatasourceParameter": "Enter a valid value for {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"newKnowledge.keepEditing": "Keep editing",
|
||||
"newKnowledge.lastIndexed": "Last indexed",
|
||||
"newKnowledge.lastReadyRevisionHint": "Failed processing never interrupts retrieval — the last ready revision stays available in the content index.",
|
||||
"newKnowledge.lastSyncColumn": "Last sync",
|
||||
"newKnowledge.learnMore": "Learn more",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Leave setup",
|
||||
"newKnowledge.leavePartialSetupDescription": "The knowledge base was created, but its permission could not be confirmed. Leaving keeps the knowledge base; you can review access later.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Leave incomplete setup?",
|
||||
"newKnowledge.legacy": "Legacy",
|
||||
"newKnowledge.loadMore": "Load more",
|
||||
"newKnowledge.loadMoreRevisions": "Load older revisions",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "No pudimos cargar esta base de conocimientos. Intentar otra vez.",
|
||||
"newKnowledge.detailErrorTitle": "algo salió mal",
|
||||
"newKnowledge.disableSource": "Desactivar",
|
||||
"newKnowledge.discardDraftConfirm": "Descartar borrador",
|
||||
"newKnowledge.discardDraftDescription": "Se perderán los cambios realizados en este borrador de base de conocimientos.",
|
||||
"newKnowledge.discardDraftTitle": "¿Descartar este borrador de base de conocimientos?",
|
||||
"newKnowledge.discardSourceChanges": "¿Descartar los cambios de la fuente?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Descartar cambios",
|
||||
"newKnowledge.discardSourceChangesDescription": "Se perderán la vista previa del rastreo, la selección de páginas y la configuración de sincronización.",
|
||||
"newKnowledge.discardSourceDraftDescription": "Se eliminarán la configuración de la fuente y la vista previa del rastreo.",
|
||||
"newKnowledge.discardSourceDraftFailed": "No se pudo descartar el borrador de la fuente. Vuelve a intentarlo.",
|
||||
"newKnowledge.discardSourceDraftTitle": "¿Descartar este borrador de fuente?",
|
||||
"newKnowledge.documentActions": "Acciones para {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Las acciones de documentos aún no están disponibles para esta base de conocimientos.",
|
||||
"newKnowledge.documentChunksEmpty": "Aún no hay fragmentos disponibles para esta revisión.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Interrumpir",
|
||||
"newKnowledge.invalidDatasourceParameter": "Introduce un valor válido para {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Introduce una URL http(s) válida.",
|
||||
"newKnowledge.keepEditing": "Seguir editando",
|
||||
"newKnowledge.lastIndexed": "Última indexación",
|
||||
"newKnowledge.lastReadyRevisionHint": "El procesamiento fallido nunca interrumpe la recuperación: la última revisión lista permanece disponible en el índice de contenido.",
|
||||
"newKnowledge.lastSyncColumn": "Última sincronización",
|
||||
"newKnowledge.learnMore": "Más información",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Salir de la configuración",
|
||||
"newKnowledge.leavePartialSetupDescription": "La base de conocimientos se creó, pero no se pudo confirmar su permiso. Si sales, la base de conocimientos se conservará; puedes revisar el acceso más adelante.",
|
||||
"newKnowledge.leavePartialSetupTitle": "¿Salir de la configuración incompleta?",
|
||||
"newKnowledge.legacy": "Anterior",
|
||||
"newKnowledge.loadMore": "Cargar más",
|
||||
"newKnowledge.loadMoreRevisions": "Cargar revisiones anteriores",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "ما نتوانستیم این پایگاه دانش را بارگیری کنیم. دوباره امتحان کنید.",
|
||||
"newKnowledge.detailErrorTitle": "مشکلی پیش آمد",
|
||||
"newKnowledge.disableSource": "غیر فعال کردن",
|
||||
"newKnowledge.discardDraftConfirm": "حذف پیشنویس",
|
||||
"newKnowledge.discardDraftDescription": "تغییرات شما در این پیشنویس پایگاه دانش از بین خواهد رفت.",
|
||||
"newKnowledge.discardDraftTitle": "این پیشنویس پایگاه دانش حذف شود؟",
|
||||
"newKnowledge.discardSourceChanges": "تغییرات منبع کنار گذاشته شود؟",
|
||||
"newKnowledge.discardSourceChangesConfirm": "کنار گذاشتن تغییرات",
|
||||
"newKnowledge.discardSourceChangesDescription": "پیشنمایش خزش، انتخاب صفحهها و تنظیمات همگامسازی از دست میروند.",
|
||||
"newKnowledge.discardSourceDraftDescription": "پیکربندی منبع و پیشنمایش پیمایش حذف خواهند شد.",
|
||||
"newKnowledge.discardSourceDraftFailed": "حذف پیشنویس منبع ممکن نشد. دوباره تلاش کنید.",
|
||||
"newKnowledge.discardSourceDraftTitle": "این پیشنویس منبع حذف شود؟",
|
||||
"newKnowledge.documentActions": "اقدامات برای {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "اقدامات سند هنوز برای این پایگاه دانش در دسترس نیست.",
|
||||
"newKnowledge.documentChunksEmpty": "هنوز قطعهای برای این بازبینی موجود نیست.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "توقف",
|
||||
"newKnowledge.invalidDatasourceParameter": "یک مقدار معتبر برای {{parameter}} وارد کنید.",
|
||||
"newKnowledge.invalidRootUrl": "یک نشانی معتبر http(s) وارد کنید.",
|
||||
"newKnowledge.keepEditing": "ادامه ویرایش",
|
||||
"newKnowledge.lastIndexed": "آخرین نمایهسازی",
|
||||
"newKnowledge.lastReadyRevisionHint": "پردازش ناموفق هرگز بازیابی را قطع نمی کند - آخرین نسخه آماده در فهرست محتوا در دسترس باقی می ماند.",
|
||||
"newKnowledge.lastSyncColumn": "آخرین همگام سازی",
|
||||
"newKnowledge.learnMore": "بیشتر بدانید",
|
||||
"newKnowledge.leavePartialSetupConfirm": "خروج از راهاندازی",
|
||||
"newKnowledge.leavePartialSetupDescription": "پایگاه دانش ایجاد شد، اما مجوز آن تأیید نشد. با خروج، پایگاه دانش حفظ میشود؛ میتوانید بعداً دسترسی را بررسی کنید.",
|
||||
"newKnowledge.leavePartialSetupTitle": "از راهاندازی ناتمام خارج میشوید؟",
|
||||
"newKnowledge.legacy": "قدیمی",
|
||||
"newKnowledge.loadMore": "بارگذاری بیشتر",
|
||||
"newKnowledge.loadMoreRevisions": "بارگیری بازبینیهای قدیمیتر",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Nous n'avons pas pu charger cette base de connaissances. Essayer à nouveau.",
|
||||
"newKnowledge.detailErrorTitle": "Quelque chose s'est mal passé",
|
||||
"newKnowledge.disableSource": "Désactiver",
|
||||
"newKnowledge.discardDraftConfirm": "Abandonner le brouillon",
|
||||
"newKnowledge.discardDraftDescription": "Les modifications apportées à ce brouillon de base de connaissances seront perdues.",
|
||||
"newKnowledge.discardDraftTitle": "Abandonner ce brouillon de base de connaissances ?",
|
||||
"newKnowledge.discardSourceChanges": "Ignorer les modifications de la source ?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Ignorer les modifications",
|
||||
"newKnowledge.discardSourceChangesDescription": "L’aperçu de l’exploration, la sélection des pages et les paramètres de synchronisation seront perdus.",
|
||||
"newKnowledge.discardSourceDraftDescription": "La configuration de la source et l’aperçu de l’exploration seront supprimés.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Impossible d’abandonner le brouillon de la source. Réessayez.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Abandonner ce brouillon de source ?",
|
||||
"newKnowledge.documentActions": "Actions pour {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Les actions sur les documents ne sont pas encore disponibles pour cette base de connaissances.",
|
||||
"newKnowledge.documentChunksEmpty": "Aucun segment n’est encore disponible pour cette révision.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Interrompre",
|
||||
"newKnowledge.invalidDatasourceParameter": "Saisissez une valeur valide pour {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Saisissez une URL http(s) valide.",
|
||||
"newKnowledge.keepEditing": "Continuer la modification",
|
||||
"newKnowledge.lastIndexed": "Dernière indexation",
|
||||
"newKnowledge.lastReadyRevisionHint": "Un traitement échoué n'interrompt jamais la récupération : la dernière révision prête reste disponible dans l'index de contenu.",
|
||||
"newKnowledge.lastSyncColumn": "Dernière synchronisation",
|
||||
"newKnowledge.learnMore": "En savoir plus",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Quitter la configuration",
|
||||
"newKnowledge.leavePartialSetupDescription": "La base de connaissances a été créée, mais son autorisation n’a pas pu être confirmée. Si vous quittez, la base de connaissances sera conservée ; vous pourrez vérifier l’accès plus tard.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Quitter la configuration incomplète ?",
|
||||
"newKnowledge.legacy": "Ancien",
|
||||
"newKnowledge.loadMore": "Charger plus",
|
||||
"newKnowledge.loadMoreRevisions": "Charger les anciennes révisions",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "हम इस ज्ञानकोष को लोड नहीं कर सके. पुनः प्रयास करें।",
|
||||
"newKnowledge.detailErrorTitle": "कुछ ग़लत हो गया",
|
||||
"newKnowledge.disableSource": "अक्षम करें",
|
||||
"newKnowledge.discardDraftConfirm": "ड्राफ़्ट हटाएँ",
|
||||
"newKnowledge.discardDraftDescription": "इस नॉलेज बेस ड्राफ़्ट में किए गए आपके बदलाव खो जाएँगे।",
|
||||
"newKnowledge.discardDraftTitle": "क्या यह नॉलेज बेस ड्राफ़्ट हटाना है?",
|
||||
"newKnowledge.discardSourceChanges": "स्रोत में किए बदलाव छोड़ें?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "बदलाव छोड़ें",
|
||||
"newKnowledge.discardSourceChangesDescription": "क्रॉल पूर्वावलोकन, पेज चयन और सिंक सेटिंग खो जाएँगी।",
|
||||
"newKnowledge.discardSourceDraftDescription": "स्रोत कॉन्फ़िगरेशन और क्रॉल पूर्वावलोकन हटा दिए जाएँगे।",
|
||||
"newKnowledge.discardSourceDraftFailed": "स्रोत ड्राफ़्ट हटाया नहीं जा सका। फिर से प्रयास करें।",
|
||||
"newKnowledge.discardSourceDraftTitle": "यह स्रोत ड्राफ़्ट हटाएँ?",
|
||||
"newKnowledge.documentActions": "{{name}} के लिए क्रियाएँ",
|
||||
"newKnowledge.documentActionsUnavailable": "इस ज्ञानकोष के लिए दस्तावेज़ क्रियाएँ अभी तक उपलब्ध नहीं हैं।",
|
||||
"newKnowledge.documentChunksEmpty": "इस संशोधन के लिए अभी कोई खंड उपलब्ध नहीं है।",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "रोकें",
|
||||
"newKnowledge.invalidDatasourceParameter": "{{parameter}} के लिए मान्य मान दर्ज करें।",
|
||||
"newKnowledge.invalidRootUrl": "मान्य http(s) URL दर्ज करें।",
|
||||
"newKnowledge.keepEditing": "संपादन जारी रखें",
|
||||
"newKnowledge.lastIndexed": "पिछली बार अनुक्रमित",
|
||||
"newKnowledge.lastReadyRevisionHint": "विफल प्रसंस्करण कभी भी पुनर्प्राप्ति को बाधित नहीं करता है - अंतिम तैयार संशोधन सामग्री सूचकांक में उपलब्ध रहता है।",
|
||||
"newKnowledge.lastSyncColumn": "अंतिम सिंक",
|
||||
"newKnowledge.learnMore": "और जानें",
|
||||
"newKnowledge.leavePartialSetupConfirm": "सेटअप छोड़ें",
|
||||
"newKnowledge.leavePartialSetupDescription": "नॉलेज बेस बन गया है, लेकिन उसकी अनुमति की पुष्टि नहीं हो सकी। छोड़ने पर नॉलेज बेस बना रहेगा; आप बाद में एक्सेस की समीक्षा कर सकते हैं।",
|
||||
"newKnowledge.leavePartialSetupTitle": "क्या अधूरा सेटअप छोड़ना है?",
|
||||
"newKnowledge.legacy": "पुराना",
|
||||
"newKnowledge.loadMore": "और लोड करें",
|
||||
"newKnowledge.loadMoreRevisions": "पुराने संशोधन लोड करें",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Kami tidak dapat memuat basis pengetahuan ini. Coba lagi.",
|
||||
"newKnowledge.detailErrorTitle": "Ada yang tidak beres",
|
||||
"newKnowledge.disableSource": "Nonaktifkan",
|
||||
"newKnowledge.discardDraftConfirm": "Buang draf",
|
||||
"newKnowledge.discardDraftDescription": "Perubahan pada draf basis pengetahuan ini akan hilang.",
|
||||
"newKnowledge.discardDraftTitle": "Buang draf basis pengetahuan ini?",
|
||||
"newKnowledge.discardSourceChanges": "Buang perubahan sumber?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Buang perubahan",
|
||||
"newKnowledge.discardSourceChangesDescription": "Pratinjau crawl, pilihan halaman, dan pengaturan sinkronisasi akan hilang.",
|
||||
"newKnowledge.discardSourceDraftDescription": "Konfigurasi sumber dan pratinjau perayapan akan dihapus.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Draf sumber tidak dapat dibuang. Coba lagi.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Buang draf sumber ini?",
|
||||
"newKnowledge.documentActions": "Tindakan untuk {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Tindakan dokumen belum tersedia untuk basis pengetahuan ini.",
|
||||
"newKnowledge.documentChunksEmpty": "Belum ada potongan yang tersedia untuk revisi ini.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Hentikan",
|
||||
"newKnowledge.invalidDatasourceParameter": "Masukkan nilai yang valid untuk {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Masukkan URL http(s) yang valid.",
|
||||
"newKnowledge.keepEditing": "Lanjutkan mengedit",
|
||||
"newKnowledge.lastIndexed": "Terakhir diindeks",
|
||||
"newKnowledge.lastReadyRevisionHint": "Pemrosesan yang gagal tidak pernah mengganggu pengambilan — revisi terakhir yang siap tetap tersedia di indeks konten.",
|
||||
"newKnowledge.lastSyncColumn": "Sinkronisasi terakhir",
|
||||
"newKnowledge.learnMore": "Pelajari lebih lanjut",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Keluar dari penyiapan",
|
||||
"newKnowledge.leavePartialSetupDescription": "Basis pengetahuan telah dibuat, tetapi izinnya tidak dapat dikonfirmasi. Jika keluar, basis pengetahuan akan tetap tersimpan; Anda dapat meninjau akses nanti.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Keluar dari penyiapan yang belum selesai?",
|
||||
"newKnowledge.legacy": "Lama",
|
||||
"newKnowledge.loadMore": "Muat lebih banyak",
|
||||
"newKnowledge.loadMoreRevisions": "Muat revisi lama",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Impossibile caricare questa knowledge base. Riprova.",
|
||||
"newKnowledge.detailErrorTitle": "Qualcosa è andato storto",
|
||||
"newKnowledge.disableSource": "Disabilita",
|
||||
"newKnowledge.discardDraftConfirm": "Scarta bozza",
|
||||
"newKnowledge.discardDraftDescription": "Le modifiche a questa bozza della knowledge base andranno perse.",
|
||||
"newKnowledge.discardDraftTitle": "Scartare questa bozza della knowledge base?",
|
||||
"newKnowledge.discardSourceChanges": "Ignorare le modifiche all’origine?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Ignora modifiche",
|
||||
"newKnowledge.discardSourceChangesDescription": "L’anteprima della scansione, la selezione delle pagine e le impostazioni di sincronizzazione andranno perse.",
|
||||
"newKnowledge.discardSourceDraftDescription": "La configurazione della fonte e l’anteprima della scansione verranno rimosse.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Impossibile eliminare la bozza della fonte. Riprova.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Eliminare questa bozza della fonte?",
|
||||
"newKnowledge.documentActions": "Azioni per {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Le azioni del documento non sono ancora disponibili per questa knowledge base.",
|
||||
"newKnowledge.documentChunksEmpty": "Non sono ancora disponibili segmenti per questa revisione.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Interrompere",
|
||||
"newKnowledge.invalidDatasourceParameter": "Inserisci un valore valido per {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Inserisci un URL http(s) valido.",
|
||||
"newKnowledge.keepEditing": "Continua a modificare",
|
||||
"newKnowledge.lastIndexed": "Ultima indicizzazione",
|
||||
"newKnowledge.lastReadyRevisionHint": "L'elaborazione non riuscita non interrompe mai il recupero: l'ultima revisione pronta rimane disponibile nell'indice del contenuto.",
|
||||
"newKnowledge.lastSyncColumn": "Ultima sincronizzazione",
|
||||
"newKnowledge.learnMore": "Scopri di più",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Esci dalla configurazione",
|
||||
"newKnowledge.leavePartialSetupDescription": "La knowledge base è stata creata, ma non è stato possibile confermarne l’autorizzazione. Uscendo, la knowledge base verrà mantenuta; potrai verificare l’accesso in seguito.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Uscire dalla configurazione incompleta?",
|
||||
"newKnowledge.legacy": "Legacy",
|
||||
"newKnowledge.loadMore": "Carica altro",
|
||||
"newKnowledge.loadMoreRevisions": "Carica revisioni precedenti",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "このナレッジベースをロードできませんでした。もう一度やり直してください。",
|
||||
"newKnowledge.detailErrorTitle": "何か問題が発生しました",
|
||||
"newKnowledge.disableSource": "無効にする",
|
||||
"newKnowledge.discardDraftConfirm": "下書きを破棄",
|
||||
"newKnowledge.discardDraftDescription": "このナレッジベースの下書きに加えた変更は失われます。",
|
||||
"newKnowledge.discardDraftTitle": "このナレッジベースの下書きを破棄しますか?",
|
||||
"newKnowledge.discardSourceChanges": "ソースの変更を破棄しますか?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "変更を破棄",
|
||||
"newKnowledge.discardSourceChangesDescription": "クロールのプレビュー、ページの選択、同期設定は失われます。",
|
||||
"newKnowledge.discardSourceDraftDescription": "ソース設定とクロールプレビューは削除されます。",
|
||||
"newKnowledge.discardSourceDraftFailed": "ソースの下書きを破棄できませんでした。もう一度お試しください。",
|
||||
"newKnowledge.discardSourceDraftTitle": "このソースの下書きを破棄しますか?",
|
||||
"newKnowledge.documentActions": "{{name}} のアクション",
|
||||
"newKnowledge.documentActionsUnavailable": "ドキュメント アクションは、このナレッジ ベースではまだ利用できません。",
|
||||
"newKnowledge.documentChunksEmpty": "このリビジョンで利用できるチャンクはまだありません。",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "中断",
|
||||
"newKnowledge.invalidDatasourceParameter": "{{parameter}} に有効な値を入力してください。",
|
||||
"newKnowledge.invalidRootUrl": "有効なhttp(s) URLを入力してください。",
|
||||
"newKnowledge.keepEditing": "編集を続ける",
|
||||
"newKnowledge.lastIndexed": "最終インデックス日時",
|
||||
"newKnowledge.lastReadyRevisionHint": "処理が失敗しても取得が中断されることはありません。準備ができた最後のリビジョンはコンテンツ インデックス内で利用可能なままになります。",
|
||||
"newKnowledge.lastSyncColumn": "最終同期",
|
||||
"newKnowledge.learnMore": "詳細を見る",
|
||||
"newKnowledge.leavePartialSetupConfirm": "セットアップを終了",
|
||||
"newKnowledge.leavePartialSetupDescription": "ナレッジベースは作成されましたが、権限を確認できませんでした。終了してもナレッジベースは保持され、アクセス権は後で確認できます。",
|
||||
"newKnowledge.leavePartialSetupTitle": "未完了のセットアップを終了しますか?",
|
||||
"newKnowledge.legacy": "旧バージョン",
|
||||
"newKnowledge.loadMore": "さらに読み込む",
|
||||
"newKnowledge.loadMoreRevisions": "古いリビジョンを読み込む",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "이 기술 자료를 로드할 수 없습니다. 다시 시도해 보세요.",
|
||||
"newKnowledge.detailErrorTitle": "문제가 발생했습니다.",
|
||||
"newKnowledge.disableSource": "비활성화",
|
||||
"newKnowledge.discardDraftConfirm": "초안 삭제",
|
||||
"newKnowledge.discardDraftDescription": "이 지식 베이스 초안의 변경 사항이 삭제됩니다.",
|
||||
"newKnowledge.discardDraftTitle": "이 지식 베이스 초안을 삭제할까요?",
|
||||
"newKnowledge.discardSourceChanges": "소스 변경 사항을 삭제할까요?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "변경 사항 삭제",
|
||||
"newKnowledge.discardSourceChangesDescription": "크롤링 미리보기, 페이지 선택 및 동기화 설정이 사라집니다.",
|
||||
"newKnowledge.discardSourceDraftDescription": "소스 구성과 크롤링 미리보기가 삭제됩니다.",
|
||||
"newKnowledge.discardSourceDraftFailed": "소스 초안을 삭제할 수 없습니다. 다시 시도하세요.",
|
||||
"newKnowledge.discardSourceDraftTitle": "이 소스 초안을 삭제할까요?",
|
||||
"newKnowledge.documentActions": "{{name}}에 대한 작업",
|
||||
"newKnowledge.documentActionsUnavailable": "이 기술 자료에는 아직 문서 작업을 사용할 수 없습니다.",
|
||||
"newKnowledge.documentChunksEmpty": "이 리비전에 사용할 수 있는 청크가 아직 없습니다.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "중단",
|
||||
"newKnowledge.invalidDatasourceParameter": "{{parameter}}에 유효한 값을 입력하세요.",
|
||||
"newKnowledge.invalidRootUrl": "유효한 http(s) URL을 입력하세요.",
|
||||
"newKnowledge.keepEditing": "계속 편집",
|
||||
"newKnowledge.lastIndexed": "마지막 인덱싱",
|
||||
"newKnowledge.lastReadyRevisionHint": "실패한 처리는 검색을 중단하지 않습니다. 마지막으로 준비된 개정은 콘텐츠 인덱스에서 계속 사용할 수 있습니다.",
|
||||
"newKnowledge.lastSyncColumn": "마지막 동기화",
|
||||
"newKnowledge.learnMore": "자세히 알아보기",
|
||||
"newKnowledge.leavePartialSetupConfirm": "설정 나가기",
|
||||
"newKnowledge.leavePartialSetupDescription": "지식 베이스가 생성되었지만 권한을 확인하지 못했습니다. 나가도 지식 베이스는 유지되며 나중에 액세스 권한을 검토할 수 있습니다.",
|
||||
"newKnowledge.leavePartialSetupTitle": "완료되지 않은 설정을 나갈까요?",
|
||||
"newKnowledge.legacy": "레거시",
|
||||
"newKnowledge.loadMore": "더 보기",
|
||||
"newKnowledge.loadMoreRevisions": "이전 리비전 불러오기",
|
||||
|
||||
@ -205,15 +205,6 @@
|
||||
"newKnowledge.detailErrorDescription": "ພວກເຮົາບໍ່ສາມາດໂຫຼດຖານຄວາມຮູ້ນີ້ໄດ້. ລອງໃໝ່ອີກຄັ້ງ.",
|
||||
"newKnowledge.detailErrorTitle": "ມີບາງຢ່າງຜິດພາດ",
|
||||
"newKnowledge.disableSource": "ປິດໃຊ້ງານ",
|
||||
"newKnowledge.discardDraftConfirm": "ຖິ້ມສະບັບຮ່າງ",
|
||||
"newKnowledge.discardDraftDescription": "ການປ່ຽນແປງຂອງທ່ານໃນສະບັບຮ່າງຖານຄວາມຮູ້ນີ້ຈະສູນຫາຍໄປ.",
|
||||
"newKnowledge.discardDraftTitle": "ຖິ້ມສະບັບຮ່າງຖານຄວາມຮູ້ນີ້ຫຼືບໍ່?",
|
||||
"newKnowledge.discardSourceChanges": "ຍົກເລີກການປ່ຽນແປງແຫຼ່ງ?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "ຍົກເລີກການປ່ຽນແປງ",
|
||||
"newKnowledge.discardSourceChangesDescription": "ຕົວຢ່າງການລວບລວມຂໍ້ມູນ, ການເລືອກໜ້າ ແລະການຕັ້ງຄ່າການຊິງຄ໌ຂອງທ່ານຈະເສຍໄປ.",
|
||||
"newKnowledge.discardSourceDraftDescription": "ການກຳນົດຄ່າແຫຼ່ງທີ່ມາ ແລະການສະແດງຕົວຢ່າງການລວບລວມຂໍ້ມູນຈະຖືກລຶບອອກ.",
|
||||
"newKnowledge.discardSourceDraftFailed": "ພວກເຮົາບໍ່ສາມາດຍົກເລີກສະບັບຮ່າງຕົ້ນສະບັບໄດ້. ລອງອີກຄັ້ງ.",
|
||||
"newKnowledge.discardSourceDraftTitle": "ຍົກເລີກສະບັບຮ່າງຕົ້ນສະບັບນີ້ບໍ?",
|
||||
"newKnowledge.documentActions": "ຄຳສັ່ງສຳລັບ {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "ການດໍາເນີນການເອກະສານແມ່ນຍັງບໍ່ທັນມີໃຫ້ສໍາລັບພື້ນຖານຄວາມຮູ້ນີ້.",
|
||||
"newKnowledge.documentChunksEmpty": "ບໍ່ມີການແກ້ໄຂສໍາລັບການແກ້ໄຂນີ້.",
|
||||
@ -289,14 +280,10 @@
|
||||
"newKnowledge.interruptTask": "ຂັດຂວາງ",
|
||||
"newKnowledge.invalidDatasourceParameter": "ປ້ອນຄ່າທີ່ຖືກຕ້ອງສຳລັບ {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "ລະບຸ URL http(s) ທີ່ຖືກຕ້ອງ.",
|
||||
"newKnowledge.keepEditing": "ສືບຕໍ່ແກ້ໄຂ",
|
||||
"newKnowledge.lastIndexed": "ດັດສະນີຫຼ້າສຸດ",
|
||||
"newKnowledge.lastReadyRevisionHint": "ການປະມວນຜົນທີ່ລົ້ມເຫລວບໍ່ເຄີຍຂັດຂວາງການດຶງຂໍ້ມູນ — ການແກ້ໄຂທີ່ກຽມພ້ອມຫຼ້າສຸດຈະມີຢູ່ໃນດັດຊະນີເນື້ອຫາ.",
|
||||
"newKnowledge.lastSyncColumn": "ຊິງຄ໌ຄັ້ງສຸດທ້າຍ",
|
||||
"newKnowledge.learnMore": "ຮຽນຮູ້ເພີ່ມເຕີມ",
|
||||
"newKnowledge.leavePartialSetupConfirm": "ອອກຈາກການຕັ້ງຄ່າ",
|
||||
"newKnowledge.leavePartialSetupDescription": "ຖານຄວາມຮູ້ຖືກສ້າງຂຶ້ນແລ້ວ, ແຕ່ຍັງບໍ່ສາມາດຢືນຢັນສິດອະນຸຍາດໄດ້. ການອອກຈະຍັງຄົງຮັກສາຖານຄວາມຮູ້ໄວ້; ທ່ານສາມາດກວດສອບສິດການເຂົ້າເຖິງພາຍຫຼັງໄດ້.",
|
||||
"newKnowledge.leavePartialSetupTitle": "ອອກຈາກການຕັ້ງຄ່າທີ່ຍັງບໍ່ສຳເລັດຫຼືບໍ່?",
|
||||
"newKnowledge.legacy": "ແບບເກົ່າ (Legacy)",
|
||||
"newKnowledge.loadMore": "ໂຫຼດເພີ່ມເຕີມ",
|
||||
"newKnowledge.loadMoreRevisions": "ໂຫຼດການດັດແກ້ເກົ່າ",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "We kunnen deze kennisbank niet laden. Probeer het opnieuw.",
|
||||
"newKnowledge.detailErrorTitle": "Er is iets misgegaan",
|
||||
"newKnowledge.disableSource": "Uitschakelen",
|
||||
"newKnowledge.discardDraftConfirm": "Concept verwijderen",
|
||||
"newKnowledge.discardDraftDescription": "Uw wijzigingen aan dit kennisbankconcept gaan verloren.",
|
||||
"newKnowledge.discardDraftTitle": "Dit kennisbankconcept verwijderen?",
|
||||
"newKnowledge.discardSourceChanges": "Wijzigingen aan de bron negeren?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Wijzigingen negeren",
|
||||
"newKnowledge.discardSourceChangesDescription": "De crawlvoorvertoning, paginaselectie en synchronisatie-instellingen gaan verloren.",
|
||||
"newKnowledge.discardSourceDraftDescription": "De bronconfiguratie en het crawlvoorbeeld worden verwijderd.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Het bronconcept kon niet worden verwijderd. Probeer het opnieuw.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Dit bronconcept verwijderen?",
|
||||
"newKnowledge.documentActions": "Acties voor {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Documentacties zijn nog niet beschikbaar voor deze kennisbank.",
|
||||
"newKnowledge.documentChunksEmpty": "Er zijn nog geen fragmenten beschikbaar voor deze revisie.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Onderbreken",
|
||||
"newKnowledge.invalidDatasourceParameter": "Voer een geldige waarde in voor {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Voer een geldige http(s)-URL in.",
|
||||
"newKnowledge.keepEditing": "Doorgaan met bewerken",
|
||||
"newKnowledge.lastIndexed": "Laatst geïndexeerd",
|
||||
"newKnowledge.lastReadyRevisionHint": "Een mislukte verwerking onderbreekt het ophalen nooit; de laatste gereedstaande revisie blijft beschikbaar in de inhoudsindex.",
|
||||
"newKnowledge.lastSyncColumn": "Laatste synchronisatie",
|
||||
"newKnowledge.learnMore": "Meer informatie",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Configuratie verlaten",
|
||||
"newKnowledge.leavePartialSetupDescription": "De kennisbank is gemaakt, maar de machtiging kon niet worden bevestigd. Als u vertrekt, blijft de kennisbank behouden; u kunt de toegang later controleren.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Onvoltooide configuratie verlaten?",
|
||||
"newKnowledge.legacy": "Oud",
|
||||
"newKnowledge.loadMore": "Meer laden",
|
||||
"newKnowledge.loadMoreRevisions": "Oudere revisies laden",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Nie mogliśmy załadować tej bazy wiedzy. Spróbuj ponownie.",
|
||||
"newKnowledge.detailErrorTitle": "Coś poszło nie tak",
|
||||
"newKnowledge.disableSource": "Wyłącz",
|
||||
"newKnowledge.discardDraftConfirm": "Odrzuć wersję roboczą",
|
||||
"newKnowledge.discardDraftDescription": "Zmiany w tej wersji roboczej bazy wiedzy zostaną utracone.",
|
||||
"newKnowledge.discardDraftTitle": "Odrzucić tę wersję roboczą bazy wiedzy?",
|
||||
"newKnowledge.discardSourceChanges": "Odrzucić zmiany źródła?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Odrzuć zmiany",
|
||||
"newKnowledge.discardSourceChangesDescription": "Podgląd indeksowania, wybór stron i ustawienia synchronizacji zostaną utracone.",
|
||||
"newKnowledge.discardSourceDraftDescription": "Konfiguracja źródła i podgląd skanowania zostaną usunięte.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Nie udało się odrzucić wersji roboczej źródła. Spróbuj ponownie.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Odrzucić tę wersję roboczą źródła?",
|
||||
"newKnowledge.documentActions": "Działania dla {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Działania dotyczące dokumentów nie są jeszcze dostępne dla tej bazy wiedzy.",
|
||||
"newKnowledge.documentChunksEmpty": "Dla tej wersji nie ma jeszcze dostępnych fragmentów.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Przerwij",
|
||||
"newKnowledge.invalidDatasourceParameter": "Wprowadź prawidłową wartość dla {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Wprowadź prawidłowy adres URL http(s).",
|
||||
"newKnowledge.keepEditing": "Kontynuuj edycję",
|
||||
"newKnowledge.lastIndexed": "Ostatnio zindeksowano",
|
||||
"newKnowledge.lastReadyRevisionHint": "Nieudane przetwarzanie nigdy nie przerywa pobierania — ostatnia gotowa wersja pozostaje dostępna w indeksie zawartości.",
|
||||
"newKnowledge.lastSyncColumn": "Ostatnia synchronizacja",
|
||||
"newKnowledge.learnMore": "Dowiedz się więcej",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Opuść konfigurację",
|
||||
"newKnowledge.leavePartialSetupDescription": "Baza wiedzy została utworzona, ale nie udało się potwierdzić jej uprawnień. Po opuszczeniu baza wiedzy zostanie zachowana; dostęp można sprawdzić później.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Opuścić nieukończoną konfigurację?",
|
||||
"newKnowledge.legacy": "Starsza wersja",
|
||||
"newKnowledge.loadMore": "Załaduj więcej",
|
||||
"newKnowledge.loadMoreRevisions": "Wczytaj starsze wersje",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Não foi possível carregar esta base de conhecimento. Tente novamente.",
|
||||
"newKnowledge.detailErrorTitle": "Algo deu errado",
|
||||
"newKnowledge.disableSource": "Desativar",
|
||||
"newKnowledge.discardDraftConfirm": "Descartar rascunho",
|
||||
"newKnowledge.discardDraftDescription": "Suas alterações neste rascunho da base de conhecimento serão perdidas.",
|
||||
"newKnowledge.discardDraftTitle": "Descartar este rascunho da base de conhecimento?",
|
||||
"newKnowledge.discardSourceChanges": "Descartar alterações da fonte?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Descartar alterações",
|
||||
"newKnowledge.discardSourceChangesDescription": "A prévia do rastreamento, a seleção de páginas e as configurações de sincronização serão perdidas.",
|
||||
"newKnowledge.discardSourceDraftDescription": "A configuração da fonte e a visualização do rastreamento serão removidas.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Não foi possível descartar o rascunho da fonte. Tente novamente.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Descartar este rascunho de fonte?",
|
||||
"newKnowledge.documentActions": "Ações para {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "As ações do documento ainda não estão disponíveis para esta base de conhecimento.",
|
||||
"newKnowledge.documentChunksEmpty": "Ainda não há trechos disponíveis para esta revisão.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Interromper",
|
||||
"newKnowledge.invalidDatasourceParameter": "Insira um valor válido para {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Insira uma URL http(s) válida.",
|
||||
"newKnowledge.keepEditing": "Continuar editando",
|
||||
"newKnowledge.lastIndexed": "Última indexação",
|
||||
"newKnowledge.lastReadyRevisionHint": "O processamento com falha nunca interrompe a recuperação — a última revisão pronta permanece disponível no índice de conteúdo.",
|
||||
"newKnowledge.lastSyncColumn": "Última sincronização",
|
||||
"newKnowledge.learnMore": "Saiba mais",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Sair da configuração",
|
||||
"newKnowledge.leavePartialSetupDescription": "A base de conhecimento foi criada, mas não foi possível confirmar sua permissão. Ao sair, a base de conhecimento será mantida; você poderá revisar o acesso depois.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Sair da configuração incompleta?",
|
||||
"newKnowledge.legacy": "Legado",
|
||||
"newKnowledge.loadMore": "Carregar mais",
|
||||
"newKnowledge.loadMoreRevisions": "Carregar revisões anteriores",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Nu am putut încărca această bază de cunoștințe. Încearcă din nou.",
|
||||
"newKnowledge.detailErrorTitle": "Ceva a mers prost",
|
||||
"newKnowledge.disableSource": "Dezactivați",
|
||||
"newKnowledge.discardDraftConfirm": "Renunță la schiță",
|
||||
"newKnowledge.discardDraftDescription": "Modificările aduse acestei schițe a bazei de cunoștințe se vor pierde.",
|
||||
"newKnowledge.discardDraftTitle": "Renunțați la această schiță a bazei de cunoștințe?",
|
||||
"newKnowledge.discardSourceChanges": "Renunți la modificările sursei?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Renunță la modificări",
|
||||
"newKnowledge.discardSourceChangesDescription": "Previzualizarea accesării cu crawlere, selectarea paginilor și setările de sincronizare se vor pierde.",
|
||||
"newKnowledge.discardSourceDraftDescription": "Configurația sursei și previzualizarea explorării vor fi eliminate.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Schița sursei nu a putut fi eliminată. Încearcă din nou.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Elimini această schiță de sursă?",
|
||||
"newKnowledge.documentActions": "Acțiuni pentru {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Acțiunile documentului nu sunt încă disponibile pentru această bază de cunoștințe.",
|
||||
"newKnowledge.documentChunksEmpty": "Nu există încă fragmente disponibile pentru această revizie.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Întrerupe",
|
||||
"newKnowledge.invalidDatasourceParameter": "Introdu o valoare validă pentru {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Introdu o adresă URL http(s) validă.",
|
||||
"newKnowledge.keepEditing": "Continuă editarea",
|
||||
"newKnowledge.lastIndexed": "Ultima indexare",
|
||||
"newKnowledge.lastReadyRevisionHint": "Procesarea eșuată nu întrerupe niciodată recuperarea — ultima revizuire gata rămâne disponibilă în indexul de conținut.",
|
||||
"newKnowledge.lastSyncColumn": "Ultima sincronizare",
|
||||
"newKnowledge.learnMore": "Află mai multe",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Ieși din configurare",
|
||||
"newKnowledge.leavePartialSetupDescription": "Baza de cunoștințe a fost creată, dar permisiunea nu a putut fi confirmată. Dacă ieșiți, baza de cunoștințe va fi păstrată; puteți verifica accesul mai târziu.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Ieșiți din configurarea incompletă?",
|
||||
"newKnowledge.legacy": "Versiune veche",
|
||||
"newKnowledge.loadMore": "Încarcă mai multe",
|
||||
"newKnowledge.loadMoreRevisions": "Încarcă revizii mai vechi",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Нам не удалось загрузить эту базу знаний. Попробуйте еще раз.",
|
||||
"newKnowledge.detailErrorTitle": "Что-то пошло не так",
|
||||
"newKnowledge.disableSource": "Отключить",
|
||||
"newKnowledge.discardDraftConfirm": "Удалить черновик",
|
||||
"newKnowledge.discardDraftDescription": "Изменения в этом черновике базы знаний будут потеряны.",
|
||||
"newKnowledge.discardDraftTitle": "Удалить этот черновик базы знаний?",
|
||||
"newKnowledge.discardSourceChanges": "Отменить изменения источника?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Отменить изменения",
|
||||
"newKnowledge.discardSourceChangesDescription": "Предпросмотр сканирования, выбор страниц и настройки синхронизации будут потеряны.",
|
||||
"newKnowledge.discardSourceDraftDescription": "Конфигурация источника и предварительное сканирование будут удалены.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Не удалось удалить черновик источника. Повторите попытку.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Удалить этот черновик источника?",
|
||||
"newKnowledge.documentActions": "Действия для {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Действия с документами пока недоступны для этой базы знаний.",
|
||||
"newKnowledge.documentChunksEmpty": "Для этой редакции пока нет доступных фрагментов.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Прерывание",
|
||||
"newKnowledge.invalidDatasourceParameter": "Введите допустимое значение для {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Введите корректный URL с http(s).",
|
||||
"newKnowledge.keepEditing": "Продолжить редактирование",
|
||||
"newKnowledge.lastIndexed": "Последняя индексация",
|
||||
"newKnowledge.lastReadyRevisionHint": "Неудачная обработка никогда не прерывает извлечение — последняя готовая редакция остается доступной в индексе контента.",
|
||||
"newKnowledge.lastSyncColumn": "Последняя синхронизация",
|
||||
"newKnowledge.learnMore": "Подробнее",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Выйти из настройки",
|
||||
"newKnowledge.leavePartialSetupDescription": "База знаний создана, но подтвердить её разрешения не удалось. При выходе база знаний сохранится; доступ можно проверить позже.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Выйти из незавершённой настройки?",
|
||||
"newKnowledge.legacy": "Предыдущая версия",
|
||||
"newKnowledge.loadMore": "Загрузить ещё",
|
||||
"newKnowledge.loadMoreRevisions": "Загрузить предыдущие редакции",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Te baze znanja nismo mogli naložiti. Poskusite znova.",
|
||||
"newKnowledge.detailErrorTitle": "Nekaj je šlo narobe",
|
||||
"newKnowledge.disableSource": "Onemogoči",
|
||||
"newKnowledge.discardDraftConfirm": "Zavrzi osnutek",
|
||||
"newKnowledge.discardDraftDescription": "Spremembe tega osnutka zbirke znanja bodo izgubljene.",
|
||||
"newKnowledge.discardDraftTitle": "Želite zavreči ta osnutek zbirke znanja?",
|
||||
"newKnowledge.discardSourceChanges": "Zavreči spremembe vira?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Zavrzi spremembe",
|
||||
"newKnowledge.discardSourceChangesDescription": "Predogled iskanja po vsebini, izbira strani in nastavitve sinhronizacije bodo izgubljeni.",
|
||||
"newKnowledge.discardSourceDraftDescription": "Nastavitev vira in predogled preiskovanja bosta odstranjena.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Osnutka vira ni bilo mogoče zavreči. Poskusite znova.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Želite zavreči ta osnutek vira?",
|
||||
"newKnowledge.documentActions": "Dejanja za {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Dejanja dokumentov še niso na voljo za to bazo znanja.",
|
||||
"newKnowledge.documentChunksEmpty": "Za to različico še ni razpoložljivih delov.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Prekini",
|
||||
"newKnowledge.invalidDatasourceParameter": "Vnesite veljavno vrednost za {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Vnesite veljaven URL http(s).",
|
||||
"newKnowledge.keepEditing": "Nadaljuj urejanje",
|
||||
"newKnowledge.lastIndexed": "Nazadnje indeksirano",
|
||||
"newKnowledge.lastReadyRevisionHint": "Neuspešna obdelava nikoli ne prekine pridobivanja — zadnja pripravljena revizija ostane na voljo v indeksu vsebine.",
|
||||
"newKnowledge.lastSyncColumn": "Zadnja sinhronizacija",
|
||||
"newKnowledge.learnMore": "Več informacij",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Zapusti nastavitev",
|
||||
"newKnowledge.leavePartialSetupDescription": "Zbirka znanja je bila ustvarjena, vendar njenega dovoljenja ni bilo mogoče potrditi. Če zapustite nastavitev, bo zbirka znanja ohranjena; dostop lahko preverite pozneje.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Želite zapustiti nedokončano nastavitev?",
|
||||
"newKnowledge.legacy": "Staro",
|
||||
"newKnowledge.loadMore": "Naloži več",
|
||||
"newKnowledge.loadMoreRevisions": "Naloži starejše različice",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "เราไม่สามารถโหลดฐานความรู้นี้ได้ ลองอีกครั้ง",
|
||||
"newKnowledge.detailErrorTitle": "มีบางอย่างผิดพลาด",
|
||||
"newKnowledge.disableSource": "ปิดการใช้งาน",
|
||||
"newKnowledge.discardDraftConfirm": "ละทิ้งฉบับร่าง",
|
||||
"newKnowledge.discardDraftDescription": "การเปลี่ยนแปลงในฉบับร่างฐานความรู้นี้จะสูญหาย",
|
||||
"newKnowledge.discardDraftTitle": "ละทิ้งฉบับร่างฐานความรู้นี้หรือไม่?",
|
||||
"newKnowledge.discardSourceChanges": "ละทิ้งการเปลี่ยนแปลงแหล่งข้อมูลหรือไม่",
|
||||
"newKnowledge.discardSourceChangesConfirm": "ละทิ้งการเปลี่ยนแปลง",
|
||||
"newKnowledge.discardSourceChangesDescription": "ตัวอย่างการรวบรวมข้อมูล การเลือกหน้า และการตั้งค่าการซิงค์จะสูญหาย",
|
||||
"newKnowledge.discardSourceDraftDescription": "การกำหนดค่าแหล่งข้อมูลและตัวอย่างการรวบรวมข้อมูลจะถูกลบ",
|
||||
"newKnowledge.discardSourceDraftFailed": "ไม่สามารถละทิ้งฉบับร่างแหล่งข้อมูลได้ ลองอีกครั้ง",
|
||||
"newKnowledge.discardSourceDraftTitle": "ละทิ้งฉบับร่างแหล่งข้อมูลนี้หรือไม่",
|
||||
"newKnowledge.documentActions": "การดำเนินการสำหรับ {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "การดำเนินการกับเอกสารยังไม่พร้อมใช้งานสำหรับฐานความรู้นี้",
|
||||
"newKnowledge.documentChunksEmpty": "ยังไม่มีส่วนข้อมูลสำหรับรุ่นแก้ไขนี้",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "ขัดจังหวะ",
|
||||
"newKnowledge.invalidDatasourceParameter": "ป้อนค่าที่ถูกต้องสำหรับ {{parameter}}",
|
||||
"newKnowledge.invalidRootUrl": "ป้อน URL http(s) ที่ถูกต้อง",
|
||||
"newKnowledge.keepEditing": "แก้ไขต่อ",
|
||||
"newKnowledge.lastIndexed": "จัดทำดัชนีล่าสุด",
|
||||
"newKnowledge.lastReadyRevisionHint": "การประมวลผลที่ล้มเหลวจะไม่ขัดจังหวะการดึงข้อมูล — การแก้ไขที่พร้อมใช้ครั้งล่าสุดจะยังคงอยู่ในดัชนีเนื้อหา",
|
||||
"newKnowledge.lastSyncColumn": "ซิงค์ครั้งล่าสุด",
|
||||
"newKnowledge.learnMore": "เรียนรู้เพิ่มเติม",
|
||||
"newKnowledge.leavePartialSetupConfirm": "ออกจากการตั้งค่า",
|
||||
"newKnowledge.leavePartialSetupDescription": "สร้างฐานความรู้แล้ว แต่ไม่สามารถยืนยันสิทธิ์ได้ หากออก ฐานความรู้จะยังคงอยู่ และคุณสามารถตรวจสอบการเข้าถึงภายหลังได้",
|
||||
"newKnowledge.leavePartialSetupTitle": "ออกจากการตั้งค่าที่ยังไม่เสร็จหรือไม่?",
|
||||
"newKnowledge.legacy": "แบบเดิม",
|
||||
"newKnowledge.loadMore": "โหลดเพิ่มเติม",
|
||||
"newKnowledge.loadMoreRevisions": "โหลดรุ่นแก้ไขก่อนหน้า",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Bu bilgi tabanını yükleyemedik. Tekrar deneyin.",
|
||||
"newKnowledge.detailErrorTitle": "Bir şeyler ters gitti",
|
||||
"newKnowledge.disableSource": "Devre Dışı Bırak",
|
||||
"newKnowledge.discardDraftConfirm": "Taslağı sil",
|
||||
"newKnowledge.discardDraftDescription": "Bu bilgi tabanı taslağındaki değişiklikleriniz kaybolacak.",
|
||||
"newKnowledge.discardDraftTitle": "Bu bilgi tabanı taslağı silinsin mi?",
|
||||
"newKnowledge.discardSourceChanges": "Kaynak değişiklikleri silinsin mi?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Değişiklikleri sil",
|
||||
"newKnowledge.discardSourceChangesDescription": "Tarama önizlemesi, sayfa seçimi ve eşitleme ayarları kaybolacak.",
|
||||
"newKnowledge.discardSourceDraftDescription": "Kaynak yapılandırması ve tarama önizlemesi kaldırılacak.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Kaynak taslağı silinemedi. Yeniden deneyin.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Bu kaynak taslağı silinsin mi?",
|
||||
"newKnowledge.documentActions": "{{name}} için eylemler",
|
||||
"newKnowledge.documentActionsUnavailable": "Belge eylemleri henüz bu bilgi tabanında kullanılamıyor.",
|
||||
"newKnowledge.documentChunksEmpty": "Bu revizyon için henüz parça yok.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Kesinti",
|
||||
"newKnowledge.invalidDatasourceParameter": "{{parameter}} için geçerli bir değer girin.",
|
||||
"newKnowledge.invalidRootUrl": "Geçerli bir http(s) URL’si girin.",
|
||||
"newKnowledge.keepEditing": "Düzenlemeye devam et",
|
||||
"newKnowledge.lastIndexed": "Son dizinleme",
|
||||
"newKnowledge.lastReadyRevisionHint": "Başarısız işleme hiçbir zaman alımı kesintiye uğratmaz; son hazır revizyon içerik dizininde mevcut kalır.",
|
||||
"newKnowledge.lastSyncColumn": "Son senkronizasyon",
|
||||
"newKnowledge.learnMore": "Daha fazla bilgi",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Kurulumdan çık",
|
||||
"newKnowledge.leavePartialSetupDescription": "Bilgi tabanı oluşturuldu ancak izni doğrulanamadı. Çıkarsanız bilgi tabanı korunur; erişimi daha sonra gözden geçirebilirsiniz.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Tamamlanmamış kurulumdan çıkılsın mı?",
|
||||
"newKnowledge.legacy": "Eski",
|
||||
"newKnowledge.loadMore": "Daha fazla yükle",
|
||||
"newKnowledge.loadMoreRevisions": "Eski revizyonları yükle",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Нам не вдалося завантажити цю базу знань. Спробуйте знову.",
|
||||
"newKnowledge.detailErrorTitle": "Щось пішло не так",
|
||||
"newKnowledge.disableSource": "Вимкнути",
|
||||
"newKnowledge.discardDraftConfirm": "Відхилити чернетку",
|
||||
"newKnowledge.discardDraftDescription": "Зміни в цій чернетці бази знань буде втрачено.",
|
||||
"newKnowledge.discardDraftTitle": "Відхилити цю чернетку бази знань?",
|
||||
"newKnowledge.discardSourceChanges": "Відхилити зміни джерела?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Відхилити зміни",
|
||||
"newKnowledge.discardSourceChangesDescription": "Попередній перегляд сканування, вибір сторінок і налаштування синхронізації буде втрачено.",
|
||||
"newKnowledge.discardSourceDraftDescription": "Конфігурацію джерела й попередній перегляд сканування буде видалено.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Не вдалося відхилити чернетку джерела. Повторіть спробу.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Відхилити цю чернетку джерела?",
|
||||
"newKnowledge.documentActions": "Дії для {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Дії з документами поки що недоступні для цієї бази знань.",
|
||||
"newKnowledge.documentChunksEmpty": "Для цієї редакції ще немає доступних фрагментів.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Переривати",
|
||||
"newKnowledge.invalidDatasourceParameter": "Введіть припустиме значення для {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Введіть дійсну URL-адресу http(s).",
|
||||
"newKnowledge.keepEditing": "Продовжити редагування",
|
||||
"newKnowledge.lastIndexed": "Останнє індексування",
|
||||
"newKnowledge.lastReadyRevisionHint": "Невдала обробка ніколи не перериває пошук — остання готова версія залишається доступною в індексі вмісту.",
|
||||
"newKnowledge.lastSyncColumn": "Остання синхронізація",
|
||||
"newKnowledge.learnMore": "Докладніше",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Вийти з налаштування",
|
||||
"newKnowledge.leavePartialSetupDescription": "Базу знань створено, але не вдалося підтвердити її дозвіл. Якщо вийти, базу знань буде збережено; доступ можна перевірити пізніше.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Вийти з незавершеного налаштування?",
|
||||
"newKnowledge.legacy": "Попередня версія",
|
||||
"newKnowledge.loadMore": "Завантажити ще",
|
||||
"newKnowledge.loadMoreRevisions": "Завантажити попередні редакції",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "Chúng tôi không thể tải cơ sở kiến thức này. Hãy thử lại.",
|
||||
"newKnowledge.detailErrorTitle": "Đã xảy ra lỗi",
|
||||
"newKnowledge.disableSource": "Tắt",
|
||||
"newKnowledge.discardDraftConfirm": "Hủy bản nháp",
|
||||
"newKnowledge.discardDraftDescription": "Các thay đổi đối với bản nháp cơ sở kiến thức này sẽ bị mất.",
|
||||
"newKnowledge.discardDraftTitle": "Hủy bản nháp cơ sở kiến thức này?",
|
||||
"newKnowledge.discardSourceChanges": "Hủy thay đổi nguồn?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Hủy thay đổi",
|
||||
"newKnowledge.discardSourceChangesDescription": "Bản xem trước thu thập dữ liệu, lựa chọn trang và cài đặt đồng bộ sẽ bị mất.",
|
||||
"newKnowledge.discardSourceDraftDescription": "Cấu hình nguồn và bản xem trước thu thập sẽ bị xóa.",
|
||||
"newKnowledge.discardSourceDraftFailed": "Không thể hủy bản nháp nguồn. Hãy thử lại.",
|
||||
"newKnowledge.discardSourceDraftTitle": "Hủy bản nháp nguồn này?",
|
||||
"newKnowledge.documentActions": "Các hành động dành cho {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Hành động tài liệu chưa có sẵn cho cơ sở kiến thức này.",
|
||||
"newKnowledge.documentChunksEmpty": "Chưa có đoạn nào cho bản sửa đổi này.",
|
||||
@ -297,14 +288,10 @@
|
||||
"newKnowledge.interruptTask": "Ngắt",
|
||||
"newKnowledge.invalidDatasourceParameter": "Nhập giá trị hợp lệ cho {{parameter}}.",
|
||||
"newKnowledge.invalidRootUrl": "Nhập URL http(s) hợp lệ.",
|
||||
"newKnowledge.keepEditing": "Tiếp tục chỉnh sửa",
|
||||
"newKnowledge.lastIndexed": "Lập chỉ mục gần nhất",
|
||||
"newKnowledge.lastReadyRevisionHint": "Quá trình xử lý không thành công không bao giờ làm gián đoạn quá trình truy xuất — bản sửa đổi sẵn sàng cuối cùng vẫn có sẵn trong chỉ mục nội dung.",
|
||||
"newKnowledge.lastSyncColumn": "Đồng bộ hóa lần cuối",
|
||||
"newKnowledge.learnMore": "Tìm hiểu thêm",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Thoát thiết lập",
|
||||
"newKnowledge.leavePartialSetupDescription": "Cơ sở kiến thức đã được tạo nhưng không thể xác nhận quyền. Nếu thoát, cơ sở kiến thức vẫn được giữ lại; bạn có thể xem lại quyền truy cập sau.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Thoát khỏi thiết lập chưa hoàn tất?",
|
||||
"newKnowledge.legacy": "Cũ",
|
||||
"newKnowledge.loadMore": "Tải thêm",
|
||||
"newKnowledge.loadMoreRevisions": "Tải các bản sửa đổi cũ hơn",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "无法加载此知识库,请重试。",
|
||||
"newKnowledge.detailErrorTitle": "出现错误",
|
||||
"newKnowledge.disableSource": "禁用",
|
||||
"newKnowledge.discardDraftConfirm": "放弃草稿",
|
||||
"newKnowledge.discardDraftDescription": "你对该知识库草稿所做的更改将丢失。",
|
||||
"newKnowledge.discardDraftTitle": "放弃此知识库草稿?",
|
||||
"newKnowledge.discardSourceChanges": "放弃数据源更改?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "放弃更改",
|
||||
"newKnowledge.discardSourceChangesDescription": "抓取预览、页面选择和同步设置将会丢失。",
|
||||
"newKnowledge.discardSourceDraftDescription": "数据源配置和抓取预览将被移除。",
|
||||
"newKnowledge.discardSourceDraftFailed": "无法放弃数据源草稿,请重试。",
|
||||
"newKnowledge.discardSourceDraftTitle": "放弃此数据源草稿?",
|
||||
"newKnowledge.documentActions": "{{name}} 的操作",
|
||||
"newKnowledge.documentActionsUnavailable": "此知识库暂不支持文档操作。",
|
||||
"newKnowledge.documentChunksEmpty": "此修订版本暂时没有可用分段。",
|
||||
@ -301,14 +292,10 @@
|
||||
"newKnowledge.interruptTask": "中断",
|
||||
"newKnowledge.invalidDatasourceParameter": "请输入有效的 {{parameter}}。",
|
||||
"newKnowledge.invalidRootUrl": "请输入有效的 http(s) URL。",
|
||||
"newKnowledge.keepEditing": "继续编辑",
|
||||
"newKnowledge.lastIndexed": "最近索引时间",
|
||||
"newKnowledge.lastReadyRevisionHint": "处理失败不会中断检索,系统仍会使用最近一个已就绪的修订版本。",
|
||||
"newKnowledge.lastSyncColumn": "上次同步",
|
||||
"newKnowledge.learnMore": "了解更多",
|
||||
"newKnowledge.leavePartialSetupConfirm": "离开设置",
|
||||
"newKnowledge.leavePartialSetupDescription": "知识库已创建,但无法确认其权限状态。离开后知识库仍会保留,你可以稍后检查访问权限。",
|
||||
"newKnowledge.leavePartialSetupTitle": "离开未完成的设置?",
|
||||
"newKnowledge.legacy": "旧版",
|
||||
"newKnowledge.loadMore": "加载更多",
|
||||
"newKnowledge.loadMoreRevisions": "加载更早的修订版本",
|
||||
|
||||
@ -211,15 +211,6 @@
|
||||
"newKnowledge.detailErrorDescription": "無法載入此知識庫,請再試一次。",
|
||||
"newKnowledge.detailErrorTitle": "發生錯誤",
|
||||
"newKnowledge.disableSource": "停用",
|
||||
"newKnowledge.discardDraftConfirm": "放棄草稿",
|
||||
"newKnowledge.discardDraftDescription": "對此知識庫草稿的變更將會遺失。",
|
||||
"newKnowledge.discardDraftTitle": "放棄此知識庫草稿?",
|
||||
"newKnowledge.discardSourceChanges": "放棄來源變更?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "放棄變更",
|
||||
"newKnowledge.discardSourceChangesDescription": "檢索預覽、頁面選取和同步設定將會遺失。",
|
||||
"newKnowledge.discardSourceDraftDescription": "來源設定和抓取預覽將被移除。",
|
||||
"newKnowledge.discardSourceDraftFailed": "無法捨棄來源草稿。請重試。",
|
||||
"newKnowledge.discardSourceDraftTitle": "捨棄此來源草稿?",
|
||||
"newKnowledge.documentActions": "{{name}} 的操作",
|
||||
"newKnowledge.documentActionsUnavailable": "此知識庫暫不支援文件操作。",
|
||||
"newKnowledge.documentChunksEmpty": "此修訂版本暫時沒有可用分段。",
|
||||
@ -300,14 +291,10 @@
|
||||
"newKnowledge.interruptTask": "中斷",
|
||||
"newKnowledge.invalidDatasourceParameter": "請輸入有效的 {{parameter}}。",
|
||||
"newKnowledge.invalidRootUrl": "請輸入有效的 http(s) URL。",
|
||||
"newKnowledge.keepEditing": "繼續編輯",
|
||||
"newKnowledge.lastIndexed": "最近建立索引時間",
|
||||
"newKnowledge.lastReadyRevisionHint": "處理失敗不會中斷檢索,內容索引中會繼續保留上一個就緒的修訂。",
|
||||
"newKnowledge.lastSyncColumn": "上次同步",
|
||||
"newKnowledge.learnMore": "瞭解更多",
|
||||
"newKnowledge.leavePartialSetupConfirm": "離開設定",
|
||||
"newKnowledge.leavePartialSetupDescription": "知識庫已建立,但無法確認其權限狀態。離開後知識庫仍會保留,你可以稍後檢查存取權限。",
|
||||
"newKnowledge.leavePartialSetupTitle": "離開未完成的設定?",
|
||||
"newKnowledge.legacy": "舊版",
|
||||
"newKnowledge.loadMore": "載入更多",
|
||||
"newKnowledge.loadMoreRevisions": "載入較早的修訂版本",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user