chore: harden lightrag knowledge rag runtime

This commit is contained in:
lix-2026
2026-06-09 22:12:09 +08:00
parent 922965d30f
commit f698aad964
22 changed files with 899 additions and 101 deletions
@@ -690,11 +690,18 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const entry = resourceTabRegistry.get(activeResource);
const documentId = String(entry?.ownerDocumentId || entry?.documentId || '').trim();
if (documentId) url.pathname = `/documents/${encodeURIComponent(documentId)}`;
const sourceKind = String(entry?.sourceKind || currentWebShellSourceKind() || '').trim();
const rootUri = String(entry?.rootUri || currentWebShellRootUri() || '').trim();
const resourcePath = String(entry?.path || entry?.session?.resourcePath || '').trim();
if (sourceKind) url.searchParams.set('sourceKind', sourceKind);
if (rootUri) url.searchParams.set('rootUri', rootUri);
if (resourcePath) url.searchParams.set('resourcePath', resourcePath);
url.searchParams.set('resourceTab', activeResource);
} else {
const documentId = currentWebShellDocumentId();
if (documentId) url.pathname = `/documents/${encodeURIComponent(documentId)}`;
url.searchParams.delete('resourceTab');
url.searchParams.delete('resourcePath');
}
replaceUrlState(url);
};
@@ -1085,6 +1092,59 @@ export const createResourceTabRuntime = (dependencies = {}) => {
overlay.style.height = `${Math.max(1, bbox.y1 - bbox.y0)}px`;
};
const applyPdfEvidenceOverlayToCanvas = (canvas, locator) => {
if (!(canvas instanceof HTMLCanvasElement)) return null;
const frame = canvas.parentElement instanceof HTMLElement && canvas.parentElement.hasAttribute('data-mnote-pdf-page-frame')
? canvas.parentElement
: null;
if (!(frame instanceof HTMLElement)) return null;
const bbox = normalizeEvidenceBBox(locator?.bbox);
let overlay = frame.querySelector('[data-mnote-evidence-bbox-highlight]');
if (!bbox) {
if (overlay instanceof HTMLElement) overlay.hidden = true;
return null;
}
const viewport = canvas.__mnotePdfViewport;
if (!viewport || typeof viewport.convertToViewportRectangle !== 'function') return null;
if (!(overlay instanceof HTMLElement)) {
overlay = document.createElement('div');
overlay.className = 'mnote-resource-tab-bbox-highlight';
overlay.setAttribute('data-mnote-evidence-bbox-highlight', 'true');
frame.append(overlay);
}
const normalizedMineruBox = bbox.x0 >= 0 && bbox.y0 >= 0 && bbox.x1 <= 1000 && bbox.y1 <= 1000;
let x;
let y;
let width;
let height;
if (normalizedMineruBox) {
x = Math.min(bbox.x0, bbox.x1) / 1000 * viewport.width;
y = Math.min(bbox.y0, bbox.y1) / 1000 * viewport.height;
width = Math.max(1, Math.abs(bbox.x1 - bbox.x0) / 1000 * viewport.width);
height = Math.max(1, Math.abs(bbox.y1 - bbox.y0) / 1000 * viewport.height);
} else {
const rect = viewport.convertToViewportRectangle([bbox.x0, bbox.y0, bbox.x1, bbox.y1]);
x = Math.min(rect[0], rect[2]);
y = Math.min(rect[1], rect[3]);
width = Math.max(1, Math.abs(rect[2] - rect[0]));
height = Math.max(1, Math.abs(rect[3] - rect[1]));
}
overlay.hidden = false;
overlay.style.left = `${Math.max(0, x)}px`;
overlay.style.top = `${Math.max(0, y)}px`;
overlay.style.width = `${width}px`;
overlay.style.height = `${height}px`;
return overlay;
};
const scrollPdfEvidenceTargetIntoView = (canvas, overlay) => {
const target = overlay instanceof HTMLElement && !overlay.hidden
? overlay
: (canvas?.parentElement instanceof HTMLElement ? canvas.parentElement : canvas);
if (!(target instanceof HTMLElement)) return;
window.setTimeout(() => target.scrollIntoView({ block: 'center', inline: 'nearest' }), 0);
};
const applyEvidenceLocatorToTextPanel = (entry, locator) => {
if (!(entry?.panel instanceof HTMLElement) || !locator) return;
const root = entry.panel.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
@@ -1119,8 +1179,12 @@ export const createResourceTabRuntime = (dependencies = {}) => {
entry.panel.querySelectorAll('canvas.mnote-pdf-page[data-mnote-evidence-page="true"]').forEach((node) => {
if (node instanceof HTMLElement) node.removeAttribute('data-mnote-evidence-page');
});
entry.panel.querySelectorAll('[data-mnote-evidence-bbox-highlight]').forEach((node) => {
if (node instanceof HTMLElement) node.hidden = true;
});
canvas.setAttribute('data-mnote-evidence-page', 'true');
canvas.scrollIntoView({ block: 'center', inline: 'nearest' });
const overlay = applyPdfEvidenceOverlayToCanvas(canvas, locator);
scrollPdfEvidenceTargetIntoView(canvas, overlay);
};
const applyEvidenceLocatorToEntry = (entry, input = {}) => {
@@ -1328,16 +1392,23 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const scale = Math.max(0.6, Math.min(2.4, availableWidth / baseViewport.width));
const viewport = page.getViewport({ scale });
const outputScale = Math.min(2.5, Math.max(2, window.devicePixelRatio || 1));
const frame = document.createElement('div');
frame.setAttribute('data-mnote-pdf-page-frame', 'true');
frame.style.position = 'relative';
frame.style.width = `${Math.floor(viewport.width)}px`;
frame.style.maxWidth = '100%';
frame.style.margin = '0 auto 14px';
const canvas = document.createElement('canvas');
canvas.className = 'mnote-pdf-page';
canvas.setAttribute('data-page-number', String(pageNumber));
canvas.__mnotePdfViewport = viewport;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.display = 'block';
canvas.style.maxWidth = '100%';
canvas.style.width = `${Math.floor(viewport.width)}px`;
canvas.style.height = `${Math.floor(viewport.height)}px`;
canvas.style.margin = '0 auto 14px';
canvas.style.margin = '0';
canvas.style.background = '#fff';
canvas.style.border = '1px solid #d8d8d2';
canvas.style.boxShadow = '0 2px 10px rgba(25, 25, 22, .08)';
@@ -1351,25 +1422,12 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const evidencePage = Number(evidenceLocator?.page || 0);
if (evidencePage === pageNumber) {
canvas.setAttribute('data-mnote-evidence-page', 'true');
const bbox = normalizeEvidenceBBox(evidenceLocator?.bbox);
if (bbox) {
const rect = viewport.convertToViewportRectangle([bbox.x0, bbox.y0, bbox.x1, bbox.y1]);
const x = Math.min(rect[0], rect[2]);
const y = Math.min(rect[1], rect[3]);
const width = Math.max(1, Math.abs(rect[2] - rect[0]));
const height = Math.max(1, Math.abs(rect[3] - rect[1]));
context.save();
context.scale(outputScale, outputScale);
context.fillStyle = 'rgba(37, 99, 235, 0.18)';
context.strokeStyle = 'rgba(37, 99, 235, 0.9)';
context.lineWidth = 2;
context.fillRect(x, y, width, height);
context.strokeRect(x, y, width, height);
context.restore();
}
}
if (entry.inlinePdfDocument === pdf) viewer.append(canvas);
if (evidencePage === pageNumber) window.setTimeout(() => canvas.scrollIntoView({ block: 'center', inline: 'nearest' }), 0);
frame.append(canvas);
let evidenceOverlay = null;
if (evidencePage === pageNumber) evidenceOverlay = applyPdfEvidenceOverlayToCanvas(canvas, evidenceLocator);
if (entry.inlinePdfDocument === pdf) viewer.append(frame);
if (evidencePage === pageNumber) scrollPdfEvidenceTargetIntoView(canvas, evidenceOverlay);
};
const openInlinePdfResourceTab = async (entry, input) => {
@@ -41,6 +41,8 @@ export function createSidebarPageAiRenderRuntime(context) {
pageAiSessionAgentFilterValue,
pageAiSessionPreviewText,
pageAiSessionStorageLabel,
pageAiSessionSelected,
pageAiSelectedSessionCount,
pageAiSkillEnabled,
pageAiSkillGroupCollapsed,
pageAiSkillListEntries,
@@ -601,6 +603,15 @@ export function createSidebarPageAiRenderRuntime(context) {
sessionError.textContent = pageUiState.pageAiSessionError || '';
sessionError.hidden = !pageUiState.pageAiSessionError;
}
var selectedSessionCount = pageAiSelectedSessionCount();
var selectedSessionToolbar = drawer.querySelector('[data-page-ai-history-selection-toolbar]');
if (selectedSessionToolbar instanceof HTMLElement) {
selectedSessionToolbar.hidden = selectedSessionCount <= 0;
}
var selectedSessionCountNode = drawer.querySelector('[data-page-ai-history-selection-count]');
if (selectedSessionCountNode instanceof HTMLElement) {
selectedSessionCountNode.textContent = '已选 ' + String(selectedSessionCount) + ' 个';
}
var sessionSearch = drawer.querySelector('[data-page-ai-session-search]');
if (sessionSearch instanceof HTMLInputElement && documentRef.activeElement !== sessionSearch) {
sessionSearch.value = pageUiState.pageAiSessionSearchQuery || '';
@@ -1009,6 +1020,11 @@ export function createSidebarPageAiRenderRuntime(context) {
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="history" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-history-selection" data-page-ai-history-selection-toolbar hidden>' +
'<span data-page-ai-history-selection-count>已选 0 个</span>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-clear-selection>取消</button>' +
'<button type="button" class="wolai-page-ai-ghost wolai-page-ai-ghost--danger" data-page-ai-session-delete-selected>删除</button>' +
'</div>' +
'</div>' +
'<label class="wolai-page-ai-skill-search">' +
'<span>搜索会话</span>' +
@@ -1293,6 +1309,20 @@ export function createSidebarPageAiRenderRuntime(context) {
}).join('');
}
function pageAiHistoryTextKey(value) {
return String(value || '')
.replace(/\s+/g, ' ')
.replace(/[.…]+$/g, '')
.trim();
}
function pageAiHistoryPreviewIsDuplicate(title, preview) {
var titleKey = pageAiHistoryTextKey(title);
var previewKey = pageAiHistoryTextKey(preview);
if (!titleKey || !previewKey) return false;
return titleKey === previewKey || previewKey.indexOf(titleKey) === 0 || titleKey.indexOf(previewKey) === 0;
}
function renderPageAiConversation() {
var drawer = ensurePageAiDrawer();
renderPageAiSuggestions();
@@ -1308,22 +1338,28 @@ export function createSidebarPageAiRenderRuntime(context) {
return;
}
conversation.innerHTML = historyRows.map(function(session) {
var title = String(session.title || '新会话').trim() || '新会话';
var preview = pageAiSessionPreviewText(session);
var previewHtml = pageAiHistoryPreviewIsDuplicate(title, preview) ? '' : '<span>' + escapeHtml(preview) + '</span>';
var active = session.id === pageUiState.pageAiActiveSessionId;
var selected = pageAiSessionSelected(session.id);
var usage = pageAiUsageSummary(session.usage);
var agentLabel = pageAiSessionAgentLabel(session);
var meta = [agentLabel, pageAiSessionStorageLabel(session), session.permissionLevel, session.shareId, session.status, usage].filter(Boolean).join(' · ');
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--history' + (active ? ' is-active' : '') + '" data-page-ai-session-row="' + escapeHtml(session.id) + '" data-page-ai-session-agent="' + escapeHtml(pageAiSessionAgentFilterValue(session)) + '">' +
'<button type="button" class="wolai-page-ai-message-text" data-page-ai-session="' + escapeHtml(session.id) + '">' +
'<strong>' + escapeHtml(session.title || '新会话') + '</strong><br />' +
'<span>' + escapeHtml(preview) + '</span>' +
(meta ? '<br /><span class="wolai-page-ai-tool-meta">' + escapeHtml(meta) + '</span>' : '') +
'<div class="wolai-page-ai-message wolai-page-ai-message--history' + (active ? ' is-active' : '') + (selected ? ' is-selected' : '') + '" data-page-ai-session-row="' + escapeHtml(session.id) + '" data-page-ai-session-agent="' + escapeHtml(pageAiSessionAgentFilterValue(session)) + '">' +
'<button type="button" class="wolai-page-ai-session-check" data-page-ai-session-select="' + escapeHtml(session.id) + '" aria-checked="' + (selected ? 'true' : 'false') + '" aria-label="选择会话 ' + escapeHtml(session.title || session.id) + '">' +
'<span>' + (selected ? '✓' : '') + '</span>' +
'</button>' +
'<div class="wolai-page-ai-message-actions">' +
'<button type="button" class="wolai-page-ai-message-text wolai-page-ai-history-main" data-page-ai-session="' + escapeHtml(session.id) + '">' +
'<strong>' + escapeHtml(title) + '</strong>' +
previewHtml +
(meta ? '<span class="wolai-page-ai-tool-meta">' + escapeHtml(meta) + '</span>' : '') +
'</button>' +
'<div class="wolai-page-ai-message-actions wolai-page-ai-history-actions">' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-resume="' + escapeHtml(session.id) + '">恢复</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-rename="' + escapeHtml(session.id) + '">重命名</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-delete="' + escapeHtml(session.id) + '">删除</button>' +
'<button type="button" class="wolai-page-ai-ghost wolai-page-ai-ghost--danger" data-page-ai-session-delete="' + escapeHtml(session.id) + '">删除</button>' +
'</div>' +
'</div>';
}).join('');
@@ -184,6 +184,7 @@ export function createSidebarPageAiRuntime(context) {
pageAiSessionSearchResults: [],
pageAiSessionSearchTimer: 0,
pageAiSessionAgentFilter: 'all',
pageAiSelectedSessionIds: {},
pageAiSessionError: '',
pageAiPermissionRequests: []
};
@@ -494,7 +495,12 @@ export function createSidebarPageAiRuntime(context) {
const pageAiStartNewSession = (...args) => pageAiSessionRuntime.pageAiStartNewSession(...args);
const pageAiRenameBackendSession = (...args) => pageAiSessionRuntime.pageAiRenameBackendSession(...args);
const pageAiDeleteBackendSession = (...args) => pageAiSessionRuntime.pageAiDeleteBackendSession(...args);
const pageAiDeleteSelectedBackendSessions = (...args) => pageAiSessionRuntime.pageAiDeleteSelectedBackendSessions(...args);
const pageAiResumeBackendSession = (...args) => pageAiSessionRuntime.pageAiResumeBackendSession(...args);
const pageAiSessionSelected = (...args) => pageAiSessionRuntime.pageAiSessionSelected(...args);
const pageAiToggleSessionSelection = (...args) => pageAiSessionRuntime.pageAiToggleSessionSelection(...args);
const pageAiClearSessionSelection = (...args) => pageAiSessionRuntime.pageAiClearSessionSelection(...args);
const pageAiSelectedSessionCount = (...args) => pageAiSessionRuntime.pageAiSelectedSessionCount(...args);
const pageAiCheckActiveRun = (...args) => pageAiSessionRuntime.pageAiCheckActiveRun(...args);
const pageAiSkillRuntime = createSidebarPageAiSkillRuntime({
pageAiCurrentAgentId,
@@ -632,6 +638,8 @@ export function createSidebarPageAiRuntime(context) {
pageAiSessionAgentLabel,
pageAiSessionPreviewText,
pageAiSessionStorageLabel,
pageAiSessionSelected,
pageAiSelectedSessionCount,
pageAiSkillEnabled,
pageAiSkillGroupCollapsed,
pageAiSkillListEntries,
@@ -3244,6 +3252,30 @@ export function createSidebarPageAiRuntime(context) {
void pageAiToggleTool(pageAiToolToggle.getAttribute('data-page-ai-tool-toggle') || '', pageAiToolToggle.getAttribute('aria-pressed') !== 'true');
return true;
}
var pageAiSessionSelect = closestAction(event.target, '[data-page-ai-session-select]');
if (pageAiSessionSelect) {
event.preventDefault();
pageAiToggleSessionSelection(
pageAiSessionSelect.getAttribute('data-page-ai-session-select') || '',
pageAiSessionSelect.getAttribute('aria-checked') !== 'true'
);
return true;
}
var pageAiClearSelectedSessions = closestAction(event.target, '[data-page-ai-session-clear-selection]');
if (pageAiClearSelectedSessions) {
event.preventDefault();
pageAiClearSessionSelection();
return true;
}
var pageAiDeleteSelectedSessions = closestAction(event.target, '[data-page-ai-session-delete-selected]');
if (pageAiDeleteSelectedSessions) {
event.preventDefault();
void pageAiDeleteSelectedBackendSessions().catch(function(error) {
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
renderPageAiControls();
});
return true;
}
var pageAiSessionResume = closestAction(event.target, '[data-page-ai-session-resume]');
if (pageAiSessionResume) {
event.preventDefault();
@@ -519,6 +519,49 @@ export function createSidebarPageAiSessionRuntime(context) {
}) || null;
}
function pageAiSelectedSessionIds() {
if (!pageUiState.pageAiSelectedSessionIds || typeof pageUiState.pageAiSelectedSessionIds !== 'object') {
pageUiState.pageAiSelectedSessionIds = {};
}
return pageUiState.pageAiSelectedSessionIds;
}
function pageAiSessionSelected(sessionId) {
sessionId = String(sessionId || '').trim();
return Boolean(sessionId && pageAiSelectedSessionIds()[sessionId]);
}
function pageAiSelectedSessionList() {
var selected = pageAiSelectedSessionIds();
return Object.keys(selected).filter(function(sessionId) {
return selected[sessionId] === true;
});
}
function pageAiSelectedSessionCount() {
return pageAiSelectedSessionList().length;
}
function pageAiToggleSessionSelection(sessionId, selected) {
sessionId = String(sessionId || '').trim();
if (!sessionId) return;
var selectedMap = Object.assign({}, pageAiSelectedSessionIds());
if (selected === false) {
delete selectedMap[sessionId];
} else {
selectedMap[sessionId] = true;
}
pageUiState.pageAiSelectedSessionIds = selectedMap;
renderPageAiConversation();
renderPageAiControls();
}
function pageAiClearSessionSelection() {
pageUiState.pageAiSelectedSessionIds = {};
renderPageAiConversation();
renderPageAiControls();
}
function pageAiSyncCurrentSessionMessages() {
var session = pageAiCurrentSession();
if (!session) return;
@@ -585,30 +628,87 @@ export function createSidebarPageAiSessionRuntime(context) {
renderPageAiControls();
}
async function pageAiDeleteBackendSession(sessionId) {
var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; });
if (!session) return;
if (!win.confirm('确定删除 AI 会话“' + (session.title || sessionId) + '”吗?')) return;
var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '?' + pageAiBackendSessionQuery({}), {
method: 'DELETE',
headers: { 'accept': 'application/json' }
function pageAiSessionRequiresBackendDelete(session) {
var id = String(session && session.id || '').trim();
var source = String(session && session.source || '').trim();
return Boolean(id && (
id.startsWith('mnote_')
|| source === 'acp'
|| String(session && session.persistence || '').trim()
|| String(session && (session.sessionStorage || session.session_storage) || '').trim()
));
}
function pageAiFindSessionById(sessionId) {
sessionId = String(sessionId || '').trim();
if (!sessionId) return null;
return pageAiNormalizeArray(pageUiState.pageAiSessions).find(function(item) {
return String(item && item.id || '').trim() === sessionId;
}) || pageAiNormalizeArray(pageUiState.pageAiSessionSearchResults).find(function(item) {
return String(item && item.id || '').trim() === sessionId;
}) || null;
}
function pageAiApplyDeletedSessionIds(sessionIds) {
var deleted = {};
pageAiNormalizeArray(sessionIds).forEach(function(sessionId) {
sessionId = String(sessionId || '').trim();
if (sessionId) deleted[sessionId] = true;
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(pageAiErrorMessage(payload, 'session_delete_failed_' + response.status));
}
pageUiState.pageAiSessions = pageUiState.pageAiSessions.filter(function(item) { return item.id !== sessionId; });
if (pageUiState.pageAiActiveSessionId === sessionId) {
pageUiState.pageAiSessions = pageAiNormalizeArray(pageUiState.pageAiSessions).filter(function(item) {
return !deleted[String(item && item.id || '').trim()];
});
pageUiState.pageAiSessionSearchResults = pageAiNormalizeArray(pageUiState.pageAiSessionSearchResults).filter(function(item) {
return !deleted[String(item && item.id || '').trim()];
});
var selected = Object.assign({}, pageAiSelectedSessionIds());
Object.keys(deleted).forEach(function(sessionId) { delete selected[sessionId]; });
pageUiState.pageAiSelectedSessionIds = selected;
if (deleted[pageUiState.pageAiActiveSessionId]) {
var next = pageUiState.pageAiSessions[0] || pageAiNewSession();
if (!pageUiState.pageAiSessions.length) pageUiState.pageAiSessions = [next];
pageUiState.pageAiActiveSessionId = next.id;
pageUiState.pageAiMessages = Array.isArray(next.messages) ? next.messages.slice() : [];
}
}
async function pageAiDeleteBackendSessions(sessionIds) {
var ids = pageAiNormalizeArray(sessionIds).map(function(sessionId) {
return String(sessionId || '').trim();
}).filter(Boolean);
if (!ids.length) return;
var sessions = ids.map(pageAiFindSessionById).filter(Boolean);
if (!sessions.length) return;
var confirmText = sessions.length === 1
? '确定删除 AI 会话“' + (sessions[0].title || sessions[0].id) + '”吗?'
: '确定删除选中的 ' + String(sessions.length) + ' 个 AI 会话吗?';
if (!win.confirm(confirmText)) return;
for (var index = 0; index < sessions.length; index += 1) {
var session = sessions[index];
if (!pageAiSessionRequiresBackendDelete(session)) continue;
var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(session.id) + '?' + pageAiBackendSessionQuery({}), {
method: 'DELETE',
headers: { 'accept': 'application/json' }
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(pageAiErrorMessage(payload, 'session_delete_failed_' + response.status));
}
}
pageAiApplyDeletedSessionIds(sessions.map(function(session) { return session.id; }));
pageAiPersistSessions();
renderPageAiConversation();
renderPageAiControls();
}
async function pageAiDeleteBackendSession(sessionId) {
return pageAiDeleteBackendSessions([sessionId]);
}
async function pageAiDeleteSelectedBackendSessions() {
return pageAiDeleteBackendSessions(pageAiSelectedSessionList());
}
async function pageAiResumeBackendSession(sessionId) {
sessionId = String(sessionId || pageUiState.pageAiActiveSessionId || '').trim();
if (!sessionId) return;
@@ -676,11 +776,16 @@ export function createSidebarPageAiSessionRuntime(context) {
pageAiEnsureHermesSession,
pageAiRestoreHermesSession,
pageAiCurrentSession,
pageAiSessionSelected,
pageAiToggleSessionSelection,
pageAiClearSessionSelection,
pageAiSelectedSessionCount,
pageAiSyncCurrentSessionMessages,
pageAiSetActiveSession,
pageAiStartNewSession,
pageAiRenameBackendSession,
pageAiDeleteBackendSession,
pageAiDeleteSelectedBackendSessions,
pageAiResumeBackendSession,
pageAiCheckActiveRun
};
@@ -30,6 +30,9 @@ export function createSidebarPageSettingsRuntime(context) {
ppt: { mode: 'wide', resolvedMode: 'wide', cssMaxWidth: '1180px', source: 'system' },
mindmap: { mode: 'full', resolvedMode: 'full', cssMaxWidth: 'none', source: 'system' }
};
var knowledgeRagPipelineEventSource = null;
var knowledgeRagPipelineWatcherScopeKey = '';
var knowledgeRagPipelineWatcherRefreshPending = false;
function defaultPageOptions() {
return {
@@ -71,11 +74,6 @@ export function createSidebarPageSettingsRuntime(context) {
return pageUiState.knowledgeRagSummary || {};
}
var knowledgeRagBridgeTimer = 0;
var knowledgeRagBridgeGeneration = 0;
var knowledgeRagBridgeAttempt = 0;
var knowledgeRagBridgeDelays = [1200, 2000, 3200, 5000, 8000, 13000, 21000, 34000];
function pageWidthModeLabel(mode) {
if (mode === 'inherit') return '继承默认';
if (mode === 'readable') return '阅读';
@@ -1099,21 +1097,85 @@ export function createSidebarPageSettingsRuntime(context) {
}
function knowledgeRagStatusHasInFlight(status) {
var pipeline = status && status.pipeline && typeof status.pipeline === 'object' ? status.pipeline : {};
if (pipeline.busy === true || pipeline.scanning === true || pipeline.destructiveBusy === true || pipeline.requestPending === true || pipeline.pendingRequests === true || Number(pipeline.pendingEnqueues || 0) > 0) return true;
var registry = status && status.registry && typeof status.registry === 'object' ? status.registry : {};
var entries = Array.isArray(registry.entries) ? registry.entries : [];
if (entries.some(function(entry) {
var providerStatus = String(entry && entry.lightRagStatus || '').trim();
return !entry?.deletedAtMs && !entry?.stale && !(entry?.indexedAtMs && entry?.lightRagDocId)
&& ['submitted', 'pending', 'processing', 'parsed', ''].includes(providerStatus);
&& ['submitted', 'pending', 'scanning', 'parsing', 'parse', 'parsed', 'analyzing', 'analyze', 'processing', 'process', ''].includes(providerStatus);
})) return true;
var groups = status && status.documents && status.documents.rawStatusGroups && typeof status.documents.rawStatusGroups === 'object'
? status.documents.rawStatusGroups
: {};
return ['pending', 'parse', 'parsed', 'analyze', 'processing', 'process'].some(function(key) {
return ['pending', 'scanning', 'parsing', 'parse', 'parsed', 'analyzing', 'analyze', 'processing', 'process'].some(function(key) {
return Number(groups[key] || 0) > 0;
});
}
function knowledgeRagPipelineHasInFlight(pipeline) {
pipeline = pipeline && typeof pipeline === 'object' ? pipeline : {};
return pipeline.busy === true
|| pipeline.scanning === true
|| pipeline.destructiveBusy === true
|| pipeline.requestPending === true
|| pipeline.pendingRequests === true
|| Number(pipeline.pendingEnqueues || 0) > 0;
}
function parseKnowledgeRagChunkProgress(message) {
var text = String(message || '');
var match = text.match(/Chunk\s+(\d+)\s+of\s+(\d+).*?\b(doc-[A-Za-z0-9_-]+?)(?=-(?:chunk|mm)-|\s|$)/i);
if (!match) return null;
var current = Number(match[1]);
var total = Number(match[2]);
if (!Number.isFinite(current) || !Number.isFinite(total) || current <= 0 || total <= 0) return null;
return {
current: Math.min(current, total),
total: total,
docId: String(match[3] || ''),
latestMessage: text
};
}
function normalizeKnowledgeRagPipelineEvent(payload) {
var input = payload && typeof payload === 'object' ? payload : {};
var latestMessage = String(input.latestMessage || input.latest_message || '');
var historyMessages = Array.isArray(input.historyMessages)
? input.historyMessages
: (Array.isArray(input.history_messages) ? input.history_messages : []);
var progress = input.progress && typeof input.progress === 'object'
? input.progress
: parseKnowledgeRagChunkProgress(latestMessage);
if (!progress) {
for (var index = historyMessages.length - 1; index >= 0; index -= 1) {
progress = parseKnowledgeRagChunkProgress(historyMessages[index]);
if (progress) break;
}
}
return {
ok: input.ok !== false,
busy: input.busy === true,
destructiveBusy: input.destructiveBusy === true || input.destructive_busy === true,
scanning: input.scanning === true,
scanningExclusive: input.scanningExclusive === true || input.scanning_exclusive === true,
requestPending: input.requestPending === true || input.request_pending === true,
pendingRequests: input.pendingRequests === true || input.pending_requests === true,
pendingEnqueues: Number(input.pendingEnqueues ?? input.pending_enqueues ?? 0) || 0,
docs: Number(input.docs || 0) || 0,
batches: Number(input.batches ?? input.batchs ?? 0) || 0,
currentBatch: Number(input.currentBatch ?? input.cur_batch ?? 0) || 0,
jobName: String(input.jobName || input.job_name || ''),
jobStart: input.jobStart || input.job_start || null,
latestMessage: latestMessage,
historyMessages: historyMessages.map(function(message) { return String(message || ''); }),
cancellationRequested: input.cancellationRequested === true || input.cancellation_requested === true,
cancellationReason: input.cancellationReason || input.cancellation_reason || null,
progress: progress || null
};
}
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;
@@ -1131,35 +1193,113 @@ export function createSidebarPageSettingsRuntime(context) {
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 + '%';
var current = Number(progress.current || 0);
var total = Number(progress.total || 0);
var determinate = Number.isFinite(current) && Number.isFinite(total) && current > 0 && total > 0;
var percent = determinate ? Math.max(0, Math.min(100, Math.round((current / total) * 100))) : 0;
var label = determinate ? current + ' / ' + total + ' · ' + percent + '%' : String(progress.label || '处理中');
var mode = determinate ? 'determinate' : 'indeterminate';
var ariaValues = determinate
? ' aria-valuemin="0" aria-valuemax="' + String(total) + '" aria-valuenow="' + String(current) + '"'
: ' aria-valuetext="' + escapeHtml(label) + '"';
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>' +
'<div class="mnote-knowledge-rag-source-progress" data-progress-mode="' + mode + '" role="progressbar"' + ariaValues + ' aria-label="' + escapeHtml('索引进度 ' + label) + '">' +
'<div><i' + (determinate ? ' style="width:' + String(percent) + '%"' : '') + '></i></div>' +
'<span>' + escapeHtml(label) + '</span>' +
'</div>';
}
function scheduleKnowledgeRagStatusBridge(reason) {
if (!knowledgeRagIsAvailable()) return;
if (knowledgeRagBridgeTimer) window.clearTimeout(knowledgeRagBridgeTimer);
knowledgeRagBridgeGeneration += 1;
knowledgeRagBridgeAttempt = 0;
runKnowledgeRagStatusBridge(knowledgeRagBridgeGeneration, reason || 'source-updated');
function knowledgeRagDocumentIsInFlight(doc) {
var status = String(doc && (doc.statusGroup || doc.status) || '').toLowerCase();
return ['pending', 'scanning', 'parsing', 'parse', 'parsed', 'analyzing', 'analyze', 'processing', 'process'].includes(status);
}
function runKnowledgeRagStatusBridge(generation, reason) {
if (generation !== knowledgeRagBridgeGeneration || !knowledgeRagIsAvailable()) return;
var delay = knowledgeRagBridgeDelays[Math.min(knowledgeRagBridgeAttempt, knowledgeRagBridgeDelays.length - 1)];
knowledgeRagBridgeTimer = window.setTimeout(async function() {
if (generation !== knowledgeRagBridgeGeneration || !knowledgeRagIsAvailable()) return;
knowledgeRagBridgeAttempt += 1;
await loadKnowledgeRagStatus(true, { silentLoading: true, notifyUpdated: true, reason: reason || 'bridge' });
var status = currentKnowledgeRagSummary().status || {};
if (knowledgeRagStatusHasInFlight(status) && knowledgeRagBridgeAttempt < knowledgeRagBridgeDelays.length) {
runKnowledgeRagStatusBridge(generation, reason || 'bridge');
function knowledgeRagProgressLabel(status, doc, fallback) {
var pipeline = status && status.pipeline && typeof status.pipeline === 'object' ? status.pipeline : {};
var latest = String(pipeline.latestMessage || '').trim();
if (latest) return '处理中';
var providerStatus = String(doc && (doc.statusGroup || doc.status) || fallback || '').toLowerCase();
if (providerStatus === 'pending') return '排队中';
if (providerStatus === 'scanning') return '扫描中';
if (providerStatus === 'parsing' || providerStatus === 'parse' || providerStatus === 'parsed') return '解析中';
if (providerStatus === 'analyzing' || providerStatus === 'analyze') return '分析中';
return '索引中';
}
function knowledgeRagIndeterminateProgress(status, doc, fallback) {
return { mode: 'indeterminate', label: knowledgeRagProgressLabel(status, doc, fallback) };
}
function stopKnowledgeRagPipelineWatcher() {
if (knowledgeRagPipelineEventSource && typeof knowledgeRagPipelineEventSource.close === 'function') {
knowledgeRagPipelineEventSource.close();
}
knowledgeRagPipelineEventSource = null;
knowledgeRagPipelineWatcherScopeKey = '';
}
function handleKnowledgeRagPipelineEvent(payload) {
var scopeKey = knowledgeRagScopeKey();
if (knowledgeRagPipelineWatcherScopeKey && knowledgeRagPipelineWatcherScopeKey !== scopeKey) return;
var current = currentKnowledgeRagSummary();
var currentStatus = current.status && typeof current.status === 'object' ? current.status : {};
var previousPipeline = currentStatus.pipeline && typeof currentStatus.pipeline === 'object' ? currentStatus.pipeline : {};
var previousInFlight = knowledgeRagPipelineHasInFlight(previousPipeline);
var pipeline = normalizeKnowledgeRagPipelineEvent(payload);
var nextStatus = Object.assign({}, currentStatus, { pipeline: pipeline });
pageUiState.knowledgeRagSummary = Object.assign({}, current, {
scopeKey: scopeKey,
loading: false,
error: '',
status: nextStatus
});
if (isKnowledgeRagSettingsOpen()) renderKnowledgeRagSettings();
var currentInFlight = knowledgeRagPipelineHasInFlight(pipeline);
if (currentInFlight) return;
var latest = String(pipeline.latestMessage || '').toLowerCase();
var finished = previousInFlight || latest.indexOf('completed') !== -1 || latest.indexOf('complete') !== -1;
if (!finished || knowledgeRagPipelineWatcherRefreshPending) return;
knowledgeRagPipelineWatcherRefreshPending = true;
void loadKnowledgeRagStatus(true, {
silentLoading: true,
notifyUpdated: true,
reason: 'pipeline-event-complete'
}).finally(function() {
knowledgeRagPipelineWatcherRefreshPending = false;
if (!knowledgeRagStatusHasInFlight(currentKnowledgeRagSummary().status || {})) {
stopKnowledgeRagPipelineWatcher();
}
}, delay);
});
}
function startKnowledgeRagPipelineWatcher(reason) {
document.documentElement.setAttribute('data-mnote-knowledge-rag-watcher-required', String(reason || 'source-updated'));
if (!knowledgeRagIsAvailable() || typeof window.EventSource !== 'function') return;
var scopeKey = knowledgeRagScopeKey();
if (knowledgeRagPipelineEventSource && knowledgeRagPipelineWatcherScopeKey === scopeKey) return;
stopKnowledgeRagPipelineWatcher();
var url = new URL('/api/knowledge-rag/pipeline-events', window.location.origin);
url.searchParams.set('workspaceId', resolveWorkspaceId(document.body));
url.searchParams.set('rootUri', currentRootUri());
var source = new EventSource(url.toString());
knowledgeRagPipelineEventSource = source;
knowledgeRagPipelineWatcherScopeKey = scopeKey;
source.addEventListener('pipeline_status', function(event) {
var payload = null;
try {
payload = JSON.parse(event.data || '{}');
} catch (_) {
return;
}
handleKnowledgeRagPipelineEvent(payload);
});
source.onerror = function() {
document.documentElement.setAttribute('data-mnote-knowledge-rag-watcher-error', 'true');
};
}
function scheduleKnowledgeRagStatusBridge(reason) {
startKnowledgeRagPipelineWatcher(reason);
}
function knowledgeRagUnmappedProviderRows(status, entries) {
@@ -1245,6 +1385,15 @@ export function createSidebarPageSettingsRuntime(context) {
return;
}
var pipelineProgress = knowledgeRagPipelineProgress(status || {});
var providerDocs = knowledgeRagProviderDocuments(status || {});
var providerDocsById = new Map();
var providerDocsByPath = new Map();
providerDocs.forEach(function(doc) {
var docId = String(doc && doc.id || '').trim();
var filePath = String(doc && doc.filePath || '').trim();
if (docId) providerDocsById.set(docId, doc);
if (filePath) providerDocsByPath.set(filePath, doc);
});
sourcesNode.innerHTML = items.map(function(row) {
if (row.kind === 'provider') {
var doc = row.doc || {};
@@ -1252,6 +1401,9 @@ export function createSidebarPageSettingsRuntime(context) {
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;
if (!providerProgress && !failedDoc && knowledgeRagDocumentIsInFlight(doc)) {
providerProgress = knowledgeRagIndeterminateProgress(status || {}, doc, docStatus);
}
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>' +
@@ -1286,6 +1438,10 @@ export function createSidebarPageSettingsRuntime(context) {
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;
var providerDoc = (docId && providerDocsById.get(docId)) || providerDocsByPath.get(String(entry && entry.lightRagFilePath || '').trim()) || null;
if (!entryProgress && !indexed && !failed && !stale && !deleted && !deletedDone && !deleting && (knowledgeRagDocumentIsInFlight(providerDoc) || docId || providerStatus)) {
entryProgress = knowledgeRagIndeterminateProgress(status || {}, providerDoc, providerStatus);
}
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(statusLabel) + '"></span>' +
@@ -1396,6 +1552,8 @@ export function createSidebarPageSettingsRuntime(context) {
}
var scopeKey = knowledgeRagScopeKey();
var current = currentKnowledgeRagSummary();
var previousStatus = current.status || {};
var previousDigest = knowledgeRagRegistryStatusDigest(previousStatus);
if (!force && current.scopeKey === scopeKey && !current.error && !current.loading) {
renderKnowledgeRagSettings();
return;
@@ -1419,14 +1577,20 @@ export function createSidebarPageSettingsRuntime(context) {
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_status_' + response.status);
}
var nextDigest = knowledgeRagRegistryStatusDigest(payload);
pageUiState.knowledgeRagSummary = {
scopeKey: scopeKey,
loading: false,
error: '',
status: payload
};
if (options.notifyUpdated) {
notifyKnowledgeRagSourceUpdated({ action: 'status-sync', reason: options.reason || 'bridge', status: payload });
if (knowledgeRagStatusHasInFlight(payload)) {
startKnowledgeRagPipelineWatcher(options.reason || 'status-in-flight');
} else if (!knowledgeRagPipelineWatcherRefreshPending) {
stopKnowledgeRagPipelineWatcher();
}
if (options.notifyUpdated || previousDigest !== nextDigest) {
notifyKnowledgeRagSourceUpdated({ action: 'status-sync', reason: options.reason || 'registry-status-changed', status: payload });
}
} catch (error) {
pageUiState.knowledgeRagSummary = {
@@ -1495,6 +1659,21 @@ export function createSidebarPageSettingsRuntime(context) {
}));
}
function knowledgeRagRegistryStatusDigest(status) {
var registry = status && status.registry && typeof status.registry === 'object' ? status.registry : {};
var entries = Array.isArray(registry.entries) ? registry.entries : [];
return entries.map(function(entry) {
return [
String(entry && entry.sourceRootRelativePath || ''),
String(entry && entry.lightRagDocId || ''),
String(entry && entry.lightRagStatus || ''),
String(entry && entry.indexedAtMs || ''),
String(entry && entry.deletedAtMs || ''),
entry && entry.stale ? 'stale' : 'fresh'
].join('|');
}).sort().join('\n');
}
function removeKnowledgeRagSourceInput(button) {
var row = button && button.closest ? button.closest('[data-knowledge-rag-source-input-row]') : null;
if (row instanceof HTMLElement) row.remove();