Improve LightRAG knowledge search locator alignment

This commit is contained in:
lix-2026
2026-06-08 20:35:49 +08:00
parent 9551d4c1dc
commit 0e8b03daf8
28 changed files with 5769 additions and 140 deletions
@@ -19,47 +19,260 @@ export function createSidebarPageAiMarkdownRuntime(context) {
function renderPageAiMarkdownInline(text) {
var html = escapeHtml(String(text || ''));
var codeSpans = [];
var htmlSpans = [];
function stashHtml(value) {
var key = '\u0000HTML' + htmlSpans.length + '\u0000';
htmlSpans.push(value);
return key;
}
html = html.replace(/`([^`\n]+)`/g, function(_, code) {
var key = '\u0000CODE' + codeSpans.length + '\u0000';
codeSpans.push('<code>' + code + '</code>');
return key;
});
html = html.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g, function(match, label, href) {
html = html.replace(/\[((?:\\.|[^\]\n])+)\]\(([^)\n]+)\)/g, function(match, label, href) {
var normalizedHref = normalizePageAiMarkdownHref(href);
if (!normalizedHref) return match;
var citationAttr = isMnoteCitationHref(normalizedHref) ? ' data-page-ai-citation-link="true"' : '';
return '<a href="' + escapeHtml(normalizedHref) + '" target="_blank" rel="noopener noreferrer"' + citationAttr + '>' + label + '</a>';
return stashHtml('<a href="' + escapeHtml(normalizedHref) + '" target="_blank" rel="noopener noreferrer"' + citationAttr + '>' + unescapePageAiMarkdownLabel(label) + '</a>');
});
html = html.replace(/(^|[\s(])((?:https?:\/\/|\/documents\/|mnote:\/\/open(?:Resource)?)[^\s<>()]+[^\s<>().,;:!?])/g, function(_, prefix, href) {
var normalizedHref = normalizePageAiMarkdownHref(href);
if (!normalizedHref) return prefix + href;
var citationAttr = isMnoteCitationHref(normalizedHref) ? ' data-page-ai-citation-link="true"' : '';
return prefix + stashHtml('<a href="' + escapeHtml(normalizedHref) + '" target="_blank" rel="noopener noreferrer"' + citationAttr + '>' + escapeHtml(normalizedHref) + '</a>');
});
html = html.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>');
html = html.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1<em>$2</em>');
codeSpans.forEach(function(value, index) {
html = html.replace('\u0000CODE' + index + '\u0000', value);
});
htmlSpans.forEach(function(value, index) {
html = html.replace('\u0000HTML' + index + '\u0000', value);
});
return html;
}
function normalizePageAiMarkdownHref(value) {
var href = String(value || '').trim()
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'");
while (href.includes('&amp;')) href = href.replace(/&amp;/g, '&');
if (href.startsWith('<') && href.endsWith('>')) href = href.slice(1, -1).trim();
if (!href || /[\u0000-\u001f<>"']/.test(href)) return '';
var lower = href.toLowerCase();
if (lower.startsWith('javascript:') || lower.startsWith('data:') || lower.startsWith('vbscript:')) return '';
if (lower.startsWith('mnote://open')) href = normalizePageAiMnoteOpenHref(href);
href = normalizePageAiLegacyCitationHref(href);
if (href.startsWith('/') || href.startsWith('#')) return href;
if (/^https?:\/\//i.test(href) || /^mailto:/i.test(href)) return href;
return '';
}
function isMnoteCitationHref(href) {
function unescapePageAiMarkdownLabel(value) {
return String(value || '').replace(/\\([\\\[\]()*_`])/g, '$1');
}
function normalizePageAiMnoteOpenHref(href) {
try {
var url = new URL(href);
if (url.protocol !== 'mnote:' || (url.hostname !== 'open' && url.hostname !== 'openResource')) return href;
var path = String(url.searchParams.get('path') || url.searchParams.get('resourcePath') || '').trim();
if (!path) return '';
var params = new URLSearchParams();
var rootUri = String(url.searchParams.get('rootUri') || '').trim();
if (!rootUri) {
try {
rootUri = new URL(window.location.href).searchParams.get('rootUri') || '';
} catch (_locationError) {}
}
if (rootUri) params.set('rootUri', rootUri);
params.set('path', path);
['page', 'bbox', 'sourceMapPath', 'blockId', 'evidenceText', 'lineRange', 'charRange'].forEach(function(key) {
var value = String(url.searchParams.get(key) || '').trim();
if (value) params.set(key, value);
});
return '/api/local-folder/files/open?' + params.toString();
} catch (_error) {
return href;
}
}
function normalizePageAiLegacyCitationHref(href) {
try {
var url = new URL(href, window.location.origin);
return url.origin === window.location.origin && url.pathname.startsWith('/documents/');
var unwrappedHref = normalizePageAiSearchWrappedCitationHref(url);
if (unwrappedHref) return normalizePageAiLegacyCitationHref(unwrappedHref);
if (!isPageAiSameMnoteOrigin(url) && !isPageAiPortableMnoteCitationUrl(url)) return href;
if (url.pathname === '/api/local-folder/files/open') return url.pathname + url.search;
if (!url.pathname.startsWith('/documents/')) return href;
if (isPageAiUnsafeCitationDocumentId(url)) {
var fileOpenHref = normalizePageAiCitationFileOpenHref(url);
if (fileOpenHref) return fileOpenHref;
}
var decodedHash = '';
try {
decodedHash = decodeURIComponent(url.hash || '');
} catch (_decodeError) {
decodedHash = url.hash || '';
}
var marker = '#resource-tab-';
var markerIndex = decodedHash.indexOf(marker);
if (markerIndex < 0 || url.searchParams.get('resourceTab')) {
return url.pathname + url.search + url.hash;
}
var identity = decodedHash.slice(markerIndex + marker.length).replace(/^#+/, '').trim();
if (!identity.startsWith('resource:file:')) return url.pathname + url.search + url.hash;
url.searchParams.set('resourceTab', identity);
if (!url.searchParams.get('sourceKind')) url.searchParams.set('sourceKind', 'local_folder');
var rootUri = String(url.searchParams.get('rootUri') || '').trim();
var prefix = 'resource:file:' + rootUri + ':';
if (rootUri && identity.startsWith(prefix) && !url.searchParams.get('resourcePath')) {
url.searchParams.set('resourcePath', identity.slice(prefix.length).replace(/^\/+/, ''));
}
url.hash = '';
return url.pathname + url.search;
} catch (_error) {
return href;
}
}
function normalizePageAiSearchWrappedCitationHref(url) {
var raw = '';
['wd', 'q', 'query'].some(function(key) {
raw = String(url.searchParams.get(key) || '').trim();
return Boolean(raw);
});
if (!raw) return '';
if (/^documents\//i.test(raw)) raw = '/' + raw;
if (!/^\/documents\//i.test(raw) && !/^https?:\/\/[^/]+\/documents\//i.test(raw) && !/^mnote:\/\/open/i.test(raw)) return '';
if (/^mnote:\/\/open/i.test(raw)) return normalizePageAiMnoteOpenHref(raw);
var nested = new URL(raw, window.location.origin);
if (!nested.pathname.startsWith('/documents/')) return '';
['sourceKind', 'rootUri', 'workspaceId', 'resourceTab', 'resourcePath', 'page', 'bbox', 'sourceMapPath', 'blockId', 'evidenceText', 'lineRange', 'charRange'].forEach(function(key) {
if (nested.searchParams.get(key)) return;
var value = String(url.searchParams.get(key) || '').trim();
if (value) nested.searchParams.set(key, value);
});
return nested.pathname + nested.search;
}
function normalizePageAiCitationFileOpenHref(url) {
var rootUri = String(url.searchParams.get('rootUri') || '').trim();
var path = String(url.searchParams.get('resourcePath') || '').trim();
if (!rootUri || !path) return '';
var params = new URLSearchParams();
params.set('rootUri', rootUri);
params.set('path', path);
['page', 'bbox', 'sourceMapPath', 'blockId', 'evidenceText', 'lineRange', 'charRange'].forEach(function(key) {
var value = String(url.searchParams.get(key) || '').trim();
if (value) params.set(key, value);
});
return '/api/local-folder/files/open?' + params.toString();
}
function isPageAiUnsafeCitationDocumentId(url) {
try {
var raw = String(url.pathname || '').replace(/^\/documents\//, '').split('/')[0] || '';
if (!raw) return false;
var decoded = decodeURIComponent(raw);
return decoded.indexOf('%') >= 0 || decoded.indexOf('\uFFFD') >= 0;
} catch (_error) {
return true;
}
}
function isPageAiPortableMnoteCitationUrl(url) {
try {
if (url.pathname === '/api/local-folder/files/open') {
return Boolean(String(url.searchParams.get('rootUri') || '').trim()) &&
Boolean(String(url.searchParams.get('path') || '').trim());
}
if (!url.pathname.startsWith('/documents/')) return false;
var resourceTab = String(url.searchParams.get('resourceTab') || '').trim();
return String(url.searchParams.get('sourceKind') || '').trim() === 'local_folder' ||
Boolean(String(url.searchParams.get('rootUri') || '').trim()) ||
Boolean(String(url.searchParams.get('resourcePath') || '').trim()) ||
resourceTab.startsWith('resource:file:');
} catch (_error) {
return false;
}
}
function isPageAiMarkdownTableDivider(line) {
var cells = splitPageAiMarkdownTableRow(line);
if (cells.length < 2) return false;
return cells.every(function(cell) {
return /^:?-{3,}:?$/.test(cell.trim());
});
}
function splitPageAiMarkdownTableRow(line) {
var text = String(line || '').trim();
if (!text.includes('|')) return [];
if (text.startsWith('|')) text = text.slice(1);
if (text.endsWith('|')) text = text.slice(0, -1);
return text.split('|').map(function(cell) { return cell.trim(); });
}
function renderPageAiMarkdownTable(lines, startIndex) {
if (startIndex + 1 >= lines.length || !isPageAiMarkdownTableDivider(lines[startIndex + 1])) return null;
var header = splitPageAiMarkdownTableRow(lines[startIndex]);
var divider = splitPageAiMarkdownTableRow(lines[startIndex + 1]);
if (!header.length || header.length !== divider.length) return null;
var rows = [];
var index = startIndex + 2;
while (index < lines.length && lines[index].trim() && lines[index].includes('|')) {
var cells = splitPageAiMarkdownTableRow(lines[index]);
if (!cells.length) break;
rows.push(cells);
index += 1;
}
function cellHtml(tag, value) {
return '<' + tag + '>' + renderPageAiMarkdownInline(value) + '</' + tag + '>';
}
var head = '<thead><tr>' + header.map(function(cell) { return cellHtml('th', cell); }).join('') + '</tr></thead>';
var body = rows.length
? '<tbody>' + rows.map(function(row) {
return '<tr>' + header.map(function(_, cellIndex) { return cellHtml('td', row[cellIndex] || ''); }).join('') + '</tr>';
}).join('') + '</tbody>'
: '';
return {
html: '<div class="wolai-page-ai-markdown-table-wrap"><table>' + head + body + '</table></div>',
nextIndex: index
};
}
function isMnoteCitationHref(href) {
try {
var url = new URL(href, window.location.origin);
if (!isPageAiSameMnoteOrigin(url)) return false;
return url.pathname.startsWith('/documents/') || url.pathname === '/api/local-folder/files/open';
} catch (_error) {
return false;
}
}
function isPageAiSameMnoteOrigin(url) {
try {
var current = new URL(window.location.origin);
if (url.origin === current.origin) return true;
if (url.hostname === 'mnote.local') return true;
var localNames = ['localhost', '127.0.0.1', '::1', 'mnote.local'];
return localNames.indexOf(url.hostname) >= 0 &&
localNames.indexOf(current.hostname) >= 0 &&
String(url.port || defaultPortForProtocol(url.protocol)) === String(current.port || defaultPortForProtocol(current.protocol));
} catch (_error) {
return false;
}
}
function defaultPortForProtocol(protocol) {
return protocol === 'https:' ? '443' : protocol === 'http:' ? '80' : '';
}
function renderPageAiMarkdown(content) {
var lines = String(content || '').replace(/\r\n?/g, '\n').split('\n');
var blocks = [];
@@ -88,6 +301,17 @@ export function createSidebarPageAiMarkdownRuntime(context) {
blocks.push('<pre><code>' + escapeHtml(codeLines.join('\n')) + '</code></pre>');
continue;
}
if (/^\s*-{3,}\s*$/.test(line)) {
blocks.push('<hr />');
index += 1;
continue;
}
var table = renderPageAiMarkdownTable(lines, index);
if (table) {
blocks.push(table.html);
index = table.nextIndex;
continue;
}
var heading = line.match(/^(#{1,6})\s+(.+)$/);
if (heading) {
var level = Math.min(6, heading[1].length);