- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目 - 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线 - 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径 验证: - cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1 - cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1 - git diff --check - git diff --cached --check - codegraph index . --force && codegraph status . - codegraph sync . && codegraph status .
759 lines
35 KiB
JavaScript
759 lines
35 KiB
JavaScript
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();
|
|
});
|
|
}
|
|
|
|
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,
|
|
pageAiBuildRunTargetSnapshot,
|
|
pageAiCloneJson,
|
|
pageAiContextKindsFromRefs,
|
|
pageAiEditorTargetCandidates,
|
|
pageAiFallbackEditorTarget,
|
|
pageAiPageContextForRefs,
|
|
pageAiProjectionBlocks,
|
|
pageAiResourceKindForTarget,
|
|
pageAiSelectedBlockIdsFromSelection,
|
|
pageAiSetRunTargetSnapshot,
|
|
pageAiScopedPageContext,
|
|
pageAiTargetFromOpenEditor,
|
|
pageAiTargetId,
|
|
pageAiWorkspacePathForDocument,
|
|
pageAiWorkspacePathForTarget,
|
|
assertPageAiTargetInCurrentWorkspace,
|
|
assertPageAiTargetWritable,
|
|
buildPageAiContext,
|
|
fetchPageAiTargetBufferState,
|
|
localMarkdownDocumentIdFromPageAiRelativePath,
|
|
localMarkdownRelativePathFromPageAiDocumentId,
|
|
};
|
|
}
|