Compare commits
159
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d1e583978 | ||
|
|
825b749501 | ||
|
|
fc5dcb10d9 | ||
|
|
50915d4b57 | ||
|
|
7c20436611 | ||
|
|
bde17dc8b0 | ||
|
|
a489dd48cf | ||
|
|
f71e31351d | ||
|
|
2a12c2fa29 | ||
|
|
84d9399093 | ||
|
|
041960d9c7 | ||
|
|
35a5bf21ee | ||
|
|
4b4b109bbb | ||
|
|
8e6eb0eaae | ||
|
|
fe67e094aa | ||
|
|
919a8090c3 | ||
|
|
d7e1b2a996 | ||
|
|
3f99f16bd0 | ||
|
|
2da74263d7 | ||
|
|
bc8ae85b88 | ||
|
|
e3a53c8822 | ||
|
|
0ad1631c4a | ||
|
|
513ea8b5fa | ||
|
|
2107c9b9c7 | ||
|
|
6e34915c7a | ||
|
|
cea92da293 | ||
|
|
5a6532c74e | ||
|
|
e6cc04fc36 | ||
|
|
3738920820 | ||
|
|
e98bbea93a | ||
|
|
a86d413ed9 | ||
|
|
e9c1147146 | ||
|
|
9d922114d5 | ||
|
|
ccdb1249d2 | ||
|
|
831acbb34d | ||
|
|
8239658259 | ||
|
|
6ac0aeeb2a | ||
|
|
7c8037f496 | ||
|
|
5b14e29c08 | ||
|
|
566359fefc | ||
|
|
b65c51f5f5 | ||
|
|
68a5c97baf | ||
|
|
f60a42f781 | ||
|
|
54a334303f | ||
|
|
6190202a4e | ||
|
|
f0961f7484 | ||
|
|
a002efaac5 | ||
|
|
d7aa59e72d | ||
|
|
75a9a67947 | ||
|
|
10495a7103 | ||
|
|
de146c4efe | ||
|
|
6a7aa2222c | ||
|
|
aeafe69626 | ||
|
|
1fc1ac16d0 | ||
|
|
9fb67fade9 | ||
|
|
de56f03784 | ||
|
|
558d0a3af3 | ||
|
|
301a32e7c0 | ||
|
|
cff84914bd | ||
|
|
833bdc4b70 | ||
|
|
e50daa9081 | ||
|
|
01f03c5f7a | ||
|
|
f3cbfacd3e | ||
|
|
268b8e91d2 | ||
|
|
0a31eb298b | ||
|
|
18a888b6bb | ||
|
|
49634eb302 | ||
|
|
9daa6a876b | ||
|
|
828a6f02dd | ||
|
|
c68e2bdc60 | ||
|
|
d9ab09c303 | ||
|
|
a6bc237d34 | ||
|
|
2df867ad1d | ||
|
|
5ffe4d8ab7 | ||
|
|
ef050944cf | ||
|
|
225c6e5d30 | ||
|
|
0f77618fcc | ||
|
|
018640d069 | ||
|
|
1f8a8dfc4d | ||
|
|
ca9a771985 | ||
|
|
f6e5cdaf28 | ||
|
|
2c88ae3ea2 | ||
|
|
ca36ba5585 | ||
|
|
8a0438825c | ||
|
|
737ce40c0d | ||
|
|
23946300ae | ||
|
|
9ea92a2528 | ||
|
|
7b74b0434c | ||
|
|
4aaf347798 | ||
|
|
63673e2243 | ||
|
|
3c512de30d | ||
|
|
66e3afb850 | ||
|
|
9f43e962ef | ||
|
|
05e06c88ee | ||
|
|
1753d256c2 | ||
|
|
c684679ce9 | ||
|
|
2231289f6a | ||
|
|
cbd48e9d7e | ||
|
|
f6b19fbfa8 | ||
|
|
518a4a7226 | ||
|
|
6444ff8d93 | ||
|
|
f60575bd1d | ||
|
|
5bd7664d1e | ||
|
|
e6d0e71f89 | ||
|
|
1e539101e9 | ||
|
|
6732cadc0f | ||
|
|
551885dc10 | ||
|
|
ccf62b17f1 | ||
|
|
942b7a4796 | ||
|
|
7481cd2e7f | ||
|
|
ab249cf248 | ||
|
|
c4ee6d3c77 | ||
|
|
c2f23edc6a | ||
|
|
445c36c356 | ||
|
|
fcb0de7747 | ||
|
|
98869def82 | ||
|
|
bc1de7a5d0 | ||
|
|
1ffb081a05 | ||
|
|
4b658b2028 | ||
|
|
63dc57a70b | ||
|
|
532025c46b | ||
|
|
e26c85b07e | ||
|
|
5e6382b20e | ||
|
|
1f418ad5d2 | ||
|
|
cf98fc7ed2 | ||
|
|
28762fef0a | ||
|
|
f14b666127 | ||
|
|
2a43b5a168 | ||
|
|
5012d4643d | ||
|
|
fb1a9901d4 | ||
|
|
ee0a0a5af3 | ||
|
|
820eaa1a63 | ||
|
|
3d3ecb73f6 | ||
|
|
457247c159 | ||
|
|
3dbf6b00e3 | ||
|
|
9c36aed1f8 | ||
|
|
b0c8a791ba | ||
|
|
6a3dcd179a | ||
|
|
ee867d53ad | ||
|
|
a440659408 | ||
|
|
ad0b90e0d7 | ||
|
|
2529973c21 | ||
|
|
619920c1df | ||
|
|
e8679d1d23 | ||
|
|
5c99fd1b6b | ||
|
|
45d2f2761b | ||
|
|
7101bd46db | ||
|
|
897b79980a | ||
|
|
a9f78ea568 | ||
|
|
0857172091 | ||
|
|
d5e95cf989 | ||
|
|
eca1af1b9d | ||
|
|
f3f8eb655d | ||
|
|
34747ff0eb | ||
|
|
bb07109753 | ||
|
|
8a74f7e23f | ||
|
|
76e846382c | ||
|
|
3392cd8606 | ||
|
|
de4a2c6910 |
@@ -5,6 +5,8 @@ from dify_app import DifyApp
|
||||
BASE_CORS_HEADERS: tuple[str, ...] = ("Content-Type", HEADER_NAME_APP_CODE, HEADER_NAME_PASSPORT)
|
||||
SERVICE_API_HEADERS: tuple[str, ...] = (*BASE_CORS_HEADERS, "Authorization")
|
||||
AUTHENTICATED_HEADERS: tuple[str, ...] = (*SERVICE_API_HEADERS, HEADER_NAME_CSRF_TOKEN)
|
||||
KNOWLEDGE_FS_REQUEST_HEADERS: tuple[str, ...] = ("Idempotency-Key", "Last-Event-Id", "X-Trace-Id")
|
||||
CONSOLE_HEADERS: tuple[str, ...] = (*AUTHENTICATED_HEADERS, *KNOWLEDGE_FS_REQUEST_HEADERS)
|
||||
FILES_HEADERS: tuple[str, ...] = (*BASE_CORS_HEADERS, HEADER_NAME_CSRF_TOKEN)
|
||||
EMBED_HEADERS: tuple[str, ...] = ("Content-Type", HEADER_NAME_APP_CODE)
|
||||
EXPOSED_HEADERS: tuple[str, ...] = ("X-Version", "X-Env", "X-Trace-Id")
|
||||
@@ -93,7 +95,7 @@ def init_app(app: DifyApp):
|
||||
console_app_bp,
|
||||
resources={r"/*": {"origins": dify_config.CONSOLE_CORS_ALLOW_ORIGINS}},
|
||||
supports_credentials=True,
|
||||
allow_headers=list(AUTHENTICATED_HEADERS),
|
||||
allow_headers=list(CONSOLE_HEADERS),
|
||||
methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"],
|
||||
expose_headers=list(EXPOSED_HEADERS),
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ from flask_cors import CORS
|
||||
from configs import dify_config
|
||||
from controllers.fastopenapi import console_router
|
||||
from dify_app import DifyApp
|
||||
from extensions.ext_blueprints import AUTHENTICATED_HEADERS, EXPOSED_HEADERS
|
||||
from extensions.ext_blueprints import CONSOLE_HEADERS, EXPOSED_HEADERS
|
||||
|
||||
DOCS_PREFIX = "/fastopenapi"
|
||||
|
||||
@@ -47,7 +47,7 @@ def init_app(app: DifyApp) -> None:
|
||||
app,
|
||||
resources={r"/console/api/.*": {"origins": dify_config.CONSOLE_CORS_ALLOW_ORIGINS}},
|
||||
supports_credentials=True,
|
||||
allow_headers=list(AUTHENTICATED_HEADERS),
|
||||
allow_headers=list(CONSOLE_HEADERS),
|
||||
methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"],
|
||||
expose_headers=list(EXPOSED_HEADERS),
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ from pydantic import SecretStr
|
||||
from core.helper import ssrf_proxy
|
||||
from core.rbac import RBACPermission
|
||||
from core.tools.errors import ToolSSRFError
|
||||
from extensions.ext_blueprints import CONSOLE_HEADERS
|
||||
from services.knowledge_fs_operations import (
|
||||
KNOWLEDGE_FS_CONSOLE_OPERATIONS,
|
||||
KnowledgeFSMethod,
|
||||
@@ -250,6 +251,17 @@ def test_console_registry_preserves_explicit_scope_and_authorization_policies()
|
||||
assert operation.response_headers == ("x-trace-id",)
|
||||
|
||||
|
||||
def test_console_cors_allows_every_contract_declared_request_header() -> None:
|
||||
cors_headers = {header.lower() for header in CONSOLE_HEADERS}
|
||||
contract_headers = {
|
||||
header.lower()
|
||||
for operation in KNOWLEDGE_FS_CONSOLE_OPERATIONS
|
||||
for header in operation.request_headers
|
||||
}
|
||||
|
||||
assert contract_headers <= cors_headers
|
||||
|
||||
|
||||
def test_console_registry_preserves_special_transport_contracts() -> None:
|
||||
crawl_preview = get_knowledge_fs_operation(
|
||||
"POST",
|
||||
|
||||
+14
-1
@@ -114,7 +114,20 @@ Behavior depends on instance state:
|
||||
- uninitialized instance: completes install and stores authenticated state
|
||||
- initialized instance: signs in and reuses authenticated state
|
||||
|
||||
The `pnpm -C e2e e2e:full*` flows prove reset and authentication bootstrap by failing setup when initialization cannot complete; they do not model bootstrap state as a Gherkin scenario. Deterministic runs exclude `@prepared`, `@external-model`, and `@external-tool`. Post-merge first seeds required fixtures, then runs prepared and external scenarios.
|
||||
The `pnpm -C e2e e2e:full*` flows prove reset and authentication bootstrap by failing setup when initialization cannot complete; they do not model bootstrap state as a Gherkin scenario. Deterministic runs exclude `@prepared`, `@external-model`, `@external-tool`, and `@new-rag-smoke`. Post-merge first seeds required fixtures, then runs prepared and external scenarios.
|
||||
|
||||
The New RAG release smoke is an opt-in live integration matrix. The first matrix entry resets E2E state; later entries restart middleware, API, Celery, and web without resetting persisted data. The default-disabled, explicit-disabled, and enabled configurations therefore verify the same Legacy dataset snapshot while reusing a running KnowledgeFS service and a real website-source provider. Run it with:
|
||||
|
||||
```bash
|
||||
E2E_NEW_RAG_KNOWLEDGE_FS_BASE_URL=http://127.0.0.1:8788 \
|
||||
E2E_NEW_RAG_KNOWLEDGE_FS_JWT_SECRET='<shared-jwt-secret>' \
|
||||
E2E_NEW_RAG_CRAWL_URL=https://example.com \
|
||||
E2E_NEW_RAG_CONNECTION_CREDENTIALS_JSON='{"apiKey":"<provider-key>"}' \
|
||||
pnpm -C e2e e2e:new-rag
|
||||
```
|
||||
|
||||
Keep `@new-rag-smoke` scenarios out of the default suite. They require a reachable KnowledgeFS `/health` endpoint, valid provider credentials, and real external crawling; the runner fails before starting Dify when any live input is missing or invalid.
|
||||
Credential object keys must match the fields exposed by the live datasource provider catalog.
|
||||
|
||||
Reset all persisted E2E state:
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { IConfiguration } from '@cucumber/cucumber'
|
||||
import './scripts/env-register'
|
||||
|
||||
const hasCliTags = process.argv.some((arg) => arg === '--tags' || arg.startsWith('--tags='))
|
||||
const defaultNonExternalTags = 'not @prepared and not @external-model and not @external-tool'
|
||||
const defaultNonExternalTags =
|
||||
'not @prepared and not @external-model and not @external-tool and not @new-rag-smoke'
|
||||
const defaultTags =
|
||||
process.env.E2E_CUCUMBER_TAGS || (hasCliTags ? undefined : defaultNonExternalTags)
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
@datasets @authenticated @new-rag-smoke
|
||||
Feature: New RAG release smoke
|
||||
@new-rag-flag-default
|
||||
Scenario: The default feature configuration preserves the Legacy Knowledge experience
|
||||
Given I am signed in as the default E2E admin
|
||||
And a Legacy Knowledge dataset exists
|
||||
And I monitor New RAG network requests
|
||||
When I open the Knowledge console
|
||||
Then the New RAG feature should be disabled by "default-disabled"
|
||||
And the New Knowledge view should be unavailable
|
||||
And the Legacy Knowledge dataset should remain available
|
||||
When I try to open the New Knowledge creation route
|
||||
Then I should return to the Legacy Knowledge console
|
||||
And the Legacy Knowledge dataset should remain available
|
||||
And no KnowledgeFS request should leave the browser
|
||||
|
||||
@new-rag-flag-disabled
|
||||
Scenario: Explicitly disabling the feature preserves the Legacy Knowledge experience
|
||||
Given I am signed in as the default E2E admin
|
||||
And a Legacy Knowledge dataset exists
|
||||
And I monitor New RAG network requests
|
||||
When I open the Knowledge console
|
||||
Then the New RAG feature should be disabled by "explicit-disabled"
|
||||
And the New Knowledge view should be unavailable
|
||||
And the Legacy Knowledge dataset should remain available
|
||||
When I try to open the New Knowledge creation route
|
||||
Then I should return to the Legacy Knowledge console
|
||||
And the Legacy Knowledge dataset should remain available
|
||||
And no KnowledgeFS request should leave the browser
|
||||
|
||||
@new-rag-happy-path @external-source
|
||||
Scenario: Create a Knowledge space from a website and inspect its ready document
|
||||
Given I am signed in as the default E2E admin
|
||||
And a Legacy Knowledge dataset exists
|
||||
And the Firecrawl datasource plugin is installed
|
||||
And I monitor New RAG network requests
|
||||
When I open the Knowledge console
|
||||
Then the New RAG feature should be enabled
|
||||
And the Legacy Knowledge view should remain available
|
||||
And the Legacy Knowledge dataset should remain available
|
||||
When I switch to the New Knowledge view
|
||||
And I create a private E2E Knowledge space
|
||||
And I connect the configured Firecrawl provider
|
||||
And I crawl the configured website
|
||||
And I select every crawled page with a manual sync policy
|
||||
Then the website source should become active
|
||||
When I open the source Documents
|
||||
Then a crawled document should become ready
|
||||
When I open the ready document
|
||||
Then I should see its revision and chunk tree
|
||||
When I refresh the current page
|
||||
Then the same document detail should be restored
|
||||
When I return to the source Documents
|
||||
Then the same ready document should remain available
|
||||
When I reopen the ready document
|
||||
Then the same document detail should be restored
|
||||
And the New RAG requests should be proxied with diagnostics
|
||||
When I return to Legacy Knowledge
|
||||
Then the Legacy Knowledge dataset should remain available
|
||||
And the KnowledgeFS tenant and read-only boundaries should hold
|
||||
@@ -0,0 +1,163 @@
|
||||
import type { ConsoleClient } from '../../../support/api/console-client'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { createHmac } from 'node:crypto'
|
||||
import { resolveNewRagSmokeConfig } from '../../../scripts/run-new-rag-smoke'
|
||||
|
||||
type KnowledgeFsScope = 'knowledge-spaces:read' | 'knowledge-spaces:write'
|
||||
type DifyIdentity = { accountId: string; tenantId: string }
|
||||
type KnowledgeSpaceDeletionSnapshot = { name: string; revision: number }
|
||||
|
||||
const base64Url = (value: string | Buffer) => Buffer.from(value).toString('base64url')
|
||||
|
||||
export const createKnowledgeFsJwt = ({
|
||||
accountId,
|
||||
scope,
|
||||
secret,
|
||||
tenantId,
|
||||
}: {
|
||||
accountId: string
|
||||
scope: KnowledgeFsScope
|
||||
secret: string
|
||||
tenantId: string
|
||||
}) => {
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const header = base64Url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
|
||||
const payload = base64Url(
|
||||
JSON.stringify({
|
||||
aud: 'knowledge-fs',
|
||||
caller_kind: 'interactive',
|
||||
dify_account_id: `dify-account:${accountId}`,
|
||||
exp: now + 60,
|
||||
iat: now,
|
||||
iss: 'dify',
|
||||
scopes: [scope],
|
||||
sub: `dify-workspace:${tenantId}`,
|
||||
tenant_id: tenantId,
|
||||
}),
|
||||
)
|
||||
const signature = createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url')
|
||||
return `${header}.${payload}.${signature}`
|
||||
}
|
||||
|
||||
const getDifyIdentity = async (client: ConsoleClient) => {
|
||||
const [account, workspace] = await Promise.all([
|
||||
client.account.profile.get(),
|
||||
client.workspaces.current.post(),
|
||||
])
|
||||
if (!account.id || !workspace.id)
|
||||
throw new Error('The current Dify account or workspace has no stable identifier.')
|
||||
return { accountId: account.id, tenantId: workspace.id }
|
||||
}
|
||||
|
||||
const directKnowledgeFsRequest = async ({
|
||||
accountId,
|
||||
body,
|
||||
method,
|
||||
path,
|
||||
requestHeaders,
|
||||
scope,
|
||||
tenantId,
|
||||
}: {
|
||||
accountId: string
|
||||
body?: Record<string, unknown>
|
||||
method: 'DELETE' | 'GET' | 'POST'
|
||||
path: string
|
||||
requestHeaders?: Record<string, string>
|
||||
scope: KnowledgeFsScope
|
||||
tenantId: string
|
||||
}) => {
|
||||
const config = resolveNewRagSmokeConfig(process.env)
|
||||
const token = createKnowledgeFsJwt({
|
||||
accountId,
|
||||
scope,
|
||||
secret: config.knowledgeFsJwtSecret,
|
||||
tenantId,
|
||||
})
|
||||
return fetch(new URL(path, `${config.knowledgeFsBaseUrl}/`), {
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
...(body ? { 'Content-Type': 'application/json' } : {}),
|
||||
...requestHeaders,
|
||||
},
|
||||
method,
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
})
|
||||
}
|
||||
|
||||
export const assertKnowledgeFsAccessBoundaries = async (
|
||||
knowledgeSpaceId: string,
|
||||
client: ConsoleClient,
|
||||
) => {
|
||||
const identity = await getDifyIdentity(client)
|
||||
const crossTenant = await directKnowledgeFsRequest({
|
||||
accountId: identity.accountId,
|
||||
method: 'GET',
|
||||
path: `knowledge-spaces/${knowledgeSpaceId}`,
|
||||
scope: 'knowledge-spaces:read',
|
||||
tenantId: `${identity.tenantId}-other`,
|
||||
})
|
||||
if (![403, 404].includes(crossTenant.status))
|
||||
throw new Error(
|
||||
`Cross-tenant KnowledgeFS read returned ${crossTenant.status}; expected 403 or 404.`,
|
||||
)
|
||||
|
||||
const readOnlyWrite = await directKnowledgeFsRequest({
|
||||
accountId: identity.accountId,
|
||||
body: {
|
||||
idempotencyKey: `e2e-read-only-${knowledgeSpaceId}`,
|
||||
name: `E2E forbidden ${knowledgeSpaceId}`,
|
||||
},
|
||||
method: 'POST',
|
||||
path: 'knowledge-spaces',
|
||||
scope: 'knowledge-spaces:read',
|
||||
tenantId: identity.tenantId,
|
||||
})
|
||||
if (readOnlyWrite.status !== 403)
|
||||
throw new Error(`Read-only KnowledgeFS write returned ${readOnlyWrite.status}; expected 403.`)
|
||||
}
|
||||
|
||||
export const deleteKnowledgeFsSpaceWithIdentity = async (
|
||||
knowledgeSpaceId: string,
|
||||
identity: DifyIdentity,
|
||||
) => {
|
||||
const snapshotResponse = await directKnowledgeFsRequest({
|
||||
accountId: identity.accountId,
|
||||
method: 'GET',
|
||||
path: `knowledge-spaces/${knowledgeSpaceId}`,
|
||||
scope: 'knowledge-spaces:read',
|
||||
tenantId: identity.tenantId,
|
||||
})
|
||||
if (snapshotResponse.status === 404) return
|
||||
if (!snapshotResponse.ok)
|
||||
throw new Error(
|
||||
`Read KnowledgeFS smoke space ${knowledgeSpaceId} for cleanup failed with ${snapshotResponse.status} ${snapshotResponse.statusText}.`,
|
||||
)
|
||||
const snapshot = (await snapshotResponse.json()) as Partial<KnowledgeSpaceDeletionSnapshot>
|
||||
if (
|
||||
!snapshot.name ||
|
||||
typeof snapshot.revision !== 'number' ||
|
||||
!Number.isInteger(snapshot.revision) ||
|
||||
snapshot.revision < 1
|
||||
)
|
||||
throw new Error(`KnowledgeFS smoke space ${knowledgeSpaceId} has no valid deletion snapshot.`)
|
||||
|
||||
const response = await directKnowledgeFsRequest({
|
||||
accountId: identity.accountId,
|
||||
body: { challenge: snapshot.name, expectedRevision: snapshot.revision },
|
||||
method: 'DELETE',
|
||||
path: `knowledge-spaces/${knowledgeSpaceId}`,
|
||||
requestHeaders: { 'Idempotency-Key': `e2e-delete-${knowledgeSpaceId}` },
|
||||
scope: 'knowledge-spaces:write',
|
||||
tenantId: identity.tenantId,
|
||||
})
|
||||
if (response.ok || response.status === 404) return
|
||||
const body = await response.text().catch(() => '')
|
||||
throw new Error(
|
||||
`Delete KnowledgeFS smoke space ${knowledgeSpaceId} failed with ${response.status} ${response.statusText}${body ? `: ${body}` : ''}.`,
|
||||
)
|
||||
}
|
||||
|
||||
export const deleteKnowledgeFsSpace = async (knowledgeSpaceId: string, client: ConsoleClient) =>
|
||||
deleteKnowledgeFsSpaceWithIdentity(knowledgeSpaceId, await getDifyIdentity(client))
|
||||
@@ -0,0 +1,399 @@
|
||||
import type { DatasetDetailResponse } from '@dify/contracts/api/console/datasets/types.gen'
|
||||
import type { DifyWorld } from '../../support/world'
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { Given, Then, When } from '@cucumber/cucumber'
|
||||
import { expect } from '@playwright/test'
|
||||
import { resolveNewRagSmokeConfig } from '../../../scripts/run-new-rag-smoke'
|
||||
import { bootstrapMarketplacePlugins } from '../../../support/marketplace-plugins'
|
||||
import { createE2EResourceName } from '../../../support/naming'
|
||||
import {
|
||||
assertKnowledgeFsAccessBoundaries,
|
||||
deleteKnowledgeFsSpace,
|
||||
} from '../../new-rag/support/runtime'
|
||||
|
||||
const knowledgeFsProxyPath = '/console/api/knowledge-fs/'
|
||||
|
||||
type LegacyDatasetSnapshot = Pick<
|
||||
DatasetDetailResponse,
|
||||
'created_at' | 'created_by' | 'description' | 'id' | 'name' | 'permission' | 'updated_at'
|
||||
>
|
||||
|
||||
const legacyDatasetEnvironment = () => {
|
||||
const name = process.env.E2E_NEW_RAG_LEGACY_DATASET_NAME?.trim()
|
||||
const statePath = process.env.E2E_NEW_RAG_LEGACY_DATASET_STATE_PATH?.trim()
|
||||
if (!name || !statePath)
|
||||
throw new Error('The shared Legacy Knowledge dataset configuration is missing.')
|
||||
return { name, statePath }
|
||||
}
|
||||
|
||||
const legacyDatasetSnapshot = (dataset: DatasetDetailResponse): LegacyDatasetSnapshot => ({
|
||||
created_at: dataset.created_at,
|
||||
created_by: dataset.created_by,
|
||||
description: dataset.description,
|
||||
id: dataset.id,
|
||||
name: dataset.name,
|
||||
permission: dataset.permission,
|
||||
updated_at: dataset.updated_at,
|
||||
})
|
||||
|
||||
const isLegacyDatasetSnapshot = (value: unknown): value is LegacyDatasetSnapshot => {
|
||||
if (!value || typeof value !== 'object') return false
|
||||
const snapshot = value as Record<string, unknown>
|
||||
return (
|
||||
typeof snapshot.created_at === 'number' &&
|
||||
typeof snapshot.created_by === 'string' &&
|
||||
(snapshot.description === null || typeof snapshot.description === 'string') &&
|
||||
typeof snapshot.id === 'string' &&
|
||||
typeof snapshot.name === 'string' &&
|
||||
typeof snapshot.permission === 'string' &&
|
||||
typeof snapshot.updated_at === 'number'
|
||||
)
|
||||
}
|
||||
|
||||
const readLegacyDatasetSnapshot = async (statePath: string) => {
|
||||
const snapshot = JSON.parse(await readFile(statePath, 'utf8')) as unknown
|
||||
if (!isLegacyDatasetSnapshot(snapshot))
|
||||
throw new Error('The shared Legacy Knowledge dataset snapshot is invalid.')
|
||||
return snapshot
|
||||
}
|
||||
|
||||
const humanizeFieldName = (name: string) =>
|
||||
name
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
||||
.replace(/[-_]+/g, ' ')
|
||||
.replace(/^./, (character) => character.toUpperCase())
|
||||
|
||||
Given('a Legacy Knowledge dataset exists', async function (this: DifyWorld) {
|
||||
const { name, statePath } = legacyDatasetEnvironment()
|
||||
const mode = process.env.E2E_NEW_RAG_EXPECTED_FLAG_MODE
|
||||
if (mode === 'default-disabled') {
|
||||
const dataset = await this.getConsoleClient().datasets.post({ body: { name } })
|
||||
await writeFile(statePath, JSON.stringify(legacyDatasetSnapshot(dataset)), 'utf8')
|
||||
this.newRag.legacyDatasetName = name
|
||||
return
|
||||
}
|
||||
|
||||
const expectedSnapshot = await readLegacyDatasetSnapshot(statePath)
|
||||
if (mode === 'enabled') this.createdDatasetIds.push(expectedSnapshot.id)
|
||||
const dataset = await this.getConsoleClient().datasets.byDatasetId.get({
|
||||
params: { dataset_id: expectedSnapshot.id },
|
||||
})
|
||||
expect(legacyDatasetSnapshot(dataset)).toEqual(expectedSnapshot)
|
||||
this.newRag.legacyDatasetName = expectedSnapshot.name
|
||||
})
|
||||
|
||||
Given(
|
||||
'the Firecrawl datasource plugin is installed',
|
||||
{ timeout: 360_000 },
|
||||
async function (this: DifyWorld) {
|
||||
const result = await bootstrapMarketplacePlugins(
|
||||
{ consoleClient: this.getConsoleClient(), dryRun: false, resources: new Map() },
|
||||
{
|
||||
defaultPluginIds: ['langgenius/firecrawl_datasource'],
|
||||
pluginIdsEnv: 'E2E_NEW_RAG_PLUGIN_IDS',
|
||||
title: 'New RAG Firecrawl datasource plugin',
|
||||
},
|
||||
)
|
||||
if (result.status === 'blocked')
|
||||
throw new Error(result.reason ?? 'The Firecrawl datasource plugin could not be installed.')
|
||||
},
|
||||
)
|
||||
|
||||
Given('I monitor New RAG network requests', async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
page.on('request', (request) => {
|
||||
const url = new URL(request.url())
|
||||
if (url.pathname.includes(knowledgeFsProxyPath))
|
||||
this.newRag.knowledgeFsRequests.push(request.url())
|
||||
})
|
||||
page.on('requestfailed', (request) => {
|
||||
const url = new URL(request.url())
|
||||
if (!url.pathname.includes(knowledgeFsProxyPath)) return
|
||||
this.newRag.knowledgeFsRequestFailures.push({
|
||||
error: request.failure()?.errorText ?? 'Unknown browser request failure',
|
||||
method: request.method(),
|
||||
url: request.url(),
|
||||
})
|
||||
})
|
||||
page.on('response', (response) => {
|
||||
const url = new URL(response.url())
|
||||
if (!url.pathname.includes(knowledgeFsProxyPath)) return
|
||||
const traceId = response.headers()['x-trace-id']
|
||||
this.newRag.knowledgeFsResponses.push({
|
||||
method: response.request().method(),
|
||||
status: response.status(),
|
||||
...(traceId ? { traceId } : {}),
|
||||
url: response.url(),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When('I open the Knowledge console', async function (this: DifyWorld) {
|
||||
await this.getPage().goto('/datasets')
|
||||
await expect(
|
||||
this.getPage().getByRole('heading', { name: 'Knowledge', exact: true }),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
Then(
|
||||
'the New RAG feature should be disabled by {string}',
|
||||
async function (this: DifyWorld, expectedMode: string) {
|
||||
void this
|
||||
expect(process.env.E2E_NEW_RAG_EXPECTED_FLAG_MODE).toBe(expectedMode)
|
||||
expect((await this.getConsoleClient().systemFeatures.get()).knowledge_fs_enabled).toBe(false)
|
||||
},
|
||||
)
|
||||
|
||||
Then('the New RAG feature should be enabled', async function (this: DifyWorld) {
|
||||
expect(process.env.E2E_NEW_RAG_EXPECTED_FLAG_MODE).toBe('enabled')
|
||||
expect((await this.getConsoleClient().systemFeatures.get()).knowledge_fs_enabled).toBe(true)
|
||||
await expect(this.getPage().getByRole('button', { name: 'New', exact: true })).toBeVisible()
|
||||
})
|
||||
|
||||
Then('the Legacy Knowledge view should remain available', async function (this: DifyWorld) {
|
||||
const legacyView = this.getPage().getByRole('button', { name: 'Legacy', exact: true })
|
||||
await expect(legacyView).toBeVisible()
|
||||
await expect(legacyView).toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
|
||||
Then('the Legacy Knowledge dataset should remain available', async function (this: DifyWorld) {
|
||||
const name = this.newRag.legacyDatasetName
|
||||
if (!name) throw new Error('The Legacy Knowledge smoke dataset name is missing.')
|
||||
await expect(this.getPage().getByText(name, { exact: true })).toBeVisible()
|
||||
})
|
||||
|
||||
Then('the New Knowledge view should be unavailable', async function (this: DifyWorld) {
|
||||
await expect(this.getPage().getByRole('button', { name: 'New', exact: true })).toHaveCount(0)
|
||||
})
|
||||
|
||||
When('I try to open the New Knowledge creation route', async function (this: DifyWorld) {
|
||||
await this.getPage().goto('/datasets/new/create')
|
||||
})
|
||||
|
||||
Then('I should return to the Legacy Knowledge console', async function (this: DifyWorld) {
|
||||
await expect(this.getPage()).toHaveURL(/\/datasets(?:\?.*)?$/)
|
||||
await expect(
|
||||
this.getPage().getByRole('heading', { name: 'Knowledge', exact: true }),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
Then('no KnowledgeFS request should leave the browser', async function (this: DifyWorld) {
|
||||
expect(this.newRag.knowledgeFsRequests).toEqual([])
|
||||
expect(this.newRag.knowledgeFsRequestFailures).toEqual([])
|
||||
expect(this.newRag.knowledgeFsResponses).toEqual([])
|
||||
})
|
||||
|
||||
When('I switch to the New Knowledge view', async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
const guide = page.getByRole('dialog', { name: 'Meet the new Knowledge Base ✨' })
|
||||
await expect(guide).toBeVisible()
|
||||
await guide.getByRole('button', { name: 'Got it' }).click()
|
||||
await page.getByRole('button', { name: 'New', exact: true }).click()
|
||||
await expect(page.getByRole('region', { name: 'New', exact: true })).toBeVisible()
|
||||
})
|
||||
|
||||
When('I create a private E2E Knowledge space', async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
const name = createE2EResourceName('New RAG Knowledge')
|
||||
await page.getByRole('link', { name: 'Create' }).click()
|
||||
await expect(page.getByRole('heading', { name: 'Create Knowledge' })).toBeVisible()
|
||||
await page.getByLabel('Knowledge name').fill(name)
|
||||
await page.getByLabel('Description').fill('New RAG end-to-end release smoke resource')
|
||||
const permission = page.getByRole('combobox', { name: 'Permission' })
|
||||
if (await permission.isEnabled()) {
|
||||
await permission.click()
|
||||
await page.getByRole('option', { name: 'Only me' }).click()
|
||||
} else {
|
||||
await expect(permission).toBeDisabled()
|
||||
await expect(permission).toContainText('Only me')
|
||||
}
|
||||
await page.getByRole('button', { name: 'Create Knowledge' }).click()
|
||||
await expect(page).toHaveURL(/\/datasets\/new\/([^/]+)\/sources$/)
|
||||
const match = page.url().match(/\/datasets\/new\/([^/]+)\/sources$/)
|
||||
const knowledgeSpaceId = match?.[1]
|
||||
if (!knowledgeSpaceId) throw new Error('Created Knowledge route did not include a space id.')
|
||||
this.newRag.knowledgeSpaceId = knowledgeSpaceId
|
||||
this.newRag.knowledgeSpaceName = name
|
||||
this.registerCleanup(() => deleteKnowledgeFsSpace(knowledgeSpaceId, this.getConsoleClient()))
|
||||
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||||
await expect(page.getByRole('heading', { name: 'No sources connected yet' })).toBeVisible()
|
||||
})
|
||||
|
||||
When('I connect the configured Firecrawl provider', async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
const config = resolveNewRagSmokeConfig(process.env)
|
||||
await page.getByRole('link', { name: 'Add source' }).click()
|
||||
await expect(page.getByRole('heading', { name: 'Add source' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Configure Firecrawl' }).click()
|
||||
for (const [field, value] of Object.entries(config.connectionCredentials))
|
||||
await page.getByLabel(humanizeFieldName(field)).fill(value)
|
||||
await page.getByRole('button', { name: 'Connect Firecrawl' }).click()
|
||||
await expect(page.getByRole('status').filter({ hasText: 'Firecrawl connected' })).toBeVisible({
|
||||
timeout: 60_000,
|
||||
})
|
||||
})
|
||||
|
||||
When('I crawl the configured website', { timeout: 210_000 }, async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
const config = resolveNewRagSmokeConfig(process.env)
|
||||
const sourceName = createE2EResourceName('Website Source')
|
||||
this.newRag.sourceName = sourceName
|
||||
await page.getByLabel('Root URL').fill(config.crawlUrl)
|
||||
await page.getByLabel('Source name').fill(sourceName)
|
||||
const responseStart = this.newRag.knowledgeFsResponses.length
|
||||
const failureStart = this.newRag.knowledgeFsRequestFailures.length
|
||||
await page.getByRole('button', { name: 'Crawl & preview' }).click()
|
||||
const selectAll = page.getByRole('checkbox', { name: 'Select all' })
|
||||
const crawlFailure = page.getByRole('alert').filter({ hasText: "Couldn't crawl" })
|
||||
await selectAll.or(crawlFailure).waitFor({ state: 'visible', timeout: 180_000 })
|
||||
if (await crawlFailure.isVisible()) {
|
||||
const errorResponse = this.newRag.knowledgeFsResponses
|
||||
.slice(responseStart)
|
||||
.filter((response) => response.status >= 400)
|
||||
.at(-1)
|
||||
const failure = this.newRag.knowledgeFsRequestFailures.slice(failureStart).at(-1)
|
||||
const requestDiagnostic = errorResponse
|
||||
? `${errorResponse.method} ${new URL(errorResponse.url).pathname}: HTTP ${errorResponse.status}${errorResponse.traceId ? ` (trace ${errorResponse.traceId})` : ''}`
|
||||
: failure
|
||||
? `${failure.method} ${new URL(failure.url).pathname}: ${failure.error}`
|
||||
: 'No failed KnowledgeFS browser request or error response was captured.'
|
||||
const failureText = (await crawlFailure.textContent()) ?? 'Crawl preview failed.'
|
||||
const message = failureText.replaceAll(/\s+/g, ' ').trim()
|
||||
throw new Error(`${message} ${requestDiagnostic}`)
|
||||
}
|
||||
})
|
||||
|
||||
When('I select every crawled page with a manual sync policy', async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
await page.getByRole('checkbox', { name: 'Select all' }).click()
|
||||
await page.getByLabel('Sync policy').selectOption({ label: 'Manual only' })
|
||||
await page.getByRole('button', { name: 'Add source' }).click()
|
||||
await expect(page).toHaveURL(/\/datasets\/new\/[^/]+\/sources$/)
|
||||
})
|
||||
|
||||
Then(
|
||||
'the website source should become active',
|
||||
{ timeout: 210_000 },
|
||||
async function (this: DifyWorld) {
|
||||
const sourceName = this.newRag.sourceName
|
||||
if (!sourceName) throw new Error('The New RAG smoke source name is missing.')
|
||||
const row = this.getPage().getByRole('row').filter({ hasText: sourceName })
|
||||
await expect(row).toContainText('Active', { timeout: 180_000 })
|
||||
},
|
||||
)
|
||||
|
||||
When('I open the source Documents', async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
await page.getByRole('link', { name: 'Documents' }).click()
|
||||
await expect(page.getByRole('heading', { name: 'Documents' })).toBeVisible()
|
||||
})
|
||||
|
||||
Then(
|
||||
'a crawled document should become ready',
|
||||
{ timeout: 210_000 },
|
||||
async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
const table = page.getByRole('table')
|
||||
const documentLink = table.getByRole('link').first()
|
||||
await expect(documentLink).toBeVisible({ timeout: 180_000 })
|
||||
const row = page.getByRole('row').filter({ has: documentLink })
|
||||
await expect(row).toContainText('Ready', { timeout: 180_000 })
|
||||
this.newRag.documentTitle = (await documentLink.textContent())?.trim()
|
||||
},
|
||||
)
|
||||
|
||||
When('I open the ready document', async function (this: DifyWorld) {
|
||||
const title = this.newRag.documentTitle
|
||||
if (!title) throw new Error('The ready New RAG document title is missing.')
|
||||
const page = this.getPage()
|
||||
await page.getByRole('table').getByRole('link', { name: title }).click()
|
||||
await expect(page).toHaveURL(/\/datasets\/new\/[^/]+\/documents\/[^/]+$/)
|
||||
this.newRag.documentUrl = page.url()
|
||||
})
|
||||
|
||||
Then('I should see its revision and chunk tree', async function (this: DifyWorld) {
|
||||
const title = this.newRag.documentTitle
|
||||
if (!title) throw new Error('The ready New RAG document title is missing.')
|
||||
const page = this.getPage()
|
||||
await expect(page.getByRole('heading', { name: title })).toBeVisible()
|
||||
await expect(page.getByRole('combobox', { name: 'Revision' })).toBeVisible()
|
||||
await expect(page.getByRole('tree')).toBeVisible()
|
||||
})
|
||||
|
||||
Then('the same document detail should be restored', async function (this: DifyWorld) {
|
||||
const { documentTitle, documentUrl } = this.newRag
|
||||
if (!documentTitle || !documentUrl) throw new Error('The document restore checkpoint is missing.')
|
||||
await expect(this.getPage()).toHaveURL(documentUrl)
|
||||
await expect(this.getPage().getByRole('heading', { name: documentTitle })).toBeVisible()
|
||||
await expect(this.getPage().getByRole('tree')).toBeVisible()
|
||||
})
|
||||
|
||||
When('I return to the source Documents', async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
const knowledgeSpaceName = this.newRag.knowledgeSpaceName
|
||||
if (!knowledgeSpaceName) throw new Error('The New RAG smoke Knowledge name is missing.')
|
||||
await page
|
||||
.getByRole('navigation', { name: knowledgeSpaceName })
|
||||
.getByRole('link', { name: 'Documents' })
|
||||
.click()
|
||||
await expect(page.getByRole('heading', { name: 'Documents' })).toBeVisible()
|
||||
})
|
||||
|
||||
Then('the same ready document should remain available', async function (this: DifyWorld) {
|
||||
const title = this.newRag.documentTitle
|
||||
if (!title) throw new Error('The ready New RAG document title is missing.')
|
||||
const documentLink = this.getPage().getByRole('table').getByRole('link', { name: title })
|
||||
await expect(documentLink).toBeVisible()
|
||||
await expect(this.getPage().getByRole('row').filter({ has: documentLink })).toContainText('Ready')
|
||||
})
|
||||
|
||||
When('I reopen the ready document', async function (this: DifyWorld) {
|
||||
const title = this.newRag.documentTitle
|
||||
if (!title) throw new Error('The ready New RAG document title is missing.')
|
||||
const page = this.getPage()
|
||||
await page.getByRole('table').getByRole('link', { name: title }).click()
|
||||
await expect(page).toHaveURL(
|
||||
this.newRag.documentUrl ?? /\/datasets\/new\/[^/]+\/documents\/[^/]+$/,
|
||||
)
|
||||
})
|
||||
|
||||
When('I return to Legacy Knowledge', async function (this: DifyWorld) {
|
||||
const page = this.getPage()
|
||||
await page.goto('/datasets')
|
||||
await expect(page.getByRole('button', { name: 'Legacy', exact: true })).toHaveAttribute(
|
||||
'aria-pressed',
|
||||
'true',
|
||||
)
|
||||
})
|
||||
|
||||
Then(
|
||||
'the KnowledgeFS tenant and read-only boundaries should hold',
|
||||
async function (this: DifyWorld) {
|
||||
const knowledgeSpaceId = this.newRag.knowledgeSpaceId
|
||||
if (!knowledgeSpaceId) throw new Error('The New RAG smoke Knowledge id is missing.')
|
||||
await assertKnowledgeFsAccessBoundaries(knowledgeSpaceId, this.getConsoleClient())
|
||||
},
|
||||
)
|
||||
|
||||
Then('the New RAG requests should be proxied with diagnostics', async function (this: DifyWorld) {
|
||||
const config = resolveNewRagSmokeConfig(process.env)
|
||||
expect(this.newRag.knowledgeFsRequests.length).toBeGreaterThan(0)
|
||||
expect(this.newRag.knowledgeFsRequestFailures).toEqual([])
|
||||
expect(
|
||||
this.newRag.knowledgeFsRequests.every((url) =>
|
||||
new URL(url).pathname.includes(knowledgeFsProxyPath),
|
||||
),
|
||||
).toBe(true)
|
||||
expect(
|
||||
this.newRag.knowledgeFsRequests.every(
|
||||
(url) => new URL(url).origin !== new URL(config.knowledgeFsBaseUrl).origin,
|
||||
),
|
||||
).toBe(true)
|
||||
expect(this.newRag.knowledgeFsResponses.some((response) => response.method !== 'GET')).toBe(true)
|
||||
expect(this.newRag.knowledgeFsResponses.every((response) => response.status < 400)).toBe(true)
|
||||
expect(
|
||||
this.newRag.knowledgeFsResponses
|
||||
.filter((response) => response.method !== 'GET')
|
||||
.every((response) => Boolean(response.traceId)),
|
||||
).toBe(true)
|
||||
})
|
||||
@@ -77,6 +77,29 @@ export const createAgentBuilderWorldState = () => ({
|
||||
|
||||
export type AgentBuilderWorldState = ReturnType<typeof createAgentBuilderWorldState>
|
||||
|
||||
export const createNewRagWorldState = () => ({
|
||||
documentTitle: undefined as string | undefined,
|
||||
documentUrl: undefined as string | undefined,
|
||||
knowledgeFsRequests: [] as string[],
|
||||
knowledgeFsRequestFailures: [] as Array<{
|
||||
error: string
|
||||
method: string
|
||||
url: string
|
||||
}>,
|
||||
knowledgeFsResponses: [] as Array<{
|
||||
method: string
|
||||
status: number
|
||||
traceId?: string
|
||||
url: string
|
||||
}>,
|
||||
knowledgeSpaceId: undefined as string | undefined,
|
||||
knowledgeSpaceName: undefined as string | undefined,
|
||||
legacyDatasetName: undefined as string | undefined,
|
||||
sourceName: undefined as string | undefined,
|
||||
})
|
||||
|
||||
export type NewRagWorldState = ReturnType<typeof createNewRagWorldState>
|
||||
|
||||
export class DifyWorld extends World {
|
||||
context: BrowserContext | undefined
|
||||
consoleRequestContext: APIRequestContext | undefined
|
||||
@@ -98,6 +121,7 @@ export class DifyWorld extends World {
|
||||
createdAgentDriveFiles: CreatedAgentDriveFile[] = []
|
||||
createdBuiltinToolCredentials: CreatedBuiltinToolCredential[] = []
|
||||
agentBuilder: AgentBuilderWorldState = createAgentBuilderWorldState()
|
||||
newRag: NewRagWorldState = createNewRagWorldState()
|
||||
scenarioCleanups: ScenarioCleanup[] = []
|
||||
capturedDownloads: Download[] = []
|
||||
shareURL: string | undefined
|
||||
@@ -123,6 +147,7 @@ export class DifyWorld extends World {
|
||||
this.createdAgentDriveFiles = []
|
||||
this.createdBuiltinToolCredentials = []
|
||||
this.agentBuilder = createAgentBuilderWorldState()
|
||||
this.newRag = createNewRagWorldState()
|
||||
this.scenarioCleanups = []
|
||||
this.capturedDownloads = []
|
||||
this.shareURL = undefined
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"e2e:install": "playwright install --with-deps chromium webkit",
|
||||
"e2e:middleware:down": "tsx ./scripts/setup.ts middleware-down",
|
||||
"e2e:middleware:up": "tsx ./scripts/setup.ts middleware-up",
|
||||
"e2e:new-rag": "tsx ./scripts/run-new-rag-smoke.ts",
|
||||
"e2e:post-merge": "tsx ./scripts/run-post-merge.ts",
|
||||
"e2e:post-merge:prepare": "tsx ./scripts/seed.ts --pack agent-v2 --profile post-merge",
|
||||
"e2e:reset": "tsx ./scripts/setup.ts reset",
|
||||
|
||||
@@ -13,6 +13,7 @@ type RunCommandOptions = {
|
||||
args: string[]
|
||||
cwd: string
|
||||
env?: NodeJS.ProcessEnv
|
||||
inheritEnv?: boolean
|
||||
stdio?: 'inherit' | 'pipe'
|
||||
}
|
||||
|
||||
@@ -63,14 +64,12 @@ export const runCommand = async ({
|
||||
args,
|
||||
cwd,
|
||||
env,
|
||||
inheritEnv = true,
|
||||
stdio = 'inherit',
|
||||
}: RunCommandOptions): Promise<RunCommandResult> => {
|
||||
const childProcess = spawn(command, args, {
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
...env,
|
||||
},
|
||||
env: inheritEnv ? { ...process.env, ...env } : env,
|
||||
stdio: stdio === 'inherit' ? 'inherit' : 'pipe',
|
||||
})
|
||||
|
||||
|
||||
@@ -14,17 +14,19 @@ type RunOptions = {
|
||||
forwardArgs: string[]
|
||||
full: boolean
|
||||
headed: boolean
|
||||
preserveState: boolean
|
||||
}
|
||||
|
||||
const parseArgs = (argv: string[]): RunOptions => {
|
||||
let full = false
|
||||
let headed = false
|
||||
let preserveState = false
|
||||
const forwardArgs: string[] = []
|
||||
|
||||
for (const [index, arg] of argv.entries()) {
|
||||
if (arg === '--') {
|
||||
forwardArgs.push(...argv.slice(index + 1))
|
||||
return { forwardArgs, full, headed }
|
||||
return { forwardArgs, full, headed, preserveState }
|
||||
}
|
||||
|
||||
if (arg === '--full') {
|
||||
@@ -37,16 +39,22 @@ const parseArgs = (argv: string[]): RunOptions => {
|
||||
continue
|
||||
}
|
||||
|
||||
if (arg === '--preserve-state') {
|
||||
preserveState = true
|
||||
continue
|
||||
}
|
||||
|
||||
forwardArgs.push(arg)
|
||||
}
|
||||
|
||||
return { forwardArgs, full, headed }
|
||||
return { forwardArgs, full, headed, preserveState }
|
||||
}
|
||||
|
||||
const hasCustomTags = (forwardArgs: string[]) =>
|
||||
forwardArgs.some((arg) => arg === '--tags' || arg.startsWith('--tags='))
|
||||
|
||||
const fullNonExternalTags = 'not @prepared and not @external-model and not @external-tool'
|
||||
const fullNonExternalTags =
|
||||
'not @prepared and not @external-model and not @external-tool and not @new-rag-smoke'
|
||||
|
||||
const isTruthyEnv = (value: string | undefined) => value === '1' || value === 'true'
|
||||
|
||||
@@ -88,9 +96,10 @@ const waitForUnexpectedProcessExit = async (
|
||||
}
|
||||
|
||||
const main = async () => {
|
||||
const { forwardArgs, full, headed } = parseArgs(process.argv.slice(2))
|
||||
const { forwardArgs, full, headed, preserveState } = parseArgs(process.argv.slice(2))
|
||||
if (preserveState && !full) throw new Error('--preserve-state requires --full.')
|
||||
const startMiddlewareForRun = full
|
||||
const resetStateForRun = full
|
||||
const resetStateForRun = full && !preserveState
|
||||
const startAgentBackendForRun = shouldStartAgentBackend()
|
||||
|
||||
if (resetStateForRun) await resetState()
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { e2eDir, isMainModule, runCommand } from './common'
|
||||
import { resetState } from './setup'
|
||||
|
||||
type SmokeEnvironment = NodeJS.ProcessEnv
|
||||
|
||||
export type NewRagSmokeConfig = {
|
||||
connectionCredentials: Record<string, string>
|
||||
crawlUrl: string
|
||||
knowledgeFsBaseUrl: string
|
||||
knowledgeFsJwtSecret: string
|
||||
}
|
||||
|
||||
export type NewRagSmokeRun = {
|
||||
env: SmokeEnvironment
|
||||
label: 'default-disabled' | 'explicit-disabled' | 'enabled-happy-path'
|
||||
preserveState: boolean
|
||||
tag: '@new-rag-flag-default' | '@new-rag-flag-disabled' | '@new-rag-happy-path'
|
||||
}
|
||||
|
||||
const knowledgeFsEnvironmentKeys = [
|
||||
'KNOWLEDGE_FS_BASE_URL',
|
||||
'KNOWLEDGE_FS_ENABLED',
|
||||
'KNOWLEDGE_FS_JWT_SECRET',
|
||||
] as const
|
||||
|
||||
const providerSecretEnvironmentKeys = [
|
||||
'E2E_NEW_RAG_CONNECTION_CREDENTIALS_JSON',
|
||||
'E2E_NEW_RAG_KNOWLEDGE_FS_JWT_SECRET',
|
||||
] as const
|
||||
|
||||
const executionOverrideEnvironmentKeys = ['E2E_CUCUMBER_TAGS', 'E2E_REUSE_WEB_SERVER'] as const
|
||||
|
||||
const required = (env: SmokeEnvironment, name: string) => {
|
||||
const value = env[name]?.trim()
|
||||
if (!value) throw new Error(`${name} is required for the New RAG smoke test.`)
|
||||
return value
|
||||
}
|
||||
|
||||
const httpUrl = (env: SmokeEnvironment, name: string, trimTrailingSlash: boolean) => {
|
||||
const value = required(env, name)
|
||||
let url: URL
|
||||
try {
|
||||
url = new URL(value)
|
||||
} catch {
|
||||
throw new Error(`${name} must be an absolute HTTP(S) URL.`)
|
||||
}
|
||||
|
||||
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password)
|
||||
throw new Error(`${name} must be an absolute HTTP(S) URL without credentials.`)
|
||||
|
||||
const normalized = url.toString()
|
||||
return trimTrailingSlash ? normalized.replace(/\/$/, '') : normalized
|
||||
}
|
||||
|
||||
const connectionCredentials = (env: SmokeEnvironment) => {
|
||||
const value = required(env, 'E2E_NEW_RAG_CONNECTION_CREDENTIALS_JSON')
|
||||
let parsed: unknown
|
||||
try {
|
||||
parsed = JSON.parse(value)
|
||||
} catch {
|
||||
throw new Error('E2E_NEW_RAG_CONNECTION_CREDENTIALS_JSON must be a JSON object.')
|
||||
}
|
||||
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
||||
throw new Error('E2E_NEW_RAG_CONNECTION_CREDENTIALS_JSON must be a JSON object.')
|
||||
|
||||
const entries = Object.entries(parsed)
|
||||
if (
|
||||
entries.length === 0 ||
|
||||
entries.some(
|
||||
([name, credential]) => !name.trim() || typeof credential !== 'string' || !credential.trim(),
|
||||
)
|
||||
)
|
||||
throw new Error(
|
||||
'E2E_NEW_RAG_CONNECTION_CREDENTIALS_JSON must contain non-empty string credentials.',
|
||||
)
|
||||
|
||||
return Object.fromEntries(entries.map(([name, credential]) => [name, credential.trim()]))
|
||||
}
|
||||
|
||||
export const resolveNewRagSmokeConfig = (env: SmokeEnvironment): NewRagSmokeConfig => {
|
||||
const knowledgeFsBaseUrl = httpUrl(env, 'E2E_NEW_RAG_KNOWLEDGE_FS_BASE_URL', true)
|
||||
const knowledgeFsJwtSecret = required(env, 'E2E_NEW_RAG_KNOWLEDGE_FS_JWT_SECRET')
|
||||
if (knowledgeFsJwtSecret.length < 32)
|
||||
throw new Error('E2E_NEW_RAG_KNOWLEDGE_FS_JWT_SECRET must contain at least 32 characters.')
|
||||
|
||||
return {
|
||||
connectionCredentials: connectionCredentials(env),
|
||||
crawlUrl: httpUrl(env, 'E2E_NEW_RAG_CRAWL_URL', false),
|
||||
knowledgeFsBaseUrl,
|
||||
knowledgeFsJwtSecret,
|
||||
}
|
||||
}
|
||||
|
||||
const withoutKnowledgeFsConfiguration = (source: SmokeEnvironment) => {
|
||||
const env = { ...source }
|
||||
for (const key of knowledgeFsEnvironmentKeys) delete env[key]
|
||||
for (const key of providerSecretEnvironmentKeys) delete env[key]
|
||||
return env
|
||||
}
|
||||
|
||||
const withoutExecutionOverrides = (source: SmokeEnvironment) => {
|
||||
const env = { ...source }
|
||||
for (const key of executionOverrideEnvironmentKeys) delete env[key]
|
||||
return env
|
||||
}
|
||||
|
||||
export const buildNewRagSmokeRuns = (source: SmokeEnvironment): NewRagSmokeRun[] => {
|
||||
const config = resolveNewRagSmokeConfig(source)
|
||||
const isolatedSource = withoutExecutionOverrides(source)
|
||||
const disabledBase = withoutKnowledgeFsConfiguration(isolatedSource)
|
||||
|
||||
return [
|
||||
{
|
||||
env: {
|
||||
...disabledBase,
|
||||
E2E_NEW_RAG_EXPECTED_FLAG_MODE: 'default-disabled',
|
||||
},
|
||||
label: 'default-disabled',
|
||||
preserveState: false,
|
||||
tag: '@new-rag-flag-default',
|
||||
},
|
||||
{
|
||||
env: {
|
||||
...disabledBase,
|
||||
E2E_NEW_RAG_EXPECTED_FLAG_MODE: 'explicit-disabled',
|
||||
KNOWLEDGE_FS_ENABLED: 'false',
|
||||
},
|
||||
label: 'explicit-disabled',
|
||||
preserveState: true,
|
||||
tag: '@new-rag-flag-disabled',
|
||||
},
|
||||
{
|
||||
env: {
|
||||
...isolatedSource,
|
||||
E2E_NEW_RAG_CRAWL_URL: config.crawlUrl,
|
||||
E2E_NEW_RAG_EXPECTED_FLAG_MODE: 'enabled',
|
||||
E2E_NEW_RAG_KNOWLEDGE_FS_BASE_URL: config.knowledgeFsBaseUrl,
|
||||
KNOWLEDGE_FS_BASE_URL: config.knowledgeFsBaseUrl,
|
||||
KNOWLEDGE_FS_ENABLED: 'true',
|
||||
KNOWLEDGE_FS_JWT_SECRET: config.knowledgeFsJwtSecret,
|
||||
},
|
||||
label: 'enabled-happy-path',
|
||||
preserveState: true,
|
||||
tag: '@new-rag-happy-path',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export const newRagCucumberArgs = (tag: NewRagSmokeRun['tag'], preserveState: boolean) => [
|
||||
'exec',
|
||||
'tsx',
|
||||
'./scripts/run-cucumber.ts',
|
||||
'--full',
|
||||
...(preserveState ? ['--preserve-state'] : []),
|
||||
'--',
|
||||
'--tags',
|
||||
tag,
|
||||
]
|
||||
|
||||
const requireKnowledgeFsHealth = async (baseUrl: string) => {
|
||||
const healthUrl = new URL('/health', `${baseUrl}/`)
|
||||
const response = await fetch(healthUrl, { signal: AbortSignal.timeout(10_000) }).catch(
|
||||
(error: unknown) => {
|
||||
throw new Error(
|
||||
`KnowledgeFS is not reachable at ${healthUrl}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
},
|
||||
)
|
||||
if (!response.ok)
|
||||
throw new Error(
|
||||
`KnowledgeFS health check failed with ${response.status} ${response.statusText}.`,
|
||||
)
|
||||
}
|
||||
|
||||
const main = async () => {
|
||||
const config = resolveNewRagSmokeConfig(process.env)
|
||||
await requireKnowledgeFsHealth(config.knowledgeFsBaseUrl)
|
||||
const stateDirectory = await mkdtemp(path.join(tmpdir(), 'dify-new-rag-smoke-'))
|
||||
const smokeEnvironment = {
|
||||
...process.env,
|
||||
E2E_NEW_RAG_LEGACY_DATASET_NAME: `Legacy flag ${Date.now().toString(36)}`,
|
||||
E2E_NEW_RAG_LEGACY_DATASET_STATE_PATH: path.join(stateDirectory, 'legacy-dataset.json'),
|
||||
}
|
||||
|
||||
try {
|
||||
for (const run of buildNewRagSmokeRuns(smokeEnvironment)) {
|
||||
console.warn(`[new-rag-smoke] start ${run.label}`)
|
||||
const result = await runCommand({
|
||||
command: 'pnpm',
|
||||
args: newRagCucumberArgs(run.tag, run.preserveState),
|
||||
cwd: e2eDir,
|
||||
env: run.env,
|
||||
inheritEnv: false,
|
||||
})
|
||||
if (result.exitCode !== 0)
|
||||
throw new Error(`New RAG smoke run ${run.label} failed with exit code ${result.exitCode}.`)
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
await resetState({ preserveArtifacts: true })
|
||||
} finally {
|
||||
await rm(stateDirectory, { force: true, recursive: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMainModule(import.meta.url)) {
|
||||
void main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error))
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
@@ -52,8 +52,9 @@ const middlewareDataPaths = [
|
||||
path.join(dockerDir, 'volumes', 'weaviate'),
|
||||
]
|
||||
|
||||
const e2eStatePaths = [
|
||||
path.join(e2eDir, '.auth'),
|
||||
const e2eAuthStatePaths = [path.join(e2eDir, '.auth')]
|
||||
|
||||
const e2eArtifactPaths = [
|
||||
path.join(e2eDir, 'cucumber-report'),
|
||||
path.join(e2eDir, 'cucumber-report-non-external'),
|
||||
path.join(e2eDir, 'cucumber-report-webkit'),
|
||||
@@ -65,6 +66,8 @@ const e2eStatePaths = [
|
||||
path.join(e2eDir, 'test-results'),
|
||||
]
|
||||
|
||||
const e2eStatePaths = [...e2eAuthStatePaths, ...e2eArtifactPaths]
|
||||
|
||||
const composeArgs = [
|
||||
'compose',
|
||||
'-f',
|
||||
@@ -466,7 +469,7 @@ export const stopMiddleware = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
export const resetState = async () => {
|
||||
export const resetState = async ({ preserveArtifacts = false } = {}) => {
|
||||
console.log('Stopping middleware services...')
|
||||
try {
|
||||
await stopMiddleware()
|
||||
@@ -483,8 +486,9 @@ export const resetState = async () => {
|
||||
)
|
||||
|
||||
console.log('Removing E2E local state...')
|
||||
const localStatePaths = preserveArtifacts ? e2eAuthStatePaths : e2eStatePaths
|
||||
await Promise.all(
|
||||
e2eStatePaths.map((targetPath) => rm(targetPath, { force: true, recursive: true })),
|
||||
localStatePaths.map((targetPath) => rm(targetPath, { force: true, recursive: true })),
|
||||
)
|
||||
|
||||
console.log('E2E state reset complete.')
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { createHmac } from 'node:crypto'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
createKnowledgeFsJwt,
|
||||
deleteKnowledgeFsSpaceWithIdentity,
|
||||
} from '../features/new-rag/support/runtime'
|
||||
|
||||
const decode = (value: string) => JSON.parse(Buffer.from(value, 'base64url').toString('utf8'))
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('createKnowledgeFsJwt', () => {
|
||||
it('binds the interactive Dify identity, tenant, and requested scope', () => {
|
||||
try {
|
||||
vi.setSystemTime(new Date('2026-07-21T09:00:00.000Z'))
|
||||
const token = createKnowledgeFsJwt({
|
||||
accountId: 'account-1',
|
||||
scope: 'knowledge-spaces:read',
|
||||
secret: 'knowledge-fs-smoke-secret-at-least-32-characters',
|
||||
tenantId: 'tenant-1',
|
||||
})
|
||||
const [header, payload, signature] = token.split('.')
|
||||
|
||||
expect(decode(header!)).toEqual({ alg: 'HS256', typ: 'JWT' })
|
||||
expect(decode(payload!)).toEqual({
|
||||
aud: 'knowledge-fs',
|
||||
caller_kind: 'interactive',
|
||||
dify_account_id: 'dify-account:account-1',
|
||||
exp: 1784624460,
|
||||
iat: 1784624400,
|
||||
iss: 'dify',
|
||||
scopes: ['knowledge-spaces:read'],
|
||||
sub: 'dify-workspace:tenant-1',
|
||||
tenant_id: 'tenant-1',
|
||||
})
|
||||
expect(signature).toBe(
|
||||
createHmac('sha256', 'knowledge-fs-smoke-secret-at-least-32-characters')
|
||||
.update(`${header}.${payload}`)
|
||||
.digest('base64url'),
|
||||
)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteKnowledgeFsSpaceWithIdentity', () => {
|
||||
it('reads the current revision and submits the durable deletion challenge', async () => {
|
||||
vi.stubEnv('E2E_NEW_RAG_CONNECTION_CREDENTIALS_JSON', '{"apiKey":"provider-secret"}')
|
||||
vi.stubEnv('E2E_NEW_RAG_CRAWL_URL', 'https://docs.example.com')
|
||||
vi.stubEnv('E2E_NEW_RAG_KNOWLEDGE_FS_BASE_URL', 'http://127.0.0.1:8788')
|
||||
vi.stubEnv(
|
||||
'E2E_NEW_RAG_KNOWLEDGE_FS_JWT_SECRET',
|
||||
'knowledge-fs-smoke-secret-at-least-32-characters',
|
||||
)
|
||||
const fetch = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
new Response(JSON.stringify({ name: 'E2E Knowledge', revision: 3 }), { status: 200 }),
|
||||
)
|
||||
.mockResolvedValueOnce(new Response('{}', { status: 202 }))
|
||||
vi.stubGlobal('fetch', fetch)
|
||||
|
||||
await deleteKnowledgeFsSpaceWithIdentity('space-1', {
|
||||
accountId: 'account-1',
|
||||
tenantId: 'tenant-1',
|
||||
})
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(2)
|
||||
const deletion = fetch.mock.calls[1]!
|
||||
expect(deletion[0].toString()).toBe('http://127.0.0.1:8788/knowledge-spaces/space-1')
|
||||
expect(deletion[1]).toMatchObject({
|
||||
body: JSON.stringify({ challenge: 'E2E Knowledge', expectedRevision: 3 }),
|
||||
method: 'DELETE',
|
||||
})
|
||||
expect(new Headers(deletion[1]?.headers).get('Idempotency-Key')).toBe('e2e-delete-space-1')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,120 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
buildNewRagSmokeRuns,
|
||||
newRagCucumberArgs,
|
||||
resolveNewRagSmokeConfig,
|
||||
} from '../scripts/run-new-rag-smoke'
|
||||
|
||||
const completeEnvironment = {
|
||||
E2E_NEW_RAG_CONNECTION_CREDENTIALS_JSON: JSON.stringify({
|
||||
base_url: 'http://host.docker.internal:3002',
|
||||
firecrawl_api_key: 'firecrawl-secret',
|
||||
}),
|
||||
E2E_NEW_RAG_CRAWL_URL: 'https://docs.example.com',
|
||||
E2E_NEW_RAG_KNOWLEDGE_FS_BASE_URL: 'http://127.0.0.1:8788/',
|
||||
E2E_NEW_RAG_KNOWLEDGE_FS_JWT_SECRET: 'knowledge-fs-smoke-secret-at-least-32-characters',
|
||||
E2E_NEW_RAG_LEGACY_DATASET_NAME: 'Legacy flag preservation fixture',
|
||||
E2E_NEW_RAG_LEGACY_DATASET_STATE_PATH: '/tmp/new-rag-legacy-dataset.json',
|
||||
E2E_CUCUMBER_TAGS: '@stale-caller-tag',
|
||||
E2E_REUSE_WEB_SERVER: 'true',
|
||||
PATH: '/test/bin',
|
||||
} satisfies NodeJS.ProcessEnv
|
||||
|
||||
describe('resolveNewRagSmokeConfig', () => {
|
||||
it('requires every live integration input before the smoke starts', () => {
|
||||
expect(() => resolveNewRagSmokeConfig({})).toThrow(
|
||||
'E2E_NEW_RAG_KNOWLEDGE_FS_BASE_URL is required',
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects weak JWT secrets and non-object provider credentials', () => {
|
||||
expect(() =>
|
||||
resolveNewRagSmokeConfig({
|
||||
...completeEnvironment,
|
||||
E2E_NEW_RAG_KNOWLEDGE_FS_JWT_SECRET: 'too-short',
|
||||
}),
|
||||
).toThrow('at least 32 characters')
|
||||
|
||||
expect(() =>
|
||||
resolveNewRagSmokeConfig({
|
||||
...completeEnvironment,
|
||||
E2E_NEW_RAG_CONNECTION_CREDENTIALS_JSON: '[]',
|
||||
}),
|
||||
).toThrow('must be a JSON object')
|
||||
})
|
||||
|
||||
it('accepts provider-specific credential field names from the live catalog', () => {
|
||||
expect(
|
||||
resolveNewRagSmokeConfig({
|
||||
...completeEnvironment,
|
||||
E2E_NEW_RAG_CONNECTION_CREDENTIALS_JSON: JSON.stringify({ apiKey: 'provider-secret' }),
|
||||
}).connectionCredentials,
|
||||
).toEqual({ apiKey: 'provider-secret' })
|
||||
})
|
||||
|
||||
it('normalizes the live endpoints without exposing credentials', () => {
|
||||
expect(resolveNewRagSmokeConfig(completeEnvironment)).toEqual({
|
||||
connectionCredentials: {
|
||||
base_url: 'http://host.docker.internal:3002',
|
||||
firecrawl_api_key: 'firecrawl-secret',
|
||||
},
|
||||
crawlUrl: 'https://docs.example.com/',
|
||||
knowledgeFsBaseUrl: 'http://127.0.0.1:8788',
|
||||
knowledgeFsJwtSecret: 'knowledge-fs-smoke-secret-at-least-32-characters',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildNewRagSmokeRuns', () => {
|
||||
it('builds an isolated default, explicit-off, and enabled matrix in order', () => {
|
||||
const runs = buildNewRagSmokeRuns(completeEnvironment)
|
||||
|
||||
expect(runs.map(({ label, preserveState, tag }) => ({ label, preserveState, tag }))).toEqual([
|
||||
{ label: 'default-disabled', preserveState: false, tag: '@new-rag-flag-default' },
|
||||
{ label: 'explicit-disabled', preserveState: true, tag: '@new-rag-flag-disabled' },
|
||||
{ label: 'enabled-happy-path', preserveState: true, tag: '@new-rag-happy-path' },
|
||||
])
|
||||
|
||||
expect(runs[0]?.env).not.toHaveProperty('KNOWLEDGE_FS_ENABLED')
|
||||
expect(runs[0]?.env).not.toHaveProperty('KNOWLEDGE_FS_BASE_URL')
|
||||
expect(runs[0]?.env).not.toHaveProperty('KNOWLEDGE_FS_JWT_SECRET')
|
||||
expect(runs[1]?.env).toMatchObject({ KNOWLEDGE_FS_ENABLED: 'false' })
|
||||
expect(runs[1]?.env).not.toHaveProperty('KNOWLEDGE_FS_BASE_URL')
|
||||
expect(runs[2]?.env).toMatchObject({
|
||||
KNOWLEDGE_FS_BASE_URL: 'http://127.0.0.1:8788',
|
||||
KNOWLEDGE_FS_ENABLED: 'true',
|
||||
KNOWLEDGE_FS_JWT_SECRET: 'knowledge-fs-smoke-secret-at-least-32-characters',
|
||||
})
|
||||
expect(runs[2]?.env.PATH).toBe('/test/bin')
|
||||
for (const run of runs) {
|
||||
expect(run.env.E2E_NEW_RAG_LEGACY_DATASET_NAME).toBe('Legacy flag preservation fixture')
|
||||
expect(run.env.E2E_NEW_RAG_LEGACY_DATASET_STATE_PATH).toBe('/tmp/new-rag-legacy-dataset.json')
|
||||
expect(run.env).not.toHaveProperty('E2E_CUCUMBER_TAGS')
|
||||
expect(run.env).not.toHaveProperty('E2E_REUSE_WEB_SERVER')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('newRagCucumberArgs', () => {
|
||||
it('resets the first matrix entry and preserves its data across later service restarts', () => {
|
||||
expect(newRagCucumberArgs('@new-rag-flag-default', false)).toEqual([
|
||||
'exec',
|
||||
'tsx',
|
||||
'./scripts/run-cucumber.ts',
|
||||
'--full',
|
||||
'--',
|
||||
'--tags',
|
||||
'@new-rag-flag-default',
|
||||
])
|
||||
expect(newRagCucumberArgs('@new-rag-happy-path', true)).toEqual([
|
||||
'exec',
|
||||
'tsx',
|
||||
'./scripts/run-cucumber.ts',
|
||||
'--full',
|
||||
'--preserve-state',
|
||||
'--',
|
||||
'--tags',
|
||||
'@new-rag-happy-path',
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -8,5 +8,5 @@ export const knowledgeFsConsoleDeclarationsSha256 =
|
||||
export const knowledgeFsGeneratedArtifactSha256 = {
|
||||
'orpc.gen.ts': 'e0d9954f817e97a4e95dd38c4522fb403c8659d741ce485fa671b1b4ce90a540',
|
||||
'types.gen.ts': 'a558ab80f32a8555bb5b44b7a596ef4a4a7a8cb7904390993aabcc587915f530',
|
||||
'zod.gen.ts': '6aa3d3e768fe0008ca405ecbe423ee325bd8f65f745dd2d1c86f2bdb887a97fd',
|
||||
'zod.gen.ts': 'ca698a6fa64a0717e29da5d4678976b55355a2c0fe5ddfa7037325aa79ab4762',
|
||||
} as const
|
||||
|
||||
@@ -1725,7 +1725,7 @@ export const zPostKnowledgeSpacesByIdDocumentsBody = z.object({
|
||||
documentId: z.uuid().optional(),
|
||||
expectedActiveRevision: z.union([z.int().gt(0), z.enum(['null'])]).optional(),
|
||||
expectedDocumentRowVersion: z.int().gte(0).nullish(),
|
||||
file: z.string(),
|
||||
file: z.custom<Blob | File>(),
|
||||
sourceId: z.uuid().optional(),
|
||||
})
|
||||
|
||||
@@ -1768,7 +1768,7 @@ export const zDeleteKnowledgeSpacesByIdDocumentsBulkPath = z.object({
|
||||
export const zDeleteKnowledgeSpacesByIdDocumentsBulkResponse = zDurableBulkDeletionAccepted
|
||||
|
||||
export const zPostKnowledgeSpacesByIdDocumentsBulkBody = z.object({
|
||||
files: z.array(z.string()).min(1),
|
||||
files: z.array(z.custom<Blob | File>()).min(1),
|
||||
targets: z.string().optional(),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineConfig } from '@hey-api/openapi-ts'
|
||||
import { $, defineConfig } from '@hey-api/openapi-ts'
|
||||
|
||||
const input = process.env.KNOWLEDGE_FS_OPENAPI
|
||||
const outputPath = process.env.KNOWLEDGE_FS_OUTPUT ?? 'generated/knowledge-fs'
|
||||
@@ -38,6 +38,17 @@ export default defineConfig({
|
||||
},
|
||||
{
|
||||
name: 'zod',
|
||||
'~resolvers': {
|
||||
string: (ctx) => {
|
||||
if (ctx.schema.format === 'binary')
|
||||
return $(ctx.symbols.z)
|
||||
.attr('custom')
|
||||
.call()
|
||||
.generic($.type.or($.type('Blob'), $.type('File')))
|
||||
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
contracts: {
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { DocumentDetailPage } from '@/features/new-rag/document-detail-page'
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ documentId: string; knowledgeSpaceId: string }>
|
||||
}) {
|
||||
const { documentId, knowledgeSpaceId } = await params
|
||||
|
||||
return <DocumentDetailPage documentId={documentId} knowledgeSpaceId={knowledgeSpaceId} />
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { KnowledgeRoutePlaceholder } from '@/features/new-rag/knowledge-route-placeholder'
|
||||
import { DocumentsPage } from '@/features/new-rag/documents-page'
|
||||
|
||||
export default function Page() {
|
||||
return <KnowledgeRoutePlaceholder type="documents" />
|
||||
export default async function Page({ params }: { params: Promise<{ knowledgeSpaceId: string }> }) {
|
||||
const { knowledgeSpaceId } = await params
|
||||
|
||||
return <DocumentsPage knowledgeSpaceId={knowledgeSpaceId} />
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { AddSourcePage } from '@/features/new-rag/add-source-page'
|
||||
import { singleSearchParam } from '@/features/new-rag/routes'
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ knowledgeSpaceId: string }>
|
||||
searchParams: Promise<{
|
||||
draft?: string | string[]
|
||||
type?: string | string[]
|
||||
}>
|
||||
}) {
|
||||
const { knowledgeSpaceId } = await params
|
||||
const { draft, type } = await searchParams
|
||||
|
||||
return (
|
||||
<AddSourcePage
|
||||
initialSourceType={singleSearchParam(type)}
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
sourceDraftKey={singleSearchParam(draft)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { KnowledgeRoutePlaceholder } from '@/features/new-rag/knowledge-route-placeholder'
|
||||
import { SourcesPage } from '@/features/new-rag/sources-page'
|
||||
|
||||
export default function Page() {
|
||||
return <KnowledgeRoutePlaceholder type="sources" />
|
||||
export default async function Page({ params }: { params: Promise<{ knowledgeSpaceId: string }> }) {
|
||||
const { knowledgeSpaceId } = await params
|
||||
|
||||
return <SourcesPage knowledgeSpaceId={knowledgeSpaceId} />
|
||||
}
|
||||
|
||||
@@ -39,3 +39,9 @@
|
||||
.spin-animation path:nth-child(4) {
|
||||
animation-delay: 1.5s;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.spin-animation path {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import { ZENDESK_FIELD_IDS } from '@/config'
|
||||
import { refreshUserProfileAtom, userProfileAtom } from '../account-state'
|
||||
import { initialWorkspaceInfo } from '../app-context-defaults'
|
||||
import {
|
||||
datasetDefaultPermissionKeysAtom,
|
||||
refreshWorkspacePermissionKeysAfterMutationDenialAtom,
|
||||
workspacePermissionKeysAtom,
|
||||
workspacePermissionKeysLoadingAtom,
|
||||
} from '../permission-state'
|
||||
@@ -30,6 +32,7 @@ import {
|
||||
|
||||
const mockGetRequest = vi.hoisted(() => vi.fn())
|
||||
const mockPermissionKeysState = vi.hoisted(() => ({
|
||||
datasetPermissionKeys: ['dataset.acl.edit'],
|
||||
isPending: false,
|
||||
permissionKeys: ['app.create_and_management'],
|
||||
}))
|
||||
@@ -209,11 +212,15 @@ function ConsoleBootstrapProbe() {
|
||||
const isCurrentWorkspaceEditor = useAtomValue(isCurrentWorkspaceEditorAtom)
|
||||
const isCurrentWorkspaceDatasetOperator = useAtomValue(isCurrentWorkspaceDatasetOperatorAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const datasetDefaultPermissionKeys = useAtomValue(datasetDefaultPermissionKeysAtom)
|
||||
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const refreshUserProfile = useSetAtom(refreshUserProfileAtom)
|
||||
const refreshCurrentWorkspace = useSetAtom(refreshCurrentWorkspaceAtom)
|
||||
const refreshPermissionsAfterMutationDenial = useSetAtom(
|
||||
refreshWorkspacePermissionKeysAfterMutationDenialAtom,
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -221,6 +228,10 @@ function ConsoleBootstrapProbe() {
|
||||
keys:
|
||||
{workspacePermissionKeys.join(',')}
|
||||
</span>
|
||||
<span>
|
||||
dataset keys:
|
||||
{datasetDefaultPermissionKeys.join(',')}
|
||||
</span>
|
||||
<span>
|
||||
permission loading:
|
||||
{String(isLoadingWorkspacePermissionKeys)}
|
||||
@@ -268,6 +279,9 @@ function ConsoleBootstrapProbe() {
|
||||
<button type="button" onClick={refreshCurrentWorkspace}>
|
||||
refresh workspace
|
||||
</button>
|
||||
<button type="button" onClick={() => void refreshPermissionsAfterMutationDenial()}>
|
||||
refresh permissions after denial
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -324,6 +338,7 @@ describe('Console bootstrap', () => {
|
||||
vi.clearAllMocks()
|
||||
setAnalyticsConsent('granted')
|
||||
mockPermissionKeysState.isPending = false
|
||||
mockPermissionKeysState.datasetPermissionKeys = ['dataset.acl.edit']
|
||||
mockPermissionKeysState.permissionKeys = ['app.create_and_management']
|
||||
mockCurrentWorkspaceQueryState.data = mockCurrentWorkspaceResponse
|
||||
mockCurrentWorkspaceQueryState.isPending = false
|
||||
@@ -369,7 +384,7 @@ describe('Console bootstrap', () => {
|
||||
overrides: [],
|
||||
},
|
||||
dataset: {
|
||||
default_permission_keys: [],
|
||||
default_permission_keys: mockPermissionKeysState.datasetPermissionKeys,
|
||||
overrides: [],
|
||||
},
|
||||
})
|
||||
@@ -388,6 +403,7 @@ describe('Console bootstrap', () => {
|
||||
expect(await screen.findByText('user:user@example.com')).toBeInTheDocument()
|
||||
expect(await screen.findByText('workspace:Workspace')).toBeInTheDocument()
|
||||
expect(await screen.findByText('keys:app.create_and_management')).toBeInTheDocument()
|
||||
expect(screen.getByText('dataset keys:dataset.acl.edit')).toBeInTheDocument()
|
||||
expect(screen.getByText('permission loading:false')).toBeInTheDocument()
|
||||
expect(screen.getByText('workspace loading:false')).toBeInTheDocument()
|
||||
expect(await screen.findByText('version:1.0.0/1.0.1/cloud')).toBeInTheDocument()
|
||||
@@ -395,6 +411,7 @@ describe('Console bootstrap', () => {
|
||||
|
||||
it('should fall back to placeholder values when workspace, permission, or version data is missing', async () => {
|
||||
mockCurrentWorkspaceQueryState.data = undefined
|
||||
mockPermissionKeysState.datasetPermissionKeys = []
|
||||
mockPermissionKeysState.permissionKeys = []
|
||||
mockLangGeniusVersionState.data = undefined
|
||||
|
||||
@@ -404,6 +421,7 @@ describe('Console bootstrap', () => {
|
||||
expect(screen.getByText(`workspace:${initialWorkspaceInfo.name}`)).toBeInTheDocument()
|
||||
expect(screen.getByText(`role:${initialWorkspaceInfo.role}`)).toBeInTheDocument()
|
||||
expect(screen.getByText('keys:')).toBeInTheDocument()
|
||||
expect(screen.getByText('dataset keys:')).toBeInTheDocument()
|
||||
expect(screen.getByText('version://')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -454,6 +472,33 @@ describe('Console bootstrap', () => {
|
||||
expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['user-profile'] })
|
||||
expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['current-workspace'] })
|
||||
})
|
||||
|
||||
it('starts a fresh permission request without waiting for an older request', async () => {
|
||||
const { queryClient } = renderConsoleBootstrap()
|
||||
await screen.findByText('dataset keys:dataset.acl.edit')
|
||||
const olderRequest = new Promise(() => {})
|
||||
let permissionRequestCount = 0
|
||||
mockGetRequest.mockImplementation((url: string) => {
|
||||
if (url !== '/workspaces/current/rbac/my-permissions')
|
||||
return Promise.reject(new Error(`Unexpected GET ${url}`))
|
||||
permissionRequestCount += 1
|
||||
if (permissionRequestCount === 1) return olderRequest
|
||||
return Promise.resolve({
|
||||
workspace: { permission_keys: [] },
|
||||
app: { default_permission_keys: [], overrides: [] },
|
||||
dataset: { default_permission_keys: ['dataset.acl.readonly'], overrides: [] },
|
||||
})
|
||||
})
|
||||
|
||||
const backgroundRefresh = queryClient.refetchQueries({
|
||||
queryKey: ['workspace-permission-keys'],
|
||||
})
|
||||
await waitFor(() => expect(permissionRequestCount).toBe(1))
|
||||
fireEvent.click(screen.getByRole('button', { name: /refresh permissions after denial/i }))
|
||||
await waitFor(() => expect(permissionRequestCount).toBe(2))
|
||||
expect(await screen.findByText('dataset keys:dataset.acl.readonly')).toBeInTheDocument()
|
||||
await backgroundRefresh
|
||||
})
|
||||
})
|
||||
|
||||
describe('External side effects', () => {
|
||||
|
||||
@@ -19,6 +19,26 @@ export const workspacePermissionKeysAtom = atom((get) => {
|
||||
)
|
||||
})
|
||||
|
||||
export const datasetDefaultPermissionKeysAtom = atom((get) => {
|
||||
return get(workspacePermissionKeysQueryAtom).data?.dataset?.default_permission_keys ?? []
|
||||
})
|
||||
|
||||
export const workspacePermissionKeysLoadingAtom = atom((get) => {
|
||||
return get(workspacePermissionKeysQueryAtom).isPending
|
||||
})
|
||||
|
||||
export const workspacePermissionKeysFetchingAtom = atom((get) => {
|
||||
return get(workspacePermissionKeysQueryAtom).isFetching
|
||||
})
|
||||
|
||||
export const workspacePermissionKeysErrorAtom = atom((get) => {
|
||||
return get(workspacePermissionKeysQueryAtom).error
|
||||
})
|
||||
|
||||
export const retryWorkspacePermissionKeysAtom = atom(null, (get) => {
|
||||
return get(workspacePermissionKeysQueryAtom).refetch({ cancelRefetch: false })
|
||||
})
|
||||
|
||||
export const refreshWorkspacePermissionKeysAfterMutationDenialAtom = atom(null, (get) => {
|
||||
return get(workspacePermissionKeysQueryAtom).refetch({ cancelRefetch: true })
|
||||
})
|
||||
|
||||
@@ -1,20 +1,48 @@
|
||||
# New RAG
|
||||
|
||||
Renders the KnowledgeFS-backed knowledge list, creation entry points, and first-visit guidance.
|
||||
Renders the KnowledgeFS-backed knowledge list, creation flows, sources, documents, and processing
|
||||
tasks.
|
||||
|
||||
## Internal Modules
|
||||
|
||||
- `add-source-page`
|
||||
- `components/create-knowledge-dialog-parts`
|
||||
- `components/create-knowledge-exit-dialog`
|
||||
- `components/knowledge-space-card`
|
||||
- `components/knowledge-view-switcher`
|
||||
- `components/new-knowledge-list-states`
|
||||
- `crawl-selection-form`
|
||||
- `create-knowledge-page`
|
||||
- `create-knowledge-workflow`
|
||||
- `document-list`
|
||||
- `document-chunk-detail`
|
||||
- `document-chunk-tree`
|
||||
- `document-detail-header`
|
||||
- `document-detail-model`
|
||||
- `document-detail-queries`
|
||||
- `document-detail-page`
|
||||
- `document-detail-status`
|
||||
- `document-revision-content`
|
||||
- `document-model`
|
||||
- `documents-page`
|
||||
- `knowledge-route-guard`
|
||||
- `knowledge-route-placeholder`
|
||||
- `knowledge-space-shell`
|
||||
- `new-knowledge-list`
|
||||
- `processing-tasks-drawer`
|
||||
- `routes`
|
||||
- `services/processing-task-events`
|
||||
- `sources-page`
|
||||
- `storage`
|
||||
- `task-event-observer`
|
||||
- `use-document-task-status`
|
||||
- `use-document-reindex`
|
||||
- `website-crawl-preview`
|
||||
|
||||
## External Modules
|
||||
|
||||
- `app/components/base/corner-label`
|
||||
- `app/components/base/loading`
|
||||
- `app/components/base/search-input`
|
||||
- `app/components/base/skeleton`
|
||||
- `app/components/datasets/external-api/external-api-panel`
|
||||
|
||||
@@ -0,0 +1,962 @@
|
||||
import type {
|
||||
GetKnowledgeSpacesByIdSourceConnectionsResponse,
|
||||
GetSourceProvidersResponse,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { StrictMode } from 'react'
|
||||
import { render } from '@/test/console/render'
|
||||
import { AddSourcePage } from '../add-source-page'
|
||||
import { newKnowledgeSourceDraftStorageKey } from '../routes'
|
||||
|
||||
vi.mock('@/next/navigation', () => ({ useRouter: () => ({ push: vi.fn() }) }))
|
||||
|
||||
const toastInfoMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: { info: toastInfoMock },
|
||||
}))
|
||||
|
||||
type ConnectionsInfiniteData = {
|
||||
pages: GetKnowledgeSpacesByIdSourceConnectionsResponse[]
|
||||
}
|
||||
|
||||
type ConnectionsInfiniteOptions = {
|
||||
enabled?: boolean
|
||||
getNextPageParam: (
|
||||
lastPage: GetKnowledgeSpacesByIdSourceConnectionsResponse,
|
||||
) => string | undefined
|
||||
input: (pageParam: string | null) => unknown
|
||||
initialPageParam: string | null
|
||||
}
|
||||
|
||||
const queryState = vi.hoisted(() => ({
|
||||
connections: {
|
||||
data: { pages: [{ items: [] }] } as ConnectionsInfiniteData | undefined,
|
||||
error: null as unknown,
|
||||
fetchNextPage: vi.fn(),
|
||||
hasNextPage: false,
|
||||
isFetchNextPageError: false,
|
||||
isFetchingNextPage: false,
|
||||
isPending: false,
|
||||
refetch: vi.fn(),
|
||||
},
|
||||
providers: {
|
||||
data: undefined as GetSourceProvidersResponse | undefined,
|
||||
error: null as unknown,
|
||||
isPending: false,
|
||||
refetch: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
const clientMock = vi.hoisted(() => ({
|
||||
createConnection: vi.fn(),
|
||||
refreshConnection: vi.fn(),
|
||||
}))
|
||||
|
||||
const queryClientMock = vi.hoisted(() => ({
|
||||
invalidateQueries: vi.fn(),
|
||||
}))
|
||||
|
||||
const providerQueryOptionsMock = vi.hoisted(() =>
|
||||
vi.fn((options: { enabled?: boolean }) => ({
|
||||
enabled: options.enabled,
|
||||
queryKey: ['source-providers'],
|
||||
})),
|
||||
)
|
||||
const connectionInfiniteOptionsMock = vi.hoisted(() =>
|
||||
vi.fn((options: ConnectionsInfiniteOptions) => ({
|
||||
enabled: options.enabled,
|
||||
queryKey: ['source-connections'],
|
||||
})),
|
||||
)
|
||||
const providerHookOptionsMock = vi.hoisted(() => vi.fn())
|
||||
const connectionHookOptionsMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...original,
|
||||
useInfiniteQuery: (options: unknown) => {
|
||||
connectionHookOptionsMock(options)
|
||||
return queryState.connections
|
||||
},
|
||||
useQuery: (options: unknown) => {
|
||||
providerHookOptionsMock(options)
|
||||
return queryState.providers
|
||||
},
|
||||
useQueryClient: () => queryClientMock,
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleClient: {
|
||||
knowledgeFs: {
|
||||
postKnowledgeSpacesByIdSourceConnections: clientMock.createConnection,
|
||||
postKnowledgeSpacesByIdSourceConnectionsByConnectionIdRefresh: clientMock.refreshConnection,
|
||||
},
|
||||
},
|
||||
consoleQuery: {
|
||||
knowledgeFs: {
|
||||
getSourceProviders: {
|
||||
queryOptions: providerQueryOptionsMock,
|
||||
},
|
||||
getKnowledgeSpacesByIdSourceConnections: {
|
||||
infiniteOptions: connectionInfiniteOptionsMock,
|
||||
key: vi.fn(() => ['source-connections']),
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const firecrawlProvider: GetSourceProvidersResponse['items'][number] = {
|
||||
authKinds: ['api-key', 'endpoint'],
|
||||
available: true,
|
||||
capabilities: ['website-crawl'],
|
||||
configuration: [
|
||||
{
|
||||
description: 'Plugin identifier',
|
||||
name: 'pluginId',
|
||||
required: true,
|
||||
secret: false,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
description: 'Plugin provider',
|
||||
name: 'provider',
|
||||
required: true,
|
||||
secret: false,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
description: 'Plugin datasource',
|
||||
name: 'datasource',
|
||||
required: true,
|
||||
secret: false,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
description: 'Firecrawl API key',
|
||||
format: 'password',
|
||||
name: 'apiKey',
|
||||
required: true,
|
||||
secret: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
description: 'Self-hosted endpoint',
|
||||
format: 'uri',
|
||||
name: 'endpoint',
|
||||
required: false,
|
||||
secret: false,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
displayName: 'Plugin daemon website crawl',
|
||||
id: 'plugin-daemon-website',
|
||||
}
|
||||
|
||||
const connection = (
|
||||
status: 'provisioning' | 'active' | 'expired' | 'error' | 'revoked',
|
||||
version = 2,
|
||||
) => ({
|
||||
authKind: 'api-key' as const,
|
||||
configuration: {
|
||||
datasource: 'crawl',
|
||||
pluginId: 'langgenius/firecrawl_datasource',
|
||||
provider: 'firecrawl',
|
||||
},
|
||||
createdAt: '2026-07-20T10:00:00Z',
|
||||
id: 'connection-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
name: 'Firecrawl',
|
||||
providerId: 'plugin-daemon-website',
|
||||
scopes: [],
|
||||
status,
|
||||
updatedAt: '2026-07-20T10:00:00Z',
|
||||
version,
|
||||
})
|
||||
|
||||
describe('AddSourcePage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
globalThis.sessionStorage.clear()
|
||||
clientMock.createConnection.mockReset()
|
||||
clientMock.refreshConnection.mockReset()
|
||||
queryState.connections.refetch.mockReset()
|
||||
queryState.providers.refetch.mockReset()
|
||||
queryState.providers.data = { items: [firecrawlProvider] }
|
||||
queryState.providers.error = null
|
||||
queryState.providers.isPending = false
|
||||
queryState.connections.data = { pages: [{ items: [] }] }
|
||||
queryState.connections.error = null
|
||||
queryState.connections.hasNextPage = false
|
||||
queryState.connections.isFetchNextPageError = false
|
||||
queryState.connections.isFetchingNextPage = false
|
||||
queryState.connections.isPending = false
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.sessionStorage.clear()
|
||||
})
|
||||
|
||||
it('loads the provider catalog and every scoped connection cursor page', () => {
|
||||
queryState.providers.isPending = true
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(providerQueryOptionsMock).toHaveBeenCalledWith({
|
||||
context: { silent: true },
|
||||
enabled: true,
|
||||
input: {},
|
||||
retry: false,
|
||||
})
|
||||
const options = connectionInfiniteOptionsMock.mock.lastCall?.[0]
|
||||
expect(options).toBeDefined()
|
||||
if (!options) throw new Error('Expected connection infinite query options')
|
||||
expect(options.input(null)).toEqual({ params: { id: 'space-1' }, query: { limit: 200 } })
|
||||
expect(options.input('next')).toEqual({
|
||||
params: { id: 'space-1' },
|
||||
query: { cursor: 'next', limit: 200 },
|
||||
})
|
||||
expect(options.getNextPageParam({ items: [], nextCursor: 'next' })).toBe('next')
|
||||
expect(options.initialPageParam).toBeNull()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('continues loading connection pages automatically', async () => {
|
||||
queryState.connections.hasNextPage = true
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
await waitFor(() => expect(queryState.connections.fetchNextPage).toHaveBeenCalledOnce())
|
||||
})
|
||||
|
||||
it('stops website connection pagination after switching source type', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
queryState.connections.hasNextPage = true
|
||||
|
||||
await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.onlineDocuments' }))
|
||||
|
||||
expect(queryState.connections.fetchNextPage).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each(['onlineDocuments', 'onlineDrive'])(
|
||||
'does not request website provider data for the %s state',
|
||||
(initialSourceType) => {
|
||||
queryState.providers.isPending = true
|
||||
queryState.connections.isPending = true
|
||||
|
||||
render(<AddSourcePage initialSourceType={initialSourceType} knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(providerHookOptionsMock.mock.lastCall?.[0]).toMatchObject({ enabled: false })
|
||||
expect(connectionHookOptionsMock.mock.lastCall?.[0]).toMatchObject({ enabled: false })
|
||||
expect(screen.queryByRole('status', { name: 'common.loading' })).not.toBeInTheDocument()
|
||||
expect(screen.getByText('dataset.newKnowledge.providerUnavailable')).toBeInTheDocument()
|
||||
},
|
||||
)
|
||||
|
||||
it('stops automatic connection pagination after a cursor error', () => {
|
||||
queryState.connections.error = new Error('next page failed')
|
||||
queryState.connections.hasNextPage = true
|
||||
queryState.connections.isFetchNextPageError = true
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(queryState.connections.fetchNextPage).not.toHaveBeenCalled()
|
||||
expect(screen.getByText('dataset.newKnowledge.providerLoadFailed')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('finds the provider connection on a later loaded page', () => {
|
||||
queryState.connections.data = { pages: [{ items: [] }, { items: [connection('active')] }] }
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getByText('dataset.newKnowledge.providerConnected')).toBeInTheDocument()
|
||||
expect(screen.getByRole('radio', { name: 'Firecrawl' })).toBeChecked()
|
||||
expect(screen.getByRole('textbox', { name: /dataset\.newKnowledge\.rootUrl/ })).toBeEnabled()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.crawlAndPreview' }),
|
||||
).toBeDisabled()
|
||||
})
|
||||
|
||||
it('restores a website draft handed off by the creation flow', async () => {
|
||||
const user = userEvent.setup()
|
||||
queryState.connections.data = { pages: [{ items: [connection('active')] }] }
|
||||
|
||||
render(
|
||||
<AddSourcePage
|
||||
initialSourceDraft={{
|
||||
includeSubpages: false,
|
||||
maxPages: 25,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy: 'provider',
|
||||
}}
|
||||
knowledgeSpaceId="space-1"
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('textbox', { name: /dataset\.newKnowledge\.rootUrl/ })).toHaveValue(
|
||||
'https://docs.dify.ai',
|
||||
)
|
||||
expect(screen.getByRole('textbox', { name: /dataset\.newKnowledge\.sourceName/ })).toHaveValue(
|
||||
'Dify docs',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: /dataset\.newKnowledge\.crawlOptions/ }))
|
||||
expect(
|
||||
screen.getByRole('checkbox', { name: 'dataset.newKnowledge.includeSubpages' }),
|
||||
).not.toBeChecked()
|
||||
expect(screen.getByRole('spinbutton', { name: 'dataset.newKnowledge.maxPages' })).toHaveValue(
|
||||
25,
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps the exact website provider selected and lets the user switch to Firecrawl', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<AddSourcePage
|
||||
initialSourceDraft={{
|
||||
includeSubpages: true,
|
||||
maxPages: 100,
|
||||
provider: 'Jina Reader',
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy: 'manual',
|
||||
}}
|
||||
knowledgeSpaceId="space-1"
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('radio', { name: 'Jina Reader' })).toBeChecked()
|
||||
expect(providerHookOptionsMock.mock.lastCall?.[0]).toMatchObject({ enabled: false })
|
||||
expect(connectionHookOptionsMock.mock.lastCall?.[0]).toMatchObject({ enabled: false })
|
||||
expect(screen.getByText('dataset.newKnowledge.providerUnavailable')).toBeInTheDocument()
|
||||
await user.click(screen.getByRole('radio', { name: 'Firecrawl' }))
|
||||
expect(screen.getByRole('radio', { name: 'Firecrawl' })).toBeChecked()
|
||||
expect(providerHookOptionsMock.mock.lastCall?.[0]).toMatchObject({ enabled: true })
|
||||
expect(connectionHookOptionsMock.mock.lastCall?.[0]).toMatchObject({ enabled: true })
|
||||
})
|
||||
|
||||
it('clears a website dependency alert when the provider changes', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<AddSourcePage
|
||||
initialSourceDraft={{
|
||||
includeSubpages: true,
|
||||
maxPages: 100,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy: 'provider',
|
||||
}}
|
||||
knowledgeSpaceId="space-1"
|
||||
/>,
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.crawlAndPreview' }))
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('radio', { name: 'Jina Reader' }))
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('restores online document configuration and reaches an honest backend boundary', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<AddSourcePage
|
||||
initialSourceDraft={{
|
||||
provider: 'Google Docs',
|
||||
sourceName: 'Shared product docs',
|
||||
sourceType: 'onlineDocuments',
|
||||
syncPolicy: 'daily',
|
||||
}}
|
||||
knowledgeSpaceId="space-1"
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('radio', { name: 'Google Docs' })).toBeChecked()
|
||||
expect(screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' })).toHaveValue(
|
||||
'Shared product docs',
|
||||
)
|
||||
expect(screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' })).toHaveValue(
|
||||
'daily',
|
||||
)
|
||||
const addSource = screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' })
|
||||
expect(addSource).toBeEnabled()
|
||||
await user.click(addSource)
|
||||
expect(screen.getByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.sourceSetupBackendDependency',
|
||||
)
|
||||
})
|
||||
|
||||
it('restores a website draft from session storage', async () => {
|
||||
queryState.connections.data = { pages: [{ items: [connection('active')] }] }
|
||||
const storageKey = newKnowledgeSourceDraftStorageKey('draft-1')
|
||||
globalThis.sessionStorage.setItem(
|
||||
storageKey,
|
||||
JSON.stringify({
|
||||
includeSubpages: false,
|
||||
maxPages: 25,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy: 'provider',
|
||||
}),
|
||||
)
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" sourceDraftKey="draft-1" />)
|
||||
|
||||
expect(
|
||||
await screen.findByRole('textbox', { name: /dataset\.newKnowledge\.rootUrl/ }),
|
||||
).toHaveValue('https://docs.dify.ai')
|
||||
expect(screen.getByRole('textbox', { name: /dataset\.newKnowledge\.sourceName/ })).toHaveValue(
|
||||
'Dify docs',
|
||||
)
|
||||
expect(globalThis.sessionStorage.getItem(storageKey)).not.toBeNull()
|
||||
})
|
||||
|
||||
it('retains the draft through Strict Mode, delayed loading, and a real remount', async () => {
|
||||
queryState.connections.data = { pages: [{ items: [connection('active')] }] }
|
||||
queryState.connections.isPending = true
|
||||
const storageKey = newKnowledgeSourceDraftStorageKey('strict-draft')
|
||||
globalThis.sessionStorage.setItem(
|
||||
storageKey,
|
||||
JSON.stringify({
|
||||
includeSubpages: true,
|
||||
maxPages: 100,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: 'https://docs.dify.ai/strict',
|
||||
sourceName: 'Strict docs',
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy: 'provider',
|
||||
}),
|
||||
)
|
||||
|
||||
const view = render(
|
||||
<StrictMode>
|
||||
<AddSourcePage knowledgeSpaceId="space-1" sourceDraftKey="strict-draft" />
|
||||
</StrictMode>,
|
||||
)
|
||||
await waitFor(() => expect(globalThis.sessionStorage.getItem(storageKey)).not.toBeNull())
|
||||
|
||||
queryState.connections.isPending = false
|
||||
view.rerender(
|
||||
<StrictMode>
|
||||
<AddSourcePage knowledgeSpaceId="space-1" sourceDraftKey="strict-draft" />
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
expect(
|
||||
await screen.findByRole('textbox', { name: /dataset\.newKnowledge\.rootUrl/ }),
|
||||
).toHaveValue('https://docs.dify.ai/strict')
|
||||
expect(screen.getByRole('textbox', { name: /dataset\.newKnowledge\.sourceName/ })).toHaveValue(
|
||||
'Strict docs',
|
||||
)
|
||||
|
||||
view.unmount()
|
||||
queryState.connections.isPending = false
|
||||
render(
|
||||
<StrictMode>
|
||||
<AddSourcePage knowledgeSpaceId="space-1" sourceDraftKey="strict-draft" />
|
||||
</StrictMode>,
|
||||
)
|
||||
expect(
|
||||
await screen.findByRole('textbox', { name: /dataset\.newKnowledge\.rootUrl/ }),
|
||||
).toHaveValue('https://docs.dify.ai/strict')
|
||||
})
|
||||
|
||||
it('clears the stored draft when source setup is canceled', async () => {
|
||||
const user = userEvent.setup()
|
||||
const storageKey = newKnowledgeSourceDraftStorageKey('cancel-draft')
|
||||
globalThis.sessionStorage.setItem(
|
||||
storageKey,
|
||||
JSON.stringify({
|
||||
includeSubpages: true,
|
||||
maxPages: 100,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy: 'provider',
|
||||
}),
|
||||
)
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" sourceDraftKey="cancel-draft" />)
|
||||
await user.click(
|
||||
await screen.findByRole('link', { name: 'dataset.newKnowledge.cancelAddSource' }),
|
||||
)
|
||||
|
||||
expect(globalThis.sessionStorage.getItem(storageKey)).toBeNull()
|
||||
})
|
||||
|
||||
it('creates the exact Firecrawl provider connection without leaking credentials', async () => {
|
||||
const user = userEvent.setup()
|
||||
clientMock.createConnection.mockResolvedValue(connection('active'))
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.configureProvider' }))
|
||||
await user.type(screen.getByLabelText(/Api Key/), 'secret-value')
|
||||
await user.type(screen.getByLabelText('Endpoint'), 'https://crawl.example.com')
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.connectProvider' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(clientMock.createConnection).toHaveBeenCalledWith({
|
||||
body: {
|
||||
authKind: 'api-key',
|
||||
configuration: {
|
||||
datasource: 'crawl',
|
||||
endpoint: 'https://crawl.example.com',
|
||||
pluginId: 'langgenius/firecrawl_datasource',
|
||||
provider: 'firecrawl',
|
||||
},
|
||||
credentials: { apiKey: 'secret-value' },
|
||||
name: 'Firecrawl',
|
||||
providerId: 'plugin-daemon-website',
|
||||
},
|
||||
params: { id: 'space-1' },
|
||||
}),
|
||||
)
|
||||
expect(queryClientMock.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ['source-connections'],
|
||||
})
|
||||
expect(screen.getByText('dataset.newKnowledge.providerConnected')).toBeInTheDocument()
|
||||
expect(screen.queryByDisplayValue('secret-value')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps the website setup interactive until it reaches the backend boundary', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<AddSourcePage
|
||||
initialSourceDraft={{
|
||||
includeSubpages: true,
|
||||
maxPages: 100,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy: 'provider',
|
||||
}}
|
||||
knowledgeSpaceId="space-1"
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('textbox', { name: 'dataset.newKnowledge.rootUrl' })).toHaveValue(
|
||||
'https://docs.dify.ai',
|
||||
)
|
||||
expect(screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' })).toHaveValue(
|
||||
'Dify docs',
|
||||
)
|
||||
expect(screen.getByRole('textbox', { name: 'dataset.newKnowledge.rootUrl' })).toBeEnabled()
|
||||
expect(screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' })).toBeEnabled()
|
||||
const crawlAndPreview = screen.getByRole('button', {
|
||||
name: 'dataset.newKnowledge.crawlAndPreview',
|
||||
})
|
||||
expect(crawlAndPreview).toBeEnabled()
|
||||
await user.click(crawlAndPreview)
|
||||
expect(screen.getByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.sourceSetupBackendDependency',
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps pending website edits when the provider connection becomes active', async () => {
|
||||
const user = userEvent.setup()
|
||||
const initialSourceDraft = {
|
||||
includeSubpages: true,
|
||||
maxPages: 100,
|
||||
provider: 'Firecrawl' as const,
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
sourceType: 'websiteCrawl' as const,
|
||||
syncPolicy: 'provider' as const,
|
||||
}
|
||||
const view = render(
|
||||
<AddSourcePage initialSourceDraft={initialSourceDraft} knowledgeSpaceId="space-1" />,
|
||||
)
|
||||
const rootUrl = screen.getByRole('textbox', { name: 'dataset.newKnowledge.rootUrl' })
|
||||
const sourceName = screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' })
|
||||
await user.clear(rootUrl)
|
||||
await user.type(rootUrl, 'https://docs.dify.ai/edited')
|
||||
await user.clear(sourceName)
|
||||
await user.type(sourceName, 'Edited docs')
|
||||
|
||||
queryState.connections.data = { pages: [{ items: [connection('active')] }] }
|
||||
view.rerender(
|
||||
<AddSourcePage initialSourceDraft={initialSourceDraft} knowledgeSpaceId="space-1" />,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('textbox', { name: /dataset\.newKnowledge\.rootUrl/ })).toHaveValue(
|
||||
'https://docs.dify.ai/edited',
|
||||
)
|
||||
expect(screen.getByRole('textbox', { name: /dataset\.newKnowledge\.sourceName/ })).toHaveValue(
|
||||
'Edited docs',
|
||||
)
|
||||
})
|
||||
|
||||
it('does not select a lookalike provider by fuzzy display name', () => {
|
||||
queryState.providers.data = {
|
||||
items: [{ ...firecrawlProvider, displayName: 'Firecrawl impostor', id: 'impostor' }],
|
||||
}
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getByText('dataset.newKnowledge.firecrawlUnavailable')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('clears sensitive input but retains non-sensitive input after a connection error', async () => {
|
||||
const user = userEvent.setup()
|
||||
clientMock.createConnection.mockRejectedValue(new Error('provider unavailable'))
|
||||
queryState.connections.refetch.mockResolvedValue({ data: queryState.connections.data })
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.configureProvider' }))
|
||||
await user.type(screen.getByLabelText(/Api Key/), 'do-not-retain')
|
||||
await user.type(screen.getByLabelText('Endpoint'), 'https://crawl.example.com')
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.connectProvider' }))
|
||||
|
||||
expect(await screen.findByText('dataset.newKnowledge.connectionFailed')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText(/Api Key/)).toHaveValue('')
|
||||
expect(screen.getByLabelText('Endpoint')).toHaveValue('https://crawl.example.com')
|
||||
})
|
||||
|
||||
it('reconciles a response-lost create before showing an error', async () => {
|
||||
const user = userEvent.setup()
|
||||
clientMock.createConnection.mockRejectedValue(new Error('response lost'))
|
||||
queryState.connections.refetch.mockResolvedValue({
|
||||
data: { pages: [{ items: [connection('active')] }] },
|
||||
})
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.configureProvider' }))
|
||||
await user.type(screen.getByLabelText(/Api Key/), 'secret-value')
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.connectProvider' }))
|
||||
|
||||
expect(await screen.findByText('dataset.newKnowledge.providerConnected')).toBeInTheDocument()
|
||||
expect(screen.queryByText('dataset.newKnowledge.connectionFailed')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('clears an API key when authentication modes are changed and changed back', async () => {
|
||||
const user = userEvent.setup()
|
||||
clientMock.createConnection.mockResolvedValue(connection('active'))
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.configureProvider' }))
|
||||
await user.type(screen.getByLabelText(/Api Key/), 'must-not-return')
|
||||
await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.authKind.endpoint' }))
|
||||
await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.authKind.api-key' }))
|
||||
|
||||
expect(screen.getByLabelText(/Api Key/)).toHaveValue('')
|
||||
})
|
||||
|
||||
it('supports an endpoint descriptor without sending a hidden secret field', async () => {
|
||||
const user = userEvent.setup()
|
||||
clientMock.createConnection.mockResolvedValue({ ...connection('active'), authKind: 'endpoint' })
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.configureProvider' }))
|
||||
await user.type(screen.getByLabelText(/Api Key/), 'must-not-be-sent')
|
||||
await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.authKind.endpoint' }))
|
||||
await user.type(screen.getByLabelText('Endpoint'), 'https://crawl.example.com')
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.connectProvider' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(clientMock.createConnection).toHaveBeenCalledWith({
|
||||
body: {
|
||||
authKind: 'endpoint',
|
||||
configuration: {
|
||||
datasource: 'crawl',
|
||||
endpoint: 'https://crawl.example.com',
|
||||
pluginId: 'langgenius/firecrawl_datasource',
|
||||
provider: 'firecrawl',
|
||||
},
|
||||
credentials: {},
|
||||
name: 'Firecrawl',
|
||||
providerId: 'plugin-daemon-website',
|
||||
},
|
||||
params: { id: 'space-1' },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('associates provider field descriptions with their controls', async () => {
|
||||
const user = userEvent.setup()
|
||||
const booleanProvider = {
|
||||
...firecrawlProvider,
|
||||
configuration: [
|
||||
...firecrawlProvider.configuration,
|
||||
{
|
||||
description: 'Use stealth mode',
|
||||
name: 'stealth',
|
||||
required: false,
|
||||
secret: false,
|
||||
type: 'boolean' as const,
|
||||
},
|
||||
],
|
||||
}
|
||||
queryState.providers.data = { items: [booleanProvider] }
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.configureProvider' }))
|
||||
|
||||
expect(screen.getByLabelText(/Api Key/)).toHaveAccessibleDescription('Firecrawl API key')
|
||||
expect(screen.getByLabelText('Stealth')).toHaveAccessibleDescription('Use stealth mode')
|
||||
})
|
||||
|
||||
it('refreshes an errored connection using its current version', async () => {
|
||||
const user = userEvent.setup()
|
||||
queryState.connections.data = { pages: [{ items: [connection('error')] }] }
|
||||
clientMock.refreshConnection.mockResolvedValue(connection('active'))
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(clientMock.refreshConnection).toHaveBeenCalledWith({
|
||||
body: { expectedVersion: 2 },
|
||||
params: { connectionId: 'connection-1', id: 'space-1' },
|
||||
}),
|
||||
)
|
||||
expect(queryClientMock.invalidateQueries).toHaveBeenCalled()
|
||||
expect(screen.getByText('dataset.newKnowledge.providerConnected')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('reconciles a refresh version race and retries with the server version', async () => {
|
||||
const user = userEvent.setup()
|
||||
queryState.connections.data = { pages: [{ items: [connection('error')] }] }
|
||||
clientMock.refreshConnection
|
||||
.mockRejectedValueOnce(new Error('version conflict'))
|
||||
.mockResolvedValueOnce(connection('active', 4))
|
||||
queryState.connections.refetch.mockResolvedValue({
|
||||
data: {
|
||||
pages: [
|
||||
{
|
||||
items: [
|
||||
connection('error', 3),
|
||||
{
|
||||
...connection('error', 8),
|
||||
id: 'connection-2',
|
||||
updatedAt: '2026-07-20T11:00:00Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
|
||||
await waitFor(() => expect(queryState.connections.refetch).toHaveBeenCalledOnce())
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(clientMock.refreshConnection).toHaveBeenLastCalledWith({
|
||||
body: { expectedVersion: 3 },
|
||||
params: { connectionId: 'connection-1', id: 'space-1' },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('does not regress the current version when reconciliation reads a stale replica', async () => {
|
||||
const user = userEvent.setup()
|
||||
queryState.connections.data = { pages: [{ items: [connection('error', 3)] }] }
|
||||
clientMock.refreshConnection
|
||||
.mockRejectedValueOnce(new Error('response lost'))
|
||||
.mockResolvedValueOnce(connection('active', 4))
|
||||
queryState.connections.refetch.mockResolvedValue({
|
||||
data: { pages: [{ items: [connection('error', 2)] }] },
|
||||
})
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
|
||||
await waitFor(() => expect(queryState.connections.refetch).toHaveBeenCalledOnce())
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(clientMock.refreshConnection).toHaveBeenLastCalledWith({
|
||||
body: { expectedVersion: 3 },
|
||||
params: { connectionId: 'connection-1', id: 'space-1' },
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('lets a newer remote connection version replace a local mutation response', async () => {
|
||||
const user = userEvent.setup()
|
||||
queryState.connections.data = { pages: [{ items: [connection('error')] }] }
|
||||
clientMock.refreshConnection.mockResolvedValue(connection('provisioning', 3))
|
||||
|
||||
const view = render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
|
||||
expect(
|
||||
await screen.findByText('dataset.newKnowledge.connectionProvisioning'),
|
||||
).toBeInTheDocument()
|
||||
|
||||
queryState.connections.data = {
|
||||
pages: [
|
||||
{
|
||||
items: [
|
||||
connection('active', 3),
|
||||
{
|
||||
...connection('active', 10),
|
||||
id: 'connection-2',
|
||||
updatedAt: '2026-07-20T11:00:00Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
view.rerender(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getByText('dataset.newKnowledge.providerConnected')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('reconciles a provisioning connection with the refreshed server state', async () => {
|
||||
const user = userEvent.setup()
|
||||
queryState.connections.data = { pages: [{ items: [connection('provisioning')] }] }
|
||||
queryState.connections.refetch.mockResolvedValue({
|
||||
data: { pages: [{ items: [connection('active')] }] },
|
||||
})
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.refreshConnectionStatus' }),
|
||||
)
|
||||
|
||||
expect(await screen.findByText('dataset.newKnowledge.providerConnected')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows a retryable error when provisioning reconciliation fails', async () => {
|
||||
const user = userEvent.setup()
|
||||
queryState.connections.data = { pages: [{ items: [connection('provisioning')] }] }
|
||||
queryState.connections.refetch.mockResolvedValue({
|
||||
data: queryState.connections.data,
|
||||
error: new Error('temporary failure'),
|
||||
})
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.refreshConnectionStatus' }),
|
||||
)
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.connectionRefreshFailed',
|
||||
)
|
||||
})
|
||||
|
||||
it('shows a retryable error when provisioning reconciliation finds no connection', async () => {
|
||||
const user = userEvent.setup()
|
||||
queryState.connections.data = { pages: [{ items: [connection('provisioning')] }] }
|
||||
queryState.connections.refetch.mockImplementation(async () => {
|
||||
queryState.connections.data = { pages: [{ items: [] }] }
|
||||
return { data: queryState.connections.data }
|
||||
})
|
||||
|
||||
const view = render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.refreshConnectionStatus' }),
|
||||
)
|
||||
view.rerender(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.connectionRefreshFailed',
|
||||
)
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'dataset.newKnowledge.configureProvider' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps source types selectable and shows an honest dependency state', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getByRole('radio', { name: 'dataset.newKnowledge.websiteCrawl' })).toBeChecked()
|
||||
const onlineDocuments = screen.getByRole('radio', {
|
||||
name: 'dataset.newKnowledge.onlineDocuments',
|
||||
})
|
||||
expect(onlineDocuments).toBeEnabled()
|
||||
expect(screen.getByRole('radio', { name: 'dataset.newKnowledge.onlineDrive' })).toBeEnabled()
|
||||
expect(screen.getByRole('radio', { name: 'Jina Reader' })).toBeEnabled()
|
||||
expect(
|
||||
screen.getByRole('group', { name: 'datasetCreation.stepOne.website.chooseProvider' }),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByRole('radio', { name: 'Firecrawl' })).toBeChecked()
|
||||
|
||||
await user.click(onlineDocuments)
|
||||
expect(onlineDocuments).toBeChecked()
|
||||
expect(
|
||||
screen.getByRole('group', { name: 'datasetCreation.stepOne.website.chooseProvider' }),
|
||||
).toBeVisible()
|
||||
expect(screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' })).toBeEnabled()
|
||||
expect(screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' })).toBeEnabled()
|
||||
expect(screen.getByRole('status')).toHaveTextContent('dataset.newKnowledge.providerUnavailable')
|
||||
await user.type(
|
||||
screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' }),
|
||||
'Product docs',
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.onlineDrive' }))
|
||||
expect(screen.getByRole('radio', { name: 'Google Drive' })).toBeChecked()
|
||||
await user.click(onlineDocuments)
|
||||
expect(screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' })).toHaveValue(
|
||||
'Product docs',
|
||||
)
|
||||
})
|
||||
|
||||
it('restores the selected source type from the create flow', () => {
|
||||
render(<AddSourcePage initialSourceType="onlineDrive" knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getByRole('radio', { name: 'dataset.newKnowledge.onlineDrive' })).toBeChecked()
|
||||
expect(
|
||||
screen.getByRole('region', { name: 'dataset.newKnowledge.selectFilesAndFolders' }),
|
||||
).toBeVisible()
|
||||
expect(screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' })).toBeEnabled()
|
||||
expect(screen.getByRole('status')).toHaveTextContent('dataset.newKnowledge.providerUnavailable')
|
||||
})
|
||||
|
||||
it('disables the final Add source action while its backend dependency is missing', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.onlineDocuments' }))
|
||||
const addSource = screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' })
|
||||
expect(addSource).toBeDisabled()
|
||||
expect(toastInfoMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows catalog unavailability instead of offering a fake connection', () => {
|
||||
queryState.providers.data = {
|
||||
items: [{ ...firecrawlProvider, available: false, unavailableReason: 'Disabled by admin' }],
|
||||
}
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getByText('Disabled by admin')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'dataset.newKnowledge.configureProvider' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not offer unsupported OAuth or undocumented direct connection paths', () => {
|
||||
queryState.providers.data = {
|
||||
items: [
|
||||
{
|
||||
...firecrawlProvider,
|
||||
authKinds: ['oauth2'],
|
||||
configuration: firecrawlProvider.configuration.slice(0, 3),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
render(<AddSourcePage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getByText('dataset.newKnowledge.providerUnavailable')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'dataset.newKnowledge.configureProvider' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,96 @@
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import {
|
||||
createAuxiliaryTaskReadGuard,
|
||||
useAuxiliaryTaskReadGuard,
|
||||
} from '../auxiliary-task-read-guard'
|
||||
|
||||
describe('createAuxiliaryTaskReadGuard', () => {
|
||||
it('blocks every auxiliary reader for the denied task version', () => {
|
||||
const guard = createAuxiliaryTaskReadGuard()
|
||||
const olderVersion = '2026-07-20T10:01:00Z'
|
||||
const deniedVersion = '2026-07-20T10:02:00Z'
|
||||
const newerVersion = '2026-07-20T10:03:00Z'
|
||||
|
||||
guard.block('task-1', deniedVersion)
|
||||
|
||||
expect(guard.isBlocked('task-1', olderVersion)).toBe(true)
|
||||
expect(guard.isBlocked('task-1', deniedVersion)).toBe(true)
|
||||
expect(guard.isBlocked('task-1', newerVersion)).toBe(false)
|
||||
expect(guard.isBlocked('task-2', deniedVersion)).toBe(false)
|
||||
|
||||
guard.block('task-1', olderVersion)
|
||||
expect(guard.isBlocked('task-1', olderVersion)).toBe(true)
|
||||
expect(guard.isBlocked('task-1', deniedVersion)).toBe(true)
|
||||
})
|
||||
|
||||
it('retires blocks after task or document authority changes', () => {
|
||||
const guard = createAuxiliaryTaskReadGuard()
|
||||
guard.block('task-1', 'version-1')
|
||||
guard.block('task-2', 'version-1')
|
||||
|
||||
guard.retain(new Set(['task-1']))
|
||||
expect(guard.isBlocked('task-1', 'version-1')).toBe(true)
|
||||
expect(guard.isBlocked('task-2', 'version-1')).toBe(false)
|
||||
|
||||
guard.clearTask('task-1')
|
||||
expect(guard.isBlocked('task-1', 'version-1')).toBe(false)
|
||||
|
||||
guard.block('task-1', 'version-2')
|
||||
guard.clear()
|
||||
expect(guard.isBlocked('task-1', 'version-2')).toBe(false)
|
||||
})
|
||||
|
||||
it('retires a block only after an authoritative version catches up', () => {
|
||||
const guard = createAuxiliaryTaskReadGuard()
|
||||
guard.block('task-1', '2026-07-20T10:02:00Z')
|
||||
|
||||
expect(guard.clearThrough('task-1', '2026-07-20T10:01:00Z')).toBe(false)
|
||||
expect(guard.isBlocked('task-1', '2026-07-20T10:02:00Z')).toBe(true)
|
||||
|
||||
expect(guard.clearThrough('task-1', '2026-07-20T10:02:00Z')).toBe(true)
|
||||
expect(guard.isBlocked('task-1', '2026-07-20T10:02:00Z')).toBe(false)
|
||||
})
|
||||
|
||||
it('restores the page after authoritative document verification without retrying the task version', async () => {
|
||||
let resolveRefetch!: (result: { error: null }) => void
|
||||
const refetchDocuments = vi.fn(
|
||||
() =>
|
||||
new Promise<{ error: null }>((resolve) => {
|
||||
resolveRefetch = resolve
|
||||
}),
|
||||
)
|
||||
const { result } = renderHook(() =>
|
||||
useAuxiliaryTaskReadGuard({ documentPermissionDenied: false, refetchDocuments }),
|
||||
)
|
||||
|
||||
act(() => result.current.deny('task-1', 'version-1'))
|
||||
expect(result.current.permissionDenied).toBe(true)
|
||||
expect(result.current.guard.isBlocked('task-1', 'version-1')).toBe(true)
|
||||
|
||||
await act(async () => resolveRefetch({ error: null }))
|
||||
expect(result.current.permissionDenied).toBe(false)
|
||||
expect(result.current.guard.isBlocked('task-1', 'version-1')).toBe(true)
|
||||
})
|
||||
|
||||
it('clears local denial and version blocks after document permission returns', async () => {
|
||||
const refetchDocuments = vi
|
||||
.fn()
|
||||
.mockResolvedValue({ error: new Response(null, { status: 403 }) })
|
||||
const { result, rerender } = renderHook(
|
||||
({ documentPermissionDenied }) =>
|
||||
useAuxiliaryTaskReadGuard({ documentPermissionDenied, refetchDocuments }),
|
||||
{ initialProps: { documentPermissionDenied: false } },
|
||||
)
|
||||
|
||||
act(() => result.current.deny('task-1', 'version-1'))
|
||||
await act(async () => {})
|
||||
expect(result.current.permissionDenied).toBe(true)
|
||||
|
||||
rerender({ documentPermissionDenied: true })
|
||||
rerender({ documentPermissionDenied: false })
|
||||
|
||||
expect(result.current.permissionDenied).toBe(false)
|
||||
expect(result.current.guard.isBlocked('task-1', 'version-1')).toBe(false)
|
||||
expect(result.current.guardRevision).toBe(1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,599 @@
|
||||
import type {
|
||||
GetKnowledgeSpacesByIdSourcesBySourceIdSyncPolicyResponse,
|
||||
GetKnowledgeSpacesByIdSourceWorkflowsByRunIdPagesResponse,
|
||||
Source,
|
||||
SourceWorkflowRun,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { act, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { createInstance } from 'i18next'
|
||||
import { getInitOptions } from '@/i18n-config/settings'
|
||||
import datasetTranslations from '@/i18n/en-US/dataset.json'
|
||||
import { render } from '@/test/console/render'
|
||||
import { CrawlSelectionForm } from '../crawl-selection-form'
|
||||
|
||||
const clientMock = vi.hoisted(() => ({
|
||||
getPolicy: vi.fn(),
|
||||
getWorkflow: vi.fn(),
|
||||
selectPages: vi.fn(),
|
||||
updatePolicy: vi.fn(),
|
||||
}))
|
||||
|
||||
const routerMock = vi.hoisted(() => ({ push: vi.fn() }))
|
||||
const queryClientMock = vi.hoisted(() => ({ invalidateQueries: vi.fn() }))
|
||||
const policyQueryOptionsMock = vi.hoisted(() =>
|
||||
vi.fn(({ input }) => ({
|
||||
queryFn: () => clientMock.getPolicy(input),
|
||||
queryKey: ['sync-policy', input.params.sourceId],
|
||||
retry: false,
|
||||
})),
|
||||
)
|
||||
|
||||
vi.mock('@/next/navigation', () => ({ useRouter: () => routerMock }))
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return { ...original, useQueryClient: () => queryClientMock }
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleClient: {
|
||||
knowledgeFs: {
|
||||
getKnowledgeSpacesByIdSourcesBySourceIdSyncPolicy: clientMock.getPolicy,
|
||||
getKnowledgeSpacesByIdSourceWorkflowsByRunId: clientMock.getWorkflow,
|
||||
},
|
||||
},
|
||||
consoleQuery: {
|
||||
knowledgeFs: {
|
||||
getKnowledgeSpacesByIdSources: {
|
||||
key: vi.fn(() => ['knowledge-sources']),
|
||||
},
|
||||
getKnowledgeSpacesByIdSourcesBySourceIdSyncPolicy: {
|
||||
queryOptions: policyQueryOptionsMock,
|
||||
},
|
||||
postKnowledgeSpacesByIdSourceWorkflowsByRunIdSelection: {
|
||||
mutationOptions: vi.fn(() => ({
|
||||
mutationFn: (input: unknown) => clientMock.selectPages(input),
|
||||
})),
|
||||
},
|
||||
putKnowledgeSpacesByIdSourcesBySourceIdSyncPolicy: {
|
||||
mutationOptions: vi.fn(() => ({
|
||||
mutationFn: (input: unknown) => clientMock.updatePolicy(input),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
type PreviewPage = GetKnowledgeSpacesByIdSourceWorkflowsByRunIdPagesResponse['items'][number]
|
||||
|
||||
const source: Source = {
|
||||
connectionId: 'connection-1',
|
||||
createdAt: '2026-07-20T10:00:00Z',
|
||||
id: 'source-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
metadata: {},
|
||||
name: 'Dify docs',
|
||||
status: 'disabled',
|
||||
type: 'web',
|
||||
updatedAt: '2026-07-20T10:00:00Z',
|
||||
uri: 'https://docs.dify.ai/',
|
||||
version: 3,
|
||||
}
|
||||
|
||||
const run: SourceWorkflowRun = {
|
||||
checkpoint: 'crawl',
|
||||
createdAt: '2026-07-20T10:00:00Z',
|
||||
executionAttempts: 1,
|
||||
id: 'run-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
kind: 'crawl-preview',
|
||||
maxExecutionAttempts: 3,
|
||||
progressCompleted: 3,
|
||||
progressFailed: 1,
|
||||
progressSkipped: 0,
|
||||
progressTotal: 4,
|
||||
sourceId: source.id,
|
||||
state: 'succeeded',
|
||||
updatedAt: '2026-07-20T10:01:00Z',
|
||||
}
|
||||
|
||||
const pages: PreviewPage[] = [
|
||||
{
|
||||
pageId: 'page-1',
|
||||
sourceUrl: 'https://docs.dify.ai/getting-started',
|
||||
title: 'Getting started',
|
||||
},
|
||||
{
|
||||
pageId: 'page-2',
|
||||
sourceUrl: 'https://docs.dify.ai/guides',
|
||||
title: 'Guides',
|
||||
},
|
||||
{
|
||||
pageId: 'off-domain',
|
||||
sourceUrl: 'https://github.com/langgenius/dify/edit/main/docs.md',
|
||||
title: 'Edit this page',
|
||||
},
|
||||
]
|
||||
|
||||
const policy = (
|
||||
overrides: Partial<GetKnowledgeSpacesByIdSourcesBySourceIdSyncPolicyResponse> = {},
|
||||
): GetKnowledgeSpacesByIdSourcesBySourceIdSyncPolicyResponse => ({
|
||||
createdAt: '2026-07-20T10:00:00Z',
|
||||
enabled: true,
|
||||
expectedSourceVersion: 3,
|
||||
id: 'policy-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
mode: 'interval',
|
||||
revision: 2,
|
||||
sourceId: source.id,
|
||||
updatedAt: '2026-07-20T10:00:00Z',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T) => void
|
||||
const promise = new Promise<T>((nextResolve) => {
|
||||
resolve = nextResolve
|
||||
})
|
||||
return { promise, resolve }
|
||||
}
|
||||
|
||||
function renderSelectionForm(
|
||||
onCancel = vi.fn(),
|
||||
workflowUncertain = false,
|
||||
discardRequested = () => false,
|
||||
previewPages = pages,
|
||||
) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { mutations: { retry: false }, queries: { retry: false } },
|
||||
})
|
||||
const onSubmissionUncertainChange = vi.fn()
|
||||
const onRecrawl = vi.fn()
|
||||
const onWorkflowPending = vi.fn()
|
||||
const onWorkflowRun = vi.fn()
|
||||
const view = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<CrawlSelectionForm
|
||||
discardRequested={discardRequested}
|
||||
knowledgeSpaceId="space-1"
|
||||
onCancel={onCancel}
|
||||
onRecrawl={onRecrawl}
|
||||
onSubmissionUncertainChange={onSubmissionUncertainChange}
|
||||
onSubmitted={vi.fn()}
|
||||
onWorkflowPending={onWorkflowPending}
|
||||
onWorkflowRun={onWorkflowRun}
|
||||
pages={previewPages}
|
||||
rootUrl="https://docs.dify.ai/"
|
||||
run={run}
|
||||
source={source}
|
||||
workflowUncertain={workflowUncertain}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
return {
|
||||
...view,
|
||||
onRecrawl,
|
||||
onSubmissionUncertainChange,
|
||||
onWorkflowPending,
|
||||
onWorkflowRun,
|
||||
}
|
||||
}
|
||||
|
||||
describe('CrawlSelectionForm', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
clientMock.getPolicy.mockResolvedValue(policy())
|
||||
clientMock.getWorkflow.mockResolvedValue({ ...run, checkpoint: 'complete', state: 'completed' })
|
||||
clientMock.updatePolicy.mockResolvedValue(policy({ mode: 'manual', revision: 3 }))
|
||||
clientMock.selectPages.mockResolvedValue({ ...run, checkpoint: 'import', state: 'queued' })
|
||||
})
|
||||
|
||||
it('uses singular and plural copy for the crawl summary', async () => {
|
||||
const instance = createInstance()
|
||||
await instance.init({
|
||||
...getInitOptions(),
|
||||
defaultNS: 'dataset',
|
||||
lng: 'en-US',
|
||||
ns: ['dataset'],
|
||||
resources: { 'en-US': { dataset: datasetTranslations } },
|
||||
})
|
||||
const tDataset = instance.getFixedT('en-US', 'dataset')
|
||||
expect(
|
||||
tDataset(($) => $['newKnowledge.pagesCrawled_one'], { count: 1, host: 'example.com' }),
|
||||
).toBe('1 page crawled at example.com')
|
||||
expect(
|
||||
tDataset(($) => $['newKnowledge.pagesCrawled_other'], { count: 2, host: 'example.com' }),
|
||||
).toBe('2 pages crawled at example.com')
|
||||
})
|
||||
|
||||
it('loads a missing initial sync policy without a global error notification', async () => {
|
||||
renderSelectionForm()
|
||||
await screen.findByRole('button', { name: 'dataset.newKnowledge.addSource' })
|
||||
|
||||
expect(policyQueryOptionsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ context: { silent: true } }),
|
||||
)
|
||||
})
|
||||
|
||||
it('selects only valid same-domain pages and exposes an indeterminate select-all state', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderSelectionForm()
|
||||
|
||||
const addSource = await screen.findByRole('button', {
|
||||
name: 'dataset.newKnowledge.addSource',
|
||||
})
|
||||
expect(addSource).toBeDisabled()
|
||||
const offDomainPage = screen.getByRole('checkbox', { name: 'Edit this page' })
|
||||
expect(offDomainPage).toHaveAttribute('aria-disabled', 'true')
|
||||
expect(offDomainPage).toHaveAccessibleDescription(
|
||||
'https://github.com/langgenius/dify/edit/main/docs.md dataset.newKnowledge.skippedOffDomain',
|
||||
)
|
||||
expect(screen.getByText('dataset.newKnowledge.skippedOffDomain')).toBeInTheDocument()
|
||||
expect(screen.getByText(/dataset\.newKnowledge\.skippedFailed/)).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('checkbox', { name: 'Getting started' }))
|
||||
expect(addSource).toBeEnabled()
|
||||
expect(
|
||||
screen.getByRole('checkbox', { name: 'dataset.newKnowledge.selectAll' }),
|
||||
).toHaveAttribute('aria-checked', 'mixed')
|
||||
expect(
|
||||
screen.getByRole('checkbox', { name: 'dataset.newKnowledge.selectAll' }),
|
||||
).toHaveAttribute('data-indeterminate')
|
||||
expect(screen.getByText('dataset.newKnowledge.pagesSelected:{"count":1}')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('checkbox', { name: 'dataset.newKnowledge.selectAll' }))
|
||||
expect(screen.getByRole('checkbox', { name: 'Getting started' })).toBeChecked()
|
||||
expect(screen.getByRole('checkbox', { name: 'Guides' })).toBeChecked()
|
||||
expect(screen.getByRole('checkbox', { name: 'Edit this page' })).not.toBeChecked()
|
||||
})
|
||||
|
||||
it('toggles selection from the visible checkbox labels', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderSelectionForm()
|
||||
await screen.findByRole('button', { name: 'dataset.newKnowledge.addSource' })
|
||||
|
||||
await user.click(screen.getByText('Getting started'))
|
||||
expect(screen.getByRole('checkbox', { name: 'Getting started' })).toBeChecked()
|
||||
await user.click(screen.getByText('dataset.newKnowledge.selectAll'))
|
||||
|
||||
expect(screen.getByRole('checkbox', { name: 'Getting started' })).toBeChecked()
|
||||
expect(screen.getByRole('checkbox', { name: 'Guides' })).toBeChecked()
|
||||
})
|
||||
|
||||
it('locks stale selections while keeping workflow reconciliation available', async () => {
|
||||
const onCancel = vi.fn()
|
||||
const user = userEvent.setup()
|
||||
const { onRecrawl } = renderSelectionForm(onCancel, true)
|
||||
await screen.findByRole('button', { name: 'dataset.newKnowledge.addSource' })
|
||||
|
||||
expect(screen.getByRole('checkbox', { name: 'Getting started' })).toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
)
|
||||
expect(screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' })).toBeDisabled()
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' })).toBeDisabled()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.reCrawl' }))
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
|
||||
expect(onRecrawl).toHaveBeenCalledOnce()
|
||||
expect(onCancel).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('never submits more page IDs than the selection contract accepts', async () => {
|
||||
const longPageList: PreviewPage[] = Array.from({ length: 201 }, (_, index) => ({
|
||||
pageId: `page-${index + 1}`,
|
||||
sourceUrl: `https://docs.dify.ai/page-${index + 1}`,
|
||||
title: `Page ${index + 1}`,
|
||||
}))
|
||||
const user = userEvent.setup()
|
||||
renderSelectionForm(vi.fn(), false, () => false, longPageList)
|
||||
await user.click(
|
||||
await screen.findByRole('checkbox', { name: 'dataset.newKnowledge.selectAll' }),
|
||||
)
|
||||
|
||||
const overflowPage = screen.getByRole('checkbox', { name: 'Page 201' })
|
||||
expect(overflowPage).toHaveAttribute('aria-disabled', 'true')
|
||||
expect(overflowPage).toHaveAccessibleDescription(
|
||||
'https://docs.dify.ai/page-201 dataset.newKnowledge.maxPages: 200',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' }))
|
||||
|
||||
await waitFor(() => expect(clientMock.selectPages).toHaveBeenCalledOnce())
|
||||
expect(clientMock.selectPages.mock.calls[0]?.[0].body.pageIds).toHaveLength(200)
|
||||
})
|
||||
|
||||
it('validates a custom interval and submits policy plus selection exactly once', async () => {
|
||||
const selectionRequest = deferred<SourceWorkflowRun>()
|
||||
clientMock.selectPages.mockReturnValue(selectionRequest.promise)
|
||||
const user = userEvent.setup()
|
||||
renderSelectionForm()
|
||||
|
||||
await user.click(await screen.findByRole('checkbox', { name: 'Getting started' }))
|
||||
await user.selectOptions(
|
||||
screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' }),
|
||||
'custom',
|
||||
)
|
||||
const interval = screen.getByRole('spinbutton', {
|
||||
name: 'dataset.newKnowledge.customIntervalHours',
|
||||
})
|
||||
await user.clear(interval)
|
||||
await user.type(interval, '0')
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' })).toBeDisabled()
|
||||
expect(interval).toHaveAccessibleDescription('dataset.newKnowledge.customIntervalInvalid')
|
||||
await user.clear(interval)
|
||||
await user.type(interval, '6')
|
||||
|
||||
const addSource = screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' })
|
||||
await user.dblClick(addSource)
|
||||
|
||||
await waitFor(() => expect(clientMock.updatePolicy).toHaveBeenCalledOnce())
|
||||
expect(clientMock.updatePolicy).toHaveBeenCalledWith({
|
||||
body: {
|
||||
customIntervalSeconds: 21600,
|
||||
enabled: true,
|
||||
expectedRevision: 2,
|
||||
expectedSourceVersion: 3,
|
||||
mode: 'custom',
|
||||
},
|
||||
params: { id: 'space-1', sourceId: 'source-1' },
|
||||
})
|
||||
expect(clientMock.selectPages).toHaveBeenCalledOnce()
|
||||
expect(clientMock.selectPages).toHaveBeenCalledWith({
|
||||
body: { pageIds: ['page-1'] },
|
||||
headers: { 'Idempotency-Key': expect.any(String) },
|
||||
params: { id: 'space-1', runId: 'run-1' },
|
||||
})
|
||||
|
||||
selectionRequest.resolve({ ...run, checkpoint: 'import', state: 'queued' })
|
||||
await waitFor(() => expect(clientMock.getWorkflow).toHaveBeenCalledOnce())
|
||||
await waitFor(() =>
|
||||
expect(queryClientMock.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ['knowledge-sources'],
|
||||
}),
|
||||
)
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/datasets/new/space-1/sources')
|
||||
})
|
||||
|
||||
it('tracks policy updates and stops before selection when discard is requested', async () => {
|
||||
const policyRequest = deferred<GetKnowledgeSpacesByIdSourcesBySourceIdSyncPolicyResponse>()
|
||||
clientMock.updatePolicy.mockReturnValue(policyRequest.promise)
|
||||
let discardRequested = false
|
||||
const user = userEvent.setup()
|
||||
const { onWorkflowPending } = renderSelectionForm(vi.fn(), false, () => discardRequested)
|
||||
await user.click(await screen.findByRole('checkbox', { name: 'Getting started' }))
|
||||
await user.selectOptions(
|
||||
screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' }),
|
||||
'manual',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' }))
|
||||
|
||||
expect(onWorkflowPending).toHaveBeenCalledOnce()
|
||||
expect(clientMock.selectPages).not.toHaveBeenCalled()
|
||||
discardRequested = true
|
||||
await act(async () =>
|
||||
policyRequest.resolve(policy({ enabled: false, mode: 'manual', revision: 3 })),
|
||||
)
|
||||
await expect(onWorkflowPending.mock.calls[0]?.[0]).resolves.toBeUndefined()
|
||||
expect(clientMock.selectPages).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ enabled: true, initialMode: 'interval', mode: 'provider' },
|
||||
{ enabled: false, initialMode: 'interval', mode: 'manual' },
|
||||
{ enabled: true, initialMode: 'manual', mode: 'interval' },
|
||||
] as const)(
|
||||
'submits the $mode sync policy using the KnowledgeFS contract',
|
||||
async ({ enabled, initialMode, mode }) => {
|
||||
clientMock.getPolicy.mockResolvedValue(
|
||||
policy({ enabled: initialMode !== 'manual', mode: initialMode }),
|
||||
)
|
||||
clientMock.updatePolicy.mockResolvedValue(policy({ enabled, mode, revision: 3 }))
|
||||
const user = userEvent.setup()
|
||||
renderSelectionForm()
|
||||
|
||||
await user.click(await screen.findByRole('checkbox', { name: 'Getting started' }))
|
||||
await user.selectOptions(
|
||||
screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' }),
|
||||
mode,
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' }))
|
||||
|
||||
await waitFor(() => expect(clientMock.updatePolicy).toHaveBeenCalledOnce())
|
||||
expect(clientMock.updatePolicy).toHaveBeenCalledWith({
|
||||
body: {
|
||||
enabled,
|
||||
expectedRevision: 2,
|
||||
expectedSourceVersion: 3,
|
||||
mode,
|
||||
},
|
||||
params: { id: 'space-1', sourceId: 'source-1' },
|
||||
})
|
||||
expect(clientMock.selectPages).toHaveBeenCalledOnce()
|
||||
},
|
||||
)
|
||||
|
||||
it('reconciles a lost sync-policy response before importing the selected pages', async () => {
|
||||
clientMock.getPolicy
|
||||
.mockReset()
|
||||
.mockResolvedValueOnce(policy())
|
||||
.mockResolvedValueOnce(policy({ enabled: false, mode: 'manual', revision: 3 }))
|
||||
clientMock.updatePolicy.mockRejectedValue(new Error('response lost'))
|
||||
const user = userEvent.setup()
|
||||
renderSelectionForm()
|
||||
|
||||
await user.click(await screen.findByRole('checkbox', { name: 'Getting started' }))
|
||||
await user.selectOptions(
|
||||
screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' }),
|
||||
'manual',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' }))
|
||||
|
||||
await waitFor(() => expect(clientMock.selectPages).toHaveBeenCalledOnce())
|
||||
expect(clientMock.getPolicy).toHaveBeenCalledTimes(2)
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('allows discard when only the sync-policy outcome is uncertain', async () => {
|
||||
clientMock.getPolicy
|
||||
.mockReset()
|
||||
.mockResolvedValueOnce(policy())
|
||||
.mockRejectedValueOnce(new Error('reconciliation unavailable'))
|
||||
clientMock.updatePolicy.mockRejectedValue(new Error('response lost'))
|
||||
const onCancel = vi.fn()
|
||||
const user = userEvent.setup()
|
||||
const { onSubmissionUncertainChange } = renderSelectionForm(onCancel)
|
||||
await user.click(await screen.findByRole('checkbox', { name: 'Getting started' }))
|
||||
await user.selectOptions(
|
||||
screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' }),
|
||||
'manual',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' }))
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.addSourceFailed',
|
||||
)
|
||||
expect(onSubmissionUncertainChange).not.toHaveBeenCalledWith(true)
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' })).toBeEnabled()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
expect(onCancel).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('uses the latest concurrency tokens after a conflicting sync-policy update', async () => {
|
||||
clientMock.getPolicy
|
||||
.mockReset()
|
||||
.mockResolvedValueOnce(policy())
|
||||
.mockResolvedValueOnce(
|
||||
policy({
|
||||
expectedSourceVersion: 4,
|
||||
mode: 'provider',
|
||||
revision: 3,
|
||||
}),
|
||||
)
|
||||
clientMock.updatePolicy
|
||||
.mockRejectedValueOnce(Object.assign(new Error('conflict'), { status: 409 }))
|
||||
.mockResolvedValueOnce(policy({ enabled: false, mode: 'manual', revision: 4 }))
|
||||
const user = userEvent.setup()
|
||||
renderSelectionForm()
|
||||
|
||||
await user.click(await screen.findByRole('checkbox', { name: 'Getting started' }))
|
||||
await user.selectOptions(
|
||||
screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' }),
|
||||
'manual',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' }))
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.addSourceFailed',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' }))
|
||||
|
||||
await waitFor(() => expect(clientMock.updatePolicy).toHaveBeenCalledTimes(2))
|
||||
expect(clientMock.updatePolicy.mock.calls[1]?.[0]).toEqual({
|
||||
body: {
|
||||
enabled: false,
|
||||
expectedRevision: 3,
|
||||
expectedSourceVersion: 4,
|
||||
mode: 'manual',
|
||||
},
|
||||
params: { id: 'space-1', sourceId: 'source-1' },
|
||||
})
|
||||
expect(clientMock.selectPages).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('recovers when the initial sync policy cannot load', async () => {
|
||||
clientMock.getPolicy
|
||||
.mockReset()
|
||||
.mockRejectedValueOnce(new Error('temporary failure'))
|
||||
.mockResolvedValueOnce(policy())
|
||||
const user = userEvent.setup()
|
||||
renderSelectionForm()
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.syncPolicyLoadFailed',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.retrySyncPolicy' }))
|
||||
|
||||
expect(
|
||||
await screen.findByRole('button', { name: 'dataset.newKnowledge.addSource' }),
|
||||
).toBeDisabled()
|
||||
})
|
||||
|
||||
it('creates the initial provider policy when a provisional source has no policy yet', async () => {
|
||||
clientMock.getPolicy.mockRejectedValue(
|
||||
Object.assign(new Error('policy not found'), { status: 404 }),
|
||||
)
|
||||
clientMock.updatePolicy.mockResolvedValue(policy({ mode: 'provider', revision: 1 }))
|
||||
const user = userEvent.setup()
|
||||
renderSelectionForm()
|
||||
|
||||
await user.click(await screen.findByRole('checkbox', { name: 'Getting started' }))
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' }))
|
||||
|
||||
await waitFor(() => expect(clientMock.updatePolicy).toHaveBeenCalledOnce())
|
||||
expect(clientMock.updatePolicy).toHaveBeenCalledWith({
|
||||
body: {
|
||||
enabled: true,
|
||||
expectedRevision: 0,
|
||||
expectedSourceVersion: 3,
|
||||
mode: 'provider',
|
||||
},
|
||||
params: { id: 'space-1', sourceId: 'source-1' },
|
||||
})
|
||||
expect(clientMock.selectPages).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('keeps Cancel available when the sync policy cannot load', async () => {
|
||||
clientMock.getPolicy.mockRejectedValue(new Error('temporary failure'))
|
||||
const onCancel = vi.fn()
|
||||
const user = userEvent.setup()
|
||||
renderSelectionForm(onCancel)
|
||||
|
||||
await screen.findByRole('alert')
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' })).toBeDisabled()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }))
|
||||
|
||||
expect(onCancel).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('reuses the selection idempotency key after a lost response without rewriting policy', async () => {
|
||||
clientMock.getPolicy.mockResolvedValue(policy({ enabled: false, mode: 'manual' }))
|
||||
clientMock.selectPages
|
||||
.mockRejectedValueOnce(new Error('response lost'))
|
||||
.mockResolvedValueOnce({ ...run, checkpoint: 'import', state: 'queued' })
|
||||
const user = userEvent.setup()
|
||||
const { onSubmissionUncertainChange, onWorkflowPending, onWorkflowRun } = renderSelectionForm()
|
||||
|
||||
await user.click(await screen.findByRole('checkbox', { name: 'Getting started' }))
|
||||
await user.selectOptions(
|
||||
screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' }),
|
||||
'manual',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' }))
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.addSourceFailed',
|
||||
)
|
||||
expect(onWorkflowPending).toHaveBeenCalledOnce()
|
||||
expect(onSubmissionUncertainChange).toHaveBeenLastCalledWith(true)
|
||||
expect(screen.getByRole('checkbox', { name: 'Guides' })).toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
)
|
||||
expect(screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' })).toBeDisabled()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.cancelAddSource' }),
|
||||
).toBeDisabled()
|
||||
await user.click(screen.getByText('Guides'))
|
||||
expect(screen.getByRole('checkbox', { name: 'Guides' })).not.toBeChecked()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.addSource' }))
|
||||
|
||||
await waitFor(() => expect(clientMock.selectPages).toHaveBeenCalledTimes(2))
|
||||
expect(onWorkflowRun).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ checkpoint: 'import', state: 'queued' }),
|
||||
)
|
||||
expect(clientMock.updatePolicy).not.toHaveBeenCalled()
|
||||
expect(clientMock.selectPages.mock.calls[0]?.[0].headers).toEqual(
|
||||
clientMock.selectPages.mock.calls[1]?.[0].headers,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -3,11 +3,14 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { act, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { CreateKnowledgePage } from '../create-knowledge-page'
|
||||
import { newKnowledgeSourceDraftStorageKey } from '../routes'
|
||||
|
||||
const serviceMock = vi.hoisted(() => ({
|
||||
create: vi.fn(),
|
||||
getPolicy: vi.fn(),
|
||||
patchPolicy: vi.fn(),
|
||||
upload: vi.fn(),
|
||||
uploadBulk: vi.fn(),
|
||||
listKey: vi.fn(() => ['console', 'knowledgeFs', 'listKnowledgeSpaces']),
|
||||
}))
|
||||
|
||||
@@ -53,6 +56,8 @@ vi.mock('@/service/client', () => ({
|
||||
createKnowledgeSpace: serviceMock.create,
|
||||
getKnowledgeSpacesByIdAccessPolicy: serviceMock.getPolicy,
|
||||
patchKnowledgeSpacesByIdAccessPolicy: serviceMock.patchPolicy,
|
||||
postKnowledgeSpacesByIdDocuments: serviceMock.upload,
|
||||
postKnowledgeSpacesByIdDocumentsBulk: serviceMock.uploadBulk,
|
||||
},
|
||||
},
|
||||
consoleQuery: {
|
||||
@@ -103,6 +108,7 @@ async function choosePermission(user: ReturnType<typeof userEvent.setup>, option
|
||||
describe('CreateKnowledgePage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
globalThis.sessionStorage.clear()
|
||||
serviceMock.create.mockResolvedValue(createdKnowledge)
|
||||
serviceMock.getPolicy.mockResolvedValue({
|
||||
id: 'policy-1',
|
||||
@@ -118,6 +124,14 @@ describe('CreateKnowledgePage', () => {
|
||||
revision: 5,
|
||||
visibility: 'all_members',
|
||||
})
|
||||
serviceMock.upload.mockResolvedValue({
|
||||
id: 'document-1',
|
||||
})
|
||||
serviceMock.uploadBulk.mockResolvedValue({
|
||||
accepted: 2,
|
||||
excluded: 0,
|
||||
items: [],
|
||||
})
|
||||
permissionStateMock.keys = ['dataset.create_and_management', 'dataset.acl.access_config']
|
||||
navigationMock.startMode = null
|
||||
vi.spyOn(globalThis.crypto, 'randomUUID').mockReturnValue(
|
||||
@@ -126,6 +140,8 @@ describe('CreateKnowledgePage', () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.sessionStorage.clear()
|
||||
vi.useRealTimers()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
@@ -370,8 +386,8 @@ describe('CreateKnowledgePage', () => {
|
||||
expect(serviceMock.patchPolicy).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('keeps source and upload disabled until their stacked flows land', () => {
|
||||
navigationMock.startMode = 'source'
|
||||
it('keeps every start mode interactive without simulating backend success', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderPage()
|
||||
|
||||
const startEmpty = screen.getByRole('radio', { name: 'dataset.newKnowledge.startEmpty' })
|
||||
@@ -381,14 +397,307 @@ describe('CreateKnowledgePage', () => {
|
||||
name: 'dataset.newKnowledge.connectSource',
|
||||
})
|
||||
const uploadFiles = screen.getByRole('radio', { name: 'dataset.newKnowledge.uploadFiles' })
|
||||
expect(connectSource).toBeDisabled()
|
||||
expect(connectSource).toHaveAccessibleDescription(
|
||||
'dataset.newKnowledge.connectSourceDescription dataset.cornerLabel.unavailable',
|
||||
expect(connectSource).toBeEnabled()
|
||||
expect(uploadFiles).toBeEnabled()
|
||||
|
||||
await user.click(connectSource)
|
||||
expect(connectSource).toBeChecked()
|
||||
expect(screen.getByRole('radio', { name: 'dataset.newKnowledge.websiteCrawl' })).toBeChecked()
|
||||
expect(screen.getByRole('radio', { name: 'Firecrawl' })).toBeChecked()
|
||||
for (const unavailableProvider of ['Jina Reader', 'WaterCrawl']) {
|
||||
await user.click(screen.getByRole('radio', { name: unavailableProvider }))
|
||||
expect(screen.getByRole('radio', { name: unavailableProvider })).toBeChecked()
|
||||
}
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.moreProviders' })).toBeEnabled()
|
||||
expect(screen.getByText('dataset.newKnowledge.crawlOptions')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.crawlAndPreview' }),
|
||||
).toBeDisabled()
|
||||
expect(screen.getByText('dataset.newKnowledge.pagesAppearTitle')).toBeInTheDocument()
|
||||
expect(screen.getByText('dataset.newKnowledge.usingDefaults')).toBeInTheDocument()
|
||||
const rootUrl = screen.getByPlaceholderText('dataset.newKnowledge.rootUrlPlaceholder')
|
||||
const sourceName = screen.getByPlaceholderText('dataset.newKnowledge.sourceNamePlaceholder')
|
||||
expect(rootUrl).toBeEnabled()
|
||||
expect(sourceName).toBeEnabled()
|
||||
await user.type(rootUrl, 'https://docs.dify.ai')
|
||||
await user.type(sourceName, 'Dify docs')
|
||||
const crawlAndPreview = screen.getByRole('button', {
|
||||
name: 'dataset.newKnowledge.crawlAndPreview',
|
||||
})
|
||||
expect(crawlAndPreview).toBeEnabled()
|
||||
await user.click(crawlAndPreview)
|
||||
expect(screen.getByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.sourceSetupBackendDependency',
|
||||
)
|
||||
expect(uploadFiles).toBeDisabled()
|
||||
expect(uploadFiles).toHaveAccessibleDescription(
|
||||
'dataset.newKnowledge.uploadFilesDescription dataset.cornerLabel.unavailable',
|
||||
expect(screen.queryByText(/^dataset\.newKnowledge\.crawlingPages/)).not.toBeInTheDocument()
|
||||
expect(screen.queryByText(/^dataset\.newKnowledge\.pagesCrawled/)).not.toBeInTheDocument()
|
||||
const onlineDocuments = screen.getByRole('radio', {
|
||||
name: 'dataset.newKnowledge.onlineDocuments',
|
||||
})
|
||||
await user.click(onlineDocuments)
|
||||
expect(onlineDocuments).toBeChecked()
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('Notion')).toBeInTheDocument()
|
||||
expect(screen.getByText('dataset.newKnowledge.notionNotConnected')).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.connectNotion' })).toBeEnabled()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.connectNotion' }))
|
||||
expect(screen.getByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.sourceSetupBackendDependency',
|
||||
)
|
||||
await user.click(screen.getByRole('radio', { name: 'Google Docs' }))
|
||||
expect(screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' })).toBeEnabled()
|
||||
expect(screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' })).toBeEnabled()
|
||||
await user.click(uploadFiles)
|
||||
expect(uploadFiles).toBeChecked()
|
||||
const uploadInput = screen.getByLabelText('dataset.newKnowledge.uploadFiles', {
|
||||
selector: 'input[type="file"]',
|
||||
})
|
||||
expect(uploadInput).toBeInTheDocument()
|
||||
expect(uploadInput).not.toHaveAttribute('hidden')
|
||||
expect(uploadInput.nextElementSibling).toHaveClass('peer-focus-visible:ring-2')
|
||||
uploadInput.focus()
|
||||
expect(uploadInput).toHaveFocus()
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' })).toBeDisabled()
|
||||
})
|
||||
|
||||
it.each([
|
||||
[
|
||||
'source',
|
||||
'/datasets/new/e735c1dc-d2b8-4dc4-86dc-abaf2fb7d084/sources/new?type=websiteCrawl&draft=a9c36c57-2d84-44d6-a36d-841f0d92a179',
|
||||
],
|
||||
['upload', '/datasets/new/e735c1dc-d2b8-4dc4-86dc-abaf2fb7d084/documents'],
|
||||
])('continues from the %s mode after real creation succeeds', async (startMode, path) => {
|
||||
const user = userEvent.setup()
|
||||
navigationMock.startMode = startMode
|
||||
renderPage()
|
||||
|
||||
expect(
|
||||
screen.getByRole('radio', {
|
||||
name:
|
||||
startMode === 'source'
|
||||
? 'dataset.newKnowledge.connectSource'
|
||||
: 'dataset.newKnowledge.uploadFiles',
|
||||
}),
|
||||
).toBeChecked()
|
||||
if (startMode === 'upload') {
|
||||
await user.upload(
|
||||
screen.getByLabelText('dataset.newKnowledge.uploadFiles', {
|
||||
selector: 'input[type="file"]',
|
||||
}),
|
||||
new File(['content'], 'handbook.md', { type: 'text/markdown' }),
|
||||
)
|
||||
}
|
||||
await fillRequiredFields(user)
|
||||
await choosePermission(user, 'dataset.newKnowledge.permissionOnlyMe')
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' }))
|
||||
|
||||
await waitFor(() => expect(routerMock.replace).toHaveBeenCalledWith(path))
|
||||
if (startMode === 'upload')
|
||||
expect(serviceMock.upload).toHaveBeenCalledWith({
|
||||
body: { file: expect.objectContaining({ name: 'handbook.md' }) },
|
||||
params: { id: createdKnowledge.id },
|
||||
})
|
||||
})
|
||||
|
||||
it('hands the configured website draft to the real add-source workflow', async () => {
|
||||
const user = userEvent.setup()
|
||||
navigationMock.startMode = 'source'
|
||||
renderPage()
|
||||
await fillRequiredFields(user)
|
||||
await user.type(
|
||||
screen.getByPlaceholderText('dataset.newKnowledge.rootUrlPlaceholder'),
|
||||
'https://docs.dify.ai',
|
||||
)
|
||||
await user.type(
|
||||
screen.getByPlaceholderText('dataset.newKnowledge.sourceNamePlaceholder'),
|
||||
'Dify docs',
|
||||
)
|
||||
await user.keyboard('{Enter}')
|
||||
expect(serviceMock.create).not.toHaveBeenCalled()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.crawlOptions' }))
|
||||
await user.click(screen.getByRole('checkbox', { name: 'dataset.newKnowledge.includeSubpages' }))
|
||||
const maxPages = screen.getByRole('spinbutton', { name: 'dataset.newKnowledge.maxPages' })
|
||||
await user.clear(maxPages)
|
||||
await user.type(maxPages, '25')
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(routerMock.replace).toHaveBeenCalledWith(
|
||||
'/datasets/new/e735c1dc-d2b8-4dc4-86dc-abaf2fb7d084/sources/new?type=websiteCrawl&draft=a9c36c57-2d84-44d6-a36d-841f0d92a179',
|
||||
),
|
||||
)
|
||||
expect(
|
||||
JSON.parse(
|
||||
globalThis.sessionStorage.getItem(
|
||||
newKnowledgeSourceDraftStorageKey('a9c36c57-2d84-44d6-a36d-841f0d92a179'),
|
||||
) ?? '',
|
||||
),
|
||||
).toEqual({
|
||||
includeSubpages: false,
|
||||
maxPages: 25,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy: 'provider',
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves online document configuration across the real navigation boundary', async () => {
|
||||
const user = userEvent.setup()
|
||||
navigationMock.startMode = 'source'
|
||||
renderPage()
|
||||
await fillRequiredFields(user)
|
||||
await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.onlineDocuments' }))
|
||||
await user.click(screen.getByRole('radio', { name: 'Google Docs' }))
|
||||
await user.type(
|
||||
screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' }),
|
||||
'Shared product docs',
|
||||
)
|
||||
await user.selectOptions(
|
||||
screen.getByRole('combobox', { name: 'dataset.newKnowledge.syncPolicy' }),
|
||||
'daily',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(routerMock.replace).toHaveBeenCalledWith(
|
||||
'/datasets/new/e735c1dc-d2b8-4dc4-86dc-abaf2fb7d084/sources/new?type=onlineDocuments&draft=a9c36c57-2d84-44d6-a36d-841f0d92a179',
|
||||
),
|
||||
)
|
||||
expect(
|
||||
JSON.parse(
|
||||
globalThis.sessionStorage.getItem(
|
||||
newKnowledgeSourceDraftStorageKey('a9c36c57-2d84-44d6-a36d-841f0d92a179'),
|
||||
) ?? '',
|
||||
),
|
||||
).toEqual({
|
||||
provider: 'Google Docs',
|
||||
sourceName: 'Shared product docs',
|
||||
sourceType: 'onlineDocuments',
|
||||
syncPolicy: 'daily',
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps each source type draft when the user switches between them', async () => {
|
||||
const user = userEvent.setup()
|
||||
navigationMock.startMode = 'source'
|
||||
renderPage()
|
||||
await user.type(
|
||||
screen.getByPlaceholderText('dataset.newKnowledge.rootUrlPlaceholder'),
|
||||
'https://docs.dify.ai',
|
||||
)
|
||||
await user.type(
|
||||
screen.getByPlaceholderText('dataset.newKnowledge.sourceNamePlaceholder'),
|
||||
'Website docs',
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.onlineDocuments' }))
|
||||
expect(screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' })).toBeEnabled()
|
||||
await user.type(
|
||||
screen.getByRole('textbox', { name: 'dataset.newKnowledge.sourceName' }),
|
||||
'Notion docs',
|
||||
)
|
||||
await user.click(screen.getByRole('radio', { name: 'dataset.newKnowledge.websiteCrawl' }))
|
||||
|
||||
expect(screen.getByPlaceholderText('dataset.newKnowledge.rootUrlPlaceholder')).toHaveValue(
|
||||
'https://docs.dify.ai',
|
||||
)
|
||||
expect(screen.getByPlaceholderText('dataset.newKnowledge.sourceNamePlaceholder')).toHaveValue(
|
||||
'Website docs',
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the same website validation as the add-source workflow', async () => {
|
||||
const user = userEvent.setup()
|
||||
navigationMock.startMode = 'source'
|
||||
renderPage()
|
||||
await fillRequiredFields(user)
|
||||
const rootUrl = screen.getByPlaceholderText('dataset.newKnowledge.rootUrlPlaceholder')
|
||||
const sourceName = screen.getByPlaceholderText('dataset.newKnowledge.sourceNamePlaceholder')
|
||||
expect(rootUrl).toHaveAttribute('maxlength', '2048')
|
||||
expect(sourceName).toHaveAttribute('maxlength', '200')
|
||||
|
||||
await user.type(rootUrl, 'https://user:secret@docs.dify.ai')
|
||||
await user.type(sourceName, 'Dify docs')
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' })).toBeDisabled()
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' }))
|
||||
expect(serviceMock.create).not.toHaveBeenCalled()
|
||||
|
||||
await user.clear(rootUrl)
|
||||
await user.type(rootUrl, 'https://docs.dify.ai')
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' })).toBeEnabled()
|
||||
})
|
||||
|
||||
it('keeps an invalid upload visible and prevents creating the knowledge space', async () => {
|
||||
const user = userEvent.setup()
|
||||
navigationMock.startMode = 'upload'
|
||||
renderPage()
|
||||
await fillRequiredFields(user)
|
||||
const oversizedFile = new File(['content'], 'oversized.pdf', { type: 'application/pdf' })
|
||||
Object.defineProperty(oversizedFile, 'size', { value: 16 * 1024 * 1024 })
|
||||
await user.upload(
|
||||
screen.getByLabelText('dataset.newKnowledge.uploadFiles', {
|
||||
selector: 'input[type="file"]',
|
||||
}),
|
||||
oversizedFile,
|
||||
)
|
||||
|
||||
expect(screen.getByText('oversized.pdf')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('dataset.newKnowledge.documentUploadExclusion.fileSize'),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' })).toBeDisabled()
|
||||
expect(screen.queryByRole('button', { name: 'dataset.newKnowledge.preview' })).toBeNull()
|
||||
expect(serviceMock.create).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows the real uploading state on each valid file row', async () => {
|
||||
const user = userEvent.setup()
|
||||
navigationMock.startMode = 'upload'
|
||||
serviceMock.upload.mockImplementation(() => new Promise(() => {}))
|
||||
renderPage()
|
||||
await fillRequiredFields(user)
|
||||
await user.upload(
|
||||
screen.getByLabelText('dataset.newKnowledge.uploadFiles', {
|
||||
selector: 'input[type="file"]',
|
||||
}),
|
||||
new File(['content'], 'handbook.md', { type: 'text/markdown' }),
|
||||
)
|
||||
const queue = screen.getByRole('list', { name: 'dataset.newKnowledge.uploadFiles' })
|
||||
const preview = within(queue).getByRole('button', { name: 'dataset.newKnowledge.preview' })
|
||||
expect(preview).toBeDisabled()
|
||||
expect(preview).toHaveAccessibleDescription('dataset.newKnowledge.previewUnavailable')
|
||||
expect(screen.getByText('dataset.newKnowledge.previewUnavailable')).toBeVisible()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' }))
|
||||
|
||||
expect(await within(queue).findByText('dataset.newKnowledge.uploadingFiles')).toBeVisible()
|
||||
expect(within(queue).queryByRole('button', { name: 'dataset.newKnowledge.preview' })).toBeNull()
|
||||
})
|
||||
|
||||
it('retries upload without creating a duplicate knowledge space', async () => {
|
||||
const user = userEvent.setup()
|
||||
navigationMock.startMode = 'upload'
|
||||
serviceMock.upload.mockRejectedValueOnce(new Error('KnowledgeFS unavailable'))
|
||||
renderPage()
|
||||
await fillRequiredFields(user)
|
||||
await user.upload(
|
||||
screen.getByLabelText('dataset.newKnowledge.uploadFiles', {
|
||||
selector: 'input[type="file"]',
|
||||
}),
|
||||
new File(['content'], 'handbook.md', { type: 'text/markdown' }),
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' }))
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(
|
||||
'dataset.newKnowledge.documentUploadFailed',
|
||||
)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.createTitle' }))
|
||||
|
||||
await waitFor(() => expect(routerMock.replace).toHaveBeenCalled())
|
||||
expect(serviceMock.create).toHaveBeenCalledOnce()
|
||||
expect(serviceMock.upload).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('renders the approved creation modal and exposes both dismiss actions', async () => {
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
import type {
|
||||
DocumentRevisionChunk,
|
||||
LogicalDocument,
|
||||
LogicalDocumentRevision,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import {
|
||||
buildDocumentChunkTree,
|
||||
chunkCharacterCount,
|
||||
chunkMetadataEntries,
|
||||
chunkTreeLabel,
|
||||
initialDocumentRevision,
|
||||
visibleDocumentChunkNodes,
|
||||
} from '../document-detail-model'
|
||||
|
||||
const chunk = (overrides: Partial<DocumentRevisionChunk>): DocumentRevisionChunk => ({
|
||||
createdAt: '2026-07-21T10:00:00Z',
|
||||
documentId: 'document-1',
|
||||
documentRevision: 3,
|
||||
enabled: true,
|
||||
id: 'chunk-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
ordinal: 1,
|
||||
text: 'Chunk content',
|
||||
tokenCount: 2,
|
||||
userMetadata: {},
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const document = (overrides: Partial<LogicalDocument> = {}): LogicalDocument => ({
|
||||
active: {
|
||||
contentHash: 'hash-3',
|
||||
createdAt: '2026-07-21T10:00:00Z',
|
||||
documentAssetId: 'asset-1',
|
||||
documentAssetVersion: 1,
|
||||
documentId: 'document-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
mimeType: 'text/markdown',
|
||||
revision: 3,
|
||||
sizeBytes: 1200,
|
||||
state: 'active',
|
||||
},
|
||||
activeRevision: 3,
|
||||
createdAt: '2026-07-21T09:00:00Z',
|
||||
id: 'document-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
rowVersion: 2,
|
||||
status: 'ready',
|
||||
title: 'SSO enterprise',
|
||||
updatedAt: '2026-07-21T10:00:00Z',
|
||||
userMetadata: {},
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const revision = (value: number): Exclude<LogicalDocumentRevision, null> => ({
|
||||
contentHash: `hash-${value}`,
|
||||
createdAt: `2026-07-21T0${value}:00:00Z`,
|
||||
documentAssetId: 'asset-1',
|
||||
documentAssetVersion: value,
|
||||
documentId: 'document-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
mimeType: 'text/markdown',
|
||||
revision: value,
|
||||
sizeBytes: value * 100,
|
||||
state: value === 3 ? 'active' : 'superseded',
|
||||
})
|
||||
|
||||
describe('document detail model', () => {
|
||||
it('builds a deterministic parent-child tree and keeps orphans visible', () => {
|
||||
const tree = buildDocumentChunkTree([
|
||||
chunk({ id: 'child-b', ordinal: 3, parentChunkId: 'parent' }),
|
||||
chunk({ id: 'parent', ordinal: 1 }),
|
||||
chunk({ id: 'orphan', ordinal: 2, parentChunkId: 'missing' }),
|
||||
chunk({ id: 'child-a', ordinal: 2, parentChunkId: 'parent' }),
|
||||
])
|
||||
|
||||
expect(tree.roots.map((node) => node.chunk.id)).toEqual(['parent', 'orphan'])
|
||||
expect(tree.byId.get('parent')?.children.map((node) => node.chunk.id)).toEqual([
|
||||
'child-a',
|
||||
'child-b',
|
||||
])
|
||||
})
|
||||
|
||||
it('breaks cyclic parent links instead of losing every node', () => {
|
||||
const tree = buildDocumentChunkTree([
|
||||
chunk({ id: 'cycle-a', ordinal: 1, parentChunkId: 'cycle-b' }),
|
||||
chunk({ id: 'cycle-b', ordinal: 2, parentChunkId: 'cycle-a' }),
|
||||
chunk({ id: 'self', ordinal: 3, parentChunkId: 'self' }),
|
||||
])
|
||||
|
||||
expect(tree.roots.map((node) => node.chunk.id)).toEqual(['cycle-a', 'cycle-b', 'self'])
|
||||
})
|
||||
|
||||
it('builds a long parent chain without repeated ancestor walks', () => {
|
||||
const chunks = Array.from({ length: 5000 }, (_, index) =>
|
||||
chunk({
|
||||
id: `chunk-${index}`,
|
||||
ordinal: index,
|
||||
parentChunkId: index ? `chunk-${index - 1}` : undefined,
|
||||
}),
|
||||
)
|
||||
|
||||
const tree = buildDocumentChunkTree(chunks)
|
||||
const visible = visibleDocumentChunkNodes(tree.roots, new Set(tree.byId.keys()))
|
||||
|
||||
expect(tree.byId).toHaveLength(5000)
|
||||
expect(tree.roots.map((node) => node.chunk.id)).toEqual(['chunk-0'])
|
||||
expect(visible).toHaveLength(5000)
|
||||
expect(visible.at(-1)).toMatchObject({ depth: 4999 })
|
||||
})
|
||||
|
||||
it('flattens only expanded descendants in tree order', () => {
|
||||
const tree = buildDocumentChunkTree([
|
||||
chunk({ id: 'parent', ordinal: 1 }),
|
||||
chunk({ id: 'child', ordinal: 2, parentChunkId: 'parent' }),
|
||||
chunk({ id: 'grandchild', ordinal: 3, parentChunkId: 'child' }),
|
||||
])
|
||||
|
||||
expect(
|
||||
visibleDocumentChunkNodes(tree.roots, new Set()).map(({ node }) => node.chunk.id),
|
||||
).toEqual(['parent'])
|
||||
expect(
|
||||
visibleDocumentChunkNodes(tree.roots, new Set(['parent', 'child'])).map(
|
||||
({ depth, node }) => `${depth}:${node.chunk.id}`,
|
||||
),
|
||||
).toEqual(['0:parent', '1:child', '2:grandchild'])
|
||||
})
|
||||
|
||||
it('selects the active revision and falls back to the newest available revision', () => {
|
||||
expect(initialDocumentRevision(document(), [revision(1), revision(2), revision(3)])).toBe(3)
|
||||
expect(
|
||||
initialDocumentRevision(document({ active: null, activeRevision: undefined }), [
|
||||
revision(1),
|
||||
revision(2),
|
||||
]),
|
||||
).toBe(2)
|
||||
expect(initialDocumentRevision(document({ active: null, activeRevision: undefined }), [])).toBe(
|
||||
undefined,
|
||||
)
|
||||
expect(
|
||||
initialDocumentRevision(document({ active: null, activeRevision: undefined }), [
|
||||
{ ...revision(4), state: 'failed' },
|
||||
{ ...revision(5), state: 'candidate' },
|
||||
]),
|
||||
).toBeUndefined()
|
||||
expect(
|
||||
initialDocumentRevision(document({ active: null, activeRevision: undefined }), [
|
||||
revision(2),
|
||||
{ ...revision(4), state: 'failed' },
|
||||
]),
|
||||
).toBe(2)
|
||||
})
|
||||
|
||||
it('counts unicode characters and formats metadata deterministically', () => {
|
||||
expect(chunkCharacterCount('A📙B')).toBe(3)
|
||||
expect(chunkCharacterCount('👨👩👧👦')).toBe(1)
|
||||
expect(
|
||||
chunkMetadataEntries({
|
||||
page: 2,
|
||||
section: 'Security',
|
||||
tags: ['sso', 'saml'],
|
||||
}),
|
||||
).toEqual([
|
||||
['page', '2'],
|
||||
['section', 'Security'],
|
||||
['tags', '["sso","saml"]'],
|
||||
])
|
||||
})
|
||||
|
||||
it('bounds tree labels to a single readable summary', () => {
|
||||
expect(chunkTreeLabel('First line\nfull body', 3)).toBe('First line')
|
||||
expect(chunkTreeLabel(' ', 3)).toBe('#3')
|
||||
expect(chunkTreeLabel('x'.repeat(121), 3)).toBe(`${'x'.repeat(119)}…`)
|
||||
expect(chunkTreeLabel(`${'x'.repeat(118)}👨👩👧👦yz`, 3)).toBe(`${'x'.repeat(118)}👨👩👧👦…`)
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,118 @@
|
||||
import type {
|
||||
DocumentProcessingTask,
|
||||
LogicalDocument,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import {
|
||||
documentDisplayStatus,
|
||||
newestTaskByDocument,
|
||||
taskCanRetry,
|
||||
taskIsActive,
|
||||
taskNeedsAttention,
|
||||
taskVersionIsAfter,
|
||||
} from '../document-model'
|
||||
|
||||
const document = (overrides: Partial<LogicalDocument> = {}): LogicalDocument => ({
|
||||
active: {
|
||||
contentHash: 'hash',
|
||||
createdAt: '2026-07-20T10:00:00Z',
|
||||
documentAssetId: 'asset-1',
|
||||
documentAssetVersion: 1,
|
||||
documentId: 'document-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
mimeType: 'text/plain',
|
||||
revision: 2,
|
||||
sizeBytes: 10,
|
||||
state: 'active',
|
||||
},
|
||||
activeRevision: 2,
|
||||
createdAt: '2026-07-20T10:00:00Z',
|
||||
id: 'document-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
rowVersion: 1,
|
||||
status: 'ready',
|
||||
title: 'Document',
|
||||
updatedAt: '2026-07-20T10:00:00Z',
|
||||
userMetadata: {},
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const task = (overrides: Partial<DocumentProcessingTask> = {}): DocumentProcessingTask => ({
|
||||
createdAt: '2026-07-20T10:00:00Z',
|
||||
documentId: 'document-1',
|
||||
documentRevision: 3,
|
||||
id: 'task-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
progressPercent: 10,
|
||||
stage: 'queued',
|
||||
state: 'queued',
|
||||
updatedAt: '2026-07-20T10:00:00Z',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
describe('new Knowledge document model', () => {
|
||||
it('chooses the highest revision before a more recently updated stale task', () => {
|
||||
const result = newestTaskByDocument([
|
||||
task({ documentRevision: 2, id: 'stale', updatedAt: '2026-07-20T10:10:00Z' }),
|
||||
task({ documentRevision: 3, id: 'current', updatedAt: '2026-07-20T10:01:00Z' }),
|
||||
])
|
||||
|
||||
expect(result.get('document-1')?.id).toBe('current')
|
||||
})
|
||||
|
||||
it('compares task versions without losing sub-millisecond precision', () => {
|
||||
expect(taskVersionIsAfter('2026-07-20T10:00:00.123789Z', '2026-07-20T10:00:00.123456Z')).toBe(
|
||||
true,
|
||||
)
|
||||
expect(taskVersionIsAfter('2026-07-20T10:00:00.000001Z', '2026-07-20T10:00:00Z')).toBe(true)
|
||||
expect(taskVersionIsAfter('2026-07-20T10:00:00Z', '2026-07-20T10:00:00.000001Z')).toBe(false)
|
||||
})
|
||||
|
||||
it('selects the newest task when versions differ only after milliseconds', () => {
|
||||
const result = newestTaskByDocument([
|
||||
task({ id: 'older', updatedAt: '2026-07-20T10:00:00.123456Z' }),
|
||||
task({ id: 'newer', updatedAt: '2026-07-20T10:00:00.123789Z' }),
|
||||
])
|
||||
|
||||
expect(result.get('document-1')?.id).toBe('newer')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['dispatch_pending', 'queued'],
|
||||
['queued', 'queued'],
|
||||
['retry_wait', 'queued'],
|
||||
['running', 'processing'],
|
||||
['failed', 'failed'],
|
||||
] as const)('maps a latest %s task to %s', (state, expected) => {
|
||||
expect(documentDisplayStatus(document(), task({ state }))).toBe(expected)
|
||||
})
|
||||
|
||||
it('keeps a last ready revision available after a canceled re-index', () => {
|
||||
expect(documentDisplayStatus(document(), task({ state: 'canceled' }))).toBe('ready')
|
||||
expect(
|
||||
documentDisplayStatus(
|
||||
document({ active: null, activeRevision: undefined, status: 'pending' }),
|
||||
task({ state: 'canceled' }),
|
||||
),
|
||||
).toBe('failed')
|
||||
})
|
||||
|
||||
it('does not let an older failed task override the active revision', () => {
|
||||
expect(documentDisplayStatus(document(), task({ documentRevision: 1, state: 'failed' }))).toBe(
|
||||
'ready',
|
||||
)
|
||||
})
|
||||
|
||||
it('maps a disabled source and deleting lifecycle to the designed disabled state', () => {
|
||||
expect(documentDisplayStatus(document(), undefined, true)).toBe('disabled')
|
||||
expect(documentDisplayStatus(document({ status: 'deleting' }))).toBe('disabled')
|
||||
})
|
||||
|
||||
it('keeps task badge and actions aligned with the contract states', () => {
|
||||
expect(taskIsActive(task({ state: 'running' }))).toBe(true)
|
||||
expect(taskNeedsAttention(task({ state: 'failed' }))).toBe(true)
|
||||
expect(taskCanRetry(task({ state: 'canceled' }))).toBe(false)
|
||||
expect(taskNeedsAttention(task({ state: 'canceled' }))).toBe(false)
|
||||
expect(taskNeedsAttention(task({ state: 'succeeded' }))).toBe(false)
|
||||
expect(taskCanRetry(task({ state: 'superseded' }))).toBe(false)
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
newKnowledgeAddSourcePath,
|
||||
normalizeWebsiteSourceUrl,
|
||||
parseNewKnowledgeSourceDraft,
|
||||
singleSearchParam,
|
||||
} from '../routes'
|
||||
|
||||
describe('New RAG routes', () => {
|
||||
it('keeps source details out of the add-source URL', () => {
|
||||
expect(newKnowledgeAddSourcePath('space-1', 'websiteCrawl', 'opaque-draft-key')).toBe(
|
||||
'/datasets/new/space-1/sources/new?type=websiteCrawl&draft=opaque-draft-key',
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects repeated search parameters instead of selecting an ambiguous value', () => {
|
||||
expect(singleSearchParam(['websiteCrawl', 'onlineDrive'])).toBeUndefined()
|
||||
expect(singleSearchParam('websiteCrawl')).toBe('websiteCrawl')
|
||||
})
|
||||
|
||||
it('rejects credentials embedded in website URLs', () => {
|
||||
expect(normalizeWebsiteSourceUrl('https://user:secret@docs.dify.ai')).toBeUndefined()
|
||||
expect(normalizeWebsiteSourceUrl('https://docs.dify.ai/docs#intro')?.toString()).toBe(
|
||||
'https://docs.dify.ai/docs',
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects malformed session drafts', () => {
|
||||
expect(parseNewKnowledgeSourceDraft('{')).toBeUndefined()
|
||||
expect(
|
||||
parseNewKnowledgeSourceDraft(
|
||||
JSON.stringify({
|
||||
includeSubpages: true,
|
||||
maxPages: 201,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy: 'provider',
|
||||
}),
|
||||
),
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
it('migrates a valid website draft saved before source discriminators were added', () => {
|
||||
expect(
|
||||
parseNewKnowledgeSourceDraft(
|
||||
JSON.stringify({
|
||||
includeSubpages: false,
|
||||
maxPages: 25,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
includeSubpages: false,
|
||||
maxPages: 25,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: 'https://docs.dify.ai',
|
||||
sourceName: 'Dify docs',
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy: 'provider',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,410 @@
|
||||
import type { Source } from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import { screen, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { render } from '@/test/console/render'
|
||||
import { SourcesPage } from '../sources-page'
|
||||
|
||||
const toastInfoMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: { info: toastInfoMock },
|
||||
}))
|
||||
|
||||
type SourcesInfiniteOptions = {
|
||||
getNextPageParam: (lastPage: { nextCursor?: string }) => string | undefined
|
||||
input: (pageParam: string | null) => unknown
|
||||
initialPageParam: string | null
|
||||
refetchInterval: (query: {
|
||||
state: { data?: { pages: Array<{ items: Source[] }> } }
|
||||
}) => false | number
|
||||
}
|
||||
|
||||
const sourcesQuery = vi.hoisted(() => ({
|
||||
data: undefined as { pages: Array<{ items: Source[]; nextCursor?: string }> } | undefined,
|
||||
error: null as unknown,
|
||||
fetchNextPage: vi.fn(),
|
||||
hasNextPage: false,
|
||||
isFetchNextPageError: false,
|
||||
isFetchingNextPage: false,
|
||||
isPending: false,
|
||||
refetch: vi.fn(),
|
||||
}))
|
||||
|
||||
const infiniteOptionsMock = vi.hoisted(() => vi.fn((_options: SourcesInfiniteOptions) => ({})))
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...original,
|
||||
useInfiniteQuery: () => sourcesQuery,
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
knowledgeFs: {
|
||||
getKnowledgeSpacesByIdSources: {
|
||||
infiniteOptions: infiniteOptionsMock,
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const source = (overrides: Partial<Source>): Source => ({
|
||||
createdAt: '2026-07-20T10:00:00Z',
|
||||
id: 'source-1',
|
||||
knowledgeSpaceId: 'space-1',
|
||||
metadata: {},
|
||||
name: 'Product documentation',
|
||||
status: 'active',
|
||||
type: 'web',
|
||||
updatedAt: '2026-07-20T10:00:00Z',
|
||||
uri: 'https://docs.example.com',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
describe('SourcesPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
sourcesQuery.data = undefined
|
||||
sourcesQuery.error = null
|
||||
sourcesQuery.hasNextPage = false
|
||||
sourcesQuery.isFetchNextPageError = false
|
||||
sourcesQuery.isFetchingNextPage = false
|
||||
sourcesQuery.isPending = false
|
||||
})
|
||||
|
||||
it('loads sources through the KnowledgeFS contract', () => {
|
||||
sourcesQuery.isPending = true
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
|
||||
const options = infiniteOptionsMock.mock.lastCall?.[0]
|
||||
expect(options).toBeDefined()
|
||||
if (!options) throw new Error('Expected source infinite query options')
|
||||
expect(options.input(null)).toEqual({
|
||||
params: { id: 'space-1' },
|
||||
query: { limit: 50 },
|
||||
})
|
||||
expect(options.input('next')).toEqual({
|
||||
params: { id: 'space-1' },
|
||||
query: { cursor: 'next', limit: 50 },
|
||||
})
|
||||
expect(options.getNextPageParam({ nextCursor: 'next' })).toBe('next')
|
||||
expect(options.initialPageParam).toBeNull()
|
||||
expect(
|
||||
options.refetchInterval({
|
||||
state: { data: { pages: [{ items: [source({ status: 'syncing' })] }] } },
|
||||
}),
|
||||
).toBe(2000)
|
||||
expect(
|
||||
options.refetchInterval({
|
||||
state: { data: { pages: [{ items: [source({ status: 'active' })] }] } },
|
||||
}),
|
||||
).toBe(false)
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders the designed empty state and enters the real add-source route', () => {
|
||||
sourcesQuery.data = { pages: [{ items: [] }] }
|
||||
|
||||
const { container } = render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getByText('dataset.newKnowledge.sourcesEmptyTitle')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('heading', { name: 'dataset.newKnowledge.sources' }),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByRole('link', { name: 'dataset.newKnowledge.addSource' })).toHaveAttribute(
|
||||
'href',
|
||||
'/datasets/new/space-1/sources/new',
|
||||
)
|
||||
for (const brand of ['firecrawl', 'jina', 'notion', 'google-drive', 'confluence', 'dropbox'])
|
||||
expect(container.querySelector(`[data-brand="${brand}"]`)).toBeInTheDocument()
|
||||
expect(container.querySelector('[data-brand="firecrawl"]')?.tagName).toBe('svg')
|
||||
expect(container.querySelector('[data-brand="jina"]')).toHaveClass('i-custom-public-llm-jina')
|
||||
})
|
||||
|
||||
it('renders real source statuses and filters by status and search text', async () => {
|
||||
const user = userEvent.setup()
|
||||
sourcesQuery.data = {
|
||||
pages: [
|
||||
{
|
||||
items: [
|
||||
source({ id: 'active', name: 'Product documentation', status: 'active' }),
|
||||
source({ id: 'syncing', name: 'API reference', status: 'syncing' }),
|
||||
source({ id: 'disabled', name: 'Legacy FAQ', status: 'disabled' }),
|
||||
source({ id: 'error', name: 'Support site', status: 'error' }),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getAllByText('dataset.newKnowledge.sourceStatus.active')).toHaveLength(2)
|
||||
expect(screen.getAllByText('dataset.newKnowledge.sourceStatus.syncing')).toHaveLength(2)
|
||||
expect(screen.getAllByText('dataset.newKnowledge.sourceStatus.disabled')).toHaveLength(2)
|
||||
expect(screen.getAllByText('dataset.newKnowledge.sourceStatus.error')).toHaveLength(2)
|
||||
|
||||
await user.selectOptions(
|
||||
screen.getByRole('combobox', { name: 'dataset.newKnowledge.sourceFilterLabel' }),
|
||||
'error',
|
||||
)
|
||||
expect(screen.getByText('Support site')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Product documentation')).not.toBeInTheDocument()
|
||||
|
||||
await user.selectOptions(
|
||||
screen.getByRole('combobox', { name: 'dataset.newKnowledge.sourceFilterLabel' }),
|
||||
'all',
|
||||
)
|
||||
await user.type(
|
||||
screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchSources' }),
|
||||
'api',
|
||||
)
|
||||
expect(screen.getByText('API reference')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Support site')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('places the newest source first after creation', () => {
|
||||
sourcesQuery.data = {
|
||||
pages: [
|
||||
{
|
||||
items: [
|
||||
source({ id: 'older', name: 'Older source', createdAt: '2026-07-20T10:00:00Z' }),
|
||||
source({ id: 'newer', name: 'Newest source', createdAt: '2026-07-20T10:01:00Z' }),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
|
||||
const rows = screen.getAllByRole('row').slice(1)
|
||||
expect(within(rows[0]!).getByText('Newest source')).toBeInTheDocument()
|
||||
expect(within(rows[1]!).getByText('Older source')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps provisional crawl sources out of the source list', () => {
|
||||
sourcesQuery.data = {
|
||||
pages: [
|
||||
{
|
||||
items: [
|
||||
source({
|
||||
id: 'preview',
|
||||
metadata: { preview: true },
|
||||
name: 'Discarded preview',
|
||||
status: 'disabled',
|
||||
}),
|
||||
source({ id: 'connected', name: 'Connected source' }),
|
||||
source({
|
||||
id: 'submitted-preview',
|
||||
metadata: { preview: true },
|
||||
name: 'Submitted preview',
|
||||
status: 'syncing',
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.queryByText('Discarded preview')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('Connected source')).toBeInTheDocument()
|
||||
expect(screen.getByText('Submitted preview')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('continues past cursor pages containing only hidden preview drafts', () => {
|
||||
sourcesQuery.data = {
|
||||
pages: [
|
||||
{
|
||||
items: [
|
||||
source({
|
||||
id: 'preview',
|
||||
metadata: { preview: true },
|
||||
status: 'disabled',
|
||||
}),
|
||||
],
|
||||
nextCursor: 'next',
|
||||
},
|
||||
],
|
||||
}
|
||||
sourcesQuery.hasNextPage = true
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(sourcesQuery.fetchNextPage).toHaveBeenCalledOnce()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
expect(screen.queryByText('dataset.newKnowledge.sourcesEmptyTitle')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('continues after a loaded cursor page contributes only hidden preview drafts', () => {
|
||||
sourcesQuery.data = {
|
||||
pages: [
|
||||
{ items: [source({ id: 'connected', name: 'Connected source' })] },
|
||||
{
|
||||
items: [
|
||||
source({
|
||||
id: 'preview',
|
||||
metadata: { preview: true },
|
||||
status: 'disabled',
|
||||
}),
|
||||
],
|
||||
nextCursor: 'next',
|
||||
},
|
||||
],
|
||||
}
|
||||
sourcesQuery.hasNextPage = true
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getByText('Connected source')).toBeInTheDocument()
|
||||
expect(sourcesQuery.fetchNextPage).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('keeps backend-dependent row actions available without pretending they work', async () => {
|
||||
const user = userEvent.setup()
|
||||
sourcesQuery.data = { pages: [{ items: [source({})] }] }
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'dataset.newKnowledge.sourceActions:{"name":"Product documentation"}',
|
||||
}),
|
||||
)
|
||||
|
||||
const labels = [
|
||||
'dataset.newKnowledge.syncNow',
|
||||
'dataset.newKnowledge.editSource',
|
||||
'dataset.newKnowledge.disableSource',
|
||||
'dataset.newKnowledge.removeSource',
|
||||
]
|
||||
for (const label of labels)
|
||||
expect(screen.getByRole('menuitem', { name: label })).not.toHaveAttribute('data-disabled')
|
||||
await user.click(screen.getByRole('menuitem', { name: 'dataset.newKnowledge.syncNow' }))
|
||||
expect(toastInfoMock).toHaveBeenCalledWith('dataset.cornerLabel.unavailable')
|
||||
})
|
||||
|
||||
it('offers a real retry when the source list cannot load', async () => {
|
||||
const user = userEvent.setup()
|
||||
sourcesQuery.error = new Error('temporary failure')
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument()
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
|
||||
|
||||
expect(sourcesQuery.refetch).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('loads the next real cursor page on demand', async () => {
|
||||
const user = userEvent.setup()
|
||||
sourcesQuery.data = { pages: [{ items: [source({})], nextCursor: 'next' }] }
|
||||
sourcesQuery.hasNextPage = true
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.loadMore' }))
|
||||
|
||||
expect(sourcesQuery.fetchNextPage).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('continues through cursor pages before declaring a filtered search empty', async () => {
|
||||
const user = userEvent.setup()
|
||||
sourcesQuery.data = { pages: [{ items: [source({})], nextCursor: 'next' }] }
|
||||
sourcesQuery.hasNextPage = true
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
await user.type(
|
||||
screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchSources' }),
|
||||
'later page',
|
||||
)
|
||||
|
||||
expect(sourcesQuery.fetchNextPage).toHaveBeenCalledOnce()
|
||||
expect(screen.queryByText('dataset.newKnowledge.noMatchingSources')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('announces automatic filtered pagination when some results already match', async () => {
|
||||
const user = userEvent.setup()
|
||||
sourcesQuery.data = {
|
||||
pages: [{ items: [source({ name: 'Product documentation' })], nextCursor: 'next' }],
|
||||
}
|
||||
sourcesQuery.hasNextPage = true
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
await user.type(
|
||||
screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchSources' }),
|
||||
'product',
|
||||
)
|
||||
|
||||
expect(screen.getByText('Product documentation')).toBeInTheDocument()
|
||||
expect(sourcesQuery.fetchNextPage).toHaveBeenCalledOnce()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('announces a filtered search with no matches', async () => {
|
||||
const user = userEvent.setup()
|
||||
sourcesQuery.data = { pages: [{ items: [source({})] }] }
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
await user.type(
|
||||
screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchSources' }),
|
||||
'missing',
|
||||
)
|
||||
|
||||
expect(screen.getByRole('status')).toHaveTextContent('dataset.newKnowledge.noMatchingSources')
|
||||
})
|
||||
|
||||
it('stops automatic filtered pagination after a cursor error', async () => {
|
||||
const user = userEvent.setup()
|
||||
sourcesQuery.data = { pages: [{ items: [source({})], nextCursor: 'next' }] }
|
||||
sourcesQuery.hasNextPage = true
|
||||
sourcesQuery.isFetchNextPageError = true
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
await user.type(
|
||||
screen.getByRole('searchbox', { name: 'dataset.newKnowledge.searchSources' }),
|
||||
'later page',
|
||||
)
|
||||
|
||||
expect(sourcesQuery.fetchNextPage).not.toHaveBeenCalled()
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument()
|
||||
expect(screen.queryByRole('status')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('dataset.newKnowledge.noMatchingSources')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows provider and source type as separate row details', () => {
|
||||
sourcesQuery.data = {
|
||||
pages: [{ items: [source({ metadata: { providerName: 'Firecrawl' } })] }],
|
||||
}
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
|
||||
expect(screen.getByText('Firecrawl')).toBeInTheDocument()
|
||||
expect(screen.getByText('dataset.newKnowledge.sourceType.web')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders the designed source selection column and selects visible rows', async () => {
|
||||
const user = userEvent.setup()
|
||||
sourcesQuery.data = {
|
||||
pages: [
|
||||
{
|
||||
items: [
|
||||
source({ id: 'one', name: 'One source' }),
|
||||
source({ id: 'two', name: 'Two source' }),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
render(<SourcesPage knowledgeSpaceId="space-1" />)
|
||||
const selectAll = screen.getByRole('checkbox', {
|
||||
name: 'dataset.newKnowledge.selectAllSources',
|
||||
})
|
||||
expect(screen.getByRole('checkbox', { name: 'One source' })).not.toBeChecked()
|
||||
|
||||
await user.click(selectAll)
|
||||
|
||||
expect(selectAll).toBeChecked()
|
||||
expect(screen.getByRole('checkbox', { name: 'One source' })).toBeChecked()
|
||||
expect(screen.getByRole('checkbox', { name: 'Two source' })).toBeChecked()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,50 @@
|
||||
import { QueryClient, QueryClientProvider, useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import { useQueryDataUpdateCount } from '../use-query-data-update-count'
|
||||
|
||||
const queryKey = ['task-generation'] as const
|
||||
const sharedPage = { items: ['task-1'] }
|
||||
|
||||
function GenerationProbe({ queryClient }: { queryClient: QueryClient }) {
|
||||
const query = useInfiniteQuery({
|
||||
queryFn: async () => sharedPage,
|
||||
initialPageParam: null,
|
||||
getNextPageParam: () => undefined,
|
||||
queryKey,
|
||||
})
|
||||
const dataUpdateCount = useQueryDataUpdateCount(queryClient, queryKey)
|
||||
|
||||
return (
|
||||
<output>
|
||||
{query.data ? 'ready' : 'pending'}:{query.dataUpdatedAt}:{dataUpdateCount}
|
||||
</output>
|
||||
)
|
||||
}
|
||||
|
||||
describe('useQueryDataUpdateCount', () => {
|
||||
it('observes a real same-reference refetch when response timestamps collide', async () => {
|
||||
vi.spyOn(Date, 'now').mockReturnValue(100)
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false, staleTime: Infinity } },
|
||||
})
|
||||
const rendered = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<GenerationProbe queryClient={queryClient} />
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
|
||||
try {
|
||||
await screen.findByText('ready:100:1')
|
||||
const firstData = queryClient.getQueryData(queryKey)
|
||||
|
||||
await act(async () => queryClient.refetchQueries({ queryKey }))
|
||||
|
||||
await waitFor(() => expect(screen.getByText('ready:100:2')).toBeInTheDocument())
|
||||
expect(queryClient.getQueryData(queryKey)).toBe(firstData)
|
||||
} finally {
|
||||
rendered.unmount()
|
||||
queryClient.clear()
|
||||
vi.restoreAllMocks()
|
||||
}
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,896 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
GetKnowledgeSpacesByIdSourceConnectionsResponse,
|
||||
GetSourceProvidersResponse,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import type {
|
||||
NewKnowledgeSourceDraft,
|
||||
NewKnowledgeSourceType,
|
||||
NewKnowledgeWebsiteProvider,
|
||||
} from './routes'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Link from '@/next/link'
|
||||
import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import { PendingWebsiteSetup, UnavailableConnectedSourceSetup } from './add-source-placeholder'
|
||||
import {
|
||||
createNewKnowledgeSourceDraft,
|
||||
newKnowledgeDetailPath,
|
||||
newKnowledgeSourceDraftStorageKey,
|
||||
parseNewKnowledgeSourceDraft,
|
||||
} from './routes'
|
||||
import { WebsiteCrawlPreview } from './website-crawl-preview'
|
||||
|
||||
type Provider = GetSourceProvidersResponse['items'][number]
|
||||
type ProviderField = Provider['configuration'][number]
|
||||
type Connection = GetKnowledgeSpacesByIdSourceConnectionsResponse['items'][number]
|
||||
type ConnectionAuthKind = 'api-key' | 'endpoint'
|
||||
type SourceType = NewKnowledgeSourceType
|
||||
|
||||
const CONNECTION_PAGE_SIZE = 200
|
||||
const FIRECRAWL_PROVIDER_ID = 'plugin-daemon-website'
|
||||
const FIRECRAWL_CONNECTION_NAME = 'Firecrawl'
|
||||
const FIRECRAWL_CONFIGURATION = {
|
||||
datasource: 'crawl',
|
||||
pluginId: 'langgenius/firecrawl_datasource',
|
||||
provider: 'firecrawl',
|
||||
} as const
|
||||
const WEBSITE_PROVIDER_OPTIONS: Array<{
|
||||
icon: string
|
||||
value: NewKnowledgeWebsiteProvider
|
||||
}> = [
|
||||
{ icon: 'i-ri-fire-fill text-orange-500', value: 'Firecrawl' },
|
||||
{ icon: 'i-custom-public-llm-jina', value: 'Jina Reader' },
|
||||
{ icon: 'i-ri-water-flash-line', value: 'WaterCrawl' },
|
||||
]
|
||||
const FIRECRAWL_FIXED_FIELD_NAMES = new Set(Object.keys(FIRECRAWL_CONFIGURATION))
|
||||
const CONNECTION_STATUS_PRIORITY: Record<Connection['status'], number> = {
|
||||
active: 0,
|
||||
provisioning: 1,
|
||||
error: 2,
|
||||
expired: 3,
|
||||
revoked: 4,
|
||||
}
|
||||
function humanizeFieldName(name: string) {
|
||||
return name
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
||||
.replace(/[-_]+/g, ' ')
|
||||
.replace(/^./, (character) => character.toUpperCase())
|
||||
}
|
||||
|
||||
function fieldValue(value: string, type: ProviderField['type']) {
|
||||
if (type === 'boolean') return value === 'true'
|
||||
if (type === 'integer') return Number.parseInt(value, 10)
|
||||
return value.trim()
|
||||
}
|
||||
|
||||
function findFirecrawl(providers: Provider[]) {
|
||||
return providers.find((provider) => provider.id === FIRECRAWL_PROVIDER_ID)
|
||||
}
|
||||
|
||||
function findProviderConnection(connections: Connection[], providerId?: string) {
|
||||
if (!providerId) return undefined
|
||||
return [...connections.filter((connection) => connection.providerId === providerId)].sort(
|
||||
(left, right) =>
|
||||
CONNECTION_STATUS_PRIORITY[left.status] - CONNECTION_STATUS_PRIORITY[right.status] ||
|
||||
right.updatedAt.localeCompare(left.updatedAt),
|
||||
)[0]
|
||||
}
|
||||
|
||||
function findConnectionById(connections: Connection[], connectionId: string) {
|
||||
return [...connections.filter((connection) => connection.id === connectionId)].sort(
|
||||
(left, right) =>
|
||||
right.version - left.version ||
|
||||
right.updatedAt.localeCompare(left.updatedAt) ||
|
||||
CONNECTION_STATUS_PRIORITY[left.status] - CONNECTION_STATUS_PRIORITY[right.status],
|
||||
)[0]
|
||||
}
|
||||
|
||||
function normalizeSourceType(value: string | null): SourceType {
|
||||
if (value === 'onlineDocuments' || value === 'onlineDrive') return value
|
||||
return 'websiteCrawl'
|
||||
}
|
||||
|
||||
function getSupportedAuthKinds(provider: Provider) {
|
||||
const fields = provider.configuration.filter(
|
||||
(field) => !FIRECRAWL_FIXED_FIELD_NAMES.has(field.name),
|
||||
)
|
||||
const supported: ConnectionAuthKind[] = []
|
||||
if (provider.authKinds.includes('api-key') && fields.some((field) => field.secret))
|
||||
supported.push('api-key')
|
||||
if (
|
||||
provider.authKinds.includes('endpoint') &&
|
||||
fields.some((field) => !field.secret && field.format === 'uri')
|
||||
)
|
||||
supported.push('endpoint')
|
||||
return supported
|
||||
}
|
||||
|
||||
function SourceTypeSelector({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: SourceType
|
||||
onChange: (value: SourceType) => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const options = [
|
||||
{ icon: 'i-ri-global-line', key: 'websiteCrawl' as const },
|
||||
{ icon: 'i-ri-file-text-line', key: 'onlineDocuments' as const },
|
||||
{ icon: 'i-ri-hard-drive-3-line', key: 'onlineDrive' as const },
|
||||
]
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
<legend className="mb-1.5 system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.sourceTypeLabel'])}
|
||||
</legend>
|
||||
<div className="grid grid-cols-1 gap-0.5 rounded-lg bg-background-section p-0.5 sm:grid-cols-3">
|
||||
{options.map((option) => (
|
||||
<label
|
||||
key={option.key}
|
||||
className={cn(
|
||||
'relative flex h-8 items-center justify-center gap-1.5 rounded-md system-xs-medium outline-hidden has-focus-visible:ring-2 has-focus-visible:ring-state-accent-solid',
|
||||
value === option.key
|
||||
? 'bg-background-default text-text-primary shadow-xs'
|
||||
: 'cursor-pointer text-text-tertiary hover:text-text-secondary',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="source-type"
|
||||
value={option.key}
|
||||
checked={value === option.key}
|
||||
onChange={() => onChange(option.key)}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span aria-hidden className={`${option.icon} size-4`} />
|
||||
{t(($) => $[`newKnowledge.${option.key}`])}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
function ProviderSelector({
|
||||
provider,
|
||||
onChange,
|
||||
}: {
|
||||
provider: NewKnowledgeWebsiteProvider
|
||||
onChange: (provider: NewKnowledgeWebsiteProvider) => void
|
||||
}) {
|
||||
const { t } = useTranslation('datasetCreation')
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
<legend className="mb-1.5 system-xs-medium text-text-secondary">
|
||||
{t(($) => $['stepOne.website.chooseProvider'])}
|
||||
</legend>
|
||||
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
|
||||
{WEBSITE_PROVIDER_OPTIONS.map((option) => (
|
||||
<label
|
||||
key={option.value}
|
||||
className={cn(
|
||||
'relative flex min-h-9 items-center justify-center gap-2 rounded-lg border px-3 system-xs-medium outline-hidden has-focus-visible:ring-2 has-focus-visible:ring-state-accent-solid',
|
||||
provider === option.value
|
||||
? 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary'
|
||||
: 'cursor-pointer border-divider-subtle text-text-secondary hover:bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="source-provider"
|
||||
value={option.value}
|
||||
checked={provider === option.value}
|
||||
onChange={() => onChange(option.value)}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span aria-hidden className={`${option.icon} size-4`} />
|
||||
{option.value}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
function ProviderFieldControl({
|
||||
field,
|
||||
setValues,
|
||||
values,
|
||||
}: {
|
||||
field: ProviderField
|
||||
setValues: React.Dispatch<React.SetStateAction<Record<string, string>>>
|
||||
values: Record<string, string>
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const generatedId = useId()
|
||||
const inputId = `${generatedId}-input`
|
||||
const descriptionId = field.description ? `${generatedId}-description` : undefined
|
||||
const label = humanizeFieldName(field.name)
|
||||
const sharedProps = {
|
||||
'aria-describedby': descriptionId,
|
||||
id: inputId,
|
||||
required: field.required,
|
||||
value: values[field.name] ?? '',
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) =>
|
||||
setValues((current) => ({ ...current, [field.name]: event.target.value })),
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={inputId} className="system-xs-medium text-text-secondary">
|
||||
{label}
|
||||
{field.required && <span className="ml-0.5 text-text-destructive">*</span>}
|
||||
</label>
|
||||
{field.type === 'boolean' ? (
|
||||
<select
|
||||
{...sharedProps}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden focus:ring-2 focus:ring-state-accent-solid"
|
||||
>
|
||||
<option value="">—</option>
|
||||
<option value="true">{t(($) => $['newKnowledge.booleanTrue'])}</option>
|
||||
<option value="false">{t(($) => $['newKnowledge.booleanFalse'])}</option>
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
{...sharedProps}
|
||||
type={
|
||||
field.secret
|
||||
? 'password'
|
||||
: field.type === 'integer'
|
||||
? 'number'
|
||||
: field.format === 'uri'
|
||||
? 'url'
|
||||
: 'text'
|
||||
}
|
||||
inputMode={field.format === 'uri' ? 'url' : undefined}
|
||||
autoComplete={field.secret ? 'new-password' : 'off'}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden focus:ring-2 focus:ring-state-accent-solid"
|
||||
/>
|
||||
)}
|
||||
{field.description && (
|
||||
<p id={descriptionId} className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{field.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ConnectionForm({
|
||||
knowledgeSpaceId,
|
||||
onConnected,
|
||||
onReconcile,
|
||||
provider,
|
||||
}: {
|
||||
knowledgeSpaceId: string
|
||||
onConnected: (connection: Connection) => void
|
||||
onReconcile: () => Promise<Connection | undefined>
|
||||
provider: Provider
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const supportedAuthKinds = getSupportedAuthKinds(provider)
|
||||
const [authKind, setAuthKind] = useState<ConnectionAuthKind>(supportedAuthKinds[0] ?? 'api-key')
|
||||
const [configuration, setConfiguration] = useState<Record<string, string>>({})
|
||||
const [credentials, setCredentials] = useState<Record<string, string>>({})
|
||||
const [error, setError] = useState(false)
|
||||
const [pending, setPending] = useState(false)
|
||||
const configurableFields = provider.configuration.filter(
|
||||
(field) => !FIRECRAWL_FIXED_FIELD_NAMES.has(field.name),
|
||||
)
|
||||
const visibleFields = configurableFields.filter(
|
||||
(field) => authKind === 'api-key' || (!field.secret && field.format === 'uri'),
|
||||
)
|
||||
|
||||
const changeAuthKind = (nextAuthKind: ConnectionAuthKind) => {
|
||||
if (nextAuthKind !== authKind) setCredentials({})
|
||||
setAuthKind(nextAuthKind)
|
||||
}
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
if (pending) return
|
||||
|
||||
const missingRequiredField = visibleFields.some((field) => {
|
||||
const values = field.secret ? credentials : configuration
|
||||
return field.required && !values[field.name]?.trim()
|
||||
})
|
||||
if (missingRequiredField) return
|
||||
|
||||
setError(false)
|
||||
setPending(true)
|
||||
try {
|
||||
const fixedConfiguration = Object.fromEntries(
|
||||
provider.configuration
|
||||
.filter((field) => FIRECRAWL_FIXED_FIELD_NAMES.has(field.name))
|
||||
.map((field) => [
|
||||
field.name,
|
||||
FIRECRAWL_CONFIGURATION[field.name as keyof typeof FIRECRAWL_CONFIGURATION],
|
||||
]),
|
||||
)
|
||||
const safeConfiguration = {
|
||||
...fixedConfiguration,
|
||||
...Object.fromEntries(
|
||||
visibleFields
|
||||
.filter((field) => !field.secret && configuration[field.name]?.trim())
|
||||
.map((field) => [field.name, fieldValue(configuration[field.name] ?? '', field.type)]),
|
||||
),
|
||||
}
|
||||
const secretCredentials = Object.fromEntries(
|
||||
visibleFields
|
||||
.filter((field) => field.secret && credentials[field.name]?.trim())
|
||||
.map((field) => [field.name, fieldValue(credentials[field.name] ?? '', field.type)]),
|
||||
)
|
||||
const createdConnection =
|
||||
await consoleClient.knowledgeFs.postKnowledgeSpacesByIdSourceConnections({
|
||||
body: {
|
||||
authKind,
|
||||
configuration: safeConfiguration,
|
||||
credentials: secretCredentials,
|
||||
name: FIRECRAWL_CONNECTION_NAME,
|
||||
providerId: provider.id,
|
||||
},
|
||||
params: { id: knowledgeSpaceId },
|
||||
})
|
||||
setCredentials({})
|
||||
onConnected(createdConnection)
|
||||
} catch {
|
||||
setCredentials({})
|
||||
let reconciledConnection: Connection | undefined
|
||||
try {
|
||||
reconciledConnection = await onReconcile()
|
||||
} catch {
|
||||
reconciledConnection = undefined
|
||||
}
|
||||
if (!reconciledConnection) setError(true)
|
||||
} finally {
|
||||
setPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="rounded-xl bg-background-section p-4" onSubmit={handleSubmit}>
|
||||
{supportedAuthKinds.length > 1 && (
|
||||
<fieldset className="mb-4">
|
||||
<legend className="mb-1.5 system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.authenticationMethod'])}
|
||||
</legend>
|
||||
<div className="flex gap-2">
|
||||
{supportedAuthKinds.map((kind) => (
|
||||
<label
|
||||
key={kind}
|
||||
className="flex items-center gap-1.5 system-xs-regular text-text-secondary"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="auth-kind"
|
||||
value={kind}
|
||||
checked={authKind === kind}
|
||||
onChange={() => changeAuthKind(kind)}
|
||||
/>
|
||||
{t(($) => $[`newKnowledge.authKind.${kind}`])}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
)}
|
||||
<div className="space-y-3">
|
||||
{visibleFields.map((field) => (
|
||||
<ProviderFieldControl
|
||||
key={field.name}
|
||||
field={field}
|
||||
values={field.secret ? credentials : configuration}
|
||||
setValues={field.secret ? setCredentials : setConfiguration}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{error && (
|
||||
<p role="alert" className="mt-3 system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.connectionFailed'])}
|
||||
</p>
|
||||
)}
|
||||
<Button type="submit" variant="primary" className="mt-4" disabled={pending}>
|
||||
{pending
|
||||
? t(($) => $['newKnowledge.connectingProvider'])
|
||||
: t(($) => $['newKnowledge.connectProvider'])}
|
||||
</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
function UnconfiguredProvider({
|
||||
knowledgeSpaceId,
|
||||
onConnected,
|
||||
onReconcile,
|
||||
provider,
|
||||
}: {
|
||||
knowledgeSpaceId: string
|
||||
onConnected: (connection: Connection) => void
|
||||
onReconcile: () => Promise<Connection | undefined>
|
||||
provider: Provider
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const [configuring, setConfiguring] = useState(false)
|
||||
|
||||
if (configuring)
|
||||
return (
|
||||
<ConnectionForm
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
onConnected={onConnected}
|
||||
onReconcile={onReconcile}
|
||||
provider={provider}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="rounded-xl bg-background-section p-4">
|
||||
<span className="flex size-9 items-center justify-center rounded-lg border border-divider-subtle bg-background-default">
|
||||
<span aria-hidden className="i-ri-fire-line size-[18px] text-text-tertiary" />
|
||||
</span>
|
||||
<h3 className="mt-3 system-sm-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.providerNotConfigured'], {
|
||||
provider: FIRECRAWL_CONNECTION_NAME,
|
||||
})}
|
||||
</h3>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.providerNotConfiguredDescription'], {
|
||||
provider: FIRECRAWL_CONNECTION_NAME,
|
||||
})}
|
||||
</p>
|
||||
<Button variant="primary" className="mt-4" onClick={() => setConfiguring(true)}>
|
||||
{t(($) => $['newKnowledge.configureProvider'])}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ConnectionProblem({
|
||||
connection,
|
||||
knowledgeSpaceId,
|
||||
onConnected,
|
||||
onReconcile,
|
||||
}: {
|
||||
connection: Connection
|
||||
knowledgeSpaceId: string
|
||||
onConnected: (connection: Connection) => void
|
||||
onReconcile: () => Promise<Connection | undefined>
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const [pending, setPending] = useState(false)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const refresh = async () => {
|
||||
if (pending) return
|
||||
setPending(true)
|
||||
setError(false)
|
||||
try {
|
||||
const refreshed =
|
||||
await consoleClient.knowledgeFs.postKnowledgeSpacesByIdSourceConnectionsByConnectionIdRefresh(
|
||||
{
|
||||
body: { expectedVersion: connection.version },
|
||||
params: { connectionId: connection.id, id: knowledgeSpaceId },
|
||||
},
|
||||
)
|
||||
onConnected(refreshed)
|
||||
} catch {
|
||||
let reconciledConnection: Connection | undefined
|
||||
try {
|
||||
reconciledConnection = await onReconcile()
|
||||
} catch {
|
||||
reconciledConnection = undefined
|
||||
}
|
||||
if (!reconciledConnection) setError(true)
|
||||
} finally {
|
||||
setPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-components-option-card-option-border bg-background-section p-4">
|
||||
<h3 className="system-sm-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.connectionNeedsAttention'])}
|
||||
</h3>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.connectionNeedsAttentionDescription'])}
|
||||
</p>
|
||||
{error && (
|
||||
<p role="alert" className="mt-2 system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.connectionRefreshFailed'])}
|
||||
</p>
|
||||
)}
|
||||
<Button className="mt-4" onClick={() => void refresh()} disabled={pending}>
|
||||
{pending
|
||||
? t(($) => $['newKnowledge.refreshingConnection'])
|
||||
: tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ProvisioningConnection({
|
||||
onReconcile,
|
||||
}: {
|
||||
onReconcile: () => Promise<Connection | undefined>
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const [pending, setPending] = useState(false)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const refresh = async () => {
|
||||
if (pending) return
|
||||
setPending(true)
|
||||
setError(false)
|
||||
try {
|
||||
const reconciledConnection = await onReconcile()
|
||||
if (!reconciledConnection) setError(true)
|
||||
} catch {
|
||||
setError(true)
|
||||
} finally {
|
||||
setPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-xl bg-background-section p-4">
|
||||
<p className="system-sm-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.connectionProvisioning'])}
|
||||
</p>
|
||||
{error && (
|
||||
<p role="alert" className="mt-2 system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.connectionRefreshFailed'])}
|
||||
</p>
|
||||
)}
|
||||
<Button className="mt-3" loading={pending} onClick={() => void refresh()} disabled={pending}>
|
||||
{t(($) => $['newKnowledge.refreshConnectionStatus'])}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function AddSourcePage({
|
||||
initialSourceDraft,
|
||||
initialSourceType,
|
||||
knowledgeSpaceId,
|
||||
sourceDraftKey,
|
||||
}: {
|
||||
initialSourceDraft?: NewKnowledgeSourceDraft
|
||||
initialSourceType?: string
|
||||
knowledgeSpaceId: string
|
||||
sourceDraftKey?: string
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const queryClient = useQueryClient()
|
||||
const [sourceDraft, setSourceDraft] = useState<NewKnowledgeSourceDraft>(
|
||||
() =>
|
||||
initialSourceDraft ??
|
||||
createNewKnowledgeSourceDraft(normalizeSourceType(initialSourceType ?? null)),
|
||||
)
|
||||
const [sourceDraftResolved, setSourceDraftResolved] = useState(!sourceDraftKey)
|
||||
const [connectedSourceBoundaryVisible, setConnectedSourceBoundaryVisible] = useState(false)
|
||||
const sourceDraftsRef = useRef<
|
||||
Partial<Record<NewKnowledgeSourceDraft['sourceType'], NewKnowledgeSourceDraft>>
|
||||
>({ [sourceDraft.sourceType]: sourceDraft })
|
||||
const sourceType = sourceDraft.sourceType
|
||||
const websiteSourceSelected =
|
||||
sourceDraft.sourceType === 'websiteCrawl' && sourceDraft.provider === FIRECRAWL_CONNECTION_NAME
|
||||
const updateSourceDraft = (draft: NewKnowledgeSourceDraft) => {
|
||||
sourceDraftsRef.current[draft.sourceType] = draft
|
||||
setSourceDraft(draft)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!sourceDraftKey) return undefined
|
||||
let active = true
|
||||
globalThis.queueMicrotask(() => {
|
||||
if (!active) return
|
||||
let draft: NewKnowledgeSourceDraft | undefined
|
||||
try {
|
||||
const storageKey = newKnowledgeSourceDraftStorageKey(sourceDraftKey)
|
||||
const storedDraft = globalThis.sessionStorage.getItem(storageKey)
|
||||
if (storedDraft) draft = parseNewKnowledgeSourceDraft(storedDraft)
|
||||
} catch {
|
||||
// Continue without the optional draft when browser storage is unavailable.
|
||||
}
|
||||
if (active) {
|
||||
const nextDraft =
|
||||
draft ?? createNewKnowledgeSourceDraft(normalizeSourceType(initialSourceType ?? null))
|
||||
sourceDraftsRef.current[nextDraft.sourceType] = nextDraft
|
||||
setSourceDraft(nextDraft)
|
||||
setSourceDraftResolved(true)
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
active = false
|
||||
}
|
||||
}, [initialSourceType, sourceDraftKey])
|
||||
const clearStoredSourceDraft = useCallback(() => {
|
||||
if (!sourceDraftKey) return
|
||||
try {
|
||||
globalThis.sessionStorage.removeItem(newKnowledgeSourceDraftStorageKey(sourceDraftKey))
|
||||
} catch {
|
||||
// The draft remains scoped to this browser session when storage cleanup is unavailable.
|
||||
}
|
||||
}, [sourceDraftKey])
|
||||
const providersQuery = useQuery(
|
||||
consoleQuery.knowledgeFs.getSourceProviders.queryOptions({
|
||||
input: {},
|
||||
context: { silent: true },
|
||||
enabled: websiteSourceSelected,
|
||||
retry: false,
|
||||
}),
|
||||
)
|
||||
const connectionsQuery = useInfiniteQuery(
|
||||
consoleQuery.knowledgeFs.getKnowledgeSpacesByIdSourceConnections.infiniteOptions({
|
||||
context: { silent: true },
|
||||
enabled: websiteSourceSelected,
|
||||
input: (pageParam) => ({
|
||||
params: { id: knowledgeSpaceId },
|
||||
query: {
|
||||
limit: CONNECTION_PAGE_SIZE,
|
||||
...(typeof pageParam === 'string' ? { cursor: pageParam } : {}),
|
||||
},
|
||||
}),
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
initialPageParam: null as string | null,
|
||||
retry: false,
|
||||
}),
|
||||
)
|
||||
const provider = findFirecrawl(providersQuery.data?.items ?? [])
|
||||
const remoteConnections = connectionsQuery.data?.pages.flatMap((page) => page.items) ?? []
|
||||
const remoteConnection = findProviderConnection(remoteConnections, provider?.id)
|
||||
const [connectionOverride, setConnectionOverride] = useState<Connection>()
|
||||
const matchingRemoteConnection = connectionOverride
|
||||
? remoteConnections.find((candidate) => candidate.id === connectionOverride.id)
|
||||
: undefined
|
||||
const connection = useMemo(() => {
|
||||
const localConnection = connectionOverride
|
||||
if (!localConnection || localConnection.providerId !== provider?.id) return remoteConnection
|
||||
if (!matchingRemoteConnection) return localConnection
|
||||
if (matchingRemoteConnection.id === localConnection.id) {
|
||||
if (matchingRemoteConnection.version > localConnection.version)
|
||||
return matchingRemoteConnection
|
||||
if (matchingRemoteConnection.version < localConnection.version) return localConnection
|
||||
if (matchingRemoteConnection.updatedAt > localConnection.updatedAt)
|
||||
return matchingRemoteConnection
|
||||
if (matchingRemoteConnection.updatedAt < localConnection.updatedAt) return localConnection
|
||||
if (
|
||||
CONNECTION_STATUS_PRIORITY[matchingRemoteConnection.status] <
|
||||
CONNECTION_STATUS_PRIORITY[localConnection.status]
|
||||
)
|
||||
return matchingRemoteConnection
|
||||
}
|
||||
return localConnection
|
||||
}, [connectionOverride, matchingRemoteConnection, provider?.id, remoteConnection])
|
||||
const supportsDirectConnection = provider ? getSupportedAuthKinds(provider).length > 0 : false
|
||||
const {
|
||||
fetchNextPage: fetchNextConnectionPage,
|
||||
hasNextPage: hasNextConnectionPage,
|
||||
isFetchingNextPage: isFetchingNextConnectionPage,
|
||||
refetch: refetchConnections,
|
||||
} = connectionsQuery
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
websiteSourceSelected &&
|
||||
hasNextConnectionPage &&
|
||||
!isFetchingNextConnectionPage &&
|
||||
!connectionsQuery.isFetchNextPageError
|
||||
)
|
||||
void fetchNextConnectionPage()
|
||||
}, [
|
||||
connectionsQuery.isFetchNextPageError,
|
||||
fetchNextConnectionPage,
|
||||
hasNextConnectionPage,
|
||||
isFetchingNextConnectionPage,
|
||||
websiteSourceSelected,
|
||||
])
|
||||
|
||||
const rememberConnection = useCallback(
|
||||
(updatedConnection: Connection) => {
|
||||
setConnectionOverride(updatedConnection)
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: consoleQuery.knowledgeFs.getKnowledgeSpacesByIdSourceConnections.key(),
|
||||
})
|
||||
},
|
||||
[queryClient],
|
||||
)
|
||||
|
||||
const reconcileConnection = useCallback(async () => {
|
||||
if (connection) setConnectionOverride(connection)
|
||||
const refreshed = await refetchConnections()
|
||||
if (refreshed.error) throw refreshed.error
|
||||
const refreshedConnections = refreshed.data?.pages.flatMap((page) => page.items) ?? []
|
||||
const refreshedCurrentConnection = connection
|
||||
? findConnectionById(refreshedConnections, connection.id)
|
||||
: undefined
|
||||
const updatedConnection = connection
|
||||
? refreshedCurrentConnection
|
||||
? findConnectionById([connection, refreshedCurrentConnection], connection.id)
|
||||
: undefined
|
||||
: findProviderConnection(refreshedConnections, provider?.id)
|
||||
if (updatedConnection) setConnectionOverride(updatedConnection)
|
||||
return updatedConnection
|
||||
}, [connection, provider?.id, refetchConnections])
|
||||
|
||||
const loadingConnections =
|
||||
connectionsQuery.isPending ||
|
||||
(!connectionsQuery.isFetchNextPageError &&
|
||||
(connectionsQuery.hasNextPage || connectionsQuery.isFetchingNextPage))
|
||||
if (
|
||||
!sourceDraftResolved ||
|
||||
(websiteSourceSelected && (providersQuery.isPending || loadingConnections))
|
||||
)
|
||||
return (
|
||||
<div className="flex min-h-64 items-center justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
|
||||
const queryError =
|
||||
providersQuery.error || connectionsQuery.error || connectionsQuery.isFetchNextPageError
|
||||
const websiteReady = Boolean(
|
||||
websiteSourceSelected &&
|
||||
!queryError &&
|
||||
provider?.available &&
|
||||
supportsDirectConnection &&
|
||||
connection?.status === 'active',
|
||||
)
|
||||
|
||||
return (
|
||||
<main className="min-h-full px-4 py-6 sm:px-8 sm:py-7">
|
||||
<header>
|
||||
<h2 className="title-xl-semi-bold text-text-primary">
|
||||
{t(($) => $['newKnowledge.addSource'])}
|
||||
</h2>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.addSourceDescription'])}
|
||||
</p>
|
||||
</header>
|
||||
<div className="mt-5 w-full max-w-2xl space-y-4">
|
||||
<SourceTypeSelector
|
||||
value={sourceType}
|
||||
onChange={(value) => {
|
||||
sourceDraftsRef.current[sourceDraft.sourceType] = sourceDraft
|
||||
updateSourceDraft(
|
||||
sourceDraftsRef.current[value] ?? createNewKnowledgeSourceDraft(value),
|
||||
)
|
||||
setConnectedSourceBoundaryVisible(false)
|
||||
}}
|
||||
/>
|
||||
{sourceDraft.sourceType === 'websiteCrawl' ? (
|
||||
<>
|
||||
<ProviderSelector
|
||||
provider={sourceDraft.provider}
|
||||
onChange={(provider) => {
|
||||
updateSourceDraft({ ...sourceDraft, provider })
|
||||
setConnectedSourceBoundaryVisible(false)
|
||||
}}
|
||||
/>
|
||||
{!websiteSourceSelected ? (
|
||||
<div className="rounded-xl bg-background-section p-4">
|
||||
<p className="system-sm-semibold text-text-primary">{sourceDraft.provider}</p>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.providerUnavailable'])}
|
||||
</p>
|
||||
</div>
|
||||
) : queryError ? (
|
||||
<div className="rounded-xl bg-background-section p-4">
|
||||
<p className="system-sm-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.providerLoadFailed'])}
|
||||
</p>
|
||||
<Button
|
||||
className="mt-3"
|
||||
onClick={() =>
|
||||
void Promise.all([providersQuery.refetch(), connectionsQuery.refetch()])
|
||||
}
|
||||
>
|
||||
{t(($) => $['newKnowledge.retryProviderLoad'])}
|
||||
</Button>
|
||||
</div>
|
||||
) : !provider ? (
|
||||
<div className="rounded-xl bg-background-section p-4 system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.firecrawlUnavailable'])}
|
||||
</div>
|
||||
) : !provider.available || !supportsDirectConnection ? (
|
||||
<div className="rounded-xl bg-background-section p-4">
|
||||
<p className="system-sm-semibold text-text-primary">{FIRECRAWL_CONNECTION_NAME}</p>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{provider.unavailableReason ?? t(($) => $['newKnowledge.providerUnavailable'])}
|
||||
</p>
|
||||
</div>
|
||||
) : connection?.status === 'active' ? (
|
||||
<WebsiteCrawlPreview
|
||||
connection={connection}
|
||||
initialDraft={sourceDraft}
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
onDraftFinished={clearStoredSourceDraft}
|
||||
/>
|
||||
) : connection?.status === 'provisioning' ? (
|
||||
<ProvisioningConnection onReconcile={reconcileConnection} />
|
||||
) : connection ? (
|
||||
<ConnectionProblem
|
||||
connection={connection}
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
onConnected={rememberConnection}
|
||||
onReconcile={reconcileConnection}
|
||||
/>
|
||||
) : (
|
||||
<UnconfiguredProvider
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
onConnected={rememberConnection}
|
||||
onReconcile={reconcileConnection}
|
||||
provider={provider}
|
||||
/>
|
||||
)}
|
||||
{!websiteReady && (
|
||||
<PendingWebsiteSetup
|
||||
key={sourceDraft.provider}
|
||||
draft={sourceDraft}
|
||||
onDraftChange={updateSourceDraft}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<UnavailableConnectedSourceSetup
|
||||
draft={sourceDraft}
|
||||
onDraftChange={(draft) => {
|
||||
updateSourceDraft(draft)
|
||||
setConnectedSourceBoundaryVisible(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{sourceType === 'websiteCrawl' && !websiteReady && (
|
||||
<div className="flex justify-end gap-2 border-t border-divider-subtle pt-5">
|
||||
<Link
|
||||
href={newKnowledgeDetailPath(knowledgeSpaceId)}
|
||||
onClick={clearStoredSourceDraft}
|
||||
className="inline-flex h-8 items-center justify-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3.5 system-sm-medium text-components-button-secondary-text shadow-xs outline-hidden hover:bg-components-button-secondary-bg-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
{t(($) => $['newKnowledge.cancelAddSource'])}
|
||||
</Link>
|
||||
<span id="add-source-selection-requirement" className="sr-only">
|
||||
{t(($) => $['newKnowledge.addSourceRequiresSelection'])}
|
||||
</span>
|
||||
<Button variant="primary" disabled aria-describedby="add-source-selection-requirement">
|
||||
{t(($) => $['newKnowledge.addSource'])}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{sourceType !== 'websiteCrawl' && (
|
||||
<div className="flex justify-end gap-2 border-t border-divider-subtle pt-5">
|
||||
<Link
|
||||
href={newKnowledgeDetailPath(knowledgeSpaceId)}
|
||||
onClick={clearStoredSourceDraft}
|
||||
className="inline-flex h-8 items-center justify-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3.5 system-sm-medium text-components-button-secondary-text shadow-xs outline-hidden hover:bg-components-button-secondary-bg-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
{t(($) => $['newKnowledge.cancelAddSource'])}
|
||||
</Link>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={!sourceDraft.sourceName.trim()}
|
||||
onClick={() => setConnectedSourceBoundaryVisible(true)}
|
||||
>
|
||||
{t(($) => $['newKnowledge.addSource'])}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{connectedSourceBoundaryVisible && (
|
||||
<p
|
||||
role="alert"
|
||||
className="rounded-md bg-components-badge-status-light-warning-bg px-3 py-2 system-xs-regular text-text-warning"
|
||||
>
|
||||
{t(($) => $['newKnowledge.sourceSetupBackendDependency'])}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
NewKnowledgeOnlineDocumentsProvider,
|
||||
NewKnowledgeOnlineDocumentsSourceDraft,
|
||||
NewKnowledgeOnlineDriveProvider,
|
||||
NewKnowledgeOnlineDriveSourceDraft,
|
||||
NewKnowledgeSourceDraft,
|
||||
NewKnowledgeWebsiteSourceDraft,
|
||||
} from './routes'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
isValidWebsiteSourceDraft,
|
||||
NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH,
|
||||
NEW_KNOWLEDGE_SOURCE_URL_MAX_LENGTH,
|
||||
} from './routes'
|
||||
|
||||
const connectedProviders = {
|
||||
onlineDocuments: [
|
||||
{ icon: 'i-custom-public-common-notion', label: 'Notion' },
|
||||
{ icon: 'i-ri-file-text-line', label: 'Google Docs' },
|
||||
{ icon: 'i-ri-links-line', label: 'Confluence' },
|
||||
],
|
||||
onlineDrive: [
|
||||
{ icon: 'i-ri-google-drive-line', label: 'Google Drive' },
|
||||
{ icon: 'i-ri-cloud-line', label: 'OneDrive' },
|
||||
{ icon: 'i-ri-box-3-line', label: 'Amazon S3' },
|
||||
],
|
||||
} as const
|
||||
|
||||
export function PendingWebsiteSetup({
|
||||
draft,
|
||||
onDraftChange,
|
||||
}: {
|
||||
draft: NewKnowledgeWebsiteSourceDraft
|
||||
onDraftChange: (draft: NewKnowledgeWebsiteSourceDraft) => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const [optionsExpanded, setOptionsExpanded] = useState(false)
|
||||
const [backendBoundaryVisible, setBackendBoundaryVisible] = useState(false)
|
||||
const updateDraft = (nextDraft: NewKnowledgeWebsiteSourceDraft) => {
|
||||
onDraftChange(nextDraft)
|
||||
setBackendBoundaryVisible(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="space-y-4" aria-label={t(($) => $['newKnowledge.crawlAndPreview'])}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<label className="block system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.rootUrl'])}
|
||||
<input
|
||||
type="url"
|
||||
inputMode="url"
|
||||
autoComplete="off"
|
||||
maxLength={NEW_KNOWLEDGE_SOURCE_URL_MAX_LENGTH}
|
||||
value={draft.rootUrl}
|
||||
placeholder={t(($) => $['newKnowledge.rootUrlPlaceholder'])}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden placeholder:text-text-quaternary focus:ring-2 focus:ring-state-accent-solid"
|
||||
onChange={(event) => {
|
||||
updateDraft({ ...draft, rootUrl: event.target.value })
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<label className="block system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.sourceName'])}
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
maxLength={NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH}
|
||||
value={draft.sourceName}
|
||||
placeholder={t(($) => $['newKnowledge.sourceNamePlaceholder'])}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden placeholder:text-text-quaternary focus:ring-2 focus:ring-state-accent-solid"
|
||||
onChange={(event) => {
|
||||
updateDraft({ ...draft, sourceName: event.target.value })
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-lg bg-background-section">
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={optionsExpanded}
|
||||
className="flex min-h-9 w-full items-center gap-2 px-3 text-left system-xs-medium text-text-secondary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
onClick={() => setOptionsExpanded((value) => !value)}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'size-4',
|
||||
optionsExpanded ? 'i-ri-arrow-down-s-line' : 'i-ri-arrow-right-s-line',
|
||||
)}
|
||||
/>
|
||||
{t(($) => $['newKnowledge.crawlOptions'])}
|
||||
{!optionsExpanded && (
|
||||
<span className="ml-auto system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.usingDefaults'])}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{optionsExpanded && (
|
||||
<fieldset className="grid grid-cols-1 gap-3 px-3 pb-3 sm:grid-cols-2">
|
||||
<label className="flex items-center gap-2 system-xs-regular text-text-secondary">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={draft.includeSubpages}
|
||||
onChange={(event) =>
|
||||
updateDraft({ ...draft, includeSubpages: event.target.checked })
|
||||
}
|
||||
/>
|
||||
{t(($) => $['newKnowledge.includeSubpages'])}
|
||||
</label>
|
||||
<label className="system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.maxPages'])}
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={200}
|
||||
value={draft.maxPages}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden focus:ring-2 focus:ring-state-accent-solid"
|
||||
onChange={(event) =>
|
||||
updateDraft({ ...draft, maxPages: event.target.valueAsNumber || 0 })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
disabled={!isValidWebsiteSourceDraft(draft)}
|
||||
onClick={() => setBackendBoundaryVisible(true)}
|
||||
>
|
||||
{t(($) => $['newKnowledge.crawlAndPreview'])}
|
||||
</Button>
|
||||
<div className="flex min-h-40 flex-col items-center justify-center rounded-lg border border-dashed border-divider-regular px-6 text-center">
|
||||
<span className="flex size-10 items-center justify-center rounded-lg bg-background-section">
|
||||
<span aria-hidden className="i-ri-global-line size-5 text-text-tertiary" />
|
||||
</span>
|
||||
<p className="mt-2 system-xs-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.pagesAppearTitle'])}
|
||||
</p>
|
||||
<p className="mt-2 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.pagesAppearDescription'])}
|
||||
</p>
|
||||
</div>
|
||||
{backendBoundaryVisible && (
|
||||
<p
|
||||
role="alert"
|
||||
className="rounded-md bg-components-badge-status-light-warning-bg px-3 py-2 system-xs-regular text-text-warning"
|
||||
>
|
||||
{t(($) => $['newKnowledge.sourceSetupBackendDependency'])}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export function UnavailableConnectedSourceSetup({
|
||||
draft,
|
||||
onDraftChange,
|
||||
}: {
|
||||
draft: NewKnowledgeOnlineDocumentsSourceDraft | NewKnowledgeOnlineDriveSourceDraft
|
||||
onDraftChange: (draft: NewKnowledgeSourceDraft) => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCreation } = useTranslation('datasetCreation')
|
||||
const sourceType = draft.sourceType
|
||||
const providers = connectedProviders[sourceType]
|
||||
const activeProvider = draft.provider
|
||||
const selectProvider = (provider: string) => {
|
||||
if (draft.sourceType === 'onlineDocuments')
|
||||
onDraftChange({ ...draft, provider: provider as NewKnowledgeOnlineDocumentsProvider })
|
||||
else onDraftChange({ ...draft, provider: provider as NewKnowledgeOnlineDriveProvider })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<fieldset>
|
||||
<legend className="mb-1.5 system-xs-medium text-text-secondary">
|
||||
{tCreation(($) => $['stepOne.website.chooseProvider'])}
|
||||
</legend>
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
||||
{providers.map((option) => (
|
||||
<label
|
||||
key={option.label}
|
||||
className={cn(
|
||||
'flex min-h-9 cursor-pointer items-center justify-center gap-2 rounded-lg border px-3 system-xs-medium outline-hidden has-focus-visible:ring-2 has-focus-visible:ring-state-accent-solid',
|
||||
activeProvider === option.label
|
||||
? 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary'
|
||||
: 'border-divider-subtle text-text-secondary hover:bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={`${sourceType}-provider`}
|
||||
value={option.label}
|
||||
checked={activeProvider === option.label}
|
||||
onChange={() => selectProvider(option.label)}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span aria-hidden className={`${option.icon} size-4 shrink-0`} />
|
||||
<span className="truncate">{option.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{sourceType === 'onlineDrive' && (
|
||||
<section
|
||||
aria-label={t(($) => $['newKnowledge.selectFilesAndFolders'])}
|
||||
className="flex min-h-40 flex-col items-center justify-center rounded-lg border border-dashed border-divider-regular px-6 text-center"
|
||||
>
|
||||
<span className="flex size-10 items-center justify-center rounded-lg bg-background-section">
|
||||
<span aria-hidden className="i-ri-folder-open-line size-5 text-text-tertiary" />
|
||||
</span>
|
||||
<p className="mt-2 system-xs-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.selectFilesAndFolders'])}
|
||||
</p>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<label className="block system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.sourceName'])}
|
||||
<span aria-hidden className="ml-0.5 text-text-destructive">
|
||||
*
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
maxLength={NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH}
|
||||
value={draft.sourceName}
|
||||
placeholder={t(($) => $['newKnowledge.sourceNamePlaceholder'])}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden placeholder:text-text-quaternary focus:ring-2 focus:ring-state-accent-solid"
|
||||
onChange={(event) => onDraftChange({ ...draft, sourceName: event.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label className="block system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.syncPolicy'])}
|
||||
<select
|
||||
value={draft.syncPolicy}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden focus:ring-2 focus:ring-state-accent-solid"
|
||||
onChange={(event) =>
|
||||
onDraftChange({
|
||||
...draft,
|
||||
syncPolicy: event.target.value as NewKnowledgeSourceDraft['syncPolicy'],
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="provider">{t(($) => $['newKnowledge.syncPolicyProvider'])}</option>
|
||||
<option value="daily">{t(($) => $['newKnowledge.syncPolicyDaily'])}</option>
|
||||
<option value="manual">{t(($) => $['newKnowledge.syncPolicyManual'])}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div role="status" className="rounded-lg bg-background-section px-3 py-2">
|
||||
<p className="system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.providerUnavailable'])}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { taskVersionIsAfter } from './document-model'
|
||||
|
||||
export type AuxiliaryTaskReadGuard = ReturnType<typeof createAuxiliaryTaskReadGuard>
|
||||
|
||||
type RefetchDocuments = (options: { cancelRefetch: true }) => Promise<{ error: unknown }>
|
||||
|
||||
export function createAuxiliaryTaskReadGuard() {
|
||||
const blockedVersions = new Map<string, string>()
|
||||
|
||||
return {
|
||||
block(taskId: string, taskVersion: string) {
|
||||
const blockedVersion = blockedVersions.get(taskId)
|
||||
if (blockedVersion && taskVersionIsAfter(blockedVersion, taskVersion)) return
|
||||
blockedVersions.set(taskId, taskVersion)
|
||||
},
|
||||
clear() {
|
||||
blockedVersions.clear()
|
||||
},
|
||||
clearTask(taskId: string) {
|
||||
blockedVersions.delete(taskId)
|
||||
},
|
||||
clearThrough(taskId: string, authoritativeVersion: string) {
|
||||
const blockedVersion = blockedVersions.get(taskId)
|
||||
if (!blockedVersion || taskVersionIsAfter(blockedVersion, authoritativeVersion)) return false
|
||||
blockedVersions.delete(taskId)
|
||||
return true
|
||||
},
|
||||
isBlocked(taskId: string, taskVersion: string) {
|
||||
const blockedVersion = blockedVersions.get(taskId)
|
||||
return Boolean(blockedVersion && !taskVersionIsAfter(taskVersion, blockedVersion))
|
||||
},
|
||||
retain(taskIds: Set<string>) {
|
||||
for (const taskId of blockedVersions.keys()) {
|
||||
if (!taskIds.has(taskId)) blockedVersions.delete(taskId)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function useAuxiliaryTaskReadGuard({
|
||||
documentPermissionDenied,
|
||||
refetchDocuments,
|
||||
}: {
|
||||
documentPermissionDenied: boolean
|
||||
refetchDocuments: RefetchDocuments
|
||||
}) {
|
||||
const guardRef = useRef<AuxiliaryTaskReadGuard | null>(null)
|
||||
if (!guardRef.current) guardRef.current = createAuxiliaryTaskReadGuard()
|
||||
const guard = guardRef.current
|
||||
const previousDocumentPermissionDeniedRef = useRef(documentPermissionDenied)
|
||||
const denialGenerationRef = useRef(0)
|
||||
const [permissionDenied, setPermissionDenied] = useState(false)
|
||||
const [guardRevision, setGuardRevision] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
const wasDenied = previousDocumentPermissionDeniedRef.current
|
||||
previousDocumentPermissionDeniedRef.current = documentPermissionDenied
|
||||
if (!wasDenied || documentPermissionDenied) return
|
||||
guard.clear()
|
||||
// oxlint-disable-next-line eslint-react/set-state-in-effect -- The authoritative document permission transition retires the local denial.
|
||||
setPermissionDenied(false)
|
||||
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Guard mutations need one render so blocked readers are reconsidered.
|
||||
setGuardRevision((current) => current + 1)
|
||||
}, [documentPermissionDenied, guard])
|
||||
|
||||
const retry = useCallback(() => {
|
||||
const denialGeneration = denialGenerationRef.current + 1
|
||||
denialGenerationRef.current = denialGeneration
|
||||
setPermissionDenied(true)
|
||||
void refetchDocuments({ cancelRefetch: true }).then(
|
||||
(result) => {
|
||||
if (denialGenerationRef.current === denialGeneration && !result.error)
|
||||
setPermissionDenied(false)
|
||||
},
|
||||
() => undefined,
|
||||
)
|
||||
}, [refetchDocuments])
|
||||
|
||||
const deny = useCallback(
|
||||
(taskId: string, taskVersion: string) => {
|
||||
guard.block(taskId, taskVersion)
|
||||
retry()
|
||||
},
|
||||
[guard, retry],
|
||||
)
|
||||
|
||||
return { deny, guard, guardRevision, permissionDenied, retry }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import type { NewKnowledgeStartMode } from '../routes'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RadioControl, RadioItem } from '@langgenius/dify-ui/radio'
|
||||
@@ -7,15 +8,19 @@ import { useId } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export function StartMode({
|
||||
children,
|
||||
description,
|
||||
disabled = false,
|
||||
icon,
|
||||
selected = false,
|
||||
title,
|
||||
value,
|
||||
}: {
|
||||
children?: ReactNode
|
||||
description: string
|
||||
disabled?: boolean
|
||||
icon: string
|
||||
selected?: boolean
|
||||
title: string
|
||||
value: NewKnowledgeStartMode
|
||||
}) {
|
||||
@@ -25,44 +30,52 @@ export function StartMode({
|
||||
const unavailableId = useId()
|
||||
|
||||
return (
|
||||
<RadioItem
|
||||
value={value}
|
||||
nativeButton
|
||||
render={<button type="button" />}
|
||||
aria-labelledby={titleId}
|
||||
aria-describedby={disabled ? `${descriptionId} ${unavailableId}` : descriptionId}
|
||||
disabled={disabled}
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex min-h-16 w-full items-center gap-3 overflow-hidden rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg px-4 py-3.5 text-left outline-hidden transition-colors motion-reduce:transition-none',
|
||||
'hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
||||
'data-checked:border-[1.5px] data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg',
|
||||
'data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:bg-components-option-card-option-bg',
|
||||
'overflow-hidden rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg transition-colors motion-reduce:transition-none',
|
||||
selected &&
|
||||
'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg',
|
||||
)}
|
||||
>
|
||||
<RadioControl aria-hidden />
|
||||
<span className="flex size-9 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-components-option-card-option-border bg-background-default">
|
||||
<span aria-hidden className={`${icon} size-[18px] text-text-accent`} />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span id={titleId} className="block system-sm-medium text-text-primary">
|
||||
{title}
|
||||
<RadioItem
|
||||
value={value}
|
||||
nativeButton
|
||||
render={<button type="button" />}
|
||||
aria-labelledby={titleId}
|
||||
aria-describedby={disabled ? `${descriptionId} ${unavailableId}` : descriptionId}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'relative flex min-h-16 w-full items-center gap-3 px-4 py-3.5 text-left outline-hidden',
|
||||
'hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset',
|
||||
'data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:bg-transparent',
|
||||
)}
|
||||
>
|
||||
<RadioControl aria-hidden />
|
||||
<span className="flex size-9 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-components-option-card-option-border bg-background-default">
|
||||
<span aria-hidden className={`${icon} size-[18px] text-text-accent`} />
|
||||
</span>
|
||||
<span id={descriptionId} className="mt-0.5 block system-xs-regular text-text-tertiary">
|
||||
{description}
|
||||
<span className="min-w-0 flex-1">
|
||||
<span id={titleId} className="block system-sm-medium text-text-primary">
|
||||
{title}
|
||||
</span>
|
||||
<span id={descriptionId} className="mt-0.5 block system-xs-regular text-text-tertiary">
|
||||
{description}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
{value === 'source' && (
|
||||
<span
|
||||
aria-hidden
|
||||
className="h-4 w-[82px] shrink-0 bg-[url('/images/new-rag/create-knowledge-connectors.svg')] bg-contain bg-center bg-no-repeat"
|
||||
/>
|
||||
)}
|
||||
{disabled && (
|
||||
<span id={unavailableId} className="ml-3 shrink-0 system-xs-medium text-text-disabled">
|
||||
{t(($) => $['cornerLabel.unavailable'])}
|
||||
</span>
|
||||
)}
|
||||
</RadioItem>
|
||||
{value === 'source' && (
|
||||
<span
|
||||
aria-hidden
|
||||
className="h-4 w-[82px] shrink-0 bg-[url('/images/new-rag/create-knowledge-connectors.svg')] bg-contain bg-center bg-no-repeat"
|
||||
/>
|
||||
)}
|
||||
{disabled && (
|
||||
<span id={unavailableId} className="ml-3 shrink-0 system-xs-medium text-text-disabled">
|
||||
{t(($) => $['cornerLabel.unavailable'])}
|
||||
</span>
|
||||
)}
|
||||
</RadioItem>
|
||||
{selected && children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,650 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
GetKnowledgeSpacesByIdSourcesBySourceIdSyncPolicyResponse,
|
||||
GetKnowledgeSpacesByIdSourceWorkflowsByRunIdPagesResponse,
|
||||
PutKnowledgeSpacesByIdSourcesBySourceIdSyncPolicyData,
|
||||
Source,
|
||||
SourceWorkflowRun,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import type { FormEvent } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useId, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import { newKnowledgeDetailPath } from './routes'
|
||||
|
||||
type PreviewPage = GetKnowledgeSpacesByIdSourceWorkflowsByRunIdPagesResponse['items'][number]
|
||||
type SyncPolicy = GetKnowledgeSpacesByIdSourcesBySourceIdSyncPolicyResponse
|
||||
type SyncMode = SyncPolicy['mode']
|
||||
type SyncPolicyBody = PutKnowledgeSpacesByIdSourcesBySourceIdSyncPolicyData['body']
|
||||
|
||||
const MIN_CUSTOM_INTERVAL_HOURS = 1
|
||||
const MAX_CUSTOM_INTERVAL_HOURS = 720
|
||||
const MAX_SELECTED_PAGES = 200
|
||||
const IMPORT_POLL_INTERVAL_MS = 1_000
|
||||
const IMPORT_POLL_ATTEMPTS = 120
|
||||
|
||||
type PageSkipReason = 'failed' | 'off-domain'
|
||||
|
||||
function requestStatus(error: unknown) {
|
||||
if (error instanceof Response) return error.status
|
||||
if (!error || typeof error !== 'object') return undefined
|
||||
if ('status' in error && typeof error.status === 'number') return error.status
|
||||
if ('data' in error && error.data && typeof error.data === 'object' && 'status' in error.data)
|
||||
return typeof error.data.status === 'number' ? error.data.status : undefined
|
||||
}
|
||||
|
||||
function isDefinitiveRequestFailure(error: unknown) {
|
||||
const status = requestStatus(error)
|
||||
return status !== undefined && [400, 401, 403, 404, 409, 422, 429].includes(status)
|
||||
}
|
||||
|
||||
function normalizedWorkflowState(run: SourceWorkflowRun) {
|
||||
return run.state.toLocaleLowerCase().replaceAll('-', '_')
|
||||
}
|
||||
|
||||
function isSuccessfulImport(run: SourceWorkflowRun) {
|
||||
return ['completed', 'succeeded'].includes(normalizedWorkflowState(run))
|
||||
}
|
||||
|
||||
function isTerminalImport(run: SourceWorkflowRun) {
|
||||
return [
|
||||
'canceled',
|
||||
'cancelled',
|
||||
'completed',
|
||||
'failed',
|
||||
'succeeded',
|
||||
'timed_out',
|
||||
'timeout',
|
||||
'zero_results',
|
||||
].includes(normalizedWorkflowState(run))
|
||||
}
|
||||
|
||||
async function waitForImportTerminal(
|
||||
knowledgeSpaceId: string,
|
||||
initialRun: SourceWorkflowRun,
|
||||
onWorkflowRun: (run: SourceWorkflowRun) => void,
|
||||
) {
|
||||
let current = initialRun
|
||||
for (let attempt = 0; attempt < IMPORT_POLL_ATTEMPTS; attempt += 1) {
|
||||
if (isTerminalImport(current)) return current
|
||||
current = await consoleClient.knowledgeFs.getKnowledgeSpacesByIdSourceWorkflowsByRunId({
|
||||
params: { id: knowledgeSpaceId, runId: current.id },
|
||||
})
|
||||
onWorkflowRun(current)
|
||||
if (isTerminalImport(current)) return current
|
||||
await new Promise((resolve) => setTimeout(resolve, IMPORT_POLL_INTERVAL_MS))
|
||||
}
|
||||
throw new Error('Source import did not reach a terminal state')
|
||||
}
|
||||
|
||||
function pageSkipReason(page: PreviewPage, rootUrl: string): PageSkipReason | undefined {
|
||||
try {
|
||||
const root = new URL(rootUrl)
|
||||
const candidate = new URL(page.sourceUrl)
|
||||
if (
|
||||
!['http:', 'https:'].includes(candidate.protocol) ||
|
||||
candidate.username ||
|
||||
candidate.password
|
||||
)
|
||||
return 'failed'
|
||||
if (candidate.hostname.toLocaleLowerCase() !== root.hostname.toLocaleLowerCase())
|
||||
return 'off-domain'
|
||||
return undefined
|
||||
} catch {
|
||||
return 'failed'
|
||||
}
|
||||
}
|
||||
|
||||
function policyConfiguration(mode: SyncMode, customIntervalHours: number) {
|
||||
if (mode === 'manual') return { enabled: false, mode } as const
|
||||
if (mode === 'custom') {
|
||||
return {
|
||||
customIntervalSeconds: customIntervalHours * 3600,
|
||||
enabled: true,
|
||||
mode,
|
||||
} as const
|
||||
}
|
||||
return { enabled: true, mode } as const
|
||||
}
|
||||
|
||||
function policyMatches(policy: SyncPolicy, desired: ReturnType<typeof policyConfiguration>) {
|
||||
return (
|
||||
policy.revision > 0 &&
|
||||
policy.enabled === desired.enabled &&
|
||||
policy.mode === desired.mode &&
|
||||
(desired.mode !== 'custom' || policy.customIntervalSeconds === desired.customIntervalSeconds)
|
||||
)
|
||||
}
|
||||
|
||||
function initialSyncPolicy(source: Source): SyncPolicy | undefined {
|
||||
if (!source.version) return undefined
|
||||
return {
|
||||
createdAt: source.createdAt,
|
||||
enabled: true,
|
||||
expectedSourceVersion: source.version,
|
||||
id: source.id,
|
||||
knowledgeSpaceId: source.knowledgeSpaceId,
|
||||
mode: 'provider',
|
||||
revision: 0,
|
||||
sourceId: source.id,
|
||||
updatedAt: source.updatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
function PolicyLoading() {
|
||||
const { t } = useTranslation('dataset')
|
||||
return (
|
||||
<div
|
||||
role="status"
|
||||
aria-label={t(($) => $['newKnowledge.loadingSyncPolicy'])}
|
||||
className="space-y-3"
|
||||
>
|
||||
<div className="h-6 w-28 animate-pulse rounded bg-background-section" />
|
||||
<div className="h-9 w-full animate-pulse rounded-lg bg-background-section" />
|
||||
<div className="h-8 w-full animate-pulse rounded-lg bg-background-section" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ReadyCrawlSelectionForm({
|
||||
busy,
|
||||
discardRequested,
|
||||
knowledgeSpaceId,
|
||||
onCancel,
|
||||
onRecrawl,
|
||||
onSubmissionUncertainChange,
|
||||
onSubmitted,
|
||||
onWorkflowPending,
|
||||
onWorkflowRun,
|
||||
pages,
|
||||
policy,
|
||||
rootUrl,
|
||||
run,
|
||||
source,
|
||||
workflowUncertain,
|
||||
}: {
|
||||
busy: boolean
|
||||
discardRequested: () => boolean
|
||||
knowledgeSpaceId: string
|
||||
onCancel: () => void
|
||||
onRecrawl: () => void
|
||||
onSubmissionUncertainChange: (uncertain: boolean) => void
|
||||
onSubmitted: () => Promise<void> | void
|
||||
onWorkflowPending: (request: Promise<SourceWorkflowRun | undefined>) => void
|
||||
onWorkflowRun: (run: SourceWorkflowRun) => void
|
||||
pages: PreviewPage[]
|
||||
policy: SyncPolicy
|
||||
rootUrl: string
|
||||
run: SourceWorkflowRun
|
||||
source: Source
|
||||
workflowUncertain: boolean
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const router = useRouter()
|
||||
const queryClient = useQueryClient()
|
||||
const customIntervalErrorId = 'crawl-custom-interval-error'
|
||||
const pageDescriptionPrefixId = useId()
|
||||
const pageSkipReasons = useMemo(
|
||||
() => new Map(pages.map((page) => [page.pageId, pageSkipReason(page, rootUrl)])),
|
||||
[pages, rootUrl],
|
||||
)
|
||||
const selectablePages = useMemo(
|
||||
() => pages.filter((page) => !pageSkipReasons.get(page.pageId)),
|
||||
[pageSkipReasons, pages],
|
||||
)
|
||||
const selectablePageIds = useMemo(
|
||||
() => new Set(selectablePages.map((page) => page.pageId)),
|
||||
[selectablePages],
|
||||
)
|
||||
const bulkSelectablePages = selectablePages.slice(0, MAX_SELECTED_PAGES)
|
||||
const [selectedPageIds, setSelectedPageIds] = useState<Set<string>>(() => new Set())
|
||||
const [syncMode, setSyncMode] = useState<SyncMode>(policy.enabled ? policy.mode : 'manual')
|
||||
const [customIntervalHours, setCustomIntervalHours] = useState<number | ''>(
|
||||
policy.customIntervalSeconds ? policy.customIntervalSeconds / 3600 : MIN_CUSTOM_INTERVAL_HOURS,
|
||||
)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [submitError, setSubmitError] = useState(false)
|
||||
const [policyUncertain, setPolicyUncertain] = useState(false)
|
||||
const [selectionUncertain, setSelectionUncertain] = useState(false)
|
||||
const policySnapshotRef = useRef(policy)
|
||||
const submissionPendingRef = useRef(false)
|
||||
const selectionRequestRef = useRef<{ fingerprint: string; requestId: string } | undefined>(
|
||||
undefined,
|
||||
)
|
||||
const updatePolicy = useMutation(
|
||||
consoleQuery.knowledgeFs.putKnowledgeSpacesByIdSourcesBySourceIdSyncPolicy.mutationOptions(),
|
||||
)
|
||||
const selectPages = useMutation(
|
||||
consoleQuery.knowledgeFs.postKnowledgeSpacesByIdSourceWorkflowsByRunIdSelection.mutationOptions(),
|
||||
)
|
||||
const allSelected =
|
||||
bulkSelectablePages.length > 0 &&
|
||||
bulkSelectablePages.every((page) => selectedPageIds.has(page.pageId))
|
||||
const someSelected = selectedPageIds.size > 0
|
||||
const selectionAtLimit = selectedPageIds.size >= MAX_SELECTED_PAGES
|
||||
const customIntervalValid =
|
||||
typeof customIntervalHours === 'number' &&
|
||||
Number.isInteger(customIntervalHours) &&
|
||||
customIntervalHours >= MIN_CUSTOM_INTERVAL_HOURS &&
|
||||
customIntervalHours <= MAX_CUSTOM_INTERVAL_HOURS
|
||||
const canSubmit = selectedPageIds.size > 0 && (syncMode !== 'custom' || customIntervalValid)
|
||||
const formBusy = busy || submitting
|
||||
const submissionLocked = formBusy || policyUncertain || selectionUncertain
|
||||
const selectionLocked = submissionLocked || workflowUncertain
|
||||
const updateSelectionUncertain = (uncertain: boolean) => {
|
||||
setSelectionUncertain(uncertain)
|
||||
onSubmissionUncertainChange(uncertain)
|
||||
}
|
||||
|
||||
const togglePage = (pageId: string) => {
|
||||
if (
|
||||
!selectablePageIds.has(pageId) ||
|
||||
submissionPendingRef.current ||
|
||||
policyUncertain ||
|
||||
selectionUncertain
|
||||
)
|
||||
return
|
||||
setSelectedPageIds((current) => {
|
||||
const next = new Set(current)
|
||||
if (next.has(pageId)) next.delete(pageId)
|
||||
else if (next.size < MAX_SELECTED_PAGES) next.add(pageId)
|
||||
return next
|
||||
})
|
||||
setSubmitError(false)
|
||||
}
|
||||
|
||||
const toggleAll = () => {
|
||||
if (submissionPendingRef.current || policyUncertain || selectionUncertain) return
|
||||
setSelectedPageIds(
|
||||
allSelected ? new Set() : new Set(bulkSelectablePages.map((page) => page.pageId)),
|
||||
)
|
||||
setSubmitError(false)
|
||||
}
|
||||
|
||||
const submit = async (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
if (!canSubmit || busy || submissionPendingRef.current) return
|
||||
submissionPendingRef.current = true
|
||||
setSubmitting(true)
|
||||
setSubmitError(false)
|
||||
const desiredPolicy = policyConfiguration(
|
||||
syncMode,
|
||||
typeof customIntervalHours === 'number' ? customIntervalHours : 0,
|
||||
)
|
||||
const sortedPageIds = [...selectedPageIds].sort()
|
||||
const fingerprint = JSON.stringify({ pageIds: sortedPageIds, policy: desiredPolicy })
|
||||
if (
|
||||
(policyUncertain || selectionUncertain) &&
|
||||
selectionRequestRef.current?.fingerprint !== fingerprint
|
||||
) {
|
||||
submissionPendingRef.current = false
|
||||
setSubmitting(false)
|
||||
setSubmitError(true)
|
||||
return
|
||||
}
|
||||
if (selectionRequestRef.current?.fingerprint !== fingerprint) {
|
||||
selectionRequestRef.current = {
|
||||
fingerprint,
|
||||
requestId: globalThis.crypto.randomUUID(),
|
||||
}
|
||||
}
|
||||
|
||||
let resolveTransaction!: (run: SourceWorkflowRun | undefined) => void
|
||||
let transactionRun: SourceWorkflowRun | undefined
|
||||
const transaction = new Promise<SourceWorkflowRun | undefined>((resolve) => {
|
||||
resolveTransaction = resolve
|
||||
})
|
||||
onWorkflowPending(transaction)
|
||||
|
||||
try {
|
||||
let currentPolicy = policySnapshotRef.current
|
||||
if (!policyMatches(currentPolicy, desiredPolicy)) {
|
||||
const body: SyncPolicyBody = {
|
||||
...desiredPolicy,
|
||||
expectedRevision: currentPolicy.revision,
|
||||
expectedSourceVersion: currentPolicy.expectedSourceVersion,
|
||||
}
|
||||
try {
|
||||
currentPolicy = await updatePolicy.mutateAsync({
|
||||
body,
|
||||
params: { id: knowledgeSpaceId, sourceId: source.id },
|
||||
})
|
||||
} catch (error) {
|
||||
let reconciled: SyncPolicy
|
||||
try {
|
||||
reconciled =
|
||||
await consoleClient.knowledgeFs.getKnowledgeSpacesByIdSourcesBySourceIdSyncPolicy({
|
||||
params: { id: knowledgeSpaceId, sourceId: source.id },
|
||||
})
|
||||
} catch (reconciliationError) {
|
||||
setPolicyUncertain(!isDefinitiveRequestFailure(error))
|
||||
throw reconciliationError
|
||||
}
|
||||
policySnapshotRef.current = reconciled
|
||||
if (!policyMatches(reconciled, desiredPolicy)) {
|
||||
setPolicyUncertain(!isDefinitiveRequestFailure(error))
|
||||
throw error
|
||||
}
|
||||
currentPolicy = reconciled
|
||||
}
|
||||
policySnapshotRef.current = currentPolicy
|
||||
}
|
||||
setPolicyUncertain(false)
|
||||
|
||||
if (discardRequested()) return
|
||||
|
||||
try {
|
||||
const selectionRequest = selectPages.mutateAsync({
|
||||
body: { pageIds: sortedPageIds },
|
||||
headers: { 'Idempotency-Key': selectionRequestRef.current.requestId },
|
||||
params: { id: knowledgeSpaceId, runId: run.id },
|
||||
})
|
||||
const selectionRun = await selectionRequest
|
||||
transactionRun = selectionRun
|
||||
onWorkflowRun(selectionRun)
|
||||
} catch (error) {
|
||||
updateSelectionUncertain(!isDefinitiveRequestFailure(error))
|
||||
throw error
|
||||
}
|
||||
updateSelectionUncertain(true)
|
||||
if (discardRequested()) return
|
||||
const terminalRun = await waitForImportTerminal(
|
||||
knowledgeSpaceId,
|
||||
transactionRun,
|
||||
onWorkflowRun,
|
||||
)
|
||||
transactionRun = terminalRun
|
||||
if (discardRequested()) return
|
||||
if (!isSuccessfulImport(terminalRun)) {
|
||||
updateSelectionUncertain(false)
|
||||
throw new Error('Source import failed')
|
||||
}
|
||||
updateSelectionUncertain(false)
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: consoleQuery.knowledgeFs.getKnowledgeSpacesByIdSources.key(),
|
||||
})
|
||||
if (discardRequested()) return
|
||||
await onSubmitted()
|
||||
router.push(newKnowledgeDetailPath(knowledgeSpaceId))
|
||||
} catch {
|
||||
setSubmitError(true)
|
||||
} finally {
|
||||
resolveTransaction(transactionRun)
|
||||
submissionPendingRef.current = false
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="space-y-4" onSubmit={(event) => void submit(event)}>
|
||||
<section aria-labelledby="crawl-selection-summary">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<h3
|
||||
id="crawl-selection-summary"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className="min-w-0 flex-1 truncate system-xs-semibold text-text-primary"
|
||||
>
|
||||
{t(
|
||||
($) =>
|
||||
pages.length === 1
|
||||
? $['newKnowledge.pagesCrawled_one']
|
||||
: $['newKnowledge.pagesCrawled_other'],
|
||||
{
|
||||
count: pages.length,
|
||||
host: new URL(rootUrl).host,
|
||||
},
|
||||
)}
|
||||
</h3>
|
||||
<span className="system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.pagesSelected'], { count: selectedPageIds.size })}
|
||||
</span>
|
||||
{run.progressFailed > 0 && (
|
||||
<span className="system-xs-regular text-text-destructive">
|
||||
{run.progressFailed} {t(($) => $['newKnowledge.skippedFailed'])}
|
||||
</span>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="tertiary"
|
||||
size="small"
|
||||
disabled={submissionLocked}
|
||||
loading={busy}
|
||||
onClick={onRecrawl}
|
||||
>
|
||||
{t(($) => $['newKnowledge.reCrawl'])}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-3 overflow-hidden rounded-xl border border-divider-regular">
|
||||
<label className="flex cursor-pointer items-center gap-2.5 border-b border-divider-subtle bg-background-section px-3 py-2.5 system-xs-medium text-text-secondary">
|
||||
<Checkbox
|
||||
checked={allSelected}
|
||||
indeterminate={someSelected && !allSelected}
|
||||
onCheckedChange={toggleAll}
|
||||
disabled={!selectablePages.length || selectionLocked}
|
||||
/>
|
||||
{t(($) => $['newKnowledge.selectAll'])}
|
||||
</label>
|
||||
<ul className="max-h-[280px] divide-y divide-divider-subtle overflow-y-auto">
|
||||
{pages.map((page, index) => {
|
||||
const skipReason = pageSkipReasons.get(page.pageId)
|
||||
const selectable = !skipReason
|
||||
const selectionLimitReached =
|
||||
selectable && selectionAtLimit && !selectedPageIds.has(page.pageId)
|
||||
const titleId = `${pageDescriptionPrefixId}-title-${index}`
|
||||
const urlId = `${pageDescriptionPrefixId}-url-${index}`
|
||||
const reasonId = `${pageDescriptionPrefixId}-reason-${index}`
|
||||
return (
|
||||
<li key={page.pageId}>
|
||||
<label className="flex cursor-pointer items-center gap-2.5 px-3 py-2.5">
|
||||
<Checkbox
|
||||
checked={selectedPageIds.has(page.pageId)}
|
||||
disabled={!selectable || selectionLimitReached || selectionLocked}
|
||||
aria-labelledby={titleId}
|
||||
aria-describedby={`${urlId}${skipReason || selectionLimitReached ? ` ${reasonId}` : ''}`}
|
||||
onCheckedChange={() => togglePage(page.pageId)}
|
||||
/>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span
|
||||
id={titleId}
|
||||
className="block truncate system-xs-medium text-text-primary"
|
||||
>
|
||||
{page.title || page.sourceUrl}
|
||||
</span>
|
||||
<span
|
||||
id={urlId}
|
||||
className="block truncate system-2xs-regular text-text-tertiary"
|
||||
>
|
||||
{page.sourceUrl}
|
||||
</span>
|
||||
</span>
|
||||
{(!selectable || selectionLimitReached) && (
|
||||
<span id={reasonId} className="shrink-0 system-xs-medium text-text-tertiary">
|
||||
{selectionLimitReached
|
||||
? `${t(($) => $['newKnowledge.maxPages'])}: ${MAX_SELECTED_PAGES}`
|
||||
: skipReason === 'off-domain'
|
||||
? t(($) => $['newKnowledge.skippedOffDomain'])
|
||||
: t(($) => $['newKnowledge.skippedFailed'])}
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<fieldset disabled={selectionLocked}>
|
||||
<label className="block">
|
||||
<span className="system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.syncPolicy'])}
|
||||
</span>
|
||||
<select
|
||||
value={syncMode}
|
||||
onChange={(event) => {
|
||||
setSyncMode(event.target.value as SyncMode)
|
||||
setSubmitError(false)
|
||||
}}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden focus:ring-2 focus:ring-state-accent-solid sm:w-72"
|
||||
>
|
||||
<option value="provider">{t(($) => $['newKnowledge.syncPolicyProvider'])}</option>
|
||||
<option value="manual">{t(($) => $['newKnowledge.syncPolicyManual'])}</option>
|
||||
<option value="interval">{t(($) => $['newKnowledge.syncPolicyDaily'])}</option>
|
||||
<option value="custom">{t(($) => $['newKnowledge.syncPolicyCustom'])}</option>
|
||||
</select>
|
||||
</label>
|
||||
{syncMode === 'custom' && (
|
||||
<label className="mt-3 block sm:w-72">
|
||||
<span className="system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.customIntervalHours'])}
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
min={MIN_CUSTOM_INTERVAL_HOURS}
|
||||
max={MAX_CUSTOM_INTERVAL_HOURS}
|
||||
step={1}
|
||||
value={customIntervalHours}
|
||||
aria-invalid={!customIntervalValid}
|
||||
aria-describedby={!customIntervalValid ? customIntervalErrorId : undefined}
|
||||
onChange={(event) => {
|
||||
setCustomIntervalHours(
|
||||
Number.isFinite(event.target.valueAsNumber) ? event.target.valueAsNumber : '',
|
||||
)
|
||||
setSubmitError(false)
|
||||
}}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden focus:ring-2 focus:ring-state-accent-solid"
|
||||
/>
|
||||
{!customIntervalValid && (
|
||||
<span
|
||||
id={customIntervalErrorId}
|
||||
className="mt-1 block system-xs-regular text-text-destructive"
|
||||
>
|
||||
{t(($) => $['newKnowledge.customIntervalInvalid'])}
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
)}
|
||||
</fieldset>
|
||||
|
||||
{submitError && (
|
||||
<p role="alert" className="system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.addSourceFailed'])}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex justify-end gap-2 border-t border-divider-subtle pt-5">
|
||||
<Button type="button" disabled={formBusy || selectionUncertain} onClick={onCancel}>
|
||||
{t(($) => $['newKnowledge.cancelAddSource'])}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={!canSubmit || formBusy || workflowUncertain}
|
||||
loading={submitting}
|
||||
aria-describedby={!selectedPageIds.size ? 'add-source-selection-requirement' : undefined}
|
||||
>
|
||||
{t(($) => $['newKnowledge.addSource'])}
|
||||
</Button>
|
||||
{!selectedPageIds.size && (
|
||||
<span id="add-source-selection-requirement" className="sr-only">
|
||||
{t(($) => $['newKnowledge.addSourceRequiresSelection'])}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export function CrawlSelectionForm({
|
||||
busy = false,
|
||||
discardRequested,
|
||||
knowledgeSpaceId,
|
||||
onCancel,
|
||||
onRecrawl,
|
||||
onSubmissionUncertainChange,
|
||||
onSubmitted,
|
||||
onWorkflowPending,
|
||||
onWorkflowRun,
|
||||
pages,
|
||||
rootUrl,
|
||||
run,
|
||||
source,
|
||||
workflowUncertain = false,
|
||||
}: {
|
||||
busy?: boolean
|
||||
discardRequested: () => boolean
|
||||
knowledgeSpaceId: string
|
||||
onCancel: () => void
|
||||
onRecrawl: () => void
|
||||
onSubmissionUncertainChange: (uncertain: boolean) => void
|
||||
onSubmitted: () => Promise<void> | void
|
||||
onWorkflowPending: (request: Promise<SourceWorkflowRun | undefined>) => void
|
||||
onWorkflowRun: (run: SourceWorkflowRun) => void
|
||||
pages: PreviewPage[]
|
||||
rootUrl: string
|
||||
run: SourceWorkflowRun
|
||||
source: Source
|
||||
workflowUncertain?: boolean
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const policyQuery = useQuery(
|
||||
consoleQuery.knowledgeFs.getKnowledgeSpacesByIdSourcesBySourceIdSyncPolicy.queryOptions({
|
||||
context: { silent: true },
|
||||
input: { params: { id: knowledgeSpaceId, sourceId: source.id } },
|
||||
retry: false,
|
||||
}),
|
||||
)
|
||||
const policy =
|
||||
policyQuery.data ??
|
||||
(requestStatus(policyQuery.error) === 404 ? initialSyncPolicy(source) : undefined)
|
||||
|
||||
if (policyQuery.isPending) return <PolicyLoading />
|
||||
if (!policy) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div role="alert" className="rounded-xl border border-divider-regular p-4">
|
||||
<p className="system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.syncPolicyLoadFailed'])}
|
||||
</p>
|
||||
<Button className="mt-3" onClick={() => void policyQuery.refetch()}>
|
||||
{t(($) => $['newKnowledge.retrySyncPolicy'])}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 border-t border-divider-subtle pt-5">
|
||||
<Button type="button" onClick={onCancel}>
|
||||
{t(($) => $['newKnowledge.cancelAddSource'])}
|
||||
</Button>
|
||||
<Button type="button" variant="primary" disabled>
|
||||
{t(($) => $['newKnowledge.addSource'])}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ReadyCrawlSelectionForm
|
||||
key={`${run.id}:${policy.revision}`}
|
||||
busy={busy}
|
||||
discardRequested={discardRequested}
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
onCancel={onCancel}
|
||||
onRecrawl={onRecrawl}
|
||||
onSubmissionUncertainChange={onSubmissionUncertainChange}
|
||||
onSubmitted={onSubmitted}
|
||||
onWorkflowPending={onWorkflowPending}
|
||||
onWorkflowRun={onWorkflowRun}
|
||||
pages={pages}
|
||||
policy={policy}
|
||||
rootUrl={rootUrl}
|
||||
run={run}
|
||||
source={source}
|
||||
workflowUncertain={workflowUncertain}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
import type { KnowledgeSpaceCreationResponse } from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import type { CreateKnowledgeExitReason } from './components/create-knowledge-exit-dialog'
|
||||
import type { KnowledgeVisibility } from './create-knowledge-workflow'
|
||||
import type { QueuedUpload } from './create-upload-queue'
|
||||
import type { NewKnowledgeSourceDraft, NewKnowledgeStartMode } from './routes'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
@@ -30,13 +32,14 @@ import {
|
||||
SelectTrigger,
|
||||
} from '@langgenius/dify-ui/select'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useEffect, useId, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { useRouter, useSearchParams } from '@/next/navigation'
|
||||
import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import { DatasetACLPermission, hasPermission } from '@/utils/permission'
|
||||
import { KnowledgeIllustration, StartMode } from './components/create-knowledge-dialog-parts'
|
||||
import { CreateKnowledgeExitDialog } from './components/create-knowledge-exit-dialog'
|
||||
@@ -47,12 +50,45 @@ import {
|
||||
KnowledgeCreationError,
|
||||
NAME_MAX_LENGTH,
|
||||
} from './create-knowledge-workflow'
|
||||
import { newKnowledgeDetailPath, newKnowledgeListPath } from './routes'
|
||||
import { CreateSourceSetup } from './create-source-setup'
|
||||
import { CreateUploadQueue } from './create-upload-queue'
|
||||
import {
|
||||
createNewKnowledgeSourceDraft,
|
||||
isValidWebsiteSourceDraft,
|
||||
newKnowledgeAddSourcePath,
|
||||
newKnowledgeDetailPath,
|
||||
newKnowledgeDocumentsPath,
|
||||
newKnowledgeListPath,
|
||||
newKnowledgeSourceDraftStorageKey,
|
||||
} from './routes'
|
||||
|
||||
function normalizeStartMode(value: string | null): NewKnowledgeStartMode {
|
||||
if (value === 'source' || value === 'upload') return value
|
||||
return 'empty'
|
||||
}
|
||||
|
||||
async function uploadCreatedDocuments(knowledgeSpaceId: string, files: File[]) {
|
||||
if (files.length === 1) {
|
||||
await consoleClient.knowledgeFs.postKnowledgeSpacesByIdDocuments({
|
||||
body: { file: files[0]! },
|
||||
params: { id: knowledgeSpaceId },
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const result = await consoleClient.knowledgeFs.postKnowledgeSpacesByIdDocumentsBulk({
|
||||
body: { files },
|
||||
params: { id: knowledgeSpaceId },
|
||||
})
|
||||
if (!result.accepted) throw new Error('No files were accepted')
|
||||
return result
|
||||
}
|
||||
|
||||
export function CreateKnowledgePage() {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const queryClient = useQueryClient()
|
||||
const dialogTitleId = useId()
|
||||
const permissionDescriptionId = useId()
|
||||
@@ -65,16 +101,41 @@ export function CreateKnowledgePage() {
|
||||
const [name, setName] = useState('')
|
||||
const [description, setDescription] = useState('')
|
||||
const [visibility, setVisibility] = useState<KnowledgeVisibility>(defaultVisibility)
|
||||
const initialStartMode = normalizeStartMode(searchParams.get('start'))
|
||||
const [startMode, setStartMode] = useState<NewKnowledgeStartMode>(initialStartMode)
|
||||
const [sourceDraft, setSourceDraft] = useState<NewKnowledgeSourceDraft>(() =>
|
||||
createNewKnowledgeSourceDraft('websiteCrawl'),
|
||||
)
|
||||
const sourceDraftsRef = useRef<
|
||||
Partial<Record<NewKnowledgeSourceDraft['sourceType'], NewKnowledgeSourceDraft>>
|
||||
>({})
|
||||
const [uploads, setUploads] = useState<QueuedUpload[]>([])
|
||||
const [createdKnowledge, setCreatedKnowledge] = useState<KnowledgeSpaceCreationResponse>()
|
||||
const [submissionLocked, setSubmissionLocked] = useState(false)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [uploadError, setUploadError] = useState(false)
|
||||
const [exitReason, setExitReason] = useState<CreateKnowledgeExitReason | null>(null)
|
||||
const idempotencyKeyRef = useRef<string | undefined>(undefined)
|
||||
const historyGuardArmedRef = useRef(false)
|
||||
const browserBackExitRef = useRef(false)
|
||||
const pendingNavigationRef = useRef<string | undefined>(undefined)
|
||||
const createMutation = useMutation({ mutationFn: createKnowledge })
|
||||
const submissionPending = createMutation.isPending || uploading
|
||||
const uploadSubmissionBlocked =
|
||||
startMode === 'upload' && (!uploads.length || uploads.some((upload) => upload.issue))
|
||||
const sourceSubmissionBlocked =
|
||||
startMode === 'source' &&
|
||||
(sourceDraft.sourceType === 'websiteCrawl'
|
||||
? !isValidWebsiteSourceDraft(sourceDraft, { allowEmpty: true })
|
||||
: !sourceDraft.sourceName.trim())
|
||||
const hasUnsavedChanges = Boolean(
|
||||
name || description || visibility !== defaultVisibility || createdKnowledge,
|
||||
name ||
|
||||
description ||
|
||||
visibility !== defaultVisibility ||
|
||||
startMode !== initialStartMode ||
|
||||
sourceDraft.sourceName ||
|
||||
uploads.length ||
|
||||
createdKnowledge,
|
||||
)
|
||||
|
||||
const armHistoryGuard = useCallback(() => {
|
||||
@@ -145,10 +206,11 @@ export function CreateKnowledgePage() {
|
||||
|
||||
const resetUnsubmittedError = () => {
|
||||
if (!submissionLocked) createMutation.reset()
|
||||
setUploadError(false)
|
||||
}
|
||||
|
||||
const requestClose = () => {
|
||||
if (createMutation.isPending) return
|
||||
if (submissionPending) return
|
||||
if (createdKnowledge) {
|
||||
setExitReason('partial')
|
||||
return
|
||||
@@ -181,7 +243,7 @@ export function CreateKnowledgePage() {
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (createMutation.isPending) return
|
||||
if (submissionPending || uploadSubmissionBlocked || sourceSubmissionBlocked) return
|
||||
|
||||
const normalizedName = name.trim()
|
||||
const normalizedDescription = description.trim()
|
||||
@@ -203,7 +265,54 @@ export function CreateKnowledgePage() {
|
||||
},
|
||||
visibility,
|
||||
})
|
||||
replaceAfterHistoryGuard(newKnowledgeDetailPath(created.id))
|
||||
if (startMode === 'upload') {
|
||||
setUploading(true)
|
||||
setUploadError(false)
|
||||
try {
|
||||
const result = await uploadCreatedDocuments(
|
||||
created.id,
|
||||
uploads.map((upload) => upload.file),
|
||||
)
|
||||
if (result?.excluded)
|
||||
toast.warning(
|
||||
t(($) => $['newKnowledge.documentUploadPartial'], {
|
||||
accepted: result.accepted,
|
||||
details: result.items
|
||||
.filter((item) => 'reason' in item)
|
||||
.map((item) => item.filename)
|
||||
.join(', '),
|
||||
excluded: result.excluded,
|
||||
}),
|
||||
)
|
||||
} catch {
|
||||
setUploadError(true)
|
||||
return
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
let sourceDraftKey: string | undefined
|
||||
if (startMode === 'source') {
|
||||
try {
|
||||
sourceDraftKey = globalThis.crypto.randomUUID()
|
||||
globalThis.sessionStorage.setItem(
|
||||
newKnowledgeSourceDraftStorageKey(sourceDraftKey),
|
||||
JSON.stringify(sourceDraft),
|
||||
)
|
||||
} catch {
|
||||
toast.error(t(($) => $['newKnowledge.addSourceFailed']))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
replaceAfterHistoryGuard(
|
||||
startMode === 'source'
|
||||
? newKnowledgeAddSourcePath(created.id, sourceDraft.sourceType, sourceDraftKey)
|
||||
: startMode === 'upload'
|
||||
? newKnowledgeDocumentsPath(created.id)
|
||||
: newKnowledgeDetailPath(created.id),
|
||||
)
|
||||
} catch (error) {
|
||||
if (error instanceof KnowledgeCreationError && error.createdKnowledge)
|
||||
setCreatedKnowledge(error.createdKnowledge)
|
||||
@@ -238,7 +347,7 @@ export function CreateKnowledgePage() {
|
||||
aria-label={tCommon(($) => $['operation.close'])}
|
||||
className="absolute top-3 right-3 z-10 flex size-9 items-center justify-center rounded-xl bg-background-section-burn text-text-tertiary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed disabled:text-text-disabled"
|
||||
onClick={requestClose}
|
||||
disabled={createMutation.isPending}
|
||||
disabled={submissionPending}
|
||||
>
|
||||
<span aria-hidden className="i-ri-close-line size-5" />
|
||||
</button>
|
||||
@@ -361,32 +470,65 @@ export function CreateKnowledgePage() {
|
||||
<p className="pb-0.5 body-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.startWithHelp'])}
|
||||
</p>
|
||||
<RadioGroup
|
||||
value="empty"
|
||||
<RadioGroup<NewKnowledgeStartMode>
|
||||
value={startMode}
|
||||
aria-label={t(($) => $['newKnowledge.startWith'])}
|
||||
className="mt-2 flex-col items-stretch gap-2"
|
||||
disabled={createMutation.isPending}
|
||||
disabled={submissionLocked}
|
||||
onValueChange={(value) => {
|
||||
setStartMode(value)
|
||||
resetUnsubmittedError()
|
||||
}}
|
||||
>
|
||||
<StartMode
|
||||
value="empty"
|
||||
icon="i-ri-folder-6-line"
|
||||
selected={startMode === 'empty'}
|
||||
title={t(($) => $['newKnowledge.startEmpty'])}
|
||||
description={t(($) => $['newKnowledge.startEmptyDescription'])}
|
||||
/>
|
||||
<StartMode
|
||||
disabled
|
||||
value="source"
|
||||
icon="i-custom-vender-solid-development-api-connection-mod"
|
||||
selected={startMode === 'source'}
|
||||
title={t(($) => $['newKnowledge.connectSource'])}
|
||||
description={t(($) => $['newKnowledge.connectSourceDescription'])}
|
||||
/>
|
||||
>
|
||||
<CreateSourceSetup
|
||||
disabled={submissionLocked}
|
||||
draft={sourceDraft}
|
||||
onDraftChange={(value) => {
|
||||
sourceDraftsRef.current[value.sourceType] = value
|
||||
setSourceDraft(value)
|
||||
resetUnsubmittedError()
|
||||
}}
|
||||
onSourceTypeChange={(value) => {
|
||||
sourceDraftsRef.current[sourceDraft.sourceType] = sourceDraft
|
||||
const nextDraft =
|
||||
sourceDraftsRef.current[value] ?? createNewKnowledgeSourceDraft(value)
|
||||
sourceDraftsRef.current[value] = nextDraft
|
||||
setSourceDraft(nextDraft)
|
||||
resetUnsubmittedError()
|
||||
}}
|
||||
/>
|
||||
</StartMode>
|
||||
<StartMode
|
||||
disabled
|
||||
value="upload"
|
||||
icon="i-ri-file-text-line"
|
||||
selected={startMode === 'upload'}
|
||||
title={t(($) => $['newKnowledge.uploadFiles'])}
|
||||
description={t(($) => $['newKnowledge.uploadFilesDescription'])}
|
||||
/>
|
||||
>
|
||||
<CreateUploadQueue
|
||||
disabled={submissionPending}
|
||||
uploads={uploads}
|
||||
uploading={uploading}
|
||||
onChange={(value) => {
|
||||
setUploads(value)
|
||||
resetUnsubmittedError()
|
||||
}}
|
||||
/>
|
||||
</StartMode>
|
||||
</RadioGroup>
|
||||
</fieldset>
|
||||
|
||||
@@ -403,14 +545,27 @@ export function CreateKnowledgePage() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{uploadError && (
|
||||
<div
|
||||
className="mt-5 rounded-lg bg-components-badge-status-light-error-bg px-3 py-2 system-sm-regular text-text-destructive"
|
||||
role="alert"
|
||||
>
|
||||
{t(($) => $['newKnowledge.documentUploadFailed'])}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 px-6 pt-5 pb-10 sm:px-10">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" disabled={createMutation.isPending} onClick={requestClose}>
|
||||
<Button type="button" disabled={submissionPending} onClick={requestClose}>
|
||||
{tCommon(($) => $['operation.cancel'])}
|
||||
</Button>
|
||||
<Button type="submit" variant="primary" loading={createMutation.isPending}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
loading={submissionPending}
|
||||
disabled={uploadSubmissionBlocked || sourceSubmissionBlocked}
|
||||
>
|
||||
{t(($) => $['newKnowledge.createTitle'])}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
NewKnowledgeOnlineDocumentsProvider,
|
||||
NewKnowledgeOnlineDriveProvider,
|
||||
NewKnowledgeSourceDraft,
|
||||
NewKnowledgeWebsiteProvider,
|
||||
} from './routes'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
isValidWebsiteSourceDraft,
|
||||
NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH,
|
||||
NEW_KNOWLEDGE_SOURCE_URL_MAX_LENGTH,
|
||||
} from './routes'
|
||||
|
||||
const sourceTypes = [
|
||||
{ icon: 'i-ri-global-line', value: 'websiteCrawl' },
|
||||
{ icon: 'i-ri-file-text-line', value: 'onlineDocuments' },
|
||||
{ icon: 'i-ri-hard-drive-3-line', value: 'onlineDrive' },
|
||||
] as const
|
||||
|
||||
const providers = {
|
||||
onlineDocuments: [
|
||||
{ icon: 'i-custom-public-common-notion', label: 'Notion' },
|
||||
{ icon: 'i-ri-file-text-line', label: 'Google Docs' },
|
||||
{ icon: 'i-ri-links-line', label: 'Confluence' },
|
||||
],
|
||||
onlineDrive: [
|
||||
{ icon: 'i-ri-google-drive-line', label: 'Google Drive' },
|
||||
{ icon: 'i-ri-cloud-line', label: 'OneDrive' },
|
||||
{ icon: 'i-ri-box-3-line', label: 'Amazon S3' },
|
||||
],
|
||||
websiteCrawl: [
|
||||
{ icon: 'i-ri-fire-fill text-orange-500', label: 'Firecrawl', available: true },
|
||||
{ icon: 'i-custom-public-llm-jina', label: 'Jina Reader' },
|
||||
{ icon: 'i-ri-water-flash-line', label: 'WaterCrawl' },
|
||||
],
|
||||
} as const
|
||||
|
||||
function ConnectedSourceConfiguration({
|
||||
disabled,
|
||||
draft,
|
||||
onDraftChange,
|
||||
}: {
|
||||
disabled: boolean
|
||||
draft: NewKnowledgeSourceDraft
|
||||
onDraftChange: (draft: NewKnowledgeSourceDraft) => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<label className="block system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.sourceName'])}
|
||||
<span aria-hidden className="ml-0.5 text-text-destructive">
|
||||
*
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
disabled={disabled}
|
||||
maxLength={NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH}
|
||||
value={draft.sourceName}
|
||||
placeholder={t(($) => $['newKnowledge.sourceNamePlaceholder'])}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden placeholder:text-text-quaternary focus:ring-2 focus:ring-state-accent-solid disabled:text-text-disabled"
|
||||
onChange={(event) => onDraftChange({ ...draft, sourceName: event.target.value })}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') event.preventDefault()
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<label className="block system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.syncPolicy'])}
|
||||
<select
|
||||
disabled={disabled}
|
||||
value={draft.syncPolicy}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden focus:ring-2 focus:ring-state-accent-solid disabled:text-text-disabled"
|
||||
onChange={(event) =>
|
||||
onDraftChange({
|
||||
...draft,
|
||||
syncPolicy: event.target.value as NewKnowledgeSourceDraft['syncPolicy'],
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="provider">{t(($) => $['newKnowledge.syncPolicyProvider'])}</option>
|
||||
<option value="daily">{t(($) => $['newKnowledge.syncPolicyDaily'])}</option>
|
||||
<option value="manual">{t(($) => $['newKnowledge.syncPolicyManual'])}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CreateSourceSetup({
|
||||
disabled,
|
||||
draft,
|
||||
onDraftChange,
|
||||
onSourceTypeChange,
|
||||
}: {
|
||||
disabled: boolean
|
||||
draft: NewKnowledgeSourceDraft
|
||||
onDraftChange: (draft: NewKnowledgeSourceDraft) => void
|
||||
onSourceTypeChange: (sourceType: NewKnowledgeSourceDraft['sourceType']) => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCreation } = useTranslation('datasetCreation')
|
||||
const [optionsExpanded, setOptionsExpanded] = useState(false)
|
||||
const [backendBoundaryVisible, setBackendBoundaryVisible] = useState(false)
|
||||
const sourceType = draft.sourceType
|
||||
const availableProviders = providers[sourceType]
|
||||
const activeProvider = availableProviders.some((provider) => provider.label === draft.provider)
|
||||
? draft.provider
|
||||
: availableProviders[0].label
|
||||
const previewReady = draft.sourceType === 'websiteCrawl' && isValidWebsiteSourceDraft(draft)
|
||||
const showBackendBoundary = () => setBackendBoundaryVisible(true)
|
||||
const updateDraft = (nextDraft: NewKnowledgeSourceDraft) => {
|
||||
onDraftChange(nextDraft)
|
||||
setBackendBoundaryVisible(false)
|
||||
}
|
||||
const selectProvider = (provider: string) => {
|
||||
if (draft.sourceType === 'onlineDocuments')
|
||||
updateDraft({ ...draft, provider: provider as NewKnowledgeOnlineDocumentsProvider })
|
||||
else if (draft.sourceType === 'onlineDrive')
|
||||
updateDraft({ ...draft, provider: provider as NewKnowledgeOnlineDriveProvider })
|
||||
else updateDraft({ ...draft, provider: provider as NewKnowledgeWebsiteProvider })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-4 mb-4 space-y-4 border-t border-divider-subtle pt-4">
|
||||
<fieldset disabled={disabled}>
|
||||
<legend className="mb-1.5 system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.sourceTypeLabel'])}
|
||||
</legend>
|
||||
<div className="grid grid-cols-1 gap-0.5 rounded-lg bg-background-default p-0.5 sm:grid-cols-3">
|
||||
{sourceTypes.map((option) => (
|
||||
<label
|
||||
key={option.value}
|
||||
className={cn(
|
||||
'relative flex min-h-8 items-center justify-center gap-1.5 rounded-md px-2 system-xs-medium outline-hidden has-focus-visible:ring-2 has-focus-visible:ring-state-accent-solid',
|
||||
sourceType === option.value
|
||||
? 'bg-components-option-card-option-selected-bg text-text-primary shadow-xs'
|
||||
: 'cursor-pointer text-text-tertiary hover:text-text-secondary',
|
||||
disabled && 'cursor-not-allowed opacity-60',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="create-source-type"
|
||||
value={option.value}
|
||||
checked={sourceType === option.value}
|
||||
onChange={() => {
|
||||
setBackendBoundaryVisible(false)
|
||||
onSourceTypeChange(option.value)
|
||||
}}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span aria-hidden className={`${option.icon} size-4`} />
|
||||
{t(($) => $[`newKnowledge.${option.value}`])}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset disabled={disabled}>
|
||||
<legend className="sr-only">{tCreation(($) => $['stepOne.website.chooseProvider'])}</legend>
|
||||
<div className="mb-1.5 flex items-center justify-between gap-3">
|
||||
<span className="system-xs-medium text-text-secondary">
|
||||
{tCreation(($) => $['stepOne.website.chooseProvider'])}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className="rounded-sm system-xs-medium text-text-accent outline-hidden hover:text-text-accent-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed disabled:text-text-disabled"
|
||||
onClick={showBackendBoundary}
|
||||
>
|
||||
{t(($) => $['newKnowledge.moreProviders'])}
|
||||
</button>
|
||||
</div>
|
||||
<div className={cn('grid grid-cols-2 gap-2', 'sm:grid-cols-3')}>
|
||||
{providers[sourceType].map((provider) => {
|
||||
return (
|
||||
<label
|
||||
key={provider.label}
|
||||
className={cn(
|
||||
'flex min-h-10 items-center gap-2 rounded-lg border bg-background-default px-3 system-xs-medium outline-hidden has-focus-visible:ring-2 has-focus-visible:ring-state-accent-solid',
|
||||
activeProvider === provider.label
|
||||
? 'border-components-option-card-option-selected-border text-text-primary'
|
||||
: 'cursor-pointer border-divider-subtle text-text-secondary hover:bg-state-base-hover',
|
||||
disabled && 'cursor-not-allowed opacity-60',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="create-source-provider"
|
||||
value={provider.label}
|
||||
checked={activeProvider === provider.label}
|
||||
disabled={disabled}
|
||||
onChange={() => selectProvider(provider.label)}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span aria-hidden className={`${provider.icon} size-4 shrink-0`} />
|
||||
<span className="truncate">{provider.label}</span>
|
||||
</label>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{draft.sourceType === 'websiteCrawl' && (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<label className="block system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.rootUrl'])}
|
||||
<input
|
||||
type="url"
|
||||
inputMode="url"
|
||||
autoComplete="off"
|
||||
disabled={disabled}
|
||||
maxLength={NEW_KNOWLEDGE_SOURCE_URL_MAX_LENGTH}
|
||||
value={draft.rootUrl}
|
||||
placeholder={t(($) => $['newKnowledge.rootUrlPlaceholder'])}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden placeholder:text-text-quaternary focus:ring-2 focus:ring-state-accent-solid disabled:text-text-disabled"
|
||||
onChange={(event) => {
|
||||
updateDraft({ ...draft, rootUrl: event.target.value })
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') event.preventDefault()
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<label className="block system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.sourceName'])}
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
disabled={disabled}
|
||||
maxLength={NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH}
|
||||
value={draft.sourceName}
|
||||
placeholder={t(($) => $['newKnowledge.sourceNamePlaceholder'])}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden placeholder:text-text-quaternary focus:ring-2 focus:ring-state-accent-solid disabled:text-text-disabled"
|
||||
onChange={(event) => {
|
||||
updateDraft({ ...draft, sourceName: event.target.value })
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') event.preventDefault()
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-lg bg-background-section">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t(($) => $['newKnowledge.crawlOptions'])}
|
||||
aria-expanded={optionsExpanded}
|
||||
disabled={disabled}
|
||||
className="flex min-h-9 w-full items-center gap-2 px-3 text-left system-xs-medium text-text-secondary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed"
|
||||
onClick={() => setOptionsExpanded((value) => !value)}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'size-4 transition-transform motion-reduce:transition-none',
|
||||
optionsExpanded ? 'i-ri-arrow-down-s-line' : 'i-ri-arrow-right-s-line',
|
||||
)}
|
||||
/>
|
||||
{t(($) => $['newKnowledge.crawlOptions'])}
|
||||
{!optionsExpanded && (
|
||||
<span className="ml-auto system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.usingDefaults'])}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{optionsExpanded && (
|
||||
<fieldset
|
||||
disabled={disabled}
|
||||
className="grid grid-cols-1 gap-3 px-3 pb-3 sm:grid-cols-2"
|
||||
>
|
||||
<label className="flex items-center gap-2 system-xs-regular text-text-secondary">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={draft.includeSubpages}
|
||||
onChange={(event) =>
|
||||
updateDraft({ ...draft, includeSubpages: event.target.checked })
|
||||
}
|
||||
/>
|
||||
{t(($) => $['newKnowledge.includeSubpages'])}
|
||||
</label>
|
||||
<label className="system-xs-medium text-text-secondary">
|
||||
{t(($) => $['newKnowledge.maxPages'])}
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={200}
|
||||
value={draft.maxPages}
|
||||
className="mt-1.5 h-9 w-full rounded-lg border-0 bg-components-input-bg-normal px-3 system-sm-regular text-text-primary outline-hidden focus:ring-2 focus:ring-state-accent-solid"
|
||||
onChange={(event) =>
|
||||
updateDraft({ ...draft, maxPages: event.target.valueAsNumber || 0 })
|
||||
}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') event.preventDefault()
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
disabled={disabled || !previewReady}
|
||||
onClick={showBackendBoundary}
|
||||
>
|
||||
{t(($) => $['newKnowledge.crawlAndPreview'])}
|
||||
</Button>
|
||||
<section
|
||||
aria-label={t(($) => $['newKnowledge.crawlPreview'])}
|
||||
className="flex min-h-40 flex-col items-center justify-center rounded-lg border border-dashed border-divider-regular px-6 text-center"
|
||||
>
|
||||
<span className="flex size-10 items-center justify-center rounded-lg bg-background-section">
|
||||
<span aria-hidden className="i-ri-global-line size-5 text-text-tertiary" />
|
||||
</span>
|
||||
<p className="mt-2 system-xs-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.pagesAppearTitle'])}
|
||||
</p>
|
||||
<p className="mt-2 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.pagesAppearDescription'])}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{draft.sourceType !== 'websiteCrawl' && (
|
||||
<div className="space-y-3">
|
||||
{draft.sourceType === 'onlineDocuments' && activeProvider === 'Notion' && (
|
||||
<section className="rounded-lg border border-divider-subtle bg-background-default p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'mt-0.5 size-5 shrink-0',
|
||||
availableProviders.find((provider) => provider.label === activeProvider)?.icon,
|
||||
)}
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="system-sm-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.notionNotConnected'])}
|
||||
</p>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.notionNotConnectedDescription'])}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className="h-8 shrink-0 rounded-lg bg-components-button-primary-bg px-3 system-xs-medium text-components-button-primary-text outline-hidden hover:bg-components-button-primary-bg-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed disabled:opacity-50"
|
||||
onClick={showBackendBoundary}
|
||||
>
|
||||
{t(($) => $['newKnowledge.connectNotion'])}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
<ConnectedSourceConfiguration
|
||||
disabled={disabled}
|
||||
draft={draft}
|
||||
onDraftChange={updateDraft}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{backendBoundaryVisible && (
|
||||
<p
|
||||
role="alert"
|
||||
className="rounded-md bg-components-badge-status-light-warning-bg px-3 py-2 system-xs-regular text-text-warning"
|
||||
>
|
||||
{t(($) => $['newKnowledge.sourceSetupBackendDependency'])}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
'use client'
|
||||
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useId, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const MAX_FILE_SIZE = 15 * 1024 * 1024
|
||||
const ALLOWED_EXTENSIONS = new Set([
|
||||
'csv',
|
||||
'doc',
|
||||
'docx',
|
||||
'htm',
|
||||
'html',
|
||||
'jsonl',
|
||||
'markdown',
|
||||
'md',
|
||||
'pdf',
|
||||
'txt',
|
||||
'xls',
|
||||
'xlsx',
|
||||
])
|
||||
const PREVIEWABLE_EXTENSIONS = new Set(['doc', 'docx', 'html', 'markdown', 'md', 'pdf', 'txt'])
|
||||
|
||||
const CREATE_UPLOAD_ACCEPT = [...ALLOWED_EXTENSIONS].map((extension) => `.${extension}`).join(',')
|
||||
|
||||
export type QueuedUpload = {
|
||||
file: File
|
||||
id: string
|
||||
issue?: 'fileSize' | 'fileType'
|
||||
}
|
||||
|
||||
function fileExtension(name: string) {
|
||||
const extension = name.split('.').pop()?.toLocaleLowerCase()
|
||||
return extension && extension !== name.toLocaleLowerCase() ? extension : ''
|
||||
}
|
||||
|
||||
function createQueuedUpload(file: File): QueuedUpload {
|
||||
const extension = fileExtension(file.name)
|
||||
return {
|
||||
file,
|
||||
id: `${file.name}:${file.size}:${file.lastModified}:${globalThis.crypto.randomUUID()}`,
|
||||
issue:
|
||||
file.size > MAX_FILE_SIZE
|
||||
? 'fileSize'
|
||||
: !ALLOWED_EXTENSIONS.has(extension)
|
||||
? 'fileType'
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number) {
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB`
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
||||
}
|
||||
|
||||
function fileBadge(file: File) {
|
||||
return fileExtension(file.name).toLocaleUpperCase() || 'FILE'
|
||||
}
|
||||
|
||||
function mergeFiles(current: QueuedUpload[], files: File[]) {
|
||||
const fingerprints = new Set(
|
||||
current.map(({ file }) => `${file.name}:${file.size}:${file.lastModified}`),
|
||||
)
|
||||
return [
|
||||
...current,
|
||||
...files.flatMap((file) => {
|
||||
const fingerprint = `${file.name}:${file.size}:${file.lastModified}`
|
||||
if (fingerprints.has(fingerprint)) return []
|
||||
fingerprints.add(fingerprint)
|
||||
return [createQueuedUpload(file)]
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
export function CreateUploadQueue({
|
||||
disabled,
|
||||
uploading,
|
||||
uploads,
|
||||
onChange,
|
||||
}: {
|
||||
disabled: boolean
|
||||
uploading: boolean
|
||||
uploads: QueuedUpload[]
|
||||
onChange: (uploads: QueuedUpload[]) => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const inputId = useId()
|
||||
const [dragging, setDragging] = useState(false)
|
||||
const previewUnavailable = uploads.some(
|
||||
(upload) =>
|
||||
!upload.issue && !uploading && PREVIEWABLE_EXTENSIONS.has(fileExtension(upload.file.name)),
|
||||
)
|
||||
|
||||
const addFiles = (files: File[]) => {
|
||||
if (!disabled && files.length) onChange(mergeFiles(uploads, files))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-4 mb-4 space-y-2 border-t border-divider-subtle pt-4">
|
||||
<input
|
||||
id={inputId}
|
||||
className="peer sr-only"
|
||||
multiple
|
||||
type="file"
|
||||
accept={CREATE_UPLOAD_ACCEPT}
|
||||
aria-label={t(($) => $['newKnowledge.uploadFiles'])}
|
||||
disabled={disabled}
|
||||
onChange={(event) => {
|
||||
addFiles([...(event.currentTarget.files ?? [])])
|
||||
event.currentTarget.value = ''
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
htmlFor={inputId}
|
||||
className={cn(
|
||||
'flex min-h-20 flex-col items-center justify-center rounded-lg border border-dashed border-divider-regular px-4 py-3 text-center outline-hidden transition-colors motion-reduce:transition-none',
|
||||
'peer-focus-visible:ring-2 peer-focus-visible:ring-state-accent-solid',
|
||||
disabled
|
||||
? 'cursor-not-allowed opacity-60'
|
||||
: 'cursor-pointer hover:border-components-option-card-option-selected-border hover:bg-state-base-hover has-focus-visible:ring-2 has-focus-visible:ring-state-accent-solid',
|
||||
dragging && 'border-components-option-card-option-selected-border bg-state-base-hover',
|
||||
)}
|
||||
onDragEnter={(event) => {
|
||||
event.preventDefault()
|
||||
if (!disabled) setDragging(true)
|
||||
}}
|
||||
onDragLeave={(event) => {
|
||||
event.preventDefault()
|
||||
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setDragging(false)
|
||||
}}
|
||||
onDragOver={(event) => event.preventDefault()}
|
||||
onDrop={(event) => {
|
||||
event.preventDefault()
|
||||
setDragging(false)
|
||||
addFiles([...event.dataTransfer.files])
|
||||
}}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'size-5 text-text-tertiary',
|
||||
uploading
|
||||
? 'i-ri-loader-2-line animate-spin motion-reduce:animate-none'
|
||||
: 'i-ri-upload-cloud-2-line',
|
||||
)}
|
||||
/>
|
||||
<span className="mt-1.5 system-sm-medium text-text-primary">
|
||||
{uploading
|
||||
? t(($) => $['newKnowledge.uploadingFiles'])
|
||||
: t(($) => $['newKnowledge.uploadFiles'])}
|
||||
</span>
|
||||
<span className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.documentsDropHint'])}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{!!uploads.length && (
|
||||
<ul className="space-y-1.5" aria-label={t(($) => $['newKnowledge.uploadFiles'])}>
|
||||
{uploads.map((upload) => (
|
||||
<li
|
||||
key={upload.id}
|
||||
className={cn(
|
||||
'flex min-w-0 items-center gap-2 rounded-lg border border-divider-subtle bg-background-default px-3 py-2',
|
||||
upload.issue && 'border-components-option-card-option-border',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'system-2xs-semibold flex h-7 min-w-9 shrink-0 items-center justify-center rounded bg-background-section px-1',
|
||||
upload.issue ? 'text-text-destructive' : 'text-text-tertiary',
|
||||
)}
|
||||
>
|
||||
{uploading && !upload.issue ? (
|
||||
<span className="i-ri-loader-2-line size-4 animate-spin motion-reduce:animate-none" />
|
||||
) : (
|
||||
fileBadge(upload.file)
|
||||
)}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate system-xs-medium text-text-primary">
|
||||
{upload.file.name}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
'block system-2xs-regular text-text-tertiary',
|
||||
upload.issue && 'text-text-destructive',
|
||||
)}
|
||||
>
|
||||
{uploading && !upload.issue
|
||||
? t(($) => $['newKnowledge.uploadingFiles'])
|
||||
: upload.issue === 'fileSize'
|
||||
? t(($) => $['newKnowledge.documentUploadExclusion.fileSize'])
|
||||
: upload.issue === 'fileType'
|
||||
? t(($) => $['newKnowledge.documentUploadExclusion.fileType'])
|
||||
: `${formatFileSize(upload.file.size)} · ${t(($) => $['newKnowledge.uploadCharactersUnavailable'])}`}
|
||||
</span>
|
||||
</span>
|
||||
{!uploading &&
|
||||
!upload.issue &&
|
||||
PREVIEWABLE_EXTENSIONS.has(fileExtension(upload.file.name)) && (
|
||||
<button
|
||||
type="button"
|
||||
disabled
|
||||
aria-describedby={`${inputId}-preview-unavailable`}
|
||||
className="h-7 shrink-0 rounded-md px-2 system-xs-medium text-text-disabled"
|
||||
>
|
||||
{t(($) => $['newKnowledge.preview'])}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
aria-label={`${tCommon(($) => $['operation.remove'])} ${upload.file.name}`}
|
||||
className="flex size-7 shrink-0 items-center justify-center rounded-md text-text-tertiary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed disabled:text-text-disabled"
|
||||
onClick={() => onChange(uploads.filter((candidate) => candidate.id !== upload.id))}
|
||||
>
|
||||
<span aria-hidden className="i-ri-close-line size-4" />
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{previewUnavailable && (
|
||||
<p id={`${inputId}-preview-unavailable`} className="system-2xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.previewUnavailable'])}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
import type {
|
||||
DocumentRevisionChunk,
|
||||
LogicalDocument,
|
||||
LogicalDocumentRevision,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { chunkCharacterCount, chunkMetadataEntries, chunkTreeLabel } from './document-detail-model'
|
||||
|
||||
function formatBytes(bytes: number, locale: string) {
|
||||
const numberFormat = new Intl.NumberFormat(locale, { maximumFractionDigits: 1 })
|
||||
if (bytes < 1024) return `${numberFormat.format(bytes)} B`
|
||||
const units = ['KB', 'MB', 'GB']
|
||||
let value = bytes / 1024
|
||||
let unit = units[0]
|
||||
for (let index = 1; index < units.length && value >= 1024; index++) {
|
||||
value /= 1024
|
||||
unit = units[index]
|
||||
}
|
||||
return `${new Intl.NumberFormat(locale, {
|
||||
maximumFractionDigits: value >= 10 ? 0 : 1,
|
||||
}).format(value)} ${unit}`
|
||||
}
|
||||
|
||||
function formatDate(value: string | undefined, locale: string) {
|
||||
if (!value) return '—'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
return new Intl.DateTimeFormat(locale, {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
export function DocumentChunkDetail({
|
||||
document,
|
||||
chunks,
|
||||
chunksComplete,
|
||||
locale,
|
||||
revision,
|
||||
selectedChunkId,
|
||||
}: {
|
||||
document: LogicalDocument
|
||||
chunks: DocumentRevisionChunk[]
|
||||
chunksComplete: boolean
|
||||
locale: string
|
||||
revision?: Exclude<LogicalDocumentRevision, null>
|
||||
selectedChunkId?: string
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const selectedChunk = useMemo(
|
||||
() => chunks.find((chunk) => chunk.id === selectedChunkId) ?? chunks[0],
|
||||
[chunks, selectedChunkId],
|
||||
)
|
||||
const characterCount = useMemo(
|
||||
() => chunks.reduce((total, chunk) => total + chunkCharacterCount(chunk.text), 0),
|
||||
[chunks],
|
||||
)
|
||||
const averageChunkLength = chunks.length ? Math.round(characterCount / chunks.length) : 0
|
||||
const sizeBytes = revision?.sizeBytes ?? document.active?.sizeBytes
|
||||
const sourceName =
|
||||
typeof document.userMetadata.sourceName === 'string'
|
||||
? document.userMetadata.sourceName
|
||||
: undefined
|
||||
const retrievalCount =
|
||||
typeof document.userMetadata.retrievalCount === 'number'
|
||||
? document.userMetadata.retrievalCount
|
||||
: undefined
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedChunkId) return
|
||||
globalThis.document
|
||||
?.getElementById(`document-chunk-${selectedChunkId}`)
|
||||
?.scrollIntoView({ block: 'nearest' })
|
||||
}, [selectedChunkId])
|
||||
|
||||
return (
|
||||
<>
|
||||
<article
|
||||
aria-busy={!chunksComplete}
|
||||
className="min-h-72 min-w-0 overflow-hidden bg-background-default"
|
||||
>
|
||||
{!chunksComplete && (
|
||||
<p
|
||||
className="border-b border-divider-subtle bg-state-accent-hover px-5 py-2 system-xs-regular text-text-accent"
|
||||
role="status"
|
||||
>
|
||||
{t(($) => $['newKnowledge.documentContentIncomplete'])}
|
||||
</p>
|
||||
)}
|
||||
{chunks.length ? (
|
||||
<div
|
||||
className="max-h-[70vh] space-y-6 overflow-auto px-2 py-1"
|
||||
data-testid="chunk-content-scroll"
|
||||
>
|
||||
{chunks.map((chunk) => (
|
||||
<section
|
||||
key={chunk.id}
|
||||
id={`document-chunk-${chunk.id}`}
|
||||
className={cn(
|
||||
'scroll-mt-4 rounded-lg border border-transparent px-3 py-4 transition-colors [contain-intrinsic-size:auto_160px] [content-visibility:auto] motion-reduce:transition-none',
|
||||
selectedChunk?.id === chunk.id &&
|
||||
'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg',
|
||||
)}
|
||||
>
|
||||
<h3 className="system-sm-semibold text-text-primary">
|
||||
{chunkTreeLabel(chunk.text, chunk.ordinal)}
|
||||
</h3>
|
||||
<p className="mt-1 system-2xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.chunkLocation'], { position: chunk.ordinal })}
|
||||
</p>
|
||||
<p className="mt-3 body-md-regular break-words whitespace-pre-wrap text-text-primary">
|
||||
{chunk.text || t(($) => $['newKnowledge.emptyChunk'])}
|
||||
</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-h-72 items-center justify-center px-6 text-center body-sm-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.selectChunk'])}
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
|
||||
<aside>
|
||||
<section className="overflow-hidden rounded-xl border border-divider-subtle bg-background-default">
|
||||
<div className="p-4">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<h2 className="system-sm-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.metadata'])}
|
||||
</h2>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => toast.info(t(($) => $['newKnowledge.filtersUnavailable']))}
|
||||
>
|
||||
{t(($) => $['metadata.documentMetadata.startLabeling'])}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="mt-3 system-xs-regular text-text-secondary">
|
||||
{t(($) => $['newKnowledge.documentOverviewDescription'])}
|
||||
</p>
|
||||
<dl className="mt-3 space-y-3">
|
||||
<div>
|
||||
<dt className="system-2xs-medium text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.sourceColumn'])}
|
||||
</dt>
|
||||
<dd className="mt-1 system-xs-regular break-words text-text-secondary">
|
||||
{sourceName ??
|
||||
(document.sourceId
|
||||
? t(($) => $['newKnowledge.sourceType.connector'])
|
||||
: t(($) => $['newKnowledge.manualUpload']))}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="system-2xs-medium text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.fileSize'])}
|
||||
</dt>
|
||||
<dd className="mt-1 system-xs-regular text-text-secondary">
|
||||
{sizeBytes !== undefined ? formatBytes(sizeBytes, locale) : '—'}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="system-2xs-medium text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.createdAt'])}
|
||||
</dt>
|
||||
<dd className="mt-1 system-xs-regular text-text-secondary">
|
||||
{formatDate(document.createdAt, locale)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="system-2xs-medium text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.lastIndexed'])}
|
||||
</dt>
|
||||
<dd className="mt-1 system-xs-regular text-text-secondary">
|
||||
{formatDate(revision?.activatedAt ?? revision?.createdAt, locale)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="system-2xs-medium text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.documentRevision'])}
|
||||
</dt>
|
||||
<dd className="mt-1 system-xs-regular text-text-secondary">
|
||||
{revision?.revision ?? document.activeRevision ?? '—'}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div className="border-t border-divider-subtle p-4">
|
||||
<h2 className="system-sm-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.indexInformation'])}
|
||||
</h2>
|
||||
<dl className="mt-3 space-y-3">
|
||||
<div>
|
||||
<dt className="system-2xs-medium text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.chunkCount'])}
|
||||
</dt>
|
||||
<dd className="mt-1 system-xs-regular text-text-secondary">
|
||||
{chunksComplete ? new Intl.NumberFormat(locale).format(chunks.length) : '—'}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="system-2xs-medium text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.averageChunkLength'])}
|
||||
</dt>
|
||||
<dd className="mt-1 system-xs-regular text-text-secondary">
|
||||
{chunksComplete ? new Intl.NumberFormat(locale).format(averageChunkLength) : '—'}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="system-2xs-medium text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.retrievalCount'])}
|
||||
</dt>
|
||||
<dd className="mt-1 system-xs-regular text-text-secondary">
|
||||
{retrievalCount === undefined
|
||||
? '—'
|
||||
: new Intl.NumberFormat(locale).format(retrievalCount)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="system-2xs-medium text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.mimeType'])}
|
||||
</dt>
|
||||
<dd className="mt-1 system-xs-regular break-words text-text-secondary">
|
||||
{revision?.mimeType ?? document.active?.mimeType ?? '—'}
|
||||
</dd>
|
||||
</div>
|
||||
{selectedChunk && (
|
||||
<>
|
||||
<div>
|
||||
<dt className="system-2xs-medium text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.tokenCount'])}
|
||||
</dt>
|
||||
<dd className="mt-1 system-xs-regular text-text-secondary">
|
||||
{new Intl.NumberFormat(locale).format(selectedChunk.tokenCount)}
|
||||
</dd>
|
||||
</div>
|
||||
{chunkMetadataEntries(selectedChunk.userMetadata).map(([key, value]) => (
|
||||
<div key={key}>
|
||||
<dt className="system-2xs-medium break-words text-text-tertiary">{key}</dt>
|
||||
<dd className="mt-1 system-xs-regular break-words text-text-secondary">
|
||||
{value}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
'use client'
|
||||
|
||||
import type { DocumentChunkTree } from './document-detail-model'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { defaultRangeExtractor, useVirtualizer } from '@tanstack/react-virtual'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { chunkTreeLabel, visibleDocumentChunkNodes } from './document-detail-model'
|
||||
|
||||
const VIRTUALIZATION_THRESHOLD = 80
|
||||
const TREE_ROW_HEIGHT = 56
|
||||
|
||||
export function DocumentChunkTreePanel({
|
||||
chunkCount,
|
||||
error,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchNextPageError,
|
||||
isFetchingNextPage,
|
||||
isPending,
|
||||
onRetry,
|
||||
onSelectChunk,
|
||||
selectedChunkId,
|
||||
tree,
|
||||
}: {
|
||||
chunkCount: number
|
||||
error: boolean
|
||||
fetchNextPage: () => Promise<unknown>
|
||||
hasNextPage: boolean
|
||||
isFetchNextPageError: boolean
|
||||
isFetchingNextPage: boolean
|
||||
isPending: boolean
|
||||
onRetry: () => void
|
||||
onSelectChunk: (chunkId: string) => void
|
||||
selectedChunkId?: string
|
||||
tree: DocumentChunkTree
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const [collapsedChunkIds, setCollapsedChunkIds] = useState<Set<string>>(() => new Set())
|
||||
const [focusedChunkId, setFocusedChunkId] = useState<string>()
|
||||
const [treeHasFocus, setTreeHasFocus] = useState(false)
|
||||
const treeScrollRef = useRef<HTMLDivElement>(null)
|
||||
const loadMoreRequestedRef = useRef(false)
|
||||
const chunkIdsBeforeLoadRef = useRef<Set<string>>(new Set())
|
||||
const wasFetchingNextPageRef = useRef(false)
|
||||
const expandedChunkIds = useMemo(
|
||||
() => new Set([...tree.byId.keys()].filter((id) => !collapsedChunkIds.has(id))),
|
||||
[collapsedChunkIds, tree.byId],
|
||||
)
|
||||
const visibleNodes = useMemo(
|
||||
() => visibleDocumentChunkNodes(tree.roots, expandedChunkIds),
|
||||
[expandedChunkIds, tree.roots],
|
||||
)
|
||||
const shouldVirtualize = visibleNodes.length > VIRTUALIZATION_THRESHOLD
|
||||
const currentFocusedChunkId = focusedChunkId ?? visibleNodes[0]?.node.chunk.id
|
||||
const focusedIndex = visibleNodes.findIndex(
|
||||
(item) => item.node.chunk.id === currentFocusedChunkId,
|
||||
)
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: shouldVirtualize ? visibleNodes.length : 0,
|
||||
estimateSize: () => TREE_ROW_HEIGHT,
|
||||
getItemKey: (index) => visibleNodes[index]?.node.chunk.id ?? index,
|
||||
getScrollElement: () => treeScrollRef.current,
|
||||
overscan: 8,
|
||||
rangeExtractor: (range) => {
|
||||
const indexes = defaultRangeExtractor(range)
|
||||
if (focusedIndex >= 0 && !indexes.includes(focusedIndex)) indexes.push(focusedIndex)
|
||||
return indexes.sort((left, right) => left - right)
|
||||
},
|
||||
})
|
||||
const rowVirtualizerRef = useRef(rowVirtualizer)
|
||||
rowVirtualizerRef.current = rowVirtualizer
|
||||
const virtualRows = rowVirtualizer.getVirtualItems()
|
||||
|
||||
const toggleExpanded = (chunkId: string) => {
|
||||
setCollapsedChunkIds((current) => {
|
||||
const next = new Set(current)
|
||||
if (next.has(chunkId)) next.delete(chunkId)
|
||||
else next.add(chunkId)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const focusChunk = (chunkId: string) => {
|
||||
const index = visibleNodes.findIndex((item) => item.node.chunk.id === chunkId)
|
||||
if (index < 0) return
|
||||
setFocusedChunkId(chunkId)
|
||||
if (shouldVirtualize) rowVirtualizer.scrollToIndex(index, { align: 'auto' })
|
||||
treeScrollRef.current?.focus()
|
||||
}
|
||||
|
||||
const handleTreeKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
const chunkId = currentFocusedChunkId
|
||||
if (!chunkId) return
|
||||
const index = visibleNodes.findIndex((item) => item.node.chunk.id === chunkId)
|
||||
const current = visibleNodes[index]
|
||||
if (!current) return
|
||||
const parentId = current.node.chunk.parentChunkId
|
||||
let nextId: string | undefined
|
||||
if (event.key === 'ArrowDown') nextId = visibleNodes[index + 1]?.node.chunk.id
|
||||
else if (event.key === 'ArrowUp') nextId = visibleNodes[index - 1]?.node.chunk.id
|
||||
else if (event.key === 'Home') nextId = visibleNodes[0]?.node.chunk.id
|
||||
else if (event.key === 'End') nextId = visibleNodes.at(-1)?.node.chunk.id
|
||||
else if (event.key === 'ArrowRight' && current.node.children.length) {
|
||||
if (collapsedChunkIds.has(chunkId)) toggleExpanded(chunkId)
|
||||
else nextId = current.node.children[0]?.chunk.id
|
||||
} else if (event.key === 'ArrowLeft') {
|
||||
if (current.node.children.length && !collapsedChunkIds.has(chunkId)) toggleExpanded(chunkId)
|
||||
else if (parentId && tree.byId.has(parentId)) nextId = parentId
|
||||
} else if (event.key === 'Enter' || event.key === ' ') onSelectChunk(chunkId)
|
||||
else return
|
||||
event.preventDefault()
|
||||
if (nextId) focusChunk(nextId)
|
||||
}
|
||||
|
||||
const handleLoadMore = () => {
|
||||
if (isFetchingNextPage) return
|
||||
chunkIdsBeforeLoadRef.current = new Set(tree.byId.keys())
|
||||
loadMoreRequestedRef.current = true
|
||||
void fetchNextPage()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isFetchingNextPage) wasFetchingNextPageRef.current = true
|
||||
if (isFetchingNextPage || !wasFetchingNextPageRef.current || !loadMoreRequestedRef.current)
|
||||
return
|
||||
wasFetchingNextPageRef.current = false
|
||||
loadMoreRequestedRef.current = false
|
||||
if (isFetchNextPageError) return
|
||||
const firstNewNode = visibleNodes.find(
|
||||
(item) => !chunkIdsBeforeLoadRef.current.has(item.node.chunk.id),
|
||||
)
|
||||
requestAnimationFrame(() => {
|
||||
if (!firstNewNode) {
|
||||
treeScrollRef.current?.focus()
|
||||
return
|
||||
}
|
||||
const chunkId = firstNewNode.node.chunk.id
|
||||
const index = visibleNodes.findIndex((item) => item.node.chunk.id === chunkId)
|
||||
setFocusedChunkId(chunkId)
|
||||
if (shouldVirtualize) rowVirtualizerRef.current.scrollToIndex(index, { align: 'auto' })
|
||||
treeScrollRef.current?.focus()
|
||||
})
|
||||
}, [isFetchNextPageError, isFetchingNextPage, shouldVirtualize, visibleNodes])
|
||||
|
||||
const renderTreeItem = (item: (typeof visibleNodes)[number], style?: React.CSSProperties) => {
|
||||
const { depth, node, positionInSet, setSize } = item
|
||||
const { chunk } = node
|
||||
const hasChildren = node.children.length > 0
|
||||
const expanded = !collapsedChunkIds.has(chunk.id)
|
||||
const label = chunkTreeLabel(chunk.text, chunk.ordinal)
|
||||
return (
|
||||
<button
|
||||
key={chunk.id}
|
||||
id={`document-chunk-treeitem-${chunk.id}`}
|
||||
aria-expanded={hasChildren ? expanded : undefined}
|
||||
aria-label={label}
|
||||
aria-level={depth + 1}
|
||||
aria-posinset={positionInSet}
|
||||
aria-selected={selectedChunkId === chunk.id}
|
||||
aria-setsize={hasNextPage ? -1 : setSize}
|
||||
className={cn(
|
||||
'flex w-full items-start gap-1.5 rounded-lg py-2 pr-2 text-left system-xs-regular outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset',
|
||||
selectedChunkId === chunk.id && 'bg-state-accent-hover text-text-accent',
|
||||
treeHasFocus &&
|
||||
currentFocusedChunkId === chunk.id &&
|
||||
'bg-state-base-hover ring-1 ring-state-accent-solid ring-inset',
|
||||
)}
|
||||
role="treeitem"
|
||||
style={{ ...style, paddingInlineStart: `${8 + depth * 16}px` }}
|
||||
tabIndex={-1}
|
||||
onClick={() => {
|
||||
setFocusedChunkId(chunk.id)
|
||||
treeScrollRef.current?.focus()
|
||||
onSelectChunk(chunk.id)
|
||||
if (hasChildren) toggleExpanded(chunk.id)
|
||||
}}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'mt-0.5 size-4 shrink-0 rtl:-scale-x-100',
|
||||
hasChildren
|
||||
? expanded
|
||||
? 'i-ri-arrow-down-s-line'
|
||||
: 'i-ri-arrow-right-s-line'
|
||||
: 'i-ri-file-text-line text-text-tertiary',
|
||||
)}
|
||||
/>
|
||||
<span className="line-clamp-2 min-w-0 break-words">{label}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="min-h-52 overflow-hidden rounded-xl border border-divider-subtle bg-background-default-subtle">
|
||||
<h2 className="border-b border-divider-subtle px-4 py-3 system-sm-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.documentContents'])}
|
||||
</h2>
|
||||
{error && !isFetchNextPageError && chunkCount > 0 && (
|
||||
<div
|
||||
className="flex items-center justify-between gap-2 border-b border-divider-subtle bg-state-warning-hover px-3 py-2 system-xs-regular text-text-warning"
|
||||
role="alert"
|
||||
>
|
||||
<span>{t(($) => $['newKnowledge.documentChunksLoadError'])}</span>
|
||||
<Button onClick={onRetry}>{tCommon(($) => $['operation.retry'])}</Button>
|
||||
</div>
|
||||
)}
|
||||
{isPending ? (
|
||||
<div className="flex min-h-40 items-center justify-center" role="status">
|
||||
<Loading />
|
||||
<span className="sr-only">{tCommon(($) => $.loading)}</span>
|
||||
</div>
|
||||
) : error && !isFetchNextPageError && !chunkCount ? (
|
||||
<div className="p-4 text-center">
|
||||
<p className="system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.documentChunksLoadError'])}
|
||||
</p>
|
||||
<Button className="mt-3" onClick={onRetry}>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
) : !chunkCount ? (
|
||||
<p className="p-6 text-center system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.documentChunksEmpty'])}
|
||||
</p>
|
||||
) : (
|
||||
<div
|
||||
ref={treeScrollRef}
|
||||
aria-activedescendant={
|
||||
currentFocusedChunkId ? `document-chunk-treeitem-${currentFocusedChunkId}` : undefined
|
||||
}
|
||||
aria-label={t(($) => $['newKnowledge.documentContents'])}
|
||||
className="max-h-[58vh] overflow-auto p-2 outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset"
|
||||
role="tree"
|
||||
tabIndex={0}
|
||||
onBlur={() => setTreeHasFocus(false)}
|
||||
onFocus={() => setTreeHasFocus(true)}
|
||||
onKeyDown={handleTreeKeyDown}
|
||||
>
|
||||
{shouldVirtualize ? (
|
||||
<div className="relative" style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
|
||||
{virtualRows.map((virtualRow) =>
|
||||
renderTreeItem(visibleNodes[virtualRow.index]!, {
|
||||
height: `${virtualRow.size}px`,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
transform: `translateY(${virtualRow.start}px)`,
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
visibleNodes.map((item) => renderTreeItem(item))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{(hasNextPage || isFetchNextPageError) && (
|
||||
<div className="border-t border-divider-subtle p-3 text-center">
|
||||
{isFetchNextPageError && (
|
||||
<p className="mb-2 system-xs-regular text-text-destructive" role="alert">
|
||||
{t(($) => $['newKnowledge.documentChunksLoadMoreError'])}
|
||||
</p>
|
||||
)}
|
||||
<Button
|
||||
disabled={isFetchingNextPage}
|
||||
loading={isFetchingNextPage}
|
||||
onClick={handleLoadMore}
|
||||
>
|
||||
{isFetchNextPageError
|
||||
? tCommon(($) => $['operation.retry'])
|
||||
: t(($) => $['newKnowledge.loadMore'])}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import type {
|
||||
LogicalDocument,
|
||||
LogicalDocumentRevision,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import type { RefObject } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectItemIndicator,
|
||||
SelectItemText,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
} from '@langgenius/dify-ui/select'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Link from '@/next/link'
|
||||
import { sourceName } from './document-model'
|
||||
|
||||
export function DocumentDetailHeader({
|
||||
backPath,
|
||||
document,
|
||||
effectiveRevision,
|
||||
fetchNextRevisionPage,
|
||||
hasNextRevisionPage,
|
||||
isFetchNextRevisionPageError,
|
||||
isFetchingNextRevisionPage,
|
||||
onReindex,
|
||||
onRevisionChange,
|
||||
reindexDisabled,
|
||||
reindexing,
|
||||
revisions,
|
||||
taskIsActive,
|
||||
titleRef,
|
||||
}: {
|
||||
backPath: string
|
||||
document: LogicalDocument
|
||||
effectiveRevision?: number
|
||||
fetchNextRevisionPage: () => void
|
||||
hasNextRevisionPage: boolean
|
||||
isFetchNextRevisionPageError: boolean
|
||||
isFetchingNextRevisionPage: boolean
|
||||
onReindex: () => void
|
||||
onRevisionChange: (revision: number) => void
|
||||
reindexDisabled: boolean
|
||||
reindexing: boolean
|
||||
revisions: Array<Exclude<LogicalDocumentRevision, null>>
|
||||
taskIsActive: boolean
|
||||
titleRef: RefObject<HTMLHeadingElement | null>
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const revisionTriggerRef = useRef<HTMLButtonElement>(null)
|
||||
const loadMoreRequestedRef = useRef(false)
|
||||
const wasFetchingNextPageRef = useRef(false)
|
||||
const explicitSourceName = sourceName(document)
|
||||
const sourceLabel =
|
||||
explicitSourceName ??
|
||||
(document.sourceId
|
||||
? t(($) => $['newKnowledge.sourceType.connector'])
|
||||
: t(($) => $['newKnowledge.manualUpload']))
|
||||
|
||||
useEffect(() => {
|
||||
if (isFetchingNextRevisionPage) wasFetchingNextPageRef.current = true
|
||||
if (
|
||||
isFetchingNextRevisionPage ||
|
||||
!wasFetchingNextPageRef.current ||
|
||||
!loadMoreRequestedRef.current
|
||||
)
|
||||
return
|
||||
wasFetchingNextPageRef.current = false
|
||||
loadMoreRequestedRef.current = false
|
||||
if (!isFetchNextRevisionPageError && !hasNextRevisionPage) revisionTriggerRef.current?.focus()
|
||||
}, [hasNextRevisionPage, isFetchNextRevisionPageError, isFetchingNextRevisionPage])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Link
|
||||
className="inline-flex w-fit items-center gap-1 system-xs-medium text-text-tertiary hover:text-text-secondary focus-visible:rounded focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden"
|
||||
href={backPath}
|
||||
>
|
||||
<span aria-hidden className="i-ri-arrow-left-line size-4" />
|
||||
{t(($) => $['newKnowledge.documents'])}
|
||||
</Link>
|
||||
<div className="mt-3 flex flex-wrap items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<h1
|
||||
ref={titleRef}
|
||||
className="truncate title-2xl-semi-bold text-text-primary outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{document.title}
|
||||
</h1>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">{sourceLabel}</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{revisions.length > 0 && effectiveRevision !== undefined && (
|
||||
<Select
|
||||
value={String(effectiveRevision)}
|
||||
onValueChange={(value) => {
|
||||
if (value) onRevisionChange(Number(value))
|
||||
}}
|
||||
>
|
||||
<SelectLabel>{t(($) => $['newKnowledge.documentRevision'])}</SelectLabel>
|
||||
<SelectTrigger ref={revisionTriggerRef} className="h-8 w-fit min-w-28">
|
||||
v{effectiveRevision}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{revisions.map((revision) => (
|
||||
<SelectItem key={revision.revision} value={String(revision.revision)}>
|
||||
<SelectItemText>
|
||||
v{revision.revision} ·{' '}
|
||||
{t(($) => $[`newKnowledge.revisionState.${revision.state}`])}
|
||||
</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
{(hasNextRevisionPage || isFetchNextRevisionPageError) && (
|
||||
<Button
|
||||
disabled={isFetchingNextRevisionPage}
|
||||
loading={isFetchingNextRevisionPage}
|
||||
onClick={() => {
|
||||
loadMoreRequestedRef.current = true
|
||||
fetchNextRevisionPage()
|
||||
}}
|
||||
>
|
||||
{isFetchNextRevisionPageError
|
||||
? tCommon(($) => $['operation.retry'])
|
||||
: t(($) => $['newKnowledge.loadMoreRevisions'])}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
aria-busy={reindexing || taskIsActive}
|
||||
disabled={reindexDisabled}
|
||||
loading={reindexing}
|
||||
onClick={onReindex}
|
||||
>
|
||||
<span aria-hidden className="i-ri-refresh-line size-4" />
|
||||
{t(($) => $['newKnowledge.reindexDocument'])}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{isFetchNextRevisionPageError && (
|
||||
<p className="mt-2 system-xs-regular text-text-destructive" role="alert">
|
||||
{t(($) => $['newKnowledge.documentRevisionsLoadError'])}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import type {
|
||||
DocumentRevisionChunk,
|
||||
LogicalDocument,
|
||||
LogicalDocumentRevision,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
|
||||
export type DocumentChunkTreeNode = {
|
||||
children: DocumentChunkTreeNode[]
|
||||
chunk: DocumentRevisionChunk
|
||||
}
|
||||
|
||||
export type DocumentChunkTree = {
|
||||
byId: Map<string, DocumentChunkTreeNode>
|
||||
roots: DocumentChunkTreeNode[]
|
||||
}
|
||||
|
||||
export function responseStatus(error: unknown): number | undefined {
|
||||
if (error instanceof Response) return error.status
|
||||
if (error && typeof error === 'object' && 'status' in error)
|
||||
return typeof error.status === 'number' ? error.status : undefined
|
||||
if (error && typeof error === 'object' && 'data' in error) {
|
||||
const data = error.data
|
||||
if (data && typeof data === 'object' && 'status' in data)
|
||||
return typeof data.status === 'number' ? data.status : undefined
|
||||
}
|
||||
}
|
||||
|
||||
function compareChunks(left: DocumentRevisionChunk, right: DocumentRevisionChunk) {
|
||||
return left.ordinal - right.ordinal || left.id.localeCompare(right.id)
|
||||
}
|
||||
|
||||
function cyclicChunkIds(chunksById: Map<string, DocumentRevisionChunk>) {
|
||||
const cycleIds = new Set<string>()
|
||||
const settledIds = new Set<string>()
|
||||
|
||||
for (const chunkId of chunksById.keys()) {
|
||||
if (settledIds.has(chunkId)) continue
|
||||
const path: string[] = []
|
||||
const pathIndexes = new Map<string, number>()
|
||||
let candidateId: string | undefined = chunkId
|
||||
while (candidateId && chunksById.has(candidateId) && !settledIds.has(candidateId)) {
|
||||
const cycleStart = pathIndexes.get(candidateId)
|
||||
if (cycleStart !== undefined) {
|
||||
for (const cycleId of path.slice(cycleStart)) cycleIds.add(cycleId)
|
||||
break
|
||||
}
|
||||
pathIndexes.set(candidateId, path.length)
|
||||
path.push(candidateId)
|
||||
candidateId = chunksById.get(candidateId)?.parentChunkId
|
||||
}
|
||||
for (const pathId of path) settledIds.add(pathId)
|
||||
}
|
||||
|
||||
return cycleIds
|
||||
}
|
||||
|
||||
export function buildDocumentChunkTree(chunks: DocumentRevisionChunk[]): DocumentChunkTree {
|
||||
const sortedChunks = [...chunks].sort(compareChunks)
|
||||
const chunksById = new Map(sortedChunks.map((chunk) => [chunk.id, chunk]))
|
||||
const cycleIds = cyclicChunkIds(chunksById)
|
||||
const byId = new Map<string, DocumentChunkTreeNode>(
|
||||
sortedChunks.map((chunk) => [
|
||||
chunk.id,
|
||||
{ children: [], chunk } satisfies DocumentChunkTreeNode,
|
||||
]),
|
||||
)
|
||||
const roots: DocumentChunkTreeNode[] = []
|
||||
|
||||
for (const chunk of sortedChunks) {
|
||||
const node = byId.get(chunk.id)!
|
||||
const parentId = chunk.parentChunkId
|
||||
const parent = parentId ? byId.get(parentId) : undefined
|
||||
if (!parent || !parentId || cycleIds.has(chunk.id)) roots.push(node)
|
||||
else parent.children.push(node)
|
||||
}
|
||||
|
||||
for (const node of byId.values())
|
||||
node.children.sort((left, right) => compareChunks(left.chunk, right.chunk))
|
||||
roots.sort((left, right) => compareChunks(left.chunk, right.chunk))
|
||||
return { byId, roots }
|
||||
}
|
||||
|
||||
export function visibleDocumentChunkNodes(
|
||||
roots: DocumentChunkTreeNode[],
|
||||
expandedChunkIds: Set<string>,
|
||||
) {
|
||||
const visible: Array<{
|
||||
depth: number
|
||||
node: DocumentChunkTreeNode
|
||||
positionInSet: number
|
||||
setSize: number
|
||||
}> = []
|
||||
const pending = roots
|
||||
.map((node, index) => ({
|
||||
depth: 0,
|
||||
node,
|
||||
positionInSet: index + 1,
|
||||
setSize: roots.length,
|
||||
}))
|
||||
.reverse()
|
||||
while (pending.length) {
|
||||
const item = pending.pop()!
|
||||
const { depth, node } = item
|
||||
visible.push(item)
|
||||
if (!expandedChunkIds.has(node.chunk.id)) continue
|
||||
for (let index = node.children.length - 1; index >= 0; index--)
|
||||
pending.push({
|
||||
depth: depth + 1,
|
||||
node: node.children[index]!,
|
||||
positionInSet: index + 1,
|
||||
setSize: node.children.length,
|
||||
})
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
export function initialDocumentRevision(
|
||||
document: LogicalDocument,
|
||||
revisions: LogicalDocumentRevision[],
|
||||
) {
|
||||
const activeRevision = document.activeRevision ?? document.active?.revision
|
||||
if (activeRevision !== undefined) return activeRevision
|
||||
return revisions
|
||||
.filter((revision) => revision?.state === 'active' || revision?.state === 'superseded')
|
||||
.reduce<number | undefined>(
|
||||
(latest, revision) =>
|
||||
revision && (latest === undefined || revision.revision > latest)
|
||||
? revision.revision
|
||||
: latest,
|
||||
undefined,
|
||||
)
|
||||
}
|
||||
|
||||
export function chunkTreeLabel(text: string, ordinal: number) {
|
||||
const firstLine = text.split(/\r?\n/, 1)[0]?.trim()
|
||||
if (!firstLine) return `#${ordinal}`
|
||||
const graphemes: string[] = []
|
||||
for (const { segment } of new Intl.Segmenter(undefined, {
|
||||
granularity: 'grapheme',
|
||||
}).segment(firstLine)) {
|
||||
graphemes.push(segment)
|
||||
if (graphemes.length > 120) break
|
||||
}
|
||||
return graphemes.length > 120 ? `${graphemes.slice(0, 119).join('')}…` : firstLine
|
||||
}
|
||||
|
||||
export function chunkCharacterCount(text: string) {
|
||||
let count = 0
|
||||
for (const _segment of new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text))
|
||||
count++
|
||||
return count
|
||||
}
|
||||
|
||||
function metadataValue(value: unknown) {
|
||||
if (typeof value === 'string') return value
|
||||
if (typeof value === 'number' || typeof value === 'boolean' || value === null)
|
||||
return String(value)
|
||||
try {
|
||||
return JSON.stringify(value) ?? ''
|
||||
} catch {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
export function chunkMetadataEntries(metadata: Record<string, unknown>) {
|
||||
return Object.entries(metadata)
|
||||
.sort(([left], [right]) => left.localeCompare(right))
|
||||
.map(([key, value]) => [key, metadataValue(value)] as const)
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
'use client'
|
||||
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { datasetDefaultPermissionKeysAtom } from '@/context/permission-state'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { DatasetACLPermission, hasPermission } from '@/utils/permission'
|
||||
import { DocumentDetailHeader } from './document-detail-header'
|
||||
import { initialDocumentRevision, responseStatus } from './document-detail-model'
|
||||
import { documentChunksQueryOptions } from './document-detail-queries'
|
||||
import { DocumentDetailStatus } from './document-detail-status'
|
||||
import { DocumentRevisionContent } from './document-revision-content'
|
||||
import { newKnowledgeDocumentsPath } from './routes'
|
||||
import { useDocumentReindex } from './use-document-reindex'
|
||||
|
||||
const REVISION_PAGE_SIZE = 50
|
||||
function ErrorState({
|
||||
description,
|
||||
onRetry,
|
||||
title,
|
||||
}: {
|
||||
description: string
|
||||
onRetry?: () => void
|
||||
title: string
|
||||
}) {
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
return (
|
||||
<div className="flex min-h-80 flex-col items-center justify-center px-6 text-center">
|
||||
<span aria-hidden className="i-ri-error-warning-line size-8 text-text-destructive" />
|
||||
<h1 className="mt-3 title-2xl-semi-bold text-text-primary">{title}</h1>
|
||||
<p className="mt-2 max-w-lg body-sm-regular text-text-tertiary">{description}</p>
|
||||
{onRetry && (
|
||||
<Button className="mt-4" onClick={onRetry}>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DocumentDetailPage({
|
||||
documentId,
|
||||
knowledgeSpaceId,
|
||||
}: {
|
||||
documentId: string
|
||||
knowledgeSpaceId: string
|
||||
}) {
|
||||
const { i18n, t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const permissionKeys = useAtomValue(datasetDefaultPermissionKeysAtom)
|
||||
const [selectedRevision, setSelectedRevision] = useState<number>()
|
||||
const titleRef = useRef<HTMLHeadingElement>(null)
|
||||
|
||||
const documentQueryOptions = useMemo(
|
||||
() =>
|
||||
consoleQuery.knowledgeFs.getKnowledgeSpacesByIdLogicalDocumentsByDocumentId.queryOptions({
|
||||
input: { params: { documentId, id: knowledgeSpaceId } },
|
||||
retry: (failureCount, error) => {
|
||||
const status = responseStatus(error)
|
||||
return status !== 403 && status !== 404 && failureCount < 2
|
||||
},
|
||||
}),
|
||||
[documentId, knowledgeSpaceId],
|
||||
)
|
||||
const documentQuery = useQuery(documentQueryOptions)
|
||||
const revisionsQueryOptions = useMemo(
|
||||
() =>
|
||||
consoleQuery.knowledgeFs.getKnowledgeSpacesByIdDocumentsByDocumentIdRevisions.infiniteOptions(
|
||||
{
|
||||
input: (pageParam) => ({
|
||||
params: { documentId, id: knowledgeSpaceId },
|
||||
query: {
|
||||
limit: REVISION_PAGE_SIZE,
|
||||
...(typeof pageParam === 'string' ? { cursor: pageParam } : {}),
|
||||
},
|
||||
}),
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
initialPageParam: null as string | null,
|
||||
},
|
||||
),
|
||||
[documentId, knowledgeSpaceId],
|
||||
)
|
||||
const revisionsQuery = useInfiniteQuery(revisionsQueryOptions)
|
||||
const revisions = useMemo(
|
||||
() => revisionsQuery.data?.pages.flatMap((page) => page.items).filter(Boolean) ?? [],
|
||||
[revisionsQuery.data],
|
||||
)
|
||||
const availableRevisions = useMemo(() => {
|
||||
const byRevision = new Map(revisions.map((revision) => [revision.revision, revision]))
|
||||
if (documentQuery.data?.active)
|
||||
byRevision.set(documentQuery.data.active.revision, documentQuery.data.active)
|
||||
return [...byRevision.values()].sort((left, right) => right.revision - left.revision)
|
||||
}, [documentQuery.data?.active, revisions])
|
||||
const effectiveRevision = documentQuery.data
|
||||
? (selectedRevision ?? initialDocumentRevision(documentQuery.data, availableRevisions))
|
||||
: undefined
|
||||
const chunksQueryOptions = useMemo(
|
||||
() => documentChunksQueryOptions({ documentId, effectiveRevision, knowledgeSpaceId }),
|
||||
[documentId, effectiveRevision, knowledgeSpaceId],
|
||||
)
|
||||
const documentActiveRevision =
|
||||
documentQuery.data?.activeRevision ?? documentQuery.data?.active?.revision ?? 0
|
||||
const documentErrorStatus = responseStatus(documentQuery.error)
|
||||
const {
|
||||
continueLookup,
|
||||
documentMissing,
|
||||
isFetchingNextPage: isFetchingNextTaskPage,
|
||||
isLookingUp: isLookingUpTask,
|
||||
isPending: tasksPending,
|
||||
latestTask,
|
||||
lookupExhausted,
|
||||
permissionRecoveryBusy,
|
||||
permissionRecoveryNeeded,
|
||||
refetch: refetchTasks,
|
||||
recheckTimedOutSubmission,
|
||||
reindex,
|
||||
reindexBusy,
|
||||
retryTimedOutSubmission,
|
||||
retryWritePermission,
|
||||
submissionPending,
|
||||
submissionRecoveryBusy,
|
||||
submissionTimedOut,
|
||||
taskIsActive,
|
||||
tasksError,
|
||||
writePermissionRevoked,
|
||||
} = useDocumentReindex({
|
||||
documentActiveRevision,
|
||||
chunksQueryKey: chunksQueryOptions.queryKey,
|
||||
documentId,
|
||||
documentQueryKey: documentQueryOptions.queryKey,
|
||||
enabled:
|
||||
Boolean(documentQuery.data) && documentErrorStatus !== 403 && documentErrorStatus !== 404,
|
||||
knowledgeSpaceId,
|
||||
revisionsQueryKey: revisionsQueryOptions.queryKey,
|
||||
})
|
||||
const canEdit =
|
||||
hasPermission(permissionKeys, DatasetACLPermission.Edit) && !writePermissionRevoked
|
||||
const activeRevision = availableRevisions.find(
|
||||
(revision) => revision.revision === effectiveRevision,
|
||||
)
|
||||
const backPath = newKnowledgeDocumentsPath(knowledgeSpaceId)
|
||||
const locale = i18n.resolvedLanguage ?? i18n.language
|
||||
|
||||
if (documentQuery.isPending)
|
||||
return (
|
||||
<div className="flex min-h-80 items-center justify-center">
|
||||
<Loading />
|
||||
<span className="sr-only">{tCommon(($) => $.loading)}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
if (documentMissing || documentErrorStatus === 403 || documentErrorStatus === 404)
|
||||
return (
|
||||
<ErrorState
|
||||
description={t(($) => $['newKnowledge.documentNotFoundDescription'])}
|
||||
title={t(($) => $['newKnowledge.documentNotFoundTitle'])}
|
||||
/>
|
||||
)
|
||||
|
||||
if (!documentQuery.data) {
|
||||
return (
|
||||
<ErrorState
|
||||
description={t(($) => $['newKnowledge.documentLoadErrorDescription'])}
|
||||
onRetry={() => void documentQuery.refetch()}
|
||||
title={t(($) => $['newKnowledge.documentLoadErrorTitle'])}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const document = documentQuery.data
|
||||
return (
|
||||
<section className="flex min-h-0 flex-1 flex-col px-6 py-5 lg:px-8">
|
||||
<DocumentDetailHeader
|
||||
backPath={backPath}
|
||||
document={document}
|
||||
effectiveRevision={effectiveRevision}
|
||||
fetchNextRevisionPage={() => void revisionsQuery.fetchNextPage()}
|
||||
hasNextRevisionPage={revisionsQuery.hasNextPage}
|
||||
isFetchNextRevisionPageError={revisionsQuery.isFetchNextPageError}
|
||||
isFetchingNextRevisionPage={revisionsQuery.isFetchingNextPage}
|
||||
onReindex={() => void reindex()}
|
||||
onRevisionChange={(revision) => {
|
||||
setSelectedRevision(revision)
|
||||
}}
|
||||
reindexDisabled={
|
||||
!canEdit ||
|
||||
reindexBusy ||
|
||||
submissionPending ||
|
||||
taskIsActive ||
|
||||
tasksPending ||
|
||||
isFetchingNextTaskPage ||
|
||||
isLookingUpTask ||
|
||||
lookupExhausted ||
|
||||
document.status === 'deleting' ||
|
||||
Boolean(tasksError)
|
||||
}
|
||||
reindexing={reindexBusy || submissionPending}
|
||||
revisions={availableRevisions}
|
||||
taskIsActive={taskIsActive}
|
||||
titleRef={titleRef}
|
||||
/>
|
||||
|
||||
<DocumentDetailStatus
|
||||
continueLookup={continueLookup}
|
||||
effectiveRevision={effectiveRevision}
|
||||
isLookingUpTask={isLookingUpTask}
|
||||
latestTask={latestTask}
|
||||
locale={locale}
|
||||
lookupExhausted={lookupExhausted}
|
||||
permissionRecoveryBusy={permissionRecoveryBusy}
|
||||
permissionRecoveryNeeded={permissionRecoveryNeeded}
|
||||
recheckTimedOutSubmission={recheckTimedOutSubmission}
|
||||
refetchRevisions={() => void revisionsQuery.refetch()}
|
||||
refetchTasks={() => void refetchTasks()}
|
||||
retryTimedOutSubmission={retryTimedOutSubmission}
|
||||
retryWritePermission={retryWritePermission}
|
||||
revisionHistoryBackgroundError={Boolean(
|
||||
revisionsQuery.error && !revisionsQuery.isFetchNextPageError,
|
||||
)}
|
||||
submissionRecoveryBusy={submissionRecoveryBusy}
|
||||
submissionTimedOut={submissionTimedOut}
|
||||
taskIsActive={taskIsActive}
|
||||
tasksError={Boolean(tasksError)}
|
||||
titleRef={titleRef}
|
||||
/>
|
||||
|
||||
<DocumentRevisionContent
|
||||
key={effectiveRevision ?? 'missing'}
|
||||
document={document}
|
||||
documentId={documentId}
|
||||
effectiveRevision={effectiveRevision}
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
locale={locale}
|
||||
revision={activeRevision}
|
||||
revisionHistoryError={Boolean(revisionsQuery.error)}
|
||||
revisionHistoryPending={revisionsQuery.isPending}
|
||||
retryRevisionHistory={() => void revisionsQuery.refetch()}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { skipToken } from '@tanstack/react-query'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
|
||||
const CHUNK_PAGE_SIZE = 100
|
||||
|
||||
export function documentChunksQueryOptions({
|
||||
documentId,
|
||||
effectiveRevision,
|
||||
knowledgeSpaceId,
|
||||
}: {
|
||||
documentId: string
|
||||
effectiveRevision?: number
|
||||
knowledgeSpaceId: string
|
||||
}) {
|
||||
const chunksQuery =
|
||||
consoleQuery.knowledgeFs.getKnowledgeSpacesByIdDocumentsByDocumentIdRevisionsByRevisionChunks
|
||||
|
||||
return chunksQuery.infiniteOptions({
|
||||
enabled: effectiveRevision !== undefined,
|
||||
input:
|
||||
effectiveRevision === undefined
|
||||
? skipToken
|
||||
: (pageParam) => ({
|
||||
params: { documentId, id: knowledgeSpaceId, revision: effectiveRevision },
|
||||
query: {
|
||||
limit: CHUNK_PAGE_SIZE,
|
||||
...(typeof pageParam === 'string' ? { cursor: pageParam } : {}),
|
||||
},
|
||||
}),
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
initialPageParam: null as string | null,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import type { DocumentProcessingTask } from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import type { RefObject } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export function DocumentDetailStatus({
|
||||
continueLookup,
|
||||
effectiveRevision,
|
||||
isLookingUpTask,
|
||||
latestTask,
|
||||
locale,
|
||||
lookupExhausted,
|
||||
permissionRecoveryBusy,
|
||||
permissionRecoveryNeeded,
|
||||
recheckTimedOutSubmission,
|
||||
refetchRevisions,
|
||||
refetchTasks,
|
||||
retryTimedOutSubmission,
|
||||
retryWritePermission,
|
||||
revisionHistoryBackgroundError,
|
||||
submissionRecoveryBusy,
|
||||
submissionTimedOut,
|
||||
taskIsActive,
|
||||
tasksError,
|
||||
titleRef,
|
||||
}: {
|
||||
continueLookup: () => void
|
||||
effectiveRevision?: number
|
||||
isLookingUpTask: boolean
|
||||
latestTask?: DocumentProcessingTask
|
||||
locale: string
|
||||
lookupExhausted: boolean
|
||||
permissionRecoveryBusy: boolean
|
||||
permissionRecoveryNeeded: boolean
|
||||
recheckTimedOutSubmission: () => Promise<unknown>
|
||||
refetchRevisions: () => void
|
||||
refetchTasks: () => void
|
||||
retryTimedOutSubmission: () => Promise<unknown>
|
||||
retryWritePermission: () => Promise<boolean>
|
||||
revisionHistoryBackgroundError: boolean
|
||||
submissionRecoveryBusy: boolean
|
||||
submissionTimedOut: boolean
|
||||
taskIsActive: boolean
|
||||
tasksError: boolean
|
||||
titleRef: RefObject<HTMLHeadingElement | null>
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const permissionRetryRef = useRef<HTMLButtonElement>(null)
|
||||
const permissionRecoveryWasNeededRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (permissionRecoveryNeeded && !permissionRecoveryWasNeededRef.current)
|
||||
requestAnimationFrame(() => permissionRetryRef.current?.focus())
|
||||
permissionRecoveryWasNeededRef.current = permissionRecoveryNeeded
|
||||
}, [permissionRecoveryNeeded])
|
||||
|
||||
return (
|
||||
<>
|
||||
{taskIsActive && (
|
||||
<div
|
||||
className="mt-4 flex items-center gap-2 rounded-lg bg-state-accent-hover px-3 py-2 system-xs-regular text-text-accent"
|
||||
role="status"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-loader-2-line size-4 animate-spin motion-reduce:animate-none"
|
||||
/>
|
||||
{t(($) => $['newKnowledge.documentReindexProgress'], {
|
||||
progress: new Intl.NumberFormat(locale).format(latestTask?.progressPercent ?? 0),
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{latestTask?.state === 'failed' && (
|
||||
<div
|
||||
className="mt-4 rounded-lg bg-state-destructive-hover px-3 py-2 system-xs-regular text-text-destructive"
|
||||
role="alert"
|
||||
>
|
||||
<p>{t(($) => $['newKnowledge.documentReindexFailed'])}</p>
|
||||
<p className="mt-1 text-text-secondary">
|
||||
{t(($) => $['newKnowledge.lastReadyRevisionHint'])}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tasksError && (
|
||||
<div
|
||||
className="mt-4 flex flex-wrap items-center justify-between gap-2 rounded-lg bg-state-warning-hover px-3 py-2 system-xs-regular text-text-warning"
|
||||
role="alert"
|
||||
>
|
||||
<span>{t(($) => $['newKnowledge.tasksErrorDescription'])}</span>
|
||||
<Button onClick={refetchTasks}>{tCommon(($) => $['operation.retry'])}</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{permissionRecoveryNeeded && (
|
||||
<div
|
||||
className="mt-4 flex flex-wrap items-center justify-between gap-2 rounded-lg bg-state-warning-hover px-3 py-2 system-xs-regular text-text-warning"
|
||||
role="alert"
|
||||
>
|
||||
<span>{t(($) => $['newKnowledge.documentPermissionRestricted'])}</span>
|
||||
<Button
|
||||
ref={permissionRetryRef}
|
||||
disabled={permissionRecoveryBusy}
|
||||
loading={permissionRecoveryBusy}
|
||||
onClick={() =>
|
||||
void retryWritePermission().then((recovered) => {
|
||||
if (recovered) titleRef.current?.focus()
|
||||
else permissionRetryRef.current?.focus()
|
||||
})
|
||||
}
|
||||
>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(lookupExhausted || isLookingUpTask) && (
|
||||
<div
|
||||
className="mt-4 flex flex-wrap items-center justify-between gap-2 rounded-lg bg-state-warning-hover px-3 py-2 system-xs-regular text-text-warning"
|
||||
role={isLookingUpTask ? 'status' : 'alert'}
|
||||
>
|
||||
{isLookingUpTask ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-loader-2-line size-4 animate-spin motion-reduce:animate-none"
|
||||
/>
|
||||
{tCommon(($) => $.loading)}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span>{t(($) => $['newKnowledge.documentTaskLookupIncomplete'])}</span>
|
||||
<Button
|
||||
onClick={() => {
|
||||
continueLookup()
|
||||
requestAnimationFrame(() => titleRef.current?.focus())
|
||||
}}
|
||||
>
|
||||
{t(($) => $['newKnowledge.continueCheckingTaskStatus'])}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{submissionTimedOut && (
|
||||
<div
|
||||
className="mt-4 flex flex-wrap items-center justify-between gap-2 rounded-lg bg-state-warning-hover px-3 py-2 system-xs-regular text-text-warning"
|
||||
role="alert"
|
||||
>
|
||||
<span>{t(($) => $['newKnowledge.documentReindexConfirmationDelayed'])}</span>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
disabled={submissionRecoveryBusy}
|
||||
loading={submissionRecoveryBusy}
|
||||
onClick={() =>
|
||||
void recheckTimedOutSubmission().finally(() => titleRef.current?.focus())
|
||||
}
|
||||
>
|
||||
{t(($) => $['newKnowledge.checkReindexStatus'])}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={submissionRecoveryBusy}
|
||||
onClick={() =>
|
||||
void retryTimedOutSubmission().finally(() => titleRef.current?.focus())
|
||||
}
|
||||
>
|
||||
{t(($) => $['newKnowledge.retryReindexDocument'])}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{revisionHistoryBackgroundError && effectiveRevision !== undefined && (
|
||||
<div
|
||||
className="mt-4 flex flex-wrap items-center justify-between gap-2 rounded-lg bg-state-warning-hover px-3 py-2 system-xs-regular text-text-warning"
|
||||
role="alert"
|
||||
>
|
||||
<span>{t(($) => $['newKnowledge.documentRevisionsLoadError'])}</span>
|
||||
<Button onClick={refetchRevisions}>{tCommon(($) => $['operation.retry'])}</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,722 @@
|
||||
'use client'
|
||||
|
||||
import type { LogicalDocument } from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import type { FocusEventHandler } from 'react'
|
||||
import type { DocumentDisplayStatus } from './document-model'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { memo, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import Link from '@/next/link'
|
||||
import { sourceName } from './document-model'
|
||||
|
||||
export type DocumentFilter = DocumentDisplayStatus | 'all'
|
||||
|
||||
const DOCUMENT_RENDER_BATCH_SIZE = 100
|
||||
const PARTIAL_RESULTS_DESCRIPTION_ID = 'partial-document-results'
|
||||
|
||||
const statusIconClass: Record<DocumentDisplayStatus, string> = {
|
||||
ready: 'i-ri-check-line text-text-success',
|
||||
queued: 'i-ri-time-line text-text-tertiary',
|
||||
processing: 'i-ri-loader-2-line animate-spin text-text-accent motion-reduce:animate-none',
|
||||
failed: 'i-ri-error-warning-fill text-text-destructive',
|
||||
disabled: 'i-ri-indeterminate-circle-line text-text-tertiary',
|
||||
}
|
||||
|
||||
function TaskTrigger({
|
||||
activeTaskCount,
|
||||
attentionTaskBadge,
|
||||
hasTaskError,
|
||||
onOpenTasks,
|
||||
tasksButtonLabel,
|
||||
tasksLiveStatus,
|
||||
}: {
|
||||
activeTaskCount: number
|
||||
attentionTaskBadge?: string
|
||||
hasTaskError: boolean
|
||||
onOpenTasks: () => void
|
||||
tasksButtonLabel: string
|
||||
tasksLiveStatus: string
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
return (
|
||||
<>
|
||||
<Button aria-label={tasksButtonLabel} data-has-error={hasTaskError} onClick={onOpenTasks}>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'size-4',
|
||||
activeTaskCount ? 'i-ri-loader-2-line animate-spin' : 'i-ri-task-line',
|
||||
activeTaskCount && 'motion-reduce:animate-none',
|
||||
)}
|
||||
/>
|
||||
{t(($) => $['newKnowledge.tasks'])}
|
||||
{attentionTaskBadge && (
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'flex min-w-4 items-center justify-center rounded px-1 system-2xs-medium',
|
||||
hasTaskError
|
||||
? 'bg-state-destructive-hover text-text-destructive'
|
||||
: 'bg-state-accent-hover text-text-accent',
|
||||
)}
|
||||
>
|
||||
{attentionTaskBadge}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
<span className="sr-only" role="status" aria-live="polite">
|
||||
{tasksLiveStatus}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const DocumentRow = memo(
|
||||
({
|
||||
document,
|
||||
documentHref,
|
||||
formatTimeFromNow,
|
||||
onSelectedChange,
|
||||
readOnlyReasonId,
|
||||
selected,
|
||||
selectionDisabled,
|
||||
source,
|
||||
sourcePending,
|
||||
status,
|
||||
statusPending,
|
||||
}: {
|
||||
document: LogicalDocument
|
||||
documentHref: string
|
||||
formatTimeFromNow: (time: number) => string
|
||||
onSelectedChange: (documentId: string) => void
|
||||
readOnlyReasonId?: string
|
||||
selected: boolean
|
||||
selectionDisabled: boolean
|
||||
source?: string
|
||||
sourcePending: boolean
|
||||
status: DocumentDisplayStatus
|
||||
statusPending: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const titleId = `new-document-${document.id}`
|
||||
const revision = document.activeRevision ?? document.active?.revision
|
||||
const updatedTime = Date.parse(document.updatedAt)
|
||||
|
||||
return (
|
||||
<tr
|
||||
className={cn(
|
||||
'border-t border-divider-subtle',
|
||||
status === 'disabled' && !statusPending && 'opacity-60',
|
||||
)}
|
||||
>
|
||||
<td className="w-10 py-3 pr-3">
|
||||
<Checkbox
|
||||
checked={selected}
|
||||
disabled={selectionDisabled || status === 'disabled'}
|
||||
aria-describedby={selectionDisabled ? readOnlyReasonId : undefined}
|
||||
aria-labelledby={titleId}
|
||||
onCheckedChange={() => onSelectedChange(document.id)}
|
||||
/>
|
||||
</td>
|
||||
<td className="min-w-0 py-3 pr-3 sm:min-w-72 sm:pr-6">
|
||||
<div className="flex min-w-0 items-center gap-2.5">
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-file-text-line size-[18px] shrink-0 text-text-tertiary"
|
||||
/>
|
||||
<Link
|
||||
id={titleId}
|
||||
className="truncate rounded system-xs-medium text-text-primary hover:text-text-accent focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden"
|
||||
href={documentHref}
|
||||
>
|
||||
{document.title}
|
||||
</Link>
|
||||
{revision !== undefined && (
|
||||
<span className="shrink-0 rounded border border-divider-regular px-1 system-2xs-medium text-text-tertiary">
|
||||
v{revision}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="hidden w-52 py-3 pr-6 system-xs-regular text-text-secondary lg:table-cell">
|
||||
{sourcePending ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<span
|
||||
aria-hidden
|
||||
className="h-3 w-24 animate-pulse rounded bg-background-section motion-reduce:animate-none"
|
||||
/>
|
||||
<span className="sr-only">{tCommon(($) => $.loading)}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="block truncate">
|
||||
{source ?? t(($) => $['newKnowledge.manualUpload'])}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="w-24 py-3 pr-2 sm:w-56 sm:pr-6">
|
||||
{statusPending ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<span
|
||||
aria-hidden
|
||||
className="h-3 w-20 animate-pulse rounded bg-background-section motion-reduce:animate-none"
|
||||
/>
|
||||
<span className="sr-only">{tCommon(($) => $.loading)}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5 system-xs-regular',
|
||||
status === 'failed' ? 'text-text-destructive' : 'text-text-secondary',
|
||||
)}
|
||||
>
|
||||
<span aria-hidden className={cn('size-3.5', statusIconClass[status])} />
|
||||
{t(($) => $[`newKnowledge.documentStatus.${status}`])}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="hidden w-40 py-3 pr-6 system-xs-regular text-text-tertiary lg:table-cell">
|
||||
{Number.isNaN(updatedTime) ? document.updatedAt : formatTimeFromNow(updatedTime)}
|
||||
</td>
|
||||
<td className="w-10 py-3 text-right">
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t(($) => $['newKnowledge.documentActions'], { name: document.title })}
|
||||
className="flex size-7 items-center justify-center rounded-md text-text-tertiary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-44">
|
||||
<DropdownMenuItem
|
||||
className="gap-2 px-3"
|
||||
onClick={() => toast.info(t(($) => $['newKnowledge.documentActionsUnavailable']))}
|
||||
>
|
||||
<span aria-hidden className="i-ri-download-line size-4" />
|
||||
{t(($) => $['newKnowledge.downloadDocuments'])}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
className="gap-2 px-3"
|
||||
onClick={() => toast.info(t(($) => $['newKnowledge.documentActionsUnavailable']))}
|
||||
>
|
||||
<span aria-hidden className="i-ri-delete-bin-line size-4" />
|
||||
{t(($) => $['newKnowledge.deleteDocuments'])}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export function DocumentsEmpty({
|
||||
activeTaskCount,
|
||||
attentionTaskBadge,
|
||||
canEdit,
|
||||
hasTaskError,
|
||||
onAddDocument,
|
||||
onDropFiles,
|
||||
onOpenTasks,
|
||||
readOnlyReasonId,
|
||||
tasksButtonLabel,
|
||||
tasksLiveStatus,
|
||||
uploading,
|
||||
}: {
|
||||
activeTaskCount: number
|
||||
attentionTaskBadge?: string
|
||||
canEdit: boolean
|
||||
hasTaskError: boolean
|
||||
onAddDocument: () => void
|
||||
onDropFiles: (files: File[]) => void
|
||||
onOpenTasks: () => void
|
||||
readOnlyReasonId?: string
|
||||
tasksButtonLabel: string
|
||||
tasksLiveStatus: string
|
||||
uploading: boolean
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
return (
|
||||
<div
|
||||
className="flex min-h-96 flex-1 flex-col items-center justify-center px-6 text-center"
|
||||
onDragOver={(event) => {
|
||||
event.preventDefault()
|
||||
event.dataTransfer.dropEffect = canEdit ? 'copy' : 'none'
|
||||
}}
|
||||
onDrop={(event) => {
|
||||
event.preventDefault()
|
||||
if (canEdit) onDropFiles([...event.dataTransfer.files])
|
||||
}}
|
||||
>
|
||||
<span className="flex size-12 items-center justify-center rounded-xl bg-background-section text-text-accent">
|
||||
<span aria-hidden className="i-ri-file-text-fill size-6" />
|
||||
</span>
|
||||
<h2 className="mt-4 system-md-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.documentsEmptyTitle'])}
|
||||
</h2>
|
||||
<p className="mt-2 max-w-lg system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.documentsEmptyDescription'])}
|
||||
</p>
|
||||
<Button
|
||||
className="mt-4"
|
||||
variant="primary"
|
||||
aria-busy={uploading}
|
||||
disabled={!canEdit}
|
||||
loading={uploading}
|
||||
aria-describedby={!canEdit ? readOnlyReasonId : undefined}
|
||||
onClick={onAddDocument}
|
||||
>
|
||||
<span aria-hidden className="i-ri-add-line size-4" />
|
||||
{t(($) => $['newKnowledge.addDocument'])}
|
||||
</Button>
|
||||
{canEdit && (
|
||||
<p className="mt-2 system-2xs-regular text-text-quaternary">
|
||||
{t(($) => $['newKnowledge.documentsDropHint'])}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 flex flex-wrap items-center justify-center gap-2">
|
||||
<TaskTrigger
|
||||
activeTaskCount={activeTaskCount}
|
||||
attentionTaskBadge={attentionTaskBadge}
|
||||
hasTaskError={hasTaskError}
|
||||
onOpenTasks={onOpenTasks}
|
||||
tasksButtonLabel={tasksButtonLabel}
|
||||
tasksLiveStatus={tasksLiveStatus}
|
||||
/>
|
||||
<Button onClick={() => toast.info(t(($) => $['newKnowledge.filtersUnavailable']))}>
|
||||
<span aria-hidden className="i-ri-price-tag-3-line size-4" />
|
||||
{t(($) => $['newKnowledge.metadata'])}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DocumentsList({
|
||||
activeTaskCount,
|
||||
allSelected,
|
||||
attentionTaskBadge,
|
||||
canEdit,
|
||||
completingResults,
|
||||
documents,
|
||||
filter,
|
||||
getDocumentHref,
|
||||
hasNextPage,
|
||||
hasSelectableDocuments,
|
||||
hasTaskError,
|
||||
isFetchNextPageError,
|
||||
isFetchingNextDocumentPage,
|
||||
isFetchingNextPage,
|
||||
onAddDocument,
|
||||
onFilterChange,
|
||||
onLoadMore,
|
||||
onOpenTasks,
|
||||
onSearchChange,
|
||||
onSelectAll,
|
||||
onSelectDocument,
|
||||
readOnlyReasonId,
|
||||
resultsIncomplete,
|
||||
search,
|
||||
selectionDisabled,
|
||||
selectedDocumentIds,
|
||||
someSelected,
|
||||
sourcesPending,
|
||||
sourceNames,
|
||||
statusPending,
|
||||
statuses,
|
||||
tasksPending,
|
||||
tasksButtonLabel,
|
||||
tasksLiveStatus,
|
||||
uploading,
|
||||
}: {
|
||||
activeTaskCount: number
|
||||
allSelected: boolean
|
||||
attentionTaskBadge?: string
|
||||
canEdit: boolean
|
||||
completingResults: boolean
|
||||
documents: LogicalDocument[]
|
||||
filter: DocumentFilter
|
||||
getDocumentHref: (documentId: string) => string
|
||||
hasNextPage: boolean
|
||||
hasSelectableDocuments: boolean
|
||||
hasTaskError: boolean
|
||||
isFetchNextPageError: boolean
|
||||
isFetchingNextDocumentPage: boolean
|
||||
isFetchingNextPage: boolean
|
||||
onAddDocument: () => void
|
||||
onFilterChange: (filter: DocumentFilter) => void
|
||||
onLoadMore: () => void
|
||||
onOpenTasks: () => void
|
||||
onSearchChange: (search: string) => void
|
||||
onSelectAll: () => void
|
||||
onSelectDocument: (documentId: string) => void
|
||||
readOnlyReasonId?: string
|
||||
resultsIncomplete: boolean
|
||||
search: string
|
||||
selectionDisabled: boolean
|
||||
selectedDocumentIds: Set<string>
|
||||
someSelected: boolean
|
||||
sourcesPending: boolean
|
||||
sourceNames: Map<string, string>
|
||||
statusPending: boolean
|
||||
statuses: Map<string, DocumentDisplayStatus>
|
||||
tasksPending: boolean
|
||||
tasksButtonLabel: string
|
||||
tasksLiveStatus: string
|
||||
uploading: boolean
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const [visibleDocumentLimit, setVisibleDocumentLimit] = useState(DOCUMENT_RENDER_BATCH_SIZE)
|
||||
const loadMoreButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const resultsContainerRef = useRef<HTMLDivElement>(null)
|
||||
const restoreLoadMoreFocusRef = useRef(false)
|
||||
const filterActive = filter !== 'all' || Boolean(search.trim())
|
||||
const visibleDocuments = documents.slice(0, visibleDocumentLimit)
|
||||
const hasHiddenDocuments = visibleDocuments.length < documents.length
|
||||
|
||||
useEffect(() => {
|
||||
if (isFetchingNextPage || !restoreLoadMoreFocusRef.current) return
|
||||
if (hasHiddenDocuments || hasNextPage || isFetchNextPageError) {
|
||||
loadMoreButtonRef.current?.focus()
|
||||
return
|
||||
}
|
||||
restoreLoadMoreFocusRef.current = false
|
||||
resultsContainerRef.current?.focus()
|
||||
}, [hasHiddenDocuments, hasNextPage, isFetchNextPageError, isFetchingNextPage])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-5 flex flex-col gap-2 xl:flex-row xl:items-center">
|
||||
<label className="sr-only" htmlFor="document-filter">
|
||||
{t(($) => $['newKnowledge.documentFilterLabel'])}
|
||||
</label>
|
||||
<select
|
||||
id="document-filter"
|
||||
disabled={statusPending}
|
||||
value={filter}
|
||||
onChange={(event) => {
|
||||
setVisibleDocumentLimit(DOCUMENT_RENDER_BATCH_SIZE)
|
||||
onFilterChange(event.target.value as DocumentFilter)
|
||||
}}
|
||||
className="h-8 rounded-lg border-0 bg-components-input-bg-normal px-3 system-xs-regular text-text-secondary outline-hidden focus:ring-2 focus:ring-state-accent-solid xl:w-36"
|
||||
>
|
||||
<option value="all">{t(($) => $['newKnowledge.allDocumentStatuses'])}</option>
|
||||
{(['ready', 'queued', 'processing', 'failed', 'disabled'] as const).map((status) => (
|
||||
<option key={status} value={status}>
|
||||
{t(($) => $[`newKnowledge.documentStatus.${status}`])}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="relative xl:w-60">
|
||||
<span className="sr-only">{t(($) => $['newKnowledge.searchDocuments'])}</span>
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute top-2 left-2.5 i-ri-search-line size-4 text-text-quaternary"
|
||||
/>
|
||||
<input
|
||||
type="search"
|
||||
value={search}
|
||||
onChange={(event) => {
|
||||
setVisibleDocumentLimit(DOCUMENT_RENDER_BATCH_SIZE)
|
||||
onSearchChange(event.target.value)
|
||||
}}
|
||||
placeholder={t(($) => $['newKnowledge.searchDocuments'])}
|
||||
className="h-8 w-full rounded-lg border-0 bg-components-input-bg-normal pr-3 pl-8 system-xs-regular text-text-primary outline-hidden placeholder:text-text-quaternary focus:ring-2 focus:ring-state-accent-solid"
|
||||
/>
|
||||
</label>
|
||||
<span className="min-w-0 flex-1" />
|
||||
<TaskTrigger
|
||||
activeTaskCount={activeTaskCount}
|
||||
attentionTaskBadge={attentionTaskBadge}
|
||||
hasTaskError={hasTaskError}
|
||||
onOpenTasks={onOpenTasks}
|
||||
tasksButtonLabel={tasksButtonLabel}
|
||||
tasksLiveStatus={tasksLiveStatus}
|
||||
/>
|
||||
<Button onClick={() => toast.info(t(($) => $['newKnowledge.filtersUnavailable']))}>
|
||||
<span aria-hidden className="i-ri-price-tag-3-line size-4" />
|
||||
{t(($) => $['newKnowledge.metadata'])}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
aria-busy={uploading}
|
||||
disabled={!canEdit}
|
||||
loading={uploading}
|
||||
aria-describedby={!canEdit ? readOnlyReasonId : undefined}
|
||||
onClick={onAddDocument}
|
||||
>
|
||||
<span aria-hidden className="i-ri-add-line size-4" />
|
||||
{t(($) => $['newKnowledge.addDocument'])}
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
ref={resultsContainerRef}
|
||||
aria-labelledby="new-knowledge-documents-title"
|
||||
aria-busy={completingResults || isFetchingNextPage || sourcesPending || tasksPending}
|
||||
className="mt-4 overflow-x-auto rounded-lg outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
role="region"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<table className="w-full table-fixed border-collapse text-left lg:min-w-[900px] lg:table-auto">
|
||||
<thead className="system-2xs-medium text-text-tertiary uppercase">
|
||||
<tr>
|
||||
<th className="pb-2 font-medium">
|
||||
<Checkbox
|
||||
checked={allSelected}
|
||||
indeterminate={someSelected && !allSelected}
|
||||
disabled={!canEdit || selectionDisabled || !hasSelectableDocuments}
|
||||
aria-describedby={
|
||||
!canEdit
|
||||
? readOnlyReasonId
|
||||
: selectionDisabled && resultsIncomplete
|
||||
? PARTIAL_RESULTS_DESCRIPTION_ID
|
||||
: undefined
|
||||
}
|
||||
aria-label={t(($) => $['newKnowledge.selectAllDocuments'])}
|
||||
onCheckedChange={onSelectAll}
|
||||
/>
|
||||
</th>
|
||||
<th className="pb-2 font-medium">{t(($) => $['newKnowledge.documentColumn'])}</th>
|
||||
<th className="hidden pb-2 font-medium lg:table-cell">
|
||||
{t(($) => $['newKnowledge.sourceColumn'])}
|
||||
</th>
|
||||
<th className="pb-2 font-medium">{t(($) => $['newKnowledge.statusColumn'])}</th>
|
||||
<th className="hidden pb-2 font-medium lg:table-cell">
|
||||
{t(($) => $['newKnowledge.updatedColumn'])}
|
||||
</th>
|
||||
<th aria-label={t(($) => $['newKnowledge.actionsColumn'])} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{visibleDocuments.map((document) => (
|
||||
<DocumentRow
|
||||
key={document.id}
|
||||
document={document}
|
||||
documentHref={getDocumentHref(document.id)}
|
||||
formatTimeFromNow={formatTimeFromNow}
|
||||
onSelectedChange={onSelectDocument}
|
||||
readOnlyReasonId={
|
||||
!canEdit
|
||||
? readOnlyReasonId
|
||||
: selectionDisabled && resultsIncomplete
|
||||
? PARTIAL_RESULTS_DESCRIPTION_ID
|
||||
: undefined
|
||||
}
|
||||
selected={selectedDocumentIds.has(document.id)}
|
||||
selectionDisabled={!canEdit || selectionDisabled}
|
||||
source={
|
||||
(document.sourceId && sourceNames.get(document.sourceId)) ?? sourceName(document)
|
||||
}
|
||||
sourcePending={Boolean(
|
||||
sourcesPending && document.sourceId && !sourceNames.has(document.sourceId),
|
||||
)}
|
||||
status={statuses.get(document.id) ?? 'queued'}
|
||||
statusPending={Boolean(
|
||||
tasksPending ||
|
||||
(statusPending && document.sourceId && !sourceNames.has(document.sourceId)),
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{!documents.length && !completingResults && !isFetchNextPageError && !resultsIncomplete && (
|
||||
<p
|
||||
aria-live="polite"
|
||||
className="py-16 text-center body-sm-regular text-text-tertiary"
|
||||
role="status"
|
||||
>
|
||||
{t(($) => $['newKnowledge.noMatchingDocuments'])}
|
||||
</p>
|
||||
)}
|
||||
{resultsIncomplete && (
|
||||
<p
|
||||
id={PARTIAL_RESULTS_DESCRIPTION_ID}
|
||||
aria-live="polite"
|
||||
className={cn(
|
||||
'text-center body-sm-regular text-text-tertiary',
|
||||
completingResults || isFetchNextPageError
|
||||
? 'sr-only'
|
||||
: documents.length
|
||||
? 'py-4'
|
||||
: 'py-16',
|
||||
)}
|
||||
role="status"
|
||||
>
|
||||
{t(($) => $['newKnowledge.partialDocumentResults'])}
|
||||
</p>
|
||||
)}
|
||||
{completingResults && (
|
||||
<div className="flex min-h-32 items-center justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-3 flex items-center gap-1.5 system-xs-regular text-text-tertiary">
|
||||
<span aria-hidden className="i-ri-information-2-line size-3.5" />
|
||||
{t(($) => $['newKnowledge.lastReadyRevisionHint'])}
|
||||
</p>
|
||||
{hasHiddenDocuments ? (
|
||||
<div className="mt-5 flex justify-center">
|
||||
<Button
|
||||
ref={loadMoreButtonRef}
|
||||
onBlur={() => {
|
||||
restoreLoadMoreFocusRef.current = false
|
||||
}}
|
||||
onClick={() => {
|
||||
restoreLoadMoreFocusRef.current = document.activeElement === loadMoreButtonRef.current
|
||||
setVisibleDocumentLimit((current) => current + DOCUMENT_RENDER_BATCH_SIZE)
|
||||
}}
|
||||
>
|
||||
{t(($) => $['newKnowledge.loadMore'])}
|
||||
</Button>
|
||||
</div>
|
||||
) : isFetchNextPageError ? (
|
||||
<div className="mt-5 flex items-center justify-center gap-3" role="alert">
|
||||
<span className="system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.documentsErrorDescription'])}
|
||||
</span>
|
||||
<Button
|
||||
ref={loadMoreButtonRef}
|
||||
aria-label={`${tCommon(($) => $['operation.retry'])} · ${t(($) => $['newKnowledge.documentsErrorDescription'])}`}
|
||||
aria-busy={isFetchingNextDocumentPage}
|
||||
loading={isFetchingNextDocumentPage}
|
||||
onBlur={(event) => {
|
||||
if (event.relatedTarget) restoreLoadMoreFocusRef.current = false
|
||||
}}
|
||||
onClick={() => {
|
||||
restoreLoadMoreFocusRef.current = document.activeElement === loadMoreButtonRef.current
|
||||
onLoadMore()
|
||||
}}
|
||||
>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
) : hasNextPage && (!filterActive || !completingResults) ? (
|
||||
<div className="mt-5 flex justify-center">
|
||||
<Button
|
||||
ref={loadMoreButtonRef}
|
||||
aria-busy={isFetchingNextPage}
|
||||
loading={isFetchingNextPage}
|
||||
onBlur={(event) => {
|
||||
if (event.relatedTarget) restoreLoadMoreFocusRef.current = false
|
||||
}}
|
||||
onClick={() => {
|
||||
restoreLoadMoreFocusRef.current = document.activeElement === loadMoreButtonRef.current
|
||||
onLoadMore()
|
||||
}}
|
||||
>
|
||||
{t(($) => $['newKnowledge.loadMore'])}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function DocumentBulkActions({
|
||||
disabled,
|
||||
disabledReason,
|
||||
onClear,
|
||||
onBlurCapture,
|
||||
onFocusCapture,
|
||||
onReindex,
|
||||
reindexing,
|
||||
selectedCount,
|
||||
}: {
|
||||
disabled: boolean
|
||||
disabledReason?: string
|
||||
onClear: () => void
|
||||
onBlurCapture: FocusEventHandler<HTMLDivElement>
|
||||
onFocusCapture: FocusEventHandler<HTMLDivElement>
|
||||
onReindex: () => void
|
||||
reindexing: boolean
|
||||
selectedCount: number
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
return (
|
||||
<div className="pointer-events-none fixed inset-x-0 bottom-[calc(1.75rem+env(safe-area-inset-bottom,0px))] z-20 flex justify-center pr-[calc(1rem+env(safe-area-inset-right,0px))] pl-[calc(1rem+env(safe-area-inset-left,0px))]">
|
||||
<div
|
||||
aria-label={t(($) => $['newKnowledge.bulkDocumentActions'])}
|
||||
className="pointer-events-auto flex max-w-full min-w-0 items-center gap-2 overflow-x-auto rounded-[14px] border border-divider-subtle bg-components-panel-bg px-3 py-2.5 shadow-xl"
|
||||
role="group"
|
||||
onBlurCapture={onBlurCapture}
|
||||
onFocusCapture={onFocusCapture}
|
||||
>
|
||||
<Button
|
||||
aria-describedby={disabled ? 'document-reindex-unavailable' : undefined}
|
||||
aria-busy={reindexing}
|
||||
className="shrink-0"
|
||||
disabled={disabled}
|
||||
loading={reindexing}
|
||||
size="small"
|
||||
onClick={onReindex}
|
||||
>
|
||||
{t(($) => $['newKnowledge.reindexDocuments'])}
|
||||
</Button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t(($) => $['newKnowledge.clearDocumentSelection'])}
|
||||
className="flex size-7 shrink-0 items-center justify-center rounded-md text-text-tertiary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
onClick={onClear}
|
||||
>
|
||||
<span aria-hidden className="i-ri-close-line size-4" />
|
||||
</button>
|
||||
<span className="shrink-0 px-1 system-xs-medium text-text-primary">
|
||||
{t(($) => $['newKnowledge.documentsSelected'], { count: selectedCount })}
|
||||
</span>
|
||||
{disabled && disabledReason && (
|
||||
<span
|
||||
id="document-reindex-unavailable"
|
||||
className="max-w-44 shrink-0 system-2xs-regular text-text-tertiary"
|
||||
role="status"
|
||||
>
|
||||
{t(($) => $['newKnowledge.reindexDocuments'])}
|
||||
{' · '}
|
||||
{disabledReason}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
id="document-actions-unavailable"
|
||||
className="max-w-44 shrink-0 system-2xs-regular text-text-tertiary"
|
||||
>
|
||||
{t(($) => $['newKnowledge.downloadDocuments'])}
|
||||
{' / '}
|
||||
{t(($) => $['newKnowledge.deleteDocuments'])}
|
||||
{' · '}
|
||||
{t(($) => $['cornerLabel.unavailable'])}
|
||||
</span>
|
||||
<Button
|
||||
aria-describedby="document-actions-unavailable"
|
||||
className="shrink-0"
|
||||
size="small"
|
||||
disabled
|
||||
>
|
||||
{t(($) => $['newKnowledge.downloadDocuments'])}
|
||||
</Button>
|
||||
<Button
|
||||
aria-describedby="document-actions-unavailable"
|
||||
className="shrink-0"
|
||||
size="small"
|
||||
disabled
|
||||
>
|
||||
{t(($) => $['newKnowledge.deleteDocuments'])}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type {
|
||||
DocumentProcessingTask,
|
||||
LogicalDocument,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
|
||||
export type DocumentDisplayStatus = 'ready' | 'queued' | 'processing' | 'failed' | 'disabled'
|
||||
|
||||
export const ACTIVE_TASK_STATES = new Set<DocumentProcessingTask['state']>([
|
||||
'dispatch_pending',
|
||||
'queued',
|
||||
'running',
|
||||
'retry_wait',
|
||||
])
|
||||
|
||||
export const ATTENTION_TASK_STATES = new Set<DocumentProcessingTask['state']>([
|
||||
...ACTIVE_TASK_STATES,
|
||||
'failed',
|
||||
])
|
||||
|
||||
function rfc3339Parts(value: string) {
|
||||
const match = /^(.*:\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})$/.exec(value)
|
||||
if (!match) return
|
||||
const epochSecond = Date.parse(`${match[1]}${match[3]}`)
|
||||
if (Number.isNaN(epochSecond)) return
|
||||
return { epochSecond, fraction: match[2] ?? '' }
|
||||
}
|
||||
|
||||
export function taskVersionIsAfter(candidate: string, baseline: string) {
|
||||
const candidateParts = rfc3339Parts(candidate)
|
||||
const baselineParts = rfc3339Parts(baseline)
|
||||
if (candidateParts && baselineParts) {
|
||||
if (candidateParts.epochSecond !== baselineParts.epochSecond)
|
||||
return candidateParts.epochSecond > baselineParts.epochSecond
|
||||
const precision = Math.max(candidateParts.fraction.length, baselineParts.fraction.length)
|
||||
return (
|
||||
candidateParts.fraction.padEnd(precision, '0') > baselineParts.fraction.padEnd(precision, '0')
|
||||
)
|
||||
}
|
||||
return candidate.localeCompare(baseline) > 0
|
||||
}
|
||||
|
||||
export function sourceName(document: LogicalDocument) {
|
||||
const value = document.userMetadata.sourceName
|
||||
return typeof value === 'string' && value.trim() ? value : undefined
|
||||
}
|
||||
|
||||
export function documentDisabled(document: LogicalDocument, sourceDisabled = false) {
|
||||
return document.status === 'deleting' || sourceDisabled
|
||||
}
|
||||
|
||||
export function newestTaskByDocument(tasks: DocumentProcessingTask[]) {
|
||||
const result = new Map<string, DocumentProcessingTask>()
|
||||
for (const task of tasks) {
|
||||
const current = result.get(task.documentId)
|
||||
if (
|
||||
!current ||
|
||||
task.documentRevision > current.documentRevision ||
|
||||
(task.documentRevision === current.documentRevision &&
|
||||
(taskVersionIsAfter(task.updatedAt, current.updatedAt) ||
|
||||
(task.updatedAt === current.updatedAt && task.id > current.id)))
|
||||
)
|
||||
result.set(task.documentId, task)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function documentDisplayStatus(
|
||||
document: LogicalDocument,
|
||||
task?: DocumentProcessingTask,
|
||||
sourceDisabled = false,
|
||||
): DocumentDisplayStatus {
|
||||
if (documentDisabled(document, sourceDisabled)) return 'disabled'
|
||||
|
||||
const activeRevision = document.activeRevision ?? document.active?.revision ?? 0
|
||||
const taskRepresentsLatestRevision = task && task.documentRevision >= activeRevision
|
||||
if (taskRepresentsLatestRevision) {
|
||||
if (task.state === 'running') return 'processing'
|
||||
if (task.state === 'dispatch_pending' || task.state === 'queued' || task.state === 'retry_wait')
|
||||
return 'queued'
|
||||
if (task.state === 'failed') return 'failed'
|
||||
if (task.state === 'canceled' && !document.active) return 'failed'
|
||||
}
|
||||
|
||||
if (document.status === 'failed') return 'failed'
|
||||
if (document.status === 'pending') return 'queued'
|
||||
return 'ready'
|
||||
}
|
||||
|
||||
export function taskNeedsAttention(task: DocumentProcessingTask) {
|
||||
return ATTENTION_TASK_STATES.has(task.state)
|
||||
}
|
||||
|
||||
export function taskIsActive(task: DocumentProcessingTask) {
|
||||
return ACTIVE_TASK_STATES.has(task.state)
|
||||
}
|
||||
|
||||
export function taskCanRetry(task: DocumentProcessingTask) {
|
||||
return task.state === 'failed'
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
LogicalDocument,
|
||||
LogicalDocumentRevision,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { DocumentChunkDetail } from './document-chunk-detail'
|
||||
import { DocumentChunkTreePanel } from './document-chunk-tree'
|
||||
import { buildDocumentChunkTree } from './document-detail-model'
|
||||
import { documentChunksQueryOptions } from './document-detail-queries'
|
||||
|
||||
export function DocumentRevisionContent({
|
||||
document,
|
||||
documentId,
|
||||
effectiveRevision,
|
||||
knowledgeSpaceId,
|
||||
locale,
|
||||
revision,
|
||||
revisionHistoryError,
|
||||
revisionHistoryPending,
|
||||
retryRevisionHistory,
|
||||
}: {
|
||||
document: LogicalDocument
|
||||
documentId: string
|
||||
effectiveRevision?: number
|
||||
knowledgeSpaceId: string
|
||||
locale: string
|
||||
revision?: Exclude<LogicalDocumentRevision, null>
|
||||
revisionHistoryError: boolean
|
||||
revisionHistoryPending: boolean
|
||||
retryRevisionHistory: () => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const [selectedChunkId, setSelectedChunkId] = useState<string>()
|
||||
const chunksQueryOptions = useMemo(
|
||||
() => documentChunksQueryOptions({ documentId, effectiveRevision, knowledgeSpaceId }),
|
||||
[documentId, effectiveRevision, knowledgeSpaceId],
|
||||
)
|
||||
const chunksQuery = useInfiniteQuery(chunksQueryOptions)
|
||||
const chunks = useMemo(
|
||||
() =>
|
||||
[...(chunksQuery.data?.pages.flatMap((page) => page.items) ?? [])].sort(
|
||||
(left, right) => left.ordinal - right.ordinal || left.id.localeCompare(right.id),
|
||||
),
|
||||
[chunksQuery.data],
|
||||
)
|
||||
const tree = useMemo(() => buildDocumentChunkTree(chunks), [chunks])
|
||||
const selectedChunk =
|
||||
(selectedChunkId ? tree.byId.get(selectedChunkId)?.chunk : undefined) ?? tree.roots[0]?.chunk
|
||||
const {
|
||||
fetchNextPage: fetchNextChunkPage,
|
||||
hasNextPage: hasNextChunkPage,
|
||||
isFetchNextPageError: isFetchNextChunkPageError,
|
||||
isFetchingNextPage: isFetchingNextChunkPage,
|
||||
} = chunksQuery
|
||||
|
||||
useEffect(() => {
|
||||
if (hasNextChunkPage && !isFetchingNextChunkPage && !isFetchNextChunkPageError)
|
||||
void fetchNextChunkPage()
|
||||
}, [fetchNextChunkPage, hasNextChunkPage, isFetchNextChunkPageError, isFetchingNextChunkPage])
|
||||
|
||||
if (effectiveRevision === undefined && revisionHistoryPending)
|
||||
return (
|
||||
<div className="flex min-h-80 items-center justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
|
||||
if (effectiveRevision === undefined && revisionHistoryError)
|
||||
return (
|
||||
<div className="flex min-h-80 flex-col items-center justify-center px-6 text-center">
|
||||
<span aria-hidden className="i-ri-error-warning-line size-8 text-text-destructive" />
|
||||
<h2 className="mt-3 title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['newKnowledge.documentLoadErrorTitle'])}
|
||||
</h2>
|
||||
<p className="mt-2 max-w-lg body-sm-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.documentLoadErrorDescription'])}
|
||||
</p>
|
||||
<Button className="mt-4" onClick={retryRevisionHistory}>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
if (effectiveRevision === undefined)
|
||||
return (
|
||||
<div className="flex min-h-80 flex-col items-center justify-center text-center">
|
||||
<span aria-hidden className="i-ri-file-warning-line size-8 text-text-tertiary" />
|
||||
<h2 className="mt-3 title-xl-semi-bold text-text-primary">
|
||||
{t(($) => $['newKnowledge.documentRevisionMissingTitle'])}
|
||||
</h2>
|
||||
<p className="mt-2 max-w-lg body-sm-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.documentRevisionMissingDescription'])}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="mt-5 grid min-h-0 flex-1 gap-4 xl:grid-cols-[minmax(12rem,16rem)_minmax(0,1fr)_minmax(13rem,16rem)]">
|
||||
<DocumentChunkTreePanel
|
||||
chunkCount={chunks.length}
|
||||
error={Boolean(chunksQuery.error)}
|
||||
fetchNextPage={chunksQuery.fetchNextPage}
|
||||
hasNextPage={chunksQuery.hasNextPage}
|
||||
isFetchNextPageError={chunksQuery.isFetchNextPageError}
|
||||
isFetchingNextPage={chunksQuery.isFetchingNextPage}
|
||||
isPending={chunksQuery.isPending}
|
||||
onRetry={() => void chunksQuery.refetch()}
|
||||
onSelectChunk={setSelectedChunkId}
|
||||
selectedChunkId={selectedChunk?.id}
|
||||
tree={tree}
|
||||
/>
|
||||
|
||||
<DocumentChunkDetail
|
||||
chunks={chunks}
|
||||
chunksComplete={
|
||||
Boolean(chunksQuery.data) &&
|
||||
!chunksQuery.error &&
|
||||
!chunksQuery.hasNextPage &&
|
||||
!chunksQuery.isFetchingNextPage &&
|
||||
!chunksQuery.isFetchNextPageError
|
||||
}
|
||||
document={document}
|
||||
locale={locale}
|
||||
revision={revision}
|
||||
selectedChunkId={selectedChunk?.id}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@@ -87,6 +88,7 @@ export function KnowledgeSpaceShell({
|
||||
const documentsPath = newKnowledgeDocumentsPath(knowledgeSpaceId)
|
||||
const sourcesActive = pathname === sourcesPath || pathname.startsWith(`${sourcesPath}/`)
|
||||
const documentsActive = pathname === documentsPath || pathname.startsWith(`${documentsPath}/`)
|
||||
const showDeferredPage = () => toast.info(t(($) => $['cornerLabel.unavailable']))
|
||||
const navItemClassName =
|
||||
'flex h-8 shrink-0 items-center gap-2 rounded-lg px-3 system-sm-medium outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
|
||||
@@ -120,7 +122,9 @@ export function KnowledgeSpaceShell({
|
||||
{knowledgeSpaceQuery.data.name}
|
||||
</h1>
|
||||
<p className="mt-0.5 truncate system-2xs-medium-uppercase text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.cardType'])}
|
||||
{t(($) => $['chunkingMode.parentChild'])} ·{' '}
|
||||
{t(($) => $['indexingTechnique.high_quality'])} ·{' '}
|
||||
{t(($) => $['retrieval.semantic_search.title'])}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,6 +133,14 @@ export function KnowledgeSpaceShell({
|
||||
className="flex gap-0.5 overflow-x-auto px-2 py-1 sm:flex-1 sm:flex-col"
|
||||
aria-label={knowledgeSpaceQuery.data.name}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(navItemClassName, 'text-text-secondary')}
|
||||
onClick={showDeferredPage}
|
||||
>
|
||||
<span aria-hidden className="i-ri-layout-grid-line size-4" />
|
||||
{t(($) => $['newKnowledge.overview'])}
|
||||
</button>
|
||||
<Link
|
||||
href={sourcesPath}
|
||||
aria-current={sourcesActive ? 'page' : undefined}
|
||||
@@ -151,6 +163,38 @@ export function KnowledgeSpaceShell({
|
||||
<span aria-hidden className="i-ri-file-text-line size-4" />
|
||||
{t(($) => $['newKnowledge.documents'])}
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(navItemClassName, 'text-text-secondary')}
|
||||
onClick={showDeferredPage}
|
||||
>
|
||||
<span aria-hidden className="i-ri-test-tube-line size-4" />
|
||||
{tCommon(($) => $['datasetMenus.hitTesting'])}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(navItemClassName, 'text-text-secondary')}
|
||||
onClick={showDeferredPage}
|
||||
>
|
||||
<span aria-hidden className="i-ri-bar-chart-box-line size-4" />
|
||||
{t(($) => $['newKnowledge.quality'])}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(navItemClassName, 'text-text-secondary')}
|
||||
onClick={showDeferredPage}
|
||||
>
|
||||
<span aria-hidden className="i-ri-settings-3-line size-4" />
|
||||
{tCommon(($) => $['datasetMenus.settings'])}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(navItemClassName, 'text-text-secondary')}
|
||||
onClick={showDeferredPage}
|
||||
>
|
||||
<span aria-hidden className="i-ri-code-box-line size-4" />
|
||||
{t(($) => $['newKnowledge.apiAgentAccess'])}
|
||||
</button>
|
||||
</nav>
|
||||
</aside>
|
||||
<section className="min-h-0 min-w-0 flex-1 overflow-auto rounded-lg bg-components-panel-bg shadow-xs">
|
||||
|
||||
@@ -0,0 +1,636 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
DocumentProcessingTask,
|
||||
LogicalDocument,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import type { TaskProgressStore } from './task-progress-store'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerBackdrop,
|
||||
DrawerCloseButton,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerPopup,
|
||||
DrawerPortal,
|
||||
DrawerTitle,
|
||||
DrawerViewport,
|
||||
} from '@langgenius/dify-ui/drawer'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { taskCanRetry, taskIsActive, taskVersionIsAfter } from './document-model'
|
||||
|
||||
type TaskAction = 'cancel' | 'retry'
|
||||
|
||||
const TASK_DRAWER_LIMIT = 100
|
||||
const noopSubscribe = () => () => undefined
|
||||
|
||||
function taskTime(task: DocumentProcessingTask) {
|
||||
return task.completedAt ?? task.updatedAt
|
||||
}
|
||||
|
||||
function taskLifecycle(task: DocumentProcessingTask) {
|
||||
return `${task.updatedAt}:${task.state}`
|
||||
}
|
||||
|
||||
function responseStatus(error: unknown): number | undefined {
|
||||
if (error instanceof Response) return error.status
|
||||
if (error && typeof error === 'object' && 'status' in error)
|
||||
return typeof error.status === 'number' ? error.status : undefined
|
||||
if (error && typeof error === 'object' && 'data' in error) {
|
||||
const data = error.data
|
||||
if (data && typeof data === 'object' && 'status' in data)
|
||||
return typeof data.status === 'number' ? data.status : undefined
|
||||
}
|
||||
}
|
||||
|
||||
function compareTaskRecency(left: DocumentProcessingTask, right: DocumentProcessingTask) {
|
||||
if (taskVersionIsAfter(left.updatedAt, right.updatedAt)) return -1
|
||||
if (taskVersionIsAfter(right.updatedAt, left.updatedAt)) return 1
|
||||
return right.id.localeCompare(left.id)
|
||||
}
|
||||
|
||||
function newestTasks(
|
||||
tasks: DocumentProcessingTask[],
|
||||
limit: number,
|
||||
predicate: (task: DocumentProcessingTask) => boolean,
|
||||
) {
|
||||
const selected: DocumentProcessingTask[] = []
|
||||
for (const task of tasks) {
|
||||
if (!predicate(task)) continue
|
||||
let low = 0
|
||||
let high = selected.length
|
||||
while (low < high) {
|
||||
const middle = Math.floor((low + high) / 2)
|
||||
if (compareTaskRecency(task, selected[middle]!) < 0) high = middle
|
||||
else low = middle + 1
|
||||
}
|
||||
selected.splice(low, 0, task)
|
||||
if (selected.length > limit) selected.pop()
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
export function ProcessingTasksDrawer({
|
||||
canEdit,
|
||||
actionResultsValid,
|
||||
documentQueryError,
|
||||
documentQueryFetching,
|
||||
documents,
|
||||
documentsPending,
|
||||
hasNextDocumentPage,
|
||||
hasNextTaskPage,
|
||||
hasUnresolvedTaskDocuments,
|
||||
isFetchingNextDocumentPage,
|
||||
isFetchingNextTaskPage,
|
||||
knowledgeSpaceId,
|
||||
onLoadMoreDocuments,
|
||||
onLoadMoreTasks,
|
||||
onOpenChange,
|
||||
onRefreshDocumentsAndTasks,
|
||||
onRetryPermissionQuery,
|
||||
onTaskUpdated,
|
||||
onWritePermissionDenied,
|
||||
open,
|
||||
permissionQueryError,
|
||||
permissionQueryFetching,
|
||||
permissionQueryPending,
|
||||
readOnlyReason,
|
||||
taskQueryError,
|
||||
taskQueryFetching,
|
||||
taskQueryPending,
|
||||
tasks,
|
||||
taskProgressStore,
|
||||
onRetryTaskQuery,
|
||||
onRetryDocumentQuery,
|
||||
}: {
|
||||
canEdit: boolean
|
||||
actionResultsValid: boolean
|
||||
documentQueryError: boolean
|
||||
documentQueryFetching: boolean
|
||||
documents: LogicalDocument[]
|
||||
documentsPending: boolean
|
||||
hasNextDocumentPage: boolean
|
||||
hasNextTaskPage: boolean
|
||||
hasUnresolvedTaskDocuments: boolean
|
||||
isFetchingNextDocumentPage: boolean
|
||||
isFetchingNextTaskPage: boolean
|
||||
knowledgeSpaceId: string
|
||||
onLoadMoreDocuments: () => void
|
||||
onLoadMoreTasks: () => void
|
||||
onOpenChange: (open: boolean) => void
|
||||
onRefreshDocumentsAndTasks: () => void
|
||||
onRetryPermissionQuery: () => void
|
||||
onTaskUpdated: (task: DocumentProcessingTask) => void
|
||||
onWritePermissionDenied: () => void
|
||||
open: boolean
|
||||
permissionQueryError: boolean
|
||||
permissionQueryFetching: boolean
|
||||
permissionQueryPending: boolean
|
||||
readOnlyReason?: string
|
||||
taskQueryError: boolean
|
||||
taskQueryFetching: boolean
|
||||
taskQueryPending: boolean
|
||||
tasks: DocumentProcessingTask[]
|
||||
taskProgressStore: TaskProgressStore
|
||||
onRetryDocumentQuery: () => void
|
||||
onRetryTaskQuery: () => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const cancelTask = useMutation(
|
||||
consoleQuery.knowledgeFs.deleteKnowledgeSpacesByIdDocumentsByDocumentIdProcessingTasksByTaskId.mutationOptions(),
|
||||
)
|
||||
const retryTask = useMutation(
|
||||
consoleQuery.knowledgeFs.postKnowledgeSpacesByIdDocumentsByDocumentIdProcessingTasksByTaskIdRetry.mutationOptions(),
|
||||
)
|
||||
const pendingActionsRef = useRef(new Set<string>())
|
||||
const drawerCloseButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const taskQueryRetryButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const documentQueryRetryButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const permissionQueryRetryButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const focusedTaskActionRef = useRef<HTMLButtonElement | null>(null)
|
||||
const loadMoreRequestedRef = useRef(false)
|
||||
const queryRetryFocusRequestedRef = useRef(false)
|
||||
const permissionRetryFocusRequestedRef = useRef(false)
|
||||
const loadMoreButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const openCycleRef = useRef(0)
|
||||
const openRef = useRef(open)
|
||||
const actionResultsValidRef = useRef(actionResultsValid)
|
||||
const previousOpenRef = useRef(open)
|
||||
const [pendingActions, setPendingActions] = useState<Set<string>>(() => new Set())
|
||||
const [actionErrors, setActionErrors] = useState<Record<string, string>>({})
|
||||
const [visibleTaskLimit, setVisibleTaskLimit] = useState(TASK_DRAWER_LIMIT)
|
||||
useSyncExternalStore(
|
||||
open ? taskProgressStore.subscribe : noopSubscribe,
|
||||
taskProgressStore.getSnapshot,
|
||||
taskProgressStore.getSnapshot,
|
||||
)
|
||||
const documentTitles = useMemo(
|
||||
() => new Map(documents.map((document) => [document.id, document.title])),
|
||||
[documents],
|
||||
)
|
||||
const orderedBaseTasks = useMemo(() => {
|
||||
if (!open) return []
|
||||
const reservedLimit = Math.min(TASK_DRAWER_LIMIT / 2, visibleTaskLimit)
|
||||
const retryableTasks = newestTasks(tasks, reservedLimit, taskCanRetry)
|
||||
const activeTasks = newestTasks(tasks, reservedLimit, taskIsActive)
|
||||
const attentionTaskIds = new Set([
|
||||
...retryableTasks.map((task) => task.id),
|
||||
...activeTasks.map((task) => task.id),
|
||||
])
|
||||
const remainingAttentionTasks = newestTasks(
|
||||
tasks,
|
||||
visibleTaskLimit - attentionTaskIds.size,
|
||||
(task) => (taskCanRetry(task) || taskIsActive(task)) && !attentionTaskIds.has(task.id),
|
||||
)
|
||||
for (const task of remainingAttentionTasks) attentionTaskIds.add(task.id)
|
||||
const terminalTasks = newestTasks(
|
||||
tasks,
|
||||
visibleTaskLimit - attentionTaskIds.size,
|
||||
(task) => !attentionTaskIds.has(task.id),
|
||||
)
|
||||
return [...retryableTasks, ...activeTasks, ...remainingAttentionTasks, ...terminalTasks].sort(
|
||||
compareTaskRecency,
|
||||
)
|
||||
}, [open, tasks, visibleTaskLimit])
|
||||
const hasMoreTasks =
|
||||
open &&
|
||||
(tasks.length > orderedBaseTasks.length ||
|
||||
hasNextTaskPage ||
|
||||
(hasUnresolvedTaskDocuments && hasNextDocumentPage))
|
||||
const orderedTasks = orderedBaseTasks.map((task) => {
|
||||
const progress = taskProgressStore.get(task.id)
|
||||
if (!progress || !taskIsActive(task) || taskVersionIsAfter(task.updatedAt, progress.updatedAt))
|
||||
return task
|
||||
return {
|
||||
...task,
|
||||
errorCode: undefined,
|
||||
errorMessage: undefined,
|
||||
...progress,
|
||||
}
|
||||
})
|
||||
const activeActionCount = orderedTasks.filter(taskIsActive).length
|
||||
const retryActionCount = orderedTasks.filter(taskCanRetry).length
|
||||
const taskLifecycles = useMemo(
|
||||
() => new Map(tasks.map((task) => [task.id, taskLifecycle(task)])),
|
||||
[tasks],
|
||||
)
|
||||
const taskLifecycleGenerationsRef = useRef(
|
||||
new Map<string, { generation: number; lifecycle: string }>(),
|
||||
)
|
||||
useLayoutEffect(() => {
|
||||
actionResultsValidRef.current = actionResultsValid
|
||||
const currentTaskIds = new Set(tasks.map((task) => task.id))
|
||||
for (const task of tasks) {
|
||||
const lifecycle = taskLifecycle(task)
|
||||
const previous = taskLifecycleGenerationsRef.current.get(task.id)
|
||||
if (previous?.lifecycle === lifecycle) continue
|
||||
taskLifecycleGenerationsRef.current.set(task.id, {
|
||||
generation: (previous?.generation ?? 0) + 1,
|
||||
lifecycle,
|
||||
})
|
||||
}
|
||||
for (const taskId of taskLifecycleGenerationsRef.current.keys()) {
|
||||
if (!currentTaskIds.has(taskId)) taskLifecycleGenerationsRef.current.delete(taskId)
|
||||
}
|
||||
}, [actionResultsValid, tasks])
|
||||
|
||||
useEffect(() => {
|
||||
openRef.current = open
|
||||
const wasOpen = previousOpenRef.current
|
||||
previousOpenRef.current = open
|
||||
if (!wasOpen || open) return
|
||||
openCycleRef.current += 1
|
||||
loadMoreRequestedRef.current = false
|
||||
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Every committed controlled close resets drawer-local pagination and errors.
|
||||
setVisibleTaskLimit(TASK_DRAWER_LIMIT)
|
||||
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Every committed controlled close starts a fresh action-error cycle.
|
||||
setActionErrors({})
|
||||
}, [open])
|
||||
|
||||
useEffect(() => {
|
||||
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Task lifecycle changes retire action errors from older task versions.
|
||||
setActionErrors((current) => {
|
||||
const staleTaskIds = Object.keys(current).filter(
|
||||
(taskId) => taskLifecycles.get(taskId) !== current[taskId],
|
||||
)
|
||||
if (!staleTaskIds.length) return current
|
||||
const next = { ...current }
|
||||
for (const taskId of staleTaskIds) delete next[taskId]
|
||||
return next
|
||||
})
|
||||
}, [taskLifecycles])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
// oxlint-disable-next-line eslint-react/set-state-in-effect -- A newly opened drawer starts a fresh action-error cycle.
|
||||
setActionErrors({})
|
||||
}, [open])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || hasMoreTasks || !loadMoreRequestedRef.current) return
|
||||
loadMoreRequestedRef.current = false
|
||||
drawerCloseButtonRef.current?.focus()
|
||||
}, [hasMoreTasks, open])
|
||||
|
||||
useEffect(() => {
|
||||
const queryRetryVisible = taskQueryError || (documentQueryError && hasUnresolvedTaskDocuments)
|
||||
if (!open || !queryRetryFocusRequestedRef.current) return
|
||||
if (queryRetryVisible) {
|
||||
if (taskQueryError) taskQueryRetryButtonRef.current?.focus()
|
||||
else documentQueryRetryButtonRef.current?.focus()
|
||||
return
|
||||
}
|
||||
queryRetryFocusRequestedRef.current = false
|
||||
drawerCloseButtonRef.current?.focus()
|
||||
}, [documentQueryError, hasUnresolvedTaskDocuments, open, taskQueryError])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !permissionRetryFocusRequestedRef.current) return
|
||||
if (permissionQueryError) {
|
||||
permissionQueryRetryButtonRef.current?.focus()
|
||||
return
|
||||
}
|
||||
permissionRetryFocusRequestedRef.current = false
|
||||
drawerCloseButtonRef.current?.focus()
|
||||
}, [open, permissionQueryError])
|
||||
|
||||
useEffect(() => {
|
||||
const focusedAction = focusedTaskActionRef.current
|
||||
if (!open || !focusedAction || focusedAction.isConnected) return
|
||||
focusedTaskActionRef.current = null
|
||||
drawerCloseButtonRef.current?.focus()
|
||||
}, [open, orderedTasks])
|
||||
|
||||
const performAction = async (task: DocumentProcessingTask, action: TaskAction) => {
|
||||
if (!canEdit || pendingActionsRef.current.has(task.id)) return
|
||||
pendingActionsRef.current.add(task.id)
|
||||
const actionOpenCycle = openCycleRef.current
|
||||
const actionLifecycleGeneration = taskLifecycleGenerationsRef.current.get(task.id)?.generation
|
||||
const actionFocusTarget = document.activeElement
|
||||
setPendingActions((current) => new Set(current).add(task.id))
|
||||
setActionErrors((current) => {
|
||||
const next = { ...current }
|
||||
delete next[task.id]
|
||||
return next
|
||||
})
|
||||
try {
|
||||
const input = {
|
||||
params: {
|
||||
documentId: task.documentId,
|
||||
id: knowledgeSpaceId,
|
||||
taskId: task.id,
|
||||
},
|
||||
}
|
||||
const updated =
|
||||
action === 'cancel'
|
||||
? await cancelTask.mutateAsync(input)
|
||||
: await retryTask.mutateAsync(input)
|
||||
if (
|
||||
!actionResultsValidRef.current ||
|
||||
taskLifecycleGenerationsRef.current.get(task.id)?.generation !== actionLifecycleGeneration
|
||||
)
|
||||
return
|
||||
onTaskUpdated(updated)
|
||||
setActionErrors((current) => {
|
||||
const next = { ...current }
|
||||
delete next[task.id]
|
||||
return next
|
||||
})
|
||||
if (
|
||||
actionResultsValidRef.current &&
|
||||
openRef.current &&
|
||||
openCycleRef.current === actionOpenCycle &&
|
||||
document.activeElement === actionFocusTarget
|
||||
)
|
||||
drawerCloseButtonRef.current?.focus()
|
||||
} catch (error) {
|
||||
const permissionDenied = responseStatus(error) === 403
|
||||
if (permissionDenied) onWritePermissionDenied()
|
||||
if (
|
||||
!permissionDenied &&
|
||||
actionResultsValidRef.current &&
|
||||
openRef.current &&
|
||||
openCycleRef.current === actionOpenCycle &&
|
||||
taskLifecycleGenerationsRef.current.get(task.id)?.generation === actionLifecycleGeneration
|
||||
)
|
||||
setActionErrors((current) => ({ ...current, [task.id]: taskLifecycle(task) }))
|
||||
} finally {
|
||||
pendingActionsRef.current.delete(task.id)
|
||||
setPendingActions((current) => {
|
||||
const next = new Set(current)
|
||||
next.delete(task.id)
|
||||
return next
|
||||
})
|
||||
if (actionResultsValidRef.current) onRefreshDocumentsAndTasks()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={open}
|
||||
modal
|
||||
swipeDirection="right"
|
||||
onOpenChange={(nextOpen) => {
|
||||
openRef.current = nextOpen
|
||||
onOpenChange(nextOpen)
|
||||
}}
|
||||
>
|
||||
<DrawerPortal>
|
||||
<DrawerBackdrop />
|
||||
<DrawerViewport>
|
||||
<DrawerPopup className="data-[swipe-direction=right]:w-[440px] data-[swipe-direction=right]:max-w-[calc(100vw-1rem)]">
|
||||
<DrawerContent className="flex min-h-0 flex-1 flex-col bg-components-panel-bg p-0 pb-0">
|
||||
<header className="shrink-0 pt-[calc(1.25rem+env(safe-area-inset-top,0px))] pr-[calc(1.5rem+env(safe-area-inset-right,0px))] pb-4 pl-[calc(1.5rem+env(safe-area-inset-left,0px))]">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<DrawerTitle className="system-md-semibold text-text-primary">
|
||||
{t(($) => $['newKnowledge.backgroundTasks'])}
|
||||
</DrawerTitle>
|
||||
<DrawerCloseButton
|
||||
ref={drawerCloseButtonRef}
|
||||
aria-label={tCommon(($) => $['operation.close'])}
|
||||
className="size-6 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
<DrawerDescription className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.backgroundTasksDescription'])}
|
||||
</DrawerDescription>
|
||||
{readOnlyReason && (
|
||||
<p
|
||||
className="mt-2 inline-flex items-center gap-1.5 system-xs-regular text-text-warning"
|
||||
role="status"
|
||||
>
|
||||
<span aria-hidden className="i-ri-lock-line size-3.5" />
|
||||
{readOnlyReason}
|
||||
</p>
|
||||
)}
|
||||
{permissionQueryPending && (
|
||||
<p className="mt-2 system-xs-regular text-text-tertiary" role="status">
|
||||
{t(($) => $['newKnowledge.permission'])}
|
||||
{' · '}
|
||||
{tCommon(($) => $.loading)}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto pr-[calc(1.5rem+env(safe-area-inset-right,0px))] pb-[calc(1.5rem+env(safe-area-inset-bottom,0px))] pl-[calc(1.5rem+env(safe-area-inset-left,0px))]">
|
||||
{permissionQueryError && (
|
||||
<div className="mb-3 rounded-xl border border-divider-regular p-4" role="alert">
|
||||
<p className="system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.permissionLoadFailed'])}
|
||||
</p>
|
||||
<Button
|
||||
ref={permissionQueryRetryButtonRef}
|
||||
aria-label={`${tCommon(($) => $['operation.retry'])} · ${t(($) => $['newKnowledge.permissionLoadFailed'])}`}
|
||||
aria-busy={permissionQueryFetching}
|
||||
className="mt-3"
|
||||
loading={permissionQueryFetching}
|
||||
size="small"
|
||||
onBlur={(event) => {
|
||||
if (event.relatedTarget) permissionRetryFocusRequestedRef.current = false
|
||||
}}
|
||||
onClick={() => {
|
||||
permissionRetryFocusRequestedRef.current = true
|
||||
onRetryPermissionQuery()
|
||||
}}
|
||||
>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{taskQueryError && (
|
||||
<div className="mb-3 rounded-xl border border-divider-regular p-4" role="alert">
|
||||
<p className="system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.tasksErrorDescription'])}
|
||||
</p>
|
||||
<Button
|
||||
ref={taskQueryRetryButtonRef}
|
||||
aria-label={`${tCommon(($) => $['operation.retry'])} · ${t(($) => $['newKnowledge.tasksErrorDescription'])}`}
|
||||
aria-busy={taskQueryFetching}
|
||||
className="mt-3"
|
||||
loading={taskQueryFetching}
|
||||
size="small"
|
||||
onBlur={(event) => {
|
||||
if (event.relatedTarget) queryRetryFocusRequestedRef.current = false
|
||||
}}
|
||||
onClick={() => {
|
||||
queryRetryFocusRequestedRef.current = true
|
||||
onRetryTaskQuery()
|
||||
}}
|
||||
>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{documentQueryError && hasUnresolvedTaskDocuments && (
|
||||
<div className="mb-3 rounded-xl border border-divider-regular p-4" role="alert">
|
||||
<p className="system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.documentsErrorDescription'])}
|
||||
</p>
|
||||
<Button
|
||||
ref={documentQueryRetryButtonRef}
|
||||
aria-label={`${tCommon(($) => $['operation.retry'])} · ${t(($) => $['newKnowledge.documentsErrorDescription'])}`}
|
||||
aria-busy={documentQueryFetching}
|
||||
className="mt-3"
|
||||
loading={documentQueryFetching}
|
||||
size="small"
|
||||
onBlur={(event) => {
|
||||
if (event.relatedTarget) queryRetryFocusRequestedRef.current = false
|
||||
}}
|
||||
onClick={() => {
|
||||
queryRetryFocusRequestedRef.current = true
|
||||
onRetryDocumentQuery()
|
||||
}}
|
||||
>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{taskQueryPending && !orderedTasks.length ? (
|
||||
<div className="flex min-h-40 items-center justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
) : orderedTasks.length ? (
|
||||
<ul className="divide-y divide-divider-subtle">
|
||||
{orderedTasks.map((task) => {
|
||||
const title =
|
||||
documentTitles.get(task.documentId) ??
|
||||
(documentsPending
|
||||
? t(($) => $['newKnowledge.documentColumn'])
|
||||
: task.documentId)
|
||||
const timestamp = Date.parse(
|
||||
taskIsActive(task) ? task.createdAt : taskTime(task),
|
||||
)
|
||||
const taskError = task.errorMessage ?? task.errorCode
|
||||
const actionTarget = `${documentTitles.get(task.documentId) ?? task.documentId} · ${task.id}`
|
||||
return (
|
||||
<li key={task.id} className="flex min-h-[62px] items-center gap-2.5 py-3.5">
|
||||
<span
|
||||
aria-hidden
|
||||
className={
|
||||
task.state === 'failed'
|
||||
? 'i-ri-error-warning-fill size-4 shrink-0 text-text-destructive'
|
||||
: taskIsActive(task)
|
||||
? 'i-ri-loader-2-line size-4 shrink-0 animate-spin text-text-accent motion-reduce:animate-none'
|
||||
: task.state === 'succeeded'
|
||||
? 'i-ri-check-line size-4 shrink-0 text-text-success'
|
||||
: 'i-ri-indeterminate-circle-line size-4 shrink-0 text-text-tertiary'
|
||||
}
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate system-xs-medium text-text-primary">
|
||||
{t(($) => $['newKnowledge.processDocument'], { name: title })}
|
||||
</p>
|
||||
<p className="mt-0.5 truncate system-2xs-regular text-text-tertiary">
|
||||
{t(($) => $[`newKnowledge.processingTaskState.${task.state}`], {
|
||||
progress: task.progressPercent,
|
||||
})}
|
||||
{!Number.isNaN(timestamp) && (
|
||||
<>
|
||||
<span aria-hidden> · </span>
|
||||
{formatTimeFromNow(timestamp)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
{taskError && (
|
||||
<p className="mt-1 system-2xs-regular break-words whitespace-pre-wrap text-text-destructive">
|
||||
{taskError}
|
||||
</p>
|
||||
)}
|
||||
{actionErrors[task.id] === taskLifecycle(task) && (
|
||||
<p
|
||||
className="mt-1 system-2xs-regular text-text-destructive"
|
||||
role="alert"
|
||||
>
|
||||
{t(($) => $['newKnowledge.taskActionFailed'])}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{canEdit && taskIsActive(task) ? (
|
||||
<Button
|
||||
aria-label={
|
||||
activeActionCount > 1
|
||||
? `${t(($) => $['newKnowledge.interruptTask'])} · ${actionTarget}`
|
||||
: undefined
|
||||
}
|
||||
size="small"
|
||||
aria-busy={pendingActions.has(task.id)}
|
||||
disabled={pendingActions.has(task.id)}
|
||||
loading={pendingActions.has(task.id)}
|
||||
onBlur={(event) => {
|
||||
if (event.relatedTarget) focusedTaskActionRef.current = null
|
||||
}}
|
||||
onFocus={(event) => {
|
||||
focusedTaskActionRef.current = event.currentTarget
|
||||
}}
|
||||
onClick={() => void performAction(task, 'cancel')}
|
||||
>
|
||||
{t(($) => $['newKnowledge.interruptTask'])}
|
||||
</Button>
|
||||
) : canEdit && taskCanRetry(task) ? (
|
||||
<Button
|
||||
aria-label={
|
||||
retryActionCount > 1
|
||||
? `${t(($) => $['newKnowledge.retryTask'])} · ${actionTarget}`
|
||||
: undefined
|
||||
}
|
||||
size="small"
|
||||
aria-busy={pendingActions.has(task.id)}
|
||||
disabled={pendingActions.has(task.id)}
|
||||
loading={pendingActions.has(task.id)}
|
||||
onBlur={(event) => {
|
||||
if (event.relatedTarget) focusedTaskActionRef.current = null
|
||||
}}
|
||||
onFocus={(event) => {
|
||||
focusedTaskActionRef.current = event.currentTarget
|
||||
}}
|
||||
onClick={() => void performAction(task, 'retry')}
|
||||
>
|
||||
{t(($) => $['newKnowledge.retryTask'])}
|
||||
</Button>
|
||||
) : null}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
) : !taskQueryError && !hasMoreTasks ? (
|
||||
<p className="py-16 text-center system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.noBackgroundTasks'])}
|
||||
</p>
|
||||
) : null}
|
||||
{hasMoreTasks && (
|
||||
<div className="mt-4 flex justify-center">
|
||||
<Button
|
||||
ref={loadMoreButtonRef}
|
||||
aria-busy={isFetchingNextTaskPage || isFetchingNextDocumentPage}
|
||||
loading={isFetchingNextTaskPage || isFetchingNextDocumentPage}
|
||||
onBlur={() => {
|
||||
loadMoreRequestedRef.current = false
|
||||
}}
|
||||
onClick={() => {
|
||||
loadMoreRequestedRef.current =
|
||||
document.activeElement === loadMoreButtonRef.current
|
||||
if (tasks.length <= orderedBaseTasks.length && hasNextTaskPage)
|
||||
onLoadMoreTasks()
|
||||
if (hasUnresolvedTaskDocuments && hasNextDocumentPage) onLoadMoreDocuments()
|
||||
setVisibleTaskLimit((current) => current + TASK_DRAWER_LIMIT)
|
||||
}}
|
||||
>
|
||||
{t(($) => $['newKnowledge.loadMore'])}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</DrawerPopup>
|
||||
</DrawerViewport>
|
||||
</DrawerPortal>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,181 @@
|
||||
export type NewKnowledgeStartMode = 'empty' | 'source' | 'upload'
|
||||
export type NewKnowledgeSourceType = 'onlineDocuments' | 'onlineDrive' | 'websiteCrawl'
|
||||
export type NewKnowledgeSyncPolicy = 'daily' | 'manual' | 'provider'
|
||||
export type NewKnowledgeWebsiteProvider = 'Firecrawl' | 'Jina Reader' | 'WaterCrawl'
|
||||
export type NewKnowledgeOnlineDocumentsProvider = 'Confluence' | 'Google Docs' | 'Notion'
|
||||
export type NewKnowledgeOnlineDriveProvider = 'Amazon S3' | 'Google Drive' | 'OneDrive'
|
||||
|
||||
const newKnowledgeCreatePath = '/datasets/new/create'
|
||||
type NewKnowledgeSourceDraftBase = {
|
||||
sourceName: string
|
||||
syncPolicy: NewKnowledgeSyncPolicy
|
||||
}
|
||||
|
||||
export type NewKnowledgeWebsiteSourceDraft = NewKnowledgeSourceDraftBase & {
|
||||
includeSubpages: boolean
|
||||
maxPages: number
|
||||
provider: NewKnowledgeWebsiteProvider
|
||||
rootUrl: string
|
||||
sourceType: 'websiteCrawl'
|
||||
}
|
||||
|
||||
export type NewKnowledgeOnlineDocumentsSourceDraft = NewKnowledgeSourceDraftBase & {
|
||||
provider: NewKnowledgeOnlineDocumentsProvider
|
||||
sourceType: 'onlineDocuments'
|
||||
}
|
||||
|
||||
export type NewKnowledgeOnlineDriveSourceDraft = NewKnowledgeSourceDraftBase & {
|
||||
provider: NewKnowledgeOnlineDriveProvider
|
||||
sourceType: 'onlineDrive'
|
||||
}
|
||||
|
||||
export type NewKnowledgeSourceDraft =
|
||||
| NewKnowledgeOnlineDocumentsSourceDraft
|
||||
| NewKnowledgeOnlineDriveSourceDraft
|
||||
| NewKnowledgeWebsiteSourceDraft
|
||||
|
||||
export const NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH = 200
|
||||
export const NEW_KNOWLEDGE_SOURCE_URL_MAX_LENGTH = 2048
|
||||
const NEW_KNOWLEDGE_SOURCE_DRAFT_STORAGE_PREFIX = 'new-knowledge-source-draft:'
|
||||
|
||||
export function createNewKnowledgeSourceDraft(
|
||||
sourceType: NewKnowledgeSourceType,
|
||||
): NewKnowledgeSourceDraft {
|
||||
if (sourceType === 'onlineDocuments')
|
||||
return {
|
||||
provider: 'Notion',
|
||||
sourceName: '',
|
||||
sourceType,
|
||||
syncPolicy: 'provider',
|
||||
}
|
||||
if (sourceType === 'onlineDrive')
|
||||
return {
|
||||
provider: 'Google Drive',
|
||||
sourceName: '',
|
||||
sourceType,
|
||||
syncPolicy: 'provider',
|
||||
}
|
||||
return {
|
||||
includeSubpages: true,
|
||||
maxPages: 100,
|
||||
provider: 'Firecrawl',
|
||||
rootUrl: '',
|
||||
sourceName: '',
|
||||
sourceType,
|
||||
syncPolicy: 'provider',
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeWebsiteSourceUrl(value: string) {
|
||||
if (value.length > NEW_KNOWLEDGE_SOURCE_URL_MAX_LENGTH) return undefined
|
||||
try {
|
||||
const url = new URL(value.trim())
|
||||
if (
|
||||
!['http:', 'https:'].includes(url.protocol) ||
|
||||
!url.hostname ||
|
||||
url.username ||
|
||||
url.password
|
||||
)
|
||||
return undefined
|
||||
url.hash = ''
|
||||
return url
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidWebsiteSourceDraft(
|
||||
draft: NewKnowledgeWebsiteSourceDraft,
|
||||
{ allowEmpty = false }: { allowEmpty?: boolean } = {},
|
||||
) {
|
||||
const hasInput = Boolean(
|
||||
draft.rootUrl.length ||
|
||||
draft.sourceName.length ||
|
||||
!draft.includeSubpages ||
|
||||
draft.maxPages !== 100,
|
||||
)
|
||||
if (allowEmpty && !hasInput) return true
|
||||
const sourceName = draft.sourceName.trim()
|
||||
return Boolean(
|
||||
normalizeWebsiteSourceUrl(draft.rootUrl) &&
|
||||
sourceName &&
|
||||
sourceName.length <= NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH &&
|
||||
Number.isInteger(draft.maxPages) &&
|
||||
draft.maxPages > 0 &&
|
||||
draft.maxPages <= 200,
|
||||
)
|
||||
}
|
||||
|
||||
export function newKnowledgeSourceDraftStorageKey(draftKey: string) {
|
||||
return `${NEW_KNOWLEDGE_SOURCE_DRAFT_STORAGE_PREFIX}${draftKey}`
|
||||
}
|
||||
|
||||
export function parseNewKnowledgeSourceDraft(value: string): NewKnowledgeSourceDraft | undefined {
|
||||
try {
|
||||
const draft: unknown = JSON.parse(value)
|
||||
if (!draft || typeof draft !== 'object') return undefined
|
||||
const candidate = draft as Record<string, unknown>
|
||||
const syncPolicy = ['daily', 'manual', 'provider'].includes(String(candidate.syncPolicy))
|
||||
? (candidate.syncPolicy as NewKnowledgeSyncPolicy)
|
||||
: candidate.syncPolicy === undefined
|
||||
? 'provider'
|
||||
: undefined
|
||||
if (
|
||||
typeof candidate.sourceName !== 'string' ||
|
||||
candidate.sourceName.length > NEW_KNOWLEDGE_SOURCE_NAME_MAX_LENGTH ||
|
||||
!syncPolicy
|
||||
)
|
||||
return undefined
|
||||
if (candidate.sourceType === 'onlineDocuments') {
|
||||
if (!['Confluence', 'Google Docs', 'Notion'].includes(String(candidate.provider)))
|
||||
return undefined
|
||||
return {
|
||||
provider: candidate.provider as NewKnowledgeOnlineDocumentsProvider,
|
||||
sourceName: candidate.sourceName,
|
||||
sourceType: candidate.sourceType,
|
||||
syncPolicy,
|
||||
}
|
||||
}
|
||||
if (candidate.sourceType === 'onlineDrive') {
|
||||
if (!['Amazon S3', 'Google Drive', 'OneDrive'].includes(String(candidate.provider)))
|
||||
return undefined
|
||||
return {
|
||||
provider: candidate.provider as NewKnowledgeOnlineDriveProvider,
|
||||
sourceName: candidate.sourceName,
|
||||
sourceType: candidate.sourceType,
|
||||
syncPolicy,
|
||||
}
|
||||
}
|
||||
if (
|
||||
(candidate.sourceType !== undefined && candidate.sourceType !== 'websiteCrawl') ||
|
||||
!['Firecrawl', 'Jina Reader', 'WaterCrawl'].includes(String(candidate.provider)) ||
|
||||
typeof candidate.includeSubpages !== 'boolean' ||
|
||||
typeof candidate.maxPages !== 'number' ||
|
||||
!Number.isInteger(candidate.maxPages) ||
|
||||
candidate.maxPages < 1 ||
|
||||
candidate.maxPages > 200 ||
|
||||
typeof candidate.rootUrl !== 'string' ||
|
||||
candidate.rootUrl.length > NEW_KNOWLEDGE_SOURCE_URL_MAX_LENGTH
|
||||
)
|
||||
return undefined
|
||||
return {
|
||||
includeSubpages: candidate.includeSubpages,
|
||||
maxPages: candidate.maxPages,
|
||||
provider: candidate.provider as NewKnowledgeWebsiteProvider,
|
||||
rootUrl: candidate.rootUrl,
|
||||
sourceName: candidate.sourceName,
|
||||
sourceType: 'websiteCrawl',
|
||||
syncPolicy,
|
||||
}
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function singleSearchParam(value: string | string[] | undefined) {
|
||||
return typeof value === 'string' ? value : undefined
|
||||
}
|
||||
|
||||
export const newKnowledgeCreatePath = '/datasets/new/create'
|
||||
|
||||
export const newKnowledgeListPath = '/datasets?view=new'
|
||||
|
||||
@@ -12,3 +187,18 @@ export const newKnowledgeDetailPath = (knowledgeSpaceId: string) =>
|
||||
|
||||
export const newKnowledgeDocumentsPath = (knowledgeSpaceId: string) =>
|
||||
`/datasets/new/${knowledgeSpaceId}/documents`
|
||||
|
||||
export const newKnowledgeDocumentDetailPath = (knowledgeSpaceId: string, documentId: string) =>
|
||||
`/datasets/new/${knowledgeSpaceId}/documents/${documentId}`
|
||||
|
||||
export const newKnowledgeAddSourcePath = (
|
||||
knowledgeSpaceId: string,
|
||||
sourceType?: NewKnowledgeSourceType,
|
||||
draftKey?: string,
|
||||
) => {
|
||||
const searchParams = new URLSearchParams()
|
||||
if (sourceType) searchParams.set('type', sourceType)
|
||||
if (draftKey) searchParams.set('draft', draftKey)
|
||||
const query = searchParams.toString()
|
||||
return `/datasets/new/${knowledgeSpaceId}/sources/new${query ? `?${query}` : ''}`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
'use client'
|
||||
|
||||
import type { Source } from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import type { StatusDotStatus } from '@langgenius/dify-ui/status-dot'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Link from '@/next/link'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { newKnowledgeAddSourcePath } from './routes'
|
||||
|
||||
type SourceStatus = Source['status']
|
||||
type SourceFilter = SourceStatus | 'all'
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
|
||||
const statusDotStatus: Record<SourceStatus, StatusDotStatus> = {
|
||||
active: 'success',
|
||||
syncing: 'normal',
|
||||
disabled: 'disabled',
|
||||
error: 'error',
|
||||
}
|
||||
|
||||
function metadataString(metadata: Source['metadata'], key: string) {
|
||||
const value = metadata[key]
|
||||
return typeof value === 'string' && value.trim() ? value : undefined
|
||||
}
|
||||
|
||||
function isPreviewDraft(source: Source) {
|
||||
return source.metadata.preview === true && source.status === 'disabled'
|
||||
}
|
||||
|
||||
function SourceActions({ source }: { source: Source }) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const showBackendBoundary = () => toast.info(t(($) => $['cornerLabel.unavailable']))
|
||||
|
||||
return (
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t(($) => $['newKnowledge.sourceActions'], { name: source.name })}
|
||||
className="flex size-7 items-center justify-center rounded-md text-text-tertiary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-48">
|
||||
<DropdownMenuItem className="gap-2 px-3" onClick={showBackendBoundary}>
|
||||
<span aria-hidden className="i-ri-refresh-line size-4" />
|
||||
{t(($) => $['newKnowledge.syncNow'])}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="gap-2 px-3" onClick={showBackendBoundary}>
|
||||
<span aria-hidden className="i-ri-edit-line size-4" />
|
||||
{t(($) => $['newKnowledge.editSource'])}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="gap-2 px-3" onClick={showBackendBoundary}>
|
||||
<span aria-hidden className="i-ri-indeterminate-circle-line size-4" />
|
||||
{t(($) => $['newKnowledge.disableSource'])}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
className="gap-2 px-3"
|
||||
onClick={showBackendBoundary}
|
||||
>
|
||||
<span aria-hidden className="i-ri-delete-bin-line size-4" />
|
||||
{t(($) => $['newKnowledge.removeSource'])}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
function SourceRow({
|
||||
onSelectedChange,
|
||||
selected,
|
||||
source,
|
||||
}: {
|
||||
onSelectedChange: (sourceId: string) => void
|
||||
selected: boolean
|
||||
source: Source
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const providerName = metadataString(source.metadata, 'providerName')
|
||||
const syncPolicy = metadataString(source.metadata, 'syncPolicy')
|
||||
const lastSync = metadataString(source.metadata, 'lastSyncedAt')
|
||||
const typeLabel = t(($) => $[`newKnowledge.sourceType.${source.type}`])
|
||||
const titleId = `new-source-${source.id}`
|
||||
|
||||
return (
|
||||
<tr
|
||||
className={cn('border-t border-divider-subtle', source.status === 'disabled' && 'opacity-60')}
|
||||
>
|
||||
<td className="w-10 py-2 pr-3">
|
||||
<Checkbox
|
||||
checked={selected}
|
||||
aria-labelledby={titleId}
|
||||
onCheckedChange={() => onSelectedChange(source.id)}
|
||||
/>
|
||||
</td>
|
||||
<td className="min-w-0 py-2 pr-3 sm:min-w-64">
|
||||
<div className="flex min-w-0 items-center gap-2.5">
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'size-[18px] shrink-0 text-text-tertiary',
|
||||
source.type === 'web' ? 'i-ri-global-line' : 'i-ri-links-line',
|
||||
)}
|
||||
/>
|
||||
<p
|
||||
id={titleId}
|
||||
title={source.uri}
|
||||
className="min-w-0 truncate system-xs-medium text-text-primary"
|
||||
>
|
||||
{source.name}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="hidden w-44 py-2 pr-3 sm:table-cell">
|
||||
<p className="system-xs-regular text-text-secondary">{providerName ?? typeLabel}</p>
|
||||
{providerName && <p className="system-2xs-regular text-text-tertiary">{typeLabel}</p>}
|
||||
</td>
|
||||
<td className="w-24 py-2 pr-3 sm:w-32">
|
||||
<span className="inline-flex items-center gap-1.5 system-xs-medium text-text-secondary">
|
||||
<StatusDot
|
||||
status={statusDotStatus[source.status]}
|
||||
className={cn(
|
||||
'shrink-0',
|
||||
source.status === 'syncing' && 'animate-pulse motion-reduce:animate-none',
|
||||
)}
|
||||
/>
|
||||
{t(($) => $[`newKnowledge.sourceStatus.${source.status}`])}
|
||||
</span>
|
||||
</td>
|
||||
<td className="hidden w-32 py-2 pr-3 system-xs-regular text-text-secondary lg:table-cell">
|
||||
{syncPolicy ?? '—'}
|
||||
</td>
|
||||
<td className="hidden w-40 py-2 pr-3 system-xs-regular text-text-secondary lg:table-cell">
|
||||
{lastSync ?? '—'}
|
||||
</td>
|
||||
<td className="w-12 py-2 text-right">
|
||||
<SourceActions source={source} />
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
function SourcesEmpty({ knowledgeSpaceId }: { knowledgeSpaceId: string }) {
|
||||
const { t } = useTranslation('dataset')
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col items-center justify-center px-6 py-16 text-center">
|
||||
<div aria-hidden className="flex items-center gap-3">
|
||||
<svg data-brand="firecrawl" viewBox="0 0 28 40" className="size-5">
|
||||
<path
|
||||
fill="#FA5D19"
|
||||
d="M23.36 12.83c-1.55.46-2.71 1.5-3.57 2.62-.18.25-.56.07-.49-.23 1.64-6.73-.52-12.31-7.26-15.07-.34-.14-.7.17-.6.53C14.5 12.98 1.61 11.94 3.24 25.88c.03.24-.24.41-.44.27-.6-.44-1.29-1.36-1.75-2-.14-.19-.44-.14-.5.09A14.3 14.3 0 0 0 0 28.12c0 4.9 2.52 9.21 6.33 11.71.22.14.5-.06.42-.31a7.7 7.7 0 0 1-.31-2.08c0-.44.03-.89.1-1.31.16-1.06.52-2.06 1.14-2.97 2.11-3.17 6.35-6.24 5.67-10.4-.04-.26.27-.43.46-.25 2.99 2.72 3.58 6.39 3.09 9.68-.05.28.31.44.49.21.46-.57 1.01-1.07 1.62-1.45.15-.09.35-.02.41.15.34.98.84 1.9 1.31 2.82.57 1.11.87 2.37.82 3.71a7.7 7.7 0 0 1-.31 1.88c-.08.26.2.47.42.32A14 14 0 0 0 28 28.12c0-1.71-.3-3.38-.86-4.94-1.19-3.29-4.19-5.75-3.43-10.03.04-.2-.15-.38-.35-.32Z"
|
||||
/>
|
||||
</svg>
|
||||
<span data-brand="jina" className="i-custom-public-llm-jina size-5" />
|
||||
<span
|
||||
data-brand="notion"
|
||||
className="i-custom-public-common-notion size-5 text-text-primary"
|
||||
/>
|
||||
<svg data-brand="google-drive" viewBox="0 0 24 24" className="size-5">
|
||||
<path fill="#0F9D58" d="M8.2 3h5.1l7.6 13.2h-5.1z" />
|
||||
<path fill="#F4B400" d="M8.2 3 .7 16.2l2.6 4.5 7.5-13.2z" />
|
||||
<path fill="#4285F4" d="M3.3 20.7h15.2l2.4-4.5H5.9z" />
|
||||
</svg>
|
||||
<svg data-brand="confluence" viewBox="0 0 24 24" className="size-5 text-blue-600">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M4.1 15.7c-.4.7-.8 1.5-1.1 2.1-.2.5 0 1 .5 1.3l3.4 1.6c.5.2 1 0 1.3-.4.3-.6.7-1.3 1.1-2 2.9-4.8 5.9-4.2 11.1-1.8.5.2 1.1 0 1.3-.5l1.3-3.5c.2-.5-.1-1.1-.6-1.3-7.6-3.5-13.8-3.6-18.3 4.5Z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
opacity=".55"
|
||||
d="M19.9 8.3c.4-.7.8-1.5 1.1-2.1.2-.5 0-1-.5-1.3l-3.4-1.6c-.5-.2-1 0-1.3.4-.3.6-.7 1.3-1.1 2-2.9 4.8-5.9 4.2-11.1 1.8-.5-.2-1.1 0-1.3.5L1 11.5c-.2.5.1 1.1.6 1.3 7.6 3.5 13.8 3.6 18.3-4.5Z"
|
||||
/>
|
||||
</svg>
|
||||
<svg data-brand="dropbox" viewBox="0 0 24 24" className="size-5 text-blue-500">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m7 3-5 3.2L7 9.4l5-3.2L7 3Zm10 0-5 3.2 5 3.2 5-3.2L17 3ZM7 10.6l-5 3.2L7 17l5-3.2-5-3.2Zm10 0-5 3.2 5 3.2 5-3.2-5-3.2ZM7.2 18.1l4.8 3 4.8-3-4.8-3-4.8 3Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-5 title-xl-semi-bold text-text-primary">
|
||||
{t(($) => $['newKnowledge.sourcesEmptyTitle'])}
|
||||
</h2>
|
||||
<p className="mt-2 max-w-md body-sm-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.sourcesEmptyDescription'])}
|
||||
</p>
|
||||
<Link
|
||||
href={newKnowledgeAddSourcePath(knowledgeSpaceId)}
|
||||
className="mt-5 inline-flex h-8 items-center justify-center gap-1 rounded-lg bg-components-button-primary-bg px-3.5 system-sm-medium text-components-button-primary-text shadow-sm outline-hidden hover:bg-components-button-primary-bg-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
<span aria-hidden className="i-ri-add-line size-4" />
|
||||
{t(($) => $['newKnowledge.addSource'])}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SourcesPage({ knowledgeSpaceId }: { knowledgeSpaceId: string }) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const [filter, setFilter] = useState<SourceFilter>('all')
|
||||
const [search, setSearch] = useState('')
|
||||
const [selectedSourceIds, setSelectedSourceIds] = useState<Set<string>>(() => new Set())
|
||||
const sourcesQuery = useInfiniteQuery(
|
||||
consoleQuery.knowledgeFs.getKnowledgeSpacesByIdSources.infiniteOptions({
|
||||
input: (pageParam) => ({
|
||||
params: { id: knowledgeSpaceId },
|
||||
query: {
|
||||
limit: PAGE_SIZE,
|
||||
...(typeof pageParam === 'string' ? { cursor: pageParam } : {}),
|
||||
},
|
||||
}),
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
initialPageParam: null as string | null,
|
||||
refetchInterval: (query) =>
|
||||
query.state.data?.pages.some((page) =>
|
||||
page.items.some((source) => source.status === 'syncing'),
|
||||
)
|
||||
? 2000
|
||||
: false,
|
||||
}),
|
||||
)
|
||||
const sources = useMemo(
|
||||
() =>
|
||||
sourcesQuery.data
|
||||
? sourcesQuery.data.pages
|
||||
.flatMap((page) => page.items)
|
||||
.filter((source) => !isPreviewDraft(source))
|
||||
.sort(
|
||||
(left, right) =>
|
||||
right.createdAt.localeCompare(left.createdAt) || right.id.localeCompare(left.id),
|
||||
)
|
||||
: undefined,
|
||||
[sourcesQuery.data],
|
||||
)
|
||||
const filteredSources = useMemo(() => {
|
||||
const normalizedSearch = search.trim().toLocaleLowerCase()
|
||||
return (sources ?? []).filter((source) => {
|
||||
if (filter !== 'all' && source.status !== filter) return false
|
||||
if (!normalizedSearch) return true
|
||||
return `${source.name} ${source.uri}`.toLocaleLowerCase().includes(normalizedSearch)
|
||||
})
|
||||
}, [filter, search, sources])
|
||||
const filterActive = filter !== 'all' || Boolean(search.trim())
|
||||
const allFilteredSelected =
|
||||
filteredSources.length > 0 &&
|
||||
filteredSources.every((source) => selectedSourceIds.has(source.id))
|
||||
const someFilteredSelected = filteredSources.some((source) => selectedSourceIds.has(source.id))
|
||||
const toggleSource = (sourceId: string) => {
|
||||
setSelectedSourceIds((current) => {
|
||||
const next = new Set(current)
|
||||
if (next.has(sourceId)) next.delete(sourceId)
|
||||
else next.add(sourceId)
|
||||
return next
|
||||
})
|
||||
}
|
||||
const toggleAllSources = () => {
|
||||
setSelectedSourceIds((current) => {
|
||||
const next = new Set(current)
|
||||
if (allFilteredSelected) {
|
||||
for (const source of filteredSources) next.delete(source.id)
|
||||
} else {
|
||||
for (const source of filteredSources) next.add(source.id)
|
||||
}
|
||||
return next
|
||||
})
|
||||
}
|
||||
const latestSourcePage = sourcesQuery.data?.pages[sourcesQuery.data.pages.length - 1]
|
||||
const needsVisibleSource =
|
||||
latestSourcePage !== undefined &&
|
||||
!latestSourcePage.items.some((source) => !isPreviewDraft(source))
|
||||
const completingFilteredResults =
|
||||
filterActive &&
|
||||
!sourcesQuery.isFetchNextPageError &&
|
||||
(sourcesQuery.hasNextPage || sourcesQuery.isFetchingNextPage)
|
||||
const {
|
||||
fetchNextPage: fetchNextSourcePage,
|
||||
hasNextPage: hasNextSourcePage,
|
||||
isFetchingNextPage: isFetchingNextSourcePage,
|
||||
} = sourcesQuery
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
(filterActive || needsVisibleSource) &&
|
||||
hasNextSourcePage &&
|
||||
!isFetchingNextSourcePage &&
|
||||
!sourcesQuery.isFetchNextPageError
|
||||
)
|
||||
void fetchNextSourcePage()
|
||||
}, [
|
||||
filterActive,
|
||||
fetchNextSourcePage,
|
||||
hasNextSourcePage,
|
||||
isFetchingNextSourcePage,
|
||||
needsVisibleSource,
|
||||
sourcesQuery.isFetchNextPageError,
|
||||
])
|
||||
|
||||
return (
|
||||
<main className="flex min-h-full flex-col px-4 py-6 sm:px-8 sm:py-7">
|
||||
<header className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<h2 className="title-xl-semi-bold text-text-primary">
|
||||
{t(($) => $['newKnowledge.sources'])}
|
||||
</h2>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.sourcesDescription'])}
|
||||
</p>
|
||||
</div>
|
||||
{!!sources?.length && (
|
||||
<Link
|
||||
href={newKnowledgeAddSourcePath(knowledgeSpaceId)}
|
||||
className="inline-flex h-8 items-center justify-center gap-1 rounded-lg bg-components-button-primary-bg px-3.5 system-sm-medium text-components-button-primary-text shadow-sm outline-hidden hover:bg-components-button-primary-bg-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
<span aria-hidden className="i-ri-add-line size-4" />
|
||||
{t(($) => $['newKnowledge.addSource'])}
|
||||
</Link>
|
||||
)}
|
||||
</header>
|
||||
{sourcesQuery.isPending ? (
|
||||
<div className="flex min-h-64 flex-1 items-center justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
) : sourcesQuery.error && !sourcesQuery.data ? (
|
||||
<div
|
||||
className="flex min-h-64 flex-1 flex-col items-center justify-center px-6 text-center"
|
||||
role="alert"
|
||||
>
|
||||
<span aria-hidden className="i-ri-error-warning-line size-7 text-text-tertiary" />
|
||||
<h2 className="mt-3 title-xl-semi-bold text-text-primary">
|
||||
{t(($) => $['newKnowledge.sourcesErrorTitle'])}
|
||||
</h2>
|
||||
<p className="mt-2 body-sm-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.sourcesErrorDescription'])}
|
||||
</p>
|
||||
<Button className="mt-4" onClick={() => void sourcesQuery.refetch()}>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
) : !sources?.length && sourcesQuery.isFetchNextPageError ? (
|
||||
<div className="flex min-h-64 flex-1 items-center justify-center gap-3" role="alert">
|
||||
<span className="system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.sourcesErrorDescription'])}
|
||||
</span>
|
||||
<Button onClick={() => void sourcesQuery.fetchNextPage()}>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
) : !sources?.length && (sourcesQuery.hasNextPage || sourcesQuery.isFetchingNextPage) ? (
|
||||
<div className="flex min-h-64 flex-1 items-center justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
) : !sources?.length ? (
|
||||
<SourcesEmpty knowledgeSpaceId={knowledgeSpaceId} />
|
||||
) : (
|
||||
<>
|
||||
<div className="mt-5 flex flex-col gap-2 sm:flex-row">
|
||||
<label className="sr-only" htmlFor="source-filter">
|
||||
{t(($) => $['newKnowledge.sourceFilterLabel'])}
|
||||
</label>
|
||||
<select
|
||||
id="source-filter"
|
||||
value={filter}
|
||||
onChange={(event) => setFilter(event.target.value as SourceFilter)}
|
||||
className="h-8 rounded-lg border-0 bg-components-input-bg-normal px-3 system-xs-regular text-text-secondary outline-hidden focus:ring-2 focus:ring-state-accent-solid"
|
||||
>
|
||||
<option value="all">{t(($) => $['newKnowledge.allSources'])}</option>
|
||||
{(['active', 'syncing', 'disabled', 'error'] as const).map((status) => (
|
||||
<option key={status} value={status}>
|
||||
{t(($) => $[`newKnowledge.sourceStatus.${status}`])}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="relative sm:w-64">
|
||||
<span className="sr-only">{t(($) => $['newKnowledge.searchSources'])}</span>
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute top-2 left-2.5 i-ri-search-line size-4 text-text-quaternary"
|
||||
/>
|
||||
<input
|
||||
type="search"
|
||||
value={search}
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
placeholder={t(($) => $['newKnowledge.searchSources'])}
|
||||
className="h-8 w-full rounded-lg border-0 bg-components-input-bg-normal pr-3 pl-8 system-xs-regular text-text-primary outline-hidden placeholder:text-text-quaternary focus:ring-2 focus:ring-state-accent-solid"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="mt-4 overflow-x-auto">
|
||||
<table className="w-full table-fixed border-collapse text-left lg:min-w-[900px] lg:table-auto">
|
||||
<thead className="system-2xs-medium text-text-tertiary uppercase">
|
||||
<tr>
|
||||
<th className="w-10 pb-2 font-medium">
|
||||
<Checkbox
|
||||
checked={allFilteredSelected}
|
||||
indeterminate={someFilteredSelected && !allFilteredSelected}
|
||||
disabled={!filteredSources.length}
|
||||
aria-label={t(($) => $['newKnowledge.selectAllSources'])}
|
||||
onCheckedChange={toggleAllSources}
|
||||
/>
|
||||
</th>
|
||||
<th className="pb-2 font-medium">{t(($) => $['newKnowledge.sourceColumn'])}</th>
|
||||
<th className="hidden pb-2 font-medium sm:table-cell">
|
||||
{t(($) => $['metadata.createMetadata.type'])}
|
||||
</th>
|
||||
<th className="pb-2 font-medium">{t(($) => $['newKnowledge.statusColumn'])}</th>
|
||||
<th className="hidden pb-2 font-medium lg:table-cell">
|
||||
{t(($) => $['newKnowledge.syncPolicyColumn'])}
|
||||
</th>
|
||||
<th className="hidden pb-2 font-medium lg:table-cell">
|
||||
{t(($) => $['newKnowledge.lastSyncColumn'])}
|
||||
</th>
|
||||
<th aria-label={t(($) => $['newKnowledge.actionsColumn'])} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredSources.map((source) => (
|
||||
<SourceRow
|
||||
key={source.id}
|
||||
source={source}
|
||||
selected={selectedSourceIds.has(source.id)}
|
||||
onSelectedChange={toggleSource}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{!filteredSources.length &&
|
||||
!completingFilteredResults &&
|
||||
!sourcesQuery.isFetchNextPageError && (
|
||||
<p
|
||||
aria-live="polite"
|
||||
className="py-16 text-center body-sm-regular text-text-tertiary"
|
||||
role="status"
|
||||
>
|
||||
{t(($) => $['newKnowledge.noMatchingSources'])}
|
||||
</p>
|
||||
)}
|
||||
{completingFilteredResults && (
|
||||
<div className="flex min-h-40 items-center justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{sourcesQuery.isFetchNextPageError ? (
|
||||
<div className="mt-5 flex items-center justify-center gap-3" role="alert">
|
||||
<span className="system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.sourcesErrorDescription'])}
|
||||
</span>
|
||||
<Button onClick={() => void sourcesQuery.fetchNextPage()}>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
) : sourcesQuery.hasNextPage && !filterActive ? (
|
||||
<div className="mt-5 flex justify-center">
|
||||
<Button
|
||||
loading={sourcesQuery.isFetchingNextPage}
|
||||
onClick={() => void sourcesQuery.fetchNextPage()}
|
||||
>
|
||||
{t(($) => $['newKnowledge.loadMore'])}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
'use client'
|
||||
|
||||
import type { ProcessingTaskEvent } from './services/processing-task-events'
|
||||
import { useEffect, useLayoutEffect, useRef } from 'react'
|
||||
import { taskVersionIsAfter } from './document-model'
|
||||
import { streamProcessingTaskEvents } from './services/processing-task-events'
|
||||
|
||||
const TASK_EVENT_RECONNECT_DELAY = 1000
|
||||
const TASK_EVENT_MAX_RECONNECT_DELAY = 30000
|
||||
|
||||
function waitForTaskEventReconnect(signal: AbortSignal, delay: number) {
|
||||
if (signal.aborted) return Promise.resolve()
|
||||
return new Promise<void>((resolve) => {
|
||||
let settled = false
|
||||
const timeout = window.setTimeout(finish, delay)
|
||||
signal.addEventListener('abort', finish, { once: true })
|
||||
if (signal.aborted) finish()
|
||||
|
||||
function finish() {
|
||||
if (settled) return
|
||||
settled = true
|
||||
window.clearTimeout(timeout)
|
||||
signal.removeEventListener('abort', finish)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function responseStatus(error: unknown): number | undefined {
|
||||
if (error instanceof Response) return error.status
|
||||
if (error && typeof error === 'object' && 'status' in error)
|
||||
return typeof error.status === 'number' ? error.status : undefined
|
||||
if (error && typeof error === 'object' && 'data' in error) {
|
||||
const data = error.data
|
||||
if (data && typeof data === 'object' && 'status' in data)
|
||||
return typeof data.status === 'number' ? data.status : undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function TaskEventObserver({
|
||||
documentId,
|
||||
knowledgeSpaceId,
|
||||
lastEventId,
|
||||
onEvent,
|
||||
onLastEventIdChange,
|
||||
onPermissionDenied,
|
||||
taskId,
|
||||
taskVersion,
|
||||
}: {
|
||||
documentId: string
|
||||
knowledgeSpaceId: string
|
||||
lastEventId?: string
|
||||
onEvent: (taskId: string, taskVersion: string, event: ProcessingTaskEvent) => boolean
|
||||
onLastEventIdChange: (taskId: string, eventId?: string) => void
|
||||
onPermissionDenied: (taskId: string, taskVersion: string) => void
|
||||
taskId: string
|
||||
taskVersion: string
|
||||
}) {
|
||||
const resumeEventIdRef = useRef(lastEventId)
|
||||
useLayoutEffect(() => {
|
||||
resumeEventIdRef.current = lastEventId
|
||||
}, [lastEventId])
|
||||
const latestTaskVersionRef = useRef(taskVersion)
|
||||
const streamTaskVersionRef = useRef(taskVersion)
|
||||
useLayoutEffect(() => {
|
||||
if (taskVersionIsAfter(taskVersion, latestTaskVersionRef.current))
|
||||
latestTaskVersionRef.current = taskVersion
|
||||
}, [taskVersion])
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController()
|
||||
void (async () => {
|
||||
let reconnectDelay = TASK_EVENT_RECONNECT_DELAY
|
||||
while (!controller.signal.aborted) {
|
||||
if (taskVersionIsAfter(latestTaskVersionRef.current, streamTaskVersionRef.current))
|
||||
streamTaskVersionRef.current = latestTaskVersionRef.current
|
||||
try {
|
||||
for await (const event of streamProcessingTaskEvents({
|
||||
documentId,
|
||||
knowledgeSpaceId,
|
||||
lastEventId: resumeEventIdRef.current,
|
||||
signal: controller.signal,
|
||||
taskId,
|
||||
})) {
|
||||
if (controller.signal.aborted) return
|
||||
resumeEventIdRef.current = event.id
|
||||
onLastEventIdChange(taskId, event.id)
|
||||
const acceptedTaskVersion = streamTaskVersionRef.current
|
||||
const eventTaskVersion =
|
||||
event.event === 'progress' ? event.data.updatedAt : acceptedTaskVersion
|
||||
const accepted = onEvent(taskId, eventTaskVersion, event)
|
||||
if (!accepted) {
|
||||
resumeEventIdRef.current = undefined
|
||||
onLastEventIdChange(taskId)
|
||||
streamTaskVersionRef.current = taskVersionIsAfter(
|
||||
latestTaskVersionRef.current,
|
||||
acceptedTaskVersion,
|
||||
)
|
||||
? latestTaskVersionRef.current
|
||||
: acceptedTaskVersion
|
||||
break
|
||||
}
|
||||
streamTaskVersionRef.current = eventTaskVersion
|
||||
reconnectDelay = TASK_EVENT_RECONNECT_DELAY
|
||||
if (event.event === 'terminal') {
|
||||
resumeEventIdRef.current = undefined
|
||||
onLastEventIdChange(taskId)
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (controller.signal.aborted) return
|
||||
if (responseStatus(error) === 403) {
|
||||
if (taskVersionIsAfter(latestTaskVersionRef.current, streamTaskVersionRef.current))
|
||||
streamTaskVersionRef.current = latestTaskVersionRef.current
|
||||
onPermissionDenied(taskId, streamTaskVersionRef.current)
|
||||
return
|
||||
}
|
||||
}
|
||||
await waitForTaskEventReconnect(controller.signal, reconnectDelay)
|
||||
reconnectDelay = Math.min(reconnectDelay * 2, TASK_EVENT_MAX_RECONNECT_DELAY)
|
||||
}
|
||||
})()
|
||||
return () => controller.abort()
|
||||
}, [documentId, knowledgeSpaceId, onEvent, onLastEventIdChange, onPermissionDenied, taskId])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { ProcessingTaskProgressEvent } from './services/processing-task-events'
|
||||
import { taskVersionIsAfter } from './document-model'
|
||||
|
||||
type TaskProgress = ProcessingTaskProgressEvent['data']
|
||||
type Listener = () => void
|
||||
|
||||
export type TaskProgressStore = {
|
||||
delete: (taskId: string) => void
|
||||
get: (taskId: string) => TaskProgress | undefined
|
||||
getSnapshot: () => number
|
||||
retain: (taskIds: Set<string>) => void
|
||||
set: (taskId: string, progress: TaskProgress) => void
|
||||
subscribe: (listener: Listener) => () => void
|
||||
}
|
||||
|
||||
export function createTaskProgressStore(): TaskProgressStore {
|
||||
const progressByTaskId = new Map<string, TaskProgress>()
|
||||
const listeners = new Set<Listener>()
|
||||
let revision = 0
|
||||
|
||||
const emit = () => {
|
||||
revision += 1
|
||||
for (const listener of listeners) listener()
|
||||
}
|
||||
|
||||
return {
|
||||
delete(taskId) {
|
||||
if (!progressByTaskId.delete(taskId)) return
|
||||
emit()
|
||||
},
|
||||
get: (taskId) => progressByTaskId.get(taskId),
|
||||
getSnapshot: () => revision,
|
||||
retain(taskIds) {
|
||||
let changed = false
|
||||
for (const taskId of progressByTaskId.keys()) {
|
||||
if (taskIds.has(taskId)) continue
|
||||
progressByTaskId.delete(taskId)
|
||||
changed = true
|
||||
}
|
||||
if (changed) emit()
|
||||
},
|
||||
set(taskId, progress) {
|
||||
const current = progressByTaskId.get(taskId)
|
||||
if (current && taskVersionIsAfter(current.updatedAt, progress.updatedAt)) return
|
||||
progressByTaskId.set(taskId, progress)
|
||||
emit()
|
||||
},
|
||||
subscribe(listener) {
|
||||
listeners.add(listener)
|
||||
return () => listeners.delete(listener)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
'use client'
|
||||
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { refreshWorkspacePermissionKeysAfterMutationDenialAtom } from '@/context/permission-state'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { DatasetACLPermission, hasPermission } from '@/utils/permission'
|
||||
import { responseStatus } from './document-detail-model'
|
||||
import { useDocumentTaskStatus } from './use-document-task-status'
|
||||
|
||||
const REINDEX_CONFIRMATION_TIMEOUT = 30000
|
||||
|
||||
export function useDocumentReindex({
|
||||
chunksQueryKey,
|
||||
documentActiveRevision,
|
||||
documentId,
|
||||
documentQueryKey,
|
||||
enabled,
|
||||
knowledgeSpaceId,
|
||||
revisionsQueryKey,
|
||||
}: {
|
||||
chunksQueryKey: readonly unknown[]
|
||||
documentActiveRevision: number
|
||||
documentId: string
|
||||
documentQueryKey: readonly unknown[]
|
||||
enabled: boolean
|
||||
knowledgeSpaceId: string
|
||||
revisionsQueryKey: readonly unknown[]
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const queryClient = useQueryClient()
|
||||
const refreshWorkspacePermissionKeysAfterMutationDenial = useSetAtom(
|
||||
refreshWorkspacePermissionKeysAfterMutationDenialAtom,
|
||||
)
|
||||
const [writePermissionRevoked, setWritePermissionRevoked] = useState(false)
|
||||
const [documentMissing, setDocumentMissing] = useState(false)
|
||||
const [permissionRecoveryBusy, setPermissionRecoveryBusy] = useState(false)
|
||||
const [permissionRecoveryNeeded, setPermissionRecoveryNeeded] = useState(false)
|
||||
const [reindexBusy, setReindexBusy] = useState(false)
|
||||
const [submissionRecoveryBusy, setSubmissionRecoveryBusy] = useState(false)
|
||||
const [submittedReindex, setSubmittedReindex] = useState<{
|
||||
baselineRevision: number
|
||||
generation: number
|
||||
timedOut: boolean
|
||||
}>()
|
||||
const nextSubmissionGenerationRef = useRef(0)
|
||||
const permissionRecoveryPendingRef = useRef(false)
|
||||
const reindexPendingRef = useRef(false)
|
||||
const previousTaskStateRef = useRef<string | undefined>(undefined)
|
||||
const acceptedTaskIdRef = useRef<string | undefined>(undefined)
|
||||
const invalidatedTerminalTaskRef = useRef<string | undefined>(undefined)
|
||||
const { mutateAsync: reindexDocument } = useMutation(
|
||||
consoleQuery.knowledgeFs.postKnowledgeSpacesByIdDocumentsBulkReindex.mutationOptions(),
|
||||
)
|
||||
const taskStatus = useDocumentTaskStatus({
|
||||
documentId,
|
||||
enabled,
|
||||
knowledgeSpaceId,
|
||||
minimumRevision: submittedReindex
|
||||
? submittedReindex.baselineRevision + 1
|
||||
: documentActiveRevision,
|
||||
submissionDiscoveryGeneration: submittedReindex?.generation,
|
||||
submissionNeedsRecheck: Boolean(submittedReindex),
|
||||
submissionPending: Boolean(submittedReindex && !submittedReindex.timedOut),
|
||||
})
|
||||
const { latestTask, taskIsActive } = taskStatus
|
||||
const latestTaskRef = useRef(latestTask)
|
||||
latestTaskRef.current = latestTask
|
||||
const submittedTaskObserved = Boolean(
|
||||
latestTask &&
|
||||
submittedReindex &&
|
||||
latestTask.documentRevision > submittedReindex.baselineRevision,
|
||||
)
|
||||
const submissionPending = Boolean(submittedReindex && !submittedTaskObserved)
|
||||
|
||||
const retryWritePermission = async () => {
|
||||
if (permissionRecoveryPendingRef.current) return false
|
||||
permissionRecoveryPendingRef.current = true
|
||||
setPermissionRecoveryBusy(true)
|
||||
try {
|
||||
const result = await refreshWorkspacePermissionKeysAfterMutationDenial()
|
||||
const refreshedPermissionKeys = result.data?.dataset?.default_permission_keys
|
||||
if (
|
||||
!result.error &&
|
||||
refreshedPermissionKeys &&
|
||||
hasPermission(refreshedPermissionKeys, DatasetACLPermission.Edit)
|
||||
) {
|
||||
setWritePermissionRevoked(false)
|
||||
setPermissionRecoveryNeeded(false)
|
||||
return true
|
||||
}
|
||||
setPermissionRecoveryNeeded(true)
|
||||
return false
|
||||
} catch {
|
||||
setPermissionRecoveryNeeded(true)
|
||||
return false
|
||||
} finally {
|
||||
permissionRecoveryPendingRef.current = false
|
||||
setPermissionRecoveryBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!submittedReindex || submittedReindex.timedOut || submittedTaskObserved) return
|
||||
const timeout = window.setTimeout(
|
||||
() =>
|
||||
setSubmittedReindex((current) =>
|
||||
current?.baselineRevision === submittedReindex.baselineRevision
|
||||
? { ...current, timedOut: true }
|
||||
: current,
|
||||
),
|
||||
REINDEX_CONFIRMATION_TIMEOUT,
|
||||
)
|
||||
return () => window.clearTimeout(timeout)
|
||||
}, [submittedReindex, submittedTaskObserved])
|
||||
|
||||
useEffect(() => {
|
||||
const previousState = previousTaskStateRef.current
|
||||
const previousWasActive =
|
||||
previousState === 'dispatch_pending' ||
|
||||
previousState === 'queued' ||
|
||||
previousState === 'running' ||
|
||||
previousState === 'retry_wait'
|
||||
previousTaskStateRef.current = latestTask?.state
|
||||
const taskMatchesAcceptedSubmission = Boolean(
|
||||
latestTask &&
|
||||
submittedReindex &&
|
||||
latestTask.documentRevision > submittedReindex.baselineRevision,
|
||||
)
|
||||
if (taskMatchesAcceptedSubmission && latestTask) acceptedTaskIdRef.current = latestTask.id
|
||||
const terminalTaskKey = latestTask ? `${latestTask.id}:${latestTask.updatedAt}` : undefined
|
||||
const shouldInvalidateTerminalTask = Boolean(
|
||||
latestTask &&
|
||||
!taskIsActive &&
|
||||
(previousWasActive ||
|
||||
acceptedTaskIdRef.current === latestTask.id ||
|
||||
taskMatchesAcceptedSubmission ||
|
||||
latestTask.documentRevision > documentActiveRevision) &&
|
||||
invalidatedTerminalTaskRef.current !== terminalTaskKey,
|
||||
)
|
||||
if (!shouldInvalidateTerminalTask || !terminalTaskKey) return
|
||||
invalidatedTerminalTaskRef.current = terminalTaskKey
|
||||
void Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: documentQueryKey,
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: revisionsQueryKey,
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: chunksQueryKey,
|
||||
}),
|
||||
queryClient.invalidateQueries({ queryKey: taskStatus.queryKey }),
|
||||
])
|
||||
}, [
|
||||
chunksQueryKey,
|
||||
documentActiveRevision,
|
||||
documentQueryKey,
|
||||
latestTask,
|
||||
queryClient,
|
||||
revisionsQueryKey,
|
||||
submittedReindex,
|
||||
taskIsActive,
|
||||
taskStatus.queryKey,
|
||||
])
|
||||
|
||||
const reindex = async (baselineRevision = documentActiveRevision) => {
|
||||
if (reindexPendingRef.current) return
|
||||
reindexPendingRef.current = true
|
||||
setReindexBusy(true)
|
||||
try {
|
||||
const result = await reindexDocument({
|
||||
body: { documentIds: [documentId] },
|
||||
params: { id: knowledgeSpaceId },
|
||||
})
|
||||
if (!result.items[0] || result.items[0].status === 'not_found') {
|
||||
setDocumentMissing(true)
|
||||
queryClient.removeQueries({ queryKey: documentQueryKey })
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: documentQueryKey,
|
||||
})
|
||||
toast.error(t(($) => $['newKnowledge.documentNotFoundTitle']))
|
||||
return
|
||||
}
|
||||
setSubmittedReindex({
|
||||
baselineRevision: Math.max(
|
||||
baselineRevision,
|
||||
documentActiveRevision,
|
||||
latestTaskRef.current?.documentRevision ?? documentActiveRevision,
|
||||
),
|
||||
generation: ++nextSubmissionGenerationRef.current,
|
||||
timedOut: false,
|
||||
})
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: documentQueryKey,
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: revisionsQueryKey,
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: chunksQueryKey,
|
||||
}),
|
||||
queryClient.invalidateQueries({ queryKey: taskStatus.queryKey }),
|
||||
])
|
||||
toast.success(t(($) => $['newKnowledge.documentsReindexStarted']))
|
||||
} catch (error) {
|
||||
if (responseStatus(error) === 403) {
|
||||
setWritePermissionRevoked(true)
|
||||
await retryWritePermission()
|
||||
}
|
||||
toast.error(t(($) => $['newKnowledge.documentsReindexFailed']))
|
||||
} finally {
|
||||
reindexPendingRef.current = false
|
||||
setReindexBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...taskStatus,
|
||||
documentMissing,
|
||||
permissionRecoveryBusy,
|
||||
permissionRecoveryNeeded,
|
||||
reindex,
|
||||
reindexBusy,
|
||||
recheckTimedOutSubmission: async () => {
|
||||
if (submissionRecoveryBusy) return
|
||||
setSubmissionRecoveryBusy(true)
|
||||
try {
|
||||
await taskStatus.refetch()
|
||||
} finally {
|
||||
setSubmissionRecoveryBusy(false)
|
||||
}
|
||||
},
|
||||
retryTimedOutSubmission: async () => {
|
||||
if (submissionRecoveryBusy) return
|
||||
const baselineRevision = submittedReindex?.baselineRevision ?? documentActiveRevision
|
||||
setSubmittedReindex(undefined)
|
||||
await reindex(baselineRevision)
|
||||
},
|
||||
retryWritePermission,
|
||||
submissionRecoveryBusy,
|
||||
submissionPending,
|
||||
submissionTimedOut: Boolean(submittedReindex?.timedOut && !submittedTaskObserved),
|
||||
writePermissionRevoked,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
DocumentProcessingTask,
|
||||
DocumentProcessingTaskList,
|
||||
} from '@dify/contracts/knowledge-fs/types.gen'
|
||||
import type { InfiniteData } from '@tanstack/react-query'
|
||||
import { skipToken, useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { responseStatus } from './document-detail-model'
|
||||
import { newestTaskByDocument } from './document-model'
|
||||
|
||||
const TASK_PAGE_SIZE = 100
|
||||
const TASK_LOOKUP_PAGE_BATCH = 3
|
||||
const ACTIVE_TASK_REFRESH_INTERVAL = 5000
|
||||
const SUBMISSION_DISCOVERY_REFRESH_INTERVAL = 2000
|
||||
|
||||
export function documentTaskIsActive(task: DocumentProcessingTask | undefined) {
|
||||
return (
|
||||
task?.state === 'dispatch_pending' ||
|
||||
task?.state === 'queued' ||
|
||||
task?.state === 'running' ||
|
||||
task?.state === 'retry_wait'
|
||||
)
|
||||
}
|
||||
|
||||
export function useDocumentTaskStatus({
|
||||
documentId,
|
||||
enabled,
|
||||
knowledgeSpaceId,
|
||||
minimumRevision,
|
||||
submissionDiscoveryGeneration,
|
||||
submissionNeedsRecheck,
|
||||
submissionPending,
|
||||
}: {
|
||||
documentId: string
|
||||
enabled: boolean
|
||||
knowledgeSpaceId: string
|
||||
minimumRevision: number
|
||||
submissionDiscoveryGeneration?: number
|
||||
submissionNeedsRecheck: boolean
|
||||
submissionPending: boolean
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const completedDiscoveryGenerationRef = useRef<number | undefined>(undefined)
|
||||
const missingTaskIdsRef = useRef(new Set<string>())
|
||||
const [lookupPageLimit, setLookupPageLimit] = useState(TASK_LOOKUP_PAGE_BATCH)
|
||||
const tasksQueryOptions = useMemo(
|
||||
() =>
|
||||
consoleQuery.knowledgeFs.getKnowledgeSpacesByIdProcessingTasks.infiniteOptions({
|
||||
enabled,
|
||||
input: (pageParam) => ({
|
||||
params: { id: knowledgeSpaceId },
|
||||
query: {
|
||||
limit: TASK_PAGE_SIZE,
|
||||
...(typeof pageParam === 'string' ? { cursor: pageParam } : {}),
|
||||
},
|
||||
}),
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
initialPageParam: null as string | null,
|
||||
}),
|
||||
[enabled, knowledgeSpaceId],
|
||||
)
|
||||
const tasksQuery = useInfiniteQuery(tasksQueryOptions)
|
||||
const {
|
||||
data: tasksData,
|
||||
error: tasksQueryError,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
isPending,
|
||||
refetch: refetchTasks,
|
||||
} = tasksQuery
|
||||
const historyTasks = useMemo(
|
||||
() => tasksData?.pages.flatMap((page) => page.items) ?? [],
|
||||
[tasksData],
|
||||
)
|
||||
const historyHasSubmittedTask = historyTasks.some(
|
||||
(task) =>
|
||||
!missingTaskIdsRef.current.has(task.id) &&
|
||||
task.documentId === documentId &&
|
||||
task.documentRevision >= minimumRevision,
|
||||
)
|
||||
const submissionTasksQueryOptions = useMemo(
|
||||
() =>
|
||||
consoleQuery.knowledgeFs.getKnowledgeSpacesByIdProcessingTasks.queryOptions({
|
||||
enabled: enabled && submissionPending && !historyHasSubmittedTask,
|
||||
input: {
|
||||
params: { id: knowledgeSpaceId },
|
||||
query: { limit: TASK_PAGE_SIZE },
|
||||
},
|
||||
refetchInterval: (query) => {
|
||||
if (
|
||||
query.state.error ||
|
||||
!submissionDiscoveryGeneration ||
|
||||
completedDiscoveryGenerationRef.current === submissionDiscoveryGeneration
|
||||
)
|
||||
return false
|
||||
const hasSubmittedTask = query.state.data?.items.some(
|
||||
(task) =>
|
||||
!missingTaskIdsRef.current.has(task.id) &&
|
||||
task.documentId === documentId &&
|
||||
task.documentRevision >= minimumRevision,
|
||||
)
|
||||
if (hasSubmittedTask) {
|
||||
completedDiscoveryGenerationRef.current = submissionDiscoveryGeneration
|
||||
return false
|
||||
}
|
||||
return enabled && submissionPending && !hasSubmittedTask
|
||||
? SUBMISSION_DISCOVERY_REFRESH_INTERVAL
|
||||
: false
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
retry: (failureCount, error) => {
|
||||
const status = responseStatus(error)
|
||||
return status !== 403 && status !== 404 && failureCount < 2
|
||||
},
|
||||
}),
|
||||
[
|
||||
documentId,
|
||||
enabled,
|
||||
historyHasSubmittedTask,
|
||||
knowledgeSpaceId,
|
||||
minimumRevision,
|
||||
submissionDiscoveryGeneration,
|
||||
submissionPending,
|
||||
],
|
||||
)
|
||||
const submissionTasksQuery = useQuery(submissionTasksQueryOptions)
|
||||
const tasks = useMemo(
|
||||
() => [...(submissionTasksQuery.data?.items ?? []), ...historyTasks],
|
||||
[historyTasks, submissionTasksQuery.data],
|
||||
)
|
||||
const discoveredTask = useMemo(() => {
|
||||
const candidate = newestTaskByDocument(
|
||||
tasks.filter(
|
||||
(task) =>
|
||||
!missingTaskIdsRef.current.has(task.id) &&
|
||||
task.documentId === documentId &&
|
||||
task.documentRevision >= minimumRevision,
|
||||
),
|
||||
).get(documentId)
|
||||
return candidate && candidate.documentRevision >= minimumRevision ? candidate : undefined
|
||||
}, [documentId, minimumRevision, tasks])
|
||||
const discoveredTaskIsActive = documentTaskIsActive(discoveredTask)
|
||||
const taskSnapshotQuery = useQuery(
|
||||
consoleQuery.knowledgeFs.getKnowledgeSpacesByIdDocumentsByDocumentIdProcessingTasksByTaskId.queryOptions(
|
||||
{
|
||||
enabled: enabled && discoveredTaskIsActive,
|
||||
input:
|
||||
enabled && discoveredTaskIsActive && discoveredTask
|
||||
? {
|
||||
params: {
|
||||
documentId,
|
||||
id: knowledgeSpaceId,
|
||||
taskId: discoveredTask.id,
|
||||
},
|
||||
}
|
||||
: skipToken,
|
||||
refetchInterval: (query) =>
|
||||
!query.state.error && documentTaskIsActive(query.state.data ?? discoveredTask)
|
||||
? ACTIVE_TASK_REFRESH_INTERVAL
|
||||
: false,
|
||||
retry: (failureCount, error) => {
|
||||
const status = responseStatus(error)
|
||||
return status !== 403 && status !== 404 && failureCount < 2
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
const taskSnapshotErrorStatus = responseStatus(taskSnapshotQuery.error)
|
||||
const latestTask =
|
||||
taskSnapshotErrorStatus === 403 || taskSnapshotErrorStatus === 404
|
||||
? undefined
|
||||
: taskSnapshotQuery.data?.id === discoveredTask?.id
|
||||
? taskSnapshotQuery.data
|
||||
: discoveredTask
|
||||
const lookupExhausted = Boolean(
|
||||
!latestTask && hasNextPage && (tasksData?.pages.length ?? 0) >= lookupPageLimit,
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isPending ||
|
||||
!enabled ||
|
||||
isFetchingNextPage ||
|
||||
tasksQueryError ||
|
||||
latestTask ||
|
||||
!hasNextPage ||
|
||||
lookupExhausted
|
||||
)
|
||||
return
|
||||
void fetchNextPage()
|
||||
}, [
|
||||
fetchNextPage,
|
||||
enabled,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
isPending,
|
||||
latestTask,
|
||||
lookupExhausted,
|
||||
tasksQueryError,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
taskSnapshotErrorStatus !== 404 ||
|
||||
!discoveredTask ||
|
||||
missingTaskIdsRef.current.has(discoveredTask.id)
|
||||
)
|
||||
return
|
||||
missingTaskIdsRef.current.add(discoveredTask.id)
|
||||
if (completedDiscoveryGenerationRef.current === submissionDiscoveryGeneration)
|
||||
completedDiscoveryGenerationRef.current = undefined
|
||||
queryClient.setQueryData<DocumentProcessingTaskList>(
|
||||
submissionTasksQueryOptions.queryKey,
|
||||
(current) =>
|
||||
current
|
||||
? { ...current, items: current.items.filter((task) => task.id !== discoveredTask.id) }
|
||||
: current,
|
||||
)
|
||||
queryClient.setQueryData<InfiniteData<DocumentProcessingTaskList, string | null>>(
|
||||
tasksQueryOptions.queryKey,
|
||||
(current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
pages: current.pages.map((page) => ({
|
||||
...page,
|
||||
items: page.items.filter((task) => task.id !== discoveredTask.id),
|
||||
})),
|
||||
}
|
||||
: current,
|
||||
)
|
||||
void Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: tasksQueryOptions.queryKey }),
|
||||
queryClient.invalidateQueries({ queryKey: submissionTasksQueryOptions.queryKey }),
|
||||
])
|
||||
}, [
|
||||
discoveredTask,
|
||||
queryClient,
|
||||
submissionDiscoveryGeneration,
|
||||
submissionTasksQueryOptions.queryKey,
|
||||
taskSnapshotErrorStatus,
|
||||
tasksQueryOptions.queryKey,
|
||||
])
|
||||
|
||||
return {
|
||||
continueLookup: () => setLookupPageLimit((current) => current + TASK_LOOKUP_PAGE_BATCH),
|
||||
isFetchingNextPage,
|
||||
isLookingUp: Boolean(!latestTask && hasNextPage && !lookupExhausted),
|
||||
isPending,
|
||||
latestTask,
|
||||
lookupExhausted,
|
||||
queryKey: tasksQueryOptions.queryKey,
|
||||
refetch: async () => {
|
||||
const results = await Promise.all([
|
||||
refetchTasks(),
|
||||
...(enabled && submissionNeedsRecheck ? [submissionTasksQuery.refetch()] : []),
|
||||
...(enabled && discoveredTaskIsActive ? [taskSnapshotQuery.refetch()] : []),
|
||||
])
|
||||
return results[0]
|
||||
},
|
||||
taskIsActive: documentTaskIsActive(latestTask),
|
||||
tasksError:
|
||||
tasksQueryError ??
|
||||
(submissionPending && !historyHasSubmittedTask ? submissionTasksQuery.error : undefined) ??
|
||||
(taskSnapshotErrorStatus === 404 ? undefined : taskSnapshotQuery.error),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
'use client'
|
||||
|
||||
import type { QueryClient, QueryKey } from '@tanstack/react-query'
|
||||
import { hashKey } from '@tanstack/react-query'
|
||||
import { useCallback, useSyncExternalStore } from 'react'
|
||||
|
||||
export function useQueryDataUpdateCount(queryClient: QueryClient, queryKey: QueryKey) {
|
||||
const queryHash = hashKey(queryKey)
|
||||
const subscribe = useCallback(
|
||||
(onStoreChange: () => void) =>
|
||||
queryClient.getQueryCache().subscribe((event) => {
|
||||
if (
|
||||
event.type === 'updated' &&
|
||||
event.action.type === 'success' &&
|
||||
event.query.queryHash === queryHash
|
||||
)
|
||||
onStoreChange()
|
||||
}),
|
||||
[queryClient, queryHash],
|
||||
)
|
||||
const getSnapshot = useCallback(
|
||||
() => queryClient.getQueryState(queryKey)?.dataUpdateCount ?? 0,
|
||||
[queryClient, queryKey],
|
||||
)
|
||||
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "مطلوب نموذج إعادة الترتيب لخلط قواعد المعرفة عالية الجودة والاقتصادية.",
|
||||
"mixtureInternalAndExternalTip": "مطلوب نموذج إعادة الترتيب لخلط المعرفة الداخلية والخارجية.",
|
||||
"multimodal": "متعدد الوسائط",
|
||||
"newKnowledge.actionsColumn": "الإجراءات",
|
||||
"newKnowledge.addDocument": "أضف وثيقة",
|
||||
"newKnowledge.addSource": "أضف المصدر",
|
||||
"newKnowledge.addSourceDescription": "قم بتوصيل مصدر بقاعدة المعرفة هذه - تتم مزامنته تلقائيًا ويظل محدثًا.",
|
||||
"newKnowledge.addSourceFailed": "تعذّرت إضافة هذا المصدر. راجع الإعدادات وحاول مرة أخرى.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "كل الوضع",
|
||||
"newKnowledge.allSources": "جميع المصادر",
|
||||
"newKnowledge.apiAgentAccess": "الوصول إلى API والوكيل",
|
||||
"newKnowledge.appsUnavailable": "— تطبيقات",
|
||||
"newKnowledge.authKind.api-key": "مفتاح API",
|
||||
"newKnowledge.authKind.endpoint": "نقطة النهاية",
|
||||
"newKnowledge.authenticationMethod": "طريقة المصادقة",
|
||||
"newKnowledge.averageChunkLength": "متوسط طول المقطع",
|
||||
"newKnowledge.backToList": "العودة إلى قواعد المعرفة",
|
||||
"newKnowledge.backgroundTasks": "مهام الخلفية",
|
||||
"newKnowledge.backgroundTasksDescription": "يضيف المستند، ويتزامن المصدر، ويعيد فهرسة المهام.",
|
||||
"newKnowledge.booleanFalse": "لا",
|
||||
"newKnowledge.booleanTrue": "نعم",
|
||||
"newKnowledge.bulkDocumentActions": "إجراءات المستندات المجمعة",
|
||||
"newKnowledge.cancelAddSource": "إلغاء",
|
||||
"newKnowledge.cardType": "مساحة معرفة",
|
||||
"newKnowledge.characterCount": "الأحرف",
|
||||
"newKnowledge.checkReindexStatus": "تحقق من حالة إعادة الفهرسة",
|
||||
"newKnowledge.chunkCount": "المقاطع",
|
||||
"newKnowledge.chunkHeading": "المقطع {{position}}",
|
||||
"newKnowledge.chunkLocation": "الموضع {{position}} في هذه المراجعة",
|
||||
"newKnowledge.clearDocumentSelection": "مسح تحديد المستندات",
|
||||
"newKnowledge.configureProvider": "تكوين الزحف الناري",
|
||||
"newKnowledge.connectNotion": "ربط Notion",
|
||||
"newKnowledge.connectProvider": "قم بتوصيل الزحف الناري",
|
||||
"newKnowledge.connectProviderGeneric": "ربط المزوّد",
|
||||
"newKnowledge.connectSource": "ربط مصدر",
|
||||
"newKnowledge.connectSourceDescription": "يبقى المحتوى متزامنًا مع Notion والمواقع الإلكترونية والخدمات الأخرى.",
|
||||
"newKnowledge.connectingProvider": "جارٍ الاتصال…",
|
||||
"newKnowledge.connectionFailed": "لم نتمكن من توصيل Firecrawl. أعد إدخال بيانات الاعتماد وحاول مرة أخرى.",
|
||||
"newKnowledge.connectionNeedsAttention": "يحتاج اتصال الزحف الناري إلى الاهتمام",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "قم بتحديث الاتصال قبل إضافة مصدر موقع الويب.",
|
||||
"newKnowledge.connectionProvisioning": "جارٍ الاتصال بالزحف الناري",
|
||||
"newKnowledge.connectionRefreshFailed": "لم نتمكن من تحديث هذا الاتصال. حاول ثانية.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "متابعة التحقق من حالة المهمة",
|
||||
"newKnowledge.crawlAndPreview": "الزحف والمعاينة",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "لم يبدأ",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "معاينة الزحف",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "يتم تعطيل تكوين الزحف إلى موقع الويب حتى يتوفر سير عمل الزحف.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "الزحف الناري جاهز",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "لم نتمكن من إنشاء قاعدة المعرفة هذه. تحقق من وصولك وحاول مرة أخرى.",
|
||||
"newKnowledge.createIllustrationTitle": "اربط محتواك مرة واحدة — وسيجيب كل تطبيق بأحدث المعارف.",
|
||||
"newKnowledge.createTitle": "خلق المعرفة",
|
||||
"newKnowledge.createdAt": "تم الإنشاء",
|
||||
"newKnowledge.creators": "المنشئون",
|
||||
"newKnowledge.customIntervalHours": "فاصل زمني مخصّص (بالساعات)",
|
||||
"newKnowledge.customIntervalInvalid": "أدخل عددًا صحيحًا من 1 إلى 720 ساعة.",
|
||||
"newKnowledge.deleteDocuments": "حذف...",
|
||||
"newKnowledge.description": "الوصف",
|
||||
"newKnowledge.descriptionHelp": "يساعد الوصف الواضح التطبيقات على تحديد وقت البحث في هذه المعرفة.",
|
||||
"newKnowledge.descriptionPlaceholder": "على سبيل المثال سياسة استرداد الأموال، والأسئلة الشائعة حول الشحن، وخطوات التصعيد - تُستخدم لطرح أسئلة دعم العملاء",
|
||||
"newKnowledge.detailErrorDescription": "لم نتمكن من تحميل قاعدة المعرفة هذه. حاول ثانية.",
|
||||
"newKnowledge.detailErrorTitle": "حدث خطأ ما",
|
||||
"newKnowledge.disableSource": "تعطيل",
|
||||
"newKnowledge.discardDraftConfirm": "تجاهل المسودة",
|
||||
"newKnowledge.discardDraftDescription": "ستُفقد تغييراتك على مسودة قاعدة المعرفة هذه.",
|
||||
"newKnowledge.discardDraftTitle": "تجاهل مسودة قاعدة المعرفة هذه؟",
|
||||
"newKnowledge.discardSourceChanges": "هل تريد تجاهل تغييرات المصدر؟",
|
||||
"newKnowledge.discardSourceChangesConfirm": "تجاهل التغييرات",
|
||||
"newKnowledge.discardSourceChangesDescription": "ستفقد معاينة الزحف وتحديد الصفحات وإعدادات المزامنة.",
|
||||
"newKnowledge.documentActions": "إجراءات {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "إجراءات المستند غير متاحة لقاعدة المعرفة هذه حتى الآن.",
|
||||
"newKnowledge.documentChunksEmpty": "لا توجد مقاطع متاحة لهذه المراجعة بعد.",
|
||||
"newKnowledge.documentChunksLoadError": "تعذّر تحميل مقاطع هذه المراجعة.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "تعذّر تحميل المزيد من المقاطع. حاول مرة أخرى.",
|
||||
"newKnowledge.documentColumn": "وثيقة",
|
||||
"newKnowledge.documentContentIncomplete": "جارٍ تحميل بقية محتوى المستند…",
|
||||
"newKnowledge.documentContents": "المحتويات",
|
||||
"newKnowledge.documentFacts": "تفاصيل المستند",
|
||||
"newKnowledge.documentFilterLabel": "تصفية المستندات حسب الحالة",
|
||||
"newKnowledge.documentLoadErrorDescription": "تعذّر تحميل هذا المستند. حاول مرة أخرى.",
|
||||
"newKnowledge.documentLoadErrorTitle": "تعذّر تحميل المستند",
|
||||
"newKnowledge.documentNotFoundDescription": "ربما تمت إزالة هذا المستند أو لا تملك صلاحية الوصول إليه.",
|
||||
"newKnowledge.documentNotFoundTitle": "المستند غير موجود",
|
||||
"newKnowledge.documentOverviewDescription": "اقرأ النسخة المفهرسة كاملة وراجع تفاصيل مصدرها وفهرسها.",
|
||||
"newKnowledge.documentPermissionRestricted": "لا تسمح أذونات مساحة العمل الخاصة بك بإجراءات المستندات أو المهام.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "تم قبول إعادة الفهرسة، لكن حالة المهمة متأخرة. تحقق منها قبل الإرسال مرة أخرى.",
|
||||
"newKnowledge.documentReindexFailed": "فشلت إعادة الفهرسة. تظل آخر مراجعة جاهزة متاحة.",
|
||||
"newKnowledge.documentReindexProgress": "جارٍ إعادة الفهرسة · {{progress}}٪",
|
||||
"newKnowledge.documentRevision": "المراجعة",
|
||||
"newKnowledge.documentRevisionMissingDescription": "لا توجد مراجعة جاهزة لهذا المستند بعد. تحقق مجددًا بعد اكتمال المعالجة.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "لا توجد مراجعة متاحة",
|
||||
"newKnowledge.documentRevisionsLoadError": "تعذّر تحميل سجل المراجعات. تظل المراجعة الحالية متاحة.",
|
||||
"newKnowledge.documentStatus.disabled": "معطل",
|
||||
"newKnowledge.documentStatus.failed": "فشل",
|
||||
"newKnowledge.documentStatus.processing": "قيد المعالجة",
|
||||
"newKnowledge.documentStatus.queued": "في قائمة الانتظار",
|
||||
"newKnowledge.documentStatus.ready": "جاهز",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "يتوفر المزيد من سجل المهام. تابع التحقق للعثور على حالة هذا المستند.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "تم تجاوز الحد الأقصى لحجم الدفعة",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "تم تجاوز الحد الأقصى لعدد الملفات",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "الملف كبير جدًا",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "نوع الملف غير مدعوم أو غير صالح",
|
||||
"newKnowledge.documentUploadExclusion.more": "و{{count}} أخرى",
|
||||
"newKnowledge.documentUploadExclusion.processing": "فشلت المعالجة",
|
||||
"newKnowledge.documentUploadExclusion.quota": "تم تجاوز حصة مساحة العمل",
|
||||
"newKnowledge.documentUploadExclusion.target": "لم يعد هدف المستند صالحًا",
|
||||
"newKnowledge.documentUploadFailed": "لم نتمكن من تحميل هذه المستندات. حاول ثانية.",
|
||||
"newKnowledge.documentUploadPartial": "بدأت معالجة {{accepted}} مستندًا؛ تعذرت إضافة {{excluded}}: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "لم يتم قبول أي مستند: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "بدأت معالجة المستندات.",
|
||||
"newKnowledge.documents": "المستندات",
|
||||
"newKnowledge.documentsDescription": "الأصول المعرفية ومراجعاتها - من أين جاءت كل وثيقة وما إذا كانت المراجعة الأخيرة مفهرسة أم لا.",
|
||||
"newKnowledge.documentsDropHint": "أو قم بإسقاط PDF أو DOCX أو Markdown أو HTML أو XLSX أو TXT للتحميل",
|
||||
"newKnowledge.documentsEmptyDescription": "قم بتحميل الملفات هنا، أو قم بتوصيل مصدر في علامة التبويب \"المصادر\" - يتم تحليل المستندات وتجميعها وفهرستها تلقائيًا.",
|
||||
"newKnowledge.documentsEmptyTitle": "لا توجد وثائق حتى الآن",
|
||||
"newKnowledge.documentsErrorDescription": "لم نتمكن من تحميل المستندات. حاول ثانية.",
|
||||
"newKnowledge.documentsErrorTitle": "تعذر تحميل المستندات",
|
||||
"newKnowledge.documentsPermissionDescription": "ليس لديك إذن لعرض المستندات في قاعدة المعرفة هذه.",
|
||||
"newKnowledge.documentsPermissionTitle": "الوثائق مقيدة",
|
||||
"newKnowledge.documentsReindexFailed": "لم نتمكن من البدء في إعادة الفهرسة. حاول ثانية.",
|
||||
"newKnowledge.documentsReindexPartial": "مستندات {{queued}} في قائمة الانتظار؛ {{missing}} لم تعد موجودة.",
|
||||
"newKnowledge.documentsReindexStarted": "بدأت عملية إعادة الفهرسة.",
|
||||
"newKnowledge.documentsSelected": "تم تحديد وثائق {{count}}",
|
||||
"newKnowledge.documentsUnavailable": "— مستندات",
|
||||
"newKnowledge.downloadDocuments": "تحميل",
|
||||
"newKnowledge.editSource": "تحرير",
|
||||
"newKnowledge.emptyChunk": "مقطع فارغ",
|
||||
"newKnowledge.emptyDescription": "أضف مستنداتك ليتمكن الوكلاء من قراءتها مباشرة — من دون إعداد الفهرسة.",
|
||||
"newKnowledge.emptyTitle": "طريقة أذكى لبناء قواعد المعرفة",
|
||||
"newKnowledge.errorDescription": "تعذر تحميل قواعد المعرفة الجديدة. حاول مرة أخرى.",
|
||||
"newKnowledge.errorTitle": "حدث خطأ ما",
|
||||
"newKnowledge.fileSize": "حجم الملف",
|
||||
"newKnowledge.filtersUnavailable": "سيصبح هذا المرشح متاحًا عندما يوفر KnowledgeFS بياناته الوصفية.",
|
||||
"newKnowledge.firecrawlUnavailable": "الزحف الناري غير متوفر في كتالوج الموفر لمساحة العمل هذه.",
|
||||
"newKnowledge.gotIt": "فهمت",
|
||||
"newKnowledge.guideDescription": "اربط مصدرًا وستبقى مستنداته متزامنة ومفهرسة تلقائيًا وجاهزة للاستخدام، من دون إعدادات تحتاج إلى إدارة. ستواصل قواعد المعرفة السابقة العمل كما هي.",
|
||||
"newKnowledge.guideTitle": "تعرّف على قاعدة المعرفة الجديدة ✨",
|
||||
"newKnowledge.illustrationHeadline": "اربط محتواك مرة واحدة — ليجيب كل تطبيق بأحدث معرفة.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "معلومات الفهرسة",
|
||||
"newKnowledge.interruptTask": "إيقاف",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid 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": "تحميل مراجعات أقدم",
|
||||
"newKnowledge.loadingSyncPolicy": "جارٍ تحميل سياسة المزامنة",
|
||||
"newKnowledge.manualUpload": "التحميل اليدوي",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "بيانات التعريف",
|
||||
"newKnowledge.mimeType": "نوع الملف",
|
||||
"newKnowledge.moreProviders": "مزوّدون آخرون",
|
||||
"newKnowledge.name": "اسم المعرفة",
|
||||
"newKnowledge.namePlaceholder": "على سبيل المثال دليل دعم العملاء",
|
||||
"newKnowledge.nameRequired": "أدخل اسم قاعدة المعرفة.",
|
||||
"newKnowledge.new": "جديد",
|
||||
"newKnowledge.noBackgroundTasks": "لا توجد مهام خلفية حتى الآن.",
|
||||
"newKnowledge.noDescription": "لا يوجد وصف",
|
||||
"newKnowledge.noMatchingDocuments": "لا توجد وثائق تطابق هذا البحث.",
|
||||
"newKnowledge.noMatchingSources": "لا توجد مصادر تطابق هذا البحث.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "ربما تم حذف قاعدة المعرفة هذه أو ربما لا يمكنك الوصول إليها.",
|
||||
"newKnowledge.notFoundTitle": "لم يتم العثور على قاعدة المعرفة",
|
||||
"newKnowledge.notionNotConnected": "Notion غير متصل",
|
||||
"newKnowledge.notionNotConnectedDescription": "اربط Notion لاختيار الصفحات والحفاظ على مزامنتها.",
|
||||
"newKnowledge.onlineDocuments": "المستندات عبر الإنترنت",
|
||||
"newKnowledge.onlineDrive": "محرك الأقراص عبر الإنترنت",
|
||||
"newKnowledge.overview": "نظرة عامة",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "تم تحديد {{count}}",
|
||||
"newKnowledge.partialDocumentResults": "قد تتطابق مستندات أخرى. حمّل المزيد لمواصلة البحث.",
|
||||
"newKnowledge.permission": "إذن",
|
||||
"newKnowledge.permissionAllMembers": "مساحة العمل · يمكن لجميع الأعضاء العرض والتحرير",
|
||||
"newKnowledge.permissionLoadFailed": "تعذر التحقق من أذونات التحرير. حاول مرة أخرى.",
|
||||
"newKnowledge.permissionOnlyMe": "أنا فقط",
|
||||
"newKnowledge.permissionRestricted": "لا تسمح أذونات مساحة العمل الخاصة بك بتغيير هذا الإعداد.",
|
||||
"newKnowledge.permissionUpdateFailed": "تم إنشاء قاعدة المعرفة، لكن تعذّر تأكيد أذوناتها. أعد المحاولة لإكمال الإعداد.",
|
||||
"newKnowledge.preview": "معاينة",
|
||||
"newKnowledge.previewUnavailable": "تتوفر المعاينة بعد أن تنتهي الواجهة الخلفية من معالجة هذا الملف.",
|
||||
"newKnowledge.processDocument": "عملية {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "تمت مقاطعته",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "تحضير",
|
||||
"newKnowledge.processingTaskState.failed": "فشل",
|
||||
"newKnowledge.processingTaskState.queued": "في قائمة الانتظار",
|
||||
"newKnowledge.processingTaskState.retry_wait": "في انتظار إعادة المحاولة",
|
||||
"newKnowledge.processingTaskState.running": "المعالجة · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "مكتمل",
|
||||
"newKnowledge.processingTaskState.superseded": "تم استبداله",
|
||||
"newKnowledge.providerConnected": "تم توصيل الزحف الناري",
|
||||
"newKnowledge.providerLoadFailed": "لم يتمكن الموفرون من التحميل",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} لم يتم تكوينه",
|
||||
"newKnowledge.providerNotConfiguredDescription": "أضف مفتاح API أو عنوان URL لنقطة النهاية للزحف إلى مواقع الويب باستخدام {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "هذا الموفر غير متوفر.",
|
||||
"newKnowledge.quality": "الجودة",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "اطلب من محرر مساحة العمل إنشاء أول قاعدة معرفة.",
|
||||
"newKnowledge.refreshConnectionStatus": "تحديث الحالة",
|
||||
"newKnowledge.refreshingConnection": "منعش…",
|
||||
"newKnowledge.reindexDocument": "إعادة الفهرسة",
|
||||
"newKnowledge.reindexDocuments": "إعادة الفهرسة",
|
||||
"newKnowledge.removeSource": "قم بإزالة المصدر",
|
||||
"newKnowledge.retrievalCount": "عدد مرات الاسترجاع",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "أعد المحاولة",
|
||||
"newKnowledge.retryReindexDocument": "إعادة الفهرسة مرة أخرى",
|
||||
"newKnowledge.retrySyncPolicy": "إعادة محاولة سياسة المزامنة",
|
||||
"newKnowledge.retryTask": "أعد المحاولة",
|
||||
"newKnowledge.revisionState.active": "نشطة",
|
||||
"newKnowledge.revisionState.candidate": "قيد المعالجة",
|
||||
"newKnowledge.revisionState.failed": "فشلت",
|
||||
"newKnowledge.revisionState.superseded": "سابقة",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "سيصبح هذا القسم متاحًا في خطوة الإعداد التالية.",
|
||||
"newKnowledge.searchDocuments": "البحث في المستندات",
|
||||
"newKnowledge.searchSources": "مصادر البحث",
|
||||
"newKnowledge.selectAll": "تحديد الكل",
|
||||
"newKnowledge.selectAllDocuments": "حدد كافة المستندات",
|
||||
"newKnowledge.selectAllSources": "تحديد كل المصادر",
|
||||
"newKnowledge.selectChunk": "حدد مقطعًا لفحص محتواه.",
|
||||
"newKnowledge.selectFilesAndFolders": "حدد الملفات والمجلدات للمزامنة",
|
||||
"newKnowledge.skippedFailed": "تم التخطي (فشل)",
|
||||
"newKnowledge.skippedOffDomain": "تم التخطي (خارج النطاق)",
|
||||
"newKnowledge.sourceActions": "إجراءات {{name}}",
|
||||
"newKnowledge.sourceColumn": "المصدر",
|
||||
"newKnowledge.sourceFilterLabel": "تصفية المصادر حسب الحالة",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "يُعرض إعداد المصدر بالكامل؛ تتطلب إجراءات التعديل والمعاينة مسار الواجهة الخلفية لمصدر المسودة.",
|
||||
"newKnowledge.sourceStatus.active": "نشط",
|
||||
"newKnowledge.sourceStatus.disabled": "معطل",
|
||||
"newKnowledge.sourceStatus.error": "خطأ",
|
||||
"newKnowledge.sourceStatus.syncing": "المزامنة",
|
||||
"newKnowledge.sourceType.connector": "التطبيق المتصل",
|
||||
"newKnowledge.sourceType.object-storage": "محرك الأقراص عبر الإنترنت",
|
||||
"newKnowledge.sourceType.upload": "تحميل",
|
||||
"newKnowledge.sourceType.web": "الزحف إلى موقع الويب",
|
||||
"newKnowledge.sourceTypeLabel": "نوع المصدر",
|
||||
"newKnowledge.sources": "المصادر",
|
||||
"newKnowledge.sourcesDescription": "من أين تأتي المعرفة، ومدى حداثتها، وما إذا كانت المزامنة الأخيرة قد نجحت.",
|
||||
"newKnowledge.sourcesEmptyDescription": "قم بتوصيل موقع ويب — Dify يبقيه متزامنًا وحديثًا، لذلك لا تنقطع عملية الاسترجاع أبدًا.",
|
||||
"newKnowledge.sourcesEmptyTitle": "لا توجد مصادر متصلة بعد",
|
||||
"newKnowledge.sourcesErrorDescription": "لم نتمكن من تحميل المصادر. حاول ثانية.",
|
||||
"newKnowledge.sourcesErrorTitle": "تعذر تحميل المصادر",
|
||||
"newKnowledge.startEmpty": "ابدأ فارغًا",
|
||||
"newKnowledge.startEmptyDescription": "أنشئها الآن وأضف المحتوى لاحقًا.",
|
||||
"newKnowledge.startWith": "ابدأ ب",
|
||||
"newKnowledge.startWithHelp": "يمكنك إضافة المزيد من المصادر أو المستندات في أي وقت.",
|
||||
"newKnowledge.statusColumn": "الحالة",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "المزامنة الآن",
|
||||
"newKnowledge.syncPolicy": "سياسة المزامنة",
|
||||
"newKnowledge.syncPolicyColumn": "سياسة المزامنة",
|
||||
"newKnowledge.syncPolicyCustom": "فاصل زمني مخصّص",
|
||||
"newKnowledge.syncPolicyDaily": "كل 24 ساعة",
|
||||
"newKnowledge.syncPolicyLoadFailed": "تعذّر تحميل سياسة المزامنة.",
|
||||
"newKnowledge.syncPolicyManual": "يدوي فقط",
|
||||
"newKnowledge.syncPolicyProvider": "استخدام جدول المزوّد",
|
||||
"newKnowledge.tags": "الوسوم",
|
||||
"newKnowledge.taskActionFailed": "لم نتمكن من تحديث هذه المهمة. قم بالتحديث وحاول مرة أخرى.",
|
||||
"newKnowledge.taskAttentionClear": "لا توجد مهام في الخلفية تحتاج إلى الاهتمام.",
|
||||
"newKnowledge.taskAttentionCount": "مهام الخلفية {{count}} تحتاج إلى الاهتمام.",
|
||||
"newKnowledge.taskAttentionErrorCount": "تحتاج مهام الخلفية {{count}} إلى الاهتمام، بما في ذلك المهام الفاشلة.",
|
||||
"newKnowledge.taskFailedNotification": "فشلت معالجة المستند — أعد المحاولة من المهام",
|
||||
"newKnowledge.taskHistoryIncomplete": "يتوفر المزيد من سجل المهام؛ عدد المهام التي تتطلب الانتباه غير مكتمل.",
|
||||
"newKnowledge.tasks": "المهام",
|
||||
"newKnowledge.tasksErrorDescription": "لم نتمكن من تحميل مهام الخلفية. حاول ثانية.",
|
||||
"newKnowledge.tasksWithAttention": "المهام، {{count}} تحتاج إلى الاهتمام",
|
||||
"newKnowledge.tokenCount": "الرموز",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS غير متاح لمساحة العمل هذه حاليًا.",
|
||||
"newKnowledge.unavailableTitle": "المعرفة الجديدة غير متاحة",
|
||||
"newKnowledge.updated": "تم التحديث في {{date}}",
|
||||
"newKnowledge.updatedAt": "آخر تحديث",
|
||||
"newKnowledge.updatedColumn": "تم التحديث",
|
||||
"newKnowledge.uploadCharactersUnavailable": "الأحرف: —",
|
||||
"newKnowledge.uploadDocuments": "تحميل المستندات",
|
||||
"newKnowledge.uploadFiles": "رفع الملفات",
|
||||
"newKnowledge.uploadFilesDescription": "أضف ملفات من جهازك — ستتم فهرستها بمجرد إنشائها.",
|
||||
"newKnowledge.uploadingFiles": "جارٍ الرفع…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "عرض المعرفة",
|
||||
"newKnowledge.websiteCrawl": "الزحف إلى موقع الويب",
|
||||
"noExternalKnowledge": "لا توجد واجهة برمجة تطبيقات معرفة خارجية حتى الآن، انقر هنا لإنشاء",
|
||||
"parentMode.fullDoc": "مستند كامل",
|
||||
"parentMode.paragraph": "فقرة",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Für die Mischung von hochwertigen und wirtschaftlichen Wissensbasen ist das Rerank-Modell erforderlich.",
|
||||
"mixtureInternalAndExternalTip": "Das Rerank-Modell ist für die Mischung von internem und externem Wissen erforderlich.",
|
||||
"multimodal": "Multimodal",
|
||||
"newKnowledge.actionsColumn": "Aktionen",
|
||||
"newKnowledge.addDocument": "Dokument hinzufügen",
|
||||
"newKnowledge.addSource": "Quelle hinzufügen",
|
||||
"newKnowledge.addSourceDescription": "Verbinden Sie eine Quelle mit dieser Wissensdatenbank – sie wird automatisch synchronisiert und bleibt aktuell.",
|
||||
"newKnowledge.addSourceFailed": "Diese Quelle konnte nicht hinzugefügt werden. Prüfe die Einstellungen und versuche es erneut.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Alle Status",
|
||||
"newKnowledge.allSources": "Alle Quellen",
|
||||
"newKnowledge.apiAgentAccess": "API- & Agent-Zugriff",
|
||||
"newKnowledge.appsUnavailable": "— Apps",
|
||||
"newKnowledge.authKind.api-key": "API-Schlüssel",
|
||||
"newKnowledge.authKind.endpoint": "Endpunkt",
|
||||
"newKnowledge.authenticationMethod": "Authentifizierungsmethode",
|
||||
"newKnowledge.averageChunkLength": "Durchschnittliche Abschnittslänge",
|
||||
"newKnowledge.backToList": "Zurück zu den Wissensdatenbanken",
|
||||
"newKnowledge.backgroundTasks": "Hintergrundaufgaben",
|
||||
"newKnowledge.backgroundTasksDescription": "Dokumenthinzufügungen, Quellsynchronisierungen und Neuindizierungsaufträge.",
|
||||
"newKnowledge.booleanFalse": "Nein",
|
||||
"newKnowledge.booleanTrue": "Ja",
|
||||
"newKnowledge.bulkDocumentActions": "Massendokumentaktionen",
|
||||
"newKnowledge.cancelAddSource": "Abbrechen",
|
||||
"newKnowledge.cardType": "Wissensbereich",
|
||||
"newKnowledge.characterCount": "Zeichen",
|
||||
"newKnowledge.checkReindexStatus": "Neuindizierungsstatus prüfen",
|
||||
"newKnowledge.chunkCount": "Abschnitte",
|
||||
"newKnowledge.chunkHeading": "Abschnitt {{position}}",
|
||||
"newKnowledge.chunkLocation": "Position {{position}} in dieser Revision",
|
||||
"newKnowledge.clearDocumentSelection": "Dokumentauswahl löschen",
|
||||
"newKnowledge.configureProvider": "Firecrawl konfigurieren",
|
||||
"newKnowledge.connectNotion": "Notion verbinden",
|
||||
"newKnowledge.connectProvider": "Verbinden Sie Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Anbieter verbinden",
|
||||
"newKnowledge.connectSource": "Quelle verbinden",
|
||||
"newKnowledge.connectSourceDescription": "Inhalte bleiben mit Notion, Websites und anderen Diensten synchron.",
|
||||
"newKnowledge.connectingProvider": "Verbinden…",
|
||||
"newKnowledge.connectionFailed": "Wir konnten Firecrawl nicht verbinden. Geben Sie die Anmeldedaten erneut ein und versuchen Sie es erneut.",
|
||||
"newKnowledge.connectionNeedsAttention": "Firecrawl-Verbindung erfordert Aufmerksamkeit",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Aktualisieren Sie die Verbindung, bevor Sie eine Website-Quelle hinzufügen.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl stellt eine Verbindung her",
|
||||
"newKnowledge.connectionRefreshFailed": "Wir konnten diese Verbindung nicht aktualisieren. Versuchen Sie es erneut.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Aufgabenstatus weiter prüfen",
|
||||
"newKnowledge.crawlAndPreview": "Crawlen und Vorschau",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Nicht gestartet",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Crawl-Vorschau",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Die Website-Crawling-Konfiguration ist deaktiviert, bis der Crawl-Workflow verfügbar ist.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl ist bereit",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Wir konnten diese Wissensdatenbank nicht erstellen. Überprüfen Sie Ihren Zugang und versuchen Sie es erneut.",
|
||||
"newKnowledge.createIllustrationTitle": "Verbinde deine Inhalte einmal – jede App antwortet mit dem aktuellsten Wissen.",
|
||||
"newKnowledge.createTitle": "Wissen schaffen",
|
||||
"newKnowledge.createdAt": "Erstellt",
|
||||
"newKnowledge.creators": "Ersteller",
|
||||
"newKnowledge.customIntervalHours": "Benutzerdefiniertes Intervall (Stunden)",
|
||||
"newKnowledge.customIntervalInvalid": "Gib eine ganze Zahl zwischen 1 und 720 Stunden ein.",
|
||||
"newKnowledge.deleteDocuments": "Löschen…",
|
||||
"newKnowledge.description": "Beschreibung",
|
||||
"newKnowledge.descriptionHelp": "Eine klare Beschreibung hilft Apps bei der Entscheidung, wann dieses Wissen durchsucht werden soll.",
|
||||
"newKnowledge.descriptionPlaceholder": "z.B. Rückerstattungsrichtlinien, häufig gestellte Fragen zum Versand und Eskalationsschritte – für Fragen zum Kundensupport verwenden",
|
||||
"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.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.",
|
||||
"newKnowledge.documentChunksLoadError": "Die Abschnitte dieser Revision konnten nicht geladen werden.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Weitere Abschnitte konnten nicht geladen werden. Versuchen Sie es erneut.",
|
||||
"newKnowledge.documentColumn": "Dokument",
|
||||
"newKnowledge.documentContentIncomplete": "Verbleibender Dokumentinhalt wird geladen…",
|
||||
"newKnowledge.documentContents": "Inhalt",
|
||||
"newKnowledge.documentFacts": "Dokumentdetails",
|
||||
"newKnowledge.documentFilterLabel": "Dokumente nach Status filtern",
|
||||
"newKnowledge.documentLoadErrorDescription": "Dieses Dokument konnte nicht geladen werden. Versuchen Sie es erneut.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Dokument konnte nicht geladen werden",
|
||||
"newKnowledge.documentNotFoundDescription": "Dieses Dokument wurde möglicherweise entfernt oder Sie haben keinen Zugriff.",
|
||||
"newKnowledge.documentNotFoundTitle": "Dokument nicht gefunden",
|
||||
"newKnowledge.documentOverviewDescription": "Lesen Sie die vollständig indexierte Revision und prüfen Sie ihre Quellen- und Indexdetails.",
|
||||
"newKnowledge.documentPermissionRestricted": "Ihre Workspace-Berechtigungen erlauben keine Dokument- oder Aufgabenaktionen.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "Die Neuindizierung wurde angenommen, aber der Aufgabenstatus ist verzögert. Prüfe ihn vor einer erneuten Übermittlung.",
|
||||
"newKnowledge.documentReindexFailed": "Die Neuindizierung ist fehlgeschlagen. Die letzte bereite Revision bleibt verfügbar.",
|
||||
"newKnowledge.documentReindexProgress": "Neuindizierung · {{progress}} %",
|
||||
"newKnowledge.documentRevision": "Versionsstand",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Dieses Dokument hat noch keine bereite Revision. Prüfen Sie es nach Abschluss der Verarbeitung erneut.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Keine Revision verfügbar",
|
||||
"newKnowledge.documentRevisionsLoadError": "Der Revisionsverlauf konnte nicht geladen werden. Die aktuelle Revision bleibt verfügbar.",
|
||||
"newKnowledge.documentStatus.disabled": "Deaktiviert",
|
||||
"newKnowledge.documentStatus.failed": "Fehlgeschlagen",
|
||||
"newKnowledge.documentStatus.processing": "Verarbeitung",
|
||||
"newKnowledge.documentStatus.queued": "In der Warteschlange",
|
||||
"newKnowledge.documentStatus.ready": "Bereit",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Weitere Aufgabenverläufe sind verfügbar. Prüfe weiter, um den Status dieses Dokuments zu finden.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "Maximale Batchgröße überschritten",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "Maximale Dateianzahl überschritten",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "Datei ist zu groß",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "Nicht unterstützter oder ungültiger Dateityp",
|
||||
"newKnowledge.documentUploadExclusion.more": "und {{count}} weitere",
|
||||
"newKnowledge.documentUploadExclusion.processing": "Verarbeitung fehlgeschlagen",
|
||||
"newKnowledge.documentUploadExclusion.quota": "Arbeitsbereichskontingent überschritten",
|
||||
"newKnowledge.documentUploadExclusion.target": "Dokumentziel ist nicht mehr gültig",
|
||||
"newKnowledge.documentUploadFailed": "Wir konnten diese Dokumente nicht hochladen. Versuchen Sie es erneut.",
|
||||
"newKnowledge.documentUploadPartial": "{{accepted}} Dokumente wurden gestartet; {{excluded}} konnten nicht hinzugefügt werden: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Keine Dokumente wurden akzeptiert: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Die Dokumentenverarbeitung wurde gestartet.",
|
||||
"newKnowledge.documents": "Dokumente",
|
||||
"newKnowledge.documentsDescription": "Wissensbestände und ihre Überarbeitungen – woher jedes Dokument stammt und ob die neueste Überarbeitung indiziert ist.",
|
||||
"newKnowledge.documentsDropHint": "oder laden Sie PDF, DOCX, Markdown, HTML, XLSX oder TXT hoch",
|
||||
"newKnowledge.documentsEmptyDescription": "Laden Sie hier Dateien hoch oder verbinden Sie eine Quelle auf der Registerkarte „Quellen“ – Dokumente werden automatisch analysiert, segmentiert und indiziert.",
|
||||
"newKnowledge.documentsEmptyTitle": "Noch keine Dokumente",
|
||||
"newKnowledge.documentsErrorDescription": "Wir konnten keine Dokumente laden. Versuchen Sie es erneut.",
|
||||
"newKnowledge.documentsErrorTitle": "Dokumente konnten nicht geladen werden",
|
||||
"newKnowledge.documentsPermissionDescription": "Sie sind nicht berechtigt, Dokumente in dieser Wissensdatenbank anzuzeigen.",
|
||||
"newKnowledge.documentsPermissionTitle": "Dokumente sind eingeschränkt",
|
||||
"newKnowledge.documentsReindexFailed": "Wir konnten nicht mit der Neuindizierung beginnen. Versuchen Sie es erneut.",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} Dokumente in der Warteschlange; {{missing}} existiert nicht mehr.",
|
||||
"newKnowledge.documentsReindexStarted": "Die Neuindizierung wurde gestartet.",
|
||||
"newKnowledge.documentsSelected": "{{count}} Dokumente ausgewählt",
|
||||
"newKnowledge.documentsUnavailable": "— Dokumente",
|
||||
"newKnowledge.downloadDocuments": "Herunterladen",
|
||||
"newKnowledge.editSource": "Bearbeiten",
|
||||
"newKnowledge.emptyChunk": "Leerer Abschnitt",
|
||||
"newKnowledge.emptyDescription": "Fügen Sie Ihre Dokumente hinzu, damit Agents sie direkt lesen können — ohne eine Indizierung einzurichten.",
|
||||
"newKnowledge.emptyTitle": "Wissensdatenbanken intelligenter aufbauen",
|
||||
"newKnowledge.errorDescription": "Die neuen Wissensdatenbanken konnten nicht geladen werden. Versuchen Sie es erneut.",
|
||||
"newKnowledge.errorTitle": "Etwas ist schiefgelaufen",
|
||||
"newKnowledge.fileSize": "Dateigröße",
|
||||
"newKnowledge.filtersUnavailable": "Dieser Filter wird verfügbar, sobald KnowledgeFS Metadaten bereitstellt.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl ist im Anbieterkatalog für diesen Arbeitsbereich nicht verfügbar.",
|
||||
"newKnowledge.gotIt": "Verstanden",
|
||||
"newKnowledge.guideDescription": "Verbinden Sie eine Quelle, und ihre Dokumente bleiben synchron — automatisch indiziert und sofort einsatzbereit, ohne Verwaltungsaufwand. Ihre bisherigen Wissensdatenbanken funktionieren unverändert weiter.",
|
||||
"newKnowledge.guideTitle": "Lernen Sie die neue Wissensdatenbank kennen ✨",
|
||||
"newKnowledge.illustrationHeadline": "Verbinde deine Inhalte einmal – und jede App antwortet mit dem neuesten Wissen.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Indexinformationen",
|
||||
"newKnowledge.interruptTask": "Unterbrechen",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Synchronisierungsrichtlinie wird geladen",
|
||||
"newKnowledge.manualUpload": "Manueller Upload",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Metadaten",
|
||||
"newKnowledge.mimeType": "Dateityp",
|
||||
"newKnowledge.moreProviders": "Weitere Anbieter",
|
||||
"newKnowledge.name": "Wissensname",
|
||||
"newKnowledge.namePlaceholder": "z.B. Kundendiensthandbuch",
|
||||
"newKnowledge.nameRequired": "Geben Sie einen Namen für die Wissensdatenbank ein.",
|
||||
"newKnowledge.new": "Neu",
|
||||
"newKnowledge.noBackgroundTasks": "Noch keine Hintergrundaufgaben.",
|
||||
"newKnowledge.noDescription": "Keine Beschreibung",
|
||||
"newKnowledge.noMatchingDocuments": "Zu dieser Suche passen keine Dokumente.",
|
||||
"newKnowledge.noMatchingSources": "Keine Quellen passen zu dieser Suche.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Diese Wissensdatenbank wurde möglicherweise gelöscht oder Sie haben keinen Zugriff darauf.",
|
||||
"newKnowledge.notFoundTitle": "Wissensdatenbank nicht gefunden",
|
||||
"newKnowledge.notionNotConnected": "Notion ist nicht verbunden",
|
||||
"newKnowledge.notionNotConnectedDescription": "Verbinden Sie Notion, um Seiten auszuwählen und synchron zu halten.",
|
||||
"newKnowledge.onlineDocuments": "Online-Dokumente",
|
||||
"newKnowledge.onlineDrive": "Online-Laufwerk",
|
||||
"newKnowledge.overview": "Übersicht",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} ausgewählt",
|
||||
"newKnowledge.partialDocumentResults": "Weitere Dokumente könnten passen. Lade mehr, um die Suche fortzusetzen.",
|
||||
"newKnowledge.permission": "Erlaubnis",
|
||||
"newKnowledge.permissionAllMembers": "Arbeitsbereich · Alle Mitglieder können ihn ansehen und bearbeiten",
|
||||
"newKnowledge.permissionLoadFailed": "Ihre Bearbeitungsberechtigungen konnten nicht überprüft werden. Versuchen Sie es erneut.",
|
||||
"newKnowledge.permissionOnlyMe": "Nur ich",
|
||||
"newKnowledge.permissionRestricted": "Ihre Arbeitsbereichsberechtigungen erlauben keine Änderung dieser Einstellung.",
|
||||
"newKnowledge.permissionUpdateFailed": "Die Wissensdatenbank wurde erstellt, aber ihre Berechtigung konnte nicht bestätigt werden. Versuchen Sie es erneut, um die Einrichtung abzuschließen.",
|
||||
"newKnowledge.preview": "Vorschau",
|
||||
"newKnowledge.previewUnavailable": "Die Vorschau ist verfügbar, nachdem das Backend diese Datei verarbeitet hat.",
|
||||
"newKnowledge.processDocument": "Prozess {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Unterbrochen",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Vorbereiten",
|
||||
"newKnowledge.processingTaskState.failed": "Fehlgeschlagen",
|
||||
"newKnowledge.processingTaskState.queued": "In der Warteschlange",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Warte auf einen erneuten Versuch",
|
||||
"newKnowledge.processingTaskState.running": "Verarbeitung · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Abgeschlossen",
|
||||
"newKnowledge.processingTaskState.superseded": "Abgelöst",
|
||||
"newKnowledge.providerConnected": "Firecrawl verbunden",
|
||||
"newKnowledge.providerLoadFailed": "Anbieter konnten nicht geladen werden",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} ist nicht konfiguriert",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Fügen Sie einen API-Schlüssel oder eine Endpunkt-URL hinzu, um Websites mit {{provider}} zu crawlen.",
|
||||
"newKnowledge.providerUnavailable": "Dieser Anbieter ist nicht verfügbar.",
|
||||
"newKnowledge.quality": "Qualität",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Bitten Sie einen Arbeitsbereich-Editor, die erste Wissensdatenbank zu erstellen.",
|
||||
"newKnowledge.refreshConnectionStatus": "Status aktualisieren",
|
||||
"newKnowledge.refreshingConnection": "Erfrischend…",
|
||||
"newKnowledge.reindexDocument": "Neu indizieren",
|
||||
"newKnowledge.reindexDocuments": "Neu indizieren",
|
||||
"newKnowledge.removeSource": "Quelle entfernen",
|
||||
"newKnowledge.retrievalCount": "Anzahl der Abrufe",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Wiederholen",
|
||||
"newKnowledge.retryReindexDocument": "Erneut neu indizieren",
|
||||
"newKnowledge.retrySyncPolicy": "Synchronisierungsrichtlinie erneut laden",
|
||||
"newKnowledge.retryTask": "Versuchen Sie es noch einmal",
|
||||
"newKnowledge.revisionState.active": "Aktiv",
|
||||
"newKnowledge.revisionState.candidate": "In Verarbeitung",
|
||||
"newKnowledge.revisionState.failed": "Fehlgeschlagen",
|
||||
"newKnowledge.revisionState.superseded": "Vorherige",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Dieser Abschnitt wird im nächsten Einrichtungsschritt verfügbar.",
|
||||
"newKnowledge.searchDocuments": "Dokumente durchsuchen",
|
||||
"newKnowledge.searchSources": "Quellen durchsuchen",
|
||||
"newKnowledge.selectAll": "Alle auswählen",
|
||||
"newKnowledge.selectAllDocuments": "Wählen Sie alle Dokumente aus",
|
||||
"newKnowledge.selectAllSources": "Alle Quellen auswählen",
|
||||
"newKnowledge.selectChunk": "Wählen Sie einen Abschnitt aus, um seinen Inhalt zu prüfen.",
|
||||
"newKnowledge.selectFilesAndFolders": "Dateien und Ordner zum Synchronisieren auswählen",
|
||||
"newKnowledge.skippedFailed": "Übersprungen (fehlgeschlagen)",
|
||||
"newKnowledge.skippedOffDomain": "Übersprungen (außerhalb der Domain)",
|
||||
"newKnowledge.sourceActions": "Aktionen für {{name}}",
|
||||
"newKnowledge.sourceColumn": "Quelle",
|
||||
"newKnowledge.sourceFilterLabel": "Quellen nach Status filtern",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "Die Quelleneinrichtung wird vollständig angezeigt; Bearbeitung und Vorschau erfordern den Backend-Ablauf für Entwurfsquellen.",
|
||||
"newKnowledge.sourceStatus.active": "Aktiv",
|
||||
"newKnowledge.sourceStatus.disabled": "Deaktiviert",
|
||||
"newKnowledge.sourceStatus.error": "Fehler",
|
||||
"newKnowledge.sourceStatus.syncing": "Synchronisierung",
|
||||
"newKnowledge.sourceType.connector": "Verbundene App",
|
||||
"newKnowledge.sourceType.object-storage": "Online-Laufwerk",
|
||||
"newKnowledge.sourceType.upload": "Hochladen",
|
||||
"newKnowledge.sourceType.web": "Website-Crawling",
|
||||
"newKnowledge.sourceTypeLabel": "Quelltyp",
|
||||
"newKnowledge.sources": "Quellen",
|
||||
"newKnowledge.sourcesDescription": "Woher das Wissen kommt, wie aktuell es ist und ob die letzte Synchronisierung erfolgreich war.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Verbinden Sie eine Website – Dify hält sie synchronisiert und aktuell, sodass der Abruf nie unterbrochen wird.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Noch keine Quellen verbunden",
|
||||
"newKnowledge.sourcesErrorDescription": "Wir konnten die Quellen nicht laden. Versuchen Sie es erneut.",
|
||||
"newKnowledge.sourcesErrorTitle": "Quellen konnten nicht geladen werden",
|
||||
"newKnowledge.startEmpty": "Leer beginnen",
|
||||
"newKnowledge.startEmptyDescription": "Jetzt erstellen und Inhalte später hinzufügen.",
|
||||
"newKnowledge.startWith": "Beginnen Sie mit",
|
||||
"newKnowledge.startWithHelp": "Sie können jederzeit weitere Quellen oder Dokumente hinzufügen.",
|
||||
"newKnowledge.statusColumn": "Status",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Jetzt synchronisieren",
|
||||
"newKnowledge.syncPolicy": "Synchronisierungsrichtlinie",
|
||||
"newKnowledge.syncPolicyColumn": "Synchronisierungsrichtlinie",
|
||||
"newKnowledge.syncPolicyCustom": "Benutzerdefiniertes Intervall",
|
||||
"newKnowledge.syncPolicyDaily": "Alle 24 Stunden",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Die Synchronisierungsrichtlinie konnte nicht geladen werden.",
|
||||
"newKnowledge.syncPolicyManual": "Nur manuell",
|
||||
"newKnowledge.syncPolicyProvider": "Zeitplan des Anbieters verwenden",
|
||||
"newKnowledge.tags": "Tags",
|
||||
"newKnowledge.taskActionFailed": "Wir konnten diese Aufgabe nicht aktualisieren. Aktualisieren Sie und versuchen Sie es erneut.",
|
||||
"newKnowledge.taskAttentionClear": "Keine Hintergrundaufgaben erfordern Aufmerksamkeit.",
|
||||
"newKnowledge.taskAttentionCount": "{{count}} Hintergrundaufgaben erfordern Aufmerksamkeit.",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}} Hintergrundaufgaben erfordern Aufmerksamkeit, einschließlich fehlgeschlagener Aufgaben.",
|
||||
"newKnowledge.taskFailedNotification": "Die Dokumentverarbeitung ist fehlgeschlagen. Versuchen Sie es erneut über „Aufgaben“.",
|
||||
"newKnowledge.taskHistoryIncomplete": "Weitere Aufgaben sind verfügbar; die Anzahl der Aufgaben mit Handlungsbedarf ist unvollständig.",
|
||||
"newKnowledge.tasks": "Aufgaben",
|
||||
"newKnowledge.tasksErrorDescription": "Wir konnten keine Hintergrundaufgaben laden. Versuchen Sie es erneut.",
|
||||
"newKnowledge.tasksWithAttention": "Aufgaben, {{count}} erfordern Aufmerksamkeit",
|
||||
"newKnowledge.tokenCount": "Token",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS ist für diesen Arbeitsbereich derzeit nicht verfügbar.",
|
||||
"newKnowledge.unavailableTitle": "Neues Wissen ist nicht verfügbar",
|
||||
"newKnowledge.updated": "Aktualisiert am {{date}}",
|
||||
"newKnowledge.updatedAt": "Zuletzt aktualisiert",
|
||||
"newKnowledge.updatedColumn": "Aktualisiert",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Zeichen: —",
|
||||
"newKnowledge.uploadDocuments": "Dokumente hochladen",
|
||||
"newKnowledge.uploadFiles": "Dateien hochladen",
|
||||
"newKnowledge.uploadFilesDescription": "Fügen Sie Dateien von Ihrem Computer hinzu — sie werden beim Erstellen sofort indiziert.",
|
||||
"newKnowledge.uploadingFiles": "Wird hochgeladen…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Wissensansicht",
|
||||
"newKnowledge.websiteCrawl": "Website-Crawling",
|
||||
"noExternalKnowledge": "Es gibt noch keine External Knowledge API, klicken Sie hier, um zu erstellen",
|
||||
"parentMode.fullDoc": "Vollständiges Dokument",
|
||||
"parentMode.paragraph": "Absatz",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "The Rerank model is required for mixture of high quality and economical knowledge bases.",
|
||||
"mixtureInternalAndExternalTip": "The Rerank model is required for mixture of internal and external knowledge.",
|
||||
"multimodal": "Multimodal",
|
||||
"newKnowledge.actionsColumn": "Actions",
|
||||
"newKnowledge.addDocument": "Add document",
|
||||
"newKnowledge.addSource": "Add source",
|
||||
"newKnowledge.addSourceDescription": "Connect a source to this knowledge base — it syncs automatically and stays fresh.",
|
||||
"newKnowledge.addSourceFailed": "We couldn't add this source. Review the settings and try again.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "All status",
|
||||
"newKnowledge.allSources": "All sources",
|
||||
"newKnowledge.apiAgentAccess": "API & Agent Access",
|
||||
"newKnowledge.appsUnavailable": "— apps",
|
||||
"newKnowledge.authKind.api-key": "API key",
|
||||
"newKnowledge.authKind.endpoint": "Endpoint",
|
||||
"newKnowledge.authenticationMethod": "Authentication method",
|
||||
"newKnowledge.averageChunkLength": "Average chunk length",
|
||||
"newKnowledge.backToList": "Back to knowledge bases",
|
||||
"newKnowledge.backgroundTasks": "Background tasks",
|
||||
"newKnowledge.backgroundTasksDescription": "Document adds, source syncs, and re-index jobs.",
|
||||
"newKnowledge.booleanFalse": "No",
|
||||
"newKnowledge.booleanTrue": "Yes",
|
||||
"newKnowledge.bulkDocumentActions": "Bulk document actions",
|
||||
"newKnowledge.cancelAddSource": "Cancel",
|
||||
"newKnowledge.cardType": "Knowledge space",
|
||||
"newKnowledge.characterCount": "Characters",
|
||||
"newKnowledge.checkReindexStatus": "Check re-index status",
|
||||
"newKnowledge.chunkCount": "Chunks",
|
||||
"newKnowledge.chunkHeading": "Chunk {{position}}",
|
||||
"newKnowledge.chunkLocation": "Position {{position}} in this revision",
|
||||
"newKnowledge.clearDocumentSelection": "Clear document selection",
|
||||
"newKnowledge.configureProvider": "Configure Firecrawl",
|
||||
"newKnowledge.connectNotion": "Connect Notion",
|
||||
"newKnowledge.connectProvider": "Connect Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Connect provider",
|
||||
"newKnowledge.connectSource": "Connect a source",
|
||||
"newKnowledge.connectSourceDescription": "Content stays in sync with Notion, websites, and other services.",
|
||||
"newKnowledge.connectingProvider": "Connecting…",
|
||||
"newKnowledge.connectionFailed": "We couldn't connect Firecrawl. Re-enter the credential and try again.",
|
||||
"newKnowledge.connectionNeedsAttention": "Firecrawl connection needs attention",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Refresh the connection before adding a website source.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl is connecting",
|
||||
"newKnowledge.connectionRefreshFailed": "We couldn't refresh this connection. Try again.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Continue checking task status",
|
||||
"newKnowledge.crawlAndPreview": "Crawl & preview",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Not started",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Crawl preview",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Website crawl configuration is disabled until the crawl workflow is available.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl is ready",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "We couldn't create this knowledge base. Check your access and try again.",
|
||||
"newKnowledge.createIllustrationTitle": "Connect your content once — every app answers with the freshest knowledge.",
|
||||
"newKnowledge.createTitle": "Create Knowledge",
|
||||
"newKnowledge.createdAt": "Created",
|
||||
"newKnowledge.creators": "Creators",
|
||||
"newKnowledge.customIntervalHours": "Custom interval (hours)",
|
||||
"newKnowledge.customIntervalInvalid": "Enter a whole number from 1 to 720 hours.",
|
||||
"newKnowledge.deleteDocuments": "Delete…",
|
||||
"newKnowledge.description": "Description",
|
||||
"newKnowledge.descriptionHelp": "A clear description helps apps decide when to search this Knowledge.",
|
||||
"newKnowledge.descriptionPlaceholder": "e.g. Refund policy, shipping FAQs, and escalation steps — use for customer support questions",
|
||||
"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.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.",
|
||||
"newKnowledge.documentChunksLoadError": "We couldn't load chunks for this revision.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "We couldn't load more chunks. Try again.",
|
||||
"newKnowledge.documentColumn": "Document",
|
||||
"newKnowledge.documentContentIncomplete": "Loading the remaining document content…",
|
||||
"newKnowledge.documentContents": "Contents",
|
||||
"newKnowledge.documentFacts": "Document details",
|
||||
"newKnowledge.documentFilterLabel": "Filter documents by status",
|
||||
"newKnowledge.documentLoadErrorDescription": "We couldn't load this document. Try again.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Document couldn't load",
|
||||
"newKnowledge.documentNotFoundDescription": "This document may have been removed, or you may not have access.",
|
||||
"newKnowledge.documentNotFoundTitle": "Document not found",
|
||||
"newKnowledge.documentOverviewDescription": "Read the complete indexed revision and inspect its source and index details.",
|
||||
"newKnowledge.documentPermissionRestricted": "Your workspace permissions don't allow document or task actions.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "Re-indexing was accepted, but its task status is delayed. Check again before submitting another re-index.",
|
||||
"newKnowledge.documentReindexFailed": "Re-indexing failed. The last ready revision remains available.",
|
||||
"newKnowledge.documentReindexProgress": "Re-indexing · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Revision",
|
||||
"newKnowledge.documentRevisionMissingDescription": "This document doesn't have a ready revision yet. Check back after processing finishes.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "No revision available",
|
||||
"newKnowledge.documentRevisionsLoadError": "We couldn't load revision history. The current revision remains available.",
|
||||
"newKnowledge.documentStatus.disabled": "Disabled",
|
||||
"newKnowledge.documentStatus.failed": "Failed",
|
||||
"newKnowledge.documentStatus.processing": "Processing",
|
||||
"newKnowledge.documentStatus.queued": "Queued",
|
||||
"newKnowledge.documentStatus.ready": "Ready",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "More task history is available. Continue checking to find this document's status.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "batch size limit exceeded",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "file count limit exceeded",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "file is too large",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "unsupported or invalid file type",
|
||||
"newKnowledge.documentUploadExclusion.more": "and {{count}} more",
|
||||
"newKnowledge.documentUploadExclusion.processing": "processing failed",
|
||||
"newKnowledge.documentUploadExclusion.quota": "workspace quota exceeded",
|
||||
"newKnowledge.documentUploadExclusion.target": "document target is no longer valid",
|
||||
"newKnowledge.documentUploadFailed": "We couldn't upload these documents. Try again.",
|
||||
"newKnowledge.documentUploadPartial": "{{accepted}} documents started; {{excluded}} could not be added: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "No documents were accepted: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Document processing started.",
|
||||
"newKnowledge.documents": "Documents",
|
||||
"newKnowledge.documentsDescription": "Knowledge assets and their revisions — where each document came from and whether the latest revision is indexed.",
|
||||
"newKnowledge.documentsDropHint": "or drop PDF, DOCX, Markdown, HTML, XLSX, or TXT to upload",
|
||||
"newKnowledge.documentsEmptyDescription": "Upload files here, or connect a source on the Sources tab — documents are parsed, chunked, and indexed automatically.",
|
||||
"newKnowledge.documentsEmptyTitle": "No documents yet",
|
||||
"newKnowledge.documentsErrorDescription": "We couldn't load documents. Try again.",
|
||||
"newKnowledge.documentsErrorTitle": "Documents couldn't load",
|
||||
"newKnowledge.documentsPermissionDescription": "You don't have permission to view documents in this knowledge base.",
|
||||
"newKnowledge.documentsPermissionTitle": "Documents are restricted",
|
||||
"newKnowledge.documentsReindexFailed": "We couldn't start re-indexing. Try again.",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} documents queued; {{missing}} no longer exist.",
|
||||
"newKnowledge.documentsReindexStarted": "Re-indexing started.",
|
||||
"newKnowledge.documentsSelected": "{{count}} documents selected",
|
||||
"newKnowledge.documentsUnavailable": "— documents",
|
||||
"newKnowledge.downloadDocuments": "Download",
|
||||
"newKnowledge.editSource": "Edit",
|
||||
"newKnowledge.emptyChunk": "Empty chunk",
|
||||
"newKnowledge.emptyDescription": "Add your documents and agents can read them directly — no indexing to configure.",
|
||||
"newKnowledge.emptyTitle": "A smarter way to build knowledge bases",
|
||||
"newKnowledge.errorDescription": "We couldn't load your new knowledge bases. Try again.",
|
||||
"newKnowledge.errorTitle": "Something went wrong",
|
||||
"newKnowledge.fileSize": "File size",
|
||||
"newKnowledge.filtersUnavailable": "This filter will become available when KnowledgeFS exposes its metadata.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl isn't available in the provider catalog for this workspace.",
|
||||
"newKnowledge.gotIt": "Got it",
|
||||
"newKnowledge.guideDescription": "Connect a Source and its Documents stay in sync — indexed automatically and ready to use, with no setup to manage. Your Legacy knowledge bases keep working as-is.",
|
||||
"newKnowledge.guideTitle": "Meet the new Knowledge Base ✨",
|
||||
"newKnowledge.illustrationHeadline": "Connect your content once — every app answers with the freshest knowledge.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Index information",
|
||||
"newKnowledge.interruptTask": "Interrupt",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Loading sync policy",
|
||||
"newKnowledge.manualUpload": "Manual upload",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Meta Data",
|
||||
"newKnowledge.mimeType": "File type",
|
||||
"newKnowledge.moreProviders": "More providers",
|
||||
"newKnowledge.name": "Knowledge name",
|
||||
"newKnowledge.namePlaceholder": "e.g. Customer Support Handbook",
|
||||
"newKnowledge.nameRequired": "Enter a knowledge base name.",
|
||||
"newKnowledge.new": "New",
|
||||
"newKnowledge.noBackgroundTasks": "No background tasks yet.",
|
||||
"newKnowledge.noDescription": "No description",
|
||||
"newKnowledge.noMatchingDocuments": "No documents match this search.",
|
||||
"newKnowledge.noMatchingSources": "No sources match this search.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "This knowledge base may have been deleted or you may not have access.",
|
||||
"newKnowledge.notFoundTitle": "Knowledge base not found",
|
||||
"newKnowledge.notionNotConnected": "Notion isn't connected",
|
||||
"newKnowledge.notionNotConnectedDescription": "Connect Notion to choose pages and keep them synchronized.",
|
||||
"newKnowledge.onlineDocuments": "Online documents",
|
||||
"newKnowledge.onlineDrive": "Online drive",
|
||||
"newKnowledge.overview": "Overview",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} selected",
|
||||
"newKnowledge.partialDocumentResults": "More documents may match. Load more to continue searching.",
|
||||
"newKnowledge.permission": "Permission",
|
||||
"newKnowledge.permissionAllMembers": "Workspace · all members can view and edit",
|
||||
"newKnowledge.permissionLoadFailed": "We couldn't verify your editing permissions. Try again.",
|
||||
"newKnowledge.permissionOnlyMe": "Only me",
|
||||
"newKnowledge.permissionRestricted": "Your workspace permissions don't allow changing this setting.",
|
||||
"newKnowledge.permissionUpdateFailed": "The knowledge base was created, but its permission could not be confirmed. Retry to finish setup.",
|
||||
"newKnowledge.preview": "Preview",
|
||||
"newKnowledge.previewUnavailable": "Preview becomes available after the backend finishes processing this file.",
|
||||
"newKnowledge.processDocument": "Process {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Interrupted",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Preparing",
|
||||
"newKnowledge.processingTaskState.failed": "Failed",
|
||||
"newKnowledge.processingTaskState.queued": "Queued",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Waiting to retry",
|
||||
"newKnowledge.processingTaskState.running": "Processing · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Completed",
|
||||
"newKnowledge.processingTaskState.superseded": "Superseded",
|
||||
"newKnowledge.providerConnected": "Firecrawl connected",
|
||||
"newKnowledge.providerLoadFailed": "Providers couldn't load",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} is not configured",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Add an API key or endpoint URL to crawl websites with {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "This provider is unavailable.",
|
||||
"newKnowledge.quality": "Quality",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Ask a workspace editor to create the first knowledge base.",
|
||||
"newKnowledge.refreshConnectionStatus": "Refresh status",
|
||||
"newKnowledge.refreshingConnection": "Refreshing…",
|
||||
"newKnowledge.reindexDocument": "Re-index",
|
||||
"newKnowledge.reindexDocuments": "Re-index",
|
||||
"newKnowledge.removeSource": "Remove source",
|
||||
"newKnowledge.retrievalCount": "Retrieval count",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Retry",
|
||||
"newKnowledge.retryReindexDocument": "Re-index again",
|
||||
"newKnowledge.retrySyncPolicy": "Retry sync policy",
|
||||
"newKnowledge.retryTask": "Retry",
|
||||
"newKnowledge.revisionState.active": "Active",
|
||||
"newKnowledge.revisionState.candidate": "Processing",
|
||||
"newKnowledge.revisionState.failed": "Failed",
|
||||
"newKnowledge.revisionState.superseded": "Previous",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "This section will become available in the next setup step.",
|
||||
"newKnowledge.searchDocuments": "Search documents",
|
||||
"newKnowledge.searchSources": "Search sources",
|
||||
"newKnowledge.selectAll": "Select all",
|
||||
"newKnowledge.selectAllDocuments": "Select all documents",
|
||||
"newKnowledge.selectAllSources": "Select all sources",
|
||||
"newKnowledge.selectChunk": "Select a chunk to inspect its content.",
|
||||
"newKnowledge.selectFilesAndFolders": "Select files & folders to sync",
|
||||
"newKnowledge.skippedFailed": "Skipped (failed)",
|
||||
"newKnowledge.skippedOffDomain": "Skipped (off-domain)",
|
||||
"newKnowledge.sourceActions": "Actions for {{name}}",
|
||||
"newKnowledge.sourceColumn": "Source",
|
||||
"newKnowledge.sourceFilterLabel": "Filter sources by status",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "Source setup is shown in full; editing and preview actions require the draft source backend workflow.",
|
||||
"newKnowledge.sourceStatus.active": "Active",
|
||||
"newKnowledge.sourceStatus.disabled": "Disabled",
|
||||
"newKnowledge.sourceStatus.error": "Error",
|
||||
"newKnowledge.sourceStatus.syncing": "Syncing",
|
||||
"newKnowledge.sourceType.connector": "Connected app",
|
||||
"newKnowledge.sourceType.object-storage": "Online drive",
|
||||
"newKnowledge.sourceType.upload": "Upload",
|
||||
"newKnowledge.sourceType.web": "Website crawl",
|
||||
"newKnowledge.sourceTypeLabel": "Source type",
|
||||
"newKnowledge.sources": "Sources",
|
||||
"newKnowledge.sourcesDescription": "Where knowledge comes from, how fresh it is, and whether the last sync succeeded.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Connect a website — Dify keeps it synced and fresh, so retrieval never breaks.",
|
||||
"newKnowledge.sourcesEmptyTitle": "No sources connected yet",
|
||||
"newKnowledge.sourcesErrorDescription": "We couldn't load sources. Try again.",
|
||||
"newKnowledge.sourcesErrorTitle": "Sources couldn't load",
|
||||
"newKnowledge.startEmpty": "Start empty",
|
||||
"newKnowledge.startEmptyDescription": "Create it now, add content later.",
|
||||
"newKnowledge.startWith": "Start with",
|
||||
"newKnowledge.startWithHelp": "You can add more Sources or Documents anytime.",
|
||||
"newKnowledge.statusColumn": "Status",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Sync now",
|
||||
"newKnowledge.syncPolicy": "Sync policy",
|
||||
"newKnowledge.syncPolicyColumn": "Sync policy",
|
||||
"newKnowledge.syncPolicyCustom": "Custom interval",
|
||||
"newKnowledge.syncPolicyDaily": "Every 24 hours",
|
||||
"newKnowledge.syncPolicyLoadFailed": "We couldn't load the sync policy.",
|
||||
"newKnowledge.syncPolicyManual": "Manual only",
|
||||
"newKnowledge.syncPolicyProvider": "Use provider schedule",
|
||||
"newKnowledge.tags": "Tags",
|
||||
"newKnowledge.taskActionFailed": "We couldn't update this task. Refresh and try again.",
|
||||
"newKnowledge.taskAttentionClear": "No background tasks need attention.",
|
||||
"newKnowledge.taskAttentionCount": "{{count}} background tasks need attention.",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}} background tasks need attention, including failed tasks.",
|
||||
"newKnowledge.taskFailedNotification": "Document processing failed — retry from Tasks",
|
||||
"newKnowledge.taskHistoryIncomplete": "More task history is available; the attention count is incomplete.",
|
||||
"newKnowledge.tasks": "Tasks",
|
||||
"newKnowledge.tasksErrorDescription": "We couldn't load background tasks. Try again.",
|
||||
"newKnowledge.tasksWithAttention": "Tasks, {{count}} need attention",
|
||||
"newKnowledge.tokenCount": "Tokens",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS isn't available for this workspace right now.",
|
||||
"newKnowledge.unavailableTitle": "New Knowledge is unavailable",
|
||||
"newKnowledge.updated": "Updated {{date}}",
|
||||
"newKnowledge.updatedAt": "Last updated",
|
||||
"newKnowledge.updatedColumn": "Updated",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Characters: —",
|
||||
"newKnowledge.uploadDocuments": "Upload documents",
|
||||
"newKnowledge.uploadFiles": "Upload files",
|
||||
"newKnowledge.uploadFilesDescription": "Add files from your computer — they're indexed as soon as you create.",
|
||||
"newKnowledge.uploadingFiles": "Uploading…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Knowledge view",
|
||||
"newKnowledge.websiteCrawl": "Website crawl",
|
||||
"noExternalKnowledge": "There is no External Knowledge API yet, click here to create",
|
||||
"parentMode.fullDoc": "Full-doc",
|
||||
"parentMode.paragraph": "Paragraph",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Se requiere el modelo de reclasificación para la mezcla de bases de conocimiento de alta calidad y económicas.",
|
||||
"mixtureInternalAndExternalTip": "El modelo de Rerank es necesario para la mezcla de conocimiento interno y externo.",
|
||||
"multimodal": "Multimodal",
|
||||
"newKnowledge.actionsColumn": "Acciones",
|
||||
"newKnowledge.addDocument": "Agregar documento",
|
||||
"newKnowledge.addSource": "Agregar fuente",
|
||||
"newKnowledge.addSourceDescription": "Conecte una fuente a esta base de conocimientos: se sincroniza automáticamente y se mantiene actualizada.",
|
||||
"newKnowledge.addSourceFailed": "No se pudo añadir esta fuente. Revisa la configuración e inténtalo de nuevo.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Todo el estado",
|
||||
"newKnowledge.allSources": "Todas las fuentes",
|
||||
"newKnowledge.apiAgentAccess": "Acceso a API y agentes",
|
||||
"newKnowledge.appsUnavailable": "— aplicaciones",
|
||||
"newKnowledge.authKind.api-key": "Clave API",
|
||||
"newKnowledge.authKind.endpoint": "Punto final",
|
||||
"newKnowledge.authenticationMethod": "Método de autenticación",
|
||||
"newKnowledge.averageChunkLength": "Longitud media del fragmento",
|
||||
"newKnowledge.backToList": "Volver a las bases de conocimiento",
|
||||
"newKnowledge.backgroundTasks": "Tareas en segundo plano",
|
||||
"newKnowledge.backgroundTasksDescription": "Agrega documentos, sincroniza fuentes y vuelve a indexar trabajos.",
|
||||
"newKnowledge.booleanFalse": "No",
|
||||
"newKnowledge.booleanTrue": "Sí",
|
||||
"newKnowledge.bulkDocumentActions": "Acciones de documentos masivos",
|
||||
"newKnowledge.cancelAddSource": "Cancelar",
|
||||
"newKnowledge.cardType": "Espacio de conocimiento",
|
||||
"newKnowledge.characterCount": "Caracteres",
|
||||
"newKnowledge.checkReindexStatus": "Comprobar estado de reindexación",
|
||||
"newKnowledge.chunkCount": "Fragmentos",
|
||||
"newKnowledge.chunkHeading": "Fragmento {{position}}",
|
||||
"newKnowledge.chunkLocation": "Posición {{position}} en esta revisión",
|
||||
"newKnowledge.clearDocumentSelection": "Borrar selección de documentos",
|
||||
"newKnowledge.configureProvider": "Configurar Firecrawl",
|
||||
"newKnowledge.connectNotion": "Conectar Notion",
|
||||
"newKnowledge.connectProvider": "Conectar Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Conectar proveedor",
|
||||
"newKnowledge.connectSource": "Conectar una fuente",
|
||||
"newKnowledge.connectSourceDescription": "El contenido se mantiene sincronizado con Notion, sitios web y otros servicios.",
|
||||
"newKnowledge.connectingProvider": "Conectando…",
|
||||
"newKnowledge.connectionFailed": "No pudimos conectar Firecrawl. Vuelva a ingresar la credencial e inténtelo nuevamente.",
|
||||
"newKnowledge.connectionNeedsAttention": "La conexión Firecrawl necesita atención",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Actualiza la conexión antes de agregar una fuente de sitio web.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl se está conectando",
|
||||
"newKnowledge.connectionRefreshFailed": "No pudimos actualizar esta conexión. Intentar otra vez.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Seguir comprobando el estado",
|
||||
"newKnowledge.crawlAndPreview": "Rastreo y vista previa",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Sin iniciar",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Vista previa del rastreo",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "La configuración de rastreo del sitio web está deshabilitada hasta que el flujo de trabajo de rastreo esté disponible.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl está listo",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "No pudimos crear esta base de conocimientos. Comprueba tu acceso y vuelve a intentarlo.",
|
||||
"newKnowledge.createIllustrationTitle": "Conecta tu contenido una vez: cada aplicación responderá con el conocimiento más reciente.",
|
||||
"newKnowledge.createTitle": "Crear conocimiento",
|
||||
"newKnowledge.createdAt": "Creado",
|
||||
"newKnowledge.creators": "Creadores",
|
||||
"newKnowledge.customIntervalHours": "Intervalo personalizado (horas)",
|
||||
"newKnowledge.customIntervalInvalid": "Introduce un número entero de 1 a 720 horas.",
|
||||
"newKnowledge.deleteDocuments": "Eliminar…",
|
||||
"newKnowledge.description": "Descripción",
|
||||
"newKnowledge.descriptionHelp": "Una descripción clara ayuda a las aplicaciones a decidir cuándo buscar este conocimiento.",
|
||||
"newKnowledge.descriptionPlaceholder": "p.ej. Política de reembolso, preguntas frecuentes sobre envíos y pasos a seguir: utilícelos para preguntas de atención al cliente",
|
||||
"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.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.",
|
||||
"newKnowledge.documentChunksLoadError": "No se pudieron cargar los fragmentos de esta revisión.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "No se pudieron cargar más fragmentos. Inténtalo de nuevo.",
|
||||
"newKnowledge.documentColumn": "Documento",
|
||||
"newKnowledge.documentContentIncomplete": "Cargando el contenido restante del documento…",
|
||||
"newKnowledge.documentContents": "Contenido",
|
||||
"newKnowledge.documentFacts": "Detalles del documento",
|
||||
"newKnowledge.documentFilterLabel": "Filtrar documentos por estado",
|
||||
"newKnowledge.documentLoadErrorDescription": "No se pudo cargar este documento. Inténtalo de nuevo.",
|
||||
"newKnowledge.documentLoadErrorTitle": "No se pudo cargar el documento",
|
||||
"newKnowledge.documentNotFoundDescription": "Es posible que este documento se haya eliminado o que no tengas acceso.",
|
||||
"newKnowledge.documentNotFoundTitle": "Documento no encontrado",
|
||||
"newKnowledge.documentOverviewDescription": "Lee la revisión indexada completa y consulta los detalles de su origen e índice.",
|
||||
"newKnowledge.documentPermissionRestricted": "Los permisos de tu espacio de trabajo no permiten acciones sobre documentos o tareas.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "La reindexación fue aceptada, pero el estado de la tarea está retrasado. Compruébalo antes de volver a enviarla.",
|
||||
"newKnowledge.documentReindexFailed": "La reindexación falló. La última revisión lista sigue disponible.",
|
||||
"newKnowledge.documentReindexProgress": "Reindexando · {{progress}} %",
|
||||
"newKnowledge.documentRevision": "Revisión",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Este documento aún no tiene una revisión lista. Vuelve cuando termine el procesamiento.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "No hay ninguna revisión disponible",
|
||||
"newKnowledge.documentRevisionsLoadError": "No se pudo cargar el historial de revisiones. La revisión actual sigue disponible.",
|
||||
"newKnowledge.documentStatus.disabled": "Desactivado",
|
||||
"newKnowledge.documentStatus.failed": "Fallido",
|
||||
"newKnowledge.documentStatus.processing": "Procesamiento",
|
||||
"newKnowledge.documentStatus.queued": "En cola",
|
||||
"newKnowledge.documentStatus.ready": "Listo",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Hay más historial de tareas disponible. Sigue comprobando para encontrar el estado de este documento.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "se superó el límite de tamaño del lote",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "se superó el límite de archivos",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "el archivo es demasiado grande",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "tipo de archivo no compatible o no válido",
|
||||
"newKnowledge.documentUploadExclusion.more": "y {{count}} más",
|
||||
"newKnowledge.documentUploadExclusion.processing": "error de procesamiento",
|
||||
"newKnowledge.documentUploadExclusion.quota": "se superó la cuota del espacio de trabajo",
|
||||
"newKnowledge.documentUploadExclusion.target": "el destino del documento ya no es válido",
|
||||
"newKnowledge.documentUploadFailed": "No pudimos cargar estos documentos. Intentar otra vez.",
|
||||
"newKnowledge.documentUploadPartial": "Se inició el procesamiento de {{accepted}} documentos; no se pudieron añadir {{excluded}}: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "No se aceptó ningún documento: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Se inició el procesamiento de documentos.",
|
||||
"newKnowledge.documents": "Documentos",
|
||||
"newKnowledge.documentsDescription": "Activos de conocimiento y sus revisiones: de dónde proviene cada documento y si la última revisión está indexada.",
|
||||
"newKnowledge.documentsDropHint": "o suelte PDF, DOCX, Markdown, HTML, XLSX o TXT para cargar",
|
||||
"newKnowledge.documentsEmptyDescription": "Cargue archivos aquí o conecte una fuente en la pestaña Fuentes: los documentos se analizan, fragmentan e indexan automáticamente.",
|
||||
"newKnowledge.documentsEmptyTitle": "Aún no hay documentos",
|
||||
"newKnowledge.documentsErrorDescription": "No pudimos cargar documentos. Intentar otra vez.",
|
||||
"newKnowledge.documentsErrorTitle": "No se pudieron cargar los documentos",
|
||||
"newKnowledge.documentsPermissionDescription": "No tiene permiso para ver documentos en esta base de conocimientos.",
|
||||
"newKnowledge.documentsPermissionTitle": "Los documentos están restringidos.",
|
||||
"newKnowledge.documentsReindexFailed": "No pudimos comenzar a volver a indexar. Intentar otra vez.",
|
||||
"newKnowledge.documentsReindexPartial": "Documentos {{queued}} en cola; {{missing}} ya no existe.",
|
||||
"newKnowledge.documentsReindexStarted": "Se inició la reindexación.",
|
||||
"newKnowledge.documentsSelected": "Documentos {{count}} seleccionados",
|
||||
"newKnowledge.documentsUnavailable": "— documentos",
|
||||
"newKnowledge.downloadDocuments": "Descargar",
|
||||
"newKnowledge.editSource": "Editar",
|
||||
"newKnowledge.emptyChunk": "Fragmento vacío",
|
||||
"newKnowledge.emptyDescription": "Añade tus documentos y los agentes podrán leerlos directamente, sin configurar la indexación.",
|
||||
"newKnowledge.emptyTitle": "Una forma más inteligente de crear bases de conocimiento",
|
||||
"newKnowledge.errorDescription": "No pudimos cargar tus nuevas bases de conocimiento. Inténtalo de nuevo.",
|
||||
"newKnowledge.errorTitle": "Algo salió mal",
|
||||
"newKnowledge.fileSize": "Tamaño del archivo",
|
||||
"newKnowledge.filtersUnavailable": "Este filtro estará disponible cuando KnowledgeFS exponga sus metadatos.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl no está disponible en el catálogo de proveedores para este espacio de trabajo.",
|
||||
"newKnowledge.gotIt": "Entendido",
|
||||
"newKnowledge.guideDescription": "Conecta una fuente y sus documentos se mantendrán sincronizados, indexados automáticamente y listos para usar, sin nada que administrar. Tus bases de conocimiento anteriores seguirán funcionando igual.",
|
||||
"newKnowledge.guideTitle": "Conoce la nueva base de conocimiento ✨",
|
||||
"newKnowledge.illustrationHeadline": "Conecta tu contenido una vez: cada aplicación responderá con el conocimiento más reciente.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Información del índice",
|
||||
"newKnowledge.interruptTask": "Interrumpir",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Cargando la política de sincronización",
|
||||
"newKnowledge.manualUpload": "Carga manual",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Metadatos",
|
||||
"newKnowledge.mimeType": "Tipo de archivo",
|
||||
"newKnowledge.moreProviders": "Más proveedores",
|
||||
"newKnowledge.name": "Nombre del conocimiento",
|
||||
"newKnowledge.namePlaceholder": "por ej. Manual de atención al cliente",
|
||||
"newKnowledge.nameRequired": "Introduzca un nombre para la base de conocimientos.",
|
||||
"newKnowledge.new": "Nuevo",
|
||||
"newKnowledge.noBackgroundTasks": "Aún no hay tareas en segundo plano.",
|
||||
"newKnowledge.noDescription": "Sin descripción",
|
||||
"newKnowledge.noMatchingDocuments": "Ningún documento coincide con esta búsqueda.",
|
||||
"newKnowledge.noMatchingSources": "Ninguna fuente coincide con esta búsqueda.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Es posible que esta base de conocimientos haya sido eliminada o que no tengas acceso.",
|
||||
"newKnowledge.notFoundTitle": "Base de conocimientos no encontrada",
|
||||
"newKnowledge.notionNotConnected": "Notion no está conectado",
|
||||
"newKnowledge.notionNotConnectedDescription": "Conecta Notion para elegir páginas y mantenerlas sincronizadas.",
|
||||
"newKnowledge.onlineDocuments": "Documentos en línea",
|
||||
"newKnowledge.onlineDrive": "Unidad en línea",
|
||||
"newKnowledge.overview": "Resumen",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} seleccionados",
|
||||
"newKnowledge.partialDocumentResults": "Puede haber más documentos coincidentes. Carga más para continuar la búsqueda.",
|
||||
"newKnowledge.permission": "Permiso",
|
||||
"newKnowledge.permissionAllMembers": "Espacio de trabajo · todos los miembros pueden ver y editar",
|
||||
"newKnowledge.permissionLoadFailed": "No se pudieron verificar sus permisos de edición. Inténtelo de nuevo.",
|
||||
"newKnowledge.permissionOnlyMe": "solo yo",
|
||||
"newKnowledge.permissionRestricted": "Los permisos de su espacio de trabajo no permiten cambiar esta configuración.",
|
||||
"newKnowledge.permissionUpdateFailed": "La base de conocimientos se creó, pero no se pudo confirmar su permiso. Vuelve a intentarlo para finalizar la configuración.",
|
||||
"newKnowledge.preview": "Vista previa",
|
||||
"newKnowledge.previewUnavailable": "La vista previa estará disponible cuando el backend termine de procesar este archivo.",
|
||||
"newKnowledge.processDocument": "Proceso {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "interrumpido",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "preparando",
|
||||
"newKnowledge.processingTaskState.failed": "Fallido",
|
||||
"newKnowledge.processingTaskState.queued": "En cola",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Esperando para volver a intentarlo",
|
||||
"newKnowledge.processingTaskState.running": "Procesamiento · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Completado",
|
||||
"newKnowledge.processingTaskState.superseded": "Reemplazado",
|
||||
"newKnowledge.providerConnected": "Firecrawl conectado",
|
||||
"newKnowledge.providerLoadFailed": "Los proveedores no pudieron cargar",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} no está configurado",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Agregue una clave API o una URL de punto final para rastrear sitios web con {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Este proveedor no está disponible.",
|
||||
"newKnowledge.quality": "Calidad",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Pide a un editor del espacio de trabajo que cree la primera base de conocimiento.",
|
||||
"newKnowledge.refreshConnectionStatus": "Estado de actualización",
|
||||
"newKnowledge.refreshingConnection": "Refrescante…",
|
||||
"newKnowledge.reindexDocument": "Reindexar",
|
||||
"newKnowledge.reindexDocuments": "Volver a indexar",
|
||||
"newKnowledge.removeSource": "Eliminar fuente",
|
||||
"newKnowledge.retrievalCount": "Número de recuperaciones",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Reintentar",
|
||||
"newKnowledge.retryReindexDocument": "Reindexar de nuevo",
|
||||
"newKnowledge.retrySyncPolicy": "Reintentar la política de sincronización",
|
||||
"newKnowledge.retryTask": "Reintentar",
|
||||
"newKnowledge.revisionState.active": "Activa",
|
||||
"newKnowledge.revisionState.candidate": "Procesando",
|
||||
"newKnowledge.revisionState.failed": "Fallida",
|
||||
"newKnowledge.revisionState.superseded": "Anterior",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Esta sección estará disponible en el siguiente paso de configuración.",
|
||||
"newKnowledge.searchDocuments": "Buscar documentos",
|
||||
"newKnowledge.searchSources": "Buscar fuentes",
|
||||
"newKnowledge.selectAll": "Seleccionar todo",
|
||||
"newKnowledge.selectAllDocuments": "Seleccionar todos los documentos",
|
||||
"newKnowledge.selectAllSources": "Seleccionar todos los orígenes",
|
||||
"newKnowledge.selectChunk": "Selecciona un fragmento para revisar su contenido.",
|
||||
"newKnowledge.selectFilesAndFolders": "Selecciona archivos y carpetas para sincronizar",
|
||||
"newKnowledge.skippedFailed": "Omitido (fallido)",
|
||||
"newKnowledge.skippedOffDomain": "Omitido (fuera del dominio)",
|
||||
"newKnowledge.sourceActions": "Acciones para {{name}}",
|
||||
"newKnowledge.sourceColumn": "Fuente",
|
||||
"newKnowledge.sourceFilterLabel": "Filtrar fuentes por estado",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "La configuración del origen se muestra completa; la edición y la vista previa requieren el flujo de backend del origen en borrador.",
|
||||
"newKnowledge.sourceStatus.active": "Activo",
|
||||
"newKnowledge.sourceStatus.disabled": "Deshabilitado",
|
||||
"newKnowledge.sourceStatus.error": "Error",
|
||||
"newKnowledge.sourceStatus.syncing": "Sincronización",
|
||||
"newKnowledge.sourceType.connector": "Aplicación conectada",
|
||||
"newKnowledge.sourceType.object-storage": "Unidad en línea",
|
||||
"newKnowledge.sourceType.upload": "Subir",
|
||||
"newKnowledge.sourceType.web": "Rastreo del sitio web",
|
||||
"newKnowledge.sourceTypeLabel": "Tipo de fuente",
|
||||
"newKnowledge.sources": "Fuentes",
|
||||
"newKnowledge.sourcesDescription": "De dónde proviene el conocimiento, qué tan actualizado está y si la última sincronización fue exitosa.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Conecte un sitio web: Dify lo mantiene sincronizado y actualizado, para que la recuperación nunca se interrumpa.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Aún no hay fuentes conectadas",
|
||||
"newKnowledge.sourcesErrorDescription": "No pudimos cargar fuentes. Intentar otra vez.",
|
||||
"newKnowledge.sourcesErrorTitle": "No se pudieron cargar las fuentes",
|
||||
"newKnowledge.startEmpty": "Empezar vacía",
|
||||
"newKnowledge.startEmptyDescription": "Créala ahora y añade contenido más tarde.",
|
||||
"newKnowledge.startWith": "Empezar con",
|
||||
"newKnowledge.startWithHelp": "Puede agregar más fuentes o documentos en cualquier momento.",
|
||||
"newKnowledge.statusColumn": "Estado",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Sincronizar ahora",
|
||||
"newKnowledge.syncPolicy": "Política de sincronización",
|
||||
"newKnowledge.syncPolicyColumn": "Política de sincronización",
|
||||
"newKnowledge.syncPolicyCustom": "Intervalo personalizado",
|
||||
"newKnowledge.syncPolicyDaily": "Cada 24 horas",
|
||||
"newKnowledge.syncPolicyLoadFailed": "No se pudo cargar la política de sincronización.",
|
||||
"newKnowledge.syncPolicyManual": "Solo manual",
|
||||
"newKnowledge.syncPolicyProvider": "Usar la programación del proveedor",
|
||||
"newKnowledge.tags": "Etiquetas",
|
||||
"newKnowledge.taskActionFailed": "No pudimos actualizar esta tarea. Actualiza e inténtalo de nuevo.",
|
||||
"newKnowledge.taskAttentionClear": "Ninguna tarea en segundo plano necesita atención.",
|
||||
"newKnowledge.taskAttentionCount": "Las tareas en segundo plano {{count}} necesitan atención.",
|
||||
"newKnowledge.taskAttentionErrorCount": "Las tareas en segundo plano de {{count}} necesitan atención, incluidas las tareas fallidas.",
|
||||
"newKnowledge.taskFailedNotification": "Error al procesar el documento: vuelva a intentarlo desde Tareas",
|
||||
"newKnowledge.taskHistoryIncomplete": "Hay más historial de tareas disponible; el recuento de tareas que requieren atención está incompleto.",
|
||||
"newKnowledge.tasks": "Tareas",
|
||||
"newKnowledge.tasksErrorDescription": "No pudimos cargar tareas en segundo plano. Intentar otra vez.",
|
||||
"newKnowledge.tasksWithAttention": "Tareas, {{count}} necesitan atención",
|
||||
"newKnowledge.tokenCount": "Unidades de token",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS no está disponible para este espacio de trabajo ahora mismo.",
|
||||
"newKnowledge.unavailableTitle": "El nuevo conocimiento no está disponible",
|
||||
"newKnowledge.updated": "Actualizado el {{date}}",
|
||||
"newKnowledge.updatedAt": "Última actualización",
|
||||
"newKnowledge.updatedColumn": "Actualizado",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Caracteres: —",
|
||||
"newKnowledge.uploadDocuments": "Subir documentos",
|
||||
"newKnowledge.uploadFiles": "Subir archivos",
|
||||
"newKnowledge.uploadFilesDescription": "Añade archivos desde tu ordenador; se indexarán en cuanto los crees.",
|
||||
"newKnowledge.uploadingFiles": "Subiendo…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Vista de conocimiento",
|
||||
"newKnowledge.websiteCrawl": "Rastreo del sitio web",
|
||||
"noExternalKnowledge": "Todavía no hay una API de conocimiento externo, haga clic aquí para crear",
|
||||
"parentMode.fullDoc": "Documento completo",
|
||||
"parentMode.paragraph": "Párrafo",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "مدل دوباره رتبهبندی برای ترکیب پایگاههای دانش با کیفیت بالا و اقتصادی لازم است.",
|
||||
"mixtureInternalAndExternalTip": "مدل Rerank برای آمیختگی دانش درونی و بیرونی مورد نیاز است.",
|
||||
"multimodal": "چندوجهی",
|
||||
"newKnowledge.actionsColumn": "اقدامات",
|
||||
"newKnowledge.addDocument": "سند اضافه کنید",
|
||||
"newKnowledge.addSource": "اضافه کردن منبع",
|
||||
"newKnowledge.addSourceDescription": "منبعی را به این پایگاه دانش متصل کنید - به طور خودکار همگامسازی میشود و تازه میماند.",
|
||||
"newKnowledge.addSourceFailed": "افزودن این منبع ممکن نشد. تنظیمات را بررسی کنید و دوباره تلاش کنید.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "همه وضعیت",
|
||||
"newKnowledge.allSources": "همه منابع",
|
||||
"newKnowledge.apiAgentAccess": "دسترسی API و عامل",
|
||||
"newKnowledge.appsUnavailable": "— برنامه",
|
||||
"newKnowledge.authKind.api-key": "کلید API",
|
||||
"newKnowledge.authKind.endpoint": "نقطه پایانی",
|
||||
"newKnowledge.authenticationMethod": "روش احراز هویت",
|
||||
"newKnowledge.averageChunkLength": "میانگین طول قطعه",
|
||||
"newKnowledge.backToList": "بازگشت به پایگاه های دانش",
|
||||
"newKnowledge.backgroundTasks": "وظایف پس زمینه",
|
||||
"newKnowledge.backgroundTasksDescription": "افزودن اسناد، همگام سازی منبع، و فهرست مجدد مشاغل.",
|
||||
"newKnowledge.booleanFalse": "نه",
|
||||
"newKnowledge.booleanTrue": "بله",
|
||||
"newKnowledge.bulkDocumentActions": "اقدامات سند انبوه",
|
||||
"newKnowledge.cancelAddSource": "لغو",
|
||||
"newKnowledge.cardType": "فضای دانش",
|
||||
"newKnowledge.characterCount": "نویسهها",
|
||||
"newKnowledge.checkReindexStatus": "بررسی وضعیت بازفهرستسازی",
|
||||
"newKnowledge.chunkCount": "قطعهها",
|
||||
"newKnowledge.chunkHeading": "قطعه {{position}}",
|
||||
"newKnowledge.chunkLocation": "موقعیت {{position}} در این بازبینی",
|
||||
"newKnowledge.clearDocumentSelection": "انتخاب سند را پاک کنید",
|
||||
"newKnowledge.configureProvider": "Firecrawl را پیکربندی کنید",
|
||||
"newKnowledge.connectNotion": "اتصال Notion",
|
||||
"newKnowledge.connectProvider": "Firecrawl را وصل کنید",
|
||||
"newKnowledge.connectProviderGeneric": "اتصال ارائهدهنده",
|
||||
"newKnowledge.connectSource": "اتصال منبع",
|
||||
"newKnowledge.connectSourceDescription": "محتوا با Notion، وبسایتها و سرویسهای دیگر همگام میماند.",
|
||||
"newKnowledge.connectingProvider": "در حال اتصال…",
|
||||
"newKnowledge.connectionFailed": "ما نتوانستیم Firecrawl را متصل کنیم. اعتبارنامه را دوباره وارد کنید و دوباره امتحان کنید.",
|
||||
"newKnowledge.connectionNeedsAttention": "اتصال Firecrawl نیاز به توجه دارد",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "قبل از افزودن منبع وب سایت، اتصال را تازه کنید.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl در حال اتصال است",
|
||||
"newKnowledge.connectionRefreshFailed": "ما نتوانستیم این اتصال را بازخوانی کنیم. دوباره امتحان کنید.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "ادامه بررسی وضعیت کار",
|
||||
"newKnowledge.crawlAndPreview": "خزیدن و پیشنمایش",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "شروع نشده",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "پیشنمایش خزش",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "پیکربندی خزیدن وبسایت تا زمانی که گردش کار خزیدن در دسترس نباشد، غیرفعال است.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl آماده است",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "ما نتوانستیم این پایگاه دانش را ایجاد کنیم. دسترسی خود را بررسی کنید و دوباره امتحان کنید.",
|
||||
"newKnowledge.createIllustrationTitle": "محتوای خود را یکبار متصل کنید — هر برنامه با تازهترین دانش پاسخ میدهد.",
|
||||
"newKnowledge.createTitle": "ایجاد دانش",
|
||||
"newKnowledge.createdAt": "ایجادشده",
|
||||
"newKnowledge.creators": "ایجادکنندگان",
|
||||
"newKnowledge.customIntervalHours": "بازه سفارشی (ساعت)",
|
||||
"newKnowledge.customIntervalInvalid": "یک عدد صحیح بین ۱ تا ۷۲۰ ساعت وارد کنید.",
|
||||
"newKnowledge.deleteDocuments": "حذف…",
|
||||
"newKnowledge.description": "توضیحات",
|
||||
"newKnowledge.descriptionHelp": "توضیح واضح به برنامهها کمک میکند تصمیم بگیرند که چه زمانی این دانش را جستجو کنند.",
|
||||
"newKnowledge.descriptionPlaceholder": "به عنوان مثال خط مشی بازپرداخت، سؤالات متداول حمل و نقل، و مراحل افزایش - برای سؤالات پشتیبانی مشتری استفاده کنید",
|
||||
"newKnowledge.detailErrorDescription": "ما نتوانستیم این پایگاه دانش را بارگیری کنیم. دوباره امتحان کنید.",
|
||||
"newKnowledge.detailErrorTitle": "مشکلی پیش آمد",
|
||||
"newKnowledge.disableSource": "غیر فعال کردن",
|
||||
"newKnowledge.discardDraftConfirm": "حذف پیشنویس",
|
||||
"newKnowledge.discardDraftDescription": "تغییرات شما در این پیشنویس پایگاه دانش از بین خواهد رفت.",
|
||||
"newKnowledge.discardDraftTitle": "این پیشنویس پایگاه دانش حذف شود؟",
|
||||
"newKnowledge.discardSourceChanges": "تغییرات منبع کنار گذاشته شود؟",
|
||||
"newKnowledge.discardSourceChangesConfirm": "کنار گذاشتن تغییرات",
|
||||
"newKnowledge.discardSourceChangesDescription": "پیشنمایش خزش، انتخاب صفحهها و تنظیمات همگامسازی از دست میروند.",
|
||||
"newKnowledge.documentActions": "اقدامات برای {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "اقدامات سند هنوز برای این پایگاه دانش در دسترس نیست.",
|
||||
"newKnowledge.documentChunksEmpty": "هنوز قطعهای برای این بازبینی موجود نیست.",
|
||||
"newKnowledge.documentChunksLoadError": "قطعههای این بازبینی بارگیری نشد.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "قطعههای بیشتری بارگیری نشد. دوباره تلاش کنید.",
|
||||
"newKnowledge.documentColumn": "سند",
|
||||
"newKnowledge.documentContentIncomplete": "در حال بارگذاری ادامه محتوای سند…",
|
||||
"newKnowledge.documentContents": "محتوا",
|
||||
"newKnowledge.documentFacts": "جزئیات سند",
|
||||
"newKnowledge.documentFilterLabel": "اسناد را بر اساس وضعیت فیلتر کنید",
|
||||
"newKnowledge.documentLoadErrorDescription": "این سند بارگیری نشد. دوباره تلاش کنید.",
|
||||
"newKnowledge.documentLoadErrorTitle": "سند بارگیری نشد",
|
||||
"newKnowledge.documentNotFoundDescription": "ممکن است این سند حذف شده باشد یا شما دسترسی نداشته باشید.",
|
||||
"newKnowledge.documentNotFoundTitle": "سند پیدا نشد",
|
||||
"newKnowledge.documentOverviewDescription": "نسخه نمایهشده کامل را بخوانید و جزئیات منبع و نمایه آن را بررسی کنید.",
|
||||
"newKnowledge.documentPermissionRestricted": "مجوزهای فضای کاری شما اجازه انجام عملیات روی اسناد یا وظایف را نمیدهد.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "بازفهرستسازی پذیرفته شد، اما وضعیت کار با تأخیر نمایش داده میشود. پیش از ارسال دوباره آن را بررسی کنید.",
|
||||
"newKnowledge.documentReindexFailed": "نمایهسازی دوباره ناموفق بود. آخرین بازبینی آماده همچنان در دسترس است.",
|
||||
"newKnowledge.documentReindexProgress": "در حال نمایهسازی دوباره · {{progress}}٪",
|
||||
"newKnowledge.documentRevision": "بازبینی",
|
||||
"newKnowledge.documentRevisionMissingDescription": "این سند هنوز بازبینی آمادهای ندارد. پس از پایان پردازش دوباره بررسی کنید.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "هیچ بازبینی در دسترس نیست",
|
||||
"newKnowledge.documentRevisionsLoadError": "تاریخچه بازبینی بارگیری نشد. بازبینی فعلی همچنان در دسترس است.",
|
||||
"newKnowledge.documentStatus.disabled": "غیرفعال",
|
||||
"newKnowledge.documentStatus.failed": "شکست خورد",
|
||||
"newKnowledge.documentStatus.processing": "در حال پردازش",
|
||||
"newKnowledge.documentStatus.queued": "در صف",
|
||||
"newKnowledge.documentStatus.ready": "آماده است",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "سابقه کار بیشتری موجود است. برای یافتن وضعیت این سند بررسی را ادامه دهید.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "محدودیت اندازه دسته رد شده است",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "محدودیت تعداد فایل رد شده است",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "فایل بیش از حد بزرگ است",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "نوع فایل پشتیبانی نمیشود یا نامعتبر است",
|
||||
"newKnowledge.documentUploadExclusion.more": "و {{count}} مورد دیگر",
|
||||
"newKnowledge.documentUploadExclusion.processing": "پردازش ناموفق بود",
|
||||
"newKnowledge.documentUploadExclusion.quota": "سهمیه فضای کاری رد شده است",
|
||||
"newKnowledge.documentUploadExclusion.target": "هدف سند دیگر معتبر نیست",
|
||||
"newKnowledge.documentUploadFailed": "ما نتوانستیم این اسناد را آپلود کنیم. دوباره امتحان کنید.",
|
||||
"newKnowledge.documentUploadPartial": "پردازش {{accepted}} سند آغاز شد؛ {{excluded}} سند افزوده نشد: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "هیچ سندی پذیرفته نشد: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "پردازش اسناد آغاز شد.",
|
||||
"newKnowledge.documents": "اسناد",
|
||||
"newKnowledge.documentsDescription": "داراییهای دانش و بازبینیهای آنها - هر سند از کجا آمده است و آیا آخرین ویرایش نمایه شده است یا خیر.",
|
||||
"newKnowledge.documentsDropHint": "یا PDF، DOCX، Markdown، HTML، XLSX یا TXT را برای آپلود رها کنید",
|
||||
"newKnowledge.documentsEmptyDescription": "فایلها را در اینجا آپلود کنید، یا یک منبع را در برگه منابع وصل کنید - اسناد به طور خودکار تجزیه، تکهتکه و فهرستبندی میشوند.",
|
||||
"newKnowledge.documentsEmptyTitle": "هنوز هیچ سندی وجود ندارد",
|
||||
"newKnowledge.documentsErrorDescription": "نتوانستیم اسناد را بارگیری کنیم. دوباره امتحان کنید.",
|
||||
"newKnowledge.documentsErrorTitle": "اسناد بارگیری نشد",
|
||||
"newKnowledge.documentsPermissionDescription": "شما اجازه مشاهده اسناد در این پایگاه دانش را ندارید.",
|
||||
"newKnowledge.documentsPermissionTitle": "اسناد محدود شده است",
|
||||
"newKnowledge.documentsReindexFailed": "نتوانستیم فهرستبندی مجدد را شروع کنیم. دوباره امتحان کنید.",
|
||||
"newKnowledge.documentsReindexPartial": "اسناد {{queued}} در صف قرار گرفتند. {{missing}} دیگر وجود ندارد.",
|
||||
"newKnowledge.documentsReindexStarted": "فهرست بندی مجدد آغاز شد.",
|
||||
"newKnowledge.documentsSelected": "اسناد {{count}} انتخاب شد",
|
||||
"newKnowledge.documentsUnavailable": "— سند",
|
||||
"newKnowledge.downloadDocuments": "دانلود کنید",
|
||||
"newKnowledge.editSource": "ویرایش",
|
||||
"newKnowledge.emptyChunk": "قطعه خالی",
|
||||
"newKnowledge.emptyDescription": "اسناد خود را اضافه کنید تا عاملها بتوانند مستقیماً آنها را بخوانند — بدون نیاز به تنظیم نمایهسازی.",
|
||||
"newKnowledge.emptyTitle": "روشی هوشمندتر برای ساخت پایگاههای دانش",
|
||||
"newKnowledge.errorDescription": "پایگاههای دانش جدید بارگذاری نشدند. دوباره تلاش کنید.",
|
||||
"newKnowledge.errorTitle": "مشکلی پیش آمد",
|
||||
"newKnowledge.fileSize": "اندازه فایل",
|
||||
"newKnowledge.filtersUnavailable": "این فیلتر پس از ارائه فراداده توسط KnowledgeFS در دسترس خواهد بود.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl در کاتالوگ ارائه دهنده برای این فضای کاری موجود نیست.",
|
||||
"newKnowledge.gotIt": "متوجه شدم",
|
||||
"newKnowledge.guideDescription": "یک منبع را متصل کنید تا اسناد آن همگام، خودکار نمایهسازی و آماده استفاده بمانند؛ بدون تنظیماتی برای مدیریت. پایگاههای دانش قبلی شما همچنان مانند گذشته کار میکنند.",
|
||||
"newKnowledge.guideTitle": "با پایگاه دانش جدید آشنا شوید ✨",
|
||||
"newKnowledge.illustrationHeadline": "محتوای خود را یکبار متصل کنید تا هر برنامه با تازهترین دانش پاسخ دهد.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "اطلاعات نمایه",
|
||||
"newKnowledge.interruptTask": "توقف",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid 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": "بارگیری بازبینیهای قدیمیتر",
|
||||
"newKnowledge.loadingSyncPolicy": "در حال بارگیری سیاست همگامسازی",
|
||||
"newKnowledge.manualUpload": "آپلود دستی",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "متا دیتا",
|
||||
"newKnowledge.mimeType": "نوع فایل",
|
||||
"newKnowledge.moreProviders": "ارائهدهندگان بیشتر",
|
||||
"newKnowledge.name": "نام دانش",
|
||||
"newKnowledge.namePlaceholder": "به عنوان مثال راهنمای پشتیبانی مشتری",
|
||||
"newKnowledge.nameRequired": "یک نام پایگاه دانش وارد کنید.",
|
||||
"newKnowledge.new": "جدید",
|
||||
"newKnowledge.noBackgroundTasks": "هنوز هیچ کار پس زمینه ای وجود ندارد.",
|
||||
"newKnowledge.noDescription": "بدون توضیح",
|
||||
"newKnowledge.noMatchingDocuments": "هیچ سندی با این جستجو مطابقت ندارد.",
|
||||
"newKnowledge.noMatchingSources": "هیچ منبعی با این جستجو مطابقت ندارد.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "این پایگاه دانش ممکن است حذف شده باشد یا ممکن است دسترسی نداشته باشید.",
|
||||
"newKnowledge.notFoundTitle": "پایگاه دانش یافت نشد",
|
||||
"newKnowledge.notionNotConnected": "Notion متصل نیست",
|
||||
"newKnowledge.notionNotConnectedDescription": "برای انتخاب صفحهها و همگام نگهداشتن آنها، Notion را متصل کنید.",
|
||||
"newKnowledge.onlineDocuments": "اسناد آنلاین",
|
||||
"newKnowledge.onlineDrive": "درایو آنلاین",
|
||||
"newKnowledge.overview": "نمای کلی",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} مورد انتخاب شد",
|
||||
"newKnowledge.partialDocumentResults": "ممکن است اسناد بیشتری مطابقت داشته باشند. برای ادامه جستجو موارد بیشتری بارگیری کنید.",
|
||||
"newKnowledge.permission": "اجازه",
|
||||
"newKnowledge.permissionAllMembers": "فضای کاری · همه اعضا می توانند مشاهده و ویرایش کنند",
|
||||
"newKnowledge.permissionLoadFailed": "تأیید مجوزهای ویرایش ممکن نشد. دوباره تلاش کنید.",
|
||||
"newKnowledge.permissionOnlyMe": "فقط من",
|
||||
"newKnowledge.permissionRestricted": "مجوزهای فضای کاری شما اجازه تغییر این تنظیم را نمی دهد.",
|
||||
"newKnowledge.permissionUpdateFailed": "پایگاه دانش ایجاد شد، اما مجوز آن تأیید نشد. برای تکمیل راهاندازی دوباره تلاش کنید.",
|
||||
"newKnowledge.preview": "پیشنمایش",
|
||||
"newKnowledge.previewUnavailable": "پس از پایان پردازش این فایل در بخش پشتیبان، پیشنمایش در دسترس میشود.",
|
||||
"newKnowledge.processDocument": "فرآیند {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "قطع شد",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "آماده سازی",
|
||||
"newKnowledge.processingTaskState.failed": "شکست خورد",
|
||||
"newKnowledge.processingTaskState.queued": "در صف",
|
||||
"newKnowledge.processingTaskState.retry_wait": "در انتظار تلاش مجدد",
|
||||
"newKnowledge.processingTaskState.running": "پردازش · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "تکمیل شد",
|
||||
"newKnowledge.processingTaskState.superseded": "جایگزین شد",
|
||||
"newKnowledge.providerConnected": "Firecrawl متصل شد",
|
||||
"newKnowledge.providerLoadFailed": "ارائهدهندگان نتوانستند بارگیری کنند",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} پیکربندی نشده است",
|
||||
"newKnowledge.providerNotConfiguredDescription": "برای خزیدن وبسایتها با {{provider}}، یک کلید API یا URL نقطه پایانی اضافه کنید.",
|
||||
"newKnowledge.providerUnavailable": "این ارائه دهنده در دسترس نیست.",
|
||||
"newKnowledge.quality": "کیفیت",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "از یک ویرایشگر فضای کاری بخواهید اولین پایگاه دانش را ایجاد کند.",
|
||||
"newKnowledge.refreshConnectionStatus": "وضعیت بازخوانی",
|
||||
"newKnowledge.refreshingConnection": "تازهسازی…",
|
||||
"newKnowledge.reindexDocument": "نمایهسازی دوباره",
|
||||
"newKnowledge.reindexDocuments": "فهرست مجدد",
|
||||
"newKnowledge.removeSource": "حذف منبع",
|
||||
"newKnowledge.retrievalCount": "تعداد بازیابی",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "دوباره امتحان کنید",
|
||||
"newKnowledge.retryReindexDocument": "بازفهرستسازی دوباره",
|
||||
"newKnowledge.retrySyncPolicy": "تلاش دوباره برای سیاست همگامسازی",
|
||||
"newKnowledge.retryTask": "دوباره امتحان کنید",
|
||||
"newKnowledge.revisionState.active": "فعال",
|
||||
"newKnowledge.revisionState.candidate": "در حال پردازش",
|
||||
"newKnowledge.revisionState.failed": "ناموفق",
|
||||
"newKnowledge.revisionState.superseded": "قبلی",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "این بخش در مرحله راه اندازی بعدی در دسترس خواهد بود.",
|
||||
"newKnowledge.searchDocuments": "جستجوی اسناد",
|
||||
"newKnowledge.searchSources": "منابع جستجو",
|
||||
"newKnowledge.selectAll": "انتخاب همه",
|
||||
"newKnowledge.selectAllDocuments": "همه اسناد را انتخاب کنید",
|
||||
"newKnowledge.selectAllSources": "انتخاب همه منابع",
|
||||
"newKnowledge.selectChunk": "یک قطعه را برای بررسی محتوای آن انتخاب کنید.",
|
||||
"newKnowledge.selectFilesAndFolders": "فایلها و پوشهها را برای همگامسازی انتخاب کنید",
|
||||
"newKnowledge.skippedFailed": "رد شد (ناموفق)",
|
||||
"newKnowledge.skippedOffDomain": "رد شد (خارج از دامنه)",
|
||||
"newKnowledge.sourceActions": "اقدامات برای {{name}}",
|
||||
"newKnowledge.sourceColumn": "منبع",
|
||||
"newKnowledge.sourceFilterLabel": "منابع را بر اساس وضعیت فیلتر کنید",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "تنظیم منبع بهطور کامل نمایش داده شده است؛ ویرایش و پیشنمایش به گردشکار پشتیبان منبع پیشنویس نیاز دارد.",
|
||||
"newKnowledge.sourceStatus.active": "فعال",
|
||||
"newKnowledge.sourceStatus.disabled": "غیرفعال شد",
|
||||
"newKnowledge.sourceStatus.error": "خطا",
|
||||
"newKnowledge.sourceStatus.syncing": "در حال همگام سازی",
|
||||
"newKnowledge.sourceType.connector": "برنامه متصل",
|
||||
"newKnowledge.sourceType.object-storage": "درایو آنلاین",
|
||||
"newKnowledge.sourceType.upload": "آپلود",
|
||||
"newKnowledge.sourceType.web": "خزیدن وب سایت",
|
||||
"newKnowledge.sourceTypeLabel": "نوع منبع",
|
||||
"newKnowledge.sources": "منابع",
|
||||
"newKnowledge.sourcesDescription": "دانش از کجا می آید، چقدر تازه است و آیا آخرین همگام سازی موفق بوده است یا خیر.",
|
||||
"newKnowledge.sourcesEmptyDescription": "اتصال یک وبسایت - Dify آن را همگامسازی و تازه نگه میدارد، بنابراین بازیابی هرگز خراب نمیشود.",
|
||||
"newKnowledge.sourcesEmptyTitle": "هنوز منبعی متصل نشده است",
|
||||
"newKnowledge.sourcesErrorDescription": "ما نتوانستیم منابع را بارگیری کنیم. دوباره امتحان کنید.",
|
||||
"newKnowledge.sourcesErrorTitle": "منابع بارگیری نشدند",
|
||||
"newKnowledge.startEmpty": "شروع از خالی",
|
||||
"newKnowledge.startEmptyDescription": "اکنون ایجاد کنید و بعداً محتوا اضافه کنید.",
|
||||
"newKnowledge.startWith": "شروع کنید با",
|
||||
"newKnowledge.startWithHelp": "میتوانید منابع یا اسناد بیشتری را در هر زمان اضافه کنید.",
|
||||
"newKnowledge.statusColumn": "وضعیت",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "اکنون همگام سازی کنید",
|
||||
"newKnowledge.syncPolicy": "سیاست همگامسازی",
|
||||
"newKnowledge.syncPolicyColumn": "خط مشی همگام سازی",
|
||||
"newKnowledge.syncPolicyCustom": "بازه سفارشی",
|
||||
"newKnowledge.syncPolicyDaily": "هر ۲۴ ساعت",
|
||||
"newKnowledge.syncPolicyLoadFailed": "بارگیری سیاست همگامسازی ممکن نشد.",
|
||||
"newKnowledge.syncPolicyManual": "فقط دستی",
|
||||
"newKnowledge.syncPolicyProvider": "استفاده از برنامه ارائهدهنده",
|
||||
"newKnowledge.tags": "برچسبها",
|
||||
"newKnowledge.taskActionFailed": "ما نتوانستیم این کار را به روز کنیم. بازخوانی کنید و دوباره امتحان کنید.",
|
||||
"newKnowledge.taskAttentionClear": "هیچ کار پس زمینه ای نیاز به توجه ندارد.",
|
||||
"newKnowledge.taskAttentionCount": "وظایف پس زمینه {{count}} نیاز به توجه دارند.",
|
||||
"newKnowledge.taskAttentionErrorCount": "وظایف پس زمینه {{count}} نیاز به توجه دارند، از جمله وظایف ناموفق.",
|
||||
"newKnowledge.taskFailedNotification": "پردازش سند ناموفق بود — از بخش وظایف دوباره امتحان کنید",
|
||||
"newKnowledge.taskHistoryIncomplete": "تاریخچه وظایف بیشتری موجود است؛ تعداد وظایف نیازمند توجه کامل نیست.",
|
||||
"newKnowledge.tasks": "وظایف",
|
||||
"newKnowledge.tasksErrorDescription": "نمیتوانیم وظایف پسزمینه را بارگیری کنیم. دوباره امتحان کنید.",
|
||||
"newKnowledge.tasksWithAttention": "وظایف، {{count}} نیاز به توجه دارد",
|
||||
"newKnowledge.tokenCount": "توکنها",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS اکنون برای این فضای کاری در دسترس نیست.",
|
||||
"newKnowledge.unavailableTitle": "دانش جدید در دسترس نیست",
|
||||
"newKnowledge.updated": "بهروزرسانی در {{date}}",
|
||||
"newKnowledge.updatedAt": "آخرین بهروزرسانی",
|
||||
"newKnowledge.updatedColumn": "به روز شد",
|
||||
"newKnowledge.uploadCharactersUnavailable": "نویسهها: —",
|
||||
"newKnowledge.uploadDocuments": "اسناد را بارگذاری کنید",
|
||||
"newKnowledge.uploadFiles": "بارگذاری فایلها",
|
||||
"newKnowledge.uploadFilesDescription": "فایلها را از رایانه اضافه کنید — بهمحض ایجاد نمایهسازی میشوند.",
|
||||
"newKnowledge.uploadingFiles": "در حال بارگذاری…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "نمای دانش",
|
||||
"newKnowledge.websiteCrawl": "خزیدن وب سایت",
|
||||
"noExternalKnowledge": "هنوز هیچ API دانش خارجی وجود ندارد، برای ایجاد اینجا را کلیک کنید",
|
||||
"parentMode.fullDoc": "مستند کامل",
|
||||
"parentMode.paragraph": "پاراگراف",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Le modèle de reclassement est nécessaire pour le mélange de bases de connaissances de haute qualité et économiques.",
|
||||
"mixtureInternalAndExternalTip": "Le modèle Rerank est nécessaire pour mélanger les connaissances internes et externes.",
|
||||
"multimodal": "Multimodal",
|
||||
"newKnowledge.actionsColumn": "Actions",
|
||||
"newKnowledge.addDocument": "Ajouter un document",
|
||||
"newKnowledge.addSource": "Ajouter une source",
|
||||
"newKnowledge.addSourceDescription": "Connectez une source à cette base de connaissances : elle se synchronise automatiquement et reste à jour.",
|
||||
"newKnowledge.addSourceFailed": "Impossible d’ajouter cette source. Vérifiez les paramètres et réessayez.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Tous les statuts",
|
||||
"newKnowledge.allSources": "Toutes les sources",
|
||||
"newKnowledge.apiAgentAccess": "Accès API et agents",
|
||||
"newKnowledge.appsUnavailable": "— applications",
|
||||
"newKnowledge.authKind.api-key": "Clé API",
|
||||
"newKnowledge.authKind.endpoint": "Point de terminaison",
|
||||
"newKnowledge.authenticationMethod": "Méthode d'authentification",
|
||||
"newKnowledge.averageChunkLength": "Longueur moyenne des segments",
|
||||
"newKnowledge.backToList": "Retour aux bases de connaissances",
|
||||
"newKnowledge.backgroundTasks": "Tâches en arrière-plan",
|
||||
"newKnowledge.backgroundTasksDescription": "Ajouts de documents, synchronisations de sources et tâches de réindexation.",
|
||||
"newKnowledge.booleanFalse": "Non",
|
||||
"newKnowledge.booleanTrue": "Oui",
|
||||
"newKnowledge.bulkDocumentActions": "Actions de documents en masse",
|
||||
"newKnowledge.cancelAddSource": "Annuler",
|
||||
"newKnowledge.cardType": "Espace de connaissances",
|
||||
"newKnowledge.characterCount": "Caractères",
|
||||
"newKnowledge.checkReindexStatus": "Vérifier l’état de réindexation",
|
||||
"newKnowledge.chunkCount": "Segments",
|
||||
"newKnowledge.chunkHeading": "Segment {{position}}",
|
||||
"newKnowledge.chunkLocation": "Position {{position}} dans cette révision",
|
||||
"newKnowledge.clearDocumentSelection": "Effacer la sélection des documents",
|
||||
"newKnowledge.configureProvider": "Configurer Firecrawl",
|
||||
"newKnowledge.connectNotion": "Connecter Notion",
|
||||
"newKnowledge.connectProvider": "Connecter Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Connecter un fournisseur",
|
||||
"newKnowledge.connectSource": "Connecter une source",
|
||||
"newKnowledge.connectSourceDescription": "Le contenu reste synchronisé avec Notion, les sites web et d’autres services.",
|
||||
"newKnowledge.connectingProvider": "Connexion…",
|
||||
"newKnowledge.connectionFailed": "Nous n'avons pas pu connecter Firecrawl. Saisissez à nouveau les informations d'identification et réessayez.",
|
||||
"newKnowledge.connectionNeedsAttention": "La connexion Firecrawl nécessite une attention particulière",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Actualisez la connexion avant d'ajouter une source de site Web.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl se connecte",
|
||||
"newKnowledge.connectionRefreshFailed": "Nous n'avons pas pu actualiser cette connexion. Essayer à nouveau.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Continuer à vérifier l’état",
|
||||
"newKnowledge.crawlAndPreview": "Exploration et aperçu",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Non démarré",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Aperçu de l’exploration",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "La configuration de l'analyse du site Web est désactivée jusqu'à ce que le flux de travail d'analyse soit disponible.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl est prêt",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Nous n'avons pas pu créer cette base de connaissances. Vérifiez votre accès et réessayez.",
|
||||
"newKnowledge.createIllustrationTitle": "Connectez votre contenu une fois — chaque application répond avec les connaissances les plus récentes.",
|
||||
"newKnowledge.createTitle": "Créer des connaissances",
|
||||
"newKnowledge.createdAt": "Créé",
|
||||
"newKnowledge.creators": "Créateurs",
|
||||
"newKnowledge.customIntervalHours": "Intervalle personnalisé (heures)",
|
||||
"newKnowledge.customIntervalInvalid": "Saisissez un nombre entier compris entre 1 et 720 heures.",
|
||||
"newKnowledge.deleteDocuments": "Supprimer…",
|
||||
"newKnowledge.description": "Descriptif",
|
||||
"newKnowledge.descriptionHelp": "Une description claire aide les applications à décider quand rechercher ces connaissances.",
|
||||
"newKnowledge.descriptionPlaceholder": "par ex. Politique de remboursement, FAQ sur l'expédition et étapes de remontée d'informations – à utiliser pour les questions d'assistance client",
|
||||
"newKnowledge.detailErrorDescription": "Nous n'avons pas pu charger cette base de connaissances. Essayer à nouveau.",
|
||||
"newKnowledge.detailErrorTitle": "Quelque chose s'est mal passé",
|
||||
"newKnowledge.disableSource": "Désactiver",
|
||||
"newKnowledge.discardDraftConfirm": "Abandonner le brouillon",
|
||||
"newKnowledge.discardDraftDescription": "Les modifications apportées à ce brouillon de base de connaissances seront perdues.",
|
||||
"newKnowledge.discardDraftTitle": "Abandonner ce brouillon de base de connaissances ?",
|
||||
"newKnowledge.discardSourceChanges": "Ignorer les modifications de la source ?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Ignorer les modifications",
|
||||
"newKnowledge.discardSourceChangesDescription": "L’aperçu de l’exploration, la sélection des pages et les paramètres de synchronisation seront perdus.",
|
||||
"newKnowledge.documentActions": "Actions pour {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Les actions sur les documents ne sont pas encore disponibles pour cette base de connaissances.",
|
||||
"newKnowledge.documentChunksEmpty": "Aucun segment n’est encore disponible pour cette révision.",
|
||||
"newKnowledge.documentChunksLoadError": "Impossible de charger les segments de cette révision.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Impossible de charger davantage de segments. Réessayez.",
|
||||
"newKnowledge.documentColumn": "Document",
|
||||
"newKnowledge.documentContentIncomplete": "Chargement du reste du document…",
|
||||
"newKnowledge.documentContents": "Contenu",
|
||||
"newKnowledge.documentFacts": "Détails du document",
|
||||
"newKnowledge.documentFilterLabel": "Filtrer les documents par statut",
|
||||
"newKnowledge.documentLoadErrorDescription": "Impossible de charger ce document. Réessayez.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Impossible de charger le document",
|
||||
"newKnowledge.documentNotFoundDescription": "Ce document a peut-être été supprimé ou vous n’y avez pas accès.",
|
||||
"newKnowledge.documentNotFoundTitle": "Document introuvable",
|
||||
"newKnowledge.documentOverviewDescription": "Lisez la révision indexée complète et consultez les détails de sa source et de son index.",
|
||||
"newKnowledge.documentPermissionRestricted": "Les autorisations de votre espace de travail ne permettent pas les actions sur les documents ou les tâches.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "La réindexation a été acceptée, mais l’état de la tâche est retardé. Vérifiez-le avant un nouvel envoi.",
|
||||
"newKnowledge.documentReindexFailed": "La réindexation a échoué. La dernière révision prête reste disponible.",
|
||||
"newKnowledge.documentReindexProgress": "Réindexation · {{progress}} %",
|
||||
"newKnowledge.documentRevision": "Révision",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Ce document ne possède pas encore de révision prête. Revenez une fois le traitement terminé.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Aucune révision disponible",
|
||||
"newKnowledge.documentRevisionsLoadError": "Impossible de charger l’historique des révisions. La révision actuelle reste disponible.",
|
||||
"newKnowledge.documentStatus.disabled": "Désactivé",
|
||||
"newKnowledge.documentStatus.failed": "Échec",
|
||||
"newKnowledge.documentStatus.processing": "Traitement",
|
||||
"newKnowledge.documentStatus.queued": "En file d'attente",
|
||||
"newKnowledge.documentStatus.ready": "Prêt",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Un historique supplémentaire est disponible. Continuez la vérification pour trouver l’état de ce document.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "limite de taille du lot dépassée",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "limite du nombre de fichiers dépassée",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "le fichier est trop volumineux",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "type de fichier non pris en charge ou non valide",
|
||||
"newKnowledge.documentUploadExclusion.more": "et {{count}} autres",
|
||||
"newKnowledge.documentUploadExclusion.processing": "échec du traitement",
|
||||
"newKnowledge.documentUploadExclusion.quota": "quota de l’espace de travail dépassé",
|
||||
"newKnowledge.documentUploadExclusion.target": "la cible du document n’est plus valide",
|
||||
"newKnowledge.documentUploadFailed": "Nous n'avons pas pu télécharger ces documents. Essayer à nouveau.",
|
||||
"newKnowledge.documentUploadPartial": "Le traitement de {{accepted}} documents a démarré ; {{excluded}} n’ont pas pu être ajoutés : {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Aucun document n’a été accepté : {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Le traitement du document a commencé.",
|
||||
"newKnowledge.documents": "Documents",
|
||||
"newKnowledge.documentsDescription": "Actifs de connaissances et leurs révisions — d'où provient chaque document et si la dernière révision est indexée.",
|
||||
"newKnowledge.documentsDropHint": "ou déposez un PDF, DOCX, Markdown, HTML, XLSX ou TXT pour le télécharger",
|
||||
"newKnowledge.documentsEmptyDescription": "Téléchargez des fichiers ici ou connectez une source dans l'onglet Sources : les documents sont analysés, fragmentés et indexés automatiquement.",
|
||||
"newKnowledge.documentsEmptyTitle": "Aucun document pour l'instant",
|
||||
"newKnowledge.documentsErrorDescription": "Nous n'avons pas pu charger les documents. Essayer à nouveau.",
|
||||
"newKnowledge.documentsErrorTitle": "Les documents n'ont pas pu être chargés",
|
||||
"newKnowledge.documentsPermissionDescription": "Vous n'êtes pas autorisé à afficher les documents de cette base de connaissances.",
|
||||
"newKnowledge.documentsPermissionTitle": "Les documents sont restreints",
|
||||
"newKnowledge.documentsReindexFailed": "Nous n'avons pas pu commencer la réindexation. Essayer à nouveau.",
|
||||
"newKnowledge.documentsReindexPartial": "Documents {{queued}} mis en file d'attente ; {{missing}} n'existe plus.",
|
||||
"newKnowledge.documentsReindexStarted": "La réindexation a commencé.",
|
||||
"newKnowledge.documentsSelected": "Documents {{count}} sélectionnés",
|
||||
"newKnowledge.documentsUnavailable": "— documents",
|
||||
"newKnowledge.downloadDocuments": "Télécharger",
|
||||
"newKnowledge.editSource": "Modifier",
|
||||
"newKnowledge.emptyChunk": "Segment vide",
|
||||
"newKnowledge.emptyDescription": "Ajoutez vos documents et les agents pourront les lire directement, sans indexation à configurer.",
|
||||
"newKnowledge.emptyTitle": "Une façon plus intelligente de créer des bases de connaissances",
|
||||
"newKnowledge.errorDescription": "Impossible de charger vos nouvelles bases de connaissances. Réessayez.",
|
||||
"newKnowledge.errorTitle": "Une erreur s’est produite",
|
||||
"newKnowledge.fileSize": "Taille du fichier",
|
||||
"newKnowledge.filtersUnavailable": "Ce filtre sera disponible lorsque KnowledgeFS exposera ses métadonnées.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl n'est pas disponible dans le catalogue des fournisseurs pour cet espace de travail.",
|
||||
"newKnowledge.gotIt": "Compris",
|
||||
"newKnowledge.guideDescription": "Connectez une source et ses documents restent synchronisés, indexés automatiquement et prêts à l’emploi, sans configuration à gérer. Vos anciennes bases de connaissances continuent de fonctionner telles quelles.",
|
||||
"newKnowledge.guideTitle": "Découvrez la nouvelle base de connaissances ✨",
|
||||
"newKnowledge.illustrationHeadline": "Connectez votre contenu une fois : chaque application répond avec les connaissances les plus récentes.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Informations d’indexation",
|
||||
"newKnowledge.interruptTask": "Interrompre",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"newKnowledge.keepEditing": "Continuer la modification",
|
||||
"newKnowledge.lastIndexed": "Dernière indexation",
|
||||
"newKnowledge.lastReadyRevisionHint": "Un traitement échoué n'interrompt jamais la récupération : la dernière révision prête reste disponible dans l'index de contenu.",
|
||||
"newKnowledge.lastSyncColumn": "Dernière synchronisation",
|
||||
"newKnowledge.learnMore": "En savoir plus",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Quitter la configuration",
|
||||
"newKnowledge.leavePartialSetupDescription": "La base de connaissances a été créée, mais son autorisation n’a pas pu être confirmée. Si vous quittez, la base de connaissances sera conservée ; vous pourrez vérifier l’accès plus tard.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Quitter la configuration incomplète ?",
|
||||
"newKnowledge.legacy": "Ancien",
|
||||
"newKnowledge.loadMore": "Charger plus",
|
||||
"newKnowledge.loadMoreRevisions": "Charger les anciennes révisions",
|
||||
"newKnowledge.loadingSyncPolicy": "Chargement de la politique de synchronisation",
|
||||
"newKnowledge.manualUpload": "Téléchargement manuel",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Métadonnées",
|
||||
"newKnowledge.mimeType": "Type de fichier",
|
||||
"newKnowledge.moreProviders": "Plus de fournisseurs",
|
||||
"newKnowledge.name": "Nom de la connaissance",
|
||||
"newKnowledge.namePlaceholder": "par ex. Manuel de support client",
|
||||
"newKnowledge.nameRequired": "Entrez un nom de base de connaissances.",
|
||||
"newKnowledge.new": "Nouveau",
|
||||
"newKnowledge.noBackgroundTasks": "Aucune tâche en arrière-plan pour l'instant.",
|
||||
"newKnowledge.noDescription": "Aucune description",
|
||||
"newKnowledge.noMatchingDocuments": "Aucun document ne correspond à cette recherche.",
|
||||
"newKnowledge.noMatchingSources": "Aucune source ne correspond à cette recherche.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Cette base de connaissances a peut-être été supprimée ou vous n'y avez peut-être pas accès.",
|
||||
"newKnowledge.notFoundTitle": "Base de connaissances introuvable",
|
||||
"newKnowledge.notionNotConnected": "Notion n’est pas connecté",
|
||||
"newKnowledge.notionNotConnectedDescription": "Connectez Notion pour choisir des pages et les maintenir synchronisées.",
|
||||
"newKnowledge.onlineDocuments": "Documents en ligne",
|
||||
"newKnowledge.onlineDrive": "Drive en ligne",
|
||||
"newKnowledge.overview": "Vue d’ensemble",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} sélectionné(s)",
|
||||
"newKnowledge.partialDocumentResults": "D’autres documents peuvent correspondre. Chargez-en davantage pour poursuivre la recherche.",
|
||||
"newKnowledge.permission": "Autorisation",
|
||||
"newKnowledge.permissionAllMembers": "Espace de travail · tous les membres peuvent consulter et modifier",
|
||||
"newKnowledge.permissionLoadFailed": "Impossible de vérifier vos autorisations de modification. Réessayez.",
|
||||
"newKnowledge.permissionOnlyMe": "Seulement moi",
|
||||
"newKnowledge.permissionRestricted": "Les autorisations de votre espace de travail ne permettent pas de modifier ce paramètre.",
|
||||
"newKnowledge.permissionUpdateFailed": "La base de connaissances a été créée, mais son autorisation n’a pas pu être confirmée. Réessayez pour terminer la configuration.",
|
||||
"newKnowledge.preview": "Aperçu",
|
||||
"newKnowledge.previewUnavailable": "L’aperçu sera disponible une fois le traitement de ce fichier terminé par le backend.",
|
||||
"newKnowledge.processDocument": "Processus {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Interrompu",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Préparation",
|
||||
"newKnowledge.processingTaskState.failed": "Échec",
|
||||
"newKnowledge.processingTaskState.queued": "En file d'attente",
|
||||
"newKnowledge.processingTaskState.retry_wait": "En attendant de réessayer",
|
||||
"newKnowledge.processingTaskState.running": "Traitement · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Terminé",
|
||||
"newKnowledge.processingTaskState.superseded": "Remplacé",
|
||||
"newKnowledge.providerConnected": "Firecrawl connecté",
|
||||
"newKnowledge.providerLoadFailed": "Les fournisseurs n'ont pas pu charger",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} n'est pas configuré",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Ajoutez une clé API ou une URL de point de terminaison pour explorer les sites Web avec {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Ce fournisseur n'est pas disponible.",
|
||||
"newKnowledge.quality": "Qualité",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Demandez à un éditeur de l’espace de travail de créer la première base de connaissances.",
|
||||
"newKnowledge.refreshConnectionStatus": "Actualiser l'état",
|
||||
"newKnowledge.refreshingConnection": "Rafraîchissant…",
|
||||
"newKnowledge.reindexDocument": "Réindexer",
|
||||
"newKnowledge.reindexDocuments": "Réindexer",
|
||||
"newKnowledge.removeSource": "Supprimer la source",
|
||||
"newKnowledge.retrievalCount": "Nombre de récupérations",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Réessayer",
|
||||
"newKnowledge.retryReindexDocument": "Réindexer à nouveau",
|
||||
"newKnowledge.retrySyncPolicy": "Réessayer la politique de synchronisation",
|
||||
"newKnowledge.retryTask": "Réessayer",
|
||||
"newKnowledge.revisionState.active": "En service",
|
||||
"newKnowledge.revisionState.candidate": "Traitement en cours",
|
||||
"newKnowledge.revisionState.failed": "Échec",
|
||||
"newKnowledge.revisionState.superseded": "Précédente",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Cette section deviendra disponible lors de la prochaine étape de configuration.",
|
||||
"newKnowledge.searchDocuments": "Rechercher des documents",
|
||||
"newKnowledge.searchSources": "Sources de recherche",
|
||||
"newKnowledge.selectAll": "Tout sélectionner",
|
||||
"newKnowledge.selectAllDocuments": "Sélectionnez tous les documents",
|
||||
"newKnowledge.selectAllSources": "Sélectionner toutes les sources",
|
||||
"newKnowledge.selectChunk": "Sélectionnez un segment pour examiner son contenu.",
|
||||
"newKnowledge.selectFilesAndFolders": "Sélectionnez les fichiers et dossiers à synchroniser",
|
||||
"newKnowledge.skippedFailed": "Ignoré (échec)",
|
||||
"newKnowledge.skippedOffDomain": "Ignoré (hors domaine)",
|
||||
"newKnowledge.sourceActions": "Actions pour {{name}}",
|
||||
"newKnowledge.sourceColumn": "Origine",
|
||||
"newKnowledge.sourceFilterLabel": "Filtrer les sources par statut",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "La configuration de la source est affichée en entier ; la modification et l’aperçu nécessitent le flux backend de source brouillon.",
|
||||
"newKnowledge.sourceStatus.active": "Actif",
|
||||
"newKnowledge.sourceStatus.disabled": "Désactivé",
|
||||
"newKnowledge.sourceStatus.error": "Erreur",
|
||||
"newKnowledge.sourceStatus.syncing": "Synchronisation",
|
||||
"newKnowledge.sourceType.connector": "Application connectée",
|
||||
"newKnowledge.sourceType.object-storage": "Lecteur en ligne",
|
||||
"newKnowledge.sourceType.upload": "Télécharger",
|
||||
"newKnowledge.sourceType.web": "Exploration du site Web",
|
||||
"newKnowledge.sourceTypeLabel": "Type de source",
|
||||
"newKnowledge.sources": "Sources",
|
||||
"newKnowledge.sourcesDescription": "D'où vient la connaissance, à quel point elle est fraîche et si la dernière synchronisation a réussi.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Connectez un site Web : Dify le maintient synchronisé et à jour, afin que la récupération ne soit jamais interrompue.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Aucune source connectée pour le moment",
|
||||
"newKnowledge.sourcesErrorDescription": "Nous n'avons pas pu charger les sources. Essayer à nouveau.",
|
||||
"newKnowledge.sourcesErrorTitle": "Les sources n'ont pas pu être chargées",
|
||||
"newKnowledge.startEmpty": "Commencer à vide",
|
||||
"newKnowledge.startEmptyDescription": "Créez-la maintenant et ajoutez du contenu plus tard.",
|
||||
"newKnowledge.startWith": "Commencez par",
|
||||
"newKnowledge.startWithHelp": "Vous pouvez ajouter plus de sources ou de documents à tout moment.",
|
||||
"newKnowledge.statusColumn": "Statut",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Synchroniser maintenant",
|
||||
"newKnowledge.syncPolicy": "Politique de synchronisation",
|
||||
"newKnowledge.syncPolicyColumn": "Politique de synchronisation",
|
||||
"newKnowledge.syncPolicyCustom": "Intervalle personnalisé",
|
||||
"newKnowledge.syncPolicyDaily": "Toutes les 24 heures",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Impossible de charger la politique de synchronisation.",
|
||||
"newKnowledge.syncPolicyManual": "Manuel uniquement",
|
||||
"newKnowledge.syncPolicyProvider": "Utiliser la planification du fournisseur",
|
||||
"newKnowledge.tags": "Étiquettes",
|
||||
"newKnowledge.taskActionFailed": "Nous n'avons pas pu mettre à jour cette tâche. Actualisez et réessayez.",
|
||||
"newKnowledge.taskAttentionClear": "Aucune tâche en arrière-plan ne nécessite d'attention.",
|
||||
"newKnowledge.taskAttentionCount": "Les tâches en arrière-plan {{count}} nécessitent une attention particulière.",
|
||||
"newKnowledge.taskAttentionErrorCount": "Les tâches en arrière-plan {{count}} nécessitent une attention particulière, y compris les tâches ayant échoué.",
|
||||
"newKnowledge.taskFailedNotification": "Échec du traitement du document — réessayez depuis Tâches",
|
||||
"newKnowledge.taskHistoryIncomplete": "D’autres tâches sont disponibles ; le nombre de tâches nécessitant une attention est incomplet.",
|
||||
"newKnowledge.tasks": "Tâches",
|
||||
"newKnowledge.tasksErrorDescription": "Nous ne pouvions pas charger les tâches en arrière-plan. Essayer à nouveau.",
|
||||
"newKnowledge.tasksWithAttention": "Tâches, {{count}} nécessitent une attention particulière",
|
||||
"newKnowledge.tokenCount": "Jetons",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS n’est pas disponible pour cet espace de travail actuellement.",
|
||||
"newKnowledge.unavailableTitle": "Les nouvelles connaissances sont indisponibles",
|
||||
"newKnowledge.updated": "Mis à jour le {{date}}",
|
||||
"newKnowledge.updatedAt": "Dernière mise à jour",
|
||||
"newKnowledge.updatedColumn": "Mis à jour",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Caractères : —",
|
||||
"newKnowledge.uploadDocuments": "Télécharger des documents",
|
||||
"newKnowledge.uploadFiles": "Importer des fichiers",
|
||||
"newKnowledge.uploadFilesDescription": "Ajoutez des fichiers depuis votre ordinateur : ils sont indexés dès leur création.",
|
||||
"newKnowledge.uploadingFiles": "Téléversement…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Vue des connaissances",
|
||||
"newKnowledge.websiteCrawl": "Exploration du site Web",
|
||||
"noExternalKnowledge": "Il n’y a pas encore d’API de connaissances externes, cliquez ici pour créer",
|
||||
"parentMode.fullDoc": "Doc complet",
|
||||
"parentMode.paragraph": "Paragraphe",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "उच्च गुणवत्ता और किफायती ज्ञान आधारों के मिश्रण के लिए पुनः रैंकिंग मॉडल आवश्यक है।",
|
||||
"mixtureInternalAndExternalTip": "आंतरिक और बाहरी ज्ञान के मिश्रण के लिए रीरैंक मॉडल की आवश्यकता होती है।",
|
||||
"multimodal": "बहु-मोडल",
|
||||
"newKnowledge.actionsColumn": "क्रियाएँ",
|
||||
"newKnowledge.addDocument": "दस्तावेज़ जोड़ें",
|
||||
"newKnowledge.addSource": "स्रोत जोड़ें",
|
||||
"newKnowledge.addSourceDescription": "इस ज्ञानकोष से एक स्रोत कनेक्ट करें - यह स्वचालित रूप से समन्वयित होता है और ताज़ा रहता है।",
|
||||
"newKnowledge.addSourceFailed": "यह स्रोत जोड़ा नहीं जा सका। सेटिंग की समीक्षा करें और फिर से कोशिश करें।",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "सभी स्थिति",
|
||||
"newKnowledge.allSources": "सभी स्रोत",
|
||||
"newKnowledge.apiAgentAccess": "API और एजेंट एक्सेस",
|
||||
"newKnowledge.appsUnavailable": "— ऐप",
|
||||
"newKnowledge.authKind.api-key": "एपीआई कुंजी",
|
||||
"newKnowledge.authKind.endpoint": "समापन बिंदु",
|
||||
"newKnowledge.authenticationMethod": "प्रमाणीकरण विधि",
|
||||
"newKnowledge.averageChunkLength": "औसत खंड लंबाई",
|
||||
"newKnowledge.backToList": "ज्ञानकोष पर वापस जाएँ",
|
||||
"newKnowledge.backgroundTasks": "पृष्ठभूमि कार्य",
|
||||
"newKnowledge.backgroundTasksDescription": "दस्तावेज़ जोड़ता है, स्रोत सिंक करता है, और नौकरियों को पुन: अनुक्रमित करता है।",
|
||||
"newKnowledge.booleanFalse": "नहीं",
|
||||
"newKnowledge.booleanTrue": "हाँ",
|
||||
"newKnowledge.bulkDocumentActions": "थोक दस्तावेज़ क्रियाएँ",
|
||||
"newKnowledge.cancelAddSource": "रद्द करें",
|
||||
"newKnowledge.cardType": "नॉलेज स्पेस",
|
||||
"newKnowledge.characterCount": "अक्षर",
|
||||
"newKnowledge.checkReindexStatus": "पुनः इंडेक्स स्थिति जाँचें",
|
||||
"newKnowledge.chunkCount": "खंड",
|
||||
"newKnowledge.chunkHeading": "खंड {{position}}",
|
||||
"newKnowledge.chunkLocation": "इस संशोधन में स्थान {{position}}",
|
||||
"newKnowledge.clearDocumentSelection": "दस्तावेज़ चयन साफ़ करें",
|
||||
"newKnowledge.configureProvider": "फायरक्रॉल कॉन्फ़िगर करें",
|
||||
"newKnowledge.connectNotion": "Notion कनेक्ट करें",
|
||||
"newKnowledge.connectProvider": "फायरक्रॉल कनेक्ट करें",
|
||||
"newKnowledge.connectProviderGeneric": "प्रदाता कनेक्ट करें",
|
||||
"newKnowledge.connectSource": "स्रोत कनेक्ट करें",
|
||||
"newKnowledge.connectSourceDescription": "कंटेंट Notion, वेबसाइटों और अन्य सेवाओं के साथ सिंक रहता है।",
|
||||
"newKnowledge.connectingProvider": "कनेक्ट हो रहा है...",
|
||||
"newKnowledge.connectionFailed": "हम फायरक्रॉल को कनेक्ट नहीं कर सके। क्रेडेंशियल पुनः दर्ज करें और पुनः प्रयास करें।",
|
||||
"newKnowledge.connectionNeedsAttention": "फायरक्रॉल कनेक्शन पर ध्यान देने की जरूरत है",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "वेबसाइट स्रोत जोड़ने से पहले कनेक्शन को ताज़ा करें।",
|
||||
"newKnowledge.connectionProvisioning": "फायरक्रॉल कनेक्ट हो रहा है",
|
||||
"newKnowledge.connectionRefreshFailed": "हम इस कनेक्शन को ताज़ा नहीं कर सके। पुनः प्रयास करें।",
|
||||
"newKnowledge.continueCheckingTaskStatus": "कार्य स्थिति जाँचना जारी रखें",
|
||||
"newKnowledge.crawlAndPreview": "क्रॉल और पूर्वावलोकन",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "शुरू नहीं हुआ",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "क्रॉल पूर्वावलोकन",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "क्रॉल वर्कफ़्लो उपलब्ध होने तक वेबसाइट क्रॉल कॉन्फ़िगरेशन अक्षम है।",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "फायरक्रॉल तैयार है",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "हम यह ज्ञानकोष नहीं बना सके। अपनी पहुंच जांचें और पुनः प्रयास करें।",
|
||||
"newKnowledge.createIllustrationTitle": "अपनी सामग्री को एक बार जोड़ें — हर ऐप नवीनतम ज्ञान के साथ उत्तर देगा।",
|
||||
"newKnowledge.createTitle": "ज्ञान पैदा करें",
|
||||
"newKnowledge.createdAt": "बनाया गया",
|
||||
"newKnowledge.creators": "निर्माता",
|
||||
"newKnowledge.customIntervalHours": "कस्टम अंतराल (घंटे)",
|
||||
"newKnowledge.customIntervalInvalid": "1 से 720 घंटे तक की पूर्ण संख्या दर्ज करें।",
|
||||
"newKnowledge.deleteDocuments": "हटाएं...",
|
||||
"newKnowledge.description": "विवरण",
|
||||
"newKnowledge.descriptionHelp": "एक स्पष्ट विवरण ऐप्स को यह निर्णय लेने में मदद करता है कि इस ज्ञान को कब खोजना है।",
|
||||
"newKnowledge.descriptionPlaceholder": "जैसे रिफंड नीति, शिपिंग एफएक्यू और एस्केलेशन चरण - ग्राहक सहायता प्रश्नों के लिए उपयोग करें",
|
||||
"newKnowledge.detailErrorDescription": "हम इस ज्ञानकोष को लोड नहीं कर सके. पुनः प्रयास करें।",
|
||||
"newKnowledge.detailErrorTitle": "कुछ ग़लत हो गया",
|
||||
"newKnowledge.disableSource": "अक्षम करें",
|
||||
"newKnowledge.discardDraftConfirm": "ड्राफ़्ट हटाएँ",
|
||||
"newKnowledge.discardDraftDescription": "इस नॉलेज बेस ड्राफ़्ट में किए गए आपके बदलाव खो जाएँगे।",
|
||||
"newKnowledge.discardDraftTitle": "क्या यह नॉलेज बेस ड्राफ़्ट हटाना है?",
|
||||
"newKnowledge.discardSourceChanges": "स्रोत में किए बदलाव छोड़ें?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "बदलाव छोड़ें",
|
||||
"newKnowledge.discardSourceChangesDescription": "क्रॉल पूर्वावलोकन, पेज चयन और सिंक सेटिंग खो जाएँगी।",
|
||||
"newKnowledge.documentActions": "{{name}} के लिए क्रियाएँ",
|
||||
"newKnowledge.documentActionsUnavailable": "इस ज्ञानकोष के लिए दस्तावेज़ क्रियाएँ अभी तक उपलब्ध नहीं हैं।",
|
||||
"newKnowledge.documentChunksEmpty": "इस संशोधन के लिए अभी कोई खंड उपलब्ध नहीं है।",
|
||||
"newKnowledge.documentChunksLoadError": "इस संशोधन के खंड लोड नहीं किए जा सके।",
|
||||
"newKnowledge.documentChunksLoadMoreError": "और खंड लोड नहीं किए जा सके। फिर से कोशिश करें।",
|
||||
"newKnowledge.documentColumn": "दस्तावेज़",
|
||||
"newKnowledge.documentContentIncomplete": "दस्तावेज़ की शेष सामग्री लोड हो रही है…",
|
||||
"newKnowledge.documentContents": "विषय-सूची",
|
||||
"newKnowledge.documentFacts": "दस्तावेज़ विवरण",
|
||||
"newKnowledge.documentFilterLabel": "स्थिति के अनुसार दस्तावेज़ फ़िल्टर करें",
|
||||
"newKnowledge.documentLoadErrorDescription": "यह दस्तावेज़ लोड नहीं किया जा सका। फिर से कोशिश करें।",
|
||||
"newKnowledge.documentLoadErrorTitle": "दस्तावेज़ लोड नहीं हुआ",
|
||||
"newKnowledge.documentNotFoundDescription": "यह दस्तावेज़ हटाया गया हो सकता है या आपके पास पहुँच नहीं है।",
|
||||
"newKnowledge.documentNotFoundTitle": "दस्तावेज़ नहीं मिला",
|
||||
"newKnowledge.documentOverviewDescription": "पूरी अनुक्रमित समीक्षा पढ़ें और उसके स्रोत व अनुक्रमणिका का विवरण देखें।",
|
||||
"newKnowledge.documentPermissionRestricted": "आपके कार्यस्थान की अनुमतियाँ दस्तावेज़ या कार्य संबंधी कार्रवाइयों की अनुमति नहीं देती हैं।",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "पुनः इंडेक्स अनुरोध स्वीकार हुआ, लेकिन कार्य स्थिति में देरी है। दोबारा भेजने से पहले जाँचें।",
|
||||
"newKnowledge.documentReindexFailed": "पुनः इंडेक्सिंग विफल हुई। अंतिम तैयार संशोधन उपलब्ध रहेगा।",
|
||||
"newKnowledge.documentReindexProgress": "पुनः इंडेक्स हो रहा है · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "संशोधन",
|
||||
"newKnowledge.documentRevisionMissingDescription": "इस दस्तावेज़ का कोई तैयार संशोधन अभी नहीं है। प्रोसेसिंग पूरी होने के बाद फिर देखें।",
|
||||
"newKnowledge.documentRevisionMissingTitle": "कोई संशोधन उपलब्ध नहीं",
|
||||
"newKnowledge.documentRevisionsLoadError": "संशोधन इतिहास लोड नहीं हुआ। वर्तमान संशोधन उपलब्ध रहेगा।",
|
||||
"newKnowledge.documentStatus.disabled": "निष्क्रिय",
|
||||
"newKnowledge.documentStatus.failed": "असफल",
|
||||
"newKnowledge.documentStatus.processing": "प्रोसेस हो रहा है",
|
||||
"newKnowledge.documentStatus.queued": "कतारबद्ध",
|
||||
"newKnowledge.documentStatus.ready": "तैयार",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "अधिक कार्य इतिहास उपलब्ध है। इस दस्तावेज़ की स्थिति खोजने के लिए जाँच जारी रखें।",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "बैच आकार सीमा पार हो गई",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "फ़ाइल संख्या सीमा पार हो गई",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "फ़ाइल बहुत बड़ी है",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "फ़ाइल प्रकार असमर्थित या अमान्य है",
|
||||
"newKnowledge.documentUploadExclusion.more": "और {{count}} अन्य",
|
||||
"newKnowledge.documentUploadExclusion.processing": "प्रोसेसिंग विफल रही",
|
||||
"newKnowledge.documentUploadExclusion.quota": "कार्यस्थान कोटा पार हो गया",
|
||||
"newKnowledge.documentUploadExclusion.target": "दस्तावेज़ लक्ष्य अब मान्य नहीं है",
|
||||
"newKnowledge.documentUploadFailed": "हम ये दस्तावेज़ अपलोड नहीं कर सके. पुनः प्रयास करें।",
|
||||
"newKnowledge.documentUploadPartial": "{{accepted}} दस्तावेज़ों की प्रोसेसिंग शुरू हुई; {{excluded}} जोड़े नहीं जा सके: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "कोई दस्तावेज़ स्वीकार नहीं किया गया: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "दस्तावेज़ प्रसंस्करण शुरू हुआ.",
|
||||
"newKnowledge.documents": "दस्तावेज़",
|
||||
"newKnowledge.documentsDescription": "ज्ञान परिसंपत्तियाँ और उनके संशोधन - प्रत्येक दस्तावेज़ कहाँ से आया और क्या नवीनतम संशोधन अनुक्रमित है।",
|
||||
"newKnowledge.documentsDropHint": "या अपलोड करने के लिए PDF, DOCX, Markdown, HTML, XLSX, या TXT छोड़ें",
|
||||
"newKnowledge.documentsEmptyDescription": "यहां फ़ाइलें अपलोड करें, या स्रोत टैब पर एक स्रोत कनेक्ट करें - दस्तावेज़ों को स्वचालित रूप से पार्स, खंडित और अनुक्रमित किया जाता है।",
|
||||
"newKnowledge.documentsEmptyTitle": "अभी तक कोई दस्तावेज़ नहीं",
|
||||
"newKnowledge.documentsErrorDescription": "हम दस्तावेज़ लोड नहीं कर सके. पुनः प्रयास करें।",
|
||||
"newKnowledge.documentsErrorTitle": "दस्तावेज़ लोड नहीं हो सके",
|
||||
"newKnowledge.documentsPermissionDescription": "आपको इस ज्ञानकोष में दस्तावेज़ देखने की अनुमति नहीं है.",
|
||||
"newKnowledge.documentsPermissionTitle": "दस्तावेज़ प्रतिबंधित हैं",
|
||||
"newKnowledge.documentsReindexFailed": "हम पुनः अनुक्रमणिका प्रारंभ नहीं कर सके. पुनः प्रयास करें।",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} दस्तावेज़ कतारबद्ध; {{missing}} अब मौजूद नहीं है।",
|
||||
"newKnowledge.documentsReindexStarted": "पुन: अनुक्रमणिका प्रारंभ हुई.",
|
||||
"newKnowledge.documentsSelected": "{{count}} दस्तावेज़ चयनित",
|
||||
"newKnowledge.documentsUnavailable": "— दस्तावेज़",
|
||||
"newKnowledge.downloadDocuments": "डाउनलोड करें",
|
||||
"newKnowledge.editSource": "संपादित करें",
|
||||
"newKnowledge.emptyChunk": "खाली खंड",
|
||||
"newKnowledge.emptyDescription": "अपने दस्तावेज़ जोड़ें ताकि एजेंट उन्हें सीधे पढ़ सकें — इंडेक्सिंग कॉन्फ़िगर करने की ज़रूरत नहीं।",
|
||||
"newKnowledge.emptyTitle": "नॉलेज बेस बनाने का अधिक स्मार्ट तरीका",
|
||||
"newKnowledge.errorDescription": "नए नॉलेज बेस लोड नहीं हो सके। फिर से कोशिश करें।",
|
||||
"newKnowledge.errorTitle": "कुछ गलत हुआ",
|
||||
"newKnowledge.fileSize": "फ़ाइल का आकार",
|
||||
"newKnowledge.filtersUnavailable": "KnowledgeFS के मेटाडेटा उपलब्ध कराने पर यह फ़िल्टर उपलब्ध होगा।",
|
||||
"newKnowledge.firecrawlUnavailable": "इस कार्यक्षेत्र के लिए प्रदाता कैटलॉग में फायरक्रॉल उपलब्ध नहीं है।",
|
||||
"newKnowledge.gotIt": "समझ गया",
|
||||
"newKnowledge.guideDescription": "किसी स्रोत को कनेक्ट करें और उसके दस्तावेज़ सिंक, अपने-आप इंडेक्स और उपयोग के लिए तैयार रहेंगे—किसी सेटअप को संभालने की ज़रूरत नहीं। आपके पुराने नॉलेज बेस पहले की तरह काम करते रहेंगे।",
|
||||
"newKnowledge.guideTitle": "नए नॉलेज बेस से मिलें ✨",
|
||||
"newKnowledge.illustrationHeadline": "अपनी सामग्री एक बार कनेक्ट करें — हर ऐप नवीनतम ज्ञान के साथ जवाब देगा।",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "अनुक्रमणिका जानकारी",
|
||||
"newKnowledge.interruptTask": "रोकें",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid 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": "पुराने संशोधन लोड करें",
|
||||
"newKnowledge.loadingSyncPolicy": "सिंक नीति लोड हो रही है",
|
||||
"newKnowledge.manualUpload": "मैन्युअल अपलोड",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "मेटा डेटा",
|
||||
"newKnowledge.mimeType": "फ़ाइल प्रकार",
|
||||
"newKnowledge.moreProviders": "और प्रदाता",
|
||||
"newKnowledge.name": "ज्ञान नाम",
|
||||
"newKnowledge.namePlaceholder": "जैसे ग्राहक सहायता पुस्तिका",
|
||||
"newKnowledge.nameRequired": "नॉलेज बेस नाम दर्ज करें.",
|
||||
"newKnowledge.new": "नया",
|
||||
"newKnowledge.noBackgroundTasks": "अभी तक कोई पृष्ठभूमि कार्य नहीं.",
|
||||
"newKnowledge.noDescription": "कोई विवरण नहीं",
|
||||
"newKnowledge.noMatchingDocuments": "इस खोज से कोई दस्तावेज़ मेल नहीं खाता.",
|
||||
"newKnowledge.noMatchingSources": "इस खोज से कोई स्रोत मेल नहीं खाता।",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "हो सकता है कि यह ज्ञानकोष हटा दिया गया हो या आपके पास पहुंच न हो।",
|
||||
"newKnowledge.notFoundTitle": "ज्ञान का आधार नहीं मिला",
|
||||
"newKnowledge.notionNotConnected": "Notion कनेक्ट नहीं है",
|
||||
"newKnowledge.notionNotConnectedDescription": "पृष्ठ चुनने और उन्हें सिंक रखने के लिए Notion कनेक्ट करें।",
|
||||
"newKnowledge.onlineDocuments": "ऑनलाइन दस्तावेज़",
|
||||
"newKnowledge.onlineDrive": "ऑनलाइन ड्राइव",
|
||||
"newKnowledge.overview": "अवलोकन",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} चयनित",
|
||||
"newKnowledge.partialDocumentResults": "और दस्तावेज़ मेल खा सकते हैं। खोज जारी रखने के लिए और लोड करें।",
|
||||
"newKnowledge.permission": "अनुमति",
|
||||
"newKnowledge.permissionAllMembers": "कार्यक्षेत्र · सभी सदस्य देख और संपादित कर सकते हैं",
|
||||
"newKnowledge.permissionLoadFailed": "आपकी संपादन अनुमतियाँ सत्यापित नहीं की जा सकीं। फिर से कोशिश करें।",
|
||||
"newKnowledge.permissionOnlyMe": "केवल मैं",
|
||||
"newKnowledge.permissionRestricted": "आपकी कार्यस्थान अनुमतियाँ इस सेटिंग को बदलने की अनुमति नहीं देतीं।",
|
||||
"newKnowledge.permissionUpdateFailed": "नॉलेज बेस बन गया है, लेकिन उसकी अनुमति की पुष्टि नहीं हो सकी। सेटअप पूरा करने के लिए फिर से कोशिश करें।",
|
||||
"newKnowledge.preview": "पूर्वावलोकन",
|
||||
"newKnowledge.previewUnavailable": "बैकएंड द्वारा इस फ़ाइल की प्रोसेसिंग पूरी होने के बाद पूर्वावलोकन उपलब्ध होगा।",
|
||||
"newKnowledge.processDocument": "प्रक्रिया {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "बाधित",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "तैयारी",
|
||||
"newKnowledge.processingTaskState.failed": "असफल",
|
||||
"newKnowledge.processingTaskState.queued": "कतारबद्ध",
|
||||
"newKnowledge.processingTaskState.retry_wait": "पुनः प्रयास करने की प्रतीक्षा की जा रही है",
|
||||
"newKnowledge.processingTaskState.running": "प्रसंस्करण · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "पूरा हुआ",
|
||||
"newKnowledge.processingTaskState.superseded": "अधिक्रमण किया हुआ",
|
||||
"newKnowledge.providerConnected": "फायरक्रॉल जुड़ा",
|
||||
"newKnowledge.providerLoadFailed": "प्रदाता लोड नहीं कर सके",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} कॉन्फ़िगर नहीं है",
|
||||
"newKnowledge.providerNotConfiguredDescription": "{{provider}} के साथ वेबसाइटों को क्रॉल करने के लिए एक एपीआई कुंजी या एंडपॉइंट यूआरएल जोड़ें।",
|
||||
"newKnowledge.providerUnavailable": "यह प्रदाता अनुपलब्ध है।",
|
||||
"newKnowledge.quality": "गुणवत्ता",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "पहला नॉलेज बेस बनाने के लिए कार्यस्थान संपादक से कहें।",
|
||||
"newKnowledge.refreshConnectionStatus": "स्थिति ताज़ा करें",
|
||||
"newKnowledge.refreshingConnection": "ताज़ा…",
|
||||
"newKnowledge.reindexDocument": "पुनः इंडेक्स करें",
|
||||
"newKnowledge.reindexDocuments": "पुन: अनुक्रमणिका",
|
||||
"newKnowledge.removeSource": "स्रोत हटाएं",
|
||||
"newKnowledge.retrievalCount": "पुनर्प्राप्ति संख्या",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "पुनः प्रयास करें",
|
||||
"newKnowledge.retryReindexDocument": "फिर से पुनः इंडेक्स करें",
|
||||
"newKnowledge.retrySyncPolicy": "सिंक नीति फिर से लोड करें",
|
||||
"newKnowledge.retryTask": "पुनः प्रयास करें",
|
||||
"newKnowledge.revisionState.active": "सक्रिय",
|
||||
"newKnowledge.revisionState.candidate": "प्रोसेस हो रहा है",
|
||||
"newKnowledge.revisionState.failed": "विफल",
|
||||
"newKnowledge.revisionState.superseded": "पिछला",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "यह अनुभाग अगले सेटअप चरण में उपलब्ध हो जाएगा.",
|
||||
"newKnowledge.searchDocuments": "दस्तावेज़ खोजें",
|
||||
"newKnowledge.searchSources": "स्रोत खोजें",
|
||||
"newKnowledge.selectAll": "सभी चुनें",
|
||||
"newKnowledge.selectAllDocuments": "सभी दस्तावेज़ चुनें",
|
||||
"newKnowledge.selectAllSources": "सभी स्रोत चुनें",
|
||||
"newKnowledge.selectChunk": "सामग्री देखने के लिए कोई खंड चुनें।",
|
||||
"newKnowledge.selectFilesAndFolders": "सिंक करने के लिए फ़ाइलें और फ़ोल्डर चुनें",
|
||||
"newKnowledge.skippedFailed": "छोड़ा गया (विफल)",
|
||||
"newKnowledge.skippedOffDomain": "छोड़ा गया (डोमेन से बाहर)",
|
||||
"newKnowledge.sourceActions": "{{name}} के लिए कार्रवाइयां",
|
||||
"newKnowledge.sourceColumn": "स्रोत",
|
||||
"newKnowledge.sourceFilterLabel": "स्थिति के अनुसार स्रोतों को फ़िल्टर करें",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "स्रोत सेटअप पूरा दिखाया गया है; संपादन और पूर्वावलोकन के लिए ड्राफ़्ट स्रोत बैकएंड कार्यप्रवाह आवश्यक है।",
|
||||
"newKnowledge.sourceStatus.active": "सक्रिय",
|
||||
"newKnowledge.sourceStatus.disabled": "विकलांग",
|
||||
"newKnowledge.sourceStatus.error": "त्रुटि",
|
||||
"newKnowledge.sourceStatus.syncing": "सिंक हो रहा है",
|
||||
"newKnowledge.sourceType.connector": "कनेक्टेड ऐप",
|
||||
"newKnowledge.sourceType.object-storage": "ऑनलाइन ड्राइव",
|
||||
"newKnowledge.sourceType.upload": "अपलोड करें",
|
||||
"newKnowledge.sourceType.web": "वेबसाइट क्रॉल",
|
||||
"newKnowledge.sourceTypeLabel": "स्रोत प्रकार",
|
||||
"newKnowledge.sources": "सूत्र",
|
||||
"newKnowledge.sourcesDescription": "ज्ञान कहां से आता है, यह कितना ताज़ा है, और क्या अंतिम सिंक सफल हुआ।",
|
||||
"newKnowledge.sourcesEmptyDescription": "एक वेबसाइट कनेक्ट करें - Dify इसे सिंक और ताज़ा रखता है, इसलिए पुनर्प्राप्ति कभी नहीं टूटती।",
|
||||
"newKnowledge.sourcesEmptyTitle": "अभी तक कोई स्रोत नहीं जुड़ा है",
|
||||
"newKnowledge.sourcesErrorDescription": "हम स्रोत लोड नहीं कर सके। पुनः प्रयास करें।",
|
||||
"newKnowledge.sourcesErrorTitle": "स्रोत लोड नहीं हो सके",
|
||||
"newKnowledge.startEmpty": "खाली शुरू करें",
|
||||
"newKnowledge.startEmptyDescription": "अभी बनाएँ, कंटेंट बाद में जोड़ें।",
|
||||
"newKnowledge.startWith": "से शुरू करें",
|
||||
"newKnowledge.startWithHelp": "आप किसी भी समय अधिक स्रोत या दस्तावेज़ जोड़ सकते हैं।",
|
||||
"newKnowledge.statusColumn": "स्थिति",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "अभी सिंक करें",
|
||||
"newKnowledge.syncPolicy": "सिंक नीति",
|
||||
"newKnowledge.syncPolicyColumn": "सिंक नीति",
|
||||
"newKnowledge.syncPolicyCustom": "कस्टम अंतराल",
|
||||
"newKnowledge.syncPolicyDaily": "हर 24 घंटे",
|
||||
"newKnowledge.syncPolicyLoadFailed": "सिंक नीति लोड नहीं की जा सकी।",
|
||||
"newKnowledge.syncPolicyManual": "केवल मैन्युअल",
|
||||
"newKnowledge.syncPolicyProvider": "प्रदाता का शेड्यूल उपयोग करें",
|
||||
"newKnowledge.tags": "टैग",
|
||||
"newKnowledge.taskActionFailed": "हम इस कार्य को अद्यतन नहीं कर सके. ताज़ा करें और पुनः प्रयास करें.",
|
||||
"newKnowledge.taskAttentionClear": "किसी भी पृष्ठभूमि कार्य पर ध्यान देने की आवश्यकता नहीं है।",
|
||||
"newKnowledge.taskAttentionCount": "{{count}} पृष्ठभूमि कार्यों पर ध्यान देने की आवश्यकता है।",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}} पृष्ठभूमि कार्यों पर ध्यान देने की आवश्यकता है, जिसमें विफल कार्य भी शामिल हैं।",
|
||||
"newKnowledge.taskFailedNotification": "दस्तावेज़ प्रोसेसिंग विफल — कार्य से दोबारा कोशिश करें",
|
||||
"newKnowledge.taskHistoryIncomplete": "और कार्य इतिहास उपलब्ध है; ध्यान देने वाले कार्यों की संख्या अधूरी है।",
|
||||
"newKnowledge.tasks": "कार्य",
|
||||
"newKnowledge.tasksErrorDescription": "हम पृष्ठभूमि कार्य लोड नहीं कर सके. पुनः प्रयास करें।",
|
||||
"newKnowledge.tasksWithAttention": "कार्य, {{count}} पर ध्यान देने की आवश्यकता है",
|
||||
"newKnowledge.tokenCount": "टोकन",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS अभी इस कार्यस्थान के लिए उपलब्ध नहीं है।",
|
||||
"newKnowledge.unavailableTitle": "नया नॉलेज उपलब्ध नहीं है",
|
||||
"newKnowledge.updated": "{{date}} को अपडेट किया गया",
|
||||
"newKnowledge.updatedAt": "अंतिम अपडेट",
|
||||
"newKnowledge.updatedColumn": "अद्यतन किया गया",
|
||||
"newKnowledge.uploadCharactersUnavailable": "वर्ण: —",
|
||||
"newKnowledge.uploadDocuments": "दस्तावेज़ अपलोड करें",
|
||||
"newKnowledge.uploadFiles": "फ़ाइलें अपलोड करें",
|
||||
"newKnowledge.uploadFilesDescription": "अपने कंप्यूटर से फ़ाइलें जोड़ें — बनाते ही वे इंडेक्स हो जाएँगी।",
|
||||
"newKnowledge.uploadingFiles": "अपलोड हो रहा है…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "नॉलेज दृश्य",
|
||||
"newKnowledge.websiteCrawl": "वेबसाइट क्रॉल",
|
||||
"noExternalKnowledge": "अभी तक कोई बाहरी ज्ञान एपीआई नहीं है, बनाने के लिए यहां क्लिक करें",
|
||||
"parentMode.fullDoc": "पूर्ण-दस्तावेज़",
|
||||
"parentMode.paragraph": "अनुच्छेद",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Model Rerank diperlukan untuk campuran basis pengetahuan berkualitas tinggi dan ekonomis.",
|
||||
"mixtureInternalAndExternalTip": "Model Rerank diperlukan untuk campuran pengetahuan internal dan eksternal.",
|
||||
"multimodal": "Multimodal",
|
||||
"newKnowledge.actionsColumn": "Tindakan",
|
||||
"newKnowledge.addDocument": "Tambahkan dokumen",
|
||||
"newKnowledge.addSource": "Tambahkan sumber",
|
||||
"newKnowledge.addSourceDescription": "Hubungkan sumber ke basis pengetahuan ini — sumber tersebut akan disinkronkan secara otomatis dan tetap segar.",
|
||||
"newKnowledge.addSourceFailed": "Sumber ini tidak dapat ditambahkan. Tinjau pengaturan lalu coba lagi.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Semua status",
|
||||
"newKnowledge.allSources": "Semua sumber",
|
||||
"newKnowledge.apiAgentAccess": "Akses API & Agen",
|
||||
"newKnowledge.appsUnavailable": "— aplikasi",
|
||||
"newKnowledge.authKind.api-key": "kunci API",
|
||||
"newKnowledge.authKind.endpoint": "Titik akhir",
|
||||
"newKnowledge.authenticationMethod": "Metode otentikasi",
|
||||
"newKnowledge.averageChunkLength": "Panjang potongan rata-rata",
|
||||
"newKnowledge.backToList": "Kembali ke basis pengetahuan",
|
||||
"newKnowledge.backgroundTasks": "Tugas latar belakang",
|
||||
"newKnowledge.backgroundTasksDescription": "Penambahan dokumen, sinkronisasi sumber, dan mengindeks ulang pekerjaan.",
|
||||
"newKnowledge.booleanFalse": "Tidak",
|
||||
"newKnowledge.booleanTrue": "Ya",
|
||||
"newKnowledge.bulkDocumentActions": "Tindakan dokumen massal",
|
||||
"newKnowledge.cancelAddSource": "Batal",
|
||||
"newKnowledge.cardType": "Ruang pengetahuan",
|
||||
"newKnowledge.characterCount": "Karakter",
|
||||
"newKnowledge.checkReindexStatus": "Periksa status pengindeksan ulang",
|
||||
"newKnowledge.chunkCount": "Potongan",
|
||||
"newKnowledge.chunkHeading": "Potongan {{position}}",
|
||||
"newKnowledge.chunkLocation": "Posisi {{position}} dalam revisi ini",
|
||||
"newKnowledge.clearDocumentSelection": "Hapus pilihan dokumen",
|
||||
"newKnowledge.configureProvider": "Konfigurasikan Firecrawl",
|
||||
"newKnowledge.connectNotion": "Hubungkan Notion",
|
||||
"newKnowledge.connectProvider": "Hubungkan Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Hubungkan penyedia",
|
||||
"newKnowledge.connectSource": "Hubungkan sumber",
|
||||
"newKnowledge.connectSourceDescription": "Konten tetap tersinkronisasi dengan Notion, situs web, dan layanan lainnya.",
|
||||
"newKnowledge.connectingProvider": "Menghubungkan…",
|
||||
"newKnowledge.connectionFailed": "Kami tidak dapat menghubungkan Firecrawl. Masukkan kembali kredensial dan coba lagi.",
|
||||
"newKnowledge.connectionNeedsAttention": "Koneksi Firecrawl perlu diperhatikan",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Segarkan koneksi sebelum menambahkan sumber situs web.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl terhubung",
|
||||
"newKnowledge.connectionRefreshFailed": "Kami tidak dapat menyegarkan koneksi ini. Coba lagi.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Lanjutkan pemeriksaan status tugas",
|
||||
"newKnowledge.crawlAndPreview": "Perayapan & pratinjau",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Belum dimulai",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Pratinjau perayapan",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Konfigurasi perayapan situs web dinonaktifkan hingga alur kerja perayapan tersedia.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl sudah siap",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Kami tidak dapat membuat basis pengetahuan ini. Periksa akses Anda dan coba lagi.",
|
||||
"newKnowledge.createIllustrationTitle": "Hubungkan konten sekali — setiap aplikasi menjawab dengan pengetahuan terbaru.",
|
||||
"newKnowledge.createTitle": "Ciptakan Pengetahuan",
|
||||
"newKnowledge.createdAt": "Dibuat",
|
||||
"newKnowledge.creators": "Pembuat",
|
||||
"newKnowledge.customIntervalHours": "Interval khusus (jam)",
|
||||
"newKnowledge.customIntervalInvalid": "Masukkan bilangan bulat antara 1 hingga 720 jam.",
|
||||
"newKnowledge.deleteDocuments": "Hapus…",
|
||||
"newKnowledge.description": "Deskripsi",
|
||||
"newKnowledge.descriptionHelp": "Deskripsi yang jelas membantu aplikasi memutuskan kapan harus mencari Pengetahuan ini.",
|
||||
"newKnowledge.descriptionPlaceholder": "misalnya Kebijakan pengembalian dana, FAQ pengiriman, dan langkah eskalasi — digunakan untuk pertanyaan dukungan pelanggan",
|
||||
"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.documentActions": "Tindakan untuk {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Tindakan dokumen belum tersedia untuk basis pengetahuan ini.",
|
||||
"newKnowledge.documentChunksEmpty": "Belum ada potongan yang tersedia untuk revisi ini.",
|
||||
"newKnowledge.documentChunksLoadError": "Potongan untuk revisi ini tidak dapat dimuat.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Potongan lainnya tidak dapat dimuat. Coba lagi.",
|
||||
"newKnowledge.documentColumn": "Dokumen",
|
||||
"newKnowledge.documentContentIncomplete": "Memuat sisa konten dokumen…",
|
||||
"newKnowledge.documentContents": "Isi",
|
||||
"newKnowledge.documentFacts": "Detail dokumen",
|
||||
"newKnowledge.documentFilterLabel": "Filter dokumen berdasarkan status",
|
||||
"newKnowledge.documentLoadErrorDescription": "Dokumen ini tidak dapat dimuat. Coba lagi.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Dokumen tidak dapat dimuat",
|
||||
"newKnowledge.documentNotFoundDescription": "Dokumen ini mungkin telah dihapus atau Anda tidak memiliki akses.",
|
||||
"newKnowledge.documentNotFoundTitle": "Dokumen tidak ditemukan",
|
||||
"newKnowledge.documentOverviewDescription": "Baca revisi terindeks secara lengkap dan periksa detail sumber serta indeksnya.",
|
||||
"newKnowledge.documentPermissionRestricted": "Izin ruang kerja Anda tidak mengizinkan tindakan pada dokumen atau tugas.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "Pengindeksan ulang diterima, tetapi status tugas tertunda. Periksa sebelum mengirim ulang.",
|
||||
"newKnowledge.documentReindexFailed": "Pengindeksan ulang gagal. Revisi siap terakhir tetap tersedia.",
|
||||
"newKnowledge.documentReindexProgress": "Mengindeks ulang · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Revisi",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Dokumen ini belum memiliki revisi siap. Periksa lagi setelah pemrosesan selesai.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Tidak ada revisi yang tersedia",
|
||||
"newKnowledge.documentRevisionsLoadError": "Riwayat revisi tidak dapat dimuat. Revisi saat ini tetap tersedia.",
|
||||
"newKnowledge.documentStatus.disabled": "Dinonaktifkan",
|
||||
"newKnowledge.documentStatus.failed": "Gagal",
|
||||
"newKnowledge.documentStatus.processing": "Pemrosesan",
|
||||
"newKnowledge.documentStatus.queued": "Mengantri",
|
||||
"newKnowledge.documentStatus.ready": "Siap",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Riwayat tugas lainnya tersedia. Lanjutkan pemeriksaan untuk menemukan status dokumen ini.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "batas ukuran batch terlampaui",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "batas jumlah file terlampaui",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "file terlalu besar",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "jenis file tidak didukung atau tidak valid",
|
||||
"newKnowledge.documentUploadExclusion.more": "dan {{count}} lainnya",
|
||||
"newKnowledge.documentUploadExclusion.processing": "pemrosesan gagal",
|
||||
"newKnowledge.documentUploadExclusion.quota": "kuota ruang kerja terlampaui",
|
||||
"newKnowledge.documentUploadExclusion.target": "target dokumen tidak lagi valid",
|
||||
"newKnowledge.documentUploadFailed": "Kami tidak dapat mengunggah dokumen-dokumen ini. Coba lagi.",
|
||||
"newKnowledge.documentUploadPartial": "Pemrosesan {{accepted}} dokumen dimulai; {{excluded}} tidak dapat ditambahkan: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Tidak ada dokumen yang diterima: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Pemrosesan dokumen dimulai.",
|
||||
"newKnowledge.documents": "Dokumen",
|
||||
"newKnowledge.documentsDescription": "Aset pengetahuan dan revisinya — dari mana setiap dokumen berasal dan apakah revisi terbaru diindeks.",
|
||||
"newKnowledge.documentsDropHint": "atau letakkan PDF, DOCX, Markdown, HTML, XLSX, atau TXT untuk diunggah",
|
||||
"newKnowledge.documentsEmptyDescription": "Unggah file di sini, atau sambungkan sumber di tab Sumber — dokumen diurai, dikelompokkan, dan diindeks secara otomatis.",
|
||||
"newKnowledge.documentsEmptyTitle": "Belum ada dokumen",
|
||||
"newKnowledge.documentsErrorDescription": "Kami tidak dapat memuat dokumen. Coba lagi.",
|
||||
"newKnowledge.documentsErrorTitle": "Dokumen tidak dapat dimuat",
|
||||
"newKnowledge.documentsPermissionDescription": "Anda tidak memiliki izin untuk melihat dokumen di basis pengetahuan ini.",
|
||||
"newKnowledge.documentsPermissionTitle": "Dokumen dibatasi",
|
||||
"newKnowledge.documentsReindexFailed": "Kami tidak dapat memulai pengindeksan ulang. Coba lagi.",
|
||||
"newKnowledge.documentsReindexPartial": "Dokumen {{queued}} antri; {{missing}} sudah tidak ada lagi.",
|
||||
"newKnowledge.documentsReindexStarted": "Pengindeksan ulang dimulai.",
|
||||
"newKnowledge.documentsSelected": "Dokumen {{count}} dipilih",
|
||||
"newKnowledge.documentsUnavailable": "— dokumen",
|
||||
"newKnowledge.downloadDocuments": "Unduh",
|
||||
"newKnowledge.editSource": "Sunting",
|
||||
"newKnowledge.emptyChunk": "Potongan kosong",
|
||||
"newKnowledge.emptyDescription": "Tambahkan dokumen agar agen dapat membacanya langsung — tanpa perlu mengatur pengindeksan.",
|
||||
"newKnowledge.emptyTitle": "Cara yang lebih cerdas untuk membuat basis pengetahuan",
|
||||
"newKnowledge.errorDescription": "Kami tidak dapat memuat basis pengetahuan baru Anda. Coba lagi.",
|
||||
"newKnowledge.errorTitle": "Terjadi kesalahan",
|
||||
"newKnowledge.fileSize": "Ukuran file",
|
||||
"newKnowledge.filtersUnavailable": "Filter ini akan tersedia saat KnowledgeFS menyediakan metadatanya.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl tidak tersedia di katalog penyedia untuk ruang kerja ini.",
|
||||
"newKnowledge.gotIt": "Mengerti",
|
||||
"newKnowledge.guideDescription": "Hubungkan sumber dan dokumennya akan tetap tersinkronisasi, diindeks otomatis, serta siap digunakan tanpa pengaturan yang perlu dikelola. Basis pengetahuan lama Anda tetap berfungsi seperti biasa.",
|
||||
"newKnowledge.guideTitle": "Kenali Basis Pengetahuan baru ✨",
|
||||
"newKnowledge.illustrationHeadline": "Hubungkan konten Anda sekali — setiap aplikasi menjawab dengan pengetahuan terbaru.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Informasi indeks",
|
||||
"newKnowledge.interruptTask": "Hentikan",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Memuat kebijakan sinkronisasi",
|
||||
"newKnowledge.manualUpload": "Unggah secara manual",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Data Meta",
|
||||
"newKnowledge.mimeType": "Jenis file",
|
||||
"newKnowledge.moreProviders": "Penyedia lainnya",
|
||||
"newKnowledge.name": "Nama pengetahuan",
|
||||
"newKnowledge.namePlaceholder": "misalnya Buku Pegangan Dukungan Pelanggan",
|
||||
"newKnowledge.nameRequired": "Masukkan nama basis pengetahuan.",
|
||||
"newKnowledge.new": "Baru",
|
||||
"newKnowledge.noBackgroundTasks": "Belum ada tugas latar belakang.",
|
||||
"newKnowledge.noDescription": "Tidak ada deskripsi",
|
||||
"newKnowledge.noMatchingDocuments": "Tidak ada dokumen yang cocok dengan pencarian ini.",
|
||||
"newKnowledge.noMatchingSources": "Tidak ada sumber yang cocok dengan pencarian ini.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Basis pengetahuan ini mungkin telah dihapus atau Anda mungkin tidak memiliki akses.",
|
||||
"newKnowledge.notFoundTitle": "Basis pengetahuan tidak ditemukan",
|
||||
"newKnowledge.notionNotConnected": "Notion belum terhubung",
|
||||
"newKnowledge.notionNotConnectedDescription": "Hubungkan Notion untuk memilih halaman dan menjaganya tetap tersinkron.",
|
||||
"newKnowledge.onlineDocuments": "Dokumen daring",
|
||||
"newKnowledge.onlineDrive": "Perjalanan daring",
|
||||
"newKnowledge.overview": "Ringkasan",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} dipilih",
|
||||
"newKnowledge.partialDocumentResults": "Mungkin ada dokumen lain yang cocok. Muat lebih banyak untuk melanjutkan pencarian.",
|
||||
"newKnowledge.permission": "Izin",
|
||||
"newKnowledge.permissionAllMembers": "Ruang Kerja · semua anggota dapat melihat dan mengedit",
|
||||
"newKnowledge.permissionLoadFailed": "Izin pengeditan Anda tidak dapat diverifikasi. Coba lagi.",
|
||||
"newKnowledge.permissionOnlyMe": "Hanya aku",
|
||||
"newKnowledge.permissionRestricted": "Izin ruang kerja Anda tidak mengizinkan perubahan pengaturan ini.",
|
||||
"newKnowledge.permissionUpdateFailed": "Basis pengetahuan telah dibuat, tetapi izinnya tidak dapat dikonfirmasi. Coba lagi untuk menyelesaikan penyiapan.",
|
||||
"newKnowledge.preview": "Pratinjau",
|
||||
"newKnowledge.previewUnavailable": "Pratinjau tersedia setelah backend selesai memproses file ini.",
|
||||
"newKnowledge.processDocument": "Proses {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Terganggu",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Mempersiapkan",
|
||||
"newKnowledge.processingTaskState.failed": "Gagal",
|
||||
"newKnowledge.processingTaskState.queued": "Mengantri",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Menunggu untuk mencoba lagi",
|
||||
"newKnowledge.processingTaskState.running": "Pemrosesan · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Selesai",
|
||||
"newKnowledge.processingTaskState.superseded": "Digantikan",
|
||||
"newKnowledge.providerConnected": "Firecrawl terhubung",
|
||||
"newKnowledge.providerLoadFailed": "Penyedia tidak dapat memuat",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} tidak dikonfigurasi",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Tambahkan kunci API atau URL titik akhir untuk merayapi situs web dengan {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Penyedia ini tidak tersedia.",
|
||||
"newKnowledge.quality": "Kualitas",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Minta editor ruang kerja membuat basis pengetahuan pertama.",
|
||||
"newKnowledge.refreshConnectionStatus": "Segarkan status",
|
||||
"newKnowledge.refreshingConnection": "Menyegarkan…",
|
||||
"newKnowledge.reindexDocument": "Indeks ulang",
|
||||
"newKnowledge.reindexDocuments": "Indeks ulang",
|
||||
"newKnowledge.removeSource": "Hapus sumber",
|
||||
"newKnowledge.retrievalCount": "Jumlah pengambilan",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Coba lagi",
|
||||
"newKnowledge.retryReindexDocument": "Indeks ulang lagi",
|
||||
"newKnowledge.retrySyncPolicy": "Coba lagi kebijakan sinkronisasi",
|
||||
"newKnowledge.retryTask": "Coba lagi",
|
||||
"newKnowledge.revisionState.active": "Aktif",
|
||||
"newKnowledge.revisionState.candidate": "Memproses",
|
||||
"newKnowledge.revisionState.failed": "Gagal",
|
||||
"newKnowledge.revisionState.superseded": "Sebelumnya",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Bagian ini akan tersedia pada langkah penyiapan berikutnya.",
|
||||
"newKnowledge.searchDocuments": "Cari dokumen",
|
||||
"newKnowledge.searchSources": "Cari sumber",
|
||||
"newKnowledge.selectAll": "Pilih semua",
|
||||
"newKnowledge.selectAllDocuments": "Pilih semua dokumen",
|
||||
"newKnowledge.selectAllSources": "Pilih semua sumber",
|
||||
"newKnowledge.selectChunk": "Pilih potongan untuk memeriksa isinya.",
|
||||
"newKnowledge.selectFilesAndFolders": "Pilih file dan folder untuk disinkronkan",
|
||||
"newKnowledge.skippedFailed": "Dilewati (gagal)",
|
||||
"newKnowledge.skippedOffDomain": "Dilewati (di luar domain)",
|
||||
"newKnowledge.sourceActions": "Tindakan untuk {{name}}",
|
||||
"newKnowledge.sourceColumn": "Sumber",
|
||||
"newKnowledge.sourceFilterLabel": "Filter sumber berdasarkan status",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "Penyiapan sumber ditampilkan secara lengkap; tindakan edit dan pratinjau memerlukan alur backend sumber draf.",
|
||||
"newKnowledge.sourceStatus.active": "Aktif",
|
||||
"newKnowledge.sourceStatus.disabled": "Dinonaktifkan",
|
||||
"newKnowledge.sourceStatus.error": "Kesalahan",
|
||||
"newKnowledge.sourceStatus.syncing": "Sinkronisasi",
|
||||
"newKnowledge.sourceType.connector": "Aplikasi terhubung",
|
||||
"newKnowledge.sourceType.object-storage": "Perjalanan daring",
|
||||
"newKnowledge.sourceType.upload": "Unggah",
|
||||
"newKnowledge.sourceType.web": "Perayapan situs web",
|
||||
"newKnowledge.sourceTypeLabel": "Jenis sumber",
|
||||
"newKnowledge.sources": "Sumber",
|
||||
"newKnowledge.sourcesDescription": "Dari mana pengetahuan berasal, seberapa segar pengetahuan tersebut, dan apakah sinkronisasi terakhir berhasil.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Hubungkan situs web — Dify menjaganya tetap tersinkronisasi dan segar, sehingga pengambilan tidak pernah terputus.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Belum ada sumber yang terhubung",
|
||||
"newKnowledge.sourcesErrorDescription": "Kami tidak dapat memuat sumber. Coba lagi.",
|
||||
"newKnowledge.sourcesErrorTitle": "Sumber tidak dapat dimuat",
|
||||
"newKnowledge.startEmpty": "Mulai kosong",
|
||||
"newKnowledge.startEmptyDescription": "Buat sekarang, tambahkan konten nanti.",
|
||||
"newKnowledge.startWith": "Mulailah dengan",
|
||||
"newKnowledge.startWithHelp": "Anda dapat menambahkan lebih banyak Sumber atau Dokumen kapan saja.",
|
||||
"newKnowledge.statusColumn": "Status",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Sinkronkan sekarang",
|
||||
"newKnowledge.syncPolicy": "Kebijakan sinkronisasi",
|
||||
"newKnowledge.syncPolicyColumn": "Kebijakan sinkronisasi",
|
||||
"newKnowledge.syncPolicyCustom": "Interval khusus",
|
||||
"newKnowledge.syncPolicyDaily": "Setiap 24 jam",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Kebijakan sinkronisasi tidak dapat dimuat.",
|
||||
"newKnowledge.syncPolicyManual": "Hanya manual",
|
||||
"newKnowledge.syncPolicyProvider": "Gunakan jadwal penyedia",
|
||||
"newKnowledge.tags": "Tag",
|
||||
"newKnowledge.taskActionFailed": "Kami tidak dapat memperbarui tugas ini. Segarkan dan coba lagi.",
|
||||
"newKnowledge.taskAttentionClear": "Tidak ada tugas latar belakang yang memerlukan perhatian.",
|
||||
"newKnowledge.taskAttentionCount": "Tugas latar belakang {{count}} perlu diperhatikan.",
|
||||
"newKnowledge.taskAttentionErrorCount": "Tugas latar belakang {{count}} memerlukan perhatian, termasuk tugas yang gagal.",
|
||||
"newKnowledge.taskFailedNotification": "Pemrosesan dokumen gagal — coba lagi dari Tugas",
|
||||
"newKnowledge.taskHistoryIncomplete": "Riwayat tugas lainnya tersedia; jumlah tugas yang perlu diperhatikan belum lengkap.",
|
||||
"newKnowledge.tasks": "Tugas",
|
||||
"newKnowledge.tasksErrorDescription": "Kami tidak dapat memuat tugas latar belakang. Coba lagi.",
|
||||
"newKnowledge.tasksWithAttention": "Tugas, {{count}} perlu perhatian",
|
||||
"newKnowledge.tokenCount": "Token",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS saat ini tidak tersedia untuk ruang kerja ini.",
|
||||
"newKnowledge.unavailableTitle": "Pengetahuan Baru tidak tersedia",
|
||||
"newKnowledge.updated": "Diperbarui {{date}}",
|
||||
"newKnowledge.updatedAt": "Terakhir diperbarui",
|
||||
"newKnowledge.updatedColumn": "Diperbarui",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Karakter: —",
|
||||
"newKnowledge.uploadDocuments": "Unggah dokumen",
|
||||
"newKnowledge.uploadFiles": "Unggah file",
|
||||
"newKnowledge.uploadFilesDescription": "Tambahkan file dari komputer — file akan diindeks segera setelah dibuat.",
|
||||
"newKnowledge.uploadingFiles": "Mengunggah…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Tampilan pengetahuan",
|
||||
"newKnowledge.websiteCrawl": "Perayapan situs web",
|
||||
"noExternalKnowledge": "Belum ada API Pengetahuan Eksternal, klik di sini untuk membuat",
|
||||
"parentMode.fullDoc": "Dokumen lengkap",
|
||||
"parentMode.paragraph": "Paragraf",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Il modello di riclassificazione è necessario per la miscela di basi di conoscenza di alta qualità ed economiche.",
|
||||
"mixtureInternalAndExternalTip": "Il modello Rerank è necessario per la combinazione di conoscenze interne ed esterne.",
|
||||
"multimodal": "Multimodale",
|
||||
"newKnowledge.actionsColumn": "Azioni",
|
||||
"newKnowledge.addDocument": "Aggiungi documento",
|
||||
"newKnowledge.addSource": "Aggiungi fonte",
|
||||
"newKnowledge.addSourceDescription": "Collega una fonte a questa knowledge base: si sincronizza automaticamente e rimane aggiornata.",
|
||||
"newKnowledge.addSourceFailed": "Impossibile aggiungere questa origine. Controlla le impostazioni e riprova.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Tutto lo stato",
|
||||
"newKnowledge.allSources": "Tutte le fonti",
|
||||
"newKnowledge.apiAgentAccess": "Accesso API e agenti",
|
||||
"newKnowledge.appsUnavailable": "— app",
|
||||
"newKnowledge.authKind.api-key": "Chiave API",
|
||||
"newKnowledge.authKind.endpoint": "Punto finale",
|
||||
"newKnowledge.authenticationMethod": "Metodo di autenticazione",
|
||||
"newKnowledge.averageChunkLength": "Lunghezza media dei segmenti",
|
||||
"newKnowledge.backToList": "Torniamo alle basi di conoscenza",
|
||||
"newKnowledge.backgroundTasks": "Attività in background",
|
||||
"newKnowledge.backgroundTasksDescription": "Aggiunte di documenti, sincronizzazioni di origini e processi di reindicizzazione.",
|
||||
"newKnowledge.booleanFalse": "No",
|
||||
"newKnowledge.booleanTrue": "Sì",
|
||||
"newKnowledge.bulkDocumentActions": "Azioni di documenti in blocco",
|
||||
"newKnowledge.cancelAddSource": "Annulla",
|
||||
"newKnowledge.cardType": "Spazio di conoscenza",
|
||||
"newKnowledge.characterCount": "Caratteri",
|
||||
"newKnowledge.checkReindexStatus": "Controlla stato reindicizzazione",
|
||||
"newKnowledge.chunkCount": "Segmenti",
|
||||
"newKnowledge.chunkHeading": "Segmento {{position}}",
|
||||
"newKnowledge.chunkLocation": "Posizione {{position}} in questa revisione",
|
||||
"newKnowledge.clearDocumentSelection": "Cancella la selezione del documento",
|
||||
"newKnowledge.configureProvider": "Configura Firecrawl",
|
||||
"newKnowledge.connectNotion": "Connetti Notion",
|
||||
"newKnowledge.connectProvider": "Connetti Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Connetti provider",
|
||||
"newKnowledge.connectSource": "Collega una fonte",
|
||||
"newKnowledge.connectSourceDescription": "I contenuti restano sincronizzati con Notion, siti web e altri servizi.",
|
||||
"newKnowledge.connectingProvider": "Connessione…",
|
||||
"newKnowledge.connectionFailed": "Impossibile connettere Firecrawl. Reinserire le credenziali e riprovare.",
|
||||
"newKnowledge.connectionNeedsAttention": "La connessione Firecrawl richiede attenzione",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Aggiorna la connessione prima di aggiungere una fonte web.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl si sta connettendo",
|
||||
"newKnowledge.connectionRefreshFailed": "Impossibile aggiornare questa connessione. Riprova.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Continua a controllare lo stato",
|
||||
"newKnowledge.crawlAndPreview": "Scansione e anteprima",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Non avviato",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Anteprima scansione",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "La configurazione della scansione del sito web è disabilitata finché il flusso di lavoro di scansione non è disponibile.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl è pronto",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Non è stato possibile creare questa base di conoscenza. Controlla il tuo accesso e riprova.",
|
||||
"newKnowledge.createIllustrationTitle": "Collega i contenuti una sola volta: ogni app risponde con le conoscenze più aggiornate.",
|
||||
"newKnowledge.createTitle": "Creare Conoscenza",
|
||||
"newKnowledge.createdAt": "Creato",
|
||||
"newKnowledge.creators": "Autori",
|
||||
"newKnowledge.customIntervalHours": "Intervallo personalizzato (ore)",
|
||||
"newKnowledge.customIntervalInvalid": "Inserisci un numero intero compreso tra 1 e 720 ore.",
|
||||
"newKnowledge.deleteDocuments": "Elimina…",
|
||||
"newKnowledge.description": "Descrizione",
|
||||
"newKnowledge.descriptionHelp": "Una descrizione chiara aiuta le app a decidere quando cercare questa conoscenza.",
|
||||
"newKnowledge.descriptionPlaceholder": "per esempio. Politica di rimborso, domande frequenti sulla spedizione e passaggi di escalation: utilizzali per domande sull'assistenza clienti",
|
||||
"newKnowledge.detailErrorDescription": "Impossibile caricare questa knowledge base. Riprova.",
|
||||
"newKnowledge.detailErrorTitle": "Qualcosa è andato storto",
|
||||
"newKnowledge.disableSource": "Disabilita",
|
||||
"newKnowledge.discardDraftConfirm": "Scarta bozza",
|
||||
"newKnowledge.discardDraftDescription": "Le modifiche a questa bozza della knowledge base andranno perse.",
|
||||
"newKnowledge.discardDraftTitle": "Scartare questa bozza della knowledge base?",
|
||||
"newKnowledge.discardSourceChanges": "Ignorare le modifiche all’origine?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Ignora modifiche",
|
||||
"newKnowledge.discardSourceChangesDescription": "L’anteprima della scansione, la selezione delle pagine e le impostazioni di sincronizzazione andranno perse.",
|
||||
"newKnowledge.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.",
|
||||
"newKnowledge.documentChunksLoadError": "Impossibile caricare i segmenti di questa revisione.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Impossibile caricare altri segmenti. Riprova.",
|
||||
"newKnowledge.documentColumn": "Documento",
|
||||
"newKnowledge.documentContentIncomplete": "Caricamento del contenuto restante del documento…",
|
||||
"newKnowledge.documentContents": "Contenuti",
|
||||
"newKnowledge.documentFacts": "Dettagli documento",
|
||||
"newKnowledge.documentFilterLabel": "Filtra i documenti per stato",
|
||||
"newKnowledge.documentLoadErrorDescription": "Impossibile caricare questo documento. Riprova.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Impossibile caricare il documento",
|
||||
"newKnowledge.documentNotFoundDescription": "Il documento potrebbe essere stato rimosso oppure potresti non avere accesso.",
|
||||
"newKnowledge.documentNotFoundTitle": "Documento non trovato",
|
||||
"newKnowledge.documentOverviewDescription": "Leggi la revisione indicizzata completa e consulta i dettagli di origine e indice.",
|
||||
"newKnowledge.documentPermissionRestricted": "Le autorizzazioni del workspace non consentono azioni su documenti o attività.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "La reindicizzazione è stata accettata, ma lo stato dell’attività è in ritardo. Controllalo prima di inviarne un’altra.",
|
||||
"newKnowledge.documentReindexFailed": "La reindicizzazione non è riuscita. L’ultima revisione pronta resta disponibile.",
|
||||
"newKnowledge.documentReindexProgress": "Reindicizzazione · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Revisione",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Questo documento non ha ancora una revisione pronta. Ricontrolla al termine dell’elaborazione.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Nessuna revisione disponibile",
|
||||
"newKnowledge.documentRevisionsLoadError": "Impossibile caricare la cronologia delle revisioni. La revisione corrente resta disponibile.",
|
||||
"newKnowledge.documentStatus.disabled": "Disattivato",
|
||||
"newKnowledge.documentStatus.failed": "Fallito",
|
||||
"newKnowledge.documentStatus.processing": "Elaborazione",
|
||||
"newKnowledge.documentStatus.queued": "In coda",
|
||||
"newKnowledge.documentStatus.ready": "Pronto",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "È disponibile altra cronologia delle attività. Continua a controllare per trovare lo stato del documento.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "limite delle dimensioni del batch superato",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "limite del numero di file superato",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "il file è troppo grande",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "tipo di file non supportato o non valido",
|
||||
"newKnowledge.documentUploadExclusion.more": "e altri {{count}}",
|
||||
"newKnowledge.documentUploadExclusion.processing": "elaborazione non riuscita",
|
||||
"newKnowledge.documentUploadExclusion.quota": "quota dell'area di lavoro superata",
|
||||
"newKnowledge.documentUploadExclusion.target": "la destinazione del documento non è più valida",
|
||||
"newKnowledge.documentUploadFailed": "Non è stato possibile caricare questi documenti. Riprova.",
|
||||
"newKnowledge.documentUploadPartial": "Elaborazione avviata per {{accepted}} documenti; impossibile aggiungerne {{excluded}}: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Nessun documento è stato accettato: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "È iniziata l'elaborazione del documento.",
|
||||
"newKnowledge.documents": "Documenti",
|
||||
"newKnowledge.documentsDescription": "Risorse di conoscenza e relative revisioni: da dove proviene ciascun documento e se l'ultima revisione è indicizzata.",
|
||||
"newKnowledge.documentsDropHint": "oppure rilascia PDF, DOCX, Markdown, HTML, XLSX o TXT da caricare",
|
||||
"newKnowledge.documentsEmptyDescription": "Carica i file qui o collega una fonte nella scheda Fonti: i documenti vengono analizzati, suddivisi in blocchi e indicizzati automaticamente.",
|
||||
"newKnowledge.documentsEmptyTitle": "Nessun documento ancora",
|
||||
"newKnowledge.documentsErrorDescription": "Non è stato possibile caricare i documenti. Riprova.",
|
||||
"newKnowledge.documentsErrorTitle": "Impossibile caricare i documenti",
|
||||
"newKnowledge.documentsPermissionDescription": "Non hai l'autorizzazione per visualizzare i documenti in questa knowledge base.",
|
||||
"newKnowledge.documentsPermissionTitle": "I documenti sono limitati",
|
||||
"newKnowledge.documentsReindexFailed": "Impossibile avviare la reindicizzazione. Riprova.",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} documenti in coda; {{missing}} non esistono più.",
|
||||
"newKnowledge.documentsReindexStarted": "È iniziata la reindicizzazione.",
|
||||
"newKnowledge.documentsSelected": "Documenti {{count}} selezionati",
|
||||
"newKnowledge.documentsUnavailable": "— documenti",
|
||||
"newKnowledge.downloadDocuments": "Scarica",
|
||||
"newKnowledge.editSource": "Modifica",
|
||||
"newKnowledge.emptyChunk": "Segmento vuoto",
|
||||
"newKnowledge.emptyDescription": "Aggiungi i tuoi documenti e gli agenti potranno leggerli direttamente, senza configurare l’indicizzazione.",
|
||||
"newKnowledge.emptyTitle": "Un modo più intelligente per creare knowledge base",
|
||||
"newKnowledge.errorDescription": "Non è stato possibile caricare le nuove knowledge base. Riprova.",
|
||||
"newKnowledge.errorTitle": "Si è verificato un problema",
|
||||
"newKnowledge.fileSize": "Dimensione file",
|
||||
"newKnowledge.filtersUnavailable": "Questo filtro sarà disponibile quando KnowledgeFS esporrà i relativi metadati.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl non è disponibile nel catalogo del provider per questa area di lavoro.",
|
||||
"newKnowledge.gotIt": "Ho capito",
|
||||
"newKnowledge.guideDescription": "Collega una fonte e i suoi documenti resteranno sincronizzati, indicizzati automaticamente e pronti all’uso, senza nulla da gestire. Le knowledge base precedenti continueranno a funzionare come prima.",
|
||||
"newKnowledge.guideTitle": "Scopri la nuova Knowledge Base ✨",
|
||||
"newKnowledge.illustrationHeadline": "Collega i tuoi contenuti una sola volta: ogni app risponderà con le conoscenze più aggiornate.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Informazioni sull’indice",
|
||||
"newKnowledge.interruptTask": "Interrompere",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"newKnowledge.keepEditing": "Continua a modificare",
|
||||
"newKnowledge.lastIndexed": "Ultima indicizzazione",
|
||||
"newKnowledge.lastReadyRevisionHint": "L'elaborazione non riuscita non interrompe mai il recupero: l'ultima revisione pronta rimane disponibile nell'indice del contenuto.",
|
||||
"newKnowledge.lastSyncColumn": "Ultima sincronizzazione",
|
||||
"newKnowledge.learnMore": "Scopri di più",
|
||||
"newKnowledge.leavePartialSetupConfirm": "Esci dalla configurazione",
|
||||
"newKnowledge.leavePartialSetupDescription": "La knowledge base è stata creata, ma non è stato possibile confermarne l’autorizzazione. Uscendo, la knowledge base verrà mantenuta; potrai verificare l’accesso in seguito.",
|
||||
"newKnowledge.leavePartialSetupTitle": "Uscire dalla configurazione incompleta?",
|
||||
"newKnowledge.legacy": "Legacy",
|
||||
"newKnowledge.loadMore": "Carica altro",
|
||||
"newKnowledge.loadMoreRevisions": "Carica revisioni precedenti",
|
||||
"newKnowledge.loadingSyncPolicy": "Caricamento dei criteri di sincronizzazione",
|
||||
"newKnowledge.manualUpload": "Caricamento manuale",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Metadati",
|
||||
"newKnowledge.mimeType": "Tipo di file",
|
||||
"newKnowledge.moreProviders": "Altri provider",
|
||||
"newKnowledge.name": "Nome della conoscenza",
|
||||
"newKnowledge.namePlaceholder": "ad es. Manuale dell'assistenza clienti",
|
||||
"newKnowledge.nameRequired": "Inserisci un nome per la knowledge base.",
|
||||
"newKnowledge.new": "Nuovo",
|
||||
"newKnowledge.noBackgroundTasks": "Ancora nessuna attività in background.",
|
||||
"newKnowledge.noDescription": "Nessuna descrizione",
|
||||
"newKnowledge.noMatchingDocuments": "Nessun documento corrisponde a questa ricerca.",
|
||||
"newKnowledge.noMatchingSources": "Nessuna fonte corrisponde a questa ricerca.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Questa knowledge base potrebbe essere stata eliminata o potresti non avere accesso.",
|
||||
"newKnowledge.notFoundTitle": "Base di conoscenza non trovata",
|
||||
"newKnowledge.notionNotConnected": "Notion non è connesso",
|
||||
"newKnowledge.notionNotConnectedDescription": "Connetti Notion per scegliere le pagine e mantenerle sincronizzate.",
|
||||
"newKnowledge.onlineDocuments": "Documenti online",
|
||||
"newKnowledge.onlineDrive": "Unità in linea",
|
||||
"newKnowledge.overview": "Panoramica",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} selezionati",
|
||||
"newKnowledge.partialDocumentResults": "Potrebbero esserci altri documenti corrispondenti. Caricane altri per continuare la ricerca.",
|
||||
"newKnowledge.permission": "Autorizzazione",
|
||||
"newKnowledge.permissionAllMembers": "Area di lavoro · tutti i membri possono visualizzare e modificare",
|
||||
"newKnowledge.permissionLoadFailed": "Impossibile verificare le autorizzazioni di modifica. Riprova.",
|
||||
"newKnowledge.permissionOnlyMe": "Solo io",
|
||||
"newKnowledge.permissionRestricted": "Le autorizzazioni dell'area di lavoro non consentono di modificare questa impostazione.",
|
||||
"newKnowledge.permissionUpdateFailed": "La knowledge base è stata creata, ma non è stato possibile confermarne l’autorizzazione. Riprova per completare la configurazione.",
|
||||
"newKnowledge.preview": "Anteprima",
|
||||
"newKnowledge.previewUnavailable": "L’anteprima sarà disponibile quando il backend avrà elaborato questo file.",
|
||||
"newKnowledge.processDocument": "Processo {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Interrotto",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Preparazione",
|
||||
"newKnowledge.processingTaskState.failed": "Fallito",
|
||||
"newKnowledge.processingTaskState.queued": "In coda",
|
||||
"newKnowledge.processingTaskState.retry_wait": "In attesa di riprovare",
|
||||
"newKnowledge.processingTaskState.running": "Elaborazione · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Completato",
|
||||
"newKnowledge.processingTaskState.superseded": "Sostituito",
|
||||
"newKnowledge.providerConnected": "Firecrawl connesso",
|
||||
"newKnowledge.providerLoadFailed": "Impossibile caricare i provider",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} non è configurato",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Aggiungi una chiave API o un URL endpoint per eseguire la scansione dei siti web con {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Questo fornitore non è disponibile.",
|
||||
"newKnowledge.quality": "Qualità",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Chiedi a un editor dello spazio di lavoro di creare la prima knowledge base.",
|
||||
"newKnowledge.refreshConnectionStatus": "Aggiorna lo stato",
|
||||
"newKnowledge.refreshingConnection": "Aggiornamento…",
|
||||
"newKnowledge.reindexDocument": "Reindicizza",
|
||||
"newKnowledge.reindexDocuments": "Reindicizzare",
|
||||
"newKnowledge.removeSource": "Rimuovi la fonte",
|
||||
"newKnowledge.retrievalCount": "Numero di recuperi",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Riprova",
|
||||
"newKnowledge.retryReindexDocument": "Reindicizza di nuovo",
|
||||
"newKnowledge.retrySyncPolicy": "Riprova i criteri di sincronizzazione",
|
||||
"newKnowledge.retryTask": "Riprova",
|
||||
"newKnowledge.revisionState.active": "Attiva",
|
||||
"newKnowledge.revisionState.candidate": "In elaborazione",
|
||||
"newKnowledge.revisionState.failed": "Non riuscita",
|
||||
"newKnowledge.revisionState.superseded": "Precedente",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Questa sezione diventerà disponibile nella fase di configurazione successiva.",
|
||||
"newKnowledge.searchDocuments": "Cerca documenti",
|
||||
"newKnowledge.searchSources": "Cerca fonti",
|
||||
"newKnowledge.selectAll": "Seleziona tutto",
|
||||
"newKnowledge.selectAllDocuments": "Seleziona tutti i documenti",
|
||||
"newKnowledge.selectAllSources": "Seleziona tutte le origini",
|
||||
"newKnowledge.selectChunk": "Seleziona un segmento per esaminarne il contenuto.",
|
||||
"newKnowledge.selectFilesAndFolders": "Seleziona file e cartelle da sincronizzare",
|
||||
"newKnowledge.skippedFailed": "Ignorato (errore)",
|
||||
"newKnowledge.skippedOffDomain": "Ignorato (fuori dominio)",
|
||||
"newKnowledge.sourceActions": "Azioni per {{name}}",
|
||||
"newKnowledge.sourceColumn": "Fonte",
|
||||
"newKnowledge.sourceFilterLabel": "Filtra le fonti per stato",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "La configurazione dell’origine è mostrata per intero; modifica e anteprima richiedono il flusso backend dell’origine in bozza.",
|
||||
"newKnowledge.sourceStatus.active": "Attivo",
|
||||
"newKnowledge.sourceStatus.disabled": "Disabilitato",
|
||||
"newKnowledge.sourceStatus.error": "Errore",
|
||||
"newKnowledge.sourceStatus.syncing": "Sincronizzazione",
|
||||
"newKnowledge.sourceType.connector": "Applicazione connessa",
|
||||
"newKnowledge.sourceType.object-storage": "Unità in linea",
|
||||
"newKnowledge.sourceType.upload": "Carica",
|
||||
"newKnowledge.sourceType.web": "Scansione del sito web",
|
||||
"newKnowledge.sourceTypeLabel": "Tipo di origine",
|
||||
"newKnowledge.sources": "Fonti",
|
||||
"newKnowledge.sourcesDescription": "Da dove proviene la conoscenza, quanto è recente e se l'ultima sincronizzazione è riuscita.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Collega un sito web: Dify lo mantiene aggiornato e sincronizzato, quindi il recupero non si interrompe mai.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Nessuna sorgente ancora collegata",
|
||||
"newKnowledge.sourcesErrorDescription": "Impossibile caricare i sorgenti. Riprova.",
|
||||
"newKnowledge.sourcesErrorTitle": "Impossibile caricare le fonti",
|
||||
"newKnowledge.startEmpty": "Inizia da zero",
|
||||
"newKnowledge.startEmptyDescription": "Creala ora e aggiungi i contenuti in seguito.",
|
||||
"newKnowledge.startWith": "Inizia con",
|
||||
"newKnowledge.startWithHelp": "Puoi aggiungere più fonti o documenti in qualsiasi momento.",
|
||||
"newKnowledge.statusColumn": "Stato",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Sincronizza ora",
|
||||
"newKnowledge.syncPolicy": "Criteri di sincronizzazione",
|
||||
"newKnowledge.syncPolicyColumn": "Criterio di sincronizzazione",
|
||||
"newKnowledge.syncPolicyCustom": "Intervallo personalizzato",
|
||||
"newKnowledge.syncPolicyDaily": "Ogni 24 ore",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Impossibile caricare i criteri di sincronizzazione.",
|
||||
"newKnowledge.syncPolicyManual": "Solo manuale",
|
||||
"newKnowledge.syncPolicyProvider": "Usa la pianificazione del provider",
|
||||
"newKnowledge.tags": "Tag",
|
||||
"newKnowledge.taskActionFailed": "Impossibile aggiornare questa attività. Aggiorna e riprova.",
|
||||
"newKnowledge.taskAttentionClear": "Nessuna attività in background richiede attenzione.",
|
||||
"newKnowledge.taskAttentionCount": "Le attività in background {{count}} richiedono attenzione.",
|
||||
"newKnowledge.taskAttentionErrorCount": "Le attività in background {{count}} richiedono attenzione, comprese le attività non riuscite.",
|
||||
"newKnowledge.taskFailedNotification": "Elaborazione del documento non riuscita — riprova da Attività",
|
||||
"newKnowledge.taskHistoryIncomplete": "È disponibile altra cronologia delle attività; il conteggio delle attività che richiedono attenzione è incompleto.",
|
||||
"newKnowledge.tasks": "Compiti",
|
||||
"newKnowledge.tasksErrorDescription": "Non è stato possibile caricare le attività in background. Riprova.",
|
||||
"newKnowledge.tasksWithAttention": "I compiti, {{count}} richiedono attenzione",
|
||||
"newKnowledge.tokenCount": "Token",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS non è al momento disponibile per questo spazio di lavoro.",
|
||||
"newKnowledge.unavailableTitle": "La nuova conoscenza non è disponibile",
|
||||
"newKnowledge.updated": "Aggiornato il {{date}}",
|
||||
"newKnowledge.updatedAt": "Ultimo aggiornamento",
|
||||
"newKnowledge.updatedColumn": "Aggiornato",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Caratteri: —",
|
||||
"newKnowledge.uploadDocuments": "Carica documenti",
|
||||
"newKnowledge.uploadFiles": "Carica file",
|
||||
"newKnowledge.uploadFilesDescription": "Aggiungi file dal computer: vengono indicizzati appena li crei.",
|
||||
"newKnowledge.uploadingFiles": "Caricamento…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Vista conoscenza",
|
||||
"newKnowledge.websiteCrawl": "Scansione del sito web",
|
||||
"noExternalKnowledge": "Non esiste ancora un'API di conoscenza esterna, fai clic qui per creare",
|
||||
"parentMode.fullDoc": "Full-doc",
|
||||
"parentMode.paragraph": "Paragrafo",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "高品質なナレッジベースとコスト重視のナレッジベースを混在させるには、Rerank モデルが必要です。",
|
||||
"mixtureInternalAndExternalTip": "内部と外部のナレッジベースを混在させる場合、Rerank モデルが必要です。",
|
||||
"multimodal": "マルチモーダル",
|
||||
"newKnowledge.actionsColumn": "アクション",
|
||||
"newKnowledge.addDocument": "ドキュメントの追加",
|
||||
"newKnowledge.addSource": "ソースを追加",
|
||||
"newKnowledge.addSourceDescription": "ソースをこのナレッジ ベースに接続すると、自動的に同期され、最新の状態が保たれます。",
|
||||
"newKnowledge.addSourceFailed": "このソースを追加できませんでした。設定を確認して、もう一度お試しください。",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "すべてのステータス",
|
||||
"newKnowledge.allSources": "すべてのソース",
|
||||
"newKnowledge.apiAgentAccess": "API とエージェントアクセス",
|
||||
"newKnowledge.appsUnavailable": "— 個のアプリ",
|
||||
"newKnowledge.authKind.api-key": "APIキー",
|
||||
"newKnowledge.authKind.endpoint": "エンドポイント",
|
||||
"newKnowledge.authenticationMethod": "認証方法",
|
||||
"newKnowledge.averageChunkLength": "チャンクの平均長",
|
||||
"newKnowledge.backToList": "ナレッジベースに戻る",
|
||||
"newKnowledge.backgroundTasks": "バックグラウンドタスク",
|
||||
"newKnowledge.backgroundTasksDescription": "ドキュメントの追加、ソースの同期、ジョブの再インデックス。",
|
||||
"newKnowledge.booleanFalse": "いいえ",
|
||||
"newKnowledge.booleanTrue": "はい",
|
||||
"newKnowledge.bulkDocumentActions": "一括ドキュメントアクション",
|
||||
"newKnowledge.cancelAddSource": "キャンセル",
|
||||
"newKnowledge.cardType": "ナレッジスペース",
|
||||
"newKnowledge.characterCount": "文字数",
|
||||
"newKnowledge.checkReindexStatus": "再インデックス状態を確認",
|
||||
"newKnowledge.chunkCount": "チャンク",
|
||||
"newKnowledge.chunkHeading": "チャンク {{position}}",
|
||||
"newKnowledge.chunkLocation": "このリビジョン内の位置 {{position}}",
|
||||
"newKnowledge.clearDocumentSelection": "ドキュメントの選択をクリアする",
|
||||
"newKnowledge.configureProvider": "Firecrawl を構成する",
|
||||
"newKnowledge.connectNotion": "Notion に接続",
|
||||
"newKnowledge.connectProvider": "Firecrawl を接続する",
|
||||
"newKnowledge.connectProviderGeneric": "プロバイダーに接続",
|
||||
"newKnowledge.connectSource": "ソースを接続",
|
||||
"newKnowledge.connectSourceDescription": "コンテンツは Notion、Web サイト、その他のサービスと同期され続けます。",
|
||||
"newKnowledge.connectingProvider": "接続中…",
|
||||
"newKnowledge.connectionFailed": "Firecrawl に接続できませんでした。資格情報を再入力して、再試行してください。",
|
||||
"newKnowledge.connectionNeedsAttention": "Firecrawl 接続には注意が必要です",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Web サイトのソースを追加する前に接続を更新してください。",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl が接続中です",
|
||||
"newKnowledge.connectionRefreshFailed": "この接続を更新できませんでした。もう一度やり直してください。",
|
||||
"newKnowledge.continueCheckingTaskStatus": "タスク状態の確認を続ける",
|
||||
"newKnowledge.crawlAndPreview": "クロールとプレビュー",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "未開始",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "クロールのプレビュー",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Web サイトのクロール設定は、クロール ワークフローが利用可能になるまで無効になります。",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl の準備ができました",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "このナレッジ ベースを作成できませんでした。アクセスを確認して再試行してください。",
|
||||
"newKnowledge.createIllustrationTitle": "コンテンツを一度接続すれば、すべてのアプリが最新のナレッジで回答します。",
|
||||
"newKnowledge.createTitle": "知識の創造",
|
||||
"newKnowledge.createdAt": "作成日時",
|
||||
"newKnowledge.creators": "作成者",
|
||||
"newKnowledge.customIntervalHours": "カスタム間隔(時間)",
|
||||
"newKnowledge.customIntervalInvalid": "1~720 の整数を入力してください。",
|
||||
"newKnowledge.deleteDocuments": "削除…",
|
||||
"newKnowledge.description": "説明",
|
||||
"newKnowledge.descriptionHelp": "明確な説明は、アプリがこのナレッジをいつ検索するかを決定するのに役立ちます。",
|
||||
"newKnowledge.descriptionPlaceholder": "例:返金ポリシー、配送に関する FAQ、エスカレーション手順 - カスタマー サポートの質問に使用します",
|
||||
"newKnowledge.detailErrorDescription": "このナレッジベースをロードできませんでした。もう一度やり直してください。",
|
||||
"newKnowledge.detailErrorTitle": "何か問題が発生しました",
|
||||
"newKnowledge.disableSource": "無効にする",
|
||||
"newKnowledge.discardDraftConfirm": "下書きを破棄",
|
||||
"newKnowledge.discardDraftDescription": "このナレッジベースの下書きに加えた変更は失われます。",
|
||||
"newKnowledge.discardDraftTitle": "このナレッジベースの下書きを破棄しますか?",
|
||||
"newKnowledge.discardSourceChanges": "ソースの変更を破棄しますか?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "変更を破棄",
|
||||
"newKnowledge.discardSourceChangesDescription": "クロールのプレビュー、ページの選択、同期設定は失われます。",
|
||||
"newKnowledge.documentActions": "{{name}} のアクション",
|
||||
"newKnowledge.documentActionsUnavailable": "ドキュメント アクションは、このナレッジ ベースではまだ利用できません。",
|
||||
"newKnowledge.documentChunksEmpty": "このリビジョンで利用できるチャンクはまだありません。",
|
||||
"newKnowledge.documentChunksLoadError": "このリビジョンのチャンクを読み込めませんでした。",
|
||||
"newKnowledge.documentChunksLoadMoreError": "追加のチャンクを読み込めませんでした。もう一度お試しください。",
|
||||
"newKnowledge.documentColumn": "文書",
|
||||
"newKnowledge.documentContentIncomplete": "ドキュメントの残りの内容を読み込んでいます…",
|
||||
"newKnowledge.documentContents": "目次",
|
||||
"newKnowledge.documentFacts": "ドキュメントの詳細",
|
||||
"newKnowledge.documentFilterLabel": "ドキュメントをステータスでフィルタリングする",
|
||||
"newKnowledge.documentLoadErrorDescription": "このドキュメントを読み込めませんでした。もう一度お試しください。",
|
||||
"newKnowledge.documentLoadErrorTitle": "ドキュメントを読み込めませんでした",
|
||||
"newKnowledge.documentNotFoundDescription": "このドキュメントは削除されたか、アクセス権がない可能性があります。",
|
||||
"newKnowledge.documentNotFoundTitle": "ドキュメントが見つかりません",
|
||||
"newKnowledge.documentOverviewDescription": "インデックス済みのリビジョン全体を読み、ソースとインデックスの詳細を確認します。",
|
||||
"newKnowledge.documentPermissionRestricted": "ワークスペースの権限では、ドキュメントまたはタスクの操作を実行できません。",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "再インデックスは受理されましたが、タスク状態の反映が遅れています。再送信前に確認してください。",
|
||||
"newKnowledge.documentReindexFailed": "再インデックスに失敗しました。最後に準備完了となったリビジョンは引き続き利用できます。",
|
||||
"newKnowledge.documentReindexProgress": "再インデックス中 · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "リビジョン",
|
||||
"newKnowledge.documentRevisionMissingDescription": "このドキュメントには準備完了のリビジョンがまだありません。処理完了後に再度確認してください。",
|
||||
"newKnowledge.documentRevisionMissingTitle": "利用可能なリビジョンはありません",
|
||||
"newKnowledge.documentRevisionsLoadError": "リビジョン履歴を読み込めませんでした。現在のリビジョンは引き続き利用できます。",
|
||||
"newKnowledge.documentStatus.disabled": "無効",
|
||||
"newKnowledge.documentStatus.failed": "失敗しました",
|
||||
"newKnowledge.documentStatus.processing": "処理中",
|
||||
"newKnowledge.documentStatus.queued": "キューに入れられました",
|
||||
"newKnowledge.documentStatus.ready": "準備完了",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "さらにタスク履歴があります。このドキュメントの状態を確認し続けてください。",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "バッチサイズの上限を超えています",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "ファイル数の上限を超えています",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "ファイルが大きすぎます",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "サポートされていないか無効なファイル形式です",
|
||||
"newKnowledge.documentUploadExclusion.more": "ほか {{count}} 件",
|
||||
"newKnowledge.documentUploadExclusion.processing": "処理に失敗しました",
|
||||
"newKnowledge.documentUploadExclusion.quota": "ワークスペースのクォータを超えています",
|
||||
"newKnowledge.documentUploadExclusion.target": "ドキュメントの対象が無効になっています",
|
||||
"newKnowledge.documentUploadFailed": "これらの書類をアップロードできませんでした。もう一度やり直してください。",
|
||||
"newKnowledge.documentUploadPartial": "{{accepted}} 件のドキュメントの処理を開始しました。{{excluded}} 件は追加できませんでした:{{details}}",
|
||||
"newKnowledge.documentUploadRejected": "ドキュメントを受け付けられませんでした:{{details}}",
|
||||
"newKnowledge.documentUploadStarted": "文書処理が開始されました。",
|
||||
"newKnowledge.documents": "書類",
|
||||
"newKnowledge.documentsDescription": "ナレッジ資産とそのリビジョン - 各ドキュメントの出所と、最新のリビジョンがインデックス付けされているかどうか。",
|
||||
"newKnowledge.documentsDropHint": "または、PDF、DOCX、Markdown、HTML、XLSX、または TXT をドロップしてアップロードします",
|
||||
"newKnowledge.documentsEmptyDescription": "ここでファイルをアップロードするか、「ソース」タブでソースを接続します。ドキュメントは自動的に解析され、チャンク化され、インデックスが作成されます。",
|
||||
"newKnowledge.documentsEmptyTitle": "まだドキュメントがありません",
|
||||
"newKnowledge.documentsErrorDescription": "書類を読み込めませんでした。もう一度やり直してください。",
|
||||
"newKnowledge.documentsErrorTitle": "ドキュメントを読み込めませんでした",
|
||||
"newKnowledge.documentsPermissionDescription": "このナレッジ ベースのドキュメントを表示する権限がありません。",
|
||||
"newKnowledge.documentsPermissionTitle": "書類が制限されている",
|
||||
"newKnowledge.documentsReindexFailed": "インデックスの再作成を開始できませんでした。もう一度やり直してください。",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} ドキュメントがキューに入れられました。 {{missing}} は存在しません。",
|
||||
"newKnowledge.documentsReindexStarted": "再インデックス作成が開始されました。",
|
||||
"newKnowledge.documentsSelected": "{{count}} ドキュメントが選択されました",
|
||||
"newKnowledge.documentsUnavailable": "— 件のドキュメント",
|
||||
"newKnowledge.downloadDocuments": "ダウンロード",
|
||||
"newKnowledge.editSource": "編集",
|
||||
"newKnowledge.emptyChunk": "空のチャンク",
|
||||
"newKnowledge.emptyDescription": "ドキュメントを追加すると、エージェントが直接読み取れます。インデックス設定は不要です。",
|
||||
"newKnowledge.emptyTitle": "ナレッジベースをよりスマートに構築",
|
||||
"newKnowledge.errorDescription": "新しいナレッジベースを読み込めませんでした。もう一度お試しください。",
|
||||
"newKnowledge.errorTitle": "問題が発生しました",
|
||||
"newKnowledge.fileSize": "ファイルサイズ",
|
||||
"newKnowledge.filtersUnavailable": "KnowledgeFS がメタデータを提供すると、このフィルターを利用できます。",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl は、このワークスペースのプロバイダー カタログでは使用できません。",
|
||||
"newKnowledge.gotIt": "了解",
|
||||
"newKnowledge.guideDescription": "ソースを接続すると、そのドキュメントは同期され、自動的にインデックス化されてすぐに利用できます。管理する設定はありません。従来のナレッジベースもこれまでどおり動作します。",
|
||||
"newKnowledge.guideTitle": "新しいナレッジベースへようこそ ✨",
|
||||
"newKnowledge.illustrationHeadline": "コンテンツを一度接続すれば、すべてのアプリが最新のナレッジで回答します。",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "インデックス情報",
|
||||
"newKnowledge.interruptTask": "中断",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid 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": "古いリビジョンを読み込む",
|
||||
"newKnowledge.loadingSyncPolicy": "同期ポリシーを読み込み中",
|
||||
"newKnowledge.manualUpload": "手動アップロード",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "メタデータ",
|
||||
"newKnowledge.mimeType": "ファイル形式",
|
||||
"newKnowledge.moreProviders": "その他のプロバイダー",
|
||||
"newKnowledge.name": "ナレッジ名",
|
||||
"newKnowledge.namePlaceholder": "例:カスタマーサポートハンドブック",
|
||||
"newKnowledge.nameRequired": "ナレッジベース名を入力します。",
|
||||
"newKnowledge.new": "新バージョン",
|
||||
"newKnowledge.noBackgroundTasks": "バックグラウンドタスクはまだありません。",
|
||||
"newKnowledge.noDescription": "説明なし",
|
||||
"newKnowledge.noMatchingDocuments": "この検索に一致するドキュメントはありません。",
|
||||
"newKnowledge.noMatchingSources": "この検索に一致するソースはありません。",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "このナレッジ ベースは削除されているか、アクセスできない可能性があります。",
|
||||
"newKnowledge.notFoundTitle": "ナレッジベースが見つかりません",
|
||||
"newKnowledge.notionNotConnected": "Notion に接続されていません",
|
||||
"newKnowledge.notionNotConnectedDescription": "Notion に接続してページを選択し、同期を維持します。",
|
||||
"newKnowledge.onlineDocuments": "オンライン文書",
|
||||
"newKnowledge.onlineDrive": "オンラインドライブ",
|
||||
"newKnowledge.overview": "概要",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} 件選択済み",
|
||||
"newKnowledge.partialDocumentResults": "一致するドキュメントがほかにもある可能性があります。検索を続けるにはさらに読み込んでください。",
|
||||
"newKnowledge.permission": "許可",
|
||||
"newKnowledge.permissionAllMembers": "ワークスペース · すべてのメンバーが表示および編集できます",
|
||||
"newKnowledge.permissionLoadFailed": "編集権限を確認できませんでした。もう一度お試しください。",
|
||||
"newKnowledge.permissionOnlyMe": "私だけ",
|
||||
"newKnowledge.permissionRestricted": "ワークスペースの権限では、この設定を変更することはできません。",
|
||||
"newKnowledge.permissionUpdateFailed": "ナレッジベースは作成されましたが、権限を確認できませんでした。再試行してセットアップを完了してください。",
|
||||
"newKnowledge.preview": "プレビュー",
|
||||
"newKnowledge.previewUnavailable": "バックエンドでこのファイルの処理が完了すると、プレビューを利用できます。",
|
||||
"newKnowledge.processDocument": "プロセス {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "中断されました",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "準備中",
|
||||
"newKnowledge.processingTaskState.failed": "失敗しました",
|
||||
"newKnowledge.processingTaskState.queued": "キューに入れられました",
|
||||
"newKnowledge.processingTaskState.retry_wait": "再試行を待っています",
|
||||
"newKnowledge.processingTaskState.running": "処理中・{{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "完了しました",
|
||||
"newKnowledge.processingTaskState.superseded": "置き換えられました",
|
||||
"newKnowledge.providerConnected": "Firecrawl が接続されました",
|
||||
"newKnowledge.providerLoadFailed": "プロバイダーを読み込めませんでした",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} が構成されていません",
|
||||
"newKnowledge.providerNotConfiguredDescription": "{{provider}} を使用して Web サイトをクロールするための API キーまたはエンドポイント URL を追加します。",
|
||||
"newKnowledge.providerUnavailable": "このプロバイダーは利用できません。",
|
||||
"newKnowledge.quality": "品質",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "最初のナレッジベースの作成をワークスペース編集者に依頼してください。",
|
||||
"newKnowledge.refreshConnectionStatus": "ステータスの更新",
|
||||
"newKnowledge.refreshingConnection": "爽やか…",
|
||||
"newKnowledge.reindexDocument": "再インデックス",
|
||||
"newKnowledge.reindexDocuments": "インデックスを再作成する",
|
||||
"newKnowledge.removeSource": "ソースを削除",
|
||||
"newKnowledge.retrievalCount": "取得回数",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "再試行",
|
||||
"newKnowledge.retryReindexDocument": "もう一度再インデックス",
|
||||
"newKnowledge.retrySyncPolicy": "同期ポリシーを再読み込み",
|
||||
"newKnowledge.retryTask": "再試行",
|
||||
"newKnowledge.revisionState.active": "有効",
|
||||
"newKnowledge.revisionState.candidate": "処理中",
|
||||
"newKnowledge.revisionState.failed": "失敗",
|
||||
"newKnowledge.revisionState.superseded": "以前",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "このセクションは、次のセットアップ手順で使用できるようになります。",
|
||||
"newKnowledge.searchDocuments": "文書の検索",
|
||||
"newKnowledge.searchSources": "ソースを検索",
|
||||
"newKnowledge.selectAll": "すべて選択",
|
||||
"newKnowledge.selectAllDocuments": "すべてのドキュメントを選択します",
|
||||
"newKnowledge.selectAllSources": "すべてのソースを選択",
|
||||
"newKnowledge.selectChunk": "内容を確認するチャンクを選択してください。",
|
||||
"newKnowledge.selectFilesAndFolders": "同期するファイルとフォルダーを選択",
|
||||
"newKnowledge.skippedFailed": "スキップ(失敗)",
|
||||
"newKnowledge.skippedOffDomain": "スキップ(ドメイン外)",
|
||||
"newKnowledge.sourceActions": "{{name}} のアクション",
|
||||
"newKnowledge.sourceColumn": "ソース",
|
||||
"newKnowledge.sourceFilterLabel": "ソースをステータスでフィルタリングする",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "ソース設定はすべて表示されています。編集とプレビューには、下書きソースのバックエンドワークフローが必要です。",
|
||||
"newKnowledge.sourceStatus.active": "アクティブ",
|
||||
"newKnowledge.sourceStatus.disabled": "無効",
|
||||
"newKnowledge.sourceStatus.error": "エラー",
|
||||
"newKnowledge.sourceStatus.syncing": "同期中",
|
||||
"newKnowledge.sourceType.connector": "接続済みアプリ",
|
||||
"newKnowledge.sourceType.object-storage": "オンラインドライブ",
|
||||
"newKnowledge.sourceType.upload": "アップロード",
|
||||
"newKnowledge.sourceType.web": "ウェブサイトのクロール",
|
||||
"newKnowledge.sourceTypeLabel": "ソースタイプ",
|
||||
"newKnowledge.sources": "情報源",
|
||||
"newKnowledge.sourcesDescription": "知識がどこから来たのか、その新鮮さはどれくらいか、最後の同期が成功したかどうか。",
|
||||
"newKnowledge.sourcesEmptyDescription": "Web サイトを接続する — Dify は Web サイトを同期して最新の状態に保つため、検索が途切れることはありません。",
|
||||
"newKnowledge.sourcesEmptyTitle": "ソースがまだ接続されていません",
|
||||
"newKnowledge.sourcesErrorDescription": "ソースをロードできませんでした。もう一度やり直してください。",
|
||||
"newKnowledge.sourcesErrorTitle": "ソースをロードできませんでした",
|
||||
"newKnowledge.startEmpty": "空から始める",
|
||||
"newKnowledge.startEmptyDescription": "今すぐ作成し、コンテンツは後から追加できます。",
|
||||
"newKnowledge.startWith": "から始める",
|
||||
"newKnowledge.startWithHelp": "いつでもソースやドキュメントを追加できます。",
|
||||
"newKnowledge.statusColumn": "ステータス",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "今すぐ同期",
|
||||
"newKnowledge.syncPolicy": "同期ポリシー",
|
||||
"newKnowledge.syncPolicyColumn": "同期ポリシー",
|
||||
"newKnowledge.syncPolicyCustom": "カスタム間隔",
|
||||
"newKnowledge.syncPolicyDaily": "24時間ごと",
|
||||
"newKnowledge.syncPolicyLoadFailed": "同期ポリシーを読み込めませんでした。",
|
||||
"newKnowledge.syncPolicyManual": "手動のみ",
|
||||
"newKnowledge.syncPolicyProvider": "プロバイダーのスケジュールを使用",
|
||||
"newKnowledge.tags": "タグ",
|
||||
"newKnowledge.taskActionFailed": "このタスクを更新できませんでした。更新して再試行してください。",
|
||||
"newKnowledge.taskAttentionClear": "注意を必要とするバックグラウンド タスクはありません。",
|
||||
"newKnowledge.taskAttentionCount": "{{count}} バックグラウンド タスクには注意が必要です。",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}} バックグラウンド タスク (失敗したタスクも含む) には注意が必要です。",
|
||||
"newKnowledge.taskFailedNotification": "ドキュメントの処理に失敗しました —「タスク」から再試行してください",
|
||||
"newKnowledge.taskHistoryIncomplete": "さらにタスク履歴があります。対応が必要なタスク数は確定していません。",
|
||||
"newKnowledge.tasks": "タスク",
|
||||
"newKnowledge.tasksErrorDescription": "バックグラウンドタスクをロードできませんでした。もう一度やり直してください。",
|
||||
"newKnowledge.tasksWithAttention": "タスク、{{count}} は注意が必要です",
|
||||
"newKnowledge.tokenCount": "トークン",
|
||||
"newKnowledge.unavailableDescription": "このワークスペースでは現在 KnowledgeFS を利用できません。",
|
||||
"newKnowledge.unavailableTitle": "新しいナレッジは利用できません",
|
||||
"newKnowledge.updated": "{{date}} に更新",
|
||||
"newKnowledge.updatedAt": "最終更新",
|
||||
"newKnowledge.updatedColumn": "更新されました",
|
||||
"newKnowledge.uploadCharactersUnavailable": "文字数:—",
|
||||
"newKnowledge.uploadDocuments": "書類のアップロード",
|
||||
"newKnowledge.uploadFiles": "ファイルをアップロード",
|
||||
"newKnowledge.uploadFilesDescription": "コンピューターからファイルを追加すると、作成時にすぐインデックス化されます。",
|
||||
"newKnowledge.uploadingFiles": "アップロード中…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "ナレッジ表示",
|
||||
"newKnowledge.websiteCrawl": "ウェブサイトのクロール",
|
||||
"noExternalKnowledge": "外部ナレッジベース連携 API がありません。ここをクリックして作成してください",
|
||||
"parentMode.fullDoc": "全体",
|
||||
"parentMode.paragraph": "段落",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "고품질과 경제적 지식 베이스의 혼합을 위해서는 재순위 모델이 필요합니다.",
|
||||
"mixtureInternalAndExternalTip": "리랭크 모델은 내부 및 외부 지식의 혼합에 필요합니다.",
|
||||
"multimodal": "멀티모달",
|
||||
"newKnowledge.actionsColumn": "작업",
|
||||
"newKnowledge.addDocument": "문서 추가",
|
||||
"newKnowledge.addSource": "소스 추가",
|
||||
"newKnowledge.addSourceDescription": "이 지식 베이스에 소스를 연결하세요. 자동으로 동기화되고 최신 상태로 유지됩니다.",
|
||||
"newKnowledge.addSourceFailed": "이 소스를 추가하지 못했습니다. 설정을 검토한 후 다시 시도하세요.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "모든 상태",
|
||||
"newKnowledge.allSources": "모든 소스",
|
||||
"newKnowledge.apiAgentAccess": "API 및 에이전트 액세스",
|
||||
"newKnowledge.appsUnavailable": "앱 —개",
|
||||
"newKnowledge.authKind.api-key": "API 키",
|
||||
"newKnowledge.authKind.endpoint": "종점",
|
||||
"newKnowledge.authenticationMethod": "인증 방법",
|
||||
"newKnowledge.averageChunkLength": "평균 청크 길이",
|
||||
"newKnowledge.backToList": "지식 베이스로 돌아가기",
|
||||
"newKnowledge.backgroundTasks": "백그라운드 작업",
|
||||
"newKnowledge.backgroundTasksDescription": "문서 추가, 소스 동기화 및 재색인 작업.",
|
||||
"newKnowledge.booleanFalse": "아니요",
|
||||
"newKnowledge.booleanTrue": "예",
|
||||
"newKnowledge.bulkDocumentActions": "대량 문서 작업",
|
||||
"newKnowledge.cancelAddSource": "취소",
|
||||
"newKnowledge.cardType": "지식 공간",
|
||||
"newKnowledge.characterCount": "문자 수",
|
||||
"newKnowledge.checkReindexStatus": "재인덱싱 상태 확인",
|
||||
"newKnowledge.chunkCount": "청크",
|
||||
"newKnowledge.chunkHeading": "청크 {{position}}",
|
||||
"newKnowledge.chunkLocation": "이 리비전의 위치 {{position}}",
|
||||
"newKnowledge.clearDocumentSelection": "문서 선택 지우기",
|
||||
"newKnowledge.configureProvider": "Firecrawl 구성",
|
||||
"newKnowledge.connectNotion": "Notion 연결",
|
||||
"newKnowledge.connectProvider": "Firecrawl 연결",
|
||||
"newKnowledge.connectProviderGeneric": "공급자 연결",
|
||||
"newKnowledge.connectSource": "소스 연결",
|
||||
"newKnowledge.connectSourceDescription": "콘텐츠가 Notion, 웹사이트 및 기타 서비스와 계속 동기화됩니다.",
|
||||
"newKnowledge.connectingProvider": "연결 중…",
|
||||
"newKnowledge.connectionFailed": "Firecrawl을 연결할 수 없습니다. 자격 증명을 다시 입력하고 다시 시도하세요.",
|
||||
"newKnowledge.connectionNeedsAttention": "Firecrawl 연결에 주의가 필요합니다.",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "웹사이트 소스를 추가하기 전에 연결을 새로 고치세요.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl이 연결 중입니다.",
|
||||
"newKnowledge.connectionRefreshFailed": "이 연결을 새로 고칠 수 없습니다. 다시 시도해 보세요.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "작업 상태 계속 확인",
|
||||
"newKnowledge.crawlAndPreview": "크롤링 및 미리보기",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "시작되지 않음",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "크롤링 미리보기",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "크롤링 워크플로를 사용할 수 있을 때까지 웹사이트 크롤링 구성이 비활성화됩니다.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl이 준비되었습니다.",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "이 기술 자료를 만들 수 없습니다. 액세스 권한을 확인하고 다시 시도하세요.",
|
||||
"newKnowledge.createIllustrationTitle": "콘텐츠를 한 번 연결하면 모든 앱이 최신 지식으로 답변합니다.",
|
||||
"newKnowledge.createTitle": "지식 창조",
|
||||
"newKnowledge.createdAt": "생성됨",
|
||||
"newKnowledge.creators": "생성자",
|
||||
"newKnowledge.customIntervalHours": "사용자 지정 간격(시간)",
|
||||
"newKnowledge.customIntervalInvalid": "1~720 사이의 정수를 입력하세요.",
|
||||
"newKnowledge.deleteDocuments": "삭제…",
|
||||
"newKnowledge.description": "설명",
|
||||
"newKnowledge.descriptionHelp": "명확한 설명은 앱이 이 지식을 검색할 시기를 결정하는 데 도움이 됩니다.",
|
||||
"newKnowledge.descriptionPlaceholder": "예를 들어 환불 정책, 배송 FAQ, 에스컬레이션 단계 - 고객 지원 질문에 사용",
|
||||
"newKnowledge.detailErrorDescription": "이 기술 자료를 로드할 수 없습니다. 다시 시도해 보세요.",
|
||||
"newKnowledge.detailErrorTitle": "문제가 발생했습니다.",
|
||||
"newKnowledge.disableSource": "비활성화",
|
||||
"newKnowledge.discardDraftConfirm": "초안 삭제",
|
||||
"newKnowledge.discardDraftDescription": "이 지식 베이스 초안의 변경 사항이 삭제됩니다.",
|
||||
"newKnowledge.discardDraftTitle": "이 지식 베이스 초안을 삭제할까요?",
|
||||
"newKnowledge.discardSourceChanges": "소스 변경 사항을 삭제할까요?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "변경 사항 삭제",
|
||||
"newKnowledge.discardSourceChangesDescription": "크롤링 미리보기, 페이지 선택 및 동기화 설정이 사라집니다.",
|
||||
"newKnowledge.documentActions": "{{name}}에 대한 작업",
|
||||
"newKnowledge.documentActionsUnavailable": "이 기술 자료에는 아직 문서 작업을 사용할 수 없습니다.",
|
||||
"newKnowledge.documentChunksEmpty": "이 리비전에 사용할 수 있는 청크가 아직 없습니다.",
|
||||
"newKnowledge.documentChunksLoadError": "이 리비전의 청크를 불러오지 못했습니다.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "청크를 더 불러오지 못했습니다. 다시 시도하세요.",
|
||||
"newKnowledge.documentColumn": "문서",
|
||||
"newKnowledge.documentContentIncomplete": "나머지 문서 콘텐츠를 불러오는 중…",
|
||||
"newKnowledge.documentContents": "목차",
|
||||
"newKnowledge.documentFacts": "문서 세부 정보",
|
||||
"newKnowledge.documentFilterLabel": "상태별로 문서 필터링",
|
||||
"newKnowledge.documentLoadErrorDescription": "이 문서를 불러오지 못했습니다. 다시 시도하세요.",
|
||||
"newKnowledge.documentLoadErrorTitle": "문서를 불러오지 못함",
|
||||
"newKnowledge.documentNotFoundDescription": "이 문서가 삭제되었거나 접근 권한이 없을 수 있습니다.",
|
||||
"newKnowledge.documentNotFoundTitle": "문서를 찾을 수 없음",
|
||||
"newKnowledge.documentOverviewDescription": "인덱싱된 전체 리비전을 읽고 소스와 인덱스 세부 정보를 확인하세요.",
|
||||
"newKnowledge.documentPermissionRestricted": "워크스페이스 권한으로는 문서 또는 작업 동작을 수행할 수 없습니다.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "재인덱싱이 접수되었지만 작업 상태 반영이 지연되고 있습니다. 다시 제출하기 전에 확인하세요.",
|
||||
"newKnowledge.documentReindexFailed": "재인덱싱에 실패했습니다. 마지막 준비 완료 리비전은 계속 사용할 수 있습니다.",
|
||||
"newKnowledge.documentReindexProgress": "재인덱싱 중 · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "리비전",
|
||||
"newKnowledge.documentRevisionMissingDescription": "이 문서에는 아직 준비 완료된 리비전이 없습니다. 처리가 끝난 후 다시 확인하세요.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "사용 가능한 리비전 없음",
|
||||
"newKnowledge.documentRevisionsLoadError": "리비전 기록을 불러오지 못했습니다. 현재 리비전은 계속 사용할 수 있습니다.",
|
||||
"newKnowledge.documentStatus.disabled": "비활성화",
|
||||
"newKnowledge.documentStatus.failed": "실패",
|
||||
"newKnowledge.documentStatus.processing": "처리 중",
|
||||
"newKnowledge.documentStatus.queued": "대기 중",
|
||||
"newKnowledge.documentStatus.ready": "준비됨",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "더 많은 작업 기록이 있습니다. 이 문서의 상태를 찾으려면 계속 확인하세요.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "배치 크기 제한을 초과했습니다",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "파일 수 제한을 초과했습니다",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "파일이 너무 큽니다",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "지원되지 않거나 잘못된 파일 형식입니다",
|
||||
"newKnowledge.documentUploadExclusion.more": "외 {{count}}개",
|
||||
"newKnowledge.documentUploadExclusion.processing": "처리에 실패했습니다",
|
||||
"newKnowledge.documentUploadExclusion.quota": "워크스페이스 할당량을 초과했습니다",
|
||||
"newKnowledge.documentUploadExclusion.target": "문서 대상이 더 이상 유효하지 않습니다",
|
||||
"newKnowledge.documentUploadFailed": "이 문서를 업로드할 수 없습니다. 다시 시도해 보세요.",
|
||||
"newKnowledge.documentUploadPartial": "문서 {{accepted}}개의 처리를 시작했습니다. {{excluded}}개는 추가하지 못했습니다: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "수락된 문서가 없습니다: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "문서 처리가 시작되었습니다.",
|
||||
"newKnowledge.documents": "문서",
|
||||
"newKnowledge.documentsDescription": "지식 자산 및 해당 개정판 - 각 문서의 출처와 최신 개정판의 색인 여부.",
|
||||
"newKnowledge.documentsDropHint": "또는 PDF, DOCX, Markdown, HTML, XLSX 또는 TXT를 드롭하여 업로드하세요.",
|
||||
"newKnowledge.documentsEmptyDescription": "여기에 파일을 업로드하거나 소스 탭에서 소스를 연결하세요. 문서는 자동으로 구문 분석, 청크화 및 색인화됩니다.",
|
||||
"newKnowledge.documentsEmptyTitle": "아직 문서가 없습니다.",
|
||||
"newKnowledge.documentsErrorDescription": "문서를 로드할 수 없습니다. 다시 시도해 보세요.",
|
||||
"newKnowledge.documentsErrorTitle": "문서를 로드할 수 없습니다.",
|
||||
"newKnowledge.documentsPermissionDescription": "이 기술 자료의 문서를 볼 권한이 없습니다.",
|
||||
"newKnowledge.documentsPermissionTitle": "문서가 제한되어 있습니다.",
|
||||
"newKnowledge.documentsReindexFailed": "색인 다시 생성을 시작할 수 없습니다. 다시 시도해 보세요.",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} 문서가 대기 중입니다. {{missing}}는 더 이상 존재하지 않습니다.",
|
||||
"newKnowledge.documentsReindexStarted": "재인덱싱이 시작되었습니다.",
|
||||
"newKnowledge.documentsSelected": "{{count}} 문서가 선택됨",
|
||||
"newKnowledge.documentsUnavailable": "문서 —개",
|
||||
"newKnowledge.downloadDocuments": "다운로드",
|
||||
"newKnowledge.editSource": "편집",
|
||||
"newKnowledge.emptyChunk": "빈 청크",
|
||||
"newKnowledge.emptyDescription": "문서를 추가하면 에이전트가 직접 읽을 수 있습니다. 인덱싱 설정은 필요하지 않습니다.",
|
||||
"newKnowledge.emptyTitle": "지식 베이스를 구축하는 더 스마트한 방법",
|
||||
"newKnowledge.errorDescription": "새 지식 베이스를 불러오지 못했습니다. 다시 시도하세요.",
|
||||
"newKnowledge.errorTitle": "문제가 발생했습니다",
|
||||
"newKnowledge.fileSize": "파일 크기",
|
||||
"newKnowledge.filtersUnavailable": "KnowledgeFS에서 메타데이터를 제공하면 이 필터를 사용할 수 있습니다.",
|
||||
"newKnowledge.firecrawlUnavailable": "이 작업공간의 공급자 카탈로그에서는 Firecrawl을 사용할 수 없습니다.",
|
||||
"newKnowledge.gotIt": "확인",
|
||||
"newKnowledge.guideDescription": "소스를 연결하면 문서가 계속 동기화되고 자동으로 인덱싱되어 바로 사용할 수 있습니다. 관리할 설정은 없습니다. 기존 지식 베이스도 그대로 작동합니다.",
|
||||
"newKnowledge.guideTitle": "새로운 지식 베이스를 만나보세요 ✨",
|
||||
"newKnowledge.illustrationHeadline": "콘텐츠를 한 번 연결하면 모든 앱이 최신 지식으로 답변합니다.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "인덱스 정보",
|
||||
"newKnowledge.interruptTask": "중단",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid 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": "이전 리비전 불러오기",
|
||||
"newKnowledge.loadingSyncPolicy": "동기화 정책 불러오는 중",
|
||||
"newKnowledge.manualUpload": "수동 업로드",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "메타데이터",
|
||||
"newKnowledge.mimeType": "파일 형식",
|
||||
"newKnowledge.moreProviders": "더 많은 공급자",
|
||||
"newKnowledge.name": "지식 이름",
|
||||
"newKnowledge.namePlaceholder": "예를 들어 고객 지원 핸드북",
|
||||
"newKnowledge.nameRequired": "기술 자료 이름을 입력하세요.",
|
||||
"newKnowledge.new": "신규",
|
||||
"newKnowledge.noBackgroundTasks": "아직 백그라운드 작업이 없습니다.",
|
||||
"newKnowledge.noDescription": "설명 없음",
|
||||
"newKnowledge.noMatchingDocuments": "이 검색과 일치하는 문서가 없습니다.",
|
||||
"newKnowledge.noMatchingSources": "이 검색과 일치하는 소스가 없습니다.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "이 기술 자료가 삭제되었거나 액세스 권한이 없을 수 있습니다.",
|
||||
"newKnowledge.notFoundTitle": "기술 자료를 찾을 수 없습니다.",
|
||||
"newKnowledge.notionNotConnected": "Notion이 연결되지 않음",
|
||||
"newKnowledge.notionNotConnectedDescription": "페이지를 선택하고 동기화 상태를 유지하려면 Notion을 연결하세요.",
|
||||
"newKnowledge.onlineDocuments": "온라인 문서",
|
||||
"newKnowledge.onlineDrive": "온라인 드라이브",
|
||||
"newKnowledge.overview": "개요",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}}개 선택됨",
|
||||
"newKnowledge.partialDocumentResults": "일치하는 문서가 더 있을 수 있습니다. 검색을 계속하려면 더 불러오세요.",
|
||||
"newKnowledge.permission": "허가",
|
||||
"newKnowledge.permissionAllMembers": "작업공간 · 모든 구성원이 보고 편집할 수 있습니다.",
|
||||
"newKnowledge.permissionLoadFailed": "편집 권한을 확인하지 못했습니다. 다시 시도해 주세요.",
|
||||
"newKnowledge.permissionOnlyMe": "나만",
|
||||
"newKnowledge.permissionRestricted": "작업공간 권한으로 인해 이 설정을 변경할 수 없습니다.",
|
||||
"newKnowledge.permissionUpdateFailed": "지식 베이스가 생성되었지만 권한을 확인하지 못했습니다. 다시 시도하여 설정을 완료하세요.",
|
||||
"newKnowledge.preview": "미리보기",
|
||||
"newKnowledge.previewUnavailable": "백엔드에서 이 파일 처리를 완료하면 미리보기를 사용할 수 있습니다.",
|
||||
"newKnowledge.processDocument": "프로세스 {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "중단됨",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "준비 중",
|
||||
"newKnowledge.processingTaskState.failed": "실패",
|
||||
"newKnowledge.processingTaskState.queued": "대기 중",
|
||||
"newKnowledge.processingTaskState.retry_wait": "재시도 대기 중",
|
||||
"newKnowledge.processingTaskState.running": "처리 중 · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "완료됨",
|
||||
"newKnowledge.processingTaskState.superseded": "대체됨",
|
||||
"newKnowledge.providerConnected": "Firecrawl 연결됨",
|
||||
"newKnowledge.providerLoadFailed": "제공자를 로드할 수 없습니다.",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}}이 구성되지 않았습니다.",
|
||||
"newKnowledge.providerNotConfiguredDescription": "{{provider}}을(를) 사용하여 웹사이트를 크롤링하려면 API 키 또는 엔드포인트 URL을 추가하세요.",
|
||||
"newKnowledge.providerUnavailable": "이 제공자를 사용할 수 없습니다.",
|
||||
"newKnowledge.quality": "품질",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "워크스페이스 편집자에게 첫 번째 지식 베이스 생성을 요청하세요.",
|
||||
"newKnowledge.refreshConnectionStatus": "새로 고침 상태",
|
||||
"newKnowledge.refreshingConnection": "상쾌하다…",
|
||||
"newKnowledge.reindexDocument": "재인덱싱",
|
||||
"newKnowledge.reindexDocuments": "재인덱싱",
|
||||
"newKnowledge.removeSource": "소스 제거",
|
||||
"newKnowledge.retrievalCount": "검색 횟수",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "재시도",
|
||||
"newKnowledge.retryReindexDocument": "다시 재인덱싱",
|
||||
"newKnowledge.retrySyncPolicy": "동기화 정책 다시 시도",
|
||||
"newKnowledge.retryTask": "재시도",
|
||||
"newKnowledge.revisionState.active": "활성",
|
||||
"newKnowledge.revisionState.candidate": "처리 중",
|
||||
"newKnowledge.revisionState.failed": "실패",
|
||||
"newKnowledge.revisionState.superseded": "이전",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "이 섹션은 다음 설정 단계에서 사용할 수 있습니다.",
|
||||
"newKnowledge.searchDocuments": "문서 검색",
|
||||
"newKnowledge.searchSources": "소스 검색",
|
||||
"newKnowledge.selectAll": "모두 선택",
|
||||
"newKnowledge.selectAllDocuments": "모든 문서 선택",
|
||||
"newKnowledge.selectAllSources": "모든 소스 선택",
|
||||
"newKnowledge.selectChunk": "내용을 확인할 청크를 선택하세요.",
|
||||
"newKnowledge.selectFilesAndFolders": "동기화할 파일과 폴더 선택",
|
||||
"newKnowledge.skippedFailed": "건너뜀(실패)",
|
||||
"newKnowledge.skippedOffDomain": "건너뜀(도메인 외부)",
|
||||
"newKnowledge.sourceActions": "{{name}}에 대한 작업",
|
||||
"newKnowledge.sourceColumn": "출처",
|
||||
"newKnowledge.sourceFilterLabel": "상태별로 소스 필터링",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "소스 설정은 전체가 표시되며 편집과 미리보기에는 초안 소스 백엔드 워크플로가 필요합니다.",
|
||||
"newKnowledge.sourceStatus.active": "활성",
|
||||
"newKnowledge.sourceStatus.disabled": "비활성화됨",
|
||||
"newKnowledge.sourceStatus.error": "오류",
|
||||
"newKnowledge.sourceStatus.syncing": "동기화 중",
|
||||
"newKnowledge.sourceType.connector": "연결된 앱",
|
||||
"newKnowledge.sourceType.object-storage": "온라인 드라이브",
|
||||
"newKnowledge.sourceType.upload": "업로드",
|
||||
"newKnowledge.sourceType.web": "웹사이트 크롤링",
|
||||
"newKnowledge.sourceTypeLabel": "소스 유형",
|
||||
"newKnowledge.sources": "출처",
|
||||
"newKnowledge.sourcesDescription": "지식의 출처, 정보의 최신성, 마지막 동기화 성공 여부.",
|
||||
"newKnowledge.sourcesEmptyDescription": "웹사이트 연결 — Dify는 웹사이트를 동기화되고 최신 상태로 유지하므로 검색이 중단되지 않습니다.",
|
||||
"newKnowledge.sourcesEmptyTitle": "아직 연결된 소스가 없습니다.",
|
||||
"newKnowledge.sourcesErrorDescription": "소스를 로드할 수 없습니다. 다시 시도해 보세요.",
|
||||
"newKnowledge.sourcesErrorTitle": "소스를 로드할 수 없습니다.",
|
||||
"newKnowledge.startEmpty": "빈 상태로 시작",
|
||||
"newKnowledge.startEmptyDescription": "지금 만들고 콘텐츠는 나중에 추가하세요.",
|
||||
"newKnowledge.startWith": "다음으로 시작",
|
||||
"newKnowledge.startWithHelp": "언제든지 더 많은 소스나 문서를 추가할 수 있습니다.",
|
||||
"newKnowledge.statusColumn": "상태",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "지금 동기화",
|
||||
"newKnowledge.syncPolicy": "동기화 정책",
|
||||
"newKnowledge.syncPolicyColumn": "동기화 정책",
|
||||
"newKnowledge.syncPolicyCustom": "사용자 지정 간격",
|
||||
"newKnowledge.syncPolicyDaily": "24시간마다",
|
||||
"newKnowledge.syncPolicyLoadFailed": "동기화 정책을 불러오지 못했습니다.",
|
||||
"newKnowledge.syncPolicyManual": "수동 전용",
|
||||
"newKnowledge.syncPolicyProvider": "제공업체 일정 사용",
|
||||
"newKnowledge.tags": "태그",
|
||||
"newKnowledge.taskActionFailed": "이 작업을 업데이트할 수 없습니다. 새로고침하고 다시 시도하세요.",
|
||||
"newKnowledge.taskAttentionClear": "주의가 필요한 백그라운드 작업은 없습니다.",
|
||||
"newKnowledge.taskAttentionCount": "{{count}} 백그라운드 작업에 주의가 필요합니다.",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}} 실패한 작업을 포함하여 백그라운드 작업에 주의가 필요합니다.",
|
||||
"newKnowledge.taskFailedNotification": "문서 처리 실패 — 작업에서 다시 시도하세요",
|
||||
"newKnowledge.taskHistoryIncomplete": "더 많은 작업 기록이 있습니다. 주의가 필요한 작업 수는 아직 완전하지 않습니다.",
|
||||
"newKnowledge.tasks": "작업",
|
||||
"newKnowledge.tasksErrorDescription": "백그라운드 작업을 로드할 수 없습니다. 다시 시도해 보세요.",
|
||||
"newKnowledge.tasksWithAttention": "작업, {{count}}에 주의가 필요합니다.",
|
||||
"newKnowledge.tokenCount": "토큰",
|
||||
"newKnowledge.unavailableDescription": "현재 이 워크스페이스에서 KnowledgeFS를 사용할 수 없습니다.",
|
||||
"newKnowledge.unavailableTitle": "새 지식을 사용할 수 없음",
|
||||
"newKnowledge.updated": "{{date}} 업데이트",
|
||||
"newKnowledge.updatedAt": "마지막 업데이트",
|
||||
"newKnowledge.updatedColumn": "업데이트됨",
|
||||
"newKnowledge.uploadCharactersUnavailable": "문자 수: —",
|
||||
"newKnowledge.uploadDocuments": "문서 업로드",
|
||||
"newKnowledge.uploadFiles": "파일 업로드",
|
||||
"newKnowledge.uploadFilesDescription": "컴퓨터에서 파일을 추가하면 생성 즉시 인덱싱됩니다.",
|
||||
"newKnowledge.uploadingFiles": "업로드 중…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "지식 보기",
|
||||
"newKnowledge.websiteCrawl": "웹사이트 크롤링",
|
||||
"noExternalKnowledge": "아직 외부 지식 API 가 없으므로 여기를 클릭하여 생성하십시오.",
|
||||
"parentMode.fullDoc": "전체 문서",
|
||||
"parentMode.paragraph": "단락",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "The Rerank model is required for mixture of high quality and economical knowledge bases.",
|
||||
"mixtureInternalAndExternalTip": "The Rerank model is required for mixture of internal and external knowledge.",
|
||||
"multimodal": "Multimodal",
|
||||
"newKnowledge.actionsColumn": "Acties",
|
||||
"newKnowledge.addDocument": "Document toevoegen",
|
||||
"newKnowledge.addSource": "Bron toevoegen",
|
||||
"newKnowledge.addSourceDescription": "Verbind een bron met deze kennisbank: deze synchroniseert automatisch en blijft actueel.",
|
||||
"newKnowledge.addSourceFailed": "Deze bron kon niet worden toegevoegd. Controleer de instellingen en probeer het opnieuw.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Alle status",
|
||||
"newKnowledge.allSources": "Alle bronnen",
|
||||
"newKnowledge.apiAgentAccess": "API- en agenttoegang",
|
||||
"newKnowledge.appsUnavailable": "— apps",
|
||||
"newKnowledge.authKind.api-key": "API-sleutel",
|
||||
"newKnowledge.authKind.endpoint": "Eindpunt",
|
||||
"newKnowledge.authenticationMethod": "Authenticatiemethode",
|
||||
"newKnowledge.averageChunkLength": "Gemiddelde segmentlengte",
|
||||
"newKnowledge.backToList": "Terug naar kennisbanken",
|
||||
"newKnowledge.backgroundTasks": "Achtergrondtaken",
|
||||
"newKnowledge.backgroundTasksDescription": "Documenten toevoegen, bronsynchronisaties en taken opnieuw indexeren.",
|
||||
"newKnowledge.booleanFalse": "Nee",
|
||||
"newKnowledge.booleanTrue": "Ja",
|
||||
"newKnowledge.bulkDocumentActions": "Bulkdocumentacties",
|
||||
"newKnowledge.cancelAddSource": "Annuleren",
|
||||
"newKnowledge.cardType": "Kennisruimte",
|
||||
"newKnowledge.characterCount": "Tekens",
|
||||
"newKnowledge.checkReindexStatus": "Status van herindexering controleren",
|
||||
"newKnowledge.chunkCount": "Segmenten",
|
||||
"newKnowledge.chunkHeading": "Fragment {{position}}",
|
||||
"newKnowledge.chunkLocation": "Positie {{position}} in deze revisie",
|
||||
"newKnowledge.clearDocumentSelection": "Documentselectie wissen",
|
||||
"newKnowledge.configureProvider": "Firecrawl configureren",
|
||||
"newKnowledge.connectNotion": "Notion verbinden",
|
||||
"newKnowledge.connectProvider": "Firecrawl verbinden",
|
||||
"newKnowledge.connectProviderGeneric": "Provider verbinden",
|
||||
"newKnowledge.connectSource": "Bron verbinden",
|
||||
"newKnowledge.connectSourceDescription": "Inhoud blijft gesynchroniseerd met Notion, websites en andere diensten.",
|
||||
"newKnowledge.connectingProvider": "Verbinden…",
|
||||
"newKnowledge.connectionFailed": "We konden Firecrawl niet verbinden. Voer de inloggegevens opnieuw in en probeer het opnieuw.",
|
||||
"newKnowledge.connectionNeedsAttention": "Firecrawl-verbinding heeft aandacht nodig",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Vernieuw de verbinding voordat u een websitebron toevoegt.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl maakt verbinding",
|
||||
"newKnowledge.connectionRefreshFailed": "We konden deze verbinding niet vernieuwen. Probeer het opnieuw.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Taakstatus blijven controleren",
|
||||
"newKnowledge.crawlAndPreview": "Crawlen en bekijken",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Niet gestart",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Crawlvoorbeeld",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "De configuratie van de websitecrawl is uitgeschakeld totdat de crawlworkflow beschikbaar is.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl is klaar",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "We konden deze kennisbank niet creëren. Controleer uw toegang en probeer het opnieuw.",
|
||||
"newKnowledge.createIllustrationTitle": "Koppel je inhoud één keer — elke app antwoordt met de meest actuele kennis.",
|
||||
"newKnowledge.createTitle": "Creëer kennis",
|
||||
"newKnowledge.createdAt": "Aangemaakt",
|
||||
"newKnowledge.creators": "Makers",
|
||||
"newKnowledge.customIntervalHours": "Aangepast interval (uren)",
|
||||
"newKnowledge.customIntervalInvalid": "Voer een geheel getal van 1 tot 720 uur in.",
|
||||
"newKnowledge.deleteDocuments": "Verwijderen…",
|
||||
"newKnowledge.description": "Beschrijving",
|
||||
"newKnowledge.descriptionHelp": "Een duidelijke beschrijving helpt apps beslissen wanneer ze deze kennis moeten doorzoeken.",
|
||||
"newKnowledge.descriptionPlaceholder": "bijv. Terugbetalingsbeleid, veelgestelde vragen over verzending en escalatiestappen - gebruik deze voor vragen over klantenondersteuning",
|
||||
"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.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.",
|
||||
"newKnowledge.documentChunksLoadError": "De fragmenten voor deze revisie konden niet worden geladen.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Meer fragmenten konden niet worden geladen. Probeer het opnieuw.",
|
||||
"newKnowledge.documentColumn": "Document",
|
||||
"newKnowledge.documentContentIncomplete": "Resterende documentinhoud laden…",
|
||||
"newKnowledge.documentContents": "Inhoud",
|
||||
"newKnowledge.documentFacts": "Documentdetails",
|
||||
"newKnowledge.documentFilterLabel": "Documenten filteren op status",
|
||||
"newKnowledge.documentLoadErrorDescription": "Dit document kon niet worden geladen. Probeer het opnieuw.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Document kon niet worden geladen",
|
||||
"newKnowledge.documentNotFoundDescription": "Dit document is mogelijk verwijderd of je hebt geen toegang.",
|
||||
"newKnowledge.documentNotFoundTitle": "Document niet gevonden",
|
||||
"newKnowledge.documentOverviewDescription": "Lees de volledige geïndexeerde revisie en bekijk de bron- en indexdetails.",
|
||||
"newKnowledge.documentPermissionRestricted": "Uw werkruimtemachtigingen staan geen document- of taakacties toe.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "De herindexering is geaccepteerd, maar de taakstatus is vertraagd. Controleer deze voordat je opnieuw indient.",
|
||||
"newKnowledge.documentReindexFailed": "Opnieuw indexeren is mislukt. De laatste gereedstaande revisie blijft beschikbaar.",
|
||||
"newKnowledge.documentReindexProgress": "Opnieuw indexeren · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Revisie",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Dit document heeft nog geen gereedstaande revisie. Kijk opnieuw nadat de verwerking is voltooid.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Geen revisie beschikbaar",
|
||||
"newKnowledge.documentRevisionsLoadError": "De revisiegeschiedenis kon niet worden geladen. De huidige revisie blijft beschikbaar.",
|
||||
"newKnowledge.documentStatus.disabled": "Uitgeschakeld",
|
||||
"newKnowledge.documentStatus.failed": "Mislukt",
|
||||
"newKnowledge.documentStatus.processing": "Verwerking",
|
||||
"newKnowledge.documentStatus.queued": "In de wachtrij",
|
||||
"newKnowledge.documentStatus.ready": "Klaar",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Er is meer taakgeschiedenis beschikbaar. Blijf controleren om de status van dit document te vinden.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "limiet voor batchgrootte overschreden",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "limiet voor aantal bestanden overschreden",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "bestand is te groot",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "niet-ondersteund of ongeldig bestandstype",
|
||||
"newKnowledge.documentUploadExclusion.more": "en nog {{count}}",
|
||||
"newKnowledge.documentUploadExclusion.processing": "verwerking mislukt",
|
||||
"newKnowledge.documentUploadExclusion.quota": "werkruimtequotum overschreden",
|
||||
"newKnowledge.documentUploadExclusion.target": "documentdoel is niet meer geldig",
|
||||
"newKnowledge.documentUploadFailed": "We konden deze documenten niet uploaden. Probeer het opnieuw.",
|
||||
"newKnowledge.documentUploadPartial": "Verwerking van {{accepted}} documenten gestart; {{excluded}} konden niet worden toegevoegd: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Er zijn geen documenten geaccepteerd: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Documentverwerking gestart.",
|
||||
"newKnowledge.documents": "Documenten",
|
||||
"newKnowledge.documentsDescription": "Kennismiddelen en hun herzieningen – waar elk document vandaan komt en of de laatste herziening is geïndexeerd.",
|
||||
"newKnowledge.documentsDropHint": "of zet PDF, DOCX, Markdown, HTML, XLSX of TXT neer om te uploaden",
|
||||
"newKnowledge.documentsEmptyDescription": "Upload hier bestanden of sluit een bron aan op het tabblad Bronnen: documenten worden automatisch geparseerd, gesegmenteerd en geïndexeerd.",
|
||||
"newKnowledge.documentsEmptyTitle": "Nog geen documenten",
|
||||
"newKnowledge.documentsErrorDescription": "We konden geen documenten laden. Probeer het opnieuw.",
|
||||
"newKnowledge.documentsErrorTitle": "Documenten konden niet worden geladen",
|
||||
"newKnowledge.documentsPermissionDescription": "U heeft geen toestemming om documenten in deze kennisbank te bekijken.",
|
||||
"newKnowledge.documentsPermissionTitle": "Documenten zijn beperkt",
|
||||
"newKnowledge.documentsReindexFailed": "We konden niet beginnen met herindexeren. Probeer het opnieuw.",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}}-documenten in wachtrij; {{missing}} bestaat niet meer.",
|
||||
"newKnowledge.documentsReindexStarted": "Het herindexeren is gestart.",
|
||||
"newKnowledge.documentsSelected": "{{count}}-documenten geselecteerd",
|
||||
"newKnowledge.documentsUnavailable": "— documenten",
|
||||
"newKnowledge.downloadDocuments": "Downloaden",
|
||||
"newKnowledge.editSource": "Bewerken",
|
||||
"newKnowledge.emptyChunk": "Leeg fragment",
|
||||
"newKnowledge.emptyDescription": "Voeg je documenten toe zodat agents ze direct kunnen lezen, zonder indexering te configureren.",
|
||||
"newKnowledge.emptyTitle": "Een slimmere manier om kennisbanken te bouwen",
|
||||
"newKnowledge.errorDescription": "We konden je nieuwe kennisbanken niet laden. Probeer het opnieuw.",
|
||||
"newKnowledge.errorTitle": "Er is iets misgegaan",
|
||||
"newKnowledge.fileSize": "Bestandsgrootte",
|
||||
"newKnowledge.filtersUnavailable": "Dit filter wordt beschikbaar zodra KnowledgeFS metadata aanbiedt.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl is niet beschikbaar in de providercatalogus voor deze werkruimte.",
|
||||
"newKnowledge.gotIt": "Begrepen",
|
||||
"newKnowledge.guideDescription": "Verbind een bron en de documenten blijven gesynchroniseerd, automatisch geïndexeerd en direct klaar voor gebruik, zonder beheer. Je bestaande kennisbanken blijven ongewijzigd werken.",
|
||||
"newKnowledge.guideTitle": "Maak kennis met de nieuwe Kennisbank ✨",
|
||||
"newKnowledge.illustrationHeadline": "Koppel je content één keer — elke app antwoordt met de meest actuele kennis.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Indexinformatie",
|
||||
"newKnowledge.interruptTask": "Onderbreken",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Synchronisatiebeleid laden",
|
||||
"newKnowledge.manualUpload": "Handmatig uploaden",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Metagegevens",
|
||||
"newKnowledge.mimeType": "Bestandstype",
|
||||
"newKnowledge.moreProviders": "Meer providers",
|
||||
"newKnowledge.name": "Kennis naam",
|
||||
"newKnowledge.namePlaceholder": "bijv. Handboek voor klantenondersteuning",
|
||||
"newKnowledge.nameRequired": "Voer een kennisbanknaam in.",
|
||||
"newKnowledge.new": "Nieuw",
|
||||
"newKnowledge.noBackgroundTasks": "Nog geen achtergrondtaken.",
|
||||
"newKnowledge.noDescription": "Geen beschrijving",
|
||||
"newKnowledge.noMatchingDocuments": "Er zijn geen documenten die overeenkomen met deze zoekopdracht.",
|
||||
"newKnowledge.noMatchingSources": "Er zijn geen bronnen die overeenkomen met deze zoekopdracht.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Deze kennisbank is mogelijk verwijderd of u heeft mogelijk geen toegang.",
|
||||
"newKnowledge.notFoundTitle": "Kennisbank niet gevonden",
|
||||
"newKnowledge.notionNotConnected": "Notion is niet verbonden",
|
||||
"newKnowledge.notionNotConnectedDescription": "Verbind Notion om pagina’s te kiezen en gesynchroniseerd te houden.",
|
||||
"newKnowledge.onlineDocuments": "Onlinedocumenten",
|
||||
"newKnowledge.onlineDrive": "Online rijden",
|
||||
"newKnowledge.overview": "Overzicht",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} geselecteerd",
|
||||
"newKnowledge.partialDocumentResults": "Er kunnen meer overeenkomende documenten zijn. Laad meer om verder te zoeken.",
|
||||
"newKnowledge.permission": "Toestemming",
|
||||
"newKnowledge.permissionAllMembers": "Werkruimte · alle leden kunnen bekijken en bewerken",
|
||||
"newKnowledge.permissionLoadFailed": "Uw bewerkingsmachtigingen konden niet worden gecontroleerd. Probeer het opnieuw.",
|
||||
"newKnowledge.permissionOnlyMe": "Alleen ik",
|
||||
"newKnowledge.permissionRestricted": "Uw werkruimtemachtigingen staan het wijzigen van deze instelling niet toe.",
|
||||
"newKnowledge.permissionUpdateFailed": "De kennisbank is gemaakt, maar de machtiging kon niet worden bevestigd. Probeer opnieuw om de configuratie te voltooien.",
|
||||
"newKnowledge.preview": "Voorbeeld",
|
||||
"newKnowledge.previewUnavailable": "Het voorbeeld is beschikbaar nadat de backend dit bestand heeft verwerkt.",
|
||||
"newKnowledge.processDocument": "Proces {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Onderbroken",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Voorbereiden",
|
||||
"newKnowledge.processingTaskState.failed": "Mislukt",
|
||||
"newKnowledge.processingTaskState.queued": "In de wachtrij",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Wachten op nieuwe poging",
|
||||
"newKnowledge.processingTaskState.running": "Verwerking · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Voltooid",
|
||||
"newKnowledge.processingTaskState.superseded": "Vervangen",
|
||||
"newKnowledge.providerConnected": "Firecrawl aangesloten",
|
||||
"newKnowledge.providerLoadFailed": "Providers konden niet laden",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} is niet geconfigureerd",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Voeg een API-sleutel of eindpunt-URL toe om websites te crawlen met {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Deze aanbieder is niet beschikbaar.",
|
||||
"newKnowledge.quality": "Kwaliteit",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Vraag een werkruimte-editor om de eerste kennisbank te maken.",
|
||||
"newKnowledge.refreshConnectionStatus": "Vernieuwingsstatus",
|
||||
"newKnowledge.refreshingConnection": "Verfrissen…",
|
||||
"newKnowledge.reindexDocument": "Opnieuw indexeren",
|
||||
"newKnowledge.reindexDocuments": "Opnieuw indexeren",
|
||||
"newKnowledge.removeSource": "Bron verwijderen",
|
||||
"newKnowledge.retrievalCount": "Aantal zoekacties",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Probeer het opnieuw",
|
||||
"newKnowledge.retryReindexDocument": "Opnieuw herindexeren",
|
||||
"newKnowledge.retrySyncPolicy": "Synchronisatiebeleid opnieuw proberen",
|
||||
"newKnowledge.retryTask": "Opnieuw proberen",
|
||||
"newKnowledge.revisionState.active": "Actief",
|
||||
"newKnowledge.revisionState.candidate": "Wordt verwerkt",
|
||||
"newKnowledge.revisionState.failed": "Mislukt",
|
||||
"newKnowledge.revisionState.superseded": "Vorige",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Deze sectie komt beschikbaar in de volgende installatiestap.",
|
||||
"newKnowledge.searchDocuments": "Documenten zoeken",
|
||||
"newKnowledge.searchSources": "Zoek bronnen",
|
||||
"newKnowledge.selectAll": "Alles selecteren",
|
||||
"newKnowledge.selectAllDocuments": "Selecteer alle documenten",
|
||||
"newKnowledge.selectAllSources": "Alle bronnen selecteren",
|
||||
"newKnowledge.selectChunk": "Selecteer een fragment om de inhoud te bekijken.",
|
||||
"newKnowledge.selectFilesAndFolders": "Selecteer bestanden en mappen om te synchroniseren",
|
||||
"newKnowledge.skippedFailed": "Overgeslagen (mislukt)",
|
||||
"newKnowledge.skippedOffDomain": "Overgeslagen (buiten domein)",
|
||||
"newKnowledge.sourceActions": "Acties voor {{name}}",
|
||||
"newKnowledge.sourceColumn": "Bron",
|
||||
"newKnowledge.sourceFilterLabel": "Filter bronnen op status",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "De bronconfiguratie wordt volledig getoond; bewerken en bekijken vereisen de backendworkflow voor conceptbronnen.",
|
||||
"newKnowledge.sourceStatus.active": "Actief",
|
||||
"newKnowledge.sourceStatus.disabled": "Uitgeschakeld",
|
||||
"newKnowledge.sourceStatus.error": "Fout",
|
||||
"newKnowledge.sourceStatus.syncing": "Synchroniseren",
|
||||
"newKnowledge.sourceType.connector": "Verbonden app",
|
||||
"newKnowledge.sourceType.object-storage": "Online rijden",
|
||||
"newKnowledge.sourceType.upload": "Uploaden",
|
||||
"newKnowledge.sourceType.web": "Websitecrawl",
|
||||
"newKnowledge.sourceTypeLabel": "Brontype",
|
||||
"newKnowledge.sources": "Bronnen",
|
||||
"newKnowledge.sourcesDescription": "Waar kennis vandaan komt, hoe vers deze is en of de laatste synchronisatie is gelukt.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Verbind een website — Dify houdt deze gesynchroniseerd en actueel, zodat het ophalen nooit wordt afgebroken.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Nog geen bronnen aangesloten",
|
||||
"newKnowledge.sourcesErrorDescription": "We konden de bronnen niet laden. Probeer het opnieuw.",
|
||||
"newKnowledge.sourcesErrorTitle": "Bronnen konden niet laden",
|
||||
"newKnowledge.startEmpty": "Leeg beginnen",
|
||||
"newKnowledge.startEmptyDescription": "Maak de kennisbank nu en voeg later inhoud toe.",
|
||||
"newKnowledge.startWith": "Begin met",
|
||||
"newKnowledge.startWithHelp": "U kunt op elk gewenst moment meer bronnen of documenten toevoegen.",
|
||||
"newKnowledge.statusColumn": "Status",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Synchroniseer nu",
|
||||
"newKnowledge.syncPolicy": "Synchronisatiebeleid",
|
||||
"newKnowledge.syncPolicyColumn": "Synchronisatiebeleid",
|
||||
"newKnowledge.syncPolicyCustom": "Aangepast interval",
|
||||
"newKnowledge.syncPolicyDaily": "Elke 24 uur",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Het synchronisatiebeleid kon niet worden geladen.",
|
||||
"newKnowledge.syncPolicyManual": "Alleen handmatig",
|
||||
"newKnowledge.syncPolicyProvider": "Planning van provider gebruiken",
|
||||
"newKnowledge.tags": "Tags",
|
||||
"newKnowledge.taskActionFailed": "We kunnen deze taak niet updaten. Vernieuw en probeer het opnieuw.",
|
||||
"newKnowledge.taskAttentionClear": "Er zijn geen achtergrondtaken die aandacht behoeven.",
|
||||
"newKnowledge.taskAttentionCount": "{{count}}-achtergrondtaken vereisen aandacht.",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}}-achtergrondtaken hebben aandacht nodig, inclusief mislukte taken.",
|
||||
"newKnowledge.taskFailedNotification": "Documentverwerking mislukt — probeer opnieuw via Taken",
|
||||
"newKnowledge.taskHistoryIncomplete": "Er is meer taakgeschiedenis beschikbaar; het aantal taken dat aandacht vereist is onvolledig.",
|
||||
"newKnowledge.tasks": "Taken",
|
||||
"newKnowledge.tasksErrorDescription": "We konden achtergrondtaken niet laden. Probeer het opnieuw.",
|
||||
"newKnowledge.tasksWithAttention": "Taken, {{count}} hebben aandacht nodig",
|
||||
"newKnowledge.tokenCount": "Tokeneenheden",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS is momenteel niet beschikbaar voor deze werkruimte.",
|
||||
"newKnowledge.unavailableTitle": "Nieuwe kennis is niet beschikbaar",
|
||||
"newKnowledge.updated": "Bijgewerkt op {{date}}",
|
||||
"newKnowledge.updatedAt": "Laatst bijgewerkt",
|
||||
"newKnowledge.updatedColumn": "Bijgewerkt",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Tekens: —",
|
||||
"newKnowledge.uploadDocuments": "Documenten uploaden",
|
||||
"newKnowledge.uploadFiles": "Bestanden uploaden",
|
||||
"newKnowledge.uploadFilesDescription": "Voeg bestanden van je computer toe; ze worden geïndexeerd zodra je ze aanmaakt.",
|
||||
"newKnowledge.uploadingFiles": "Uploaden…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Kennisweergave",
|
||||
"newKnowledge.websiteCrawl": "Websitecrawl",
|
||||
"noExternalKnowledge": "There is no External Knowledge API yet, click here to create",
|
||||
"parentMode.fullDoc": "Full-doc",
|
||||
"parentMode.paragraph": "Paragraph",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Model ponownego rankingu jest wymagany dla mieszanki wysokiej jakości i ekonomicznych baz wiedzy.",
|
||||
"mixtureInternalAndExternalTip": "Model Rerank jest wymagany do połączenia wiedzy wewnętrznej i zewnętrznej.",
|
||||
"multimodal": "Multimodalny",
|
||||
"newKnowledge.actionsColumn": "Działania",
|
||||
"newKnowledge.addDocument": "Dodaj dokument",
|
||||
"newKnowledge.addSource": "Dodaj źródło",
|
||||
"newKnowledge.addSourceDescription": "Podłącz źródło do tej bazy wiedzy — synchronizuje się ona automatycznie i pozostaje aktualna.",
|
||||
"newKnowledge.addSourceFailed": "Nie udało się dodać tego źródła. Sprawdź ustawienia i spróbuj ponownie.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Wszystkie stany",
|
||||
"newKnowledge.allSources": "Wszystkie źródła",
|
||||
"newKnowledge.apiAgentAccess": "Dostęp do API i agentów",
|
||||
"newKnowledge.appsUnavailable": "— aplikacji",
|
||||
"newKnowledge.authKind.api-key": "Klucz API",
|
||||
"newKnowledge.authKind.endpoint": "Punkt końcowy",
|
||||
"newKnowledge.authenticationMethod": "Metoda uwierzytelniania",
|
||||
"newKnowledge.averageChunkLength": "Średnia długość fragmentu",
|
||||
"newKnowledge.backToList": "Powrót do baz wiedzy",
|
||||
"newKnowledge.backgroundTasks": "Zadania w tle",
|
||||
"newKnowledge.backgroundTasksDescription": "Dodawanie dokumentów, synchronizacja źródeł i ponowne indeksowanie zadań.",
|
||||
"newKnowledge.booleanFalse": "Nie",
|
||||
"newKnowledge.booleanTrue": "Tak",
|
||||
"newKnowledge.bulkDocumentActions": "Zbiorcze akcje dokumentów",
|
||||
"newKnowledge.cancelAddSource": "Anuluj",
|
||||
"newKnowledge.cardType": "Przestrzeń wiedzy",
|
||||
"newKnowledge.characterCount": "Znaki",
|
||||
"newKnowledge.checkReindexStatus": "Sprawdź stan ponownego indeksowania",
|
||||
"newKnowledge.chunkCount": "Fragmenty",
|
||||
"newKnowledge.chunkHeading": "Fragment {{position}}",
|
||||
"newKnowledge.chunkLocation": "Pozycja {{position}} w tej wersji",
|
||||
"newKnowledge.clearDocumentSelection": "Wyczyść wybór dokumentu",
|
||||
"newKnowledge.configureProvider": "Skonfiguruj Firecrawl",
|
||||
"newKnowledge.connectNotion": "Połącz z Notion",
|
||||
"newKnowledge.connectProvider": "Połącz Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Połącz dostawcę",
|
||||
"newKnowledge.connectSource": "Połącz źródło",
|
||||
"newKnowledge.connectSourceDescription": "Treści pozostają zsynchronizowane z Notion, witrynami internetowymi i innymi usługami.",
|
||||
"newKnowledge.connectingProvider": "Łączenie…",
|
||||
"newKnowledge.connectionFailed": "Nie mogliśmy połączyć Firecrawl. Wprowadź ponownie dane uwierzytelniające i spróbuj jeszcze raz.",
|
||||
"newKnowledge.connectionNeedsAttention": "Połączenie Firecrawl wymaga uwagi",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Odśwież połączenie przed dodaniem źródła strony internetowej.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl łączy się",
|
||||
"newKnowledge.connectionRefreshFailed": "Nie mogliśmy odświeżyć tego połączenia. Spróbuj ponownie.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Kontynuuj sprawdzanie stanu zadania",
|
||||
"newKnowledge.crawlAndPreview": "Indeksowanie i podgląd",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Nie rozpoczęto",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Podgląd indeksowania",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Konfiguracja indeksowania witryny jest wyłączona do czasu udostępnienia przepływu pracy indeksowania.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl jest gotowy",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Nie mogliśmy utworzyć tej bazy wiedzy. Sprawdź swój dostęp i spróbuj ponownie.",
|
||||
"newKnowledge.createIllustrationTitle": "Połącz treści raz — każda aplikacja odpowie, korzystając z najnowszej wiedzy.",
|
||||
"newKnowledge.createTitle": "Twórz wiedzę",
|
||||
"newKnowledge.createdAt": "Utworzono",
|
||||
"newKnowledge.creators": "Twórcy",
|
||||
"newKnowledge.customIntervalHours": "Niestandardowy interwał (godziny)",
|
||||
"newKnowledge.customIntervalInvalid": "Wprowadź liczbę całkowitą od 1 do 720 godzin.",
|
||||
"newKnowledge.deleteDocuments": "Usuń…",
|
||||
"newKnowledge.description": "Opis",
|
||||
"newKnowledge.descriptionHelp": "Przejrzysty opis pomaga aplikacjom zdecydować, kiedy przeszukiwać tę wiedzę.",
|
||||
"newKnowledge.descriptionPlaceholder": "np. Polityka zwrotów, często zadawane pytania dotyczące wysyłki i etapy eskalacji — używaj w przypadku pytań do obsługi klienta",
|
||||
"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.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.",
|
||||
"newKnowledge.documentChunksLoadError": "Nie udało się wczytać fragmentów tej wersji.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Nie udało się wczytać kolejnych fragmentów. Spróbuj ponownie.",
|
||||
"newKnowledge.documentColumn": "Dokument",
|
||||
"newKnowledge.documentContentIncomplete": "Wczytywanie pozostałej treści dokumentu…",
|
||||
"newKnowledge.documentContents": "Zawartość",
|
||||
"newKnowledge.documentFacts": "Szczegóły dokumentu",
|
||||
"newKnowledge.documentFilterLabel": "Filtruj dokumenty według statusu",
|
||||
"newKnowledge.documentLoadErrorDescription": "Nie udało się wczytać tego dokumentu. Spróbuj ponownie.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Nie udało się wczytać dokumentu",
|
||||
"newKnowledge.documentNotFoundDescription": "Dokument mógł zostać usunięty lub nie masz do niego dostępu.",
|
||||
"newKnowledge.documentNotFoundTitle": "Nie znaleziono dokumentu",
|
||||
"newKnowledge.documentOverviewDescription": "Przeczytaj pełną zindeksowaną wersję i sprawdź szczegóły jej źródła oraz indeksu.",
|
||||
"newKnowledge.documentPermissionRestricted": "Uprawnienia Twojego obszaru roboczego nie pozwalają na działania na dokumentach ani zadaniach.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "Ponowne indeksowanie zostało przyjęte, ale stan zadania jest opóźniony. Sprawdź go przed ponownym wysłaniem.",
|
||||
"newKnowledge.documentReindexFailed": "Ponowne indeksowanie nie powiodło się. Ostatnia gotowa wersja pozostaje dostępna.",
|
||||
"newKnowledge.documentReindexProgress": "Ponowne indeksowanie · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Wersja",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Ten dokument nie ma jeszcze gotowej wersji. Sprawdź ponownie po zakończeniu przetwarzania.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Brak dostępnej wersji",
|
||||
"newKnowledge.documentRevisionsLoadError": "Nie udało się wczytać historii wersji. Bieżąca wersja pozostaje dostępna.",
|
||||
"newKnowledge.documentStatus.disabled": "Wyłączony",
|
||||
"newKnowledge.documentStatus.failed": "Nie udało się",
|
||||
"newKnowledge.documentStatus.processing": "Przetwarzanie",
|
||||
"newKnowledge.documentStatus.queued": "W kolejce",
|
||||
"newKnowledge.documentStatus.ready": "Gotowy",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Dostępna jest dalsza historia zadań. Kontynuuj sprawdzanie, aby znaleźć stan tego dokumentu.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "przekroczono limit rozmiaru partii",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "przekroczono limit liczby plików",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "plik jest zbyt duży",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "nieobsługiwany lub nieprawidłowy typ pliku",
|
||||
"newKnowledge.documentUploadExclusion.more": "i jeszcze {{count}}",
|
||||
"newKnowledge.documentUploadExclusion.processing": "przetwarzanie nie powiodło się",
|
||||
"newKnowledge.documentUploadExclusion.quota": "przekroczono limit obszaru roboczego",
|
||||
"newKnowledge.documentUploadExclusion.target": "miejsce docelowe dokumentu jest już nieprawidłowe",
|
||||
"newKnowledge.documentUploadFailed": "Nie mogliśmy przesłać tych dokumentów. Spróbuj ponownie.",
|
||||
"newKnowledge.documentUploadPartial": "Rozpoczęto przetwarzanie {{accepted}} dokumentów; nie udało się dodać {{excluded}}: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Nie zaakceptowano żadnych dokumentów: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Rozpoczęto przetwarzanie dokumentu.",
|
||||
"newKnowledge.documents": "Dokumenty",
|
||||
"newKnowledge.documentsDescription": "Zasoby wiedzy i ich wersje — skąd pochodzi każdy dokument i czy najnowsza wersja jest indeksowana.",
|
||||
"newKnowledge.documentsDropHint": "lub upuść pliki PDF, DOCX, Markdown, HTML, XLSX lub TXT, aby je przesłać",
|
||||
"newKnowledge.documentsEmptyDescription": "Prześlij pliki tutaj lub podłącz źródło na karcie Źródła — dokumenty są automatycznie analizowane, dzielone na porcje i indeksowane.",
|
||||
"newKnowledge.documentsEmptyTitle": "Nie ma jeszcze żadnych dokumentów",
|
||||
"newKnowledge.documentsErrorDescription": "Nie mogliśmy załadować dokumentów. Spróbuj ponownie.",
|
||||
"newKnowledge.documentsErrorTitle": "Nie udało się wczytać dokumentów",
|
||||
"newKnowledge.documentsPermissionDescription": "Nie masz uprawnień do przeglądania dokumentów w tej bazie wiedzy.",
|
||||
"newKnowledge.documentsPermissionTitle": "Dokumenty są zastrzeżone",
|
||||
"newKnowledge.documentsReindexFailed": "Nie można rozpocząć ponownego indeksowania. Spróbuj ponownie.",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} dokumenty w kolejce; {{missing}} już nie istnieje.",
|
||||
"newKnowledge.documentsReindexStarted": "Rozpoczęło się ponowne indeksowanie.",
|
||||
"newKnowledge.documentsSelected": "Wybrano dokumenty {{count}}",
|
||||
"newKnowledge.documentsUnavailable": "— dokumentów",
|
||||
"newKnowledge.downloadDocuments": "Pobierz",
|
||||
"newKnowledge.editSource": "Edytuj",
|
||||
"newKnowledge.emptyChunk": "Pusty fragment",
|
||||
"newKnowledge.emptyDescription": "Dodaj dokumenty, aby agenci mogli je czytać bezpośrednio — bez konfigurowania indeksowania.",
|
||||
"newKnowledge.emptyTitle": "Inteligentniejszy sposób tworzenia baz wiedzy",
|
||||
"newKnowledge.errorDescription": "Nie udało się wczytać nowych baz wiedzy. Spróbuj ponownie.",
|
||||
"newKnowledge.errorTitle": "Coś poszło nie tak",
|
||||
"newKnowledge.fileSize": "Rozmiar pliku",
|
||||
"newKnowledge.filtersUnavailable": "Filtr będzie dostępny, gdy KnowledgeFS udostępni metadane.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl nie jest dostępny w katalogu dostawców dla tego obszaru roboczego.",
|
||||
"newKnowledge.gotIt": "Rozumiem",
|
||||
"newKnowledge.guideDescription": "Połącz źródło, a jego dokumenty pozostaną zsynchronizowane, automatycznie zindeksowane i gotowe do użycia, bez konieczności zarządzania konfiguracją. Dotychczasowe bazy wiedzy będą nadal działać bez zmian.",
|
||||
"newKnowledge.guideTitle": "Poznaj nową bazę wiedzy ✨",
|
||||
"newKnowledge.illustrationHeadline": "Połącz treści raz — każda aplikacja odpowie, korzystając z najnowszej wiedzy.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Informacje o indeksie",
|
||||
"newKnowledge.interruptTask": "Przerwij",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Ładowanie zasad synchronizacji",
|
||||
"newKnowledge.manualUpload": "Przesyłanie ręczne",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Metadane",
|
||||
"newKnowledge.mimeType": "Typ pliku",
|
||||
"newKnowledge.moreProviders": "Więcej dostawców",
|
||||
"newKnowledge.name": "Nazwa wiedzy",
|
||||
"newKnowledge.namePlaceholder": "np. Podręcznik obsługi klienta",
|
||||
"newKnowledge.nameRequired": "Wprowadź nazwę bazy wiedzy.",
|
||||
"newKnowledge.new": "Nowa wersja",
|
||||
"newKnowledge.noBackgroundTasks": "Nie ma jeszcze żadnych zadań w tle.",
|
||||
"newKnowledge.noDescription": "Brak opisu",
|
||||
"newKnowledge.noMatchingDocuments": "Żadne dokumenty nie pasują do tego wyszukiwania.",
|
||||
"newKnowledge.noMatchingSources": "Żadne źródła nie pasują do tego wyszukiwania.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Ta baza wiedzy mogła zostać usunięta lub możesz nie mieć do niej dostępu.",
|
||||
"newKnowledge.notFoundTitle": "Nie znaleziono bazy wiedzy",
|
||||
"newKnowledge.notionNotConnected": "Notion nie jest połączony",
|
||||
"newKnowledge.notionNotConnectedDescription": "Połącz z Notion, aby wybrać strony i utrzymywać ich synchronizację.",
|
||||
"newKnowledge.onlineDocuments": "Dokumenty online",
|
||||
"newKnowledge.onlineDrive": "Dysk online",
|
||||
"newKnowledge.overview": "Przegląd",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "Wybrano: {{count}}",
|
||||
"newKnowledge.partialDocumentResults": "Więcej dokumentów może pasować. Wczytaj więcej, aby kontynuować wyszukiwanie.",
|
||||
"newKnowledge.permission": "Pozwolenie",
|
||||
"newKnowledge.permissionAllMembers": "Obszar roboczy · wszyscy członkowie mogą przeglądać i edytować",
|
||||
"newKnowledge.permissionLoadFailed": "Nie udało się zweryfikować uprawnień do edycji. Spróbuj ponownie.",
|
||||
"newKnowledge.permissionOnlyMe": "Tylko ja",
|
||||
"newKnowledge.permissionRestricted": "Uprawnienia Twojego obszaru roboczego nie pozwalają na zmianę tego ustawienia.",
|
||||
"newKnowledge.permissionUpdateFailed": "Baza wiedzy została utworzona, ale nie udało się potwierdzić jej uprawnień. Spróbuj ponownie, aby ukończyć konfigurację.",
|
||||
"newKnowledge.preview": "Podgląd",
|
||||
"newKnowledge.previewUnavailable": "Podgląd będzie dostępny po zakończeniu przetwarzania tego pliku przez backend.",
|
||||
"newKnowledge.processDocument": "Proces {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Przerwano",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Przygotowanie",
|
||||
"newKnowledge.processingTaskState.failed": "Nie udało się",
|
||||
"newKnowledge.processingTaskState.queued": "W kolejce",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Oczekiwanie na ponowną próbę",
|
||||
"newKnowledge.processingTaskState.running": "Przetwarzanie · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Ukończono",
|
||||
"newKnowledge.processingTaskState.superseded": "Zastąpione",
|
||||
"newKnowledge.providerConnected": "Firecrawl podłączony",
|
||||
"newKnowledge.providerLoadFailed": "Dostawcy nie mogli załadować",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} nie jest skonfigurowany",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Dodaj klucz API lub adres URL punktu końcowego, aby indeksować strony internetowe za pomocą {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Ten dostawca jest niedostępny.",
|
||||
"newKnowledge.quality": "Jakość",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Poproś edytora obszaru roboczego o utworzenie pierwszej bazy wiedzy.",
|
||||
"newKnowledge.refreshConnectionStatus": "Odśwież status",
|
||||
"newKnowledge.refreshingConnection": "Odświeżanie…",
|
||||
"newKnowledge.reindexDocument": "Indeksuj ponownie",
|
||||
"newKnowledge.reindexDocuments": "Ponowne indeksowanie",
|
||||
"newKnowledge.removeSource": "Usuń źródło",
|
||||
"newKnowledge.retrievalCount": "Liczba pobrań",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Spróbuj ponownie",
|
||||
"newKnowledge.retryReindexDocument": "Indeksuj ponownie",
|
||||
"newKnowledge.retrySyncPolicy": "Ponów ładowanie zasad synchronizacji",
|
||||
"newKnowledge.retryTask": "Spróbuj ponownie",
|
||||
"newKnowledge.revisionState.active": "Aktywna",
|
||||
"newKnowledge.revisionState.candidate": "Przetwarzanie",
|
||||
"newKnowledge.revisionState.failed": "Niepowodzenie",
|
||||
"newKnowledge.revisionState.superseded": "Poprzednia",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Ta sekcja będzie dostępna w następnym kroku konfiguracji.",
|
||||
"newKnowledge.searchDocuments": "Szukaj dokumentów",
|
||||
"newKnowledge.searchSources": "Wyszukaj źródła",
|
||||
"newKnowledge.selectAll": "Zaznacz wszystko",
|
||||
"newKnowledge.selectAllDocuments": "Wybierz wszystkie dokumenty",
|
||||
"newKnowledge.selectAllSources": "Wybierz wszystkie źródła",
|
||||
"newKnowledge.selectChunk": "Wybierz fragment, aby sprawdzić jego zawartość.",
|
||||
"newKnowledge.selectFilesAndFolders": "Wybierz pliki i foldery do synchronizacji",
|
||||
"newKnowledge.skippedFailed": "Pominięto (błąd)",
|
||||
"newKnowledge.skippedOffDomain": "Pominięto (poza domeną)",
|
||||
"newKnowledge.sourceActions": "Działania dla {{name}}",
|
||||
"newKnowledge.sourceColumn": "Źródło",
|
||||
"newKnowledge.sourceFilterLabel": "Filtruj źródła według stanu",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "Konfiguracja źródła jest pokazana w całości; edycja i podgląd wymagają przepływu backendowego źródła roboczego.",
|
||||
"newKnowledge.sourceStatus.active": "Aktywny",
|
||||
"newKnowledge.sourceStatus.disabled": "Wyłączone",
|
||||
"newKnowledge.sourceStatus.error": "Błąd",
|
||||
"newKnowledge.sourceStatus.syncing": "Synchronizacja",
|
||||
"newKnowledge.sourceType.connector": "Połączona aplikacja",
|
||||
"newKnowledge.sourceType.object-storage": "Dysk online",
|
||||
"newKnowledge.sourceType.upload": "Prześlij",
|
||||
"newKnowledge.sourceType.web": "Indeksowanie witryny",
|
||||
"newKnowledge.sourceTypeLabel": "Typ źródła",
|
||||
"newKnowledge.sources": "Źródła",
|
||||
"newKnowledge.sourcesDescription": "Skąd pochodzi wiedza, jak bardzo jest świeża i czy ostatnia synchronizacja się powiodła.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Połącz witrynę internetową — Dify zapewnia jej synchronizację i aktualność, dzięki czemu pobieranie nigdy się nie przerywa.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Żadne źródła nie są jeszcze podłączone",
|
||||
"newKnowledge.sourcesErrorDescription": "Nie mogliśmy załadować źródeł. Spróbuj ponownie.",
|
||||
"newKnowledge.sourcesErrorTitle": "Nie udało się załadować źródeł",
|
||||
"newKnowledge.startEmpty": "Zacznij od pustej",
|
||||
"newKnowledge.startEmptyDescription": "Utwórz ją teraz, a zawartość dodaj później.",
|
||||
"newKnowledge.startWith": "Zacznij od",
|
||||
"newKnowledge.startWithHelp": "W dowolnym momencie możesz dodać więcej źródeł lub dokumentów.",
|
||||
"newKnowledge.statusColumn": "Stan",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Synchronizuj teraz",
|
||||
"newKnowledge.syncPolicy": "Zasady synchronizacji",
|
||||
"newKnowledge.syncPolicyColumn": "Polityka synchronizacji",
|
||||
"newKnowledge.syncPolicyCustom": "Niestandardowy interwał",
|
||||
"newKnowledge.syncPolicyDaily": "Co 24 godziny",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Nie udało się załadować zasad synchronizacji.",
|
||||
"newKnowledge.syncPolicyManual": "Tylko ręcznie",
|
||||
"newKnowledge.syncPolicyProvider": "Użyj harmonogramu dostawcy",
|
||||
"newKnowledge.tags": "Tagi",
|
||||
"newKnowledge.taskActionFailed": "Nie mogliśmy zaktualizować tego zadania. Odśwież i spróbuj ponownie.",
|
||||
"newKnowledge.taskAttentionClear": "Żadne zadania w tle nie wymagają uwagi.",
|
||||
"newKnowledge.taskAttentionCount": "Zadania w tle {{count}} wymagają uwagi.",
|
||||
"newKnowledge.taskAttentionErrorCount": "Zadania w tle {{count}} wymagają uwagi, w tym zadania zakończone niepowodzeniem.",
|
||||
"newKnowledge.taskFailedNotification": "Przetwarzanie dokumentu nie powiodło się — spróbuj ponownie w Zadaniach",
|
||||
"newKnowledge.taskHistoryIncomplete": "Dostępna jest dalsza historia zadań; liczba zadań wymagających uwagi jest niepełna.",
|
||||
"newKnowledge.tasks": "Zadania",
|
||||
"newKnowledge.tasksErrorDescription": "Nie udało się wczytać zadań w tle. Spróbuj ponownie.",
|
||||
"newKnowledge.tasksWithAttention": "Zadania, {{count}} wymagają uwagi",
|
||||
"newKnowledge.tokenCount": "Tokeny",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS nie jest obecnie dostępny dla tego obszaru roboczego.",
|
||||
"newKnowledge.unavailableTitle": "Nowa wiedza jest niedostępna",
|
||||
"newKnowledge.updated": "Zaktualizowano {{date}}",
|
||||
"newKnowledge.updatedAt": "Ostatnia aktualizacja",
|
||||
"newKnowledge.updatedColumn": "Zaktualizowano",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Znaki: —",
|
||||
"newKnowledge.uploadDocuments": "Prześlij dokumenty",
|
||||
"newKnowledge.uploadFiles": "Prześlij pliki",
|
||||
"newKnowledge.uploadFilesDescription": "Dodaj pliki z komputera — zostaną zindeksowane od razu po utworzeniu.",
|
||||
"newKnowledge.uploadingFiles": "Przesyłanie…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Widok wiedzy",
|
||||
"newKnowledge.websiteCrawl": "Indeksowanie witryny",
|
||||
"noExternalKnowledge": "Nie ma jeszcze interfejsu API wiedzy zewnętrznej, kliknij tutaj, aby utworzyć",
|
||||
"parentMode.fullDoc": "Pełna wersja dokumentu",
|
||||
"parentMode.paragraph": "Akapit",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "O modelo de reclassificação é necessário para a mistura de bases de conhecimento de alta qualidade e econômicas.",
|
||||
"mixtureInternalAndExternalTip": "O modelo de Reclassificação é necessário para a mistura de conhecimento interno e externo.",
|
||||
"multimodal": "Multimodal",
|
||||
"newKnowledge.actionsColumn": "Ações",
|
||||
"newKnowledge.addDocument": "Adicionar documento",
|
||||
"newKnowledge.addSource": "Adicionar fonte",
|
||||
"newKnowledge.addSourceDescription": "Conecte uma fonte a esta base de conhecimento — ela sincroniza automaticamente e permanece atualizada.",
|
||||
"newKnowledge.addSourceFailed": "Não foi possível adicionar esta fonte. Revise as configurações e tente novamente.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Todos os status",
|
||||
"newKnowledge.allSources": "Todas as fontes",
|
||||
"newKnowledge.apiAgentAccess": "Acesso à API e aos agentes",
|
||||
"newKnowledge.appsUnavailable": "— apps",
|
||||
"newKnowledge.authKind.api-key": "Chave de API",
|
||||
"newKnowledge.authKind.endpoint": "Ponto final",
|
||||
"newKnowledge.authenticationMethod": "Método de autenticação",
|
||||
"newKnowledge.averageChunkLength": "Comprimento médio do segmento",
|
||||
"newKnowledge.backToList": "Voltar às bases de conhecimento",
|
||||
"newKnowledge.backgroundTasks": "Tarefas em segundo plano",
|
||||
"newKnowledge.backgroundTasksDescription": "Adições de documentos, sincronizações de origem e reindexação de trabalhos.",
|
||||
"newKnowledge.booleanFalse": "Não",
|
||||
"newKnowledge.booleanTrue": "Sim",
|
||||
"newKnowledge.bulkDocumentActions": "Ações de documentos em massa",
|
||||
"newKnowledge.cancelAddSource": "Cancelar",
|
||||
"newKnowledge.cardType": "Espaço de conhecimento",
|
||||
"newKnowledge.characterCount": "Caracteres",
|
||||
"newKnowledge.checkReindexStatus": "Verificar status da reindexação",
|
||||
"newKnowledge.chunkCount": "Segmentos",
|
||||
"newKnowledge.chunkHeading": "Trecho {{position}}",
|
||||
"newKnowledge.chunkLocation": "Posição {{position}} nesta revisão",
|
||||
"newKnowledge.clearDocumentSelection": "Limpar seleção de documentos",
|
||||
"newKnowledge.configureProvider": "Configurar Firecrawl",
|
||||
"newKnowledge.connectNotion": "Conectar o Notion",
|
||||
"newKnowledge.connectProvider": "Conectar Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Conectar provedor",
|
||||
"newKnowledge.connectSource": "Conectar uma fonte",
|
||||
"newKnowledge.connectSourceDescription": "O conteúdo permanece sincronizado com Notion, sites e outros serviços.",
|
||||
"newKnowledge.connectingProvider": "Conectando…",
|
||||
"newKnowledge.connectionFailed": "Não foi possível conectar o Firecrawl. Insira novamente a credencial e tente novamente.",
|
||||
"newKnowledge.connectionNeedsAttention": "Conexão Firecrawl precisa de atenção",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Atualize a conexão antes de adicionar uma fonte de site.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl está se conectando",
|
||||
"newKnowledge.connectionRefreshFailed": "Não foi possível atualizar esta conexão. Tente novamente.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Continuar verificando o status",
|
||||
"newKnowledge.crawlAndPreview": "Rastreamento e visualização",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Não iniciado",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Prévia do rastreamento",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "A configuração de rastreamento de sites será desativada até que o fluxo de trabalho de rastreamento esteja disponível.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "O Firecrawl está pronto",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Não foi possível criar esta base de conhecimento. Verifique seu acesso e tente novamente.",
|
||||
"newKnowledge.createIllustrationTitle": "Conecte seu conteúdo uma vez — cada aplicativo responde com o conhecimento mais atualizado.",
|
||||
"newKnowledge.createTitle": "Crie Conhecimento",
|
||||
"newKnowledge.createdAt": "Criado",
|
||||
"newKnowledge.creators": "Criadores",
|
||||
"newKnowledge.customIntervalHours": "Intervalo personalizado (horas)",
|
||||
"newKnowledge.customIntervalInvalid": "Digite um número inteiro de 1 a 720 horas.",
|
||||
"newKnowledge.deleteDocuments": "Excluir…",
|
||||
"newKnowledge.description": "Descrição",
|
||||
"newKnowledge.descriptionHelp": "Uma descrição clara ajuda os aplicativos a decidir quando pesquisar esse conhecimento.",
|
||||
"newKnowledge.descriptionPlaceholder": "por exemplo Política de reembolso, perguntas frequentes sobre envio e etapas de escalonamento — use para perguntas de suporte ao cliente",
|
||||
"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.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.",
|
||||
"newKnowledge.documentChunksLoadError": "Não foi possível carregar os trechos desta revisão.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Não foi possível carregar mais trechos. Tente novamente.",
|
||||
"newKnowledge.documentColumn": "Documento",
|
||||
"newKnowledge.documentContentIncomplete": "Carregando o restante do conteúdo do documento…",
|
||||
"newKnowledge.documentContents": "Conteúdo",
|
||||
"newKnowledge.documentFacts": "Detalhes do documento",
|
||||
"newKnowledge.documentFilterLabel": "Filtrar documentos por status",
|
||||
"newKnowledge.documentLoadErrorDescription": "Não foi possível carregar este documento. Tente novamente.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Não foi possível carregar o documento",
|
||||
"newKnowledge.documentNotFoundDescription": "Este documento pode ter sido removido ou você pode não ter acesso.",
|
||||
"newKnowledge.documentNotFoundTitle": "Documento não encontrado",
|
||||
"newKnowledge.documentOverviewDescription": "Leia a revisão indexada completa e confira os detalhes da origem e do índice.",
|
||||
"newKnowledge.documentPermissionRestricted": "As permissões do seu workspace não permitem ações em documentos ou tarefas.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "A reindexação foi aceita, mas o status da tarefa está atrasado. Verifique antes de enviar novamente.",
|
||||
"newKnowledge.documentReindexFailed": "A reindexação falhou. A última revisão pronta continua disponível.",
|
||||
"newKnowledge.documentReindexProgress": "Reindexando · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Revisão",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Este documento ainda não tem uma revisão pronta. Volte após o processamento terminar.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Nenhuma revisão disponível",
|
||||
"newKnowledge.documentRevisionsLoadError": "Não foi possível carregar o histórico de revisões. A revisão atual continua disponível.",
|
||||
"newKnowledge.documentStatus.disabled": "Desativado",
|
||||
"newKnowledge.documentStatus.failed": "Falha",
|
||||
"newKnowledge.documentStatus.processing": "Processamento",
|
||||
"newKnowledge.documentStatus.queued": "Na fila",
|
||||
"newKnowledge.documentStatus.ready": "Pronto",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Há mais histórico de tarefas disponível. Continue verificando para encontrar o status deste documento.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "limite de tamanho do lote excedido",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "limite de quantidade de arquivos excedido",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "o arquivo é muito grande",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "tipo de arquivo não suportado ou inválido",
|
||||
"newKnowledge.documentUploadExclusion.more": "e mais {{count}}",
|
||||
"newKnowledge.documentUploadExclusion.processing": "falha no processamento",
|
||||
"newKnowledge.documentUploadExclusion.quota": "cota do espaço de trabalho excedida",
|
||||
"newKnowledge.documentUploadExclusion.target": "o destino do documento não é mais válido",
|
||||
"newKnowledge.documentUploadFailed": "Não foi possível fazer upload desses documentos. Tente novamente.",
|
||||
"newKnowledge.documentUploadPartial": "O processamento de {{accepted}} documentos foi iniciado; não foi possível adicionar {{excluded}}: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Nenhum documento foi aceito: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "O processamento do documento foi iniciado.",
|
||||
"newKnowledge.documents": "Documentos",
|
||||
"newKnowledge.documentsDescription": "Ativos de conhecimento e suas revisões — de onde veio cada documento e se a revisão mais recente está indexada.",
|
||||
"newKnowledge.documentsDropHint": "ou solte PDF, DOCX, Markdown, HTML, XLSX ou TXT para fazer upload",
|
||||
"newKnowledge.documentsEmptyDescription": "Faça upload de arquivos aqui ou conecte uma fonte na guia Fontes — os documentos são analisados, agrupados e indexados automaticamente.",
|
||||
"newKnowledge.documentsEmptyTitle": "Ainda não há documentos",
|
||||
"newKnowledge.documentsErrorDescription": "Não foi possível carregar os documentos. Tente novamente.",
|
||||
"newKnowledge.documentsErrorTitle": "Não foi possível carregar os documentos",
|
||||
"newKnowledge.documentsPermissionDescription": "Você não tem permissão para visualizar documentos nesta base de conhecimento.",
|
||||
"newKnowledge.documentsPermissionTitle": "Documentos são restritos",
|
||||
"newKnowledge.documentsReindexFailed": "Não foi possível iniciar a reindexação. Tente novamente.",
|
||||
"newKnowledge.documentsReindexPartial": "Documentos {{queued}} enfileirados; {{missing}} não existe mais.",
|
||||
"newKnowledge.documentsReindexStarted": "A reindexação foi iniciada.",
|
||||
"newKnowledge.documentsSelected": "Documentos {{count}} selecionados",
|
||||
"newKnowledge.documentsUnavailable": "— documentos",
|
||||
"newKnowledge.downloadDocuments": "Baixar",
|
||||
"newKnowledge.editSource": "Editar",
|
||||
"newKnowledge.emptyChunk": "Trecho vazio",
|
||||
"newKnowledge.emptyDescription": "Adicione seus documentos para que os agentes possam lê-los diretamente, sem configurar a indexação.",
|
||||
"newKnowledge.emptyTitle": "Uma forma mais inteligente de criar bases de conhecimento",
|
||||
"newKnowledge.errorDescription": "Não foi possível carregar suas novas bases de conhecimento. Tente novamente.",
|
||||
"newKnowledge.errorTitle": "Algo deu errado",
|
||||
"newKnowledge.fileSize": "Tamanho do arquivo",
|
||||
"newKnowledge.filtersUnavailable": "Este filtro ficará disponível quando o KnowledgeFS expuser seus metadados.",
|
||||
"newKnowledge.firecrawlUnavailable": "O Firecrawl não está disponível no catálogo de provedores deste espaço de trabalho.",
|
||||
"newKnowledge.gotIt": "Entendi",
|
||||
"newKnowledge.guideDescription": "Conecte uma fonte e os documentos permanecerão sincronizados, indexados automaticamente e prontos para uso, sem nada para gerenciar. Suas bases de conhecimento anteriores continuarão funcionando normalmente.",
|
||||
"newKnowledge.guideTitle": "Conheça a nova Base de Conhecimento ✨",
|
||||
"newKnowledge.illustrationHeadline": "Conecte seu conteúdo uma vez — cada aplicativo responderá com o conhecimento mais recente.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Informações do índice",
|
||||
"newKnowledge.interruptTask": "Interromper",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Carregando política de sincronização",
|
||||
"newKnowledge.manualUpload": "Carregamento manual",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Metadados",
|
||||
"newKnowledge.mimeType": "Tipo de arquivo",
|
||||
"newKnowledge.moreProviders": "Mais provedores",
|
||||
"newKnowledge.name": "Nome do conhecimento",
|
||||
"newKnowledge.namePlaceholder": "por exemplo Manual de Suporte ao Cliente",
|
||||
"newKnowledge.nameRequired": "Insira um nome para a base de conhecimento.",
|
||||
"newKnowledge.new": "Novo",
|
||||
"newKnowledge.noBackgroundTasks": "Nenhuma tarefa em segundo plano ainda.",
|
||||
"newKnowledge.noDescription": "Sem descrição",
|
||||
"newKnowledge.noMatchingDocuments": "Nenhum documento corresponde a esta pesquisa.",
|
||||
"newKnowledge.noMatchingSources": "Nenhuma fonte corresponde a esta pesquisa.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Esta base de conhecimento pode ter sido excluída ou você pode não ter acesso.",
|
||||
"newKnowledge.notFoundTitle": "Base de conhecimento não encontrada",
|
||||
"newKnowledge.notionNotConnected": "O Notion não está conectado",
|
||||
"newKnowledge.notionNotConnectedDescription": "Conecte o Notion para escolher páginas e mantê-las sincronizadas.",
|
||||
"newKnowledge.onlineDocuments": "Documentos on-line",
|
||||
"newKnowledge.onlineDrive": "Unidade on-line",
|
||||
"newKnowledge.overview": "Visão geral",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} selecionado(s)",
|
||||
"newKnowledge.partialDocumentResults": "Pode haver mais documentos correspondentes. Carregue mais para continuar a pesquisa.",
|
||||
"newKnowledge.permission": "Permissão",
|
||||
"newKnowledge.permissionAllMembers": "Espaço de trabalho · todos os membros podem visualizar e editar",
|
||||
"newKnowledge.permissionLoadFailed": "Não foi possível verificar suas permissões de edição. Tente novamente.",
|
||||
"newKnowledge.permissionOnlyMe": "Só eu",
|
||||
"newKnowledge.permissionRestricted": "As permissões do seu espaço de trabalho não permitem alterar essa configuração.",
|
||||
"newKnowledge.permissionUpdateFailed": "A base de conhecimento foi criada, mas não foi possível confirmar sua permissão. Tente novamente para concluir a configuração.",
|
||||
"newKnowledge.preview": "Prévia",
|
||||
"newKnowledge.previewUnavailable": "A prévia estará disponível quando o backend terminar de processar este arquivo.",
|
||||
"newKnowledge.processDocument": "Processo {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Interrompido",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Preparando",
|
||||
"newKnowledge.processingTaskState.failed": "Falha",
|
||||
"newKnowledge.processingTaskState.queued": "Na fila",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Esperando para tentar novamente",
|
||||
"newKnowledge.processingTaskState.running": "Processando · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Concluído",
|
||||
"newKnowledge.processingTaskState.superseded": "Substituído",
|
||||
"newKnowledge.providerConnected": "Firecrawl conectado",
|
||||
"newKnowledge.providerLoadFailed": "Os provedores não conseguiram carregar",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} não está configurado",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Adicione uma chave de API ou URL de endpoint para rastrear sites com {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Este provedor não está disponível.",
|
||||
"newKnowledge.quality": "Qualidade",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Peça a um editor do espaço de trabalho para criar a primeira base de conhecimento.",
|
||||
"newKnowledge.refreshConnectionStatus": "Atualizar status",
|
||||
"newKnowledge.refreshingConnection": "Refrescante…",
|
||||
"newKnowledge.reindexDocument": "Reindexar",
|
||||
"newKnowledge.reindexDocuments": "Reindexar",
|
||||
"newKnowledge.removeSource": "Remover fonte",
|
||||
"newKnowledge.retrievalCount": "Quantidade de recuperações",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Tentar novamente",
|
||||
"newKnowledge.retryReindexDocument": "Reindexar novamente",
|
||||
"newKnowledge.retrySyncPolicy": "Tentar novamente a política de sincronização",
|
||||
"newKnowledge.retryTask": "Tentar novamente",
|
||||
"newKnowledge.revisionState.active": "Ativa",
|
||||
"newKnowledge.revisionState.candidate": "Processando",
|
||||
"newKnowledge.revisionState.failed": "Falhou",
|
||||
"newKnowledge.revisionState.superseded": "Anterior",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Esta seção estará disponível na próxima etapa de configuração.",
|
||||
"newKnowledge.searchDocuments": "Pesquisar documentos",
|
||||
"newKnowledge.searchSources": "Fontes de pesquisa",
|
||||
"newKnowledge.selectAll": "Selecionar tudo",
|
||||
"newKnowledge.selectAllDocuments": "Selecione todos os documentos",
|
||||
"newKnowledge.selectAllSources": "Selecionar todas as origens",
|
||||
"newKnowledge.selectChunk": "Selecione um trecho para inspecionar seu conteúdo.",
|
||||
"newKnowledge.selectFilesAndFolders": "Selecione arquivos e pastas para sincronizar",
|
||||
"newKnowledge.skippedFailed": "Ignorado (falha)",
|
||||
"newKnowledge.skippedOffDomain": "Ignorado (fora do domínio)",
|
||||
"newKnowledge.sourceActions": "Ações para {{name}}",
|
||||
"newKnowledge.sourceColumn": "Fonte",
|
||||
"newKnowledge.sourceFilterLabel": "Filtrar fontes por status",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "A configuração da origem é exibida por completo; a edição e a prévia exigem o fluxo de backend da origem em rascunho.",
|
||||
"newKnowledge.sourceStatus.active": "Ativo",
|
||||
"newKnowledge.sourceStatus.disabled": "Desativado",
|
||||
"newKnowledge.sourceStatus.error": "Erro",
|
||||
"newKnowledge.sourceStatus.syncing": "Sincronizando",
|
||||
"newKnowledge.sourceType.connector": "Aplicativo conectado",
|
||||
"newKnowledge.sourceType.object-storage": "Unidade on-line",
|
||||
"newKnowledge.sourceType.upload": "Carregar",
|
||||
"newKnowledge.sourceType.web": "Rastreamento de site",
|
||||
"newKnowledge.sourceTypeLabel": "Tipo de fonte",
|
||||
"newKnowledge.sources": "Fontes",
|
||||
"newKnowledge.sourcesDescription": "De onde vem o conhecimento, quão recente ele é e se a última sincronização foi bem-sucedida.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Conecte um site - o Dify o mantém sincronizado e atualizado, para que a recuperação nunca seja interrompida.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Nenhuma fonte conectada ainda",
|
||||
"newKnowledge.sourcesErrorDescription": "Não foi possível carregar as fontes. Tente novamente.",
|
||||
"newKnowledge.sourcesErrorTitle": "Não foi possível carregar as fontes",
|
||||
"newKnowledge.startEmpty": "Começar vazia",
|
||||
"newKnowledge.startEmptyDescription": "Crie agora e adicione conteúdo depois.",
|
||||
"newKnowledge.startWith": "Comece com",
|
||||
"newKnowledge.startWithHelp": "Você pode adicionar mais fontes ou documentos a qualquer momento.",
|
||||
"newKnowledge.statusColumn": "Estado",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Sincronize agora",
|
||||
"newKnowledge.syncPolicy": "Política de sincronização",
|
||||
"newKnowledge.syncPolicyColumn": "Política de sincronização",
|
||||
"newKnowledge.syncPolicyCustom": "Intervalo personalizado",
|
||||
"newKnowledge.syncPolicyDaily": "A cada 24 horas",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Não foi possível carregar a política de sincronização.",
|
||||
"newKnowledge.syncPolicyManual": "Somente manual",
|
||||
"newKnowledge.syncPolicyProvider": "Usar agenda do provedor",
|
||||
"newKnowledge.tags": "Tags",
|
||||
"newKnowledge.taskActionFailed": "Não foi possível atualizar esta tarefa. Atualize e tente novamente.",
|
||||
"newKnowledge.taskAttentionClear": "Nenhuma tarefa em segundo plano precisa de atenção.",
|
||||
"newKnowledge.taskAttentionCount": "As tarefas em segundo plano do {{count}} precisam de atenção.",
|
||||
"newKnowledge.taskAttentionErrorCount": "As tarefas em segundo plano {{count}} precisam de atenção, incluindo tarefas com falha.",
|
||||
"newKnowledge.taskFailedNotification": "Falha no processamento do documento — tente novamente em Tarefas",
|
||||
"newKnowledge.taskHistoryIncomplete": "Há mais histórico de tarefas disponível; a contagem de tarefas que precisam de atenção está incompleta.",
|
||||
"newKnowledge.tasks": "Tarefas",
|
||||
"newKnowledge.tasksErrorDescription": "Não foi possível carregar tarefas em segundo plano. Tente novamente.",
|
||||
"newKnowledge.tasksWithAttention": "Tarefas, {{count}} precisam de atenção",
|
||||
"newKnowledge.tokenCount": "Unidades de token",
|
||||
"newKnowledge.unavailableDescription": "O KnowledgeFS não está disponível para este espaço de trabalho no momento.",
|
||||
"newKnowledge.unavailableTitle": "O Novo Conhecimento não está disponível",
|
||||
"newKnowledge.updated": "Atualizado em {{date}}",
|
||||
"newKnowledge.updatedAt": "Última atualização",
|
||||
"newKnowledge.updatedColumn": "Atualizado",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Caracteres: —",
|
||||
"newKnowledge.uploadDocuments": "Carregar documentos",
|
||||
"newKnowledge.uploadFiles": "Enviar arquivos",
|
||||
"newKnowledge.uploadFilesDescription": "Adicione arquivos do seu computador; eles são indexados assim que você os cria.",
|
||||
"newKnowledge.uploadingFiles": "Enviando…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Visualização de conhecimento",
|
||||
"newKnowledge.websiteCrawl": "Rastreamento de site",
|
||||
"noExternalKnowledge": "Ainda não existe uma API de conhecimento externo, clique aqui para criar",
|
||||
"parentMode.fullDoc": "Documento completo",
|
||||
"parentMode.paragraph": "Parágrafo",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Modelul de reclasificare este necesar pentru amestecul de baze de cunoștințe de înaltă calitate și economice.",
|
||||
"mixtureInternalAndExternalTip": "Modelul Rerank este necesar pentru amestecul de cunoștințe interne și externe.",
|
||||
"multimodal": "Multimodal",
|
||||
"newKnowledge.actionsColumn": "Acțiuni",
|
||||
"newKnowledge.addDocument": "Adăugați document",
|
||||
"newKnowledge.addSource": "Adăugați sursa",
|
||||
"newKnowledge.addSourceDescription": "Conectați o sursă la această bază de cunoștințe — se sincronizează automat și rămâne actuală.",
|
||||
"newKnowledge.addSourceFailed": "Această sursă nu a putut fi adăugată. Verifică setările și încearcă din nou.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Toate statutul",
|
||||
"newKnowledge.allSources": "Toate sursele",
|
||||
"newKnowledge.apiAgentAccess": "Acces API și agenți",
|
||||
"newKnowledge.appsUnavailable": "— aplicații",
|
||||
"newKnowledge.authKind.api-key": "cheie API",
|
||||
"newKnowledge.authKind.endpoint": "Punct final",
|
||||
"newKnowledge.authenticationMethod": "Metoda de autentificare",
|
||||
"newKnowledge.averageChunkLength": "Lungimea medie a segmentului",
|
||||
"newKnowledge.backToList": "Înapoi la bazele de cunoștințe",
|
||||
"newKnowledge.backgroundTasks": "Sarcini de fundal",
|
||||
"newKnowledge.backgroundTasksDescription": "Adăugări de documente, sincronizare sursă și reindexare lucrări.",
|
||||
"newKnowledge.booleanFalse": "Nu",
|
||||
"newKnowledge.booleanTrue": "Da",
|
||||
"newKnowledge.bulkDocumentActions": "Acțiuni în bloc pentru documente",
|
||||
"newKnowledge.cancelAddSource": "Anula",
|
||||
"newKnowledge.cardType": "Spațiu de cunoștințe",
|
||||
"newKnowledge.characterCount": "Caractere",
|
||||
"newKnowledge.checkReindexStatus": "Verifică starea reindexării",
|
||||
"newKnowledge.chunkCount": "Segmente",
|
||||
"newKnowledge.chunkHeading": "Fragment {{position}}",
|
||||
"newKnowledge.chunkLocation": "Poziția {{position}} în această revizie",
|
||||
"newKnowledge.clearDocumentSelection": "Ștergeți selecția documentului",
|
||||
"newKnowledge.configureProvider": "Configurați Firecrawl",
|
||||
"newKnowledge.connectNotion": "Conectează Notion",
|
||||
"newKnowledge.connectProvider": "Conectați Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Conectează furnizorul",
|
||||
"newKnowledge.connectSource": "Conectează o sursă",
|
||||
"newKnowledge.connectSourceDescription": "Conținutul rămâne sincronizat cu Notion, site-uri web și alte servicii.",
|
||||
"newKnowledge.connectingProvider": "Se conectează...",
|
||||
"newKnowledge.connectionFailed": "Nu am putut conecta Firecrawl. Reintroduceți acreditările și încercați din nou.",
|
||||
"newKnowledge.connectionNeedsAttention": "Conexiunea Firecrawl necesită atenție",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Reîmprospătați conexiunea înainte de a adăuga o sursă de site.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl se conectează",
|
||||
"newKnowledge.connectionRefreshFailed": "Nu am putut reîmprospăta această conexiune. Încearcă din nou.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Continuă verificarea stării",
|
||||
"newKnowledge.crawlAndPreview": "Accesați cu crawlere și previzualizați",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Nepornit",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Previzualizare accesare",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Configurarea accesării cu crawlere a site-ului este dezactivată până când fluxul de lucru cu crawlere este disponibil.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl este gata",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Nu am putut crea această bază de cunoștințe. Verificați accesul și încercați din nou.",
|
||||
"newKnowledge.createIllustrationTitle": "Conectează conținutul o singură dată — fiecare aplicație răspunde cu cele mai noi cunoștințe.",
|
||||
"newKnowledge.createTitle": "Creați cunoștințe",
|
||||
"newKnowledge.createdAt": "Creat",
|
||||
"newKnowledge.creators": "Creatori",
|
||||
"newKnowledge.customIntervalHours": "Interval personalizat (ore)",
|
||||
"newKnowledge.customIntervalInvalid": "Introdu un număr întreg între 1 și 720 de ore.",
|
||||
"newKnowledge.deleteDocuments": "Șterge…",
|
||||
"newKnowledge.description": "Descriere",
|
||||
"newKnowledge.descriptionHelp": "O descriere clară ajută aplicațiile să decidă când să caute aceste cunoștințe.",
|
||||
"newKnowledge.descriptionPlaceholder": "de ex. Politica de rambursare, întrebări frecvente despre expediere și pași de escaladare — utilizați pentru întrebările de asistență pentru clienți",
|
||||
"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.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.",
|
||||
"newKnowledge.documentChunksLoadError": "Nu am putut încărca fragmentele acestei revizii.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Nu am putut încărca mai multe fragmente. Încearcă din nou.",
|
||||
"newKnowledge.documentColumn": "Fișier",
|
||||
"newKnowledge.documentContentIncomplete": "Se încarcă restul conținutului documentului…",
|
||||
"newKnowledge.documentContents": "Conținut",
|
||||
"newKnowledge.documentFacts": "Detalii document",
|
||||
"newKnowledge.documentFilterLabel": "Filtrați documentele după stare",
|
||||
"newKnowledge.documentLoadErrorDescription": "Nu am putut încărca acest document. Încearcă din nou.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Documentul nu a putut fi încărcat",
|
||||
"newKnowledge.documentNotFoundDescription": "Este posibil ca documentul să fi fost eliminat sau să nu ai acces.",
|
||||
"newKnowledge.documentNotFoundTitle": "Document negăsit",
|
||||
"newKnowledge.documentOverviewDescription": "Citește revizia indexată completă și verifică detaliile sursei și ale indexului.",
|
||||
"newKnowledge.documentPermissionRestricted": "Permisiunile spațiului de lucru nu permit acțiuni asupra documentelor sau sarcinilor.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "Reindexarea a fost acceptată, dar starea activității întârzie. Verific-o înainte de a retrimite.",
|
||||
"newKnowledge.documentReindexFailed": "Reindexarea a eșuat. Ultima revizie pregătită rămâne disponibilă.",
|
||||
"newKnowledge.documentReindexProgress": "Se reindexează · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Revizie",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Acest document nu are încă o revizie pregătită. Revino după finalizarea procesării.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Nicio revizie disponibilă",
|
||||
"newKnowledge.documentRevisionsLoadError": "Nu am putut încărca istoricul reviziilor. Revizia curentă rămâne disponibilă.",
|
||||
"newKnowledge.documentStatus.disabled": "Dezactivat",
|
||||
"newKnowledge.documentStatus.failed": "A eșuat",
|
||||
"newKnowledge.documentStatus.processing": "Prelucrare",
|
||||
"newKnowledge.documentStatus.queued": "În coadă",
|
||||
"newKnowledge.documentStatus.ready": "Gata",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Este disponibil mai mult istoric al activităților. Continuă verificarea pentru a găsi starea documentului.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "limita dimensiunii lotului a fost depășită",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "limita numărului de fișiere a fost depășită",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "fișierul este prea mare",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "tip de fișier neacceptat sau nevalid",
|
||||
"newKnowledge.documentUploadExclusion.more": "și încă {{count}}",
|
||||
"newKnowledge.documentUploadExclusion.processing": "procesarea a eșuat",
|
||||
"newKnowledge.documentUploadExclusion.quota": "cota spațiului de lucru a fost depășită",
|
||||
"newKnowledge.documentUploadExclusion.target": "destinația documentului nu mai este validă",
|
||||
"newKnowledge.documentUploadFailed": "Nu am putut încărca aceste documente. Încearcă din nou.",
|
||||
"newKnowledge.documentUploadPartial": "Procesarea a {{accepted}} documente a început; {{excluded}} nu au putut fi adăugate: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Nu a fost acceptat niciun document: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Procesarea documentelor a început.",
|
||||
"newKnowledge.documents": "Documente",
|
||||
"newKnowledge.documentsDescription": "Activele de cunoștințe și revizuirile acestora — de unde provine fiecare document și dacă cea mai recentă revizuire este indexată.",
|
||||
"newKnowledge.documentsDropHint": "sau plasați PDF, DOCX, Markdown, HTML, XLSX sau TXT pentru a încărca",
|
||||
"newKnowledge.documentsEmptyDescription": "Încărcați fișiere aici sau conectați o sursă în fila Surse - documentele sunt analizate, fragmentate și indexate automat.",
|
||||
"newKnowledge.documentsEmptyTitle": "Încă nu există documente",
|
||||
"newKnowledge.documentsErrorDescription": "Nu am putut încărca documente. Încearcă din nou.",
|
||||
"newKnowledge.documentsErrorTitle": "Documentele nu s-au putut încărca",
|
||||
"newKnowledge.documentsPermissionDescription": "Nu aveți permisiunea de a vizualiza documente din această bază de cunoștințe.",
|
||||
"newKnowledge.documentsPermissionTitle": "Documentele sunt restricționate",
|
||||
"newKnowledge.documentsReindexFailed": "Nu am putut începe reindexarea. Încearcă din nou.",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} documente puse în coadă; {{missing}} nu mai există.",
|
||||
"newKnowledge.documentsReindexStarted": "A început reindexarea.",
|
||||
"newKnowledge.documentsSelected": "{{count}} documente selectate",
|
||||
"newKnowledge.documentsUnavailable": "— documente",
|
||||
"newKnowledge.downloadDocuments": "Descărcați",
|
||||
"newKnowledge.editSource": "Edita",
|
||||
"newKnowledge.emptyChunk": "Fragment gol",
|
||||
"newKnowledge.emptyDescription": "Adaugă documentele, iar agenții le pot citi direct — fără indexare de configurat.",
|
||||
"newKnowledge.emptyTitle": "O modalitate mai inteligentă de a crea baze de cunoștințe",
|
||||
"newKnowledge.errorDescription": "Nu am putut încărca noile baze de cunoștințe. Încearcă din nou.",
|
||||
"newKnowledge.errorTitle": "Ceva nu a funcționat",
|
||||
"newKnowledge.fileSize": "Dimensiunea fișierului",
|
||||
"newKnowledge.filtersUnavailable": "Acest filtru va fi disponibil când KnowledgeFS va furniza metadatele.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl nu este disponibil în catalogul furnizorilor pentru acest spațiu de lucru.",
|
||||
"newKnowledge.gotIt": "Am înțeles",
|
||||
"newKnowledge.guideDescription": "Conectează o sursă, iar documentele ei rămân sincronizate, indexate automat și gata de utilizare, fără nimic de administrat. Bazele de cunoștințe existente continuă să funcționeze ca înainte.",
|
||||
"newKnowledge.guideTitle": "Descoperă noua bază de cunoștințe ✨",
|
||||
"newKnowledge.illustrationHeadline": "Conectează conținutul o singură dată — fiecare aplicație răspunde cu cele mai noi informații.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Informații despre index",
|
||||
"newKnowledge.interruptTask": "Întrerupe",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Se încarcă politica de sincronizare",
|
||||
"newKnowledge.manualUpload": "Încărcare manuală",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Metadate",
|
||||
"newKnowledge.mimeType": "Tip de fișier",
|
||||
"newKnowledge.moreProviders": "Mai mulți furnizori",
|
||||
"newKnowledge.name": "Nume de cunoștințe",
|
||||
"newKnowledge.namePlaceholder": "de ex. Manual de asistență pentru clienți",
|
||||
"newKnowledge.nameRequired": "Introduceți un nume de bază de cunoștințe.",
|
||||
"newKnowledge.new": "Versiune nouă",
|
||||
"newKnowledge.noBackgroundTasks": "Nu există încă sarcini de fundal.",
|
||||
"newKnowledge.noDescription": "Fără descriere",
|
||||
"newKnowledge.noMatchingDocuments": "Niciun document nu corespunde acestei căutări.",
|
||||
"newKnowledge.noMatchingSources": "Nicio sursă nu corespunde acestei căutări.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Este posibil ca această bază de cunoștințe să fi fost ștearsă sau este posibil să nu aveți acces.",
|
||||
"newKnowledge.notFoundTitle": "Baza de cunoștințe nu a fost găsită",
|
||||
"newKnowledge.notionNotConnected": "Notion nu este conectat",
|
||||
"newKnowledge.notionNotConnectedDescription": "Conectează Notion pentru a alege pagini și a le menține sincronizate.",
|
||||
"newKnowledge.onlineDocuments": "Documente online",
|
||||
"newKnowledge.onlineDrive": "Conducere online",
|
||||
"newKnowledge.overview": "Prezentare generală",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} selectate",
|
||||
"newKnowledge.partialDocumentResults": "Este posibil să existe mai multe documente potrivite. Încarcă mai multe pentru a continua căutarea.",
|
||||
"newKnowledge.permission": "Permisiune",
|
||||
"newKnowledge.permissionAllMembers": "Spațiu de lucru · toți membrii pot vizualiza și edita",
|
||||
"newKnowledge.permissionLoadFailed": "Permisiunile de editare nu au putut fi verificate. Încercați din nou.",
|
||||
"newKnowledge.permissionOnlyMe": "Doar eu",
|
||||
"newKnowledge.permissionRestricted": "Permisiunile pentru spațiul de lucru nu permit modificarea acestei setări.",
|
||||
"newKnowledge.permissionUpdateFailed": "Baza de cunoștințe a fost creată, dar permisiunea nu a putut fi confirmată. Încercați din nou pentru a finaliza configurarea.",
|
||||
"newKnowledge.preview": "Previzualizare",
|
||||
"newKnowledge.previewUnavailable": "Previzualizarea va fi disponibilă după ce backendul finalizează procesarea fișierului.",
|
||||
"newKnowledge.processDocument": "Procesează {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Întrerupt",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Pregătirea",
|
||||
"newKnowledge.processingTaskState.failed": "A eșuat",
|
||||
"newKnowledge.processingTaskState.queued": "În coadă",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Aștept să încerci din nou",
|
||||
"newKnowledge.processingTaskState.running": "Procesare · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Terminat",
|
||||
"newKnowledge.processingTaskState.superseded": "Inlocuit",
|
||||
"newKnowledge.providerConnected": "Firecrawl conectat",
|
||||
"newKnowledge.providerLoadFailed": "Furnizorii nu s-au putut încărca",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} nu este configurat",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Adăugați o cheie API sau o adresă URL a punctului final pentru a accesa cu crawlere site-urile web cu {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Acest furnizor nu este disponibil.",
|
||||
"newKnowledge.quality": "Calitate",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Cere unui editor al spațiului de lucru să creeze prima bază de cunoștințe.",
|
||||
"newKnowledge.refreshConnectionStatus": "Starea de reîmprospătare",
|
||||
"newKnowledge.refreshingConnection": "Înviorare…",
|
||||
"newKnowledge.reindexDocument": "Reindexează",
|
||||
"newKnowledge.reindexDocuments": "Reindexați",
|
||||
"newKnowledge.removeSource": "Eliminați sursa",
|
||||
"newKnowledge.retrievalCount": "Număr de regăsiri",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Reîncercați",
|
||||
"newKnowledge.retryReindexDocument": "Reindexează din nou",
|
||||
"newKnowledge.retrySyncPolicy": "Reîncearcă politica de sincronizare",
|
||||
"newKnowledge.retryTask": "Reîncercați",
|
||||
"newKnowledge.revisionState.active": "Activă",
|
||||
"newKnowledge.revisionState.candidate": "În procesare",
|
||||
"newKnowledge.revisionState.failed": "Eșuată",
|
||||
"newKnowledge.revisionState.superseded": "Anterioară",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Această secțiune va deveni disponibilă în următorul pas de configurare.",
|
||||
"newKnowledge.searchDocuments": "Căutați documente",
|
||||
"newKnowledge.searchSources": "Căutați surse",
|
||||
"newKnowledge.selectAll": "Selectează tot",
|
||||
"newKnowledge.selectAllDocuments": "Selectați toate documentele",
|
||||
"newKnowledge.selectAllSources": "Selectează toate sursele",
|
||||
"newKnowledge.selectChunk": "Selectează un fragment pentru a-i verifica conținutul.",
|
||||
"newKnowledge.selectFilesAndFolders": "Selectează fișierele și folderele de sincronizat",
|
||||
"newKnowledge.skippedFailed": "Omis (eșuat)",
|
||||
"newKnowledge.skippedOffDomain": "Omis (în afara domeniului)",
|
||||
"newKnowledge.sourceActions": "Acțiuni pentru {{name}}",
|
||||
"newKnowledge.sourceColumn": "Sursă",
|
||||
"newKnowledge.sourceFilterLabel": "Filtrați sursele după stare",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "Configurarea sursei este afișată integral; editarea și previzualizarea necesită fluxul backend pentru sursa ciornă.",
|
||||
"newKnowledge.sourceStatus.active": "Activ",
|
||||
"newKnowledge.sourceStatus.disabled": "Dezactivat",
|
||||
"newKnowledge.sourceStatus.error": "Eroare",
|
||||
"newKnowledge.sourceStatus.syncing": "Sincronizare",
|
||||
"newKnowledge.sourceType.connector": "Aplicație conectată",
|
||||
"newKnowledge.sourceType.object-storage": "Conducere online",
|
||||
"newKnowledge.sourceType.upload": "Încărcați",
|
||||
"newKnowledge.sourceType.web": "Accesare cu crawlere a site-ului",
|
||||
"newKnowledge.sourceTypeLabel": "Tip sursă",
|
||||
"newKnowledge.sources": "Surse",
|
||||
"newKnowledge.sourcesDescription": "De unde provin cunoștințele, cât de proaspete sunt și dacă ultima sincronizare a reușit.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Conectați un site web — Dify îl menține sincronizat și actual, astfel încât recuperarea nu se întrerupe niciodată.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Nicio sursă conectată încă",
|
||||
"newKnowledge.sourcesErrorDescription": "Nu am putut încărca sursele. Încearcă din nou.",
|
||||
"newKnowledge.sourcesErrorTitle": "Sursele nu s-au putut încărca",
|
||||
"newKnowledge.startEmpty": "Începe de la zero",
|
||||
"newKnowledge.startEmptyDescription": "Creeaz-o acum și adaugă conținut mai târziu.",
|
||||
"newKnowledge.startWith": "Începe cu",
|
||||
"newKnowledge.startWithHelp": "Puteți adăuga oricând mai multe surse sau documente.",
|
||||
"newKnowledge.statusColumn": "Stare",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Sincronizează acum",
|
||||
"newKnowledge.syncPolicy": "Politică de sincronizare",
|
||||
"newKnowledge.syncPolicyColumn": "Politica de sincronizare",
|
||||
"newKnowledge.syncPolicyCustom": "Interval personalizat",
|
||||
"newKnowledge.syncPolicyDaily": "La fiecare 24 de ore",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Politica de sincronizare nu a putut fi încărcată.",
|
||||
"newKnowledge.syncPolicyManual": "Doar manual",
|
||||
"newKnowledge.syncPolicyProvider": "Folosește programul furnizorului",
|
||||
"newKnowledge.tags": "Etichete",
|
||||
"newKnowledge.taskActionFailed": "Nu am putut actualiza această sarcină. Actualizați și încercați din nou.",
|
||||
"newKnowledge.taskAttentionClear": "Nicio activitate de fundal nu necesită atenție.",
|
||||
"newKnowledge.taskAttentionCount": "{{count}} sarcinile de fundal necesită atenție.",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}} sarcinile de fundal necesită atenție, inclusiv sarcinile eșuate.",
|
||||
"newKnowledge.taskFailedNotification": "Procesarea documentului a eșuat — reîncercați din Sarcini",
|
||||
"newKnowledge.taskHistoryIncomplete": "Este disponibil mai mult istoric al sarcinilor; numărul sarcinilor care necesită atenție este incomplet.",
|
||||
"newKnowledge.tasks": "Sarcini",
|
||||
"newKnowledge.tasksErrorDescription": "Nu am putut încărca sarcinile de fundal. Încearcă din nou.",
|
||||
"newKnowledge.tasksWithAttention": "Sarcini, {{count}} necesită atenție",
|
||||
"newKnowledge.tokenCount": "Tokenuri",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS nu este disponibil momentan pentru acest spațiu de lucru.",
|
||||
"newKnowledge.unavailableTitle": "Noua bază de cunoștințe nu este disponibilă",
|
||||
"newKnowledge.updated": "Actualizat la {{date}}",
|
||||
"newKnowledge.updatedAt": "Ultima actualizare",
|
||||
"newKnowledge.updatedColumn": "Actualizat",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Caractere: —",
|
||||
"newKnowledge.uploadDocuments": "Încărcați documente",
|
||||
"newKnowledge.uploadFiles": "Încarcă fișiere",
|
||||
"newKnowledge.uploadFilesDescription": "Adaugă fișiere de pe computer — sunt indexate imediat ce le creezi.",
|
||||
"newKnowledge.uploadingFiles": "Se încarcă…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Vizualizare cunoștințe",
|
||||
"newKnowledge.websiteCrawl": "Accesare cu crawlere a site-ului",
|
||||
"noExternalKnowledge": "Nu există încă un API de cunoștințe externe, faceți clic aici pentru a crea",
|
||||
"parentMode.fullDoc": "Documentar complet",
|
||||
"parentMode.paragraph": "Paragraf",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Для смешивания высококачественных и экономичных баз знаний требуется модель переранжирования.",
|
||||
"mixtureInternalAndExternalTip": "Модель Rerank необходима для смешивания внутренних и внешних знаний.",
|
||||
"multimodal": "Мультимодальный",
|
||||
"newKnowledge.actionsColumn": "Действия",
|
||||
"newKnowledge.addDocument": "Добавить документ",
|
||||
"newKnowledge.addSource": "Добавить источник",
|
||||
"newKnowledge.addSourceDescription": "Подключите источник к этой базе знаний — она синхронизируется автоматически и остается актуальной.",
|
||||
"newKnowledge.addSourceFailed": "Не удалось добавить этот источник. Проверьте настройки и повторите попытку.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Все статусы",
|
||||
"newKnowledge.allSources": "Все источники",
|
||||
"newKnowledge.apiAgentAccess": "Доступ к API и агентам",
|
||||
"newKnowledge.appsUnavailable": "— приложений",
|
||||
"newKnowledge.authKind.api-key": "API-ключ",
|
||||
"newKnowledge.authKind.endpoint": "Конечная точка",
|
||||
"newKnowledge.authenticationMethod": "Метод аутентификации",
|
||||
"newKnowledge.averageChunkLength": "Средняя длина фрагмента",
|
||||
"newKnowledge.backToList": "Вернуться к базам знаний",
|
||||
"newKnowledge.backgroundTasks": "Фоновые задачи",
|
||||
"newKnowledge.backgroundTasksDescription": "Добавление документов, синхронизация источников и переиндексация заданий.",
|
||||
"newKnowledge.booleanFalse": "Нет",
|
||||
"newKnowledge.booleanTrue": "Да",
|
||||
"newKnowledge.bulkDocumentActions": "Массовые действия с документами",
|
||||
"newKnowledge.cancelAddSource": "Отмена",
|
||||
"newKnowledge.cardType": "Пространство знаний",
|
||||
"newKnowledge.characterCount": "Символы",
|
||||
"newKnowledge.checkReindexStatus": "Проверить статус переиндексации",
|
||||
"newKnowledge.chunkCount": "Фрагменты",
|
||||
"newKnowledge.chunkHeading": "Фрагмент {{position}}",
|
||||
"newKnowledge.chunkLocation": "Позиция {{position}} в этой редакции",
|
||||
"newKnowledge.clearDocumentSelection": "Очистить выбор документа",
|
||||
"newKnowledge.configureProvider": "Настройка FireCrowl",
|
||||
"newKnowledge.connectNotion": "Подключить Notion",
|
||||
"newKnowledge.connectProvider": "Подключите Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Подключить провайдера",
|
||||
"newKnowledge.connectSource": "Подключить источник",
|
||||
"newKnowledge.connectSourceDescription": "Контент остаётся синхронизированным с Notion, веб-сайтами и другими сервисами.",
|
||||
"newKnowledge.connectingProvider": "Подключение…",
|
||||
"newKnowledge.connectionFailed": "Нам не удалось подключить Firecrawl. Повторно введите учетные данные и повторите попытку.",
|
||||
"newKnowledge.connectionNeedsAttention": "Соединение FireCrow требует внимания",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Обновите соединение перед добавлением источника веб-сайта.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl подключается",
|
||||
"newKnowledge.connectionRefreshFailed": "Не удалось обновить это соединение. Попробуйте еще раз.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Продолжить проверку статуса",
|
||||
"newKnowledge.crawlAndPreview": "Сканирование и предварительный просмотр",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Не запущено",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Предпросмотр обхода",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Конфигурация сканирования веб-сайта отключена до тех пор, пока не будет доступен рабочий процесс сканирования.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl готов",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Мы не смогли создать эту базу знаний. Проверьте доступ и повторите попытку.",
|
||||
"newKnowledge.createIllustrationTitle": "Подключите контент один раз — каждое приложение будет отвечать на основе самых свежих знаний.",
|
||||
"newKnowledge.createTitle": "Создавайте знания",
|
||||
"newKnowledge.createdAt": "Создано",
|
||||
"newKnowledge.creators": "Создатели",
|
||||
"newKnowledge.customIntervalHours": "Пользовательский интервал (часы)",
|
||||
"newKnowledge.customIntervalInvalid": "Введите целое число от 1 до 720 часов.",
|
||||
"newKnowledge.deleteDocuments": "Удалить…",
|
||||
"newKnowledge.description": "Описание",
|
||||
"newKnowledge.descriptionHelp": "Четкое описание помогает приложениям решить, когда следует искать эти знания.",
|
||||
"newKnowledge.descriptionPlaceholder": "например Политика возврата, часто задаваемые вопросы о доставке и шаги эскалации — используйте для вопросов поддержки клиентов.",
|
||||
"newKnowledge.detailErrorDescription": "Нам не удалось загрузить эту базу знаний. Попробуйте еще раз.",
|
||||
"newKnowledge.detailErrorTitle": "Что-то пошло не так",
|
||||
"newKnowledge.disableSource": "Отключить",
|
||||
"newKnowledge.discardDraftConfirm": "Удалить черновик",
|
||||
"newKnowledge.discardDraftDescription": "Изменения в этом черновике базы знаний будут потеряны.",
|
||||
"newKnowledge.discardDraftTitle": "Удалить этот черновик базы знаний?",
|
||||
"newKnowledge.discardSourceChanges": "Отменить изменения источника?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Отменить изменения",
|
||||
"newKnowledge.discardSourceChangesDescription": "Предпросмотр сканирования, выбор страниц и настройки синхронизации будут потеряны.",
|
||||
"newKnowledge.documentActions": "Действия для {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Действия с документами пока недоступны для этой базы знаний.",
|
||||
"newKnowledge.documentChunksEmpty": "Для этой редакции пока нет доступных фрагментов.",
|
||||
"newKnowledge.documentChunksLoadError": "Не удалось загрузить фрагменты этой редакции.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Не удалось загрузить дополнительные фрагменты. Повторите попытку.",
|
||||
"newKnowledge.documentColumn": "Документ",
|
||||
"newKnowledge.documentContentIncomplete": "Загружается оставшееся содержимое документа…",
|
||||
"newKnowledge.documentContents": "Содержание",
|
||||
"newKnowledge.documentFacts": "Сведения о документе",
|
||||
"newKnowledge.documentFilterLabel": "Фильтровать документы по статусу",
|
||||
"newKnowledge.documentLoadErrorDescription": "Не удалось загрузить этот документ. Повторите попытку.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Не удалось загрузить документ",
|
||||
"newKnowledge.documentNotFoundDescription": "Возможно, документ удалён или у вас нет доступа.",
|
||||
"newKnowledge.documentNotFoundTitle": "Документ не найден",
|
||||
"newKnowledge.documentOverviewDescription": "Прочитайте всю проиндексированную редакцию и проверьте сведения об источнике и индексе.",
|
||||
"newKnowledge.documentPermissionRestricted": "Разрешения вашего рабочего пространства не позволяют выполнять действия с документами или задачами.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "Переиндексация принята, но статус задачи задерживается. Проверьте его перед повторной отправкой.",
|
||||
"newKnowledge.documentReindexFailed": "Переиндексация завершилась с ошибкой. Последняя готовая редакция остаётся доступной.",
|
||||
"newKnowledge.documentReindexProgress": "Переиндексация · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Редакция",
|
||||
"newKnowledge.documentRevisionMissingDescription": "У этого документа пока нет готовой редакции. Проверьте снова после завершения обработки.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Нет доступной редакции",
|
||||
"newKnowledge.documentRevisionsLoadError": "Не удалось загрузить историю редакций. Текущая редакция остаётся доступной.",
|
||||
"newKnowledge.documentStatus.disabled": "Отключено",
|
||||
"newKnowledge.documentStatus.failed": "Не удалось",
|
||||
"newKnowledge.documentStatus.processing": "Обработка",
|
||||
"newKnowledge.documentStatus.queued": "В очереди",
|
||||
"newKnowledge.documentStatus.ready": "Готово",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Доступна дополнительная история задач. Продолжите проверку, чтобы найти статус документа.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "превышен предел размера пакета",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "превышен предел количества файлов",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "файл слишком большой",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "неподдерживаемый или недопустимый тип файла",
|
||||
"newKnowledge.documentUploadExclusion.more": "и ещё {{count}}",
|
||||
"newKnowledge.documentUploadExclusion.processing": "ошибка обработки",
|
||||
"newKnowledge.documentUploadExclusion.quota": "превышена квота рабочей области",
|
||||
"newKnowledge.documentUploadExclusion.target": "назначение документа больше недействительно",
|
||||
"newKnowledge.documentUploadFailed": "Нам не удалось загрузить эти документы. Попробуйте еще раз.",
|
||||
"newKnowledge.documentUploadPartial": "Начата обработка {{accepted}} документов; не удалось добавить {{excluded}}: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Ни один документ не принят: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Началась обработка документов.",
|
||||
"newKnowledge.documents": "Документы",
|
||||
"newKnowledge.documentsDescription": "Активы знаний и их версии — откуда взялся каждый документ и индексируется ли последняя версия.",
|
||||
"newKnowledge.documentsDropHint": "или перетащите PDF, DOCX, Markdown, HTML, XLSX или TXT для загрузки.",
|
||||
"newKnowledge.documentsEmptyDescription": "Загрузите файлы сюда или подключите источник на вкладке «Источники» — документы анализируются, разбиваются на части и индексируются автоматически.",
|
||||
"newKnowledge.documentsEmptyTitle": "Документов пока нет",
|
||||
"newKnowledge.documentsErrorDescription": "Не удалось загрузить документы. Попробуйте еще раз.",
|
||||
"newKnowledge.documentsErrorTitle": "Не удалось загрузить документы",
|
||||
"newKnowledge.documentsPermissionDescription": "У вас нет разрешения на просмотр документов в этой базе знаний.",
|
||||
"newKnowledge.documentsPermissionTitle": "Документы ограничены",
|
||||
"newKnowledge.documentsReindexFailed": "Не удалось начать переиндексацию. Попробуйте еще раз.",
|
||||
"newKnowledge.documentsReindexPartial": "Документы {{queued}} поставлены в очередь; {{missing}} больше не существует.",
|
||||
"newKnowledge.documentsReindexStarted": "Началась переиндексация.",
|
||||
"newKnowledge.documentsSelected": "{{count}} выбраны документы",
|
||||
"newKnowledge.documentsUnavailable": "— документов",
|
||||
"newKnowledge.downloadDocuments": "Скачать",
|
||||
"newKnowledge.editSource": "Править",
|
||||
"newKnowledge.emptyChunk": "Пустой фрагмент",
|
||||
"newKnowledge.emptyDescription": "Добавьте документы, и агенты смогут читать их напрямую — без настройки индексации.",
|
||||
"newKnowledge.emptyTitle": "Более умный способ создавать базы знаний",
|
||||
"newKnowledge.errorDescription": "Не удалось загрузить новые базы знаний. Повторите попытку.",
|
||||
"newKnowledge.errorTitle": "Что-то пошло не так",
|
||||
"newKnowledge.fileSize": "Размер файла",
|
||||
"newKnowledge.filtersUnavailable": "Фильтр станет доступен, когда KnowledgeFS предоставит метаданные.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl недоступен в каталоге поставщиков для этой рабочей области.",
|
||||
"newKnowledge.gotIt": "Понятно",
|
||||
"newKnowledge.guideDescription": "Подключите источник, и его документы будут синхронизироваться, автоматически индексироваться и сразу станут готовы к работе — без дополнительной настройки. Ваши прежние базы знаний продолжат работать как раньше.",
|
||||
"newKnowledge.guideTitle": "Познакомьтесь с новой базой знаний ✨",
|
||||
"newKnowledge.illustrationHeadline": "Подключите контент один раз — и каждое приложение будет отвечать на основе самых свежих знаний.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Сведения об индексе",
|
||||
"newKnowledge.interruptTask": "Прерывание",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid 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": "Загрузить предыдущие редакции",
|
||||
"newKnowledge.loadingSyncPolicy": "Загрузка политики синхронизации",
|
||||
"newKnowledge.manualUpload": "Загрузка вручную",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Метаданные",
|
||||
"newKnowledge.mimeType": "Тип файла",
|
||||
"newKnowledge.moreProviders": "Другие провайдеры",
|
||||
"newKnowledge.name": "Название знания",
|
||||
"newKnowledge.namePlaceholder": "например Руководство по поддержке клиентов",
|
||||
"newKnowledge.nameRequired": "Введите название базы знаний.",
|
||||
"newKnowledge.new": "Новая версия",
|
||||
"newKnowledge.noBackgroundTasks": "Фоновых задач пока нет.",
|
||||
"newKnowledge.noDescription": "Нет описания",
|
||||
"newKnowledge.noMatchingDocuments": "Нет документов, соответствующих этому запросу.",
|
||||
"newKnowledge.noMatchingSources": "Ни один источник не соответствует этому запросу.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Возможно, эта база знаний была удалена, или у вас нет доступа.",
|
||||
"newKnowledge.notFoundTitle": "База знаний не найдена",
|
||||
"newKnowledge.notionNotConnected": "Notion не подключён",
|
||||
"newKnowledge.notionNotConnectedDescription": "Подключите Notion, чтобы выбирать страницы и поддерживать их синхронизацию.",
|
||||
"newKnowledge.onlineDocuments": "Онлайн-документы",
|
||||
"newKnowledge.onlineDrive": "Онлайн-диск",
|
||||
"newKnowledge.overview": "Обзор",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "Выбрано: {{count}}",
|
||||
"newKnowledge.partialDocumentResults": "Подходящих документов может быть больше. Загрузите ещё, чтобы продолжить поиск.",
|
||||
"newKnowledge.permission": "Разрешение",
|
||||
"newKnowledge.permissionAllMembers": "Рабочая область · все участники могут просматривать и редактировать",
|
||||
"newKnowledge.permissionLoadFailed": "Не удалось проверить права на редактирование. Повторите попытку.",
|
||||
"newKnowledge.permissionOnlyMe": "Только я",
|
||||
"newKnowledge.permissionRestricted": "Разрешения вашей рабочей области не позволяют изменить этот параметр.",
|
||||
"newKnowledge.permissionUpdateFailed": "База знаний создана, но подтвердить её разрешения не удалось. Повторите попытку, чтобы завершить настройку.",
|
||||
"newKnowledge.preview": "Предпросмотр",
|
||||
"newKnowledge.previewUnavailable": "Предпросмотр станет доступен после завершения обработки файла сервером.",
|
||||
"newKnowledge.processDocument": "Процесс {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Прерванный",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Подготовка",
|
||||
"newKnowledge.processingTaskState.failed": "Не удалось",
|
||||
"newKnowledge.processingTaskState.queued": "В очереди",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Ожидание повторной попытки",
|
||||
"newKnowledge.processingTaskState.running": "Обработка · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Завершено",
|
||||
"newKnowledge.processingTaskState.superseded": "Заменено",
|
||||
"newKnowledge.providerConnected": "FireCrawl подключен",
|
||||
"newKnowledge.providerLoadFailed": "Поставщикам не удалось загрузить",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} не настроено",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Добавьте ключ API или URL-адрес конечной точки для сканирования веб-сайтов с помощью {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Этот провайдер недоступен.",
|
||||
"newKnowledge.quality": "Качество",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Попросите редактора рабочего пространства создать первую базу знаний.",
|
||||
"newKnowledge.refreshConnectionStatus": "Обновить статус",
|
||||
"newKnowledge.refreshingConnection": "Обновление…",
|
||||
"newKnowledge.reindexDocument": "Переиндексировать",
|
||||
"newKnowledge.reindexDocuments": "Переиндексация",
|
||||
"newKnowledge.removeSource": "Удалить источник",
|
||||
"newKnowledge.retrievalCount": "Количество извлечений",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Повторить попытку",
|
||||
"newKnowledge.retryReindexDocument": "Переиндексировать снова",
|
||||
"newKnowledge.retrySyncPolicy": "Повторить загрузку политики синхронизации",
|
||||
"newKnowledge.retryTask": "Повторить попытку",
|
||||
"newKnowledge.revisionState.active": "Активная",
|
||||
"newKnowledge.revisionState.candidate": "Обработка",
|
||||
"newKnowledge.revisionState.failed": "Ошибка",
|
||||
"newKnowledge.revisionState.superseded": "Предыдущая",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Этот раздел станет доступен на следующем этапе настройки.",
|
||||
"newKnowledge.searchDocuments": "Поиск документов",
|
||||
"newKnowledge.searchSources": "Поиск источников",
|
||||
"newKnowledge.selectAll": "Выбрать все",
|
||||
"newKnowledge.selectAllDocuments": "Выбрать все документы",
|
||||
"newKnowledge.selectAllSources": "Выбрать все источники",
|
||||
"newKnowledge.selectChunk": "Выберите фрагмент, чтобы просмотреть его содержимое.",
|
||||
"newKnowledge.selectFilesAndFolders": "Выберите файлы и папки для синхронизации",
|
||||
"newKnowledge.skippedFailed": "Пропущено (ошибка)",
|
||||
"newKnowledge.skippedOffDomain": "Пропущено (вне домена)",
|
||||
"newKnowledge.sourceActions": "Действия для {{name}}",
|
||||
"newKnowledge.sourceColumn": "Источник",
|
||||
"newKnowledge.sourceFilterLabel": "Фильтровать источники по статусу",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "Настройка источника показана полностью; для редактирования и предпросмотра требуется серверный процесс черновика источника.",
|
||||
"newKnowledge.sourceStatus.active": "Активен",
|
||||
"newKnowledge.sourceStatus.disabled": "Отключено",
|
||||
"newKnowledge.sourceStatus.error": "Ошибка",
|
||||
"newKnowledge.sourceStatus.syncing": "Синхронизация",
|
||||
"newKnowledge.sourceType.connector": "Подключенное приложение",
|
||||
"newKnowledge.sourceType.object-storage": "Онлайн-диск",
|
||||
"newKnowledge.sourceType.upload": "Загрузить",
|
||||
"newKnowledge.sourceType.web": "Сканирование веб-сайта",
|
||||
"newKnowledge.sourceTypeLabel": "Тип источника",
|
||||
"newKnowledge.sources": "Источники",
|
||||
"newKnowledge.sourcesDescription": "Откуда берутся знания, насколько они свежие и успешна ли последняя синхронизация.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Подключите веб-сайт — Dify поддерживает его синхронизацию и актуальность, поэтому поиск никогда не прерывается.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Источники пока не подключены",
|
||||
"newKnowledge.sourcesErrorDescription": "Не удалось загрузить исходники. Попробуйте еще раз.",
|
||||
"newKnowledge.sourcesErrorTitle": "Не удалось загрузить исходники.",
|
||||
"newKnowledge.startEmpty": "Начать с пустой",
|
||||
"newKnowledge.startEmptyDescription": "Создайте сейчас, а контент добавьте позже.",
|
||||
"newKnowledge.startWith": "Начните с",
|
||||
"newKnowledge.startWithHelp": "Вы можете добавить больше источников или документов в любое время.",
|
||||
"newKnowledge.statusColumn": "Статус",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Синхронизировать сейчас",
|
||||
"newKnowledge.syncPolicy": "Политика синхронизации",
|
||||
"newKnowledge.syncPolicyColumn": "Политика синхронизации",
|
||||
"newKnowledge.syncPolicyCustom": "Пользовательский интервал",
|
||||
"newKnowledge.syncPolicyDaily": "Каждые 24 часа",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Не удалось загрузить политику синхронизации.",
|
||||
"newKnowledge.syncPolicyManual": "Только вручную",
|
||||
"newKnowledge.syncPolicyProvider": "Использовать расписание поставщика",
|
||||
"newKnowledge.tags": "Теги",
|
||||
"newKnowledge.taskActionFailed": "Не удалось обновить эту задачу. Обновите и повторите попытку.",
|
||||
"newKnowledge.taskAttentionClear": "Никакие фоновые задачи не требуют внимания.",
|
||||
"newKnowledge.taskAttentionCount": "Фоновые задачи {{count}} требуют внимания.",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}} фоновые задачи требуют внимания, включая невыполненные задачи.",
|
||||
"newKnowledge.taskFailedNotification": "Не удалось обработать документ — повторите попытку в разделе «Задачи».",
|
||||
"newKnowledge.taskHistoryIncomplete": "Доступна дополнительная история задач; число задач, требующих внимания, пока неполное.",
|
||||
"newKnowledge.tasks": "Задачи",
|
||||
"newKnowledge.tasksErrorDescription": "Не удалось загрузить фоновые задачи. Попробуйте еще раз.",
|
||||
"newKnowledge.tasksWithAttention": "Задачи, {{count}} требуют внимания",
|
||||
"newKnowledge.tokenCount": "Токены",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS сейчас недоступен для этого рабочего пространства.",
|
||||
"newKnowledge.unavailableTitle": "Новая база знаний недоступна",
|
||||
"newKnowledge.updated": "Обновлено {{date}}",
|
||||
"newKnowledge.updatedAt": "Последнее обновление",
|
||||
"newKnowledge.updatedColumn": "Обновлено",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Символы: —",
|
||||
"newKnowledge.uploadDocuments": "Загрузить документы",
|
||||
"newKnowledge.uploadFiles": "Загрузить файлы",
|
||||
"newKnowledge.uploadFilesDescription": "Добавьте файлы с компьютера — они будут проиндексированы сразу после создания.",
|
||||
"newKnowledge.uploadingFiles": "Загрузка…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Вид базы знаний",
|
||||
"newKnowledge.websiteCrawl": "Сканирование веб-сайта",
|
||||
"noExternalKnowledge": "У нас еще нет External Knowledge API, нажмите здесь, чтобы создать",
|
||||
"parentMode.fullDoc": "Полный документ",
|
||||
"parentMode.paragraph": "Параграф",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Model za ponovno razvrščanje je potreben za mešanico baz znanja visoke kakovosti in varčnih baz.",
|
||||
"mixtureInternalAndExternalTip": "Model za ponovno razvrščanje je potreben za mešanico notranjega in zunanjega znanja.",
|
||||
"multimodal": "Multimodalen",
|
||||
"newKnowledge.actionsColumn": "Dejanja",
|
||||
"newKnowledge.addDocument": "Dodaj dokument",
|
||||
"newKnowledge.addSource": "Dodaj vir",
|
||||
"newKnowledge.addSourceDescription": "Povežite vir s to bazo znanja — samodejno se sinhronizira in ostane sveža.",
|
||||
"newKnowledge.addSourceFailed": "Tega vira ni bilo mogoče dodati. Preverite nastavitve in poskusite znova.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Vsi statusi",
|
||||
"newKnowledge.allSources": "Vsi viri",
|
||||
"newKnowledge.apiAgentAccess": "Dostop do API-ja in agentov",
|
||||
"newKnowledge.appsUnavailable": "— aplikacij",
|
||||
"newKnowledge.authKind.api-key": "API ključ",
|
||||
"newKnowledge.authKind.endpoint": "Končna točka",
|
||||
"newKnowledge.authenticationMethod": "Metoda avtentikacije",
|
||||
"newKnowledge.averageChunkLength": "Povprečna dolžina odseka",
|
||||
"newKnowledge.backToList": "Nazaj k bazam znanja",
|
||||
"newKnowledge.backgroundTasks": "Naloge v ozadju",
|
||||
"newKnowledge.backgroundTasksDescription": "Dodajanje dokumentov, sinhronizacija virov in ponovno indeksiranje opravil.",
|
||||
"newKnowledge.booleanFalse": "Št",
|
||||
"newKnowledge.booleanTrue": "Da",
|
||||
"newKnowledge.bulkDocumentActions": "Dejanja množičnih dokumentov",
|
||||
"newKnowledge.cancelAddSource": "Prekliči",
|
||||
"newKnowledge.cardType": "Prostor znanja",
|
||||
"newKnowledge.characterCount": "Znaki",
|
||||
"newKnowledge.checkReindexStatus": "Preveri stanje ponovnega indeksiranja",
|
||||
"newKnowledge.chunkCount": "Odseki",
|
||||
"newKnowledge.chunkHeading": "Del {{position}}",
|
||||
"newKnowledge.chunkLocation": "Položaj {{position}} v tej različici",
|
||||
"newKnowledge.clearDocumentSelection": "Počisti izbiro dokumenta",
|
||||
"newKnowledge.configureProvider": "Konfigurirajte Firecrawl",
|
||||
"newKnowledge.connectNotion": "Poveži Notion",
|
||||
"newKnowledge.connectProvider": "Povežite Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Poveži ponudnika",
|
||||
"newKnowledge.connectSource": "Poveži vir",
|
||||
"newKnowledge.connectSourceDescription": "Vsebina ostaja sinhronizirana z Notionom, spletnimi mesti in drugimi storitvami.",
|
||||
"newKnowledge.connectingProvider": "Povezovanje ...",
|
||||
"newKnowledge.connectionFailed": "Firecrawl nismo mogli povezati. Znova vnesite poverilnico in poskusite znova.",
|
||||
"newKnowledge.connectionNeedsAttention": "Povezava požarnega pajkanja zahteva pozornost",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Osvežite povezavo, preden dodate vir spletnega mesta.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl vzpostavlja povezavo",
|
||||
"newKnowledge.connectionRefreshFailed": "Te povezave nismo mogli osvežiti. Poskusite znova.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Nadaljuj preverjanje stanja opravila",
|
||||
"newKnowledge.crawlAndPreview": "Iskanje in predogled",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Ni začeto",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Predogled iskanja",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Konfiguracija iskanja po vsebini spletnega mesta je onemogočena, dokler potek dela po vsebini ni na voljo.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl je pripravljen",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Te baze znanja nismo mogli ustvariti. Preverite dostop in poskusite znova.",
|
||||
"newKnowledge.createIllustrationTitle": "Vsebino povežite enkrat — vsaka aplikacija odgovarja z najnovejšim znanjem.",
|
||||
"newKnowledge.createTitle": "Ustvarite znanje",
|
||||
"newKnowledge.createdAt": "Ustvarjeno",
|
||||
"newKnowledge.creators": "Ustvarjalci",
|
||||
"newKnowledge.customIntervalHours": "Interval po meri (ure)",
|
||||
"newKnowledge.customIntervalInvalid": "Vnesite celo število od 1 do 720 ur.",
|
||||
"newKnowledge.deleteDocuments": "Izbriši ...",
|
||||
"newKnowledge.description": "Opis",
|
||||
"newKnowledge.descriptionHelp": "Jasen opis pomaga aplikacijam, da se odločijo, kdaj naj iščejo to znanje.",
|
||||
"newKnowledge.descriptionPlaceholder": "npr. Politika vračil, pogosta vprašanja o pošiljanju in koraki za eskalacijo — uporabite za vprašanja podpore strankam",
|
||||
"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.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.",
|
||||
"newKnowledge.documentChunksLoadError": "Delov te različice ni bilo mogoče naložiti.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Dodatnih delov ni bilo mogoče naložiti. Poskusite znova.",
|
||||
"newKnowledge.documentColumn": "Dokument",
|
||||
"newKnowledge.documentContentIncomplete": "Nalaganje preostale vsebine dokumenta…",
|
||||
"newKnowledge.documentContents": "Vsebina",
|
||||
"newKnowledge.documentFacts": "Podrobnosti dokumenta",
|
||||
"newKnowledge.documentFilterLabel": "Filtrirajte dokumente po statusu",
|
||||
"newKnowledge.documentLoadErrorDescription": "Tega dokumenta ni bilo mogoče naložiti. Poskusite znova.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Dokumenta ni bilo mogoče naložiti",
|
||||
"newKnowledge.documentNotFoundDescription": "Dokument je bil morda odstranjen ali pa nimate dostopa.",
|
||||
"newKnowledge.documentNotFoundTitle": "Dokument ni najden",
|
||||
"newKnowledge.documentOverviewDescription": "Preberite celotno indeksirano različico ter preverite podrobnosti vira in indeksa.",
|
||||
"newKnowledge.documentPermissionRestricted": "Dovoljenja vašega delovnega prostora ne omogočajo dejanj dokumentov ali opravil.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "Ponovno indeksiranje je bilo sprejeto, vendar stanje opravila zamuja. Preverite ga pred ponovno oddajo.",
|
||||
"newKnowledge.documentReindexFailed": "Ponovno indeksiranje ni uspelo. Zadnja pripravljena različica ostaja na voljo.",
|
||||
"newKnowledge.documentReindexProgress": "Ponovno indeksiranje · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Različica",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Ta dokument še nima pripravljene različice. Preverite po končani obdelavi.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Ni razpoložljive različice",
|
||||
"newKnowledge.documentRevisionsLoadError": "Zgodovine različic ni bilo mogoče naložiti. Trenutna različica ostaja na voljo.",
|
||||
"newKnowledge.documentStatus.disabled": "Onemogočeno",
|
||||
"newKnowledge.documentStatus.failed": "Ni uspelo",
|
||||
"newKnowledge.documentStatus.processing": "Obdelava",
|
||||
"newKnowledge.documentStatus.queued": "V čakalni vrsti",
|
||||
"newKnowledge.documentStatus.ready": "Pripravljeno",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Na voljo je več zgodovine opravil. Nadaljujte preverjanje, da najdete stanje dokumenta.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "omejitev velikosti paketa je presežena",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "omejitev števila datotek je presežena",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "datoteka je prevelika",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "nepodprta ali neveljavna vrsta datoteke",
|
||||
"newKnowledge.documentUploadExclusion.more": "in še {{count}}",
|
||||
"newKnowledge.documentUploadExclusion.processing": "obdelava ni uspela",
|
||||
"newKnowledge.documentUploadExclusion.quota": "kvota delovnega prostora je presežena",
|
||||
"newKnowledge.documentUploadExclusion.target": "cilj dokumenta ni več veljaven",
|
||||
"newKnowledge.documentUploadFailed": "Teh dokumentov nismo mogli naložiti. poskusi ponovno",
|
||||
"newKnowledge.documentUploadPartial": "Obdelava {{accepted}} dokumentov se je začela; {{excluded}} jih ni bilo mogoče dodati: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Noben dokument ni bil sprejet: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Začela se je obdelava dokumentov.",
|
||||
"newKnowledge.documents": "Dokumenti",
|
||||
"newKnowledge.documentsDescription": "Sredstva znanja in njihove revizije — od kod prihaja posamezni dokument in ali je zadnja revizija indeksirana.",
|
||||
"newKnowledge.documentsDropHint": "ali spustite PDF, DOCX, Markdown, HTML, XLSX ali TXT za nalaganje",
|
||||
"newKnowledge.documentsEmptyDescription": "Tukaj naložite datoteke ali povežite vir na zavihku Viri – dokumenti so samodejno razčlenjeni, razdeljeni na kose in indeksirani.",
|
||||
"newKnowledge.documentsEmptyTitle": "Dokumentov še ni",
|
||||
"newKnowledge.documentsErrorDescription": "Dokumentov nismo mogli naložiti. poskusi ponovno",
|
||||
"newKnowledge.documentsErrorTitle": "Dokumentov ni bilo mogoče naložiti",
|
||||
"newKnowledge.documentsPermissionDescription": "Nimate dovoljenja za ogled dokumentov v tej bazi znanja.",
|
||||
"newKnowledge.documentsPermissionTitle": "Dokumenti so omejeni",
|
||||
"newKnowledge.documentsReindexFailed": "Ponovnega indeksiranja nismo mogli začeti. poskusi ponovno",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} dokumenti v čakalni vrsti; {{missing}} ne obstaja več.",
|
||||
"newKnowledge.documentsReindexStarted": "Začelo se je ponovno indeksiranje.",
|
||||
"newKnowledge.documentsSelected": "Izbrani dokumenti {{count}}",
|
||||
"newKnowledge.documentsUnavailable": "— dokumentov",
|
||||
"newKnowledge.downloadDocuments": "Prenos",
|
||||
"newKnowledge.editSource": "Uredi",
|
||||
"newKnowledge.emptyChunk": "Prazen del",
|
||||
"newKnowledge.emptyDescription": "Dodajte dokumente in agenti jih lahko berejo neposredno — brez nastavljanja indeksiranja.",
|
||||
"newKnowledge.emptyTitle": "Pametnejši način ustvarjanja baz znanja",
|
||||
"newKnowledge.errorDescription": "Novih baz znanja ni bilo mogoče naložiti. Poskusite znova.",
|
||||
"newKnowledge.errorTitle": "Prišlo je do napake",
|
||||
"newKnowledge.fileSize": "Velikost datoteke",
|
||||
"newKnowledge.filtersUnavailable": "Ta filter bo na voljo, ko bo KnowledgeFS razkril metapodatke.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl ni na voljo v katalogu ponudnika za ta delovni prostor.",
|
||||
"newKnowledge.gotIt": "Razumem",
|
||||
"newKnowledge.guideDescription": "Povežite vir in njegovi dokumenti bodo ostali sinhronizirani, samodejno indeksirani in pripravljeni za uporabo brez dodatnega upravljanja. Vaše obstoječe baze znanja bodo še naprej delovale kot doslej.",
|
||||
"newKnowledge.guideTitle": "Spoznajte novo bazo znanja ✨",
|
||||
"newKnowledge.illustrationHeadline": "Vsebino povežite enkrat — vsaka aplikacija bo odgovarjala z najnovejšim znanjem.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Podatki o indeksu",
|
||||
"newKnowledge.interruptTask": "Prekini",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Nalaganje pravilnika sinhronizacije",
|
||||
"newKnowledge.manualUpload": "Ročno nalaganje",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Meta podatki",
|
||||
"newKnowledge.mimeType": "Vrsta datoteke",
|
||||
"newKnowledge.moreProviders": "Več ponudnikov",
|
||||
"newKnowledge.name": "Ime znanja",
|
||||
"newKnowledge.namePlaceholder": "npr. Priročnik za podporo strankam",
|
||||
"newKnowledge.nameRequired": "Vnesite ime baze znanja.",
|
||||
"newKnowledge.new": "Novo",
|
||||
"newKnowledge.noBackgroundTasks": "Ni še opravil v ozadju.",
|
||||
"newKnowledge.noDescription": "Brez opisa",
|
||||
"newKnowledge.noMatchingDocuments": "Noben dokument ne ustreza temu iskanju.",
|
||||
"newKnowledge.noMatchingSources": "Noben vir ne ustreza temu iskanju.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Ta baza znanja je bila morda izbrisana ali pa nimate dostopa.",
|
||||
"newKnowledge.notFoundTitle": "Baza znanja ni bila najdena",
|
||||
"newKnowledge.notionNotConnected": "Notion ni povezan",
|
||||
"newKnowledge.notionNotConnectedDescription": "Povežite Notion, da izberete strani in jih ohranite sinhronizirane.",
|
||||
"newKnowledge.onlineDocuments": "Spletni dokumenti",
|
||||
"newKnowledge.onlineDrive": "Spletna vožnja",
|
||||
"newKnowledge.overview": "Pregled",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "Izbrano: {{count}}",
|
||||
"newKnowledge.partialDocumentResults": "Morda se ujema več dokumentov. Naložite jih več za nadaljevanje iskanja.",
|
||||
"newKnowledge.permission": "Dovoljenje",
|
||||
"newKnowledge.permissionAllMembers": "Delovni prostor · vsi člani si lahko ogledujejo in urejajo",
|
||||
"newKnowledge.permissionLoadFailed": "Dovoljenj za urejanje ni bilo mogoče preveriti. Poskusite znova.",
|
||||
"newKnowledge.permissionOnlyMe": "Samo jaz",
|
||||
"newKnowledge.permissionRestricted": "Vaša dovoljenja za delovni prostor ne dovoljujejo spreminjanja te nastavitve.",
|
||||
"newKnowledge.permissionUpdateFailed": "Zbirka znanja je bila ustvarjena, vendar njenega dovoljenja ni bilo mogoče potrditi. Poskusite znova, da dokončate nastavitev.",
|
||||
"newKnowledge.preview": "Predogled",
|
||||
"newKnowledge.previewUnavailable": "Predogled bo na voljo, ko zaledje dokonča obdelavo te datoteke.",
|
||||
"newKnowledge.processDocument": "Proces {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Prekinjeno",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Priprava",
|
||||
"newKnowledge.processingTaskState.failed": "Ni uspelo",
|
||||
"newKnowledge.processingTaskState.queued": "V čakalni vrsti",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Čakam na ponovni poskus",
|
||||
"newKnowledge.processingTaskState.running": "Obdelava · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Dokončano",
|
||||
"newKnowledge.processingTaskState.superseded": "Nadomeščeno",
|
||||
"newKnowledge.providerConnected": "Firecrawl je povezan",
|
||||
"newKnowledge.providerLoadFailed": "Ponudnikov ni bilo mogoče naložiti",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} ni konfiguriran",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Dodajte ključ API ali URL končne točke za iskanje po spletnih mestih z {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Ta ponudnik ni dosegljiv.",
|
||||
"newKnowledge.quality": "Kakovost",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Prosite urednika delovnega prostora, naj ustvari prvo bazo znanja.",
|
||||
"newKnowledge.refreshConnectionStatus": "Osveži stanje",
|
||||
"newKnowledge.refreshingConnection": "Osvežitev …",
|
||||
"newKnowledge.reindexDocument": "Ponovno indeksiraj",
|
||||
"newKnowledge.reindexDocuments": "Ponovno indeksiraj",
|
||||
"newKnowledge.removeSource": "Odstrani vir",
|
||||
"newKnowledge.retrievalCount": "Število pridobitev",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Poskusi znova",
|
||||
"newKnowledge.retryReindexDocument": "Ponovno indeksiraj",
|
||||
"newKnowledge.retrySyncPolicy": "Znova poskusi naložiti pravilnik sinhronizacije",
|
||||
"newKnowledge.retryTask": "Poskusite znova",
|
||||
"newKnowledge.revisionState.active": "Aktivna",
|
||||
"newKnowledge.revisionState.candidate": "Obdelava",
|
||||
"newKnowledge.revisionState.failed": "Neuspešno",
|
||||
"newKnowledge.revisionState.superseded": "Prejšnja",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Ta razdelek bo na voljo v naslednjem koraku nastavitve.",
|
||||
"newKnowledge.searchDocuments": "Iskanje dokumentov",
|
||||
"newKnowledge.searchSources": "Iskanje virov",
|
||||
"newKnowledge.selectAll": "Izberi vse",
|
||||
"newKnowledge.selectAllDocuments": "Izberite vse dokumente",
|
||||
"newKnowledge.selectAllSources": "Izberi vse vire",
|
||||
"newKnowledge.selectChunk": "Izberite del za pregled njegove vsebine.",
|
||||
"newKnowledge.selectFilesAndFolders": "Izberite datoteke in mape za sinhronizacijo",
|
||||
"newKnowledge.skippedFailed": "Preskočeno (neuspešno)",
|
||||
"newKnowledge.skippedOffDomain": "Preskočeno (zunaj domene)",
|
||||
"newKnowledge.sourceActions": "Dejanja za {{name}}",
|
||||
"newKnowledge.sourceColumn": "Vir",
|
||||
"newKnowledge.sourceFilterLabel": "Filtrirajte vire po statusu",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "Nastavitev vira je prikazana v celoti; urejanje in predogled zahtevata zaledni potek za osnutek vira.",
|
||||
"newKnowledge.sourceStatus.active": "Aktivno",
|
||||
"newKnowledge.sourceStatus.disabled": "Onemogočeno",
|
||||
"newKnowledge.sourceStatus.error": "Napaka",
|
||||
"newKnowledge.sourceStatus.syncing": "Sinhronizacija",
|
||||
"newKnowledge.sourceType.connector": "Povezana aplikacija",
|
||||
"newKnowledge.sourceType.object-storage": "Spletna vožnja",
|
||||
"newKnowledge.sourceType.upload": "Naloži",
|
||||
"newKnowledge.sourceType.web": "Iskanje po vsebini spletne strani",
|
||||
"newKnowledge.sourceTypeLabel": "Vrsta vira",
|
||||
"newKnowledge.sources": "Viri",
|
||||
"newKnowledge.sourcesDescription": "Od kod prihaja znanje, kako sveže je in ali je zadnja sinhronizacija uspela.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Povežite spletno mesto – Dify ga ohranja sinhroniziranega in svežega, tako da se iskanje nikoli ne prekine.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Ni še povezanih virov",
|
||||
"newKnowledge.sourcesErrorDescription": "Virov ni bilo mogoče naložiti. Poskusite znova.",
|
||||
"newKnowledge.sourcesErrorTitle": "Virov ni bilo mogoče naložiti",
|
||||
"newKnowledge.startEmpty": "Začni prazno",
|
||||
"newKnowledge.startEmptyDescription": "Ustvarite jo zdaj in vsebino dodajte pozneje.",
|
||||
"newKnowledge.startWith": "Začni z",
|
||||
"newKnowledge.startWithHelp": "Kadar koli lahko dodate več virov ali dokumentov.",
|
||||
"newKnowledge.statusColumn": "Stanje",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Sinhroniziraj zdaj",
|
||||
"newKnowledge.syncPolicy": "Pravilnik sinhronizacije",
|
||||
"newKnowledge.syncPolicyColumn": "Pravilnik o sinhronizaciji",
|
||||
"newKnowledge.syncPolicyCustom": "Interval po meri",
|
||||
"newKnowledge.syncPolicyDaily": "Vsakih 24 ur",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Pravilnika sinhronizacije ni bilo mogoče naložiti.",
|
||||
"newKnowledge.syncPolicyManual": "Samo ročno",
|
||||
"newKnowledge.syncPolicyProvider": "Uporabi urnik ponudnika",
|
||||
"newKnowledge.tags": "Oznake",
|
||||
"newKnowledge.taskActionFailed": "Tega opravila nismo mogli posodobiti. Osvežite in poskusite znova.",
|
||||
"newKnowledge.taskAttentionClear": "Nobena opravila v ozadju ne potrebujejo pozornosti.",
|
||||
"newKnowledge.taskAttentionCount": "Opravila v ozadju {{count}} zahtevajo pozornost.",
|
||||
"newKnowledge.taskAttentionErrorCount": "Opravila v ozadju {{count}} zahtevajo pozornost, vključno z neuspešnimi opravili.",
|
||||
"newKnowledge.taskFailedNotification": "Obdelava dokumenta ni uspela — poskusite znova v opravilih",
|
||||
"newKnowledge.taskHistoryIncomplete": "Na voljo je več zgodovine opravil; število opravil, ki zahtevajo pozornost, ni popolno.",
|
||||
"newKnowledge.tasks": "Naloge",
|
||||
"newKnowledge.tasksErrorDescription": "Opravil v ozadju nismo mogli naložiti. poskusi ponovno",
|
||||
"newKnowledge.tasksWithAttention": "Naloge, {{count}} potrebujejo pozornost",
|
||||
"newKnowledge.tokenCount": "Žetoni",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS trenutno ni na voljo za ta delovni prostor.",
|
||||
"newKnowledge.unavailableTitle": "Novo znanje ni na voljo",
|
||||
"newKnowledge.updated": "Posodobljeno {{date}}",
|
||||
"newKnowledge.updatedAt": "Nazadnje posodobljeno",
|
||||
"newKnowledge.updatedColumn": "Posodobljeno",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Znaki: —",
|
||||
"newKnowledge.uploadDocuments": "Nalaganje dokumentov",
|
||||
"newKnowledge.uploadFiles": "Naloži datoteke",
|
||||
"newKnowledge.uploadFilesDescription": "Dodajte datoteke iz računalnika — indeksirajo se takoj ob ustvarjanju.",
|
||||
"newKnowledge.uploadingFiles": "Nalaganje…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Prikaz znanja",
|
||||
"newKnowledge.websiteCrawl": "Iskanje po vsebini spletne strani",
|
||||
"noExternalKnowledge": "Zunanjega API-ja za znanje še ni, kliknite tukaj za ustvarjanje",
|
||||
"parentMode.fullDoc": "Celoten dokument",
|
||||
"parentMode.paragraph": "Odstavek",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "โมเดล Rerank จําเป็นสําหรับการผสมผสานระหว่างฐานความรู้คุณภาพสูงและประหยัด",
|
||||
"mixtureInternalAndExternalTip": "โมเดล Rerank จําเป็นสําหรับการผสมผสานระหว่างความรู้ภายในและภายนอก",
|
||||
"multimodal": "หลายรูปแบบ",
|
||||
"newKnowledge.actionsColumn": "การดำเนินการ",
|
||||
"newKnowledge.addDocument": "เพิ่มเอกสาร",
|
||||
"newKnowledge.addSource": "เพิ่มแหล่งที่มา",
|
||||
"newKnowledge.addSourceDescription": "เชื่อมต่อแหล่งข้อมูลกับฐานความรู้นี้ โดยจะซิงค์โดยอัตโนมัติและมีความสดใหม่อยู่เสมอ",
|
||||
"newKnowledge.addSourceFailed": "ไม่สามารถเพิ่มแหล่งข้อมูลนี้ได้ โปรดตรวจสอบการตั้งค่าแล้วลองอีกครั้ง",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "สถานะทั้งหมด",
|
||||
"newKnowledge.allSources": "แหล่งที่มาทั้งหมด",
|
||||
"newKnowledge.apiAgentAccess": "การเข้าถึง API และเอเจนต์",
|
||||
"newKnowledge.appsUnavailable": "— แอป",
|
||||
"newKnowledge.authKind.api-key": "คีย์ API",
|
||||
"newKnowledge.authKind.endpoint": "จุดสิ้นสุด",
|
||||
"newKnowledge.authenticationMethod": "วิธีการตรวจสอบความถูกต้อง",
|
||||
"newKnowledge.averageChunkLength": "ความยาวส่วนข้อมูลเฉลี่ย",
|
||||
"newKnowledge.backToList": "กลับสู่ฐานความรู้",
|
||||
"newKnowledge.backgroundTasks": "งานเบื้องหลัง",
|
||||
"newKnowledge.backgroundTasksDescription": "การเพิ่มเอกสาร การซิงค์แหล่งที่มา และงานจัดทำดัชนีใหม่",
|
||||
"newKnowledge.booleanFalse": "ไม่",
|
||||
"newKnowledge.booleanTrue": "ใช่",
|
||||
"newKnowledge.bulkDocumentActions": "การดำเนินการกับเอกสารจำนวนมาก",
|
||||
"newKnowledge.cancelAddSource": "ยกเลิก",
|
||||
"newKnowledge.cardType": "พื้นที่ความรู้",
|
||||
"newKnowledge.characterCount": "อักขระ",
|
||||
"newKnowledge.checkReindexStatus": "ตรวจสอบสถานะการทำดัชนีใหม่",
|
||||
"newKnowledge.chunkCount": "ส่วนข้อมูล",
|
||||
"newKnowledge.chunkHeading": "ส่วนข้อมูล {{position}}",
|
||||
"newKnowledge.chunkLocation": "ตำแหน่ง {{position}} ในรุ่นแก้ไขนี้",
|
||||
"newKnowledge.clearDocumentSelection": "ล้างการเลือกเอกสาร",
|
||||
"newKnowledge.configureProvider": "กำหนดค่า Firecrawl",
|
||||
"newKnowledge.connectNotion": "เชื่อมต่อ Notion",
|
||||
"newKnowledge.connectProvider": "เชื่อมต่อ Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "เชื่อมต่อผู้ให้บริการ",
|
||||
"newKnowledge.connectSource": "เชื่อมต่อแหล่งข้อมูล",
|
||||
"newKnowledge.connectSourceDescription": "เนื้อหาจะซิงค์กับ Notion เว็บไซต์ และบริการอื่น ๆ อยู่เสมอ",
|
||||
"newKnowledge.connectingProvider": "กำลังเชื่อมต่อ...",
|
||||
"newKnowledge.connectionFailed": "เราไม่สามารถเชื่อมต่อ Firecrawl ได้ ป้อนข้อมูลรับรองอีกครั้งแล้วลองอีกครั้ง",
|
||||
"newKnowledge.connectionNeedsAttention": "การเชื่อมต่อ Firecrawl ต้องการความสนใจ",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "รีเฟรชการเชื่อมต่อก่อนที่จะเพิ่มแหล่งที่มาของเว็บไซต์",
|
||||
"newKnowledge.connectionProvisioning": "กำลังเชื่อมต่อ Firecrawl",
|
||||
"newKnowledge.connectionRefreshFailed": "เราไม่สามารถรีเฟรชการเชื่อมต่อนี้ได้ ลองอีกครั้ง",
|
||||
"newKnowledge.continueCheckingTaskStatus": "ตรวจสอบสถานะงานต่อ",
|
||||
"newKnowledge.crawlAndPreview": "รวบรวมข้อมูลและดูตัวอย่าง",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "ยังไม่เริ่ม",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "ตัวอย่างการเก็บข้อมูล",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "การกำหนดค่าการรวบรวมข้อมูลเว็บไซต์ถูกปิดใช้งานจนกว่าเวิร์กโฟลว์การรวบรวมข้อมูลจะพร้อมใช้งาน",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl พร้อมแล้ว",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "เราไม่สามารถสร้างฐานความรู้นี้ได้ ตรวจสอบการเข้าถึงของคุณแล้วลองอีกครั้ง",
|
||||
"newKnowledge.createIllustrationTitle": "เชื่อมต่อเนื้อหาเพียงครั้งเดียว — ทุกแอปจะตอบด้วยความรู้ล่าสุด",
|
||||
"newKnowledge.createTitle": "สร้างองค์ความรู้",
|
||||
"newKnowledge.createdAt": "สร้างเมื่อ",
|
||||
"newKnowledge.creators": "ผู้สร้าง",
|
||||
"newKnowledge.customIntervalHours": "ช่วงเวลาแบบกำหนดเอง (ชั่วโมง)",
|
||||
"newKnowledge.customIntervalInvalid": "ป้อนจำนวนเต็มตั้งแต่ 1 ถึง 720 ชั่วโมง",
|
||||
"newKnowledge.deleteDocuments": "ลบ...",
|
||||
"newKnowledge.description": "คำอธิบาย",
|
||||
"newKnowledge.descriptionHelp": "คำอธิบายที่ชัดเจนช่วยให้แอปตัดสินใจว่าจะค้นหาความรู้นี้เมื่อใด",
|
||||
"newKnowledge.descriptionPlaceholder": "เช่น นโยบายการคืนเงิน คำถามที่พบบ่อยเกี่ยวกับการจัดส่ง และขั้นตอนการยกระดับ — ใช้สำหรับคำถามด้านการสนับสนุนลูกค้า",
|
||||
"newKnowledge.detailErrorDescription": "เราไม่สามารถโหลดฐานความรู้นี้ได้ ลองอีกครั้ง",
|
||||
"newKnowledge.detailErrorTitle": "มีบางอย่างผิดพลาด",
|
||||
"newKnowledge.disableSource": "ปิดการใช้งาน",
|
||||
"newKnowledge.discardDraftConfirm": "ละทิ้งฉบับร่าง",
|
||||
"newKnowledge.discardDraftDescription": "การเปลี่ยนแปลงในฉบับร่างฐานความรู้นี้จะสูญหาย",
|
||||
"newKnowledge.discardDraftTitle": "ละทิ้งฉบับร่างฐานความรู้นี้หรือไม่?",
|
||||
"newKnowledge.discardSourceChanges": "ละทิ้งการเปลี่ยนแปลงแหล่งข้อมูลหรือไม่",
|
||||
"newKnowledge.discardSourceChangesConfirm": "ละทิ้งการเปลี่ยนแปลง",
|
||||
"newKnowledge.discardSourceChangesDescription": "ตัวอย่างการรวบรวมข้อมูล การเลือกหน้า และการตั้งค่าการซิงค์จะสูญหาย",
|
||||
"newKnowledge.documentActions": "การดำเนินการสำหรับ {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "การดำเนินการกับเอกสารยังไม่พร้อมใช้งานสำหรับฐานความรู้นี้",
|
||||
"newKnowledge.documentChunksEmpty": "ยังไม่มีส่วนข้อมูลสำหรับรุ่นแก้ไขนี้",
|
||||
"newKnowledge.documentChunksLoadError": "ไม่สามารถโหลดส่วนข้อมูลของรุ่นแก้ไขนี้ได้",
|
||||
"newKnowledge.documentChunksLoadMoreError": "ไม่สามารถโหลดส่วนข้อมูลเพิ่มเติมได้ โปรดลองอีกครั้ง",
|
||||
"newKnowledge.documentColumn": "เอกสาร",
|
||||
"newKnowledge.documentContentIncomplete": "กำลังโหลดเนื้อหาเอกสารที่เหลือ…",
|
||||
"newKnowledge.documentContents": "สารบัญ",
|
||||
"newKnowledge.documentFacts": "รายละเอียดเอกสาร",
|
||||
"newKnowledge.documentFilterLabel": "กรองเอกสารตามสถานะ",
|
||||
"newKnowledge.documentLoadErrorDescription": "ไม่สามารถโหลดเอกสารนี้ได้ โปรดลองอีกครั้ง",
|
||||
"newKnowledge.documentLoadErrorTitle": "โหลดเอกสารไม่ได้",
|
||||
"newKnowledge.documentNotFoundDescription": "เอกสารนี้อาจถูกลบหรือคุณอาจไม่มีสิทธิ์เข้าถึง",
|
||||
"newKnowledge.documentNotFoundTitle": "ไม่พบเอกสาร",
|
||||
"newKnowledge.documentOverviewDescription": "อ่านรุ่นที่จัดทำดัชนีทั้งหมดและตรวจสอบรายละเอียดแหล่งที่มาและดัชนี",
|
||||
"newKnowledge.documentPermissionRestricted": "สิทธิ์ของพื้นที่ทำงานไม่อนุญาตให้ดำเนินการกับเอกสารหรืองาน",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "ระบบรับคำขอทำดัชนีใหม่แล้ว แต่สถานะงานล่าช้า โปรดตรวจสอบก่อนส่งอีกครั้ง",
|
||||
"newKnowledge.documentReindexFailed": "การจัดทำดัชนีใหม่ล้มเหลว รุ่นแก้ไขล่าสุดที่พร้อมใช้งานยังคงอยู่",
|
||||
"newKnowledge.documentReindexProgress": "กำลังจัดทำดัชนีใหม่ · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "รุ่นแก้ไข",
|
||||
"newKnowledge.documentRevisionMissingDescription": "เอกสารนี้ยังไม่มีรุ่นแก้ไขที่พร้อม โปรดตรวจสอบอีกครั้งหลังประมวลผลเสร็จ",
|
||||
"newKnowledge.documentRevisionMissingTitle": "ไม่มีรุ่นแก้ไขที่พร้อมใช้งาน",
|
||||
"newKnowledge.documentRevisionsLoadError": "ไม่สามารถโหลดประวัติรุ่นแก้ไขได้ รุ่นปัจจุบันยังคงใช้งานได้",
|
||||
"newKnowledge.documentStatus.disabled": "ปิดการใช้งาน",
|
||||
"newKnowledge.documentStatus.failed": "ล้มเหลว",
|
||||
"newKnowledge.documentStatus.processing": "กำลังประมวลผล",
|
||||
"newKnowledge.documentStatus.queued": "เข้าคิว",
|
||||
"newKnowledge.documentStatus.ready": "พร้อม",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "ยังมีประวัติงานเพิ่มเติม โปรดตรวจสอบต่อเพื่อค้นหาสถานะของเอกสารนี้",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "เกินขีดจำกัดขนาดแบตช์",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "เกินขีดจำกัดจำนวนไฟล์",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "ไฟล์มีขนาดใหญ่เกินไป",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "ประเภทไฟล์ไม่รองรับหรือไม่ถูกต้อง",
|
||||
"newKnowledge.documentUploadExclusion.more": "และอีก {{count}} รายการ",
|
||||
"newKnowledge.documentUploadExclusion.processing": "การประมวลผลล้มเหลว",
|
||||
"newKnowledge.documentUploadExclusion.quota": "เกินโควตาพื้นที่ทำงาน",
|
||||
"newKnowledge.documentUploadExclusion.target": "เป้าหมายเอกสารไม่ถูกต้องอีกต่อไป",
|
||||
"newKnowledge.documentUploadFailed": "เราไม่สามารถอัปโหลดเอกสารเหล่านี้ได้ ลองอีกครั้ง",
|
||||
"newKnowledge.documentUploadPartial": "เริ่มประมวลผลเอกสาร {{accepted}} รายการแล้ว; เพิ่มไม่ได้ {{excluded}} รายการ: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "ไม่มีเอกสารที่ได้รับการยอมรับ: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "การประมวลผลเอกสารเริ่มต้นขึ้น",
|
||||
"newKnowledge.documents": "เอกสาร",
|
||||
"newKnowledge.documentsDescription": "สินทรัพย์ความรู้และการแก้ไข — เอกสารแต่ละฉบับมาจากไหนและมีการจัดทำดัชนีการแก้ไขล่าสุดหรือไม่",
|
||||
"newKnowledge.documentsDropHint": "หรือวาง PDF, DOCX, Markdown, HTML, XLSX หรือ TXT เพื่ออัปโหลด",
|
||||
"newKnowledge.documentsEmptyDescription": "อัปโหลดไฟล์ที่นี่ หรือเชื่อมต่อแหล่งข้อมูลบนแท็บแหล่งที่มา เอกสารจะถูกแยกวิเคราะห์ แบ่งกลุ่ม และจัดทำดัชนีโดยอัตโนมัติ",
|
||||
"newKnowledge.documentsEmptyTitle": "ยังไม่มีเอกสาร",
|
||||
"newKnowledge.documentsErrorDescription": "เราไม่สามารถโหลดเอกสารได้ ลองอีกครั้ง",
|
||||
"newKnowledge.documentsErrorTitle": "ไม่สามารถโหลดเอกสารได้",
|
||||
"newKnowledge.documentsPermissionDescription": "คุณไม่มีสิทธิ์ดูเอกสารในฐานความรู้นี้",
|
||||
"newKnowledge.documentsPermissionTitle": "เอกสารมีข้อจำกัด",
|
||||
"newKnowledge.documentsReindexFailed": "เราไม่สามารถเริ่มการจัดทำดัชนีใหม่ได้ ลองอีกครั้ง",
|
||||
"newKnowledge.documentsReindexPartial": "เอกสาร {{queued}} อยู่ในคิว; {{missing}} ไม่มีอยู่แล้ว",
|
||||
"newKnowledge.documentsReindexStarted": "เริ่มการจัดทำดัชนีใหม่แล้ว",
|
||||
"newKnowledge.documentsSelected": "เลือกเอกสาร {{count}} แล้ว",
|
||||
"newKnowledge.documentsUnavailable": "— เอกสาร",
|
||||
"newKnowledge.downloadDocuments": "ดาวน์โหลด",
|
||||
"newKnowledge.editSource": "แก้ไข",
|
||||
"newKnowledge.emptyChunk": "ส่วนข้อมูลว่าง",
|
||||
"newKnowledge.emptyDescription": "เพิ่มเอกสารของคุณ แล้วเอเจนต์จะอ่านได้โดยตรง โดยไม่ต้องตั้งค่าการทำดัชนี",
|
||||
"newKnowledge.emptyTitle": "วิธีที่ชาญฉลาดกว่าในการสร้างฐานความรู้",
|
||||
"newKnowledge.errorDescription": "ไม่สามารถโหลดฐานความรู้ใหม่ได้ โปรดลองอีกครั้ง",
|
||||
"newKnowledge.errorTitle": "เกิดข้อผิดพลาด",
|
||||
"newKnowledge.fileSize": "ขนาดไฟล์",
|
||||
"newKnowledge.filtersUnavailable": "ตัวกรองนี้จะพร้อมใช้งานเมื่อ KnowledgeFS เปิดเผยข้อมูลเมตา",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl ไม่พร้อมใช้งานในแค็ตตาล็อกผู้ให้บริการสำหรับพื้นที่ทำงานนี้",
|
||||
"newKnowledge.gotIt": "เข้าใจแล้ว",
|
||||
"newKnowledge.guideDescription": "เชื่อมต่อแหล่งข้อมูล แล้วเอกสารจะซิงค์ ทำดัชนีอัตโนมัติ และพร้อมใช้งานโดยไม่ต้องจัดการการตั้งค่า ฐานความรู้เดิมของคุณยังคงทำงานได้ตามปกติ",
|
||||
"newKnowledge.guideTitle": "พบกับฐานความรู้แบบใหม่ ✨",
|
||||
"newKnowledge.illustrationHeadline": "เชื่อมต่อเนื้อหาครั้งเดียว ทุกแอปจะตอบด้วยความรู้ล่าสุด",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "ข้อมูลดัชนี",
|
||||
"newKnowledge.interruptTask": "ขัดจังหวะ",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid 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": "โหลดรุ่นแก้ไขก่อนหน้า",
|
||||
"newKnowledge.loadingSyncPolicy": "กำลังโหลดนโยบายการซิงค์",
|
||||
"newKnowledge.manualUpload": "อัพโหลดด้วยตนเอง",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "ข้อมูลเมตา",
|
||||
"newKnowledge.mimeType": "ประเภทไฟล์",
|
||||
"newKnowledge.moreProviders": "ผู้ให้บริการเพิ่มเติม",
|
||||
"newKnowledge.name": "ชื่อความรู้",
|
||||
"newKnowledge.namePlaceholder": "เช่น คู่มือการสนับสนุนลูกค้า",
|
||||
"newKnowledge.nameRequired": "ป้อนชื่อฐานความรู้",
|
||||
"newKnowledge.new": "แบบใหม่",
|
||||
"newKnowledge.noBackgroundTasks": "ยังไม่มีงานพื้นหลัง",
|
||||
"newKnowledge.noDescription": "ไม่มีคำอธิบาย",
|
||||
"newKnowledge.noMatchingDocuments": "ไม่มีเอกสารที่ตรงกับการค้นหานี้",
|
||||
"newKnowledge.noMatchingSources": "ไม่มีแหล่งที่มาที่ตรงกับการค้นหานี้",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "ฐานความรู้นี้อาจถูกลบไปแล้วหรือคุณอาจไม่สามารถเข้าถึงได้",
|
||||
"newKnowledge.notFoundTitle": "ไม่พบฐานความรู้",
|
||||
"newKnowledge.notionNotConnected": "ยังไม่ได้เชื่อมต่อ Notion",
|
||||
"newKnowledge.notionNotConnectedDescription": "เชื่อมต่อ Notion เพื่อเลือกหน้าและคงการซิงค์ไว้",
|
||||
"newKnowledge.onlineDocuments": "เอกสารออนไลน์",
|
||||
"newKnowledge.onlineDrive": "ไดรฟ์ออนไลน์",
|
||||
"newKnowledge.overview": "ภาพรวม",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "เลือกแล้ว {{count}} รายการ",
|
||||
"newKnowledge.partialDocumentResults": "อาจมีเอกสารที่ตรงกันเพิ่มเติม โหลดเพิ่มเพื่อค้นหาต่อ",
|
||||
"newKnowledge.permission": "การอนุญาต",
|
||||
"newKnowledge.permissionAllMembers": "พื้นที่ทำงาน · สมาชิกทุกคนสามารถดูและแก้ไขได้",
|
||||
"newKnowledge.permissionLoadFailed": "ไม่สามารถตรวจสอบสิทธิ์การแก้ไขของคุณได้ โปรดลองอีกครั้ง",
|
||||
"newKnowledge.permissionOnlyMe": "มีเพียงฉันเท่านั้น",
|
||||
"newKnowledge.permissionRestricted": "สิทธิ์พื้นที่ทำงานของคุณไม่อนุญาตให้เปลี่ยนการตั้งค่านี้",
|
||||
"newKnowledge.permissionUpdateFailed": "สร้างฐานความรู้แล้ว แต่ไม่สามารถยืนยันสิทธิ์ได้ โปรดลองอีกครั้งเพื่อดำเนินการตั้งค่าให้เสร็จ",
|
||||
"newKnowledge.preview": "ตัวอย่าง",
|
||||
"newKnowledge.previewUnavailable": "ตัวอย่างจะพร้อมใช้งานหลังจากแบ็กเอนด์ประมวลผลไฟล์นี้เสร็จสิ้น",
|
||||
"newKnowledge.processDocument": "ประมวลผล {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "ขัดจังหวะ",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "กำลังเตรียมตัว",
|
||||
"newKnowledge.processingTaskState.failed": "ล้มเหลว",
|
||||
"newKnowledge.processingTaskState.queued": "เข้าคิว",
|
||||
"newKnowledge.processingTaskState.retry_wait": "กำลังรอที่จะลองอีกครั้ง",
|
||||
"newKnowledge.processingTaskState.running": "กำลังประมวลผล · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "เสร็จสิ้น",
|
||||
"newKnowledge.processingTaskState.superseded": "เข้ามาแทนที่",
|
||||
"newKnowledge.providerConnected": "เชื่อมต่อ Firecrawl แล้ว",
|
||||
"newKnowledge.providerLoadFailed": "ผู้ให้บริการไม่สามารถโหลดได้",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} ไม่ได้กำหนดค่า",
|
||||
"newKnowledge.providerNotConfiguredDescription": "เพิ่มคีย์ API หรือ URL ปลายทางเพื่อรวบรวมข้อมูลเว็บไซต์ด้วย {{provider}}",
|
||||
"newKnowledge.providerUnavailable": "ผู้ให้บริการนี้ไม่พร้อมใช้งาน",
|
||||
"newKnowledge.quality": "คุณภาพ",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "ขอให้ผู้แก้ไขพื้นที่ทำงานสร้างฐานความรู้แรก",
|
||||
"newKnowledge.refreshConnectionStatus": "รีเฟรชสถานะ",
|
||||
"newKnowledge.refreshingConnection": "สดชื่น...",
|
||||
"newKnowledge.reindexDocument": "จัดทำดัชนีใหม่",
|
||||
"newKnowledge.reindexDocuments": "จัดทำดัชนีใหม่",
|
||||
"newKnowledge.removeSource": "ลบแหล่งสัญญาณ",
|
||||
"newKnowledge.retrievalCount": "จำนวนการเรียกค้น",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "ลองอีกครั้ง",
|
||||
"newKnowledge.retryReindexDocument": "ทำดัชนีใหม่อีกครั้ง",
|
||||
"newKnowledge.retrySyncPolicy": "ลองโหลดนโยบายการซิงค์อีกครั้ง",
|
||||
"newKnowledge.retryTask": "ลองอีกครั้ง",
|
||||
"newKnowledge.revisionState.active": "ใช้งานอยู่",
|
||||
"newKnowledge.revisionState.candidate": "กำลังประมวลผล",
|
||||
"newKnowledge.revisionState.failed": "ล้มเหลว",
|
||||
"newKnowledge.revisionState.superseded": "ก่อนหน้า",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "ส่วนนี้จะพร้อมใช้งานในขั้นตอนการตั้งค่าถัดไป",
|
||||
"newKnowledge.searchDocuments": "ค้นหาเอกสาร",
|
||||
"newKnowledge.searchSources": "แหล่งค้นหา",
|
||||
"newKnowledge.selectAll": "เลือกทั้งหมด",
|
||||
"newKnowledge.selectAllDocuments": "เลือกเอกสารทั้งหมด",
|
||||
"newKnowledge.selectAllSources": "เลือกแหล่งที่มาทั้งหมด",
|
||||
"newKnowledge.selectChunk": "เลือกส่วนข้อมูลเพื่อตรวจสอบเนื้อหา",
|
||||
"newKnowledge.selectFilesAndFolders": "เลือกไฟล์และโฟลเดอร์ที่จะซิงค์",
|
||||
"newKnowledge.skippedFailed": "ข้าม (ล้มเหลว)",
|
||||
"newKnowledge.skippedOffDomain": "ข้าม (นอกโดเมน)",
|
||||
"newKnowledge.sourceActions": "การดำเนินการสำหรับ {{name}}",
|
||||
"newKnowledge.sourceColumn": "ที่มา",
|
||||
"newKnowledge.sourceFilterLabel": "กรองแหล่งที่มาตามสถานะ",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "การตั้งค่าแหล่งที่มาแสดงอย่างครบถ้วน การแก้ไขและดูตัวอย่างต้องใช้เวิร์กโฟลว์แบ็กเอนด์ของแหล่งที่มาฉบับร่าง",
|
||||
"newKnowledge.sourceStatus.active": "แอคทีฟ",
|
||||
"newKnowledge.sourceStatus.disabled": "ปิดใช้งาน",
|
||||
"newKnowledge.sourceStatus.error": "เกิดข้อผิดพลาด",
|
||||
"newKnowledge.sourceStatus.syncing": "กำลังซิงค์",
|
||||
"newKnowledge.sourceType.connector": "แอปที่เชื่อมต่อ",
|
||||
"newKnowledge.sourceType.object-storage": "ไดรฟ์ออนไลน์",
|
||||
"newKnowledge.sourceType.upload": "อัปโหลด",
|
||||
"newKnowledge.sourceType.web": "การรวบรวมข้อมูลเว็บไซต์",
|
||||
"newKnowledge.sourceTypeLabel": "ประเภทแหล่งสัญญาณ",
|
||||
"newKnowledge.sources": "แหล่งที่มา",
|
||||
"newKnowledge.sourcesDescription": "ความรู้มาจากไหน ความสดใหม่ และการซิงค์ครั้งล่าสุดสำเร็จหรือไม่",
|
||||
"newKnowledge.sourcesEmptyDescription": "เชื่อมต่อเว็บไซต์ — Dify ช่วยให้ข้อมูลตรงกันและสดใหม่อยู่เสมอ ดังนั้นการเรียกดูข้อมูลจึงไม่หยุดชะงัก",
|
||||
"newKnowledge.sourcesEmptyTitle": "ยังไม่มีการเชื่อมต่อแหล่งที่มา",
|
||||
"newKnowledge.sourcesErrorDescription": "เราไม่สามารถโหลดแหล่งที่มาได้ ลองอีกครั้ง",
|
||||
"newKnowledge.sourcesErrorTitle": "ไม่สามารถโหลดแหล่งที่มาได้",
|
||||
"newKnowledge.startEmpty": "เริ่มจากว่างเปล่า",
|
||||
"newKnowledge.startEmptyDescription": "สร้างตอนนี้แล้วเพิ่มเนื้อหาภายหลัง",
|
||||
"newKnowledge.startWith": "เริ่มต้นด้วย",
|
||||
"newKnowledge.startWithHelp": "คุณสามารถเพิ่มแหล่งที่มาหรือเอกสารเพิ่มเติมได้ตลอดเวลา",
|
||||
"newKnowledge.statusColumn": "สถานะ",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "ซิงค์เลย",
|
||||
"newKnowledge.syncPolicy": "นโยบายการซิงค์",
|
||||
"newKnowledge.syncPolicyColumn": "นโยบายการซิงค์",
|
||||
"newKnowledge.syncPolicyCustom": "ช่วงเวลาแบบกำหนดเอง",
|
||||
"newKnowledge.syncPolicyDaily": "ทุก 24 ชั่วโมง",
|
||||
"newKnowledge.syncPolicyLoadFailed": "ไม่สามารถโหลดนโยบายการซิงค์ได้",
|
||||
"newKnowledge.syncPolicyManual": "ด้วยตนเองเท่านั้น",
|
||||
"newKnowledge.syncPolicyProvider": "ใช้กำหนดการของผู้ให้บริการ",
|
||||
"newKnowledge.tags": "แท็ก",
|
||||
"newKnowledge.taskActionFailed": "เราไม่สามารถอัปเดตงานนี้ได้ รีเฟรชแล้วลองอีกครั้ง",
|
||||
"newKnowledge.taskAttentionClear": "ไม่มีงานพื้นหลังที่ต้องให้ความสนใจ",
|
||||
"newKnowledge.taskAttentionCount": "งานพื้นหลัง {{count}} จำเป็นต้องได้รับการดูแล",
|
||||
"newKnowledge.taskAttentionErrorCount": "งานเบื้องหลัง {{count}} ต้องการความสนใจ รวมถึงงานที่ล้มเหลวด้วย",
|
||||
"newKnowledge.taskFailedNotification": "การประมวลผลเอกสารล้มเหลว — ลองอีกครั้งจากงาน",
|
||||
"newKnowledge.taskHistoryIncomplete": "มีประวัติงานเพิ่มเติม จำนวนงานที่ต้องตรวจสอบยังไม่ครบถ้วน",
|
||||
"newKnowledge.tasks": "งาน",
|
||||
"newKnowledge.tasksErrorDescription": "เราไม่สามารถโหลดงานพื้นหลังได้ ลองอีกครั้ง",
|
||||
"newKnowledge.tasksWithAttention": "งาน {{count}} ต้องการความสนใจ",
|
||||
"newKnowledge.tokenCount": "โทเค็น",
|
||||
"newKnowledge.unavailableDescription": "ขณะนี้ KnowledgeFS ไม่พร้อมใช้งานสำหรับพื้นที่ทำงานนี้",
|
||||
"newKnowledge.unavailableTitle": "ความรู้แบบใหม่ไม่พร้อมใช้งาน",
|
||||
"newKnowledge.updated": "อัปเดตเมื่อ {{date}}",
|
||||
"newKnowledge.updatedAt": "อัปเดตล่าสุด",
|
||||
"newKnowledge.updatedColumn": "อัปเดตแล้ว",
|
||||
"newKnowledge.uploadCharactersUnavailable": "อักขระ: —",
|
||||
"newKnowledge.uploadDocuments": "อัพโหลดเอกสาร",
|
||||
"newKnowledge.uploadFiles": "อัปโหลดไฟล์",
|
||||
"newKnowledge.uploadFilesDescription": "เพิ่มไฟล์จากคอมพิวเตอร์ ไฟล์จะถูกทำดัชนีทันทีที่สร้าง",
|
||||
"newKnowledge.uploadingFiles": "กำลังอัปโหลด…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "มุมมองความรู้",
|
||||
"newKnowledge.websiteCrawl": "การรวบรวมข้อมูลเว็บไซต์",
|
||||
"noExternalKnowledge": "ยังไม่มี External Knowledge API คลิกที่นี่เพื่อสร้าง",
|
||||
"parentMode.fullDoc": "เอกสารฉบับเต็ม",
|
||||
"parentMode.paragraph": "วรรค",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Yüksek kaliteli ve ekonomik bilgi tabanlarının karışımı için Yeniden Sıralama modeli gereklidir.",
|
||||
"mixtureInternalAndExternalTip": "Yeniden Sıralama modeli, iç ve dış bilgilerin karışımı için gereklidir.",
|
||||
"multimodal": "Çok Modlu",
|
||||
"newKnowledge.actionsColumn": "Eylemler",
|
||||
"newKnowledge.addDocument": "Belge ekle",
|
||||
"newKnowledge.addSource": "Kaynak ekle",
|
||||
"newKnowledge.addSourceDescription": "Bir kaynağı bu bilgi tabanına bağlayın; otomatik olarak senkronize edilir ve güncel kalır.",
|
||||
"newKnowledge.addSourceFailed": "Bu kaynak eklenemedi. Ayarları gözden geçirip tekrar deneyin.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Tüm durum",
|
||||
"newKnowledge.allSources": "Tüm kaynaklar",
|
||||
"newKnowledge.apiAgentAccess": "API ve Ajan Erişimi",
|
||||
"newKnowledge.appsUnavailable": "— uygulama",
|
||||
"newKnowledge.authKind.api-key": "API anahtarı",
|
||||
"newKnowledge.authKind.endpoint": "Uç nokta",
|
||||
"newKnowledge.authenticationMethod": "Kimlik doğrulama yöntemi",
|
||||
"newKnowledge.averageChunkLength": "Ortalama parça uzunluğu",
|
||||
"newKnowledge.backToList": "Bilgi tabanlarına geri dön",
|
||||
"newKnowledge.backgroundTasks": "Arka plan görevleri",
|
||||
"newKnowledge.backgroundTasksDescription": "Belge ekleme, kaynak senkronizasyonu ve işleri yeniden indeksleme.",
|
||||
"newKnowledge.booleanFalse": "Hayır",
|
||||
"newKnowledge.booleanTrue": "Evet",
|
||||
"newKnowledge.bulkDocumentActions": "Toplu belge işlemleri",
|
||||
"newKnowledge.cancelAddSource": "İptal et",
|
||||
"newKnowledge.cardType": "Bilgi alanı",
|
||||
"newKnowledge.characterCount": "Karakterler",
|
||||
"newKnowledge.checkReindexStatus": "Yeniden indeksleme durumunu kontrol et",
|
||||
"newKnowledge.chunkCount": "Parçalar",
|
||||
"newKnowledge.chunkHeading": "Parça {{position}}",
|
||||
"newKnowledge.chunkLocation": "Bu revizyondaki {{position}}. konum",
|
||||
"newKnowledge.clearDocumentSelection": "Belge seçimini temizle",
|
||||
"newKnowledge.configureProvider": "Firecrawl'ı Yapılandır",
|
||||
"newKnowledge.connectNotion": "Notion’a bağlan",
|
||||
"newKnowledge.connectProvider": "Firecrawl'a bağlanın",
|
||||
"newKnowledge.connectProviderGeneric": "Sağlayıcıya bağlan",
|
||||
"newKnowledge.connectSource": "Kaynak bağla",
|
||||
"newKnowledge.connectSourceDescription": "İçerik Notion, web siteleri ve diğer hizmetlerle senkronize kalır.",
|
||||
"newKnowledge.connectingProvider": "Bağlanıyor…",
|
||||
"newKnowledge.connectionFailed": "Firecrawl'a bağlanamadık. Kimlik bilgilerini tekrar girin ve tekrar deneyin.",
|
||||
"newKnowledge.connectionNeedsAttention": "Firecrawl bağlantısına dikkat edilmesi gerekiyor",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Bir web sitesi kaynağı eklemeden önce bağlantıyı yenileyin.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl bağlanıyor",
|
||||
"newKnowledge.connectionRefreshFailed": "Bu bağlantıyı yenileyemedik. Tekrar deneyin.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Görev durumunu kontrol etmeye devam et",
|
||||
"newKnowledge.crawlAndPreview": "Tara ve önizle",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Başlatılmadı",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Tarama önizlemesi",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Web sitesi tarama yapılandırması, tarama iş akışı kullanılabilir olana kadar devre dışı bırakılır.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl hazır",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Bu bilgi tabanını oluşturamadık. Erişiminizi kontrol edip tekrar deneyin.",
|
||||
"newKnowledge.createIllustrationTitle": "İçeriğinizi bir kez bağlayın — her uygulama en güncel bilgilerle yanıt versin.",
|
||||
"newKnowledge.createTitle": "Bilgi Yaratın",
|
||||
"newKnowledge.createdAt": "Oluşturuldu",
|
||||
"newKnowledge.creators": "Oluşturanlar",
|
||||
"newKnowledge.customIntervalHours": "Özel aralık (saat)",
|
||||
"newKnowledge.customIntervalInvalid": "1 ile 720 saat arasında bir tam sayı girin.",
|
||||
"newKnowledge.deleteDocuments": "Sil…",
|
||||
"newKnowledge.description": "Açıklama",
|
||||
"newKnowledge.descriptionHelp": "Açık bir açıklama, uygulamaların bu Bilgiyi ne zaman arayacaklarına karar vermesine yardımcı olur.",
|
||||
"newKnowledge.descriptionPlaceholder": "örneğin Geri ödeme politikası, gönderimle ilgili SSS'ler ve üst kademeye yükseltme adımları — müşteri desteği soruları için kullanın",
|
||||
"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.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.",
|
||||
"newKnowledge.documentChunksLoadError": "Bu revizyonun parçaları yüklenemedi.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Daha fazla parça yüklenemedi. Tekrar deneyin.",
|
||||
"newKnowledge.documentColumn": "Belge",
|
||||
"newKnowledge.documentContentIncomplete": "Belgenin kalan içeriği yükleniyor…",
|
||||
"newKnowledge.documentContents": "İçerik",
|
||||
"newKnowledge.documentFacts": "Belge ayrıntıları",
|
||||
"newKnowledge.documentFilterLabel": "Belgeleri duruma göre filtreleyin",
|
||||
"newKnowledge.documentLoadErrorDescription": "Bu belge yüklenemedi. Tekrar deneyin.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Belge yüklenemedi",
|
||||
"newKnowledge.documentNotFoundDescription": "Bu belge kaldırılmış olabilir veya erişiminiz olmayabilir.",
|
||||
"newKnowledge.documentNotFoundTitle": "Belge bulunamadı",
|
||||
"newKnowledge.documentOverviewDescription": "Dizinlenen sürümün tamamını okuyun, kaynak ve dizin ayrıntılarını inceleyin.",
|
||||
"newKnowledge.documentPermissionRestricted": "Çalışma alanı izinleriniz belge veya görev işlemlerine izin vermiyor.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "Yeniden indeksleme kabul edildi ancak görev durumu gecikiyor. Tekrar göndermeden önce kontrol edin.",
|
||||
"newKnowledge.documentReindexFailed": "Yeniden dizinleme başarısız oldu. Son hazır revizyon kullanılabilir durumda.",
|
||||
"newKnowledge.documentReindexProgress": "Yeniden dizinleniyor · %{{progress}}",
|
||||
"newKnowledge.documentRevision": "Revizyon",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Bu belgenin henüz hazır bir revizyonu yok. İşlem tamamlandıktan sonra tekrar kontrol edin.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Kullanılabilir revizyon yok",
|
||||
"newKnowledge.documentRevisionsLoadError": "Revizyon geçmişi yüklenemedi. Geçerli revizyon kullanılabilir durumda.",
|
||||
"newKnowledge.documentStatus.disabled": "Devre dışı",
|
||||
"newKnowledge.documentStatus.failed": "Başarısız",
|
||||
"newKnowledge.documentStatus.processing": "İşleme",
|
||||
"newKnowledge.documentStatus.queued": "Sıraya alındı",
|
||||
"newKnowledge.documentStatus.ready": "Hazır",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Daha fazla görev geçmişi var. Bu belgenin durumunu bulmak için kontrol etmeye devam edin.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "toplu iş boyutu sınırı aşıldı",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "dosya sayısı sınırı aşıldı",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "dosya çok büyük",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "desteklenmeyen veya geçersiz dosya türü",
|
||||
"newKnowledge.documentUploadExclusion.more": "ve {{count}} tane daha",
|
||||
"newKnowledge.documentUploadExclusion.processing": "işleme başarısız oldu",
|
||||
"newKnowledge.documentUploadExclusion.quota": "çalışma alanı kotası aşıldı",
|
||||
"newKnowledge.documentUploadExclusion.target": "belge hedefi artık geçerli değil",
|
||||
"newKnowledge.documentUploadFailed": "Bu belgeleri yükleyemedik. Tekrar deneyin.",
|
||||
"newKnowledge.documentUploadPartial": "{{accepted}} belgenin işlenmesi başladı; {{excluded}} belge eklenemedi: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Hiçbir belge kabul edilmedi: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Evrak işlemleri başlatıldı.",
|
||||
"newKnowledge.documents": "Belgeler",
|
||||
"newKnowledge.documentsDescription": "Bilgi varlıkları ve revizyonları — her belgenin nereden geldiği ve en son revizyonun indekslenip indekslenmediği.",
|
||||
"newKnowledge.documentsDropHint": "veya yüklemek için PDF, DOCX, Markdown, HTML, XLSX veya TXT'yi bırakın",
|
||||
"newKnowledge.documentsEmptyDescription": "Dosyaları buraya yükleyin veya Kaynaklar sekmesinde bir kaynağa bağlanın; belgeler otomatik olarak ayrıştırılır, parçalanır ve dizine eklenir.",
|
||||
"newKnowledge.documentsEmptyTitle": "Henüz belge yok",
|
||||
"newKnowledge.documentsErrorDescription": "Belgeleri yükleyemedik. Tekrar deneyin.",
|
||||
"newKnowledge.documentsErrorTitle": "Dokümanlar yüklenemedi",
|
||||
"newKnowledge.documentsPermissionDescription": "Bu bilgi tabanındaki belgeleri görüntüleme izniniz yok.",
|
||||
"newKnowledge.documentsPermissionTitle": "Belgeler kısıtlı",
|
||||
"newKnowledge.documentsReindexFailed": "Yeniden indekslemeye başlayamadık. Tekrar deneyin.",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} belgeleri sıraya alındı; {{missing}} artık mevcut değil.",
|
||||
"newKnowledge.documentsReindexStarted": "Yeniden indeksleme başladı.",
|
||||
"newKnowledge.documentsSelected": "{{count}} belgeleri seçildi",
|
||||
"newKnowledge.documentsUnavailable": "— belge",
|
||||
"newKnowledge.downloadDocuments": "İndir",
|
||||
"newKnowledge.editSource": "Düzenle",
|
||||
"newKnowledge.emptyChunk": "Boş parça",
|
||||
"newKnowledge.emptyDescription": "Belgelerinizi ekleyin; ajanlar indeksleme yapılandırmadan belgeleri doğrudan okuyabilsin.",
|
||||
"newKnowledge.emptyTitle": "Bilgi tabanları oluşturmanın daha akıllı yolu",
|
||||
"newKnowledge.errorDescription": "Yeni bilgi tabanlarınız yüklenemedi. Tekrar deneyin.",
|
||||
"newKnowledge.errorTitle": "Bir şeyler yanlış gitti",
|
||||
"newKnowledge.fileSize": "Dosya boyutu",
|
||||
"newKnowledge.filtersUnavailable": "KnowledgeFS meta verilerini sunduğunda bu filtre kullanılabilir olacak.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl bu çalışma alanının sağlayıcı kataloğunda mevcut değil.",
|
||||
"newKnowledge.gotIt": "Anladım",
|
||||
"newKnowledge.guideDescription": "Bir kaynak bağladığınızda belgeleri senkronize kalır, otomatik olarak indekslenir ve yönetilecek bir kurulum olmadan kullanıma hazır olur. Mevcut bilgi tabanlarınız olduğu gibi çalışmaya devam eder.",
|
||||
"newKnowledge.guideTitle": "Yeni Bilgi Tabanı ile tanışın ✨",
|
||||
"newKnowledge.illustrationHeadline": "İçeriğinizi bir kez bağlayın — her uygulama en güncel bilgilerle yanıt versin.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Dizin bilgileri",
|
||||
"newKnowledge.interruptTask": "Kesinti",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Eşitleme politikası yükleniyor",
|
||||
"newKnowledge.manualUpload": "Manuel yükleme",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Meta Veriler",
|
||||
"newKnowledge.mimeType": "Dosya türü",
|
||||
"newKnowledge.moreProviders": "Daha fazla sağlayıcı",
|
||||
"newKnowledge.name": "Bilgi adı",
|
||||
"newKnowledge.namePlaceholder": "örneğin Müşteri Desteği El Kitabı",
|
||||
"newKnowledge.nameRequired": "Bir bilgi tabanı adı girin.",
|
||||
"newKnowledge.new": "Yeni",
|
||||
"newKnowledge.noBackgroundTasks": "Henüz arka plan görevi yok.",
|
||||
"newKnowledge.noDescription": "Açıklama yok",
|
||||
"newKnowledge.noMatchingDocuments": "Bu aramayla eşleşen belge yok.",
|
||||
"newKnowledge.noMatchingSources": "Bu aramayla eşleşen kaynak yok.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Bu bilgi tabanı silinmiş olabilir veya erişiminiz olmayabilir.",
|
||||
"newKnowledge.notFoundTitle": "Bilgi tabanı bulunamadı",
|
||||
"newKnowledge.notionNotConnected": "Notion bağlı değil",
|
||||
"newKnowledge.notionNotConnectedDescription": "Sayfaları seçmek ve eşitlenmiş tutmak için Notion’a bağlanın.",
|
||||
"newKnowledge.onlineDocuments": "Çevrimiçi belgeler",
|
||||
"newKnowledge.onlineDrive": "Çevrimiçi sürücü",
|
||||
"newKnowledge.overview": "Genel bakış",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "{{count}} seçildi",
|
||||
"newKnowledge.partialDocumentResults": "Eşleşen başka belgeler olabilir. Aramaya devam etmek için daha fazla yükleyin.",
|
||||
"newKnowledge.permission": "İzin",
|
||||
"newKnowledge.permissionAllMembers": "Çalışma alanı · tüm üyeler görüntüleyebilir ve düzenleyebilir",
|
||||
"newKnowledge.permissionLoadFailed": "Düzenleme izinleriniz doğrulanamadı. Tekrar deneyin.",
|
||||
"newKnowledge.permissionOnlyMe": "Sadece ben",
|
||||
"newKnowledge.permissionRestricted": "Çalışma alanı izinleriniz bu ayarın değiştirilmesine izin vermiyor.",
|
||||
"newKnowledge.permissionUpdateFailed": "Bilgi tabanı oluşturuldu ancak izni doğrulanamadı. Kurulumu tamamlamak için yeniden deneyin.",
|
||||
"newKnowledge.preview": "Önizleme",
|
||||
"newKnowledge.previewUnavailable": "Arka uç bu dosyayı işlemeyi tamamladığında önizleme kullanılabilir.",
|
||||
"newKnowledge.processDocument": "Süreç {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Kesintiye uğradı",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Hazırlanıyor",
|
||||
"newKnowledge.processingTaskState.failed": "Başarısız",
|
||||
"newKnowledge.processingTaskState.queued": "Sıraya alındı",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Yeniden denenmeyi bekliyor",
|
||||
"newKnowledge.processingTaskState.running": "İşleniyor · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Tamamlandı",
|
||||
"newKnowledge.processingTaskState.superseded": "Değiştirildi",
|
||||
"newKnowledge.providerConnected": "Firecrawl bağlandı",
|
||||
"newKnowledge.providerLoadFailed": "Sağlayıcılar yüklenemedi",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} yapılandırılmadı",
|
||||
"newKnowledge.providerNotConfiguredDescription": "{{provider}} ile web sitelerini taramak için bir API anahtarı veya uç nokta URL'si ekleyin.",
|
||||
"newKnowledge.providerUnavailable": "Bu sağlayıcı kullanılamıyor.",
|
||||
"newKnowledge.quality": "Kalite",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "İlk bilgi tabanını oluşturması için bir çalışma alanı editörüne başvurun.",
|
||||
"newKnowledge.refreshConnectionStatus": "Durumu yenile",
|
||||
"newKnowledge.refreshingConnection": "Yenileniyor…",
|
||||
"newKnowledge.reindexDocument": "Yeniden dizinle",
|
||||
"newKnowledge.reindexDocuments": "Yeniden indeksle",
|
||||
"newKnowledge.removeSource": "Kaynağı kaldır",
|
||||
"newKnowledge.retrievalCount": "Getirme sayısı",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Tekrar dene",
|
||||
"newKnowledge.retryReindexDocument": "Yeniden indeksle",
|
||||
"newKnowledge.retrySyncPolicy": "Eşitleme politikasını yeniden dene",
|
||||
"newKnowledge.retryTask": "Yeniden dene",
|
||||
"newKnowledge.revisionState.active": "Etkin",
|
||||
"newKnowledge.revisionState.candidate": "İşleniyor",
|
||||
"newKnowledge.revisionState.failed": "Başarısız",
|
||||
"newKnowledge.revisionState.superseded": "Önceki",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Bu bölüm bir sonraki kurulum adımında kullanılabilir hale gelecektir.",
|
||||
"newKnowledge.searchDocuments": "Belgeleri arayın",
|
||||
"newKnowledge.searchSources": "Kaynakları ara",
|
||||
"newKnowledge.selectAll": "Tümünü seç",
|
||||
"newKnowledge.selectAllDocuments": "Tüm belgeleri seç",
|
||||
"newKnowledge.selectAllSources": "Tüm kaynakları seç",
|
||||
"newKnowledge.selectChunk": "İçeriğini incelemek için bir parça seçin.",
|
||||
"newKnowledge.selectFilesAndFolders": "Senkronize edilecek dosya ve klasörleri seçin",
|
||||
"newKnowledge.skippedFailed": "Atlandı (başarısız)",
|
||||
"newKnowledge.skippedOffDomain": "Atlandı (alan adı dışında)",
|
||||
"newKnowledge.sourceActions": "{{name}} için eylemler",
|
||||
"newKnowledge.sourceColumn": "Kaynak",
|
||||
"newKnowledge.sourceFilterLabel": "Kaynakları duruma göre filtrele",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "Kaynak kurulumu eksiksiz gösterilir; düzenleme ve önizleme işlemleri taslak kaynak arka uç akışını gerektirir.",
|
||||
"newKnowledge.sourceStatus.active": "Aktif",
|
||||
"newKnowledge.sourceStatus.disabled": "Devre Dışı",
|
||||
"newKnowledge.sourceStatus.error": "Hata",
|
||||
"newKnowledge.sourceStatus.syncing": "Senkronizasyon",
|
||||
"newKnowledge.sourceType.connector": "Bağlı uygulama",
|
||||
"newKnowledge.sourceType.object-storage": "Çevrimiçi sürücü",
|
||||
"newKnowledge.sourceType.upload": "Yükle",
|
||||
"newKnowledge.sourceType.web": "Web sitesi taraması",
|
||||
"newKnowledge.sourceTypeLabel": "Kaynak türü",
|
||||
"newKnowledge.sources": "Kaynaklar",
|
||||
"newKnowledge.sourcesDescription": "Bilginin nereden geldiği, ne kadar yeni olduğu ve son senkronizasyonun başarılı olup olmadığı.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Bir web sitesine bağlanın — Dify, web sitesini senkronize ve güncel tutar, böylece erişim hiçbir zaman kesintiye uğramaz.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Henüz bağlı kaynak yok",
|
||||
"newKnowledge.sourcesErrorDescription": "Kaynakları yükleyemedik. Tekrar deneyin.",
|
||||
"newKnowledge.sourcesErrorTitle": "Kaynaklar yüklenemedi",
|
||||
"newKnowledge.startEmpty": "Boş başla",
|
||||
"newKnowledge.startEmptyDescription": "Şimdi oluşturun, içeriği daha sonra ekleyin.",
|
||||
"newKnowledge.startWith": "Şununla başla:",
|
||||
"newKnowledge.startWithHelp": "İstediğiniz zaman daha fazla Kaynak veya Belge ekleyebilirsiniz.",
|
||||
"newKnowledge.statusColumn": "Durum",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Şimdi senkronize et",
|
||||
"newKnowledge.syncPolicy": "Eşitleme politikası",
|
||||
"newKnowledge.syncPolicyColumn": "Senkronizasyon politikası",
|
||||
"newKnowledge.syncPolicyCustom": "Özel aralık",
|
||||
"newKnowledge.syncPolicyDaily": "Her 24 saatte bir",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Eşitleme politikası yüklenemedi.",
|
||||
"newKnowledge.syncPolicyManual": "Yalnızca manuel",
|
||||
"newKnowledge.syncPolicyProvider": "Sağlayıcı zamanlamasını kullan",
|
||||
"newKnowledge.tags": "Etiketler",
|
||||
"newKnowledge.taskActionFailed": "Bu görevi güncelleyemedik. Yenileyin ve tekrar deneyin.",
|
||||
"newKnowledge.taskAttentionClear": "Hiçbir arka plan görevinin dikkat edilmesi gerekmez.",
|
||||
"newKnowledge.taskAttentionCount": "{{count}} arka plan görevlerine dikkat edilmesi gerekiyor.",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}} Başarısız görevler de dahil olmak üzere arka plan görevlerine dikkat edilmesi gerekir.",
|
||||
"newKnowledge.taskFailedNotification": "Belge işleme başarısız oldu — Görevler'den yeniden deneyin",
|
||||
"newKnowledge.taskHistoryIncomplete": "Daha fazla görev geçmişi mevcut; ilgilenilmesi gereken görev sayısı henüz tamamlanmadı.",
|
||||
"newKnowledge.tasks": "Görevler",
|
||||
"newKnowledge.tasksErrorDescription": "Arka plan görevlerini yükleyemedik. Tekrar deneyin.",
|
||||
"newKnowledge.tasksWithAttention": "Görevler, {{count}} tanesiyle ilgilenilmesi gerekiyor",
|
||||
"newKnowledge.tokenCount": "Jetonlar",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS şu anda bu çalışma alanında kullanılamıyor.",
|
||||
"newKnowledge.unavailableTitle": "Yeni Bilgi kullanılamıyor",
|
||||
"newKnowledge.updated": "{{date}} tarihinde güncellendi",
|
||||
"newKnowledge.updatedAt": "Son güncelleme",
|
||||
"newKnowledge.updatedColumn": "Güncellendi",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Karakterler: —",
|
||||
"newKnowledge.uploadDocuments": "Belgeleri yükleyin",
|
||||
"newKnowledge.uploadFiles": "Dosya yükle",
|
||||
"newKnowledge.uploadFilesDescription": "Bilgisayarınızdan dosya ekleyin; oluşturduğunuz anda indekslenirler.",
|
||||
"newKnowledge.uploadingFiles": "Yükleniyor…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Bilgi görünümü",
|
||||
"newKnowledge.websiteCrawl": "Web sitesi taraması",
|
||||
"noExternalKnowledge": "Henüz Harici Bilgi API'si yok, oluşturmak için buraya tıklayın",
|
||||
"parentMode.fullDoc": "Tam doküman",
|
||||
"parentMode.paragraph": "Paragraf",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Модель перерангування потрібна для суміші високоякісних та економічних баз знань.",
|
||||
"mixtureInternalAndExternalTip": "Модель Rerank необхідна для поєднання внутрішніх і зовнішніх знань.",
|
||||
"multimodal": "Мультимодальний",
|
||||
"newKnowledge.actionsColumn": "Дії",
|
||||
"newKnowledge.addDocument": "Додати документ",
|
||||
"newKnowledge.addSource": "Додати джерело",
|
||||
"newKnowledge.addSourceDescription": "Підключіть джерело до цієї бази знань — вона автоматично синхронізується та залишається актуальною.",
|
||||
"newKnowledge.addSourceFailed": "Не вдалося додати це джерело. Перевірте налаштування та повторіть спробу.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Всі статуси",
|
||||
"newKnowledge.allSources": "Усі джерела",
|
||||
"newKnowledge.apiAgentAccess": "Доступ до API та агентів",
|
||||
"newKnowledge.appsUnavailable": "— застосунків",
|
||||
"newKnowledge.authKind.api-key": "API ключ",
|
||||
"newKnowledge.authKind.endpoint": "Кінцева точка",
|
||||
"newKnowledge.authenticationMethod": "Метод автентифікації",
|
||||
"newKnowledge.averageChunkLength": "Середня довжина фрагмента",
|
||||
"newKnowledge.backToList": "Назад до баз знань",
|
||||
"newKnowledge.backgroundTasks": "Фонові завдання",
|
||||
"newKnowledge.backgroundTasksDescription": "Додавання документів, синхронізація джерела та повторне індексування завдань.",
|
||||
"newKnowledge.booleanFalse": "Ні",
|
||||
"newKnowledge.booleanTrue": "Так",
|
||||
"newKnowledge.bulkDocumentActions": "Масові дії з документами",
|
||||
"newKnowledge.cancelAddSource": "Скасувати",
|
||||
"newKnowledge.cardType": "Простір знань",
|
||||
"newKnowledge.characterCount": "Символи",
|
||||
"newKnowledge.checkReindexStatus": "Перевірити стан переіндексації",
|
||||
"newKnowledge.chunkCount": "Фрагменти",
|
||||
"newKnowledge.chunkHeading": "Фрагмент {{position}}",
|
||||
"newKnowledge.chunkLocation": "Позиція {{position}} у цій редакції",
|
||||
"newKnowledge.clearDocumentSelection": "Очистити вибір документів",
|
||||
"newKnowledge.configureProvider": "Налаштувати Firecrawl",
|
||||
"newKnowledge.connectNotion": "Підключити Notion",
|
||||
"newKnowledge.connectProvider": "Підключіть Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Підключити провайдера",
|
||||
"newKnowledge.connectSource": "Підключити джерело",
|
||||
"newKnowledge.connectSourceDescription": "Вміст залишається синхронізованим із Notion, вебсайтами та іншими сервісами.",
|
||||
"newKnowledge.connectingProvider": "Підключення…",
|
||||
"newKnowledge.connectionFailed": "Не вдалося підключити Firecrawl. Повторно введіть облікові дані та повторіть спробу.",
|
||||
"newKnowledge.connectionNeedsAttention": "Підключення Firecraw потребує уваги",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Оновіть з’єднання, перш ніж додавати джерело веб-сайту.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl підключається",
|
||||
"newKnowledge.connectionRefreshFailed": "Нам не вдалося оновити це з’єднання. Спробуйте знову.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Продовжити перевірку стану",
|
||||
"newKnowledge.crawlAndPreview": "Сканування та попередній перегляд",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Не розпочато",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Попередній перегляд обходу",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Конфігурацію сканування веб-сайту вимкнено, доки робочий процес сканування не стане доступним.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl готовий",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Ми не змогли створити цю базу знань. Перевірте свій доступ і повторіть спробу.",
|
||||
"newKnowledge.createIllustrationTitle": "Підключіть вміст один раз — кожен застосунок відповідатиме на основі найсвіжіших знань.",
|
||||
"newKnowledge.createTitle": "Створюйте знання",
|
||||
"newKnowledge.createdAt": "Створено",
|
||||
"newKnowledge.creators": "Автори",
|
||||
"newKnowledge.customIntervalHours": "Власний інтервал (години)",
|
||||
"newKnowledge.customIntervalInvalid": "Введіть ціле число від 1 до 720 годин.",
|
||||
"newKnowledge.deleteDocuments": "Видалити…",
|
||||
"newKnowledge.description": "опис",
|
||||
"newKnowledge.descriptionHelp": "Чіткий опис допомагає програмам вирішити, коли шукати ці знання.",
|
||||
"newKnowledge.descriptionPlaceholder": "напр. Політика відшкодування, поширені запитання щодо доставки та етапи ескалації — використовуйте для запитань служби підтримки клієнтів",
|
||||
"newKnowledge.detailErrorDescription": "Нам не вдалося завантажити цю базу знань. Спробуйте знову.",
|
||||
"newKnowledge.detailErrorTitle": "Щось пішло не так",
|
||||
"newKnowledge.disableSource": "Вимкнути",
|
||||
"newKnowledge.discardDraftConfirm": "Відхилити чернетку",
|
||||
"newKnowledge.discardDraftDescription": "Зміни в цій чернетці бази знань буде втрачено.",
|
||||
"newKnowledge.discardDraftTitle": "Відхилити цю чернетку бази знань?",
|
||||
"newKnowledge.discardSourceChanges": "Відхилити зміни джерела?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "Відхилити зміни",
|
||||
"newKnowledge.discardSourceChangesDescription": "Попередній перегляд сканування, вибір сторінок і налаштування синхронізації буде втрачено.",
|
||||
"newKnowledge.documentActions": "Дії для {{name}}",
|
||||
"newKnowledge.documentActionsUnavailable": "Дії з документами поки що недоступні для цієї бази знань.",
|
||||
"newKnowledge.documentChunksEmpty": "Для цієї редакції ще немає доступних фрагментів.",
|
||||
"newKnowledge.documentChunksLoadError": "Не вдалося завантажити фрагменти цієї редакції.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Не вдалося завантажити додаткові фрагменти. Спробуйте ще раз.",
|
||||
"newKnowledge.documentColumn": "документ",
|
||||
"newKnowledge.documentContentIncomplete": "Завантажується решта вмісту документа…",
|
||||
"newKnowledge.documentContents": "Зміст",
|
||||
"newKnowledge.documentFacts": "Відомості про документ",
|
||||
"newKnowledge.documentFilterLabel": "Фільтр документів за статусом",
|
||||
"newKnowledge.documentLoadErrorDescription": "Не вдалося завантажити цей документ. Спробуйте ще раз.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Не вдалося завантажити документ",
|
||||
"newKnowledge.documentNotFoundDescription": "Можливо, документ видалено або у вас немає доступу.",
|
||||
"newKnowledge.documentNotFoundTitle": "Документ не знайдено",
|
||||
"newKnowledge.documentOverviewDescription": "Прочитайте всю проіндексовану редакцію та перегляньте відомості про джерело й індекс.",
|
||||
"newKnowledge.documentPermissionRestricted": "Дозволи вашого робочого простору не дозволяють виконувати дії з документами або завданнями.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "Переіндексацію прийнято, але стан завдання затримується. Перевірте його перед повторним надсиланням.",
|
||||
"newKnowledge.documentReindexFailed": "Повторне індексування завершилося помилкою. Остання готова редакція залишається доступною.",
|
||||
"newKnowledge.documentReindexProgress": "Повторне індексування · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Редакція",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Цей документ ще не має готової редакції. Перевірте після завершення обробки.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Немає доступної редакції",
|
||||
"newKnowledge.documentRevisionsLoadError": "Не вдалося завантажити історію редакцій. Поточна редакція залишається доступною.",
|
||||
"newKnowledge.documentStatus.disabled": "Вимкнено",
|
||||
"newKnowledge.documentStatus.failed": "Не вдалося",
|
||||
"newKnowledge.documentStatus.processing": "Обробка",
|
||||
"newKnowledge.documentStatus.queued": "У черзі",
|
||||
"newKnowledge.documentStatus.ready": "Готовий",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Доступна додаткова історія завдань. Продовжте перевірку, щоб знайти стан документа.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "перевищено обмеження розміру пакета",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "перевищено обмеження кількості файлів",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "файл завеликий",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "непідтримуваний або недійсний тип файлу",
|
||||
"newKnowledge.documentUploadExclusion.more": "і ще {{count}}",
|
||||
"newKnowledge.documentUploadExclusion.processing": "помилка обробки",
|
||||
"newKnowledge.documentUploadExclusion.quota": "перевищено квоту робочої області",
|
||||
"newKnowledge.documentUploadExclusion.target": "ціль документа більше недійсна",
|
||||
"newKnowledge.documentUploadFailed": "Нам не вдалося завантажити ці документи. Спробуйте знову.",
|
||||
"newKnowledge.documentUploadPartial": "Розпочато обробку {{accepted}} документів; не вдалося додати {{excluded}}: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Жодного документа не прийнято: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Розпочато обробку документів.",
|
||||
"newKnowledge.documents": "Документи",
|
||||
"newKnowledge.documentsDescription": "Бази знань та їх редакції — звідки походить кожен документ і чи проіндексовано останню редакцію.",
|
||||
"newKnowledge.documentsDropHint": "або відпустіть PDF, DOCX, Markdown, HTML, XLSX або TXT для завантаження",
|
||||
"newKnowledge.documentsEmptyDescription": "Завантажте файли сюди або підключіть джерело на вкладці «Джерела» — документи аналізуються, розбиваються на частини та індексуються автоматично.",
|
||||
"newKnowledge.documentsEmptyTitle": "Документів ще немає",
|
||||
"newKnowledge.documentsErrorDescription": "Не вдалося завантажити документи. Спробуйте знову.",
|
||||
"newKnowledge.documentsErrorTitle": "Не вдалося завантажити документи",
|
||||
"newKnowledge.documentsPermissionDescription": "Ви не маєте дозволу переглядати документи в цій базі знань.",
|
||||
"newKnowledge.documentsPermissionTitle": "Документи обмежені",
|
||||
"newKnowledge.documentsReindexFailed": "Не вдалося розпочати повторне індексування. Спробуйте знову.",
|
||||
"newKnowledge.documentsReindexPartial": "{{queued}} документи в черзі; {{missing}} більше не існує.",
|
||||
"newKnowledge.documentsReindexStarted": "Розпочато переіндексацію.",
|
||||
"newKnowledge.documentsSelected": "Вибрані документи {{count}}",
|
||||
"newKnowledge.documentsUnavailable": "— документів",
|
||||
"newKnowledge.downloadDocuments": "Завантажити",
|
||||
"newKnowledge.editSource": "Редагувати",
|
||||
"newKnowledge.emptyChunk": "Порожній фрагмент",
|
||||
"newKnowledge.emptyDescription": "Додайте документи, і агенти зможуть читати їх безпосередньо — без налаштування індексації.",
|
||||
"newKnowledge.emptyTitle": "Розумніший спосіб створювати бази знань",
|
||||
"newKnowledge.errorDescription": "Не вдалося завантажити нові бази знань. Спробуйте ще раз.",
|
||||
"newKnowledge.errorTitle": "Щось пішло не так",
|
||||
"newKnowledge.fileSize": "Розмір файлу",
|
||||
"newKnowledge.filtersUnavailable": "Фільтр стане доступним, коли KnowledgeFS надасть метадані.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl недоступний у каталозі постачальників для цієї робочої області.",
|
||||
"newKnowledge.gotIt": "Зрозуміло",
|
||||
"newKnowledge.guideDescription": "Підключіть джерело, і його документи залишатимуться синхронізованими, автоматично індексуватимуться та будуть готові до використання без додаткового налаштування. Ваші попередні бази знань працюватимуть як і раніше.",
|
||||
"newKnowledge.guideTitle": "Познайомтеся з новою базою знань ✨",
|
||||
"newKnowledge.illustrationHeadline": "Підключіть вміст один раз — і кожна програма відповідатиме на основі найсвіжіших знань.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Відомості про індекс",
|
||||
"newKnowledge.interruptTask": "Переривати",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid 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": "Завантажити попередні редакції",
|
||||
"newKnowledge.loadingSyncPolicy": "Завантаження політики синхронізації",
|
||||
"newKnowledge.manualUpload": "Ручне завантаження",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Метадані",
|
||||
"newKnowledge.mimeType": "Тип файлу",
|
||||
"newKnowledge.moreProviders": "Інші провайдери",
|
||||
"newKnowledge.name": "Назва знання",
|
||||
"newKnowledge.namePlaceholder": "напр. Посібник із підтримки клієнтів",
|
||||
"newKnowledge.nameRequired": "Введіть назву бази знань.",
|
||||
"newKnowledge.new": "Нова версія",
|
||||
"newKnowledge.noBackgroundTasks": "Фонових завдань ще немає.",
|
||||
"newKnowledge.noDescription": "Немає опису",
|
||||
"newKnowledge.noMatchingDocuments": "Немає документів, що відповідають цьому запиту.",
|
||||
"newKnowledge.noMatchingSources": "Жодне джерело не відповідає цьому запиту.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Можливо, цю базу знань було видалено або ви не маєте доступу.",
|
||||
"newKnowledge.notFoundTitle": "База знань не знайдена",
|
||||
"newKnowledge.notionNotConnected": "Notion не підключено",
|
||||
"newKnowledge.notionNotConnectedDescription": "Підключіть Notion, щоб вибирати сторінки й підтримувати їх синхронізацію.",
|
||||
"newKnowledge.onlineDocuments": "Документи онлайн",
|
||||
"newKnowledge.onlineDrive": "Онлайн драйв",
|
||||
"newKnowledge.overview": "Огляд",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "Вибрано: {{count}}",
|
||||
"newKnowledge.partialDocumentResults": "Може бути більше відповідних документів. Завантажте ще, щоб продовжити пошук.",
|
||||
"newKnowledge.permission": "Дозвіл",
|
||||
"newKnowledge.permissionAllMembers": "Робоча область · усі учасники можуть переглядати та редагувати",
|
||||
"newKnowledge.permissionLoadFailed": "Не вдалося перевірити дозволи на редагування. Спробуйте ще раз.",
|
||||
"newKnowledge.permissionOnlyMe": "Тільки я",
|
||||
"newKnowledge.permissionRestricted": "Дозволи вашої робочої області не дозволяють змінити це налаштування.",
|
||||
"newKnowledge.permissionUpdateFailed": "Базу знань створено, але не вдалося підтвердити її дозвіл. Повторіть спробу, щоб завершити налаштування.",
|
||||
"newKnowledge.preview": "Попередній перегляд",
|
||||
"newKnowledge.previewUnavailable": "Попередній перегляд стане доступним після завершення обробки цього файлу сервером.",
|
||||
"newKnowledge.processDocument": "Процес {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Перерваний",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "Готується",
|
||||
"newKnowledge.processingTaskState.failed": "Не вдалося",
|
||||
"newKnowledge.processingTaskState.queued": "У черзі",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Очікування повторної спроби",
|
||||
"newKnowledge.processingTaskState.running": "Обробка · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Виконано",
|
||||
"newKnowledge.processingTaskState.superseded": "Замінено",
|
||||
"newKnowledge.providerConnected": "Firecraw підключено",
|
||||
"newKnowledge.providerLoadFailed": "Не вдалося завантажити постачальників",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} не налаштовано",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Додайте ключ API або URL-адресу кінцевої точки, щоб сканувати веб-сайти за допомогою {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Цей провайдер недоступний.",
|
||||
"newKnowledge.quality": "Якість",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Попросіть редактора робочого простору створити першу базу знань.",
|
||||
"newKnowledge.refreshConnectionStatus": "Оновити статус",
|
||||
"newKnowledge.refreshingConnection": "Оновлення…",
|
||||
"newKnowledge.reindexDocument": "Переіндексувати",
|
||||
"newKnowledge.reindexDocuments": "Переіндексувати",
|
||||
"newKnowledge.removeSource": "Видалити джерело",
|
||||
"newKnowledge.retrievalCount": "Кількість отримань",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Спробуйте ще раз",
|
||||
"newKnowledge.retryReindexDocument": "Переіндексувати знову",
|
||||
"newKnowledge.retrySyncPolicy": "Повторити завантаження політики синхронізації",
|
||||
"newKnowledge.retryTask": "Повторіть спробу",
|
||||
"newKnowledge.revisionState.active": "Активна",
|
||||
"newKnowledge.revisionState.candidate": "Обробка",
|
||||
"newKnowledge.revisionState.failed": "Помилка",
|
||||
"newKnowledge.revisionState.superseded": "Попередня",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Цей розділ стане доступним на наступному кроці налаштування.",
|
||||
"newKnowledge.searchDocuments": "Пошук документів",
|
||||
"newKnowledge.searchSources": "Пошук джерел",
|
||||
"newKnowledge.selectAll": "Вибрати все",
|
||||
"newKnowledge.selectAllDocuments": "Виберіть усі документи",
|
||||
"newKnowledge.selectAllSources": "Вибрати всі джерела",
|
||||
"newKnowledge.selectChunk": "Виберіть фрагмент, щоб переглянути його вміст.",
|
||||
"newKnowledge.selectFilesAndFolders": "Виберіть файли й папки для синхронізації",
|
||||
"newKnowledge.skippedFailed": "Пропущено (помилка)",
|
||||
"newKnowledge.skippedOffDomain": "Пропущено (поза доменом)",
|
||||
"newKnowledge.sourceActions": "Дії для {{name}}",
|
||||
"newKnowledge.sourceColumn": "Джерело",
|
||||
"newKnowledge.sourceFilterLabel": "Фільтрувати джерела за статусом",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "Налаштування джерела показано повністю; для редагування й перегляду потрібен серверний процес чернетки джерела.",
|
||||
"newKnowledge.sourceStatus.active": "Активний",
|
||||
"newKnowledge.sourceStatus.disabled": "Вимкнено",
|
||||
"newKnowledge.sourceStatus.error": "Помилка",
|
||||
"newKnowledge.sourceStatus.syncing": "Синхронізація",
|
||||
"newKnowledge.sourceType.connector": "Підключена програма",
|
||||
"newKnowledge.sourceType.object-storage": "Онлайн-драйв",
|
||||
"newKnowledge.sourceType.upload": "Завантажити",
|
||||
"newKnowledge.sourceType.web": "Сканування веб-сайту",
|
||||
"newKnowledge.sourceTypeLabel": "Тип джерела",
|
||||
"newKnowledge.sources": "Джерела",
|
||||
"newKnowledge.sourcesDescription": "Звідки надходять знання, наскільки вони свіжі та чи вдалася остання синхронізація.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Підключіть веб-сайт — Dify підтримує його синхронізацію та оновлення, тому пошук ніколи не переривається.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Ще немає підключених джерел",
|
||||
"newKnowledge.sourcesErrorDescription": "Не вдалося завантажити джерела. Спробуйте знову.",
|
||||
"newKnowledge.sourcesErrorTitle": "Не вдалося завантажити джерела",
|
||||
"newKnowledge.startEmpty": "Почати з порожньої",
|
||||
"newKnowledge.startEmptyDescription": "Створіть зараз, а вміст додайте пізніше.",
|
||||
"newKnowledge.startWith": "Почніть з",
|
||||
"newKnowledge.startWithHelp": "Ви можете будь-коли додати більше джерел або документів.",
|
||||
"newKnowledge.statusColumn": "Статус",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Синхронізувати зараз",
|
||||
"newKnowledge.syncPolicy": "Політика синхронізації",
|
||||
"newKnowledge.syncPolicyColumn": "Політика синхронізації",
|
||||
"newKnowledge.syncPolicyCustom": "Власний інтервал",
|
||||
"newKnowledge.syncPolicyDaily": "Кожні 24 години",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Не вдалося завантажити політику синхронізації.",
|
||||
"newKnowledge.syncPolicyManual": "Лише вручну",
|
||||
"newKnowledge.syncPolicyProvider": "Використовувати розклад постачальника",
|
||||
"newKnowledge.tags": "Теги",
|
||||
"newKnowledge.taskActionFailed": "Не вдалося оновити це завдання. Оновіть і повторіть спробу.",
|
||||
"newKnowledge.taskAttentionClear": "Жодні фонові завдання не потребують уваги.",
|
||||
"newKnowledge.taskAttentionCount": "Фонові завдання {{count}} потребують уваги.",
|
||||
"newKnowledge.taskAttentionErrorCount": "Фонові завдання {{count}} потребують уваги, включаючи невдалі завдання.",
|
||||
"newKnowledge.taskFailedNotification": "Не вдалося обробити документ — повторіть спробу в розділі «Завдання»",
|
||||
"newKnowledge.taskHistoryIncomplete": "Доступна додаткова історія завдань; кількість завдань, що потребують уваги, поки неповна.",
|
||||
"newKnowledge.tasks": "завдання",
|
||||
"newKnowledge.tasksErrorDescription": "Не вдалося завантажити фонові завдання. Спробуйте знову.",
|
||||
"newKnowledge.tasksWithAttention": "Завдання, {{count}} потребують уваги",
|
||||
"newKnowledge.tokenCount": "Токени",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS зараз недоступний для цього робочого простору.",
|
||||
"newKnowledge.unavailableTitle": "Нова база знань недоступна",
|
||||
"newKnowledge.updated": "Оновлено {{date}}",
|
||||
"newKnowledge.updatedAt": "Останнє оновлення",
|
||||
"newKnowledge.updatedColumn": "Оновлено",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Символи: —",
|
||||
"newKnowledge.uploadDocuments": "Завантажити документи",
|
||||
"newKnowledge.uploadFiles": "Завантажити файли",
|
||||
"newKnowledge.uploadFilesDescription": "Додайте файли з комп’ютера — вони індексуються одразу після створення.",
|
||||
"newKnowledge.uploadingFiles": "Завантаження…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Вигляд бази знань",
|
||||
"newKnowledge.websiteCrawl": "Сканування веб-сайту",
|
||||
"noExternalKnowledge": "API зовнішніх знань поки що не існує, натисніть тут, щоб створити",
|
||||
"parentMode.fullDoc": "Повний документ",
|
||||
"parentMode.paragraph": "Абзац",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "Mô hình xếp hạng lại là cần thiết cho sự kết hợp của các cơ sở kiến thức chất lượng cao và tiết kiệm.",
|
||||
"mixtureInternalAndExternalTip": "Mô hình Rerank là cần thiết cho sự kết hợp giữa kiến thức bên trong và bên ngoài.",
|
||||
"multimodal": "Đa phương thức",
|
||||
"newKnowledge.actionsColumn": "Hành động",
|
||||
"newKnowledge.addDocument": "Thêm tài liệu",
|
||||
"newKnowledge.addSource": "Thêm nguồn",
|
||||
"newKnowledge.addSourceDescription": "Kết nối nguồn với cơ sở kiến thức này — nguồn này tự động đồng bộ hóa và luôn cập nhật.",
|
||||
"newKnowledge.addSourceFailed": "Không thể thêm nguồn này. Hãy kiểm tra cài đặt và thử lại.",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "Tất cả trạng thái",
|
||||
"newKnowledge.allSources": "Tất cả các nguồn",
|
||||
"newKnowledge.apiAgentAccess": "Quyền truy cập API và tác nhân",
|
||||
"newKnowledge.appsUnavailable": "— ứng dụng",
|
||||
"newKnowledge.authKind.api-key": "Khóa API",
|
||||
"newKnowledge.authKind.endpoint": "Điểm cuối",
|
||||
"newKnowledge.authenticationMethod": "Phương thức xác thực",
|
||||
"newKnowledge.averageChunkLength": "Độ dài đoạn trung bình",
|
||||
"newKnowledge.backToList": "Quay lại cơ sở kiến thức",
|
||||
"newKnowledge.backgroundTasks": "Tác vụ nền",
|
||||
"newKnowledge.backgroundTasksDescription": "Tài liệu thêm, đồng bộ hóa nguồn và lập chỉ mục lại công việc.",
|
||||
"newKnowledge.booleanFalse": "Không",
|
||||
"newKnowledge.booleanTrue": "Có",
|
||||
"newKnowledge.bulkDocumentActions": "Hành động tài liệu hàng loạt",
|
||||
"newKnowledge.cancelAddSource": "Hủy",
|
||||
"newKnowledge.cardType": "Không gian kiến thức",
|
||||
"newKnowledge.characterCount": "Ký tự",
|
||||
"newKnowledge.checkReindexStatus": "Kiểm tra trạng thái lập chỉ mục lại",
|
||||
"newKnowledge.chunkCount": "Đoạn",
|
||||
"newKnowledge.chunkHeading": "Đoạn {{position}}",
|
||||
"newKnowledge.chunkLocation": "Vị trí {{position}} trong bản sửa đổi này",
|
||||
"newKnowledge.clearDocumentSelection": "Xóa lựa chọn tài liệu",
|
||||
"newKnowledge.configureProvider": "Định cấu hình Firecrawl",
|
||||
"newKnowledge.connectNotion": "Kết nối Notion",
|
||||
"newKnowledge.connectProvider": "Kết nối Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "Kết nối nhà cung cấp",
|
||||
"newKnowledge.connectSource": "Kết nối nguồn",
|
||||
"newKnowledge.connectSourceDescription": "Nội dung luôn được đồng bộ với Notion, trang web và các dịch vụ khác.",
|
||||
"newKnowledge.connectingProvider": "Đang kết nối…",
|
||||
"newKnowledge.connectionFailed": "Chúng tôi không thể kết nối Firecrawl. Nhập lại thông tin xác thực và thử lại.",
|
||||
"newKnowledge.connectionNeedsAttention": "Kết nối Firecrawl cần được chú ý",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "Làm mới kết nối trước khi thêm nguồn trang web.",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl đang kết nối",
|
||||
"newKnowledge.connectionRefreshFailed": "Chúng tôi không thể làm mới kết nối này. Hãy thử lại.",
|
||||
"newKnowledge.continueCheckingTaskStatus": "Tiếp tục kiểm tra trạng thái tác vụ",
|
||||
"newKnowledge.crawlAndPreview": "Thu thập thông tin và xem trước",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "Chưa bắt đầu",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "Xem trước thu thập",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "Cấu hình thu thập thông tin trang web bị tắt cho đến khi có quy trình thu thập thông tin.",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl đã sẵn sàng",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "Chúng tôi không thể tạo ra nền tảng kiến thức này. Hãy kiểm tra quyền truy cập của bạn và thử lại.",
|
||||
"newKnowledge.createIllustrationTitle": "Kết nối nội dung một lần — mọi ứng dụng đều trả lời bằng kiến thức mới nhất.",
|
||||
"newKnowledge.createTitle": "Tạo kiến thức",
|
||||
"newKnowledge.createdAt": "Đã tạo",
|
||||
"newKnowledge.creators": "Người tạo",
|
||||
"newKnowledge.customIntervalHours": "Khoảng thời gian tùy chỉnh (giờ)",
|
||||
"newKnowledge.customIntervalInvalid": "Nhập số nguyên từ 1 đến 720 giờ.",
|
||||
"newKnowledge.deleteDocuments": "Xóa…",
|
||||
"newKnowledge.description": "Mô tả",
|
||||
"newKnowledge.descriptionHelp": "Mô tả rõ ràng giúp ứng dụng quyết định thời điểm tìm kiếm Kiến thức này.",
|
||||
"newKnowledge.descriptionPlaceholder": "ví dụ. Chính sách hoàn tiền, Câu hỏi thường gặp về vận chuyển và các bước báo cáo — sử dụng cho các câu hỏi hỗ trợ khách hàng",
|
||||
"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.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.",
|
||||
"newKnowledge.documentChunksLoadError": "Không thể tải các đoạn của bản sửa đổi này.",
|
||||
"newKnowledge.documentChunksLoadMoreError": "Không thể tải thêm đoạn. Hãy thử lại.",
|
||||
"newKnowledge.documentColumn": "tài liệu",
|
||||
"newKnowledge.documentContentIncomplete": "Đang tải phần nội dung tài liệu còn lại…",
|
||||
"newKnowledge.documentContents": "Nội dung",
|
||||
"newKnowledge.documentFacts": "Chi tiết tài liệu",
|
||||
"newKnowledge.documentFilterLabel": "Lọc tài liệu theo trạng thái",
|
||||
"newKnowledge.documentLoadErrorDescription": "Không thể tải tài liệu này. Hãy thử lại.",
|
||||
"newKnowledge.documentLoadErrorTitle": "Không thể tải tài liệu",
|
||||
"newKnowledge.documentNotFoundDescription": "Tài liệu có thể đã bị xóa hoặc bạn không có quyền truy cập.",
|
||||
"newKnowledge.documentNotFoundTitle": "Không tìm thấy tài liệu",
|
||||
"newKnowledge.documentOverviewDescription": "Đọc toàn bộ bản sửa đổi đã lập chỉ mục và xem chi tiết nguồn cùng chỉ mục.",
|
||||
"newKnowledge.documentPermissionRestricted": "Quyền trong không gian làm việc của bạn không cho phép thao tác với tài liệu hoặc tác vụ.",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "Yêu cầu lập chỉ mục lại đã được chấp nhận nhưng trạng thái tác vụ bị trễ. Hãy kiểm tra trước khi gửi lại.",
|
||||
"newKnowledge.documentReindexFailed": "Lập chỉ mục lại thất bại. Bản sửa đổi sẵn sàng gần nhất vẫn khả dụng.",
|
||||
"newKnowledge.documentReindexProgress": "Đang lập chỉ mục lại · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "Bản sửa đổi",
|
||||
"newKnowledge.documentRevisionMissingDescription": "Tài liệu này chưa có bản sửa đổi sẵn sàng. Hãy kiểm tra lại sau khi xử lý xong.",
|
||||
"newKnowledge.documentRevisionMissingTitle": "Không có bản sửa đổi",
|
||||
"newKnowledge.documentRevisionsLoadError": "Không thể tải lịch sử sửa đổi. Bản sửa đổi hiện tại vẫn khả dụng.",
|
||||
"newKnowledge.documentStatus.disabled": "Đã tắt",
|
||||
"newKnowledge.documentStatus.failed": "thất bại",
|
||||
"newKnowledge.documentStatus.processing": "Đang xử lý",
|
||||
"newKnowledge.documentStatus.queued": "Đã xếp hàng",
|
||||
"newKnowledge.documentStatus.ready": "Sẵn sàng",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "Còn thêm lịch sử tác vụ. Hãy tiếp tục kiểm tra để tìm trạng thái của tài liệu này.",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "đã vượt quá giới hạn kích thước lô",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "đã vượt quá giới hạn số lượng tệp",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "tệp quá lớn",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "loại tệp không được hỗ trợ hoặc không hợp lệ",
|
||||
"newKnowledge.documentUploadExclusion.more": "và {{count}} mục khác",
|
||||
"newKnowledge.documentUploadExclusion.processing": "xử lý thất bại",
|
||||
"newKnowledge.documentUploadExclusion.quota": "đã vượt quá hạn mức không gian làm việc",
|
||||
"newKnowledge.documentUploadExclusion.target": "đích tài liệu không còn hợp lệ",
|
||||
"newKnowledge.documentUploadFailed": "Chúng tôi không thể tải lên những tài liệu này. Hãy thử lại.",
|
||||
"newKnowledge.documentUploadPartial": "Đã bắt đầu xử lý {{accepted}} tài liệu; không thể thêm {{excluded}} tài liệu: {{details}}",
|
||||
"newKnowledge.documentUploadRejected": "Không có tài liệu nào được chấp nhận: {{details}}",
|
||||
"newKnowledge.documentUploadStarted": "Quá trình xử lý tài liệu bắt đầu.",
|
||||
"newKnowledge.documents": "Tài liệu",
|
||||
"newKnowledge.documentsDescription": "Tài sản kiến thức và các bản sửa đổi của chúng - mỗi tài liệu đến từ đâu và liệu bản sửa đổi mới nhất có được lập chỉ mục hay không.",
|
||||
"newKnowledge.documentsDropHint": "hoặc thả PDF, DOCX, Markdown, HTML, XLSX hoặc TXT để tải lên",
|
||||
"newKnowledge.documentsEmptyDescription": "Tải tệp lên đây hoặc kết nối nguồn trên tab Nguồn — tài liệu được phân tích cú pháp, phân đoạn và lập chỉ mục tự động.",
|
||||
"newKnowledge.documentsEmptyTitle": "Chưa có tài liệu nào",
|
||||
"newKnowledge.documentsErrorDescription": "Chúng tôi không thể tải tài liệu. Hãy thử lại.",
|
||||
"newKnowledge.documentsErrorTitle": "Không thể tải tài liệu",
|
||||
"newKnowledge.documentsPermissionDescription": "Bạn không có quyền xem tài liệu trong cơ sở kiến thức này.",
|
||||
"newKnowledge.documentsPermissionTitle": "Tài liệu bị hạn chế",
|
||||
"newKnowledge.documentsReindexFailed": "Chúng tôi không thể bắt đầu lập chỉ mục lại. Hãy thử lại.",
|
||||
"newKnowledge.documentsReindexPartial": "Tài liệu {{queued}} được xếp hàng đợi; {{missing}} không còn tồn tại.",
|
||||
"newKnowledge.documentsReindexStarted": "Lập chỉ mục lại bắt đầu.",
|
||||
"newKnowledge.documentsSelected": "Đã chọn tài liệu {{count}}",
|
||||
"newKnowledge.documentsUnavailable": "— tài liệu",
|
||||
"newKnowledge.downloadDocuments": "Tải xuống",
|
||||
"newKnowledge.editSource": "Chỉnh sửa",
|
||||
"newKnowledge.emptyChunk": "Đoạn trống",
|
||||
"newKnowledge.emptyDescription": "Thêm tài liệu để tác nhân có thể đọc trực tiếp — không cần cấu hình lập chỉ mục.",
|
||||
"newKnowledge.emptyTitle": "Cách thông minh hơn để xây dựng cơ sở kiến thức",
|
||||
"newKnowledge.errorDescription": "Không thể tải các cơ sở kiến thức mới. Hãy thử lại.",
|
||||
"newKnowledge.errorTitle": "Đã xảy ra lỗi",
|
||||
"newKnowledge.fileSize": "Kích thước tệp",
|
||||
"newKnowledge.filtersUnavailable": "Bộ lọc này sẽ khả dụng khi KnowledgeFS cung cấp siêu dữ liệu.",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl không có sẵn trong danh mục nhà cung cấp cho không gian làm việc này.",
|
||||
"newKnowledge.gotIt": "Đã hiểu",
|
||||
"newKnowledge.guideDescription": "Kết nối một nguồn và tài liệu của nguồn đó sẽ luôn được đồng bộ, tự động lập chỉ mục và sẵn sàng sử dụng mà không cần quản lý thiết lập. Các cơ sở kiến thức cũ vẫn hoạt động như trước.",
|
||||
"newKnowledge.guideTitle": "Khám phá Cơ sở kiến thức mới ✨",
|
||||
"newKnowledge.illustrationHeadline": "Kết nối nội dung một lần — mọi ứng dụng sẽ trả lời bằng kiến thức mới nhất.",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "Thông tin chỉ mục",
|
||||
"newKnowledge.interruptTask": "Ngắt",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid http(s) URL.",
|
||||
"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",
|
||||
"newKnowledge.loadingSyncPolicy": "Đang tải chính sách đồng bộ",
|
||||
"newKnowledge.manualUpload": "Tải lên thủ công",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "Siêu dữ liệu",
|
||||
"newKnowledge.mimeType": "Loại tệp",
|
||||
"newKnowledge.moreProviders": "Nhà cung cấp khác",
|
||||
"newKnowledge.name": "Tên kiến thức",
|
||||
"newKnowledge.namePlaceholder": "ví dụ: Sổ tay hỗ trợ khách hàng",
|
||||
"newKnowledge.nameRequired": "Nhập tên cơ sở kiến thức.",
|
||||
"newKnowledge.new": "Mới",
|
||||
"newKnowledge.noBackgroundTasks": "Chưa có tác vụ nền nào.",
|
||||
"newKnowledge.noDescription": "Không có mô tả",
|
||||
"newKnowledge.noMatchingDocuments": "Không có tài liệu phù hợp với tìm kiếm này.",
|
||||
"newKnowledge.noMatchingSources": "Không có nguồn nào phù hợp với tìm kiếm này.",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "Cơ sở kiến thức này có thể đã bị xóa hoặc bạn không có quyền truy cập.",
|
||||
"newKnowledge.notFoundTitle": "Không tìm thấy cơ sở kiến thức",
|
||||
"newKnowledge.notionNotConnected": "Notion chưa được kết nối",
|
||||
"newKnowledge.notionNotConnectedDescription": "Kết nối Notion để chọn trang và duy trì đồng bộ.",
|
||||
"newKnowledge.onlineDocuments": "Tài liệu trực tuyến",
|
||||
"newKnowledge.onlineDrive": "Ổ đĩa trực tuyến",
|
||||
"newKnowledge.overview": "Tổng quan",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "Đã chọn {{count}}",
|
||||
"newKnowledge.partialDocumentResults": "Có thể còn tài liệu phù hợp khác. Hãy tải thêm để tiếp tục tìm kiếm.",
|
||||
"newKnowledge.permission": "Quyền",
|
||||
"newKnowledge.permissionAllMembers": "Không gian làm việc · tất cả thành viên đều có thể xem và chỉnh sửa",
|
||||
"newKnowledge.permissionLoadFailed": "Không thể xác minh quyền chỉnh sửa của bạn. Hãy thử lại.",
|
||||
"newKnowledge.permissionOnlyMe": "Chỉ có tôi",
|
||||
"newKnowledge.permissionRestricted": "Quyền không gian làm việc của bạn không cho phép thay đổi cài đặt này.",
|
||||
"newKnowledge.permissionUpdateFailed": "Cơ sở kiến thức đã được tạo nhưng không thể xác nhận quyền. Hãy thử lại để hoàn tất thiết lập.",
|
||||
"newKnowledge.preview": "Xem trước",
|
||||
"newKnowledge.previewUnavailable": "Bản xem trước sẽ khả dụng sau khi backend xử lý xong tệp này.",
|
||||
"newKnowledge.processDocument": "Quá trình {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "Bị gián đoạn",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "chuẩn bị",
|
||||
"newKnowledge.processingTaskState.failed": "thất bại",
|
||||
"newKnowledge.processingTaskState.queued": "Đã xếp hàng",
|
||||
"newKnowledge.processingTaskState.retry_wait": "Đang chờ thử lại",
|
||||
"newKnowledge.processingTaskState.running": "Đang xử lý · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "Đã hoàn thành",
|
||||
"newKnowledge.processingTaskState.superseded": "Đã thay thế",
|
||||
"newKnowledge.providerConnected": "Đã kết nối Firecrawl",
|
||||
"newKnowledge.providerLoadFailed": "Nhà cung cấp không thể tải",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} chưa được định cấu hình",
|
||||
"newKnowledge.providerNotConfiguredDescription": "Thêm khóa API hoặc URL điểm cuối để thu thập dữ liệu trang web bằng {{provider}}.",
|
||||
"newKnowledge.providerUnavailable": "Nhà cung cấp này không có sẵn.",
|
||||
"newKnowledge.quality": "Chất lượng",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "Yêu cầu biên tập viên không gian làm việc tạo cơ sở kiến thức đầu tiên.",
|
||||
"newKnowledge.refreshConnectionStatus": "Trạng thái làm mới",
|
||||
"newKnowledge.refreshingConnection": "Đang làm mới…",
|
||||
"newKnowledge.reindexDocument": "Lập chỉ mục lại",
|
||||
"newKnowledge.reindexDocuments": "Lập chỉ mục lại",
|
||||
"newKnowledge.removeSource": "Xóa nguồn",
|
||||
"newKnowledge.retrievalCount": "Số lần truy xuất",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "Thử lại",
|
||||
"newKnowledge.retryReindexDocument": "Lập chỉ mục lại lần nữa",
|
||||
"newKnowledge.retrySyncPolicy": "Thử lại chính sách đồng bộ",
|
||||
"newKnowledge.retryTask": "Thử lại",
|
||||
"newKnowledge.revisionState.active": "Đang hoạt động",
|
||||
"newKnowledge.revisionState.candidate": "Đang xử lý",
|
||||
"newKnowledge.revisionState.failed": "Thất bại",
|
||||
"newKnowledge.revisionState.superseded": "Trước đó",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "Phần này sẽ có sẵn trong bước thiết lập tiếp theo.",
|
||||
"newKnowledge.searchDocuments": "Tìm kiếm tài liệu",
|
||||
"newKnowledge.searchSources": "Nguồn tìm kiếm",
|
||||
"newKnowledge.selectAll": "Chọn tất cả",
|
||||
"newKnowledge.selectAllDocuments": "Chọn tất cả tài liệu",
|
||||
"newKnowledge.selectAllSources": "Chọn tất cả nguồn",
|
||||
"newKnowledge.selectChunk": "Chọn một đoạn để xem nội dung.",
|
||||
"newKnowledge.selectFilesAndFolders": "Chọn tệp và thư mục để đồng bộ",
|
||||
"newKnowledge.skippedFailed": "Đã bỏ qua (thất bại)",
|
||||
"newKnowledge.skippedOffDomain": "Đã bỏ qua (ngoài miền)",
|
||||
"newKnowledge.sourceActions": "Hành động dành cho {{name}}",
|
||||
"newKnowledge.sourceColumn": "Nguồn",
|
||||
"newKnowledge.sourceFilterLabel": "Lọc nguồn theo trạng thái",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "Thiết lập nguồn được hiển thị đầy đủ; thao tác chỉnh sửa và xem trước cần quy trình backend cho nguồn nháp.",
|
||||
"newKnowledge.sourceStatus.active": "Đang hoạt động",
|
||||
"newKnowledge.sourceStatus.disabled": "Đã tắt",
|
||||
"newKnowledge.sourceStatus.error": "Lỗi",
|
||||
"newKnowledge.sourceStatus.syncing": "Đang đồng bộ hóa",
|
||||
"newKnowledge.sourceType.connector": "Ứng dụng được kết nối",
|
||||
"newKnowledge.sourceType.object-storage": "Ổ đĩa trực tuyến",
|
||||
"newKnowledge.sourceType.upload": "Tải lên",
|
||||
"newKnowledge.sourceType.web": "Thu thập thông tin trang web",
|
||||
"newKnowledge.sourceTypeLabel": "Loại nguồn",
|
||||
"newKnowledge.sources": "Nguồn",
|
||||
"newKnowledge.sourcesDescription": "Kiến thức đến từ đâu, nó mới như thế nào và liệu lần đồng bộ hóa cuối cùng có thành công hay không.",
|
||||
"newKnowledge.sourcesEmptyDescription": "Kết nối một trang web — Dify giữ cho trang web được đồng bộ hóa và luôn mới, để quá trình truy xuất không bao giờ bị gián đoạn.",
|
||||
"newKnowledge.sourcesEmptyTitle": "Chưa có nguồn nào được kết nối",
|
||||
"newKnowledge.sourcesErrorDescription": "Chúng tôi không thể tải nguồn. Hãy thử lại.",
|
||||
"newKnowledge.sourcesErrorTitle": "Không thể tải nguồn",
|
||||
"newKnowledge.startEmpty": "Bắt đầu trống",
|
||||
"newKnowledge.startEmptyDescription": "Tạo ngay bây giờ và thêm nội dung sau.",
|
||||
"newKnowledge.startWith": "Bắt đầu với",
|
||||
"newKnowledge.startWithHelp": "Bạn có thể thêm nhiều Nguồn hoặc Tài liệu hơn bất cứ lúc nào.",
|
||||
"newKnowledge.statusColumn": "Trạng thái",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "Đồng bộ hóa ngay",
|
||||
"newKnowledge.syncPolicy": "Chính sách đồng bộ",
|
||||
"newKnowledge.syncPolicyColumn": "Chính sách đồng bộ hóa",
|
||||
"newKnowledge.syncPolicyCustom": "Khoảng thời gian tùy chỉnh",
|
||||
"newKnowledge.syncPolicyDaily": "Mỗi 24 giờ",
|
||||
"newKnowledge.syncPolicyLoadFailed": "Không thể tải chính sách đồng bộ.",
|
||||
"newKnowledge.syncPolicyManual": "Chỉ thủ công",
|
||||
"newKnowledge.syncPolicyProvider": "Dùng lịch của nhà cung cấp",
|
||||
"newKnowledge.tags": "Thẻ",
|
||||
"newKnowledge.taskActionFailed": "Chúng tôi không thể cập nhật nhiệm vụ này. Hãy làm mới và thử lại.",
|
||||
"newKnowledge.taskAttentionClear": "Không có tác vụ nền nào cần chú ý.",
|
||||
"newKnowledge.taskAttentionCount": "Các tác vụ chạy nền {{count}} cần được chú ý.",
|
||||
"newKnowledge.taskAttentionErrorCount": "Các tác vụ chạy nền {{count}} cần được chú ý, kể cả những tác vụ bị lỗi.",
|
||||
"newKnowledge.taskFailedNotification": "Xử lý tài liệu không thành công — thử lại trong Tác vụ",
|
||||
"newKnowledge.taskHistoryIncomplete": "Còn lịch sử tác vụ khác; số tác vụ cần chú ý chưa đầy đủ.",
|
||||
"newKnowledge.tasks": "Nhiệm vụ",
|
||||
"newKnowledge.tasksErrorDescription": "Chúng tôi không thể tải tác vụ nền. Hãy thử lại.",
|
||||
"newKnowledge.tasksWithAttention": "Nhiệm vụ, {{count}} cần chú ý",
|
||||
"newKnowledge.tokenCount": "Token",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS hiện không khả dụng cho không gian làm việc này.",
|
||||
"newKnowledge.unavailableTitle": "Kiến thức mới không khả dụng",
|
||||
"newKnowledge.updated": "Cập nhật {{date}}",
|
||||
"newKnowledge.updatedAt": "Cập nhật gần nhất",
|
||||
"newKnowledge.updatedColumn": "Đã cập nhật",
|
||||
"newKnowledge.uploadCharactersUnavailable": "Ký tự: —",
|
||||
"newKnowledge.uploadDocuments": "Tải tài liệu lên",
|
||||
"newKnowledge.uploadFiles": "Tải tệp lên",
|
||||
"newKnowledge.uploadFilesDescription": "Thêm tệp từ máy tính — tệp sẽ được lập chỉ mục ngay khi tạo.",
|
||||
"newKnowledge.uploadingFiles": "Đang tải lên…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "Chế độ xem kiến thức",
|
||||
"newKnowledge.websiteCrawl": "Thu thập thông tin trang web",
|
||||
"noExternalKnowledge": "Chưa có API Kiến thức Bên ngoài, hãy nhấp vào đây để tạo",
|
||||
"parentMode.fullDoc": "Tài liệu đầy đủ",
|
||||
"parentMode.paragraph": "Đoạn",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "混合使用高质量和经济型知识库需要配置 Rerank 模型。",
|
||||
"mixtureInternalAndExternalTip": "混合使用内部和外部知识时需要配置 Rerank 模型。",
|
||||
"multimodal": "多模态",
|
||||
"newKnowledge.actionsColumn": "行动",
|
||||
"newKnowledge.addDocument": "添加文档",
|
||||
"newKnowledge.addSource": "添加来源",
|
||||
"newKnowledge.addSourceDescription": "将来源连接到该知识库 - 它会自动同步并保持最新。",
|
||||
"newKnowledge.addSourceFailed": "无法添加此来源。请检查设置后重试。",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "全部状态",
|
||||
"newKnowledge.allSources": "所有来源",
|
||||
"newKnowledge.apiAgentAccess": "API 与智能体访问",
|
||||
"newKnowledge.appsUnavailable": "— 个应用",
|
||||
"newKnowledge.authKind.api-key": "API 密钥",
|
||||
"newKnowledge.authKind.endpoint": "端点",
|
||||
"newKnowledge.authenticationMethod": "认证方式",
|
||||
"newKnowledge.averageChunkLength": "平均分段长度",
|
||||
"newKnowledge.backToList": "返回知识库列表",
|
||||
"newKnowledge.backgroundTasks": "后台任务",
|
||||
"newKnowledge.backgroundTasksDescription": "文档添加、数据源同步和重新索引任务。",
|
||||
"newKnowledge.booleanFalse": "否",
|
||||
"newKnowledge.booleanTrue": "是的",
|
||||
"newKnowledge.bulkDocumentActions": "批量文档操作",
|
||||
"newKnowledge.cancelAddSource": "取消",
|
||||
"newKnowledge.cardType": "知识空间",
|
||||
"newKnowledge.characterCount": "字符数",
|
||||
"newKnowledge.checkReindexStatus": "检查重新索引状态",
|
||||
"newKnowledge.chunkCount": "分段数",
|
||||
"newKnowledge.chunkHeading": "分段 {{position}}",
|
||||
"newKnowledge.chunkLocation": "此修订版本中的第 {{position}} 个位置",
|
||||
"newKnowledge.clearDocumentSelection": "清除文档选择",
|
||||
"newKnowledge.configureProvider": "配置Firecrawl",
|
||||
"newKnowledge.connectNotion": "连接 Notion",
|
||||
"newKnowledge.connectProvider": "连接Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "连接提供商",
|
||||
"newKnowledge.connectSource": "连接数据源",
|
||||
"newKnowledge.connectSourceDescription": "内容会与 Notion、网站及其他服务保持同步。",
|
||||
"newKnowledge.connectingProvider": "正在连接...",
|
||||
"newKnowledge.connectionFailed": "我们无法连接 Firecrawl。重新输入凭据并重试。",
|
||||
"newKnowledge.connectionNeedsAttention": "Firecrawl连接需要注意",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "添加网站源之前刷新连接。",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl 正在连接",
|
||||
"newKnowledge.connectionRefreshFailed": "我们无法刷新此连接。再试一次。",
|
||||
"newKnowledge.continueCheckingTaskStatus": "继续检查任务状态",
|
||||
"newKnowledge.crawlAndPreview": "抓取和预览",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "尚未开始",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "抓取预览",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "在爬网工作流程可用之前,网站爬网配置将被禁用。",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl 已准备就绪",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "无法创建此知识库,请检查权限后重试。",
|
||||
"newKnowledge.createIllustrationTitle": "只需连接一次内容——每个应用都能用最新知识作答。",
|
||||
"newKnowledge.createTitle": "创建知识库",
|
||||
"newKnowledge.createdAt": "创建时间",
|
||||
"newKnowledge.creators": "创建者",
|
||||
"newKnowledge.customIntervalHours": "自定义间隔(小时)",
|
||||
"newKnowledge.customIntervalInvalid": "请输入 1 到 720 之间的整数小时数。",
|
||||
"newKnowledge.deleteDocuments": "删除…",
|
||||
"newKnowledge.description": "描述",
|
||||
"newKnowledge.descriptionHelp": "清晰的描述有助于应用判断何时搜索此知识库。",
|
||||
"newKnowledge.descriptionPlaceholder": "例如:退款政策、配送常见问题和升级处理步骤——用于客户支持问题",
|
||||
"newKnowledge.detailErrorDescription": "无法加载此知识库,请重试。",
|
||||
"newKnowledge.detailErrorTitle": "出现错误",
|
||||
"newKnowledge.disableSource": "禁用",
|
||||
"newKnowledge.discardDraftConfirm": "放弃草稿",
|
||||
"newKnowledge.discardDraftDescription": "对此知识库草稿的更改将会丢失。",
|
||||
"newKnowledge.discardDraftTitle": "放弃此知识库草稿?",
|
||||
"newKnowledge.discardSourceChanges": "放弃来源更改?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "放弃更改",
|
||||
"newKnowledge.discardSourceChangesDescription": "抓取预览、页面选择和同步设置将会丢失。",
|
||||
"newKnowledge.documentActions": "{{name}} 的操作",
|
||||
"newKnowledge.documentActionsUnavailable": "此知识库暂不支持文档操作。",
|
||||
"newKnowledge.documentChunksEmpty": "此修订版本暂时没有可用分段。",
|
||||
"newKnowledge.documentChunksLoadError": "无法加载此修订版本的分段。",
|
||||
"newKnowledge.documentChunksLoadMoreError": "无法加载更多分段,请重试。",
|
||||
"newKnowledge.documentColumn": "文档",
|
||||
"newKnowledge.documentContentIncomplete": "正在加载剩余文档内容…",
|
||||
"newKnowledge.documentContents": "目录",
|
||||
"newKnowledge.documentFacts": "文档详情",
|
||||
"newKnowledge.documentFilterLabel": "按状态筛选文档",
|
||||
"newKnowledge.documentLoadErrorDescription": "无法加载此文档,请重试。",
|
||||
"newKnowledge.documentLoadErrorTitle": "文档加载失败",
|
||||
"newKnowledge.documentNotFoundDescription": "此文档可能已被删除,或你没有访问权限。",
|
||||
"newKnowledge.documentNotFoundTitle": "未找到文档",
|
||||
"newKnowledge.documentOverviewDescription": "阅读完整的已索引版本,并查看其来源和索引详情。",
|
||||
"newKnowledge.documentPermissionRestricted": "你的工作区权限不允许执行文档或任务操作。",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "重新索引已受理,但任务状态同步延迟。再次提交前请先检查状态。",
|
||||
"newKnowledge.documentReindexFailed": "重新索引失败,最后一个就绪的修订版本仍然可用。",
|
||||
"newKnowledge.documentReindexProgress": "正在重新索引 · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "修订版本",
|
||||
"newKnowledge.documentRevisionMissingDescription": "此文档还没有就绪的修订版本,请在处理完成后再查看。",
|
||||
"newKnowledge.documentRevisionMissingTitle": "没有可用的修订版本",
|
||||
"newKnowledge.documentRevisionsLoadError": "无法加载修订历史,当前修订版本仍然可用。",
|
||||
"newKnowledge.documentStatus.disabled": "已禁用",
|
||||
"newKnowledge.documentStatus.failed": "失败",
|
||||
"newKnowledge.documentStatus.processing": "处理中",
|
||||
"newKnowledge.documentStatus.queued": "排队中",
|
||||
"newKnowledge.documentStatus.ready": "就绪",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "还有更多任务历史记录。继续检查以查找此文档的状态。",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "超出批次大小限制",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "超出文件数量限制",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "文件过大",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "不支持或无效的文件类型",
|
||||
"newKnowledge.documentUploadExclusion.more": "以及另外 {{count}} 个",
|
||||
"newKnowledge.documentUploadExclusion.processing": "处理失败",
|
||||
"newKnowledge.documentUploadExclusion.quota": "超出工作区配额",
|
||||
"newKnowledge.documentUploadExclusion.target": "文档目标已失效",
|
||||
"newKnowledge.documentUploadFailed": "无法上传这些文档,请重试。",
|
||||
"newKnowledge.documentUploadPartial": "已开始处理 {{accepted}} 个文档;{{excluded}} 个无法添加:{{details}}",
|
||||
"newKnowledge.documentUploadRejected": "未接受任何文档:{{details}}",
|
||||
"newKnowledge.documentUploadStarted": "文档处理已开始。",
|
||||
"newKnowledge.documents": "文档",
|
||||
"newKnowledge.documentsDescription": "知识资产及其修订,显示每个文档的来源以及最新修订是否已建立索引。",
|
||||
"newKnowledge.documentsDropHint": "或拖放 PDF、DOCX、Markdown、HTML、XLSX 或 TXT 文件以上传",
|
||||
"newKnowledge.documentsEmptyDescription": "在此上传文件,或在“数据源”标签页连接数据源。文档会自动完成解析、分块和索引。",
|
||||
"newKnowledge.documentsEmptyTitle": "暂无文档",
|
||||
"newKnowledge.documentsErrorDescription": "无法加载文档,请重试。",
|
||||
"newKnowledge.documentsErrorTitle": "文档加载失败",
|
||||
"newKnowledge.documentsPermissionDescription": "你没有权限查看此知识库中的文档。",
|
||||
"newKnowledge.documentsPermissionTitle": "文档访问受限",
|
||||
"newKnowledge.documentsReindexFailed": "无法开始重新索引,请重试。",
|
||||
"newKnowledge.documentsReindexPartial": "已将 {{queued}} 个文档加入队列;{{missing}} 个文档已不存在。",
|
||||
"newKnowledge.documentsReindexStarted": "已开始重新索引。",
|
||||
"newKnowledge.documentsSelected": "已选择 {{count}} 个文档",
|
||||
"newKnowledge.documentsUnavailable": "— 篇文档",
|
||||
"newKnowledge.downloadDocuments": "下载",
|
||||
"newKnowledge.editSource": "编辑",
|
||||
"newKnowledge.emptyChunk": "空分段",
|
||||
"newKnowledge.emptyDescription": "添加文档后,智能体即可直接读取,无需配置索引。",
|
||||
"newKnowledge.emptyTitle": "更智能的知识库构建方式",
|
||||
"newKnowledge.errorDescription": "无法加载新知识库,请重试。",
|
||||
"newKnowledge.errorTitle": "出现错误",
|
||||
"newKnowledge.fileSize": "文件大小",
|
||||
"newKnowledge.filtersUnavailable": "KnowledgeFS 提供元数据后即可使用此筛选器。",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl 在此工作区的提供程序目录中不可用。",
|
||||
"newKnowledge.gotIt": "知道了",
|
||||
"newKnowledge.guideDescription": "连接数据源后,其文档会保持同步、自动建立索引并可立即使用,无需管理任何配置。旧版知识库仍会照常运行。",
|
||||
"newKnowledge.guideTitle": "认识全新的知识库 ✨",
|
||||
"newKnowledge.illustrationHeadline": "只需连接一次内容,每个应用都能基于最新知识回答。",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "索引信息",
|
||||
"newKnowledge.interruptTask": "中断",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid 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": "加载更早的修订版本",
|
||||
"newKnowledge.loadingSyncPolicy": "正在加载同步策略",
|
||||
"newKnowledge.manualUpload": "手动上传",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "元数据",
|
||||
"newKnowledge.mimeType": "文件类型",
|
||||
"newKnowledge.moreProviders": "更多提供商",
|
||||
"newKnowledge.name": "知识库名称",
|
||||
"newKnowledge.namePlaceholder": "例如:客户支持手册",
|
||||
"newKnowledge.nameRequired": "请输入知识库名称。",
|
||||
"newKnowledge.new": "新版",
|
||||
"newKnowledge.noBackgroundTasks": "暂无后台任务。",
|
||||
"newKnowledge.noDescription": "暂无描述",
|
||||
"newKnowledge.noMatchingDocuments": "没有符合此搜索条件的文档。",
|
||||
"newKnowledge.noMatchingSources": "没有与此搜索匹配的来源。",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "此知识库可能已被删除,或你没有访问权限。",
|
||||
"newKnowledge.notFoundTitle": "未找到知识库",
|
||||
"newKnowledge.notionNotConnected": "Notion 尚未连接",
|
||||
"newKnowledge.notionNotConnectedDescription": "连接 Notion 以选择页面并保持同步。",
|
||||
"newKnowledge.onlineDocuments": "在线文档",
|
||||
"newKnowledge.onlineDrive": "在线驱动器",
|
||||
"newKnowledge.overview": "概览",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "已选择 {{count}} 项",
|
||||
"newKnowledge.partialDocumentResults": "可能还有匹配的文档。加载更多以继续搜索。",
|
||||
"newKnowledge.permission": "权限",
|
||||
"newKnowledge.permissionAllMembers": "工作区 · 所有成员均可查看和编辑",
|
||||
"newKnowledge.permissionLoadFailed": "无法验证你的编辑权限,请重试。",
|
||||
"newKnowledge.permissionOnlyMe": "仅自己",
|
||||
"newKnowledge.permissionRestricted": "你的工作区权限不允许更改此设置。",
|
||||
"newKnowledge.permissionUpdateFailed": "知识库已创建,但无法确认其权限状态。请重试以完成设置。",
|
||||
"newKnowledge.preview": "预览",
|
||||
"newKnowledge.previewUnavailable": "后端完成此文件的处理后即可预览。",
|
||||
"newKnowledge.processDocument": "处理 {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "已中断",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "准备中",
|
||||
"newKnowledge.processingTaskState.failed": "失败",
|
||||
"newKnowledge.processingTaskState.queued": "排队中",
|
||||
"newKnowledge.processingTaskState.retry_wait": "等待重试",
|
||||
"newKnowledge.processingTaskState.running": "处理中 · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "已完成",
|
||||
"newKnowledge.processingTaskState.superseded": "已被取代",
|
||||
"newKnowledge.providerConnected": "Firecrawl已连接",
|
||||
"newKnowledge.providerLoadFailed": "提供商无法加载",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} 未配置",
|
||||
"newKnowledge.providerNotConfiguredDescription": "添加 API 密钥或端点 URL 以使用 {{provider}} 抓取网站。",
|
||||
"newKnowledge.providerUnavailable": "该提供商不可用。",
|
||||
"newKnowledge.quality": "质量",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "请联系工作区编辑者创建第一个知识库。",
|
||||
"newKnowledge.refreshConnectionStatus": "刷新状态",
|
||||
"newKnowledge.refreshingConnection": "正在刷新……",
|
||||
"newKnowledge.reindexDocument": "重新索引",
|
||||
"newKnowledge.reindexDocuments": "重新索引",
|
||||
"newKnowledge.removeSource": "删除来源",
|
||||
"newKnowledge.retrievalCount": "检索次数",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "重试",
|
||||
"newKnowledge.retryReindexDocument": "再次重新索引",
|
||||
"newKnowledge.retrySyncPolicy": "重试同步策略",
|
||||
"newKnowledge.retryTask": "重试",
|
||||
"newKnowledge.revisionState.active": "活跃",
|
||||
"newKnowledge.revisionState.candidate": "处理中",
|
||||
"newKnowledge.revisionState.failed": "失败",
|
||||
"newKnowledge.revisionState.superseded": "上一个",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "此部分将在下一设置步骤中开放。",
|
||||
"newKnowledge.searchDocuments": "搜索文档",
|
||||
"newKnowledge.searchSources": "搜索来源",
|
||||
"newKnowledge.selectAll": "全选",
|
||||
"newKnowledge.selectAllDocuments": "选择全部文档",
|
||||
"newKnowledge.selectAllSources": "选择所有来源",
|
||||
"newKnowledge.selectChunk": "选择一个分段以查看其内容。",
|
||||
"newKnowledge.selectFilesAndFolders": "选择要同步的文件和文件夹",
|
||||
"newKnowledge.skippedFailed": "已跳过(失败)",
|
||||
"newKnowledge.skippedOffDomain": "已跳过(域外)",
|
||||
"newKnowledge.sourceActions": "{{name}} 的操作",
|
||||
"newKnowledge.sourceColumn": "来源",
|
||||
"newKnowledge.sourceFilterLabel": "按状态过滤源",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "来源设置已完整展示;编辑和预览操作需要草稿来源后端流程。",
|
||||
"newKnowledge.sourceStatus.active": "活跃",
|
||||
"newKnowledge.sourceStatus.disabled": "已禁用",
|
||||
"newKnowledge.sourceStatus.error": "错误",
|
||||
"newKnowledge.sourceStatus.syncing": "正在同步",
|
||||
"newKnowledge.sourceType.connector": "连接的应用程序",
|
||||
"newKnowledge.sourceType.object-storage": "在线驱动器",
|
||||
"newKnowledge.sourceType.upload": "上传",
|
||||
"newKnowledge.sourceType.web": "网站抓取",
|
||||
"newKnowledge.sourceTypeLabel": "源类型",
|
||||
"newKnowledge.sources": "数据源",
|
||||
"newKnowledge.sourcesDescription": "知识从哪里来,新鲜程度如何,上次同步是否成功。",
|
||||
"newKnowledge.sourcesEmptyDescription": "连接网站 - Dify 保持网站同步和新鲜,因此检索永远不会中断。",
|
||||
"newKnowledge.sourcesEmptyTitle": "尚未连接来源",
|
||||
"newKnowledge.sourcesErrorDescription": "我们无法加载源。再试一次。",
|
||||
"newKnowledge.sourcesErrorTitle": "来源无法加载",
|
||||
"newKnowledge.startEmpty": "从空白开始",
|
||||
"newKnowledge.startEmptyDescription": "现在创建,稍后再添加内容。",
|
||||
"newKnowledge.startWith": "创建方式",
|
||||
"newKnowledge.startWithHelp": "你可以随时添加更多数据源或文档。",
|
||||
"newKnowledge.statusColumn": "状态",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "立即同步",
|
||||
"newKnowledge.syncPolicy": "同步策略",
|
||||
"newKnowledge.syncPolicyColumn": "同步策略",
|
||||
"newKnowledge.syncPolicyCustom": "自定义间隔",
|
||||
"newKnowledge.syncPolicyDaily": "每 24 小时",
|
||||
"newKnowledge.syncPolicyLoadFailed": "无法加载同步策略。",
|
||||
"newKnowledge.syncPolicyManual": "仅手动",
|
||||
"newKnowledge.syncPolicyProvider": "使用提供商计划",
|
||||
"newKnowledge.tags": "标签",
|
||||
"newKnowledge.taskActionFailed": "无法更新此任务,请刷新后重试。",
|
||||
"newKnowledge.taskAttentionClear": "没有需要关注的后台任务。",
|
||||
"newKnowledge.taskAttentionCount": "{{count}} 个后台任务需要关注。",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}} 个后台任务需要关注,其中包含失败的任务。",
|
||||
"newKnowledge.taskFailedNotification": "文档处理失败,请在“任务”中重试",
|
||||
"newKnowledge.taskHistoryIncomplete": "还有更多任务记录,需关注的任务数量尚未完整。",
|
||||
"newKnowledge.tasks": "任务",
|
||||
"newKnowledge.tasksErrorDescription": "无法加载后台任务,请重试。",
|
||||
"newKnowledge.tasksWithAttention": "任务,{{count}} 个需要关注",
|
||||
"newKnowledge.tokenCount": "Token 数",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS 当前在此工作区不可用。",
|
||||
"newKnowledge.unavailableTitle": "新版知识库不可用",
|
||||
"newKnowledge.updated": "更新于 {{date}}",
|
||||
"newKnowledge.updatedAt": "最后更新",
|
||||
"newKnowledge.updatedColumn": "更新时间",
|
||||
"newKnowledge.uploadCharactersUnavailable": "字符数:—",
|
||||
"newKnowledge.uploadDocuments": "上传文档",
|
||||
"newKnowledge.uploadFiles": "上传文件",
|
||||
"newKnowledge.uploadFilesDescription": "从电脑添加文件,创建后会立即建立索引。",
|
||||
"newKnowledge.uploadingFiles": "正在上传…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "知识库视图",
|
||||
"newKnowledge.websiteCrawl": "网站抓取",
|
||||
"noExternalKnowledge": "还没有外部知识库 API,点击此处创建",
|
||||
"parentMode.fullDoc": "全文",
|
||||
"parentMode.paragraph": "段落",
|
||||
|
||||
@@ -131,65 +131,310 @@
|
||||
"mixtureHighQualityAndEconomicTip": "混合高品質和經濟知識庫需要重新排序模型。",
|
||||
"mixtureInternalAndExternalTip": "Rerank 模型是內部和外部知識的混合所必需的。",
|
||||
"multimodal": "多模態",
|
||||
"newKnowledge.actionsColumn": "行動",
|
||||
"newKnowledge.addDocument": "新增文件",
|
||||
"newKnowledge.addSource": "新增來源",
|
||||
"newKnowledge.addSourceDescription": "將來源連接到該知識庫 - 它會自動同步並保持最新。",
|
||||
"newKnowledge.addSourceFailed": "無法新增此來源。請檢查設定後再試一次。",
|
||||
"newKnowledge.addSourceRequiresSelection": "Select at least one crawled page before adding the source.",
|
||||
"newKnowledge.adjustAndRecrawl": "Adjust & re-crawl",
|
||||
"newKnowledge.allDocumentStatuses": "全部狀態",
|
||||
"newKnowledge.allSources": "所有來源",
|
||||
"newKnowledge.apiAgentAccess": "API 與智慧體存取",
|
||||
"newKnowledge.appsUnavailable": "— 個應用程式",
|
||||
"newKnowledge.authKind.api-key": "API 金鑰",
|
||||
"newKnowledge.authKind.endpoint": "端點",
|
||||
"newKnowledge.authenticationMethod": "認證方式",
|
||||
"newKnowledge.averageChunkLength": "平均分段長度",
|
||||
"newKnowledge.backToList": "返回知識庫列表",
|
||||
"newKnowledge.backgroundTasks": "背景任務",
|
||||
"newKnowledge.backgroundTasksDescription": "文件新增、資料來源同步和重新索引任務。",
|
||||
"newKnowledge.booleanFalse": "否",
|
||||
"newKnowledge.booleanTrue": "是的",
|
||||
"newKnowledge.bulkDocumentActions": "批次文件操作",
|
||||
"newKnowledge.cancelAddSource": "取消",
|
||||
"newKnowledge.cardType": "知識空間",
|
||||
"newKnowledge.characterCount": "字元數",
|
||||
"newKnowledge.checkReindexStatus": "檢查重新索引狀態",
|
||||
"newKnowledge.chunkCount": "分段數",
|
||||
"newKnowledge.chunkHeading": "分段 {{position}}",
|
||||
"newKnowledge.chunkLocation": "此修訂版本中的第 {{position}} 個位置",
|
||||
"newKnowledge.clearDocumentSelection": "清除文件選取",
|
||||
"newKnowledge.configureProvider": "配置Firecrawl",
|
||||
"newKnowledge.connectNotion": "連接 Notion",
|
||||
"newKnowledge.connectProvider": "連接Firecrawl",
|
||||
"newKnowledge.connectProviderGeneric": "連接提供者",
|
||||
"newKnowledge.connectSource": "連接資料來源",
|
||||
"newKnowledge.connectSourceDescription": "內容會與 Notion、網站及其他服務保持同步。",
|
||||
"newKnowledge.connectingProvider": "正在連接...",
|
||||
"newKnowledge.connectionFailed": "我們無法連接 Firecrawl。重新輸入憑證並重試。",
|
||||
"newKnowledge.connectionNeedsAttention": "Firecrawl連接需要注意",
|
||||
"newKnowledge.connectionNeedsAttentionDescription": "新增網站來源之前刷新連線。",
|
||||
"newKnowledge.connectionProvisioning": "Firecrawl 正在連接",
|
||||
"newKnowledge.connectionRefreshFailed": "我們無法刷新此連線。再試一次。",
|
||||
"newKnowledge.continueCheckingTaskStatus": "繼續檢查任務狀態",
|
||||
"newKnowledge.crawlAndPreview": "抓取與預覽",
|
||||
"newKnowledge.crawlFailed": "Couldn't crawl {{host}}",
|
||||
"newKnowledge.crawlFailed403": "The site returned 403 — it may block crawlers or require authentication. Check the URL, adjust crawl options, or try another provider.",
|
||||
"newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.",
|
||||
"newKnowledge.crawlFailedProvider": "Firecrawl reported an error. Check the provider connection and try again.",
|
||||
"newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.",
|
||||
"newKnowledge.crawlNotStarted": "尚未開始",
|
||||
"newKnowledge.crawlOptions": "Crawl options",
|
||||
"newKnowledge.crawlPreview": "抓取預覽",
|
||||
"newKnowledge.crawlProgress": "Crawl progress for {{host}}",
|
||||
"newKnowledge.crawlSetupUnavailableDescription": "在爬網工作流程可用之前,網站爬網設定將會被停用。",
|
||||
"newKnowledge.crawlSetupUnavailableTitle": "Firecrawl 已準備就緒",
|
||||
"newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.",
|
||||
"newKnowledge.crawlStopped": "Crawl stopped",
|
||||
"newKnowledge.crawling": "Crawling…",
|
||||
"newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found",
|
||||
"newKnowledge.createFailed": "無法建立此知識庫,請檢查權限後再試一次。",
|
||||
"newKnowledge.createIllustrationTitle": "只需連接一次內容——每個應用都能以最新知識作答。",
|
||||
"newKnowledge.createTitle": "建立知識庫",
|
||||
"newKnowledge.createdAt": "建立時間",
|
||||
"newKnowledge.creators": "建立者",
|
||||
"newKnowledge.customIntervalHours": "自訂間隔(小時)",
|
||||
"newKnowledge.customIntervalInvalid": "請輸入 1 到 720 之間的整數小時數。",
|
||||
"newKnowledge.deleteDocuments": "刪除…",
|
||||
"newKnowledge.description": "描述",
|
||||
"newKnowledge.descriptionHelp": "清楚的描述有助於應用程式判斷何時搜尋此知識庫。",
|
||||
"newKnowledge.descriptionPlaceholder": "例如:退款政策、配送常見問題和升級處理步驟——用於客戶支援問題",
|
||||
"newKnowledge.detailErrorDescription": "無法載入此知識庫,請再試一次。",
|
||||
"newKnowledge.detailErrorTitle": "發生錯誤",
|
||||
"newKnowledge.disableSource": "停用",
|
||||
"newKnowledge.discardDraftConfirm": "放棄草稿",
|
||||
"newKnowledge.discardDraftDescription": "對此知識庫草稿的變更將會遺失。",
|
||||
"newKnowledge.discardDraftTitle": "放棄此知識庫草稿?",
|
||||
"newKnowledge.discardSourceChanges": "放棄來源變更?",
|
||||
"newKnowledge.discardSourceChangesConfirm": "放棄變更",
|
||||
"newKnowledge.discardSourceChangesDescription": "檢索預覽、頁面選取和同步設定將會遺失。",
|
||||
"newKnowledge.documentActions": "{{name}} 的操作",
|
||||
"newKnowledge.documentActionsUnavailable": "此知識庫暫不支援文件操作。",
|
||||
"newKnowledge.documentChunksEmpty": "此修訂版本暫時沒有可用分段。",
|
||||
"newKnowledge.documentChunksLoadError": "無法載入此修訂版本的分段。",
|
||||
"newKnowledge.documentChunksLoadMoreError": "無法載入更多分段,請再試一次。",
|
||||
"newKnowledge.documentColumn": "文件",
|
||||
"newKnowledge.documentContentIncomplete": "正在載入剩餘文件內容…",
|
||||
"newKnowledge.documentContents": "目錄",
|
||||
"newKnowledge.documentFacts": "文件詳細資料",
|
||||
"newKnowledge.documentFilterLabel": "依狀態篩選文件",
|
||||
"newKnowledge.documentLoadErrorDescription": "無法載入此文件,請再試一次。",
|
||||
"newKnowledge.documentLoadErrorTitle": "文件載入失敗",
|
||||
"newKnowledge.documentNotFoundDescription": "此文件可能已被移除,或你沒有存取權限。",
|
||||
"newKnowledge.documentNotFoundTitle": "找不到文件",
|
||||
"newKnowledge.documentOverviewDescription": "閱讀完整的已建立索引版本,並查看其來源與索引詳細資料。",
|
||||
"newKnowledge.documentPermissionRestricted": "你的工作區權限不允許執行文件或任務操作。",
|
||||
"newKnowledge.documentReindexConfirmationDelayed": "重新索引已受理,但任務狀態同步延遲。再次提交前請先檢查狀態。",
|
||||
"newKnowledge.documentReindexFailed": "重新建立索引失敗,最後一個就緒的修訂版本仍然可用。",
|
||||
"newKnowledge.documentReindexProgress": "正在重新建立索引 · {{progress}}%",
|
||||
"newKnowledge.documentRevision": "修訂版本",
|
||||
"newKnowledge.documentRevisionMissingDescription": "此文件尚未有就緒的修訂版本,請在處理完成後再查看。",
|
||||
"newKnowledge.documentRevisionMissingTitle": "沒有可用的修訂版本",
|
||||
"newKnowledge.documentRevisionsLoadError": "無法載入修訂歷程,目前的修訂版本仍然可用。",
|
||||
"newKnowledge.documentStatus.disabled": "已停用",
|
||||
"newKnowledge.documentStatus.failed": "失敗",
|
||||
"newKnowledge.documentStatus.processing": "處理中",
|
||||
"newKnowledge.documentStatus.queued": "排隊中",
|
||||
"newKnowledge.documentStatus.ready": "就緒",
|
||||
"newKnowledge.documentTaskLookupIncomplete": "還有更多任務歷史記錄。繼續檢查以尋找此文件的狀態。",
|
||||
"newKnowledge.documentUploadExclusion.batchLimit": "超出批次大小限制",
|
||||
"newKnowledge.documentUploadExclusion.countLimit": "超出檔案數量限制",
|
||||
"newKnowledge.documentUploadExclusion.fileSize": "檔案過大",
|
||||
"newKnowledge.documentUploadExclusion.fileType": "不支援或無效的檔案類型",
|
||||
"newKnowledge.documentUploadExclusion.more": "以及另外 {{count}} 個",
|
||||
"newKnowledge.documentUploadExclusion.processing": "處理失敗",
|
||||
"newKnowledge.documentUploadExclusion.quota": "超出工作區配額",
|
||||
"newKnowledge.documentUploadExclusion.target": "文件目標已失效",
|
||||
"newKnowledge.documentUploadFailed": "無法上傳這些文件,請再試一次。",
|
||||
"newKnowledge.documentUploadPartial": "已開始處理 {{accepted}} 個文件;{{excluded}} 個無法新增:{{details}}",
|
||||
"newKnowledge.documentUploadRejected": "未接受任何文件:{{details}}",
|
||||
"newKnowledge.documentUploadStarted": "文件處理已開始。",
|
||||
"newKnowledge.documents": "文件",
|
||||
"newKnowledge.documentsDescription": "知識資產及其修訂,顯示每份文件的來源以及最新修訂是否已建立索引。",
|
||||
"newKnowledge.documentsDropHint": "或拖放 PDF、DOCX、Markdown、HTML、XLSX 或 TXT 檔案以上傳",
|
||||
"newKnowledge.documentsEmptyDescription": "在此上傳檔案,或在「資料來源」分頁連接資料來源。文件會自動完成解析、分塊和索引。",
|
||||
"newKnowledge.documentsEmptyTitle": "暫無文件",
|
||||
"newKnowledge.documentsErrorDescription": "無法載入文件,請再試一次。",
|
||||
"newKnowledge.documentsErrorTitle": "文件載入失敗",
|
||||
"newKnowledge.documentsPermissionDescription": "你沒有權限查看此知識庫中的文件。",
|
||||
"newKnowledge.documentsPermissionTitle": "文件存取受限",
|
||||
"newKnowledge.documentsReindexFailed": "無法開始重新索引,請再試一次。",
|
||||
"newKnowledge.documentsReindexPartial": "已將 {{queued}} 個文件加入佇列;{{missing}} 個文件已不存在。",
|
||||
"newKnowledge.documentsReindexStarted": "已開始重新索引。",
|
||||
"newKnowledge.documentsSelected": "已選取 {{count}} 個文件",
|
||||
"newKnowledge.documentsUnavailable": "— 份文件",
|
||||
"newKnowledge.downloadDocuments": "下載",
|
||||
"newKnowledge.editSource": "編輯",
|
||||
"newKnowledge.emptyChunk": "空分段",
|
||||
"newKnowledge.emptyDescription": "加入文件後,智慧代理即可直接讀取,無需設定索引。",
|
||||
"newKnowledge.emptyTitle": "更聰明的知識庫建立方式",
|
||||
"newKnowledge.errorDescription": "無法載入新知識庫,請再試一次。",
|
||||
"newKnowledge.errorTitle": "發生錯誤",
|
||||
"newKnowledge.fileSize": "檔案大小",
|
||||
"newKnowledge.filtersUnavailable": "KnowledgeFS 提供中繼資料後即可使用此篩選器。",
|
||||
"newKnowledge.firecrawlUnavailable": "Firecrawl 在此工作區的提供者目錄中不可用。",
|
||||
"newKnowledge.gotIt": "知道了",
|
||||
"newKnowledge.guideDescription": "連接資料來源後,其文件會保持同步、自動建立索引並可立即使用,無需管理任何設定。舊版知識庫仍會照常運作。",
|
||||
"newKnowledge.guideTitle": "認識全新的知識庫 ✨",
|
||||
"newKnowledge.illustrationHeadline": "只需連接一次內容,每個應用程式都能以最新知識回答。",
|
||||
"newKnowledge.includeSubpages": "Include sub-pages",
|
||||
"newKnowledge.indexInformation": "索引資訊",
|
||||
"newKnowledge.interruptTask": "中斷",
|
||||
"newKnowledge.invalidRootUrl": "Enter a valid 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": "載入較早的修訂版本",
|
||||
"newKnowledge.loadingSyncPolicy": "正在載入同步政策",
|
||||
"newKnowledge.manualUpload": "手動上傳",
|
||||
"newKnowledge.maxPages": "Maximum pages",
|
||||
"newKnowledge.metadata": "中繼資料",
|
||||
"newKnowledge.mimeType": "檔案類型",
|
||||
"newKnowledge.moreProviders": "更多提供者",
|
||||
"newKnowledge.name": "知識庫名稱",
|
||||
"newKnowledge.namePlaceholder": "例如:客戶支援手冊",
|
||||
"newKnowledge.nameRequired": "請輸入知識庫名稱。",
|
||||
"newKnowledge.new": "新版",
|
||||
"newKnowledge.noBackgroundTasks": "暫無背景任務。",
|
||||
"newKnowledge.noDescription": "暫無描述",
|
||||
"newKnowledge.noMatchingDocuments": "沒有符合此搜尋條件的文件。",
|
||||
"newKnowledge.noMatchingSources": "沒有與此搜尋相符的來源。",
|
||||
"newKnowledge.noPagesFound": "No pages found at {{host}}",
|
||||
"newKnowledge.noPagesFoundDescription": "The crawler reached the site but found no indexable pages. Try a different URL, or expand crawl options.",
|
||||
"newKnowledge.notFoundDescription": "此知識庫可能已被刪除,或你沒有存取權限。",
|
||||
"newKnowledge.notFoundTitle": "找不到知識庫",
|
||||
"newKnowledge.notionNotConnected": "Notion 尚未連接",
|
||||
"newKnowledge.notionNotConnectedDescription": "連接 Notion 以選擇頁面並保持同步。",
|
||||
"newKnowledge.onlineDocuments": "線上文檔",
|
||||
"newKnowledge.onlineDrive": "線上驅動器",
|
||||
"newKnowledge.overview": "概覽",
|
||||
"newKnowledge.pagesAppearDescription": "Crawl the site to review which pages get added.",
|
||||
"newKnowledge.pagesAppearTitle": "Pages will appear here",
|
||||
"newKnowledge.pagesCrawled": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_one": "{{count}} page crawled at {{host}}",
|
||||
"newKnowledge.pagesCrawled_other": "{{count}} pages crawled at {{host}}",
|
||||
"newKnowledge.pagesSelected": "已選取 {{count}} 項",
|
||||
"newKnowledge.partialDocumentResults": "可能還有符合的文件。載入更多以繼續搜尋。",
|
||||
"newKnowledge.permission": "權限",
|
||||
"newKnowledge.permissionAllMembers": "工作區 · 所有成員皆可檢視和編輯",
|
||||
"newKnowledge.permissionLoadFailed": "無法驗證你的編輯權限,請再試一次。",
|
||||
"newKnowledge.permissionOnlyMe": "僅自己",
|
||||
"newKnowledge.permissionRestricted": "你的工作區權限不允許變更此設定。",
|
||||
"newKnowledge.permissionUpdateFailed": "知識庫已建立,但無法確認其權限狀態。請重試以完成設定。",
|
||||
"newKnowledge.preview": "預覽",
|
||||
"newKnowledge.previewUnavailable": "後端完成此檔案的處理後即可預覽。",
|
||||
"newKnowledge.processDocument": "處理 {{name}}",
|
||||
"newKnowledge.processingTaskState.canceled": "已中斷",
|
||||
"newKnowledge.processingTaskState.dispatch_pending": "準備中",
|
||||
"newKnowledge.processingTaskState.failed": "失敗",
|
||||
"newKnowledge.processingTaskState.queued": "排隊中",
|
||||
"newKnowledge.processingTaskState.retry_wait": "等待重試",
|
||||
"newKnowledge.processingTaskState.running": "處理中 · {{progress}}%",
|
||||
"newKnowledge.processingTaskState.succeeded": "已完成",
|
||||
"newKnowledge.processingTaskState.superseded": "已被取代",
|
||||
"newKnowledge.providerConnected": "Firecrawl已連接",
|
||||
"newKnowledge.providerLoadFailed": "提供者無法載入",
|
||||
"newKnowledge.providerNotConfigured": "{{provider}} 未配置",
|
||||
"newKnowledge.providerNotConfiguredDescription": "新增 API 金鑰或端點 URL 以使用 {{provider}} 抓取網站。",
|
||||
"newKnowledge.providerUnavailable": "該提供者不可用。",
|
||||
"newKnowledge.quality": "品質",
|
||||
"newKnowledge.reCrawl": "Re-crawl",
|
||||
"newKnowledge.readOnlyEmpty": "請聯絡工作區編輯者建立第一個知識庫。",
|
||||
"newKnowledge.refreshConnectionStatus": "刷新狀態",
|
||||
"newKnowledge.refreshingConnection": "正在重新整理…",
|
||||
"newKnowledge.reindexDocument": "重新建立索引",
|
||||
"newKnowledge.reindexDocuments": "重新索引",
|
||||
"newKnowledge.removeSource": "刪除來源",
|
||||
"newKnowledge.retrievalCount": "檢索次數",
|
||||
"newKnowledge.retryCrawl": "Retry crawl",
|
||||
"newKnowledge.retryProviderLoad": "重試",
|
||||
"newKnowledge.retryReindexDocument": "再次重新索引",
|
||||
"newKnowledge.retrySyncPolicy": "重試同步政策",
|
||||
"newKnowledge.retryTask": "重試",
|
||||
"newKnowledge.revisionState.active": "啟用中",
|
||||
"newKnowledge.revisionState.candidate": "處理中",
|
||||
"newKnowledge.revisionState.failed": "失敗",
|
||||
"newKnowledge.revisionState.superseded": "上一個",
|
||||
"newKnowledge.rootUrl": "Root URL",
|
||||
"newKnowledge.rootUrlPlaceholder": "https://docs.example.com",
|
||||
"newKnowledge.routeUnavailable": "此區段將在下一個設定步驟中開放。",
|
||||
"newKnowledge.searchDocuments": "搜尋文件",
|
||||
"newKnowledge.searchSources": "搜尋來源",
|
||||
"newKnowledge.selectAll": "全選",
|
||||
"newKnowledge.selectAllDocuments": "選取全部文件",
|
||||
"newKnowledge.selectAllSources": "選擇所有來源",
|
||||
"newKnowledge.selectChunk": "選取一個分段以查看其內容。",
|
||||
"newKnowledge.selectFilesAndFolders": "選擇要同步的檔案與資料夾",
|
||||
"newKnowledge.skippedFailed": "已略過(失敗)",
|
||||
"newKnowledge.skippedOffDomain": "已略過(網域外)",
|
||||
"newKnowledge.sourceActions": "{{name}} 的操作",
|
||||
"newKnowledge.sourceColumn": "來源",
|
||||
"newKnowledge.sourceFilterLabel": "按狀態過濾來源",
|
||||
"newKnowledge.sourceName": "Source name",
|
||||
"newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl",
|
||||
"newKnowledge.sourceSetupBackendDependency": "來源設定已完整顯示;編輯與預覽操作需要草稿來源後端流程。",
|
||||
"newKnowledge.sourceStatus.active": "活躍",
|
||||
"newKnowledge.sourceStatus.disabled": "已停用",
|
||||
"newKnowledge.sourceStatus.error": "錯誤",
|
||||
"newKnowledge.sourceStatus.syncing": "正在同步",
|
||||
"newKnowledge.sourceType.connector": "連接的應用程式",
|
||||
"newKnowledge.sourceType.object-storage": "線上驅動器",
|
||||
"newKnowledge.sourceType.upload": "上傳",
|
||||
"newKnowledge.sourceType.web": "網站抓取",
|
||||
"newKnowledge.sourceTypeLabel": "來源類型",
|
||||
"newKnowledge.sources": "資料來源",
|
||||
"newKnowledge.sourcesDescription": "知識從哪裡來,新鮮程度如何,上次同步是否成功。",
|
||||
"newKnowledge.sourcesEmptyDescription": "連結網站 - Dify 保持網站同步和新鮮,因此檢索永遠不會中斷。",
|
||||
"newKnowledge.sourcesEmptyTitle": "尚未連接來源",
|
||||
"newKnowledge.sourcesErrorDescription": "我們無法載入來源。再試一次。",
|
||||
"newKnowledge.sourcesErrorTitle": "來源無法載入",
|
||||
"newKnowledge.startEmpty": "從空白開始",
|
||||
"newKnowledge.startEmptyDescription": "現在建立,稍後再加入內容。",
|
||||
"newKnowledge.startWith": "建立方式",
|
||||
"newKnowledge.startWithHelp": "你可以隨時新增更多資料來源或文件。",
|
||||
"newKnowledge.statusColumn": "狀態",
|
||||
"newKnowledge.stopCrawl": "Stop",
|
||||
"newKnowledge.stoppingCrawl": "Stopping…",
|
||||
"newKnowledge.syncNow": "立即同步",
|
||||
"newKnowledge.syncPolicy": "同步政策",
|
||||
"newKnowledge.syncPolicyColumn": "同步策略",
|
||||
"newKnowledge.syncPolicyCustom": "自訂間隔",
|
||||
"newKnowledge.syncPolicyDaily": "每 24 小時",
|
||||
"newKnowledge.syncPolicyLoadFailed": "無法載入同步政策。",
|
||||
"newKnowledge.syncPolicyManual": "僅限手動",
|
||||
"newKnowledge.syncPolicyProvider": "使用供應商排程",
|
||||
"newKnowledge.tags": "標籤",
|
||||
"newKnowledge.taskActionFailed": "無法更新此任務,請重新整理後再試一次。",
|
||||
"newKnowledge.taskAttentionClear": "沒有需要注意的背景任務。",
|
||||
"newKnowledge.taskAttentionCount": "{{count}} 個背景任務需要注意。",
|
||||
"newKnowledge.taskAttentionErrorCount": "{{count}} 個背景任務需要注意,其中包含失敗的任務。",
|
||||
"newKnowledge.taskFailedNotification": "文件處理失敗,請在「任務」中重試",
|
||||
"newKnowledge.taskHistoryIncomplete": "還有更多任務記錄,需關注的任務數量尚未完整。",
|
||||
"newKnowledge.tasks": "任務",
|
||||
"newKnowledge.tasksErrorDescription": "無法載入背景任務,請再試一次。",
|
||||
"newKnowledge.tasksWithAttention": "任務,{{count}} 個需要注意",
|
||||
"newKnowledge.tokenCount": "Token 數",
|
||||
"newKnowledge.unavailableDescription": "KnowledgeFS 目前在此工作區不可用。",
|
||||
"newKnowledge.unavailableTitle": "新版知識庫不可用",
|
||||
"newKnowledge.updated": "更新於 {{date}}",
|
||||
"newKnowledge.updatedAt": "最後更新",
|
||||
"newKnowledge.updatedColumn": "更新時間",
|
||||
"newKnowledge.uploadCharactersUnavailable": "字元數:—",
|
||||
"newKnowledge.uploadDocuments": "上傳文件",
|
||||
"newKnowledge.uploadFiles": "上傳檔案",
|
||||
"newKnowledge.uploadFilesDescription": "從電腦加入檔案,建立後會立即建立索引。",
|
||||
"newKnowledge.uploadingFiles": "正在上傳…",
|
||||
"newKnowledge.usingDefaults": "Using defaults",
|
||||
"newKnowledge.viewLabel": "知識庫檢視",
|
||||
"newKnowledge.websiteCrawl": "網站抓取",
|
||||
"noExternalKnowledge": "目前還沒有外部知識 API,按兩下此處創建",
|
||||
"parentMode.fullDoc": "完整文件",
|
||||
"parentMode.paragraph": "段",
|
||||
|
||||
Reference in New Issue
Block a user