Files
mnote/rust/crates/mnote-web/browser/sidebar-page-ai-target-runtime.js
T

838 lines
39 KiB
JavaScript
Raw Normal View History

2026-06-01 09:29:12 +08:00
export function createSidebarPageAiTargetRuntime(context) {
const {
currentDocumentId,
currentRootUri,
currentSourceKind,
currentPageOptions,
documentRef,
escapeHtml,
pageAiEnsureContextRefState,
pageUiState,
resolveWorkspaceId,
searchText,
pageAiNormalizeArray,
} = context;
function pageAiCloneJson(value) {
if (value == null) return null;
try {
return JSON.parse(JSON.stringify(value));
} catch (_) {
return null;
}
}
function localMarkdownRelativePathFromPageAiDocumentId(documentId) {
var value = String(documentId || '').trim();
if (!value.startsWith('local-md:')) return '';
return value.slice('local-md:'.length).replace(/~2F/g, '/');
}
function localMarkdownDocumentIdFromPageAiRelativePath(relativePath) {
var normalized = String(relativePath || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
if (!normalized) return '';
return 'local-md:' + normalized.split('/').map(function(segment) {
return encodeURIComponent(segment).replace(/%20/g, '~20');
}).join('~2F');
}
function pageAiWorkspacePathForDocument(documentId, seed) {
var relativePath = String(seed && seed.relativePath || localMarkdownRelativePathFromPageAiDocumentId(documentId) || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
var resolvedDocumentId = String(documentId || seed && seed.documentId || '').trim()
|| localMarkdownDocumentIdFromPageAiRelativePath(relativePath);
return {
schema: 'mnote.workspace_path.v1',
workspaceId: String(seed && seed.workspaceId || resolveWorkspaceId(documentRef.body) || '').trim(),
sourceKind: String(seed && seed.sourceKind || currentSourceKind() || '').trim(),
rootUri: String(seed && seed.rootUri || currentRootUri() || '').trim(),
relativePath: relativePath,
documentId: resolvedDocumentId,
objectIdentity: seed && seed.objectIdentity && typeof seed.objectIdentity === 'object'
? seed.objectIdentity
: String(seed && seed.objectIdentity || resolvedDocumentId || '').trim(),
assetId: String(seed && seed.assetId || '').trim(),
resourceKind: String(seed && seed.resourceKind || 'markdown_page').trim()
};
}
function pageAiResourceKindForTarget(entry) {
var kind = String(entry && (entry.editorKind || entry.kind) || '').trim().toLowerCase();
var assetId = String(entry && entry.assetId || '').trim();
var path = String(entry && entry.path || '').trim().toLowerCase();
if (kind === 'page' || kind === 'markdown_page') return 'markdown_page';
if (kind === 'mindmap' || path.endsWith('.mindmap.json')) return 'mindmap';
if (kind === 'office' || kind === 'onlyoffice' || kind === 'only_office' || /\.(doc|docx|ppt|pptx|xls|xlsx)$/.test(path)) return 'only_office';
if (kind === 'resource' && assetId) return 'resource';
return kind || 'markdown_page';
}
function pageAiTargetId(entry) {
if (!entry || typeof entry !== 'object') return '';
var workspacePath = entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : {};
var entryIdentity = typeof entry.objectIdentity === 'string' ? entry.objectIdentity : '';
var workspaceIdentity = typeof workspacePath.objectIdentity === 'string' ? workspacePath.objectIdentity : '';
return String(entryIdentity || workspaceIdentity || entry.documentId || entry.assetId || entry.path || '').trim();
}
function pageAiWorkspacePathForTarget(entry) {
var seed = Object.assign({}, entry && entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : {});
var resourceKind = pageAiResourceKindForTarget(entry);
if (!seed.relativePath && entry && entry.path) seed.relativePath = entry.path;
if (!seed.assetId && entry && entry.assetId) seed.assetId = entry.assetId;
var seedResourceKind = String(seed.resourceKind || '').trim();
if (!seedResourceKind || seedResourceKind === 'page' || seedResourceKind === 'office') seed.resourceKind = resourceKind;
if (!seed.objectIdentity && entry && entry.objectIdentity) seed.objectIdentity = entry.objectIdentity;
if (!seed.workspaceId && entry && entry.workspaceId) seed.workspaceId = entry.workspaceId;
return pageAiWorkspacePathForDocument(entry && entry.documentId || currentDocumentId(), seed);
}
function pageAiTargetFromOpenEditor(entry, source) {
if (!entry || typeof entry !== 'object') return null;
var workspacePath = pageAiWorkspacePathForTarget(entry);
var targetId = pageAiTargetId(entry) || workspacePath.objectIdentity || workspacePath.documentId;
if (!targetId) return null;
var objectIdentity = typeof entry.objectIdentity === 'string'
? entry.objectIdentity
: (typeof workspacePath.objectIdentity === 'string' ? workspacePath.objectIdentity : targetId);
return {
schema: 'mnote.ai_editor_target.v1',
source: source || 'open_editors_snapshot',
targetId: targetId,
objectIdentity: objectIdentity,
workspacePath: workspacePath,
paneRole: entry.paneRole || 'primary',
documentId: entry.documentId || workspacePath.documentId,
workspaceId: entry.workspaceId || workspacePath.workspaceId || resolveWorkspaceId(documentRef.body),
editorKind: entry.editorKind || entry.kind || workspacePath.resourceKind,
resourceKind: workspacePath.resourceKind,
title: entry.title || '',
active: entry.active === true,
dirtyState: entry.dirtyState || '',
preview: entry.preview === true,
pinned: entry.pinned === true,
lastActiveAt: entry.lastActiveAt || 0,
assetId: entry.assetId || workspacePath.assetId || '',
path: entry.path || workspacePath.relativePath || '',
onlyofficeSessionId: entry.onlyofficeSessionId || entry.bridgeSessionId || '',
bridgeSessionId: entry.bridgeSessionId || entry.onlyofficeSessionId || '',
bridgeSessionReady: entry.bridgeSessionReady === true
};
}
function normalizeOpenEditorEntry(entry) {
if (!entry || typeof entry !== 'object') return null;
return {
objectIdentity: String(entry.objectIdentity || '').trim(),
workspacePath: entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : null,
paneRole: String(entry.paneRole || 'primary').trim() === 'secondary' ? 'secondary' : 'primary',
documentId: String(entry.documentId || '').trim(),
workspaceId: String(entry.workspaceId || '').trim(),
title: String(entry.title || '').trim(),
kind: String(entry.kind || entry.editorKind || '').trim(),
editorKind: String(entry.editorKind || entry.kind || '').trim(),
active: entry.active === true,
dirtyState: String(entry.dirtyState || entry.dirtyGuard || '').trim(),
preview: entry.preview === true,
pinned: entry.pinned === true,
lastActiveAt: Number(entry.lastActiveAt || 0) || 0,
assetId: String(entry.assetId || '').trim(),
path: String(entry.path || '').trim(),
onlyofficeSessionId: String(entry.onlyofficeSessionId || entry.bridgeSessionId || '').trim(),
bridgeSessionId: String(entry.bridgeSessionId || entry.onlyofficeSessionId || '').trim(),
bridgeSessionReady: entry.bridgeSessionReady === true
};
}
function currentPageAiOpenEditorsSnapshot() {
var snapshot = null;
try {
if (window.__mnoteDocumentPaneRuntime && typeof window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot === 'function') {
snapshot = window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot();
}
} catch (_) {}
if (!snapshot || typeof snapshot !== 'object') snapshot = window.__mnoteOpenEditorsSnapshot || null;
if (!snapshot || typeof snapshot !== 'object') return null;
var editors = Array.isArray(snapshot.editors)
? snapshot.editors.map(normalizeOpenEditorEntry).filter(Boolean)
: [];
var resources = Array.isArray(snapshot.resourceEditors)
? snapshot.resourceEditors.map(normalizeOpenEditorEntry).filter(Boolean)
: editors.filter(function(entry) { return entry.kind !== 'page'; });
var normalizeGroup = function(group, paneRole) {
var groupEditors = group && Array.isArray(group.editors)
? group.editors.map(normalizeOpenEditorEntry).filter(Boolean)
: editors.filter(function(entry) { return entry.paneRole === paneRole; });
var groupResources = group && Array.isArray(group.resourceEditors)
? group.resourceEditors.map(normalizeOpenEditorEntry).filter(Boolean)
: resources.filter(function(entry) { return entry.paneRole === paneRole; });
var groupActive = groupEditors.find(function(entry) { return entry.active; }) || null;
return {
paneRole: paneRole,
activeObjectIdentity: String(group && group.activeObjectIdentity || (groupActive ? groupActive.objectIdentity : '') || '').trim(),
editors: groupEditors,
resourceEditors: groupResources
};
};
var groups = {
primary: normalizeGroup(snapshot.groups && snapshot.groups.primary, 'primary'),
secondary: normalizeGroup(snapshot.groups && snapshot.groups.secondary, 'secondary')
};
var activeObjectIdentity = String(snapshot.activeObjectIdentity || '').trim();
var allTargets = editors.concat(resources);
var activeEditor = allTargets.find(function(entry) {
return entry.active && (!activeObjectIdentity || entry.objectIdentity === activeObjectIdentity);
}) || groups.primary.editors.find(function(entry) {
return entry.active;
}) || groups.primary.resourceEditors.find(function(entry) {
return entry.active;
}) || groups.secondary.editors.find(function(entry) {
return entry.active;
}) || groups.secondary.resourceEditors.find(function(entry) {
return entry.active;
}) || null;
return {
schema: String(snapshot.schema || 'mnote.open_editors_snapshot.v1'),
generatedAt: Number(snapshot.generatedAt || 0) || Date.now(),
activeObjectIdentity: activeObjectIdentity || (activeEditor ? activeEditor.objectIdentity : ''),
activeEditor: activeEditor,
editors: editors,
resourceEditors: resources,
groups: groups
};
}
function pageAiFallbackEditorTarget() {
var fallbackDocumentId = currentDocumentId();
var fallbackWorkspacePath = pageAiWorkspacePathForDocument(fallbackDocumentId, null);
var fallbackTargetId = fallbackWorkspacePath.objectIdentity || fallbackDocumentId || '';
return {
schema: 'mnote.ai_editor_target.v1',
source: 'fallback_current_document',
targetId: fallbackTargetId,
objectIdentity: fallbackTargetId,
workspacePath: fallbackWorkspacePath,
paneRole: 'primary',
documentId: fallbackDocumentId,
workspaceId: resolveWorkspaceId(documentRef.body),
editorKind: 'page',
active: true,
dirtyState: '',
preview: false,
pinned: true,
lastActiveAt: Date.now(),
assetId: '',
path: ''
};
}
function pageAiEditorTargetCandidates() {
var snapshot = currentPageAiOpenEditorsSnapshot();
var entries = [];
if (snapshot) {
entries = pageAiNormalizeArray(snapshot.editors).concat(pageAiNormalizeArray(snapshot.resourceEditors));
}
var seen = {};
var targets = entries.map(function(entry) {
return pageAiTargetFromOpenEditor(entry, 'open_editors_snapshot');
}).filter(function(target) {
var id = String(target && target.targetId || '').trim();
if (!id || seen[id]) return false;
seen[id] = true;
return true;
});
if (!targets.length) targets.push(pageAiFallbackEditorTarget());
return targets;
}
function currentPageAiEditorTarget() {
var targets = pageAiEditorTargetCandidates();
var selectedId = String(pageUiState.pageAiSelectedTargetId || '').trim();
var selected = selectedId ? targets.find(function(target) { return target.targetId === selectedId; }) : null;
return selected
|| targets.find(function(target) { return target.active === true; })
|| targets[0]
|| pageAiFallbackEditorTarget();
}
function currentPageAiPageEditorTarget() {
var snapshot = currentPageAiOpenEditorsSnapshot();
var documentId = currentDocumentId();
var workspaceId = resolveWorkspaceId(documentRef.body);
var sourceKind = currentSourceKind();
var rootUri = currentRootUri();
var relativePath = localMarkdownRelativePathFromPageAiDocumentId(documentId);
var fallbackWorkspacePath = pageAiWorkspacePathForDocument(documentId, {
relativePath: relativePath
});
var pageEditor = snapshot && Array.isArray(snapshot.editors)
? snapshot.editors.find(function(entry) {
return entry
&& String(entry.editorKind || entry.kind || '') === 'page'
&& String(entry.documentId || '').trim() === String(documentId || '').trim();
})
: null;
var workspacePath = pageEditor && pageEditor.workspacePath
? Object.assign({}, pageEditor.workspacePath, {
workspaceId: workspaceId,
sourceKind: sourceKind,
rootUri: rootUri,
relativePath: relativePath,
documentId: documentId,
resourceKind: pageEditor.workspacePath.resourceKind || 'markdown_page',
objectIdentity: pageEditor.workspacePath.objectIdentity || fallbackWorkspacePath.objectIdentity
})
: fallbackWorkspacePath;
var objectIdentity = String(pageEditor && pageEditor.objectIdentity || workspacePath.objectIdentity || documentId || '').trim();
return {
schema: 'mnote.ai_editor_target.v1',
source: pageEditor ? 'current_page_from_open_editors_snapshot' : 'current_page_fallback',
targetId: objectIdentity,
objectIdentity: objectIdentity,
workspacePath: Object.assign({}, workspacePath, { objectIdentity: objectIdentity }),
paneRole: String(pageEditor && pageEditor.paneRole || 'primary').trim() === 'secondary' ? 'secondary' : 'primary',
documentId: documentId,
workspaceId: workspaceId,
editorKind: 'page',
active: pageEditor ? pageEditor.active === true : true,
dirtyState: String(pageEditor && pageEditor.dirtyState || '').trim(),
preview: pageEditor ? pageEditor.preview === true : false,
pinned: true,
lastActiveAt: Number(pageEditor && pageEditor.lastActiveAt || 0) || Date.now(),
assetId: '',
path: relativePath
};
}
function currentPageAiScopedEditorTarget() {
var selected = pageAiEnsureContextRefState();
return selected.active_editor ? currentPageAiEditorTarget() : currentPageAiPageEditorTarget();
}
function pageAiSetRunTargetSnapshot(snapshot) {
pageUiState.pageAiCurrentRunTargetSnapshot = snapshot || null;
var target = snapshot && snapshot.editorTarget ? snapshot.editorTarget : null;
var documentId = String(target && target.documentId || snapshot && snapshot.documentId || '').trim();
var workspaceId = String(target && target.workspaceId || snapshot && snapshot.workspaceId || '').trim();
var rootUri = String(target && target.workspacePath && target.workspacePath.rootUri || snapshot && snapshot.rootUri || '').trim();
if (documentId) documentRef.documentElement.setAttribute('data-mnote-page-ai-run-target-document-id', documentId);
if (workspaceId) documentRef.documentElement.setAttribute('data-mnote-page-ai-run-target-workspace-id', workspaceId);
if (rootUri) documentRef.documentElement.setAttribute('data-mnote-page-ai-run-target-root-uri', rootUri);
}
function assertPageAiTargetInCurrentWorkspace(editorTarget) {
var target = editorTarget && typeof editorTarget === 'object' ? editorTarget : {};
var workspacePath = target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {};
var targetSourceKind = String(workspacePath.sourceKind || '').trim();
var currentKind = String(currentSourceKind() || '').trim();
if (targetSourceKind && currentKind && targetSourceKind !== currentKind) {
var sourceError = new Error('AI target 与当前页面 sourceKind 不一致,请重新选择当前工作区内的目标。');
sourceError.code = 'page_ai_target_workspace_mismatch';
throw sourceError;
}
var targetRootUri = String(workspacePath.rootUri || '').trim();
var currentRoot = String(currentRootUri() || '').trim();
if (targetRootUri && currentRoot && targetRootUri !== currentRoot) {
var rootError = new Error('AI target 与当前本地工作区 rootUri 不一致,请重新选择当前工作区内的目标。');
rootError.code = 'page_ai_target_workspace_mismatch';
throw rootError;
}
var targetWorkspaceId = String(target.workspaceId || workspacePath.workspaceId || '').trim();
var currentWorkspaceId = String(resolveWorkspaceId(documentRef.body) || '').trim();
if (targetWorkspaceId && currentWorkspaceId && targetWorkspaceId !== currentWorkspaceId) {
var workspaceError = new Error('AI target 与当前 workspaceId 不一致,请重新选择当前工作区内的目标。');
workspaceError.code = 'page_ai_target_workspace_mismatch';
throw workspaceError;
}
}
function pageAiBuildContextRefs(scopedContext, runTargetSnapshot) {
var selected = pageAiEnsureContextRefState();
var refs = [];
var documentId = currentDocumentId();
var rootUri = currentRootUri();
var workspaceId = resolveWorkspaceId(documentRef.body);
var editorTarget = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget();
if (selected.current_page) {
refs.push({
kind: 'current_page',
documentId: documentId,
rootUri: rootUri,
workspaceId: workspaceId
});
}
if (selected.selection && scopedContext && scopedContext.selectedText) {
refs.push({
kind: 'selection',
documentId: documentId,
rootUri: rootUri,
selectedBlockId: scopedContext.selectedBlockId || ''
});
}
if (selected.active_editor && editorTarget) {
var workspacePath = editorTarget.workspacePath && typeof editorTarget.workspacePath === 'object' ? editorTarget.workspacePath : {};
refs.push({
kind: 'active_editor',
documentId: editorTarget.documentId || documentId,
workspaceId: editorTarget.workspaceId || workspacePath.workspaceId || workspaceId,
rootUri: workspacePath.rootUri || rootUri,
relativePath: workspacePath.relativePath || '',
editorKind: editorTarget.editorKind || '',
resourceKind: editorTarget.resourceKind || workspacePath.resourceKind || '',
targetId: editorTarget.targetId || workspacePath.objectIdentity || '',
objectIdentity: editorTarget.objectIdentity || workspacePath.objectIdentity || '',
assetId: editorTarget.assetId || workspacePath.assetId || '',
onlyofficeSessionId: editorTarget.onlyofficeSessionId || editorTarget.bridgeSessionId || '',
bridgeSessionId: editorTarget.bridgeSessionId || editorTarget.onlyofficeSessionId || ''
});
}
if (selected.file) {
refs.push({
kind: 'file',
documentId: documentId,
rootUri: rootUri,
relativePath: localMarkdownRelativePathFromPageAiDocumentId(documentId)
});
}
if (selected.folder) {
refs.push({
kind: 'folder',
rootUri: rootUri,
relativePath: ''
});
}
if (selected.changed_files) {
refs.push({
kind: 'changed_files',
rootUri: rootUri,
sinceRunTargetSnapshot: runTargetSnapshot && runTargetSnapshot.frozenAt || null
});
}
return refs.filter(function(ref) {
return ref && String(ref.kind || '').trim();
});
}
2026-06-01 10:30:42 +08:00
function pageAiOcrEligibleResourceKind(value) {
var normalized = String(value || '').trim().toLowerCase();
return normalized === 'image' || normalized === 'pdf' || normalized === 'attachment' || normalized === 'resource';
}
function pageAiOcrEligiblePath(value) {
var path = String(value || '').trim().toLowerCase();
return /\.(png|jpg|jpeg|webp|bmp|tif|tiff|pdf)$/.test(path);
}
function pageAiOcrBodyPreview(markdown) {
var body = String(markdown || '').replace(/^---\n[\s\S]*?\n---\n?/, '').trim();
return body.slice(0, 1600);
}
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;
}
}
async function pageAiEnrichOcrContextRefs(contextRefs, agentTargetPackage, editorTarget) {
var ocrContext = await fetchPageAiOcrSidecarContext(editorTarget);
if (!ocrContext) return { contextRefs, agentTargetPackage };
var nextRefs = pageAiNormalizeArray(contextRefs).map(function(ref) {
if (ref && ref.kind === 'active_editor') return Object.assign({}, ref, { ocrContext: ocrContext });
return ref;
});
var nextPackage = agentTargetPackage && typeof agentTargetPackage === 'object'
? Object.assign({}, agentTargetPackage, { ocrContext: ocrContext })
: agentTargetPackage;
if (nextPackage && nextPackage.currentFile && typeof nextPackage.currentFile === 'object') {
nextPackage.currentFile = Object.assign({}, nextPackage.currentFile, { ocrRootRelativePath: ocrContext.ocrRootRelativePath });
}
if (nextPackage && Array.isArray(nextPackage.targets)) {
nextPackage.targets = nextPackage.targets.map(function(target, index) {
return index === 0 && target && typeof target === 'object'
? Object.assign({}, target, { ocrContext: ocrContext })
: target;
});
}
return { contextRefs: nextRefs, agentTargetPackage: nextPackage };
}
2026-06-01 09:29:12 +08:00
function pageAiBuildAllowedRoots() {
return pageAiNormalizeArray(pageUiState.pageAiAllowedRoots).map(function(root) {
return {
rootUri: root.rootUri,
permission: root.permission === 'write' || root.permission === 'read_write' ? 'write' : 'read',
recursive: root.recursive !== false,
source: root.source === 'auto' || root.source === 'user' || root.source === 'admin'
? 'sqlite_directory_grant'
: (root.source || 'sqlite_directory_grant'),
grantId: root.id || ''
};
});
}
function pageAiBuildRunTargetSnapshot(scopedContext, prompt) {
var aiContext = scopedContext && scopedContext.pageContext && scopedContext.pageContext.aiContext
? scopedContext.pageContext.aiContext
: {};
var editorTarget = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget();
return {
schema: 'mnote.page_ai_run_target_snapshot.v1',
source: 'open_editors_snapshot',
frozenAt: Date.now(),
workspaceId: resolveWorkspaceId(documentRef.body),
documentId: currentDocumentId(),
sourceKind: currentSourceKind(),
rootUri: currentRootUri(),
contextScope: pageUiState.pageAiContextScope || 'page',
promptPreview: searchText(prompt || '').slice(0, 160),
editorTarget: pageAiCloneJson(editorTarget),
activeEditorTarget: pageAiCloneJson(aiContext.activeEditorTarget || editorTarget),
openEditorsSnapshot: pageAiCloneJson(aiContext.openEditorsSnapshot || currentPageAiOpenEditorsSnapshot())
};
}
function pageAiContextKindsFromRefs(contextRefs) {
var kinds = {};
pageAiNormalizeArray(contextRefs).forEach(function(ref) {
var kind = String(ref && ref.kind || '').trim();
if (kind) kinds[kind] = true;
});
return kinds;
}
function pageAiPageContextForRefs(pageContext, contextRefs) {
var cloned = pageAiCloneJson(pageContext) || {};
var aiContext = cloned.aiContext && typeof cloned.aiContext === 'object' ? cloned.aiContext : {};
var kinds = pageAiContextKindsFromRefs(contextRefs);
delete cloned.documentBlocks;
delete cloned.evidence;
delete aiContext.contextBlocks;
delete aiContext.pageText;
delete aiContext.pageXml;
delete aiContext.truncated;
delete aiContext.warnings;
if (!kinds.selection) {
delete aiContext.selectedText;
delete aiContext.selectedBlockIds;
delete aiContext.selectedBlocks;
delete aiContext.allowedTargetBlockIds;
}
cloned.aiContext = aiContext;
return cloned;
}
function pageAiBuildAgentTargetPackage(scopedContext, runTargetSnapshot) {
var target = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget();
var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object'
? pageAiWorkspacePathForDocument(target.documentId || currentDocumentId(), target.workspacePath)
: pageAiWorkspacePathForDocument(target && target.documentId || currentDocumentId(), null);
var relativePath = String(workspacePath.relativePath || '').trim();
var allowedFiles = relativePath ? [relativePath] : [];
var writable = pageAiBuildAllowedRoots().some(function(root) {
return String(root && root.rootUri || '').trim() === String(workspacePath.rootUri || '').trim()
&& String(root && root.permission || '').trim() === 'write';
});
var primaryTargetId = String(target && target.targetId || workspacePath.objectIdentity || workspacePath.documentId || '').trim();
var onlyofficeSessionId = String(target && (target.onlyofficeSessionId || target.bridgeSessionId) || '').trim();
var targetEntry = {
targetId: primaryTargetId,
objectIdentity: primaryTargetId,
documentId: workspacePath.documentId,
workspaceId: workspacePath.workspaceId,
sourceKind: workspacePath.sourceKind,
rootUri: workspacePath.rootUri,
relativePath: relativePath,
resourceKind: workspacePath.resourceKind,
assetId: workspacePath.assetId || target && target.assetId || '',
onlyofficeSessionId: onlyofficeSessionId,
bridgeSessionId: onlyofficeSessionId,
paneRole: target && target.paneRole || 'primary',
title: target && target.title || '',
policy: {
permission: allowedFiles.length && writable ? 'read_write' : 'read',
writeRequiresCleanBuffer: true,
conflictPolicy: 'fail_on_dirty_or_stale'
}
};
return {
schema: 'mnote.agent_target_package.v1',
source: 'page_ai_run_target_snapshot',
frozenAt: runTargetSnapshot && runTargetSnapshot.frozenAt || Date.now(),
primaryTargetId: primaryTargetId,
onlyofficeSessionId: onlyofficeSessionId,
bridgeSessionId: onlyofficeSessionId,
workspaceId: workspacePath.workspaceId,
sourceKind: workspacePath.sourceKind,
rootUri: workspacePath.rootUri,
documentId: workspacePath.documentId,
objectIdentity: primaryTargetId,
resourceKind: workspacePath.resourceKind,
workspacePath: workspacePath,
currentFile: relativePath ? {
rootUri: workspacePath.rootUri,
relativePath: relativePath,
documentId: workspacePath.documentId,
objectIdentity: primaryTargetId,
resourceKind: workspacePath.resourceKind
} : null,
allowedFiles: allowedFiles,
targets: [targetEntry],
policy: {
writeRequiresExplicitTarget: true,
allowedFilesSource: 'selected_page_ai_target',
conflictPolicy: 'fail_on_dirty_or_stale'
}
};
}
function pageAiBlockingDirtyState(dirtyState) {
var state = String(dirtyState || '').trim();
var normalized = state.toLowerCase();
if (normalized === 'dirty') return 'Dirty';
if (normalized === 'stale') return 'Stale';
if (normalized === 'deleted') return 'Deleted';
if (normalized === 'externalmodified' || normalized === 'external-change-conflict' || normalized === 'hasexternalconflict') return 'ExternalModified';
return '';
}
async function fetchPageAiTargetBufferState(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 resourceKind = String(target.resourceKind || target.editorKind || workspacePath.resourceKind || '').trim();
if (resourceKind === 'office' || resourceKind === 'only_office' || resourceKind === 'onlyoffice') return null;
var documentId = String(target.documentId || currentDocumentId() || '').trim();
var rootUri = String(workspacePath.rootUri || currentRootUri() || '').trim();
if (!documentId || !rootUri) return null;
var relativePath = String(workspacePath.relativePath || '').trim()
|| localMarkdownRelativePathFromPageAiDocumentId(documentId);
var url = new URL('/api/documents/buffer-state', window.location.origin);
url.searchParams.set('documentId', documentId);
url.searchParams.set('sourceKind', 'local_folder');
url.searchParams.set('rootUri', rootUri);
var workspaceId = String(target.workspaceId || resolveWorkspaceId(documentRef.body) || '').trim();
if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
if (relativePath) url.searchParams.set('relativePath', relativePath);
try {
var response = await fetch(url.toString(), { cache: 'no-store', headers: { accept: 'application/json' } });
var payload = await response.json().catch(function() { return null; });
if (!response.ok || !payload || payload.ok !== true) return null;
return payload.result || null;
} catch (_) {
return null;
}
}
async function assertPageAiTargetWritable(editorTarget) {
var workspacePath = editorTarget && editorTarget.workspacePath && typeof editorTarget.workspacePath === 'object' ? editorTarget.workspacePath : {};
var resourceKind = String(editorTarget && (editorTarget.resourceKind || editorTarget.editorKind) || workspacePath.resourceKind || '').trim();
var onlyofficeSessionId = String(editorTarget && (editorTarget.onlyofficeSessionId || editorTarget.bridgeSessionId) || '').trim();
if ((resourceKind === 'office' || resourceKind === 'only_office' || resourceKind === 'onlyoffice') && !onlyofficeSessionId) {
var sessionError = new Error('ONLYOFFICE 资源仍在连接 MNote bridge,请等待 Office 页面加载完成后再让 AI 操作。');
sessionError.code = 'page_ai_onlyoffice_bridge_not_ready';
throw sessionError;
}
var snapshotState = pageAiBlockingDirtyState(editorTarget && editorTarget.dirtyState);
var bufferState = await fetchPageAiTargetBufferState(editorTarget);
var bufferDirtyState = pageAiBlockingDirtyState(bufferState && bufferState.dirtyState);
var blockedState = bufferDirtyState || snapshotState;
if (!blockedState) return bufferState;
var documentId = String(editorTarget && editorTarget.documentId || currentDocumentId() || '').trim();
var error = new Error('目标文档存在未保存或外部变更状态(' + blockedState + '),请先保存、解决冲突或刷新后再让 AI 写入。');
error.code = 'page_ai_target_buffer_not_writable';
error.documentId = documentId;
error.dirtyState = blockedState;
throw error;
}
function currentPageAiSelectedText() {
try {
var selection = window.getSelection ? window.getSelection() : null;
return selection ? searchText(selection.toString() || '') : '';
} catch (_) {
return '';
}
}
function pageAiProjectionBlocks(aggregate) {
var blocks = aggregate && aggregate.body && aggregate.body.blockDocument && aggregate.body.blockDocument.blocks;
return Array.isArray(blocks) ? blocks : [];
}
function pageAiBlockText(block) {
return searchText(block && (block.text || block.title || block.content) || '');
}
function pageAiSelectedBlockIdsFromSelection() {
try {
var selection = window.getSelection ? window.getSelection() : null;
if (!selection || selection.rangeCount === 0 || searchText(selection.toString() || '') === '') return [];
var range = selection.getRangeAt(0);
var editor = documentRef.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror');
if (!(editor instanceof HTMLElement)) return [];
return Array.from(editor.children).filter(function(node) {
if (!(node instanceof HTMLElement)) return false;
try {
return range.intersectsNode(node);
} catch (_) {
return false;
}
}).map(function(node) {
return searchText(node.getAttribute('data-id') || node.id || '');
}).filter(Boolean);
} catch (_) {
return [];
}
}
function pageAiBlocksToPageXml(blocks, aggregate) {
var revision = aggregate && aggregate.body ? String(aggregate.body.revision || '') : '';
var pageId = currentDocumentId() || 'current-page';
var lines = ['<page id="' + escapeHtml(pageId) + '" revision="' + escapeHtml(revision) + '">'];
blocks.forEach(function(block) {
var blockId = String(block && (block.blockId || block.id) || '');
var type = String(block && block.type || 'paragraph');
var revisionRef = String(block && block.revisionRef || '');
var level = block && block.attrs && block.attrs.level ? ' level="' + escapeHtml(block.attrs.level) + '"' : '';
lines.push(' <block id="' + escapeHtml(blockId) + '" type="' + escapeHtml(type) + '" revisionRef="' + escapeHtml(revisionRef) + '"' + level + '>' + escapeHtml(pageAiBlockText(block)) + '</block>');
});
lines.push('</page>');
return lines.join('\n');
}
function buildPageAiContext(contextSnapshot, scope, selectedText) {
var aggregate = contextSnapshot.aggregate || {};
var body = aggregate.body || {};
var allBlocks = pageAiProjectionBlocks(aggregate);
var selectedBlockIds = scope === 'selection' ? pageAiSelectedBlockIdsFromSelection() : [];
var selectedSet = {};
selectedBlockIds.forEach(function(id) { selectedSet[id] = true; });
var selectedBlocks = selectedBlockIds.length
? allBlocks.filter(function(block) { return selectedSet[String(block.blockId || block.id || '')]; })
: [];
var contextBlocks = selectedBlocks.length ? selectedBlocks : allBlocks.slice(0, 120);
var truncated = !selectedBlocks.length && allBlocks.length > contextBlocks.length;
return {
schema: 'mnote.page_ai_context.v1',
workspaceId: resolveWorkspaceId(documentRef.body),
documentId: currentDocumentId(),
activeEditorTarget: currentPageAiScopedEditorTarget(),
openEditorsSnapshot: currentPageAiOpenEditorsSnapshot(),
scope: scope,
revision: body.revision || null,
conflictDetectionKey: body.conflictDetectionKey || null,
selectedText: selectedText || '',
selectedBlockIds: selectedBlockIds,
allowedTargetBlockIds: selectedBlockIds,
selectedBlocks: selectedBlocks,
contextBlocks: contextBlocks,
pageText: contextBlocks.map(pageAiBlockText).filter(Boolean).join('\n'),
pageXml: pageAiBlocksToPageXml(contextBlocks, aggregate),
truncated: truncated,
warnings: truncated ? [{ code: 'page_ai_context_truncated', message: '页面 AI context 已按前 120 个块裁剪' }] : []
};
}
function pageAiScopedPageContext(contextSnapshot) {
var aggregate = contextSnapshot.aggregate || {};
var body = contextSnapshot.body || {};
var subtree = contextSnapshot.subtree || null;
var outline = subtree && subtree.outline ? subtree.outline : null;
var scope = pageUiState.pageAiContextScope || 'page';
var title = aggregate.head && aggregate.head.title ? aggregate.head.title : '';
var selectedText = scope === 'selection' ? currentPageAiSelectedText() : '';
var aiContext = buildPageAiContext(contextSnapshot, scope, selectedText);
var editorTarget = aiContext.activeEditorTarget || currentPageAiScopedEditorTarget();
return {
pageContext: {
contextScope: scope,
documentBlocks: null,
node: {
documentId: currentDocumentId(),
title: title
},
subtree: null,
outline: null,
pageSubtreeSource: scope === 'page' ? (contextSnapshot.pageSubtreeSource || 'none') : 'scope:' + scope,
evidence: selectedText ? [{ kind: 'selection', text: selectedText }] : null,
pageOptions: scope === 'options' || scope === 'page' ? currentPageOptions() : null,
contentAccess: 'mnote.context.read_current_page',
aiContext: aiContext
},
editorTarget: editorTarget,
selectedText: selectedText,
selectedBlockId: aiContext.selectedBlockIds[0] || null
};
}
return {
currentPageAiSelectedText,
currentPageAiEditorTarget,
currentPageAiPageEditorTarget,
currentPageAiScopedEditorTarget,
currentPageAiOpenEditorsSnapshot,
pageAiBlockText,
pageAiBlockingDirtyState,
pageAiBlocksToPageXml,
pageAiBuildAgentTargetPackage,
pageAiBuildAllowedRoots,
pageAiBuildContextRefs,
2026-06-01 10:30:42 +08:00
pageAiEnrichOcrContextRefs,
2026-06-01 09:29:12 +08:00
pageAiBuildRunTargetSnapshot,
pageAiCloneJson,
pageAiContextKindsFromRefs,
pageAiEditorTargetCandidates,
pageAiFallbackEditorTarget,
pageAiPageContextForRefs,
pageAiProjectionBlocks,
pageAiResourceKindForTarget,
pageAiSelectedBlockIdsFromSelection,
pageAiSetRunTargetSnapshot,
pageAiScopedPageContext,
pageAiTargetFromOpenEditor,
pageAiTargetId,
pageAiWorkspacePathForDocument,
pageAiWorkspacePathForTarget,
assertPageAiTargetInCurrentWorkspace,
assertPageAiTargetWritable,
buildPageAiContext,
fetchPageAiTargetBufferState,
localMarkdownDocumentIdFromPageAiRelativePath,
localMarkdownRelativePathFromPageAiDocumentId,
};
}