zhangwei-way b6503e9a35
feat(knowledge): add per-message RAGFlow retrieval scope (#5238)
* feat(knowledge): integrate RAGFlow retrieval and management

* test(knowledge): cover merged listing tool

* feat(knowledge): add per-message retrieval scope

* chore(docs): remove unrelated document

* docs(knowledge): add interaction screenshots

* feat(knowledge): simplify scope selector trigger

* docs(knowledge): refresh selector screenshot

* feat(knowledge): defer standalone management

* docs(knowledge): show chat-only scope UI

* fix(knowledge): honor scope on clarification replies

* fix(knowledge): harden scoped replay validation

* docs(knowledge): clarify replay scope precedence

* fix(knowledge): keep provider settings on tools

* fix(config): preserve tools-only knowledge settings

* fix(knowledge): submit custom assistant identity

* refactor(knowledge): trim PR scope changes

* fix(knowledge): sanitize document scope display

* feat(knowledge): enable scope selection in main chat

* fix(knowledge): emphasize active scope icon without button frame

* fix(knowledge): close context scrubbing and refresh e2e checks

* fix(knowledge): preserve idempotent canonical retries

* fix(knowledge): accept promptless conversation runs

* style(knowledge): format backend regression tests

* chore(knowledge): trim PR scope and fix frontend format

* fix(knowledge): remove shared-scope notice

* fix(knowledge): remove scope persistence notice

* docs(knowledge): include main chat in catalog scope

* fix(knowledge): preserve scope recovery and upgrades

* fix(config): preserve LightRAG knowledge upgrades

---------

Co-authored-by: foreleven <for-eleven@hotmail.com>
2026-09-18 16:59:31 +08:00

160 lines
4.2 KiB
TypeScript

import { describe, expect, test } from "@rstest/core";
import {
buildKnowledgeScopeSnapshot,
cloneKnowledgeScopeSelection,
readKnowledgeScopeSnapshot,
type KnowledgeScopeSelection,
} from "@/core/knowledge/scope";
describe("knowledge scope snapshots", () => {
test("builds selected datasets and file filters with bounded display labels", () => {
const snapshot = buildKnowledgeScopeSnapshot({
mode: "selected",
datasets: [
{
id: " dataset-1 ",
name: "Policies",
documents: {
mode: "selected",
items: [
{ id: "doc-1", name: "Leave.pdf" },
{ id: "doc-1", name: "Duplicate.pdf" },
],
},
},
{
id: "dataset-2",
name: "Handbook",
documents: { mode: "all" },
},
],
});
expect(snapshot).toEqual({
version: 1,
mode: "selected",
dataset_ids: ["dataset-1", "dataset-2"],
document_filters: [{ dataset_id: "dataset-1", document_ids: ["doc-1"] }],
display: {
datasets: [
{
id: "dataset-1",
name: "Policies",
documents: [{ id: "doc-1", name: "Leave.pdf" }],
},
{ id: "dataset-2", name: "Handbook" },
],
},
});
});
test("strips catalog metadata from displayed document snapshots", () => {
const catalogDocument = {
id: "doc-1",
name: "Leave.pdf",
selectable: true,
};
const snapshot = buildKnowledgeScopeSnapshot({
mode: "selected",
datasets: [
{
id: "dataset-1",
name: "Policies",
documents: { mode: "selected", items: [catalogDocument] },
},
],
});
expect(snapshot.display?.datasets[0]?.documents).toEqual([
{ id: "doc-1", name: "Leave.pdf" },
]);
});
test("rejects an empty selected document filter", () => {
expect(() =>
buildKnowledgeScopeSnapshot({
mode: "selected",
datasets: [
{
id: "dataset-1",
name: "Policies",
documents: { mode: "selected", items: [] },
},
],
}),
).toThrow(/at least one document/i);
});
test("omits overlong display names without truncating execution ids", () => {
const snapshot = buildKnowledgeScopeSnapshot({
mode: "selected",
datasets: [
{
id: "dataset-1",
name: "x".repeat(257),
documents: { mode: "all" },
},
],
});
expect(snapshot.dataset_ids).toEqual(["dataset-1"]);
expect(snapshot.display).toBeUndefined();
});
test("counts astral Unicode characters as code points", () => {
const emojiName = "😀".repeat(256);
const snapshot = buildKnowledgeScopeSnapshot({
mode: "selected",
datasets: [
{
id: "dataset-1",
name: emojiName,
documents: { mode: "all" },
},
],
});
expect(snapshot.display?.datasets[0]?.name).toBe(emojiName);
});
test("clones nested selection state for immutable message snapshots", () => {
const selection: KnowledgeScopeSelection = {
mode: "selected",
datasets: [
{
id: "dataset-1",
name: "Policies",
documents: {
mode: "selected",
items: [{ id: "doc-1", name: "Leave.pdf" }],
},
},
],
};
const cloned = cloneKnowledgeScopeSelection(selection);
if (selection.mode === "selected") selection.datasets[0]!.name = "Renamed";
expect(cloned).not.toBe(selection);
expect(cloned.mode === "selected" && cloned.datasets[0]?.name).toBe(
"Policies",
);
});
test("reads only recognized version-one modes", () => {
expect(readKnowledgeScopeSnapshot({ version: 1, mode: "all" })).toEqual({
version: 1,
mode: "all",
});
expect(readKnowledgeScopeSnapshot({ version: 2, mode: "all" })).toBeNull();
expect(
readKnowledgeScopeSnapshot({
version: 1,
mode: "selected",
dataset_ids: ["dataset-1"],
document_filters: [{ dataset_id: "dataset-1", document_ids: null }],
}),
).toBeNull();
});
});