feat(rag): harden post-LightRAG runtime
Retire legacy OCR/media/evidence fallbacks, add local-folder event bus and Page Aggregate guards, and archive completed design checklists. Validation: cargo test -p mnote-web -- --test-threads=1; cargo test --workspace -- --test-threads=1; git diff --check; codegraph sync .; codegraph_status.
This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
(function(){
|
||||
'use strict';
|
||||
|
||||
if (window.__mnoteLocalFolderEventBus) {
|
||||
return;
|
||||
}
|
||||
|
||||
var connections = new Map();
|
||||
var sidebarRefreshQueues = new Map();
|
||||
var lastSource = '';
|
||||
var lastReason = '';
|
||||
|
||||
function root() {
|
||||
return document.documentElement;
|
||||
}
|
||||
|
||||
function setDiagnostics(source, reason) {
|
||||
lastSource = source || lastSource || '';
|
||||
lastReason = reason || lastReason || '';
|
||||
root().setAttribute('data-mnote-local-folder-event-bus', 'ready');
|
||||
root().setAttribute('data-mnote-local-folder-event-bus-connections', String(connections.size));
|
||||
root().setAttribute('data-mnote-local-folder-event-bus-last-source', lastSource);
|
||||
root().setAttribute('data-mnote-local-folder-event-bus-last-reason', lastReason);
|
||||
}
|
||||
|
||||
function normalizeRootUri(rootUri) {
|
||||
return String(rootUri || '').trim();
|
||||
}
|
||||
|
||||
function normalizeWorkspaceId(workspaceId) {
|
||||
return String(workspaceId || 'default').trim() || 'default';
|
||||
}
|
||||
|
||||
function emit(name, detail) {
|
||||
window.dispatchEvent(new CustomEvent(name, { detail: detail || {} }));
|
||||
}
|
||||
|
||||
function parseEventPayload(event) {
|
||||
try {
|
||||
return JSON.parse((event && event.data) || '{}') || {};
|
||||
} catch (_) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function revisionOf(payload, event) {
|
||||
return payload.revision || payload.cursor || (event && event.lastEventId) || null;
|
||||
}
|
||||
|
||||
function arrayOf(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value.filter(Boolean).map(String);
|
||||
return [String(value)].filter(Boolean);
|
||||
}
|
||||
|
||||
function pathArrayOf(value) {
|
||||
if (!value) return [];
|
||||
var list = Array.isArray(value) ? value : [value];
|
||||
return list.map(function(item) {
|
||||
if (typeof item === 'string') return item.trim();
|
||||
if (!item || typeof item !== 'object') return '';
|
||||
return String(item.relativePath || item.relative_path || item.path || item.sourcePath || item.source_path || '').trim();
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
function collectChangedPaths(payload) {
|
||||
var paths = []
|
||||
.concat(pathArrayOf(payload.changedPaths))
|
||||
.concat(pathArrayOf(payload.changed_paths))
|
||||
.concat(pathArrayOf(payload.paths));
|
||||
var events = Array.isArray(payload.events) ? payload.events : [];
|
||||
events.forEach(function(item) {
|
||||
if (!item || typeof item !== 'object') return;
|
||||
paths = paths
|
||||
.concat(arrayOf(item.path))
|
||||
.concat(arrayOf(item.relativePath))
|
||||
.concat(arrayOf(item.relative_path))
|
||||
.concat(arrayOf(item.sourcePath))
|
||||
.concat(arrayOf(item.source_path));
|
||||
});
|
||||
return Array.from(new Set(paths));
|
||||
}
|
||||
|
||||
function collectChangedPathItems(payload, reason) {
|
||||
var raw = []
|
||||
.concat(Array.isArray(payload.changedPaths) ? payload.changedPaths : [])
|
||||
.concat(Array.isArray(payload.changed_paths) ? payload.changed_paths : [])
|
||||
.concat(Array.isArray(payload.paths) ? payload.paths : [])
|
||||
.concat(Array.isArray(payload.events) ? payload.events : []);
|
||||
var seen = new Set();
|
||||
var items = [];
|
||||
raw.forEach(function(item) {
|
||||
var relativePath = typeof item === 'string'
|
||||
? item.trim()
|
||||
: String(item && (item.relativePath || item.relative_path || item.path || item.sourcePath || item.source_path || '') || '').trim();
|
||||
if (!relativePath || seen.has(relativePath)) return;
|
||||
seen.add(relativePath);
|
||||
items.push({
|
||||
relativePath: relativePath,
|
||||
reason: reason || 'event-bus',
|
||||
kind: typeof item === 'object' && item ? String(item.kind || '') : '',
|
||||
eventKind: typeof item === 'object' && item ? String(item.eventKind || item.event_kind || item.changeType || item.change_type || '') : ''
|
||||
});
|
||||
});
|
||||
if (items.length) return items;
|
||||
return pathItems(collectChangedPaths(payload), reason);
|
||||
}
|
||||
|
||||
function collectAffectedParents(payload) {
|
||||
var parents = []
|
||||
.concat(pathArrayOf(payload.affectedParents))
|
||||
.concat(pathArrayOf(payload.affected_parents))
|
||||
.concat(pathArrayOf(payload.parentRelativePaths))
|
||||
.concat(pathArrayOf(payload.parent_relative_paths));
|
||||
return Array.from(new Set(parents));
|
||||
}
|
||||
|
||||
function pathItems(paths, reason) {
|
||||
return (paths || []).map(function(relativePath) {
|
||||
return {
|
||||
relativePath: String(relativePath || '').trim(),
|
||||
reason: reason || 'event-bus'
|
||||
};
|
||||
}).filter(function(item) { return item.relativePath || item.relativePath === ''; });
|
||||
}
|
||||
|
||||
function queueSidebarRefresh(detail) {
|
||||
var key = String(detail.rootUri || '').trim();
|
||||
if (!key) return;
|
||||
var queue = sidebarRefreshQueues.get(key);
|
||||
if (!queue) {
|
||||
queue = {
|
||||
rootUri: detail.rootUri,
|
||||
workspaceId: detail.workspaceId,
|
||||
revisions: new Set(),
|
||||
reasons: new Set(),
|
||||
changedPaths: new Map(),
|
||||
affectedParents: new Set(),
|
||||
resyncRequired: false,
|
||||
timer: 0
|
||||
};
|
||||
sidebarRefreshQueues.set(key, queue);
|
||||
}
|
||||
if (detail.revision) queue.revisions.add(String(detail.revision));
|
||||
if (detail.reason) queue.reasons.add(String(detail.reason));
|
||||
(detail.changedPaths || []).forEach(function(item) {
|
||||
var path = typeof item === 'string' ? item : String(item && (item.relativePath || item.relative_path || '') || '');
|
||||
if (!path && path !== '') return;
|
||||
var existing = queue.changedPaths.get(path) || { relativePath: path };
|
||||
queue.changedPaths.set(path, {
|
||||
relativePath: path,
|
||||
reason: String((item && item.reason) || existing.reason || detail.reason || 'event-bus-orchestrated'),
|
||||
kind: String((item && item.kind) || existing.kind || ''),
|
||||
eventKind: String((item && item.eventKind) || (item && item.event_kind) || existing.eventKind || '')
|
||||
});
|
||||
});
|
||||
(detail.affectedParents || []).forEach(function(item) {
|
||||
var parent = typeof item === 'string' ? item : String(item && (item.relativePath || item.relative_path || '') || '');
|
||||
if (parent || parent === '') queue.affectedParents.add(parent);
|
||||
});
|
||||
queue.resyncRequired = queue.resyncRequired || detail.resyncRequired === true;
|
||||
if (!queue.timer) {
|
||||
queue.timer = window.setTimeout(function() {
|
||||
flushSidebarRefresh(key);
|
||||
}, 0);
|
||||
}
|
||||
root().setAttribute('data-mnote-local-folder-event-bus-sidebar-refresh-pending', String(queue.affectedParents.size));
|
||||
}
|
||||
|
||||
function flushSidebarRefresh(key) {
|
||||
var queue = sidebarRefreshQueues.get(key);
|
||||
if (!queue) return;
|
||||
sidebarRefreshQueues.delete(key);
|
||||
var reasons = Array.from(queue.reasons);
|
||||
var detail = {
|
||||
schema: 'mnote.local_folder.event_bus.sidebar_refresh.v1',
|
||||
source: 'event_bus_orchestrator',
|
||||
reason: reasons.join(',') || 'watch_batch',
|
||||
rootUri: queue.rootUri,
|
||||
workspaceId: queue.workspaceId,
|
||||
revision: Array.from(queue.revisions).pop() || null,
|
||||
reasons: reasons,
|
||||
changedPaths: Array.from(queue.changedPaths.values()),
|
||||
affectedParents: pathItems(Array.from(queue.affectedParents), 'event-bus-orchestrated'),
|
||||
resyncRequired: queue.resyncRequired,
|
||||
viaEventBus: true
|
||||
};
|
||||
root().setAttribute('data-mnote-local-folder-event-bus-sidebar-refresh-applied', detail.reason);
|
||||
root().setAttribute('data-mnote-local-folder-event-bus-sidebar-refresh-parents', String(detail.affectedParents.length));
|
||||
emit('mnote:local-folder:sidebar-refresh-requested', detail);
|
||||
}
|
||||
|
||||
function dispatchWatchBatch(entry, event, payload, meta) {
|
||||
var revision = revisionOf(payload, event);
|
||||
var changedPaths = collectChangedPaths(payload);
|
||||
var affectedParents = collectAffectedParents(payload);
|
||||
var source = String((meta && meta.source) || payload.source || 'watcher_sse').trim() || 'watcher_sse';
|
||||
var reason = String((meta && meta.reason) || payload.reason || 'watch_batch').trim() || 'watch_batch';
|
||||
var resyncRequired = payload.fallbackResync === true
|
||||
|| payload.requiresResync === true
|
||||
|| payload.resyncRequired === true
|
||||
|| payload.resync_required === true;
|
||||
var detail = {
|
||||
schema: 'mnote.local_folder.event_bus.watch_batch.v1',
|
||||
source: source,
|
||||
reason: reason,
|
||||
rootUri: entry.rootUri,
|
||||
workspaceId: entry.workspaceId,
|
||||
revision: revision,
|
||||
payload: payload,
|
||||
bootstrap: entry.bootstrap || null,
|
||||
changedPaths: collectChangedPathItems(payload, reason),
|
||||
affectedParents: pathItems(affectedParents, reason),
|
||||
resyncRequired: resyncRequired,
|
||||
viaEventBus: true
|
||||
};
|
||||
|
||||
setDiagnostics(source, reason);
|
||||
root().setAttribute('data-mnote-tree-live-revision', String(revision || ''));
|
||||
emit('mnote:local-folder:watch-batch', detail);
|
||||
emit('tree:local-folder-watch-batch', detail);
|
||||
|
||||
if (affectedParents.length > 0) {
|
||||
emit('mnote:local-folder:filetree-parent-changed', detail);
|
||||
}
|
||||
if (changedPaths.length > 0) {
|
||||
emit('mnote:local-folder:document-changed', detail);
|
||||
emit('mnote:local-folder:resource-changed', detail);
|
||||
emit('mnote:local-folder:knowledge-rag-source-updated', detail);
|
||||
}
|
||||
queueSidebarRefresh(detail);
|
||||
if (resyncRequired) {
|
||||
emit('mnote:local-folder:resync-required', detail);
|
||||
}
|
||||
}
|
||||
|
||||
function closeEntry(entry) {
|
||||
if (!entry || !entry.source || typeof entry.source.close !== 'function') return;
|
||||
entry.source.close();
|
||||
}
|
||||
|
||||
function startLocalFolderWatcher(options) {
|
||||
var rootUri = normalizeRootUri(options && options.rootUri);
|
||||
if (!rootUri || typeof window.EventSource !== 'function') return null;
|
||||
var workspaceId = normalizeWorkspaceId(options && options.workspaceId);
|
||||
var key = rootUri;
|
||||
if (connections.has(key)) {
|
||||
var existing = connections.get(key);
|
||||
if ((!existing.workspaceId || existing.workspaceId === 'default') && workspaceId && workspaceId !== 'default') {
|
||||
existing.workspaceId = workspaceId;
|
||||
if (existing.handle) existing.handle.workspaceId = workspaceId;
|
||||
}
|
||||
setDiagnostics(existing.lastSource || 'watcher_sse', 'reuse_connection');
|
||||
return existing.handle;
|
||||
}
|
||||
|
||||
var url = new URL('/api/local-folder/events', window.location.origin);
|
||||
url.searchParams.set('rootUri', rootUri);
|
||||
url.searchParams.set('treeLive', 'true');
|
||||
|
||||
var eventSource = new EventSource(url.toString());
|
||||
var entry = {
|
||||
key: key,
|
||||
rootUri: rootUri,
|
||||
workspaceId: workspaceId,
|
||||
bootstrap: (options && options.bootstrap) || null,
|
||||
source: eventSource,
|
||||
lastSource: 'watcher_sse',
|
||||
close: function() {
|
||||
connections.delete(key);
|
||||
closeEntry(entry);
|
||||
setDiagnostics('watcher_sse', 'closed');
|
||||
}
|
||||
};
|
||||
entry.handle = {
|
||||
key: key,
|
||||
rootUri: rootUri,
|
||||
workspaceId: workspaceId,
|
||||
source: eventSource,
|
||||
close: entry.close
|
||||
};
|
||||
connections.set(key, entry);
|
||||
setDiagnostics('watcher_sse', 'connect');
|
||||
emit('mnote:local-folder:event-bus-ready', {
|
||||
schema: 'mnote.local_folder.event_bus.ready.v1',
|
||||
source: 'watcher_sse',
|
||||
reason: 'connect',
|
||||
rootUri: rootUri,
|
||||
workspaceId: workspaceId,
|
||||
connections: connections.size
|
||||
});
|
||||
|
||||
eventSource.addEventListener('open', function() {
|
||||
setDiagnostics('watcher_sse', 'open');
|
||||
});
|
||||
eventSource.addEventListener('watch_batch', function(event) {
|
||||
dispatchWatchBatch(entry, event, parseEventPayload(event), {
|
||||
source: 'watcher_sse',
|
||||
reason: 'watch_batch'
|
||||
});
|
||||
});
|
||||
eventSource.addEventListener('tree_error', function(event) {
|
||||
var payload = parseEventPayload(event);
|
||||
setDiagnostics('watcher_sse', 'tree_error');
|
||||
emit('tree:error', { payload: payload, bootstrap: entry.bootstrap || null, viaEventBus: true });
|
||||
});
|
||||
eventSource.onerror = function() {
|
||||
setDiagnostics('watcher_sse', 'error');
|
||||
};
|
||||
|
||||
return entry.handle;
|
||||
}
|
||||
|
||||
function emitSyntheticWatchBatch(detail) {
|
||||
var payload = detail && detail.payload ? detail.payload : (detail || {});
|
||||
var rootUri = normalizeRootUri(detail && detail.rootUri);
|
||||
var workspaceId = normalizeWorkspaceId(detail && detail.workspaceId);
|
||||
var entry = {
|
||||
rootUri: rootUri,
|
||||
workspaceId: workspaceId,
|
||||
bootstrap: (detail && detail.bootstrap) || null
|
||||
};
|
||||
dispatchWatchBatch(entry, { lastEventId: detail && detail.revision }, payload, {
|
||||
source: (detail && detail.source) || 'synthetic',
|
||||
reason: (detail && detail.reason) || payload.source || 'synthetic_watch_batch'
|
||||
});
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
Array.from(connections.values()).forEach(function(entry) {
|
||||
closeEntry(entry);
|
||||
});
|
||||
connections.clear();
|
||||
setDiagnostics('watcher_sse', 'closed');
|
||||
}
|
||||
|
||||
window.__mnoteLocalFolderEventBus = {
|
||||
startLocalFolderWatcher: startLocalFolderWatcher,
|
||||
emitSyntheticWatchBatch: emitSyntheticWatchBatch,
|
||||
closeAll: closeAll,
|
||||
flushSidebarRefresh: function(rootUri) {
|
||||
flushSidebarRefresh(normalizeRootUri(rootUri));
|
||||
},
|
||||
connectionCount: function() { return connections.size; },
|
||||
diagnostics: function() {
|
||||
return {
|
||||
connections: connections.size,
|
||||
lastSource: lastSource,
|
||||
lastReason: lastReason
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
setDiagnostics('', 'ready');
|
||||
})();
|
||||
Reference in New Issue
Block a user