fix(knowledge-fs): harden research progress and scoring

This commit is contained in:
Stephen Zhou 2026-07-31 11:38:52 +08:00
parent f1045bc099
commit 480deebf02
No known key found for this signature in database
41 changed files with 1361 additions and 171 deletions

View File

@ -0,0 +1,69 @@
# Research progress and structured semantic scoring hardening
Date: 2026-07-31
## What changed
- Routed PageIndex semantic candidate scoring through Dify's structured-output LLM endpoint.
Each bounded scoring batch now supplies a strict JSON schema for the exact candidate ids, while
the existing KnowledgeFS parser continues to reject missing, duplicated, unknown, or invalid
scores. The production batch size was reduced from ten candidates to five.
- Updated the Dify model-runtime client and generation provider to carry structured-output schemas,
consume Dify's validated `structured_output` stream field, and retain free-form response text
only as a compatibility fallback.
- Kept Research task stage transitions synchronized with heartbeat-updated row versions inside the
serialized lease lane, preventing a heartbeat from making the next durable transition use a
stale fence.
- Made the Research progress database repository accept PostgreSQL `BIGINT` timestamps returned as
bounded decimal strings while preserving non-negative safe-integer validation.
- Added Retrieval Test progress streaming through the console-issued Research capability, including
bounded SSE replay, cursor reconnects, terminal task/partial refreshes, actual per-stage timing,
and process-log presentation.
- Split active Research stage labels from completed milestone labels in every supported Web locale.
A streamed completion now refreshes the task first and leaves the existing active-to-completed
transition guard as the single owner of the final partial-result refresh. Historical terminal
replay does not trigger redundant task or partial requests.
## Why
- Free-form LLM output could spend tokens on prose or return malformed JSON before semantic scores,
even though Dify already owns tenant-scoped structured-output invocation and validation.
- Research heartbeats increment the durable row version while generation is running. Stage
transitions must read the latest fence from the same serialized lane to avoid false lease loss.
- PostgreSQL drivers commonly expose `BIGINT` values as strings, so valid persisted progress events
could fail when read back.
- Polling alone did not expose durable Research stage history or precise timing, while mixing SSE
terminal refreshes with the existing completion guard caused duplicate partial reads.
- Completed-milestone copy was misleading when reused for a stage that was still running.
## Verification
- Web Retrieval Test and Research event-stream focused suites pass.
- KnowledgeFS API semantic scoring, progress database, and Research runtime focused suites pass.
- Dify model-runtime client and generation-provider focused suites pass.
- TypeScript checks pass for `@knowledge/api`, `@knowledge/dify-model-runtime-client`, and
`@knowledge/generation`.
- Focused KnowledgeFS Biome checks, Web i18n synchronization, and `git diff --check` pass.
- The repository-wide Web TypeScript command remains blocked by pre-existing generated
`.next/types/validator.ts` route declarations unrelated to this change.
- Vite+ static `check` remains unavailable in the current local environment because its launcher
cannot resolve the Node binary; focused tests, i18n validation, KnowledgeFS Biome, and package
typechecks were used instead.
## Performance and operational bounds
- Semantic scoring still uses at most four concurrent LLM batches, with five candidates per batch,
bounded candidate text, output tokens, response characters, and per-batch timeouts.
- Research SSE responses remain server-bounded by page limit and connection duration. Clients
reconnect from the last durable sequence cursor rather than retaining an unbounded connection.
- Completed tasks perform one final partial refetch through the existing transition guard. Replayed
historical terminal events do not add redundant refresh requests.
## Risks and follow-up
- Smaller semantic-score batches increase the number of model calls for the same candidate window,
trading request count for more reliable structured responses within the configured output budget.
- Providers without native structured output still rely on Dify's prompt-based parsing before the
strict KnowledgeFS validation boundary.
- Progress history is held only for Research tasks selected during the current Retrieval Test page
session; switching or reloading reconstructs it from the durable cursor stream.

View File

