feat(rag): replace LiteParse flows with LightRAG provider

This commit is contained in:
lix-2026
2026-06-07 01:10:31 +08:00
parent f46c2fb5d0
commit 1f25364374
40 changed files with 7232 additions and 2350 deletions
@@ -258,6 +258,26 @@ import {
if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(text);
return text.replace(/["\\]/g, '\\$&');
};
const localFileOpenUrl = (rootUri, path) => {
const normalizedRootUri = String(rootUri || '').trim();
const normalizedPath = String(path || '').trim();
if (!normalizedRootUri || !normalizedPath) return '';
const url = new URL('/api/local-folder/files/open', window.location.origin);
url.searchParams.set('rootUri', normalizedRootUri);
url.searchParams.set('path', normalizedPath);
return url.toString();
};
const resourceHrefFromUrlState = (rootUri, path, title) => {
const fileUrl = localFileOpenUrl(rootUri, path);
if (!fileUrl) return '';
if (/\.pdf$/i.test(String(title || path || ''))) {
const url = new URL('/pdf-preview', window.location.origin);
url.searchParams.set('fileUrl', fileUrl);
url.searchParams.set('fileName', title || path);
return url.toString();
}
return fileUrl;
};
const applyDocumentEvidenceLocatorFromUrl = (root) => {
if (!(root instanceof HTMLElement)) return;
const url = currentUrl();
@@ -289,7 +309,7 @@ import {
const url = currentUrl();
const raw = String(url.searchParams.get('resourceTab') || '').trim();
if (!raw) return null;
const objectIdentity = raw.includes('::') ? raw.slice(raw.indexOf('::') + 2) : raw;
const objectIdentity = raw.startsWith('resource:') ? raw : (raw.includes('::') ? raw.slice(raw.indexOf('::') + 2) : raw);
if (!objectIdentity.startsWith('resource:file:')) return null;
const rootUri = currentWebShellRootUri();
if (!rootUri) return null;
@@ -299,6 +319,7 @@ import {
const path = rest.slice(prefix.length).replace(/^\/+/, '');
if (!path) return null;
const title = path.split('/').filter(Boolean).pop() || path;
const href = resourceHrefFromUrlState(rootUri, path, title);
return {
objectIdentity,
documentId: currentWebShellDocumentId(),
@@ -309,6 +330,8 @@ import {
assetId: `local:asset:${path}`,
title,
fileName: title,
href,
officeUrl: href,
openTarget: 'active-tab',
page: url.searchParams.get('page') || undefined,
bbox: url.searchParams.get('bbox') || undefined,
@@ -1021,18 +1044,22 @@ import {
});
}, { once: true });
const restoreResourceTabFromUrl = () => {
const restoreInput = restoreResourceTabInputFromUrl();
if (!restoreInput) return Promise.resolve(false);
return openResourceInActiveTab(restoreInput).catch((error) => {
console.warn('mnote resource tab URL 恢复失败', error);
return false;
});
};
if (paneRuntimes.length) {
Promise.all(paneRuntimes.map((paneRuntime) => mountPane(paneRuntime))).then(() => {
const restoreInput = restoreResourceTabInputFromUrl();
if (restoreInput) {
return openResourceInActiveTab(restoreInput).catch((error) => {
console.warn('mnote resource tab URL 恢复失败', error);
return false;
});
}
return false;
return restoreResourceTabFromUrl();
}).catch((error) => {
console.error('mnote multi-pane editor mount failed', error);
});
} else {
void restoreResourceTabFromUrl();
}
})();
@@ -1373,17 +1373,17 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const statusNode = entry.panel.querySelector('[data-mnote-local-ocr-status-text]');
if (statusNode instanceof HTMLElement) {
statusNode.textContent = message || (
normalizedStatus === 'done' ? 'OCR 已完成'
: normalizedStatus === 'running' ? 'OCR 处理中'
: normalizedStatus === 'failed' ? 'OCR 失败'
: normalizedStatus === 'stale' ? 'OCR 需更新'
: 'OCR 未生成'
normalizedStatus === 'done' ? '资料库已索引'
: normalizedStatus === 'running' ? '资料库索引中'
: normalizedStatus === 'failed' ? '资料库索引失败'
: normalizedStatus === 'stale' ? '资料库需更新'
: '资料库未索引'
);
}
const openButton = entry.panel.querySelector('[data-mnote-local-ocr-action="open"]');
if (openButton instanceof HTMLButtonElement) openButton.disabled = !entry.localOcrJob?.ocrRootRelativePath;
if (openButton instanceof HTMLButtonElement) openButton.disabled = true;
const insertButton = entry.panel.querySelector('[data-mnote-local-ocr-action="insert"]');
if (insertButton instanceof HTMLButtonElement) insertButton.disabled = !entry.localOcrJob?.ocrRootRelativePath;
if (insertButton instanceof HTMLButtonElement) insertButton.disabled = true;
window.dispatchEvent(new CustomEvent('mnote:local-ocr-job-updated', {
detail: {
status: normalizedStatus,
@@ -1394,15 +1394,50 @@ export const createResourceTabRuntime = (dependencies = {}) => {
}));
};
const knowledgeRagJobFromRegistryEntry = (registryEntry, rootUri) => {
if (!registryEntry || typeof registryEntry !== 'object') return null;
const sourceRootRelativePath = String(registryEntry.sourceRootRelativePath || registryEntry.source_path || '').trim();
if (!sourceRootRelativePath) return null;
const indexed = Boolean(registryEntry.indexedAtMs || registryEntry.indexed_at_ms) && Boolean(registryEntry.lightRagDocId || registryEntry.light_rag_doc_id);
const failed = registryEntry.deletedAtMs != null || registryEntry.deleted_at_ms != null || registryEntry.stale === true;
const status = failed ? 'failed' : indexed ? 'done' : 'running';
return {
taskKind: 'knowledge_rag',
jobId: String(registryEntry.sourceId || registryEntry.source_id || `knowledge-rag:${sourceRootRelativePath}`),
sourceRootRelativePath,
rootUri: String(registryEntry.rootUri || rootUri || '').trim(),
ocrRootRelativePath: '',
provider: 'lightrag',
status,
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,
error: status === 'failed' ? 'knowledge_rag_source_stale_or_deleted' : '',
lightRagDocId: String(registryEntry.lightRagDocId || registryEntry.light_rag_doc_id || ''),
lightRagFilePath: String(registryEntry.lightRagFilePath || registryEntry.light_rag_file_path || ''),
};
};
const knowledgeRagRegistryEntries = (payload) => {
const registry = payload && payload.registry && typeof payload.registry === 'object' ? payload.registry : null;
return Array.isArray(registry?.entries) ? registry.entries : [];
};
const readLocalOcrStatus = async (entry) => {
if (!isLocalOcrSourceEntry(entry)) return null;
const url = new URL('/api/local-folder/ocr/status', window.location.origin);
const url = new URL('/api/knowledge-rag/status', window.location.origin);
url.searchParams.set('rootUri', entry.rootUri);
url.searchParams.set('sourceRootRelativePath', entry.path);
const workspaceId = String(entry.workspaceId || currentWebShellWorkspaceId() || '').trim();
if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
const response = await fetch(url.toString(), { cache: 'no-store', headers: { accept: 'application/json' } });
const payload = await response.json().catch(() => null);
if (!response.ok || !payload || payload.ok !== true) return null;
return payload.job || null;
const targetPath = String(entry.path || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
const registryEntry = knowledgeRagRegistryEntries(payload).find((candidate) => {
return String(candidate?.sourceRootRelativePath || '').trim().replace(/\\/g, '/').replace(/^\/+/, '') === targetPath;
});
return knowledgeRagJobFromRegistryEntry(registryEntry, entry.rootUri);
};
const localOcrTaskState = {
@@ -1475,7 +1510,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
toggle.addEventListener('click', (event) => {
if (toggle.getAttribute('data-mnote-action') === 'open-ocr-settings') {
event.preventDefault();
window.dispatchEvent(new CustomEvent('mnote:open-local-ocr-settings'));
window.dispatchEvent(new CustomEvent('mnote:open-knowledge-rag-settings'));
return;
}
void runManualLocalOcrForActiveTarget(toggle).catch((error) => {
@@ -1506,7 +1541,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const statusTextForLocalOcrJob = (job) => {
const status = String(job?.status || '').trim();
if (job?.stageLabel) return String(job.stageLabel);
if (job?.taskKind === 'local_index') {
if (job?.taskKind === 'local_index' || job?.taskKind === 'knowledge_rag') {
return status === 'done' ? '索引已完成'
: status === 'failed' ? '索引失败'
: status === 'running' ? '索引中'
@@ -1557,20 +1592,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
document.body.appendChild(dock);
}
let toggle = document.querySelector('[data-testid="mnote-local-ocr-task-toggle"]');
if (!(toggle instanceof HTMLButtonElement)) {
const actions = document.querySelector('.wolai-topbar-actions');
toggle = document.createElement('button');
toggle.type = 'button';
toggle.className = 'wolai-icon-button mnote-local-ocr-task-toggle';
toggle.setAttribute('title', 'OCR 设置');
toggle.setAttribute('aria-label', 'OCR 设置');
toggle.setAttribute('data-testid', 'mnote-local-ocr-task-toggle');
toggle.setAttribute('data-mnote-action', 'open-ocr-settings');
toggle.innerHTML = '<span class="material-symbols-outlined" data-icon="document_scanner" aria-hidden="true"></span><span class="mnote-local-ocr-task-badge" data-mnote-local-ocr-task-count hidden>0</span>';
if (actions instanceof HTMLElement) actions.appendChild(toggle);
else document.body.appendChild(toggle);
}
bindLocalOcrTopbarAction();
if (toggle instanceof HTMLButtonElement) bindLocalOcrTopbarAction();
if (dock.getAttribute('data-mnote-local-ocr-bound') === 'true') return dock;
dock.setAttribute('data-mnote-local-ocr-bound', 'true');
dock.addEventListener('click', (event) => {
@@ -1617,23 +1639,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
}
const openButton = event.target instanceof Element ? event.target.closest('[data-mnote-local-ocr-task-open]') : null;
if (openButton instanceof HTMLElement) {
const sourcePath = openButton.getAttribute('data-mnote-local-ocr-task-open') || '';
const job = localOcrTaskState.jobsBySource.get(sourcePath) || null;
if (job) {
void openResourceInActiveTab({
kind: 'markdown',
title: String(job.ocrRootRelativePath || '').split('/').filter(Boolean).pop() || 'OCR',
path: job.ocrRootRelativePath,
objectIdentity: `local-ocr:${job.ocrRootRelativePath}`,
assetId: `local-ocr:${job.ocrRootRelativePath}`,
documentId: job.ownerDocumentId || currentWebShellDocumentId() || '',
ownerDocumentId: job.ownerDocumentId || currentWebShellDocumentId() || '',
workspaceId: currentWebShellWorkspaceId() || '',
sourceKind: 'local_folder',
rootUri: localOcrTaskState.rootUri || '',
resourceKind: 'markdown',
}).catch((error) => console.warn('mnote local OCR 任务打开失败', error));
}
window.dispatchEvent(new CustomEvent('mnote:open-knowledge-rag-settings'));
}
const retryButton = event.target instanceof Element ? event.target.closest('[data-mnote-local-ocr-task-retry]') : null;
if (retryButton instanceof HTMLElement) {
@@ -1670,7 +1676,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
.filter((node) => node instanceof HTMLButtonElement);
taskToggles.forEach((toggle) => {
const opensSettings = toggle.getAttribute('data-mnote-action') === 'open-ocr-settings';
const label = runningCount > 0 ? `${runningCount} OCR 处理中,打开 OCR 设置` : (jobs.length > 0 ? `${jobs.length} OCR 任务,打开 OCR 设置` : 'OCR 设置');
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);
@@ -1681,7 +1687,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
badge.textContent = String(runningCount > 0 ? runningCount : jobs.length);
badge.hidden = jobs.length === 0;
} else {
toggle.textContent = runningCount > 0 ? `OCR ${runningCount} 处理中` : `OCR ${jobs.length}`;
toggle.textContent = runningCount > 0 ? `索引 ${runningCount} 处理中` : `索引 ${jobs.length}`;
}
});
const drawer = dock.querySelector('[data-testid="mnote-local-ocr-task-drawer"]');
@@ -1731,7 +1737,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
row.setAttribute('data-mnote-local-ocr-task-row', String(job.sourceRootRelativePath || ''));
row.setAttribute('data-mnote-local-ocr-task-status', String(job.status || ''));
row.setAttribute('data-mnote-local-ocr-task-category', localOcrTaskCategory(job));
const title = String(job.sourceRootRelativePath || '').split('/').filter(Boolean).pop() || 'OCR';
const title = String(job.sourceRootRelativePath || '').split('/').filter(Boolean).pop() || '资料库索引';
const category = localOcrTaskCategory(job);
const progress = localOcrTaskProgress(job);
row.innerHTML = '<div class="mnote-local-ocr-task-main"><div class="mnote-local-ocr-task-title-line"><strong></strong><em></em></div><span></span><div class="mnote-local-ocr-task-progress" role="progressbar"><i></i></div></div><div class="mnote-local-ocr-task-actions"><button type="button" data-mnote-local-ocr-task-open>打开</button><button type="button" data-mnote-local-ocr-task-retry>重试</button><button type="button" data-mnote-local-ocr-task-clear>清除</button><button type="button" data-mnote-local-ocr-task-delete>删除</button></div>';
@@ -1766,7 +1772,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
if (open instanceof HTMLButtonElement) {
open.setAttribute('data-mnote-local-ocr-task-open', String(job.sourceRootRelativePath || ''));
open.disabled = !job.ocrRootRelativePath;
open.hidden = job.taskKind === 'local_index';
open.hidden = job.taskKind === 'local_index' || job.taskKind === 'knowledge_rag';
}
const retry = row.querySelector('[data-mnote-local-ocr-task-retry]');
if (retry instanceof HTMLButtonElement) {
@@ -1780,7 +1786,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const deleteOcr = row.querySelector('[data-mnote-local-ocr-task-delete]');
if (deleteOcr instanceof HTMLButtonElement) {
deleteOcr.setAttribute('data-mnote-local-ocr-task-delete', String(job.sourceRootRelativePath || ''));
deleteOcr.hidden = job.taskKind === 'local_index' || !job.ocrRootRelativePath;
deleteOcr.hidden = job.taskKind === 'local_index' || !String(job.sourceRootRelativePath || '').trim();
}
list.appendChild(row);
});
@@ -1790,13 +1796,15 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const normalizedRoot = String(rootUri || '').trim();
if (!normalizedRoot) return;
localOcrTaskState.rootUri = normalizedRoot;
const url = new URL('/api/local-folder/ocr/jobs', window.location.origin);
const url = new URL('/api/knowledge-rag/status', window.location.origin);
url.searchParams.set('rootUri', normalizedRoot);
const workspaceId = String(currentWebShellWorkspaceId() || '').trim();
if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
const response = await fetch(url.toString(), { cache: 'no-store', headers: { accept: 'application/json' } });
const payload = await response.json().catch(() => null);
if (!response.ok || !payload || payload.ok !== true) return;
(Array.isArray(payload.jobs) ? payload.jobs : []).forEach((job) => {
if (job && typeof job === 'object') job.rootUri = normalizedRoot;
knowledgeRagRegistryEntries(payload).forEach((entry) => {
const job = knowledgeRagJobFromRegistryEntry(entry, normalizedRoot);
updateLocalOcrTaskState(job);
});
renderLocalOcrTaskDock();
@@ -1827,74 +1835,37 @@ export const createResourceTabRuntime = (dependencies = {}) => {
};
const openLocalOcrSidecar = async (entry, job) => {
const target = job || entry?.localOcrJob || null;
const ocrPath = String(target?.ocrRootRelativePath || '').trim();
if (!ocrPath || typeof openResourceInActiveTab !== 'function') return false;
const title = ocrPath.split('/').filter(Boolean).pop() || 'OCR';
return await openResourceInActiveTab({
kind: 'markdown',
title,
path: ocrPath,
objectIdentity: `local-ocr:${ocrPath}`,
assetId: `local-ocr:${ocrPath}`,
documentId: String(entry.ownerDocumentId || entry.documentId || currentWebShellDocumentId() || '').trim(),
ownerDocumentId: String(entry.ownerDocumentId || entry.documentId || currentWebShellDocumentId() || '').trim(),
workspaceId: String(entry.workspaceId || currentWebShellWorkspaceId() || '').trim(),
sourceKind: 'local_folder',
rootUri: String(entry.rootUri || '').trim(),
resourceKind: 'markdown',
paneRole: normalizePaneRole(entry.paneRole),
});
window.dispatchEvent(new CustomEvent('mnote:open-knowledge-rag-settings'));
return false;
};
const insertLocalOcrLink = async (entry, job) => {
const target = job || entry?.localOcrJob || null;
const ocrPath = String(target?.ocrRootRelativePath || '').trim();
if (!ocrPath) return false;
const response = await fetch('/api/local-folder/ocr/insert', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
rootUri: String(entry.rootUri || '').trim(),
documentId: String(entry.ownerDocumentId || entry.documentId || currentWebShellDocumentId() || '').trim(),
ocrRootRelativePath: ocrPath,
mode: 'link',
}),
});
const payload = await response.json().catch(() => null);
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload?.error?.message || `local_ocr_insert_failed_${response.status}`);
}
setLocalOcrStatus(entry, 'done', 'OCR 链接已插入正文', target);
return true;
throw new Error('OCR sidecar 已退役,请使用 LightRAG 资料库引用与问答。');
};
const createLocalOcrJob = async (entry) => {
if (!isLocalOcrSourceEntry(entry)) return null;
const provider = localOcrProvider();
const entryRootUri = String(entry.rootUri || '').trim();
if (entryRootUri) localOcrTaskState.rootUri = entryRootUri;
const startedAt = Date.now();
const pendingJob = localOcrJobSnapshotFromEntry(entry, 'running', provider, '处理中', startedAt);
setLocalOcrStatus(entry, 'running', 'OCR 处理中', pendingJob);
const pendingJob = localOcrJobSnapshotFromEntry(entry, 'running', 'lightrag', '资料库索引中', startedAt);
pendingJob.taskKind = 'knowledge_rag';
setLocalOcrStatus(entry, 'running', '资料库索引中', pendingJob);
updateLocalOcrTaskState(pendingJob);
const workspaceId = String(entry.workspaceId || currentWebShellWorkspaceId() || '').trim();
const body = {
workspaceId,
rootUri: String(entry.rootUri || '').trim(),
documentId: String(entry.ownerDocumentId || entry.documentId || currentWebShellDocumentId() || '').trim(),
sourceRootRelativePath: String(entry.path || '').trim(),
provider,
sources: [{ sourcePath: String(entry.path || '').trim() }],
};
if (provider === 'mock') {
body.mockMarkdown = `# OCR Result\n\n${entry.title || entry.path} OCR UI smoke text`;
}
const response = await fetch('/api/local-folder/ocr/jobs', {
const response = await fetch('/api/knowledge-rag/ingest', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify(body),
});
const payload = await response.json().catch(() => null);
if (!response.ok || !payload || payload.ok !== true) {
const message = payload?.error?.message || payload?.message || payload?.error?.code || `local_ocr_job_failed_${response.status}`;
const message = payload?.error?.message || payload?.message || payload?.error?.code || `knowledge_rag_ingest_failed_${response.status}`;
const failedJob = {
...pendingJob,
status: 'failed',
@@ -1907,10 +1878,22 @@ export const createResourceTabRuntime = (dependencies = {}) => {
updateLocalOcrTaskState(failedJob);
throw new Error(message);
}
const job = payload.job && typeof payload.job === 'object' ? { ...payload.job, rootUri: entryRootUri } : null;
const registryEntry = knowledgeRagRegistryEntries(payload).find((candidate) => {
return String(candidate?.sourceRootRelativePath || '').trim().replace(/\\/g, '/').replace(/^\/+/, '') === String(entry.path || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
});
const job = knowledgeRagJobFromRegistryEntry(registryEntry, entryRootUri) || {
...pendingJob,
status: payload.retryRequired ? 'running' : 'done',
stageLabel: payload.retryRequired ? 'LightRAG 忙碌,等待重试' : '资料库已提交索引',
updatedAtMs: Date.now(),
finishedAtMs: payload.retryRequired ? null : Date.now(),
};
const displayStatus = localOcrDisplayStatus(job, 'done');
setLocalOcrStatus(entry, displayStatus, displayStatus === 'stale' ? 'OCR 需更新' : 'OCR 已完成', job);
setLocalOcrStatus(entry, displayStatus, displayStatus === 'stale' ? '资料库需更新' : statusTextForLocalOcrJob(job), job);
if (job) updateLocalOcrTaskState(job);
window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-source-updated', {
detail: { rootUri: entryRootUri, workspaceId, sourceRootRelativePath: String(entry.path || '').trim(), result: payload }
}));
return job;
};
@@ -1919,27 +1902,24 @@ export const createResourceTabRuntime = (dependencies = {}) => {
if (!sourcePath) return false;
const job = localOcrTaskState.jobsBySource.get(sourcePath) || null;
const rootUri = String(job?.rootUri || localOcrTaskState.rootUri || currentWebShellRootUri() || '').trim();
const response = await fetch('/api/local-folder/ocr/delete', {
const response = await fetch('/api/knowledge-rag/delete-source', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
rootUri,
sourceRootRelativePath: sourcePath,
workspaceId: String(currentWebShellWorkspaceId() || '').trim(),
sourcePath,
}),
});
const payload = await response.json().catch(() => null);
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload?.error?.message || `local_ocr_delete_failed_${response.status}`);
}
if (payload.deleted !== true) {
throw new Error('local_ocr_delete_noop');
throw new Error(payload?.error?.message || `knowledge_rag_delete_failed_${response.status}`);
}
localOcrTaskState.jobsBySource.delete(sourcePath);
renderLocalOcrTaskDock();
const ocrPath = String(job?.ocrRootRelativePath || '').trim();
if (ocrPath) {
dispatchLocalOcrFileTreeRefresh({ ...job, status: 'stale', updatedAtMs: Date.now(), ocrRootRelativePath: ocrPath }, localOcrTaskState.rootUri);
}
window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-source-updated', {
detail: { rootUri, workspaceId: String(currentWebShellWorkspaceId() || '').trim(), sourceRootRelativePath: sourcePath, result: payload }
}));
return true;
};
@@ -2118,14 +2098,14 @@ export const createResourceTabRuntime = (dependencies = {}) => {
});
});
}
setLocalOcrStatus(entry, 'idle', 'OCR 未生成', null);
setLocalOcrStatus(entry, 'idle', '资料库未索引', null);
ensureLocalOcrTaskDock();
ensureLocalOcrTaskEvents(entry.rootUri);
void loadLocalOcrJobs(entry.rootUri).catch(() => undefined);
void readLocalOcrStatus(entry).then((job) => {
if (!job) return;
const displayStatus = localOcrDisplayStatus(job, 'done');
setLocalOcrStatus(entry, displayStatus, displayStatus === 'stale' ? 'OCR 需更新' : 'OCR 已完成', job);
setLocalOcrStatus(entry, displayStatus, displayStatus === 'stale' ? '资料库需更新' : statusTextForLocalOcrJob(job), job);
updateLocalOcrTaskState(job);
}).catch(() => undefined);
};
@@ -421,70 +421,77 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
}
async function runLocalOcrForDetail(detail, trigger) {
var sourceRootRelativePath = localOcrSourceRelativePath(detail);
var rootUri = localOcrRootUri(detail, trigger);
var documentId = String(detail && detail.documentId || currentDocumentId() || '').trim();
if (!sourceRootRelativePath || !rootUri || !documentId) {
throw new Error('缺少 OCR 来源、rootUri 或 owner documentId');
}
if (!isLocalOcrSourceFileName(String(detail && (detail.title || detail.fileName) || sourceRootRelativePath))) {
throw new Error('OCR 仅支持图片和 PDF');
}
var provider = localOcrProvider();
var body = {
rootUri: rootUri,
documentId: documentId,
sourceRootRelativePath: sourceRootRelativePath,
provider: provider
};
if (provider === 'mock') {
body.mockMarkdown = '# OCR Result\n\n' + (detail.title || sourceRootRelativePath) + ' OCR menu smoke text';
}
document.documentElement.setAttribute('data-mnote-local-ocr-menu-status', 'running');
var response = await fetch('/api/local-folder/ocr/jobs', {
var payload = await ingestKnowledgeRagForDetail(detail, trigger).catch(function(error) {
document.documentElement.setAttribute('data-mnote-local-ocr-menu-status', 'failed');
throw error;
});
var sourceRootRelativePath = localOcrSourceRelativePath(detail)
|| String(detail && (detail.localRelativePath || detail.path || detail.title) || '').trim();
var rootUri = localOcrRootUri(detail, trigger);
var status = payload && payload.retryRequired ? 'retry' : 'done';
document.documentElement.setAttribute('data-mnote-local-ocr-menu-status', status);
document.documentElement.setAttribute('data-mnote-local-ocr-menu-path', sourceRootRelativePath);
window.dispatchEvent(new CustomEvent('mnote:local-ocr-job-updated', {
detail: { status: status, job: payload, rootUri: rootUri, sourceRootRelativePath: sourceRootRelativePath }
}));
return payload;
}
async function ingestKnowledgeRagForDetail(detail, trigger) {
var sourceRootRelativePath = localOcrSourceRelativePath(detail)
|| String(detail && (detail.localRelativePath || detail.path || detail.title) || '').trim();
var rootUri = localOcrRootUri(detail, trigger);
if (!sourceRootRelativePath || !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');
document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-path', sourceRootRelativePath);
var response = await fetch('/api/knowledge-rag/ingest', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify(body)
body: JSON.stringify({
workspaceId: workspaceId,
rootUri: rootUri,
sources: [{ sourcePath: sourceRootRelativePath }]
})
});
var payload = await response.json().catch(function() { return null; });
if (!response.ok || !payload || payload.ok !== true) {
var message = payload && payload.error && payload.error.message ? payload.error.message : 'OCR 生成失败';
document.documentElement.setAttribute('data-mnote-local-ocr-menu-status', 'failed');
if (!response.ok || !payload || payload.ok === false) {
var message = payload && payload.message ? payload.message : '资料库索引失败';
document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-status', 'failed');
throw new Error(message);
}
var job = payload.job || {};
document.documentElement.setAttribute('data-mnote-local-ocr-menu-status', String(job.status || 'done'));
if (job.ocrRootRelativePath) {
document.documentElement.setAttribute('data-mnote-local-ocr-menu-path', String(job.ocrRootRelativePath));
if (typeof openLocalResourceInActiveTab === 'function') {
await openLocalResourceInActiveTab({
path: String(job.ocrRootRelativePath),
title: String(job.ocrRootRelativePath).split('/').filter(Boolean).pop() || 'OCR',
kind: 'markdown',
assetId: 'local-ocr:' + String(job.ocrRootRelativePath),
documentId: documentId,
workspaceId: String(detail && detail.workspaceId || resolveWorkspaceId(document.body) || '').trim(),
rootUri: rootUri
});
}
}
window.dispatchEvent(new CustomEvent('mnote:local-ocr-job-updated', {
detail: { status: String(job.status || 'done'), job: job, rootUri: rootUri, sourceRootRelativePath: sourceRootRelativePath }
document.documentElement.setAttribute('data-mnote-knowledge-rag-menu-status', payload.retryRequired ? 'retry' : 'done');
window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-source-updated', {
detail: { rootUri: rootUri, workspaceId: workspaceId, sourceRootRelativePath: sourceRootRelativePath, result: payload }
}));
return job;
return payload;
}
function handleTreeContextMenuAction(action, detail, trigger) {
closeTreeContextMenu();
detail = detail || {};
if (action === 'local-ocr') {
recordFileTreeAction('local-ocr', detail);
recordFileTreeAction('knowledge-rag-index', detail);
recordFileTreeActionStatus('pending', detail);
void runLocalOcrForDetail(detail, trigger).then(function(job) {
recordFileTreeActionStatus(String(job && job.status || 'done'), detail);
recordFileTreeActionStatus(job && job.retryRequired ? 'retry' : 'done', detail);
}).catch(function(error) {
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
window.alert(error && error.message ? error.message : 'OCR 生成失败');
window.alert(error && error.message ? error.message : '资料库索引失败');
});
return;
}
if (action === 'knowledge-rag-index') {
recordFileTreeAction('knowledge-rag-index', detail);
recordFileTreeActionStatus('pending', detail);
void ingestKnowledgeRagForDetail(detail, trigger).then(function() {
recordFileTreeActionStatus('done', detail);
}).catch(function(error) {
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
window.alert(error && error.message ? error.message : '资料库索引失败');
});
return;
}
@@ -1010,10 +1017,10 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
{ separator: true },
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly && !editor.dirty' }
];
if (localOcrSupported) {
var ocrItem = { action: 'local-ocr', icon: 'auto_awesome', label: '生成 OCR', when: '!workspace.readonly' };
var insertAt = isAttachment ? 11 : isAsset ? 3 : -1;
if (insertAt >= 0) items.splice(insertAt, 0, ocrItem);
if (currentSourceKind() === 'local_folder' && detail.rowKind !== 'index') {
var ragItem = { action: 'knowledge-rag-index', icon: 'travel_explore', label: '加入资料库索引', when: '!workspace.readonly' };
var insertAt = isAttachment ? 11 : isAsset ? 3 : 3;
if (insertAt >= 0) items.splice(insertAt, 0, ragItem);
}
items.forEach(function(item) {
if (item.when !== undefined && !evaluateSidebarFileTreeWhen(ctx, item.when)) {
@@ -443,43 +443,7 @@ export function createSidebarPageAiTargetRuntime(context) {
}
async function fetchPageAiOcrSidecarContext(editorTarget) {
if (currentSourceKind() !== 'local_folder') return null;
var target = editorTarget && typeof editorTarget === 'object' ? editorTarget : {};
var workspacePath = target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {};
var relativePath = String(workspacePath.relativePath || target.path || '').trim();
var resourceKind = String(target.resourceKind || target.editorKind || workspacePath.resourceKind || '').trim();
if (!relativePath || !pageAiOcrEligiblePath(relativePath) || !pageAiOcrEligibleResourceKind(resourceKind)) return null;
var rootUri = String(workspacePath.rootUri || currentRootUri() || '').trim();
if (!rootUri) return null;
try {
var statusUrl = new URL('/api/local-folder/ocr/status', window.location.origin);
statusUrl.searchParams.set('rootUri', rootUri);
statusUrl.searchParams.set('sourceRootRelativePath', relativePath);
var statusResponse = await fetch(statusUrl.toString(), { cache: 'no-store', headers: { accept: 'application/json' } });
var statusPayload = await statusResponse.json().catch(function() { return null; });
var job = statusResponse.ok && statusPayload && statusPayload.ok === true ? statusPayload.job : null;
var ocrPath = String(job && job.ocrRootRelativePath || '').trim();
if (!ocrPath || String(job.status || '') === 'failed') return null;
var readUrl = new URL('/api/local-folder/ocr/read', window.location.origin);
readUrl.searchParams.set('rootUri', rootUri);
readUrl.searchParams.set('ocrRootRelativePath', ocrPath);
var readResponse = await fetch(readUrl.toString(), { cache: 'no-store', headers: { accept: 'application/json' } });
var readPayload = await readResponse.json().catch(function() { return null; });
if (!readResponse.ok || !readPayload || readPayload.ok !== true) return null;
return {
schema: 'mnote.local_ocr_context.v1',
source: 'local_ocr_sidecar',
sourceRootRelativePath: relativePath,
ocrRootRelativePath: ocrPath,
status: String(job.status || ''),
stale: job.stale === true,
provider: String(job.provider || ''),
modelVersion: String(job.modelVersion || ''),
plainTextPreview: pageAiOcrBodyPreview(readPayload.markdown || ''),
};
} catch (_) {
return null;
}
return null;
}
async function pageAiEnrichOcrContextRefs(contextRefs, agentTargetPackage, editorTarget) {
@@ -71,6 +71,15 @@ export function createSidebarPageSettingsRuntime(context) {
return Object.assign({ 'localOcr.autoEnabled': false }, pageUiState.localOcrPreferences || {});
}
function currentKnowledgeRagSummary() {
return pageUiState.knowledgeRagSummary || {};
}
var knowledgeRagBridgeTimer = 0;
var knowledgeRagBridgeGeneration = 0;
var knowledgeRagBridgeAttempt = 0;
var knowledgeRagBridgeDelays = [1200, 2000, 3200, 5000, 8000, 13000, 21000, 34000];
function pageWidthModeLabel(mode) {
if (mode === 'inherit') return '继承默认';
if (mode === 'readable') return '阅读';
@@ -337,6 +346,12 @@ export function createSidebarPageSettingsRuntime(context) {
ocrTrigger.setAttribute('data-state', ocrOpen ? 'open' : 'closed');
ocrTrigger.setAttribute('aria-expanded', ocrOpen ? 'true' : 'false');
}
var ragTrigger = document.querySelector('[data-testid="mnote-knowledge-rag-settings-toggle"]');
var ragOpen = isKnowledgeRagSettingsOpen();
if (ragTrigger instanceof HTMLElement) {
ragTrigger.setAttribute('data-state', ragOpen ? 'open' : 'closed');
ragTrigger.setAttribute('aria-expanded', ragOpen ? 'true' : 'false');
}
}
function createPageOptionRow(key, type) {
@@ -380,8 +395,8 @@ export function createSidebarPageSettingsRuntime(context) {
return '' +
'<label class="wolai-page-setting-row" data-page-setting-row="localOcrAutoEnabled">' +
'<span class="wolai-page-setting-copy">' +
'<span class="wolai-page-setting-label">自动 OCR</span>' +
'<span class="wolai-page-setting-hint">默认关闭;仅作为搜索和 AI 索引补充处理图片与图片型 PDF</span>' +
'<span class="wolai-page-setting-label">资料库自动索引</span>' +
'<span class="wolai-page-setting-hint">LiteParse/OCR sidecar 已退役;图片、PDF、Office 统一由 LightRAG 资料库处理</span>' +
'</span>' +
'<input type="checkbox" class="wolai-page-setting-checkbox" data-local-ocr-option-checkbox="autoEnabled" />' +
'</label>';
@@ -659,27 +674,53 @@ export function createSidebarPageSettingsRuntime(context) {
}
function ensureLocalOcrSettingsPopover() {
var existing = document.querySelector('[data-testid="mnote-local-ocr-settings-popover"]');
if (existing instanceof HTMLElement) return existing;
var popover = document.createElement('div');
popover.className = 'wolai-page-settings-popover mnote-local-ocr-settings-popover';
popover.setAttribute('data-testid', 'mnote-local-ocr-settings-popover');
popover.setAttribute('data-mnote-surface', 'local-ocr-settings');
popover.hidden = true;
popover.innerHTML = '' +
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-local-ocr-settings-panel" role="dialog" aria-modal="false" aria-label="OCR 设置">' +
return ensureKnowledgeRagSettingsPopover();
}
function createKnowledgeRagSettingsPanelHtml() {
return '' +
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-knowledge-rag-settings-panel" role="dialog" aria-modal="false" aria-label="资料库问答设置">' +
'<div class="mnote-settings-panel-head">' +
'<strong>OCR 设置</strong>' +
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭 OCR 设置">×</button>' +
'<strong>资料库问答</strong>' +
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭资料库问答设置">×</button>' +
'</div>' +
'<div class="wolai-page-settings-section">' +
createLocalOcrAutoRow() +
'<div class="wolai-page-settings-index-actions">' +
'<button type="button" data-local-ocr-settings-action="run-active">识别当前资源</button>' +
'<button type="button" data-local-ocr-settings-action="tasks">查看 OCR 任务</button>' +
'</div>' +
'<div class="wolai-page-settings-index-panel" data-testid="mnote-knowledge-rag-settings-panel">' +
'<div class="wolai-page-settings-index-status" data-testid="mnote-knowledge-rag-status"></div>' +
'<div class="mnote-knowledge-rag-meta" data-testid="mnote-knowledge-rag-meta"></div>' +
'<section class="wolai-page-settings-index-group mnote-knowledge-rag-control-group">' +
'<div class="wolai-page-settings-index-title">资料操作</div>' +
'<div class="mnote-knowledge-rag-source-inputs" data-testid="mnote-knowledge-rag-source-inputs">' +
knowledgeRagSourceInputRow('') +
'</div>' +
'<div class="mnote-knowledge-rag-source-tools">' +
'<button type="button" class="wolai-page-settings-index-add" data-knowledge-rag-action="add-source">新增来源</button>' +
'<button type="button" class="wolai-page-settings-index-add" data-knowledge-rag-action="use-filetree-selection">使用选中文件树项</button>' +
'</div>' +
'<div class="wolai-page-settings-index-actions">' +
'<button type="button" data-knowledge-rag-action="refresh">刷新状态</button>' +
'<button type="button" data-knowledge-rag-action="ingest">索引来源</button>' +
'<button type="button" data-knowledge-rag-action="prune">清除失效记录</button>' +
'<button type="button" data-knowledge-rag-action="open-dashboard">打开 LightRAG</button>' +
'</div>' +
'</section>' +
'<section class="wolai-page-settings-index-group">' +
'<div class="wolai-page-settings-index-title">资料来源</div>' +
'<div class="mnote-knowledge-rag-source-filters" data-testid="mnote-knowledge-rag-source-filters"></div>' +
'<div class="mnote-knowledge-rag-source-list" data-testid="mnote-knowledge-rag-sources"></div>' +
'</section>' +
'</div>' +
'</div>';
}
function ensureKnowledgeRagSettingsPopover() {
var existing = document.querySelector('[data-testid="mnote-knowledge-rag-settings-popover"]');
if (existing instanceof HTMLElement) return existing;
var popover = document.createElement('div');
popover.className = 'wolai-page-settings-popover mnote-knowledge-rag-settings-popover';
popover.setAttribute('data-testid', 'mnote-knowledge-rag-settings-popover');
popover.setAttribute('data-mnote-surface', 'knowledge-rag-settings');
popover.hidden = true;
popover.innerHTML = createKnowledgeRagSettingsPanelHtml();
document.body.appendChild(popover);
return popover;
}
@@ -1015,6 +1056,656 @@ export function createSidebarPageSettingsRuntime(context) {
}
}
function knowledgeRagScopeKey() {
return [
currentSourceKind(),
currentRootUri(),
resolveWorkspaceId(document.body)
].join('|');
}
function knowledgeRagIsAvailable() {
return currentSourceKind() === 'local_folder' && Boolean(currentRootUri());
}
function knowledgeRagSourceInputRow(value) {
return '' +
'<div class="mnote-knowledge-rag-source-input-row" data-knowledge-rag-source-input-row="true">' +
'<input type="text" class="wolai-page-settings-index-path" data-knowledge-rag-source-input="true" value="' + escapeHtml(value || '') + '" placeholder="文件或目录,例如 docs/book.pdf / docs" spellcheck="false" />' +
'<span class="mnote-knowledge-rag-input-status" data-knowledge-rag-input-status-label="true" hidden></span>' +
'<button type="button" class="wolai-page-settings-index-remove" data-knowledge-rag-action="remove-source" aria-label="删除资料来源">×</button>' +
'</div>';
}
function knowledgeRagSourceInputValues() {
var popover = ensureKnowledgeRagSettingsPopover();
return Array.from(popover.querySelectorAll('[data-knowledge-rag-source-input]')).map(function(input) {
return input instanceof HTMLInputElement ? input.value.trim() : '';
}).filter(Boolean).filter(function(value, index, list) {
return list.indexOf(value) === index;
});
}
function knowledgeRagCurrentFilter() {
return pageUiState.knowledgeRagSourceFilter || 'attention';
}
function setKnowledgeRagSourceFilter(filter) {
pageUiState.knowledgeRagSourceFilter = String(filter || 'attention').trim() || 'attention';
renderKnowledgeRagSettings();
}
function knowledgeRagRegistryEntryCategory(entry) {
var deleted = entry && entry.deletedAtMs;
var stale = entry && entry.stale;
var providerStatus = String(entry && entry.lightRagStatus || '').trim();
var failed = providerStatus === 'failed';
var deleting = providerStatus === 'delete_submitted';
var deletedDone = providerStatus === 'delete_completed';
var docId = String(entry && entry.lightRagDocId || '');
var indexed = entry && entry.indexedAtMs && docId && !deleted && !stale && !failed && !deleting && !deletedDone;
if (deletedDone) return 'removed';
if (failed || stale || deleted) return 'failed';
if (indexed) return 'indexed';
return 'indexing';
}
function knowledgeRagProviderDocuments(status) {
var documents = status && status.documents && typeof status.documents === 'object' ? status.documents : {};
return Array.isArray(documents.documents) ? documents.documents : [];
}
function knowledgeRagStatusHasInFlight(status) {
var registry = status && status.registry && typeof status.registry === 'object' ? status.registry : {};
var entries = Array.isArray(registry.entries) ? registry.entries : [];
if (entries.some(function(entry) {
var providerStatus = String(entry && entry.lightRagStatus || '').trim();
return !entry?.deletedAtMs && !entry?.stale && !(entry?.indexedAtMs && entry?.lightRagDocId)
&& ['submitted', 'pending', 'processing', 'parsed', ''].includes(providerStatus);
})) return true;
var groups = status && status.documents && status.documents.rawStatusGroups && typeof status.documents.rawStatusGroups === 'object'
? status.documents.rawStatusGroups
: {};
return ['pending', 'parse', 'parsed', 'analyze', 'processing', 'process'].some(function(key) {
return Number(groups[key] || 0) > 0;
});
}
function scheduleKnowledgeRagStatusBridge(reason) {
if (!knowledgeRagIsAvailable()) return;
if (knowledgeRagBridgeTimer) window.clearTimeout(knowledgeRagBridgeTimer);
knowledgeRagBridgeGeneration += 1;
knowledgeRagBridgeAttempt = 0;
runKnowledgeRagStatusBridge(knowledgeRagBridgeGeneration, reason || 'source-updated');
}
function runKnowledgeRagStatusBridge(generation, reason) {
if (generation !== knowledgeRagBridgeGeneration || !knowledgeRagIsAvailable()) return;
var delay = knowledgeRagBridgeDelays[Math.min(knowledgeRagBridgeAttempt, knowledgeRagBridgeDelays.length - 1)];
knowledgeRagBridgeTimer = window.setTimeout(async function() {
if (generation !== knowledgeRagBridgeGeneration || !knowledgeRagIsAvailable()) return;
knowledgeRagBridgeAttempt += 1;
await loadKnowledgeRagStatus(true, { silentLoading: true, notifyUpdated: true, reason: reason || 'bridge' });
var status = currentKnowledgeRagSummary().status || {};
if (knowledgeRagStatusHasInFlight(status) && knowledgeRagBridgeAttempt < knowledgeRagBridgeDelays.length) {
runKnowledgeRagStatusBridge(generation, reason || 'bridge');
}
}, delay);
}
function knowledgeRagUnmappedProviderRows(status, entries) {
var docs = knowledgeRagProviderDocuments(status);
if (!docs.length) return [];
var mappedDocIds = new Set();
var mappedFilePaths = new Set();
(Array.isArray(entries) ? entries : []).forEach(function(entry) {
var docId = String(entry && entry.lightRagDocId || '').trim();
var filePath = String(entry && entry.lightRagFilePath || '').trim();
if (docId) mappedDocIds.add(docId);
if (filePath) mappedFilePaths.add(filePath);
});
return docs.filter(function(doc) {
var docId = String(doc && doc.id || '').trim();
var filePath = String(doc && doc.filePath || '').trim();
return !(docId && mappedDocIds.has(docId)) && !(filePath && mappedFilePaths.has(filePath));
}).map(function(doc) {
return {
kind: 'provider',
category: String(doc.status || doc.statusGroup || '').toLowerCase().includes('fail') ? 'unmapped_failed' : 'unmapped',
doc: doc
};
});
}
function knowledgeRagVisibleForFilter(row, filter) {
if (filter === 'all') return true;
if (filter === 'attention') return row.category !== 'indexed';
if (filter === 'unmapped') return row.category === 'unmapped' || row.category === 'unmapped_failed';
if (filter === 'failed') return row.category === 'failed' || row.category === 'unmapped_failed';
return row.category === filter;
}
function renderKnowledgeRagSourceFilters(popover, rows) {
var filtersNode = popover.querySelector('[data-testid="mnote-knowledge-rag-source-filters"]');
if (!(filtersNode instanceof HTMLElement)) return;
var counts = rows.reduce(function(acc, row) {
acc.all += 1;
if (row.category !== 'indexed') acc.attention += 1;
if (row.category === 'indexing') acc.indexing += 1;
if (row.category === 'failed' || row.category === 'unmapped_failed') acc.failed += 1;
if (row.category === 'indexed') acc.indexed += 1;
if (row.category === 'removed') acc.removed += 1;
if (row.category === 'unmapped' || row.category === 'unmapped_failed') acc.unmapped += 1;
return acc;
}, { all: 0, attention: 0, indexing: 0, failed: 0, indexed: 0, removed: 0, unmapped: 0 });
var current = knowledgeRagCurrentFilter();
var options = [
['attention', '需处理'],
['indexing', '索引中'],
['failed', '失败/过期'],
['indexed', '已索引'],
['removed', '已移除'],
['unmapped', 'LightRAG 未映射'],
['all', '全部']
];
filtersNode.innerHTML = options.map(function(item) {
var key = item[0];
var label = item[1];
return '<button type="button" data-knowledge-rag-action="filter-sources" data-knowledge-rag-filter="' + key + '" data-active="' + (current === key ? 'true' : 'false') + '">' + escapeHtml(label) + ' <span>' + String(counts[key] || 0) + '</span></button>';
}).join('');
}
function renderKnowledgeRagSources(popover, entries, status) {
var sourcesNode = popover.querySelector('[data-testid="mnote-knowledge-rag-sources"]');
if (!(sourcesNode instanceof HTMLElement)) return;
var registryRows = (Array.isArray(entries) ? entries : []).map(function(entry) {
return { kind: 'registry', category: knowledgeRagRegistryEntryCategory(entry), entry: entry };
});
var rows = registryRows.concat(knowledgeRagUnmappedProviderRows(status || {}, entries));
renderKnowledgeRagSourceFilters(popover, rows);
var filter = knowledgeRagCurrentFilter();
var items = rows.filter(function(row) {
return knowledgeRagVisibleForFilter(row, filter);
});
if (!rows.length) {
sourcesNode.innerHTML = '<div class="wolai-page-settings-index-empty">还没有登记到 LightRAG 的资料来源</div>';
return;
}
if (!items.length) {
sourcesNode.innerHTML = '<div class="wolai-page-settings-index-empty">当前分类没有资料来源</div>';
return;
}
sourcesNode.innerHTML = items.map(function(row) {
if (row.kind === 'provider') {
var doc = row.doc || {};
var providerPath = String(doc.filePath || doc.id || '');
var docStatus = String(doc.status || doc.statusGroup || '');
var failedDoc = row.category === 'unmapped_failed';
return '' +
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-source-kind="provider" data-knowledge-rag-source-path="' + escapeHtml(providerPath) + '">' +
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + (failedDoc ? 'fault' : 'indexed') + '" title="' + escapeHtml(docStatus || 'LightRAG 文档') + '"></span>' +
'<div class="mnote-knowledge-rag-source-main">' +
'<strong title="' + escapeHtml(providerPath) + '">' + escapeHtml(providerPath || 'LightRAG 文档') + '</strong>' +
'<span>' + escapeHtml('LightRAG 未映射 · ' + (docStatus || 'unknown') + (doc.id ? ' · ' + doc.id : '')) + '</span>' +
'</div>' +
'<div class="mnote-knowledge-rag-source-actions"><button type="button" disabled>只读</button></div>' +
'</div>';
}
var entry = row.entry || {};
var deleted = entry && entry.deletedAtMs;
var stale = entry && entry.stale;
var providerStatus = String(entry && entry.lightRagStatus || '').trim();
var failed = providerStatus === 'failed';
var deleting = providerStatus === 'delete_submitted';
var deletedDone = providerStatus === 'delete_completed';
var docId = String(entry && entry.lightRagDocId || '');
var indexed = entry && entry.indexedAtMs && docId && !deleted && !stale && !failed && !deleting && !deletedDone;
var kind = deletedDone ? '' : (deleting ? 'indexing' : (failed || stale || deleted ? 'fault' : (indexed ? 'indexed' : 'indexing')));
var title = String(entry && entry.sourceRootRelativePath || entry && entry.lightRagFilePath || '');
var status = deletedDone ? 'LightRAG 已移除'
: deleting ? '删除已提交'
: deleted ? '已删除'
: stale ? '已过期'
: failed ? '索引失败'
: indexed ? '已索引'
: (docId || providerStatus ? '正在索引' : '待提交');
var meta = docId ? status + ' · ' + docId : status;
var sourcePath = String(entry && entry.sourceRootRelativePath || '');
var canReindex = Boolean(sourcePath && !deleting);
var canDelete = Boolean(sourcePath && !deleted && !deletedDone && !deleting);
return '' +
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '">' +
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + kind + '" title="' + escapeHtml(status) + '"></span>' +
'<div class="mnote-knowledge-rag-source-main">' +
'<strong title="' + escapeHtml(title) + '">' + escapeHtml(title || '未命名来源') + '</strong>' +
'<span>' + escapeHtml(meta) + '</span>' +
'</div>' +
'<div class="mnote-knowledge-rag-source-actions">' +
'<button type="button" data-knowledge-rag-action="reindex-source" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '"' + (canReindex ? '' : ' disabled') + '>重索引</button>' +
'<button type="button" data-knowledge-rag-action="delete-source" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '"' + (canDelete ? '' : ' disabled') + '>删除索引</button>' +
'</div>' +
'</div>';
}).join('');
}
function renderKnowledgeRagSettings(popover) {
popover = popover || ensureKnowledgeRagSettingsPopover();
var statusNode = popover.querySelector('[data-testid="mnote-knowledge-rag-status"]');
var metaNode = popover.querySelector('[data-testid="mnote-knowledge-rag-meta"]');
var dashboardButton = popover.querySelector('[data-knowledge-rag-action="open-dashboard"]');
if (!(statusNode instanceof HTMLElement)) return;
if (!knowledgeRagIsAvailable()) {
statusNode.textContent = '资料库问答仅在本地工作区可用';
if (metaNode instanceof HTMLElement) metaNode.innerHTML = '';
renderKnowledgeRagSourceFilters(popover, []);
renderKnowledgeRagSources(popover, [], {});
if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = true;
return;
}
var summary = currentKnowledgeRagSummary();
if (summary.loading) {
statusNode.textContent = '正在读取 LightRAG 状态...';
if (metaNode instanceof HTMLElement) metaNode.innerHTML = '';
renderKnowledgeRagSourceFilters(popover, []);
renderKnowledgeRagSources(popover, [], {});
if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = true;
return;
}
if (summary.error) {
statusNode.textContent = 'LightRAG 状态读取失败:' + summary.error;
if (metaNode instanceof HTMLElement) metaNode.innerHTML = '';
renderKnowledgeRagSourceFilters(popover, []);
renderKnowledgeRagSources(popover, [], {});
if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = true;
return;
}
var status = summary.status || {};
var health = status.health && typeof status.health === 'object' ? status.health : {};
var healthBody = health.health && typeof health.health === 'object' ? health.health : {};
var healthy = health.ok === true && String(healthBody.status || '') === 'healthy';
var registry = status.registry && typeof status.registry === 'object' ? status.registry : {};
var entries = Array.isArray(registry.entries) ? registry.entries : [];
var providerDocs = knowledgeRagProviderDocuments(status);
statusNode.textContent = healthy
? 'LightRAG healthyMNote 登记 ' + entries.length + ' 个来源,LightRAG 文档 ' + providerDocs.length + ' 个'
: 'LightRAG 未就绪';
if (metaNode instanceof HTMLElement) {
var dashboardUrl = publicKnowledgeRagDashboardUrl(status);
var inputDir = String(status.inputDir || '');
var workingDir = String(healthBody.working_directory || '');
var config = healthBody.configuration && typeof healthBody.configuration === 'object' ? healthBody.configuration : {};
var llmModel = String(config.llm_model || config.llmModel || '');
var vlmModel = String(config.vlm_model || config.vlmModel || '');
metaNode.innerHTML = '' +
'<div><span>Dashboard</span><code>' + escapeHtml(dashboardUrl || '未配置') + '</code></div>' +
'<div><span>Model</span><code>' + escapeHtml(llmModel || '未上报') + (vlmModel ? ' / VLM ' + escapeHtml(vlmModel) : '') + '</code></div>' +
'<div><span>Input</span><code>' + escapeHtml(inputDir || '未配置') + '</code></div>' +
'<div><span>Storage</span><code>' + escapeHtml(workingDir || '未上报') + '</code></div>';
}
renderKnowledgeRagSources(popover, entries, status);
if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = !publicKnowledgeRagDashboardUrl(status);
}
function publicKnowledgeRagDashboardUrl(status) {
var url = String(status && (status.dashboardUrl || status.endpoint) || '').trim();
if (!url) return '';
try {
var parsed = new URL(url, window.location.href);
if (['127.0.0.1', 'localhost', '::1'].includes(parsed.hostname) && window.location.hostname && !['127.0.0.1', 'localhost', '::1'].includes(window.location.hostname)) {
parsed.hostname = window.location.hostname;
return parsed.toString();
}
} catch (_) {}
return url;
}
async function loadKnowledgeRagStatus(force, options) {
options = options || {};
if (!knowledgeRagIsAvailable()) {
renderKnowledgeRagSettings();
return;
}
var scopeKey = knowledgeRagScopeKey();
var current = currentKnowledgeRagSummary();
if (!force && current.scopeKey === scopeKey && !current.error && !current.loading) {
renderKnowledgeRagSettings();
return;
}
if (!options.silentLoading) {
pageUiState.knowledgeRagSummary = {
scopeKey: scopeKey,
loading: true,
error: ''
};
renderKnowledgeRagSettings();
}
try {
var params = new URLSearchParams();
params.set('workspaceId', resolveWorkspaceId(document.body));
params.set('rootUri', currentRootUri());
var response = await fetch('/api/knowledge-rag/status?' + params.toString(), {
headers: { accept: 'application/json' }
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_status_' + response.status);
}
pageUiState.knowledgeRagSummary = {
scopeKey: scopeKey,
loading: false,
error: '',
status: payload
};
if (options.notifyUpdated) {
notifyKnowledgeRagSourceUpdated({ action: 'status-sync', reason: options.reason || 'bridge', status: payload });
}
} catch (error) {
pageUiState.knowledgeRagSummary = {
scopeKey: scopeKey,
loading: false,
error: error instanceof Error ? error.message : String(error),
status: {}
};
}
renderKnowledgeRagSettings();
}
function addKnowledgeRagSourceInput() {
var popover = ensureKnowledgeRagSettingsPopover();
var node = popover.querySelector('[data-testid="mnote-knowledge-rag-source-inputs"]');
if (!(node instanceof HTMLElement)) return;
node.insertAdjacentHTML('beforeend', knowledgeRagSourceInputRow(''));
var inputs = Array.from(node.querySelectorAll('[data-knowledge-rag-source-input]'));
var last = inputs[inputs.length - 1];
if (last instanceof HTMLInputElement) last.focus();
}
function knowledgeRagFileTreeSelectionPaths() {
var rows = Array.from(document.querySelectorAll(
'#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-selected="true"][data-local-relative-path]'
));
if (!rows.length) {
rows = Array.from(document.querySelectorAll(
'#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-active="true"][data-local-relative-path]'
));
}
return rows.map(function(row) {
return String(row.getAttribute('data-local-relative-path') || '').trim();
}).filter(Boolean).filter(function(value, index, list) {
return list.indexOf(value) === index;
});
}
function useKnowledgeRagFileTreeSelection() {
var selectedPaths = knowledgeRagFileTreeSelectionPaths();
if (!selectedPaths.length) {
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: '请先在文件树中选中要索引的文件或目录'
});
renderKnowledgeRagSettings();
return;
}
var popover = ensureKnowledgeRagSettingsPopover();
var node = popover.querySelector('[data-testid="mnote-knowledge-rag-source-inputs"]');
if (!(node instanceof HTMLElement)) return;
var existing = knowledgeRagSourceInputValues();
var next = existing.concat(selectedPaths).filter(function(value, index, list) {
return list.indexOf(value) === index;
});
node.innerHTML = next.map(knowledgeRagSourceInputRow).join('');
}
function notifyKnowledgeRagSourceUpdated(detail) {
window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-source-updated', {
detail: Object.assign({
rootUri: currentRootUri(),
workspaceId: resolveWorkspaceId(document.body)
}, detail || {})
}));
}
function removeKnowledgeRagSourceInput(button) {
var row = button && button.closest ? button.closest('[data-knowledge-rag-source-input-row]') : null;
if (row instanceof HTMLElement) row.remove();
var popover = ensureKnowledgeRagSettingsPopover();
var node = popover.querySelector('[data-testid="mnote-knowledge-rag-source-inputs"]');
if (node instanceof HTMLElement && !node.querySelector('[data-knowledge-rag-source-input]')) {
node.innerHTML = knowledgeRagSourceInputRow('');
}
}
function resetKnowledgeRagSourceInputs() {
var popover = ensureKnowledgeRagSettingsPopover();
var node = popover.querySelector('[data-testid="mnote-knowledge-rag-source-inputs"]');
if (node instanceof HTMLElement) node.innerHTML = knowledgeRagSourceInputRow('');
}
async function ingestKnowledgeRagSources() {
if (!knowledgeRagIsAvailable()) return;
var sources = knowledgeRagSourceInputValues();
if (!sources.length) {
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: '请先填写要索引的资料路径'
});
renderKnowledgeRagSettings();
return;
}
setKnowledgeRagInputStatuses(Object.fromEntries(sources.map(function(path) {
return [path, 'running'];
})));
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: true,
error: ''
});
renderKnowledgeRagSettings();
try {
var response = await fetch('/api/knowledge-rag/ingest', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
workspaceId: resolveWorkspaceId(document.body),
rootUri: currentRootUri(),
sources: sources.map(function(path) { return { sourcePath: path }; })
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_ingest_' + response.status);
}
if (payload.retryRequired) {
setKnowledgeRagInputStatuses(Object.fromEntries(sources.map(function(path) {
return [path, 'retry'];
})));
await loadKnowledgeRagStatus(true);
notifyKnowledgeRagSourceUpdated({ action: 'ingest', sources: sources, result: payload, retryRequired: true });
scheduleKnowledgeRagStatusBridge('ingest-retry');
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: 'LightRAG 正在处理其他任务,请稍后刷新或重试'
});
renderKnowledgeRagSettings();
return;
}
var configured = Array.isArray(payload.configuredSources) ? payload.configuredSources : [];
var done = {};
configured.forEach(function(item) {
var requested = String(item && item.requestedSourcePath || item && item.sourceRootRelativePath || '').trim();
if (requested) done[requested] = 'done';
});
setKnowledgeRagInputStatuses(done, 'done');
await loadKnowledgeRagStatus(true);
resetKnowledgeRagSourceInputs();
notifyKnowledgeRagSourceUpdated({ action: 'ingest', sources: sources, result: payload });
scheduleKnowledgeRagStatusBridge('ingest');
} catch (error) {
setKnowledgeRagInputStatuses(Object.fromEntries(sources.map(function(path) {
return [path, 'failed'];
})));
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: error instanceof Error ? error.message : String(error)
});
renderKnowledgeRagSettings();
}
}
async function ingestSingleKnowledgeRagSource(sourcePath) {
sourcePath = String(sourcePath || '').trim();
if (!sourcePath || !knowledgeRagIsAvailable()) return;
setKnowledgeRagInputStatuses({ [sourcePath]: 'running' });
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: true,
error: ''
});
renderKnowledgeRagSettings();
try {
var response = await fetch('/api/knowledge-rag/ingest', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
workspaceId: resolveWorkspaceId(document.body),
rootUri: currentRootUri(),
sources: [{ sourcePath: sourcePath }],
force: true
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_reindex_' + response.status);
}
if (payload.retryRequired) {
await loadKnowledgeRagStatus(true);
notifyKnowledgeRagSourceUpdated({ action: 'reindex', sourceRootRelativePath: sourcePath, result: payload, retryRequired: true });
scheduleKnowledgeRagStatusBridge('reindex-retry');
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: 'LightRAG 正在处理其他任务,请稍后重试'
});
renderKnowledgeRagSettings();
return;
}
await loadKnowledgeRagStatus(true);
notifyKnowledgeRagSourceUpdated({ action: 'reindex', sourceRootRelativePath: sourcePath, result: payload });
scheduleKnowledgeRagStatusBridge('reindex');
} catch (error) {
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: error instanceof Error ? error.message : String(error)
});
renderKnowledgeRagSettings();
}
}
async function deleteKnowledgeRagSource(sourcePath) {
sourcePath = String(sourcePath || '').trim();
if (!sourcePath || !knowledgeRagIsAvailable()) return;
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: true,
error: ''
});
renderKnowledgeRagSettings();
try {
var response = await fetch('/api/knowledge-rag/delete-source', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
workspaceId: resolveWorkspaceId(document.body),
rootUri: currentRootUri(),
sourcePath: sourcePath
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_delete_' + response.status);
}
await loadKnowledgeRagStatus(true);
notifyKnowledgeRagSourceUpdated({ action: 'delete', sourceRootRelativePath: sourcePath, result: payload });
} catch (error) {
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: error instanceof Error ? error.message : String(error)
});
renderKnowledgeRagSettings();
}
}
async function pruneKnowledgeRagRegistry() {
if (!knowledgeRagIsAvailable()) return;
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: true,
error: ''
});
renderKnowledgeRagSettings();
try {
var response = await fetch('/api/knowledge-rag/prune-registry', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
workspaceId: resolveWorkspaceId(document.body),
rootUri: currentRootUri()
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_prune_' + response.status);
}
await loadKnowledgeRagStatus(true);
notifyKnowledgeRagSourceUpdated({ action: 'prune', result: payload });
} catch (error) {
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: error instanceof Error ? error.message : String(error)
});
renderKnowledgeRagSettings();
}
}
function openKnowledgeRagDashboard() {
var status = currentKnowledgeRagSummary().status || {};
var url = publicKnowledgeRagDashboardUrl(status);
if (!url) return;
window.open(url, '_blank', 'noopener');
}
function knowledgeRagInputStatusText(status) {
if (status === 'running') return '索引中';
if (status === 'done') return '已提交';
if (status === 'retry') return '需重试';
if (status === 'failed') return '失败';
return '待索引';
}
function setKnowledgeRagInputStatuses(statusByPath, fallbackStatus) {
var popover = ensureKnowledgeRagSettingsPopover();
Array.from(popover.querySelectorAll('[data-knowledge-rag-source-input-row]')).forEach(function(row) {
var input = row.querySelector('[data-knowledge-rag-source-input]');
var label = row.querySelector('[data-knowledge-rag-input-status-label]');
if (!(input instanceof HTMLInputElement) || !(label instanceof HTMLElement)) return;
var value = input.value.trim();
var status = statusByPath && statusByPath[value] || fallbackStatus || 'idle';
label.textContent = knowledgeRagInputStatusText(status);
label.setAttribute('data-status', status);
label.hidden = status === 'idle';
});
}
function renderPageSettingsPopover() {
var popover = ensurePageSettingsPopover();
var options = currentPageOptions();
@@ -1222,14 +1913,20 @@ export function createSidebarPageSettingsRuntime(context) {
return popover instanceof HTMLElement && !popover.hidden;
}
function isKnowledgeRagSettingsOpen() {
var popover = document.querySelector('[data-testid="mnote-knowledge-rag-settings-popover"]');
return popover instanceof HTMLElement && !popover.hidden;
}
function isAnySettingsOpen() {
return isPageSettingsOpen() || isLocalIndexSettingsOpen() || isLocalOcrSettingsOpen();
return isPageSettingsOpen() || isLocalIndexSettingsOpen() || isLocalOcrSettingsOpen() || isKnowledgeRagSettingsOpen();
}
function openPageSettingsPopover(initialTab) {
if (!currentDocumentId()) return;
closeLocalIndexSettingsPopover();
closeLocalOcrSettingsPopover();
closeKnowledgeRagSettingsPopover();
var popover = ensurePageSettingsPopover();
renderPageSettingsPopover();
setActivePageSettingsTab(initialTab || 'page');
@@ -1241,6 +1938,7 @@ export function createSidebarPageSettingsRuntime(context) {
function openPageIndexSettingsPopover() {
closePageSettingsPopover();
closeLocalOcrSettingsPopover();
closeKnowledgeRagSettingsPopover();
var popover = ensureLocalIndexSettingsPopover();
renderPageSettingsLocalIndex(popover);
popover.hidden = false;
@@ -1249,11 +1947,21 @@ export function createSidebarPageSettingsRuntime(context) {
}
function openLocalOcrSettingsPopover() {
openKnowledgeRagSettingsPopover();
}
function openKnowledgeRagSettingsPopover() {
closePageSettingsPopover();
closeLocalIndexSettingsPopover();
var popover = ensureLocalOcrSettingsPopover();
renderLocalOcrOptions(popover);
closeLocalOcrSettingsPopover();
var popover = ensureKnowledgeRagSettingsPopover();
renderKnowledgeRagSettings(popover);
popover.hidden = false;
void loadKnowledgeRagStatus(false).then(function() {
if (knowledgeRagStatusHasInFlight(currentKnowledgeRagSummary().status || {})) {
scheduleKnowledgeRagStatusBridge('open-panel');
}
});
updateStandaloneSettingsTriggerState();
}
@@ -1276,10 +1984,17 @@ export function createSidebarPageSettingsRuntime(context) {
updateStandaloneSettingsTriggerState();
}
function closeKnowledgeRagSettingsPopover() {
var popover = document.querySelector('[data-testid="mnote-knowledge-rag-settings-popover"]');
if (popover instanceof HTMLElement) popover.hidden = true;
updateStandaloneSettingsTriggerState();
}
function closeAllSettingsPopovers() {
closePageSettingsPopover();
closeLocalIndexSettingsPopover();
closeLocalOcrSettingsPopover();
closeKnowledgeRagSettingsPopover();
}
function togglePageSettingsPopover() {
@@ -1295,27 +2010,36 @@ export function createSidebarPageSettingsRuntime(context) {
openLocalOcrSettingsPopover();
});
window.addEventListener('mnote:knowledge-rag-source-updated', function() {
if (isKnowledgeRagSettingsOpen()) void loadKnowledgeRagStatus(true);
});
return {
addLocalIndexRange,
applyPageOptionsToShell,
closeAllSettingsPopovers,
closeKnowledgeRagSettingsPopover,
closePageHistoryDrawer,
closeLocalIndexSettingsPopover,
closeLocalOcrSettingsPopover,
closePageSettingsPopover,
closePageShareDialog,
currentLocalOcrPreferences,
currentKnowledgeRagSummary,
currentPageOptions,
ensureHistorySnapshotsSeeded,
ensureKnowledgeRagSettingsPopover,
ensureLocalIndexSettingsPopover,
ensureLocalOcrSettingsPopover,
ensurePageHistoryDrawer,
ensurePageSettingsPopover,
ensurePageShareDialog,
isAnySettingsOpen,
isKnowledgeRagSettingsOpen,
isLocalIndexSettingsOpen,
isLocalOcrSettingsOpen,
isPageSettingsOpen,
openKnowledgeRagSettingsPopover,
openPageHistoryDrawer,
openLocalOcrSettingsPopover,
openPageSettingsPopover,
@@ -1331,6 +2055,16 @@ export function createSidebarPageSettingsRuntime(context) {
renderPageSettingsPopover,
setActivePageSettingsTab,
refreshLocalIndex,
addKnowledgeRagSourceInput,
deleteKnowledgeRagSource,
ingestKnowledgeRagSources,
ingestSingleKnowledgeRagSource,
loadKnowledgeRagStatus,
openKnowledgeRagDashboard,
pruneKnowledgeRagRegistry,
removeKnowledgeRagSourceInput,
setKnowledgeRagSourceFilter,
useKnowledgeRagFileTreeSelection,
togglePageSettingsPopover,
updatePageSettingsTriggerState,
writeGlobalShowHeadingNumbers,
@@ -1163,7 +1163,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
var ownerDocumentId = fileOwnerDocumentId(item, documentId, objectIdentity);
var iconKind = iconKindOf(item);
var indexStatus = String((item && item.indexStatus) || (item && item.resourceMeta && item.resourceMeta.indexStatus) || '').trim();
if (indexStatus !== 'indexed' && indexStatus !== 'failed') indexStatus = '';
if (!['indexed', 'indexing', 'failed'].includes(indexStatus)) indexStatus = '';
var cachedChildren = relativePath ? cachedFileTreeRows(relativePath) : [];
var title = isFileTreeProjectionPageRow(rowKind, assetId)
? fileTreePageTitle(rawTitle)
@@ -210,14 +210,25 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
const openPageHistoryDrawer = (...args) => sidebarPageSettings.openPageHistoryDrawer(...args);
const openPageIndexSettingsPopover = (...args) => sidebarPageSettings.openPageIndexSettingsPopover(...args);
const openLocalOcrSettingsPopover = (...args) => sidebarPageSettings.openLocalOcrSettingsPopover(...args);
const openKnowledgeRagSettingsPopover = (...args) => sidebarPageSettings.openKnowledgeRagSettingsPopover(...args);
const openPageShareDialog = (...args) => sidebarPageSettings.openPageShareDialog(...args);
const pageOptionIsSupported = (...args) => sidebarPageSettings.pageOptionIsSupported(...args);
const addLocalIndexRange = (...args) => sidebarPageSettings.addLocalIndexRange(...args);
const addKnowledgeRagSourceInput = (...args) => sidebarPageSettings.addKnowledgeRagSourceInput(...args);
const deleteKnowledgeRagSource = (...args) => sidebarPageSettings.deleteKnowledgeRagSource(...args);
const ingestKnowledgeRagSources = (...args) => sidebarPageSettings.ingestKnowledgeRagSources(...args);
const ingestSingleKnowledgeRagSource = (...args) => sidebarPageSettings.ingestSingleKnowledgeRagSource(...args);
const loadKnowledgeRagStatus = (...args) => sidebarPageSettings.loadKnowledgeRagStatus(...args);
const openKnowledgeRagDashboard = (...args) => sidebarPageSettings.openKnowledgeRagDashboard(...args);
const pruneKnowledgeRagRegistry = (...args) => sidebarPageSettings.pruneKnowledgeRagRegistry(...args);
const setKnowledgeRagSourceFilter = (...args) => sidebarPageSettings.setKnowledgeRagSourceFilter(...args);
const useKnowledgeRagFileTreeSelection = (...args) => sidebarPageSettings.useKnowledgeRagFileTreeSelection(...args);
const persistLocalOcrAutoPreference = (...args) => sidebarPageSettings.persistLocalOcrAutoPreference(...args);
const persistLocalIndexSettings = (...args) => sidebarPageSettings.persistLocalIndexSettings(...args);
const persistPageOptionsPatch = (...args) => sidebarPageSettings.persistPageOptionsPatch(...args);
const persistPageWidthPreference = (...args) => sidebarPageSettings.persistPageWidthPreference(...args);
const refreshLocalIndex = (...args) => sidebarPageSettings.refreshLocalIndex(...args);
const removeKnowledgeRagSourceInput = (...args) => sidebarPageSettings.removeKnowledgeRagSourceInput(...args);
const removeLocalIndexRange = (...args) => sidebarPageSettings.removeLocalIndexRange(...args);
const recordPageHistorySnapshot = (...args) => sidebarPageSettings.recordPageHistorySnapshot(...args);
const renderPageSettingsPopover = (...args) => sidebarPageSettings.renderPageSettingsPopover(...args);
@@ -1919,6 +1930,9 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
window.addEventListener('wolai:local-assets-changed', function(event) {
applyAssetsChangedToFileTree(event.detail || {});
});
window.addEventListener('mnote:knowledge-rag-source-updated', function() {
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
});
installMindmapAssetFetchObserver();
async function uploadFilesWithResolvedTarget(files, detail, options) {
@@ -2670,23 +2684,28 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
var indexSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-index-settings"]');
if (indexSettingsTrigger) {
e.preventDefault();
openPageIndexSettingsPopover();
openKnowledgeRagSettingsPopover();
return;
}
var knowledgeRagSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-knowledge-rag-settings"]');
if (knowledgeRagSettingsTrigger) {
e.preventDefault();
openKnowledgeRagSettingsPopover();
return;
}
var ocrTaskTrigger = closestAction(e.target, '[data-mnote-action="toggle-ocr-tasks"]');
if (ocrTaskTrigger) {
e.preventDefault();
window.dispatchEvent(new CustomEvent('mnote:local-ocr-settings-action', {
detail: { action: 'tasks' }
}));
openKnowledgeRagSettingsPopover();
return;
}
var ocrSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-ocr-settings"]');
if (ocrSettingsTrigger) {
e.preventDefault();
openLocalOcrSettingsPopover();
openKnowledgeRagSettingsPopover();
return;
}
@@ -2742,6 +2761,52 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
return;
}
var knowledgeRagAction = closestAction(e.target, '[data-knowledge-rag-action]');
if (knowledgeRagAction) {
e.preventDefault();
var knowledgeRagActionName = knowledgeRagAction.getAttribute('data-knowledge-rag-action') || '';
if (knowledgeRagActionName === 'refresh') {
void loadKnowledgeRagStatus(true);
return;
}
if (knowledgeRagActionName === 'add-source') {
addKnowledgeRagSourceInput();
return;
}
if (knowledgeRagActionName === 'remove-source') {
removeKnowledgeRagSourceInput(knowledgeRagAction);
return;
}
if (knowledgeRagActionName === 'use-filetree-selection') {
useKnowledgeRagFileTreeSelection();
return;
}
if (knowledgeRagActionName === 'ingest') {
void ingestKnowledgeRagSources();
return;
}
if (knowledgeRagActionName === 'reindex-source') {
void ingestSingleKnowledgeRagSource(knowledgeRagAction.getAttribute('data-knowledge-rag-source-path') || '');
return;
}
if (knowledgeRagActionName === 'delete-source') {
void deleteKnowledgeRagSource(knowledgeRagAction.getAttribute('data-knowledge-rag-source-path') || '');
return;
}
if (knowledgeRagActionName === 'prune') {
void pruneKnowledgeRagRegistry();
return;
}
if (knowledgeRagActionName === 'filter-sources') {
setKnowledgeRagSourceFilter(knowledgeRagAction.getAttribute('data-knowledge-rag-filter') || 'attention');
return;
}
if (knowledgeRagActionName === 'open-dashboard') {
openKnowledgeRagDashboard();
return;
}
}
var pageSettingsAction = closestAction(e.target, '[data-page-settings-action]');
if (pageSettingsAction) {
e.preventDefault();