Improve LightRAG knowledge search locator alignment
This commit is contained in:
@@ -270,12 +270,27 @@ import {
|
||||
const resourceHrefFromUrlState = (rootUri, path, title) => {
|
||||
const fileUrl = localFileOpenUrl(rootUri, path);
|
||||
if (!fileUrl) return '';
|
||||
if (/\.pdf$/i.test(String(title || path || ''))) {
|
||||
const name = String(title || path || '');
|
||||
if (/\.pdf$/i.test(name)) {
|
||||
const url = new URL('/pdf-preview', window.location.origin);
|
||||
url.searchParams.set('fileUrl', fileUrl);
|
||||
url.searchParams.set('fileName', title || path);
|
||||
return url.toString();
|
||||
}
|
||||
const officeMatch = name.match(/\.([a-z0-9]+)$/i);
|
||||
const officeType = officeMatch ? officeMatch[1].toLowerCase() : '';
|
||||
if (['doc', 'docx', 'odt', 'rtf', 'xls', 'xlsx', 'ods', 'csv', 'ppt', 'pptx', 'odp'].indexOf(officeType) >= 0) {
|
||||
const url = new URL('/office-preview', window.location.origin);
|
||||
url.searchParams.set('fileUrl', fileUrl);
|
||||
url.searchParams.set('fileName', title || path);
|
||||
url.searchParams.set('fileType', officeType);
|
||||
const workspaceId = currentWebShellWorkspaceId();
|
||||
const sourceKind = currentWebShellSourceKind() || 'local_folder';
|
||||
if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
|
||||
if (sourceKind) url.searchParams.set('sourceKind', sourceKind);
|
||||
if (rootUri) url.searchParams.set('rootUri', rootUri);
|
||||
return url.toString();
|
||||
}
|
||||
return fileUrl;
|
||||
};
|
||||
const applyDocumentEvidenceLocatorFromUrl = (root) => {
|
||||
@@ -337,6 +352,7 @@ import {
|
||||
bbox: url.searchParams.get('bbox') || undefined,
|
||||
sourceMapPath: String(url.searchParams.get('sourceMapPath') || '').trim(),
|
||||
blockId: String(url.searchParams.get('blockId') || '').trim(),
|
||||
evidenceText: String(url.searchParams.get('evidenceText') || '').trim(),
|
||||
lineRange: url.searchParams.get('lineRange') || null,
|
||||
charRange: url.searchParams.get('charRange') || null,
|
||||
};
|
||||
|
||||
@@ -984,9 +984,11 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
const bbox = normalizeEvidenceBBox(input.bbox ?? locator?.bbox ?? params.bbox);
|
||||
const sourceMapPath = String(input.sourceMapPath || locator?.sourceMapPath || params.sourceMapPath || '').trim();
|
||||
const blockId = String(input.blockId || locator?.blockId || params.blockId || '').trim();
|
||||
const evidenceText = String(input.evidenceText || input.query || locator?.evidenceText || locator?.query || params.evidenceText || params.query || '').trim();
|
||||
const searchQuery = String(input.searchQuery || locator?.searchQuery || params.searchQuery || '').trim();
|
||||
const lineRange = input.lineRange || locator?.lineRange || params.lineRange || null;
|
||||
const charRange = input.charRange || locator?.charRange || params.charRange || null;
|
||||
if (!locator && !Number.isFinite(page) && !bbox && !sourceMapPath && !blockId && !lineRange && !charRange) return null;
|
||||
if (!locator && !Number.isFinite(page) && !bbox && !sourceMapPath && !blockId && !evidenceText && !lineRange && !charRange) return null;
|
||||
return {
|
||||
schema: 'mnote.evidence_locator.v1',
|
||||
...(locator || {}),
|
||||
@@ -994,6 +996,8 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
bbox,
|
||||
sourceMapPath,
|
||||
blockId,
|
||||
evidenceText,
|
||||
searchQuery,
|
||||
lineRange,
|
||||
charRange,
|
||||
};
|
||||
@@ -1013,13 +1017,29 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
if (bbox) url.searchParams.set('bbox', bbox);
|
||||
if (locator.sourceMapPath) url.searchParams.set('sourceMapPath', String(locator.sourceMapPath));
|
||||
if (locator.blockId) url.searchParams.set('blockId', String(locator.blockId));
|
||||
if (locator.evidenceText) url.searchParams.set('evidenceText', String(locator.evidenceText));
|
||||
if (locator.searchQuery) url.searchParams.set('searchQuery', String(locator.searchQuery));
|
||||
if (url.pathname === '/office-preview' && frame.contentWindow) {
|
||||
const currentSrc = new URL(frame.getAttribute('src') || frame.src || '', window.location.origin);
|
||||
if (officePreviewBaseHref(currentSrc.toString()) !== officePreviewBaseHref(url.toString())) {
|
||||
frame.src = url.toString();
|
||||
return;
|
||||
}
|
||||
const frameDoc = frame.contentDocument || null;
|
||||
const previewReady = frameDoc?.documentElement?.getAttribute('data-mnote-office-preview-status') === '完成'
|
||||
|| (frameDoc?.readyState === 'complete' && Boolean(frameDoc?.body?.dataset?.fileUrl));
|
||||
if (!previewReady) {
|
||||
frame.src = url.toString();
|
||||
return;
|
||||
}
|
||||
frame.contentWindow.postMessage({
|
||||
type: 'mnote:office-evidence-locator',
|
||||
page: locator.page,
|
||||
bbox,
|
||||
sourceMapPath: locator.sourceMapPath || '',
|
||||
blockId: locator.blockId || '',
|
||||
evidenceText: locator.evidenceText || '',
|
||||
searchQuery: locator.searchQuery || '',
|
||||
}, window.location.origin);
|
||||
return;
|
||||
}
|
||||
@@ -1095,6 +1115,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
entry.panel.setAttribute('data-mnote-evidence-open', 'true');
|
||||
if (Number.isFinite(Number(locator.page))) entry.panel.setAttribute('data-mnote-evidence-page', String(Number(locator.page)));
|
||||
if (locator.blockId) entry.panel.setAttribute('data-mnote-evidence-block-id', String(locator.blockId));
|
||||
if (locator.evidenceText) entry.panel.setAttribute('data-mnote-evidence-text', String(locator.evidenceText).slice(0, 240));
|
||||
if (locator.sourceMapPath) entry.panel.setAttribute('data-mnote-evidence-source-map-path', String(locator.sourceMapPath));
|
||||
const bbox = evidenceBBoxParam(locator.bbox);
|
||||
if (bbox) entry.panel.setAttribute('data-mnote-evidence-bbox', bbox);
|
||||
@@ -2286,7 +2307,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
|
||||
if (!raw) return '';
|
||||
try {
|
||||
const url = new URL(raw, window.location.origin);
|
||||
['page', 'bbox', 'sourceMapPath', 'blockId', 'lineRange', 'charRange', 'mnoteResourceReload'].forEach((key) => {
|
||||
['page', 'bbox', 'sourceMapPath', 'blockId', 'lineRange', 'charRange', 'evidenceText', 'query', 'searchQuery', 'mnoteResourceReload'].forEach((key) => {
|
||||
url.searchParams.delete(key);
|
||||
});
|
||||
return url.pathname + '?' + url.searchParams.toString();
|
||||
|
||||
@@ -240,10 +240,10 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
var relativePath = String(input.path || workspacePath && (workspacePath.relativePath || workspacePath.localRelativePath) || '').trim();
|
||||
var rootUri = String(input.rootUri || workspacePath && workspacePath.rootUri || currentRootUri() || '').trim();
|
||||
var fallback = String(input.fallback || '').trim();
|
||||
if (relativePath && rootUri) return 'resource:file:' + rootUri + ':' + relativePath;
|
||||
if (objectKind === 'mindmap' && assetId) return 'resource:mindmap:' + documentId + ':' + assetId;
|
||||
if ((objectKind === 'only_office' || objectKind === 'office') && assetId) return 'resource:onlyoffice:' + documentId + ':' + assetId;
|
||||
if (objectKind === 'pdf' && assetId) return 'resource:pdf:' + documentId + ':' + assetId;
|
||||
if (relativePath && rootUri) return 'resource:file:' + rootUri + ':' + relativePath;
|
||||
if (assetId && documentId) return 'resource:' + (objectKind || 'attachment') + ':' + documentId + ':' + assetId;
|
||||
return fallback || assetId || '';
|
||||
}
|
||||
@@ -282,26 +282,32 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
if (!relativePath || !rootUri) return false;
|
||||
var title = String(input && input.title || '').trim() || relativePath.split('/').pop() || relativePath;
|
||||
var kind = String(input && input.kind || fileTreeIconKindForFileName(title) || '').trim();
|
||||
var documentId = String(input && input.documentId || currentDocumentId() || '').trim();
|
||||
var assetId = String(input && input.assetId || '').trim() || 'local-file:' + relativePath;
|
||||
var officeUrl = String(input && input.officeUrl || '').trim();
|
||||
if (!officeUrl && kind === 'office') {
|
||||
officeUrl = buildLocalOnlyOfficeOpenUrl(relativePath, title, documentId, assetId, 'view');
|
||||
}
|
||||
var workspacePath = input && input.workspacePath && typeof input.workspacePath === 'object' ? input.workspacePath : null;
|
||||
var objectIdentity = resourceObjectIdentityFromWorkspacePath({
|
||||
workspacePath: workspacePath,
|
||||
objectKind: kind,
|
||||
documentId: input && input.documentId,
|
||||
assetId: input && input.assetId,
|
||||
documentId: documentId,
|
||||
assetId: assetId,
|
||||
path: relativePath,
|
||||
rootUri: rootUri
|
||||
});
|
||||
return await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: objectIdentity,
|
||||
assetId: String(input && input.assetId || '').trim() || 'local-file:' + relativePath,
|
||||
assetId: assetId,
|
||||
title: title,
|
||||
fileName: title,
|
||||
kind: kind,
|
||||
rootUri: rootUri,
|
||||
path: relativePath,
|
||||
href: String(input && input.href || buildLocalFileOpenUrl(relativePath, false) || '').trim(),
|
||||
officeUrl: String(input && input.officeUrl || '').trim(),
|
||||
documentId: String(input && input.documentId || currentDocumentId() || '').trim(),
|
||||
officeUrl: officeUrl,
|
||||
documentId: documentId,
|
||||
workspaceId: String(input && input.workspaceId || resolveWorkspaceId(document.body) || '').trim(),
|
||||
workspacePath: workspacePath,
|
||||
paneRole: String(input && input.paneRole || 'primary').trim() || 'primary',
|
||||
@@ -310,6 +316,7 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
bbox: input && input.bbox,
|
||||
sourceMapPath: String(input && input.sourceMapPath || '').trim(),
|
||||
blockId: String(input && input.blockId || '').trim(),
|
||||
evidenceText: String(input && input.evidenceText || '').trim(),
|
||||
lineRange: input && input.lineRange,
|
||||
charRange: input && input.charRange
|
||||
});
|
||||
|
||||
@@ -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(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'");
|
||||
while (href.includes('&')) href = href.replace(/&/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);
|
||||
|
||||
@@ -758,6 +758,7 @@ export function createSidebarPageAiRenderRuntime(context) {
|
||||
drawer.setAttribute('data-mnote-surface', 'page-ai');
|
||||
drawer.hidden = true;
|
||||
drawer.innerHTML = '' +
|
||||
'<div class="wolai-page-ai-resize-handle" data-page-ai-resize-handle title="拖动调整页面 AI 宽度" aria-hidden="true"></div>' +
|
||||
'<div class="wolai-page-ai-panel" role="dialog" aria-modal="false">' +
|
||||
'<div class="wolai-page-ai-header">' +
|
||||
'<div class="wolai-page-ai-header-copy">' +
|
||||
|
||||
@@ -50,6 +50,10 @@ export function createSidebarPageAiRuntime(context) {
|
||||
{ id: 'changed_files', label: '最近修改' }
|
||||
];
|
||||
var pageAiDelegatesInstalled = false;
|
||||
var PAGE_AI_DRAWER_WIDTH_STORAGE_KEY = 'mnote.page_ai.drawer_width';
|
||||
var PAGE_AI_DRAWER_DEFAULT_WIDTH = 440;
|
||||
var PAGE_AI_DRAWER_MIN_WIDTH = 340;
|
||||
var PAGE_AI_DRAWER_MAX_WIDTH = 760;
|
||||
|
||||
function clonePageAiDefaultValue(value) {
|
||||
if (Array.isArray(value)) return value.slice();
|
||||
@@ -57,6 +61,70 @@ export function createSidebarPageAiRuntime(context) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function pageAiClampDrawerWidth(width) {
|
||||
var viewportMax = Math.max(PAGE_AI_DRAWER_MIN_WIDTH, Math.min(PAGE_AI_DRAWER_MAX_WIDTH, window.innerWidth - 24));
|
||||
var numeric = Number(width);
|
||||
if (!Number.isFinite(numeric) || numeric <= 0) numeric = PAGE_AI_DRAWER_DEFAULT_WIDTH;
|
||||
return Math.max(PAGE_AI_DRAWER_MIN_WIDTH, Math.min(viewportMax, Math.round(numeric)));
|
||||
}
|
||||
|
||||
function pageAiStoredDrawerWidth() {
|
||||
try {
|
||||
return pageAiClampDrawerWidth(window.localStorage ? window.localStorage.getItem(PAGE_AI_DRAWER_WIDTH_STORAGE_KEY) : PAGE_AI_DRAWER_DEFAULT_WIDTH);
|
||||
} catch (_error) {
|
||||
return pageAiClampDrawerWidth(PAGE_AI_DRAWER_DEFAULT_WIDTH);
|
||||
}
|
||||
}
|
||||
|
||||
function pageAiApplyDrawerWidth(drawer, width) {
|
||||
var target = drawer instanceof HTMLElement ? drawer : document.querySelector('[data-testid="wolai-page-ai-drawer"]');
|
||||
if (!(target instanceof HTMLElement)) return;
|
||||
var next = pageAiClampDrawerWidth(width == null ? pageAiStoredDrawerWidth() : width);
|
||||
target.style.setProperty('--mnote-page-ai-width', next + 'px');
|
||||
}
|
||||
|
||||
function handlePageAiPointerDown(event, helpers) {
|
||||
var closestAction = helpers && helpers.closestAction;
|
||||
if (typeof closestAction !== 'function') return false;
|
||||
var handle = closestAction(event.target, '[data-page-ai-resize-handle]');
|
||||
if (!(handle instanceof HTMLElement)) return false;
|
||||
var drawer = handle.closest('[data-testid="wolai-page-ai-drawer"]');
|
||||
var panel = drawer instanceof HTMLElement ? drawer.querySelector('.wolai-page-ai-panel') : null;
|
||||
if (!(drawer instanceof HTMLElement) || !(panel instanceof HTMLElement)) return false;
|
||||
event.preventDefault();
|
||||
var pointerId = event.pointerId;
|
||||
var startX = Number(event.clientX || 0);
|
||||
var startWidth = panel.getBoundingClientRect().width || pageAiStoredDrawerWidth();
|
||||
drawer.setAttribute('data-page-ai-resizing', 'true');
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-resizing', 'true');
|
||||
try {
|
||||
handle.setPointerCapture(pointerId);
|
||||
} catch (_error) {}
|
||||
function move(nextEvent) {
|
||||
var nextWidth = startWidth + (startX - Number(nextEvent.clientX || 0));
|
||||
pageAiApplyDrawerWidth(drawer, nextWidth);
|
||||
}
|
||||
function finish(nextEvent) {
|
||||
move(nextEvent);
|
||||
var value = drawer.style.getPropertyValue('--mnote-page-ai-width').replace('px', '').trim();
|
||||
try {
|
||||
if (window.localStorage) window.localStorage.setItem(PAGE_AI_DRAWER_WIDTH_STORAGE_KEY, String(pageAiClampDrawerWidth(value)));
|
||||
} catch (_error) {}
|
||||
drawer.removeAttribute('data-page-ai-resizing');
|
||||
document.documentElement.removeAttribute('data-mnote-page-ai-resizing');
|
||||
window.removeEventListener('pointermove', move, true);
|
||||
window.removeEventListener('pointerup', finish, true);
|
||||
window.removeEventListener('pointercancel', finish, true);
|
||||
try {
|
||||
handle.releasePointerCapture(pointerId);
|
||||
} catch (_error) {}
|
||||
}
|
||||
window.addEventListener('pointermove', move, true);
|
||||
window.addEventListener('pointerup', finish, true);
|
||||
window.addEventListener('pointercancel', finish, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
function ensurePageAiStateFacade(state) {
|
||||
var defaults = {
|
||||
pageAiOpen: false,
|
||||
@@ -743,9 +811,12 @@ export function createSidebarPageAiRuntime(context) {
|
||||
var prefix = '/documents/';
|
||||
if (!url.pathname.startsWith(prefix)) return '';
|
||||
try {
|
||||
return decodeURIComponent(url.pathname.slice(prefix.length).split('/')[0] || '');
|
||||
var raw = url.pathname.slice(prefix.length).split('/')[0] || '';
|
||||
var decoded = decodeURIComponent(raw);
|
||||
if (decoded.indexOf('%') >= 0 || decoded.indexOf('\uFFFD') >= 0) return '';
|
||||
return decoded;
|
||||
} catch (_error) {
|
||||
return url.pathname.slice(prefix.length).split('/')[0] || '';
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,6 +824,14 @@ export function createSidebarPageAiRuntime(context) {
|
||||
var explicitPath = String(url.searchParams.get('resourcePath') || '').trim();
|
||||
if (explicitPath) return explicitPath;
|
||||
var raw = String(url.searchParams.get('resourceTab') || '').trim();
|
||||
if (!raw) {
|
||||
try {
|
||||
var decodedHash = decodeURIComponent(String(url.hash || ''));
|
||||
var marker = '#resource-tab-';
|
||||
var markerIndex = decodedHash.indexOf(marker);
|
||||
if (markerIndex >= 0) raw = decodedHash.slice(markerIndex + marker.length).replace(/^#+/, '').trim();
|
||||
} catch (_error) {}
|
||||
}
|
||||
if (raw.indexOf('::') >= 0) raw = raw.slice(raw.indexOf('::') + 2);
|
||||
if (!raw.startsWith('resource:file:')) return '';
|
||||
var rest = raw.slice('resource:file:'.length);
|
||||
@@ -761,6 +840,40 @@ export function createSidebarPageAiRuntime(context) {
|
||||
return rest.slice(prefix.length).replace(/^\/+/, '');
|
||||
}
|
||||
|
||||
function pageAiSameMnoteOrigin(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'];
|
||||
function defaultPort(protocol) {
|
||||
return protocol === 'https:' ? '443' : protocol === 'http:' ? '80' : '';
|
||||
}
|
||||
return localNames.indexOf(url.hostname) >= 0 &&
|
||||
localNames.indexOf(current.hostname) >= 0 &&
|
||||
String(url.port || defaultPort(url.protocol)) === String(current.port || defaultPort(current.protocol));
|
||||
} catch (_error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function pageAiPortableCitationUrl(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 pageAiOpenCitationUrl(href) {
|
||||
var url;
|
||||
try {
|
||||
@@ -768,7 +881,34 @@ export function createSidebarPageAiRuntime(context) {
|
||||
} catch (_error) {
|
||||
return false;
|
||||
}
|
||||
if (url.origin !== window.location.origin || !url.pathname.startsWith('/documents/')) return false;
|
||||
var unwrappedHref = pageAiUnwrapSearchCitationUrl(url);
|
||||
if (unwrappedHref) return pageAiOpenCitationUrl(unwrappedHref);
|
||||
if (!pageAiSameMnoteOrigin(url) && !pageAiPortableCitationUrl(url)) return false;
|
||||
if (url.pathname === '/api/local-folder/files/open') {
|
||||
var fileRootUri = String(url.searchParams.get('rootUri') || currentRootUri() || '').trim();
|
||||
var filePath = String(url.searchParams.get('path') || '').trim();
|
||||
if (!filePath) return false;
|
||||
void openLocalResourceInActiveTab({
|
||||
path: filePath,
|
||||
rootUri: fileRootUri,
|
||||
documentId: currentDocumentId() || '',
|
||||
workspaceId: resolveWorkspaceId(document.body) || '',
|
||||
sourceKind: currentSourceKind() || 'local_folder',
|
||||
page: url.searchParams.get('page') || undefined,
|
||||
bbox: pageAiEvidenceBboxFromParam(url.searchParams.get('bbox')),
|
||||
sourceMapPath: String(url.searchParams.get('sourceMapPath') || '').trim(),
|
||||
blockId: String(url.searchParams.get('blockId') || '').trim(),
|
||||
evidenceText: String(url.searchParams.get('evidenceText') || '').trim(),
|
||||
lineRange: pageAiEvidenceRangeFromParam(url.searchParams.get('lineRange')),
|
||||
charRange: pageAiEvidenceRangeFromParam(url.searchParams.get('charRange')),
|
||||
openTarget: 'active-tab',
|
||||
paneRole: 'primary'
|
||||
}).then(function(opened) {
|
||||
if (!opened) window.open(url.toString(), '_blank', 'noopener,noreferrer');
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (!url.pathname.startsWith('/documents/')) return false;
|
||||
var rootUri = String(url.searchParams.get('rootUri') || currentRootUri() || '').trim();
|
||||
var resourcePath = pageAiCitationResourcePath(url, rootUri);
|
||||
var documentId = pageAiDocumentIdFromUrl(url) || currentDocumentId() || '';
|
||||
@@ -784,6 +924,7 @@ export function createSidebarPageAiRuntime(context) {
|
||||
bbox: pageAiEvidenceBboxFromParam(url.searchParams.get('bbox')),
|
||||
sourceMapPath: String(url.searchParams.get('sourceMapPath') || '').trim(),
|
||||
blockId: String(url.searchParams.get('blockId') || '').trim(),
|
||||
evidenceText: String(url.searchParams.get('evidenceText') || '').trim(),
|
||||
lineRange: pageAiEvidenceRangeFromParam(url.searchParams.get('lineRange')),
|
||||
charRange: pageAiEvidenceRangeFromParam(url.searchParams.get('charRange')),
|
||||
openTarget: 'active-tab',
|
||||
@@ -1035,6 +1176,30 @@ export function createSidebarPageAiRuntime(context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function pageAiUnwrapSearchCitationUrl(url) {
|
||||
try {
|
||||
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 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;
|
||||
} catch (_error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function pageAiPreviewValue(value) {
|
||||
if (value == null || value === '') return '';
|
||||
if (typeof value === 'string') return value.length > 120 ? value.slice(0, 120) + '…' : value;
|
||||
@@ -1259,9 +1424,12 @@ export function createSidebarPageAiRuntime(context) {
|
||||
if (argsSummary) existing.argsSummary = argsSummary;
|
||||
if (resultSummary) existing.resultSummary = resultSummary;
|
||||
if (locations.length) existing.locations = locations;
|
||||
existing.rawOutput = toolEvent && toolEvent.output;
|
||||
existing.rawResult = resultSource;
|
||||
if (status === 'completed') {
|
||||
pageAiNotifyToolWriteCompleted(toolName, toolEvent, runId, runTraceId);
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
async function pageAiCancelQueuedRun(queueId) {
|
||||
@@ -1735,6 +1903,7 @@ export function createSidebarPageAiRuntime(context) {
|
||||
renderPageAiProviderButtons();
|
||||
renderPageAiControls();
|
||||
var drawer = ensurePageAiDrawer();
|
||||
pageAiApplyDrawerWidth(drawer);
|
||||
drawer.hidden = false;
|
||||
pageUiState.pageAiOpen = true;
|
||||
updatePageAiTriggerState();
|
||||
@@ -1840,13 +2009,113 @@ export function createSidebarPageAiRuntime(context) {
|
||||
return message.content;
|
||||
}
|
||||
|
||||
function pageAiFinishStreamingAssistantMessage(runId, finalText, promptText) {
|
||||
function pageAiAppendCitationSection(content, citations) {
|
||||
var text = String(content || '');
|
||||
var unique = [];
|
||||
pageAiNormalizeArray(citations).forEach(function(value) {
|
||||
var citation = String(value || '').trim();
|
||||
if (!citation || unique.indexOf(citation) >= 0) return;
|
||||
unique.push(citation);
|
||||
});
|
||||
var missing = unique.filter(function(citation) {
|
||||
return text.indexOf(citation) < 0;
|
||||
});
|
||||
if (!missing.length) return text;
|
||||
return text.replace(/\s+$/g, '') + '\n\n**引用**\n' + missing.map(function(citation) {
|
||||
return '- ' + citation;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
function pageAiPromptNeedsKnowledgeRagCitations(prompt) {
|
||||
var text = searchText(prompt);
|
||||
if (!text) return false;
|
||||
var asksCitation = ['链接', '引用', '来源', '出处', '证据', '定位', 'link', 'citation', 'source'].some(function(word) {
|
||||
return text.indexOf(word) >= 0;
|
||||
});
|
||||
if (!asksCitation) return false;
|
||||
return ['lightrag', '资料库', '知识库', 'rag', '文献', '论文', '保护基'].some(function(word) {
|
||||
return text.indexOf(word) >= 0;
|
||||
});
|
||||
}
|
||||
|
||||
function pageAiCleanDegradedCitationNotes(content) {
|
||||
return String(content || '')
|
||||
.split('\n')
|
||||
.filter(function(line) {
|
||||
var text = String(line || '');
|
||||
return text.indexOf('来源定位降级') < 0 && text.toLowerCase().indexOf('locator degraded') < 0;
|
||||
})
|
||||
.join('\n')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function pageAiCollectKnowledgeRagApiCitations(payload) {
|
||||
var references = pageAiNormalizeArray(payload && payload.references);
|
||||
var citations = references.map(function(reference) {
|
||||
return String(reference && reference.citationMarkdown || '').trim();
|
||||
}).filter(Boolean);
|
||||
var hasPreciseCitation = references.some(function(reference) {
|
||||
return String(reference && reference.citationMarkdown || '').trim() && reference && reference.locatorDegraded !== true;
|
||||
});
|
||||
var seen = {};
|
||||
return citations.filter(function(citation) {
|
||||
if (seen[citation]) return false;
|
||||
if (hasPreciseCitation && citation.indexOf('来源定位降级') >= 0) return false;
|
||||
seen[citation] = true;
|
||||
return true;
|
||||
}).slice(0, 5);
|
||||
}
|
||||
|
||||
async function pageAiAppendKnowledgeRagFallbackCitations(runId, promptText) {
|
||||
if (!pageAiPromptNeedsKnowledgeRagCitations(promptText)) return;
|
||||
var rootUri = currentRootUri();
|
||||
if (!rootUri) return;
|
||||
try {
|
||||
var response = await fetch('/api/knowledge-rag/query', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', 'accept': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
workspaceId: resolveWorkspaceId(document.body) || undefined,
|
||||
rootUri: rootUri,
|
||||
query: String(promptText || '').trim(),
|
||||
mode: 'mix',
|
||||
topK: 8,
|
||||
chunkTopK: 8,
|
||||
includeChunkContent: true
|
||||
})
|
||||
});
|
||||
var payload = await response.json().catch(function() { return null; });
|
||||
if (!response.ok) return;
|
||||
var citations = pageAiCollectKnowledgeRagApiCitations(payload);
|
||||
if (!citations.length) return;
|
||||
var message = pageUiState.pageAiMessages.find(function(item) {
|
||||
return item.role === 'assistant' && item.runId === runId;
|
||||
}) || pageUiState.pageAiMessages.filter(function(item) { return item.role === 'assistant'; }).slice(-1)[0];
|
||||
if (!message) return;
|
||||
var hasPrecise = citations.some(function(citation) { return citation.indexOf('来源定位降级') < 0; });
|
||||
var baseContent = hasPrecise ? pageAiCleanDegradedCitationNotes(message.content) : String(message.content || '');
|
||||
message.content = pageAiAppendCitationSection(baseContent, citations);
|
||||
pageAiSyncCurrentSessionMessages();
|
||||
pageAiPersistSessions();
|
||||
renderPageAiConversation();
|
||||
} catch (error) {
|
||||
console.warn('MNote Page AI 自动追加 LightRAG 引用失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
function pageAiFinishStreamingAssistantMessage(runId, finalText, promptText, citations) {
|
||||
var id = String(runId || pageUiState.pageAiCurrentRunId || '');
|
||||
var message = pageUiState.pageAiMessages.find(function(item) {
|
||||
return item.role === 'assistant' && item.streaming === true && item.runId === id;
|
||||
});
|
||||
var text = String(finalText || (message && message.content) || '');
|
||||
var content = humanizePageAiResponse(text, promptText);
|
||||
var hasPreciseCitation = pageAiNormalizeArray(citations).some(function(citation) {
|
||||
return String(citation || '').indexOf('来源定位降级') < 0;
|
||||
});
|
||||
var contentBase = humanizePageAiResponse(text, promptText);
|
||||
if (hasPreciseCitation) contentBase = pageAiCleanDegradedCitationNotes(contentBase);
|
||||
var content = pageAiAppendCitationSection(contentBase, citations);
|
||||
if (message) {
|
||||
message.content = content;
|
||||
message.streaming = false;
|
||||
@@ -1859,6 +2128,123 @@ export function createSidebarPageAiRuntime(context) {
|
||||
}
|
||||
pageAiSyncCurrentSessionMessages();
|
||||
pageAiPersistSessions();
|
||||
void pageAiAppendKnowledgeRagFallbackCitations(id, promptText);
|
||||
}
|
||||
|
||||
function pageAiToolOutputText(value) {
|
||||
var parts = [];
|
||||
function visit(node) {
|
||||
if (node == null) return;
|
||||
if (typeof node === 'string') {
|
||||
parts.push(node);
|
||||
return;
|
||||
}
|
||||
if (typeof node === 'number' || typeof node === 'boolean') return;
|
||||
if (Array.isArray(node)) {
|
||||
node.forEach(visit);
|
||||
return;
|
||||
}
|
||||
if (typeof node !== 'object') return;
|
||||
if (typeof node.text === 'string') parts.push(node.text);
|
||||
if (typeof node.content === 'string') parts.push(node.content);
|
||||
if (node.content && typeof node.content === 'object') visit(node.content);
|
||||
if (node.output && typeof node.output === 'object') visit(node.output);
|
||||
if (node.result && typeof node.result === 'object') visit(node.result);
|
||||
}
|
||||
visit(value);
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
function pageAiParseJsonMaybe(value) {
|
||||
if (value && typeof value === 'object') return value;
|
||||
var text = String(value || '').trim();
|
||||
if (!text || (text[0] !== '{' && text[0] !== '[')) return null;
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (_error) {
|
||||
var firstLine = text.split('\n')[0].trim();
|
||||
if (firstLine && firstLine !== text && (firstLine[0] === '{' || firstLine[0] === '[')) {
|
||||
try {
|
||||
return JSON.parse(firstLine);
|
||||
} catch (_lineError) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function pageAiCollectCitationMarkdowns(value) {
|
||||
var citations = [];
|
||||
var seen = {};
|
||||
function add(value) {
|
||||
var citation = String(value || '').trim();
|
||||
if (!citation || seen[citation]) return;
|
||||
seen[citation] = true;
|
||||
citations.push(citation);
|
||||
}
|
||||
function visit(node) {
|
||||
if (node == null) return;
|
||||
if (typeof node === 'string') {
|
||||
var parsed = pageAiParseJsonMaybe(node);
|
||||
if (parsed) visit(parsed);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(node)) {
|
||||
node.forEach(visit);
|
||||
return;
|
||||
}
|
||||
if (typeof node !== 'object') return;
|
||||
if (typeof node.citationMarkdown === 'string') add(node.citationMarkdown);
|
||||
if (Array.isArray(node.citationMarkdowns)) {
|
||||
node.citationMarkdowns.forEach(function(value) {
|
||||
if (typeof value === 'string') add(value);
|
||||
else visit(value);
|
||||
});
|
||||
}
|
||||
Object.keys(node).forEach(function(key) {
|
||||
visit(node[key]);
|
||||
});
|
||||
}
|
||||
visit(value);
|
||||
return citations;
|
||||
}
|
||||
|
||||
function pageAiCollectKnowledgeRagCitations(toolItem, toolEvent) {
|
||||
var toolName = String((toolItem && toolItem.toolName) || (toolEvent && (toolEvent.name || toolEvent.tool || toolEvent.toolName)) || '');
|
||||
var sources = [
|
||||
toolEvent,
|
||||
toolItem,
|
||||
toolEvent && toolEvent.output,
|
||||
toolEvent && toolEvent.result,
|
||||
toolEvent && toolEvent.summary,
|
||||
toolItem && toolItem.rawOutput,
|
||||
toolItem && toolItem.rawResult,
|
||||
toolItem && toolItem.resultSummary
|
||||
];
|
||||
var citations = [];
|
||||
var outputText = '';
|
||||
sources.forEach(function(source) {
|
||||
citations = citations.concat(pageAiCollectCitationMarkdowns(source));
|
||||
var sourceText = pageAiToolOutputText(source);
|
||||
if (sourceText) outputText += '\n' + sourceText;
|
||||
var parsed = pageAiParseJsonMaybe(sourceText);
|
||||
if (parsed) citations = citations.concat(pageAiCollectCitationMarkdowns(parsed));
|
||||
});
|
||||
var looksLikeKnowledgeRag = toolName.indexOf('knowledge_rag') >= 0 ||
|
||||
toolName.indexOf('knowledge.rag') >= 0 ||
|
||||
outputText.indexOf('mnote.knowledge_rag') >= 0 ||
|
||||
outputText.indexOf('uiCitations') >= 0;
|
||||
if (!looksLikeKnowledgeRag && citations.length === 0) return [];
|
||||
var hasPreciseCitation = citations.some(function(value) {
|
||||
return String(value || '').indexOf('来源定位降级') < 0;
|
||||
});
|
||||
var seen = {};
|
||||
return citations.filter(function(value) {
|
||||
var citation = String(value || '').trim();
|
||||
if (!citation || seen[citation]) return false;
|
||||
if (hasPreciseCitation && citation.indexOf('来源定位降级') >= 0) return false;
|
||||
seen[citation] = true;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function pageAiLooksLikeBlockEdit(prompt) {
|
||||
@@ -2078,6 +2464,7 @@ export function createSidebarPageAiRuntime(context) {
|
||||
throw new Error(pageAiErrorMessage(eventError, 'hermes_events_failed_' + eventResponse.status));
|
||||
}
|
||||
var assistantText = '';
|
||||
var autoCitations = [];
|
||||
await streamPageAiResponse(eventResponse, function(eventName, payloadText) {
|
||||
if (eventName === 'message.delta') {
|
||||
if (pageUiState.pageAiStoppedRunIds[runId]) return;
|
||||
@@ -2222,7 +2609,15 @@ export function createSidebarPageAiRuntime(context) {
|
||||
} catch (_) {}
|
||||
}
|
||||
if (eventName === 'tool.started' || eventName === 'tool.completed' || eventName === 'tool.failed') {
|
||||
pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId);
|
||||
var toolEventPayload = null;
|
||||
try {
|
||||
toolEventPayload = JSON.parse(payloadText || 'null');
|
||||
} catch (_) {}
|
||||
var toolItem = pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId);
|
||||
if (eventName === 'tool.completed') {
|
||||
var completedCitations = pageAiCollectKnowledgeRagCitations(toolItem, toolEventPayload);
|
||||
if (completedCitations.length) autoCitations = completedCitations;
|
||||
}
|
||||
pageAiSyncCurrentSessionMessages();
|
||||
pageAiPersistSessions();
|
||||
renderPageAiConversation();
|
||||
@@ -2231,7 +2626,7 @@ export function createSidebarPageAiRuntime(context) {
|
||||
}
|
||||
});
|
||||
if (!pageUiState.pageAiStoppedRunIds[runId]) {
|
||||
pageAiFinishStreamingAssistantMessage(runId, assistantText, prompt);
|
||||
pageAiFinishStreamingAssistantMessage(runId, assistantText, prompt, autoCitations);
|
||||
}
|
||||
currentSession = pageAiCurrentSession();
|
||||
if (currentSession) {
|
||||
@@ -2596,6 +2991,9 @@ export function createSidebarPageAiRuntime(context) {
|
||||
document.addEventListener('click', function(event) {
|
||||
handlePageAiClick(event, helpers);
|
||||
});
|
||||
document.addEventListener('pointerdown', function(event) {
|
||||
handlePageAiPointerDown(event, helpers);
|
||||
});
|
||||
document.addEventListener('keydown', function(event) {
|
||||
handlePageAiKeyDown(event, helpers);
|
||||
});
|
||||
|
||||
@@ -1103,6 +1103,32 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
});
|
||||
}
|
||||
|
||||
function knowledgeRagPipelineProgress(status) {
|
||||
var pipeline = status && status.pipeline && typeof status.pipeline === 'object' ? status.pipeline : {};
|
||||
var progress = pipeline.progress && typeof pipeline.progress === 'object' ? pipeline.progress : null;
|
||||
if (!progress) return null;
|
||||
var current = Number(progress.current || 0);
|
||||
var total = Number(progress.total || 0);
|
||||
if (!Number.isFinite(current) || !Number.isFinite(total) || current <= 0 || total <= 0) return null;
|
||||
return {
|
||||
current: Math.min(current, total),
|
||||
total: total,
|
||||
docId: String(progress.docId || ''),
|
||||
latestMessage: String(pipeline.latestMessage || '')
|
||||
};
|
||||
}
|
||||
|
||||
function knowledgeRagSourceProgressHtml(progress) {
|
||||
if (!progress) return '';
|
||||
var percent = Math.max(0, Math.min(100, Math.round((progress.current / progress.total) * 100)));
|
||||
var label = progress.current + ' / ' + progress.total + ' · ' + percent + '%';
|
||||
return '' +
|
||||
'<div class="mnote-knowledge-rag-source-progress" role="progressbar" aria-valuemin="0" aria-valuemax="' + String(progress.total) + '" aria-valuenow="' + String(progress.current) + '" aria-label="' + escapeHtml('索引进度 ' + label) + '">' +
|
||||
'<div><i style="width:' + String(percent) + '%"></i></div>' +
|
||||
'<span>' + escapeHtml(label) + '</span>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function scheduleKnowledgeRagStatusBridge(reason) {
|
||||
if (!knowledgeRagIsAvailable()) return;
|
||||
if (knowledgeRagBridgeTimer) window.clearTimeout(knowledgeRagBridgeTimer);
|
||||
@@ -1207,18 +1233,21 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
sourcesNode.innerHTML = '<div class="wolai-page-settings-index-empty">当前分类没有资料来源</div>';
|
||||
return;
|
||||
}
|
||||
var pipelineProgress = knowledgeRagPipelineProgress(status || {});
|
||||
sourcesNode.innerHTML = items.map(function(row) {
|
||||
if (row.kind === 'provider') {
|
||||
var doc = row.doc || {};
|
||||
var providerPath = String(doc.filePath || doc.id || '');
|
||||
var docStatus = String(doc.status || doc.statusGroup || '');
|
||||
var failedDoc = row.category === 'unmapped_failed';
|
||||
var providerProgress = pipelineProgress && pipelineProgress.docId && pipelineProgress.docId === String(doc.id || '') ? pipelineProgress : null;
|
||||
return '' +
|
||||
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-source-kind="provider" data-knowledge-rag-source-path="' + escapeHtml(providerPath) + '">' +
|
||||
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + (failedDoc ? 'fault' : 'indexed') + '" title="' + escapeHtml(docStatus || 'LightRAG 文档') + '"></span>' +
|
||||
'<div class="mnote-knowledge-rag-source-main">' +
|
||||
'<strong title="' + escapeHtml(providerPath) + '">' + escapeHtml(providerPath || 'LightRAG 文档') + '</strong>' +
|
||||
'<span>' + escapeHtml('LightRAG 未映射 · ' + (docStatus || 'unknown') + (doc.id ? ' · ' + doc.id : '')) + '</span>' +
|
||||
knowledgeRagSourceProgressHtml(providerProgress) +
|
||||
'</div>' +
|
||||
'<div class="mnote-knowledge-rag-source-actions"><button type="button" disabled>只读</button></div>' +
|
||||
'</div>';
|
||||
@@ -1234,23 +1263,25 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
var indexed = entry && entry.indexedAtMs && docId && !deleted && !stale && !failed && !deleting && !deletedDone;
|
||||
var kind = deletedDone ? '' : (deleting ? 'indexing' : (failed || stale || deleted ? 'fault' : (indexed ? 'indexed' : 'indexing')));
|
||||
var title = String(entry && entry.sourceRootRelativePath || entry && entry.lightRagFilePath || '');
|
||||
var status = deletedDone ? 'LightRAG 已移除'
|
||||
var statusLabel = deletedDone ? 'LightRAG 已移除'
|
||||
: deleting ? '删除已提交'
|
||||
: deleted ? '已删除'
|
||||
: stale ? '已过期'
|
||||
: failed ? '索引失败'
|
||||
: indexed ? '已索引'
|
||||
: (docId || providerStatus ? '正在索引' : '待提交');
|
||||
var meta = docId ? status + ' · ' + docId : status;
|
||||
var meta = docId ? statusLabel + ' · ' + docId : statusLabel;
|
||||
var sourcePath = String(entry && entry.sourceRootRelativePath || '');
|
||||
var canReindex = Boolean(sourcePath && !deleting);
|
||||
var canDelete = Boolean(sourcePath && !deleted && !deletedDone && !deleting);
|
||||
var entryProgress = pipelineProgress && pipelineProgress.docId && pipelineProgress.docId === docId && !indexed && !failed && !stale && !deleted && !deletedDone ? pipelineProgress : null;
|
||||
return '' +
|
||||
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '">' +
|
||||
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + kind + '" title="' + escapeHtml(status) + '"></span>' +
|
||||
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + kind + '" title="' + escapeHtml(statusLabel) + '"></span>' +
|
||||
'<div class="mnote-knowledge-rag-source-main">' +
|
||||
'<strong title="' + escapeHtml(title) + '">' + escapeHtml(title || '未命名来源') + '</strong>' +
|
||||
'<span>' + escapeHtml(meta) + '</span>' +
|
||||
knowledgeRagSourceProgressHtml(entryProgress) +
|
||||
'</div>' +
|
||||
'<div class="mnote-knowledge-rag-source-actions">' +
|
||||
'<button type="button" data-knowledge-rag-action="reindex-source" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '"' + (canReindex ? '' : ' disabled') + '>重索引</button>' +
|
||||
|
||||
@@ -2139,7 +2139,9 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
'<span class="wolai-search-sort-control"><span>按创建时间</span><button type="button" class="wolai-search-sort-value" data-search-sort="created" aria-label="按创建时间范围">所有</button></span>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-search-options-right">' +
|
||||
'<span class="wolai-search-switch-control"><span>全盘资料库</span><button type="button" class="wolai-search-switch" data-search-switch="knowledge" role="switch" aria-checked="false" aria-label="全盘资料库检索"></button></span>' +
|
||||
'<span class="wolai-search-switch-control"><span>页面内搜索</span><button type="button" class="wolai-search-switch" data-search-switch="page" role="switch" aria-checked="false" aria-label="页面内搜索"></button></span>' +
|
||||
'<span class="wolai-search-switch-control"><span>折叠同来源</span><button type="button" class="wolai-search-switch is-on" data-search-switch="collapseSource" role="switch" aria-checked="true" aria-label="默认折叠相同来源结果"></button></span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-search-result-meta" data-testid="wolai-search-result-meta"></div>' +
|
||||
@@ -2149,12 +2151,22 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
document.body.appendChild(overlay);
|
||||
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
||||
var closeButton = overlay.querySelector('[data-testid="wolai-search-close"]');
|
||||
if (input) input.addEventListener('input', scheduleSearchResultsRender);
|
||||
applySearchSwitchState(overlay, { collapseSource: readSearchCollapseSourcesDefault() });
|
||||
if (input) input.addEventListener('input', function() {
|
||||
searchUiState.hasRendered = false;
|
||||
scheduleSearchResultsRender();
|
||||
});
|
||||
overlay.querySelectorAll('[data-search-switch]').forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
var switchName = searchText(button.getAttribute('data-search-switch'));
|
||||
var isOn = button.getAttribute('aria-checked') !== 'true';
|
||||
button.setAttribute('aria-checked', isOn ? 'true' : 'false');
|
||||
button.classList.toggle('is-on', isOn);
|
||||
if (switchName === 'collapseSource') {
|
||||
writeSearchCollapseSourcesDefault(isOn);
|
||||
searchUiState.sourceCollapsed = {};
|
||||
}
|
||||
searchUiState.hasRendered = false;
|
||||
scheduleSearchResultsRender();
|
||||
});
|
||||
});
|
||||
@@ -2167,11 +2179,27 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
|
||||
var activeSearchRequestId = 0;
|
||||
var searchRenderTimer = 0;
|
||||
var SEARCH_COLLAPSE_SOURCES_KEY = 'mnote.search.collapseSourcesDefault.v1';
|
||||
var searchUiState = {
|
||||
query: '',
|
||||
switches: {},
|
||||
metaHtml: '',
|
||||
resultsHtml: '',
|
||||
items: [],
|
||||
sourceCollapsed: {},
|
||||
scrollTop: 0,
|
||||
signature: '',
|
||||
hasRendered: false
|
||||
};
|
||||
|
||||
function searchText(value) {
|
||||
return String(value == null ? '' : value).replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function searchNonWhitespaceCharCount(value) {
|
||||
return String(value == null ? '' : value).replace(/\s+/g, '').length;
|
||||
}
|
||||
|
||||
function currentWorkspaceName() {
|
||||
var name = document.querySelector('.sidebar-workspace-name');
|
||||
return searchText(name && name.textContent) || '当前工作区';
|
||||
@@ -2208,6 +2236,92 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
return button instanceof HTMLElement && button.getAttribute('aria-checked') === 'true';
|
||||
}
|
||||
|
||||
function readSearchCollapseSourcesDefault() {
|
||||
try {
|
||||
var stored = window.localStorage && window.localStorage.getItem(SEARCH_COLLAPSE_SOURCES_KEY);
|
||||
if (stored === '0') return false;
|
||||
if (stored === '1') return true;
|
||||
} catch (_) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
function writeSearchCollapseSourcesDefault(value) {
|
||||
try {
|
||||
if (window.localStorage) window.localStorage.setItem(SEARCH_COLLAPSE_SOURCES_KEY, value ? '1' : '0');
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function collectSearchSwitchState(overlay) {
|
||||
var state = {};
|
||||
if (!(overlay instanceof HTMLElement)) return state;
|
||||
overlay.querySelectorAll('[data-search-switch]').forEach(function(button) {
|
||||
if (!(button instanceof HTMLElement)) return;
|
||||
var name = searchText(button.getAttribute('data-search-switch'));
|
||||
if (!name) return;
|
||||
state[name] = button.getAttribute('aria-checked') === 'true';
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
function applySearchSwitchState(overlay, switches) {
|
||||
if (!(overlay instanceof HTMLElement) || !switches || typeof switches !== 'object') return;
|
||||
overlay.querySelectorAll('[data-search-switch]').forEach(function(button) {
|
||||
if (!(button instanceof HTMLElement)) return;
|
||||
var name = searchText(button.getAttribute('data-search-switch'));
|
||||
if (!name || !Object.prototype.hasOwnProperty.call(switches, name)) return;
|
||||
var isOn = Boolean(switches[name]);
|
||||
button.setAttribute('aria-checked', isOn ? 'true' : 'false');
|
||||
button.classList.toggle('is-on', isOn);
|
||||
});
|
||||
}
|
||||
|
||||
function searchRequestSignature(overlay, query) {
|
||||
var switches = collectSearchSwitchState(overlay);
|
||||
return JSON.stringify({
|
||||
query: searchText(query),
|
||||
knowledge: Boolean(switches.knowledge),
|
||||
page: Boolean(switches.page),
|
||||
title: Boolean(switches.title),
|
||||
exact: Boolean(switches.exact),
|
||||
collapseSource: Boolean(switches.collapseSource),
|
||||
workspaceId: resolveWorkspaceId(document.body) || '',
|
||||
rootUri: currentRootUri() || ''
|
||||
});
|
||||
}
|
||||
|
||||
function saveSearchUiState(overlay) {
|
||||
if (!(overlay instanceof HTMLElement)) return;
|
||||
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
||||
var meta = overlay.querySelector('[data-testid="wolai-search-result-meta"]');
|
||||
var results = overlay.querySelector('[data-testid="wolai-search-results"]');
|
||||
var query = input && 'value' in input ? searchText(input.value) : searchUiState.query;
|
||||
searchUiState.query = query;
|
||||
searchUiState.switches = collectSearchSwitchState(overlay);
|
||||
searchUiState.metaHtml = meta ? meta.innerHTML : searchUiState.metaHtml;
|
||||
searchUiState.resultsHtml = results ? results.innerHTML : searchUiState.resultsHtml;
|
||||
searchUiState.items = Array.isArray(window.__mnoteSearchResults) ? window.__mnoteSearchResults.slice() : searchUiState.items;
|
||||
searchUiState.scrollTop = results instanceof HTMLElement ? results.scrollTop : searchUiState.scrollTop;
|
||||
searchUiState.signature = query ? searchRequestSignature(overlay, query) : '';
|
||||
}
|
||||
|
||||
function restoreSearchUiState(overlay) {
|
||||
if (!(overlay instanceof HTMLElement) || !searchUiState.hasRendered) return false;
|
||||
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
||||
var options = overlay.querySelector('[data-testid="wolai-search-options"]');
|
||||
var meta = overlay.querySelector('[data-testid="wolai-search-result-meta"]');
|
||||
var results = overlay.querySelector('[data-testid="wolai-search-results"]');
|
||||
if (input && 'value' in input) input.value = searchUiState.query || '';
|
||||
applySearchSwitchState(overlay, searchUiState.switches);
|
||||
if (options instanceof HTMLElement) options.hidden = !searchText(searchUiState.query);
|
||||
if (meta && searchUiState.metaHtml) meta.innerHTML = searchUiState.metaHtml;
|
||||
if (results && searchUiState.resultsHtml) {
|
||||
results.innerHTML = searchUiState.resultsHtml;
|
||||
results.scrollTop = Number(searchUiState.scrollTop || 0);
|
||||
}
|
||||
window.__mnoteSearchResults = Array.isArray(searchUiState.items) ? searchUiState.items.slice() : [];
|
||||
return true;
|
||||
}
|
||||
|
||||
function searchHighlightTerms(item, query) {
|
||||
var info = item && item.matchInfo || item && item.evidence && item.evidence.matchInfo || null;
|
||||
var terms = info && Array.isArray(info.matchedTerms) ? info.matchedTerms : [];
|
||||
@@ -2258,13 +2372,113 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
return html;
|
||||
}
|
||||
|
||||
function cleanSearchDisplayText(value) {
|
||||
var text = String(value == null ? '' : value);
|
||||
text = text.replace(/<drawing\b[^>]*\/?>/gi, ' ');
|
||||
text = text.replace(/<equation\b[^>]*>([\s\S]*?)<\/equation>/gi, '$1');
|
||||
text = text.replace(/<drawing\b[^<>]*$/gi, ' ');
|
||||
text = text.replace(/<\/?equat[^<>]*$/gi, ' ');
|
||||
text = text.replace(/<\/?e(?:q(?:uation)?)?[^<>]*$/gi, ' ');
|
||||
text = text.replace(/<[^>]+>/g, ' ');
|
||||
text = text
|
||||
.replace(/\b(?:equation|latex|drawing)\b(?:\s+[a-z_-]+=(?:"[^"]*"|'[^']*'|[^\s<>]+))*/gi, ' ')
|
||||
.replace(/\\left|\\right|\\mathrm|\\text|\\operatorname/g, '')
|
||||
.replace(/\\gt/g, '>')
|
||||
.replace(/\\lt/g, '<')
|
||||
.replace(/\\sim/g, '∼')
|
||||
.replace(/\\[a-zA-Z]+/g, ' ')
|
||||
.replace(/\{([^{}]*)\}/g, '$1')
|
||||
.replace(/\{([^{}]*)\}/g, '$1')
|
||||
.replace(/[_^]/g, '')
|
||||
.replace(/[<>]\/?(?:equation|eq)\b[^<>]*>?/gi, ' ')
|
||||
.replace(/<\/?e(?:q(?:uation)?)?[^<>\s]*>?/gi, ' ')
|
||||
.replace(/\s+(?:equation|latex|drawing)\s+/gi, ' ');
|
||||
return searchText(text);
|
||||
}
|
||||
|
||||
function searchResultEvidenceLocator(item) {
|
||||
if (item && item.locator && typeof item.locator === 'object') return item.locator;
|
||||
if (item && item.evidence && item.evidence.source && typeof item.evidence.source === 'object') return item.evidence.source;
|
||||
if (item && item.source && item.source.locator && typeof item.source.locator === 'object') return item.source.locator;
|
||||
if (item && Array.isArray(item.evidence) && item.evidence[0] && item.evidence[0].source && typeof item.evidence[0].source === 'object') return item.evidence[0].source;
|
||||
return null;
|
||||
}
|
||||
|
||||
function searchResultSourceKey(item) {
|
||||
var locator = searchResultEvidenceLocator(item);
|
||||
var locatorPath = locator ? searchText(locator.resourcePath || locator.resource_path || locator.ownerDocumentPath || locator.owner_document_path) : '';
|
||||
return locatorPath || searchText(item && (item.path || item.documentId || item.nodeId || item.id)) || 'unknown-source';
|
||||
}
|
||||
|
||||
function searchResultSourceTitle(item) {
|
||||
var key = searchResultSourceKey(item);
|
||||
var locator = searchResultEvidenceLocator(item);
|
||||
var path = locator ? searchText(locator.resourcePath || locator.resource_path || locator.ownerDocumentPath || locator.owner_document_path) : '';
|
||||
var sourcePath = path || searchText(item && item.path) || key;
|
||||
var title = searchText(item && (item.title || item.name || item.documentTitle));
|
||||
return title || sourcePath.split('/').filter(Boolean).pop() || sourcePath || '未知来源';
|
||||
}
|
||||
|
||||
function renderSearchResultButton(item, index, query, exact, grouped) {
|
||||
var title = searchText(item.title || item.name || item.documentTitle || '无标题');
|
||||
var snippet = cleanSearchDisplayText(item.snippet || item.evidence && item.evidence.quote || (Array.isArray(item.evidence) && item.evidence[0] && (item.evidence[0].quote || item.evidence[0].snippet)) || '');
|
||||
var path = searchText(item.path || item.parentTitle || item.workspaceName || currentWorkspaceName());
|
||||
var resourceType = searchText(item.resourceType || item.resourceKind || 'page');
|
||||
var locator = searchResultEvidenceLocator(item);
|
||||
var locatorAttr = locator ? escapeHtml(JSON.stringify(locator)) : '';
|
||||
var highlightTerms = searchHighlightTerms(item, query);
|
||||
var titleHtml = grouped && snippet ? '' : '<span class="wolai-search-result-title">' + highlightSearchText(title, query, exact, highlightTerms) + '</span>';
|
||||
var page = locator && locator.page != null && locator.page !== '' ? String(locator.page) : '';
|
||||
var metaParts = grouped ? [] : [escapeHtml(path)];
|
||||
if (page) metaParts.push('第 ' + escapeHtml(page) + ' 页');
|
||||
if (!grouped) metaParts.push('<span class="wolai-search-result-type">' + escapeHtml(resourceType) + '</span>');
|
||||
var metaHtml = metaParts.length ? '<span class="wolai-search-result-path"><span>' + metaParts.join('</span><span>') + '</span></span>' : '';
|
||||
return '<button type="button" class="wolai-search-result-row" data-testid="wolai-search-result-row" data-search-result-owner="' + escapeHtml(item.provider || 'rust-kernel') + '" data-search-result-index="' + String(index) + '" data-document-id="' + escapeHtml(item.documentId || item.nodeId || item.id || '') + '" data-resource-type="' + escapeHtml(resourceType) + '"' + (locatorAttr ? ' data-evidence-locator="' + locatorAttr + '"' : '') + '>' +
|
||||
'<span class="material-symbols-outlined wolai-search-result-icon" data-icon="article" aria-hidden="true"></span>' +
|
||||
'<span class="wolai-search-result-main">' + titleHtml +
|
||||
'<span class="wolai-search-result-snippet">' + (snippet ? highlightSearchText(snippet, query, exact, highlightTerms) : escapeHtml(path)) + '</span>' +
|
||||
metaHtml + '</span>' +
|
||||
'</button>';
|
||||
}
|
||||
|
||||
function renderSearchResultsHtml(items, overlay, query) {
|
||||
var exact = searchSwitchValue(overlay, 'exact');
|
||||
if (!searchSwitchValue(overlay, 'collapseSource')) {
|
||||
return items.map(function(item, index) {
|
||||
return renderSearchResultButton(item, index, query, exact, false);
|
||||
}).join('');
|
||||
}
|
||||
var groups = [];
|
||||
var groupByKey = {};
|
||||
items.forEach(function(item, index) {
|
||||
var key = searchResultSourceKey(item);
|
||||
var group = groupByKey[key];
|
||||
if (!group) {
|
||||
group = { key: key, title: searchResultSourceTitle(item), path: searchText(item.path || key), resourceType: searchText(item.resourceType || item.resourceKind || 'page'), rows: [] };
|
||||
groupByKey[key] = group;
|
||||
groups.push(group);
|
||||
}
|
||||
group.rows.push({ item: item, index: index });
|
||||
});
|
||||
return groups.map(function(group) {
|
||||
var collapsed = Object.prototype.hasOwnProperty.call(searchUiState.sourceCollapsed, group.key)
|
||||
? searchUiState.sourceCollapsed[group.key]
|
||||
: true;
|
||||
var children = group.rows.map(function(row) {
|
||||
return renderSearchResultButton(row.item, row.index, query, exact, true);
|
||||
}).join('');
|
||||
return '<section class="wolai-search-source-group" data-search-source-key="' + escapeHtml(group.key) + '">' +
|
||||
'<button type="button" class="wolai-search-source-header" data-search-source-toggle="true" aria-expanded="' + (collapsed ? 'false' : 'true') + '">' +
|
||||
'<span class="material-symbols-outlined wolai-search-result-icon" data-icon="article" aria-hidden="true"></span>' +
|
||||
'<span class="wolai-search-source-main"><span class="wolai-search-result-title">' + escapeHtml(group.title) + '</span>' +
|
||||
'<span class="wolai-search-result-path"><span class="wolai-search-result-type">' + escapeHtml(group.resourceType) + '</span><span>' + String(group.rows.length) + ' 处</span></span></span>' +
|
||||
'<span class="wolai-search-source-chevron" aria-hidden="true">' + (collapsed ? '展开' : '收起') + '</span>' +
|
||||
'</button>' +
|
||||
'<div class="wolai-search-source-results" data-search-source-results="true"' + (collapsed ? ' hidden' : '') + '>' + children + '</div>' +
|
||||
'</section>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function evidenceLocatorResourceKind(locator, fallback) {
|
||||
return searchText(locator && (locator.resourceKind || locator.resource_kind) || fallback || '').toLowerCase();
|
||||
}
|
||||
@@ -2316,6 +2530,12 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
async function openEvidenceSearchResult(item, event) {
|
||||
var locator = searchResultEvidenceLocator(item);
|
||||
if (!locator) {
|
||||
var citationUrl = searchText(item && (item.citationUrl || item.publicPath));
|
||||
if (citationUrl) {
|
||||
closeSearchModal();
|
||||
window.location.assign(citationUrl);
|
||||
return;
|
||||
}
|
||||
var fallbackId = searchText(item && (item.documentId || item.nodeId || item.id));
|
||||
if (fallbackId) window.location.assign('/documents/' + encodeURIComponent(fallbackId));
|
||||
return;
|
||||
@@ -2323,6 +2543,12 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
var resourcePath = searchText(locator.resourcePath || locator.resource_path || locator.ownerDocumentPath || locator.owner_document_path);
|
||||
var ownerDocumentId = searchText(locator.ownerDocumentId || locator.owner_document_id || item.documentId || '');
|
||||
var resourceKind = evidenceLocatorResourceKind(locator, item && item.resourceType);
|
||||
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
||||
var searchInput = overlay instanceof HTMLElement ? overlay.querySelector('[data-testid="wolai-search-input"]') : null;
|
||||
var searchQueryText = searchText(item && (item.query || item.searchQuery))
|
||||
|| searchText(searchInput && 'value' in searchInput ? searchInput.value : '')
|
||||
|| searchText(searchUiState.query);
|
||||
var locatorEvidenceText = searchText(locator.evidenceText || locator.query || locator.openAction && locator.openAction.params && locator.openAction.params.query || '');
|
||||
var openTarget = event && event.altKey ? 'side' : 'active-tab';
|
||||
if (resourcePath && resourceKind) {
|
||||
var fileName = resourcePath.split('/').filter(Boolean).pop() || resourcePath;
|
||||
@@ -2347,6 +2573,9 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
bbox: locator.bbox,
|
||||
sourceMapPath: searchText(locator.sourceMapPath || locator.source_map_path),
|
||||
blockId: evidenceLocatorBlockId(locator),
|
||||
evidenceText: locatorEvidenceText,
|
||||
query: searchQueryText,
|
||||
searchQuery: searchQueryText,
|
||||
lineRange: evidenceLocatorLineRange(locator),
|
||||
charRange: evidenceLocatorCharRange(locator)
|
||||
});
|
||||
@@ -2379,6 +2608,8 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
if (results) {
|
||||
results.innerHTML = '<div class="wolai-search-recent" data-testid="wolai-search-recent">暂无最近浏览</div>';
|
||||
}
|
||||
saveSearchUiState(overlay);
|
||||
searchUiState.hasRendered = true;
|
||||
}
|
||||
|
||||
function scheduleSearchResultsRender() {
|
||||
@@ -2401,14 +2632,32 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
return;
|
||||
}
|
||||
if (options instanceof HTMLElement) options.hidden = false;
|
||||
var knowledgeMode = searchSwitchValue(overlay, 'knowledge');
|
||||
if (knowledgeMode && searchNonWhitespaceCharCount(query) < 2) {
|
||||
activeSearchRequestId += 1;
|
||||
meta.innerHTML = '<span>资料库检索</span><span>请输入至少 2 个字再搜索</span>';
|
||||
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">请输入至少 2 个字再搜索</div>';
|
||||
window.__mnoteSearchResults = [];
|
||||
saveSearchUiState(overlay);
|
||||
searchUiState.hasRendered = true;
|
||||
return;
|
||||
}
|
||||
var requestId = ++activeSearchRequestId;
|
||||
meta.innerHTML = '<span>搜索中…</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
||||
results.innerHTML = '<div class="wolai-search-empty" data-search-loading="true">搜索中…</div>';
|
||||
try {
|
||||
var response = await fetch('/api/search/documents', {
|
||||
var response = await fetch(knowledgeMode ? '/api/knowledge-rag/search' : '/api/search/documents', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
body: JSON.stringify(knowledgeMode ? {
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
rootUri: currentRootUri() || null,
|
||||
query: query,
|
||||
mode: 'hybrid',
|
||||
topK: 12,
|
||||
chunkTopK: 24,
|
||||
includeChunkContent: true
|
||||
} : {
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
sourceKind: currentSourceKind() || null,
|
||||
rootUri: currentRootUri() || null,
|
||||
@@ -2418,7 +2667,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
filters: {
|
||||
titleOnly: searchSwitchValue(overlay, 'title'),
|
||||
exact: searchSwitchValue(overlay, 'exact'),
|
||||
includeOcr: true,
|
||||
includeOcr: false,
|
||||
onlyCurrentPage: searchSwitchValue(overlay, 'page'),
|
||||
timeRange: 'any',
|
||||
timeField: 'updated'
|
||||
@@ -2428,33 +2677,25 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
var payload = await response.json();
|
||||
if (requestId !== activeSearchRequestId) return;
|
||||
var items = Array.isArray(payload.results) ? payload.results : [];
|
||||
meta.innerHTML = '<span>共 ' + items.length + ' 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
||||
meta.innerHTML = '<span>' + (knowledgeMode ? '资料库检索' : '工作区搜索') + ' · 共 ' + items.length + ' 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
||||
if (!items.length) {
|
||||
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">没有搜索结果</div>';
|
||||
window.__mnoteSearchResults = [];
|
||||
saveSearchUiState(overlay);
|
||||
searchUiState.hasRendered = true;
|
||||
return;
|
||||
}
|
||||
results.innerHTML = items.map(function(item) {
|
||||
var title = searchText(item.title || item.name || item.documentTitle || '无标题');
|
||||
var snippet = searchText(item.snippet || item.evidence && item.evidence.quote || (Array.isArray(item.evidence) && item.evidence[0] && (item.evidence[0].quote || item.evidence[0].snippet)) || '');
|
||||
var path = searchText(item.path || item.parentTitle || item.workspaceName || currentWorkspaceName());
|
||||
var resourceType = searchText(item.resourceType || item.resourceKind || 'page');
|
||||
var locator = searchResultEvidenceLocator(item);
|
||||
var locatorAttr = locator ? escapeHtml(JSON.stringify(locator)) : '';
|
||||
var index = items.indexOf(item);
|
||||
var exact = searchSwitchValue(overlay, 'exact');
|
||||
var highlightTerms = searchHighlightTerms(item, query);
|
||||
return '<button type="button" class="wolai-search-result-row" data-testid="wolai-search-result-row" data-search-result-owner="rust-kernel" data-search-result-index="' + String(index) + '" data-document-id="' + escapeHtml(item.documentId || item.nodeId || item.id || '') + '" data-resource-type="' + escapeHtml(resourceType) + '"' + (locatorAttr ? ' data-evidence-locator="' + locatorAttr + '"' : '') + '>' +
|
||||
'<span class="material-symbols-outlined wolai-search-result-icon" data-icon="article" aria-hidden="true"></span>' +
|
||||
'<span class="wolai-search-result-main"><span class="wolai-search-result-title">' + highlightSearchText(title, query, exact, highlightTerms) + '</span>' +
|
||||
'<span class="wolai-search-result-snippet">' + (snippet ? highlightSearchText(snippet, query, exact, highlightTerms) : escapeHtml(path)) + '</span>' +
|
||||
'<span class="wolai-search-result-path"><span>' + escapeHtml(path) + '</span><span class="wolai-search-result-type">' + escapeHtml(resourceType) + '</span></span></span>' +
|
||||
'</button>';
|
||||
}).join('');
|
||||
window.__mnoteSearchResults = items;
|
||||
results.innerHTML = renderSearchResultsHtml(items, overlay, query);
|
||||
saveSearchUiState(overlay);
|
||||
searchUiState.hasRendered = true;
|
||||
} catch (error) {
|
||||
if (requestId !== activeSearchRequestId) return;
|
||||
meta.innerHTML = '<span>共 0 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
|
||||
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">没有搜索结果</div>';
|
||||
window.__mnoteSearchResults = [];
|
||||
saveSearchUiState(overlay);
|
||||
searchUiState.hasRendered = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2467,9 +2708,12 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
var overlay = ensureSearchModal();
|
||||
overlay.hidden = false;
|
||||
document.documentElement.setAttribute('data-mnote-search-modal-open', 'true');
|
||||
var restored = restoreSearchUiState(overlay);
|
||||
var input = overlay.querySelector('[data-testid="wolai-search-input"]');
|
||||
if (input) input.setAttribute('placeholder', '在 ' + currentWorkspaceName() + ' 中搜索');
|
||||
void renderSearchResults();
|
||||
var query = input && 'value' in input ? searchText(input.value) : '';
|
||||
var signature = query ? searchRequestSignature(overlay, query) : '';
|
||||
if (!restored || signature !== searchUiState.signature) void renderSearchResults();
|
||||
if (input) {
|
||||
setTimeout(function() { input.focus(); input.select(); }, 0);
|
||||
}
|
||||
@@ -2477,7 +2721,10 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
|
||||
function closeSearchModal() {
|
||||
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
||||
if (overlay instanceof HTMLElement) overlay.hidden = true;
|
||||
if (overlay instanceof HTMLElement) {
|
||||
saveSearchUiState(overlay);
|
||||
overlay.hidden = true;
|
||||
}
|
||||
document.documentElement.removeAttribute('data-mnote-search-modal-open');
|
||||
}
|
||||
|
||||
@@ -2486,6 +2733,17 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
else openSearchModal();
|
||||
}
|
||||
|
||||
function isSearchShortcutEvent(event) {
|
||||
return Boolean(event && (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey && event.key && event.key.toLowerCase() === 'p');
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (!isSearchShortcutEvent(event)) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
openSearchModal();
|
||||
}, true);
|
||||
|
||||
const sidebarPageAi = createSidebarPageAiRuntime({
|
||||
buildLocalFileOpenUrl,
|
||||
currentDocumentId,
|
||||
@@ -2792,6 +3050,22 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
closeSearchModal();
|
||||
return;
|
||||
}
|
||||
var searchSourceToggle = closestAction(e.target, '[data-search-source-toggle]');
|
||||
if (searchSourceToggle) {
|
||||
e.preventDefault();
|
||||
var sourceGroup = searchSourceToggle.closest('.wolai-search-source-group');
|
||||
var sourceResults = sourceGroup && sourceGroup.querySelector('[data-search-source-results="true"]');
|
||||
var sourceKey = sourceGroup ? searchText(sourceGroup.getAttribute('data-search-source-key')) : '';
|
||||
var nextCollapsed = !(sourceResults instanceof HTMLElement && sourceResults.hidden);
|
||||
if (sourceResults instanceof HTMLElement) sourceResults.hidden = nextCollapsed;
|
||||
searchSourceToggle.setAttribute('aria-expanded', nextCollapsed ? 'false' : 'true');
|
||||
var chevron = searchSourceToggle.querySelector('.wolai-search-source-chevron');
|
||||
if (chevron) chevron.textContent = nextCollapsed ? '展开' : '收起';
|
||||
if (sourceKey) searchUiState.sourceCollapsed[sourceKey] = nextCollapsed;
|
||||
var overlay = document.querySelector('[data-testid="wolai-search-modal"]');
|
||||
if (overlay instanceof HTMLElement) saveSearchUiState(overlay);
|
||||
return;
|
||||
}
|
||||
var searchResultRow = closestAction(e.target, '[data-testid="wolai-search-result-row"]');
|
||||
if (searchResultRow) {
|
||||
e.preventDefault();
|
||||
@@ -3144,9 +3418,9 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
closeAllSettingsPopovers();
|
||||
return;
|
||||
}
|
||||
if ((event.metaKey || event.ctrlKey) && !event.shiftKey && event.key && event.key.toLowerCase() === 'p') {
|
||||
if (isSearchShortcutEvent(event)) {
|
||||
event.preventDefault();
|
||||
toggleSearchModal();
|
||||
openSearchModal();
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
|
||||
Reference in New Issue
Block a user