2026-06-01 09:29:12 +08:00
|
|
|
|
export function createSidebarPageAiRenderRuntime(context) {
|
|
|
|
|
|
const {
|
|
|
|
|
|
contextRefRegistry,
|
|
|
|
|
|
cssEscape,
|
|
|
|
|
|
currentDocumentId,
|
|
|
|
|
|
documentRef,
|
|
|
|
|
|
escapeHtml,
|
|
|
|
|
|
localMarkdownRelativePathFromPageAiDocumentId,
|
2026-06-09 18:48:46 +08:00
|
|
|
|
pageAiAgentDescriptor,
|
2026-06-01 09:29:12 +08:00
|
|
|
|
pageAiAgentRecord,
|
|
|
|
|
|
pageAiAllSkillEntries,
|
|
|
|
|
|
pageAiBuildAllowedRoots,
|
|
|
|
|
|
pageAiChatOnlyProfileEntries,
|
|
|
|
|
|
pageAiChatOnlyProfileSpec,
|
|
|
|
|
|
pageAiCurrentAgentId,
|
|
|
|
|
|
pageAiCurrentProfile,
|
|
|
|
|
|
pageAiCurrentProfileRecord,
|
|
|
|
|
|
pageAiCurrentSession,
|
|
|
|
|
|
pageAiCurrentSkillSource,
|
|
|
|
|
|
pageAiCurrentSkillSourceLabel,
|
|
|
|
|
|
pageAiDefaultChatOnlyProfileSpec,
|
2026-06-09 18:48:46 +08:00
|
|
|
|
pageAiDescriptorFieldOptions,
|
|
|
|
|
|
pageAiDescriptorFieldValue,
|
2026-06-01 09:29:12 +08:00
|
|
|
|
pageAiEditorTargetCandidates,
|
|
|
|
|
|
pageAiEnsureContextRefState,
|
|
|
|
|
|
pageAiFilteredHistoryRows,
|
|
|
|
|
|
pageAiHideHermesBuiltinSkills,
|
|
|
|
|
|
pageAiHermesProfileEntries,
|
|
|
|
|
|
pageAiHermesSettingsUrl,
|
|
|
|
|
|
pageAiMnoteToolModel,
|
|
|
|
|
|
pageAiNormalizeAcpRuntimes,
|
|
|
|
|
|
pageAiNormalizeArray,
|
|
|
|
|
|
pageAiNormalizeSkillSource,
|
|
|
|
|
|
pageAiProfileDisplayLabel,
|
|
|
|
|
|
pageAiProfileValue,
|
|
|
|
|
|
pageAiProviderLabel,
|
|
|
|
|
|
pageAiReasonixMemoryEnabled,
|
|
|
|
|
|
pageAiRunProfile,
|
|
|
|
|
|
pageAiSessionAgentFilterOptions,
|
|
|
|
|
|
pageAiSessionAgentLabel,
|
|
|
|
|
|
pageAiSessionAgentFilterValue,
|
2026-06-13 22:20:01 +08:00
|
|
|
|
pageAiSessionStatusFilterOptions,
|
|
|
|
|
|
pageAiSessionStatusFilterValue,
|
|
|
|
|
|
pageAiSessionStatusLabel,
|
2026-06-01 09:29:12 +08:00
|
|
|
|
pageAiSessionPreviewText,
|
|
|
|
|
|
pageAiSessionStorageLabel,
|
2026-06-09 22:12:09 +08:00
|
|
|
|
pageAiSessionSelected,
|
|
|
|
|
|
pageAiSelectedSessionCount,
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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();
|
2026-06-09 18:48:46 +08:00
|
|
|
|
var descriptor = pageAiAgentDescriptor(agentId);
|
|
|
|
|
|
var descriptorLabel = String(descriptor && (descriptor.displayName || descriptor.label) || '').trim();
|
|
|
|
|
|
if (agentId === 'reasonix') return descriptorLabel || 'Reasonix';
|
2026-06-01 09:29:12 +08:00
|
|
|
|
var profile = pageAiCurrentProfileRecord();
|
|
|
|
|
|
var chatOnlySpec = pageAiChatOnlyProfileSpec(profile || pageAiCurrentProfile());
|
|
|
|
|
|
if (agentId === 'chat_only') {
|
|
|
|
|
|
return 'ChatOnly / ' + (chatOnlySpec || pageAiDefaultChatOnlyProfileSpec()).label;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (chatOnlySpec) {
|
|
|
|
|
|
return 'ChatOnly / ' + chatOnlySpec.label;
|
|
|
|
|
|
}
|
2026-06-09 18:48:46 +08:00
|
|
|
|
if (agentId === 'hermes') return (descriptorLabel || 'Hermes') + ' / ' + pageAiProfileDisplayLabel(profile, pageAiCurrentProfile());
|
|
|
|
|
|
return descriptorLabel || pageAiAgentRecord(agentId).label;
|
2026-06-01 09:29:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 18:48:46 +08:00
|
|
|
|
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 = '<select data-page-ai-descriptor-field="' + escapeHtml(key) + '" data-page-ai-descriptor-kind="select">' +
|
|
|
|
|
|
options.map(function(option) {
|
|
|
|
|
|
return '<option value="' + escapeHtml(option.value) + '"' + (String(value) === String(option.value) ? ' selected' : '') + '>' + escapeHtml(option.label) + '</option>';
|
|
|
|
|
|
}).join('') +
|
|
|
|
|
|
'</select>';
|
|
|
|
|
|
} else if (kind === 'boolean' || kind === 'toggle') {
|
|
|
|
|
|
control = '<input type="checkbox" data-page-ai-descriptor-field="' + escapeHtml(key) + '" data-page-ai-descriptor-kind="boolean"' + (value === true ? ' checked' : '') + ' />';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
control = '<input type="text" value="' + escapeHtml(value == null ? '' : String(value)) + '" data-page-ai-descriptor-field="' + escapeHtml(key) + '" data-page-ai-descriptor-kind="' + escapeHtml(kind || 'string') + '" />';
|
|
|
|
|
|
}
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<label class="wolai-page-ai-profile-select" data-page-ai-descriptor-field-row="' + escapeHtml(key) + '">' +
|
|
|
|
|
|
'<span>' + escapeHtml(label) + '</span>' +
|
|
|
|
|
|
control +
|
|
|
|
|
|
'</label>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 22:20:01 +08:00
|
|
|
|
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 '' +
|
|
|
|
|
|
'<label class="wolai-page-ai-reasonix-control" data-page-ai-reasonix-control-row="' + escapeHtml(key) + '">' +
|
|
|
|
|
|
'<span>' + escapeHtml(pageAiReasonixQuickControlLabel(key, field.label)) + '</span>' +
|
|
|
|
|
|
'<select data-page-ai-descriptor-field="' + escapeHtml(key) + '" data-page-ai-descriptor-kind="select" data-page-ai-reasonix-quick-control="' + escapeHtml(key) + '">' +
|
|
|
|
|
|
options.map(function(option) {
|
|
|
|
|
|
return '<option value="' + escapeHtml(option.value) + '"' + (String(value) === String(option.value) ? ' selected' : '') + '>' + escapeHtml(option.label) + '</option>';
|
|
|
|
|
|
}).join('') +
|
|
|
|
|
|
'</select>' +
|
|
|
|
|
|
'</label>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 '' +
|
|
|
|
|
|
'<div class="wolai-page-ai-reasonix-controls" data-page-ai-reasonix-quick-controls>' +
|
|
|
|
|
|
fields +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 18:48:46 +08:00
|
|
|
|
function pageAiRenderAgentDescriptorCard(agentId, fallbackTitle) {
|
|
|
|
|
|
var descriptor = pageAiAgentDescriptor(agentId) || pageAiAgentRecord(agentId);
|
|
|
|
|
|
var title = String(descriptor && (descriptor.displayName || descriptor.label) || fallbackTitle || agentId).trim();
|
|
|
|
|
|
var fields = pageAiNormalizeArray(descriptor && descriptor.fields).map(pageAiRenderDescriptorField).filter(Boolean).join('');
|
|
|
|
|
|
var disabled = pageAiNormalizeArray(descriptor && descriptor.disabledCapabilities);
|
|
|
|
|
|
var disabledText = disabled.length ? '<div class="wolai-page-ai-tool-meta">disabled: ' + escapeHtml(disabled.join(', ')) + '</div>' : '';
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<section class="wolai-page-ai-memory-card" data-page-ai-agent-descriptor-card="' + escapeHtml(agentId) + '">' +
|
|
|
|
|
|
'<div class="wolai-page-ai-memory-head">' +
|
|
|
|
|
|
'<div><div class="wolai-page-ai-memory-title">' + escapeHtml(title) + '</div><div class="wolai-page-ai-memory-scope">' + escapeHtml(pageAiAgentDescriptorDetail(descriptor) || 'descriptor fallback') + '</div></div>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
disabledText +
|
|
|
|
|
|
(fields ? '<div class="wolai-page-ai-settings-grid">' + fields + '</div>' : '<div class="wolai-page-ai-tool-meta">当前 agent 没有可配置字段。</div>') +
|
|
|
|
|
|
'</section>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
|
function pageAiCurrentModelLabel() {
|
|
|
|
|
|
var profile = pageAiCurrentProfileRecord();
|
2026-06-13 22:20:01 +08:00
|
|
|
|
var prefs = pageUiState.pageAiSkillPreferences || {};
|
2026-06-01 09:29:12 +08:00
|
|
|
|
var toolModel = pageAiMnoteToolModel();
|
2026-06-13 22:20:01 +08:00
|
|
|
|
var reasonixModel = String(prefs['ai.agent.reasonix.model_id'] || '').trim();
|
|
|
|
|
|
var approvalMode = String(prefs['ai.agent.reasonix.approval_mode'] || 'ask').trim() || 'ask';
|
|
|
|
|
|
var planMode = String(prefs['ai.agent.reasonix.plan_mode'] || 'manual').trim() || 'manual';
|
|
|
|
|
|
var approvalLabel = {
|
|
|
|
|
|
ask: '审批:询问',
|
|
|
|
|
|
allow: '审批:自动允许',
|
|
|
|
|
|
deny: '审批:自动拒绝'
|
|
|
|
|
|
}[approvalMode] || ('审批:' + approvalMode);
|
|
|
|
|
|
var planLabel = {
|
|
|
|
|
|
manual: '计划:手动',
|
|
|
|
|
|
auto: '计划:自动'
|
|
|
|
|
|
}[planMode] || ('计划:' + planMode);
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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 决定';
|
2026-06-13 22:20:01 +08:00
|
|
|
|
if (pageAiCurrentAgentId() === 'reasonix') {
|
|
|
|
|
|
return ['Reasonix', reasonixModel || toolModel, approvalLabel, planLabel].filter(Boolean).join(' · ');
|
|
|
|
|
|
}
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 22:20:01 +08:00
|
|
|
|
function pageAiRuntimeStatusValue(path, fallback) {
|
|
|
|
|
|
var node = pageUiState.pageAiRuntimeStatus || {};
|
|
|
|
|
|
String(path || '').split('.').forEach(function(part) {
|
|
|
|
|
|
if (!part || node == null || typeof node !== 'object') return;
|
|
|
|
|
|
node = node[part];
|
|
|
|
|
|
});
|
|
|
|
|
|
if (node == null || node === '') return fallback || '';
|
|
|
|
|
|
return String(node);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageAiRuntimeStatusLabel() {
|
|
|
|
|
|
var status = pageUiState.pageAiRuntimeStatus || {};
|
|
|
|
|
|
var runtime = String(status.runtime || pageUiState.pageAiAcpRuntime || 'reasonix');
|
|
|
|
|
|
var mode = String(status.mode || 'new_session');
|
|
|
|
|
|
if (runtime === 'reasonix') return pageAiCurrentModelLabel() + ' · ' + mode.replace(/_/g, '-');
|
|
|
|
|
|
return runtime + ' · ' + mode.replace(/_/g, '-');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageAiRuntimeDetailHtml() {
|
|
|
|
|
|
var status = pageUiState.pageAiRuntimeStatus || {};
|
|
|
|
|
|
var acp = status.acp && typeof status.acp === 'object' ? status.acp : {};
|
|
|
|
|
|
var roots = status.roots && typeof status.roots === 'object' ? status.roots : {};
|
|
|
|
|
|
var tools = status.tools && typeof status.tools === 'object' ? status.tools : {};
|
|
|
|
|
|
var mcp = status.mcp && typeof status.mcp === 'object' ? status.mcp : {};
|
|
|
|
|
|
var queue = status.session && status.session.queue && typeof status.session.queue === 'object' ? status.session.queue : {};
|
|
|
|
|
|
var logs = status.logs && Array.isArray(status.logs.items) ? status.logs.items : [];
|
|
|
|
|
|
var rows = [
|
|
|
|
|
|
['runtime', pageAiRuntimeStatusLabel()],
|
|
|
|
|
|
['session', String(status.mnoteSessionId || pageUiState.pageAiActiveSessionId || '无')],
|
|
|
|
|
|
['acpSessionId', String(acp.acpSessionId || '无')],
|
|
|
|
|
|
['load/replay', String(acp.loadStatus || 'not_applicable') + ' / replay=' + String(acp.replaySeen === true)],
|
|
|
|
|
|
['live binding', acp.liveBinding && typeof acp.liveBinding === 'object' ? String(acp.liveBinding.status || 'unknown') : '无'],
|
|
|
|
|
|
['roots', 'write=' + String(roots.write || 0) + ' readOnly=' + String(roots.readOnly || 0)],
|
|
|
|
|
|
['MCP/tools', String(mcp.status || 'unknown') + ' · enabled=' + String(tools.enabled || 0) + ' disabled=' + String(tools.disabled || 0)],
|
|
|
|
|
|
['queue', String(queue.queuedCount || pageUiState.pageAiQueueLength || 0)],
|
|
|
|
|
|
['jobs', pageAiRuntimeStatusValue('jobs.running', '0')]
|
|
|
|
|
|
];
|
|
|
|
|
|
var html = rows.map(function(row) {
|
|
|
|
|
|
return '<div class="wolai-page-ai-tool-row"><span class="wolai-page-ai-tool-name">' + escapeHtml(row[0]) + '</span><span class="wolai-page-ai-tool-meta">' + escapeHtml(row[1]) + '</span></div>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
if (pageUiState.pageAiRuntimeStatusError) {
|
|
|
|
|
|
html += '<div class="wolai-page-ai-inline-error">' + escapeHtml(pageUiState.pageAiRuntimeStatusError) + '</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (logs.length) {
|
|
|
|
|
|
html += logs.slice(-4).map(function(item) {
|
|
|
|
|
|
return '<div class="wolai-page-ai-tool-row"><span class="wolai-page-ai-tool-name">' + escapeHtml(String(item.kind || 'log')) + '</span><span class="wolai-page-ai-tool-meta">' + escapeHtml(String(item.seq || '')) + '</span></div>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
return html || '<div class="wolai-page-ai-empty">Runtime status 尚未加载。</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageAiUsageDetailHtml() {
|
|
|
|
|
|
var status = pageUiState.pageAiRuntimeStatus || {};
|
|
|
|
|
|
var usage = status.usage && typeof status.usage === 'object' ? status.usage : {};
|
|
|
|
|
|
var rows = [
|
|
|
|
|
|
['last run', usage.lastRunTokens || usage.lastRunCost || 'unknown'],
|
|
|
|
|
|
['session total', usage.sessionTokens || usage.sessionCost || 'unknown'],
|
|
|
|
|
|
['cache', usage.cacheTokens || usage.cacheHit || 'unknown']
|
|
|
|
|
|
];
|
|
|
|
|
|
return rows.map(function(row) {
|
|
|
|
|
|
return '<div class="wolai-page-ai-tool-row"><span class="wolai-page-ai-tool-name">' + escapeHtml(row[0]) + '</span><span class="wolai-page-ai-tool-meta">' + escapeHtml(String(row[1])) + '</span></div>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageAiSettingsTabsHtml(activeTab) {
|
|
|
|
|
|
return [
|
|
|
|
|
|
['agent', 'MNote'],
|
|
|
|
|
|
['reasonix-settings', 'Reasonix'],
|
|
|
|
|
|
['hermes-settings', 'Hermes']
|
|
|
|
|
|
].map(function(tab) {
|
|
|
|
|
|
var active = tab[0] === activeTab;
|
|
|
|
|
|
return '<button type="button" class="wolai-page-ai-tab' + (active ? ' is-active' : '') + '" data-page-ai-tab="' + escapeHtml(tab[0]) + '" role="tab" aria-selected="' + (active ? 'true' : 'false') + '">' + escapeHtml(tab[1]) + '</button>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pageAiStatusTabsHtml() {
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="chat" role="tab" aria-selected="false">聊天</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="status" role="tab" aria-selected="true">状态</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">设置</button>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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();
|
2026-06-13 22:20:01 +08:00
|
|
|
|
if (pageUiState.pageAiPage === 'runtime') pageUiState.pageAiPage = 'status';
|
|
|
|
|
|
if (pageUiState.pageAiPage === 'chat-only-settings') pageUiState.pageAiPage = 'agent';
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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, '');
|
2026-06-02 17:17:49 +08:00
|
|
|
|
var providerKind = String(profile.providerKind || (spec && spec.providerKind) || '').trim();
|
|
|
|
|
|
var detail = providerKind === 'api-chat' ? 'API 聊天 · 不申请文件写权限' : '网页问答 · 不申请文件写权限';
|
|
|
|
|
|
return pageAiRenderAgentProfileOption('chat_only', profile, label, detail);
|
2026-06-01 09:29:12 +08:00
|
|
|
|
}).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';
|
2026-06-09 18:48:46 +08:00
|
|
|
|
var reasonixDescriptor = pageAiAgentDescriptor('reasonix');
|
|
|
|
|
|
var reasonixLabel = String(reasonixDescriptor && (reasonixDescriptor.displayName || reasonixDescriptor.label) || 'Reasonix').trim() || 'Reasonix';
|
|
|
|
|
|
var reasonixDetail = pageAiAgentDescriptorDetail(reasonixDescriptor) || '可读取并在授权目录内写文件';
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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') + '">' +
|
2026-06-09 18:48:46 +08:00
|
|
|
|
'<span class="wolai-page-ai-agent-option-label">' + escapeHtml(reasonixLabel) + '</span>' +
|
|
|
|
|
|
'<span class="wolai-page-ai-agent-option-detail">' + escapeHtml(reasonixDetail) + '</span>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</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 = '' +
|
2026-06-09 18:48:46 +08:00
|
|
|
|
(pageUiState.pageAiAgentDescriptorError ? '<div class="wolai-page-ai-inline-error">' + escapeHtml(pageUiState.pageAiAgentDescriptorError) + '</div>' : '') +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<section class="wolai-page-ai-memory-card" data-page-ai-agent-descriptor-card="mnote">' +
|
|
|
|
|
|
'<div class="wolai-page-ai-memory-head"><div><div class="wolai-page-ai-memory-title">MNote</div><div class="wolai-page-ai-memory-scope">上下文、权限、工具、会话历史和审计</div></div></div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tool-meta">这里配置 MNote 发送给 agent 的 envelope,不配置 Reasonix 或 Hermes 原生 runtime。</div>' +
|
|
|
|
|
|
'</section>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<section class="wolai-page-ai-memory-card">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">授权区域</div><div class="wolai-page-ai-memory-scope">SQLite directory_grants</div></div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tool-list" data-page-ai-allowed-roots></div>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</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>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<div class="wolai-page-ai-tool-meta">由 MNote 生成 run envelope,不写入 Reasonix/Hermes native memory。</div>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</section>' +
|
|
|
|
|
|
'<section class="wolai-page-ai-memory-card">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">会话与审计</div><div class="wolai-page-ai-memory-scope">history / export / changed files</div></div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tool-meta">删除会话只删除 MNote history;外部 Hermes/provider 会话不会默认删除。</div>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</section>';
|
2026-06-13 22:20:01 +08:00
|
|
|
|
var mnoteAllowedRootsNode = agentPanel.querySelector('[data-page-ai-allowed-roots]');
|
|
|
|
|
|
if (mnoteAllowedRootsNode instanceof HTMLElement) {
|
|
|
|
|
|
var mnoteAllowedRoots = pageAiBuildAllowedRoots();
|
|
|
|
|
|
mnoteAllowedRootsNode.innerHTML = mnoteAllowedRoots.length
|
|
|
|
|
|
? mnoteAllowedRoots.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('')
|
|
|
|
|
|
: '<span class="wolai-page-ai-tool-meta">' + escapeHtml(String(pageUiState.pageAiAllowedRootsError || '').trim() || '未选择授权区域') + '</span>';
|
|
|
|
|
|
}
|
2026-06-01 09:29:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-06-13 22:20:01 +08:00
|
|
|
|
drawer.querySelectorAll('[data-page-ai-runtime-badge]').forEach(function(runtimeBadge) {
|
|
|
|
|
|
if (runtimeBadge instanceof HTMLElement) runtimeBadge.textContent = pageAiRuntimeStatusLabel();
|
|
|
|
|
|
});
|
|
|
|
|
|
var runtimeActive = drawer.querySelector('[data-page-ai-runtime-active-run]');
|
|
|
|
|
|
if (runtimeActive instanceof HTMLElement) {
|
|
|
|
|
|
var activeRunStatus = pageAiRuntimeStatusValue('activeRun.status', pageUiState.pageAiRunStatus || 'idle');
|
|
|
|
|
|
runtimeActive.textContent = pageAiRunStatusLabel(activeRunStatus);
|
|
|
|
|
|
}
|
|
|
|
|
|
var runtimeAcp = drawer.querySelector('[data-page-ai-runtime-acp]');
|
|
|
|
|
|
if (runtimeAcp instanceof HTMLElement) runtimeAcp.textContent = pageAiRuntimeStatusValue('acp.acpSessionId', '无 ACP session');
|
|
|
|
|
|
var runtimeDetail = drawer.querySelector('[data-page-ai-runtime-detail]');
|
|
|
|
|
|
if (runtimeDetail instanceof HTMLElement) runtimeDetail.innerHTML = pageAiRuntimeDetailHtml();
|
|
|
|
|
|
var jobsList = drawer.querySelector('[data-page-ai-jobs-list]');
|
|
|
|
|
|
if (jobsList instanceof HTMLElement) {
|
|
|
|
|
|
var runtimeStatus = pageUiState.pageAiRuntimeStatus || {};
|
|
|
|
|
|
var jobs = runtimeStatus.jobs && Array.isArray(runtimeStatus.jobs.items) ? runtimeStatus.jobs.items : [];
|
|
|
|
|
|
jobsList.innerHTML = jobs.length
|
|
|
|
|
|
? jobs.map(function(job) {
|
|
|
|
|
|
return '<div class="wolai-page-ai-tool-row"><span class="wolai-page-ai-tool-name">' + escapeHtml(String(job.id || job.name || 'job')) + '</span><span class="wolai-page-ai-tool-meta">' + escapeHtml(String(job.status || 'running')) + '</span></div>';
|
|
|
|
|
|
}).join('')
|
|
|
|
|
|
: '<div class="wolai-page-ai-empty">暂无运行中的 job。</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
var logsList = drawer.querySelector('[data-page-ai-logs-list]');
|
|
|
|
|
|
if (logsList instanceof HTMLElement) {
|
|
|
|
|
|
var logStatus = pageUiState.pageAiRuntimeStatus || {};
|
|
|
|
|
|
var logs = logStatus.logs && Array.isArray(logStatus.logs.items) ? logStatus.logs.items : [];
|
|
|
|
|
|
logsList.innerHTML = logs.length
|
|
|
|
|
|
? logs.slice(-8).map(function(item) {
|
|
|
|
|
|
return '<div class="wolai-page-ai-tool-row"><span class="wolai-page-ai-tool-name">' + escapeHtml(String(item.kind || 'log')) + '</span><span class="wolai-page-ai-tool-meta">' + escapeHtml(String(item.seq || '')) + '</span></div>';
|
|
|
|
|
|
}).join('')
|
|
|
|
|
|
: '<div class="wolai-page-ai-empty">暂无 runtime logs。</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
var usageDetail = drawer.querySelector('[data-page-ai-usage-detail]');
|
|
|
|
|
|
if (usageDetail instanceof HTMLElement) usageDetail.innerHTML = pageAiUsageDetailHtml();
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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);
|
2026-06-13 22:20:01 +08:00
|
|
|
|
var preview = String(item.preview || item.traceId || item.queueId || '').trim();
|
2026-06-01 09:29:12 +08:00
|
|
|
|
return '' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tool-row" data-page-ai-queue-item="' + escapeHtml(item.queueId) + '">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<div><strong>' + escapeHtml(label) + '</strong><br /><span>' + escapeHtml(preview || item.queueId) + '</span></div>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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]');
|
2026-06-13 22:20:01 +08:00
|
|
|
|
if (modelNode instanceof HTMLElement) modelNode.textContent = pageAiCurrentModelLabel();
|
|
|
|
|
|
var reasonixControls = drawer.querySelector('[data-page-ai-reasonix-quick-controls]');
|
|
|
|
|
|
if (reasonixControls instanceof HTMLElement) {
|
|
|
|
|
|
if (pageAiCurrentAgentId() === 'reasonix') {
|
|
|
|
|
|
reasonixControls.innerHTML = pageAiReasonixQuickControlsHtml().replace(/^<div[^>]*>|<\/div>$/g, '');
|
|
|
|
|
|
reasonixControls.hidden = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reasonixControls.innerHTML = '';
|
|
|
|
|
|
reasonixControls.hidden = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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) {
|
2026-06-09 18:48:46 +08:00
|
|
|
|
hermesMemoryPanel.innerHTML = pageAiRenderAgentDescriptorCard('hermes', 'Hermes') + ['soul', 'user', 'memory'].map(function(section) {
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-06-09 22:12:09 +08:00
|
|
|
|
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) + ' 个';
|
|
|
|
|
|
}
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-06-13 22:20:01 +08:00
|
|
|
|
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 '<option value="' + escapeHtml(option.value) + '"' + (option.value === activeStatusFilter ? ' selected' : '') + '>' + escapeHtml(option.label) + '</option>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
sessionStatusFilter.value = activeStatusFilter;
|
|
|
|
|
|
pageUiState.pageAiSessionStatusFilter = activeStatusFilter;
|
|
|
|
|
|
}
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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();
|
2026-06-05 23:00:53 +08:00
|
|
|
|
var sourceOptions = pageAiSkillSourceOptions();
|
|
|
|
|
|
skillSourceSelect.innerHTML = sourceOptions.map(function(option) {
|
2026-06-01 09:29:12 +08:00
|
|
|
|
return '<option value="' + escapeHtml(option.value) + '"' + (option.value === activeSkillSource ? ' selected' : '') + '>' + escapeHtml(option.label) + '</option>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
skillSourceSelect.value = activeSkillSource;
|
2026-06-05 23:00:53 +08:00
|
|
|
|
var sourceControl = skillSourceSelect.closest('[data-page-ai-skill-source-control]');
|
|
|
|
|
|
if (sourceControl instanceof HTMLElement) sourceControl.hidden = sourceOptions.length <= 1;
|
2026-06-01 09:29:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
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) {
|
2026-06-05 23:00:53 +08:00
|
|
|
|
skillList.innerHTML = '<div class="wolai-page-ai-empty">没有匹配的能力。</div>';
|
2026-06-01 09:29:12 +08:00
|
|
|
|
} 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) {
|
2026-06-05 23:00:53 +08:00
|
|
|
|
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(' · ');
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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;
|
2026-06-05 23:00:53 +08:00
|
|
|
|
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>';
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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>' +
|
2026-06-05 23:00:53 +08:00
|
|
|
|
switchControl +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</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) {
|
2026-06-13 22:20:01 +08:00
|
|
|
|
gatewayDetail.innerHTML = '<div class="wolai-page-ai-empty">打开 Hermes 设置后会检查 gateway 与当前 profile。</div>';
|
2026-06-01 09:29:12 +08:00
|
|
|
|
} 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 = '' +
|
2026-06-09 18:48:46 +08:00
|
|
|
|
pageAiRenderAgentDescriptorCard('reasonix', 'Reasonix') +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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) {
|
2026-06-13 22:20:01 +08:00
|
|
|
|
chatOnlyPanel.innerHTML = pageAiRenderAgentDescriptorCard('chat_only', 'API Chat / Chat-only profile') +
|
|
|
|
|
|
'<section class="wolai-page-ai-memory-card"><div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">API Chat / Chat-only profile</div><div class="wolai-page-ai-memory-scope">默认不申请文件写权限,归属 MNote 设置。</div></div></section>';
|
2026-06-01 09:29:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 = '' +
|
2026-06-08 20:35:49 +08:00
|
|
|
|
'<div class="wolai-page-ai-resize-handle" data-page-ai-resize-handle title="拖动调整页面 AI 宽度" aria-hidden="true"></div>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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>' +
|
2026-06-05 23:00:53 +08:00
|
|
|
|
'<button type="button" class="wolai-page-ai-icon" data-page-ai-tab="skills" aria-label="能力" title="能力">' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<button type="button" class="wolai-page-ai-runtime-strip" data-page-ai-runtime-strip data-page-ai-tab="status" aria-label="打开 Page AI 状态">' +
|
|
|
|
|
|
'<span data-page-ai-runtime-badge>reasonix · new-session</span>' +
|
|
|
|
|
|
'<span data-page-ai-runtime-active-run>空闲</span>' +
|
|
|
|
|
|
'<span data-page-ai-runtime-acp>无 ACP session</span>' +
|
|
|
|
|
|
'</button>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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 设置">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
pageAiSettingsTabsHtml('agent') +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</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>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<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-settings-stack" data-page-ai-agent-panel></div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-settings-stack" data-page-ai-chat-only-panel></div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tools-panel">' +
|
|
|
|
|
|
'<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>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</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 设置">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
pageAiSettingsTabsHtml('hermes-settings') +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</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>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<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>Hermes gateway</span>' +
|
|
|
|
|
|
'<span data-page-ai-gateway-status>未检查</span>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tool-list" data-page-ai-gateway-detail></div>' +
|
|
|
|
|
|
'</div>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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 设置">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
pageAiSettingsTabsHtml('reasonix-settings') +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</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="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 设置">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">设置</button>' +
|
2026-06-05 23:00:53 +08:00
|
|
|
|
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="skills" role="tab" aria-selected="true">能力</button>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="status" role="tab" aria-selected="false">状态</button>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</div>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-skills-toolbar">' +
|
|
|
|
|
|
'<label class="wolai-page-ai-skill-search">' +
|
2026-06-05 23:00:53 +08:00
|
|
|
|
'<span>搜索能力</span>' +
|
|
|
|
|
|
'<input type="search" data-page-ai-skill-search placeholder="搜索能力…" />' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</label>' +
|
|
|
|
|
|
'<label class="wolai-page-ai-profile-select" data-page-ai-skill-source-control>' +
|
2026-06-05 23:00:53 +08:00
|
|
|
|
'<span>能力来源</span>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="status" hidden>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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 设置">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
pageAiStatusTabsHtml() +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</div>' +
|
|
|
|
|
|
'</div>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<div class="wolai-page-ai-toolbar">' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="stop-run">Stop turn</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="reset-runtime">Reset current runtime</button>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tools-panel">' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tools-head">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<span>Current runtime</span>' +
|
|
|
|
|
|
'<span data-page-ai-runtime-badge>未加载</span>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tool-list" data-page-ai-runtime-detail></div>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tools-panel">' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tools-head">' +
|
|
|
|
|
|
'<span>Queue</span>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<span data-page-ai-queue-status>由 mnote-web 管理</span>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tool-list" data-page-ai-queue-list></div>' +
|
|
|
|
|
|
'</div>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<div class="wolai-page-ai-tools-panel">' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<div class="wolai-page-ai-tools-head">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<span>Jobs</span>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="stop-all-jobs">Stop all</button>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</div>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<div class="wolai-page-ai-tool-list" data-page-ai-jobs-list></div>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tools-panel">' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tools-head">' +
|
|
|
|
|
|
'<span>Logs tail</span>' +
|
|
|
|
|
|
'<span>ACP stderr / tool failed / MCP failed</span>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tool-list" data-page-ai-logs-list></div>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tools-panel">' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tools-head">' +
|
|
|
|
|
|
'<span>Usage</span>' +
|
|
|
|
|
|
'<span>last run / session / cache</span>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-tool-list" data-page-ai-usage-detail></div>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</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>' +
|
2026-06-09 22:12:09 +08:00
|
|
|
|
'<div class="wolai-page-ai-history-selection" data-page-ai-history-selection-toolbar hidden>' +
|
|
|
|
|
|
'<span data-page-ai-history-selection-count>已选 0 个</span>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-clear-selection>取消</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-ghost wolai-page-ai-ghost--danger" data-page-ai-session-delete-selected>删除</button>' +
|
|
|
|
|
|
'</div>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</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>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<label class="wolai-page-ai-skill-search">' +
|
|
|
|
|
|
'<span>按状态筛选</span>' +
|
|
|
|
|
|
'<select data-page-ai-session-status-filter><option value="all">全部状态</option></select>' +
|
|
|
|
|
|
'</label>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<div class="wolai-page-ai-reasonix-controls" data-page-ai-reasonix-quick-controls hidden></div>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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="Agent:Reasonix" title="Agent:Reasonix">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="当前 Agent:Reasonix">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('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-05 23:00:53 +08:00
|
|
|
|
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>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 22:20:01 +08:00
|
|
|
|
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 ? (
|
|
|
|
|
|
'<ol class="wolai-page-ai-plan-step-list">' + steps.map(function(step) {
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<li class="wolai-page-ai-plan-step-item" data-status="' + escapeHtml(step.status || 'pending') + '">' +
|
|
|
|
|
|
'<span class="wolai-page-ai-plan-step-status" data-status="' + escapeHtml(step.status || 'pending') + '">' + escapeHtml(pageAiPlanStepIcon(step.status)) + '</span>' +
|
|
|
|
|
|
'<span class="wolai-page-ai-plan-step-text">' + escapeHtml(step.text || '') + '</span>' +
|
|
|
|
|
|
'</li>';
|
|
|
|
|
|
}).join('') + '</ol>'
|
|
|
|
|
|
) : '';
|
|
|
|
|
|
return '' +
|
|
|
|
|
|
'<div class="wolai-page-ai-message wolai-page-ai-message--assistant wolai-page-ai-plan-card" data-page-ai-reasonix-plan-card="true">' +
|
|
|
|
|
|
'<div class="wolai-page-ai-plan-card-header">' +
|
|
|
|
|
|
'<div class="wolai-page-ai-plan-card-title-wrap">' +
|
|
|
|
|
|
'<span class="wolai-page-ai-plan-card-kicker">Reasonix</span>' +
|
|
|
|
|
|
'<strong class="wolai-page-ai-plan-card-title">执行计划</strong>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<span class="wolai-page-ai-plan-card-badge">待批准</span>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-plan-card-markdown">' + renderPageAiMarkdown(text) + '</div>' +
|
|
|
|
|
|
stepHtml +
|
|
|
|
|
|
'<div class="wolai-page-ai-plan-card-actions">' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-plan-card-button wolai-page-ai-plan-card-button--primary" data-page-ai-plan-action="approve">批准执行</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-plan-card-button" data-page-ai-plan-action="revise">要求修改</button>' +
|
|
|
|
|
|
'<button type="button" class="wolai-page-ai-plan-card-button wolai-page-ai-plan-card-button--danger" data-page-ai-plan-action="cancel">取消</button>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 '<div class="wolai-page-ai-permission-summary">' + rows.slice(0, 6).map(function(row) {
|
|
|
|
|
|
return '<div class="wolai-page-ai-permission-row"><span>' + escapeHtml(row.label || '') + '</span><strong>' + escapeHtml(row.value || '') + '</strong></div>';
|
|
|
|
|
|
}).join('') + '</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-05 23:00:53 +08:00
|
|
|
|
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') {
|
2026-06-13 22:20:01 +08:00
|
|
|
|
var permissionOptions = Array.isArray(item.options) ? item.options : [];
|
|
|
|
|
|
var permissionActions = '';
|
|
|
|
|
|
if (!item.resolved) {
|
|
|
|
|
|
if (permissionOptions.length) {
|
|
|
|
|
|
permissionActions = '<div class="wolai-page-ai-message-actions">' + 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 '<button type="button" class="wolai-page-ai-ghost' + (denyLike ? ' wolai-page-ai-ghost--danger' : '') + '" data-page-ai-permission-action="' + (denyLike ? 'deny' : 'allow') + '" data-page-ai-permission-option-id="' + escapeHtml(option.optionId || '') + '" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">' + escapeHtml(option.name || option.optionId || '选择') + '</button>';
|
|
|
|
|
|
}).join('') + '</div>';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
permissionActions = '' +
|
2026-06-05 23:00:53 +08:00
|
|
|
|
'<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>' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-05 23:00:53 +08:00
|
|
|
|
return '' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<div class="wolai-page-ai-message wolai-page-ai-message--tool wolai-page-ai-permission-card" data-page-ai-permission-request="' + escapeHtml(item.permissionId || '') + '">' +
|
|
|
|
|
|
'<div class="wolai-page-ai-permission-card-header">' +
|
|
|
|
|
|
'<div class="wolai-page-ai-plan-card-title-wrap">' +
|
|
|
|
|
|
'<span class="wolai-page-ai-plan-card-kicker">权限</span>' +
|
|
|
|
|
|
'<strong class="wolai-page-ai-plan-card-title">' + escapeHtml(item.toolName || 'session/request_permission') + '</strong>' +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<span class="wolai-page-ai-permission-badge">' + escapeHtml(item.resolved ? (item.decision === 'allow' || item.decision === 'allowed' ? '已允许' : '已处理') : '待确认') + '</span>' +
|
|
|
|
|
|
'</div>' +
|
2026-06-05 23:00:53 +08:00
|
|
|
|
'<div class="wolai-page-ai-message-text">' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
pageAiRenderPermissionDetails(item) +
|
2026-06-05 23:00:53 +08:00
|
|
|
|
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>';
|
|
|
|
|
|
}
|
2026-06-13 22:20:01 +08:00
|
|
|
|
if (pageAiLooksLikeReasonixPlanMessage(item)) return pageAiRenderReasonixPlanCard(item);
|
2026-06-05 23:00:53 +08:00
|
|
|
|
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('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 22:12:09 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 09:29:12 +08:00
|
|
|
|
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) {
|
2026-06-09 22:12:09 +08:00
|
|
|
|
var title = String(session.title || '新会话').trim() || '新会话';
|
2026-06-01 09:29:12 +08:00
|
|
|
|
var preview = pageAiSessionPreviewText(session);
|
2026-06-09 22:12:09 +08:00
|
|
|
|
var previewHtml = pageAiHistoryPreviewIsDuplicate(title, preview) ? '' : '<span>' + escapeHtml(preview) + '</span>';
|
2026-06-01 09:29:12 +08:00
|
|
|
|
var active = session.id === pageUiState.pageAiActiveSessionId;
|
2026-06-09 22:12:09 +08:00
|
|
|
|
var selected = pageAiSessionSelected(session.id);
|
2026-06-01 09:29:12 +08:00
|
|
|
|
var usage = pageAiUsageSummary(session.usage);
|
|
|
|
|
|
var agentLabel = pageAiSessionAgentLabel(session);
|
2026-06-13 22:20:01 +08:00
|
|
|
|
var statusValue = pageAiSessionStatusFilterValue(session);
|
|
|
|
|
|
var meta = [agentLabel, pageAiSessionStorageLabel(session), session.runtimeMode, session.permissionLevel, session.shareId, pageAiSessionStatusLabel(statusValue), usage].filter(Boolean).join(' · ');
|
2026-06-01 09:29:12 +08:00
|
|
|
|
return '' +
|
2026-06-13 22:20:01 +08:00
|
|
|
|
'<div class="wolai-page-ai-message wolai-page-ai-message--history' + (active ? ' is-active' : '') + (selected ? ' is-selected' : '') + '" data-page-ai-session-row="' + escapeHtml(session.id) + '" data-page-ai-session-agent="' + escapeHtml(pageAiSessionAgentFilterValue(session)) + '" data-page-ai-session-status="' + escapeHtml(statusValue) + '">' +
|
2026-06-09 22:12:09 +08:00
|
|
|
|
'<button type="button" class="wolai-page-ai-session-check" data-page-ai-session-select="' + escapeHtml(session.id) + '" aria-checked="' + (selected ? 'true' : 'false') + '" aria-label="选择会话 ' + escapeHtml(session.title || session.id) + '">' +
|
|
|
|
|
|
'<span>' + (selected ? '✓' : '') + '</span>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</button>' +
|
2026-06-09 22:12:09 +08:00
|
|
|
|
'<button type="button" class="wolai-page-ai-message-text wolai-page-ai-history-main" data-page-ai-session="' + escapeHtml(session.id) + '">' +
|
|
|
|
|
|
'<strong>' + escapeHtml(title) + '</strong>' +
|
|
|
|
|
|
previewHtml +
|
|
|
|
|
|
(meta ? '<span class="wolai-page-ai-tool-meta">' + escapeHtml(meta) + '</span>' : '') +
|
|
|
|
|
|
'</button>' +
|
|
|
|
|
|
'<div class="wolai-page-ai-message-actions wolai-page-ai-history-actions">' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'<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>' +
|
2026-06-10 07:09:04 +08:00
|
|
|
|
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-session-export="' + escapeHtml(session.id) + '">导出</button>' +
|
2026-06-09 22:12:09 +08:00
|
|
|
|
'<button type="button" class="wolai-page-ai-ghost wolai-page-ai-ghost--danger" data-page-ai-session-delete="' + escapeHtml(session.id) + '">删除</button>' +
|
2026-06-01 09:29:12 +08:00
|
|
|
|
'</div>' +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!pageUiState.pageAiMessages.length) {
|
|
|
|
|
|
conversation.innerHTML = '<div class="wolai-page-ai-empty">围绕当前页面提问,我会优先使用当前页内容、页面结构和已保存设置。</div>';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-05 23:00:53 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-06-01 09:29:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
ensurePageAiDrawer,
|
|
|
|
|
|
humanizePageAiResponse,
|
|
|
|
|
|
pageAiContextButtonSummary,
|
|
|
|
|
|
pageAiContextRefDetail,
|
|
|
|
|
|
pageAiContextRefDisabled,
|
|
|
|
|
|
pageAiContextRefLabel,
|
|
|
|
|
|
pageAiCurrentAgentSelectionLabel,
|
|
|
|
|
|
pageAiCurrentModelLabel,
|
|
|
|
|
|
pageAiFilteredSkillEntries,
|
|
|
|
|
|
pageAiFormatChangedFiles,
|
|
|
|
|
|
pageAiMemoryFileLabel,
|
|
|
|
|
|
pageAiRenderAgentProfileOption,
|
|
|
|
|
|
pageAiRunStatusLabel,
|
|
|
|
|
|
pageAiSuggestions,
|
|
|
|
|
|
pageAiTargetDetail,
|
|
|
|
|
|
pageAiTargetLabel,
|
|
|
|
|
|
pageAiContextScopeLabel,
|
|
|
|
|
|
renderPageAiControls,
|
|
|
|
|
|
renderPageAiConversation,
|
|
|
|
|
|
renderPageAiProviderButtons,
|
|
|
|
|
|
renderPageAiSuggestions,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|