3133 lines
143 KiB
JavaScript
3133 lines
143 KiB
JavaScript
import { createSidebarPageAiMarkdownRuntime } from './sidebar-page-ai-markdown-runtime.js';
|
||
import { createSidebarPageAiPermissionRuntime } from './sidebar-page-ai-permission-runtime.js';
|
||
import { createSidebarPageAiProfileRuntime } from './sidebar-page-ai-profile-runtime.js';
|
||
import { createSidebarPageAiRenderRuntime } from './sidebar-page-ai-render-runtime.js';
|
||
import { createSidebarPageAiSessionRuntime } from './sidebar-page-ai-session-runtime.js';
|
||
import { createSidebarPageAiSkillRuntime } from './sidebar-page-ai-skill-runtime.js';
|
||
import { createSidebarPageAiTargetRuntime } from './sidebar-page-ai-target-runtime.js';
|
||
|
||
export function createSidebarPageAiRuntime(context) {
|
||
const {
|
||
buildLocalFileOpenUrl,
|
||
currentDocumentId,
|
||
currentPageAggregate,
|
||
currentPageOptions,
|
||
currentRootUri,
|
||
currentSourceKind,
|
||
escapeHtml,
|
||
cssEscape,
|
||
openLocalResourceInActiveTab,
|
||
pageUiState,
|
||
resolveWorkspaceId,
|
||
searchText,
|
||
} = context;
|
||
var PAGE_AI_SESSION_STORAGE_VERSION = 3;
|
||
// Page AI 请求合同:agentId 只负责路由;contextRefs 是用户勾选的上下文地址;
|
||
// allowedRoots 只展示 SQLite directory_grants 解析结果,服务端仍会按当前用户重算;
|
||
// runTargetSnapshot 必须来自 OpenEditorsSnapshot,避免运行中 UI 切换导致目标漂移。
|
||
var PAGE_AI_AGENT_REGISTRY = [
|
||
{ id: 'hermes', label: 'Hermes', acpRuntime: 'hermes', canWriteFiles: true },
|
||
{ id: 'reasonix', label: 'Reasonix', acpRuntime: 'reasonix', canWriteFiles: true },
|
||
{ id: 'chat_only', label: 'Chat-only', acpRuntime: 'hermes', canWriteFiles: false }
|
||
];
|
||
var PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY = [
|
||
{ profileId: 'shared_deepseek_chat', baseProfile: 'deepseek-chat', label: 'DeepSeek' },
|
||
{ profileId: 'shared_doubao_chat', baseProfile: 'doubao-chat', label: '豆包' },
|
||
{ profileId: 'shared_gemini_chat', baseProfile: 'gemini-chat', label: 'Gemini' },
|
||
{ profileId: 'shared_api_deepseek_flash_chat', baseProfile: 'api-deepseek-flash-chat', label: 'DeepSeek Flash', providerKind: 'api-chat' },
|
||
{ profileId: 'shared_api_deepseek_pro_chat', baseProfile: 'api-deepseek-pro-chat', label: 'DeepSeek Pro', providerKind: 'api-chat' },
|
||
{ profileId: 'shared_api_gpt_chat', baseProfile: 'api-gpt-chat', label: 'GPT', providerKind: 'api-chat' },
|
||
{ profileId: 'shared_api_kimi_chat', baseProfile: 'api-kimi-chat', label: 'Kimi', providerKind: 'api-chat' },
|
||
{ profileId: 'shared_api_gemini_chat', baseProfile: 'api-gemini-chat', label: 'Gemini API', providerKind: 'api-chat' },
|
||
{ profileId: 'shared_api_grok_chat', baseProfile: 'api-grok-chat', label: 'Grok API', providerKind: 'api-chat' }
|
||
];
|
||
var PAGE_AI_CONTEXT_REF_REGISTRY = [
|
||
{ id: 'current_page', label: '当前页' },
|
||
{ id: 'selection', label: '选区' },
|
||
{ id: 'active_editor', label: '打开资源' },
|
||
{ id: 'file', label: '文件' },
|
||
{ id: 'folder', label: '文件夹' },
|
||
{ id: 'changed_files', label: '最近修改' }
|
||
];
|
||
var pageAiDelegatesInstalled = false;
|
||
var PAGE_AI_DRAWER_WIDTH_STORAGE_KEY = 'mnote.page_ai.drawer_width';
|
||
var PAGE_AI_DRAWER_DEFAULT_WIDTH = 440;
|
||
var PAGE_AI_DRAWER_MIN_WIDTH = 340;
|
||
var PAGE_AI_DRAWER_MAX_WIDTH = 760;
|
||
|
||
function clonePageAiDefaultValue(value) {
|
||
if (Array.isArray(value)) return value.slice();
|
||
if (value && typeof value === 'object') return Object.assign({}, value);
|
||
return value;
|
||
}
|
||
|
||
function pageAiClampDrawerWidth(width) {
|
||
var viewportMax = Math.max(PAGE_AI_DRAWER_MIN_WIDTH, Math.min(PAGE_AI_DRAWER_MAX_WIDTH, window.innerWidth - 24));
|
||
var numeric = Number(width);
|
||
if (!Number.isFinite(numeric) || numeric <= 0) numeric = PAGE_AI_DRAWER_DEFAULT_WIDTH;
|
||
return Math.max(PAGE_AI_DRAWER_MIN_WIDTH, Math.min(viewportMax, Math.round(numeric)));
|
||
}
|
||
|
||
function pageAiStoredDrawerWidth() {
|
||
try {
|
||
return pageAiClampDrawerWidth(window.localStorage ? window.localStorage.getItem(PAGE_AI_DRAWER_WIDTH_STORAGE_KEY) : PAGE_AI_DRAWER_DEFAULT_WIDTH);
|
||
} catch (_error) {
|
||
return pageAiClampDrawerWidth(PAGE_AI_DRAWER_DEFAULT_WIDTH);
|
||
}
|
||
}
|
||
|
||
function pageAiApplyDrawerWidth(drawer, width) {
|
||
var target = drawer instanceof HTMLElement ? drawer : document.querySelector('[data-testid="wolai-page-ai-drawer"]');
|
||
if (!(target instanceof HTMLElement)) return;
|
||
var next = pageAiClampDrawerWidth(width == null ? pageAiStoredDrawerWidth() : width);
|
||
target.style.setProperty('--mnote-page-ai-width', next + 'px');
|
||
}
|
||
|
||
function handlePageAiPointerDown(event, helpers) {
|
||
var closestAction = helpers && helpers.closestAction;
|
||
if (typeof closestAction !== 'function') return false;
|
||
var handle = closestAction(event.target, '[data-page-ai-resize-handle]');
|
||
if (!(handle instanceof HTMLElement)) return false;
|
||
var drawer = handle.closest('[data-testid="wolai-page-ai-drawer"]');
|
||
var panel = drawer instanceof HTMLElement ? drawer.querySelector('.wolai-page-ai-panel') : null;
|
||
if (!(drawer instanceof HTMLElement) || !(panel instanceof HTMLElement)) return false;
|
||
event.preventDefault();
|
||
var pointerId = event.pointerId;
|
||
var startX = Number(event.clientX || 0);
|
||
var startWidth = panel.getBoundingClientRect().width || pageAiStoredDrawerWidth();
|
||
drawer.setAttribute('data-page-ai-resizing', 'true');
|
||
document.documentElement.setAttribute('data-mnote-page-ai-resizing', 'true');
|
||
try {
|
||
handle.setPointerCapture(pointerId);
|
||
} catch (_error) {}
|
||
function move(nextEvent) {
|
||
var nextWidth = startWidth + (startX - Number(nextEvent.clientX || 0));
|
||
pageAiApplyDrawerWidth(drawer, nextWidth);
|
||
}
|
||
function finish(nextEvent) {
|
||
move(nextEvent);
|
||
var value = drawer.style.getPropertyValue('--mnote-page-ai-width').replace('px', '').trim();
|
||
try {
|
||
if (window.localStorage) window.localStorage.setItem(PAGE_AI_DRAWER_WIDTH_STORAGE_KEY, String(pageAiClampDrawerWidth(value)));
|
||
} catch (_error) {}
|
||
drawer.removeAttribute('data-page-ai-resizing');
|
||
document.documentElement.removeAttribute('data-mnote-page-ai-resizing');
|
||
window.removeEventListener('pointermove', move, true);
|
||
window.removeEventListener('pointerup', finish, true);
|
||
window.removeEventListener('pointercancel', finish, true);
|
||
try {
|
||
handle.releasePointerCapture(pointerId);
|
||
} catch (_error) {}
|
||
}
|
||
window.addEventListener('pointermove', move, true);
|
||
window.addEventListener('pointerup', finish, true);
|
||
window.addEventListener('pointercancel', finish, true);
|
||
return true;
|
||
}
|
||
|
||
function ensurePageAiStateFacade(state) {
|
||
var defaults = {
|
||
pageAiOpen: false,
|
||
pageAiBusy: false,
|
||
pageAiMessages: [],
|
||
pageAiSuggestionIndex: 0,
|
||
pageAiProvider: 'hermes',
|
||
pageAiPage: 'chat',
|
||
pageAiRunStatus: 'idle',
|
||
pageAiCurrentRunId: '',
|
||
pageAiAcpRuntime: 'reasonix',
|
||
pageAiAcpRuntimes: [],
|
||
pageAiQueueLength: 0,
|
||
pageAiQueuedItems: [],
|
||
pageAiStoppedRunIds: {},
|
||
pageAiAbortController: null,
|
||
pageAiContextScope: 'page',
|
||
pageAiContextPopoverOpen: false,
|
||
pageAiTargetPopoverOpen: false,
|
||
pageAiSelectedTargetId: '',
|
||
pageAiSelectedContextRefs: null,
|
||
pageAiCurrentRunTargetSnapshot: null,
|
||
pageAiAllowedRoots: [],
|
||
pageAiAllowedRootsError: '',
|
||
pageAiAgentId: 'reasonix',
|
||
pageAiAgentPopoverOpen: false,
|
||
pageAiTools: [],
|
||
pageAiToolsError: '',
|
||
pageAiGatewayHealth: null,
|
||
pageAiGatewayHealthError: '',
|
||
pageAiLastToolCall: null,
|
||
pageAiProfiles: [],
|
||
pageAiActiveProfileName: 'mnoteai',
|
||
pageAiProfileError: '',
|
||
pageAiProfileMemory: { memory: '', user: '', soul: '' },
|
||
pageAiProfileMemoryDrafts: { memory: '', user: '', soul: '' },
|
||
pageAiProfileMemoryError: '',
|
||
pageAiSkills: { categories: [], archived: [] },
|
||
pageAiSkillCatalogs: {
|
||
mnote: { categories: [], archived: [] },
|
||
reasonix: { categories: [], archived: [] },
|
||
hermes: { categories: [], archived: [] }
|
||
},
|
||
pageAiSkillPreferences: {},
|
||
pageAiCollapsedSkillGroups: {},
|
||
pageAiSkillQuery: '',
|
||
pageAiSkillError: '',
|
||
pageAiSkillLoadSeq: 0,
|
||
pageAiActiveSkillSource: 'mnote',
|
||
pageAiSessions: [],
|
||
pageAiActiveSessionId: '',
|
||
pageAiSessionSearchQuery: '',
|
||
pageAiSessionSearchResults: [],
|
||
pageAiSessionSearchTimer: 0,
|
||
pageAiSessionAgentFilter: 'all',
|
||
pageAiSessionError: '',
|
||
pageAiPermissionRequests: []
|
||
};
|
||
Object.keys(defaults).forEach(function(key) {
|
||
if (typeof state[key] === 'undefined') {
|
||
state[key] = clonePageAiDefaultValue(defaults[key]);
|
||
}
|
||
});
|
||
if (!state.pageAiSkillCatalogs || typeof state.pageAiSkillCatalogs !== 'object') {
|
||
state.pageAiSkillCatalogs = clonePageAiDefaultValue(defaults.pageAiSkillCatalogs);
|
||
}
|
||
if (!state.pageAiProfileMemory || typeof state.pageAiProfileMemory !== 'object') {
|
||
state.pageAiProfileMemory = clonePageAiDefaultValue(defaults.pageAiProfileMemory);
|
||
}
|
||
if (!state.pageAiProfileMemoryDrafts || typeof state.pageAiProfileMemoryDrafts !== 'object') {
|
||
state.pageAiProfileMemoryDrafts = clonePageAiDefaultValue(defaults.pageAiProfileMemoryDrafts);
|
||
}
|
||
if (!state.pageAiSkills || typeof state.pageAiSkills !== 'object') {
|
||
state.pageAiSkills = clonePageAiDefaultValue(defaults.pageAiSkills);
|
||
}
|
||
return state;
|
||
}
|
||
|
||
ensurePageAiStateFacade(pageUiState);
|
||
|
||
function closestAction(target, selector) {
|
||
var node = target && target.nodeType === Node.TEXT_NODE ? target.parentElement : target;
|
||
return node && typeof node.closest === 'function' ? node.closest(selector) : null;
|
||
}
|
||
|
||
const pageAiMarkdown = createSidebarPageAiMarkdownRuntime({ escapeHtml });
|
||
const textFromUnknown = (...args) => pageAiMarkdown.textFromUnknown(...args);
|
||
const renderPageAiMarkdown = (...args) => pageAiMarkdown.renderPageAiMarkdown(...args);
|
||
let pageAiRenderRuntime = null;
|
||
|
||
function pageAiRenderApi() {
|
||
if (!pageAiRenderRuntime) throw new Error('page_ai_render_runtime_not_ready');
|
||
return pageAiRenderRuntime;
|
||
}
|
||
|
||
function ensurePageAiDrawer(...args) { return pageAiRenderApi().ensurePageAiDrawer(...args); }
|
||
function humanizePageAiResponse(...args) { return pageAiRenderApi().humanizePageAiResponse(...args); }
|
||
function pageAiContextButtonSummary(...args) { return pageAiRenderApi().pageAiContextButtonSummary(...args); }
|
||
function pageAiContextRefDetail(...args) { return pageAiRenderApi().pageAiContextRefDetail(...args); }
|
||
function pageAiContextRefDisabled(...args) { return pageAiRenderApi().pageAiContextRefDisabled(...args); }
|
||
function pageAiContextRefLabel(...args) { return pageAiRenderApi().pageAiContextRefLabel(...args); }
|
||
function pageAiContextScopeLabel(...args) { return pageAiRenderApi().pageAiContextScopeLabel(...args); }
|
||
function pageAiCurrentAgentSelectionLabel(...args) { return pageAiRenderApi().pageAiCurrentAgentSelectionLabel(...args); }
|
||
function pageAiCurrentModelLabel(...args) { return pageAiRenderApi().pageAiCurrentModelLabel(...args); }
|
||
function pageAiFilteredSkillEntries(...args) { return pageAiRenderApi().pageAiFilteredSkillEntries(...args); }
|
||
function pageAiFormatChangedFiles(...args) { return pageAiRenderApi().pageAiFormatChangedFiles(...args); }
|
||
function pageAiMemoryFileLabel(...args) { return pageAiRenderApi().pageAiMemoryFileLabel(...args); }
|
||
function pageAiRenderAgentProfileOption(...args) { return pageAiRenderApi().pageAiRenderAgentProfileOption(...args); }
|
||
function pageAiRunStatusLabel(...args) { return pageAiRenderApi().pageAiRunStatusLabel(...args); }
|
||
function pageAiSuggestions(...args) { return pageAiRenderApi().pageAiSuggestions(...args); }
|
||
function pageAiTargetDetail(...args) { return pageAiRenderApi().pageAiTargetDetail(...args); }
|
||
function pageAiTargetLabel(...args) { return pageAiRenderApi().pageAiTargetLabel(...args); }
|
||
function renderPageAiControls(...args) { return pageAiRenderApi().renderPageAiControls(...args); }
|
||
function renderPageAiConversation(...args) { return pageAiRenderApi().renderPageAiConversation(...args); }
|
||
function renderPageAiProviderButtons(...args) { return pageAiRenderApi().renderPageAiProviderButtons(...args); }
|
||
function renderPageAiSuggestions(...args) { return pageAiRenderApi().renderPageAiSuggestions(...args); }
|
||
|
||
function readLocalEditorBlocks() {
|
||
var editor = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror');
|
||
if (!(editor instanceof HTMLElement)) return [];
|
||
return Array.from(editor.children).filter(function(node) {
|
||
return node instanceof HTMLElement;
|
||
}).map(function(node, index) {
|
||
var tag = String(node.tagName || '').toUpperCase();
|
||
var headingMatch = tag.match(/^H([1-6])$/);
|
||
var type = headingMatch ? 'heading' : 'paragraph';
|
||
var text = searchText(node.textContent || '');
|
||
var id = searchText(node.getAttribute('data-id') || node.id || 'local-block-' + index);
|
||
var props = headingMatch ? { level: Number(headingMatch[1]) } : {};
|
||
return { id: id, type: type, props: props, content: text };
|
||
}).filter(function(block) {
|
||
return block.content || block.type === 'heading';
|
||
});
|
||
}
|
||
|
||
function buildPageAiLocalSubtree(blocks, title) {
|
||
var documentId = currentDocumentId() || 'current-page';
|
||
var rootNodeId = 'page:' + documentId;
|
||
var headingCounters = [0, 0, 0, 0, 0, 0];
|
||
var headingStack = [];
|
||
var nodes = [{
|
||
id: rootNodeId,
|
||
nodeId: rootNodeId,
|
||
nodeType: 'page',
|
||
blockId: null,
|
||
blockType: 'page',
|
||
title: title || '',
|
||
parentNodeId: null,
|
||
headingLevel: null
|
||
}];
|
||
var outline = [];
|
||
var evidence = [];
|
||
blocks.forEach(function(block, index) {
|
||
var level = block.type === 'heading' ? Math.max(1, Math.min(6, Number(block.props && block.props.level || 1))) : null;
|
||
if (level != null) {
|
||
while (headingStack.length && headingStack[headingStack.length - 1].level >= level) headingStack.pop();
|
||
}
|
||
var parentNodeId = headingStack.length ? headingStack[headingStack.length - 1].nodeId : rootNodeId;
|
||
var nodeId = 'local-node:' + String(block.id || index);
|
||
var titleText = block.content || (block.type === 'heading' ? '未命名标题' : '');
|
||
nodes.push({
|
||
id: nodeId,
|
||
nodeId: nodeId,
|
||
nodeType: 'block',
|
||
blockId: block.id,
|
||
blockType: block.type,
|
||
title: titleText,
|
||
parentNodeId: parentNodeId,
|
||
headingLevel: level
|
||
});
|
||
if (level != null) {
|
||
headingCounters[level - 1] += 1;
|
||
for (var i = level; i < headingCounters.length; i += 1) headingCounters[i] = 0;
|
||
var numbering = headingCounters.slice(0, level).filter(function(count) { return count > 0; }).join('.');
|
||
outline.push({
|
||
id: 'local-outline:' + String(block.id || index),
|
||
nodeId: nodeId,
|
||
anchorBlockId: block.id,
|
||
level: level,
|
||
title: titleText,
|
||
numbering: numbering
|
||
});
|
||
headingStack.push({ level: level, nodeId: nodeId });
|
||
}
|
||
if (titleText) {
|
||
evidence.push({
|
||
id: 'local-evidence:' + String(block.id || index),
|
||
nodeId: nodeId,
|
||
anchorBlockId: block.id,
|
||
text: titleText,
|
||
kind: block.type
|
||
});
|
||
}
|
||
});
|
||
return {
|
||
projectionId: 'local-editor-dom:' + documentId,
|
||
rootNode: {
|
||
id: rootNodeId,
|
||
documentId: documentId,
|
||
title: title || '',
|
||
nodeType: 'page'
|
||
},
|
||
subtree: {
|
||
rootNodeId: rootNodeId,
|
||
nodes: nodes
|
||
},
|
||
outline: outline,
|
||
evidence: evidence,
|
||
stats: {
|
||
nodeCount: nodes.length,
|
||
headingCount: outline.length,
|
||
evidenceCount: evidence.length
|
||
},
|
||
source: 'local'
|
||
};
|
||
}
|
||
|
||
function currentPageAiContextSnapshot() {
|
||
var aggregate = currentPageAggregate();
|
||
var body = aggregate.body || {};
|
||
var serverSubtree = aggregate.tree && aggregate.tree.pageSubtree ? aggregate.tree.pageSubtree : null;
|
||
return {
|
||
aggregate: aggregate,
|
||
body: body,
|
||
subtree: serverSubtree,
|
||
pageSubtreeSource: serverSubtree ? 'server' : 'none'
|
||
};
|
||
}
|
||
|
||
function pageAiNormalizeAgentId(agentId) {
|
||
var value = String(agentId || '').trim();
|
||
return PAGE_AI_AGENT_REGISTRY.some(function(agent) { return agent.id === value; }) ? value : 'reasonix';
|
||
}
|
||
|
||
function pageAiAgentRecord(agentId) {
|
||
var normalized = pageAiNormalizeAgentId(agentId);
|
||
return PAGE_AI_AGENT_REGISTRY.find(function(agent) { return agent.id === normalized; }) || PAGE_AI_AGENT_REGISTRY[1];
|
||
}
|
||
|
||
function pageAiCurrentAgentId() {
|
||
var explicit = String(pageUiState.pageAiAgentId || '').trim();
|
||
if (explicit) return pageAiNormalizeAgentId(explicit);
|
||
var runtime = String(pageUiState.pageAiAcpRuntime || 'reasonix').trim();
|
||
if (runtime === 'hermes') return 'hermes';
|
||
return 'reasonix';
|
||
}
|
||
|
||
const pageAiProfileRuntime = createSidebarPageAiProfileRuntime({
|
||
chatOnlyProfileRegistry: PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY,
|
||
documentRef: document,
|
||
pageAiAgentRecord,
|
||
pageAiCurrentAgentId,
|
||
pageAiNormalizeAgentId,
|
||
pageUiState,
|
||
});
|
||
const pageAiProviderLabel = (...args) => pageAiProfileRuntime.pageAiProviderLabel(...args);
|
||
const pageAiNormalizeArray = (...args) => pageAiProfileRuntime.pageAiNormalizeArray(...args);
|
||
const pageAiDefaultAcpRuntimes = (...args) => pageAiProfileRuntime.pageAiDefaultAcpRuntimes(...args);
|
||
const pageAiNormalizeAcpRuntimes = (...args) => pageAiProfileRuntime.pageAiNormalizeAcpRuntimes(...args);
|
||
const pageAiUnwrapUpstream = (...args) => pageAiProfileRuntime.pageAiUnwrapUpstream(...args);
|
||
const pageAiProfileValue = (...args) => pageAiProfileRuntime.pageAiProfileValue(...args);
|
||
const pageAiCurrentProfile = (...args) => pageAiProfileRuntime.pageAiCurrentProfile(...args);
|
||
const pageAiRunProfile = (...args) => pageAiProfileRuntime.pageAiRunProfile(...args);
|
||
const pageAiMnoteToolModel = (...args) => pageAiProfileRuntime.pageAiMnoteToolModel(...args);
|
||
const pageAiCurrentProfileRecord = (...args) => pageAiProfileRuntime.pageAiCurrentProfileRecord(...args);
|
||
const pageAiChatOnlyProfileSpec = (...args) => pageAiProfileRuntime.pageAiChatOnlyProfileSpec(...args);
|
||
const pageAiDefaultChatOnlyProfileSpec = (...args) => pageAiProfileRuntime.pageAiDefaultChatOnlyProfileSpec(...args);
|
||
const pageAiNormalizeChatOnlyProfileId = (...args) => pageAiProfileRuntime.pageAiNormalizeChatOnlyProfileId(...args);
|
||
const pageAiProfileDisplayLabel = (...args) => pageAiProfileRuntime.pageAiProfileDisplayLabel(...args);
|
||
const pageAiProfileRecordById = (...args) => pageAiProfileRuntime.pageAiProfileRecordById(...args);
|
||
const pageAiSessionAgentFilterValue = (...args) => pageAiProfileRuntime.pageAiSessionAgentFilterValue(...args);
|
||
const pageAiSessionAgentLabel = (...args) => pageAiProfileRuntime.pageAiSessionAgentLabel(...args);
|
||
const pageAiSessionPreviewText = (...args) => pageAiProfileRuntime.pageAiSessionPreviewText(...args);
|
||
const pageAiSessionAgentFilterOptions = (...args) => pageAiProfileRuntime.pageAiSessionAgentFilterOptions(...args);
|
||
const pageAiFilteredHistoryRows = (...args) => pageAiProfileRuntime.pageAiFilteredHistoryRows(...args);
|
||
const pageAiTimestamp = (...args) => pageAiProfileRuntime.pageAiTimestamp(...args);
|
||
const pageAiUsageSummary = (...args) => pageAiProfileRuntime.pageAiUsageSummary(...args);
|
||
const pageAiPermissionRuntime = createSidebarPageAiPermissionRuntime({
|
||
documentRef: document,
|
||
pageAiPreviewValue,
|
||
pageUiState,
|
||
renderPageAiConversation,
|
||
});
|
||
const pageAiPermissionMessage = (...args) => pageAiPermissionRuntime.pageAiPermissionMessage(...args);
|
||
const pageAiApplyPermissionEvent = (...args) => pageAiPermissionRuntime.pageAiApplyPermissionEvent(...args);
|
||
const pageAiResolvePermission = (...args) => pageAiPermissionRuntime.pageAiResolvePermission(...args);
|
||
const pageAiHidePermissionDialog = (...args) => pageAiPermissionRuntime.pageAiHidePermissionDialog(...args);
|
||
const pageAiShowPermissionDialog = (...args) => pageAiPermissionRuntime.pageAiShowPermissionDialog(...args);
|
||
const pageAiSessionRuntime = createSidebarPageAiSessionRuntime({
|
||
currentDocumentId,
|
||
currentRootUri,
|
||
currentSourceKind,
|
||
documentRef: document,
|
||
pageAiApplyRuntimeState,
|
||
pageAiCurrentAgentId,
|
||
pageAiCurrentProfile,
|
||
pageAiErrorMessage,
|
||
pageAiNormalizeAgentId,
|
||
pageAiNormalizeArray,
|
||
pageAiNormalizeChatOnlyProfileId,
|
||
pageAiPermissionMessage,
|
||
pageAiPreviewValue,
|
||
pageAiRunProfile,
|
||
pageAiSetActiveProfile,
|
||
pageAiTimestamp,
|
||
pageUiState,
|
||
renderPageAiControls,
|
||
renderPageAiConversation,
|
||
resolveWorkspaceId,
|
||
sessionStorageVersion: PAGE_AI_SESSION_STORAGE_VERSION,
|
||
windowRef: window,
|
||
});
|
||
const pageAiStorageKey = (...args) => pageAiSessionRuntime.pageAiStorageKey(...args);
|
||
const pageAiBackendSessionQuery = (...args) => pageAiSessionRuntime.pageAiBackendSessionQuery(...args);
|
||
const pageAiNewSession = (...args) => pageAiSessionRuntime.pageAiNewSession(...args);
|
||
const pageAiNormalizeSessions = (...args) => pageAiSessionRuntime.pageAiNormalizeSessions(...args);
|
||
const pageAiNormalizeBackendSessionRow = (...args) => pageAiSessionRuntime.pageAiNormalizeBackendSessionRow(...args);
|
||
const pageAiMergeSessions = (...args) => pageAiSessionRuntime.pageAiMergeSessions(...args);
|
||
const pageAiSessionStorageLabel = (...args) => pageAiSessionRuntime.pageAiSessionStorageLabel(...args);
|
||
const pageAiLoadSessions = (...args) => pageAiSessionRuntime.pageAiLoadSessions(...args);
|
||
const pageAiLoadBackendSessions = (...args) => pageAiSessionRuntime.pageAiLoadBackendSessions(...args);
|
||
const pageAiMessageFromRuntimeEvent = (...args) => pageAiSessionRuntime.pageAiMessageFromRuntimeEvent(...args);
|
||
const pageAiApplyBackendSessionDetail = (...args) => pageAiSessionRuntime.pageAiApplyBackendSessionDetail(...args);
|
||
const pageAiLoadBackendSessionDetail = (...args) => pageAiSessionRuntime.pageAiLoadBackendSessionDetail(...args);
|
||
const pageAiSearchBackendSessions = (...args) => pageAiSessionRuntime.pageAiSearchBackendSessions(...args);
|
||
const pageAiPersistSessions = (...args) => pageAiSessionRuntime.pageAiPersistSessions(...args);
|
||
const pageAiEnsureHermesSession = (...args) => pageAiSessionRuntime.pageAiEnsureHermesSession(...args);
|
||
const pageAiRestoreHermesSession = (...args) => pageAiSessionRuntime.pageAiRestoreHermesSession(...args);
|
||
const pageAiCurrentSession = (...args) => pageAiSessionRuntime.pageAiCurrentSession(...args);
|
||
const pageAiSyncCurrentSessionMessages = (...args) => pageAiSessionRuntime.pageAiSyncCurrentSessionMessages(...args);
|
||
const pageAiSetActiveSession = (...args) => pageAiSessionRuntime.pageAiSetActiveSession(...args);
|
||
const pageAiStartNewSession = (...args) => pageAiSessionRuntime.pageAiStartNewSession(...args);
|
||
const pageAiRenameBackendSession = (...args) => pageAiSessionRuntime.pageAiRenameBackendSession(...args);
|
||
const pageAiDeleteBackendSession = (...args) => pageAiSessionRuntime.pageAiDeleteBackendSession(...args);
|
||
const pageAiResumeBackendSession = (...args) => pageAiSessionRuntime.pageAiResumeBackendSession(...args);
|
||
const pageAiSkillRuntime = createSidebarPageAiSkillRuntime({
|
||
pageAiCurrentAgentId,
|
||
pageAiCurrentProfile,
|
||
pageAiLoadSkills,
|
||
pageAiNormalizeArray,
|
||
pageAiPersistAiPreference,
|
||
pageAiPersistRawAiPreference,
|
||
pageAiProfileValue,
|
||
pageUiState,
|
||
renderPageAiControls,
|
||
});
|
||
const pageAiSkillSourceOptions = (...args) => pageAiSkillRuntime.pageAiSkillSourceOptions(...args);
|
||
const pageAiDefaultSkillSource = (...args) => pageAiSkillRuntime.pageAiDefaultSkillSource(...args);
|
||
const pageAiNormalizeSkillSource = (...args) => pageAiSkillRuntime.pageAiNormalizeSkillSource(...args);
|
||
const pageAiCurrentSkillSource = (...args) => pageAiSkillRuntime.pageAiCurrentSkillSource(...args);
|
||
const pageAiSetSkillSource = (...args) => pageAiSkillRuntime.pageAiSetSkillSource(...args);
|
||
const pageAiSkillSourceParts = (...args) => pageAiSkillRuntime.pageAiSkillSourceParts(...args);
|
||
const pageAiCurrentSkillSourceLabel = (...args) => pageAiSkillRuntime.pageAiCurrentSkillSourceLabel(...args);
|
||
const pageAiSkillOriginLabel = (...args) => pageAiSkillRuntime.pageAiSkillOriginLabel(...args);
|
||
const pageAiSkillPreferenceKey = (...args) => pageAiSkillRuntime.pageAiSkillPreferenceKey(...args);
|
||
const pageAiSkillPreferenceTable = (...args) => pageAiSkillRuntime.pageAiSkillPreferenceTable(...args);
|
||
const pageAiHermesHideBuiltinPreferenceKey = (...args) => pageAiSkillRuntime.pageAiHermesHideBuiltinPreferenceKey(...args);
|
||
const pageAiHideHermesBuiltinSkills = (...args) => pageAiSkillRuntime.pageAiHideHermesBuiltinSkills(...args);
|
||
const pageAiReasonixMemoryEnabled = (...args) => pageAiSkillRuntime.pageAiReasonixMemoryEnabled(...args);
|
||
const pageAiSetReasonixMemoryEnabled = (...args) => pageAiSkillRuntime.pageAiSetReasonixMemoryEnabled(...args);
|
||
const pageAiSetHideHermesBuiltinSkills = (...args) => pageAiSkillRuntime.pageAiSetHideHermesBuiltinSkills(...args);
|
||
const pageAiSkillIsBuiltin = (...args) => pageAiSkillRuntime.pageAiSkillIsBuiltin(...args);
|
||
const pageAiToggleableSkillEntries = (...args) => pageAiSkillRuntime.pageAiToggleableSkillEntries(...args);
|
||
const pageAiAllSkillEntries = (...args) => pageAiSkillRuntime.pageAiAllSkillEntries(...args);
|
||
const pageAiSkillGroupCollapsed = (...args) => pageAiSkillRuntime.pageAiSkillGroupCollapsed(...args);
|
||
const pageAiToggleSkillGroup = (...args) => pageAiSkillRuntime.pageAiToggleSkillGroup(...args);
|
||
const pageAiSetSkillPreference = (...args) => pageAiSkillRuntime.pageAiSetSkillPreference(...args);
|
||
const pageAiSkillEnabled = (...args) => pageAiSkillRuntime.pageAiSkillEnabled(...args);
|
||
|
||
function pageAiEnsureContextRefState() {
|
||
if (!pageUiState.pageAiSelectedContextRefs || typeof pageUiState.pageAiSelectedContextRefs !== 'object') {
|
||
pageUiState.pageAiSelectedContextRefs = {
|
||
current_page: true,
|
||
selection: false,
|
||
active_editor: true,
|
||
file: false,
|
||
folder: false,
|
||
changed_files: false
|
||
};
|
||
}
|
||
return pageUiState.pageAiSelectedContextRefs;
|
||
}
|
||
|
||
const pageAiTargetRuntime = createSidebarPageAiTargetRuntime({
|
||
currentDocumentId,
|
||
currentPageOptions,
|
||
currentRootUri,
|
||
currentSourceKind,
|
||
documentRef: document,
|
||
escapeHtml,
|
||
pageAiEnsureContextRefState,
|
||
pageAiNormalizeArray,
|
||
pageUiState,
|
||
resolveWorkspaceId,
|
||
searchText,
|
||
});
|
||
const currentPageAiOpenEditorsSnapshot = (...args) => pageAiTargetRuntime.currentPageAiOpenEditorsSnapshot(...args);
|
||
const currentPageAiEditorTarget = (...args) => pageAiTargetRuntime.currentPageAiEditorTarget(...args);
|
||
const currentPageAiPageEditorTarget = (...args) => pageAiTargetRuntime.currentPageAiPageEditorTarget(...args);
|
||
const currentPageAiScopedEditorTarget = (...args) => pageAiTargetRuntime.currentPageAiScopedEditorTarget(...args);
|
||
const pageAiCloneJson = (...args) => pageAiTargetRuntime.pageAiCloneJson(...args);
|
||
const pageAiEditorTargetCandidates = (...args) => pageAiTargetRuntime.pageAiEditorTargetCandidates(...args);
|
||
const pageAiFallbackEditorTarget = (...args) => pageAiTargetRuntime.pageAiFallbackEditorTarget(...args);
|
||
const pageAiResourceKindForTarget = (...args) => pageAiTargetRuntime.pageAiResourceKindForTarget(...args);
|
||
const pageAiTargetFromOpenEditor = (...args) => pageAiTargetRuntime.pageAiTargetFromOpenEditor(...args);
|
||
const pageAiTargetId = (...args) => pageAiTargetRuntime.pageAiTargetId(...args);
|
||
const localMarkdownRelativePathFromPageAiDocumentId = (...args) => pageAiTargetRuntime.localMarkdownRelativePathFromPageAiDocumentId(...args);
|
||
const localMarkdownDocumentIdFromPageAiRelativePath = (...args) => pageAiTargetRuntime.localMarkdownDocumentIdFromPageAiRelativePath(...args);
|
||
const pageAiWorkspacePathForDocument = (...args) => pageAiTargetRuntime.pageAiWorkspacePathForDocument(...args);
|
||
const pageAiWorkspacePathForTarget = (...args) => pageAiTargetRuntime.pageAiWorkspacePathForTarget(...args);
|
||
const pageAiBuildAllowedRoots = (...args) => pageAiTargetRuntime.pageAiBuildAllowedRoots(...args);
|
||
const pageAiBuildContextRefs = (...args) => pageAiTargetRuntime.pageAiBuildContextRefs(...args);
|
||
const pageAiBuildRunTargetSnapshot = (...args) => pageAiTargetRuntime.pageAiBuildRunTargetSnapshot(...args);
|
||
const pageAiPageContextForRefs = (...args) => pageAiTargetRuntime.pageAiPageContextForRefs(...args);
|
||
const pageAiSetRunTargetSnapshot = (...args) => pageAiTargetRuntime.pageAiSetRunTargetSnapshot(...args);
|
||
const assertPageAiTargetInCurrentWorkspace = (...args) => pageAiTargetRuntime.assertPageAiTargetInCurrentWorkspace(...args);
|
||
const pageAiBuildAgentTargetPackage = (...args) => pageAiTargetRuntime.pageAiBuildAgentTargetPackage(...args);
|
||
const pageAiBlockingDirtyState = (...args) => pageAiTargetRuntime.pageAiBlockingDirtyState(...args);
|
||
const fetchPageAiTargetBufferState = (...args) => pageAiTargetRuntime.fetchPageAiTargetBufferState(...args);
|
||
const assertPageAiTargetWritable = (...args) => pageAiTargetRuntime.assertPageAiTargetWritable(...args);
|
||
const currentPageAiSelectedText = (...args) => pageAiTargetRuntime.currentPageAiSelectedText(...args);
|
||
const pageAiProjectionBlocks = (...args) => pageAiTargetRuntime.pageAiProjectionBlocks(...args);
|
||
const pageAiBlockText = (...args) => pageAiTargetRuntime.pageAiBlockText(...args);
|
||
const pageAiSelectedBlockIdsFromSelection = (...args) => pageAiTargetRuntime.pageAiSelectedBlockIdsFromSelection(...args);
|
||
const pageAiBlocksToPageXml = (...args) => pageAiTargetRuntime.pageAiBlocksToPageXml(...args);
|
||
const buildPageAiContext = (...args) => pageAiTargetRuntime.buildPageAiContext(...args);
|
||
const pageAiScopedPageContext = (...args) => pageAiTargetRuntime.pageAiScopedPageContext(...args);
|
||
pageAiRenderRuntime = createSidebarPageAiRenderRuntime({
|
||
contextRefRegistry: PAGE_AI_CONTEXT_REF_REGISTRY,
|
||
cssEscape,
|
||
currentDocumentId,
|
||
currentPageAiEditorTarget,
|
||
currentPageAiSelectedText,
|
||
documentRef: document,
|
||
escapeHtml,
|
||
localMarkdownRelativePathFromPageAiDocumentId,
|
||
pageAiAgentRecord,
|
||
pageAiAllSkillEntries,
|
||
pageAiBuildAllowedRoots,
|
||
pageAiChatOnlyProfileEntries,
|
||
pageAiChatOnlyProfileSpec,
|
||
pageAiCurrentAgentId,
|
||
pageAiCurrentProfile,
|
||
pageAiCurrentProfileRecord,
|
||
pageAiCurrentSession,
|
||
pageAiCurrentSkillSource,
|
||
pageAiCurrentSkillSourceLabel,
|
||
pageAiDefaultChatOnlyProfileSpec,
|
||
pageAiEditorTargetCandidates,
|
||
pageAiEnsureContextRefState,
|
||
pageAiFilteredHistoryRows,
|
||
pageAiHermesProfileEntries,
|
||
pageAiHermesSettingsUrl,
|
||
pageAiHideHermesBuiltinSkills,
|
||
pageAiMnoteToolModel,
|
||
pageAiNormalizeAcpRuntimes,
|
||
pageAiNormalizeArray,
|
||
pageAiNormalizeSkillSource,
|
||
pageAiProfileDisplayLabel,
|
||
pageAiProfileValue,
|
||
pageAiProviderLabel,
|
||
pageAiReasonixMemoryEnabled,
|
||
pageAiRunProfile,
|
||
pageAiSessionAgentFilterOptions,
|
||
pageAiSessionAgentFilterValue,
|
||
pageAiSessionAgentLabel,
|
||
pageAiSessionPreviewText,
|
||
pageAiSessionStorageLabel,
|
||
pageAiSkillEnabled,
|
||
pageAiSkillGroupCollapsed,
|
||
pageAiSkillListEntries,
|
||
pageAiSkillOriginLabel,
|
||
pageAiSkillSourceOptions,
|
||
pageAiSkillSourceParts,
|
||
pageAiToggleableSkillEntries,
|
||
pageAiUsageSummary,
|
||
pageUiState,
|
||
renderPageAiMarkdown,
|
||
searchText,
|
||
});
|
||
|
||
function pageAiSetAgentId(agentId) {
|
||
var next = pageAiNormalizeAgentId(agentId);
|
||
var record = pageAiAgentRecord(next);
|
||
pageUiState.pageAiAgentId = next;
|
||
pageUiState.pageAiAcpRuntime = record.acpRuntime || 'reasonix';
|
||
pageUiState.pageAiAgentPopoverOpen = next === 'hermes';
|
||
if (next === 'hermes') pageAiSetActiveProfile(pageAiCurrentProfile());
|
||
if (next === 'chat_only') pageAiSetActiveProfile(pageAiNormalizeChatOnlyProfileId(pageAiCurrentProfile()));
|
||
document.documentElement.setAttribute('data-mnote-page-ai-agent-id', next);
|
||
pageAiPersistAiPreference('default_agent_id', next);
|
||
void pageAiLoadSkills();
|
||
renderPageAiProviderButtons();
|
||
renderPageAiControls();
|
||
}
|
||
|
||
function pageAiToggleContextRef(kind) {
|
||
var normalized = String(kind || '').trim();
|
||
if (!PAGE_AI_CONTEXT_REF_REGISTRY.some(function(ref) { return ref.id === normalized; })) return;
|
||
var selected = pageAiEnsureContextRefState();
|
||
selected[normalized] = !selected[normalized];
|
||
if (!Object.keys(selected).some(function(key) { return selected[key]; })) selected.current_page = true;
|
||
pageAiPersistAiPreference('context_refs.default_selected', selected);
|
||
renderPageAiControls();
|
||
}
|
||
|
||
function pageAiSetContextPopoverOpen(open) {
|
||
pageUiState.pageAiContextPopoverOpen = Boolean(open);
|
||
if (open) {
|
||
pageUiState.pageAiAgentPopoverOpen = false;
|
||
pageUiState.pageAiTargetPopoverOpen = false;
|
||
}
|
||
renderPageAiControls();
|
||
}
|
||
|
||
function pageAiSetAgentPopoverOpen(open) {
|
||
pageUiState.pageAiAgentPopoverOpen = Boolean(open);
|
||
if (open) {
|
||
pageUiState.pageAiContextPopoverOpen = false;
|
||
pageUiState.pageAiTargetPopoverOpen = false;
|
||
}
|
||
renderPageAiControls();
|
||
}
|
||
|
||
function pageAiSetTargetPopoverOpen(open) {
|
||
pageUiState.pageAiTargetPopoverOpen = Boolean(open);
|
||
if (open) {
|
||
pageUiState.pageAiAgentPopoverOpen = false;
|
||
pageUiState.pageAiContextPopoverOpen = false;
|
||
}
|
||
renderPageAiControls();
|
||
}
|
||
|
||
function pageAiSelectTarget(targetId) {
|
||
var normalized = String(targetId || '').trim();
|
||
var candidates = pageAiEditorTargetCandidates();
|
||
var target = candidates.find(function(candidate) { return candidate.targetId === normalized; }) || null;
|
||
if (!target) return;
|
||
pageUiState.pageAiSelectedTargetId = target.targetId;
|
||
pageUiState.pageAiTargetPopoverOpen = false;
|
||
renderPageAiControls();
|
||
}
|
||
|
||
|
||
function pageAiPersistAiPreference(key, value) {
|
||
pageAiPersistRawAiPreference('ai.common.' + key, value);
|
||
}
|
||
|
||
function pageAiPersistRawAiPreference(key, value) {
|
||
try {
|
||
var workspaceId = resolveWorkspaceId(document.body);
|
||
var body = {
|
||
workspaceId: workspaceId,
|
||
sourceKind: currentSourceKind(),
|
||
rootUri: currentRootUri(),
|
||
documentId: currentDocumentId(),
|
||
updates: {}
|
||
};
|
||
body.updates[key] = value;
|
||
void fetch('/api/ui/preferences', {
|
||
method: 'PUT',
|
||
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
||
body: JSON.stringify(body)
|
||
}).catch(function() {});
|
||
} catch (_) {}
|
||
}
|
||
|
||
async function pageAiLoadAiPreferences() {
|
||
try {
|
||
var url = new URL('/api/ui/preferences/effective', window.location.origin);
|
||
url.searchParams.set('workspaceId', resolveWorkspaceId(document.body));
|
||
url.searchParams.set('sourceKind', currentSourceKind());
|
||
url.searchParams.set('rootUri', currentRootUri());
|
||
url.searchParams.set('documentId', currentDocumentId());
|
||
var response = await fetch(url.toString(), { headers: { accept: 'application/json' }, cache: 'no-store' });
|
||
var payload = await response.json().catch(function() { return null; });
|
||
if (!response.ok || !payload || !payload.result) return;
|
||
var preferences = payload.result.aiPreferences && typeof payload.result.aiPreferences === 'object'
|
||
? payload.result.aiPreferences
|
||
: {};
|
||
pageUiState.pageAiSkillPreferences = Object.assign({}, preferences);
|
||
var defaultAgent = String(preferences['ai.common.default_agent_id'] || '').trim();
|
||
if (defaultAgent) {
|
||
var agent = pageAiAgentRecord(defaultAgent);
|
||
pageUiState.pageAiAgentId = agent.id;
|
||
pageUiState.pageAiAcpRuntime = agent.acpRuntime || pageUiState.pageAiAcpRuntime || 'reasonix';
|
||
}
|
||
var hermesProfile = String(preferences['ai.agent.hermes.profile_id'] || '').trim();
|
||
if (hermesProfile) pageAiSetActiveProfile(hermesProfile);
|
||
var skillSource = String(preferences['ai.common.skills.active_source'] || '').trim();
|
||
if (skillSource) pageUiState.pageAiActiveSkillSource = skillSource;
|
||
var selected = preferences['ai.common.context_refs.default_selected'];
|
||
if (selected && typeof selected === 'object' && !Array.isArray(selected)) {
|
||
pageUiState.pageAiSelectedContextRefs = Object.assign({}, pageAiEnsureContextRefState(), selected);
|
||
}
|
||
var collapsedSkillGroups = preferences['ai.common.skills.groups.collapsed'];
|
||
if (collapsedSkillGroups && typeof collapsedSkillGroups === 'object' && !Array.isArray(collapsedSkillGroups)) {
|
||
pageUiState.pageAiCollapsedSkillGroups = Object.assign({}, collapsedSkillGroups);
|
||
}
|
||
} catch (_) {}
|
||
renderPageAiControls();
|
||
}
|
||
|
||
function pageAiNormalizeAccessGrant(grant) {
|
||
if (!grant || typeof grant !== 'object') return null;
|
||
var rootUri = String(grant.rootUri || grant.root_uri || '').trim();
|
||
if (!rootUri) return null;
|
||
var permission = String(grant.permission || grant.access || '').trim() || 'read';
|
||
return {
|
||
id: String(grant.id || '').trim(),
|
||
rootUri: rootUri,
|
||
rootPath: String(grant.rootPath || grant.root_path || '').trim(),
|
||
permission: permission === 'read_write' ? 'write' : permission,
|
||
recursive: grant.recursive !== false,
|
||
source: String(grant.source || 'sqlite_directory_grant').trim() || 'sqlite_directory_grant',
|
||
status: String(grant.status || 'active').trim() || 'active'
|
||
};
|
||
}
|
||
|
||
async function pageAiLoadAllowedRoots() {
|
||
if (currentSourceKind() !== 'local_folder') {
|
||
pageUiState.pageAiAllowedRoots = [];
|
||
renderPageAiControls();
|
||
return [];
|
||
}
|
||
try {
|
||
var response = await fetch('/api/user/access-policy', {
|
||
headers: { accept: 'application/json' },
|
||
cache: 'no-store'
|
||
});
|
||
var payload = await response.json().catch(function() { return null; });
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'access_policy_failed_' + response.status));
|
||
var grants = pageAiNormalizeArray(payload && (payload.grants || payload.policy && payload.policy.grants));
|
||
var currentRoot = String(currentRootUri() || '').trim();
|
||
pageUiState.pageAiAllowedRoots = grants.map(pageAiNormalizeAccessGrant).filter(Boolean).filter(function(grant) {
|
||
return grant.status === 'active' && (!currentRoot || grant.rootUri === currentRoot);
|
||
});
|
||
pageUiState.pageAiAllowedRootsError = '';
|
||
} catch (error) {
|
||
pageUiState.pageAiAllowedRoots = [];
|
||
pageUiState.pageAiAllowedRootsError = error instanceof Error ? error.message : String(error);
|
||
}
|
||
renderPageAiControls();
|
||
return pageUiState.pageAiAllowedRoots;
|
||
}
|
||
|
||
function updatePageAiTriggerState() {
|
||
var trigger = document.querySelector('[data-testid="wolai-floating-ai"]');
|
||
if (!(trigger instanceof HTMLElement)) return;
|
||
trigger.setAttribute('data-state', pageUiState.pageAiOpen ? 'open' : 'closed');
|
||
trigger.setAttribute('aria-expanded', pageUiState.pageAiOpen ? 'true' : 'false');
|
||
}
|
||
|
||
|
||
|
||
function pageAiOpenLocation(loc) {
|
||
var path = String(loc || '').trim();
|
||
if (!path) return;
|
||
openLocalResourceInActiveTab({ path: path }).then(function(opened) {
|
||
if (!opened) {
|
||
var href = buildLocalFileOpenUrl(path, false);
|
||
if (href) window.open(href, '_blank', 'noopener,noreferrer');
|
||
}
|
||
});
|
||
}
|
||
|
||
function pageAiEvidenceRangeFromParam(value) {
|
||
var text = String(value || '').trim();
|
||
if (!text) return null;
|
||
var match = text.match(/^(\d+)[-:,](\d+)$/);
|
||
if (!match) return text;
|
||
return { start: Number(match[1]), end: Number(match[2]) };
|
||
}
|
||
|
||
function pageAiEvidenceBboxFromParam(value) {
|
||
var text = String(value || '').trim();
|
||
if (!text) return null;
|
||
var parts = text.split(',').map(function(part) { return Number(part.trim()); });
|
||
if (parts.length < 4 || parts.slice(0, 4).some(function(part) { return !Number.isFinite(part); })) return text;
|
||
return { x0: parts[0], y0: parts[1], x1: parts[2], y1: parts[3] };
|
||
}
|
||
|
||
function pageAiDocumentIdFromUrl(url) {
|
||
var prefix = '/documents/';
|
||
if (!url.pathname.startsWith(prefix)) return '';
|
||
try {
|
||
var raw = url.pathname.slice(prefix.length).split('/')[0] || '';
|
||
var decoded = decodeURIComponent(raw);
|
||
if (decoded.indexOf('%') >= 0 || decoded.indexOf('\uFFFD') >= 0) return '';
|
||
return decoded;
|
||
} catch (_error) {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
function pageAiCitationResourcePath(url, rootUri) {
|
||
var explicitPath = String(url.searchParams.get('resourcePath') || '').trim();
|
||
if (explicitPath) return explicitPath;
|
||
var raw = String(url.searchParams.get('resourceTab') || '').trim();
|
||
if (!raw) {
|
||
try {
|
||
var decodedHash = decodeURIComponent(String(url.hash || ''));
|
||
var marker = '#resource-tab-';
|
||
var markerIndex = decodedHash.indexOf(marker);
|
||
if (markerIndex >= 0) raw = decodedHash.slice(markerIndex + marker.length).replace(/^#+/, '').trim();
|
||
} catch (_error) {}
|
||
}
|
||
if (raw.indexOf('::') >= 0) raw = raw.slice(raw.indexOf('::') + 2);
|
||
if (!raw.startsWith('resource:file:')) return '';
|
||
var rest = raw.slice('resource:file:'.length);
|
||
var prefix = String(rootUri || '').trim() + ':';
|
||
if (!prefix.trim() || !rest.startsWith(prefix)) return '';
|
||
return rest.slice(prefix.length).replace(/^\/+/, '');
|
||
}
|
||
|
||
function pageAiSameMnoteOrigin(url) {
|
||
try {
|
||
var current = new URL(window.location.origin);
|
||
if (url.origin === current.origin) return true;
|
||
if (url.hostname === 'mnote.local') return true;
|
||
var localNames = ['localhost', '127.0.0.1', '::1', 'mnote.local'];
|
||
function defaultPort(protocol) {
|
||
return protocol === 'https:' ? '443' : protocol === 'http:' ? '80' : '';
|
||
}
|
||
return localNames.indexOf(url.hostname) >= 0 &&
|
||
localNames.indexOf(current.hostname) >= 0 &&
|
||
String(url.port || defaultPort(url.protocol)) === String(current.port || defaultPort(current.protocol));
|
||
} catch (_error) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function pageAiPortableCitationUrl(url) {
|
||
try {
|
||
if (url.pathname === '/api/local-folder/files/open') {
|
||
return Boolean(String(url.searchParams.get('rootUri') || '').trim()) &&
|
||
Boolean(String(url.searchParams.get('path') || '').trim());
|
||
}
|
||
if (!url.pathname.startsWith('/documents/')) return false;
|
||
var resourceTab = String(url.searchParams.get('resourceTab') || '').trim();
|
||
return String(url.searchParams.get('sourceKind') || '').trim() === 'local_folder' ||
|
||
Boolean(String(url.searchParams.get('rootUri') || '').trim()) ||
|
||
Boolean(String(url.searchParams.get('resourcePath') || '').trim()) ||
|
||
resourceTab.startsWith('resource:file:');
|
||
} catch (_error) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function pageAiOpenCitationUrl(href) {
|
||
var url;
|
||
try {
|
||
url = new URL(String(href || ''), window.location.origin);
|
||
} catch (_error) {
|
||
return false;
|
||
}
|
||
var unwrappedHref = pageAiUnwrapSearchCitationUrl(url);
|
||
if (unwrappedHref) return pageAiOpenCitationUrl(unwrappedHref);
|
||
if (!pageAiSameMnoteOrigin(url) && !pageAiPortableCitationUrl(url)) return false;
|
||
if (url.pathname === '/api/local-folder/files/open') {
|
||
var fileRootUri = String(url.searchParams.get('rootUri') || currentRootUri() || '').trim();
|
||
var filePath = String(url.searchParams.get('path') || '').trim();
|
||
if (!filePath) return false;
|
||
void openLocalResourceInActiveTab({
|
||
path: filePath,
|
||
rootUri: fileRootUri,
|
||
documentId: currentDocumentId() || '',
|
||
workspaceId: resolveWorkspaceId(document.body) || '',
|
||
sourceKind: currentSourceKind() || 'local_folder',
|
||
page: url.searchParams.get('page') || undefined,
|
||
bbox: pageAiEvidenceBboxFromParam(url.searchParams.get('bbox')),
|
||
sourceMapPath: String(url.searchParams.get('sourceMapPath') || '').trim(),
|
||
blockId: String(url.searchParams.get('blockId') || '').trim(),
|
||
evidenceText: String(url.searchParams.get('evidenceText') || '').trim(),
|
||
lineRange: pageAiEvidenceRangeFromParam(url.searchParams.get('lineRange')),
|
||
charRange: pageAiEvidenceRangeFromParam(url.searchParams.get('charRange')),
|
||
openTarget: 'active-tab',
|
||
paneRole: 'primary'
|
||
}).then(function(opened) {
|
||
if (!opened) window.open(url.toString(), '_blank', 'noopener,noreferrer');
|
||
});
|
||
return true;
|
||
}
|
||
if (!url.pathname.startsWith('/documents/')) return false;
|
||
var rootUri = String(url.searchParams.get('rootUri') || currentRootUri() || '').trim();
|
||
var resourcePath = pageAiCitationResourcePath(url, rootUri);
|
||
var documentId = pageAiDocumentIdFromUrl(url) || currentDocumentId() || '';
|
||
var workspaceId = String(url.searchParams.get('workspaceId') || resolveWorkspaceId(document.body) || '').trim();
|
||
if (resourcePath) {
|
||
void openLocalResourceInActiveTab({
|
||
path: resourcePath,
|
||
rootUri: rootUri,
|
||
documentId: documentId,
|
||
workspaceId: workspaceId,
|
||
sourceKind: String(url.searchParams.get('sourceKind') || currentSourceKind() || 'local_folder').trim(),
|
||
page: url.searchParams.get('page') || undefined,
|
||
bbox: pageAiEvidenceBboxFromParam(url.searchParams.get('bbox')),
|
||
sourceMapPath: String(url.searchParams.get('sourceMapPath') || '').trim(),
|
||
blockId: String(url.searchParams.get('blockId') || '').trim(),
|
||
evidenceText: String(url.searchParams.get('evidenceText') || '').trim(),
|
||
lineRange: pageAiEvidenceRangeFromParam(url.searchParams.get('lineRange')),
|
||
charRange: pageAiEvidenceRangeFromParam(url.searchParams.get('charRange')),
|
||
openTarget: 'active-tab',
|
||
paneRole: 'primary'
|
||
}).then(function(opened) {
|
||
if (!opened) window.location.assign(url.pathname + url.search + url.hash);
|
||
});
|
||
return true;
|
||
}
|
||
if (typeof window.__mnoteDocumentPaneRuntime?.openPrimaryDocument === 'function') {
|
||
void window.__mnoteDocumentPaneRuntime.openPrimaryDocument({
|
||
documentId: documentId,
|
||
workspaceId: workspaceId,
|
||
sourceKind: String(url.searchParams.get('sourceKind') || currentSourceKind() || '').trim(),
|
||
rootUri: rootUri,
|
||
url: url
|
||
});
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function pageAiChatOnlyProfileEntries() {
|
||
var profiles = pageAiNormalizeArray(pageUiState.pageAiProfiles);
|
||
return PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY.map(function(spec) {
|
||
var profile = profiles.find(function(candidate) {
|
||
var candidateSpec = pageAiChatOnlyProfileSpec(candidate);
|
||
return candidateSpec && candidateSpec.profileId === spec.profileId;
|
||
});
|
||
return Object.assign({}, profile || {
|
||
profileId: spec.profileId,
|
||
name: spec.profileId,
|
||
alias: spec.label,
|
||
kind: 'shared',
|
||
baseProfile: spec.baseProfile,
|
||
providerKind: spec.providerKind || '',
|
||
readonly: true
|
||
}, { menuLabel: spec.label });
|
||
});
|
||
}
|
||
|
||
function pageAiHermesProfileEntries() {
|
||
var profiles = pageAiNormalizeArray(pageUiState.pageAiProfiles).filter(function(profile) {
|
||
return !pageAiChatOnlyProfileSpec(profile);
|
||
});
|
||
if (profiles.length) return profiles;
|
||
return [{ profileId: pageAiCurrentProfile(), name: pageAiCurrentProfile(), alias: pageAiCurrentProfile(), kind: 'personal' }];
|
||
}
|
||
|
||
|
||
function pageAiNormalizeProfiles(payload) {
|
||
var upstream = pageAiUnwrapUpstream(payload);
|
||
var profiles = pageAiNormalizeArray(upstream && upstream.profiles ? upstream.profiles : upstream);
|
||
return profiles.map(function(profile) {
|
||
var profileId = String(profile && (profile.profileId || profile.id || profile.name) || '').trim();
|
||
var kind = String(profile && profile.kind || profile && profile.profileKind || '').trim();
|
||
var canManageSkills = profile && profile.canManageSkills !== false;
|
||
return {
|
||
profileId: profileId || pageAiProfileValue(profile) || 'default',
|
||
name: profileId || pageAiProfileValue(profile) || 'default',
|
||
active: Boolean(profile && profile.active),
|
||
model: String(profile && profile.model || '').trim(),
|
||
gateway: String(profile && profile.gateway || '').trim(),
|
||
alias: String(profile && (profile.displayName || profile.alias) || '').trim(),
|
||
kind: kind,
|
||
ownerUserId: String(profile && profile.ownerUserId || '').trim(),
|
||
baseProfile: String(profile && profile.baseProfile || '').trim(),
|
||
isolatedProfile: String(profile && profile.isolatedProfile || '').trim(),
|
||
providerKind: String(profile && profile.providerKind || profile && profile.provider_kind || '').trim(),
|
||
canRun: profile ? profile.canRun !== false : true,
|
||
canManageSkills: canManageSkills,
|
||
canManageConfig: profile ? profile.canManageConfig !== false : canManageSkills,
|
||
readonly: profile ? (profile.readonly === true || !canManageSkills) : false,
|
||
grantRole: String(profile && profile.grantRole || '').trim()
|
||
};
|
||
});
|
||
}
|
||
|
||
function pageAiNormalizeSkills(payload) {
|
||
var upstream = pageAiUnwrapUpstream(payload);
|
||
var categories = pageAiNormalizeArray(upstream && upstream.categories ? upstream.categories : []);
|
||
var archived = pageAiNormalizeArray(upstream && upstream.archived ? upstream.archived : []);
|
||
return {
|
||
categories: categories.map(function(category) {
|
||
var skillRows = pageAiNormalizeArray(category && (category.skills || category.capabilities));
|
||
return {
|
||
name: String(category && category.name || '').trim() || 'misc',
|
||
description: String(category && category.description || '').trim(),
|
||
title: String(category && category.title || '').trim(),
|
||
capabilities: pageAiNormalizeArray(category && category.capabilities),
|
||
skills: skillRows.map(function(skill) {
|
||
return {
|
||
name: String(skill && skill.name || '').trim(),
|
||
id: String(skill && skill.id || skill && skill.name || '').trim(),
|
||
title: String(skill && skill.title || skill && skill.name || skill && skill.id || '').trim(),
|
||
description: String(skill && skill.description || '').trim(),
|
||
enabled: skill && skill.enabled !== false,
|
||
toggleable: skill && skill.toggleable !== false,
|
||
source: String(skill && skill.source || 'local').trim() || 'local',
|
||
origin: String(skill && skill.origin || '').trim(),
|
||
createdBy: String(skill && skill.createdBy || '').trim(),
|
||
patchCount: Number(skill && skill.patchCount || 0),
|
||
modified: Boolean(skill && skill.modified),
|
||
builtin: Boolean(skill && skill.builtin),
|
||
configurable: skill && skill.configurable !== false,
|
||
configScope: String(skill && skill.configScope || '').trim(),
|
||
skillKind: String(skill && skill.skillKind || '').trim(),
|
||
profileId: String(skill && skill.profileId || '').trim(),
|
||
toolNames: pageAiNormalizeArray(skill && skill.toolNames),
|
||
tools: pageAiNormalizeArray(skill && skill.tools),
|
||
toolCount: Number(skill && skill.toolCount || 0),
|
||
disabledToolCount: Number(skill && skill.disabledToolCount || 0),
|
||
status: String(skill && skill.status || '').trim(),
|
||
capabilityId: String(skill && skill.capabilityId || '').trim(),
|
||
capabilityKind: String(skill && skill.capabilityKind || '').trim(),
|
||
uiKind: String(skill && skill.uiKind || '').trim(),
|
||
requiresContextRefs: pageAiNormalizeArray(skill && skill.requiresContextRefs),
|
||
readOnly: Boolean(skill && (skill.readOnly || skill.readonly || skill.configurable === false)),
|
||
category: String(skill && skill.category || category && category.name || '').trim(),
|
||
categoryTitle: String(skill && skill.categoryTitle || category && category.title || category && category.name || '').trim()
|
||
};
|
||
})
|
||
};
|
||
}),
|
||
archived: archived.map(function(skill) {
|
||
return {
|
||
name: String(skill && skill.name || '').trim(),
|
||
id: String(skill && skill.id || skill && skill.name || '').trim(),
|
||
title: String(skill && skill.title || skill && skill.name || skill && skill.id || '').trim(),
|
||
description: String(skill && skill.description || '').trim(),
|
||
enabled: skill && skill.enabled !== false,
|
||
toggleable: skill && skill.toggleable !== false,
|
||
source: String(skill && skill.source || 'local').trim() || 'local',
|
||
origin: String(skill && skill.origin || '').trim(),
|
||
createdBy: String(skill && skill.createdBy || '').trim(),
|
||
patchCount: Number(skill && skill.patchCount || 0),
|
||
modified: Boolean(skill && skill.modified),
|
||
builtin: Boolean(skill && skill.builtin),
|
||
configurable: skill && skill.configurable !== false,
|
||
configScope: String(skill && skill.configScope || '').trim(),
|
||
skillKind: String(skill && skill.skillKind || '').trim(),
|
||
profileId: String(skill && skill.profileId || '').trim(),
|
||
toolNames: pageAiNormalizeArray(skill && skill.toolNames),
|
||
tools: pageAiNormalizeArray(skill && skill.tools),
|
||
toolCount: Number(skill && skill.toolCount || 0),
|
||
disabledToolCount: Number(skill && skill.disabledToolCount || 0),
|
||
status: String(skill && skill.status || '').trim(),
|
||
capabilityId: String(skill && skill.capabilityId || '').trim(),
|
||
capabilityKind: String(skill && skill.capabilityKind || '').trim(),
|
||
uiKind: String(skill && skill.uiKind || '').trim(),
|
||
requiresContextRefs: pageAiNormalizeArray(skill && skill.requiresContextRefs),
|
||
categoryTitle: String(skill && skill.categoryTitle || '').trim(),
|
||
readOnly: Boolean(skill && (skill.readOnly || skill.readonly || skill.configurable === false))
|
||
};
|
||
})
|
||
};
|
||
}
|
||
|
||
function pageAiSkillListEntries() {
|
||
var result = [];
|
||
pageAiNormalizeArray(pageUiState.pageAiSkills.categories).forEach(function(category) {
|
||
pageAiNormalizeArray(category.skills).forEach(function(skill) {
|
||
result.push({
|
||
category: category.name,
|
||
name: skill.name,
|
||
id: skill.id || skill.name,
|
||
title: skill.title || skill.name,
|
||
description: skill.description,
|
||
enabled: skill.enabled !== false,
|
||
toggleable: skill.toggleable !== false,
|
||
source: skill.source || 'local',
|
||
origin: skill.origin || '',
|
||
createdBy: skill.createdBy || '',
|
||
patchCount: Number(skill.patchCount || 0),
|
||
modified: Boolean(skill.modified),
|
||
builtin: Boolean(skill.builtin),
|
||
configurable: skill.configurable !== false,
|
||
configScope: skill.configScope || '',
|
||
skillKind: skill.skillKind || '',
|
||
profileId: skill.profileId || '',
|
||
toolNames: skill.toolNames || [],
|
||
tools: skill.tools || [],
|
||
toolCount: Number(skill.toolCount || 0),
|
||
disabledToolCount: Number(skill.disabledToolCount || 0),
|
||
status: skill.status || '',
|
||
capabilityId: skill.capabilityId || '',
|
||
capabilityKind: skill.capabilityKind || '',
|
||
uiKind: skill.uiKind || '',
|
||
requiresContextRefs: skill.requiresContextRefs || [],
|
||
categoryTitle: skill.categoryTitle || category.title || category.name || '',
|
||
readOnly: Boolean(skill.readOnly || skill.readonly || skill.configurable === false)
|
||
});
|
||
});
|
||
});
|
||
return result.concat(pageAiNormalizeArray(pageUiState.pageAiSkills.archived));
|
||
}
|
||
|
||
function pageAiSetActiveProfile(profileName) {
|
||
var next = String(profileName || '').trim() || 'mnoteai';
|
||
pageUiState.pageAiActiveProfileName = next;
|
||
document.documentElement.setAttribute('data-mnote-page-ai-profile', next);
|
||
document.documentElement.setAttribute('data-mnote-page-ai-tool-model', pageAiMnoteToolModel());
|
||
}
|
||
|
||
function pageAiSetRunStatus(status, runId) {
|
||
pageUiState.pageAiRunStatus = status || 'idle';
|
||
pageUiState.pageAiCurrentRunId = runId || pageUiState.pageAiCurrentRunId || '';
|
||
document.documentElement.setAttribute('data-mnote-page-ai-run-status', pageUiState.pageAiRunStatus);
|
||
if (pageUiState.pageAiCurrentRunId) {
|
||
document.documentElement.setAttribute('data-mnote-page-ai-run-id', pageUiState.pageAiCurrentRunId);
|
||
}
|
||
}
|
||
|
||
function pageAiApplyRuntimeState(runtime) {
|
||
if (!runtime || typeof runtime !== 'object') return;
|
||
var status = String(runtime.status || '').trim();
|
||
var runId = String(runtime.runId || runtime.run_id || '').trim();
|
||
if (status) pageAiSetRunStatus(status, runId);
|
||
var queueLength = Number(runtime.queueLength || runtime.queue_length || 0);
|
||
if (Number.isFinite(queueLength)) pageUiState.pageAiQueueLength = Math.max(0, queueLength);
|
||
var toolName = String(runtime.lastToolName || runtime.last_tool_name || '').trim();
|
||
if (toolName) {
|
||
pageUiState.pageAiLastToolCall = {
|
||
event: String(runtime.lastEvent || runtime.last_event || ''),
|
||
name: toolName,
|
||
runId: runId,
|
||
traceId: String(runtime.traceId || runtime.trace_id || ''),
|
||
auditId: String(runtime.lastAuditId || runtime.last_audit_id || '')
|
||
};
|
||
}
|
||
}
|
||
|
||
function pageAiApplyQueuedRun(payload) {
|
||
if (!payload || payload.queued !== true) return false;
|
||
var queueId = String(payload.queueId || payload.queue_id || '').trim();
|
||
var queueLength = Number(payload.queueLength || payload.queue_length || 0);
|
||
pageUiState.pageAiQueueLength = Number.isFinite(queueLength) ? Math.max(0, queueLength) : 1;
|
||
if (queueId) {
|
||
pageUiState.pageAiQueuedItems = pageUiState.pageAiQueuedItems.filter(function(item) {
|
||
return item.queueId !== queueId;
|
||
}).concat([{
|
||
queueId: queueId,
|
||
sessionId: String(payload.sessionId || payload.session_id || pageUiState.pageAiActiveSessionId || ''),
|
||
traceId: String(payload.traceId || payload.trace_id || ''),
|
||
queuedAt: Number(payload.queuedAt || payload.queued_at || Date.now())
|
||
}]);
|
||
}
|
||
pageAiSetRunStatus('queued', pageUiState.pageAiCurrentRunId);
|
||
return true;
|
||
}
|
||
|
||
function pageAiUnwrapSearchCitationUrl(url) {
|
||
try {
|
||
var raw = '';
|
||
['wd', 'q', 'query'].some(function(key) {
|
||
raw = String(url.searchParams.get(key) || '').trim();
|
||
return Boolean(raw);
|
||
});
|
||
if (!raw) return '';
|
||
if (/^documents\//i.test(raw)) raw = '/' + raw;
|
||
if (!/^\/documents\//i.test(raw) && !/^https?:\/\/[^/]+\/documents\//i.test(raw) && !/^mnote:\/\/open/i.test(raw)) return '';
|
||
if (/^mnote:\/\/open/i.test(raw)) return raw;
|
||
var nested = new URL(raw, window.location.origin);
|
||
if (!nested.pathname.startsWith('/documents/')) return '';
|
||
['sourceKind', 'rootUri', 'workspaceId', 'resourceTab', 'resourcePath', 'page', 'bbox', 'sourceMapPath', 'blockId', 'evidenceText', 'lineRange', 'charRange'].forEach(function(key) {
|
||
if (nested.searchParams.get(key)) return;
|
||
var value = String(url.searchParams.get(key) || '').trim();
|
||
if (value) nested.searchParams.set(key, value);
|
||
});
|
||
return nested.pathname + nested.search;
|
||
} catch (_error) {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
function pageAiPreviewValue(value) {
|
||
if (value == null || value === '') return '';
|
||
if (typeof value === 'string') return value.length > 120 ? value.slice(0, 120) + '…' : value;
|
||
try {
|
||
var text = JSON.stringify(value);
|
||
return text.length > 120 ? text.slice(0, 120) + '…' : text;
|
||
} catch (_) {
|
||
return String(value);
|
||
}
|
||
}
|
||
|
||
function pageAiNormalizeToolName(name) {
|
||
return String(name || '').trim().replace(/_/g, '.');
|
||
}
|
||
|
||
|
||
function pageAiParentRelativePath(path) {
|
||
var normalized = String(path || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
|
||
if (!normalized || normalized.indexOf('/') < 0) return '';
|
||
return normalized.split('/').slice(0, -1).join('/');
|
||
}
|
||
|
||
function pageAiChangedFileRelativePath(file) {
|
||
return String(file && (file.path || file.relativePath || file.relative_path || file.filePath || file.file_path || '') || '').trim();
|
||
}
|
||
|
||
function pageAiDispatchReceiptRefresh(receipt, changedFiles, fallbackAudit, runId, traceId) {
|
||
var refresh = receipt && typeof receipt === 'object' && receipt.refresh && typeof receipt.refresh === 'object'
|
||
? receipt.refresh
|
||
: {};
|
||
var files = pageAiNormalizeArray(
|
||
receipt && typeof receipt === 'object' && receipt.changedFiles !== undefined
|
||
? receipt.changedFiles
|
||
: changedFiles
|
||
);
|
||
if (files.length) {
|
||
var changedPaths = files.map(function(file) {
|
||
var relativePath = pageAiChangedFileRelativePath(file);
|
||
return {
|
||
relativePath: relativePath,
|
||
changeType: String(file && (file.changeType || file.change_type || 'modified') || 'modified')
|
||
};
|
||
}).filter(function(item) { return item.relativePath; });
|
||
var affectedParents = changedPaths.map(function(item) {
|
||
return { relativePath: pageAiParentRelativePath(item.relativePath), reason: 'agent-run-receipt' };
|
||
}).filter(function(item, index, list) {
|
||
return index === list.findIndex(function(candidate) { return candidate.relativePath === item.relativePath; });
|
||
});
|
||
if (changedPaths.length) {
|
||
var rootUri = String((receipt && receipt.rootUri) || (fallbackAudit && fallbackAudit.rootUri) || currentRootUri() || '');
|
||
var syntheticPayload = {
|
||
schema: 'mnote.local_folder.watch_batch.v1',
|
||
source: 'agent_run_receipt',
|
||
runId: runId,
|
||
rootUri: rootUri,
|
||
changedPaths: changedPaths,
|
||
affectedParents: affectedParents
|
||
};
|
||
document.documentElement.setAttribute('data-mnote-page-ai-receipt-filetree-refresh', 'true');
|
||
if (window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitSyntheticWatchBatch === 'function') {
|
||
window.__mnoteLocalFolderEventBus.emitSyntheticWatchBatch({
|
||
source: 'synthetic_page_ai_receipt',
|
||
reason: 'agent_run_receipt',
|
||
rootUri: rootUri,
|
||
workspaceId: resolveWorkspaceId(document.body),
|
||
payload: syntheticPayload
|
||
});
|
||
} else {
|
||
window.dispatchEvent(new CustomEvent('tree:local-folder-watch-batch', {
|
||
detail: { payload: syntheticPayload }
|
||
}));
|
||
}
|
||
}
|
||
}
|
||
if (refresh.touchesCurrentFile === true) {
|
||
var documentId = String(refresh.currentDocumentId || receipt && receipt.documentId || currentDocumentId() || '').trim();
|
||
document.documentElement.setAttribute('data-mnote-page-ai-receipt-current-refresh', 'true');
|
||
window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
|
||
detail: {
|
||
toolName: 'agent.run_receipt',
|
||
normalizedToolName: 'agent.run_receipt',
|
||
documentId: documentId,
|
||
workspaceId: String(receipt && receipt.workspaceId || resolveWorkspaceId(document.body) || ''),
|
||
rootUri: String(receipt && receipt.rootUri || fallbackAudit && fallbackAudit.rootUri || currentRootUri() || ''),
|
||
changedFiles: files,
|
||
receipt: receipt || null,
|
||
runId: runId,
|
||
traceId: traceId,
|
||
toolCallId: runId + ':agent.run_receipt'
|
||
}
|
||
}));
|
||
}
|
||
}
|
||
|
||
function pageAiToolEventDeepFindString(value, keys, depth) {
|
||
if (!value || typeof value !== 'object' || depth > 5) return '';
|
||
for (var index = 0; index < keys.length; index += 1) {
|
||
var key = keys[index];
|
||
if (Object.prototype.hasOwnProperty.call(value, key) && typeof value[key] === 'string' && value[key].trim()) {
|
||
return value[key].trim();
|
||
}
|
||
}
|
||
if (Array.isArray(value)) {
|
||
for (var arrayIndex = 0; arrayIndex < value.length; arrayIndex += 1) {
|
||
var fromArray = pageAiToolEventDeepFindString(value[arrayIndex], keys, depth + 1);
|
||
if (fromArray) return fromArray;
|
||
}
|
||
return '';
|
||
}
|
||
var preferred = ['audit', 'args', 'arguments', 'input', 'result', 'summary', 'output', 'upstream'];
|
||
for (var prefIndex = 0; prefIndex < preferred.length; prefIndex += 1) {
|
||
var child = value[preferred[prefIndex]];
|
||
var fromPreferred = pageAiToolEventDeepFindString(child, keys, depth + 1);
|
||
if (fromPreferred) return fromPreferred;
|
||
}
|
||
var objectKeys = Object.keys(value);
|
||
for (var objectIndex = 0; objectIndex < objectKeys.length; objectIndex += 1) {
|
||
var fromObject = pageAiToolEventDeepFindString(value[objectKeys[objectIndex]], keys, depth + 1);
|
||
if (fromObject) return fromObject;
|
||
}
|
||
return '';
|
||
}
|
||
|
||
function pageAiNotifyToolWriteCompleted(toolName, toolEvent, runId, runTraceId) {
|
||
var normalizedTool = pageAiNormalizeToolName(toolName);
|
||
var writesCurrentPage = [
|
||
'mnote.page.save',
|
||
'mnote.page.update.title',
|
||
'mnote.page.update.options',
|
||
'mnote.doc.apply.block.ops',
|
||
'mnote.block.replace',
|
||
'mnote.block.insert.after',
|
||
'mnote.block.delete',
|
||
'mnote.block.move.after'
|
||
].indexOf(normalizedTool) >= 0 || [
|
||
'mnote.page.update_title',
|
||
'mnote.page.update_options',
|
||
'mnote.doc.apply_block_ops',
|
||
'mnote.block.replace',
|
||
'mnote.block.insert_after',
|
||
'mnote.block.delete',
|
||
'mnote.block.move_after'
|
||
].indexOf(String(toolName || '').trim()) >= 0;
|
||
if (!writesCurrentPage) return;
|
||
var documentId = pageAiToolEventDeepFindString(toolEvent, ['documentId', 'document_id'], 0) || currentDocumentId();
|
||
var workspaceId = pageAiToolEventDeepFindString(toolEvent, ['workspaceId', 'workspace_id'], 0) || resolveWorkspaceId(document.body);
|
||
try {
|
||
window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
|
||
detail: {
|
||
toolName: toolName,
|
||
normalizedToolName: normalizedTool,
|
||
documentId: documentId,
|
||
workspaceId: workspaceId,
|
||
runId: String(toolEvent && (toolEvent.run_id || toolEvent.runId) || runId || ''),
|
||
traceId: String(toolEvent && (toolEvent.trace_id || toolEvent.traceId) || runTraceId || ''),
|
||
toolCallId: String(toolEvent && (toolEvent.tool_call_id || toolEvent.toolCallId || toolEvent.id) || '')
|
||
}
|
||
}));
|
||
} catch (error) {
|
||
console.warn('mnote 页面 AI 写入刷新事件派发失败', error);
|
||
}
|
||
}
|
||
|
||
function pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId) {
|
||
var toolEvent = null;
|
||
try {
|
||
toolEvent = JSON.parse(payloadText || 'null');
|
||
} catch (_) {
|
||
toolEvent = {};
|
||
}
|
||
var rawToolName = toolEvent && (toolEvent.name || toolEvent.tool || toolEvent.toolName || '');
|
||
var toolName = String(rawToolName || eventName);
|
||
var toolCallId = String(toolEvent && (toolEvent.tool_call_id || toolEvent.toolCallId || toolEvent.id) || (runId + ':' + toolName));
|
||
var eventStatus = String(toolEvent && toolEvent.status || '').trim();
|
||
var status = eventName === 'tool.completed'
|
||
? (toolEvent && toolEvent.error ? 'failed' : 'completed')
|
||
: (eventName === 'tool.failed' ? 'failed' : (eventStatus || 'running'));
|
||
if (status === 'in_progress' || status === 'pending') status = 'running';
|
||
var traceId = String(toolEvent && (toolEvent.trace_id || toolEvent.traceId) || runTraceId || '');
|
||
var auditId = String(toolEvent && (toolEvent.audit_id || toolEvent.auditId) || '');
|
||
var argsSummary = pageAiPreviewValue(toolEvent && (toolEvent.arguments || toolEvent.args || toolEvent.input));
|
||
var resultSource = toolEvent && (toolEvent.summary || toolEvent.result || toolEvent.output);
|
||
if (!resultSource && status === 'failed') {
|
||
resultSource = [toolEvent && toolEvent.code, toolEvent && toolEvent.error].filter(Boolean).join(' ');
|
||
}
|
||
var resultSummary = pageAiPreviewValue(resultSource);
|
||
pageUiState.pageAiLastToolCall = {
|
||
event: eventName,
|
||
name: toolName,
|
||
runId: String(toolEvent && (toolEvent.run_id || toolEvent.runId) || runId),
|
||
traceId: traceId,
|
||
auditId: auditId
|
||
};
|
||
var rawLocations = toolEvent && toolEvent.locations;
|
||
var locations = Array.isArray(rawLocations) ? rawLocations.filter(function(l) { return typeof l === 'string' || (typeof l === 'object' && l && l.path); }).map(function(l) { return typeof l === 'string' ? l : l.path; }) : [];
|
||
var existing = pageUiState.pageAiMessages.find(function(item) {
|
||
return item.role === 'tool' && item.toolCallId === toolCallId;
|
||
});
|
||
if (!rawToolName && existing && existing.toolName) toolName = existing.toolName;
|
||
if (!existing) {
|
||
existing = {
|
||
role: 'tool',
|
||
content: toolName,
|
||
toolCallId: toolCallId,
|
||
toolName: toolName,
|
||
toolKind: String(toolEvent && toolEvent.kind || ''),
|
||
status: status,
|
||
argsSummary: '',
|
||
resultSummary: '',
|
||
locations: locations,
|
||
traceId: traceId,
|
||
auditId: auditId
|
||
};
|
||
pageUiState.pageAiMessages.push(existing);
|
||
}
|
||
existing.content = toolName;
|
||
existing.toolName = toolName;
|
||
existing.toolKind = String(toolEvent && toolEvent.kind || existing.toolKind || '');
|
||
existing.status = status;
|
||
existing.traceId = traceId || existing.traceId || '';
|
||
existing.auditId = auditId || existing.auditId || '';
|
||
if (argsSummary) existing.argsSummary = argsSummary;
|
||
if (resultSummary) existing.resultSummary = resultSummary;
|
||
if (locations.length) existing.locations = locations;
|
||
existing.rawOutput = toolEvent && toolEvent.output;
|
||
existing.rawResult = resultSource;
|
||
if (status === 'completed') {
|
||
pageAiNotifyToolWriteCompleted(toolName, toolEvent, runId, runTraceId);
|
||
}
|
||
return existing;
|
||
}
|
||
|
||
async function pageAiCancelQueuedRun(queueId) {
|
||
queueId = String(queueId || '').trim();
|
||
if (!queueId) return;
|
||
var item = pageUiState.pageAiQueuedItems.find(function(entry) {
|
||
return entry.queueId === queueId;
|
||
});
|
||
var sessionId = String(item && item.sessionId || pageUiState.pageAiActiveSessionId || '').trim();
|
||
if (!sessionId) return;
|
||
try {
|
||
var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/queue/' + encodeURIComponent(queueId), {
|
||
method: 'DELETE',
|
||
headers: { 'accept': 'application/json' }
|
||
});
|
||
var payload = await response.json().catch(function(){ return null; });
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'queue_cancel_failed_' + response.status));
|
||
pageUiState.pageAiQueuedItems = pageUiState.pageAiQueuedItems.filter(function(entry) {
|
||
return entry.queueId !== queueId;
|
||
});
|
||
var queueLength = Number(payload && (payload.queueLength || payload.queue_length || 0));
|
||
pageUiState.pageAiQueueLength = Number.isFinite(queueLength) ? Math.max(0, queueLength) : pageUiState.pageAiQueuedItems.length;
|
||
pageUiState.pageAiMessages.push({ role: 'assistant', content: '已取消一条 AI 队列项。' });
|
||
} catch (error) {
|
||
pageUiState.pageAiMessages.push({
|
||
role: 'assistant',
|
||
content: '取消 AI 队列项失败:' + (error instanceof Error ? error.message : String(error))
|
||
});
|
||
}
|
||
renderPageAiConversation();
|
||
renderPageAiControls();
|
||
}
|
||
|
||
function pageAiSetContextScope(scope) {
|
||
var next = String(scope || '').trim() || 'page';
|
||
pageUiState.pageAiContextScope = next;
|
||
document.documentElement.setAttribute('data-mnote-page-ai-context-scope', next);
|
||
}
|
||
|
||
|
||
function pageAiHermesSettingsUrl() {
|
||
var configured = String(window.__mnoteHermesSettingsUrl || '').trim();
|
||
return configured || '';
|
||
}
|
||
|
||
function pageAiOpenHermesSettings() {
|
||
var url = pageAiHermesSettingsUrl();
|
||
if (url) {
|
||
window.open(url, '_blank', 'noopener,noreferrer');
|
||
return;
|
||
}
|
||
pageUiState.pageAiProfileError = '未配置 Hermes 设置入口:请设置 MNOTE_WEB_HERMES_UPSTREAM_URL。';
|
||
renderPageAiControls();
|
||
}
|
||
|
||
function pageAiNormalizeTools(payload) {
|
||
var upstream = pageAiUnwrapUpstream(payload) || {};
|
||
var tools = pageAiNormalizeArray(upstream.tools || upstream);
|
||
return tools.map(function(tool) {
|
||
var name = String(tool && (tool.name || tool.toolName || tool.tool) || '').trim();
|
||
if (!name) return null;
|
||
return {
|
||
name: name,
|
||
description: String(tool && tool.description || '').trim(),
|
||
scope: String(tool && (tool.scope || tool.requiredScope || '') || '').trim(),
|
||
kind: String(tool && (tool.kind || tool.mode || '') || '').trim(),
|
||
status: String(tool && (tool.status || tool.permission || 'available') || '').trim(),
|
||
enabled: tool && tool.enabled !== false,
|
||
unavailableReason: String(tool && (tool.unavailableReason || tool.reason || '') || '').trim()
|
||
};
|
||
}).filter(Boolean);
|
||
}
|
||
|
||
function pageAiErrorMessage(payload, fallback) {
|
||
if (!payload || typeof payload !== 'object') return fallback;
|
||
return String(payload.message || payload.error || payload.code || fallback || '').trim() || fallback;
|
||
}
|
||
|
||
function pageAiNormalizeGatewayHealth(payload) {
|
||
var upstream = pageAiUnwrapUpstream(payload) || payload || {};
|
||
return {
|
||
ok: Boolean(upstream.ok),
|
||
profile: upstream.profile || null,
|
||
gateway: upstream.gateway || null,
|
||
suggestions: pageAiNormalizeArray(upstream.suggestions).map(function(item) {
|
||
return String(item || '').trim();
|
||
}).filter(Boolean)
|
||
};
|
||
}
|
||
|
||
function pageAiSetDraftForSection(section, value) {
|
||
pageUiState.pageAiProfileMemoryDrafts[section] = String(value == null ? '' : value);
|
||
}
|
||
|
||
function pageAiApplyProfileMemory(payload) {
|
||
var upstream = pageAiUnwrapUpstream(payload) || {};
|
||
pageUiState.pageAiProfileMemory = {
|
||
memory: String(upstream.memory || ''),
|
||
user: String(upstream.user || ''),
|
||
soul: String(upstream.soul || '')
|
||
};
|
||
pageUiState.pageAiProfileMemoryDrafts = {
|
||
memory: pageUiState.pageAiProfileMemory.memory,
|
||
user: pageUiState.pageAiProfileMemory.user,
|
||
soul: pageUiState.pageAiProfileMemory.soul
|
||
};
|
||
}
|
||
|
||
async function pageAiLoadTools() {
|
||
try {
|
||
var response = await fetch('/api/hermes/client/tools?scope=mnote&profile=' + encodeURIComponent(pageAiCurrentProfile()), {
|
||
headers: { 'accept': 'application/json' }
|
||
});
|
||
var payload = await response.json().catch(function(){ return null; });
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'tools_failed_' + response.status));
|
||
pageUiState.pageAiTools = pageAiNormalizeTools(payload);
|
||
pageUiState.pageAiToolsError = '';
|
||
} catch (error) {
|
||
pageUiState.pageAiToolsError = error instanceof Error ? error.message : String(error);
|
||
}
|
||
renderPageAiControls();
|
||
}
|
||
|
||
async function pageAiLoadGatewayHealth() {
|
||
try {
|
||
var response = await fetch('/api/hermes/client/gateway/health?profile=' + encodeURIComponent(pageAiCurrentProfile()), {
|
||
headers: { 'accept': 'application/json' }
|
||
});
|
||
var payload = await response.json().catch(function(){ return null; });
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'gateway_health_failed_' + response.status));
|
||
pageUiState.pageAiGatewayHealth = pageAiNormalizeGatewayHealth(payload);
|
||
pageUiState.pageAiGatewayHealthError = '';
|
||
} catch (error) {
|
||
pageUiState.pageAiGatewayHealthError = error instanceof Error ? error.message : String(error);
|
||
}
|
||
renderPageAiControls();
|
||
}
|
||
|
||
async function pageAiStopRun() {
|
||
var runId = String(pageUiState.pageAiCurrentRunId || '').trim();
|
||
if (!runId || pageUiState.pageAiRunStatus === 'idle' || pageUiState.pageAiRunStatus === 'completed') return;
|
||
pageUiState.pageAiStoppedRunIds[runId] = true;
|
||
try {
|
||
var response = await fetch('/api/hermes/client/runs/' + encodeURIComponent(runId) + '/abort', {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sessionId: pageUiState.pageAiActiveSessionId,
|
||
profile: pageAiRunProfile(),
|
||
acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
|
||
reason: 'page_ai_user_stop'
|
||
})
|
||
});
|
||
var payload = await response.json().catch(function(){ return null; });
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'run_abort_failed_' + response.status));
|
||
pageAiApplyRuntimeState(payload && payload.runtime);
|
||
pageAiSetRunStatus('aborted', runId);
|
||
pageUiState.pageAiMessages.push({ role: 'assistant', content: '已请求停止当前 AI run。' });
|
||
} catch (error) {
|
||
pageAiSetRunStatus('failed', runId);
|
||
pageUiState.pageAiMessages.push({
|
||
role: 'assistant',
|
||
content: '停止 AI run 失败:' + (error instanceof Error ? error.message : String(error))
|
||
});
|
||
}
|
||
renderPageAiControls();
|
||
renderPageAiConversation();
|
||
}
|
||
|
||
|
||
function pageAiLoadSkillCatalog(runtime, profile) {
|
||
var params = new URLSearchParams();
|
||
params.set('runtime', runtime);
|
||
var endpoint = '/api/hermes/client/skills';
|
||
if (runtime === 'mnote') {
|
||
endpoint = '/api/hermes/client/capabilities';
|
||
params.set('agentId', 'reasonix');
|
||
if (profile) params.set('profile', profile);
|
||
} else if (runtime === 'reasonix') {
|
||
params.set('runtime', 'reasonix');
|
||
} else if (runtime === 'hermes' && profile) {
|
||
params.set('profileId', profile);
|
||
}
|
||
return fetch(endpoint + '?' + params.toString(), {
|
||
headers: { 'accept': 'application/json' }
|
||
}).then(function(response) {
|
||
return response.json().catch(function(){ return null; }).then(function(payload) {
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'capabilities_failed_' + response.status));
|
||
return pageAiNormalizeSkills(payload);
|
||
});
|
||
});
|
||
}
|
||
|
||
async function pageAiLoadProfiles() {
|
||
try {
|
||
var response = await fetch('/api/ai/agent-profiles?agentId=hermes', {
|
||
headers: { 'accept': 'application/json' }
|
||
});
|
||
var payload = await response.json().catch(function(){ return null; });
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profiles_failed_' + response.status));
|
||
var profiles = pageAiNormalizeProfiles(payload);
|
||
pageUiState.pageAiProfiles = profiles.length ? profiles : [{ profileId: 'shared_lite', name: 'shared_lite', alias: 'Lite', kind: 'shared', readonly: true, canManageSkills: false }];
|
||
try {
|
||
var runtimeResponse = await fetch('/api/hermes/client/profiles', { headers: { 'accept': 'application/json' } });
|
||
var runtimePayload = await runtimeResponse.json().catch(function(){ return null; });
|
||
var acpRuntimes = Array.isArray(runtimePayload && runtimePayload.acpRuntimes) ? runtimePayload.acpRuntimes : [];
|
||
pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(acpRuntimes);
|
||
} catch (_) {
|
||
pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(pageUiState.pageAiAcpRuntimes);
|
||
}
|
||
var current = pageAiCurrentProfile();
|
||
var hasCurrent = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === current; });
|
||
var personal = pageUiState.pageAiProfiles.find(function(profile) { return profile.kind === 'personal'; });
|
||
var active = pageAiProfileValue(pageUiState.pageAiProfiles.find(function(profile) { return profile.active; }));
|
||
pageAiSetActiveProfile(hasCurrent && current !== 'default' ? current : (active || pageAiProfileValue(personal) || pageAiProfileValue(pageUiState.pageAiProfiles[0]) || current));
|
||
pageUiState.pageAiProfileError = '';
|
||
void pageAiLoadGatewayHealth();
|
||
} catch (error) {
|
||
pageUiState.pageAiProfileError = error instanceof Error ? error.message : String(error);
|
||
if (!pageUiState.pageAiProfiles.length) pageUiState.pageAiProfiles = [{ profileId: 'shared_lite', name: 'shared_lite', alias: 'Lite', kind: 'shared', readonly: true, canManageSkills: false }];
|
||
pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(pageUiState.pageAiAcpRuntimes);
|
||
pageAiSetActiveProfile(pageAiCurrentProfile());
|
||
}
|
||
renderPageAiProviderButtons();
|
||
renderPageAiControls();
|
||
}
|
||
|
||
async function pageAiSwitchProfile(profileName) {
|
||
var next = String(profileName || '').trim();
|
||
if (!next) return;
|
||
var previousSkillSource = pageAiCurrentSkillSource();
|
||
pageAiSetActiveProfile(next);
|
||
pageAiPersistRawAiPreference('ai.agent.hermes.profile_id', next);
|
||
if (previousSkillSource.indexOf('hermes:') === 0) {
|
||
pageUiState.pageAiActiveSkillSource = 'hermes:' + next;
|
||
pageAiPersistAiPreference('skills.active_source', pageUiState.pageAiActiveSkillSource);
|
||
}
|
||
pageUiState.pageAiSkills = { categories: [], archived: [] };
|
||
pageUiState.pageAiSkillCatalogs = Object.assign({}, pageUiState.pageAiSkillCatalogs || {}, {
|
||
hermes: { categories: [], archived: [] }
|
||
});
|
||
pageUiState.pageAiSkillError = '';
|
||
renderPageAiProviderButtons();
|
||
renderPageAiControls();
|
||
try {
|
||
pageUiState.pageAiSessions = pageUiState.pageAiSessions.filter(function(session) {
|
||
return session && session.profile === next;
|
||
});
|
||
pageUiState.pageAiActiveSessionId = '';
|
||
pageUiState.pageAiMessages = [];
|
||
pageAiPersistSessions();
|
||
await pageAiEnsureHermesSession(true);
|
||
await pageAiLoadProfileMemory();
|
||
await pageAiLoadSkills();
|
||
await pageAiLoadTools();
|
||
await pageAiLoadGatewayHealth();
|
||
renderPageAiControls();
|
||
} catch (error) {
|
||
pageUiState.pageAiProfileError = error instanceof Error ? error.message : String(error);
|
||
}
|
||
renderPageAiConversation();
|
||
}
|
||
|
||
async function pageAiLoadProfileMemory() {
|
||
try {
|
||
var response = await fetch('/api/hermes/client/profile-memory?profileId=' + encodeURIComponent(pageAiCurrentProfile()), {
|
||
headers: { 'accept': 'application/json' }
|
||
});
|
||
var payload = await response.json().catch(function(){ return null; });
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profile_memory_failed_' + response.status));
|
||
pageAiApplyProfileMemory(payload);
|
||
pageUiState.pageAiProfileMemoryError = '';
|
||
} catch (error) {
|
||
pageUiState.pageAiProfileMemoryError = error instanceof Error ? error.message : String(error);
|
||
}
|
||
renderPageAiControls();
|
||
renderPageAiConversation();
|
||
}
|
||
|
||
async function pageAiSaveProfileMemory(section) {
|
||
var normalized = String(section || '').trim();
|
||
if (['memory', 'user', 'soul'].indexOf(normalized) < 0) return;
|
||
var content = String(pageUiState.pageAiProfileMemoryDrafts[normalized] || '');
|
||
try {
|
||
var response = await fetch('/api/hermes/client/profile-memory', {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({
|
||
profileId: pageAiCurrentProfile(),
|
||
section: normalized,
|
||
content: content
|
||
})
|
||
});
|
||
var payload = await response.json().catch(function(){ return null; });
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profile_memory_save_failed_' + response.status));
|
||
pageUiState.pageAiProfileMemory[normalized] = content;
|
||
pageUiState.pageAiProfileMemoryError = '';
|
||
document.documentElement.setAttribute('data-mnote-page-ai-memory-saved', normalized);
|
||
} catch (error) {
|
||
pageUiState.pageAiProfileMemoryError = error instanceof Error ? error.message : String(error);
|
||
}
|
||
renderPageAiControls();
|
||
renderPageAiConversation();
|
||
}
|
||
|
||
async function pageAiLoadSkills() {
|
||
var requestedSource = pageAiCurrentSkillSource();
|
||
var sourceParts = pageAiSkillSourceParts(requestedSource);
|
||
var loadSeq = (Number(pageUiState.pageAiSkillLoadSeq || 0) || 0) + 1;
|
||
pageUiState.pageAiSkillLoadSeq = loadSeq;
|
||
try {
|
||
var catalog = await pageAiLoadSkillCatalog(sourceParts.group, sourceParts.profile || pageAiCurrentProfile());
|
||
if (pageUiState.pageAiSkillLoadSeq !== loadSeq
|
||
|| pageAiCurrentSkillSource() !== requestedSource) return;
|
||
pageUiState.pageAiSkillCatalogs = Object.assign({}, pageUiState.pageAiSkillCatalogs || {}, {
|
||
[sourceParts.group]: catalog,
|
||
[requestedSource]: catalog
|
||
});
|
||
pageUiState.pageAiSkills = catalog;
|
||
pageUiState.pageAiSkillError = '';
|
||
} catch (error) {
|
||
if (pageUiState.pageAiSkillLoadSeq !== loadSeq
|
||
|| pageAiCurrentSkillSource() !== requestedSource) return;
|
||
pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error);
|
||
pageUiState.pageAiSkills = { categories: [], archived: [] };
|
||
pageUiState.pageAiSkillCatalogs = pageUiState.pageAiSkillCatalogs || { mnote: { categories: [], archived: [] }, reasonix: { categories: [], archived: [] }, hermes: { categories: [], archived: [] } };
|
||
}
|
||
renderPageAiControls();
|
||
renderPageAiConversation();
|
||
}
|
||
|
||
async function pageAiToggleSkill(skillName, enabled, group, profile) {
|
||
var name = String(skillName || '').trim();
|
||
if (!name) return;
|
||
var skillGroup = String(group || 'hermes').trim() || 'hermes';
|
||
var skillProfile = String(profile || pageAiCurrentProfile() || '').trim();
|
||
if (skillGroup === 'reasonix') {
|
||
pageAiSetSkillPreference(skillGroup, name, Boolean(enabled), skillProfile);
|
||
document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', skillGroup + ':' + name);
|
||
renderPageAiControls();
|
||
return;
|
||
}
|
||
if (skillGroup === 'mnote') {
|
||
try {
|
||
var mnoteResponse = await fetch('/api/hermes/client/capabilities/toggle', {
|
||
method: 'PUT',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({
|
||
runtime: 'mnote',
|
||
profile: skillProfile || pageAiCurrentProfile(),
|
||
id: name,
|
||
enabled: Boolean(enabled)
|
||
})
|
||
});
|
||
var mnotePayload = await mnoteResponse.json().catch(function(){ return null; });
|
||
if (!mnoteResponse.ok) throw new Error(pageAiErrorMessage(mnotePayload, 'mnote_capability_toggle_failed_' + mnoteResponse.status));
|
||
pageAiSetSkillPreference(skillGroup, name, Boolean(enabled), skillProfile);
|
||
pageAiNormalizeArray(pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs.mnote && pageUiState.pageAiSkillCatalogs.mnote.categories).forEach(function(category) {
|
||
pageAiNormalizeArray(category.skills).forEach(function(skill) {
|
||
if (skill.id === name || skill.name === name) {
|
||
skill.enabled = Boolean(enabled);
|
||
skill.status = enabled ? 'available' : 'disabled';
|
||
pageAiNormalizeArray(skill.tools).forEach(function(tool) {
|
||
tool.enabled = Boolean(enabled);
|
||
tool.status = enabled ? 'available' : 'disabled';
|
||
});
|
||
skill.disabledToolCount = enabled ? 0 : Number(skill.toolCount || pageAiNormalizeArray(skill.tools).length || 0);
|
||
}
|
||
});
|
||
});
|
||
document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', skillGroup + ':' + name);
|
||
} catch (error) {
|
||
pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error);
|
||
}
|
||
renderPageAiControls();
|
||
return;
|
||
}
|
||
var previous = null;
|
||
pageAiToggleableSkillEntries('hermes', skillProfile || pageAiCurrentProfile()).forEach(function(skill) {
|
||
if (skill.id === name || skill.name === name) previous = skill.enabled !== false;
|
||
});
|
||
try {
|
||
var response = await fetch('/api/hermes/client/skills/toggle', {
|
||
method: 'PUT',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({
|
||
profile: skillProfile || pageAiCurrentProfile(),
|
||
profileId: skillProfile || pageAiCurrentProfile(),
|
||
skillKind: 'hermes_profile',
|
||
name: name,
|
||
enabled: Boolean(enabled)
|
||
})
|
||
});
|
||
var payload = await response.json().catch(function(){ return null; });
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'skill_toggle_failed_' + response.status));
|
||
var hermesCatalog = pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs.hermes
|
||
? pageUiState.pageAiSkillCatalogs.hermes
|
||
: pageUiState.pageAiSkills;
|
||
pageAiNormalizeArray(hermesCatalog.categories).forEach(function(category) {
|
||
pageAiNormalizeArray(category.skills).forEach(function(skill) {
|
||
if (skill.name === name || skill.id === name) skill.enabled = Boolean(enabled);
|
||
});
|
||
});
|
||
pageUiState.pageAiSkills = hermesCatalog;
|
||
pageAiSetSkillPreference('hermes', name, Boolean(enabled), skillProfile || pageAiCurrentProfile());
|
||
document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', name);
|
||
pageUiState.pageAiSkillError = '';
|
||
} catch (error) {
|
||
pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error);
|
||
if (previous != null) {
|
||
var rollbackCatalog = pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs.hermes
|
||
? pageUiState.pageAiSkillCatalogs.hermes
|
||
: pageUiState.pageAiSkills;
|
||
pageAiNormalizeArray(rollbackCatalog.categories).forEach(function(category) {
|
||
pageAiNormalizeArray(category.skills).forEach(function(skill) {
|
||
if (skill.name === name || skill.id === name) skill.enabled = previous;
|
||
});
|
||
});
|
||
}
|
||
}
|
||
renderPageAiControls();
|
||
renderPageAiConversation();
|
||
}
|
||
|
||
async function pageAiToggleTool(toolName, enabled) {
|
||
var name = String(toolName || '').trim();
|
||
if (!name) return;
|
||
var previous = null;
|
||
pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
|
||
if (tool.name === name && previous == null) previous = tool.enabled !== false;
|
||
});
|
||
try {
|
||
var response = await fetch('/api/hermes/client/tools/toggle', {
|
||
method: 'PUT',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({
|
||
profile: pageAiCurrentProfile(),
|
||
name: name,
|
||
enabled: Boolean(enabled)
|
||
})
|
||
});
|
||
var payload = await response.json().catch(function(){ return null; });
|
||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'tool_toggle_failed_' + response.status));
|
||
pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
|
||
if (tool.name === name) {
|
||
tool.enabled = Boolean(enabled);
|
||
tool.status = Boolean(enabled) ? 'available' : 'disabled';
|
||
tool.unavailableReason = Boolean(enabled) ? '' : '当前 Hermes profile 已关闭该 mnote tool';
|
||
}
|
||
});
|
||
document.documentElement.setAttribute('data-mnote-page-ai-tool-toggled', name);
|
||
pageUiState.pageAiToolsError = '';
|
||
} catch (error) {
|
||
pageUiState.pageAiToolsError = error instanceof Error ? error.message : String(error);
|
||
if (previous != null) {
|
||
pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) {
|
||
if (tool.name === name) tool.enabled = previous;
|
||
});
|
||
}
|
||
}
|
||
renderPageAiControls();
|
||
}
|
||
|
||
|
||
function openPageAiDrawer() {
|
||
pageUiState.pageAiAgentId = pageAiCurrentAgentId();
|
||
pageAiEnsureContextRefState();
|
||
pageAiLoadSessions();
|
||
renderPageAiSuggestions();
|
||
renderPageAiConversation();
|
||
renderPageAiProviderButtons();
|
||
renderPageAiControls();
|
||
var drawer = ensurePageAiDrawer();
|
||
pageAiApplyDrawerWidth(drawer);
|
||
drawer.hidden = false;
|
||
pageUiState.pageAiOpen = true;
|
||
updatePageAiTriggerState();
|
||
Promise.all([
|
||
pageAiLoadProfiles(),
|
||
pageAiLoadProfileMemory(),
|
||
pageAiLoadSkills(),
|
||
pageAiLoadTools(),
|
||
pageAiLoadGatewayHealth(),
|
||
pageAiLoadAiPreferences(),
|
||
pageAiLoadAllowedRoots(),
|
||
pageAiLoadBackendSessions()
|
||
]).then(function() {
|
||
renderPageAiControls();
|
||
}).then(function() {
|
||
return pageAiRestoreHermesSession();
|
||
}).then(function() {
|
||
renderPageAiControls();
|
||
}).catch(function(error) {
|
||
pageUiState.pageAiMessages.push({
|
||
role: 'assistant',
|
||
content: '当前 AI 不可用:' + (error instanceof Error ? error.message : String(error))
|
||
});
|
||
renderPageAiConversation();
|
||
renderPageAiControls();
|
||
});
|
||
}
|
||
|
||
function closePageAiDrawer() {
|
||
var drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]');
|
||
if (drawer instanceof HTMLElement) drawer.hidden = true;
|
||
pageUiState.pageAiOpen = false;
|
||
updatePageAiTriggerState();
|
||
}
|
||
|
||
function isPageAiDrawerOpen() {
|
||
var drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]');
|
||
return drawer instanceof HTMLElement && !drawer.hidden;
|
||
}
|
||
|
||
async function streamPageAiResponse(response, onEvent) {
|
||
if (!response.body || typeof response.body.getReader !== 'function') return;
|
||
var reader = response.body.getReader();
|
||
var decoder = new TextDecoder();
|
||
var buffer = '';
|
||
while (true) {
|
||
var chunk = await reader.read();
|
||
if (chunk.done) break;
|
||
buffer += decoder.decode(chunk.value, { stream: true });
|
||
var frames = buffer.split('\n\n');
|
||
buffer = frames.pop() || '';
|
||
frames.forEach(function(frame) {
|
||
var eventName = '';
|
||
var dataLines = [];
|
||
frame.split('\n').forEach(function(line) {
|
||
if (line.startsWith('event:')) eventName = line.slice(6).trim();
|
||
if (line.startsWith('data:')) dataLines.push(line.slice(5).trim());
|
||
});
|
||
var payloadText = dataLines.join('\n');
|
||
if (!eventName && payloadText) {
|
||
try {
|
||
var parsed = JSON.parse(payloadText);
|
||
eventName = parsed && parsed.event ? String(parsed.event) : '';
|
||
} catch (_) {}
|
||
}
|
||
if (eventName) onEvent(eventName, payloadText);
|
||
});
|
||
}
|
||
}
|
||
|
||
function pageAiDecodeDeltaText(payloadText) {
|
||
try {
|
||
var payload = JSON.parse(payloadText || 'null');
|
||
return String((payload && (payload.text || payload.delta)) || '');
|
||
} catch (_) {
|
||
return String(payloadText || '');
|
||
}
|
||
}
|
||
|
||
function pageAiEnsureStreamingAssistantMessage(runId) {
|
||
var id = String(runId || pageUiState.pageAiCurrentRunId || '');
|
||
var existing = pageUiState.pageAiMessages.find(function(item) {
|
||
return item.role === 'assistant' && item.streaming === true && item.runId === id;
|
||
});
|
||
if (existing) return existing;
|
||
existing = {
|
||
role: 'assistant',
|
||
content: '',
|
||
runId: id,
|
||
streaming: true
|
||
};
|
||
pageUiState.pageAiMessages.push(existing);
|
||
return existing;
|
||
}
|
||
|
||
function pageAiAppendStreamingAssistantDelta(runId, deltaText) {
|
||
var delta = String(deltaText || '');
|
||
if (!delta) return '';
|
||
var message = pageAiEnsureStreamingAssistantMessage(runId);
|
||
message.content = String(message.content || '') + delta;
|
||
pageAiSyncCurrentSessionMessages();
|
||
renderPageAiConversation();
|
||
return message.content;
|
||
}
|
||
|
||
function pageAiAppendCitationSection(content, citations) {
|
||
var text = String(content || '');
|
||
var unique = [];
|
||
pageAiNormalizeArray(citations).forEach(function(value) {
|
||
var citation = String(value || '').trim();
|
||
if (!citation || unique.indexOf(citation) >= 0) return;
|
||
unique.push(citation);
|
||
});
|
||
var missing = unique.filter(function(citation) {
|
||
return text.indexOf(citation) < 0;
|
||
});
|
||
if (!missing.length) return text;
|
||
return text.replace(/\s+$/g, '') + '\n\n**引用**\n' + missing.map(function(citation) {
|
||
return '- ' + citation;
|
||
}).join('\n');
|
||
}
|
||
|
||
function pageAiPromptNeedsKnowledgeRagCitations(prompt) {
|
||
var text = searchText(prompt);
|
||
if (!text) return false;
|
||
var asksCitation = ['链接', '引用', '来源', '出处', '证据', '定位', 'link', 'citation', 'source'].some(function(word) {
|
||
return text.indexOf(word) >= 0;
|
||
});
|
||
if (!asksCitation) return false;
|
||
return ['lightrag', '资料库', '知识库', 'rag', '文献', '论文', '保护基'].some(function(word) {
|
||
return text.indexOf(word) >= 0;
|
||
});
|
||
}
|
||
|
||
function pageAiKnowledgeRagFallbackQuery(prompt) {
|
||
var text = searchText(prompt);
|
||
if (!text) return '';
|
||
var afterColon = text.split(/[::]/).slice(1).join(':').trim();
|
||
var candidate = afterColon || text;
|
||
candidate = candidate
|
||
.replace(/^(请|请用|帮我|帮忙|用)?(资料库|知识库|lightrag|rag)?(搜索|检索|查找|查询|回答|说明|解释|总结)?/i, '')
|
||
.replace(/(请)?(给出|给我|附上|提供)?(链接|引用|来源|出处|证据|定位).*$/i, '')
|
||
.trim();
|
||
var stopIndex = candidate.search(/(在|的|是|有哪些|有什么|如何|怎么|用于|用途|作用|资料|文献|论文)/);
|
||
if (stopIndex > 0) candidate = candidate.slice(0, stopIndex).trim();
|
||
var cjkMatch = candidate.match(/[A-Za-z0-9\u4e00-\u9fff·α-ωΑ-Ω\-]{2,24}/);
|
||
if (cjkMatch && cjkMatch[0]) return cjkMatch[0];
|
||
return text;
|
||
}
|
||
|
||
function pageAiCleanDegradedCitationNotes(content) {
|
||
return String(content || '')
|
||
.split('\n')
|
||
.filter(function(line) {
|
||
var text = String(line || '');
|
||
return text.indexOf('来源定位降级') < 0 && text.toLowerCase().indexOf('locator degraded') < 0;
|
||
})
|
||
.join('\n')
|
||
.replace(/\n{3,}/g, '\n\n')
|
||
.trim();
|
||
}
|
||
|
||
function pageAiCitationPrecisionLabel(value) {
|
||
var precision = searchText(value || '').toLowerCase();
|
||
if (precision === 'bbox') return '精确定位';
|
||
if (precision === 'paragraph') return '段落级';
|
||
if (precision === 'file') return '文件级';
|
||
return '';
|
||
}
|
||
|
||
function pageAiCitationSourceTitle(citation) {
|
||
if (!citation || typeof citation !== 'object') return '';
|
||
var path = searchText(citation.sourceRootRelativePath || citation.sourcePath || citation.filePath || citation.lightRagFilePath || '');
|
||
if (path) {
|
||
var parts = path.split('/').filter(Boolean);
|
||
return parts[parts.length - 1] || path;
|
||
}
|
||
var markdown = searchText(citation.citationMarkdown || '');
|
||
var labelMatch = markdown.match(/^\[([^\]]+)\]/);
|
||
return labelMatch ? labelMatch[1] : '';
|
||
}
|
||
|
||
function pageAiCitationDisplayMarkdown(value) {
|
||
if (typeof value === 'string') return value.trim();
|
||
if (!value || typeof value !== 'object') return '';
|
||
var markdown = searchText(value.citationMarkdown || '');
|
||
var citationId = searchText(value.citationLabel || (value.citationId ? '[' + value.citationId + ']' : ''));
|
||
var title = pageAiCitationSourceTitle(value);
|
||
var headingPath = pageAiNormalizeArray(value.headingPath).map(function(item) {
|
||
return searchText(item);
|
||
}).filter(Boolean).join(' > ');
|
||
var precisionLabel = pageAiCitationPrecisionLabel(value.locatorPrecision);
|
||
var quote = searchText(value.displayQuote || value.quote || '');
|
||
if (quote.length > 120) quote = quote.slice(0, 117) + '...';
|
||
var head = markdown || title;
|
||
if (citationId && head.indexOf(citationId) < 0) head = citationId + ' ' + head;
|
||
var details = [];
|
||
if (precisionLabel) details.push(precisionLabel);
|
||
if (headingPath) details.push(headingPath);
|
||
if (quote) details.push(quote);
|
||
return [head].concat(details).filter(Boolean).join(' · ').trim();
|
||
}
|
||
|
||
function pageAiFilterCitationCards(values) {
|
||
var cards = pageAiNormalizeArray(values);
|
||
var hasPreciseCitation = cards.some(function(value) {
|
||
if (typeof value === 'string') return value.indexOf('来源定位降级') < 0;
|
||
return value && typeof value === 'object' && searchText(value.citationMarkdown || '') && value.locatorDegraded !== true;
|
||
});
|
||
var seen = {};
|
||
return cards.map(pageAiCitationDisplayMarkdown).filter(function(card, index) {
|
||
var source = cards[index];
|
||
if (!card || seen[card]) return false;
|
||
if (hasPreciseCitation) {
|
||
if (typeof source === 'string' && source.indexOf('来源定位降级') >= 0) return false;
|
||
if (source && typeof source === 'object' && source.locatorDegraded === true) return false;
|
||
}
|
||
seen[card] = true;
|
||
return true;
|
||
}).slice(0, 5);
|
||
}
|
||
|
||
function pageAiCollectKnowledgeRagApiCitations(payload) {
|
||
var structuredCitations = pageAiNormalizeArray(payload && payload.citations);
|
||
if (structuredCitations.length) {
|
||
return pageAiFilterCitationCards(structuredCitations);
|
||
}
|
||
var references = pageAiNormalizeArray(payload && payload.references);
|
||
return pageAiFilterCitationCards(references);
|
||
}
|
||
|
||
async function pageAiAppendKnowledgeRagFallbackCitations(runId, promptText) {
|
||
if (!pageAiPromptNeedsKnowledgeRagCitations(promptText)) return;
|
||
var rootUri = currentRootUri();
|
||
if (!rootUri) return;
|
||
var fallbackQuery = pageAiKnowledgeRagFallbackQuery(promptText);
|
||
if (!fallbackQuery) return;
|
||
try {
|
||
var response = await fetch('/api/knowledge-rag/search', {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json', 'accept': 'application/json' },
|
||
body: JSON.stringify({
|
||
workspaceId: resolveWorkspaceId(document.body) || undefined,
|
||
rootUri: rootUri,
|
||
query: fallbackQuery,
|
||
mode: 'mix',
|
||
topK: 12,
|
||
chunkTopK: 24,
|
||
includeChunkContent: true
|
||
})
|
||
});
|
||
var payload = await response.json().catch(function() { return null; });
|
||
if (!response.ok) return;
|
||
var citations = pageAiCollectKnowledgeRagApiCitations(payload);
|
||
if (!citations.length) return;
|
||
var message = pageUiState.pageAiMessages.find(function(item) {
|
||
return item.role === 'assistant' && item.runId === runId;
|
||
}) || pageUiState.pageAiMessages.filter(function(item) { return item.role === 'assistant'; }).slice(-1)[0];
|
||
if (!message) return;
|
||
var hasPrecise = citations.some(function(citation) { return citation.indexOf('来源定位降级') < 0; });
|
||
var baseContent = hasPrecise ? pageAiCleanDegradedCitationNotes(message.content) : String(message.content || '');
|
||
message.content = pageAiAppendCitationSection(baseContent, citations);
|
||
pageAiSyncCurrentSessionMessages();
|
||
pageAiPersistSessions();
|
||
renderPageAiConversation();
|
||
} catch (error) {
|
||
console.warn('MNote Page AI 自动追加 LightRAG 引用失败', error);
|
||
}
|
||
}
|
||
|
||
function pageAiFinishStreamingAssistantMessage(runId, finalText, promptText, citations) {
|
||
var id = String(runId || pageUiState.pageAiCurrentRunId || '');
|
||
var message = pageUiState.pageAiMessages.find(function(item) {
|
||
return item.role === 'assistant' && item.streaming === true && item.runId === id;
|
||
});
|
||
var text = String(finalText || (message && message.content) || '');
|
||
var hasPreciseCitation = pageAiNormalizeArray(citations).some(function(citation) {
|
||
return String(citation || '').indexOf('来源定位降级') < 0;
|
||
});
|
||
var contentBase = humanizePageAiResponse(text, promptText);
|
||
if (hasPreciseCitation) contentBase = pageAiCleanDegradedCitationNotes(contentBase);
|
||
var content = pageAiAppendCitationSection(contentBase, citations);
|
||
if (message) {
|
||
message.content = content;
|
||
message.streaming = false;
|
||
} else if (content) {
|
||
pageUiState.pageAiMessages.push({
|
||
role: 'assistant',
|
||
content: content,
|
||
runId: id
|
||
});
|
||
}
|
||
pageAiSyncCurrentSessionMessages();
|
||
pageAiPersistSessions();
|
||
void pageAiAppendKnowledgeRagFallbackCitations(id, promptText);
|
||
}
|
||
|
||
function pageAiToolOutputText(value) {
|
||
var parts = [];
|
||
function visit(node) {
|
||
if (node == null) return;
|
||
if (typeof node === 'string') {
|
||
parts.push(node);
|
||
return;
|
||
}
|
||
if (typeof node === 'number' || typeof node === 'boolean') return;
|
||
if (Array.isArray(node)) {
|
||
node.forEach(visit);
|
||
return;
|
||
}
|
||
if (typeof node !== 'object') return;
|
||
if (typeof node.text === 'string') parts.push(node.text);
|
||
if (typeof node.content === 'string') parts.push(node.content);
|
||
if (node.content && typeof node.content === 'object') visit(node.content);
|
||
if (node.output && typeof node.output === 'object') visit(node.output);
|
||
if (node.result && typeof node.result === 'object') visit(node.result);
|
||
}
|
||
visit(value);
|
||
return parts.join('\n');
|
||
}
|
||
|
||
function pageAiParseJsonMaybe(value) {
|
||
if (value && typeof value === 'object') return value;
|
||
var text = String(value || '').trim();
|
||
if (!text || (text[0] !== '{' && text[0] !== '[')) return null;
|
||
try {
|
||
return JSON.parse(text);
|
||
} catch (_error) {
|
||
var firstLine = text.split('\n')[0].trim();
|
||
if (firstLine && firstLine !== text && (firstLine[0] === '{' || firstLine[0] === '[')) {
|
||
try {
|
||
return JSON.parse(firstLine);
|
||
} catch (_lineError) {}
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function pageAiCollectCitationMarkdowns(value) {
|
||
var citations = [];
|
||
var seen = {};
|
||
function add(value) {
|
||
var citation = String(value || '').trim();
|
||
if (!citation || seen[citation]) return;
|
||
seen[citation] = true;
|
||
citations.push(citation);
|
||
}
|
||
function visit(node) {
|
||
if (node == null) return;
|
||
if (typeof node === 'string') {
|
||
var parsed = pageAiParseJsonMaybe(node);
|
||
if (parsed) visit(parsed);
|
||
return;
|
||
}
|
||
if (Array.isArray(node)) {
|
||
node.forEach(visit);
|
||
return;
|
||
}
|
||
if (typeof node !== 'object') return;
|
||
if (typeof node.citationMarkdown === 'string') add(pageAiCitationDisplayMarkdown(node));
|
||
if (Array.isArray(node.citationMarkdowns)) {
|
||
node.citationMarkdowns.forEach(function(value) {
|
||
if (typeof value === 'string') add(value);
|
||
else if (value && typeof value === 'object') add(pageAiCitationDisplayMarkdown(value));
|
||
else visit(value);
|
||
});
|
||
}
|
||
Object.keys(node).forEach(function(key) {
|
||
visit(node[key]);
|
||
});
|
||
}
|
||
visit(value);
|
||
return citations;
|
||
}
|
||
|
||
function pageAiCollectKnowledgeRagCitations(toolItem, toolEvent) {
|
||
var toolName = String((toolItem && toolItem.toolName) || (toolEvent && (toolEvent.name || toolEvent.tool || toolEvent.toolName)) || '');
|
||
var sources = [
|
||
toolEvent,
|
||
toolItem,
|
||
toolEvent && toolEvent.output,
|
||
toolEvent && toolEvent.result,
|
||
toolEvent && toolEvent.summary,
|
||
toolItem && toolItem.rawOutput,
|
||
toolItem && toolItem.rawResult,
|
||
toolItem && toolItem.resultSummary
|
||
];
|
||
var citations = [];
|
||
var outputText = '';
|
||
sources.forEach(function(source) {
|
||
citations = citations.concat(pageAiCollectCitationMarkdowns(source));
|
||
var sourceText = pageAiToolOutputText(source);
|
||
if (sourceText) outputText += '\n' + sourceText;
|
||
var parsed = pageAiParseJsonMaybe(sourceText);
|
||
if (parsed) citations = citations.concat(pageAiCollectCitationMarkdowns(parsed));
|
||
});
|
||
var looksLikeKnowledgeRag = toolName.indexOf('knowledge_rag') >= 0 ||
|
||
toolName.indexOf('knowledge.rag') >= 0 ||
|
||
outputText.indexOf('mnote.knowledge_rag') >= 0 ||
|
||
outputText.indexOf('uiCitations') >= 0;
|
||
if (!looksLikeKnowledgeRag && citations.length === 0) return [];
|
||
var hasPreciseCitation = citations.some(function(value) {
|
||
return String(value || '').indexOf('来源定位降级') < 0;
|
||
});
|
||
var seen = {};
|
||
return citations.filter(function(value) {
|
||
var citation = String(value || '').trim();
|
||
if (!citation || seen[citation]) return false;
|
||
if (hasPreciseCitation && citation.indexOf('来源定位降级') >= 0) return false;
|
||
seen[citation] = true;
|
||
return true;
|
||
});
|
||
}
|
||
|
||
function pageAiLooksLikeBlockEdit(prompt) {
|
||
var text = searchText(prompt);
|
||
return ['新增', '添加', '插入', '删除', '删掉', '修改', '替换', '改成', '移动', '移到', 'move', 'replace', 'delete', 'insert'].some(function(word) {
|
||
return text.indexOf(word) >= 0;
|
||
});
|
||
}
|
||
|
||
function assertPageAiLocalWritePermission(prompt, targetPackage) {
|
||
if (currentSourceKind() !== 'local_folder') return;
|
||
if (!pageAiLooksLikeBlockEdit(prompt)) return;
|
||
var target = pageAiNormalizeArray(targetPackage && targetPackage.targets)[0] || null;
|
||
var permission = String(target && target.policy && target.policy.permission || '').trim();
|
||
if (permission === 'read_write') return;
|
||
var error = new Error('当前本地工作区只读授权,不能让 AI 写入目标文件。请切换到有写权限的工作区或调整授权后重试。');
|
||
error.code = 'page_ai_target_readonly';
|
||
throw error;
|
||
}
|
||
|
||
async function pageAiTryBlockEditWorkflow(prompt, scopedContext, runTargetSnapshot) {
|
||
if (currentSourceKind() === 'local_folder') return false;
|
||
if (!pageAiLooksLikeBlockEdit(prompt)) return false;
|
||
var runId = 'page-ai-fast-' + Date.now().toString(36);
|
||
var traceId = 'page-ai-fast-' + Date.now().toString(36);
|
||
pageAiSetRunStatus('running', runId);
|
||
renderPageAiControls();
|
||
var started = Date.now();
|
||
var response = await fetch('/api/page-ai/block-edit-workflow', {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({
|
||
workspaceId: resolveWorkspaceId(document.body),
|
||
documentId: currentDocumentId(),
|
||
sourceKind: currentSourceKind(),
|
||
rootUri: currentRootUri(),
|
||
sessionId: pageUiState.pageAiActiveSessionId,
|
||
runId: runId,
|
||
profile: pageAiCurrentProfile(),
|
||
model: pageAiMnoteToolModel(),
|
||
message: prompt,
|
||
pageContext: scopedContext.pageContext,
|
||
editorTarget: scopedContext.editorTarget,
|
||
runTargetSnapshot: runTargetSnapshot || null,
|
||
selectedBlockId: scopedContext.selectedBlockId,
|
||
selectedText: scopedContext.selectedText,
|
||
traceId: traceId
|
||
})
|
||
});
|
||
var payload = await response.json().catch(function() { return null; });
|
||
if (!response.ok || !payload || payload.ok !== true) {
|
||
var code = payload && payload.code ? String(payload.code) : '';
|
||
if (code === 'page_ai_workflow_not_block_edit') return false;
|
||
pageUiState.pageAiMessages.push({
|
||
role: 'tool',
|
||
toolName: 'mnote.page_ai.block_edit_workflow',
|
||
status: 'failed',
|
||
toolCallId: runId,
|
||
resultSummary: pageAiErrorMessage(payload, 'fast_path_failed_' + response.status)
|
||
});
|
||
renderPageAiConversation();
|
||
pageAiSetRunStatus('failed', runId);
|
||
renderPageAiControls();
|
||
return true;
|
||
}
|
||
pageUiState.pageAiMessages.push({
|
||
role: 'tool',
|
||
toolName: 'mnote.page_ai.block_edit_workflow',
|
||
status: 'completed',
|
||
toolCallId: runId,
|
||
resultSummary: 'operations=' + String(Array.isArray(payload.operations) ? payload.operations.length : 0) + ' · ' + String(Date.now() - started) + 'ms'
|
||
});
|
||
pageUiState.pageAiMessages.push({
|
||
role: 'assistant',
|
||
content: payload.message || '已通过页面块编辑快路径完成写入。'
|
||
});
|
||
pageAiSetRunStatus('completed', runId);
|
||
try {
|
||
window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
|
||
detail: {
|
||
toolName: 'mnote.doc.apply_block_ops',
|
||
normalizedToolName: 'mnote.doc.apply.block.ops',
|
||
documentId: currentDocumentId(),
|
||
workspaceId: resolveWorkspaceId(document.body),
|
||
runId: runId,
|
||
traceId: traceId,
|
||
toolCallId: runId
|
||
}
|
||
}));
|
||
} catch (_) {}
|
||
var currentSession = pageAiCurrentSession();
|
||
if (currentSession) {
|
||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||
currentSession.updatedAt = Date.now();
|
||
}
|
||
pageAiPersistSessions();
|
||
renderPageAiConversation();
|
||
renderPageAiControls();
|
||
return true;
|
||
}
|
||
|
||
async function sendPageAiMessage(text) {
|
||
var allowQueue = ['running', 'tool_calling'].indexOf(pageUiState.pageAiRunStatus) >= 0;
|
||
if (pageUiState.pageAiBusy && !allowQueue) return;
|
||
var prompt = searchText(text);
|
||
if (!prompt) return;
|
||
if (!allowQueue) pageUiState.pageAiBusy = true;
|
||
var currentSession = null;
|
||
try {
|
||
await pageAiEnsureHermesSession();
|
||
if (!allowQueue) pageAiSetRunStatus('queued');
|
||
renderPageAiControls();
|
||
var contextSnapshot = currentPageAiContextSnapshot();
|
||
var scopedContext = pageAiScopedPageContext(contextSnapshot);
|
||
scopedContext.editorTarget = currentPageAiScopedEditorTarget();
|
||
assertPageAiTargetInCurrentWorkspace(scopedContext.editorTarget);
|
||
if (currentSourceKind() === 'local_folder' && !pageAiNormalizeArray(pageUiState.pageAiAllowedRoots).length) {
|
||
await pageAiLoadAllowedRoots();
|
||
}
|
||
await assertPageAiTargetWritable(scopedContext.editorTarget);
|
||
var runTargetSnapshot = pageAiBuildRunTargetSnapshot(scopedContext, prompt);
|
||
var contextRefs = pageAiBuildContextRefs(scopedContext, runTargetSnapshot);
|
||
var allowedRoots = pageAiBuildAllowedRoots();
|
||
var agentTargetPackage = pageAiBuildAgentTargetPackage(scopedContext, runTargetSnapshot);
|
||
assertPageAiLocalWritePermission(prompt, agentTargetPackage);
|
||
if (scopedContext.pageContext && scopedContext.pageContext.aiContext) {
|
||
scopedContext.pageContext.aiContext.runTargetSnapshot = runTargetSnapshot;
|
||
scopedContext.pageContext.aiContext.agentTargetPackage = agentTargetPackage;
|
||
}
|
||
var requestPageContext = pageAiPageContextForRefs(scopedContext.pageContext, contextRefs);
|
||
pageUiState.pageAiMessages.push({ role: 'user', content: prompt });
|
||
currentSession = pageAiCurrentSession();
|
||
var runProfile = pageAiRunProfile();
|
||
if (currentSession) {
|
||
if (currentSession.title === '新会话') {
|
||
currentSession.title = prompt.length > 18 ? prompt.slice(0, 18) + '…' : prompt;
|
||
}
|
||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||
currentSession.updatedAt = Date.now();
|
||
}
|
||
pageAiPersistSessions();
|
||
renderPageAiConversation();
|
||
if (!allowQueue && await pageAiTryBlockEditWorkflow(prompt, scopedContext, runTargetSnapshot)) {
|
||
return;
|
||
}
|
||
var response = await fetch('/api/hermes/client/runs', {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({
|
||
workspaceId: resolveWorkspaceId(document.body),
|
||
documentId: currentDocumentId(),
|
||
sourceKind: currentSourceKind(),
|
||
rootUri: currentRootUri(),
|
||
agentId: pageAiCurrentAgentId(),
|
||
sessionId: pageUiState.pageAiActiveSessionId,
|
||
acpSessionId: currentSession && currentSession.acpSessionId ? currentSession.acpSessionId : '',
|
||
profile: runProfile,
|
||
profileId: pageUiState.pageAiAcpRuntime === 'hermes' ? runProfile : '',
|
||
acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
|
||
contextScope: pageUiState.pageAiContextScope,
|
||
contextRefs: contextRefs,
|
||
allowedRoots: allowedRoots,
|
||
skillPreferences: {
|
||
mnote: pageAiSkillPreferenceTable('mnote', ''),
|
||
reasonix: pageAiSkillPreferenceTable('reasonix', ''),
|
||
hermes: pageAiSkillPreferenceTable('hermes', runProfile)
|
||
},
|
||
message: prompt,
|
||
model: pageAiMnoteToolModel(),
|
||
pageContext: requestPageContext,
|
||
editorTarget: scopedContext.editorTarget,
|
||
runTargetSnapshot: runTargetSnapshot,
|
||
targetPackage: agentTargetPackage,
|
||
selectedBlockId: scopedContext.selectedBlockId,
|
||
selectedText: scopedContext.selectedText || '',
|
||
traceId: 'page-ai-run-' + Date.now().toString(36)
|
||
})
|
||
});
|
||
if (!response.ok) {
|
||
var errorPayload = await response.json().catch(function(){ return null; });
|
||
throw new Error(pageAiErrorMessage(errorPayload, 'page_ai_failed_' + response.status));
|
||
}
|
||
var runPayload = await response.json().catch(function(){ return null; });
|
||
if (pageAiApplyQueuedRun(runPayload)) {
|
||
pageUiState.pageAiMessages.push({
|
||
role: 'assistant',
|
||
content: '已加入 AI 队列,前一条 run 完成后继续处理。'
|
||
});
|
||
if (currentSession) {
|
||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||
currentSession.updatedAt = Date.now();
|
||
}
|
||
pageAiPersistSessions();
|
||
renderPageAiConversation();
|
||
renderPageAiControls();
|
||
return;
|
||
}
|
||
if (allowQueue) {
|
||
throw new Error('hermes_queue_expected_queued_response');
|
||
}
|
||
var upstream = runPayload && runPayload.upstream ? runPayload.upstream : runPayload;
|
||
var runId = upstream && (upstream.run_id || upstream.runId);
|
||
if (!runId) throw new Error('hermes_run_missing_run_id');
|
||
var runTraceId = String((upstream && (upstream.trace_id || upstream.traceId)) || (runPayload && (runPayload.trace_id || runPayload.traceId)) || '');
|
||
pageAiSetRunStatus('running', runId);
|
||
pageAiSetRunTargetSnapshot(Object.assign({}, runTargetSnapshot, {
|
||
runId: runId,
|
||
sessionId: pageUiState.pageAiActiveSessionId,
|
||
traceId: runTraceId
|
||
}));
|
||
renderPageAiControls();
|
||
var eventResponse = await fetch('/api/hermes/client/events/' + encodeURIComponent(runId), {
|
||
headers: { 'accept': 'text/event-stream' }
|
||
});
|
||
if (!eventResponse.ok) {
|
||
var eventError = await eventResponse.json().catch(function(){ return null; });
|
||
throw new Error(pageAiErrorMessage(eventError, 'hermes_events_failed_' + eventResponse.status));
|
||
}
|
||
var assistantText = '';
|
||
var autoCitations = [];
|
||
await streamPageAiResponse(eventResponse, function(eventName, payloadText) {
|
||
if (eventName === 'message.delta') {
|
||
if (pageUiState.pageAiStoppedRunIds[runId]) return;
|
||
assistantText = pageAiAppendStreamingAssistantDelta(runId, pageAiDecodeDeltaText(payloadText));
|
||
}
|
||
if (eventName === 'thought.delta') {
|
||
try {
|
||
var thoughtPayload = JSON.parse(payloadText || 'null');
|
||
var thoughtText = String((thoughtPayload && (thoughtPayload.delta || thoughtPayload.text)) || '');
|
||
if (thoughtText) {
|
||
var msgs = pageUiState.pageAiMessages;
|
||
var lastThought = msgs.length > 0 && msgs[msgs.length - 1].kind === 'thought' ? msgs[msgs.length - 1] : null;
|
||
if (lastThought) {
|
||
lastThought.content += thoughtText;
|
||
} else {
|
||
msgs.push({ role: 'assistant', kind: 'thought', content: thoughtText });
|
||
}
|
||
pageAiSyncCurrentSessionMessages();
|
||
pageAiPersistSessions();
|
||
renderPageAiConversation();
|
||
}
|
||
} catch (_) {}
|
||
}
|
||
if (eventName === 'usage.updated') {
|
||
try {
|
||
var usagePayload = JSON.parse(payloadText || 'null') || {};
|
||
var sessionForUsage = pageAiCurrentSession();
|
||
if (sessionForUsage) {
|
||
sessionForUsage.usage = {
|
||
source: 'usage_update',
|
||
used: Number(usagePayload.used ?? usagePayload.contextUsed ?? 0),
|
||
size: Number(usagePayload.size ?? usagePayload.contextSize ?? 0)
|
||
};
|
||
pageAiPersistSessions();
|
||
}
|
||
} catch (_) {}
|
||
renderPageAiControls();
|
||
}
|
||
if (eventName === 'permission.requested' || eventName === 'permission.denied' || eventName === 'permission.allowed') {
|
||
pageAiApplyPermissionEvent(eventName, payloadText);
|
||
pageAiSyncCurrentSessionMessages();
|
||
pageAiPersistSessions();
|
||
renderPageAiConversation();
|
||
renderPageAiControls();
|
||
}
|
||
if (eventName === 'run.completed') {
|
||
try {
|
||
var completed = JSON.parse(payloadText || 'null');
|
||
if (completed && completed.output) assistantText = String(completed.output || '');
|
||
if (completed && completed.usage) {
|
||
var completedSession = pageAiCurrentSession();
|
||
if (completedSession) completedSession.usage = completed.usage;
|
||
}
|
||
var agentAudit = completed && completed.agentAudit && typeof completed.agentAudit === 'object' ? completed.agentAudit : null;
|
||
var agentRunReceipt = agentAudit && agentAudit.agentRunReceipt && typeof agentAudit.agentRunReceipt === 'object'
|
||
? agentAudit.agentRunReceipt
|
||
: null;
|
||
var changedFiles = pageAiNormalizeArray(agentAudit && agentAudit.changedFiles).map(function(file) {
|
||
if (!file || typeof file !== 'object') return file;
|
||
return Object.assign({
|
||
actorId: String(agentAudit && agentAudit.actorId || ''),
|
||
actorType: String(agentAudit && agentAudit.actorType || ''),
|
||
agentKind: String(agentAudit && agentAudit.agentKind || ''),
|
||
runId: runId
|
||
}, file);
|
||
});
|
||
if (changedFiles.length) {
|
||
pageUiState.pageAiMessages.push({
|
||
role: 'tool',
|
||
content: 'agent.changed_files',
|
||
toolCallId: runId + ':agent.changed_files',
|
||
toolName: 'agent.changed_files',
|
||
toolKind: 'audit',
|
||
status: 'completed',
|
||
argsSummary: [agentAudit.rootUri, agentAudit.actorId, agentAudit.agentKind].map(function(value) {
|
||
return String(value || '').trim();
|
||
}).filter(Boolean).join(' · '),
|
||
resultSummary: String(agentAudit.diffSummary || changedFiles.length + ' changed file(s)'),
|
||
changedFiles: changedFiles,
|
||
traceId: runTraceId,
|
||
auditId: String(agentAudit.eventId || '')
|
||
});
|
||
}
|
||
if (agentRunReceipt || changedFiles.length) {
|
||
try {
|
||
pageAiDispatchReceiptRefresh(agentRunReceipt, changedFiles, agentAudit, runId, runTraceId);
|
||
} catch (_) {}
|
||
}
|
||
} catch (_) {}
|
||
pageAiSetRunStatus('completed', runId);
|
||
}
|
||
if (eventName === 'run.failed') {
|
||
try {
|
||
var failed = JSON.parse(payloadText || 'null');
|
||
assistantText = String((failed && (failed.message || failed.code || failed.error)) || 'AI run failed');
|
||
} catch (_) {}
|
||
pageAiSetRunStatus('failed', runId);
|
||
}
|
||
if (eventName === 'run.aborted' || eventName === 'run.cancelled' || eventName === 'run.canceled') {
|
||
pageUiState.pageAiStoppedRunIds[runId] = true;
|
||
pageAiSetRunStatus('aborted', runId);
|
||
}
|
||
if (eventName === 'session.info.updated') {
|
||
try {
|
||
var infoPayload = JSON.parse(payloadText || 'null') || {};
|
||
var newTitle = String(infoPayload.title || '').trim();
|
||
var acpSessionId = String(infoPayload.acpSessionId || infoPayload.acp_session_id || '').trim();
|
||
var sessionForInfo = pageAiCurrentSession();
|
||
if (acpSessionId && sessionForInfo) {
|
||
sessionForInfo.acpSessionId = acpSessionId;
|
||
sessionForInfo.updatedAt = Date.now();
|
||
pageAiPersistSessions();
|
||
}
|
||
if (newTitle) {
|
||
var sessionForTitle = sessionForInfo || pageAiCurrentSession();
|
||
if (sessionForTitle) {
|
||
sessionForTitle.title = newTitle;
|
||
sessionForTitle.updatedAt = Date.now();
|
||
pageAiPersistSessions();
|
||
renderPageAiControls();
|
||
}
|
||
}
|
||
} catch (_) {}
|
||
}
|
||
if (eventName === 'plan.updated') {
|
||
try {
|
||
var planPayload = JSON.parse(payloadText || 'null') || {};
|
||
var planEntries = Array.isArray(planPayload.entries) ? planPayload.entries : [];
|
||
if (planEntries.length) {
|
||
var planMsgs = pageUiState.pageAiMessages;
|
||
var existingPlan = planMsgs.length > 0 && planMsgs[planMsgs.length - 1].kind === 'plan' ? planMsgs[planMsgs.length - 1] : null;
|
||
if (existingPlan) {
|
||
existingPlan.entries = planEntries;
|
||
existingPlan.updatedAt = Date.now();
|
||
} else {
|
||
planMsgs.push({ role: 'system', kind: 'plan', entries: planEntries, createdAt: Date.now(), updatedAt: Date.now() });
|
||
}
|
||
pageAiSyncCurrentSessionMessages();
|
||
pageAiPersistSessions();
|
||
renderPageAiConversation();
|
||
}
|
||
} catch (_) {}
|
||
}
|
||
if (eventName === 'tool.started' || eventName === 'tool.completed' || eventName === 'tool.failed') {
|
||
var toolEventPayload = null;
|
||
try {
|
||
toolEventPayload = JSON.parse(payloadText || 'null');
|
||
} catch (_) {}
|
||
var toolItem = pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId);
|
||
if (eventName === 'tool.completed') {
|
||
var completedCitations = pageAiCollectKnowledgeRagCitations(toolItem, toolEventPayload);
|
||
if (completedCitations.length) autoCitations = completedCitations;
|
||
}
|
||
pageAiSyncCurrentSessionMessages();
|
||
pageAiPersistSessions();
|
||
renderPageAiConversation();
|
||
pageAiSetRunStatus('tool_calling', runId);
|
||
renderPageAiControls();
|
||
}
|
||
});
|
||
if (!pageUiState.pageAiStoppedRunIds[runId]) {
|
||
pageAiFinishStreamingAssistantMessage(runId, assistantText, prompt, autoCitations);
|
||
}
|
||
currentSession = pageAiCurrentSession();
|
||
if (currentSession) {
|
||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||
currentSession.updatedAt = Date.now();
|
||
}
|
||
pageAiPersistSessions();
|
||
} catch (error) {
|
||
pageAiSetRunStatus('failed');
|
||
pageUiState.pageAiMessages.push({
|
||
role: 'assistant',
|
||
content: 'AI 当前请求失败:' + (error instanceof Error ? error.message : String(error))
|
||
});
|
||
currentSession = pageAiCurrentSession();
|
||
if (currentSession) {
|
||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||
currentSession.updatedAt = Date.now();
|
||
}
|
||
pageAiPersistSessions();
|
||
} finally {
|
||
if (!allowQueue) pageUiState.pageAiBusy = false;
|
||
renderPageAiConversation();
|
||
renderPageAiControls();
|
||
}
|
||
}
|
||
|
||
function handlePageAiClick(event, helpers) {
|
||
var closestAction = helpers && helpers.closestAction;
|
||
if (typeof closestAction !== 'function') return false;
|
||
var pageAiCitationLink = closestAction(event.target, 'a[data-page-ai-citation-link="true"]');
|
||
if (pageAiCitationLink && !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) {
|
||
var href = pageAiCitationLink.getAttribute('href') || '';
|
||
if (pageAiOpenCitationUrl(href)) {
|
||
event.preventDefault();
|
||
return true;
|
||
}
|
||
}
|
||
var pageAiClose = closestAction(event.target, '[data-page-ai-action="close"]');
|
||
if (pageAiClose) {
|
||
event.preventDefault();
|
||
closePageAiDrawer();
|
||
return true;
|
||
}
|
||
var pageAiSettings = closestAction(event.target, '[data-page-ai-action="open-hermes-settings"]');
|
||
if (pageAiSettings) {
|
||
event.preventDefault();
|
||
pageAiOpenHermesSettings();
|
||
return true;
|
||
}
|
||
var pageAiStop = closestAction(event.target, '[data-page-ai-action="stop-run"]');
|
||
if (pageAiStop) {
|
||
event.preventDefault();
|
||
void pageAiStopRun();
|
||
return true;
|
||
}
|
||
var pageAiRotate = closestAction(event.target, '[data-page-ai-action="rotate"]');
|
||
if (pageAiRotate) {
|
||
event.preventDefault();
|
||
pageUiState.pageAiSuggestionIndex += 1;
|
||
renderPageAiSuggestions();
|
||
return true;
|
||
}
|
||
var pageAiIntent = closestAction(event.target, '[data-page-ai-intent]');
|
||
if (pageAiIntent) {
|
||
event.preventDefault();
|
||
var intentName = pageAiIntent.getAttribute('data-page-ai-intent') || '';
|
||
if (intentName === 'create-summary') void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_summary,为当前页面创建或更新 AI Summary。');
|
||
if (intentName === 'create-ai-note') void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_ai_note,基于当前页面创建一篇新的 AI Note。');
|
||
return true;
|
||
}
|
||
var pageAiTab = closestAction(event.target, '[data-page-ai-tab]');
|
||
if (pageAiTab) {
|
||
event.preventDefault();
|
||
pageUiState.pageAiPage = pageAiTab.getAttribute('data-page-ai-tab') || 'chat';
|
||
if (pageUiState.pageAiPage === 'runtime') void pageAiLoadGatewayHealth();
|
||
if (pageUiState.pageAiPage === 'skills') void pageAiLoadSkills();
|
||
renderPageAiControls();
|
||
renderPageAiConversation();
|
||
return true;
|
||
}
|
||
var pageAiProvider = closestAction(event.target, '[data-page-ai-provider]');
|
||
if (pageAiProvider) {
|
||
event.preventDefault();
|
||
pageUiState.pageAiProvider = pageAiProvider.getAttribute('data-page-ai-provider') || 'hermes';
|
||
renderPageAiProviderButtons();
|
||
return true;
|
||
}
|
||
var pageAiAgent = closestAction(event.target, '[data-page-ai-agent-id]');
|
||
if (pageAiAgent) {
|
||
event.preventDefault();
|
||
var nextAgentId = pageAiAgent.getAttribute('data-page-ai-agent-id') || 'reasonix';
|
||
var nextProfileId = pageAiAgent.getAttribute('data-page-ai-profile-id') || '';
|
||
pageAiSetAgentId(nextAgentId);
|
||
if (nextProfileId) {
|
||
pageUiState.pageAiAgentPopoverOpen = false;
|
||
void pageAiSwitchProfile(nextProfileId);
|
||
renderPageAiControls();
|
||
}
|
||
return true;
|
||
}
|
||
var pageAiAgentButton = closestAction(event.target, '[data-page-ai-agent-button]');
|
||
if (pageAiAgentButton) {
|
||
event.preventDefault();
|
||
pageAiSetAgentPopoverOpen(pageAiAgentButton.getAttribute('aria-expanded') !== 'true');
|
||
return true;
|
||
}
|
||
var pageAiContextButton = closestAction(event.target, '[data-page-ai-context-button]');
|
||
if (pageAiContextButton) {
|
||
event.preventDefault();
|
||
pageAiSetContextPopoverOpen(pageAiContextButton.getAttribute('aria-expanded') !== 'true');
|
||
return true;
|
||
}
|
||
var pageAiTargetButton = closestAction(event.target, '[data-page-ai-target-button]');
|
||
if (pageAiTargetButton) {
|
||
event.preventDefault();
|
||
pageAiSetTargetPopoverOpen(pageAiTargetButton.getAttribute('aria-expanded') !== 'true');
|
||
return true;
|
||
}
|
||
var pageAiAction = closestAction(event.target, '[data-page-ai-action]');
|
||
if (pageAiAction) {
|
||
event.preventDefault();
|
||
var action = pageAiAction.getAttribute('data-page-ai-action') || '';
|
||
if (action === 'close-agent-popover') pageAiSetAgentPopoverOpen(false);
|
||
if (action === 'close-context-popover') pageAiSetContextPopoverOpen(false);
|
||
if (action === 'close-target-popover') pageAiSetTargetPopoverOpen(false);
|
||
if (action === 'new-session') {
|
||
pageUiState.pageAiPage = 'chat';
|
||
pageAiStartNewSession();
|
||
}
|
||
if (action === 'history') {
|
||
pageAiLoadSessions();
|
||
pageUiState.pageAiPage = pageUiState.pageAiPage === 'history' ? 'chat' : 'history';
|
||
renderPageAiControls();
|
||
renderPageAiConversation();
|
||
if (pageUiState.pageAiPage === 'history') {
|
||
void pageAiLoadBackendSessions().catch(function(error) {
|
||
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
||
renderPageAiControls();
|
||
});
|
||
}
|
||
}
|
||
if (action === 'send') {
|
||
var input = ensurePageAiDrawer().querySelector('[data-page-ai-input]');
|
||
if (input instanceof HTMLTextAreaElement) {
|
||
var message = input.value;
|
||
input.value = '';
|
||
void sendPageAiMessage(message);
|
||
}
|
||
}
|
||
if (action === 'cancel-queued-run') {
|
||
void pageAiCancelQueuedRun(pageAiAction.getAttribute('data-page-ai-queue-id'));
|
||
}
|
||
return true;
|
||
}
|
||
var pageAiTargetOption = closestAction(event.target, '[data-page-ai-target-option]');
|
||
if (pageAiTargetOption) {
|
||
event.preventDefault();
|
||
pageAiSelectTarget(pageAiTargetOption.getAttribute('data-page-ai-target-option') || '');
|
||
return true;
|
||
}
|
||
var pageAiContextRef = closestAction(event.target, '[data-page-ai-context-ref]');
|
||
if (pageAiContextRef) {
|
||
event.preventDefault();
|
||
pageAiToggleContextRef(pageAiContextRef.getAttribute('data-page-ai-context-ref') || '');
|
||
return true;
|
||
}
|
||
var pageAiMemorySave = closestAction(event.target, '[data-page-ai-memory-save]');
|
||
if (pageAiMemorySave) {
|
||
event.preventDefault();
|
||
void pageAiSaveProfileMemory(pageAiMemorySave.getAttribute('data-page-ai-memory-save') || '');
|
||
return true;
|
||
}
|
||
var pageAiSkillToggle = closestAction(event.target, '[data-page-ai-skill-toggle]');
|
||
if (pageAiSkillToggle) {
|
||
event.preventDefault();
|
||
void pageAiToggleSkill(
|
||
pageAiSkillToggle.getAttribute('data-page-ai-skill-toggle') || '',
|
||
pageAiSkillToggle.getAttribute('aria-pressed') !== 'true',
|
||
pageAiSkillToggle.getAttribute('data-page-ai-skill-group') || '',
|
||
pageAiSkillToggle.getAttribute('data-page-ai-skill-profile') || ''
|
||
);
|
||
return true;
|
||
}
|
||
var pageAiSkillGroupToggle = closestAction(event.target, '[data-page-ai-skill-group-toggle]');
|
||
if (pageAiSkillGroupToggle) {
|
||
event.preventDefault();
|
||
pageAiToggleSkillGroup(pageAiSkillGroupToggle.getAttribute('data-page-ai-skill-group-toggle') || '');
|
||
return true;
|
||
}
|
||
var pageAiToolToggle = closestAction(event.target, '[data-page-ai-tool-toggle]');
|
||
if (pageAiToolToggle) {
|
||
event.preventDefault();
|
||
void pageAiToggleTool(pageAiToolToggle.getAttribute('data-page-ai-tool-toggle') || '', pageAiToolToggle.getAttribute('aria-pressed') !== 'true');
|
||
return true;
|
||
}
|
||
var pageAiSessionResume = closestAction(event.target, '[data-page-ai-session-resume]');
|
||
if (pageAiSessionResume) {
|
||
event.preventDefault();
|
||
void pageAiResumeBackendSession(pageAiSessionResume.getAttribute('data-page-ai-session-resume') || '').catch(function(error) {
|
||
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
||
renderPageAiControls();
|
||
});
|
||
return true;
|
||
}
|
||
var pageAiSessionRename = closestAction(event.target, '[data-page-ai-session-rename]');
|
||
if (pageAiSessionRename) {
|
||
event.preventDefault();
|
||
void pageAiRenameBackendSession(pageAiSessionRename.getAttribute('data-page-ai-session-rename') || '').catch(function(error) {
|
||
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
||
renderPageAiControls();
|
||
});
|
||
return true;
|
||
}
|
||
var pageAiSessionDelete = closestAction(event.target, '[data-page-ai-session-delete]');
|
||
if (pageAiSessionDelete) {
|
||
event.preventDefault();
|
||
void pageAiDeleteBackendSession(pageAiSessionDelete.getAttribute('data-page-ai-session-delete') || '').catch(function(error) {
|
||
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
||
renderPageAiControls();
|
||
});
|
||
return true;
|
||
}
|
||
var pageAiPermissionAction = closestAction(event.target, '[data-page-ai-permission-action]');
|
||
if (pageAiPermissionAction) {
|
||
event.preventDefault();
|
||
pageAiResolvePermission(pageAiPermissionAction.getAttribute('data-page-ai-permission-id') || '', pageAiPermissionAction.getAttribute('data-page-ai-permission-action') || 'deny');
|
||
return true;
|
||
}
|
||
var pageAiOpenLocationAction = closestAction(event.target, '[data-page-ai-open-location]');
|
||
if (pageAiOpenLocationAction) {
|
||
event.preventDefault();
|
||
var loc = String(pageAiOpenLocationAction.getAttribute('data-page-ai-open-location') || '').trim();
|
||
if (loc) pageAiOpenLocation(loc);
|
||
return true;
|
||
}
|
||
var pageAiSession = closestAction(event.target, '[data-page-ai-session]');
|
||
if (pageAiSession) {
|
||
event.preventDefault();
|
||
pageAiSetActiveSession(pageAiSession.getAttribute('data-page-ai-session') || '');
|
||
return true;
|
||
}
|
||
var pageAiSuggestion = closestAction(event.target, '[data-page-ai-suggestion]');
|
||
if (pageAiSuggestion) {
|
||
event.preventDefault();
|
||
var text = pageAiSuggestion.getAttribute('data-page-ai-suggestion') || '';
|
||
var inputNode = ensurePageAiDrawer().querySelector('[data-page-ai-input]');
|
||
if (inputNode instanceof HTMLTextAreaElement) {
|
||
inputNode.value = text;
|
||
inputNode.focus();
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function handlePageAiKeyDown(event, helpers) {
|
||
var closestAction = helpers && helpers.closestAction;
|
||
if (typeof closestAction !== 'function') return false;
|
||
if (event.key === 'Enter' && !event.shiftKey) {
|
||
var aiInput = closestAction(event.target, '[data-page-ai-input]');
|
||
if (aiInput instanceof HTMLTextAreaElement) {
|
||
event.preventDefault();
|
||
var text = aiInput.value;
|
||
aiInput.value = '';
|
||
void sendPageAiMessage(text);
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function handlePageAiInput(event, helpers) {
|
||
var closestAction = helpers && helpers.closestAction;
|
||
if (typeof closestAction !== 'function') return false;
|
||
var skillSearch = closestAction(event.target, '[data-page-ai-skill-search]');
|
||
if (skillSearch instanceof HTMLInputElement) {
|
||
pageUiState.pageAiSkillQuery = skillSearch.value;
|
||
renderPageAiControls();
|
||
return true;
|
||
}
|
||
var sessionSearch = closestAction(event.target, '[data-page-ai-session-search]');
|
||
if (sessionSearch instanceof HTMLInputElement) {
|
||
var sessionQuery = sessionSearch.value;
|
||
window.clearTimeout(pageUiState.pageAiSessionSearchTimer || 0);
|
||
pageUiState.pageAiSessionSearchTimer = window.setTimeout(function() {
|
||
void pageAiSearchBackendSessions(sessionQuery).catch(function(error) {
|
||
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
||
renderPageAiControls();
|
||
});
|
||
}, 200);
|
||
return true;
|
||
}
|
||
var memoryEditor = closestAction(event.target, '[data-page-ai-memory-editor]');
|
||
if (memoryEditor instanceof HTMLTextAreaElement) {
|
||
var section = memoryEditor.getAttribute('data-page-ai-memory-editor') || '';
|
||
if (['memory', 'user', 'soul'].indexOf(section) >= 0) pageUiState.pageAiProfileMemoryDrafts[section] = memoryEditor.value;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function handlePageAiChange(event, helpers) {
|
||
var closestAction = helpers && helpers.closestAction;
|
||
if (typeof closestAction !== 'function') return false;
|
||
var pageAiAcpRuntimeSelect = closestAction(event.target, '[data-page-ai-acp-runtime]');
|
||
if (pageAiAcpRuntimeSelect instanceof HTMLSelectElement) {
|
||
var next = String(pageAiAcpRuntimeSelect.value || 'reasonix').trim() || 'reasonix';
|
||
pageUiState.pageAiAcpRuntime = next;
|
||
pageUiState.pageAiSkills = { categories: [], archived: [] };
|
||
pageUiState.pageAiSkillError = '';
|
||
pageAiPersistSessions();
|
||
void pageAiLoadProfiles();
|
||
if (next !== 'reasonix') void pageAiLoadProfileMemory();
|
||
void pageAiLoadSkills();
|
||
void pageAiLoadBackendSessions().catch(function(error) {
|
||
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
|
||
});
|
||
renderPageAiControls();
|
||
renderPageAiProviderButtons();
|
||
return true;
|
||
}
|
||
var pageAiProfileSelect = closestAction(event.target, '[data-page-ai-profile-select]');
|
||
if (pageAiProfileSelect instanceof HTMLSelectElement) {
|
||
void pageAiSwitchProfile(pageAiProfileSelect.value);
|
||
return true;
|
||
}
|
||
var pageAiSkillSourceSelect = closestAction(event.target, '[data-page-ai-skill-source-select]');
|
||
if (pageAiSkillSourceSelect instanceof HTMLSelectElement) {
|
||
pageAiSetSkillSource(pageAiSkillSourceSelect.value);
|
||
return true;
|
||
}
|
||
var pageAiSessionAgentFilter = closestAction(event.target, '[data-page-ai-session-agent-filter]');
|
||
if (pageAiSessionAgentFilter instanceof HTMLSelectElement) {
|
||
pageUiState.pageAiSessionAgentFilter = String(pageAiSessionAgentFilter.value || 'all').trim() || 'all';
|
||
renderPageAiControls();
|
||
renderPageAiConversation();
|
||
return true;
|
||
}
|
||
var pageAiHideHermesBuiltin = closestAction(event.target, '[data-page-ai-hide-hermes-builtin]');
|
||
if (pageAiHideHermesBuiltin instanceof HTMLInputElement) {
|
||
pageAiSetHideHermesBuiltinSkills(pageAiHideHermesBuiltin.checked);
|
||
return true;
|
||
}
|
||
var pageAiReasonixMemory = closestAction(event.target, '[data-page-ai-reasonix-memory]');
|
||
if (pageAiReasonixMemory instanceof HTMLInputElement) {
|
||
pageAiSetReasonixMemoryEnabled(pageAiReasonixMemory.checked);
|
||
return true;
|
||
}
|
||
var pageAiContextSelect = closestAction(event.target, '[data-page-ai-context-scope]');
|
||
if (pageAiContextSelect instanceof HTMLSelectElement) {
|
||
pageAiSetContextScope(pageAiContextSelect.value);
|
||
renderPageAiControls();
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function installPageAiDelegates() {
|
||
if (pageAiDelegatesInstalled) return;
|
||
pageAiDelegatesInstalled = true;
|
||
var helpers = { closestAction: closestAction };
|
||
document.addEventListener('click', function(event) {
|
||
handlePageAiClick(event, helpers);
|
||
});
|
||
document.addEventListener('pointerdown', function(event) {
|
||
handlePageAiPointerDown(event, helpers);
|
||
});
|
||
document.addEventListener('keydown', function(event) {
|
||
handlePageAiKeyDown(event, helpers);
|
||
});
|
||
document.addEventListener('input', function(event) {
|
||
handlePageAiInput(event, helpers);
|
||
});
|
||
document.addEventListener('change', function(event) {
|
||
handlePageAiChange(event, helpers);
|
||
});
|
||
}
|
||
|
||
|
||
return {
|
||
ensurePageAiStateFacade: (...args) => ensurePageAiStateFacade(...args),
|
||
installPageAiDelegates: (...args) => installPageAiDelegates(...args),
|
||
handlePageAiClick: (...args) => handlePageAiClick(...args),
|
||
handlePageAiKeyDown: (...args) => handlePageAiKeyDown(...args),
|
||
handlePageAiInput: (...args) => handlePageAiInput(...args),
|
||
handlePageAiChange: (...args) => handlePageAiChange(...args),
|
||
openPageAiDrawer: (...args) => openPageAiDrawer(...args),
|
||
closePageAiDrawer: (...args) => closePageAiDrawer(...args),
|
||
isPageAiDrawerOpen: (...args) => isPageAiDrawerOpen(...args),
|
||
ensurePageAiDrawer: (...args) => ensurePageAiDrawer(...args),
|
||
sendPageAiMessage: (...args) => sendPageAiMessage(...args),
|
||
pageAiOpenHermesSettings: (...args) => pageAiOpenHermesSettings(...args),
|
||
pageAiStopRun: (...args) => pageAiStopRun(...args),
|
||
pageAiLoadGatewayHealth: (...args) => pageAiLoadGatewayHealth(...args),
|
||
renderPageAiControls: (...args) => renderPageAiControls(...args),
|
||
renderPageAiConversation: (...args) => renderPageAiConversation(...args),
|
||
renderPageAiProviderButtons: (...args) => renderPageAiProviderButtons(...args),
|
||
renderPageAiSuggestions: (...args) => renderPageAiSuggestions(...args),
|
||
pageAiSaveProfileMemory: (...args) => pageAiSaveProfileMemory(...args),
|
||
pageAiToggleSkill: (...args) => pageAiToggleSkill(...args),
|
||
pageAiToggleTool: (...args) => pageAiToggleTool(...args),
|
||
pageAiResumeBackendSession: (...args) => pageAiResumeBackendSession(...args),
|
||
pageAiRenameBackendSession: (...args) => pageAiRenameBackendSession(...args),
|
||
pageAiDeleteBackendSession: (...args) => pageAiDeleteBackendSession(...args),
|
||
pageAiResolvePermission: (...args) => pageAiResolvePermission(...args),
|
||
pageAiOpenLocation: (...args) => pageAiOpenLocation(...args),
|
||
pageAiSetActiveSession: (...args) => pageAiSetActiveSession(...args),
|
||
pageAiStartNewSession: (...args) => pageAiStartNewSession(...args),
|
||
pageAiLoadSessions: (...args) => pageAiLoadSessions(...args),
|
||
pageAiLoadBackendSessions: (...args) => pageAiLoadBackendSessions(...args),
|
||
pageAiCancelQueuedRun: (...args) => pageAiCancelQueuedRun(...args),
|
||
pageAiSearchBackendSessions: (...args) => pageAiSearchBackendSessions(...args),
|
||
pageAiSetTargetPopoverOpen: (...args) => pageAiSetTargetPopoverOpen(...args),
|
||
pageAiSelectTarget: (...args) => pageAiSelectTarget(...args),
|
||
pageAiPersistSessions: (...args) => pageAiPersistSessions(...args),
|
||
pageAiLoadProfiles: (...args) => pageAiLoadProfiles(...args),
|
||
pageAiLoadProfileMemory: (...args) => pageAiLoadProfileMemory(...args),
|
||
pageAiLoadSkills: (...args) => pageAiLoadSkills(...args),
|
||
pageAiSwitchProfile: (...args) => pageAiSwitchProfile(...args),
|
||
pageAiToggleSkillGroup: (...args) => pageAiToggleSkillGroup(...args),
|
||
pageAiSetReasonixMemoryEnabled: (...args) => pageAiSetReasonixMemoryEnabled(...args),
|
||
pageAiSetHideHermesBuiltinSkills: (...args) => pageAiSetHideHermesBuiltinSkills(...args),
|
||
pageAiSetSkillSource: (...args) => pageAiSetSkillSource(...args),
|
||
pageAiSetContextScope: (...args) => pageAiSetContextScope(...args),
|
||
pageAiSetAgentId: (...args) => pageAiSetAgentId(...args),
|
||
pageAiToggleContextRef: (...args) => pageAiToggleContextRef(...args),
|
||
pageAiSetContextPopoverOpen: (...args) => pageAiSetContextPopoverOpen(...args),
|
||
pageAiSetAgentPopoverOpen: (...args) => pageAiSetAgentPopoverOpen(...args),
|
||
pageAiLoadAllowedRoots: (...args) => pageAiLoadAllowedRoots(...args),
|
||
updatePageAiTriggerState: (...args) => updatePageAiTriggerState(...args)
|
||
};
|
||
}
|