收口 MNote P0 P1 P2 审查尾项
- 归档 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 .
This commit is contained in:
@@ -0,0 +1,758 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user