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

1307 lines
78 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export function createSidebarPageAiRenderRuntime(context) {
const {
contextRefRegistry,
cssEscape,
currentDocumentId,
documentRef,
escapeHtml,
localMarkdownRelativePathFromPageAiDocumentId,
pageAiAgentRecord,
pageAiAllSkillEntries,
pageAiBuildAllowedRoots,
pageAiChatOnlyProfileEntries,
pageAiChatOnlyProfileSpec,
pageAiCurrentAgentId,
pageAiCurrentProfile,
pageAiCurrentProfileRecord,
pageAiCurrentSession,
pageAiCurrentSkillSource,
pageAiCurrentSkillSourceLabel,
pageAiDefaultChatOnlyProfileSpec,
pageAiEditorTargetCandidates,
pageAiEnsureContextRefState,
pageAiFilteredHistoryRows,
pageAiHideHermesBuiltinSkills,
pageAiHermesProfileEntries,
pageAiHermesSettingsUrl,
pageAiMnoteToolModel,
pageAiNormalizeAcpRuntimes,
pageAiNormalizeArray,
pageAiNormalizeSkillSource,
pageAiProfileDisplayLabel,
pageAiProfileValue,
pageAiProviderLabel,
pageAiReasonixMemoryEnabled,
pageAiRunProfile,
pageAiSessionAgentFilterOptions,
pageAiSessionAgentLabel,
pageAiSessionAgentFilterValue,
pageAiSessionPreviewText,
pageAiSessionStorageLabel,
pageAiSkillEnabled,
pageAiSkillGroupCollapsed,
pageAiSkillListEntries,
pageAiSkillOriginLabel,
pageAiSkillSourceOptions,
pageAiSkillSourceParts,
pageAiToggleableSkillEntries,
pageAiUsageSummary,
pageUiState,
renderPageAiMarkdown,
searchText,
currentPageAiEditorTarget,
currentPageAiSelectedText,
} = context;
const PAGE_AI_CONTEXT_REF_REGISTRY = Array.isArray(contextRefRegistry) ? contextRefRegistry : [];
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();
if (agentId === 'reasonix') return '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 'Hermes / ' + pageAiProfileDisplayLabel(profile, pageAiCurrentProfile());
return pageAiAgentRecord(agentId).label;
}
function pageAiRenderAgentProfileOption(agentId, profile, label, detail) {
var profileId = pageAiProfileValue(profile);
var active = pageAiCurrentAgentId() === agentId && profileId === pageAiCurrentProfile();
return '' +
'<button type="button" class="wolai-page-ai-agent-option' + (active ? ' is-active' : '') + '" data-page-ai-agent-id="' + escapeHtml(agentId) + '" data-page-ai-profile-id="' + escapeHtml(profileId) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' +
'<span class="wolai-page-ai-agent-option-label">' + escapeHtml(label) + '</span>' +
'<span class="wolai-page-ai-agent-option-detail">' + escapeHtml(detail || profileId) + '</span>' +
'</button>';
}
function pageAiCurrentModelLabel() {
var profile = pageAiCurrentProfileRecord();
var toolModel = pageAiMnoteToolModel();
if (!profile) return 'tool: ' + toolModel;
var model = String(profile.model || '').trim();
var gateway = String(profile.gateway || '').trim();
var profileLabel = [model, gateway].filter(Boolean).join(' · ') || '由 Hermes 决定';
return 'tool: ' + toolModel + ' · profile: ' + profileLabel;
}
function pageAiFormatChangedFiles(files) {
return pageAiNormalizeArray(files).map(function(file) {
var path = String(file && file.path || '').trim();
var changeType = String(file && file.changeType || file.change_type || 'modified').trim();
var summary = String(file && file.summary || '').trim();
var version = String(file && (file.version || file.revision || '') || '').trim();
var hashBefore = String(file && file.hashBefore || '').trim();
var hashAfter = String(file && file.hashAfter || '').trim();
if (!version && (hashBefore || hashAfter)) version = 'hash ' + [hashBefore || '0', hashAfter || '0'].join('→');
var modifiedBefore = Number(file && file.modifiedBeforeMs || 0) || 0;
var modifiedAfter = Number(file && file.modifiedAfterMs || 0) || 0;
if (!version && (modifiedBefore || modifiedAfter)) version = 'mtime ' + [String(modifiedBefore), String(modifiedAfter)].join('→');
var actor = [file && file.agentKind, file && file.actorType, file && file.actorId].map(function(value) {
return String(value || '').trim();
}).filter(Boolean).join('/');
return [changeType, path, version, summary, actor].filter(Boolean).join(' · ');
}).filter(Boolean).join('\n');
}
function pageAiRunStatusLabel(status) {
if (status === 'queued') return '排队中';
if (status === 'running') return '运行中';
if (status === 'tool_calling') return '调用工具';
if (status === 'completed') return '已完成';
if (status === 'failed') return '失败';
if (status === 'aborted') return '已停止';
return '空闲';
}
function pageAiMemoryFileLabel(section) {
if (section === 'soul') return 'SOUL.md';
if (section === 'user') return 'USER.md';
return 'MEMORY.md';
}
function pageAiContextScopeLabel(scope) {
if (scope === 'selection') return '当前选区';
if (scope === 'block') return '当前块';
if (scope === 'options') return '页面设置';
return '当前页';
}
function pageAiFilteredSkillEntries() {
var query = String(pageUiState.pageAiSkillQuery || '').trim().toLowerCase();
var parts = pageAiSkillSourceParts(pageAiCurrentSkillSource());
var entries = pageAiToggleableSkillEntries(parts.group, parts.profile);
if (!entries.length) {
entries = pageAiSkillListEntries().map(function(skill) {
return Object.assign({}, skill, {
group: parts.group,
profile: parts.profile || ''
});
});
}
return entries.filter(function(skill) {
if (parts.group === 'hermes' && pageAiHideHermesBuiltinSkills(parts.profile) && skill.builtin === true) return false;
if (!query) return true;
return String(skill.name || skill.id || '').toLowerCase().indexOf(query) >= 0
|| String(skill.title || '').toLowerCase().indexOf(query) >= 0
|| String(skill.description || '').toLowerCase().indexOf(query) >= 0
|| String(skill.category || '').toLowerCase().indexOf(query) >= 0
|| pageAiSkillOriginLabel(skill).toLowerCase().indexOf(query) >= 0;
});
}
function renderPageAiProviderButtons() {
var drawer = ensurePageAiDrawer();
var isAcp = pageUiState.pageAiAcpRuntime !== '';
drawer.querySelectorAll('[data-page-ai-provider]').forEach(function(button) {
var provider = button.getAttribute('data-page-ai-provider') || 'hermes';
var active = provider === pageUiState.pageAiProvider;
button.classList.toggle('is-active', active);
button.setAttribute('aria-pressed', active ? 'true' : 'false');
});
var providerNode = drawer.querySelector('.wolai-page-ai-subtitle span:first-child');
if (providerNode instanceof HTMLElement) {
providerNode.textContent = pageAiAgentRecord(pageAiCurrentAgentId()).label;
}
}
function renderPageAiControls() {
var drawer = ensurePageAiDrawer();
var isAcp = pageUiState.pageAiAcpRuntime !== '';
var activeAgentId = pageAiCurrentAgentId();
var activeProfile = pageAiCurrentProfile();
drawer.setAttribute('data-page-ai-page', pageUiState.pageAiPage || 'chat');
drawer.setAttribute('data-page-ai-active-agent-id', activeAgentId);
drawer.setAttribute('data-mnote-acp-runtime', pageUiState.pageAiAcpRuntime || 'reasonix');
documentRef.documentElement.setAttribute('data-mnote-page-ai-agent-id', activeAgentId);
var agentButton = drawer.querySelector('[data-page-ai-agent-button]');
var agentPopoverId = 'mnote-page-ai-agent-popover';
var activeAgentLabel = pageAiCurrentAgentSelectionLabel();
if (agentButton instanceof HTMLElement) {
agentButton.textContent = 'AI';
agentButton.setAttribute('aria-expanded', pageUiState.pageAiAgentPopoverOpen ? 'true' : 'false');
agentButton.setAttribute('aria-controls', agentPopoverId);
agentButton.setAttribute('data-page-ai-agent-summary', activeAgentLabel);
agentButton.setAttribute('aria-label', 'Agent' + activeAgentLabel);
agentButton.setAttribute('title', 'Agent' + activeAgentLabel);
}
var agentPopover = drawer.querySelector('[data-page-ai-agent-popover]');
if (agentPopover instanceof HTMLElement) {
agentPopover.id = agentPopoverId;
agentPopover.hidden = !pageUiState.pageAiAgentPopoverOpen;
var chatOnlyOptions = pageAiChatOnlyProfileEntries().map(function(profile) {
var spec = pageAiChatOnlyProfileSpec(profile);
var label = profile.menuLabel || (spec && spec.label) || pageAiProfileDisplayLabel(profile, '');
var providerKind = String(profile.providerKind || (spec && spec.providerKind) || '').trim();
var detail = providerKind === 'api-chat' ? 'API 聊天 · 不申请文件写权限' : '网页问答 · 不申请文件写权限';
return pageAiRenderAgentProfileOption('chat_only', profile, label, detail);
}).join('');
var hermesOptions = pageAiHermesProfileEntries().map(function(profile) {
var profileId = pageAiProfileValue(profile);
var scope = profile.kind === 'shared'
? (profile.readonly ? '共享 · 只读配置' : '共享')
: (profile.kind === 'personal' ? '个人 profile' : 'Hermes profile');
return pageAiRenderAgentProfileOption('hermes', profile, pageAiProfileDisplayLabel(profile, profileId), scope);
}).join('');
var reasonixActive = activeAgentId === 'reasonix';
agentPopover.innerHTML = '' +
'<div class="wolai-page-ai-context-popover-head">' +
'<strong>选择 Agent</strong>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="close-agent-popover">完成</button>' +
'</div>' +
'<div class="wolai-page-ai-agent-popover-section" data-page-ai-agent-section="chat_only">' +
'<div class="wolai-page-ai-agent-popover-title">ChatOnly</div>' +
'<div class="wolai-page-ai-agent-option-list">' + chatOnlyOptions + '</div>' +
'</div>' +
'<div class="wolai-page-ai-agent-popover-section" data-page-ai-agent-section="hermes">' +
'<div class="wolai-page-ai-agent-popover-title">Hermes</div>' +
'<div class="wolai-page-ai-agent-option-list">' + hermesOptions + '</div>' +
'</div>' +
'<div class="wolai-page-ai-agent-popover-section" data-page-ai-agent-section="reasonix">' +
'<div class="wolai-page-ai-agent-popover-title">Reasonix</div>' +
'<div class="wolai-page-ai-agent-option-list">' +
'<button type="button" class="wolai-page-ai-agent-option' + (reasonixActive ? ' is-active' : '') + '" data-page-ai-agent-id="reasonix" aria-pressed="' + (reasonixActive ? 'true' : 'false') + '">' +
'<span class="wolai-page-ai-agent-option-label">Reasonix</span>' +
'<span class="wolai-page-ai-agent-option-detail">可读取并在授权目录内写文件</span>' +
'</button>' +
'</div>' +
'</div>';
}
var agentChip = drawer.querySelector('[data-page-ai-agent-chip]');
if (agentChip instanceof HTMLElement) {
agentChip.textContent = activeAgentLabel;
agentChip.setAttribute('title', '当前 Agent' + activeAgentLabel);
}
var contextButton = drawer.querySelector('[data-page-ai-context-button]');
var contextPopoverId = 'mnote-page-ai-context-popover';
if (contextButton instanceof HTMLElement) {
var summary = pageAiContextButtonSummary();
contextButton.textContent = '⇅';
contextButton.setAttribute('aria-expanded', pageUiState.pageAiContextPopoverOpen ? 'true' : 'false');
contextButton.setAttribute('aria-controls', contextPopoverId);
contextButton.setAttribute('data-page-ai-context-summary', summary);
contextButton.setAttribute('aria-label', '上下文:' + summary);
contextButton.setAttribute('title', '上下文:' + summary);
}
var contextPopover = drawer.querySelector('[data-page-ai-context-popover]');
if (contextPopover instanceof HTMLElement) {
var selectedRefs = pageAiEnsureContextRefState();
contextPopover.id = contextPopoverId;
contextPopover.hidden = !pageUiState.pageAiContextPopoverOpen;
contextPopover.innerHTML = '' +
'<div class="wolai-page-ai-context-popover-head">' +
'<strong>发送给 AI 的上下文</strong>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="close-context-popover">完成</button>' +
'</div>' +
'<div class="wolai-page-ai-context-option-list">' +
PAGE_AI_CONTEXT_REF_REGISTRY.map(function(ref) {
var checked = selectedRefs[ref.id] === true;
var disabled = pageAiContextRefDisabled(ref.id);
return '' +
'<label class="wolai-page-ai-context-option' + (disabled ? ' is-disabled' : '') + '">' +
'<input type="checkbox" data-page-ai-context-ref="' + escapeHtml(ref.id) + '"' + (checked ? ' checked' : '') + (disabled ? ' disabled' : '') + ' />' +
'<span class="wolai-page-ai-context-option-main">' +
'<span class="wolai-page-ai-context-option-label">' + escapeHtml(ref.label) + '</span>' +
'<span class="wolai-page-ai-context-option-detail">' + escapeHtml(pageAiContextRefDetail(ref.id)) + '</span>' +
'</span>' +
'</label>';
}).join('') +
'</div>' +
'<div class="wolai-page-ai-context-popover-foot">' +
'<span>授权区域</span>' +
'<span>' + escapeHtml(pageAiBuildAllowedRoots().length ? pageAiBuildAllowedRoots().map(function(root) { return root.permission + ' · ' + root.rootUri.replace(/^file:\/\//, ''); }).join(' / ') : '未选择授权区域') + '</span>' +
'</div>';
}
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 = '' +
'<div class="wolai-page-ai-context-popover-head">' +
'<strong>选择写入目标</strong>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="close-target-popover">完成</button>' +
'</div>' +
'<div class="wolai-page-ai-target-option-list">' +
targetCandidates.map(function(target) {
var targetId = String(target && target.targetId || '').trim();
var active = targetId && targetId === String(activeTarget && activeTarget.targetId || '').trim();
return '' +
'<button type="button" class="wolai-page-ai-target-option' + (active ? ' is-active' : '') + '" data-page-ai-target-option="' + escapeHtml(targetId) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' +
'<span class="wolai-page-ai-target-option-label">' + escapeHtml(pageAiTargetLabel(target)) + '</span>' +
'<span class="wolai-page-ai-target-option-detail">' + escapeHtml(pageAiTargetDetail(target)) + '</span>' +
'</button>';
}).join('') +
'</div>';
}
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 = '<span class="wolai-page-ai-tool-meta">' + escapeHtml(errorText || '未选择授权区域') + '</span>';
} else {
allowedRootsNode.innerHTML = allowedRoots.map(function(root) {
var label = root.rootUri.replace(/^file:\/\//, '') || root.rootUri;
return '<span class="wolai-page-ai-model-chip is-active" data-page-ai-allowed-root="' + escapeHtml(root.rootUri) + '" data-page-ai-allowed-root-permission="' + escapeHtml(root.permission) + '">' + escapeHtml(root.permission + ' · ' + label) + '</span>';
}).join('');
}
}
// Populate ACP runtime dropdown
var acpSelect = drawer.querySelector('[data-page-ai-acp-runtime]');
if (acpSelect instanceof HTMLSelectElement) {
var runtimes = pageUiState.pageAiAcpRuntimes.length ? pageUiState.pageAiAcpRuntimes : pageAiNormalizeAcpRuntimes([]);
acpSelect.innerHTML = runtimes.map(function(rt) {
return '<option value="' + escapeHtml(rt.name || '') + '"' + ((rt.name || '') === pageUiState.pageAiAcpRuntime ? ' selected' : '') + '>' + escapeHtml(rt.title || rt.name) + '</option>';
}).join('');
acpSelect.value = pageUiState.pageAiAcpRuntime || 'reasonix';
}
// Show/hide Hermes-specific profile select
var profileLabel = drawer.querySelector('[data-page-ai-hermes-profile]');
if (profileLabel instanceof HTMLElement) {
profileLabel.style.display = pageUiState.pageAiAcpRuntime === 'reasonix' ? 'none' : '';
}
// When ACP is selected, populate agent panel with ACP runtime info
var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]');
if (agentPanel instanceof HTMLElement) {
agentPanel.innerHTML = '' +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">授权区域</div><div class="wolai-page-ai-memory-scope">' + escapeHtml(pageAiBuildAllowedRoots().length ? 'SQLite directory_grants' : '需要授权') + '</div></div>' +
'</section>' +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">默认上下文</div><div class="wolai-page-ai-memory-scope">ContextRefs</div></div>' +
'</section>' +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">审计</div><div class="wolai-page-ai-memory-scope">changed files 默认摘要</div></div>' +
'</section>';
}
var profileSummary = drawer.querySelector('[data-page-ai-profile-summary]');
if (profileSummary instanceof HTMLElement) profileSummary.textContent = '上下文可选';
var profileSummary = drawer.querySelector('[data-page-ai-profile-summary]');
if (profileSummary instanceof HTMLElement) profileSummary.textContent = '上下文可选';
drawer.querySelectorAll('[data-page-ai-profile-select]').forEach(function(profileSelect) {
if (!(profileSelect instanceof HTMLSelectElement)) return;
var profiles = pageUiState.pageAiProfiles.length ? pageUiState.pageAiProfiles : [{ name: activeProfile, active: true }];
profileSelect.innerHTML = profiles.map(function(profile) {
var name = pageAiProfileValue(profile) || 'default';
var model = [profile.model, profile.gateway].filter(Boolean).join(' / ');
var scope = profile.kind === 'shared'
? (profile.readonly ? 'shared · readonly' : 'shared · admin')
: (profile.kind === 'personal' ? 'personal · 可配置' : '');
var label = [profile.alias || name, scope, model].filter(Boolean).join(' · ');
return '<option value="' + escapeHtml(name) + '"' + (name === activeProfile ? ' selected' : '') + '>' + escapeHtml(label) + '</option>';
}).join('');
profileSelect.value = activeProfile;
});
var runStatus = drawer.querySelector('[data-page-ai-run-status]');
if (runStatus instanceof HTMLElement) {
var queueSuffix = pageUiState.pageAiQueueLength > 0 ? ' · 队列 ' + pageUiState.pageAiQueueLength : '';
runStatus.textContent = pageAiRunStatusLabel(pageUiState.pageAiRunStatus) + queueSuffix;
}
var stopButton = drawer.querySelector('[data-page-ai-action="stop-run"]');
if (stopButton instanceof HTMLButtonElement) {
var canStop = ['queued', 'running', 'tool_calling'].indexOf(pageUiState.pageAiRunStatus) >= 0 && pageUiState.pageAiCurrentRunId;
stopButton.disabled = !canStop;
stopButton.setAttribute('aria-disabled', canStop ? 'false' : 'true');
}
var settingsLink = drawer.querySelector('[data-page-ai-action="open-hermes-settings"]');
if (settingsLink instanceof HTMLButtonElement) {
settingsLink.disabled = !pageAiHermesSettingsUrl();
settingsLink.title = pageAiHermesSettingsUrl() ? '打开 Hermes 设置' : '未配置 MNOTE_WEB_HERMES_UPSTREAM_URL';
}
var queueList = drawer.querySelector('[data-page-ai-queue-list]');
if (queueList instanceof HTMLElement) {
if (!pageUiState.pageAiQueuedItems.length) {
queueList.innerHTML = '<div class="wolai-page-ai-empty">暂无排队项。</div>';
} else {
queueList.innerHTML = pageUiState.pageAiQueuedItems.map(function(item, index) {
var label = '队列 ' + (index + 1);
return '' +
'<div class="wolai-page-ai-tool-row" data-page-ai-queue-item="' + escapeHtml(item.queueId) + '">' +
'<div><strong>' + escapeHtml(label) + '</strong><br /><span>' + escapeHtml(item.queueId) + '</span></div>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="cancel-queued-run" data-page-ai-queue-id="' + escapeHtml(item.queueId) + '">取消</button>' +
'</div>';
}).join('');
}
}
var sessionNode = drawer.querySelector('[data-page-ai-session-status]');
if (sessionNode instanceof HTMLElement) {
var session = pageAiCurrentSession();
var usageText = session && session.usage ? pageAiUsageSummary(session.usage) : '';
sessionNode.textContent = session && session.id
? [String(session.title || '当前页问答'), pageAiSessionStorageLabel(session), usageText].filter(Boolean).join(' · ')
: '等待 AI session';
}
var modelNode = drawer.querySelector('[data-page-ai-model-status]');
if (modelNode instanceof HTMLElement) modelNode.textContent = pageAiBuildAllowedRoots().length ? '已授权' : '需授权';
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;
}
});
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 = ['soul', 'user', 'memory'].map(function(section) {
var label = pageAiMemoryFileLabel(section);
var value = pageUiState.pageAiProfileMemoryDrafts[section] || '';
return '' +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head">' +
'<div>' +
'<div class="wolai-page-ai-memory-title">' + escapeHtml(label) + '</div>' +
'<div class="wolai-page-ai-memory-scope">保存到 Hermes profile: ' + escapeHtml(activeProfile) + '</div>' +
'</div>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-memory-save="' + escapeHtml(section) + '">保存</button>' +
'</div>' +
'<textarea class="wolai-page-ai-memory-editor" data-page-ai-memory-editor="' + escapeHtml(section) + '" spellcheck="false">' + escapeHtml(value) + '</textarea>' +
'</section>';
}).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 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 '<option value="' + escapeHtml(option.value) + '"' + (option.value === activeFilter ? ' selected' : '') + '>' + escapeHtml(option.label) + '</option>';
}).join('');
sessionAgentFilter.value = activeFilter;
pageUiState.pageAiSessionAgentFilter = activeFilter;
}
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 '<option value="' + escapeHtml(option.value) + '"' + (option.value === activeSkillSource ? ' selected' : '') + '>' + escapeHtml(option.label) + '</option>';
}).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 = '<div class="wolai-page-ai-empty">没有匹配的能力。</div>';
} else {
var sourceParts = pageAiSkillSourceParts(pageAiCurrentSkillSource());
var group = sourceParts.group;
var headingExtra = group === 'hermes' ? ' · ' + sourceParts.profile : '';
var collapsed = pageAiSkillGroupCollapsed(pageAiCurrentSkillSource());
skillList.innerHTML = '' +
'<section class="wolai-page-ai-skill-group' + (collapsed ? ' is-collapsed' : '') + '" data-page-ai-skill-group="' + escapeHtml(group) + '">' +
'<button type="button" class="wolai-page-ai-skill-group-head" data-page-ai-skill-group-toggle="' + escapeHtml(pageAiCurrentSkillSource()) + '" aria-expanded="' + (collapsed ? 'false' : 'true') + '">' +
'<span class="wolai-page-ai-skill-group-title"><span class="wolai-page-ai-skill-group-chevron">' + (collapsed ? '' : '⌄') + '</span>' + escapeHtml(pageAiCurrentSkillSourceLabel()) + '</span>' +
'<span>' + escapeHtml(String(skills.length) + headingExtra) + '</span>' +
'</button>' +
(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'
? '<button type="button" class="wolai-page-ai-skill-switch' + (pageAiSkillEnabled(skill) ? ' is-on' : '') + '" data-page-ai-skill-toggle="' + escapeHtml(skill.id || skill.name) + '" data-page-ai-skill-group="' + escapeHtml(group) + '" data-page-ai-skill-profile="' + escapeHtml(skill.profile || sourceParts.profile || '') + '" data-page-ai-skill-kind="' + escapeHtml(skill.skillKind || '') + '" aria-pressed="' + (pageAiSkillEnabled(skill) ? 'true' : 'false') + '"' + (disabled ? ' disabled' : '') + '>' +
'<span></span>' +
'</button>'
: '<span class="wolai-page-ai-skill-readonly-badge">查看</span>';
return '' +
'<div class="wolai-page-ai-skill-row">' +
'<div class="wolai-page-ai-skill-copy">' +
'<div class="wolai-page-ai-skill-main">' +
'<div class="wolai-page-ai-skill-name">' + escapeHtml(displayName) + '</div>' +
'<div class="wolai-page-ai-skill-source">' + escapeHtml(sourceText) + '</div>' +
'</div>' +
(hasDescription ? '<div class="wolai-page-ai-skill-desc">' + escapeHtml(description) + '</div>' : '') +
'</div>' +
switchControl +
'</div>';
}).join('')) +
'</section>';
}
}
var toolsList = drawer.querySelector('[data-page-ai-tool-list]');
if (toolsList instanceof HTMLElement) {
var tools = pageAiNormalizeArray(pageUiState.pageAiTools);
if (!tools.length) {
toolsList.innerHTML = '<div class="wolai-page-ai-empty">尚未读取到 mnote tool manifest。</div>';
} else {
toolsList.innerHTML = tools.map(function(tool) {
return '' +
'<div class="wolai-page-ai-tool-row">' +
'<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('');
}
}
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 = '<div class="wolai-page-ai-empty">打开高级后会检查 agent runtime 与当前 profile。</div>';
} else {
var gatewayInfo = gatewayHealth.gateway || {};
var profileInfo = gatewayHealth.profile || {};
var suggestionText = pageAiNormalizeArray(gatewayHealth.suggestions).join('');
gatewayDetail.innerHTML = '' +
'<div class="wolai-page-ai-tool-row">' +
'<div class="wolai-page-ai-tool-name">' + escapeHtml(profileInfo.name || activeProfile) + '</div>' +
'<div class="wolai-page-ai-tool-meta">model.default: ' + escapeHtml(profileInfo.modelDefault || '未设置') + '</div>' +
'<div class="wolai-page-ai-tool-meta">provider: ' + escapeHtml(profileInfo.provider || '未设置') + ' · API key: ' + escapeHtml(profileInfo.apiKeyConfigured ? '已配置' : '未检测到') + '</div>' +
'</div>' +
'<div class="wolai-page-ai-tool-row">' +
'<div class="wolai-page-ai-tool-name">' + escapeHtml(gatewayInfo.upstream || '未配置 upstream') + '</div>' +
'<div class="wolai-page-ai-tool-meta">gateway: ' + escapeHtml(gatewayInfo.status || 'unknown') + (gatewayInfo.httpStatus ? ' · HTTP ' + escapeHtml(gatewayInfo.httpStatus) : '') + '</div>' +
(suggestionText ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml(suggestionText) + '</div>' : '') +
'</div>';
}
}
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 = '' +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head">' +
'<div><div class="wolai-page-ai-memory-title">Reasonix 专属设置</div><div class="wolai-page-ai-memory-scope">ACP runtime / memory</div></div>' +
'<label class="wolai-page-ai-toggle-line"><input type="checkbox" data-page-ai-reasonix-memory' + (pageAiReasonixMemoryEnabled() ? ' checked' : '') + ' />Memory</label>' +
'</div>' +
'<div class="wolai-page-ai-tool-meta">' + escapeHtml(pageAiReasonixMemoryEnabled() ? '当前用户已开启 Reasonix global/project memory。' : '默认关闭 Reasonix memory,避免跨用户偏好混入。') + '</div>' +
'</section>';
}
var chatOnlyPanel = drawer.querySelector('[data-page-ai-chat-only-panel]');
if (chatOnlyPanel instanceof HTMLElement) {
chatOnlyPanel.innerHTML = '<section class="wolai-page-ai-memory-card"><div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">Chat-only</div><div class="wolai-page-ai-memory-scope">默认不申请文件写权限</div></div></section>';
}
}
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 = '' +
'<div class="wolai-page-ai-panel" role="dialog" aria-modal="false">' +
'<div class="wolai-page-ai-header">' +
'<div class="wolai-page-ai-header-copy">' +
'<h2 class="wolai-page-ai-title" data-title="page-ai-title">页面 AI</h2>' +
'<div class="wolai-page-ai-subtitle">' +
'<span>Hermes</span>' +
'<span data-page-ai-profile-summary>default</span>' +
'<span data-page-ai-model-status>由 Hermes 决定</span>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-header-actions">' +
'<button type="button" class="wolai-page-ai-icon" data-page-ai-action="new-session" aria-label="新建 AI 会话" title="新建 AI 会话"></button>' +
'<button type="button" class="wolai-page-ai-icon" data-page-ai-action="history" aria-label="历史会话" title="历史会话">⌕</button>' +
'<button type="button" class="wolai-page-ai-icon" data-page-ai-tab="skills" aria-label="能力" title="能力">' +
'<svg class="wolai-page-ai-icon-svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">' +
'<path d="M12 3l1.35 4.15L17.5 8.5l-4.15 1.35L12 14l-1.35-4.15L6.5 8.5l4.15-1.35L12 3z" />' +
'<path d="M18.5 13l.8 2.4 2.2.8-2.2.8-.8 2.4-.8-2.4-2.2-.8 2.2-.8.8-2.4z" />' +
'<path d="M5.5 14l.65 1.85L8 16.5l-1.85.65L5.5 19l-.65-1.85L3 16.5l1.85-.65L5.5 14z" />' +
'</svg>' +
'</button>' +
'<button type="button" class="wolai-page-ai-icon" data-page-ai-tab="agent" aria-label="打开页面 AI 设置">⚙</button>' +
'<button type="button" class="wolai-page-ai-icon" data-page-ai-action="close" aria-label="关闭页面 AI">×</button>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-body">' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="chat">' +
'<div class="wolai-page-ai-chat-meta">' +
'<span class="wolai-page-ai-session-summary">' +
'<span data-page-ai-session-status>等待 AI session</span>' +
'</span>' +
'<span data-page-ai-context-scope-label>当前页</span>' +
'<span data-page-ai-run-status>空闲</span>' +
'</div>' +
'<div class="wolai-page-ai-conversation" data-page-ai-conversation></div>' +
'<div class="wolai-page-ai-suggestions">' +
'<div class="wolai-page-ai-suggestions-header">' +
'<span>推荐问题</span>' +
'<div class="wolai-page-ai-intents">' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-intent="create-summary">创建 Summary</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-intent="create-ai-note">创建 AI Note</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="rotate">换一换</button>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-suggestion-list" data-page-ai-suggestion-list></div>' +
'</div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="agent" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="agent" role="tab" aria-selected="true">Common</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="hermes-settings" role="tab" aria-selected="false">Hermes</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="reasonix-settings" role="tab" aria-selected="false">Reasonix</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="chat-only-settings" role="tab" aria-selected="false">Chat-only</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">高级</button>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-profile-error hidden></div>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-memory-error hidden></div>' +
'<div class="wolai-page-ai-memory-grid" data-page-ai-agent-panel></div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="hermes-settings" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Common</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="hermes-settings" role="tab" aria-selected="true">Hermes</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="reasonix-settings" role="tab" aria-selected="false">Reasonix</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="chat-only-settings" role="tab" aria-selected="false">Chat-only</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">高级</button>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-settings-grid">' +
'<label class="wolai-page-ai-profile-select" data-page-ai-hermes-profile>' +
'<span>Hermes profile</span>' +
'<select data-page-ai-profile-select></select>' +
'</label>' +
'</div>' +
'<button type="button" class="wolai-page-ai-settings-link" data-page-ai-action="open-hermes-settings">打开 Hermes 设置</button>' +
'<div class="wolai-page-ai-memory-grid" data-page-ai-hermes-panel></div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="reasonix-settings" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Common</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="hermes-settings" role="tab" aria-selected="false">Hermes</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="reasonix-settings" role="tab" aria-selected="true">Reasonix</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="chat-only-settings" role="tab" aria-selected="false">Chat-only</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">高级</button>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-settings-grid">' +
'<label class="wolai-page-ai-profile-select">' +
'<span>ACP runtime</span>' +
'<select data-page-ai-acp-runtime></select>' +
'</label>' +
'</div>' +
'<div class="wolai-page-ai-memory-grid" data-page-ai-reasonix-panel></div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="chat-only-settings" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Common</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="hermes-settings" role="tab" aria-selected="false">Hermes</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="reasonix-settings" role="tab" aria-selected="false">Reasonix</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="chat-only-settings" role="tab" aria-selected="true">Chat-only</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">高级</button>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-memory-grid" data-page-ai-chat-only-panel></div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="skills" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Agent</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="skills" role="tab" aria-selected="true">能力</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">Runtime</button>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-skills-toolbar">' +
'<label class="wolai-page-ai-skill-search">' +
'<span>搜索能力</span>' +
'<input type="search" data-page-ai-skill-search placeholder="搜索能力…" />' +
'</label>' +
'<label class="wolai-page-ai-profile-select" data-page-ai-skill-source-control>' +
'<span>能力来源</span>' +
'<select data-page-ai-skill-source-select></select>' +
'</label>' +
'<label class="wolai-page-ai-skill-filter-toggle">' +
'<input type="checkbox" data-page-ai-hide-hermes-builtin' + (pageAiHideHermesBuiltinSkills(pageAiCurrentProfile()) ? ' checked' : '') + ' />' +
'<span>隐藏 Hermes 内置</span>' +
'</label>' +
'</div>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-skill-error hidden></div>' +
'<div class="wolai-page-ai-skill-list" data-page-ai-skill-list></div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="runtime" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Agent</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="skills" role="tab" aria-selected="false">能力</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="runtime" role="tab" aria-selected="true">Runtime</button>' +
'</div>' +
'</div>' +
'<button type="button" class="wolai-page-ai-settings-link" data-page-ai-action="open-hermes-settings">打开 Hermes 设置</button>' +
'<label class="wolai-page-ai-context-select">' +
'<span>兼容上下文 scope</span>' +
'<select data-page-ai-context-scope>' +
'<option value="page">当前页</option>' +
'<option value="selection">当前选区</option>' +
'<option value="block">当前块</option>' +
'<option value="options">页面设置</option>' +
'</select>' +
'</label>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-gateway-error hidden></div>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-head">' +
'<span>gateway</span>' +
'<span data-page-ai-gateway-status>未检查</span>' +
'</div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-gateway-detail></div>' +
'</div>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-head">' +
'<span>queue</span>' +
'<span data-page-ai-queue-status>由 mnote-web 管理</span>' +
'</div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-queue-list></div>' +
'</div>' +
'<div class="wolai-page-ai-tools-panel" hidden>' +
'<div class="wolai-page-ai-tools-head">' +
'<span>MNote 工具调试</span>' +
'<span data-page-ai-last-tool>暂无 tool call</span>' +
'</div>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-tool-error hidden></div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-tool-list></div>' +
'</div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="history" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'</div>' +
'<label class="wolai-page-ai-skill-search">' +
'<span>搜索会话</span>' +
'<input type="search" data-page-ai-session-search placeholder="搜索 AI 会话…" />' +
'</label>' +
'<label class="wolai-page-ai-skill-search">' +
'<span>按 agent 筛选</span>' +
'<select data-page-ai-session-agent-filter><option value="all">全部 agent</option></select>' +
'</label>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-session-error hidden></div>' +
'<div class="wolai-page-ai-conversation" data-page-ai-conversation></div>' +
'</section>' +
'</div>' +
'<div class="wolai-page-ai-footer">' +
'<div class="wolai-page-ai-composer">' +
'<textarea class="wolai-page-ai-input" data-page-ai-input rows="3" placeholder="输入消息…"></textarea>' +
'<div class="wolai-page-ai-composer-bar">' +
'<button type="button" class="wolai-page-ai-tool-button" data-page-ai-action="new-session" title="新会话"></button>' +
'<div class="wolai-page-ai-agent-picker">' +
'<button type="button" class="wolai-page-ai-tool-button wolai-page-ai-agent-button" data-page-ai-agent-button aria-expanded="false" aria-label="AgentReasonix" title="AgentReasonix">AI</button>' +
'<div class="wolai-page-ai-agent-popover" data-page-ai-agent-popover hidden></div>' +
'</div>' +
'<div class="wolai-page-ai-context-picker">' +
'<button type="button" class="wolai-page-ai-tool-button wolai-page-ai-context-button" data-page-ai-context-button aria-expanded="false" aria-label="上下文:当前页 + 打开资源" title="上下文:当前页 + 打开资源">⇅</button>' +
'<div class="wolai-page-ai-context-popover" data-page-ai-context-popover hidden></div>' +
'</div>' +
'<div class="wolai-page-ai-target-picker">' +
'<button type="button" class="wolai-page-ai-tool-button wolai-page-ai-target-button" data-page-ai-target-button aria-expanded="false" aria-label="目标:当前页" title="目标:当前页">◎</button>' +
'<div class="wolai-page-ai-target-popover" data-page-ai-target-popover hidden></div>' +
'</div>' +
'<span class="wolai-page-ai-agent-chip" data-page-ai-agent-chip title="当前 AgentReasonix">Reasonix</span>' +
'<span class="wolai-page-ai-target-chip" data-page-ai-target-chip title="当前目标:当前页">当前页</span>' +
'<span class="wolai-page-ai-composer-spacer"></span>' +
'<button type="button" class="wolai-page-ai-stop" data-page-ai-action="stop-run" disabled>停止</button>' +
'<button type="button" class="wolai-page-ai-send" data-page-ai-action="send" aria-label="发送">发送</button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
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 '<button type="button" class="wolai-page-ai-suggestion" data-page-ai-suggestion="' + escapeHtml(text) + '">' + escapeHtml(text) + '</button>';
}).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
? '<div class="wolai-page-ai-tool-meta">位置:' + item.locations.map(function(loc) {
return '<span style="display:inline-flex;align-items:center;gap:4px;margin-right:8px">' +
'<span>' + escapeHtml(loc) + '</span>' +
'<button type="button" class="wolai-page-ai-ghost" style="font-size:0.85em;padding:0 4px;min-width:unset" data-page-ai-open-location="' + escapeHtml(loc) + '" data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" title="打开文件">打开</button>' +
'</span>';
}).join('') + '</div>'
: '';
return [
locationRows,
item.argsSummary ? '<div class="wolai-page-ai-tool-meta">参数 ' + escapeHtml(item.argsSummary) + '</div>' : '',
item.resultSummary ? '<div class="wolai-page-ai-tool-meta">结果 ' + escapeHtml(item.resultSummary) + '</div>' : '',
item.changedFiles && item.changedFiles.length ? '<pre class="wolai-page-ai-tool-meta wolai-page-ai-changed-files">' + escapeHtml(pageAiFormatChangedFiles(item.changedFiles)) + '</pre>' : '',
(item.traceId || item.auditId) ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml([item.traceId, item.auditId].filter(Boolean).join(' · ')) + '</div>' : ''
].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 '' +
'<div class="wolai-page-ai-tool-item" data-page-ai-tool-item data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" data-page-ai-tool-status="' + escapeHtml(item.status || '') + '">' +
'<div class="wolai-page-ai-tool-item-head">' +
'<strong>' + escapeHtml(item.toolName || item.content || 'tool') + '</strong>' +
'<span>' + escapeHtml(meta) + '</span>' +
'</div>' +
(pageAiRenderToolDetailRows(item) || '<div class="wolai-page-ai-tool-meta">暂无参数或结果详情。</div>') +
'</div>';
}).join('');
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-collapse-card data-page-ai-collapse-id="' + escapeHtml(groupId) + '" data-page-ai-tool-card data-page-ai-tool-group="true" data-page-ai-tool-group-id="' + escapeHtml(groupId) + '" data-page-ai-tool-status="' + escapeHtml(status) + '">' +
'<div class="wolai-page-ai-message-role">工具</div>' +
'<div class="wolai-page-ai-message-text">' +
'<details class="wolai-page-ai-tool-details wolai-page-ai-tool-group-details" data-page-ai-collapse-details>' +
'<summary>' +
'<strong>调用工具</strong>' +
'<span>' + escapeHtml(countParts.join(' · ')) + '</span>' +
'</summary>' +
'<div class="wolai-page-ai-tool-group-list">' + rows + '</div>' +
'</details>' +
'</div>' +
'</div>';
}
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 '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-collapse-card data-page-ai-collapse-id="' + escapeHtml(groupId) + '" data-page-ai-thought-card="true">' +
'<div class="wolai-page-ai-message-role">AI</div>' +
'<div class="wolai-page-ai-message-text">' +
'<details class="wolai-page-ai-tool-details wolai-page-ai-thought-group-details" data-page-ai-collapse-details data-page-ai-thought-delta="true">' +
'<summary>' +
'<strong>思考过程</strong>' +
'<span>' + escapeHtml(countLabel) + '</span>' +
'</summary>' +
'<div class="wolai-page-ai-thought-group-list">' + escapeHtml(content) + '</div>' +
'</details>' +
'</div>' +
'</div>';
}
function pageAiRenderNonToolMessage(item) {
var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI');
if (item.kind === 'thought') {
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-collapse-card data-page-ai-collapse-id="thought-single">' +
'<div class="wolai-page-ai-message-role">AI</div>' +
'<div class="wolai-page-ai-message-text">' +
'<details class="wolai-page-ai-tool-details wolai-page-ai-thought-group-details" data-page-ai-collapse-details data-page-ai-thought-delta="true">' +
'<summary><strong>思考过程</strong><span>1 段</span></summary>' +
'<div class="wolai-page-ai-thought-group-list">' + escapeHtml(item.content || '') + '</div>' +
'</details>' +
'</div>' +
'</div>';
}
if (item.kind === 'permission') {
var permissionActions = item.resolved ? '' : (
'<div class="wolai-page-ai-message-actions">' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="allow" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">允许</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="deny" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">拒绝</button>' +
'</div>'
);
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-permission-request="' + escapeHtml(item.permissionId || '') + '">' +
'<div class="wolai-page-ai-message-role">权限</div>' +
'<div class="wolai-page-ai-message-text">' +
'<strong>' + escapeHtml(item.toolName || 'session/request_permission') + '</strong><br />' +
'<span>' + escapeHtml(item.argsSummary || item.content || '') + '</span>' +
permissionActions +
'</div>' +
'</div>';
}
if (item.kind === 'plan') {
var planEntries = Array.isArray(item.entries) ? item.entries : [];
var listHtml = planEntries.map(function(entry) {
return '<li style="margin:2px 0">' + escapeHtml(String(entry || '')) + '</li>';
}).join('');
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-plan="true">' +
'<details class="wolai-page-ai-plan-details" open>' +
'<summary class="wolai-page-ai-message-role" style="cursor:pointer;user-select:none;opacity:0.8;font-size:0.85em">执行计划 · ' + planEntries.length + ' 步</summary>' +
'<div class="wolai-page-ai-message-text" style="margin-top:4px;opacity:0.75;font-size:0.9em;border-left:2px solid var(--wolai-border-color, #ddd);padding-left:8px">' +
'<ol style="margin:4px 0;padding-left:20px">' + listHtml + '</ol>' +
'</div>' +
'</details>' +
'</div>';
}
var streamingAttr = item.streaming ? ' data-page-ai-streaming="true"' : '';
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--' + escapeHtml(item.role || 'assistant') + '"' + streamingAttr + '>' +
'<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
'<div class="wolai-page-ai-message-text">' + (item.role === 'assistant' ? renderPageAiMarkdown(item.content || '') : escapeHtml(item.content || '')) + '</div>' +
'</div>';
}
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 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 = '<div class="wolai-page-ai-empty">没有匹配的历史会话。</div>';
return;
}
conversation.innerHTML = historyRows.map(function(session) {
var preview = pageAiSessionPreviewText(session);
var active = session.id === pageUiState.pageAiActiveSessionId;
var usage = pageAiUsageSummary(session.usage);
var agentLabel = pageAiSessionAgentLabel(session);
var meta = [agentLabel, pageAiSessionStorageLabel(session), session.permissionLevel, session.shareId, session.status, usage].filter(Boolean).join(' · ');
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--history' + (active ? ' is-active' : '') + '" data-page-ai-session-row="' + escapeHtml(session.id) + '" data-page-ai-session-agent="' + escapeHtml(pageAiSessionAgentFilterValue(session)) + '">' +
'<button type="button" class="wolai-page-ai-message-text" data-page-ai-session="' + escapeHtml(session.id) + '">' +
'<strong>' + escapeHtml(session.title || '新会话') + '</strong><br />' +
'<span>' + escapeHtml(preview) + '</span>' +
(meta ? '<br /><span class="wolai-page-ai-tool-meta">' + escapeHtml(meta) + '</span>' : '') +
'</button>' +
'<div class="wolai-page-ai-message-actions">' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-resume="' + escapeHtml(session.id) + '">恢复</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-rename="' + escapeHtml(session.id) + '">重命名</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-delete="' + escapeHtml(session.id) + '">删除</button>' +
'</div>' +
'</div>';
}).join('');
return;
}
if (!pageUiState.pageAiMessages.length) {
conversation.innerHTML = '<div class="wolai-page-ai-empty">围绕当前页面提问,我会优先使用当前页内容、页面结构和已保存设置。</div>';
return;
}
var shouldStickToBottom = pageAiConversationShouldStickToBottom(conversation);
var previousScrollTop = conversation.scrollTop;
var openToolDetails = pageAiCaptureOpenToolDetails(conversation);
conversation.innerHTML = pageAiConversationRenderHtml(pageUiState.pageAiMessages);
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,
};
}