chore: checkpoint turso and ai runtime work

This commit is contained in:
Agent Board
2026-07-03 23:20:16 +08:00
parent a75b3d11f9
commit 36d027a4a1
67 changed files with 4224 additions and 914 deletions
@@ -1469,7 +1469,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const isKnowledgeRagSourceEntry = (entry) => {
if (!entry || String(entry.sourceKind || '').trim() !== 'local_folder') return false;
if (!String(entry.rootUri || '').trim() || !String(entry.path || '').trim()) return false;
return entry.kind === 'image' || entry.kind === 'pdf';
return ['image', 'pdf', 'markdown', 'text', 'code'].includes(entry.kind);
};
const knowledgeRagProvider = () => {
@@ -1487,11 +1487,11 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const statusNode = entry.panel.querySelector('[data-mnote-knowledge-rag-status-text]');
if (statusNode instanceof HTMLElement) {
statusNode.textContent = message || (
normalizedStatus === 'done' ? '资料库已索引'
: normalizedStatus === 'running' ? '资料库索引中'
: normalizedStatus === 'failed' ? '资料库索引失败'
: normalizedStatus === 'stale' ? '资料库需更新'
: '资料库未索引'
normalizedStatus === 'done' ? '知识库已索引'
: normalizedStatus === 'running' ? '知识库索引中'
: normalizedStatus === 'failed' ? '知识库索引失败'
: normalizedStatus === 'stale' ? '知识库需更新'
: '知识库未索引'
);
}
const openButton = entry.panel.querySelector('[data-mnote-knowledge-rag-action="open"]');
@@ -1523,7 +1523,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
artifactRootRelativePath: '',
provider: knowledgeRagProvider(),
status,
stageLabel: status === 'done' ? '资料库已索引' : status === 'failed' ? '资料库需重建' : '资料库索引中',
stageLabel: status === 'done' ? '知识库已索引' : status === 'failed' ? '知识库需重建' : '知识库索引中',
stale: registryEntry.stale === true,
updatedAtMs: Number(registryEntry.updatedAtMs || registryEntry.updated_at_ms || Date.now()),
finishedAtMs: status === 'done' ? Number(registryEntry.indexedAtMs || registryEntry.indexed_at_ms || Date.now()) : null,
@@ -1750,7 +1750,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
.filter((node) => node instanceof HTMLButtonElement);
taskToggles.forEach((toggle) => {
const opensSettings = toggle.getAttribute('data-mnote-action') === 'open-knowledge-rag-settings';
const label = runningCount > 0 ? `${runningCount}资料库索引处理中,打开资料库设置` : (jobs.length > 0 ? `${jobs.length}资料库任务,打开资料库设置` : '资料库设置');
const label = runningCount > 0 ? `${runningCount}知识库索引处理中,打开知识库设置` : (jobs.length > 0 ? `${jobs.length}知识库任务,打开知识库设置` : '知识库设置');
const taskLabel = runningCount > 0 ? `${runningCount} 个后台任务正在运行` : (jobs.length > 0 ? `${jobs.length} 个后台任务` : '后台任务');
toggle.setAttribute('title', opensSettings ? label : taskLabel);
toggle.setAttribute('aria-label', opensSettings ? label : taskLabel);
@@ -1811,7 +1811,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
row.setAttribute('data-mnote-knowledge-rag-task-row', String(job.sourceRootRelativePath || ''));
row.setAttribute('data-mnote-knowledge-rag-task-status', String(job.status || ''));
row.setAttribute('data-mnote-knowledge-rag-task-category', knowledgeRagTaskCategory(job));
const title = String(job.sourceRootRelativePath || '').split('/').filter(Boolean).pop() || '资料库索引';
const title = String(job.sourceRootRelativePath || '').split('/').filter(Boolean).pop() || '知识库索引';
const category = knowledgeRagTaskCategory(job);
const progress = knowledgeRagTaskProgress(job);
row.innerHTML = '<div class="mnote-knowledge-rag-task-main"><div class="mnote-knowledge-rag-task-title-line"><strong></strong><em></em></div><span></span><div class="mnote-knowledge-rag-task-progress" role="progressbar"><i></i></div></div><div class="mnote-knowledge-rag-task-actions"><button type="button" data-mnote-knowledge-rag-task-open>打开</button><button type="button" data-mnote-knowledge-rag-task-retry>重试</button><button type="button" data-mnote-knowledge-rag-task-clear>清除</button><button type="button" data-mnote-knowledge-rag-task-delete>删除</button></div>';
@@ -1909,9 +1909,9 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const entryRootUri = String(entry.rootUri || '').trim();
if (entryRootUri) knowledgeRagTaskState.rootUri = entryRootUri;
const startedAt = Date.now();
const pendingJob = knowledgeRagTaskSnapshotFromEntry(entry, 'running', knowledgeRagProvider(), '资料库索引中', startedAt);
const pendingJob = knowledgeRagTaskSnapshotFromEntry(entry, 'running', knowledgeRagProvider(), '知识库索引中', startedAt);
pendingJob.taskKind = 'knowledge_rag';
setKnowledgeRagStatus(entry, 'running', '资料库索引中', pendingJob);
setKnowledgeRagStatus(entry, 'running', '知识库索引中', pendingJob);
updateKnowledgeRagTaskState(pendingJob);
const workspaceId = String(entry.workspaceId || currentWebShellWorkspaceId() || '').trim();
const body = {
@@ -1945,12 +1945,12 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const job = knowledgeRagJobFromRegistryEntry(registryEntry, entryRootUri) || {
...pendingJob,
status: payload.retryRequired ? 'running' : 'done',
stageLabel: payload.retryRequired ? '知识库忙碌,等待重试' : '资料库已提交索引',
stageLabel: payload.retryRequired ? '知识库忙碌,等待重试' : '知识库已提交索引',
updatedAtMs: Date.now(),
finishedAtMs: payload.retryRequired ? null : Date.now(),
};
const displayStatus = knowledgeRagDisplayStatus(job, 'done');
setKnowledgeRagStatus(entry, displayStatus, displayStatus === 'stale' ? '资料库需更新' : statusTextForKnowledgeRagTask(job), job);
setKnowledgeRagStatus(entry, displayStatus, displayStatus === 'stale' ? '知识库需更新' : statusTextForKnowledgeRagTask(job), job);
if (job) updateKnowledgeRagTaskState(job);
window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-source-updated', {
detail: { rootUri: entryRootUri, workspaceId, sourceRootRelativePath: String(entry.path || '').trim(), result: payload }
@@ -2003,6 +2003,25 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const workspaceId = currentWebShellWorkspaceId();
const seen = new Set();
const candidates = [];
const rawDocumentId = String(documentId || '').trim();
const markdownPath = rawDocumentId.startsWith('local-md:') ? localMarkdownRelativePathFromDocumentId(rawDocumentId) : '';
if (markdownPath) {
seen.add(markdownPath);
const title = markdownPath.split('/').filter(Boolean).pop() || markdownPath;
candidates.push({
sourceKind: 'local_folder',
rootUri,
path: markdownPath,
kind: 'markdown',
title,
fileName: title,
documentId,
ownerDocumentId: documentId,
workspaceId,
objectIdentity: `local-md:${markdownPath}`,
assetId: `local-md:${markdownPath}`,
});
}
document.querySelectorAll(`.document-pane[data-pane-role="${role}"] .ProseMirror img`).forEach((image) => {
if (!(image instanceof HTMLImageElement)) return;
const sourcePath = localFileOpenPathFromTiptapHref(image.getAttribute('src') || image.src || '');
@@ -2146,13 +2165,13 @@ export const createResourceTabRuntime = (dependencies = {}) => {
});
});
}
setKnowledgeRagStatus(entry, 'idle', '资料库未索引', null);
setKnowledgeRagStatus(entry, 'idle', '知识库未索引', null);
ensureKnowledgeRagTaskDock();
void loadKnowledgeRagTasks(entry.rootUri).catch(() => undefined);
void readKnowledgeRagStatus(entry).then((job) => {
if (!job) return;
const displayStatus = knowledgeRagDisplayStatus(job, 'done');
setKnowledgeRagStatus(entry, displayStatus, displayStatus === 'stale' ? '资料库需更新' : statusTextForKnowledgeRagTask(job), job);
setKnowledgeRagStatus(entry, displayStatus, displayStatus === 'stale' ? '知识库需更新' : statusTextForKnowledgeRagTask(job), job);
updateKnowledgeRagTaskState(job);
}).catch(() => undefined);
};
@@ -151,6 +151,10 @@ export const createDocumentSessionRuntime = (dependencies = {}) => {
relativePath: String(item.relativePath || item.relative_path || item.path || '').trim(),
documentId: String(item.documentId || item.document_id || '').trim(),
eventKind: String(item.eventKind || item.event_kind || item.changeType || item.change_type || '').trim(),
selfWriteEcho: Boolean(item.selfWriteEcho || item.self_write_echo),
fileVersion: String(item.fileVersion || item.file_version || item.observedFileVersion || item.observed_file_version || '').trim(),
observedFileVersion: String(item.observedFileVersion || item.observed_file_version || '').trim(),
bufferFileVersion: String(item.bufferFileVersion || item.buffer_file_version || '').trim(),
};
}).filter(Boolean);
}
@@ -1377,11 +1381,17 @@ export const createDocumentSessionRuntime = (dependencies = {}) => {
const relativePath = typeof item.relativePath === 'string' ? item.relativePath.trim() : '';
if (targetSession.sessionKind === 'resource') {
if (!relativePath || relativePath !== resourceWatchPath(targetSession)) return;
if (targetSession.saving) {
if (item.selfWriteEcho === true || targetSession.saving && shouldSuppressLocalFolderSelfChange(targetSession.documentId, String(item.eventKind || ''))) {
targetSession.externalChangePending = false;
targetSession.lastSelfSaveSignalAt = Date.now();
return;
}
if (targetSession.hasExternalConflict || sessionHasRecentLocalInput(targetSession) || targetSession.dirty || targetSession.saveTimer || targetSession.saving) {
targetSession.lastExternalChangeSignalAt = Date.now();
targetSession.externalChangePending = true;
markSessionExternalConflict(targetSession, '当前本地资源文件已在外部更新,请刷新或保存前先处理冲突');
return;
}
targetSession.lastExternalChangeSignalAt = Date.now();
targetSession.externalChangePending = true;
scheduleSessionExternalRefresh(targetSession, 'mnote-web-local-resource-watch');
@@ -1394,14 +1404,30 @@ export const createDocumentSessionRuntime = (dependencies = {}) => {
|| (!documentId && relativePath && relativeTarget && relativePath === relativeTarget)
);
if (!targetsCurrentDocument) return;
if (item.selfWriteEcho === true) {
targetSession.externalChangePending = false;
targetSession.lastSelfSaveSignalAt = Date.now();
if (item.fileVersion) {
targetSession.fileVersion = item.fileVersion;
targetSession.conflictDetectionKey = item.fileVersion;
targetSession.lastExternalConflictDetectionKey = item.fileVersion;
}
return;
}
if (shouldSuppressLocalFolderSelfChange(targetSession.documentId, eventKind)) {
targetSession.externalChangePending = false;
targetSession.lastSelfSaveSignalAt = Date.now();
return;
}
if (targetSession.saving) {
targetSession.externalChangePending = false;
targetSession.lastSelfSaveSignalAt = Date.now();
if (targetSession.hasExternalConflict || sessionHasRecentLocalInput(targetSession) || targetSession.dirty || targetSession.saveTimer || targetSession.saving) {
targetSession.lastExternalChangeSignalAt = Date.now();
targetSession.externalChangePending = true;
markSessionExternalConflict(targetSession, conflictMessageFromEnvelope(targetSession, {
message: externalConflictMessage,
dirtyState: targetSession.dirty ? 'Dirty' : (targetSession.saving ? 'Saving' : 'Clean'),
fileVersion: item.fileVersion || null,
externalActor: 'external-editor',
}));
return;
}
targetSession.lastExternalChangeSignalAt = Date.now();
@@ -7,8 +7,16 @@
var connections = new Map();
var sidebarRefreshQueues = new Map();
var fileChangeQueues = new Map();
var lastSource = '';
var lastReason = '';
var lastFileChangeDiagnostics = {
source: '',
changedCount: 0,
droppedCount: 0,
lastReaction: '',
lastBatchSchema: ''
};
function root() {
return document.documentElement;
@@ -21,6 +29,11 @@
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);
root().setAttribute('data-mnote-file-change-service', 'ready');
root().setAttribute('data-mnote-file-change-service-last-source', lastFileChangeDiagnostics.source || lastSource);
root().setAttribute('data-mnote-file-change-service-last-count', String(lastFileChangeDiagnostics.changedCount || 0));
root().setAttribute('data-mnote-file-change-service-dropped-count', String(lastFileChangeDiagnostics.droppedCount || 0));
root().setAttribute('data-mnote-file-change-service-last-reaction', lastFileChangeDiagnostics.lastReaction || '');
}
function normalizeRootUri(rootUri) {
@@ -53,6 +66,375 @@
return [String(value)].filter(Boolean);
}
function safeNow() {
return Date.now ? Date.now() : new Date().getTime();
}
function decodeFileUriPath(rootUri) {
var value = String(rootUri || '').trim();
if (!value) return '';
try {
var url = new URL(value);
if (url.protocol !== 'file:') return '';
return decodeURIComponent(url.pathname || '').replace(/\/+$/, '');
} catch (_) {
return '';
}
}
function normalizeSlashes(value) {
return String(value || '').trim().replace(/\\/g, '/');
}
function rootRelativePath(rawPath, rootUri) {
var value = normalizeSlashes(rawPath).replace(/^file:\/\//, '');
if (!value) return '';
if (/^[A-Za-z]:\//.test(value) || value.charAt(0) === '/') {
var rootPath = normalizeSlashes(decodeFileUriPath(rootUri));
if (rootPath && (value === rootPath || value.indexOf(rootPath + '/') === 0)) {
return value.slice(rootPath.length).replace(/^\/+/, '');
}
return '';
}
return value.replace(/^\/+/, '');
}
function localMarkdownDocumentId(relativePath) {
var normalized = normalizeSlashes(relativePath).replace(/^\/+/, '');
if (!/\.md(?:own)?$/i.test(normalized)) return '';
return 'local-md:' + normalized.replace(/\//g, '~2F');
}
function normalizeChangeType(item, fallback) {
var raw = String(
(item && (item.changeType || item.change_type || item.eventKind || item.event_kind || item.kind))
|| fallback
|| 'modified'
).trim();
if (/remove|delete/i.test(raw)) return 'deleted';
if (/create/i.test(raw)) return 'created';
if (/rename|name/i.test(raw)) return 'renamed';
return 'modified';
}
function resourceKindForPath(relativePath) {
return /\.md(?:own)?$/i.test(String(relativePath || '')) ? 'markdown' : 'resource';
}
function normalizeFileChangeItem(item, context) {
var source = String((context && context.source) || (item && item.source) || 'watcher').trim() || 'watcher';
var rootUri = normalizeRootUri((context && context.rootUri) || (item && item.rootUri));
var workspaceId = normalizeWorkspaceId((context && context.workspaceId) || (item && item.workspaceId));
var rawPath = typeof item === 'string'
? item
: String(item && (
item.relativePath
|| item.relative_path
|| item.path
|| item.sourcePath
|| item.source_path
|| item.filePath
|| item.file_path
) || '');
var relativePath = rootRelativePath(rawPath, rootUri);
if (!relativePath) {
return { dropped: true, reason: 'outside_root_or_empty_path', rawPath: rawPath };
}
var eventKind = String(item && (item.eventKind || item.event_kind || item.kind || item.changeType || item.change_type) || '').trim();
var documentId = String(item && (item.documentId || item.document_id) || '').trim() || localMarkdownDocumentId(relativePath);
var observedFileVersion = String(item && (item.observedFileVersion || item.observed_file_version || '') || '').trim();
var bufferFileVersion = String(item && (item.bufferFileVersion || item.buffer_file_version || '') || '').trim();
var fileVersion = String(item && (item.fileVersion || item.file_version || '') || '').trim()
|| observedFileVersion
|| bufferFileVersion
|| String(item && item.revision || (context && context.revision) || '').trim();
return {
schema: 'mnote.file_change.v1',
rootUri: rootUri,
workspaceId: workspaceId,
relativePath: relativePath,
documentId: documentId,
resourceKind: String(item && (item.resourceKind || item.resource_kind) || resourceKindForPath(relativePath)),
changeType: normalizeChangeType(item, eventKind),
eventKind: eventKind,
fileVersion: fileVersion,
source: source,
selfWriteEcho: Boolean(item && (item.selfWriteEcho || item.self_write_echo)),
observedFileVersion: observedFileVersion,
bufferFileVersion: bufferFileVersion,
lastWriteIntentId: String(item && (item.lastWriteIntentId || item.last_write_intent_id || '') || '').trim(),
lastSaveOperationId: String(item && (item.lastSaveOperationId || item.last_save_operation_id || '') || '').trim(),
createdAt: Number(item && (item.createdAt || item.created_at) || safeNow())
};
}
function standardBatchToCompatPayload(batch) {
return {
schema: 'mnote.local_folder.watch_batch.v1',
kind: 'watch_batch',
sourceKind: 'local_folder',
source: batch.source,
rootUri: batch.rootUri,
workspaceId: batch.workspaceId,
revision: batch.revision,
changedPaths: batch.changes.map(function(change) {
return {
relativePath: change.relativePath,
documentId: change.documentId,
kind: change.eventKind || change.changeType,
eventKind: change.eventKind || change.changeType,
changeType: change.changeType,
revision: change.fileVersion || batch.revision,
fileVersion: change.fileVersion,
observedFileVersion: change.observedFileVersion,
bufferFileVersion: change.bufferFileVersion,
lastWriteIntentId: change.lastWriteIntentId,
lastSaveOperationId: change.lastSaveOperationId,
selfWriteEcho: change.selfWriteEcho,
source: change.source
};
}),
affectedParents: batch.affectedParents,
eventKinds: Array.from(new Set(batch.changes.map(function(change) {
return change.eventKind || change.changeType || '';
}).filter(Boolean))),
fallbackResync: batch.resyncRequired === true
};
}
function normalizeFileChangeBatch(entry, event, payload, meta) {
var revision = revisionOf(payload, event);
var source = String((meta && meta.source) || payload.source || payload.sourceKind || 'watcher_sse').trim() || 'watcher_sse';
var reason = String((meta && meta.reason) || payload.reason || 'watch_batch').trim() || 'watch_batch';
var rootUri = normalizeRootUri((entry && entry.rootUri) || payload.rootUri);
var workspaceId = normalizeWorkspaceId((entry && entry.workspaceId) || payload.workspaceId);
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 : []);
if (!raw.length && payload.relativePath) raw.push(payload);
var dropped = [];
var changes = [];
raw.forEach(function(item) {
var normalized = normalizeFileChangeItem(item, {
source: source,
rootUri: rootUri,
workspaceId: workspaceId,
revision: revision
});
if (normalized && normalized.dropped) {
dropped.push(normalized);
} else if (normalized) {
changes.push(normalized);
}
});
var affectedParents = collectAffectedParents(payload).map(function(parent) {
return { relativePath: parent, reason: reason };
});
changes.forEach(function(change) {
var parent = change.relativePath.indexOf('/') >= 0 ? change.relativePath.split('/').slice(0, -1).join('/') : '';
if (!affectedParents.some(function(item) { return item.relativePath === parent; })) {
affectedParents.push({ relativePath: parent, reason: reason });
}
});
return {
schema: 'mnote.file_change_batch.v1',
rootUri: rootUri,
workspaceId: workspaceId,
source: source,
reason: reason,
changes: changes,
affectedParents: affectedParents,
revision: revision,
dropped: dropped,
resyncRequired: payload.fallbackResync === true
|| payload.requiresResync === true
|| payload.resyncRequired === true
|| payload.resync_required === true,
rawPayload: payload,
bootstrap: entry && entry.bootstrap || null
};
}
function mergeFileChange(existing, change) {
if (!existing) return change;
return Object.assign({}, existing, change, {
source: change.source || existing.source,
selfWriteEcho: change.selfWriteEcho === true ? true : (change.selfWriteEcho === false ? false : existing.selfWriteEcho),
observedFileVersion: change.observedFileVersion || existing.observedFileVersion,
bufferFileVersion: change.bufferFileVersion || existing.bufferFileVersion,
fileVersion: change.fileVersion || existing.fileVersion,
createdAt: Math.max(Number(existing.createdAt || 0), Number(change.createdAt || 0))
});
}
function fileChangeQueueKey(batch) {
return [batch.rootUri || '', batch.workspaceId || 'default'].join('#');
}
function enqueueFileChangeBatch(batch) {
if (!batch || !batch.rootUri) return;
var key = fileChangeQueueKey(batch);
var queue = fileChangeQueues.get(key);
if (!queue) {
queue = {
rootUri: batch.rootUri,
workspaceId: batch.workspaceId,
sources: new Set(),
reasons: new Set(),
revisions: new Set(),
changes: new Map(),
affectedParents: new Map(),
dropped: [],
resyncRequired: false,
bootstrap: batch.bootstrap || null,
timer: 0
};
fileChangeQueues.set(key, queue);
}
if (batch.source) queue.sources.add(batch.source);
if (batch.reason) queue.reasons.add(batch.reason);
if (batch.revision) queue.revisions.add(String(batch.revision));
queue.dropped = queue.dropped.concat(batch.dropped || []);
queue.resyncRequired = queue.resyncRequired || batch.resyncRequired === true;
(batch.changes || []).forEach(function(change) {
var changeKey = [change.rootUri || batch.rootUri, change.relativePath, change.fileVersion || ''].join('#');
queue.changes.set(changeKey, mergeFileChange(queue.changes.get(changeKey), change));
});
(batch.affectedParents || []).forEach(function(parent) {
var relativePath = String(parent && parent.relativePath || parent || '');
queue.affectedParents.set(relativePath, {
relativePath: relativePath,
reason: String(parent && parent.reason || batch.reason || 'file-change')
});
});
if (!queue.timer) {
queue.timer = window.setTimeout(function() {
flushFileChangeQueue(key);
}, 80);
}
}
function currentDocumentSessionState(change) {
var snapshot = null;
try {
snapshot = window.__mnoteDebugDocumentSessions
&& window.__mnoteDebugDocumentSessions.snapshot
&& window.__mnoteDebugDocumentSessions.snapshot();
} catch (_) {
snapshot = null;
}
root().setAttribute('data-mnote-file-change-service-session-api', snapshot ? 'available' : 'unavailable');
var sessions = Array.isArray(snapshot && snapshot.sessions) ? snapshot.sessions : [];
return sessions.find(function(session) {
if (!session || session.sourceKind !== 'local_folder') return false;
if (change.rootUri && session.rootUri && change.rootUri !== session.rootUri) return false;
if (change.documentId && session.documentId === change.documentId) return true;
return false;
}) || null;
}
function reactionTypeForChange(change) {
if (change.selfWriteEcho) return 'ignore_self_write_echo';
if (change.resourceKind !== 'markdown') return 'refresh_resource_tab';
var session = currentDocumentSessionState(change);
if (session && /dirty|external/i.test(String(session.dirtyState || session.status || ''))) {
return 'external_conflict_current_document';
}
if (session) return 'refresh_current_document';
if (change.changeType === 'created' || change.changeType === 'deleted' || change.changeType === 'renamed') {
return 'refresh_page_tree_projection';
}
return 'refresh_filetree_parents';
}
function emitFileChangeReactions(detail) {
var reactionTypes = new Set();
(detail.fileChangeBatch.changes || []).forEach(function(change) {
var type = reactionTypeForChange(change);
reactionTypes.add(type);
emit('mnote:file-change-reaction', {
schema: 'mnote.file_change_reaction.v1',
type: type,
rootUri: detail.rootUri,
workspaceId: detail.workspaceId,
source: detail.source,
change: change,
batch: detail.fileChangeBatch
});
});
if (detail.affectedParents && detail.affectedParents.length) {
reactionTypes.add('refresh_filetree_parents');
}
lastFileChangeDiagnostics.lastReaction = Array.from(reactionTypes).join(',') || '';
root().setAttribute('data-mnote-file-change-service-last-reaction', lastFileChangeDiagnostics.lastReaction);
return reactionTypes;
}
function flushFileChangeQueue(key) {
var queue = fileChangeQueues.get(key);
if (!queue) return;
fileChangeQueues.delete(key);
var sources = Array.from(queue.sources);
var reasons = Array.from(queue.reasons);
var revision = Array.from(queue.revisions).pop() || null;
var batch = {
schema: 'mnote.file_change_batch.v1',
rootUri: queue.rootUri,
workspaceId: queue.workspaceId,
source: sources.join(',') || 'watcher',
reason: reasons.join(',') || 'watch_batch',
changes: Array.from(queue.changes.values()),
affectedParents: Array.from(queue.affectedParents.values()),
revision: revision,
dropped: queue.dropped,
resyncRequired: queue.resyncRequired
};
var compatPayload = standardBatchToCompatPayload(batch);
var detail = {
schema: 'mnote.local_folder.event_bus.watch_batch.v1',
source: batch.source,
reason: batch.reason,
rootUri: batch.rootUri,
workspaceId: batch.workspaceId,
revision: batch.revision,
payload: compatPayload,
bootstrap: queue.bootstrap || null,
changedPaths: compatPayload.changedPaths,
affectedParents: batch.affectedParents,
fileChangeBatch: batch,
resyncRequired: batch.resyncRequired,
viaEventBus: true
};
lastFileChangeDiagnostics = {
source: batch.source,
changedCount: batch.changes.length,
droppedCount: batch.dropped.length,
lastReaction: '',
lastBatchSchema: batch.schema
};
setDiagnostics(batch.source, batch.reason);
root().setAttribute('data-mnote-tree-live-revision', String(batch.revision || ''));
root().setAttribute('data-mnote-file-change-service-last-schema', batch.schema);
emit('mnote:file-change-batch', batch);
emit('mnote:local-folder:watch-batch', detail);
emit('tree:local-folder-watch-batch', detail);
var reactionTypes = emitFileChangeReactions(detail);
if (batch.affectedParents.length > 0 || reactionTypes.has('refresh_filetree_parents')) {
emit('mnote:local-folder:filetree-parent-changed', detail);
}
if (batch.changes.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 (batch.resyncRequired) {
emit('mnote:local-folder:resync-required', detail);
}
}
function pathArrayOf(value) {
if (!value) return [];
var list = Array.isArray(value) ? value : [value];
@@ -191,47 +573,7 @@
}
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);
}
enqueueFileChangeBatch(normalizeFileChangeBatch(entry, event, payload, meta));
}
function closeEntry(entry) {
@@ -326,6 +668,32 @@
});
}
function emitChangedFiles(detail) {
var payload = detail && detail.payload && typeof detail.payload === 'object' ? detail.payload : {};
var changedFiles = []
.concat(Array.isArray(detail && detail.changedFiles) ? detail.changedFiles : [])
.concat(Array.isArray(payload.changedFiles) ? payload.changedFiles : [])
.concat(Array.isArray(payload.changed_files) ? payload.changed_files : []);
if (!changedFiles.length) return;
var rootUri = normalizeRootUri((detail && detail.rootUri) || payload.rootUri);
var workspaceId = normalizeWorkspaceId((detail && detail.workspaceId) || payload.workspaceId);
emitSyntheticWatchBatch({
source: (detail && detail.source) || payload.source || 'changed_files_adapter',
reason: (detail && detail.reason) || payload.reason || 'changed_files_adapter',
rootUri: rootUri,
workspaceId: workspaceId,
payload: {
schema: 'mnote.local_folder.watch_batch.v1',
source: (detail && detail.source) || payload.source || 'changed_files_adapter',
rootUri: rootUri,
workspaceId: workspaceId,
revision: (detail && detail.revision) || payload.revision || null,
changedPaths: changedFiles,
affectedParents: Array.isArray(detail && detail.affectedParents) ? detail.affectedParents : []
}
});
}
function closeAll() {
Array.from(connections.values()).forEach(function(entry) {
closeEntry(entry);
@@ -337,6 +705,7 @@
window.__mnoteLocalFolderEventBus = {
startLocalFolderWatcher: startLocalFolderWatcher,
emitSyntheticWatchBatch: emitSyntheticWatchBatch,
emitChangedFiles: emitChangedFiles,
closeAll: closeAll,
flushSidebarRefresh: function(rootUri) {
flushSidebarRefresh(normalizeRootUri(rootUri));
@@ -346,7 +715,8 @@
return {
connections: connections.size,
lastSource: lastSource,
lastReason: lastReason
lastReason: lastReason,
fileChange: Object.assign({}, lastFileChangeDiagnostics)
};
}
};
@@ -415,7 +415,7 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|| String(detail && (detail.localRelativePath || detail.path || detail.title) || '').trim();
var rootUri = knowledgeRagRootUri(detail, trigger);
if (!sourceRootRelativePath || !rootUri) {
throw new Error('缺少资料库来源或 rootUri');
throw new Error('缺少知识库来源或 rootUri');
}
var workspaceId = String(detail && detail.workspaceId || resolveWorkspaceId(document.body) || '').trim();
document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-status', 'running');
@@ -426,12 +426,14 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
body: JSON.stringify({
workspaceId: workspaceId,
rootUri: rootUri,
sources: [{ sourcePath: sourceRootRelativePath }]
sources: [{ sourcePath: sourceRootRelativePath }],
knowledgeBaseId: String(detail && detail.kbId || '').trim() || undefined,
providerKnowledgeBaseId: String(detail && (detail.providerKbId || detail.kbId) || '').trim() || undefined
})
});
var payload = await response.json().catch(function() { return null; });
if (!response.ok || !payload || payload.ok === false) {
var message = payload && payload.message ? payload.message : '资料库索引失败';
var message = payload && payload.message ? payload.message : '知识库索引失败';
document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-status', 'failed');
throw new Error(message);
}
@@ -450,12 +452,30 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
recordFileTreeActionStatus('pending', detail);
void ingestKnowledgeRagForDetail(detail, trigger).then(function() {
recordFileTreeActionStatus('done', detail);
if (window.mnote && window.mnote.toast) {
window.mnote.toast('已加入知识库', { kind: 'success' });
}
}).catch(function(error) {
var msg = error && error.message ? error.message : '知识库索引失败';
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
window.alert(error && error.message ? error.message : '资料库索引失败');
if (window.mnote && window.mnote.toast) {
window.mnote.toast(msg, { kind: 'error' });
} else {
window.alert(msg);
}
});
return;
}
if (action === 'open-knowledge-page') {
var knowledgeUrl = '/knowledge';
if (typeof window.open === 'function') {
window.open(knowledgeUrl, '_blank', 'noopener,noreferrer');
} else {
window.location.href = knowledgeUrl;
}
return;
}
if (detail.contextKind === 'attachment') {
if (action === 'copy-link') {
void copyTreeContextValue(detail.href || '', 'attachment-copy-link');
@@ -683,6 +703,148 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
menu.appendChild(sep);
return;
}
// Submenu root: render parent button with child flyout
if (item.children && Array.isArray(item.children) && item.children.length > 0) {
if (typeof injectKnowledgeSubmenuStyles === 'function') injectKnowledgeSubmenuStyles();
var rootBtn = document.createElement('button');
rootBtn.type = 'button';
rootBtn.className = 'mnote-tree-context-menu__item mnote-tree-context-menu__item--submenu-root';
rootBtn.setAttribute('role', 'menuitem');
rootBtn.setAttribute('data-action', item.action || 'knowledge-rag-root');
rootBtn.setAttribute('data-command-id', item.commandId || item.action || 'knowledge-rag-root');
if (item.when) rootBtn.setAttribute('data-command-when', item.when);
if (item.danger || item.destructive) rootBtn.setAttribute('data-destructive', 'true');
if (item.requiresApproval) rootBtn.setAttribute('data-requires-approval', 'true');
rootBtn.disabled = item.disabled === true;
if (item.title) rootBtn.title = item.title;
if (item.disabled === true && item.title) rootBtn.setAttribute('data-disabled-reason', item.title);
var rootIcon = document.createElement('span');
rootIcon.className = 'material-symbols-outlined mnote-tree-context-menu__icon';
rootIcon.setAttribute('aria-hidden', 'true');
rootIcon.setAttribute('data-icon', item.icon || 'radio_button_unchecked');
var rootLabel = document.createElement('span');
rootLabel.className = 'mnote-tree-context-menu__label';
rootLabel.textContent = item.label;
var arrow = document.createElement('span');
arrow.className = 'mnote-tree-context-menu__submenu-arrow';
arrow.textContent = '▶';
arrow.setAttribute('aria-hidden', 'true');
rootBtn.appendChild(rootIcon);
rootBtn.appendChild(rootLabel);
rootBtn.appendChild(arrow);
var submenu = document.createElement('div');
submenu.className = 'mnote-tree-context-menu__submenu';
submenu.setAttribute('data-testid', 'mnote-knowledge-submenu');
submenu.setAttribute('role', 'menu');
function renderSubmenuChildren(children) {
submenu.textContent = '';
children.forEach(function(child) {
if (child.separator) {
var childSep = document.createElement('div');
childSep.className = 'mnote-tree-context-menu__separator';
childSep.setAttribute('role', 'separator');
submenu.appendChild(childSep);
return;
}
var childBtn = document.createElement('button');
childBtn.type = 'button';
childBtn.className = child.danger ? 'mnote-tree-context-menu__item mnote-tree-context-menu__item--danger' : 'mnote-tree-context-menu__item';
if (child.heading) childBtn.className += ' mnote-tree-context-menu__item--heading';
childBtn.setAttribute('role', 'menuitem');
childBtn.setAttribute('data-action', child.action || '');
childBtn.setAttribute('data-command-id', child.commandId || child.action || '');
if (child.when) childBtn.setAttribute('data-command-when', child.when);
childBtn.disabled = child.disabled === true || child.heading === true;
if (child.title) childBtn.title = child.title;
if (child.disabled === true && child.title) childBtn.setAttribute('data-disabled-reason', child.title);
var childIcon = document.createElement('span');
childIcon.className = 'material-symbols-outlined mnote-tree-context-menu__icon';
childIcon.setAttribute('aria-hidden', 'true');
childIcon.setAttribute('data-icon', child.icon || 'radio_button_unchecked');
var childLabel = document.createElement('span');
childLabel.className = 'mnote-tree-context-menu__label';
childLabel.textContent = child.label;
childBtn.appendChild(childIcon);
childBtn.appendChild(childLabel);
if (child.shortcut) {
var childShortcut = document.createElement('span');
childShortcut.className = 'mnote-tree-context-menu__shortcut';
childShortcut.textContent = child.shortcut;
childBtn.appendChild(childShortcut);
}
var childDetail = child.kbId ? Object.assign({}, detail, { kbId: child.kbId, providerKbId: child.providerKbId || child.kbId }) : detail;
(function(act, actDetail, actTrigger) {
childBtn.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
if (!act || childBtn.disabled) return;
handleTreeContextMenuAction(act, actDetail, actTrigger);
});
})(child.action, childDetail, trigger);
submenu.appendChild(childBtn);
});
}
renderSubmenuChildren(item.children);
rootBtn.appendChild(submenu);
var submenuShown = false;
var submenuHideTimer = null;
var lastSubmenuPointerY = 0;
function knowledgeSubmenuPosition(pointerY) {
var btnRect = rootBtn.getBoundingClientRect();
submenu.style.display = 'block';
submenu.style.visibility = 'hidden';
var smRect = submenu.getBoundingClientRect();
var leftPos = btnRect.right + 4;
var anchorY = Number(pointerY || lastSubmenuPointerY || 0);
var topPos = anchorY > 0 ? anchorY - 18 : btnRect.top;
if (leftPos + smRect.width > window.innerWidth - 8) {
leftPos = Math.max(8, btnRect.left - smRect.width - 4);
}
if (topPos + smRect.height > window.innerHeight - 8) {
topPos = Math.max(8, window.innerHeight - smRect.height - 8);
}
submenu.style.left = leftPos + 'px';
submenu.style.top = topPos + 'px';
submenu.style.visibility = 'visible';
}
function knowledgeSubmenuShow(pointerY) {
if (Number(pointerY || 0) > 0) lastSubmenuPointerY = pointerY;
if (submenuHideTimer) { window.clearTimeout(submenuHideTimer); submenuHideTimer = null; }
if (!submenuShown) { knowledgeSubmenuPosition(pointerY); submenuShown = true; rootBtn.classList.add('mnote-tree-context-menu__item--submenu-open'); }
}
function knowledgeSubmenuScheduleHide() {
if (submenuHideTimer) window.clearTimeout(submenuHideTimer);
submenuHideTimer = window.setTimeout(function() {
if (submenuShown) { submenu.style.display = 'none'; submenuShown = false; rootBtn.classList.remove('mnote-tree-context-menu__item--submenu-open'); }
submenuHideTimer = null;
}, 150);
}
rootBtn.addEventListener('mouseenter', function(event) { if (!rootBtn.disabled) knowledgeSubmenuShow(event.clientY); });
rootBtn.addEventListener('mousemove', function(event) {
if (rootBtn.disabled || !submenuShown) return;
lastSubmenuPointerY = event.clientY;
});
rootBtn.addEventListener('mouseleave', function(event) {
if (event.relatedTarget && (submenu.contains(event.relatedTarget) || rootBtn.contains(event.relatedTarget))) return;
knowledgeSubmenuScheduleHide();
});
submenu.addEventListener('mouseenter', function() {
if (submenuHideTimer) { window.clearTimeout(submenuHideTimer); submenuHideTimer = null; }
});
submenu.addEventListener('mouseleave', function(event) {
if (event.relatedTarget && (rootBtn.contains(event.relatedTarget) || submenu.contains(event.relatedTarget))) return;
knowledgeSubmenuScheduleHide();
});
if (typeof item.loadChildren === 'function') {
item.loadChildren().then(function(children) {
if (!Array.isArray(children) || children.length === 0) return;
renderSubmenuChildren(children);
if (submenuShown) knowledgeSubmenuPosition(lastSubmenuPointerY);
}).catch(function() {});
}
menu.appendChild(rootBtn);
return;
}
var button = document.createElement('button');
button.type = 'button';
button.className = item.danger ? 'mnote-tree-context-menu__item mnote-tree-context-menu__item--danger' : 'mnote-tree-context-menu__item';
@@ -718,6 +880,106 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
menu.appendChild(button);
}
// ── 知识库二级菜单 CSS 注入(仅一次) ──
var __mnoteKnowledgeSubmenuStylesInjected = false;
function injectKnowledgeSubmenuStyles() {
if (__mnoteKnowledgeSubmenuStylesInjected) return;
__mnoteKnowledgeSubmenuStylesInjected = true;
var style = document.createElement('style');
style.id = 'mnote-knowledge-submenu-styles';
style.textContent = '.mnote-tree-context-menu__item--submenu-root{position:relative}.mnote-tree-context-menu__submenu{position:fixed;z-index:calc(var(--wolai-z-context-menu,9999) + 1);width:240px;min-width:220px;max-width:min(340px,calc(100vw - 16px));padding:6px;border:1px solid rgba(27,28,28,.08);border-radius:6px;background:#FFF;box-shadow:0 8px 20px rgba(27,28,28,.12);backdrop-filter:blur(24px);color:var(--atelier-text);display:none}.mnote-tree-context-menu__submenu-arrow{margin-left:auto;font-size:11px;color:#6D6A65;padding-left:12px}.mnote-tree-context-menu__item--submenu-open{background:#F4F3F3}.mnote-tree-context-menu__item--heading{font-size:12px;color:#6D6A65;background:transparent!important;cursor:default}';
document.head.appendChild(style);
}
// ── 知识库列表获取:只读 MNote 后端 registry/status,避免前端直连 WeKnora service key。 ──
function fetchKnowledgeBaseList(detail) {
var rootUri = knowledgeRagRootUri(detail, null);
if (!rootUri) return Promise.resolve([]);
var workspaceId = String(detail && detail.workspaceId || resolveWorkspaceId(document.body) || '').trim();
var sourcePath = knowledgeRagSourceRelativePath(detail);
var statusUrl = '/api/knowledge-rag/status?rootUri=' + encodeURIComponent(rootUri);
if (workspaceId) statusUrl += '&workspaceId=' + encodeURIComponent(workspaceId);
return fetch(statusUrl, { credentials: 'include', headers: { accept: 'application/json' } })
.then(function(response) { return response.ok ? response.json() : null; })
.then(function(payload) {
// 构建 knowledgeBase → 已索引 sourcePath 快速查找
var indexedByKb = {};
var registry = payload && payload.registry;
if (registry && Array.isArray(registry.entries) && sourcePath) {
registry.entries.forEach(function(entry) {
var entryKbId = String(entry.providerKnowledgeBaseId || entry.provider_kb_id || '').trim();
var entryPath = String(entry.sourceRootRelativePath || entry.source_root_relative_path || '').trim();
if (entryKbId && entryPath && entry.indexedAtMs && entryPath === sourcePath) {
if (!indexedByKb[entryKbId]) indexedByKb[entryKbId] = {};
indexedByKb[entryKbId][entryPath] = true;
}
});
}
var bases = payload && payload.knowledgeBases && Array.isArray(payload.knowledgeBases.bases)
? payload.knowledgeBases.bases
: [];
return bases.filter(function(base) {
return base && base.canWrite !== false && (base.providerKbId || base.provider_kb_id || base.baseId || base.base_id);
}).map(function(base) {
var providerKbId = String(base.providerKbId || base.provider_kb_id || '').trim();
var baseId = String(base.baseId || base.base_id || providerKbId).trim();
var kbId = providerKbId || baseId;
return {
id: baseId,
providerKbId: kbId,
name: String(base.name || baseId || providerKbId || '未命名知识库').trim(),
sourceCount: Number(base.sourceCount || base.source_count || 0) || 0,
sourceAlreadyIndexed: Boolean(sourcePath && indexedByKb[kbId] && indexedByKb[kbId][sourcePath])
};
});
}).catch(function() { return []; });
}
function buildKnowledgeRagSubmenuChildren(detail, writableBases) {
var children = [
{ action: 'knowledge-rag-index', icon: 'travel_explore', label: '索引到默认知识库', when: '!workspace.readonly' },
{ separator: true },
{ heading: true, icon: 'history', label: '最近使用知识库' }
];
if (writableBases && writableBases.length > 0) {
writableBases.slice(0, 3).forEach(function(base) {
var indexed = base.sourceAlreadyIndexed;
children.push({
action: 'knowledge-rag-index',
icon: indexed ? 'check_circle' : 'database',
label: (indexed ? '✓ ' : '') + base.name,
kbId: base.id,
providerKbId: base.providerKbId,
title: indexed ? '重新索引' : (base.sourceCount ? ('已有 ' + base.sourceCount + ' 个来源') : '加入此知识库'),
when: '!workspace.readonly'
});
});
} else {
children.push({ action: '', icon: 'database', label: '暂无可写知识库', disabled: true });
}
children.push({ heading: true, icon: 'folder_managed', label: '当前 workspace 知识库' });
(writableBases || []).slice(3).forEach(function(base) {
var indexed = base.sourceAlreadyIndexed;
children.push({
action: 'knowledge-rag-index',
icon: indexed ? 'check_circle' : 'database',
label: (indexed ? '✓ ' : '') + base.name,
kbId: base.id,
providerKbId: base.providerKbId,
title: indexed ? '重新索引' : (base.sourceCount ? ('已有 ' + base.sourceCount + ' 个来源') : '加入此知识库'),
when: '!workspace.readonly'
});
});
children.push(
{ separator: true },
{ action: 'open-knowledge-page', icon: 'add', label: '新建知识库...' },
{ action: 'open-knowledge-page', icon: 'settings', label: '管理知识库...' }
);
return children;
}
// ── CommandContext 启用态辅助:与 Rust core-protocol CommandContext 保持同一口径 ──
function currentOpenEditorsSnapshotForCommandContext() {
@@ -979,7 +1241,13 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly && !editor.dirty' }
];
if (currentSourceKind() === 'local_folder' && detail.rowKind !== 'index' && knowledgeRagSupported) {
var ragItem = { action: 'knowledge-rag-index', icon: 'travel_explore', label: '加入资料库索引', when: '!workspace.readonly' };
var ragChildren = buildKnowledgeRagSubmenuChildren(detail, []);
var ragItem = { action: 'knowledge-rag-root', icon: 'travel_explore', label: '加入知识库', children: ragChildren, when: '!workspace.readonly' };
ragItem.loadChildren = function() {
return fetchKnowledgeBaseList(detail).then(function(bases) {
return buildKnowledgeRagSubmenuChildren(detail, bases);
});
};
var insertAt = isAttachment ? 11 : isAsset ? 3 : 3;
if (insertAt >= 0) items.splice(insertAt, 0, ragItem);
}
@@ -1274,6 +1274,10 @@ export function createSidebarPageAiRuntime(context) {
window.addEventListener('message', function(event) {
if (event.origin !== window.location.origin) return;
var message = event.data && typeof event.data === 'object' ? event.data : null;
if (message && message.source === 'openhub-ai' && message.type === 'mnote:minimize-openhub') {
closePageAiDrawer();
return;
}
if (message && message.source === 'openhub-ai' && message.type === 'mnote:get-active-tab-address') {
var addressPayload = pageAiCurrentActiveTabAddressPayload();
var requestPayload = message.payload && typeof message.payload === 'object' ? message.payload : {};
@@ -1822,29 +1826,21 @@ export function createSidebarPageAiRuntime(context) {
var workspaceId = String(options && options.workspaceId || resolveWorkspaceId(document.body) || '').trim();
var isMarkdown = Boolean(documentId);
if (isMarkdown) {
if (window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitSyntheticWatchBatch === 'function') {
window.__mnoteLocalFolderEventBus.emitSyntheticWatchBatch({
if (window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitChangedFiles === 'function') {
window.__mnoteLocalFolderEventBus.emitChangedFiles({
source: 'openhub_changed_file_bridge',
reason: 'openhub_changed_file',
rootUri: rootUri,
workspaceId: workspaceId,
payload: {
schema: 'mnote.local_folder.watch_batch.v1',
source: 'openhub_changed_file_bridge',
rootUri: rootUri,
changedPaths: [{ relativePath: targetPath, documentId: documentId, changeType: 'modified' }],
affectedParents: [{ relativePath: pageAiParentRelativePath(targetPath), reason: 'openhub_changed_file' }]
}
});
}
if (typeof window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument === 'function') {
void window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument({
documentId: documentId,
workspaceId: workspaceId,
source: 'openhub-changed-file-bridge'
changedFiles: [{ relativePath: targetPath, documentId: documentId, changeType: 'modified' }],
affectedParents: [{ relativePath: pageAiParentRelativePath(targetPath), reason: 'openhub_changed_file' }]
});
}
if (typeof window.__mnoteDocumentPaneRuntime.openPrimaryDocument === 'function') {
if (documentId === String(currentDocumentId() || '').trim()) {
document.documentElement.setAttribute('data-mnote-page-ai-openhub-document-pane-refresh', targetPath);
return true;
}
void window.__mnoteDocumentPaneRuntime.openPrimaryDocument({
documentId: documentId,
workspaceId: workspaceId,
@@ -1861,12 +1857,18 @@ export function createSidebarPageAiRuntime(context) {
}
function pageAiRefreshOpencodeCurrentPage(reason) {
if (window.__mnoteDocumentPaneRuntime && typeof window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument === 'function') {
if (reason) {
void window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument({ reason: reason });
} else {
void window.__mnoteDocumentPaneRuntime.refreshPrimaryDocument({ reason: 'page-ai-opencode-host' });
}
var rootUri = String(currentRootUri() || '').trim();
var targetPath = String(pageAiOpencodePrimaryPath() || '').trim();
var documentId = String(currentDocumentId() || pageAiDocumentIdFromRootRelativePath(targetPath) || '').trim();
if (rootUri && targetPath && documentId && window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitChangedFiles === 'function') {
window.__mnoteLocalFolderEventBus.emitChangedFiles({
source: 'opencode_changed_files_adapter',
reason: reason || 'page-ai-opencode-host',
rootUri: rootUri,
workspaceId: resolveWorkspaceId(document.body),
changedFiles: [{ relativePath: targetPath, documentId: documentId, changeType: 'modified' }],
affectedParents: [{ relativePath: pageAiParentRelativePath(targetPath), reason: reason || 'page-ai-opencode-host' }]
});
return true;
}
return false;
@@ -3168,22 +3170,25 @@ export function createSidebarPageAiRuntime(context) {
affectedParents: affectedParents
};
document.documentElement.setAttribute('data-mnote-page-ai-receipt-filetree-refresh', 'true');
if (window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitSyntheticWatchBatch === 'function') {
window.__mnoteLocalFolderEventBus.emitSyntheticWatchBatch({
if (window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitChangedFiles === 'function') {
window.__mnoteLocalFolderEventBus.emitChangedFiles({
source: 'synthetic_page_ai_receipt',
reason: 'agent_run_receipt',
rootUri: rootUri,
workspaceId: resolveWorkspaceId(document.body),
changedFiles: changedPaths,
affectedParents: affectedParents,
payload: syntheticPayload
});
} else {
document.documentElement.setAttribute('data-mnote-page-ai-receipt-fallback-direct', 'true');
window.dispatchEvent(new CustomEvent('tree:local-folder-watch-batch', {
detail: { payload: syntheticPayload }
}));
}
}
}
if (refresh.touchesCurrentFile === true) {
if (refresh.touchesCurrentFile === true && !files.length) {
var documentId = String(refresh.currentDocumentId || receipt && receipt.documentId || currentDocumentId() || '').trim();
document.documentElement.setAttribute('data-mnote-page-ai-receipt-current-refresh', 'true');
window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
@@ -700,29 +700,29 @@ export function createSidebarPageSettingsRuntime(context) {
function createKnowledgeRagSettingsPanelHtmlV2() {
return '' +
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-knowledge-rag-settings-panel mnote-weknora-kb-page" role="dialog" aria-modal="false" aria-label="WeKnora 知识库" data-testid="mnote-weknora-knowledge-settings-panel" data-knowledge-rag-default-provider="weknora" data-knowledge-rag-legacy-fallback-provider="lightrag" data-knowledge-rag-source-registry="mnote" data-knowledge-rag-provider-index="weknora-kb-chunk-index" data-mnote-weknora-page-replacement="true">' +
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-knowledge-rag-settings-panel mnote-weknora-kb-page" role="dialog" aria-modal="false" aria-label="LightRAG 知识库" data-testid="mnote-weknora-knowledge-settings-panel" data-knowledge-rag-default-provider="lightrag" data-knowledge-rag-legacy-fallback-provider="lightrag" data-knowledge-rag-source-registry="mnote" data-knowledge-rag-provider-index="lightrag-source-registry" data-mnote-weknora-page-replacement="false">' +
'<div class="mnote-weknora-kb-page-header">' +
'<div class="mnote-weknora-kb-page-title">' +
'<span class="mnote-weknora-kb-page-eyebrow">WeKnora</span>' +
'<span class="mnote-weknora-kb-page-eyebrow">LightRAG</span>' +
'<strong>知识库</strong>' +
'<span>由 MNote 工作区授权文件驱动</span>' +
'<span>由 MNote source registry 与 LightRAG 索引驱动</span>' +
'</div>' +
'<div class="mnote-weknora-kb-page-actions">' +
'<button type="button" data-knowledge-rag-action="refresh" title="刷新"><span class="material-symbols-outlined" data-icon="refresh" aria-hidden="true"></span></button>' +
'<button type="button" data-knowledge-rag-action="open-dashboard" title="打开知识库" aria-label="打开知识库"><span class="material-symbols-outlined" data-icon="open_in_new" aria-hidden="true"></span></button>' +
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭 WeKnora 知识库"><span class="material-symbols-outlined" data-icon="close" aria-hidden="true"></span></button>' +
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭知识库"><span class="material-symbols-outlined" data-icon="close" aria-hidden="true"></span></button>' +
'</div>' +
'</div>' +
'<div class="mnote-weknora-kb-page-status" data-testid="mnote-knowledge-rag-status"></div>' +
'<div class="mnote-weknora-kb-page-body">' +
'<aside class="mnote-weknora-kb-list-pane" data-testid="mnote-weknora-kb-list-pane">' +
'<div class="mnote-weknora-kb-list-head">' +
'<div><strong>知识库列表</strong><span data-testid="mnote-weknora-kb-list-count">0</span></div>' +
'<button type="button" data-knowledge-rag-action="focus-create-kb" title="新建知识库"><span class="material-symbols-outlined" data-icon="create_new_folder" aria-hidden="true"></span></button>' +
'<div><strong>资料库</strong><span data-testid="mnote-weknora-kb-list-count">0</span></div>' +
'<button type="button" data-knowledge-rag-action="focus-create-kb" title="LightRAG 当前使用单资料库" hidden><span class="material-symbols-outlined" data-icon="create_new_folder" aria-hidden="true"></span></button>' +
'</div>' +
'<div class="mnote-weknora-kb-card-wrap" data-testid="mnote-weknora-kb-card-list"></div>' +
'<section class="mnote-weknora-kb-create-card" data-testid="mnote-weknora-kb-controls">' +
'<div class="mnote-weknora-kb-create-title"><span class="material-symbols-outlined" data-icon="folder_add" aria-hidden="true"></span><strong>新建知识库</strong></div>' +
'<section class="mnote-weknora-kb-create-card" data-testid="mnote-weknora-kb-controls" hidden>' +
'<div class="mnote-weknora-kb-create-title"><span class="material-symbols-outlined" data-icon="folder_add" aria-hidden="true"></span><strong>新建逻辑资料库</strong></div>' +
'<input type="text" class="wolai-page-settings-index-path" data-testid="mnote-weknora-create-kb-name" data-knowledge-rag-create-kb-name="true" placeholder="知识库名称" spellcheck="false" />' +
'<input type="text" class="wolai-page-settings-index-path" data-testid="mnote-weknora-create-kb-description" data-knowledge-rag-create-kb-description="true" placeholder="描述,可留空" spellcheck="false" />' +
'<button type="button" class="wolai-page-settings-index-add" data-knowledge-rag-action="create-kb">创建知识库</button>' +
@@ -732,19 +732,19 @@ export function createSidebarPageSettingsRuntime(context) {
'<section class="mnote-weknora-kb-detail-pane" data-testid="mnote-weknora-kb-detail-pane">' +
'<div class="mnote-weknora-kb-detail-hero" data-testid="mnote-weknora-kb-detail-hero">' +
'<div>' +
'<nav class="mnote-weknora-kb-breadcrumb" data-testid="mnote-weknora-kb-breadcrumb" aria-label="知识库面包屑"><span>知识库</span><span>/</span><strong data-testid="mnote-weknora-kb-breadcrumb-current">WeKnora 知识库</strong></nav>' +
'<span class="mnote-weknora-kb-type-pill"><span class="material-symbols-outlined" data-icon="folder" aria-hidden="true"></span>Document KB</span>' +
'<h2 data-testid="mnote-weknora-kb-detail-title">WeKnora 知识库</h2>' +
'<p data-testid="mnote-weknora-kb-detail-description">MNote 负责工作区授权和 source registryWeKnora 负责解析、chunk、检索和引用。</p>' +
'<nav class="mnote-weknora-kb-breadcrumb" data-testid="mnote-weknora-kb-breadcrumb" aria-label="知识库面包屑"><span>知识库</span><span>/</span><strong data-testid="mnote-weknora-kb-breadcrumb-current">LightRAG 资料库</strong></nav>' +
'<span class="mnote-weknora-kb-type-pill"><span class="material-symbols-outlined" data-icon="folder" aria-hidden="true"></span>Source Registry</span>' +
'<h2 data-testid="mnote-weknora-kb-detail-title">LightRAG 资料库</h2>' +
'<p data-testid="mnote-weknora-kb-detail-description">MNote 管理本地来源、引用定位和权限边界,LightRAG 负责索引与召回。</p>' +
'</div>' +
'<select class="mnote-kb-params-select" data-testid="mnote-weknora-kb-select" data-knowledge-rag-kb-select="true"><option value="">加载中...</option></select>' +
'</div>' +
'<div class="mnote-knowledge-rag-meta" data-testid="mnote-knowledge-rag-meta"></div>' +
'<div class="mnote-knowledge-rag-detail-tabs" role="tablist" aria-label="WeKnora 知识库详情">' +
'<button type="button" class="mnote-knowledge-rag-detail-tab is-active" role="tab" aria-selected="true" data-kb-rag-tab="sources" data-testid="mnote-weknora-tab-documents">Documents</button>' +
'<div class="mnote-knowledge-rag-detail-tabs" role="tablist" aria-label="LightRAG 知识库详情">' +
'<button type="button" class="mnote-knowledge-rag-detail-tab is-active" role="tab" aria-selected="true" data-kb-rag-tab="sources" data-testid="mnote-weknora-tab-documents">来源</button>' +
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="wiki" data-testid="mnote-weknora-tab-wiki">Wiki</button>' +
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="graph" data-testid="mnote-weknora-tab-graph">Graph</button>' +
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="search">Search</button>' +
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="search">检索</button>' +
'</div>' +
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="sources">' +
'<div class="mnote-weknora-doc-layout" data-testid="mnote-weknora-document-management">' +
@@ -755,7 +755,7 @@ export function createSidebarPageSettingsRuntime(context) {
'<button type="button" data-knowledge-rag-action="filter-sources" data-knowledge-rag-filter="indexed"><span class="material-symbols-outlined" data-icon="check_circle" aria-hidden="true"></span>已索引</button>' +
'<button type="button" data-knowledge-rag-action="filter-sources" data-knowledge-rag-filter="indexing"><span class="material-symbols-outlined" data-icon="sync" aria-hidden="true"></span>处理中</button>' +
'</div>' +
'<div class="mnote-weknora-doc-sidebar-head"><strong>标签</strong><span>WeKnora views</span></div>' +
'<div class="mnote-weknora-doc-sidebar-head"><strong>标签</strong><span>MNote views</span></div>' +
'<div class="mnote-weknora-doc-tags" data-testid="mnote-weknora-doc-tags"><span>PDF</span><span>Markdown</span><span>Folder</span><span>Citation</span></div>' +
'</aside>' +
'<section class="mnote-weknora-doc-main">' +
@@ -767,12 +767,13 @@ export function createSidebarPageSettingsRuntime(context) {
'</div>' +
'<button type="button" data-knowledge-rag-action="use-filetree-selection"><span class="material-symbols-outlined" data-icon="account_tree" aria-hidden="true"></span>添加文件夹</button>' +
'<button type="button" data-knowledge-rag-action="add-source"><span class="material-symbols-outlined" data-icon="upload_file" aria-hidden="true"></span>添加文档</button>' +
'<button type="button" data-knowledge-rag-action="ingest-active"><span class="material-symbols-outlined" data-icon="note_add" aria-hidden="true"></span>加入当前页</button>' +
'</div>' +
'<div class="mnote-knowledge-rag-source-inputs" data-testid="mnote-knowledge-rag-source-inputs">' +
knowledgeRagSourceInputRow('') +
'</div>' +
'<div class="mnote-weknora-doc-actions">' +
'<button type="button" data-knowledge-rag-action="ingest">上传/索引到 WeKnora</button>' +
'<button type="button" data-knowledge-rag-action="ingest">索引到 LightRAG</button>' +
'<button type="button" data-knowledge-rag-action="prune">清除失效记录</button>' +
'</div>' +
'<div class="mnote-knowledge-rag-source-filters" data-testid="mnote-knowledge-rag-source-filters"></div>' +
@@ -783,13 +784,13 @@ export function createSidebarPageSettingsRuntime(context) {
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="wiki" hidden>' +
'<div class="mnote-weknora-placeholder-panel" data-testid="mnote-weknora-wiki-panel">' +
'<strong>Wiki</strong>' +
'<span>从 WeKnora 文档解析与 chunk 结果生成知识页入口;当前保留 MNote open-reference 边界。</span>' +
'<span>后续可基于 LightRAG 召回结果生成知识页;当前 MNote open-reference 边界为准。</span>' +
'</div>' +
'</div>' +
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="graph" hidden>' +
'<div class="mnote-weknora-placeholder-panel" data-testid="mnote-weknora-graph-panel">' +
'<strong>Graph</strong>' +
'<span>展示 provider 侧文档关系与引用入口,不迁移 WeKnora tenant 或文件真相。</span>' +
'<span>展示 LightRAG 图谱和文档关系入口,本地文件真相仍由 MNote source registry 管理。</span>' +
'</div>' +
'</div>' +
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="search" hidden>' +
@@ -798,7 +799,7 @@ export function createSidebarPageSettingsRuntime(context) {
'<button type="button" class="wolai-page-settings-index-add" data-kb-rag-action="search">搜索</button>' +
'</div>' +
'<div class="mnote-kb-search-results" data-kb-rag-search-results="true">' +
'<span class="mnote-extensions-loading">输入关键词后用 WeKnora hybrid 检索</span>' +
'<span class="mnote-extensions-loading">输入关键词后用 LightRAG hybrid 检索</span>' +
'</div>' +
'</div>' +
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="params" hidden>' +
@@ -806,7 +807,7 @@ export function createSidebarPageSettingsRuntime(context) {
'<div class="mnote-kb-params-row">' +
'<label class="mnote-kb-params-label">Provider 检索策略</label>' +
'<select class="mnote-kb-params-select" data-kb-rag-param="mode">' +
'<option value="hybrid" selected>WeKnora hybrid</option>' +
'<option value="hybrid" selected>LightRAG hybrid</option>' +
'<option value="vector">向量召回</option>' +
'<option value="keyword">关键词召回</option>' +
'<option value="rerank">Hybrid + rerank</option>' +
@@ -1214,11 +1215,11 @@ export function createSidebarPageSettingsRuntime(context) {
}
function knowledgeRagCurrentFilter() {
return pageUiState.knowledgeRagSourceFilter || 'attention';
return pageUiState.knowledgeRagSourceFilter || 'all';
}
function setKnowledgeRagSourceFilter(filter) {
pageUiState.knowledgeRagSourceFilter = String(filter || 'attention').trim() || 'attention';
pageUiState.knowledgeRagSourceFilter = String(filter || 'all').trim() || 'all';
renderKnowledgeRagSettings();
}
@@ -1226,7 +1227,8 @@ export function createSidebarPageSettingsRuntime(context) {
var deleted = entry && entry.deletedAtMs;
var stale = entry && entry.stale;
var providerStatus = knowledgeRagEntryProviderStatus(entry);
var failed = providerStatus === 'failed';
var providerMissing = providerStatus === 'provider_missing';
var failed = providerStatus === 'failed' || providerMissing;
var deleting = providerStatus === 'delete_submitted';
var deletedDone = providerStatus === 'delete_completed';
var docId = knowledgeRagEntryProviderDocId(entry);
@@ -1239,14 +1241,14 @@ export function createSidebarPageSettingsRuntime(context) {
function knowledgeRagProviderName(status) {
var config = status && status.providerConfig && typeof status.providerConfig === 'object' ? status.providerConfig : {};
return String(status && status.provider || config.active || 'weknora').trim() || 'weknora';
return String(status && status.provider || config.active || 'lightrag_legacy').trim() || 'lightrag_legacy';
}
function knowledgeRagProviderLabel(status) {
var providerName = knowledgeRagProviderName(status);
if (providerName === 'weknora') return 'WeKnora';
if (providerName === 'lightrag') return 'LightRAG legacy';
if (providerName === 'lightrag_legacy') return 'LightRAG legacy';
if (providerName === 'lightrag') return 'LightRAG';
if (providerName === 'lightrag_legacy') return 'LightRAG';
return providerName;
}
@@ -1405,14 +1407,25 @@ export function createSidebarPageSettingsRuntime(context) {
seen[providerKbId] = true;
output.push({
providerKbId: providerKbId,
name: 'WeKnora KB ' + providerKbId.slice(0, 8),
description: '由 WeKnora provider 配置注入的知识库',
name: 'Provider KB ' + providerKbId.slice(0, 8),
description: '由知识库 provider 配置注入的资料库',
sourceCount: 0,
chunkCount: 0,
updatedAt: '',
status: 'configured'
});
});
if (!output.length && ['lightrag', 'lightrag_legacy'].includes(knowledgeRagProviderName(status || {}))) {
output.push({
providerKbId: 'lightrag-default',
name: '默认资料库',
description: 'LightRAG 单工作区索引,由 MNote source registry 划分范围',
sourceCount: ((status && status.registry && Array.isArray(status.registry.entries)) ? status.registry.entries.length : 0),
chunkCount: 0,
updatedAt: '',
status: 'active'
});
}
return output;
}
@@ -1432,6 +1445,7 @@ export function createSidebarPageSettingsRuntime(context) {
function knowledgeRagEntriesForKb(entries, providerKnowledgeBaseId) {
var selected = String(providerKnowledgeBaseId || '').trim();
if (selected === 'lightrag-default') return Array.isArray(entries) ? entries : [];
return (Array.isArray(entries) ? entries : []).filter(function(entry) {
return selected && knowledgeRagEntryProviderKnowledgeBaseId(entry) === selected;
});
@@ -1439,6 +1453,7 @@ export function createSidebarPageSettingsRuntime(context) {
function knowledgeRagProviderDocsForKb(providerDocs, providerKnowledgeBaseId) {
var selected = String(providerKnowledgeBaseId || '').trim();
if (selected === 'lightrag-default') return Array.isArray(providerDocs) ? providerDocs : [];
return (Array.isArray(providerDocs) ? providerDocs : []).filter(function(doc) {
var id = String(doc && (doc.knowledgeBaseId || doc.knowledge_base_id || doc.providerKnowledgeBaseId || doc.provider_knowledge_base_id) || '').trim();
return selected && id === selected;
@@ -1482,17 +1497,18 @@ export function createSidebarPageSettingsRuntime(context) {
if (!(listNode instanceof HTMLElement)) return;
var bases = knowledgeRagKnowledgeBases(status || {});
var selected = selectedKnowledgeRagProviderKnowledgeBaseId(status || {});
var providerName = knowledgeRagProviderName(status || {});
if (countNode instanceof HTMLElement) countNode.textContent = String(bases.length);
if (!bases.length) {
listNode.innerHTML = '<div class="wolai-page-settings-index-empty">还没有 WeKnora 知识库</div>';
listNode.innerHTML = '<div class="wolai-page-settings-index-empty">还没有知识库来源</div>';
return;
}
listNode.innerHTML = bases.map(function(base) {
var stats = knowledgeRagBaseStats(base, entries, providerDocs);
var isActive = base.providerKbId === selected;
var status = String(base.status || 'active');
var baseStatus = String(base.status || 'active');
return '' +
'<button type="button" class="mnote-weknora-kb-card' + (isActive ? ' is-active' : '') + '" data-knowledge-rag-action="select-kb" data-knowledge-rag-kb-id="' + escapeHtml(base.providerKbId) + '" data-knowledge-rag-kb-provider="weknora" data-knowledge-rag-processing-count="' + escapeHtml(String(stats.processingCount)) + '">' +
'<button type="button" class="mnote-weknora-kb-card' + (isActive ? ' is-active' : '') + '" data-knowledge-rag-action="select-kb" data-knowledge-rag-kb-id="' + escapeHtml(base.providerKbId) + '" data-knowledge-rag-kb-provider="' + escapeHtml(providerName) + '" data-knowledge-rag-processing-count="' + escapeHtml(String(stats.processingCount)) + '">' +
'<span class="mnote-weknora-kb-card-star"><span class="material-symbols-outlined" data-icon="folder" aria-hidden="true"></span></span>' +
'<span class="mnote-weknora-kb-card-main">' +
'<strong title="' + escapeHtml(base.name) + '">' + escapeHtml(base.name) + '</strong>' +
@@ -1503,7 +1519,7 @@ export function createSidebarPageSettingsRuntime(context) {
(stats.processingCount ? '<i class="is-processing"><span class="material-symbols-outlined" data-icon="sync" aria-hidden="true"></span>' + escapeHtml(String(stats.processingCount)) + '</i>' : '') +
'</span>' +
'</span>' +
'<span class="mnote-weknora-kb-card-status" data-status="' + escapeHtml(status) + '">' + escapeHtml(status) + '</span>' +
'<span class="mnote-weknora-kb-card-status" data-status="' + escapeHtml(baseStatus) + '">' + escapeHtml(baseStatus) + '</span>' +
'</button>';
}).join('');
}
@@ -1518,7 +1534,7 @@ export function createSidebarPageSettingsRuntime(context) {
var base = bases.find(function(item) { return item.providerKbId === selected; }) || bases[0] || null;
if (!base) {
titleNode.textContent = '未配置知识库';
descNode.textContent = '创建 WeKnora 知识库后,可在这里管理 MNote 授权的本地文件和引用。';
descNode.textContent = '加入资料来源后,可在这里管理 MNote 授权的本地文件和引用。';
if (breadcrumbNode instanceof HTMLElement) breadcrumbNode.textContent = '未配置';
return;
}
@@ -1562,7 +1578,7 @@ export function createSidebarPageSettingsRuntime(context) {
var activeCount = Object.keys(activeKbIds).length || kbIds.length;
var processingCount = knowledgeRagInFlightCount(entries, providerDocs);
return '' +
'<div class="mnote-weknora-kb-summary-card" data-testid="mnote-weknora-kb-summary" data-knowledge-rag-kb-id="' + escapeHtml(kbIds[0] || '') + '" data-knowledge-rag-kb-ids="' + escapeHtml(kbIds.join(',')) + '" data-knowledge-rag-kb-provider="weknora" data-knowledge-rag-kb-status="configured">' +
'<div class="mnote-weknora-kb-summary-card" data-testid="mnote-weknora-kb-summary" data-knowledge-rag-kb-id="' + escapeHtml(kbIds[0] || '') + '" data-knowledge-rag-kb-ids="' + escapeHtml(kbIds.join(',')) + '" data-knowledge-rag-kb-provider="' + escapeHtml(knowledgeRagProviderName(status || {})) + '" data-knowledge-rag-kb-status="configured">' +
'<div><span>知识库</span><strong>' + escapeHtml(activeCount ? String(activeCount) + ' 个' : '未配置') + '</strong></div>' +
'<div><span>文档</span><strong>' + escapeHtml(String((entries || []).length)) + '</strong></div>' +
'<div><span>Provider docs</span><strong>' + escapeHtml(String((providerDocs || []).length)) + '</strong></div>' +
@@ -1873,6 +1889,7 @@ export function createSidebarPageSettingsRuntime(context) {
return;
}
var pipelineProgress = knowledgeRagPipelineProgress(status || {});
var providerName = knowledgeRagProviderName(status || {});
var providerDocs = knowledgeRagProviderDocuments(status || {});
var providerDocsById = new Map();
var providerDocsByPath = new Map();
@@ -1895,7 +1912,7 @@ export function createSidebarPageSettingsRuntime(context) {
var knowledgeId = knowledgeRagProviderKnowledgeId(doc);
var providerTitle = providerPath || providerId || 'Provider 文档';
var chunkCount = knowledgeRagProviderChunkCount(doc);
var providerMeta = 'provider=weknora · 未映射到 MNote source registry · ' + docStatus
var providerMeta = 'provider=' + providerName + ' · 未映射到 MNote source registry · ' + docStatus
+ (providerId ? ' · provider id ' + providerId : '')
+ (chunkCount ? ' · chunk ' + chunkCount : '')
+ (providerPath ? '' : ' · 定位降级');
@@ -1903,7 +1920,7 @@ export function createSidebarPageSettingsRuntime(context) {
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-provider="weknora" data-knowledge-rag-provider-id="' + escapeHtml(providerId) + '" data-knowledge-rag-knowledge-id="' + escapeHtml(knowledgeId) + '" data-knowledge-rag-chunk-count="' + escapeHtml(String(chunkCount || 0)) + '" data-knowledge-rag-processing-status="' + escapeHtml(docStatus) + '" data-knowledge-rag-open-reference-mode="provider-reference-fallback" data-knowledge-rag-source-path="' + escapeHtml(providerPath || providerId) + '">' +
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-source-kind="provider" data-knowledge-rag-provider="' + escapeHtml(providerName) + '" data-knowledge-rag-provider-id="' + escapeHtml(providerId) + '" data-knowledge-rag-knowledge-id="' + escapeHtml(knowledgeId) + '" data-knowledge-rag-chunk-count="' + escapeHtml(String(chunkCount || 0)) + '" data-knowledge-rag-processing-status="' + escapeHtml(docStatus) + '" data-knowledge-rag-open-reference-mode="provider-reference-fallback" data-knowledge-rag-source-path="' + escapeHtml(providerPath || providerId) + '">' +
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + (failedDoc ? 'fault' : 'indexed') + '" title="' + escapeHtml(docStatus || 'Provider 文档') + '"></span>' +
'<div class="mnote-knowledge-rag-source-main">' +
'<strong title="' + escapeHtml(providerTitle) + '">' + escapeHtml(providerTitle) + '</strong>' +
@@ -1918,7 +1935,8 @@ export function createSidebarPageSettingsRuntime(context) {
var deleted = entry && entry.deletedAtMs;
var stale = entry && entry.stale;
var providerStatus = knowledgeRagEntryProviderStatus(entry);
var failed = providerStatus === 'failed';
var providerMissing = providerStatus === 'provider_missing';
var failed = providerStatus === 'failed' || providerMissing;
var deleting = providerStatus === 'delete_submitted';
var deletedDone = providerStatus === 'delete_completed';
var docId = knowledgeRagEntryProviderDocId(entry);
@@ -1930,7 +1948,8 @@ export function createSidebarPageSettingsRuntime(context) {
: deleting ? '删除已提交'
: deleted ? '已删除'
: stale ? '已过期'
: failed ? '索引失败'
: providerMissing ? 'Provider 索引缺失'
: failed ? '索引失败'
: indexed ? '已索引'
: (docId || providerStatus ? '正在索引' : '待提交');
var providerDoc = (docId && providerDocsById.get(docId)) || providerDocsByPath.get(String(entry && entry.lightRagFilePath || '').trim()) || null;
@@ -1939,7 +1958,7 @@ export function createSidebarPageSettingsRuntime(context) {
var kbId = knowledgeRagEntryProviderKnowledgeBaseId(entry);
var metaParts = [
statusLabel,
'provider=weknora',
'provider=' + providerName,
'MNote source registry',
kbId ? 'kb ' + kbId : '',
providerIds.length ? 'provider ids ' + providerIds.join(', ') : '',
@@ -1955,7 +1974,7 @@ export function createSidebarPageSettingsRuntime(context) {
entryProgress = knowledgeRagIndeterminateProgress(status || {}, providerDoc, providerStatus);
}
return '' +
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-provider="weknora" data-knowledge-rag-provider-ids="' + escapeHtml(providerIds.join(',')) + '" data-knowledge-rag-kb-id="' + escapeHtml(kbId) + '" data-knowledge-rag-knowledge-id="' + escapeHtml(knowledgeId) + '" data-knowledge-rag-chunk-count="' + escapeHtml(String(chunkCount || 0)) + '" data-knowledge-rag-processing-status="' + escapeHtml(providerStatus || statusLabel) + '" data-knowledge-rag-open-reference-mode="source-registry-reference" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '">' +
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-provider="' + escapeHtml(providerName) + '" data-knowledge-rag-provider-ids="' + escapeHtml(providerIds.join(',')) + '" data-knowledge-rag-kb-id="' + escapeHtml(kbId) + '" data-knowledge-rag-knowledge-id="' + escapeHtml(knowledgeId) + '" data-knowledge-rag-chunk-count="' + escapeHtml(String(chunkCount || 0)) + '" data-knowledge-rag-processing-status="' + escapeHtml(providerStatus || statusLabel) + '" data-knowledge-rag-open-reference-mode="source-registry-reference" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '">' +
'<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>' +
@@ -1980,7 +1999,7 @@ export function createSidebarPageSettingsRuntime(context) {
if (!(statusNode instanceof HTMLElement)) return;
if (!knowledgeRagIsAvailable()) {
statusNode.textContent = '资料库问答仅在本地工作区可用';
statusNode.textContent = '知识库仅在本地工作区可用';
if (metaNode instanceof HTMLElement) metaNode.innerHTML = '';
renderKnowledgeRagSourceFilters(popover, []);
renderKnowledgeRagSources(popover, [], {});
@@ -2013,7 +2032,7 @@ export function createSidebarPageSettingsRuntime(context) {
if (metaNode instanceof HTMLElement) {
metaNode.setAttribute('data-knowledge-rag-active-provider', knowledgeRagProviderName(status));
metaNode.setAttribute('data-knowledge-rag-legacy-fallback-provider', 'lightrag');
metaNode.setAttribute('data-knowledge-rag-reference-boundary', 'mnote-source-registry-weknora-kb-chunk-index');
metaNode.setAttribute('data-knowledge-rag-reference-boundary', 'mnote-source-registry-lightrag-sidecar');
}
var healthStatus = String(healthBody.status || '');
var healthy = health.ok === true && (!healthStatus || healthStatus === 'healthy' || healthStatus === 'ok');
@@ -2054,7 +2073,7 @@ export function createSidebarPageSettingsRuntime(context) {
metaNode.innerHTML = '' +
knowledgeRagKbSummaryHtml(status, entries, providerDocs) +
'<div><span>Provider</span><code>' + escapeHtml(providerLabel) + '</code></div>' +
'<div><span>Boundary</span><code>MNote source registry / WeKnora KB chunk index</code></div>' +
'<div><span>Boundary</span><code>MNote source registry / LightRAG sidecar locator</code></div>' +
'<div><span>Dashboard</span><code>' + escapeHtml(dashboardUrl || '未配置') + '</code></div>' +
'<div><span>Model</span><code>' + escapeHtml(llmModel || '未上报') + (vlmModel ? ' / VLM ' + escapeHtml(vlmModel) : '') + '</code></div>' +
'<div><span>Rerank</span><code>' + escapeHtml(rerankLabel + (rerankBinding ? ' · ' + rerankBinding : '') + (rerankModel ? ' · ' + rerankModel : '')) + '</code></div>' +
@@ -2067,8 +2086,12 @@ export function createSidebarPageSettingsRuntime(context) {
function publicKnowledgeRagDashboardUrl(status) {
var config = status && status.providerConfig && typeof status.providerConfig === 'object' ? status.providerConfig : {};
var active = knowledgeRagProviderName(status || {});
var weknora = config.weknora && typeof config.weknora === 'object' ? config.weknora : {};
var url = String((weknora && weknora.dashboardUrl) || (status && (status.dashboardUrl || status.endpoint)) || '').trim();
var lightrag = config.lightrag && typeof config.lightrag === 'object' ? config.lightrag : {};
var url = active === 'weknora'
? String((weknora && weknora.dashboardUrl) || (status && (status.dashboardUrl || status.endpoint)) || '').trim()
: String((status && (status.dashboardUrl || status.endpoint)) || (lightrag && lightrag.dashboardUrl) || '').trim();
if (!url) return '';
try {
var parsed = new URL(url, window.location.href);
@@ -2496,10 +2519,12 @@ export function createSidebarPageSettingsRuntime(context) {
}
function openKnowledgeRagDashboard() {
var status = currentKnowledgeRagSummary().status || {};
var url = publicKnowledgeRagDashboardUrl(status);
if (!url) return;
window.open(url, '_blank', 'noopener');
var url = publicKnowledgeRagDashboardUrl(currentKnowledgeRagSummary().status || {});
if (url && typeof window.open === 'function') {
window.open(url, '_blank', 'noopener,noreferrer');
return;
}
if (url) window.location.href = url;
}
// --- 知识库详情 tab 切换 ---
@@ -2612,7 +2637,7 @@ export function createSidebarPageSettingsRuntime(context) {
var providerKnowledgeId = String(providerIds.knowledgeId || r.providerKnowledgeId || '').trim();
var providerChunkId = String(providerIds.chunkId || r.providerChunkId || r.chunkId || '').trim();
var preview = String(content).substring(0, 200);
html += '<div class="mnote-kb-search-result-item" data-kb-rag-search-result="true" data-knowledge-rag-provider="weknora" data-knowledge-rag-kb-id="' + escapeHtml(providerKnowledgeBaseId) + '" data-knowledge-rag-knowledge-id="' + escapeHtml(providerKnowledgeId) + '" data-knowledge-rag-chunk-id="' + escapeHtml(providerChunkId) + '" data-knowledge-rag-source-path="' + escapeHtml(String(source)) + '">' +
html += '<div class="mnote-kb-search-result-item" data-kb-rag-search-result="true" data-knowledge-rag-provider="' + escapeHtml(knowledgeRagProviderName(currentKnowledgeRagSummary().status || {})) + '" data-knowledge-rag-kb-id="' + escapeHtml(providerKnowledgeBaseId) + '" data-knowledge-rag-knowledge-id="' + escapeHtml(providerKnowledgeId) + '" data-knowledge-rag-chunk-id="' + escapeHtml(providerChunkId) + '" data-knowledge-rag-source-path="' + escapeHtml(String(source)) + '">' +
'<div class="mnote-kb-search-result-header">' +
'<span class="mnote-kb-search-result-index">#' + (i + 1) + '</span>' +
'<span class="mnote-kb-search-result-score" data-kb-rag-score-label="provider-ranking-score" data-kb-rag-score-is-similarity-percent="false">provider 排序分: ' + score + '</span>' +
@@ -2809,14 +2834,20 @@ export function createSidebarPageSettingsRuntime(context) {
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
_knowledgeRagExtensionsCache = data;
var indexedCount = (data.files && data.files.length) || data.total_files || 0;
var statusText = data.ready ? '就绪' : '未就绪';
var statusClass = data.ready ? 'mnote-status-ready' : 'mnote-status-pending';
var docs = knowledgeRagProviderDocuments(data);
var registry = data && data.registry && typeof data.registry === 'object' ? data.registry : {};
var entries = Array.isArray(registry.entries) ? registry.entries : [];
var indexedCount = entries.length || docs.length || data.total_files || 0;
var health = data && data.health && typeof data.health === 'object' ? data.health : {};
var body = health.health && typeof health.health === 'object' ? health.health : {};
var ready = data && data.ok === true && health.ok === true && (!body.status || body.status === 'healthy' || body.status === 'ok');
var statusText = ready ? '就绪' : '未就绪';
var statusClass = ready ? 'mnote-status-ready' : 'mnote-status-pending';
var lastIngest = data.last_ingest_time || '';
statusEl.innerHTML = '' +
'<div class="mnote-extensions-item">' +
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">知识库 provider 状态</span><span class="' + statusClass + '">' + escapeHtml(String(statusText)) + '</span></div>' +
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">已索引文件</span><span>' + indexedCount + ' 个</span></div>' +
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">已索引来源</span><span>' + indexedCount + ' 个</span></div>' +
(lastIngest ? '<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">最近更新</span><span>' + escapeHtml(String(lastIngest)) + '</span></div>' : '') +
'<div class="mnote-extensions-item-actions">' +
'<button type="button" class="wolai-page-settings-action" data-knowledge-rag-action="manage">管理知识库...</button>' +
@@ -3166,9 +3197,15 @@ export function createSidebarPageSettingsRuntime(context) {
var resp = await fetch('/api/knowledge-rag/status');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
var count = (data.files && data.files.length) || data.total_files || 0;
var statusText = data.ready ? '就绪' : '未就绪';
el.textContent = count + ' 文件 / ' + statusText;
var docs = knowledgeRagProviderDocuments(data);
var registry = data && data.registry && typeof data.registry === 'object' ? data.registry : {};
var entries = Array.isArray(registry.entries) ? registry.entries : [];
var health = data && data.health && typeof data.health === 'object' ? data.health : {};
var body = health.health && typeof health.health === 'object' ? health.health : {};
var ready = data && data.ok === true && health.ok === true && (!body.status || body.status === 'healthy' || body.status === 'ok');
var count = entries.length || docs.length || data.total_files || 0;
var statusText = ready ? '就绪' : '未就绪';
el.textContent = count + ' 来源 / ' + statusText;
} catch (_) {
el.textContent = '不可用';
}
@@ -2157,12 +2157,12 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
'<div class="wolai-search-options-left">' +
'<span class="wolai-search-switch-control"><span>仅匹配标题</span><button type="button" class="wolai-search-switch" data-search-switch="title" 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="exact" role="switch" aria-checked="false" aria-label="精确匹配"></button></span>' +
'<span class="wolai-search-sort-control"><span>资料库模式</span><select class="wolai-search-sort-value" data-search-knowledge-mode aria-label="资料库检索模式"><option value="exact" selected>关键词</option><option value="mix">综合</option><option value="hybrid">图谱混合</option><option value="naive">向量</option><option value="local">实体</option><option value="global">关系</option></select></span>' +
'<span class="wolai-search-sort-control"><span>知识库模式</span><select class="wolai-search-sort-value" data-search-knowledge-mode aria-label="知识库检索模式"><option value="exact" selected>关键词</option><option value="mix">综合</option><option value="hybrid">图谱混合</option><option value="naive">向量</option><option value="local">实体</option><option value="global">关系</option></select></span>' +
'<span class="wolai-search-sort-control"><span>按编辑时间</span><button type="button" class="wolai-search-sort-value" data-search-sort="updated" aria-label="按编辑时间范围">所有</button></span>' +
'<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 is-on" data-search-switch="knowledge" role="switch" aria-checked="true" aria-label="全盘资料库检索"></button></span>' +
'<span class="wolai-search-switch-control"><span>全盘知识库</span><button type="button" class="wolai-search-switch is-on" data-search-switch="knowledge" role="switch" aria-checked="true" 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>' +
@@ -2703,7 +2703,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
var knowledgeMode = searchSwitchValue(overlay, 'knowledge');
if (knowledgeMode && searchNonWhitespaceCharCount(query) < 2) {
activeSearchRequestId += 1;
meta.innerHTML = '<span>资料库检索</span><span>请输入至少 2 个字再搜索</span>';
meta.innerHTML = '<span>知识库检索</span><span>请输入至少 2 个字再搜索</span>';
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">请输入至少 2 个字再搜索</div>';
window.__mnoteSearchResults = [];
saveSearchUiState(overlay);
@@ -2746,7 +2746,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
var payload = await response.json();
if (requestId !== activeSearchRequestId) return;
var items = Array.isArray(payload.results) ? payload.results : [];
var ownerLabel = knowledgeMode ? ('资料库检索 · ' + knowledgeSearchModeLabel(payload.retrievalMode || knowledgeRequestMode)) : '工作区搜索';
var ownerLabel = knowledgeMode ? ('知识库检索 · ' + knowledgeSearchModeLabel(payload.retrievalMode || knowledgeRequestMode)) : '工作区搜索';
meta.innerHTML = '<span>' + ownerLabel + ' · 共 ' + items.length + ' 条匹配结果</span><span>Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开</span>';
if (!items.length) {
results.innerHTML = '<div class="wolai-search-empty" data-search-empty="true">没有搜索结果</div>';
@@ -2988,6 +2988,13 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
var knowledgeRagSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-knowledge-rag-settings"]');
if (knowledgeRagSettingsTrigger) {
e.preventDefault();
window.location.href = '/knowledge';
return;
}
var knowledgeRagDebugTrigger = closestAction(e.target, '[data-mnote-action="open-knowledge-rag-debug-settings"]');
if (knowledgeRagDebugTrigger) {
e.preventDefault();
openKnowledgeRagSettingsPopover();
return;
@@ -3087,6 +3094,10 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
void ingestKnowledgeRagSources();
return;
}
if (knowledgeRagActionName === 'ingest-active') {
window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-settings-action', { detail: { action: 'run-active' } }));
return;
}
if (knowledgeRagActionName === 'reindex-source') {
void ingestSingleKnowledgeRagSource(knowledgeRagAction.getAttribute('data-knowledge-rag-source-path') || '');
return;
@@ -3866,5 +3877,10 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
if (isPageSettingsOpen()) renderPageSettingsPopover();
});
restoreSidebarTreeTab();
if (window.location.pathname === '/debug/knowledge-rag' || window.location.pathname === '/knowledge') {
window.setTimeout(function() {
openKnowledgeRagSettingsPopover();
}, 0);
}
startLocalFolderSidebarWatch();
})();