feat: EditorRuntimeActor - 三层缓存/delta/事件架构
Phase A — EditorRuntimeActor 内存缓存层 - 新增 editor_actor.rs: EditorBlockDocument 内存态 + apply_command + load_or_init - block.rs 四个写工具(replace/insert/delete/move)接入 actor 路径 - editor_actor feature flag(MNOTE_WEB_ENABLE_EDITOR_ACTOR=true 默认开启) - bridge-runtime 三个核心函数公开化 - rust-toolchain: 1.89 → stable(修复 spike WASM 编译阻塞) Phase B — 编辑器增量 delta channel - BlockDelta/DeltaOperation 类型 + actor.build_block_delta() - leptos-tiptap spike: mnote:editor:block-delta CustomEvent 监听 + JSON patch - DocumentAiAgentPanel: 拦截 blockDelta → window dispatchEvent - 工具响应含 blockDelta 字段供前端消费 Phase C — 事件 stream delta - broadcast channel 在 AppState/actor/SSE 三层贯通 - tree_events SSE 端点发 block.delta 事件 - 旧客户端降级兼容 环境修复 - rustc recursion_limit = 1024(修复 Leptos SSR 类型深度溢出) - run-convex-deploy.js(封装 Convex function 部署到本地后端 3210) ref: design/07-ai/process/7-13-page-block-editor-runtime-actor-v1.md
This commit is contained in:
@@ -50,7 +50,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
pageAiGatewayHealthError: '',
|
||||
pageAiLastToolCall: null,
|
||||
pageAiProfiles: [],
|
||||
pageAiActiveProfileName: 'default',
|
||||
pageAiActiveProfileName: 'mnoteai',
|
||||
pageAiProfileError: '',
|
||||
pageAiProfileMemory: { memory: '', user: '', soul: '' },
|
||||
pageAiProfileMemoryDrafts: { memory: '', user: '', soul: '' },
|
||||
@@ -256,6 +256,83 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
}
|
||||
|
||||
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 = document.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(document.body),
|
||||
documentId: currentDocumentId(),
|
||||
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 || {};
|
||||
@@ -264,6 +341,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
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);
|
||||
return {
|
||||
pageContext: {
|
||||
contextScope: scope,
|
||||
@@ -277,10 +355,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
pageSubtreeSource: scope === 'page' ? (contextSnapshot.pageSubtreeSource || 'none') : 'scope:' + scope,
|
||||
evidence: selectedText ? [{ kind: 'selection', text: selectedText }] : null,
|
||||
pageOptions: scope === 'options' || scope === 'page' ? currentPageOptions() : null,
|
||||
contentAccess: 'mnote.page.get'
|
||||
contentAccess: 'mnote.doc.fetch',
|
||||
aiContext: aiContext
|
||||
},
|
||||
selectedText: selectedText,
|
||||
selectedBlockId: null
|
||||
selectedBlockId: aiContext.selectedBlockIds[0] || null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1160,6 +1239,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (!documentId) return;
|
||||
var escaped = cssEscape(documentId);
|
||||
var escapedDocRowId = cssEscape('doc:' + documentId);
|
||||
var isCurrentDocument = currentDocumentId() === documentId;
|
||||
var pageSelectors = [
|
||||
'.tree-row[data-shell-mode="page"][data-node-id="' + escaped + '"] > .tree-link > .tree-link-title',
|
||||
'.wolai-page-row[data-node-id="' + escaped + '"] > .wolai-row-title',
|
||||
@@ -1174,6 +1254,22 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
document.querySelectorAll('.tree-row[data-shell-mode="filetree"][data-row-id="' + escapedDocRowId + '"] > .tree-link > .tree-link-title').forEach(function(node) {
|
||||
if (node instanceof HTMLElement) node.textContent = fileTreePageTitle(title);
|
||||
});
|
||||
document.querySelectorAll('[data-page-title-input="true"][data-document-id="' + escaped + '"]').forEach(function(node) {
|
||||
if (!(node instanceof HTMLTextAreaElement)) return;
|
||||
node.value = title;
|
||||
node.setAttribute('data-title-last-saved', title);
|
||||
node.setAttribute('data-title-save-status', 'saved');
|
||||
node.style.height = 'auto';
|
||||
node.style.height = Math.max(48, node.scrollHeight) + 'px';
|
||||
});
|
||||
document.querySelectorAll('[data-document-pane="true"][data-pane-document-id="' + escaped + '"] [data-page-title-current="true"]').forEach(function(node) {
|
||||
if (node instanceof HTMLElement) node.textContent = title;
|
||||
});
|
||||
if (isCurrentDocument) {
|
||||
document.title = title;
|
||||
var topbarCurrent = document.querySelector('.wolai-breadcrumb-current [data-page-title-current]');
|
||||
if (topbarCurrent instanceof HTMLElement) topbarCurrent.textContent = title;
|
||||
}
|
||||
}
|
||||
|
||||
function fileTreePageTitle(title) {
|
||||
@@ -1396,7 +1492,29 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return pageChanged || fileChanged;
|
||||
}
|
||||
|
||||
function moveDocumentRowForMode(mode, documentId, parentId) {
|
||||
function sortOrderFromDelta(data) {
|
||||
var raw = data && (data.sortOrder ?? data.sort_order);
|
||||
var value = Number(raw);
|
||||
return Number.isFinite(value) && value >= 0 ? Math.floor(value) : null;
|
||||
}
|
||||
|
||||
function insertTreeNodeAtSortOrder(targetContainer, node, sortOrder) {
|
||||
if (!targetContainer || !node) return false;
|
||||
if (sortOrder === null || sortOrder === undefined) {
|
||||
targetContainer.appendChild(node);
|
||||
return true;
|
||||
}
|
||||
var siblings = Array.from(targetContainer.querySelectorAll(':scope > .tree-node')).filter(function(candidate) {
|
||||
return candidate !== node;
|
||||
});
|
||||
var targetIndex = Math.max(0, Math.min(Number(sortOrder), siblings.length));
|
||||
var referenceNode = siblings[targetIndex] || null;
|
||||
if (referenceNode) targetContainer.insertBefore(node, referenceNode);
|
||||
else targetContainer.appendChild(node);
|
||||
return true;
|
||||
}
|
||||
|
||||
function moveDocumentRowForMode(mode, documentId, parentId, sortOrder) {
|
||||
var row = document.querySelector(rowSelectorForDocument(mode, documentId));
|
||||
var node = row ? row.closest('.tree-node') : null;
|
||||
var root = treeRootForMode(mode);
|
||||
@@ -1410,16 +1528,16 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
} else {
|
||||
row.removeAttribute('data-parent-id');
|
||||
}
|
||||
targetContainer.appendChild(node);
|
||||
return true;
|
||||
return insertTreeNodeAtSortOrder(targetContainer, node, sortOrder);
|
||||
}
|
||||
|
||||
function applyMoveDocumentDelta(data) {
|
||||
var documentId = documentIdFromDelta(data);
|
||||
if (!documentId) return false;
|
||||
var parentId = parentIdFromDelta(data);
|
||||
var movedPage = moveDocumentRowForMode('page', documentId, parentId);
|
||||
var movedFile = moveDocumentRowForMode('filetree', documentId, parentId);
|
||||
var sortOrder = sortOrderFromDelta(data);
|
||||
var movedPage = moveDocumentRowForMode('page', documentId, parentId, sortOrder);
|
||||
var movedFile = moveDocumentRowForMode('filetree', documentId, parentId, sortOrder);
|
||||
return movedPage || movedFile;
|
||||
}
|
||||
|
||||
@@ -3994,7 +4112,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var selected = pageUiState.pageAiProfiles.find(function(profile) {
|
||||
return profile && profile.active;
|
||||
});
|
||||
return pageAiProfileValue(selected) || 'default';
|
||||
return pageAiProfileValue(selected) || 'mnoteai';
|
||||
}
|
||||
|
||||
function pageAiMnoteToolModel() {
|
||||
return String(document.documentElement.getAttribute('data-mnote-page-ai-tool-model') || 'deepseek-v4-flash').trim() || 'deepseek-v4-flash';
|
||||
}
|
||||
|
||||
function pageAiCurrentProfileRecord() {
|
||||
@@ -4006,10 +4128,12 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
|
||||
function pageAiCurrentModelLabel() {
|
||||
var profile = pageAiCurrentProfileRecord();
|
||||
if (!profile) return '由 Hermes 决定';
|
||||
var toolModel = pageAiMnoteToolModel();
|
||||
if (!profile) return 'tool: ' + toolModel;
|
||||
var model = String(profile.model || '').trim();
|
||||
var gateway = String(profile.gateway || '').trim();
|
||||
return [model, gateway].filter(Boolean).join(' · ') || '由 Hermes 决定';
|
||||
var profileLabel = [model, gateway].filter(Boolean).join(' · ') || '由 Hermes 决定';
|
||||
return 'tool: ' + toolModel + ' · profile: ' + profileLabel;
|
||||
}
|
||||
|
||||
function pageAiNormalizeProfiles(payload) {
|
||||
@@ -4085,9 +4209,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
function pageAiSetActiveProfile(profileName) {
|
||||
var next = String(profileName || '').trim() || 'default';
|
||||
var next = String(profileName || '').trim() || 'mnoteai';
|
||||
pageUiState.pageAiActiveProfileName = next;
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-profile', next);
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-tool-model', pageAiMnoteToolModel());
|
||||
}
|
||||
|
||||
function pageAiSetRunStatus(status, runId) {
|
||||
@@ -4186,10 +4311,20 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var writesCurrentPage = [
|
||||
'mnote.page.save',
|
||||
'mnote.page.update.title',
|
||||
'mnote.page.update.options'
|
||||
'mnote.page.update.options',
|
||||
'mnote.doc.apply.block.ops',
|
||||
'mnote.block.replace',
|
||||
'mnote.block.insert.after',
|
||||
'mnote.block.delete',
|
||||
'mnote.block.move.after'
|
||||
].indexOf(normalizedTool) >= 0 || [
|
||||
'mnote.page.update_title',
|
||||
'mnote.page.update_options'
|
||||
'mnote.page.update_options',
|
||||
'mnote.doc.apply_block_ops',
|
||||
'mnote.block.replace',
|
||||
'mnote.block.insert_after',
|
||||
'mnote.block.delete',
|
||||
'mnote.block.move_after'
|
||||
].indexOf(String(toolName || '').trim()) >= 0;
|
||||
if (!writesCurrentPage) return;
|
||||
var documentId = pageAiToolEventDeepFindString(toolEvent, ['documentId', 'document_id'], 0) || currentDocumentId();
|
||||
@@ -4352,6 +4487,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
scope: String(tool && (tool.scope || tool.requiredScope || '') || '').trim(),
|
||||
kind: String(tool && (tool.kind || tool.mode || '') || '').trim(),
|
||||
status: String(tool && (tool.status || tool.permission || 'available') || '').trim(),
|
||||
enabled: tool && tool.enabled !== false,
|
||||
unavailableReason: String(tool && (tool.unavailableReason || tool.reason || '') || '').trim()
|
||||
};
|
||||
}).filter(Boolean);
|
||||
@@ -4394,7 +4530,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
|
||||
async function pageAiLoadTools() {
|
||||
try {
|
||||
var response = await fetch('/api/hermes/client/tools?scope=mnote', {
|
||||
var response = await fetch('/api/hermes/client/tools?scope=mnote&profile=' + encodeURIComponent(pageAiCurrentProfile()), {
|
||||
headers: { 'accept': 'application/json' }
|
||||
});
|
||||
var payload = await response.json().catch(function(){ return null; });
|
||||
@@ -4483,14 +4619,17 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var payload = await response.json().catch(function(){ return null; });
|
||||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profiles_failed_' + response.status));
|
||||
var profiles = pageAiNormalizeProfiles(payload);
|
||||
pageUiState.pageAiProfiles = profiles.length ? profiles : [{ name: 'default', active: true }];
|
||||
pageUiState.pageAiProfiles = profiles.length ? profiles : [{ name: 'mnoteai', active: true }];
|
||||
var current = pageAiCurrentProfile();
|
||||
var hasCurrent = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === current; });
|
||||
var hasMnoteAi = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === 'mnoteai'; });
|
||||
var active = pageAiProfileValue(pageUiState.pageAiProfiles.find(function(profile) { return profile.active; }));
|
||||
pageAiSetActiveProfile(active || pageAiCurrentProfile());
|
||||
pageAiSetActiveProfile(hasCurrent && current !== 'default' ? current : (hasMnoteAi ? 'mnoteai' : (active || current)));
|
||||
pageUiState.pageAiProfileError = '';
|
||||
void pageAiLoadGatewayHealth();
|
||||
} catch (error) {
|
||||
pageUiState.pageAiProfileError = error instanceof Error ? error.message : String(error);
|
||||
if (!pageUiState.pageAiProfiles.length) pageUiState.pageAiProfiles = [{ name: 'default', active: true }];
|
||||
if (!pageUiState.pageAiProfiles.length) pageUiState.pageAiProfiles = [{ name: 'mnoteai', active: true }];
|
||||
pageAiSetActiveProfile(pageAiCurrentProfile());
|
||||
}
|
||||
renderPageAiProviderButtons();
|
||||
@@ -4520,6 +4659,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
await pageAiEnsureHermesSession(true);
|
||||
await pageAiLoadProfileMemory();
|
||||
await pageAiLoadSkills();
|
||||
await pageAiLoadTools();
|
||||
await pageAiLoadGatewayHealth();
|
||||
renderPageAiControls();
|
||||
} catch (error) {
|
||||
@@ -4627,6 +4767,45 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
renderPageAiConversation();
|
||||
}
|
||||
|
||||
async function pageAiToggleTool(toolName, enabled) {
|
||||
var name = String(toolName || '').trim();
|
||||
if (!name) return;
|
||||
var previous = null;
|
||||
pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
|
||||
if (tool.name === name && previous == null) previous = tool.enabled !== false;
|
||||
});
|
||||
try {
|
||||
var response = await fetch('/api/hermes/client/tools/toggle', {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
profile: pageAiCurrentProfile(),
|
||||
name: name,
|
||||
enabled: Boolean(enabled)
|
||||
})
|
||||
});
|
||||
var payload = await response.json().catch(function(){ return null; });
|
||||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'tool_toggle_failed_' + response.status));
|
||||
pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
|
||||
if (tool.name === name) {
|
||||
tool.enabled = Boolean(enabled);
|
||||
tool.status = Boolean(enabled) ? 'available' : 'disabled';
|
||||
tool.unavailableReason = Boolean(enabled) ? '' : '当前 Hermes profile 已关闭该 mnote tool';
|
||||
}
|
||||
});
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-tool-toggled', name);
|
||||
pageUiState.pageAiToolsError = '';
|
||||
} catch (error) {
|
||||
pageUiState.pageAiToolsError = error instanceof Error ? error.message : String(error);
|
||||
if (previous != null) {
|
||||
pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
|
||||
if (tool.name === name) tool.enabled = previous;
|
||||
});
|
||||
}
|
||||
}
|
||||
renderPageAiControls();
|
||||
}
|
||||
|
||||
function renderPageAiProviderButtons() {
|
||||
var drawer = ensurePageAiDrawer();
|
||||
drawer.querySelectorAll('[data-page-ai-provider]').forEach(function(button) {
|
||||
@@ -4785,9 +4964,14 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
toolsList.innerHTML = tools.map(function(tool) {
|
||||
return '' +
|
||||
'<div class="wolai-page-ai-tool-row">' +
|
||||
'<div class="wolai-page-ai-tool-name">' + escapeHtml(tool.name) + '</div>' +
|
||||
'<div class="wolai-page-ai-tool-meta">' + escapeHtml(tool.scope || tool.kind || 'mnote') + ' · ' + escapeHtml(tool.status || 'available') + '</div>' +
|
||||
(tool.unavailableReason ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml(tool.unavailableReason) + '</div>' : '') +
|
||||
'<div class="wolai-page-ai-skill-copy">' +
|
||||
'<div class="wolai-page-ai-tool-name">' + escapeHtml(tool.name) + '</div>' +
|
||||
'<div class="wolai-page-ai-tool-meta">' + escapeHtml(tool.scope || tool.kind || 'mnote') + ' · ' + escapeHtml(tool.status || 'available') + '</div>' +
|
||||
(tool.unavailableReason ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml(tool.unavailableReason) + '</div>' : '') +
|
||||
'</div>' +
|
||||
'<button type="button" class="wolai-page-ai-skill-switch' + (tool.enabled !== false ? ' is-on' : '') + '" data-page-ai-tool-toggle="' + escapeHtml(tool.name) + '" aria-pressed="' + (tool.enabled !== false ? 'true' : 'false') + '">' +
|
||||
'<span></span>' +
|
||||
'</button>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
}
|
||||
@@ -5161,6 +5345,88 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
}
|
||||
|
||||
function pageAiLooksLikeBlockEdit(prompt) {
|
||||
var text = searchText(prompt);
|
||||
return ['新增', '添加', '插入', '删除', '删掉', '修改', '替换', '改成', '移动', '移到', 'move', 'replace', 'delete', 'insert'].some(function(word) {
|
||||
return text.indexOf(word) >= 0;
|
||||
});
|
||||
}
|
||||
|
||||
async function pageAiTryBlockEditWorkflow(prompt, scopedContext) {
|
||||
if (!pageAiLooksLikeBlockEdit(prompt)) return false;
|
||||
var runId = 'page-ai-fast-' + Date.now().toString(36);
|
||||
var traceId = 'page-ai-fast-' + Date.now().toString(36);
|
||||
pageAiSetRunStatus('running', runId);
|
||||
renderPageAiControls();
|
||||
var started = Date.now();
|
||||
var response = await fetch('/api/page-ai/block-edit-workflow', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
documentId: currentDocumentId(),
|
||||
sessionId: pageUiState.pageAiActiveSessionId,
|
||||
runId: runId,
|
||||
profile: pageAiCurrentProfile(),
|
||||
model: pageAiMnoteToolModel(),
|
||||
message: prompt,
|
||||
pageContext: scopedContext.pageContext,
|
||||
selectedBlockId: scopedContext.selectedBlockId,
|
||||
selectedText: scopedContext.selectedText,
|
||||
traceId: traceId
|
||||
})
|
||||
});
|
||||
var payload = await response.json().catch(function() { return null; });
|
||||
if (!response.ok || !payload || payload.ok !== true) {
|
||||
var code = payload && payload.code ? String(payload.code) : '';
|
||||
if (code === 'page_ai_workflow_not_block_edit') return false;
|
||||
pageUiState.pageAiMessages.push({
|
||||
role: 'tool',
|
||||
toolName: 'mnote.page_ai.block_edit_workflow',
|
||||
status: 'failed',
|
||||
toolCallId: runId,
|
||||
resultSummary: pageAiErrorMessage(payload, 'fast_path_failed_' + response.status)
|
||||
});
|
||||
renderPageAiConversation();
|
||||
pageAiSetRunStatus('failed', runId);
|
||||
renderPageAiControls();
|
||||
return true;
|
||||
}
|
||||
pageUiState.pageAiMessages.push({
|
||||
role: 'tool',
|
||||
toolName: 'mnote.page_ai.block_edit_workflow',
|
||||
status: 'completed',
|
||||
toolCallId: runId,
|
||||
resultSummary: 'operations=' + String(Array.isArray(payload.operations) ? payload.operations.length : 0) + ' · ' + String(Date.now() - started) + 'ms'
|
||||
});
|
||||
pageUiState.pageAiMessages.push({
|
||||
role: 'assistant',
|
||||
content: payload.message || '已通过页面块编辑快路径完成写入。'
|
||||
});
|
||||
pageAiSetRunStatus('completed', runId);
|
||||
try {
|
||||
window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
|
||||
detail: {
|
||||
toolName: 'mnote.doc.apply_block_ops',
|
||||
normalizedToolName: 'mnote.doc.apply.block.ops',
|
||||
documentId: currentDocumentId(),
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
runId: runId,
|
||||
traceId: traceId,
|
||||
toolCallId: runId
|
||||
}
|
||||
}));
|
||||
} catch (_) {}
|
||||
var currentSession = pageAiCurrentSession();
|
||||
if (currentSession) {
|
||||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||||
currentSession.updatedAt = Date.now();
|
||||
}
|
||||
renderPageAiConversation();
|
||||
renderPageAiControls();
|
||||
return true;
|
||||
}
|
||||
|
||||
async function sendPageAiMessage(text) {
|
||||
var allowQueue = ['running', 'tool_calling'].indexOf(pageUiState.pageAiRunStatus) >= 0;
|
||||
if (pageUiState.pageAiBusy && !allowQueue) return;
|
||||
@@ -5184,6 +5450,9 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
currentSession.updatedAt = Date.now();
|
||||
}
|
||||
renderPageAiConversation();
|
||||
if (!allowQueue && await pageAiTryBlockEditWorkflow(prompt, scopedContext)) {
|
||||
return;
|
||||
}
|
||||
var response = await fetch('/api/hermes/client/runs', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
@@ -5194,7 +5463,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
profile: pageAiCurrentProfile(),
|
||||
contextScope: pageUiState.pageAiContextScope,
|
||||
message: prompt,
|
||||
model: 'hermes-agent',
|
||||
model: pageAiMnoteToolModel(),
|
||||
pageContext: scopedContext.pageContext,
|
||||
selectedBlockId: scopedContext.selectedBlockId,
|
||||
selectedText: scopedContext.selectedText,
|
||||
@@ -5911,6 +6180,15 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return;
|
||||
}
|
||||
|
||||
var pageAiToolToggle = closestAction(e.target, '[data-page-ai-tool-toggle]');
|
||||
if (pageAiToolToggle) {
|
||||
e.preventDefault();
|
||||
var toolName = pageAiToolToggle.getAttribute('data-page-ai-tool-toggle') || '';
|
||||
var nextToolEnabled = pageAiToolToggle.getAttribute('aria-pressed') !== 'true';
|
||||
void pageAiToggleTool(toolName, nextToolEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
var pageAiSession = closestAction(e.target, '[data-page-ai-session]');
|
||||
if (pageAiSession) {
|
||||
e.preventDefault();
|
||||
@@ -6269,7 +6547,16 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
|
||||
window.__mnoteRecordPageHistorySnapshot = recordPageHistorySnapshot;
|
||||
window.__mnoteApplyPageOptionsToShell = applyPageOptionsToShell;
|
||||
setTimeout(initializePageUiSurfaces, 0);
|
||||
function scheduleInitializePageUiSurfaces() {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setTimeout(initializePageUiSurfaces, 0);
|
||||
}, { once: true });
|
||||
return;
|
||||
}
|
||||
setTimeout(initializePageUiSurfaces, 0);
|
||||
}
|
||||
scheduleInitializePageUiSurfaces();
|
||||
|
||||
function readPageDragNodeId(event) {
|
||||
var fromTransfer = event.dataTransfer ? event.dataTransfer.getData(PAGE_DRAG_MIME) || event.dataTransfer.getData('text/plain') : '';
|
||||
@@ -6441,7 +6728,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return;
|
||||
}
|
||||
if (action === 'move' && body.documentId) {
|
||||
if (applyMoveDocumentDelta({ documentId: body.documentId, parentId: body.parentId || null })) {
|
||||
if (applyMoveDocumentDelta({ documentId: body.documentId, parentId: body.parentId || null, sortOrder: body.sortOrder })) {
|
||||
document.documentElement.setAttribute('data-mnote-tree-local-command-applied', 'move');
|
||||
}
|
||||
return;
|
||||
@@ -6799,6 +7086,11 @@ mod tests {
|
||||
assert!(SIDEBAR_TREE_JS.contains("data-mnote-tree-local-command-applied', 'remove"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("data-mnote-tree-local-command-applied', 'rename"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("data-mnote-tree-local-command-applied', 'move"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function sortOrderFromDelta(data)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("data.sortOrder ?? data.sort_order"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function insertTreeNodeAtSortOrder"));
|
||||
assert!(SIDEBAR_TREE_JS
|
||||
.contains("applyMoveDocumentDelta({ documentId: body.documentId, parentId: body.parentId || null, sortOrder: body.sortOrder })"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("wolai:assets-changed"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("applyAssetsChangedToFileTree"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("installMindmapAssetFetchObserver"));
|
||||
@@ -6880,6 +7172,12 @@ mod tests {
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
r#".tree-row[data-shell-mode="filetree"][data-row-id="' + escapedDocRowId + '"] > .tree-link > .tree-link-title"#
|
||||
));
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
r#"[data-page-title-input="true"][data-document-id="' + escaped + '"]"#
|
||||
));
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
r#".wolai-breadcrumb-current [data-page-title-current]"#
|
||||
));
|
||||
assert!(!SIDEBAR_TREE_JS.contains(
|
||||
r#".tree-row[data-document-id="' + escaped + '"] > .tree-link > .tree-link-title"#
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user