feat(rag): align LightRAG native citations and MCP bridge

This commit is contained in:
lix-2026
2026-06-09 09:20:56 +08:00
parent 0e8b03daf8
commit 8e3be8b0b7
39 changed files with 2255 additions and 2732 deletions
+79 -2
View File
@@ -994,6 +994,14 @@ pub struct OfficePreviewQuery {
source_map_path: Option<String>,
#[serde(default, alias = "blockId")]
block_id: Option<String>,
#[serde(default, alias = "paragraphOrdinal")]
paragraph_ordinal: Option<String>,
#[serde(default, alias = "paraIdStart")]
para_id_start: Option<String>,
#[serde(default, alias = "paraIdEnd")]
para_id_end: Option<String>,
#[serde(default, alias = "textFingerprint")]
text_fingerprint: Option<String>,
#[serde(default, alias = "evidenceText")]
evidence_text: Option<String>,
#[serde(default, alias = "searchQuery")]
@@ -1032,6 +1040,10 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
let target_bbox = query.bbox.unwrap_or_default();
let target_source_map_path = query.source_map_path.unwrap_or_default();
let target_block_id = query.block_id.unwrap_or_default();
let target_paragraph_ordinal = query.paragraph_ordinal.unwrap_or_default();
let target_para_id_start = query.para_id_start.unwrap_or_default();
let target_para_id_end = query.para_id_end.unwrap_or_default();
let target_text_fingerprint = query.text_fingerprint.unwrap_or_default();
let target_evidence_text = query.evidence_text.unwrap_or_default();
let target_search_query = query.search_query.unwrap_or_default();
let html = format!(
@@ -1080,7 +1092,7 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
}}
</style>
</head>
<body data-file-url="{file_url}" data-file-name="{file_name}" data-file-type="{file_type}" data-page-width-content-type="{page_width_content_type}" data-workspace-id="{workspace_id}" data-mnote-source-kind="{source_kind}" data-mnote-root-uri="{root_uri}" data-document-id="{document_id}" data-evidence-page="{target_page}" data-evidence-bbox="{target_bbox}" data-evidence-source-map-path="{target_source_map_path}" data-evidence-block-id="{target_block_id}" data-evidence-text="{target_evidence_text}" data-evidence-search-query="{target_search_query}">
<body data-file-url="{file_url}" data-file-name="{file_name}" data-file-type="{file_type}" data-page-width-content-type="{page_width_content_type}" data-workspace-id="{workspace_id}" data-mnote-source-kind="{source_kind}" data-mnote-root-uri="{root_uri}" data-document-id="{document_id}" data-evidence-page="{target_page}" data-evidence-bbox="{target_bbox}" data-evidence-source-map-path="{target_source_map_path}" data-evidence-block-id="{target_block_id}" data-evidence-paragraph-ordinal="{target_paragraph_ordinal}" data-evidence-para-id-start="{target_para_id_start}" data-evidence-para-id-end="{target_para_id_end}" data-evidence-text-fingerprint="{target_text_fingerprint}" data-evidence-text="{target_evidence_text}" data-evidence-search-query="{target_search_query}">
<main class="mnote-office-preview">
<section class="mnote-office-viewer" id="mnote-office-viewer"></section>
</main>
@@ -1099,6 +1111,10 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
let evidenceBbox = body.dataset.evidenceBbox || '';
let evidenceSourceMapPath = body.dataset.evidenceSourceMapPath || '';
let evidenceBlockId = body.dataset.evidenceBlockId || '';
let evidenceParagraphOrdinal = body.dataset.evidenceParagraphOrdinal || '';
let evidenceParaIdStart = body.dataset.evidenceParaIdStart || '';
let evidenceParaIdEnd = body.dataset.evidenceParaIdEnd || '';
let evidenceTextFingerprint = body.dataset.evidenceTextFingerprint || '';
let evidenceText = body.dataset.evidenceText || '';
let evidenceSearchQuery = body.dataset.evidenceSearchQuery || '';
let currentPptxBuffer = null;
@@ -1484,6 +1500,17 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
if (queryIndex >= 0 && normalized.length > 24) push(normalized.slice(0, 36), {{ allowShort: true }});
}}
const prefixWindow = cleaned.slice(0, 260);
if (queryCompact.length >= 2) {{
const prefixCompact = compactEvidenceText(prefixWindow);
const compactIndex = prefixCompact.indexOf(queryCompact);
if (compactIndex >= 0) {{
const queryIndex = prefixWindow.indexOf(evidenceSearchQuery);
const start = queryIndex >= 0 ? queryIndex : 0;
const queryWindow = prefixWindow.slice(start, start + 96).split(/[。;;]/)[0];
push(queryWindow, {{ allowShort: true }});
queryWindow.split(/[,]/).slice(0, 2).forEach(part => push(part, {{ allowShort: true }}));
}}
}}
const catalogMatches = prefixWindow.match(/[^,。;;#]{{2,56}}[,]\s*[0-9-]{{1,5}}/g) || [];
catalogMatches.slice(0, 8).forEach(match => {{
const value = normalizeEvidenceText(match);
@@ -1711,6 +1738,42 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
return markEvidenceTarget(marker);
}}
function evidenceParagraphElements() {{
if (!viewer) return [];
const paragraphs = Array.from(viewer.querySelectorAll('p, li, td, th, blockquote'))
.filter(node => node instanceof HTMLElement)
.filter(node => normalizeEvidenceText(node.textContent || '').length > 0);
if (paragraphs.length) return paragraphs;
return Array.from(viewer.querySelectorAll('div, section.docx, section.mnote-docx'))
.filter(node => node instanceof HTMLElement)
.filter(node => normalizeEvidenceText(node.textContent || '').length > 0);
}}
function scrollToEvidenceParagraphOrdinal() {{
const ordinal = Number(evidenceParagraphOrdinal);
if (!Number.isFinite(ordinal) || ordinal < 0) return false;
const elements = evidenceParagraphElements();
if (!elements.length) return false;
const anchors = evidenceParagraphAnchors(evidenceText || evidenceSearchQuery);
const indices = [];
for (let offset = 0; offset <= 4; offset += 1) {{
if (offset === 0) indices.push(ordinal);
else {{
indices.push(ordinal - offset);
indices.push(ordinal + offset);
}}
}}
let best = null;
for (const index of indices) {{
if (index < 0 || index >= elements.length) continue;
const element = elements[index];
const score = anchors.length ? scoreEvidenceParagraphElement(element, anchors) : 0;
if (!best || score > best.score) best = {{ element, score }};
if (score >= 2400 && evidenceElementMatchesSearchQuery(element)) break;
}}
return best ? markEvidenceTarget(best.element) : false;
}}
function scrollToEvidencePageFallback() {{
if (!viewer || !Number.isFinite(evidencePage) || evidencePage <= 0) return false;
const pages = Array.from(viewer.querySelectorAll('section.docx, section.mnote-docx, .pptx-preview-slide-wrapper'));
@@ -1719,16 +1782,18 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
}}
async function applyEvidenceLocator() {{
if (!viewer || (!evidencePage && !evidenceBlockId && !evidenceBbox && !evidenceText)) return;
if (!viewer || (!evidencePage && !evidenceBlockId && !evidenceBbox && !evidenceText && !evidenceParagraphOrdinal && !evidenceTextFingerprint)) return;
try {{
const sourceMap = await fetchEvidenceSourceMap();
const block = findEvidenceBlockInSourceMap(sourceMap);
if (evidenceParagraphOrdinal && scrollToEvidenceParagraphOrdinal()) return;
if (evidenceText && scrollToEvidenceParagraph(evidenceText)) return;
if (block && scrollToEvidenceParagraph(block.text)) return;
if (evidenceText && scrollToEvidenceTextCandidates(evidenceText)) return;
if (block && scrollToEvidenceTextCandidates(block.text)) return;
if (scrollToEvidenceCoordinate(sourceMap, block)) return;
}} catch (_) {{}}
if (evidenceParagraphOrdinal && scrollToEvidenceParagraphOrdinal()) return;
if (evidenceText && scrollToEvidenceParagraph(evidenceText)) return;
if (evidenceText && scrollToEvidenceTextCandidates(evidenceText)) return;
scrollToEvidencePageFallback();
@@ -1740,12 +1805,20 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
evidenceBbox = String(next.bbox || '');
evidenceSourceMapPath = String(next.sourceMapPath || '');
evidenceBlockId = String(next.blockId || '');
evidenceParagraphOrdinal = String(next.paragraphOrdinal || '');
evidenceParaIdStart = String(next.paraIdStart || '');
evidenceParaIdEnd = String(next.paraIdEnd || '');
evidenceTextFingerprint = String(next.textFingerprint || '');
evidenceText = String(next.evidenceText || next.query || '');
evidenceSearchQuery = String(next.searchQuery || '');
body.dataset.evidencePage = evidencePage ? String(evidencePage) : '';
body.dataset.evidenceBbox = evidenceBbox;
body.dataset.evidenceSourceMapPath = evidenceSourceMapPath;
body.dataset.evidenceBlockId = evidenceBlockId;
body.dataset.evidenceParagraphOrdinal = evidenceParagraphOrdinal;
body.dataset.evidenceParaIdStart = evidenceParaIdStart;
body.dataset.evidenceParaIdEnd = evidenceParaIdEnd;
body.dataset.evidenceTextFingerprint = evidenceTextFingerprint;
body.dataset.evidenceText = evidenceText;
body.dataset.evidenceSearchQuery = evidenceSearchQuery;
document.documentElement.removeAttribute('data-mnote-office-evidence-applied');
@@ -1969,6 +2042,10 @@ pub async fn office_preview_page(Query(query): Query<OfficePreviewQuery>) -> Res
target_bbox = escape_html(&target_bbox),
target_source_map_path = escape_html(&target_source_map_path),
target_block_id = escape_html(&target_block_id),
target_paragraph_ordinal = escape_html(&target_paragraph_ordinal),
target_para_id_start = escape_html(&target_para_id_start),
target_para_id_end = escape_html(&target_para_id_end),
target_text_fingerprint = escape_html(&target_text_fingerprint),
target_evidence_text = escape_html(&target_evidence_text),
);
let mut response = Html(html).into_response();