feat(rag): harden post-LightRAG runtime

Retire legacy OCR/media/evidence fallbacks, add local-folder event bus and Page Aggregate guards, and archive completed design checklists.

Validation: cargo test -p mnote-web -- --test-threads=1; cargo test --workspace -- --test-threads=1; git diff --check; codegraph sync .; codegraph_status.
This commit is contained in:
lix-2026
2026-06-07 10:35:21 +08:00
parent 22a92edcda
commit 9551d4c1dc
59 changed files with 4053 additions and 5249 deletions
@@ -92,6 +92,58 @@ async function browserRefreshLocalIndex(page, root) {
}, { rootUri: fileUrl(root) });
}
async function browserUpdateLocalIndexSettings(page, root) {
return page.evaluate(async ({ rootUri }) => {
const response = await fetch("/api/search/local-index/settings", {
method: "PUT",
headers: { "content-type": "application/json", accept: "application/json" },
body: JSON.stringify({
workspaceId: "local-ws:user_real:task452",
rootUri,
includePaths: ["."],
scheduleMode: "manual",
scheduleTime: "02:00",
runOnChange: false,
}),
});
const payload = await response.json().catch(() => null);
return {
status: response.status,
payload,
};
}, { rootUri: fileUrl(root) });
}
async function browserFetchLocalIndexBacklinksAndTags(page, root, documentId) {
return page.evaluate(async ({ rootUri, documentIdValue }) => {
const backlinksParams = new URLSearchParams({
workspaceId: "local-ws:user_real:task452",
rootUri,
documentId: documentIdValue,
});
const tagsParams = new URLSearchParams({
workspaceId: "local-ws:user_real:task452",
rootUri,
});
const backlinksResponse = await fetch(`/api/search/local-index/backlinks?${backlinksParams.toString()}`, {
headers: { accept: "application/json" },
});
const tagsResponse = await fetch(`/api/search/local-index/tags?${tagsParams.toString()}`, {
headers: { accept: "application/json" },
});
return {
backlinks: {
status: backlinksResponse.status,
payload: await backlinksResponse.json().catch(() => null),
},
tags: {
status: tagsResponse.status,
payload: await tagsResponse.json().catch(() => null),
},
};
}, { rootUri: fileUrl(root), documentIdValue: documentId });
}
async function capturePanelDiagnostics(page) {
try {
return await page.evaluate(function() {
@@ -99,12 +151,16 @@ async function capturePanelDiagnostics(page) {
var tg = document.querySelector('[data-testid="wolai-page-settings-local-index-tags"]');
var st = document.querySelector('[data-testid="wolai-page-settings-local-index-status"]');
var popover = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
var ranges = document.querySelector('[data-testid="wolai-page-settings-local-index-ranges"]');
var pageSettingsIndexTab = document.querySelector('[data-page-settings-tab="index"]');
return {
backlinksHtml: bl ? bl.innerHTML : '(missing)',
tagsHtml: tg ? tg.innerHTML : '(missing)',
statusText: st ? st.textContent : '(missing)',
popoverExists: Boolean(popover),
popoverHidden: popover ? popover.hidden : null,
rangeInputs: ranges ? ranges.querySelectorAll('[data-local-index-range-input]').length : 0,
hasPageSettingsIndexTab: Boolean(pageSettingsIndexTab),
locationHref: window.location.href,
};
});
@@ -157,6 +213,9 @@ async function run() {
"utf8",
);
const settingsBeforeSearch = await browserUpdateLocalIndexSettings(page, root);
assert.equal(settingsBeforeSearch.status, 200, `更新本地索引设置应成功: ${JSON.stringify(settingsBeforeSearch)}`);
debug.settingsBeforeSearch = settingsBeforeSearch.payload;
const refreshedBeforeSearch = await browserRefreshLocalIndex(page, root);
assert.equal(refreshedBeforeSearch.status, 200, `刷新本地索引应成功: ${JSON.stringify(refreshedBeforeSearch)}`);
debug.refreshedBeforeSearch = refreshedBeforeSearch.payload;
@@ -173,23 +232,19 @@ async function run() {
waitUntil: "domcontentloaded",
timeout: UI_TIMEOUT_MS,
});
await page.locator('[data-testid="mnote-local-index-settings-toggle"]').click({ timeout: UI_TIMEOUT_MS });
await page.locator('[data-testid="mnote-local-index-settings-popover"]').waitFor({
state: "visible",
timeout: UI_TIMEOUT_MS,
});
debug.navigationIndexSettings = await page.evaluate(function() {
var popover = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
var ranges = document.querySelector('[data-testid="wolai-page-settings-local-index-ranges"]');
var indexToggle = document.querySelector('[data-testid="mnote-local-index-settings-toggle"]');
var ragToggle = document.querySelector('[data-testid="mnote-knowledge-rag-settings-toggle"]');
var pageSettingsIndexTab = document.querySelector('[data-page-settings-tab="index"]');
return {
path: window.location.pathname,
visible: Boolean(popover && !popover.hidden),
rangeInputs: ranges ? ranges.querySelectorAll('[data-local-index-range-input]').length : 0,
hasLocalIndexToggle: Boolean(indexToggle),
hasKnowledgeRagToggle: Boolean(ragToggle),
hasPageSettingsIndexTab: Boolean(pageSettingsIndexTab),
};
});
assert.equal(debug.navigationIndexSettings.visible, true, `导航页应能打开索引设置: ${JSON.stringify(debug.navigationIndexSettings)}`);
assert.equal(debug.navigationIndexSettings.hasLocalIndexToggle, false, `导航页不应恢复旧本地索引设置入口: ${JSON.stringify(debug.navigationIndexSettings)}`);
assert.equal(debug.navigationIndexSettings.hasKnowledgeRagToggle, true, `导航页应保留资料库问答入口: ${JSON.stringify(debug.navigationIndexSettings)}`);
assert.equal(debug.navigationIndexSettings.hasPageSettingsIndexTab, false, `索引设置不应留在页面设置页签: ${JSON.stringify(debug.navigationIndexSettings)}`);
await page.goto(documentUrl(root, firstRelativePath), {
@@ -201,49 +256,46 @@ async function run() {
timeout: UI_TIMEOUT_MS,
});
// 拦截 API 请求与页面错误做诊断
// 拦截页面错误做诊断backlink/tag 当前不再由索引面板渲染,下面走 API 直验。
var diagApiResponses = {};
page.on('response', function onDiag(resp) {
var url = resp.url();
if (url.includes('/api/search/local-index/backlinks') || url.includes('/api/search/local-index/tags')) {
resp.json().then(function(body) { diagApiResponses[url] = body; }).catch(function() {});
}
});
page.on('pageerror', function onPageError(err) { diagApiResponses._pageError = String(err); });
page.on('console', function onConsole(msg) {
if (msg.type() === 'error') { diagApiResponses._consoleErrors = (diagApiResponses._consoleErrors || []).concat([msg.text()]); }
});
await page.locator('[data-testid="mnote-local-index-settings-toggle"]').click({ timeout: UI_TIMEOUT_MS });
// 等待本地索引面板渲染;超时时捕获 DOM 诊断再抛
try {
await page.waitForFunction(() => {
var bl = document.querySelector('[data-testid="wolai-page-settings-local-index-backlinks"]');
var tg = document.querySelector('[data-testid="wolai-page-settings-local-index-tags"]');
return Boolean(
bl && bl.textContent && bl.textContent.includes("README.md")
&& bl.textContent.includes("[Target](docs/search-target.md)")
&& tg && tg.textContent && tg.textContent.includes("#alpha"),
);
}, null, { timeout: UI_TIMEOUT_MS });
} catch (waitError) {
debug.diagApiResponses = diagApiResponses;
debug.failedPanelState = await capturePanelDiagnostics(page);
throw waitError;
}
debug.localIndexPanel = await page.evaluate(function() {
var bl = document.querySelector('[data-testid="wolai-page-settings-local-index-backlinks"]');
var tg = document.querySelector('[data-testid="wolai-page-settings-local-index-tags"]');
var st = document.querySelector('[data-testid="wolai-page-settings-local-index-status"]');
var indexToggle = document.querySelector('[data-testid="mnote-local-index-settings-toggle"]');
var ragToggle = document.querySelector('[data-testid="mnote-knowledge-rag-settings-toggle"]');
var pageSettingsIndexTab = document.querySelector('[data-page-settings-tab="index"]');
return {
backlinks: bl ? bl.innerHTML : '(missing)',
tags: tg ? tg.innerHTML : '(missing)',
status: st ? st.textContent : '(missing)',
hasLocalIndexToggle: Boolean(indexToggle),
hasKnowledgeRagToggle: Boolean(ragToggle),
hasPageSettingsIndexTab: Boolean(pageSettingsIndexTab),
hasLegacyBacklinksDom: Boolean(document.querySelector('[data-testid="wolai-page-settings-local-index-backlinks"]')),
hasLegacyTagsDom: Boolean(document.querySelector('[data-testid="wolai-page-settings-local-index-tags"]')),
};
});
debug.diagApiResponses = diagApiResponses;
assert.equal(debug.localIndexPanel.hasLocalIndexToggle, false, `文档页不应恢复旧本地索引设置入口: ${JSON.stringify(debug.localIndexPanel)}`);
assert.equal(debug.localIndexPanel.hasKnowledgeRagToggle, true, `文档页应保留资料库问答入口: ${JSON.stringify(debug.localIndexPanel)}`);
assert.equal(debug.localIndexPanel.hasPageSettingsIndexTab, false, `文档页索引设置不应回落到页面设置页签: ${JSON.stringify(debug.localIndexPanel)}`);
assert.equal(debug.localIndexPanel.hasLegacyBacklinksDom, false, `独立索引面板不应恢复旧 backlink DOM: ${JSON.stringify(debug.localIndexPanel)}`);
assert.equal(debug.localIndexPanel.hasLegacyTagsDom, false, `独立索引面板不应恢复旧 tags DOM: ${JSON.stringify(debug.localIndexPanel)}`);
const backlinksAndTags = await browserFetchLocalIndexBacklinksAndTags(page, root, localMdDocumentId(firstRelativePath));
assert.equal(backlinksAndTags.backlinks.status, 200, `反链 API 应继续可用: ${JSON.stringify(backlinksAndTags)}`);
assert.equal(backlinksAndTags.tags.status, 200, `标签 API 应继续可用: ${JSON.stringify(backlinksAndTags)}`);
debug.backlinksAndTags = backlinksAndTags;
const backlinks = backlinksAndTags.backlinks.payload?.result?.backlinks || [];
assert(
backlinks.some((item) => item.documentId === localMdDocumentId("README.md")),
`本地索引反链应能找到 README.md: ${JSON.stringify(backlinksAndTags.backlinks.payload)}`,
);
const tags = backlinksAndTags.tags.payload?.result?.tags || [];
assert(
tags.some((item) => item.tag === "alpha"),
`本地索引标签 API 应能返回 alpha: ${JSON.stringify(backlinksAndTags.tags.payload)}`,
);
fs.renameSync(path.join(root, firstRelativePath), path.join(root, renamedRelativePath));
const refreshedAfterRename = await browserRefreshLocalIndex(page, root);