@ -149,7 +149,7 @@ const rerankerOptions = createApiRerankerOptions();
const semanticEntityExtractionOptions = createApiSemanticEntityExtractionOptions();
const profileReasoningCapability = createApiProfileReasoningCapability();
const pageIndexSemanticTreeSearch = createPageIndexSemanticTreeSearch({
batchSize: 10,
batchSize: 5,
maxConcurrentBatches: 4,
maxOutputTokens: profileReasoningCapability.maxOutputTokens,
maxTextCharsPerCandidate: 1_500,

View File

@ -65,6 +65,27 @@ describe("PageIndex semantic tree search", () => {
}),
);
const firstPrompt = generate.mock.calls[0]?.[0].messages;
expect(generate.mock.calls[0]?.[0].structuredOutputSchema).toMatchObject({
additionalProperties: false,
properties: {
scores: {
items: {
additionalProperties: false,
properties: {
candidateId: { enum: ["c1", "c2"], type: "string" },
score: { maximum: 1, minimum: 0, type: "number" },
},
required: ["candidateId", "score", "reason"],
type: "object",
},
maxItems: 2,
minItems: 2,
type: "array",
},
},
required: ["scores"],
type: "object",
});
expect(firstPrompt?.[0]?.content).toContain("do not score by keyword overlap alone");
expect(firstPrompt?.[1]?.content).toContain("How long must invoices be retained?");
expect(firstPrompt?.[1]?.content).toContain("Finance");

View File

@ -44,6 +44,7 @@ export interface GeneratePageIndexSemanticScoreInput {
}[];
readonly model: string;
readonly signal?: AbortSignal | undefined;
readonly structuredOutputSchema?: Readonly<Record<string, unknown>> | undefined;
readonly temperature?: number | undefined;
readonly tenantId?: string | undefined;
}
@ -159,6 +160,7 @@ export function createPageIndexSemanticTreeSearch({
}),
model: input.reasoningModel.model,
signal: controller.signal,
structuredOutputSchema: semanticScoreOutputSchema(candidates),
temperature: 0,
tenantId,
}),
@ -228,6 +230,36 @@ function semanticScoreMessages({
];
}
function semanticScoreOutputSchema(
candidates: readonly PageIndexSemanticCandidate[],
): Readonly<Record<string, unknown>> {
return {
additionalProperties: false,
properties: {
scores: {
items: {
additionalProperties: false,
properties: {
candidateId: {
enum: candidates.map((candidate) => candidate.candidateId),
type: "string",
},
reason: { maxLength: 500, minLength: 1, type: "string" },
score: { maximum: 1, minimum: 0, type: "number" },
},
required: ["candidateId", "score", "reason"],
type: "object",
},
maxItems: candidates.length,
minItems: candidates.length,
type: "array",
},
},
required: ["scores"],
type: "object",
};
}
function parseSemanticScoreOutput(
text: string,
candidates: readonly PageIndexSemanticCandidate[],

View File

@ -131,6 +131,23 @@ it("pages by tenant/task cursor and rejects unbounded reads", async () => {
).rejects.toThrow(/between 1 and 1/u);
});
it("reads PostgreSQL bigint timestamps returned as decimal strings", async () => {
const database = recordingDatabase("postgres", async () => ({
rows: [progressRow({ created_at: String(Date.parse("2026-07-14T00:00:00.000Z")) })],
rowsAffected: 0,
}));
await expect(
createRepository(database.adapter).list({
limit: 1,
researchTaskJobId: JOB_ID,
tenantId: TENANT_ID,
}),
).resolves.toMatchObject({
items: [{ createdAt: "2026-07-14T00:00:00.000Z" }],
});
});
it("polls the durable ledger independently across replicas and releases subscriber bounds", async () => {
const rows: DatabaseRow[] = [];
const database = recordingDatabase("postgres", async (input) => {

View File

@ -9,7 +9,7 @@ import type {
} from "@knowledge/core";
import { UuidSchema } from "@knowledge/core";
import { numberColumn, stringColumn } from "./database-row-utils";
import { nonnegativeSafeIntegerColumn, numberColumn, stringColumn } from "./database-row-utils";
import { databasePlaceholder, quoteDatabaseIdentifier } from "./database-sql-utils";
import { cloneJsonObject, jsonObjectColumn } from "./json-utils";
import type { ResearchTaskJobStage } from "./research-task-job";
@ -397,7 +397,7 @@ function progressFromRow(row: DatabaseRow): ResearchTaskProgressEvent {
if (!eventTypes.has(type) || !jobStages.has(stage)) {
throw new Error("Research task progress row has an invalid event type or stage");
}
const createdAt = validTimestamp(numberColumn(row, "created_at"));
const createdAt = validTimestamp(nonnegativeSafeIntegerColumn(row, "created_at"));
return {
createdAt: new Date(createdAt).toISOString(),
id: stringColumn(row, "id"),

View File

@ -344,6 +344,99 @@ describe("research task production runtime", () => {
});
});
it("advances with the latest row version after a heartbeat during generation", async () => {
const repository = new MemoryDurableRepository(baseJob());
let heartbeatObserved: (() => void) | undefined;
const heartbeat = new Promise<void>((resolve) => {
heartbeatObserved = resolve;
});
const originalHeartbeat = repository.heartbeatExecution.bind(repository);
const heartbeatExecution = vi
.spyOn(repository, "heartbeatExecution")
.mockImplementation(async (input) => {
const updated = await originalHeartbeat(input);
heartbeatObserved?.();
return updated;
});
const runtime = createResearchTaskRuntime({
...runtimeOptions(repository),
generator: {
stream: async function* () {
await heartbeat;
yield traceStep("query.retrieve");
yield traceStep("query.answer");
},
},
heartbeatIntervalMs: 1,
leaseMs: 1_000,
now: () => 1_000,
});
await expect(runtime.tick()).resolves.toMatchObject({
leased: 1,
succeeded: 1,
});
expect(heartbeatExecution).toHaveBeenCalled();
expect(repository.job.stage).toBe("completed");
});
it("refreshes the stage-transition fence inside the serialized heartbeat lane", async () => {
const repository = new MemoryDurableRepository(baseJob());
let releaseStageGuard: (() => void) | undefined;
const stageGuardRelease = new Promise<void>((resolve) => {
releaseStageGuard = resolve;
});
let stageGuardObserved: (() => void) | undefined;
const stageGuard = new Promise<void>((resolve) => {
stageGuardObserved = resolve;
});
let heartbeatObserved: (() => void) | undefined;
const heartbeat = new Promise<void>((resolve) => {
heartbeatObserved = resolve;
});
let assertionCount = 0;
const originalHeartbeat = repository.heartbeatExecution.bind(repository);
vi.spyOn(repository, "heartbeatExecution").mockImplementation(async (input) => {
const updated = await originalHeartbeat(input);
heartbeatObserved?.();
return updated;
});
const runtime = createResearchTaskRuntime({
...runtimeOptions(repository),
deletionFence: {
assertDeletionFenceUnchanged: async () => {
assertionCount += 1;
if (assertionCount === 2) {
stageGuardObserved?.();
await stageGuardRelease;
}
},
captureDeletionFence: async (scope) => ({ scope }) as never,
},
generator: {
stream: async function* () {
yield traceStep("query.retrieve");
yield traceStep("query.answer");
},
},
heartbeatIntervalMs: 1,
leaseMs: 1_000,
now: () => 1_000,
});
const tick = runtime.tick();
await stageGuard;
await heartbeat;
releaseStageGuard?.();
await expect(tick).resolves.toMatchObject({
leased: 1,
retryScheduled: 0,
succeeded: 1,
});
expect(repository.job.stage).toBe("completed");
});
it.each([
{
error: `Research task partial result answer exceeds maxChars=${RESEARCH_TASK_PARTIAL_ANSWER_MAX_CHARS}`,
@ -1210,8 +1303,23 @@ class MemoryDurableRepository implements ResearchTaskDurableRepository {
return [structuredClone(this.job)];
}
async heartbeatExecution(): Promise<ResearchTaskJob | null> {
throw new Error("Unexpected heartbeat in a sub-interval test");
async heartbeatExecution(
input: ResearchTaskExecutionFence & {
readonly leaseExpiresAt: number;
readonly workerId: string;
},
): Promise<ResearchTaskJob | null> {
if (!this.matchesFence(input) || input.workerId !== this.job.workerId) {
return null;
}
this.job = {
...this.job,
heartbeatAt: input.now,
leaseExpiresAt: input.leaseExpiresAt,
rowVersion: this.job.rowVersion + 1,
updatedAt: input.now,
};
return structuredClone(this.job);
}
async advanceExecution(

View File

@ -266,10 +266,10 @@ export function createResearchTaskRuntime({
authorizationContext,
abortSignal: abortController.signal,
capabilityGrants,
current,
deletionFence,
deletionToken,
generator,
getCurrent: () => current,
manifests,
now,
partials,
@ -277,6 +277,9 @@ export function createResearchTaskRuntime({
publishProgress,
repository,
serialize,
updateCurrent: (updated) => {
current = updated;
},
});
await assertWritable();
const completed = await serialize(() => repository.completeExecution(fence(current, now())));
@ -481,10 +484,10 @@ async function runResearchTask({
authorizationContext: initialAuthorizationContext,
abortSignal,
capabilityGrants,
current: initial,
deletionFence,
deletionToken,
generator,
getCurrent,
manifests,
now,
partials,
@ -492,6 +495,7 @@ async function runResearchTask({
publishProgress,
repository,
serialize,
updateCurrent,
}: {
readonly access: Pick<KnowledgeSpaceAccessService, "revalidatePermissionSnapshot">;
readonly allowLegacyProfileFallback: boolean;
@ -500,10 +504,10 @@ async function runResearchTask({
readonly capabilityGrants?:
| Pick<CapabilityGrantProvenanceRepository, "assertPublicationAllowed" | "get">
| undefined;
readonly current: ResearchTaskJob;
readonly deletionFence?: DeletionLifecycleFenceGuard | undefined;
readonly deletionToken?: DeletionLifecycleFenceToken | undefined;
readonly generator: QueryGenerator;
readonly getCurrent: () => ResearchTaskJob;
readonly manifests: KnowledgeSpaceManifestRepository;
readonly now: () => number;
readonly partials: ResearchTaskPartialResultRepository;
@ -515,8 +519,9 @@ async function runResearchTask({
) => Promise<void>;
readonly repository: ResearchTaskDurableRepository;
readonly serialize: <T>(operation: () => Promise<T>) => Promise<T>;
readonly updateCurrent: (current: ResearchTaskJob) => void;
}): Promise<ResearchTaskJob> {
let current = initial;
let current = getCurrent();
let authorizationContext = initialAuthorizationContext;
const assertWritable = async (): Promise<void> => {
if (deletionToken) {
@ -524,6 +529,7 @@ async function runResearchTask({
}
};
const revalidate = async () => {
current = getCurrent();
if (abortSignal.aborted) {
throw abortSignal.reason ?? new Error("Research task execution lease was lost");
}
@ -534,15 +540,19 @@ async function runResearchTask({
);
};
const advance = async (nextStage: ResearchTaskJobStage) => {
const previousStage = current.stage;
await assertWritable();
const updated = await serialize(() =>
repository.advanceExecution({ ...fence(current, now()), nextStage }),
);
const transition = await serialize(async () => {
current = getCurrent();
const previousStage = current.stage;
const updated = await repository.advanceExecution({ ...fence(current, now()), nextStage });
return { previousStage, updated };
});
const { previousStage, updated } = transition;
if (!updated) {
throw new Error("Research task stage transition lost its lease fence");
}
current = updated;
updateCurrent(updated);
await assertWritable();
await publishProgress(updated, "research_task.stage_changed", { previousStage });
};
@ -684,7 +694,7 @@ async function runResearchTask({
tenantId: current.tenantId,
});
}
return current;
return getCurrent();
}
function durableRequestedMode(

View File

@ -140,6 +140,63 @@ describe("createDifyModelRuntimeClient", () => {
expect(fetchImpl).toHaveBeenCalledOnce();
});
it("routes structured LLM requests through Dify's structured-output endpoint", async () => {
let body: Record<string, unknown> | undefined;
let url: string | undefined;
const responseBody = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(
frame({
data: {
delta: { finish_reason: "stop" },
structured_output: { scores: [{ candidateId: "c1", score: 1 }] },
},
error: "",
}),
);
controller.close();
},
});
const client = createDifyModelRuntimeClient({
apiKey: "inner-secret",
baseUrl: "http://api:5001",
fetch: vi.fn(async (input, init) => {
body = JSON.parse(String(init?.body)) as Record<string, unknown>;
url = String(input);
return new Response(responseBody, {
headers: { "content-type": "text/event-stream" },
status: 200,
});
}),
});
const structuredOutputSchema = {
properties: { scores: { type: "array" } },
required: ["scores"],
type: "object",
};
const chunks: unknown[] = [];
for await (const chunk of client.invokeLlm({
model: "openrouter/auto",
pluginId: "langgenius/openrouter",
promptMessages: [{ content: "score", role: "user" }],
provider: "openrouter",
structuredOutputSchema,
tenantId: "tenant-1",
})) {
chunks.push(chunk);
}
expect(url).toBe("http://api:5001/inner/api/invoke/llm/structured-output");
expect(body).toMatchObject({ structured_output_schema: structuredOutputSchema });
expect(chunks).toEqual([
{
delta: { finish_reason: "stop" },
structured_output: { scores: [{ candidateId: "c1", score: 1 }] },
},
]);
});
it("lists tenant-active models through the Dify catalog endpoint", async () => {
let body: unknown;
const item = {

View File

@ -50,6 +50,7 @@ export interface DifyLlmInput extends DifyModelRequestContext {
readonly completionParams?: Readonly<Record<string, unknown>> | undefined;
readonly promptMessages: readonly unknown[];
readonly stop?: readonly string[] | undefined;
readonly structuredOutputSchema?: Readonly<Record<string, unknown>> | undefined;
readonly tools?: readonly unknown[] | undefined;
}
@ -209,9 +210,12 @@ export function createDifyModelRuntimeClient(
async *invokeLlm(input) {
const deadline = createDeadline(input.signal, requestTimeoutMs);
try {
const structuredOutputSchema = input.structuredOutputSchema;
const response = await request(
fetchImpl,
`${baseUrl}/inner/api/invoke/llm`,
`${baseUrl}/inner/api/invoke/llm${
structuredOutputSchema === undefined ? "" : "/structured-output"
}`,
apiKey,
{
...commonPayload(input),
@ -221,6 +225,9 @@ export function createDifyModelRuntimeClient(
prompt_messages: [...input.promptMessages],
stop: [...(input.stop ?? [])],
stream: true,
...(structuredOutputSchema === undefined
? {}
: { structured_output_schema: { ...structuredOutputSchema } }),
tools: [...(input.tools ?? [])],
},
deadline.signal,

View File

@ -119,6 +119,42 @@ describe("Dify model runtime LLM provider", () => {
});
});
it("prefers Dify's validated structured output over free-form response text", async () => {
let captured: DifyLlmInput | undefined;
const provider = createDifyModelRuntimeLlmProvider({
...BASE,
client: fakeClient(
() => [
deltaChunk("I will explain before returning JSON."),
{
delta: { finish_reason: "stop" },
structured_output: {
scores: [{ candidateId: "c1", reason: "direct", score: 1 }],
},
},
],
(input) => {
captured = input;
},
),
});
const structuredOutputSchema = {
properties: { scores: { type: "array" } },
required: ["scores"],
type: "object",
};
const result = await provider.generate({
messages: [{ content: "Score every candidate.", role: "user" }],
model: BASE.model,
structuredOutputSchema,
tenantId: "tenant-abc",
});
expect(captured?.structuredOutputSchema).toEqual(structuredOutputSchema);
expect(result.text).toBe('{"scores":[{"candidateId":"c1","reason":"direct","score":1}]}');
});
it("keeps content from Dify stream frames with null usage", async () => {
const provider = createDifyModelRuntimeLlmProvider({
...BASE,

View File

@ -27,6 +27,7 @@ export interface GenerateTextInput {
readonly messages: readonly LlmMessage[];
readonly model: string;
readonly signal?: AbortSignal;
readonly structuredOutputSchema?: Readonly<Record<string, unknown>>;
readonly temperature?: number;
/** Tenant scope for Dify-managed model routing. */
readonly tenantId?: string;
@ -750,6 +751,7 @@ const DifyModelRuntimeLlmChunkSchema = z.object({
.partial()
.optional(),
model: z.string().optional(),
structured_output: z.unknown().optional(),
});
/**
@ -792,6 +794,8 @@ export function createDifyModelRuntimeLlmProvider(
const maxTokens = input.maxOutputTokens ?? options.maxOutputTokens;
let finishReason = "stop";
let structuredOutput: unknown;
let structuredOutputFallbackText = "";
let usage: LlmUsage | undefined;
for await (const chunk of options.client.invokeLlm({
@ -806,6 +810,9 @@ export function createDifyModelRuntimeLlmProvider(
role: message.role,
})),
provider: options.provider,
...(input.structuredOutputSchema === undefined
? {}
: { structuredOutputSchema: input.structuredOutputSchema }),
tenantId,
...(input.signal ? { signal: input.signal } : {}),
})) {
@ -818,7 +825,15 @@ export function createDifyModelRuntimeLlmProvider(
const content = parsed.data.delta?.message?.content;
if (content) {
yield { delta: content, type: "delta" };
if (input.structuredOutputSchema === undefined) {
yield { delta: content, type: "delta" };
} else {
structuredOutputFallbackText += content;
}
}
if (parsed.data.structured_output !== undefined) {
structuredOutput = parsed.data.structured_output;
}
const chunkUsage = parsed.data.delta?.usage;
@ -842,6 +857,16 @@ export function createDifyModelRuntimeLlmProvider(
}
}
if (input.structuredOutputSchema !== undefined) {
const text =
structuredOutput === undefined
? structuredOutputFallbackText
: JSON.stringify(structuredOutput);
if (text) {
yield { delta: text, type: "delta" };
}
}
yield {
finishReason,
// Dify invokes the exact tenant-scoped catalog route requested above. Individual

View File

@ -0,0 +1,114 @@
import type { KnowledgeFsStreamCapabilityResponse } from '@dify/contracts/api/console/knowledge-fs/types.gen'
import { streamResearchTaskEvents } from '../services/research-task-events'
describe('streamResearchTaskEvents', () => {
it('gets the capability stream and validates progress events', async () => {
const encoder = new TextEncoder()
const responseBody = new ReadableStream({
start(controller) {
controller.enqueue(
encoder.encode(
'id: 1\nevent: research_task.progress\ndata: {"createdAt":"2026-07-31T10:00:00.000Z","id":"event-1","payload":{},"researchTaskJobId":"task-1","sequence":1,"stage":"planning","type":"research_task.started"}\n\n',
),
)
controller.enqueue(
encoder.encode(
'id: 2\nevent: completed\ndata: {"createdAt":"2026-07-31T10:00:12.000Z","id":"event-2","payload":{},"researchTaskJobId":"task-1","sequence":2,"stage":"completed","type":"research_task.stage_changed"}\n\n',
),
)
controller.close()
},
})
const fetchMock = vi
.spyOn(globalThis, 'fetch')
.mockResolvedValue(new Response(responseBody, { status: 200 }))
const events: unknown[] = []
const capability: KnowledgeFsStreamCapabilityResponse = {
expires_at: '2026-07-31T10:05:00.000Z',
operation_id: 'streamResearchTask',
token: `header.${btoa(JSON.stringify({ trace_id: 'research-trace-1' }))}.signature`,
url: 'https://knowledge.example.test/research/events',
}
const result = await streamResearchTaskEvents({
capability,
onEvent: (event) => events.push(event),
})
expect(fetchMock).toHaveBeenCalledWith(
capability.url,
expect.objectContaining({
credentials: 'omit',
headers: expect.objectContaining({
Accept: 'text/event-stream',
Authorization: `Bearer ${capability.token}`,
'X-Trace-ID': 'research-trace-1',
}),
method: 'GET',
}),
)
expect(events).toEqual([
{
createdAt: '2026-07-31T10:00:00.000Z',
id: 'event-1',
payload: {},
researchTaskJobId: 'task-1',
sequence: 1,
stage: 'planning',
type: 'research_task.started',
},
{
createdAt: '2026-07-31T10:00:12.000Z',
id: 'event-2',
payload: {},
researchTaskJobId: 'task-1',
sequence: 2,
stage: 'completed',
type: 'research_task.stage_changed',
},
])
expect(result).toEqual({ cursor: '2', reconnect: false, terminal: true })
fetchMock.mockRestore()
})
it('resumes from the latest cursor after a timeout', async () => {
const encoder = new TextEncoder()
const responseBody = new ReadableStream({
start(controller) {
controller.enqueue(
encoder.encode(
'id: 3\nevent: research_task.progress\ndata: {"createdAt":"2026-07-31T10:00:20.000Z","id":"event-3","payload":{},"researchTaskJobId":"task-1","sequence":3,"stage":"retrieving","type":"research_task.stage_changed"}\n\n',
),
)
controller.enqueue(
encoder.encode('event: timeout\ndata: {"cursor":"3","researchTaskJobId":"task-1"}\n\n'),
)
controller.close()
},
})
const fetchMock = vi
.spyOn(globalThis, 'fetch')
.mockResolvedValue(new Response(responseBody, { status: 200 }))
const capability: KnowledgeFsStreamCapabilityResponse = {
expires_at: '2026-07-31T10:05:00.000Z',
operation_id: 'streamResearchTask',
token: `header.${btoa(JSON.stringify({ trace_id: 'research-trace-1' }))}.signature`,
url: 'https://knowledge.example.test/research/events?knowledgeSpaceId=space-1',
}
const result = await streamResearchTaskEvents({
capability,
cursor: '2',
onEvent: vi.fn(),
})
expect(fetchMock).toHaveBeenCalledWith(
`${capability.url}&cursor=2`,
expect.objectContaining({ method: 'GET' }),
)
expect(result).toEqual({ cursor: '3', reconnect: true, terminal: false })
fetchMock.mockRestore()
})
})

View File

@ -15,7 +15,10 @@ const apiMock = vi.hoisted(() => ({
refetchPartials: vi.fn(),
refetchTasks: vi.fn(),
refetchTraces: vi.fn(),
researchTasks: [] as Array<Record<string, unknown>>,
streamCapability: vi.fn(),
streamQuery: vi.fn(),
streamResearchEvents: vi.fn(),
documentReferences: {} as Record<string, { id: string; title: string }>,
evidence: undefined as Record<string, unknown> | undefined,
traceDetail: undefined as Record<string, unknown> | undefined,
@ -36,6 +39,10 @@ vi.mock('../services/knowledge-query-events', () => ({
streamKnowledgeQuery: apiMock.streamQuery,
}))
vi.mock('../services/research-task-events', () => ({
streamResearchTaskEvents: apiMock.streamResearchEvents,
}))
vi.mock('@tanstack/react-query', async (importOriginal) => {
const original = await importOriginal<typeof import('@tanstack/react-query')>()
return {
@ -65,7 +72,7 @@ vi.mock('@tanstack/react-query', async (importOriginal) => {
}
if (resource === 'tasks')
return {
data: { data: [] },
data: { data: apiMock.researchTasks },
refetch: apiMock.refetchTasks,
}
if (resource === 'partials')
@ -81,6 +88,11 @@ vi.mock('@tanstack/react-query', async (importOriginal) => {
vi.mock('@/service/client', () => ({
consoleClient: {
knowledgeFs: {
tasks: {
byTaskId: {
streamCapability: { post: apiMock.streamCapability },
},
},
spaces: {
byControlSpaceId: {
queries: { admission: { post: apiMock.queryAdmission } },
@ -178,7 +190,18 @@ describe('RetrievalTestPage', () => {
})
apiMock.refetchTasks.mockResolvedValue(undefined)
apiMock.refetchTraces.mockResolvedValue(undefined)
apiMock.researchTasks = []
apiMock.streamCapability.mockResolvedValue({
expires_at: '2026-07-31T10:30:00.000Z',
operation_id: 'streamResearchTask',
token: 'capability-token',
url: 'https://knowledge.example.test/research/events',
})
apiMock.streamQuery.mockResolvedValue(undefined)
apiMock.streamResearchEvents.mockResolvedValue({
reconnect: false,
terminal: false,
})
apiMock.queryAdmission.mockResolvedValue({})
apiMock.createBadCase.mockResolvedValue({ id: 'bad-case-1' })
apiMock.createGolden.mockResolvedValue({ id: 'golden-1' })
@ -228,6 +251,205 @@ describe('RetrievalTestPage', () => {
})
})
it('replays research progress events and shows actual stage durations', async () => {
apiMock.researchTasks = [
{
completed_at: 1_800_000_025,
cost: {},
created_at: 1_800_000_000,
id: 'research-completed',
knowledge_space_id: 'space-1',
metadata: {},
mode: 'research',
query: 'Compare the refund policies',
stage: 'completed',
updated_at: 1_800_000_025,
},
]
apiMock.streamResearchEvents.mockImplementation(
async ({
onEvent,
}: {
onEvent: (event: {
createdAt: string
id: string
payload: Record<string, unknown>
researchTaskJobId: string
sequence: number
stage: string
type: string
}) => void
}) => {
const stages = [
['planning', 0],
['retrieving', 2],
['analyzing', 7],
['generating', 14],
['completed', 25],
] as const
stages.forEach(([stage, seconds], index) =>
onEvent({
createdAt: new Date(1_800_000_000_000 + seconds * 1000).toISOString(),
id: `event-${index + 1}`,
payload: {},
researchTaskJobId: 'research-completed',
sequence: index + 1,
stage,
type: index ? 'research_task.stage_changed' : 'research_task.started',
}),
)
return { cursor: '5', reconnect: false, terminal: true }
},
)
const user = userEvent.setup()
renderPage()
await user.click(screen.getByText('Compare the refund policies'))
expect(
screen.getByRole('button', {
name: 'dataset.newKnowledge.retrievalTest.processLog',
}),
).toHaveAttribute('aria-pressed', 'true')
await waitFor(() =>
expect(apiMock.streamCapability).toHaveBeenCalledWith({
body: { control_space_id: 'space-1' },
params: { task_id: 'research-completed' },
}),
)
expect(apiMock.streamResearchEvents).toHaveBeenCalledWith(
expect.objectContaining({
capability: expect.objectContaining({ operation_id: 'streamResearchTask' }),
}),
)
expect(await screen.findByText('2s')).toBeInTheDocument()
expect(screen.getByText('5s')).toBeInTheDocument()
expect(screen.getByText('7s')).toBeInTheDocument()
expect(screen.getByText('11s')).toBeInTheDocument()
expect(apiMock.refetchTasks).not.toHaveBeenCalled()
expect(apiMock.refetchPartials).not.toHaveBeenCalled()
})
it('reconnects an active research event stream from its latest cursor', async () => {
apiMock.researchTasks = [
{
cost: {},
created_at: 1_800_000_000,
id: 'research-active',
knowledge_space_id: 'space-1',
metadata: {},
mode: 'research',
query: 'Compare the refund policies',
stage: 'retrieving',
updated_at: 1_800_000_005,
},
]
apiMock.streamResearchEvents
.mockResolvedValueOnce({ cursor: '3', reconnect: true, terminal: false })
.mockResolvedValueOnce({ cursor: '4', reconnect: false, terminal: true })
const user = userEvent.setup()
renderPage()
await user.click(screen.getByText('Compare the refund policies'))
await waitFor(() => expect(apiMock.streamCapability).toHaveBeenCalledTimes(2))
expect(apiMock.streamResearchEvents).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ cursor: '3' }),
)
})
it('refreshes final partials only once when a streamed task completes', async () => {
const activeTask = {
cost: {},
created_at: 1_800_000_000,
id: 'research-active',
knowledge_space_id: 'space-1',
metadata: {},
mode: 'research',
query: 'Compare the refund policies',
stage: 'retrieving',
updated_at: 1_800_000_005,
}
apiMock.researchTasks = [activeTask]
apiMock.refetchTasks.mockImplementation(async () => {
apiMock.researchTasks = [
{
...activeTask,
completed_at: 1_800_000_025,
stage: 'completed',
updated_at: 1_800_000_025,
},
]
})
apiMock.streamResearchEvents.mockImplementation(
async ({
onEvent,
}: {
onEvent: (event: {
createdAt: string
id: string
payload: Record<string, unknown>
researchTaskJobId: string
sequence: number
stage: string
type: string
}) => void
}) => {
onEvent({
createdAt: '2027-01-15T08:00:25.000Z',
id: 'event-completed',
payload: {},
researchTaskJobId: 'research-active',
sequence: 5,
stage: 'completed',
type: 'research_task.stage_changed',
})
return { cursor: '5', reconnect: false, terminal: true }
},
)
const user = userEvent.setup()
renderPage()
await user.click(screen.getByText('Compare the refund policies'))
await waitFor(() => expect(apiMock.refetchTasks).toHaveBeenCalledOnce())
await waitFor(() => expect(apiMock.refetchPartials).toHaveBeenCalledOnce())
})
it('does not let the composer shortcut bypass an active research task', async () => {
apiMock.researchTasks = [
{
cost: {},
created_at: 1_800_000_000,
id: 'research-active',
knowledge_space_id: 'space-1',
metadata: {},
mode: 'research',
query: 'Compare the refund policies',
stage: 'retrieving',
updated_at: 1_800_000_005,
},
]
const user = userEvent.setup()
renderPage()
expect(
screen.getByText('dataset.newKnowledge.retrievalTest.retrievingActive · 2/4'),
).toBeInTheDocument()
await user.click(screen.getByText('Compare the refund policies'))
const queryInput = screen.getByLabelText('dataset.newKnowledge.retrievalTest.queryPlaceholder')
expect(
screen.getByRole('button', { name: 'dataset.newKnowledge.retrievalTest.startResearch' }),
).toBeDisabled()
await user.click(queryInput)
await user.keyboard('{Control>}{Enter}{/Control}')
expect(apiMock.planResearch).not.toHaveBeenCalled()
expect(apiMock.queryAdmission).not.toHaveBeenCalled()
})
it('persists a selected trace as a production bad case', async () => {
apiMock.traces = [
{
@ -333,7 +555,7 @@ describe('RetrievalTestPage', () => {
expect(
screen.getByRole('heading', {
name: 'dataset.newKnowledge.retrievalTest.result:{"mode":"dataset.newKnowledge.settings.retrievalMode.deep"}',
name: 'dataset.newKnowledge.retrievalTest.result',
}),
).toBeInTheDocument()
await user.click(

View File

@ -4,15 +4,18 @@ import type {
KnowledgeFsResearchTaskPlanResponse,
KnowledgeFsResearchTaskResponse,
} from '@dify/contracts/api/console/knowledge-fs/types.gen'
import type { Hotkey } from '@tanstack/react-hotkeys'
import type {
RetrievalEvidence,
RetrievalTestMode,
RetrievalTestRecord,
} from './retrieval-test-model'
import type { KnowledgeQueryEvent } from './services/knowledge-query-events'
import type { ResearchTaskProgressEvent } from './services/research-task-events'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
import { matchesKeyboardEvent } from '@tanstack/react-hotkeys'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -30,6 +33,7 @@ import {
} from './retrieval-test-model'
import { newKnowledgeDocumentDetailPath, newKnowledgeQualityPath } from './routes'
import { streamKnowledgeQuery } from './services/knowledge-query-events'
import { streamResearchTaskEvents } from './services/research-task-events'
type LocalQueryRun = {
endedAt?: number
@ -51,11 +55,22 @@ type SelectedRun = {
type QualityDecision = 'bad-case' | 'golden'
const researchStageOrder = ['planning', 'retrieving', 'analyzing', 'generating'] as const
type ResearchStage = (typeof researchStageOrder)[number]
const runRetrievalHotkey = 'Mod+Enter' satisfies Hotkey
function timeValue(value: number) {
return value < 10_000_000_000 ? value * 1000 : value
}
function formatRecordTime(value: number) {
return new Intl.DateTimeFormat(undefined, {
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
month: 'short',
}).format(value)
}
function useClock(enabled: boolean) {
const [now, setNow] = useState(() => Date.now())
useEffect(() => {
@ -66,6 +81,72 @@ function useClock(enabled: boolean) {
return now
}
function researchStageIndex(stage: KnowledgeFsResearchTaskResponse['stage']) {
if (stage === 'queued' || stage === 'paused') return 0
if (stage === 'completed') return researchStageOrder.length
return researchStageOrder.findIndex((item) => item === stage)
}
function estimatedStageDuration(
plan: KnowledgeFsResearchTaskPlanResponse | undefined,
stage: ResearchStage,
) {
if (!plan) return undefined
const stepNames: Record<ResearchStage, Set<string>> = {
analyzing: new Set(['analyze']),
generating: new Set(['generate']),
planning: new Set(['plan']),
retrieving: new Set(['inspect', 'retrieve']),
}
const milliseconds = plan.steps.reduce((total, step) => {
if (!stepNames[stage].has(typeof step.name === 'string' ? step.name : '')) return total
return total + (typeof step.estimatedLatencyMs === 'number' ? step.estimatedLatencyMs : 0)
}, 0)
return milliseconds > 0 ? formatDuration(milliseconds) : undefined
}
function researchProgressTime(event: ResearchTaskProgressEvent) {
return Date.parse(event.createdAt)
}
function actualStageDuration(
events: ResearchTaskProgressEvent[],
stage: ResearchStage,
task: KnowledgeFsResearchTaskResponse,
now: number,
) {
const start = events.find((event) => event.stage === stage)
if (!start) return
const startedAt = researchProgressTime(start)
const next = events.find((event) => {
if (event.sequence <= start.sequence) return false
return (
event.stage === 'canceled' ||
event.stage === 'completed' ||
event.stage === 'failed' ||
(researchStageOrder.includes(event.stage as ResearchStage) && event.stage !== stage)
)
})
const endedAt = next
? researchProgressTime(next)
: task.stage === stage
? now
: task.completed_at
? timeValue(task.completed_at)
: undefined
if (endedAt === undefined || endedAt < startedAt) return
return formatDuration(endedAt - startedAt)
}
function mergeResearchProgressEvent(
events: ResearchTaskProgressEvent[],
event: ResearchTaskProgressEvent,
) {
const next = events.filter((candidate) => candidate.sequence !== event.sequence)
next.push(event)
return next.sort((left, right) => left.sequence - right.sequence)
}
function ScorePill({ score }: { score: number }) {
const normalized = Math.max(0, Math.min(1, score))
return (
@ -132,7 +213,7 @@ function EvidenceCard({
</h3>
{evidence.score !== undefined && <ScorePill score={evidence.score} />}
</div>
<p className="px-3 pt-1 pb-2 body-sm-regular text-text-secondary">
<p className="px-3 pt-1 pb-2 body-md-regular tracking-[-0.07px] text-text-secondary">
<span className="line-clamp-2 whitespace-pre-wrap">{evidence.text}</span>
</p>
{evidence.images.length > 0 && (
@ -155,7 +236,7 @@ function EvidenceCard({
)}
</div>
)}
<footer className="flex min-h-10 items-center gap-1.5 border-t border-divider-subtle py-2 pr-2 pl-3">
<footer className="flex h-10 items-center gap-1.5 border-t border-divider-subtle py-2 pr-2 pl-3">
<span
aria-hidden
className="i-ri-file-pdf-2-fill size-4 shrink-0 text-util-colors-red-red-500"
@ -294,7 +375,7 @@ function QualityActions({
<Button
disabled={pending}
loading={pending}
variant={noResults ? 'primary' : 'ghost'}
variant={noResults ? 'primary' : 'secondary'}
onClick={() => void onDecision('bad-case')}
>
<span aria-hidden className="mr-1 i-ri-thumb-down-line size-4" />
@ -316,12 +397,16 @@ function QualityActions({
}
function ResearchProcess({
evidenceCount,
events,
expanded,
onCancel,
onToggle,
plan,
task,
}: {
evidenceCount: number
events: ResearchTaskProgressEvent[]
expanded: boolean
onCancel?: () => void
onToggle: () => void
@ -331,10 +416,25 @@ function ResearchProcess({
const { t } = useTranslation('dataset')
const active = researchTaskIsActive(task)
const now = useClock(active)
const startedAt = timeValue(task.created_at)
const endedAt = task.completed_at ? timeValue(task.completed_at) : now
const firstProgressAt = events[0] ? researchProgressTime(events[0]) : undefined
const terminalProgress = [...events]
.reverse()
.find(
(event) =>
event.stage === 'canceled' || event.stage === 'completed' || event.stage === 'failed',
)
const startedAt = firstProgressAt ?? timeValue(task.created_at)
const endedAt = terminalProgress
? researchProgressTime(terminalProgress)
: task.completed_at
? timeValue(task.completed_at)
: now
const duration = formatDuration(endedAt - startedAt)
const currentIndex = researchStageOrder.findIndex((stage) => stage === task.stage)
const currentIndex = researchStageIndex(task.stage)
const latestVisitedIndex = events.reduce((latest, event) => {
const index = researchStageOrder.indexOf(event.stage as ResearchStage)
return Math.max(latest, index)
}, -1)
const summary =
task.stage === 'completed'
? t(($) => $['newKnowledge.retrievalTest.completedIn'], { duration })
@ -349,39 +449,47 @@ function ResearchProcess({
planning: t(($) => $['newKnowledge.retrievalTest.planning']),
retrieving: t(($) => $['newKnowledge.retrievalTest.retrieving']),
}
const activeLabels: Record<(typeof researchStageOrder)[number], string> = {
analyzing: t(($) => $['newKnowledge.retrievalTest.analyzingActive']),
generating: t(($) => $['newKnowledge.retrievalTest.generatingActive']),
planning: t(($) => $['newKnowledge.retrievalTest.planningActive']),
retrieving: t(($) => $['newKnowledge.retrievalTest.retrievingActive']),
}
return (
<section className="overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg">
<section
className={cn(
'max-w-full overflow-hidden rounded-[10px] bg-components-panel-bg',
expanded ? 'w-full' : 'w-fit',
)}
>
<button
type="button"
aria-expanded={expanded}
className="flex min-h-14 w-full items-center gap-3 px-4 text-left outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset"
className="flex min-h-10 max-w-full items-center gap-1.5 px-3 py-2 text-left outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset"
onClick={onToggle}
>
<span
aria-hidden
className={cn(
'size-5 text-text-accent',
active && 'i-ri-loader-4-line animate-spin motion-reduce:animate-none',
task.stage === 'completed' && 'i-ri-checkbox-circle-fill',
task.stage === 'canceled' && 'i-ri-stop-circle-fill text-text-tertiary',
task.stage === 'failed' && 'i-ri-error-warning-fill text-text-destructive',
)}
/>
<span className="min-w-0 flex-1">
<span className="block system-sm-semibold text-text-primary">{summary}</span>
{active && (
<span className="mt-0.5 block system-xs-regular text-text-tertiary">
{duration}
{plan?.budget.budget_usd !== undefined && plan.budget.budget_usd !== null
? ` · $${plan.budget.budget_usd.toFixed(2)}`
: ''}
</span>
)}
{task.stage === 'completed' ? (
<img src="/images/new-rag/vibe-coding-star.svg" alt="" className="size-3.5 shrink-0" />
) : (
<span
aria-hidden
className={cn(
'size-3.5 shrink-0 text-text-accent',
active && 'i-ri-loader-4-line animate-spin motion-reduce:animate-none',
task.stage === 'canceled' && 'i-ri-stop-circle-fill text-text-tertiary',
task.stage === 'failed' && 'i-ri-error-warning-fill text-text-destructive',
)}
/>
)}
<span className="truncate system-sm-regular whitespace-nowrap text-text-secondary">
{summary}
</span>
{active && <span className="system-xs-regular text-text-tertiary">{duration}</span>}
{active && onCancel && (
<Button
variant="ghost"
size="small"
variant="secondary"
onClick={(event) => {
event.stopPropagation()
onCancel()
@ -393,52 +501,68 @@ function ResearchProcess({
<span
aria-hidden
className={cn(
'i-ri-arrow-down-s-line size-5 text-text-tertiary transition-transform',
'i-ri-arrow-down-s-line size-4.5 shrink-0 text-text-tertiary transition-transform',
expanded && 'rotate-180',
)}
/>
</button>
{expanded && (
<div className="border-t border-divider-subtle px-4 py-4">
<div className="mb-4 flex items-center gap-4 system-xs-semibold text-text-tertiary">
<span className="text-text-primary">
{t(($) => $['newKnowledge.retrievalTest.processLog'])}
</span>
<span>{t(($) => $['newKnowledge.retrievalTest.quality'])}</span>
</div>
<ol className="space-y-0">
<div className="border-t border-divider-subtle px-3 pt-2.5 pb-3.5">
<ol>
{researchStageOrder.map((stage, index) => {
const completed = task.stage === 'completed' || index < currentIndex
const current = stage === task.stage
const completed =
task.stage === 'completed' || index < currentIndex || index < latestVisitedIndex
const current = index === currentIndex && active
const stageDuration =
actualStageDuration(events, stage, task, now) ?? estimatedStageDuration(plan, stage)
return (
<li key={stage} className="relative flex min-h-10 items-start gap-3">
{index < researchStageOrder.length - 1 && (
<span
aria-hidden
className={cn(
'absolute top-5 left-1.75 h-6 w-px',
completed ? 'bg-util-colors-blue-blue-500' : 'bg-divider-regular',
)}
/>
)}
<li key={stage} className="flex items-stretch gap-2.5 overflow-hidden">
<span
aria-hidden
className={cn(
'relative mt-0.5 size-4 shrink-0 rounded-full border-2',
completed && 'border-util-colors-blue-blue-500 bg-util-colors-blue-blue-500',
current &&
active &&
'i-ri-loader-4-line animate-spin border-0 text-text-accent motion-reduce:animate-none',
!completed && !current && 'border-divider-deep bg-components-panel-bg',
className="flex w-3 shrink-0 flex-col items-center overflow-hidden"
>
<span className="flex h-5 w-3 shrink-0 items-center justify-center">
<span
className={cn(
'block size-1.75 shrink-0 rounded-full',
completed ? 'bg-gray-400' : 'bg-divider-deep',
current &&
'i-ri-loader-4-line size-2.5 animate-spin rounded-none text-text-accent motion-reduce:animate-none',
)}
/>
</span>
{index < researchStageOrder.length - 1 && (
<span className="min-h-0 w-px flex-1 bg-divider-regular" />
)}
/>
</span>
<span
className={cn(
'system-sm-medium',
completed || current ? 'text-text-primary' : 'text-text-quaternary',
'flex min-w-0 flex-1 flex-col overflow-hidden',
index < researchStageOrder.length - 1 ? 'pb-4' : 'pb-0.5',
)}
>
{labels[stage]}
<span className="flex min-h-5 items-start justify-between gap-3">
<span
className={cn(
'text-[13px] leading-5 font-medium text-text-tertiary',
(completed || current) && 'text-text-primary',
)}
>
{current ? activeLabels[stage] : labels[stage]}
</span>
{stageDuration && (completed || current) && (
<span className="shrink-0 system-xs-regular text-text-tertiary">
{stageDuration}
</span>
)}
</span>
{current && stage === 'retrieving' && evidenceCount > 0 && (
<span className="mt-1.5 system-xs-regular text-text-tertiary">
{t(($) => $['newKnowledge.retrievalTest.foundSoFar'], {
count: evidenceCount,
})}
</span>
)}
</span>
</li>
)
@ -462,6 +586,12 @@ function RecordButton({
record: RetrievalTestRecord
}) {
const { t } = useTranslation('dataset')
const activeResearchStage =
record.kind === 'research' && record.status === 'running'
? researchStageOrder[
Math.min(Math.max(researchStageIndex(record.stage), 0), researchStageOrder.length - 1)
]
: undefined
return (
<button
type="button"
@ -478,15 +608,18 @@ function RecordButton({
<span className="line-clamp-1 system-sm-semibold text-text-secondary">{record.query}</span>
<span className="mt-1.5 flex items-center gap-1 system-xs-regular text-text-tertiary">
<span className="min-w-0 flex-1 truncate">
{t(($) => $[`newKnowledge.settings.retrievalMode.${record.mode}`])}
{activeResearchStage ? (
<>
{t(($) => $[`newKnowledge.retrievalTest.${activeResearchStage}Active`])}
{' · '}
{researchStageOrder.indexOf(activeResearchStage) + 1}/{researchStageOrder.length}
</>
) : (
t(($) => $[`newKnowledge.settings.retrievalMode.${record.mode}`])
)}
</span>
<span className="shrink-0 text-[11px] leading-4 text-text-primary opacity-30">
{new Intl.DateTimeFormat(undefined, {
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
month: 'short',
}).format(record.createdAt)}
{formatRecordTime(record.createdAt)}
</span>
</span>
</span>
@ -508,6 +641,9 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
const [researchPlans, setResearchPlans] = useState<
Record<string, KnowledgeFsResearchTaskPlanResponse>
>({})
const [researchEvents, setResearchEvents] = useState<Record<string, ResearchTaskProgressEvent[]>>(
{},
)
const [researchExpanded, setResearchExpanded] = useState<Record<string, boolean>>({})
const [qualityDecisions, setQualityDecisions] = useState<Record<string, QualityDecision>>({})
const [qualityPendingKey, setQualityPendingKey] = useState<string>()
@ -566,6 +702,14 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
selected?.kind === 'research'
? researchTasksQuery.data?.data.find((task) => task.id === selected.id)
: undefined
const selectedResearchActive = researchTaskIsActive(selectedResearchTask)
const selectedResearchActiveRef = useRef(selectedResearchActive)
useEffect(() => {
selectedResearchActiveRef.current = selectedResearchActive
}, [selectedResearchActive])
const selectedResearchExpanded = selectedResearchTask
? (researchExpanded[selectedResearchTask.id] ?? true)
: false
const selectedTraceId =
selected?.kind === 'trace'
? selected.id
@ -612,6 +756,7 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
refetchInterval: researchTaskIsActive(selectedResearchTask) ? 1000 : false,
})
const refetchResearchPartials = researchPartialsQuery.refetch
const refetchResearchTasks = researchTasksQuery.refetch
const previousSelectedResearchTaskRef = useRef<KnowledgeFsResearchTaskResponse | undefined>(
undefined,
)
@ -622,6 +767,50 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
void refetchResearchPartials()
}, [refetchResearchPartials, selectedResearchTask])
const selectedResearchTaskId = selectedResearchTask?.id
useEffect(() => {
if (!selectedResearchTaskId) return
const controller = new AbortController()
void (async () => {
try {
let cursor: string | undefined
while (!controller.signal.aborted) {
const capability = await consoleClient.knowledgeFs.tasks.byTaskId.streamCapability.post({
body: { control_space_id: knowledgeSpaceId },
params: { task_id: selectedResearchTaskId },
})
const stream = await streamResearchTaskEvents({
capability,
...(cursor ? { cursor } : {}),
onEvent: (event) => {
if (controller.signal.aborted) return
setResearchEvents((current) => ({
...current,
[selectedResearchTaskId]: mergeResearchProgressEvent(
current[selectedResearchTaskId] ?? [],
event,
),
}))
const terminal =
event.stage === 'canceled' ||
event.stage === 'completed' ||
event.stage === 'failed'
if (!terminal || !selectedResearchActiveRef.current) return
if (event.stage === 'completed') void refetchResearchTasks()
else void Promise.all([refetchResearchTasks(), refetchResearchPartials()])
},
signal: controller.signal,
})
if (stream.terminal || !stream.reconnect || !stream.cursor) return
cursor = stream.cursor
}
} catch {
// Task polling remains the fallback when capability streaming is unavailable.
}
})()
return () => controller.abort()
}, [knowledgeSpaceId, refetchResearchPartials, refetchResearchTasks, selectedResearchTaskId])
const historicalEvidence = extractRetrievalEvidence(traceEvidenceQuery.data)
const researchEvidence = extractRetrievalEvidence(researchPartialsQuery.data)
const currentEvidence =
@ -669,6 +858,11 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
selected?.kind === 'local'
? localRun?.mode
: (selectedRecord?.mode ?? traceDetailQuery.data?.mode)
const selectedCreatedAt =
selected?.kind === 'local'
? localRun?.startedAt
: (selectedRecord?.createdAt ??
(selectedResearchTask ? timeValue(selectedResearchTask.created_at) : undefined))
const selectedIsLoading =
(selected?.kind === 'local' && localRun?.status === 'running') ||
(selected?.kind === 'trace' && selectedRecord?.status === 'running') ||
@ -676,18 +870,13 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
(selected?.kind === 'trace' && traceEvidenceQuery.isPending)
const selectedFailed = selected?.kind === 'local' && localRun?.status === 'failed'
const selectedHasNoResults = selected?.kind === 'local' && localRun?.status === 'no-results'
const visibleEvidence = showAll ? currentEvidence : currentEvidence.slice(0, 3)
const visibleEvidence = showAll ? currentEvidence : currentEvidence.slice(0, 5)
const selectRecord = (record: RetrievalTestRecord) => {
setSelected({ id: record.id, kind: record.kind })
setQuery(record.query)
setMode(record.mode)
setShowAll(false)
if (record.kind === 'research' && researchExpanded[record.id] === undefined)
setResearchExpanded((current) => ({
...current,
[record.id]: record.status === 'running',
}))
}
const saveQualityDecision = async (decision: QualityDecision) => {
@ -852,12 +1041,13 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
}
const run = () => {
if (selectedResearchActive) return
if (mode === 'research') void startResearch()
else void runFastQuery()
}
return (
<main className="flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-lg bg-components-panel-bg p-5">
<main className="mx-1 flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-lg bg-components-panel-bg p-5">
<header className="shrink-0">
<h1 className="title-xl-semi-bold leading-6 text-text-primary">
{t(($) => $['newKnowledge.retrievalTest.title'])}
@ -882,7 +1072,7 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
className="block h-36 w-full resize-none bg-transparent p-3.5 body-md-regular text-text-primary outline-hidden placeholder:text-text-quaternary"
onChange={(event) => setQuery(event.target.value)}
onKeyDown={(event) => {
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
if (matchesKeyboardEvent(event.nativeEvent, runRetrievalHotkey)) {
event.preventDefault()
run()
}
@ -898,10 +1088,12 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
<button
key={item}
type="button"
disabled={selectedResearchActive}
aria-pressed={mode === item}
className={cn(
'rounded-md px-2.5 py-1 system-xs-semibold text-text-tertiary capitalize outline-hidden hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid',
mode === item && 'bg-components-panel-bg text-text-primary shadow-xs',
'rounded-md px-2.5 py-1 system-sm-regular text-text-tertiary capitalize outline-hidden hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed disabled:text-text-disabled',
mode === item &&
'bg-components-panel-bg font-medium text-text-primary shadow-xs',
)}
onClick={() => setMode(item)}
>
@ -909,7 +1101,11 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
</button>
))}
</div>
<Button variant="primary" disabled={!query.trim()} onClick={run}>
<Button
variant="primary"
disabled={!query.trim() || selectedResearchActive}
onClick={run}
>
<span aria-hidden className="mr-1 i-ri-play-circle-line size-4" />
{t(($) =>
mode === 'research'
@ -927,7 +1123,7 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
{t(($) => $['newKnowledge.retrievalTest.records'])}
</h2>
</div>
<div className="min-h-0 flex-1 overflow-y-auto">
<div className="min-h-0 flex-1 scrollbar-none overflow-y-auto">
{displayRecords.length > 0 ? (
<div>
{displayRecords.map((record, index) => (
@ -958,46 +1154,64 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
)}
{selected && (
<div className="flex h-full min-h-0 flex-col gap-3">
<div className="flex h-5 shrink-0 items-center gap-2 overflow-hidden pl-3">
<div className="flex h-6 shrink-0 items-center gap-2 overflow-hidden pl-3">
<h2 className="shrink-0 system-sm-semibold text-text-primary">
{selected?.kind === 'research'
? t(($) => $['newKnowledge.retrievalTest.researchResult'])
: t(($) => $['newKnowledge.retrievalTest.result'], {
mode: selectedMode
? t(($) => $[`newKnowledge.settings.retrievalMode.${selectedMode}`])
: '',
})}
: t(($) => $['newKnowledge.retrievalTest.result'])}
</h2>
<span className="shrink-0 rounded-md bg-divider-regular px-1.5 py-0.5 text-[11px] leading-4 font-medium text-text-tertiary capitalize">
{selectedMode
? t(($) => $[`newKnowledge.settings.retrievalMode.${selectedMode}`])
: ''}
</span>
<span className="shrink-0 text-[11px] leading-4 text-text-tertiary">
{t(($) => $['newKnowledge.retrievalTest.justNow'])}
</span>
{selectedCreatedAt && (
<span className="shrink-0 text-[11px] leading-4 text-text-tertiary">
{formatRecordTime(selectedCreatedAt)}
</span>
)}
<span className="min-w-0 flex-1" />
{selectedResearchTask && (
<button
type="button"
aria-pressed={selectedResearchExpanded}
className="flex h-6 shrink-0 items-center gap-1 rounded-md px-1.5 system-xs-medium text-text-tertiary outline-hidden hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid"
onClick={() =>
setResearchExpanded((current) => ({
...current,
[selectedResearchTask.id]: !(current[selectedResearchTask.id] ?? true),
}))
}
>
<span aria-hidden className="i-ri-search-eye-line size-3.5" />
{t(($) => $['newKnowledge.retrievalTest.processLog'])}
</button>
)}
<Link
href={newKnowledgeQualityPath(knowledgeSpaceId)}
className="flex h-6 shrink-0 items-center gap-1 rounded-md px-1.5 system-xs-medium text-text-tertiary outline-hidden hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid"
>
<span aria-hidden className="i-ri-equalizer-2-line size-3.5" />
{t(($) => $['newKnowledge.retrievalTest.quality'])}
</Link>
</div>
<div className="min-h-0 flex-1 overflow-y-auto">
<div className="min-h-0 flex-1 scrollbar-none overflow-y-auto">
{selectedResearchTask && (
<ResearchProcess
task={selectedResearchTask}
plan={researchPlans[selectedResearchTask.id]}
expanded={
researchExpanded[selectedResearchTask.id] ??
researchTaskIsActive(selectedResearchTask)
}
events={researchEvents[selectedResearchTask.id] ?? []}
evidenceCount={currentEvidence.length}
expanded={selectedResearchExpanded}
onToggle={() =>
setResearchExpanded((current) => ({
...current,
[selectedResearchTask.id]: !(
current[selectedResearchTask.id] ??
researchTaskIsActive(selectedResearchTask)
),
[selectedResearchTask.id]: !(current[selectedResearchTask.id] ?? true),
}))
}
onCancel={
researchTaskIsActive(selectedResearchTask)
selectedResearchActive
? () => void cancelResearch(selectedResearchTask.id)
: undefined
}
@ -1027,14 +1241,12 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
)}
{currentEvidence.length > 0 && (
<div className={cn(selectedResearchTask && 'mt-5')}>
{selectedResearchTask && (
<h3 className="mb-3 system-sm-semibold text-text-secondary">
{researchTaskIsActive(selectedResearchTask)
? t(($) => $['newKnowledge.retrievalTest.foundSoFar'], {
count: currentEvidence.length,
})
: `${currentEvidence.length} chunks`}
<div className={cn(selectedResearchTask && 'mt-3')}>
{selectedResearchActive && (
<h3 className="flex h-6 items-start pb-2 pl-3 system-xs-medium text-text-tertiary">
{t(($) => $['newKnowledge.retrievalTest.foundSoFar'], {
count: currentEvidence.length,
})}
</h3>
)}
<div className="space-y-3">
@ -1052,9 +1264,12 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
/>
))}
{selectedResearchTask && researchTaskIsActive(selectedResearchTask) && (
<div className="h-24 animate-pulse rounded-xl border border-components-panel-border bg-components-panel-bg p-4 motion-reduce:animate-none">
<div className="h-3 w-1/3 rounded bg-background-section-burn" />
<div className="mt-4 h-3 w-4/5 rounded bg-background-section-burn" />
<div className="h-16.5 animate-pulse rounded-xl bg-components-panel-bg px-3 py-3.5 opacity-60 motion-reduce:animate-none">
<div className="flex items-start justify-between">
<div className="h-3 w-30 rounded-xs bg-divider-regular" />
<div className="h-4 w-14 rounded-md bg-divider-subtle" />
</div>
<div className="mt-2.5 h-3 w-full rounded-xs bg-divider-subtle" />
</div>
)}
</div>
@ -1062,7 +1277,7 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
)}
</div>
{!showAll && currentEvidence.length > 3 && (
{!showAll && currentEvidence.length > 5 && (
<div className="shrink-0 pl-1">
<button
type="button"

View File

@ -34,7 +34,7 @@ function parseEventBlock(block: string): KnowledgeQueryEvent | undefined {
function capabilityTraceId(token: string) {
const payload = token.split('.')[1]
if (!payload) throw new Error('Knowledge query capability is malformed')
if (!payload) throw new Error('KnowledgeFS stream capability is malformed')
try {
const normalized = payload.replace(/-/g, '+').replace(/_/g, '/')
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=')
@ -51,32 +51,39 @@ function capabilityTraceId(token: string) {
} catch {
// The signed token is verified by KnowledgeFS; the client only echoes its public trace claim.
}
throw new Error('Knowledge query capability is missing its trace binding')
throw new Error('KnowledgeFS stream capability is missing its trace binding')
}
export async function streamKnowledgeQuery({
admission,
export async function streamCapabilityEvents({
body,
method = 'GET',
onEvent,
signal,
token,
url,
}: {
admission: KnowledgeFsQueryAdmissionResponse
body?: unknown
method?: 'GET' | 'POST'
onEvent: (event: KnowledgeQueryEvent) => void
signal?: AbortSignal
token: string
url: string
}) {
const response = await fetch(admission.url, {
body: JSON.stringify(admission.request),
const headers: Record<string, string> = {
Accept: 'text/event-stream',
Authorization: `Bearer ${token}`,
'X-Trace-ID': capabilityTraceId(token),
}
if (body !== undefined) headers['Content-Type'] = 'application/json'
const response = await fetch(url, {
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
credentials: 'omit',
headers: {
Accept: 'text/event-stream',
Authorization: `Bearer ${admission.token}`,
'Content-Type': 'application/json',
'X-Trace-ID': capabilityTraceId(admission.token),
},
method: 'POST',
headers,
method,
signal,
})
if (!response.ok) throw response
if (!response.body) throw new Error('Knowledge query stream has no response body')
if (!response.body) throw new Error('KnowledgeFS event stream has no response body')
const reader = response.body.getReader()
const decoder = new TextDecoder()
@ -97,3 +104,22 @@ export async function streamKnowledgeQuery({
const trailingEvent = parseEventBlock(buffer)
if (trailingEvent) onEvent(trailingEvent)
}
export async function streamKnowledgeQuery({
admission,
onEvent,
signal,
}: {
admission: KnowledgeFsQueryAdmissionResponse
onEvent: (event: KnowledgeQueryEvent) => void
signal?: AbortSignal
}) {
await streamCapabilityEvents({
body: admission.request,
method: 'POST',
onEvent,
signal,
token: admission.token,
url: admission.url,
})
}

View File

@ -0,0 +1,131 @@
import type {
KnowledgeFsResearchTaskResponse,
KnowledgeFsStreamCapabilityResponse,
} from '@dify/contracts/api/console/knowledge-fs/types.gen'
import { streamCapabilityEvents } from './knowledge-query-events'
export type ResearchTaskProgressEvent = {
createdAt: string
id: string
payload: Record<string, unknown>
researchTaskJobId: string
sequence: number
stage: KnowledgeFsResearchTaskResponse['stage']
type:
| 'research_task.canceled'
| 'research_task.failed'
| 'research_task.paused'
| 'research_task.resumed'
| 'research_task.stage_changed'
| 'research_task.started'
}
export type ResearchTaskEventStreamResult = {
cursor?: string
reconnect: boolean
terminal: boolean
}
const researchStages = new Set<KnowledgeFsResearchTaskResponse['stage']>([
'analyzing',
'canceled',
'completed',
'failed',
'generating',
'paused',
'planning',
'queued',
'retrieving',
])
const progressEventTypes = new Set<ResearchTaskProgressEvent['type']>([
'research_task.canceled',
'research_task.failed',
'research_task.paused',
'research_task.resumed',
'research_task.stage_changed',
'research_task.started',
])
function progressEvent(value: unknown): ResearchTaskProgressEvent | undefined {
if (!value || typeof value !== 'object' || Array.isArray(value)) return
const event = value as Record<string, unknown>
if (
typeof event.createdAt !== 'string' ||
!Number.isFinite(Date.parse(event.createdAt)) ||
typeof event.id !== 'string' ||
!event.id ||
typeof event.researchTaskJobId !== 'string' ||
!event.researchTaskJobId ||
typeof event.sequence !== 'number' ||
!Number.isInteger(event.sequence) ||
typeof event.stage !== 'string' ||
!researchStages.has(event.stage as KnowledgeFsResearchTaskResponse['stage']) ||
typeof event.type !== 'string' ||
!progressEventTypes.has(event.type as ResearchTaskProgressEvent['type'])
)
return
const payload =
event.payload && typeof event.payload === 'object' && !Array.isArray(event.payload)
? (event.payload as Record<string, unknown>)
: {}
return {
createdAt: event.createdAt,
id: event.id,
payload,
researchTaskJobId: event.researchTaskJobId,
sequence: event.sequence,
stage: event.stage as KnowledgeFsResearchTaskResponse['stage'],
type: event.type as ResearchTaskProgressEvent['type'],
}
}
function streamUrl(url: string, cursor: string | undefined) {
if (!cursor) return url
return `${url}${url.includes('?') ? '&' : '?'}cursor=${encodeURIComponent(cursor)}`
}
function timeoutCursor(data: unknown) {
if (!data || typeof data !== 'object') return
const cursor = (data as Record<string, unknown>).cursor
return typeof cursor === 'string' && cursor ? cursor : undefined
}
export async function streamResearchTaskEvents({
capability,
cursor,
onEvent,
signal,
}: {
capability: KnowledgeFsStreamCapabilityResponse
cursor?: string
onEvent: (event: ResearchTaskProgressEvent) => void
signal?: AbortSignal
}): Promise<ResearchTaskEventStreamResult> {
let nextCursor = cursor
let reconnectAfterTimeout = false
let terminal = false
await streamCapabilityEvents({
onEvent: (event) => {
if (event.event === 'timeout') {
nextCursor = timeoutCursor(event.data) ?? nextCursor
reconnectAfterTimeout = true
return
}
const parsed = progressEvent(event.data)
if (!parsed) return
nextCursor = String(parsed.sequence)
terminal =
parsed.stage === 'canceled' || parsed.stage === 'completed' || parsed.stage === 'failed'
onEvent(parsed)
},
signal,
token: capability.token,
url: streamUrl(capability.url, cursor),
})
return {
...(nextCursor ? { cursor: nextCursor } : {}),
reconnect: !terminal && (reconnectAfterTimeout || nextCursor !== cursor),
terminal,
}
}

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "عدد مرات الاسترجاع",
"newKnowledge.retrievalCountValue": "{{value}} خلال آخر 7 أيام",
"newKnowledge.retrievalTest.analyzing": "تحليل الأدلة",
"newKnowledge.retrievalTest.analyzingActive": "تحليل الأدلة…",
"newKnowledge.retrievalTest.cancel": "إلغاء",
"newKnowledge.retrievalTest.canceled": "تم الإلغاء",
"newKnowledge.retrievalTest.completedIn": "اكتمل خلال {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "فشل الاسترجاع",
"newKnowledge.retrievalTest.foundSoFar": "تم العثور حتى الآن ({{count}})",
"newKnowledge.retrievalTest.generating": "إنشاء الإجابة",
"newKnowledge.retrievalTest.generatingActive": "إنشاء الإجابة…",
"newKnowledge.retrievalTest.justNow": "الآن",
"newKnowledge.retrievalTest.keepGoldenQuestion": "الاحتفاظ كسؤال مرجعي",
"newKnowledge.retrievalTest.makeBadCase": "حفظ كحالة تحتاج إلى تحسين",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "فتح",
"newKnowledge.retrievalTest.page": "الصفحة {{page}}",
"newKnowledge.retrievalTest.planning": "تخطيط البحث",
"newKnowledge.retrievalTest.planningActive": "تخطيط البحث…",
"newKnowledge.retrievalTest.processLog": "سجل العملية",
"newKnowledge.retrievalTest.quality": "الجودة",
"newKnowledge.retrievalTest.queryPlaceholder": "أدخل استعلاماً لاختبار الاسترجاع…",
"newKnowledge.retrievalTest.records": "السجلات",
"newKnowledge.retrievalTest.researchResult": "نتيجة البحث",
"newKnowledge.retrievalTest.result": "نتيجة {{mode}}",
"newKnowledge.retrievalTest.result": "نتيجة الاسترجاع",
"newKnowledge.retrievalTest.retrieving": "استرجاع الأدلة",
"newKnowledge.retrievalTest.retrievingActive": "استرجاع الأدلة…",
"newKnowledge.retrievalTest.retry": "إعادة المحاولة",
"newKnowledge.retrievalTest.run": "تشغيل",
"newKnowledge.retrievalTest.running": "جارٍ البحث…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Anzahl der Abrufe",
"newKnowledge.retrievalCountValue": "{{value}} in den letzten 7 Tagen",
"newKnowledge.retrievalTest.analyzing": "Belege analysieren",
"newKnowledge.retrievalTest.analyzingActive": "Belege analysieren…",
"newKnowledge.retrievalTest.cancel": "Abbrechen",
"newKnowledge.retrievalTest.canceled": "Abgebrochen",
"newKnowledge.retrievalTest.completedIn": "Abgeschlossen in {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Abruf fehlgeschlagen",
"newKnowledge.retrievalTest.foundSoFar": "Bisher gefunden ({{count}})",
"newKnowledge.retrievalTest.generating": "Antwort erstellen",
"newKnowledge.retrievalTest.generatingActive": "Antwort erstellen…",
"newKnowledge.retrievalTest.justNow": "Gerade eben",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Als Referenzfrage speichern",
"newKnowledge.retrievalTest.makeBadCase": "Als Problemfall speichern",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Öffnen",
"newKnowledge.retrievalTest.page": "Seite {{page}}",
"newKnowledge.retrievalTest.planning": "Recherche planen",
"newKnowledge.retrievalTest.planningActive": "Recherche planen…",
"newKnowledge.retrievalTest.processLog": "Prozessprotokoll",
"newKnowledge.retrievalTest.quality": "Qualität",
"newKnowledge.retrievalTest.queryPlaceholder": "Abfrage eingeben, um den Abruf zu testen…",
"newKnowledge.retrievalTest.records": "Verlauf",
"newKnowledge.retrievalTest.researchResult": "Rechercheergebnis",
"newKnowledge.retrievalTest.result": "{{mode}}-Ergebnis",
"newKnowledge.retrievalTest.result": "Suchergebnis",
"newKnowledge.retrievalTest.retrieving": "Belege abrufen",
"newKnowledge.retrievalTest.retrievingActive": "Belege abrufen…",
"newKnowledge.retrievalTest.retry": "Erneut versuchen",
"newKnowledge.retrievalTest.run": "Ausführen",
"newKnowledge.retrievalTest.running": "Recherche läuft…",

View File

@ -503,7 +503,8 @@
"newKnowledge.removeSource": "Remove source",
"newKnowledge.retrievalCount": "Retrieval count",
"newKnowledge.retrievalCountValue": "{{value}} in last 7 days",
"newKnowledge.retrievalTest.analyzing": "Analyze evidence",
"newKnowledge.retrievalTest.analyzing": "Analyzed findings",
"newKnowledge.retrievalTest.analyzingActive": "Analyzing findings…",
"newKnowledge.retrievalTest.cancel": "Cancel",
"newKnowledge.retrievalTest.canceled": "Canceled",
"newKnowledge.retrievalTest.completedIn": "Completed in {{duration}}",
@ -514,7 +515,8 @@
"newKnowledge.retrievalTest.failedDescription": "The retrieval run couldn't be completed. Try the question again.",
"newKnowledge.retrievalTest.failedTitle": "Retrieval failed",
"newKnowledge.retrievalTest.foundSoFar": "Found so far ({{count}})",
"newKnowledge.retrievalTest.generating": "Generate response",
"newKnowledge.retrievalTest.generating": "Generated result",
"newKnowledge.retrievalTest.generatingActive": "Generating result…",
"newKnowledge.retrievalTest.justNow": "Just now",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Keep golden question",
"newKnowledge.retrievalTest.makeBadCase": "Make bad case",
@ -522,20 +524,22 @@
"newKnowledge.retrievalTest.noChunksTitle": "No chunks retrieved",
"newKnowledge.retrievalTest.open": "Open",
"newKnowledge.retrievalTest.page": "Page {{page}}",
"newKnowledge.retrievalTest.planning": "Plan research",
"newKnowledge.retrievalTest.planning": "Planned the research",
"newKnowledge.retrievalTest.planningActive": "Planning research…",
"newKnowledge.retrievalTest.processLog": "Process log",
"newKnowledge.retrievalTest.quality": "Quality",
"newKnowledge.retrievalTest.queryPlaceholder": "Enter a query to test retrieval…",
"newKnowledge.retrievalTest.records": "Records",
"newKnowledge.retrievalTest.researchResult": "Research result",
"newKnowledge.retrievalTest.result": "Retrieval result",
"newKnowledge.retrievalTest.retrieving": "Retrieve evidence",
"newKnowledge.retrievalTest.retrieving": "Retrieved sources",
"newKnowledge.retrievalTest.retrievingActive": "Retrieving sources…",
"newKnowledge.retrievalTest.retry": "Retry",
"newKnowledge.retrievalTest.run": "Run",
"newKnowledge.retrievalTest.running": "Researching…",
"newKnowledge.retrievalTest.savedBadCase": "Saved as bad case",
"newKnowledge.retrievalTest.savedGoldenQuestion": "Saved as golden question",
"newKnowledge.retrievalTest.showAllChunks": "Show all {{count}} chunks",
"newKnowledge.retrievalTest.showAllChunks": "Show all {{count}} sources",
"newKnowledge.retrievalTest.startResearch": "Start research",
"newKnowledge.retrievalTest.title": "Retrieval Test",
"newKnowledge.retrievalTest.viewInQuality": "View in Quality",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Número de recuperaciones",
"newKnowledge.retrievalCountValue": "{{value}} en los últimos 7 días",
"newKnowledge.retrievalTest.analyzing": "Analizar evidencias",
"newKnowledge.retrievalTest.analyzingActive": "Analizar evidencias…",
"newKnowledge.retrievalTest.cancel": "Cancelar",
"newKnowledge.retrievalTest.canceled": "Cancelado",
"newKnowledge.retrievalTest.completedIn": "Completado en {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Error de recuperación",
"newKnowledge.retrievalTest.foundSoFar": "Encontrados hasta ahora ({{count}})",
"newKnowledge.retrievalTest.generating": "Generar respuesta",
"newKnowledge.retrievalTest.generatingActive": "Generar respuesta…",
"newKnowledge.retrievalTest.justNow": "Ahora mismo",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Guardar como pregunta de referencia",
"newKnowledge.retrievalTest.makeBadCase": "Guardar como caso problemático",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Abrir",
"newKnowledge.retrievalTest.page": "Página {{page}}",
"newKnowledge.retrievalTest.planning": "Planificar investigación",
"newKnowledge.retrievalTest.planningActive": "Planificar investigación…",
"newKnowledge.retrievalTest.processLog": "Registro del proceso",
"newKnowledge.retrievalTest.quality": "Calidad",
"newKnowledge.retrievalTest.queryPlaceholder": "Introduce una consulta para probar la recuperación…",
"newKnowledge.retrievalTest.records": "Registros",
"newKnowledge.retrievalTest.researchResult": "Resultado de la investigación",
"newKnowledge.retrievalTest.result": "Resultado de {{mode}}",
"newKnowledge.retrievalTest.result": "Resultado de recuperación",
"newKnowledge.retrievalTest.retrieving": "Recuperar evidencias",
"newKnowledge.retrievalTest.retrievingActive": "Recuperar evidencias…",
"newKnowledge.retrievalTest.retry": "Reintentar",
"newKnowledge.retrievalTest.run": "Ejecutar",
"newKnowledge.retrievalTest.running": "Investigando…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "تعداد بازیابی",
"newKnowledge.retrievalCountValue": "{{value}} در ۷ روز گذشته",
"newKnowledge.retrievalTest.analyzing": "تحلیل شواهد",
"newKnowledge.retrievalTest.analyzingActive": "تحلیل شواهد…",
"newKnowledge.retrievalTest.cancel": "لغو",
"newKnowledge.retrievalTest.canceled": "لغو شد",
"newKnowledge.retrievalTest.completedIn": "در {{duration}} تکمیل شد",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "بازیابی ناموفق بود",
"newKnowledge.retrievalTest.foundSoFar": "یافته‌شده تا این لحظه ({{count}})",
"newKnowledge.retrievalTest.generating": "تولید پاسخ",
"newKnowledge.retrievalTest.generatingActive": "تولید پاسخ…",
"newKnowledge.retrievalTest.justNow": "همین حالا",
"newKnowledge.retrievalTest.keepGoldenQuestion": "ذخیره به‌عنوان پرسش مرجع",
"newKnowledge.retrievalTest.makeBadCase": "ذخیره به‌عنوان مورد نیازمند بهبود",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "باز کردن",
"newKnowledge.retrievalTest.page": "صفحه {{page}}",
"newKnowledge.retrievalTest.planning": "برنامه‌ریزی پژوهش",
"newKnowledge.retrievalTest.planningActive": "برنامه‌ریزی پژوهش…",
"newKnowledge.retrievalTest.processLog": "گزارش فرایند",
"newKnowledge.retrievalTest.quality": "کیفیت",
"newKnowledge.retrievalTest.queryPlaceholder": "برای آزمایش بازیابی، یک پرس‌وجو وارد کنید…",
"newKnowledge.retrievalTest.records": "سوابق",
"newKnowledge.retrievalTest.researchResult": "نتیجه پژوهش",
"newKnowledge.retrievalTest.result": "نتیجه {{mode}}",
"newKnowledge.retrievalTest.result": "نتیجه بازیابی",
"newKnowledge.retrievalTest.retrieving": "بازیابی شواهد",
"newKnowledge.retrievalTest.retrievingActive": "بازیابی شواهد…",
"newKnowledge.retrievalTest.retry": "تلاش دوباره",
"newKnowledge.retrievalTest.run": "اجرا",
"newKnowledge.retrievalTest.running": "در حال پژوهش…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Nombre de récupérations",
"newKnowledge.retrievalCountValue": "{{value}} au cours des 7 derniers jours",
"newKnowledge.retrievalTest.analyzing": "Analyser les éléments",
"newKnowledge.retrievalTest.analyzingActive": "Analyser les éléments…",
"newKnowledge.retrievalTest.cancel": "Annuler",
"newKnowledge.retrievalTest.canceled": "Annulé",
"newKnowledge.retrievalTest.completedIn": "Terminé en {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Échec de la récupération",
"newKnowledge.retrievalTest.foundSoFar": "Trouvés jusquici ({{count}})",
"newKnowledge.retrievalTest.generating": "Générer la réponse",
"newKnowledge.retrievalTest.generatingActive": "Générer la réponse…",
"newKnowledge.retrievalTest.justNow": "À linstant",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Conserver comme question de référence",
"newKnowledge.retrievalTest.makeBadCase": "Enregistrer comme cas problématique",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Ouvrir",
"newKnowledge.retrievalTest.page": "Page {{page}}",
"newKnowledge.retrievalTest.planning": "Planifier la recherche",
"newKnowledge.retrievalTest.planningActive": "Planifier la recherche…",
"newKnowledge.retrievalTest.processLog": "Journal du processus",
"newKnowledge.retrievalTest.quality": "Qualité",
"newKnowledge.retrievalTest.queryPlaceholder": "Saisissez une requête pour tester la récupération…",
"newKnowledge.retrievalTest.records": "Historique",
"newKnowledge.retrievalTest.researchResult": "Résultat de la recherche",
"newKnowledge.retrievalTest.result": "Résultat {{mode}}",
"newKnowledge.retrievalTest.result": "Résultat de la récupération",
"newKnowledge.retrievalTest.retrieving": "Récupérer les éléments",
"newKnowledge.retrievalTest.retrievingActive": "Récupérer les éléments…",
"newKnowledge.retrievalTest.retry": "Réessayer",
"newKnowledge.retrievalTest.run": "Exécuter",
"newKnowledge.retrievalTest.running": "Recherche en cours…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "पुनर्प्राप्ति संख्या",
"newKnowledge.retrievalCountValue": "पिछले 7 दिनों में {{value}}",
"newKnowledge.retrievalTest.analyzing": "साक्ष्य का विश्लेषण करें",
"newKnowledge.retrievalTest.analyzingActive": "साक्ष्य का विश्लेषण करें…",
"newKnowledge.retrievalTest.cancel": "रद्द करें",
"newKnowledge.retrievalTest.canceled": "रद्द किया गया",
"newKnowledge.retrievalTest.completedIn": "{{duration}} में पूरा हुआ",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "रिट्रीवल विफल रहा",
"newKnowledge.retrievalTest.foundSoFar": "अब तक मिले ({{count}})",
"newKnowledge.retrievalTest.generating": "उत्तर तैयार करें",
"newKnowledge.retrievalTest.generatingActive": "उत्तर तैयार करें…",
"newKnowledge.retrievalTest.justNow": "अभी",
"newKnowledge.retrievalTest.keepGoldenQuestion": "संदर्भ प्रश्न के रूप में रखें",
"newKnowledge.retrievalTest.makeBadCase": "सुधार योग्य मामले के रूप में सहेजें",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "खोलें",
"newKnowledge.retrievalTest.page": "पृष्ठ {{page}}",
"newKnowledge.retrievalTest.planning": "रिसर्च की योजना बनाएँ",
"newKnowledge.retrievalTest.planningActive": "रिसर्च की योजना बनाएँ…",
"newKnowledge.retrievalTest.processLog": "प्रक्रिया लॉग",
"newKnowledge.retrievalTest.quality": "गुणवत्ता",
"newKnowledge.retrievalTest.queryPlaceholder": "रिट्रीवल जाँचने के लिए क्वेरी दर्ज करें…",
"newKnowledge.retrievalTest.records": "रिकॉर्ड",
"newKnowledge.retrievalTest.researchResult": "रिसर्च परिणाम",
"newKnowledge.retrievalTest.result": "{{mode}} परिणाम",
"newKnowledge.retrievalTest.result": "पुनर्प्राप्ति परिणाम",
"newKnowledge.retrievalTest.retrieving": "साक्ष्य प्राप्त करें",
"newKnowledge.retrievalTest.retrievingActive": "साक्ष्य प्राप्त करें…",
"newKnowledge.retrievalTest.retry": "फिर से कोशिश करें",
"newKnowledge.retrievalTest.run": "चलाएँ",
"newKnowledge.retrievalTest.running": "रिसर्च जारी है…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Jumlah pengambilan",
"newKnowledge.retrievalCountValue": "{{value}} dalam 7 hari terakhir",
"newKnowledge.retrievalTest.analyzing": "Analisis bukti",
"newKnowledge.retrievalTest.analyzingActive": "Analisis bukti…",
"newKnowledge.retrievalTest.cancel": "Batal",
"newKnowledge.retrievalTest.canceled": "Dibatalkan",
"newKnowledge.retrievalTest.completedIn": "Selesai dalam {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Pengambilan gagal",
"newKnowledge.retrievalTest.foundSoFar": "Ditemukan sejauh ini ({{count}})",
"newKnowledge.retrievalTest.generating": "Buat jawaban",
"newKnowledge.retrievalTest.generatingActive": "Buat jawaban…",
"newKnowledge.retrievalTest.justNow": "Baru saja",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Simpan sebagai pertanyaan referensi",
"newKnowledge.retrievalTest.makeBadCase": "Simpan sebagai kasus bermasalah",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Buka",
"newKnowledge.retrievalTest.page": "Halaman {{page}}",
"newKnowledge.retrievalTest.planning": "Rencanakan riset",
"newKnowledge.retrievalTest.planningActive": "Rencanakan riset…",
"newKnowledge.retrievalTest.processLog": "Log proses",
"newKnowledge.retrievalTest.quality": "Kualitas",
"newKnowledge.retrievalTest.queryPlaceholder": "Masukkan kueri untuk menguji pengambilan…",
"newKnowledge.retrievalTest.records": "Riwayat",
"newKnowledge.retrievalTest.researchResult": "Hasil riset",
"newKnowledge.retrievalTest.result": "Hasil {{mode}}",
"newKnowledge.retrievalTest.result": "Hasil pengambilan",
"newKnowledge.retrievalTest.retrieving": "Ambil bukti",
"newKnowledge.retrievalTest.retrievingActive": "Ambil bukti…",
"newKnowledge.retrievalTest.retry": "Coba lagi",
"newKnowledge.retrievalTest.run": "Jalankan",
"newKnowledge.retrievalTest.running": "Sedang melakukan riset…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Numero di recuperi",
"newKnowledge.retrievalCountValue": "{{value}} negli ultimi 7 giorni",
"newKnowledge.retrievalTest.analyzing": "Analizza le evidenze",
"newKnowledge.retrievalTest.analyzingActive": "Analizza le evidenze…",
"newKnowledge.retrievalTest.cancel": "Annulla",
"newKnowledge.retrievalTest.canceled": "Annullato",
"newKnowledge.retrievalTest.completedIn": "Completato in {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Recupero non riuscito",
"newKnowledge.retrievalTest.foundSoFar": "Trovati finora ({{count}})",
"newKnowledge.retrievalTest.generating": "Genera risposta",
"newKnowledge.retrievalTest.generatingActive": "Genera risposta…",
"newKnowledge.retrievalTest.justNow": "Adesso",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Conserva come domanda di riferimento",
"newKnowledge.retrievalTest.makeBadCase": "Salva come caso problematico",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Apri",
"newKnowledge.retrievalTest.page": "Pagina {{page}}",
"newKnowledge.retrievalTest.planning": "Pianifica la ricerca",
"newKnowledge.retrievalTest.planningActive": "Pianifica la ricerca…",
"newKnowledge.retrievalTest.processLog": "Registro del processo",
"newKnowledge.retrievalTest.quality": "Qualità",
"newKnowledge.retrievalTest.queryPlaceholder": "Inserisci una query per provare il recupero…",
"newKnowledge.retrievalTest.records": "Cronologia",
"newKnowledge.retrievalTest.researchResult": "Risultato della ricerca",
"newKnowledge.retrievalTest.result": "Risultato {{mode}}",
"newKnowledge.retrievalTest.result": "Risultato del recupero",
"newKnowledge.retrievalTest.retrieving": "Recupera le evidenze",
"newKnowledge.retrievalTest.retrievingActive": "Recupera le evidenze…",
"newKnowledge.retrievalTest.retry": "Riprova",
"newKnowledge.retrievalTest.run": "Esegui",
"newKnowledge.retrievalTest.running": "Ricerca in corso…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "取得回数",
"newKnowledge.retrievalCountValue": "過去7日間で{{value}}回",
"newKnowledge.retrievalTest.analyzing": "根拠を分析",
"newKnowledge.retrievalTest.analyzingActive": "根拠を分析…",
"newKnowledge.retrievalTest.cancel": "キャンセル",
"newKnowledge.retrievalTest.canceled": "キャンセル済み",
"newKnowledge.retrievalTest.completedIn": "{{duration}} で完了",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "検索に失敗しました",
"newKnowledge.retrievalTest.foundSoFar": "現在までに取得({{count}}",
"newKnowledge.retrievalTest.generating": "回答を生成",
"newKnowledge.retrievalTest.generatingActive": "回答を生成…",
"newKnowledge.retrievalTest.justNow": "たった今",
"newKnowledge.retrievalTest.keepGoldenQuestion": "参考質問として保存",
"newKnowledge.retrievalTest.makeBadCase": "改善ケースとして保存",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "開く",
"newKnowledge.retrievalTest.page": "{{page}} ページ",
"newKnowledge.retrievalTest.planning": "調査を計画",
"newKnowledge.retrievalTest.planningActive": "調査を計画…",
"newKnowledge.retrievalTest.processLog": "プロセスログ",
"newKnowledge.retrievalTest.quality": "品質",
"newKnowledge.retrievalTest.queryPlaceholder": "検索をテストするクエリを入力…",
"newKnowledge.retrievalTest.records": "履歴",
"newKnowledge.retrievalTest.researchResult": "調査結果",
"newKnowledge.retrievalTest.result": "{{mode}} の結果",
"newKnowledge.retrievalTest.result": "検索結果",
"newKnowledge.retrievalTest.retrieving": "根拠を取得",
"newKnowledge.retrievalTest.retrievingActive": "根拠を取得…",
"newKnowledge.retrievalTest.retry": "再試行",
"newKnowledge.retrievalTest.run": "実行",
"newKnowledge.retrievalTest.running": "調査中…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "검색 횟수",
"newKnowledge.retrievalCountValue": "최근 7일 동안 {{value}}회",
"newKnowledge.retrievalTest.analyzing": "근거 분석",
"newKnowledge.retrievalTest.analyzingActive": "근거 분석…",
"newKnowledge.retrievalTest.cancel": "취소",
"newKnowledge.retrievalTest.canceled": "취소됨",
"newKnowledge.retrievalTest.completedIn": "{{duration}} 만에 완료",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "검색 실패",
"newKnowledge.retrievalTest.foundSoFar": "현재까지 검색됨({{count}})",
"newKnowledge.retrievalTest.generating": "응답 생성",
"newKnowledge.retrievalTest.generatingActive": "응답 생성…",
"newKnowledge.retrievalTest.justNow": "방금",
"newKnowledge.retrievalTest.keepGoldenQuestion": "참고 질문으로 저장",
"newKnowledge.retrievalTest.makeBadCase": "개선 사례로 저장",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "열기",
"newKnowledge.retrievalTest.page": "{{page}}페이지",
"newKnowledge.retrievalTest.planning": "리서치 계획",
"newKnowledge.retrievalTest.planningActive": "리서치 계획…",
"newKnowledge.retrievalTest.processLog": "프로세스 로그",
"newKnowledge.retrievalTest.quality": "품질",
"newKnowledge.retrievalTest.queryPlaceholder": "검색을 테스트할 쿼리를 입력하세요…",
"newKnowledge.retrievalTest.records": "기록",
"newKnowledge.retrievalTest.researchResult": "리서치 결과",
"newKnowledge.retrievalTest.result": "{{mode}} 결과",
"newKnowledge.retrievalTest.result": "검색 결과",
"newKnowledge.retrievalTest.retrieving": "근거 검색",
"newKnowledge.retrievalTest.retrievingActive": "근거 검색…",
"newKnowledge.retrievalTest.retry": "다시 시도",
"newKnowledge.retrievalTest.run": "실행",
"newKnowledge.retrievalTest.running": "리서치 중…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Aantal zoekacties",
"newKnowledge.retrievalCountValue": "{{value}} in de afgelopen 7 dagen",
"newKnowledge.retrievalTest.analyzing": "Bewijs analyseren",
"newKnowledge.retrievalTest.analyzingActive": "Bewijs analyseren…",
"newKnowledge.retrievalTest.cancel": "Annuleren",
"newKnowledge.retrievalTest.canceled": "Geannuleerd",
"newKnowledge.retrievalTest.completedIn": "Voltooid in {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Ophalen mislukt",
"newKnowledge.retrievalTest.foundSoFar": "Tot nu toe gevonden ({{count}})",
"newKnowledge.retrievalTest.generating": "Antwoord genereren",
"newKnowledge.retrievalTest.generatingActive": "Antwoord genereren…",
"newKnowledge.retrievalTest.justNow": "Zojuist",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Opslaan als referentievraag",
"newKnowledge.retrievalTest.makeBadCase": "Opslaan als probleemgeval",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Openen",
"newKnowledge.retrievalTest.page": "Pagina {{page}}",
"newKnowledge.retrievalTest.planning": "Onderzoek plannen",
"newKnowledge.retrievalTest.planningActive": "Onderzoek plannen…",
"newKnowledge.retrievalTest.processLog": "Proceslogboek",
"newKnowledge.retrievalTest.quality": "Kwaliteit",
"newKnowledge.retrievalTest.queryPlaceholder": "Voer een zoekvraag in om het ophalen te testen…",
"newKnowledge.retrievalTest.records": "Geschiedenis",
"newKnowledge.retrievalTest.researchResult": "Onderzoeksresultaat",
"newKnowledge.retrievalTest.result": "{{mode}}-resultaat",
"newKnowledge.retrievalTest.result": "Zoekresultaat",
"newKnowledge.retrievalTest.retrieving": "Bewijs ophalen",
"newKnowledge.retrievalTest.retrievingActive": "Bewijs ophalen…",
"newKnowledge.retrievalTest.retry": "Opnieuw proberen",
"newKnowledge.retrievalTest.run": "Uitvoeren",
"newKnowledge.retrievalTest.running": "Onderzoek wordt uitgevoerd…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Liczba pobrań",
"newKnowledge.retrievalCountValue": "{{value}} w ciągu ostatnich 7 dni",
"newKnowledge.retrievalTest.analyzing": "Analizuj dowody",
"newKnowledge.retrievalTest.analyzingActive": "Analizuj dowody…",
"newKnowledge.retrievalTest.cancel": "Anuluj",
"newKnowledge.retrievalTest.canceled": "Anulowano",
"newKnowledge.retrievalTest.completedIn": "Ukończono w {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Wyszukiwanie nie powiodło się",
"newKnowledge.retrievalTest.foundSoFar": "Znaleziono dotychczas ({{count}})",
"newKnowledge.retrievalTest.generating": "Generuj odpowiedź",
"newKnowledge.retrievalTest.generatingActive": "Generuj odpowiedź…",
"newKnowledge.retrievalTest.justNow": "Przed chwilą",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Zapisz jako pytanie referencyjne",
"newKnowledge.retrievalTest.makeBadCase": "Zapisz jako przypadek do poprawy",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Otwórz",
"newKnowledge.retrievalTest.page": "Strona {{page}}",
"newKnowledge.retrievalTest.planning": "Zaplanuj badanie",
"newKnowledge.retrievalTest.planningActive": "Zaplanuj badanie…",
"newKnowledge.retrievalTest.processLog": "Dziennik procesu",
"newKnowledge.retrievalTest.quality": "Jakość",
"newKnowledge.retrievalTest.queryPlaceholder": "Wprowadź zapytanie, aby przetestować wyszukiwanie…",
"newKnowledge.retrievalTest.records": "Historia",
"newKnowledge.retrievalTest.researchResult": "Wynik badania",
"newKnowledge.retrievalTest.result": "Wynik: {{mode}}",
"newKnowledge.retrievalTest.result": "Wynik wyszukiwania",
"newKnowledge.retrievalTest.retrieving": "Pobierz dowody",
"newKnowledge.retrievalTest.retrievingActive": "Pobierz dowody…",
"newKnowledge.retrievalTest.retry": "Spróbuj ponownie",
"newKnowledge.retrievalTest.run": "Uruchom",
"newKnowledge.retrievalTest.running": "Trwa badanie…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Quantidade de recuperações",
"newKnowledge.retrievalCountValue": "{{value}} nos últimos 7 dias",
"newKnowledge.retrievalTest.analyzing": "Analisar evidências",
"newKnowledge.retrievalTest.analyzingActive": "Analisar evidências…",
"newKnowledge.retrievalTest.cancel": "Cancelar",
"newKnowledge.retrievalTest.canceled": "Cancelado",
"newKnowledge.retrievalTest.completedIn": "Concluído em {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Falha na recuperação",
"newKnowledge.retrievalTest.foundSoFar": "Encontrados até agora ({{count}})",
"newKnowledge.retrievalTest.generating": "Gerar resposta",
"newKnowledge.retrievalTest.generatingActive": "Gerar resposta…",
"newKnowledge.retrievalTest.justNow": "Agora mesmo",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Salvar como pergunta de referência",
"newKnowledge.retrievalTest.makeBadCase": "Salvar como caso problemático",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Abrir",
"newKnowledge.retrievalTest.page": "Página {{page}}",
"newKnowledge.retrievalTest.planning": "Planejar pesquisa",
"newKnowledge.retrievalTest.planningActive": "Planejar pesquisa…",
"newKnowledge.retrievalTest.processLog": "Log do processo",
"newKnowledge.retrievalTest.quality": "Qualidade",
"newKnowledge.retrievalTest.queryPlaceholder": "Digite uma consulta para testar a recuperação…",
"newKnowledge.retrievalTest.records": "Histórico",
"newKnowledge.retrievalTest.researchResult": "Resultado da pesquisa",
"newKnowledge.retrievalTest.result": "Resultado de {{mode}}",
"newKnowledge.retrievalTest.result": "Resultado da recuperação",
"newKnowledge.retrievalTest.retrieving": "Recuperar evidências",
"newKnowledge.retrievalTest.retrievingActive": "Recuperar evidências…",
"newKnowledge.retrievalTest.retry": "Tentar novamente",
"newKnowledge.retrievalTest.run": "Executar",
"newKnowledge.retrievalTest.running": "Pesquisando…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Număr de regăsiri",
"newKnowledge.retrievalCountValue": "{{value}} în ultimele 7 zile",
"newKnowledge.retrievalTest.analyzing": "Analizează dovezile",
"newKnowledge.retrievalTest.analyzingActive": "Analizează dovezile…",
"newKnowledge.retrievalTest.cancel": "Anulează",
"newKnowledge.retrievalTest.canceled": "Anulat",
"newKnowledge.retrievalTest.completedIn": "Finalizat în {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Recuperarea a eșuat",
"newKnowledge.retrievalTest.foundSoFar": "Găsite până acum ({{count}})",
"newKnowledge.retrievalTest.generating": "Generează răspunsul",
"newKnowledge.retrievalTest.generatingActive": "Generează răspunsul…",
"newKnowledge.retrievalTest.justNow": "Chiar acum",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Salvează ca întrebare de referință",
"newKnowledge.retrievalTest.makeBadCase": "Salvează ca situație problematică",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Deschide",
"newKnowledge.retrievalTest.page": "Pagina {{page}}",
"newKnowledge.retrievalTest.planning": "Planifică cercetarea",
"newKnowledge.retrievalTest.planningActive": "Planifică cercetarea…",
"newKnowledge.retrievalTest.processLog": "Jurnalul procesului",
"newKnowledge.retrievalTest.quality": "Calitate",
"newKnowledge.retrievalTest.queryPlaceholder": "Introduceți o interogare pentru a testa recuperarea…",
"newKnowledge.retrievalTest.records": "Istoric",
"newKnowledge.retrievalTest.researchResult": "Rezultatul cercetării",
"newKnowledge.retrievalTest.result": "Rezultat {{mode}}",
"newKnowledge.retrievalTest.result": "Rezultatul regăsirii",
"newKnowledge.retrievalTest.retrieving": "Recuperează dovezile",
"newKnowledge.retrievalTest.retrievingActive": "Recuperează dovezile…",
"newKnowledge.retrievalTest.retry": "Reîncearcă",
"newKnowledge.retrievalTest.run": "Rulează",
"newKnowledge.retrievalTest.running": "Cercetare în curs…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Количество извлечений",
"newKnowledge.retrievalCountValue": "{{value}} за последние 7 дней",
"newKnowledge.retrievalTest.analyzing": "Анализ доказательств",
"newKnowledge.retrievalTest.analyzingActive": "Анализ доказательств…",
"newKnowledge.retrievalTest.cancel": "Отмена",
"newKnowledge.retrievalTest.canceled": "Отменено",
"newKnowledge.retrievalTest.completedIn": "Завершено за {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Ошибка поиска",
"newKnowledge.retrievalTest.foundSoFar": "Найдено на данный момент ({{count}})",
"newKnowledge.retrievalTest.generating": "Создание ответа",
"newKnowledge.retrievalTest.generatingActive": "Создание ответа…",
"newKnowledge.retrievalTest.justNow": "Только что",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Сохранить как эталонный вопрос",
"newKnowledge.retrievalTest.makeBadCase": "Сохранить как проблемный случай",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Открыть",
"newKnowledge.retrievalTest.page": "Страница {{page}}",
"newKnowledge.retrievalTest.planning": "Планирование исследования",
"newKnowledge.retrievalTest.planningActive": "Планирование исследования…",
"newKnowledge.retrievalTest.processLog": "Журнал процесса",
"newKnowledge.retrievalTest.quality": "Качество",
"newKnowledge.retrievalTest.queryPlaceholder": "Введите запрос для проверки поиска…",
"newKnowledge.retrievalTest.records": "История",
"newKnowledge.retrievalTest.researchResult": "Результат исследования",
"newKnowledge.retrievalTest.result": "Результат: {{mode}}",
"newKnowledge.retrievalTest.result": "Результат поиска",
"newKnowledge.retrievalTest.retrieving": "Поиск доказательств",
"newKnowledge.retrievalTest.retrievingActive": "Поиск доказательств…",
"newKnowledge.retrievalTest.retry": "Повторить",
"newKnowledge.retrievalTest.run": "Запустить",
"newKnowledge.retrievalTest.running": "Идёт исследование…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Število pridobitev",
"newKnowledge.retrievalCountValue": "{{value}} v zadnjih 7 dneh",
"newKnowledge.retrievalTest.analyzing": "Analiziraj dokaze",
"newKnowledge.retrievalTest.analyzingActive": "Analiziraj dokaze…",
"newKnowledge.retrievalTest.cancel": "Prekliči",
"newKnowledge.retrievalTest.canceled": "Preklicano",
"newKnowledge.retrievalTest.completedIn": "Končano v {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Pridobivanje ni uspelo",
"newKnowledge.retrievalTest.foundSoFar": "Doslej najdeno ({{count}})",
"newKnowledge.retrievalTest.generating": "Ustvari odgovor",
"newKnowledge.retrievalTest.generatingActive": "Ustvari odgovor…",
"newKnowledge.retrievalTest.justNow": "Pravkar",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Shrani kot referenčno vprašanje",
"newKnowledge.retrievalTest.makeBadCase": "Shrani kot problematičen primer",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Odpri",
"newKnowledge.retrievalTest.page": "Stran {{page}}",
"newKnowledge.retrievalTest.planning": "Načrtuj raziskavo",
"newKnowledge.retrievalTest.planningActive": "Načrtuj raziskavo…",
"newKnowledge.retrievalTest.processLog": "Dnevnik postopka",
"newKnowledge.retrievalTest.quality": "Kakovost",
"newKnowledge.retrievalTest.queryPlaceholder": "Vnesite poizvedbo za preskus pridobivanja…",
"newKnowledge.retrievalTest.records": "Zgodovina",
"newKnowledge.retrievalTest.researchResult": "Rezultat raziskave",
"newKnowledge.retrievalTest.result": "Rezultat {{mode}}",
"newKnowledge.retrievalTest.result": "Rezultat iskanja",
"newKnowledge.retrievalTest.retrieving": "Pridobi dokaze",
"newKnowledge.retrievalTest.retrievingActive": "Pridobi dokaze…",
"newKnowledge.retrievalTest.retry": "Poskusi znova",
"newKnowledge.retrievalTest.run": "Zaženi",
"newKnowledge.retrievalTest.running": "Raziskovanje…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "จำนวนการเรียกค้น",
"newKnowledge.retrievalCountValue": "{{value}} ครั้งในช่วง 7 วันที่ผ่านมา",
"newKnowledge.retrievalTest.analyzing": "วิเคราะห์หลักฐาน",
"newKnowledge.retrievalTest.analyzingActive": "วิเคราะห์หลักฐาน…",
"newKnowledge.retrievalTest.cancel": "ยกเลิก",
"newKnowledge.retrievalTest.canceled": "ยกเลิกแล้ว",
"newKnowledge.retrievalTest.completedIn": "เสร็จสิ้นใน {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "การค้นคืนล้มเหลว",
"newKnowledge.retrievalTest.foundSoFar": "พบแล้ว ({{count}})",
"newKnowledge.retrievalTest.generating": "สร้างคำตอบ",
"newKnowledge.retrievalTest.generatingActive": "สร้างคำตอบ…",
"newKnowledge.retrievalTest.justNow": "เมื่อสักครู่",
"newKnowledge.retrievalTest.keepGoldenQuestion": "บันทึกเป็นคำถามอ้างอิง",
"newKnowledge.retrievalTest.makeBadCase": "บันทึกเป็นกรณีที่ต้องปรับปรุง",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "เปิด",
"newKnowledge.retrievalTest.page": "หน้า {{page}}",
"newKnowledge.retrievalTest.planning": "วางแผนการวิจัย",
"newKnowledge.retrievalTest.planningActive": "วางแผนการวิจัย…",
"newKnowledge.retrievalTest.processLog": "บันทึกกระบวนการ",
"newKnowledge.retrievalTest.quality": "คุณภาพ",
"newKnowledge.retrievalTest.queryPlaceholder": "ป้อนคำค้นเพื่อทดสอบการค้นคืน…",
"newKnowledge.retrievalTest.records": "ประวัติ",
"newKnowledge.retrievalTest.researchResult": "ผลการวิจัย",
"newKnowledge.retrievalTest.result": "ผลลัพธ์ {{mode}}",
"newKnowledge.retrievalTest.result": "ผลลัพธ์การดึงข้อมูล",
"newKnowledge.retrievalTest.retrieving": "ค้นคืนหลักฐาน",
"newKnowledge.retrievalTest.retrievingActive": "ค้นคืนหลักฐาน…",
"newKnowledge.retrievalTest.retry": "ลองอีกครั้ง",
"newKnowledge.retrievalTest.run": "เรียกใช้",
"newKnowledge.retrievalTest.running": "กำลังวิจัย…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Getirme sayısı",
"newKnowledge.retrievalCountValue": "Son 7 günde {{value}}",
"newKnowledge.retrievalTest.analyzing": "Kanıtları analiz et",
"newKnowledge.retrievalTest.analyzingActive": "Kanıtları analiz et…",
"newKnowledge.retrievalTest.cancel": "İptal",
"newKnowledge.retrievalTest.canceled": "İptal edildi",
"newKnowledge.retrievalTest.completedIn": "{{duration}} içinde tamamlandı",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Getirme başarısız",
"newKnowledge.retrievalTest.foundSoFar": "Şimdiye kadar bulunan ({{count}})",
"newKnowledge.retrievalTest.generating": "Yanıt oluştur",
"newKnowledge.retrievalTest.generatingActive": "Yanıt oluştur…",
"newKnowledge.retrievalTest.justNow": "Az önce",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Referans sorusu olarak kaydet",
"newKnowledge.retrievalTest.makeBadCase": "Sorunlu durum olarak kaydet",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Aç",
"newKnowledge.retrievalTest.page": "Sayfa {{page}}",
"newKnowledge.retrievalTest.planning": "Araştırmayı planla",
"newKnowledge.retrievalTest.planningActive": "Araştırmayı planla…",
"newKnowledge.retrievalTest.processLog": "İşlem günlüğü",
"newKnowledge.retrievalTest.quality": "Kalite",
"newKnowledge.retrievalTest.queryPlaceholder": "Getirmeyi test etmek için bir sorgu girin…",
"newKnowledge.retrievalTest.records": "Geçmiş",
"newKnowledge.retrievalTest.researchResult": "Araştırma sonucu",
"newKnowledge.retrievalTest.result": "{{mode}} sonucu",
"newKnowledge.retrievalTest.result": "Getirme sonucu",
"newKnowledge.retrievalTest.retrieving": "Kanıtları getir",
"newKnowledge.retrievalTest.retrievingActive": "Kanıtları getir…",
"newKnowledge.retrievalTest.retry": "Tekrar dene",
"newKnowledge.retrievalTest.run": "Çalıştır",
"newKnowledge.retrievalTest.running": "Araştırılıyor…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Кількість отримань",
"newKnowledge.retrievalCountValue": "{{value}} за останні 7 днів",
"newKnowledge.retrievalTest.analyzing": "Аналіз доказів",
"newKnowledge.retrievalTest.analyzingActive": "Аналіз доказів…",
"newKnowledge.retrievalTest.cancel": "Скасувати",
"newKnowledge.retrievalTest.canceled": "Скасовано",
"newKnowledge.retrievalTest.completedIn": "Завершено за {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Помилка пошуку",
"newKnowledge.retrievalTest.foundSoFar": "Знайдено наразі ({{count}})",
"newKnowledge.retrievalTest.generating": "Створення відповіді",
"newKnowledge.retrievalTest.generatingActive": "Створення відповіді…",
"newKnowledge.retrievalTest.justNow": "Щойно",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Зберегти як еталонне запитання",
"newKnowledge.retrievalTest.makeBadCase": "Зберегти як проблемний випадок",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Відкрити",
"newKnowledge.retrievalTest.page": "Сторінка {{page}}",
"newKnowledge.retrievalTest.planning": "Планування дослідження",
"newKnowledge.retrievalTest.planningActive": "Планування дослідження…",
"newKnowledge.retrievalTest.processLog": "Журнал процесу",
"newKnowledge.retrievalTest.quality": "Якість",
"newKnowledge.retrievalTest.queryPlaceholder": "Введіть запит, щоб перевірити пошук…",
"newKnowledge.retrievalTest.records": "Історія",
"newKnowledge.retrievalTest.researchResult": "Результат дослідження",
"newKnowledge.retrievalTest.result": "Результат: {{mode}}",
"newKnowledge.retrievalTest.result": "Результат пошуку",
"newKnowledge.retrievalTest.retrieving": "Пошук доказів",
"newKnowledge.retrievalTest.retrievingActive": "Пошук доказів…",
"newKnowledge.retrievalTest.retry": "Повторити",
"newKnowledge.retrievalTest.run": "Запустити",
"newKnowledge.retrievalTest.running": "Триває дослідження…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "Số lần truy xuất",
"newKnowledge.retrievalCountValue": "{{value}} trong 7 ngày qua",
"newKnowledge.retrievalTest.analyzing": "Phân tích bằng chứng",
"newKnowledge.retrievalTest.analyzingActive": "Phân tích bằng chứng…",
"newKnowledge.retrievalTest.cancel": "Hủy",
"newKnowledge.retrievalTest.canceled": "Đã hủy",
"newKnowledge.retrievalTest.completedIn": "Hoàn tất trong {{duration}}",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "Truy xuất thất bại",
"newKnowledge.retrievalTest.foundSoFar": "Đã tìm thấy ({{count}})",
"newKnowledge.retrievalTest.generating": "Tạo câu trả lời",
"newKnowledge.retrievalTest.generatingActive": "Tạo câu trả lời…",
"newKnowledge.retrievalTest.justNow": "Vừa xong",
"newKnowledge.retrievalTest.keepGoldenQuestion": "Lưu làm câu hỏi tham chiếu",
"newKnowledge.retrievalTest.makeBadCase": "Lưu làm trường hợp cần cải thiện",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "Mở",
"newKnowledge.retrievalTest.page": "Trang {{page}}",
"newKnowledge.retrievalTest.planning": "Lập kế hoạch nghiên cứu",
"newKnowledge.retrievalTest.planningActive": "Lập kế hoạch nghiên cứu…",
"newKnowledge.retrievalTest.processLog": "Nhật ký quy trình",
"newKnowledge.retrievalTest.quality": "Chất lượng",
"newKnowledge.retrievalTest.queryPlaceholder": "Nhập truy vấn để kiểm thử truy xuất…",
"newKnowledge.retrievalTest.records": "Lịch sử",
"newKnowledge.retrievalTest.researchResult": "Kết quả nghiên cứu",
"newKnowledge.retrievalTest.result": "Kết quả {{mode}}",
"newKnowledge.retrievalTest.result": "Kết quả truy xuất",
"newKnowledge.retrievalTest.retrieving": "Truy xuất bằng chứng",
"newKnowledge.retrievalTest.retrievingActive": "Truy xuất bằng chứng…",
"newKnowledge.retrievalTest.retry": "Thử lại",
"newKnowledge.retrievalTest.run": "Chạy",
"newKnowledge.retrievalTest.running": "Đang nghiên cứu…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "检索次数",
"newKnowledge.retrievalCountValue": "过去 7 天内 {{value}} 次",
"newKnowledge.retrievalTest.analyzing": "分析证据",
"newKnowledge.retrievalTest.analyzingActive": "分析证据…",
"newKnowledge.retrievalTest.cancel": "取消",
"newKnowledge.retrievalTest.canceled": "已取消",
"newKnowledge.retrievalTest.completedIn": "已在 {{duration}} 内完成",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "检索失败",
"newKnowledge.retrievalTest.foundSoFar": "目前找到({{count}}",
"newKnowledge.retrievalTest.generating": "生成回答",
"newKnowledge.retrievalTest.generatingActive": "生成回答…",
"newKnowledge.retrievalTest.justNow": "刚刚",
"newKnowledge.retrievalTest.keepGoldenQuestion": "保留为参考题",
"newKnowledge.retrievalTest.makeBadCase": "标记为不良案例",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "打开",
"newKnowledge.retrievalTest.page": "第 {{page}} 页",
"newKnowledge.retrievalTest.planning": "规划研究",
"newKnowledge.retrievalTest.planningActive": "规划研究…",
"newKnowledge.retrievalTest.processLog": "过程日志",
"newKnowledge.retrievalTest.quality": "质量",
"newKnowledge.retrievalTest.queryPlaceholder": "输入查询以测试检索…",
"newKnowledge.retrievalTest.records": "记录",
"newKnowledge.retrievalTest.researchResult": "研究结果",
"newKnowledge.retrievalTest.result": "{{mode}} 结果",
"newKnowledge.retrievalTest.result": "检索结果",
"newKnowledge.retrievalTest.retrieving": "检索证据",
"newKnowledge.retrievalTest.retrievingActive": "检索证据…",
"newKnowledge.retrievalTest.retry": "重试",
"newKnowledge.retrievalTest.run": "运行",
"newKnowledge.retrievalTest.running": "研究中…",

View File

@ -504,6 +504,7 @@
"newKnowledge.retrievalCount": "檢索次數",
"newKnowledge.retrievalCountValue": "過去 7 天內 {{value}} 次",
"newKnowledge.retrievalTest.analyzing": "分析證據",
"newKnowledge.retrievalTest.analyzingActive": "分析證據…",
"newKnowledge.retrievalTest.cancel": "取消",
"newKnowledge.retrievalTest.canceled": "已取消",
"newKnowledge.retrievalTest.completedIn": "已在 {{duration}} 內完成",
@ -515,6 +516,7 @@
"newKnowledge.retrievalTest.failedTitle": "檢索失敗",
"newKnowledge.retrievalTest.foundSoFar": "目前找到({{count}}",
"newKnowledge.retrievalTest.generating": "產生回答",
"newKnowledge.retrievalTest.generatingActive": "產生回答…",
"newKnowledge.retrievalTest.justNow": "剛剛",
"newKnowledge.retrievalTest.keepGoldenQuestion": "保留為參考題",
"newKnowledge.retrievalTest.makeBadCase": "標記為不良案例",
@ -523,13 +525,15 @@
"newKnowledge.retrievalTest.open": "開啟",
"newKnowledge.retrievalTest.page": "第 {{page}} 頁",
"newKnowledge.retrievalTest.planning": "規劃研究",
"newKnowledge.retrievalTest.planningActive": "規劃研究…",
"newKnowledge.retrievalTest.processLog": "流程記錄",
"newKnowledge.retrievalTest.quality": "品質",
"newKnowledge.retrievalTest.queryPlaceholder": "輸入查詢以測試檢索…",
"newKnowledge.retrievalTest.records": "記錄",
"newKnowledge.retrievalTest.researchResult": "研究結果",
"newKnowledge.retrievalTest.result": "{{mode}} 結果",
"newKnowledge.retrievalTest.result": "檢索結果",
"newKnowledge.retrievalTest.retrieving": "取回證據",
"newKnowledge.retrievalTest.retrievingActive": "取回證據…",
"newKnowledge.retrievalTest.retry": "重試",
"newKnowledge.retrievalTest.run": "執行",
"newKnowledge.retrievalTest.running": "研究中…",

View File

@ -0,0 +1,8 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="12.6248" height="11.0833" viewBox="0 0 12.6248 11.0833" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Vector">
<path d="M10.4911 0.252525C10.4748 0.108798 10.3533 0.00014598 10.2086 1.46976e-07C10.064 -0.000145686 9.94222 0.108249 9.92565 0.251948C9.82217 1.14913 9.31578 1.65551 8.41861 1.75899C8.27493 1.77556 8.16649 1.89731 8.16667 2.04196C8.16684 2.18661 8.27546 2.30813 8.41919 2.32441C9.30364 2.4246 9.84515 2.9261 9.92489 3.82396C9.93796 3.9709 10.0612 4.0835 10.2086 4.08333C10.3562 4.08316 10.4791 3.97029 10.4918 3.82332C10.5684 2.93815 11.1048 2.40178 11.99 2.32516C12.137 2.31244 12.2498 2.18951 12.25 2.04199C12.2502 1.89447 12.1376 1.77128 11.9907 1.75823C11.0928 1.67847 10.5912 1.13698 10.4911 0.252525Z" fill="#354052"/>
<path d="M5.97917 3.20833C5.97917 2.40292 5.32625 1.75 4.52083 1.75C3.71542 1.75 3.0625 2.40292 3.0625 3.20833C3.0625 4.01375 3.71542 4.66667 4.52083 4.66667C5.32625 4.66667 5.97917 4.01375 5.97917 3.20833ZM7.14583 3.20833C7.14583 4.65808 5.97058 5.83333 4.52083 5.83333C3.07109 5.83333 1.89583 4.65808 1.89583 3.20833C1.89583 1.75859 3.07109 0.583333 4.52083 0.583333C5.97058 0.583333 7.14583 1.75859 7.14583 3.20833Z" fill="#354052"/>
<path d="M0 10.5C0 8.24484 1.82817 6.41667 4.08333 6.41667H4.375C4.69717 6.41667 4.95833 6.67783 4.95833 7C4.95833 7.32217 4.69717 7.58333 4.375 7.58333H4.08333C2.4725 7.58333 1.16667 8.88917 1.16667 10.5C1.16667 10.8222 0.9055 11.0833 0.583333 11.0833C0.261167 11.0833 0 10.8222 0 10.5Z" fill="#354052"/>
<path d="M11.3995 5.83333C12.2133 5.83344 12.8005 6.61236 12.577 7.39478L11.7652 10.2374C11.6221 10.7381 11.1643 11.0832 10.6436 11.0833H3.5C3.17783 11.0833 2.91667 10.8222 2.91667 10.5C2.91667 10.1778 3.17783 9.91667 3.5 9.91667H5.39299L6.25773 6.89119C6.43661 6.26521 7.00904 5.83333 7.66024 5.83333H11.3995ZM7.66024 7C7.53005 7 7.41523 7.08619 7.3794 7.21134L6.60693 9.91667H10.6436L11.4553 7.07463C11.466 7.03734 11.4381 7.00011 11.3995 7H7.66024Z" fill="#354052"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB