refactor(knowledge-fs): remove new rag edit guards

This commit is contained in:
Stephen Zhou 2026-09-01 18:01:44 +08:00
parent dbcc3a11e5
commit e9180d38e5
No known key found for this signature in database
41 changed files with 67 additions and 1637 deletions

View File

@ -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.

View File

@ -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()
})
})

View File

@ -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>
)
}

View File

@ -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>

View File

@ -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', () => {

View File

@ -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()
})
})

View File

@ -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

View File

@ -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 />
</>
)
}

View File

@ -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>
)
}

View File

@ -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,

View File

@ -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)
})

View File

@ -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"}'),

View File

@ -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()
})

View File

@ -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>
)
}

View File

@ -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}

View File

@ -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}
/>
</>
)
}

View File

@ -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>
)
}

View File

@ -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": "تحميل مراجعات أقدم",

View File

@ -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",

View File

@ -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 couldnt 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",

View File

@ -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",

View File

@ -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": "بارگیری بازبینی‌های قدیمی‌تر",

View File

@ -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": "Laperçu de lexploration, la sélection des pages et les paramètres de synchronisation seront perdus.",
"newKnowledge.discardSourceDraftDescription": "La configuration de la source et laperçu de lexploration seront supprimés.",
"newKnowledge.discardSourceDraftFailed": "Impossible dabandonner 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 nest 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 na pas pu être confirmée. Si vous quittez, la base de connaissances sera conservée ; vous pourrez vérifier laccès plus tard.",
"newKnowledge.leavePartialSetupTitle": "Quitter la configuration incomplète ?",
"newKnowledge.legacy": "Ancien",
"newKnowledge.loadMore": "Charger plus",
"newKnowledge.loadMoreRevisions": "Charger les anciennes révisions",

View File

@ -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": "पुराने संशोधन लोड करें",

View File

@ -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",

View File

@ -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 allorigine?",
"newKnowledge.discardSourceChangesConfirm": "Ignora modifiche",
"newKnowledge.discardSourceChangesDescription": "Lanteprima della scansione, la selezione delle pagine e le impostazioni di sincronizzazione andranno perse.",
"newKnowledge.discardSourceDraftDescription": "La configurazione della fonte e lanteprima 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 lautorizzazione. Uscendo, la knowledge base verrà mantenuta; potrai verificare laccesso in seguito.",
"newKnowledge.leavePartialSetupTitle": "Uscire dalla configurazione incompleta?",
"newKnowledge.legacy": "Legacy",
"newKnowledge.loadMore": "Carica altro",
"newKnowledge.loadMoreRevisions": "Carica revisioni precedenti",

View File

@ -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": "古いリビジョンを読み込む",

View File

@ -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": "이전 리비전 불러오기",

View File

@ -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": "ໂຫຼດການດັດແກ້ເກົ່າ",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "Загрузить предыдущие редакции",

View File

@ -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",

View File

@ -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": "โหลดรุ่นแก้ไขก่อนหน้า",

View File

@ -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) URLsi 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",

View File

@ -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": "Завантажити попередні редакції",

View File

@ -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",

View File

@ -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": "加载更早的修订版本",

View File

@ -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": "載入較早的修訂版本",