export function createSidebarPageAiRenderRuntime(context) {
const {
contextRefRegistry,
cssEscape,
currentDocumentId,
documentRef,
escapeHtml,
localMarkdownRelativePathFromPageAiDocumentId,
pageAiAgentDescriptor,
pageAiAgentRecord,
pageAiAllSkillEntries,
pageAiBuildAllowedRoots,
pageAiChatOnlyProfileEntries,
pageAiChatOnlyProfileSpec,
pageAiCurrentAgentId,
pageAiCurrentProfile,
pageAiCurrentProfileRecord,
pageAiCurrentSession,
pageAiCurrentSkillSource,
pageAiCurrentSkillSourceLabel,
pageAiDefaultChatOnlyProfileSpec,
pageAiDescriptorFieldOptions,
pageAiDescriptorFieldValue,
pageAiEditorTargetCandidates,
pageAiEnsureContextRefState,
pageAiFilteredHistoryRows,
pageAiHideHermesBuiltinSkills,
pageAiHermesProfileEntries,
pageAiHermesSettingsUrl,
pageAiMnoteToolModel,
pageAiNormalizeAcpRuntimes,
pageAiNormalizeArray,
pageAiNormalizeSkillSource,
pageAiProfileDisplayLabel,
pageAiProfileValue,
pageAiProviderLabel,
pageAiReasonixMemoryEnabled,
pageAiRunProfile,
pageAiSessionAgentFilterOptions,
pageAiSessionAgentLabel,
pageAiSessionAgentFilterValue,
pageAiSessionStatusFilterOptions,
pageAiSessionStatusFilterValue,
pageAiSessionStatusLabel,
pageAiSessionPreviewText,
pageAiSessionStorageLabel,
pageAiSessionSelected,
pageAiSelectedSessionCount,
pageAiSkillEnabled,
pageAiSkillGroupCollapsed,
pageAiSkillListEntries,
pageAiSkillOriginLabel,
pageAiSkillSourceOptions,
pageAiSkillSourceParts,
pageAiToggleableSkillEntries,
pageAiUsageSummary,
pageAiBoardFirstEnabled,
pageAiBoardWorkerOptions,
pageAiBoardWorkflowOptions,
pageAiBoardSelectedWorkerId,
pageAiBoardSelectedWorkflowId,
pageAiBoardWorkerLabel,
pageAiBoardWorkflowLabel,
pageAiBoardModelOptions,
pageAiBoardSelectedModelOverride,
pageAiBoardModelLabel,
pageUiState,
renderPageAiMarkdown,
searchText,
currentPageAiEditorTarget,
currentPageAiSelectedText,
} = context;
const PAGE_AI_CONTEXT_REF_REGISTRY = Array.isArray(contextRefRegistry) ? contextRefRegistry : [];
function pageAiLocalBoardFirstEnabled() {
try {
return window.localStorage ? window.localStorage.getItem('mnote.page_ai.legacy_provider_mode') !== '1' : true;
} catch (_error) {
return true;
}
}
function isPageAiBoardFirstEnabled() {
return typeof pageAiBoardFirstEnabled === 'function' ? pageAiBoardFirstEnabled() : pageAiLocalBoardFirstEnabled();
}
function pageAiTargetLabel(target) {
var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {};
var kind = String(target && (target.resourceKind || target.editorKind) || workspacePath.resourceKind || '').trim();
var title = String(target && target.title || '').trim();
var relativePath = String(workspacePath.relativePath || target && target.path || '').trim();
if (title) return title;
if (relativePath) return relativePath.split('/').filter(Boolean).pop() || relativePath;
if (kind === 'mindmap') return '思维导图';
if (kind === 'only_office') return 'Office 资源';
return '当前页';
}
function pageAiTargetDetail(target) {
var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {};
var kind = String(target && (target.resourceKind || target.editorKind) || workspacePath.resourceKind || '').trim();
var pane = String(target && target.paneRole || '').trim();
var relativePath = String(workspacePath.relativePath || target && target.path || '').trim();
return [kind || 'page', pane, relativePath].filter(Boolean).join(' · ');
}
function pageAiContextRefLabel(kind) {
var record = PAGE_AI_CONTEXT_REF_REGISTRY.find(function(ref) { return ref.id === kind; });
return record ? record.label : kind;
}
function pageAiContextButtonSummary() {
var selected = pageAiEnsureContextRefState();
var labels = PAGE_AI_CONTEXT_REF_REGISTRY
.filter(function(ref) { return selected[ref.id] === true; })
.map(function(ref) { return ref.label; });
if (!labels.length) return '选择上下文';
var head = labels.slice(0, 2).join(' + ');
return labels.length > 2 ? head + ' +' + String(labels.length - 2) : head;
}
function pageAiContextRefDetail(kind) {
var documentId = currentDocumentId();
var relativePath = localMarkdownRelativePathFromPageAiDocumentId(documentId);
if (kind === 'current_page') return relativePath || documentId || '当前页面';
if (kind === 'selection') return currentPageAiSelectedText() ? '当前选区可用' : '当前没有选区';
if (kind === 'active_editor') {
var target = currentPageAiEditorTarget();
return String(target && (target.title || target.documentId) || relativePath || '当前打开资源');
}
if (kind === 'file') return relativePath || '当前文件';
if (kind === 'folder') {
var roots = pageAiBuildAllowedRoots();
if (!roots.length) return '需要授权后可用';
return roots.map(function(root) { return root.rootUri.replace(/^file:\/\//, ''); }).filter(Boolean)[0] || '已授权文件夹';
}
if (kind === 'changed_files') return '本次 run 后可用于追问';
return '';
}
function pageAiContextRefDisabled(kind) {
if (kind === 'selection') return !currentPageAiSelectedText();
return false;
}
function pageAiSuggestions() {
var title = searchText(documentRef.querySelector('[data-page-title-current="true"]')?.textContent) || '当前页面';
return [
'帮我总结《' + title + '》当前内容',
'把当前页面改写得更简洁一些',
'提炼当前页的关键待办和行动项',
'基于当前页内容生成一个三段式摘要'
];
}
function pageAiCurrentAgentSelectionLabel() {
var agentId = pageAiCurrentAgentId();
var descriptor = pageAiAgentDescriptor(agentId);
var descriptorLabel = String(descriptor && (descriptor.displayName || descriptor.label) || '').trim();
if (agentId === 'reasonix') return descriptorLabel || 'Reasonix';
var profile = pageAiCurrentProfileRecord();
var chatOnlySpec = pageAiChatOnlyProfileSpec(profile || pageAiCurrentProfile());
if (agentId === 'chat_only') {
return 'ChatOnly / ' + (chatOnlySpec || pageAiDefaultChatOnlyProfileSpec()).label;
}
if (chatOnlySpec) {
return 'ChatOnly / ' + chatOnlySpec.label;
}
if (agentId === 'hermes') return (descriptorLabel || 'Hermes') + ' / ' + pageAiProfileDisplayLabel(profile, pageAiCurrentProfile());
return descriptorLabel || pageAiAgentRecord(agentId).label;
}
function pageAiRenderAgentProfileOption(agentId, profile, label, detail) {
var profileId = pageAiProfileValue(profile);
var active = pageAiCurrentAgentId() === agentId && profileId === pageAiCurrentProfile();
return '' +
'';
}
function pageAiAgentDescriptorDetail(descriptor) {
if (!descriptor) return '';
var capabilities = pageAiNormalizeArray(descriptor.capabilities);
var toolCount = Number(descriptor.toolCount || pageAiNormalizeArray(descriptor.tools).length || 0);
var parts = [];
parts.push(descriptor.canWriteFiles === false ? '只读/聊天' : '可写授权文件');
if (capabilities.length) parts.push('capability ' + capabilities.length);
if (toolCount) parts.push('tool ' + toolCount);
return parts.join(' · ');
}
function pageAiRenderDescriptorField(field) {
var key = String(field && field.key || '').trim();
if (!key || field.secret === true) return '';
var label = String(field.label || key).trim();
var kind = String(field.kind || 'string').trim();
var value = pageAiDescriptorFieldValue(field);
var options = pageAiDescriptorFieldOptions(field);
var control = '';
if (kind === 'select' || options.length) {
control = '';
} else if (kind === 'boolean' || kind === 'toggle') {
control = '';
} else {
control = '';
}
return '' +
'';
}
function pageAiReasonixQuickControlLabel(key, fallback) {
if (key === 'ai.agent.reasonix.model_id') return '模型';
if (key === 'ai.agent.reasonix.approval_mode') return '审批';
if (key === 'ai.agent.reasonix.plan_mode') return 'Plan';
return fallback || key;
}
function pageAiRenderReasonixQuickControl(field) {
var key = String(field && field.key || '').trim();
if (!key || field.secret === true) return '';
var value = pageAiDescriptorFieldValue(field);
var options = pageAiDescriptorFieldOptions(field);
if (!options.length) return '';
return '' +
'';
}
function pageAiReasonixQuickControlsHtml() {
if (pageAiCurrentAgentId() !== 'reasonix') return '';
var descriptor = pageAiAgentDescriptor('reasonix') || pageAiAgentRecord('reasonix');
var wanted = {
'ai.agent.reasonix.model_id': true,
'ai.agent.reasonix.approval_mode': true,
'ai.agent.reasonix.plan_mode': true
};
var fields = pageAiNormalizeArray(descriptor && descriptor.fields)
.filter(function(field) { return wanted[String(field && field.key || '').trim()] === true; })
.map(pageAiRenderReasonixQuickControl)
.filter(Boolean)
.join('');
if (!fields) return '';
return '' +
'
' +
PAGE_AI_CONTEXT_REF_REGISTRY.map(function(ref) {
var checked = selectedRefs[ref.id] === true;
var disabled = pageAiContextRefDisabled(ref.id);
return '' +
'';
}).join('') +
'
' +
'';
}
var targetButton = drawer.querySelector('[data-page-ai-target-button]');
var targetPopover = drawer.querySelector('[data-page-ai-target-popover]');
var targetChip = drawer.querySelector('[data-page-ai-target-chip]');
var targetPopoverId = 'mnote-page-ai-target-popover';
var targetCandidates = pageAiEditorTargetCandidates();
var activeTarget = currentPageAiEditorTarget();
var activeTargetLabel = pageAiTargetLabel(activeTarget);
if (targetButton instanceof HTMLElement) {
targetButton.textContent = '◎';
targetButton.setAttribute('aria-expanded', pageUiState.pageAiTargetPopoverOpen ? 'true' : 'false');
targetButton.setAttribute('aria-controls', targetPopoverId);
targetButton.setAttribute('aria-label', '目标:' + activeTargetLabel);
targetButton.setAttribute('title', '目标:' + activeTargetLabel);
}
if (targetChip instanceof HTMLElement) {
targetChip.textContent = activeTargetLabel;
targetChip.setAttribute('title', '当前目标:' + activeTargetLabel + ' · ' + pageAiTargetDetail(activeTarget));
}
if (targetPopover instanceof HTMLElement) {
targetPopover.id = targetPopoverId;
targetPopover.hidden = !pageUiState.pageAiTargetPopoverOpen;
targetPopover.innerHTML = '' +
'' +
targetCandidates.map(function(target) {
var targetId = String(target && target.targetId || '').trim();
var active = targetId && targetId === String(activeTarget && activeTarget.targetId || '').trim();
return '' +
'';
}).join('') +
'
';
}
var allowedRootsNode = drawer.querySelector('[data-page-ai-allowed-roots]');
if (allowedRootsNode instanceof HTMLElement) {
var allowedRoots = pageAiBuildAllowedRoots();
if (!allowedRoots.length) {
var errorText = String(pageUiState.pageAiAllowedRootsError || '').trim();
allowedRootsNode.innerHTML = ']*>|<\/div>$/g, '');
reasonixControls.hidden = false;
} else {
reasonixControls.innerHTML = '';
reasonixControls.hidden = true;
}
}
var scopeSelect = drawer.querySelector('[data-page-ai-context-scope]');
if (scopeSelect instanceof HTMLSelectElement) scopeSelect.value = pageUiState.pageAiContextScope;
var scopeLabel = drawer.querySelector('[data-page-ai-context-scope-label]');
if (scopeLabel instanceof HTMLElement) scopeLabel.textContent = pageAiContextScopeLabel(pageUiState.pageAiContextScope);
drawer.querySelectorAll('[data-page-ai-tab]').forEach(function(button) {
var target = button.getAttribute('data-page-ai-tab') || 'chat';
var active = target === pageUiState.pageAiPage;
button.classList.toggle('is-active', active);
button.setAttribute('aria-selected', active ? 'true' : 'false');
});
drawer.querySelectorAll('[data-page-ai-panel]').forEach(function(panel) {
if (panel instanceof HTMLElement) {
panel.hidden = panel.getAttribute('data-page-ai-panel') !== pageUiState.pageAiPage;
}
});
if (boardFirst) {
var subtitle = drawer.querySelector('.wolai-page-ai-subtitle');
if (subtitle instanceof HTMLElement) {
var workerLabel = typeof pageAiBoardWorkerLabel === 'function' ? pageAiBoardWorkerLabel() : 'ZCode 默认 developer';
subtitle.innerHTML = '
Agent Board' + escapeHtml(workerLabel) + 'local-first 文件编辑';
}
drawer.querySelectorAll('[data-page-ai-tab="agent"], [data-page-ai-tab="reasonix-settings"], [data-page-ai-tab="hermes-settings"]').forEach(function(node) {
if (node instanceof HTMLElement) node.hidden = true;
});
drawer.querySelectorAll('[data-page-ai-panel="agent"], [data-page-ai-panel="reasonix-settings"], [data-page-ai-panel="hermes-settings"]').forEach(function(node) {
if (node instanceof HTMLElement) node.setAttribute('data-page-ai-legacy-provider-panel', 'true');
});
}
var profileError = drawer.querySelector('[data-page-ai-profile-error]');
if (profileError instanceof HTMLElement) {
profileError.textContent = pageUiState.pageAiProfileError || '';
profileError.hidden = !pageUiState.pageAiProfileError;
}
var memoryError = drawer.querySelector('[data-page-ai-memory-error]');
if (memoryError instanceof HTMLElement) {
var memoryErrorText = pageUiState.pageAiProfileMemoryError || '';
if (memoryErrorText && memoryErrorText === pageUiState.pageAiProfileError) memoryErrorText = '';
memoryError.textContent = memoryErrorText;
memoryError.hidden = !memoryErrorText;
}
var hermesMemoryPanel = drawer.querySelector('[data-page-ai-hermes-panel]');
if (hermesMemoryPanel instanceof HTMLElement) {
hermesMemoryPanel.innerHTML = pageAiRenderAgentDescriptorCard('hermes', 'Hermes') + ['soul', 'user', 'memory'].map(function(section) {
var label = pageAiMemoryFileLabel(section);
var value = pageUiState.pageAiProfileMemoryDrafts[section] || '';
return '' +
'
' +
'' +
'
' +
'
' + escapeHtml(label) + '
' +
'
保存到 Hermes profile: ' + escapeHtml(activeProfile) + '
' +
'
' +
'
' +
'
' +
'' +
'';
}).join('');
}
var skillError = drawer.querySelector('[data-page-ai-skill-error]');
if (skillError instanceof HTMLElement) {
skillError.textContent = pageUiState.pageAiSkillError || '';
skillError.hidden = !pageUiState.pageAiSkillError;
}
var sessionError = drawer.querySelector('[data-page-ai-session-error]');
if (sessionError instanceof HTMLElement) {
sessionError.textContent = pageUiState.pageAiSessionError || '';
sessionError.hidden = !pageUiState.pageAiSessionError;
}
var selectedSessionCount = pageAiSelectedSessionCount();
var selectedSessionToolbar = drawer.querySelector('[data-page-ai-history-selection-toolbar]');
if (selectedSessionToolbar instanceof HTMLElement) {
selectedSessionToolbar.hidden = selectedSessionCount <= 0;
}
var selectedSessionCountNode = drawer.querySelector('[data-page-ai-history-selection-count]');
if (selectedSessionCountNode instanceof HTMLElement) {
selectedSessionCountNode.textContent = '已选 ' + String(selectedSessionCount) + ' 个';
}
var sessionSearch = drawer.querySelector('[data-page-ai-session-search]');
if (sessionSearch instanceof HTMLInputElement && documentRef.activeElement !== sessionSearch) {
sessionSearch.value = pageUiState.pageAiSessionSearchQuery || '';
}
var sessionAgentFilter = drawer.querySelector('[data-page-ai-session-agent-filter]');
if (sessionAgentFilter instanceof HTMLSelectElement) {
var historyRowsForFilter = pageUiState.pageAiSessionSearchResults.length
? pageUiState.pageAiSessionSearchResults
: pageUiState.pageAiSessions;
var options = pageAiSessionAgentFilterOptions(historyRowsForFilter);
var activeFilter = String(pageUiState.pageAiSessionAgentFilter || 'all').trim() || 'all';
if (!options.some(function(option) { return option.value === activeFilter; })) activeFilter = 'all';
sessionAgentFilter.innerHTML = options.map(function(option) {
return '
';
}).join('');
sessionAgentFilter.value = activeFilter;
pageUiState.pageAiSessionAgentFilter = activeFilter;
}
var sessionStatusFilter = drawer.querySelector('[data-page-ai-session-status-filter]');
if (sessionStatusFilter instanceof HTMLSelectElement) {
var historyRowsForStatusFilter = pageUiState.pageAiSessionSearchResults.length
? pageUiState.pageAiSessionSearchResults
: pageUiState.pageAiSessions;
var statusOptions = pageAiSessionStatusFilterOptions(historyRowsForStatusFilter);
var activeStatusFilter = String(pageUiState.pageAiSessionStatusFilter || 'all').trim() || 'all';
if (!statusOptions.some(function(option) { return option.value === activeStatusFilter; })) activeStatusFilter = 'all';
sessionStatusFilter.innerHTML = statusOptions.map(function(option) {
return '
';
}).join('');
sessionStatusFilter.value = activeStatusFilter;
pageUiState.pageAiSessionStatusFilter = activeStatusFilter;
}
var skillSearch = drawer.querySelector('[data-page-ai-skill-search]');
if (skillSearch instanceof HTMLInputElement && documentRef.activeElement !== skillSearch) {
skillSearch.value = pageUiState.pageAiSkillQuery;
}
var skillSourceSelect = drawer.querySelector('[data-page-ai-skill-source-select]');
if (skillSourceSelect instanceof HTMLSelectElement) {
var activeSkillSource = pageAiCurrentSkillSource();
var sourceOptions = pageAiSkillSourceOptions();
skillSourceSelect.innerHTML = sourceOptions.map(function(option) {
return '
';
}).join('');
skillSourceSelect.value = activeSkillSource;
var sourceControl = skillSourceSelect.closest('[data-page-ai-skill-source-control]');
if (sourceControl instanceof HTMLElement) sourceControl.hidden = sourceOptions.length <= 1;
}
var hermesBuiltinToggle = drawer.querySelector('[data-page-ai-hide-hermes-builtin]');
if (hermesBuiltinToggle instanceof HTMLInputElement) {
var skillSourceParts = pageAiSkillSourceParts(pageAiCurrentSkillSource());
var showHermesFilter = skillSourceParts.group === 'hermes';
var hermesFilterLabel = hermesBuiltinToggle.closest('.wolai-page-ai-skill-filter-toggle');
if (hermesFilterLabel instanceof HTMLElement) hermesFilterLabel.hidden = !showHermesFilter;
hermesBuiltinToggle.checked = showHermesFilter && pageAiHideHermesBuiltinSkills(skillSourceParts.profile);
}
var skillList = drawer.querySelector('[data-page-ai-skill-list]');
if (skillList instanceof HTMLElement) {
var skills = pageAiFilteredSkillEntries();
if (!skills.length) {
skillList.innerHTML = '
没有匹配的能力。
';
} else {
var sourceParts = pageAiSkillSourceParts(pageAiCurrentSkillSource());
var group = sourceParts.group;
var headingExtra = group === 'hermes' ? ' · ' + sourceParts.profile : '';
var collapsed = pageAiSkillGroupCollapsed(pageAiCurrentSkillSource());
skillList.innerHTML = '' +
'
' +
'' +
(collapsed ? '' : skills.map(function(skill) {
var toolCount = Number(skill.toolCount || pageAiNormalizeArray(skill.tools).length || 0);
var disabledToolCount = Number(skill.disabledToolCount || 0);
var capabilityMeta = [];
if (toolCount > 0) capabilityMeta.push(String(toolCount) + ' 工具');
if (disabledToolCount > 0) capabilityMeta.push(String(disabledToolCount) + ' 已关闭');
if (pageAiNormalizeArray(skill.requiresContextRefs).length > 0) capabilityMeta.push('需要上下文');
var categoryText = group === 'mnote' ? String(skill.categoryTitle || skill.category || '').trim() : '';
var sourceMetaParts = [];
if (group === 'mnote') {
sourceMetaParts.push(categoryText || 'MNote');
} else {
sourceMetaParts.push(pageAiSkillOriginLabel(skill));
sourceMetaParts.push('只读查看');
}
if (capabilityMeta.length) sourceMetaParts = sourceMetaParts.concat(capabilityMeta);
if (skill.readOnly && group === 'mnote') sourceMetaParts.push('只读');
if (skill.modified) sourceMetaParts.push('modified');
var sourceText = sourceMetaParts.filter(Boolean).join(' · ');
var description = String(skill.description || '').trim();
var hasDescription = description && description !== '---' && description !== '无描述';
var displayName = String(skill.title || skill.name || skill.id || '').trim();
var disabled = skill.toggleable === false || skill.readOnly === true;
var switchControl = group === 'mnote'
? ''
: '查看';
return '' +
'' +
'
' +
'
' +
'
' + escapeHtml(displayName) + '
' +
'
' + escapeHtml(sourceText) + '
' +
'
' +
(hasDescription ? '
' + escapeHtml(description) + '
' : '') +
'
' +
switchControl +
'
';
}).join('')) +
'';
}
}
var toolsList = drawer.querySelector('[data-page-ai-tool-list]');
if (toolsList instanceof HTMLElement) {
var tools = pageAiNormalizeArray(pageUiState.pageAiTools);
if (!tools.length) {
toolsList.innerHTML = '
尚未读取到 mnote tool manifest。
';
} else {
toolsList.innerHTML = tools.map(function(tool) {
return '' +
'
' +
'
' +
'
' + escapeHtml(tool.name) + '
' +
'
' + escapeHtml(tool.scope || tool.kind || 'mnote') + ' · ' + escapeHtml(tool.status || 'available') + '
' +
(tool.unavailableReason ? '
' + escapeHtml(tool.unavailableReason) + '
' : '') +
'
' +
'
' +
'
';
}).join('');
}
}
var gatewayError = drawer.querySelector('[data-page-ai-gateway-error]');
if (gatewayError instanceof HTMLElement) {
gatewayError.textContent = pageUiState.pageAiGatewayHealthError || '';
gatewayError.hidden = !pageUiState.pageAiGatewayHealthError;
}
var gatewayStatusNode = drawer.querySelector('[data-page-ai-gateway-status]');
var gatewayDetail = drawer.querySelector('[data-page-ai-gateway-detail]');
var gatewayHealth = pageUiState.pageAiGatewayHealth;
if (gatewayStatusNode instanceof HTMLElement) {
if (!gatewayHealth) {
gatewayStatusNode.textContent = '未检查';
} else {
var gateway = gatewayHealth.gateway || {};
var profile = gatewayHealth.profile || {};
gatewayStatusNode.textContent = gatewayHealth.ok ? '可用' : '需要设置';
if (gateway.status) gatewayStatusNode.textContent += ' · ' + gateway.status;
if (profile.name) gatewayStatusNode.textContent += ' · ' + profile.name;
}
}
if (gatewayDetail instanceof HTMLElement) {
if (!gatewayHealth) {
gatewayDetail.innerHTML = '
打开 Hermes 设置后会检查 gateway 与当前 profile。
';
} else {
var gatewayInfo = gatewayHealth.gateway || {};
var profileInfo = gatewayHealth.profile || {};
var suggestionText = pageAiNormalizeArray(gatewayHealth.suggestions).join(';');
gatewayDetail.innerHTML = '' +
'
' +
'
' + escapeHtml(profileInfo.name || activeProfile) + '
' +
'
model.default: ' + escapeHtml(profileInfo.modelDefault || '未设置') + '
' +
'
provider: ' + escapeHtml(profileInfo.provider || '未设置') + ' · API key: ' + escapeHtml(profileInfo.apiKeyConfigured ? '已配置' : '未检测到') + '
' +
'
' +
'
' +
'
' + escapeHtml(gatewayInfo.upstream || '未配置 upstream') + '
' +
'
gateway: ' + escapeHtml(gatewayInfo.status || 'unknown') + (gatewayInfo.httpStatus ? ' · HTTP ' + escapeHtml(gatewayInfo.httpStatus) : '') + '
' +
(suggestionText ? '
' + escapeHtml(suggestionText) + '
' : '') +
'
';
}
}
var toolError = drawer.querySelector('[data-page-ai-tool-error]');
if (toolError instanceof HTMLElement) {
toolError.textContent = pageUiState.pageAiToolsError || '';
toolError.hidden = !pageUiState.pageAiToolsError;
}
var lastTool = drawer.querySelector('[data-page-ai-last-tool]');
if (lastTool instanceof HTMLElement) {
var call = pageUiState.pageAiLastToolCall;
lastTool.textContent = call
? [call.event, call.name, call.runId, call.traceId || call.auditId].filter(Boolean).join(' · ')
: '暂无 tool call';
}
var reasonixPanel = drawer.querySelector('[data-page-ai-reasonix-panel]');
if (reasonixPanel instanceof HTMLElement) {
reasonixPanel.innerHTML = '' +
pageAiRenderAgentDescriptorCard('reasonix', 'Reasonix') +
'
' +
'' +
'
Reasonix 专属设置
ACP runtime / memory
' +
'
' +
'
' +
'' + escapeHtml(pageAiReasonixMemoryEnabled() ? '当前用户已开启 Reasonix global/project memory。' : '默认关闭 Reasonix memory,避免跨用户偏好混入。') + '
' +
'';
}
var chatOnlyPanel = drawer.querySelector('[data-page-ai-chat-only-panel]');
if (chatOnlyPanel instanceof HTMLElement) {
chatOnlyPanel.innerHTML = pageAiRenderAgentDescriptorCard('chat_only', 'API Chat / Chat-only profile') +
'
API Chat / Chat-only profile
默认不申请文件写权限,归属 MNote 设置。
';
}
}
function humanizePageAiResponse(rawText, promptText) {
var providerLabel = pageAiProviderLabel(pageUiState.pageAiProvider);
var text = String(rawText || '').trim();
if (!text) {
return providerLabel + ' 已收到你的问题“' + searchText(promptText) + '”,但这次没有返回可读内容。';
}
if (text.startsWith('{')) {
try {
var payload = JSON.parse(text);
var operation = payload && payload.operation ? payload.operation : {};
var normalized = operation && operation.normalized_input ? operation.normalized_input : {};
var args = normalized && normalized.args ? normalized.args : {};
var documentId = searchText(args.documentId || args.pageId || currentDocumentId());
var toolName = searchText(operation.tool_name || normalized.toolName || 'doc_get');
return providerLabel + ' 已收到你的问题“' + searchText(promptText) + '”。当前已通过 Hermes 调用 mnote plugin 工具,目标页面是 ' + (documentId || '当前页面') + ',本次选择的工具是 ' + toolName + '。如果你继续追问,我会沿当前页面上下文继续回复。';
} catch (_) {
return providerLabel + ' 已返回结果,但当前结果是结构化文本,已为你保留原始内容。';
}
}
return text;
}
function ensurePageAiDrawer() {
var existing = documentRef.querySelector('[data-testid="wolai-page-ai-drawer"]');
if (existing instanceof HTMLElement) return existing;
var drawer = documentRef.createElement('aside');
drawer.className = 'wolai-page-ai-drawer';
drawer.setAttribute('data-testid', 'wolai-page-ai-drawer');
drawer.setAttribute('data-mnote-surface', 'page-ai');
drawer.hidden = true;
drawer.innerHTML = '' +
'
' +
'
' +
'' +
'
' +
'
' +
'
' +
'' +
'' +
'等待 AI session' +
'' +
'当前页' +
'空闲' +
'
' +
'' +
'' +
'' +
'
' +
'' +
'
' +
'
' +
pageAiSettingsTabsHtml('agent') +
'
' +
'
' +
'' +
'' +
'' +
'' +
'' +
'' +
'
' +
'MNote 工具' +
'暂无 tool call' +
'
' +
'
' +
'
' +
'
' +
'' +
'
' +
'' +
'
' +
'
' +
pageAiSettingsTabsHtml('hermes-settings') +
'
' +
'
' +
'' +
'' +
'
' +
'' +
'' +
'' +
'
' +
'Hermes gateway' +
'未检查' +
'
' +
'
' +
'
' +
'' +
'' +
'
' +
'' +
'
' +
'
' +
pageAiSettingsTabsHtml('reasonix-settings') +
'
' +
'
' +
'' +
'' +
'
' +
'' +
'' +
'
' +
'' +
'
' +
'
' +
'' +
'' +
'' +
'
' +
'
' +
'' +
'' +
'' +
'' +
'
' +
'' +
'' +
'' +
'
' +
'' +
'
' +
'
' +
pageAiStatusTabsHtml() +
'
' +
'
' +
'' +
'' +
'' +
'
' +
'' +
'
' +
'Current runtime' +
'未加载' +
'
' +
'
' +
'
' +
'' +
'
' +
'Queue' +
'由 mnote-web 管理' +
'
' +
'
' +
'
' +
'' +
'
' +
'Jobs' +
'' +
'
' +
'
' +
'
' +
'' +
'
' +
'Logs tail' +
'ACP stderr / tool failed / MCP failed' +
'
' +
'
' +
'
' +
'' +
'
' +
'Usage' +
'last run / session / cache' +
'
' +
'
' +
'
' +
'' +
'
' +
'' +
'
' +
'
' +
'已选 0 个' +
'' +
'' +
'
' +
'
' +
'' +
'' +
'' +
'' +
'' +
'' +
'
' +
'' +
'
';
documentRef.body.appendChild(drawer);
return drawer;
}
function renderPageAiSuggestions() {
var drawer = ensurePageAiDrawer();
var list = drawer.querySelector('[data-page-ai-panel="chat"] [data-page-ai-suggestion-list]');
if (!(list instanceof HTMLElement)) return;
var container = list.closest('.wolai-page-ai-suggestions');
if (container instanceof HTMLElement) {
container.hidden = pageUiState.pageAiPage !== 'chat' || pageUiState.pageAiMessages.length > 0;
}
var suggestions = pageAiSuggestions();
var offset = pageUiState.pageAiSuggestionIndex % suggestions.length;
var ordered = suggestions.slice(offset).concat(suggestions.slice(0, offset)).slice(0, 3);
list.innerHTML = ordered.map(function(text) {
return '
';
}).join('');
}
function pageAiConversationShouldStickToBottom(conversation) {
var distance = conversation.scrollHeight - conversation.scrollTop - conversation.clientHeight;
return distance <= 48;
}
function pageAiCaptureOpenToolDetails(conversation) {
var openToolDetails = {};
conversation.querySelectorAll('[data-page-ai-collapse-card], [data-page-ai-tool-card]').forEach(function(card) {
if (!(card instanceof HTMLElement)) return;
var key = String(card.getAttribute('data-page-ai-collapse-id') || card.getAttribute('data-page-ai-tool-group-id') || card.getAttribute('data-page-ai-tool-call-id') || '').trim();
var details = card.querySelector('[data-page-ai-collapse-details], .wolai-page-ai-tool-details');
if (key && details instanceof HTMLDetailsElement && details.open) {
openToolDetails[key] = true;
}
});
return openToolDetails;
}
function pageAiRestoreOpenToolDetails(conversation, openToolDetails) {
if (!openToolDetails || typeof openToolDetails !== 'object') return;
conversation.querySelectorAll('[data-page-ai-collapse-card], [data-page-ai-tool-card]').forEach(function(card) {
if (!(card instanceof HTMLElement)) return;
var key = String(card.getAttribute('data-page-ai-collapse-id') || card.getAttribute('data-page-ai-tool-group-id') || card.getAttribute('data-page-ai-tool-call-id') || '').trim();
if (!key || openToolDetails[key] !== true) return;
var details = card.querySelector('[data-page-ai-collapse-details], .wolai-page-ai-tool-details');
if (details instanceof HTMLDetailsElement) details.open = true;
});
}
function pageAiRenderToolDetailRows(item) {
var locationRows = Array.isArray(item.locations) && item.locations.length
? '
位置:' + item.locations.map(function(loc) {
return '' +
'' + escapeHtml(loc) + '' +
'' +
'';
}).join('') + '
'
: '';
return [
locationRows,
item.argsSummary ? '
参数 ' + escapeHtml(item.argsSummary) + '
' : '',
item.resultSummary ? '
结果 ' + escapeHtml(item.resultSummary) + '
' : '',
item.changedFiles && item.changedFiles.length ? '
' + escapeHtml(pageAiFormatChangedFiles(item.changedFiles)) + '
' : '',
(item.traceId || item.auditId) ? '
' + escapeHtml([item.traceId, item.auditId].filter(Boolean).join(' · ')) + '
' : ''
].filter(Boolean).join('');
}
function pageAiToolStatusLabel(status) {
if (status === 'completed') return '完成';
if (status === 'failed') return '失败';
return '运行中';
}
function pageAiToolGroupStatus(items) {
if (items.some(function(item) { return item.status === 'failed'; })) return 'failed';
if (items.some(function(item) { return item.status !== 'completed'; })) return 'running';
return 'completed';
}
function pageAiRenderToolGroup(items, groupIndex) {
var tools = pageAiNormalizeArray(items);
if (!tools.length) return '';
var status = pageAiToolGroupStatus(tools);
var completedCount = tools.filter(function(item) { return item.status === 'completed'; }).length;
var failedCount = tools.filter(function(item) { return item.status === 'failed'; }).length;
var runningCount = tools.length - completedCount - failedCount;
var countParts = [String(tools.length) + ' 个'];
if (completedCount) countParts.push('完成 ' + String(completedCount));
if (failedCount) countParts.push('失败 ' + String(failedCount));
if (runningCount) countParts.push('运行中 ' + String(runningCount));
var firstToolId = String(tools[0] && tools[0].toolCallId || '').trim();
var groupId = 'tool-group-' + String(groupIndex) + '-' + (firstToolId || String(tools.length));
var rows = tools.map(function(item) {
var statusLabel = pageAiToolStatusLabel(item.status);
var meta = [statusLabel, item.toolKind, item.toolCallId].filter(Boolean).join(' · ');
return '' +
'
' +
'
' +
'' + escapeHtml(item.toolName || item.content || 'tool') + '' +
'' + escapeHtml(meta) + '' +
'
' +
(pageAiRenderToolDetailRows(item) || '
暂无参数或结果详情。
') +
'
';
}).join('');
return '' +
'
' +
'
工具
' +
'
' +
'
' +
'' +
'调用工具' +
'' + escapeHtml(countParts.join(' · ')) + '' +
'
' +
'' + rows + '
' +
' ' +
'
' +
'
';
}
function pageAiRenderThoughtGroup(items, groupIndex) {
var thoughts = pageAiNormalizeArray(items).filter(function(item) {
return item && item.kind === 'thought' && String(item.content || '').trim();
});
if (!thoughts.length) return '';
var firstText = String(thoughts[0] && thoughts[0].content || '').slice(0, 24).replace(/\s+/g, ' ').trim();
var groupId = 'thought-group-' + String(groupIndex) + '-' + String(thoughts.length) + '-' + (firstText || 'delta');
var content = thoughts.map(function(item) {
return String(item.content || '').trim();
}).filter(Boolean).join('\n\n');
var countLabel = thoughts.length > 1 ? String(thoughts.length) + ' 段' : '1 段';
return '' +
'
' +
'
AI
' +
'
' +
'
' +
'' +
'思考过程' +
'' + escapeHtml(countLabel) + '' +
'
' +
'' + escapeHtml(content) + '
' +
' ' +
'
' +
'
';
}
function pageAiLooksLikeReasonixPlanMessage(item) {
if (!item || item.role !== 'assistant') return false;
var text = String(item.content || '').trim();
if (!text || text.length < 40) return false;
if (/ACP connection closed|AI 当前请求失败|run failed/i.test(text)) return false;
var hasPlanWord = /计划|步骤|执行|修改|写入|新建|验证|plan/i.test(text);
var awaitsApproval = /请批准|待批准|批准后|确认后|审批|批准,|开始执行|approve/i.test(text);
var hasStepShape = /(^|\n)\s*(?:\d+[\.\、)]|[-*]\s+|\|\s*\d+\s*\|)/.test(text);
return hasPlanWord && awaitsApproval && hasStepShape;
}
function pageAiExtractReasonixPlanSteps(text) {
var seen = {};
var steps = [];
String(text || '').replace(/\r\n/g, '\n').split('\n').forEach(function(line) {
var trimmed = line.trim();
if (!trimmed) return;
var step = '';
var tableCells = trimmed.indexOf('|') >= 0
? trimmed.split('|').map(function(cell) { return cell.trim(); }).filter(Boolean)
: [];
if (tableCells.length >= 2 && /^\d+$/.test(tableCells[0])) {
step = tableCells.slice(1).join(' · ');
} else {
var numbered = trimmed.match(/^\s*\d+[\.\、)]\s+(.+)$/);
var bullet = trimmed.match(/^\s*[-*]\s+(?:\[[ xX~>|-]\]\s*)?(.+)$/);
step = numbered ? numbered[1] : (bullet ? bullet[1] : '');
}
step = String(step || '').replace(/\s+/g, ' ').trim();
if (!step || step.length < 3 || /^[-:|]+$/.test(step)) return;
if (seen[step]) return;
seen[step] = true;
var lower = step.toLowerCase();
var status = /✅|已完成|done|completed/.test(step) ? 'completed'
: (/当前|进行中|执行中|待批准|in progress|running/.test(lower) ? 'inProgress' : 'pending');
steps.push({ text: step, status: status });
});
return steps.slice(0, 8);
}
function pageAiPlanStepIcon(status) {
if (status === 'completed') return '✓';
if (status === 'inProgress') return '…';
return String.fromCharCode(8226);
}
function pageAiRenderReasonixPlanCard(item) {
var text = String(item.content || '').trim();
var steps = pageAiExtractReasonixPlanSteps(text);
var stepHtml = steps.length ? (
'
' + steps.map(function(step) {
return '' +
'- ' +
'' + escapeHtml(pageAiPlanStepIcon(step.status)) + '' +
'' + escapeHtml(step.text || '') + '' +
'
';
}).join('') + '
'
) : '';
return '' +
'
' +
'' +
'
' + renderPageAiMarkdown(text) + '
' +
stepHtml +
'
' +
'' +
'' +
'' +
'
' +
'
';
}
function pageAiRenderPermissionDetails(item) {
var details = item && item.permissionDetails && typeof item.permissionDetails === 'object'
? item.permissionDetails
: {};
var rows = Array.isArray(details.rows) ? details.rows : [];
if (!rows.length) {
rows = [
{ label: '请求', value: item.argsSummary || item.content || '' }
];
}
return '
' + rows.slice(0, 6).map(function(row) {
return '
' + escapeHtml(row.label || '') + '' + escapeHtml(row.value || '') + '
';
}).join('') + '
';
}
function pageAiRenderNonToolMessage(item) {
var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI');
if (item.kind === 'status') {
return '' +
'
' +
'
AI
' +
'
' + escapeHtml(item.content || '') + '
' +
'
';
}
if (item.kind === 'thought') {
return '' +
'
' +
'
AI
' +
'
' +
'
' +
'思考过程1 段
' +
'' + escapeHtml(item.content || '') + '
' +
' ' +
'
' +
'
';
}
if (item.kind === 'permission') {
var permissionOptions = Array.isArray(item.options) ? item.options : [];
var permissionActions = '';
if (!item.resolved) {
if (permissionOptions.length) {
permissionActions = '
' + permissionOptions.map(function(option) {
var optionText = String(option.optionId || '') + ' ' + String(option.kind || '') + ' ' + String(option.name || '');
var denyLike = /reject|deny|cancel|stop|no/.test(optionText.toLowerCase());
return '';
}).join('') + '
';
} else {
permissionActions = '' +
'
' +
'' +
'' +
'
';
}
}
return '' +
'
' +
'' +
'
' +
pageAiRenderPermissionDetails(item) +
permissionActions +
'
' +
'
';
}
if (item.kind === 'plan') {
var planEntries = Array.isArray(item.entries) ? item.entries : [];
var listHtml = planEntries.map(function(entry) {
return '
' + escapeHtml(String(entry || '')) + '';
}).join('');
return '' +
'
' +
'
' +
'执行计划 · ' + planEntries.length + ' 步
' +
'' +
'
' + listHtml + '
' +
'
' +
' ' +
'
';
}
if (pageAiLooksLikeReasonixPlanMessage(item)) return pageAiRenderReasonixPlanCard(item);
var streamingAttr = item.streaming ? ' data-page-ai-streaming="true"' : '';
var detailButton = item.boardRunId
? '
' + (item.retryPrompt ? '' : '') + '
'
: '';
return '' +
'
' +
'
' + escapeHtml(roleLabel) + '
' +
'
' + (item.role === 'assistant' ? renderPageAiMarkdown(item.content || '') : escapeHtml(item.content || '')) + '
' +
detailButton +
'
';
}
function pageAiConversationRenderHtml(messages) {
var turns = [];
var current = null;
function ensureTurn() {
if (!current) current = { user: null, tools: [], status: [], assistants: [], others: [] };
return current;
}
function flushTurn() {
if (!current) return;
turns.push(current);
current = null;
}
pageAiNormalizeArray(messages).forEach(function(item) {
if (item && item.role === 'user') {
flushTurn();
current = { user: item, tools: [], status: [], assistants: [], others: [] };
return;
}
var turn = ensureTurn();
if (item && item.role === 'tool' && item.kind !== 'permission') {
turn.tools.push(item);
} else if (item && (item.kind === 'thought' || item.kind === 'plan' || item.kind === 'permission')) {
turn.status.push(item);
} else if (item && item.role === 'assistant') {
turn.assistants.push(item);
} else {
turn.others.push(item);
}
});
flushTurn();
var toolGroupIndex = 0;
var thoughtGroupIndex = 0;
return turns.map(function(turn) {
var html = [];
if (turn.user) html.push(pageAiRenderNonToolMessage(turn.user));
if (turn.tools.length) html.push(pageAiRenderToolGroup(turn.tools, toolGroupIndex++));
var thoughts = turn.status.filter(function(item) { return item && item.kind === 'thought'; });
var otherStatus = turn.status.filter(function(item) { return !(item && item.kind === 'thought'); });
if (thoughts.length) html.push(pageAiRenderThoughtGroup(thoughts, thoughtGroupIndex++));
html = html.concat(otherStatus.map(pageAiRenderNonToolMessage));
html = html.concat(turn.assistants.map(pageAiRenderNonToolMessage));
html = html.concat(turn.others.map(pageAiRenderNonToolMessage));
return html.join('');
}).join('');
}
function pageAiHistoryTextKey(value) {
return String(value || '')
.replace(/\s+/g, ' ')
.replace(/[.…]+$/g, '')
.trim();
}
function pageAiHistoryPreviewIsDuplicate(title, preview) {
var titleKey = pageAiHistoryTextKey(title);
var previewKey = pageAiHistoryTextKey(preview);
if (!titleKey || !previewKey) return false;
return titleKey === previewKey || previewKey.indexOf(titleKey) === 0 || titleKey.indexOf(previewKey) === 0;
}
function pageAiBoardRunDetailHtml(runId) {
var current = typeof pageAiCurrentSession === 'function' ? pageAiCurrentSession() : null;
var detail = pageUiState.pageAiBoardRunDetails && pageUiState.pageAiBoardRunDetails[runId]
|| current && current.boardRuns && current.boardRuns[runId]
|| null;
if (!detail) return '
暂无 run detail。
';
var changedFiles = pageAiNormalizeArray(detail.changedFiles).map(function(file) {
return '
' + escapeHtml(String(file.path || file || '')) + (file.status ? ' · ' + escapeHtml(file.status) : '') + '';
}).join('');
var verification = pageAiNormalizeArray(detail.verification).map(function(item) {
return '
' + escapeHtml([item.command, item.status, item.output].filter(Boolean).join(' · ')) + '';
}).join('');
var timeline = pageAiNormalizeArray(detail.timeline).map(function(event) {
return '
' + escapeHtml([event.type, event.message, event.timestamp].filter(Boolean).join(' · ')) + '';
}).join('');
return '' +
'
' +
'' +
'
' +
'
Worker' + escapeHtml(detail.workerName || detail.workerPresetId || '') + '
' +
'
Model' + escapeHtml(detail.modelLabel || detail.modelOverride || '') + '
' +
'
Workflow' + escapeHtml(detail.workflowName || detail.workflowId || '') + '
' +
'
' +
(changedFiles ? '
changed files' + String(pageAiNormalizeArray(detail.changedFiles).length) + '
' : '') +
(verification ? '
verification' + String(pageAiNormalizeArray(detail.verification).length) + '
' : '') +
(timeline ? '
timeline' + String(pageAiNormalizeArray(detail.timeline).length) + '
' : '') +
'
';
}
function renderPageAiConversation() {
var drawer = ensurePageAiDrawer();
renderPageAiSuggestions();
var conversation = drawer.querySelector('[data-page-ai-panel="' + cssEscape(pageUiState.pageAiPage || 'chat') + '"] [data-page-ai-conversation]');
if (!(conversation instanceof HTMLElement)) return;
if (pageUiState.pageAiPage === 'history') {
var sourceRows = pageUiState.pageAiSessionSearchResults.length
? pageUiState.pageAiSessionSearchResults
: pageUiState.pageAiSessions;
var historyRows = pageAiFilteredHistoryRows(sourceRows);
if (!historyRows.length) {
conversation.innerHTML = '
没有匹配的历史会话。
';
return;
}
conversation.innerHTML = historyRows.map(function(session) {
var title = String(session.title || '新会话').trim() || '新会话';
var preview = pageAiSessionPreviewText(session);
var previewHtml = pageAiHistoryPreviewIsDuplicate(title, preview) ? '' : '
' + escapeHtml(preview) + '';
var active = session.id === pageUiState.pageAiActiveSessionId;
var selected = pageAiSessionSelected(session.id);
var usage = pageAiUsageSummary(session.usage);
var agentLabel = pageAiSessionAgentLabel(session);
var statusValue = pageAiSessionStatusFilterValue(session);
var meta = [agentLabel, pageAiSessionStorageLabel(session), session.runtimeMode, session.permissionLevel, session.shareId, pageAiSessionStatusLabel(statusValue), usage].filter(Boolean).join(' · ');
return '' +
'
' +
'
' +
'
' +
'
' +
'' +
'' +
'' +
'' +
'
' +
'
';
}).join('');
return;
}
if (!pageUiState.pageAiMessages.length) {
conversation.innerHTML = '
围绕当前页面提问,我会优先使用当前页内容、页面结构和已保存设置。
';
return;
}
var shouldStickToBottom = pageAiConversationShouldStickToBottom(conversation);
var previousScrollTop = conversation.scrollTop;
var openToolDetails = pageAiCaptureOpenToolDetails(conversation);
conversation.innerHTML = pageAiConversationRenderHtml(pageUiState.pageAiMessages);
if (pageUiState.pageAiBoardActiveDetailRunId) {
conversation.insertAdjacentHTML('beforeend', pageAiBoardRunDetailHtml(pageUiState.pageAiBoardActiveDetailRunId));
}
pageAiRestoreOpenToolDetails(conversation, openToolDetails);
if (shouldStickToBottom) {
conversation.scrollTop = conversation.scrollHeight;
} else {
var maxScrollTop = Math.max(0, conversation.scrollHeight - conversation.clientHeight);
conversation.scrollTop = Math.min(previousScrollTop, maxScrollTop);
}
}
return {
ensurePageAiDrawer,
humanizePageAiResponse,
pageAiContextButtonSummary,
pageAiContextRefDetail,
pageAiContextRefDisabled,
pageAiContextRefLabel,
pageAiCurrentAgentSelectionLabel,
pageAiCurrentModelLabel,
pageAiFilteredSkillEntries,
pageAiFormatChangedFiles,
pageAiMemoryFileLabel,
pageAiRenderAgentProfileOption,
pageAiRunStatusLabel,
pageAiSuggestions,
pageAiTargetDetail,
pageAiTargetLabel,
pageAiContextScopeLabel,
renderPageAiControls,
renderPageAiConversation,
renderPageAiProviderButtons,
renderPageAiSuggestions,
};
}