chore: checkpoint turso and ai runtime work
This commit is contained in:
@@ -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 registry,WeKnora 负责解析、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();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user