Improve local evidence search and AI capabilities

This commit is contained in:
lix-2026
2026-06-05 23:00:53 +08:00
parent a1dcfc9f76
commit 4dbd9a978b
52 changed files with 6415 additions and 527 deletions
@@ -267,7 +267,15 @@ import {
root.setAttribute('data-mnote-evidence-open', 'true');
if (blockId) root.setAttribute('data-mnote-evidence-block-id', blockId);
if (lineRange) root.setAttribute('data-mnote-evidence-line-range', lineRange);
const target = blockId ? root.querySelector(`[data-block-id="${cssSafe(blockId)}"]`) : null;
let target = blockId ? root.querySelector(`[data-block-id="${cssSafe(blockId)}"]`) : null;
if (!(target instanceof HTMLElement) && lineRange) {
const start = Number(String(lineRange).split('-')[0] || 0);
if (Number.isFinite(start) && start > 0) {
const blocks = Array.from(root.querySelectorAll('.ProseMirror [data-block-id]'))
.filter((node) => node instanceof HTMLElement);
target = blocks[Math.max(0, Math.min(blocks.length - 1, start - 1))] || null;
}
}
if (target instanceof HTMLElement) {
root.querySelectorAll('[data-mnote-evidence-text-highlight="true"]').forEach((node) => {
if (node instanceof HTMLElement) node.removeAttribute('data-mnote-evidence-text-highlight');
@@ -302,6 +310,12 @@ import {
title,
fileName: title,
openTarget: 'active-tab',
page: url.searchParams.get('page') || undefined,
bbox: url.searchParams.get('bbox') || undefined,
sourceMapPath: String(url.searchParams.get('sourceMapPath') || '').trim(),
blockId: String(url.searchParams.get('blockId') || '').trim(),
lineRange: url.searchParams.get('lineRange') || null,
charRange: url.searchParams.get('charRange') || null,
};
};
@@ -418,6 +418,21 @@ export const createResourceTabRuntime = (dependencies = {}) => {
}
};
const officeOpenModeForEntry = (entry) => {
if (!entry || normalizeResourceTabKind(entry) !== 'office') return '';
const href = String(entry.passiveFrameSrc || entry.officeUrl || entry.href || '').trim()
|| String(entry.panel?.querySelector?.('iframe.mnote-resource-tab-frame')?.getAttribute?.('src') || '').trim();
if (href) {
try {
const url = new URL(href, window.location.origin);
if (url.pathname === '/office-preview' || url.pathname === '/office-n') return 'preview';
if (url.pathname === '/onlyoffice' || url.pathname.startsWith('/office/')) return 'onlyoffice_live';
} catch (_) {}
}
if (entry.onlyofficeSessionId || entry.bridgeSessionId) return 'onlyoffice_live';
return 'preview';
};
const openEditorsSnapshotEntry = (entry, key, generatedAt = Date.now()) => {
const active = entry?.tab instanceof HTMLElement
? entry.tab.getAttribute('aria-selected') === 'true'
@@ -432,15 +447,23 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const kind = normalizeResourceTabKind(entry);
const dirtyState = resourceTabCloseGuardReason(entry?.session);
const officeBridgeDebug = kind === 'office' ? officeBridgeDebugForEntry(entry) : null;
const officeOpenMode = kind === 'office' ? officeOpenModeForEntry(entry) : '';
const onlyofficeSessionId = String(
officeBridgeDebug?.bridgeSessionId
|| entry?.onlyofficeSessionId
|| entry?.bridgeSessionId
|| '',
).trim();
const snapshotResourceKind = kind === 'office'
? (officeOpenMode === 'onlyoffice_live' || onlyofficeSessionId ? 'only_office' : 'attachment')
: kind;
const workspacePathSeed = entry?.workspacePath && typeof entry.workspacePath === 'object'
? { ...entry.workspacePath, resourceKind: snapshotResourceKind }
: entry?.workspacePath;
return {
objectIdentity,
workspacePath: buildWorkspacePath({
workspacePath: workspacePathSeed,
workspaceId,
sourceKind,
rootUri,
@@ -448,8 +471,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
documentId,
objectIdentity,
assetId,
resourceKind: kind,
workspacePath: entry?.workspacePath,
resourceKind: snapshotResourceKind,
}),
paneRole: normalizePaneRole(entry?.paneRole),
documentId,
@@ -465,13 +487,14 @@ export const createResourceTabRuntime = (dependencies = {}) => {
dirtyGuard: dirtyState,
assetId,
path: relativePath,
officeOpenMode,
onlyofficeSessionId,
bridgeSessionId: onlyofficeSessionId,
bridgeSessionReady: Boolean(onlyofficeSessionId),
bridgeDocumentId: String(officeBridgeDebug?.documentId || '').trim(),
bridgeAssetId: String(officeBridgeDebug?.assetId || '').trim(),
lastActiveAt: Number(entry?.session?.lastActiveAt || entry?.lastActiveAt || 0) || (active ? generatedAt : 0),
preview: false,
preview: officeOpenMode === 'preview',
pinned: false,
};
};
@@ -848,6 +871,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
panel.setAttribute('data-mnote-object-identity', objectIdentity);
panel.setAttribute('data-pane-role', paneRole);
panel.setAttribute('data-resource-kind', kind);
panel.setAttribute('data-resource-path', String(input.path || '').trim());
panel.hidden = true;
nodes.strip.append(tab);
nodes.panelRoot.append(panel);
@@ -989,6 +1013,16 @@ export const createResourceTabRuntime = (dependencies = {}) => {
if (bbox) url.searchParams.set('bbox', bbox);
if (locator.sourceMapPath) url.searchParams.set('sourceMapPath', String(locator.sourceMapPath));
if (locator.blockId) url.searchParams.set('blockId', String(locator.blockId));
if (url.pathname === '/office-preview' && frame.contentWindow) {
frame.contentWindow.postMessage({
type: 'mnote:office-evidence-locator',
page: locator.page,
bbox,
sourceMapPath: locator.sourceMapPath || '',
blockId: locator.blockId || '',
}, window.location.origin);
return;
}
frame.src = url.toString();
} catch (_) {}
};
@@ -1015,11 +1049,22 @@ export const createResourceTabRuntime = (dependencies = {}) => {
};
const applyEvidenceLocatorToTextPanel = (entry, locator) => {
if (!(entry?.panel instanceof HTMLElement) || !locator?.blockId) return;
if (!(entry?.panel instanceof HTMLElement) || !locator) return;
const root = entry.panel.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
if (!(root instanceof HTMLElement)) return;
const selector = `[data-block-id="${cssSafe(locator.blockId)}"]`;
const target = root.querySelector(selector);
const selector = locator.blockId ? `[data-block-id="${cssSafe(locator.blockId)}"]` : '';
let target = selector ? root.querySelector(selector) : null;
const query = String(locator?.openAction?.params?.query || locator?.query || '').trim();
if (!(target instanceof HTMLElement) && query) {
target = Array.from(root.querySelectorAll('.ProseMirror [data-block-id]'))
.find((node) => node instanceof HTMLElement && String(node.textContent || '').includes(query)) || null;
}
const lineStart = Number(locator?.lineRange?.start ?? locator?.line_range?.start ?? 0);
if (!(target instanceof HTMLElement) && Number.isFinite(lineStart) && lineStart > 0) {
const blocks = Array.from(root.querySelectorAll('.ProseMirror [data-block-id]'))
.filter((node) => node instanceof HTMLElement);
target = blocks[Math.max(0, Math.min(blocks.length - 1, lineStart - 1))] || null;
}
if (!(target instanceof HTMLElement)) return;
root.querySelectorAll('[data-mnote-evidence-text-highlight="true"]').forEach((node) => {
if (node instanceof HTMLElement) node.removeAttribute('data-mnote-evidence-text-highlight');
@@ -1364,6 +1409,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
rootUri: '',
jobsBySource: new Map(),
drawerOpen: false,
taskFilter: 'active',
eventSource: null,
fileTreeRefreshKeys: new Set(),
};
@@ -1467,13 +1513,36 @@ export const createResourceTabRuntime = (dependencies = {}) => {
: status || '未知';
};
const localOcrTaskCategory = (job) => {
const status = String(job?.status || '').trim();
if (['failed', 'stale', 'retry_scheduled'].includes(status)) return 'attention';
if (['done', 'succeeded', 'success'].includes(status)) return 'completed';
return 'active';
};
const localOcrTaskProgress = (job) => {
const current = Number(job?.progressCurrent ?? job?.currentProgress);
const total = Number(job?.progressTotal ?? job?.maxProgress);
if (Number.isFinite(current) && Number.isFinite(total) && total > 0) {
return Math.max(0, Math.min(100, Math.round((current / total) * 100)));
}
return null;
};
const localOcrTaskFilterLabel = (filter) => {
return filter === 'active' ? '进行中'
: filter === 'completed' ? '已完成'
: filter === 'attention' ? '需处理'
: '全部';
};
const ensureLocalOcrTaskDock = () => {
let dock = document.querySelector('[data-testid="mnote-local-ocr-task-dock"]');
if (!(dock instanceof HTMLElement)) {
dock = document.createElement('section');
dock.className = 'mnote-local-ocr-task-dock';
dock.setAttribute('data-testid', 'mnote-local-ocr-task-dock');
dock.innerHTML = '<div class="mnote-local-ocr-task-drawer" data-testid="mnote-local-ocr-task-drawer" hidden><div class="mnote-local-ocr-task-head"><strong>OCR 任务</strong><button type="button" class="mnote-local-ocr-task-close" data-mnote-local-ocr-task-close aria-label="收起 OCR 任务">×</button></div><div class="mnote-local-ocr-task-list" data-testid="mnote-local-ocr-task-list"></div></div>';
dock.innerHTML = '<div class="mnote-local-ocr-task-drawer" data-testid="mnote-local-ocr-task-drawer" hidden><div class="mnote-local-ocr-task-panel"><div class="mnote-local-ocr-task-head"><div><strong>后台任务</strong><span data-mnote-local-ocr-task-summary>暂无任务</span></div><button type="button" class="mnote-local-ocr-task-close" data-mnote-local-ocr-task-close aria-label="收起后台任务">×</button></div><div class="mnote-local-ocr-task-tabs" data-mnote-local-ocr-task-tabs></div><div class="mnote-local-ocr-task-toolbar"><span data-mnote-local-ocr-task-filter-label>进行中</span><button type="button" data-mnote-local-ocr-task-clear-completed>清除已完成</button></div><div class="mnote-local-ocr-task-list" data-testid="mnote-local-ocr-task-list"></div></div></div>';
document.body.appendChild(dock);
}
let toggle = document.querySelector('[data-testid="mnote-local-ocr-task-toggle"]');
@@ -1509,6 +1578,20 @@ export const createResourceTabRuntime = (dependencies = {}) => {
}
return;
}
const tabButton = event.target instanceof Element ? event.target.closest('[data-mnote-local-ocr-task-tab]') : null;
if (tabButton instanceof HTMLElement) {
localOcrTaskState.taskFilter = tabButton.getAttribute('data-mnote-local-ocr-task-tab') || 'active';
renderLocalOcrTaskDock();
return;
}
const clearCompleted = event.target instanceof Element ? event.target.closest('[data-mnote-local-ocr-task-clear-completed]') : null;
if (clearCompleted instanceof HTMLElement) {
Array.from(localOcrTaskState.jobsBySource.entries()).forEach(([key, job]) => {
if (localOcrTaskCategory(job) === 'completed') localOcrTaskState.jobsBySource.delete(key);
});
renderLocalOcrTaskDock();
return;
}
const deleteButton = event.target instanceof Element ? event.target.closest('[data-mnote-local-ocr-task-delete]') : null;
if (deleteButton instanceof HTMLElement) {
const sourcePath = deleteButton.getAttribute('data-mnote-local-ocr-task-delete') || '';
@@ -1568,12 +1651,18 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const dock = ensureLocalOcrTaskDock();
const jobs = Array.from(localOcrTaskState.jobsBySource.values())
.sort((left, right) => Number(right.updatedAtMs || 0) - Number(left.updatedAtMs || 0));
const runningCount = jobs.filter((job) => !['done', 'failed', 'stale'].includes(String(job?.status || ''))).length;
const toggle = document.querySelector('[data-testid="mnote-local-ocr-task-toggle"]');
if (toggle instanceof HTMLButtonElement) {
const activeJobs = jobs.filter((job) => localOcrTaskCategory(job) === 'active');
const attentionJobs = jobs.filter((job) => localOcrTaskCategory(job) === 'attention');
const completedJobs = jobs.filter((job) => localOcrTaskCategory(job) === 'completed');
const runningCount = activeJobs.length;
const taskToggles = Array.from(document.querySelectorAll('[data-testid="mnote-local-ocr-task-toggle"], [data-testid="mnote-floating-task-toggle"]'))
.filter((node) => node instanceof HTMLButtonElement);
taskToggles.forEach((toggle) => {
const opensSettings = toggle.getAttribute('data-mnote-action') === 'open-ocr-settings';
const label = runningCount > 0 ? `${runningCount} 个 OCR 处理中,打开 OCR 设置` : (jobs.length > 0 ? `${jobs.length} 个 OCR 任务,打开 OCR 设置` : 'OCR 设置');
toggle.setAttribute('title', label);
toggle.setAttribute('aria-label', label);
const taskLabel = runningCount > 0 ? `${runningCount} 个后台任务正在运行` : (jobs.length > 0 ? `${jobs.length} 个后台任务` : '后台任务');
toggle.setAttribute('title', opensSettings ? label : taskLabel);
toggle.setAttribute('aria-label', opensSettings ? label : taskLabel);
toggle.setAttribute('aria-expanded', localOcrTaskState.drawerOpen ? 'true' : 'false');
toggle.classList.toggle('has-mnote-local-ocr-tasks', jobs.length > 0);
const badge = toggle.querySelector('[data-mnote-local-ocr-task-count]');
@@ -1583,28 +1672,85 @@ export const createResourceTabRuntime = (dependencies = {}) => {
} else {
toggle.textContent = runningCount > 0 ? `OCR ${runningCount} 处理中` : `OCR ${jobs.length}`;
}
}
});
const drawer = dock.querySelector('[data-testid="mnote-local-ocr-task-drawer"]');
if (drawer instanceof HTMLElement) drawer.hidden = !localOcrTaskState.drawerOpen;
const summary = dock.querySelector('[data-mnote-local-ocr-task-summary]');
if (summary instanceof HTMLElement) {
summary.textContent = `${runningCount} 进行中 · ${attentionJobs.length} 需处理 · ${completedJobs.length} 已完成`;
}
const tabs = dock.querySelector('[data-mnote-local-ocr-task-tabs]');
if (tabs instanceof HTMLElement) {
const tabItems = [
['active', '进行中', activeJobs.length],
['attention', '需处理', attentionJobs.length],
['completed', '已完成', completedJobs.length],
['all', '全部', jobs.length],
];
tabs.replaceChildren();
tabItems.forEach(([key, label, count]) => {
const button = document.createElement('button');
button.type = 'button';
button.setAttribute('data-mnote-local-ocr-task-tab', key);
button.setAttribute('aria-selected', localOcrTaskState.taskFilter === key ? 'true' : 'false');
button.textContent = `${label} ${count}`;
tabs.appendChild(button);
});
}
const filterLabel = dock.querySelector('[data-mnote-local-ocr-task-filter-label]');
if (filterLabel instanceof HTMLElement) filterLabel.textContent = localOcrTaskFilterLabel(localOcrTaskState.taskFilter);
const clearCompleted = dock.querySelector('[data-mnote-local-ocr-task-clear-completed]');
if (clearCompleted instanceof HTMLButtonElement) clearCompleted.disabled = completedJobs.length === 0;
const list = dock.querySelector('[data-testid="mnote-local-ocr-task-list"]');
if (!(list instanceof HTMLElement)) return;
list.replaceChildren();
if (!jobs.length) {
const visibleJobs = jobs.filter((job) => {
return localOcrTaskState.taskFilter === 'all' || localOcrTaskCategory(job) === localOcrTaskState.taskFilter;
});
if (!visibleJobs.length) {
const empty = document.createElement('div');
empty.className = 'mnote-local-ocr-task-empty';
empty.textContent = '暂无 OCR 任务';
empty.textContent = jobs.length ? `暂无${localOcrTaskFilterLabel(localOcrTaskState.taskFilter)}任务` : '暂无后台任务';
list.appendChild(empty);
return;
}
jobs.forEach((job) => {
visibleJobs.forEach((job) => {
const row = document.createElement('div');
row.className = 'mnote-local-ocr-task-row';
row.setAttribute('data-mnote-local-ocr-task-row', String(job.sourceRootRelativePath || ''));
row.setAttribute('data-mnote-local-ocr-task-status', String(job.status || ''));
row.setAttribute('data-mnote-local-ocr-task-category', localOcrTaskCategory(job));
const title = String(job.sourceRootRelativePath || '').split('/').filter(Boolean).pop() || 'OCR';
row.innerHTML = '<div class="mnote-local-ocr-task-main"><strong></strong><span></span></div><div class="mnote-local-ocr-task-actions"><button type="button" data-mnote-local-ocr-task-open>打开</button><button type="button" data-mnote-local-ocr-task-retry>重试</button><button type="button" data-mnote-local-ocr-task-clear>清除</button><button type="button" data-mnote-local-ocr-task-delete>删除</button></div>';
row.querySelector('strong').textContent = title;
row.querySelector('span').textContent = statusTextForLocalOcrJob(job);
const category = localOcrTaskCategory(job);
const progress = localOcrTaskProgress(job);
row.innerHTML = '<div class="mnote-local-ocr-task-main"><div class="mnote-local-ocr-task-title-line"><strong></strong><em></em></div><span></span><div class="mnote-local-ocr-task-progress" role="progressbar"><i></i></div></div><div class="mnote-local-ocr-task-actions"><button type="button" data-mnote-local-ocr-task-open>打开</button><button type="button" data-mnote-local-ocr-task-retry>重试</button><button type="button" data-mnote-local-ocr-task-clear>清除</button><button type="button" data-mnote-local-ocr-task-delete>删除</button></div>';
const titleNode = row.querySelector('strong');
if (titleNode instanceof HTMLElement) {
titleNode.textContent = title;
titleNode.setAttribute('title', String(job.sourceRootRelativePath || title));
}
const categoryNode = row.querySelector('em');
if (categoryNode instanceof HTMLElement) {
categoryNode.textContent = category === 'active' ? '进行中' : category === 'attention' ? '需处理' : '已完成';
}
const statusNode = row.querySelector('span');
if (statusNode instanceof HTMLElement) statusNode.textContent = statusTextForLocalOcrJob(job);
const progressBar = row.querySelector('.mnote-local-ocr-task-progress');
const progressValue = row.querySelector('.mnote-local-ocr-task-progress i');
if (progressBar instanceof HTMLElement && progressValue instanceof HTMLElement) {
progressBar.hidden = category !== 'active' && progress === null;
progressBar.setAttribute('aria-valuemin', '0');
progressBar.setAttribute('aria-valuemax', '100');
if (progress === null) {
progressBar.setAttribute('data-progress-mode', 'indeterminate');
progressBar.removeAttribute('aria-valuenow');
progressValue.style.width = '';
} else {
progressBar.setAttribute('data-progress-mode', 'determinate');
progressBar.setAttribute('aria-valuenow', String(progress));
progressValue.style.width = `${progress}%`;
}
}
const open = row.querySelector('[data-mnote-local-ocr-task-open]');
if (open instanceof HTMLButtonElement) {
open.setAttribute('data-mnote-local-ocr-task-open', String(job.sourceRootRelativePath || ''));
@@ -2178,6 +2324,20 @@ export const createResourceTabRuntime = (dependencies = {}) => {
return true;
};
const officePreviewBaseHref = (href) => {
const raw = String(href || '').trim();
if (!raw) return '';
try {
const url = new URL(raw, window.location.origin);
['page', 'bbox', 'sourceMapPath', 'blockId', 'lineRange', 'charRange', 'mnoteResourceReload'].forEach((key) => {
url.searchParams.delete(key);
});
return url.pathname + '?' + url.searchParams.toString();
} catch (_) {
return raw;
}
};
const refreshExistingOfficeResourceTab = (entry, input) => {
if (!entry || entry.kind !== 'office') return false;
const nextHref = String(input.officeUrl || input.href || '').trim();
@@ -2186,7 +2346,7 @@ export const createResourceTabRuntime = (dependencies = {}) => {
const currentHref = frame instanceof HTMLIFrameElement
? String(frame.getAttribute('src') || frame.src || '').trim()
: '';
if (currentHref !== nextHref) void openPassiveResourceTab(entry, input);
if (officePreviewBaseHref(currentHref) !== officePreviewBaseHref(nextHref)) void openPassiveResourceTab(entry, input);
return true;
};
@@ -24,6 +24,12 @@ export function createSidebarPageAiMarkdownRuntime(context) {
codeSpans.push('<code>' + code + '</code>');
return key;
});
html = html.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g, function(match, label, href) {
var normalizedHref = normalizePageAiMarkdownHref(href);
if (!normalizedHref) return match;
var citationAttr = isMnoteCitationHref(normalizedHref) ? ' data-page-ai-citation-link="true"' : '';
return '<a href="' + escapeHtml(normalizedHref) + '" target="_blank" rel="noopener noreferrer"' + citationAttr + '>' + label + '</a>';
});
html = html.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>');
html = html.replace(/(^|[^*])\*([^*\n]+)\*/g, '$1<em>$2</em>');
codeSpans.forEach(function(value, index) {
@@ -32,6 +38,28 @@ export function createSidebarPageAiMarkdownRuntime(context) {
return html;
}
function normalizePageAiMarkdownHref(value) {
var href = String(value || '').trim()
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'");
if (!href || /[\u0000-\u001f<>"']/.test(href)) return '';
var lower = href.toLowerCase();
if (lower.startsWith('javascript:') || lower.startsWith('data:') || lower.startsWith('vbscript:')) return '';
if (href.startsWith('/') || href.startsWith('#')) return href;
if (/^https?:\/\//i.test(href) || /^mailto:/i.test(href)) return href;
return '';
}
function isMnoteCitationHref(href) {
try {
var url = new URL(href, window.location.origin);
return url.origin === window.location.origin && url.pathname.startsWith('/documents/');
} catch (_error) {
return false;
}
}
function renderPageAiMarkdown(content) {
var lines = String(content || '').replace(/\r\n?/g, '\n').split('\n');
var blocks = [];
@@ -154,7 +154,7 @@ export function createSidebarPageAiProfileRuntime(context) {
function pageAiSessionAgentFilterOptions(rows) {
var byValue = { all: '全部 agent' };
pageAiNormalizeArray(rows).forEach(function(session) {
pageAiNormalizeArray(rows).filter(pageAiVisibleHistorySession).forEach(function(session) {
var value = pageAiSessionAgentFilterValue(session);
byValue[value] = pageAiSessionAgentLabel(session);
});
@@ -163,9 +163,16 @@ export function createSidebarPageAiProfileRuntime(context) {
});
}
function pageAiVisibleHistorySession(session) {
var source = String(session && session.source || '').trim();
var status = String(session && session.status || '').trim();
var messages = pageAiNormalizeArray(session && session.messages);
return !(source === 'draft' && status === 'draft' && messages.length === 0);
}
function pageAiFilteredHistoryRows(rows) {
var filterValue = String(pageUiState.pageAiSessionAgentFilter || 'all').trim() || 'all';
var normalized = pageAiNormalizeArray(rows);
var normalized = pageAiNormalizeArray(rows).filter(pageAiVisibleHistorySession);
if (filterValue === 'all') return normalized;
return normalized.filter(function(session) {
return pageAiSessionAgentFilterValue(session) === filterValue;
@@ -563,10 +563,13 @@ export function createSidebarPageAiRenderRuntime(context) {
var skillSourceSelect = drawer.querySelector('[data-page-ai-skill-source-select]');
if (skillSourceSelect instanceof HTMLSelectElement) {
var activeSkillSource = pageAiCurrentSkillSource();
skillSourceSelect.innerHTML = pageAiSkillSourceOptions().map(function(option) {
var sourceOptions = pageAiSkillSourceOptions();
skillSourceSelect.innerHTML = sourceOptions.map(function(option) {
return '<option value="' + escapeHtml(option.value) + '"' + (option.value === activeSkillSource ? ' selected' : '') + '>' + escapeHtml(option.label) + '</option>';
}).join('');
skillSourceSelect.value = activeSkillSource;
var sourceControl = skillSourceSelect.closest('[data-page-ai-skill-source-control]');
if (sourceControl instanceof HTMLElement) sourceControl.hidden = sourceOptions.length <= 1;
}
var hermesBuiltinToggle = drawer.querySelector('[data-page-ai-hide-hermes-builtin]');
if (hermesBuiltinToggle instanceof HTMLInputElement) {
@@ -580,7 +583,7 @@ export function createSidebarPageAiRenderRuntime(context) {
if (skillList instanceof HTMLElement) {
var skills = pageAiFilteredSkillEntries();
if (!skills.length) {
skillList.innerHTML = '<div class="wolai-page-ai-empty">没有匹配的能。</div>';
skillList.innerHTML = '<div class="wolai-page-ai-empty">没有匹配的能。</div>';
} else {
var sourceParts = pageAiSkillSourceParts(pageAiCurrentSkillSource());
var group = sourceParts.group;
@@ -593,14 +596,33 @@ export function createSidebarPageAiRenderRuntime(context) {
'<span>' + escapeHtml(String(skills.length) + headingExtra) + '</span>' +
'</button>' +
(collapsed ? '' : skills.map(function(skill) {
var sourceText = pageAiSkillOriginLabel(skill)
+ (skill.configScope ? ' · ' + skill.configScope : '')
+ (skill.readOnly ? ' · 只读' : '')
+ (skill.modified ? ' · modified' : '');
var toolCount = Number(skill.toolCount || pageAiNormalizeArray(skill.tools).length || 0);
var disabledToolCount = Number(skill.disabledToolCount || 0);
var capabilityMeta = [];
if (toolCount > 0) capabilityMeta.push(String(toolCount) + ' 工具');
if (disabledToolCount > 0) capabilityMeta.push(String(disabledToolCount) + ' 已关闭');
if (pageAiNormalizeArray(skill.requiresContextRefs).length > 0) capabilityMeta.push('需要上下文');
var categoryText = group === 'mnote' ? String(skill.categoryTitle || skill.category || '').trim() : '';
var sourceMetaParts = [];
if (group === 'mnote') {
sourceMetaParts.push(categoryText || 'MNote');
} else {
sourceMetaParts.push(pageAiSkillOriginLabel(skill));
sourceMetaParts.push('只读查看');
}
if (capabilityMeta.length) sourceMetaParts = sourceMetaParts.concat(capabilityMeta);
if (skill.readOnly && group === 'mnote') sourceMetaParts.push('只读');
if (skill.modified) sourceMetaParts.push('modified');
var sourceText = sourceMetaParts.filter(Boolean).join(' · ');
var description = String(skill.description || '').trim();
var hasDescription = description && description !== '---' && description !== '无描述';
var displayName = String(skill.title || skill.name || skill.id || '').trim();
var disabled = skill.toggleable === false || skill.readOnly === true;
var switchControl = group === 'mnote'
? '<button type="button" class="wolai-page-ai-skill-switch' + (pageAiSkillEnabled(skill) ? ' is-on' : '') + '" data-page-ai-skill-toggle="' + escapeHtml(skill.id || skill.name) + '" data-page-ai-skill-group="' + escapeHtml(group) + '" data-page-ai-skill-profile="' + escapeHtml(skill.profile || sourceParts.profile || '') + '" data-page-ai-skill-kind="' + escapeHtml(skill.skillKind || '') + '" aria-pressed="' + (pageAiSkillEnabled(skill) ? 'true' : 'false') + '"' + (disabled ? ' disabled' : '') + '>' +
'<span></span>' +
'</button>'
: '<span class="wolai-page-ai-skill-readonly-badge">查看</span>';
return '' +
'<div class="wolai-page-ai-skill-row">' +
'<div class="wolai-page-ai-skill-copy">' +
@@ -610,9 +632,7 @@ export function createSidebarPageAiRenderRuntime(context) {
'</div>' +
(hasDescription ? '<div class="wolai-page-ai-skill-desc">' + escapeHtml(description) + '</div>' : '') +
'</div>' +
'<button type="button" class="wolai-page-ai-skill-switch' + (pageAiSkillEnabled(skill) ? ' is-on' : '') + '" data-page-ai-skill-toggle="' + escapeHtml(skill.id || skill.name) + '" data-page-ai-skill-group="' + escapeHtml(group) + '" data-page-ai-skill-profile="' + escapeHtml(skill.profile || sourceParts.profile || '') + '" data-page-ai-skill-kind="' + escapeHtml(skill.skillKind || '') + '" aria-pressed="' + (pageAiSkillEnabled(skill) ? 'true' : 'false') + '"' + (disabled ? ' disabled' : '') + '>' +
'<span></span>' +
'</button>' +
switchControl +
'</div>';
}).join('')) +
'</section>';
@@ -751,7 +771,7 @@ export function createSidebarPageAiRenderRuntime(context) {
'<div class="wolai-page-ai-header-actions">' +
'<button type="button" class="wolai-page-ai-icon" data-page-ai-action="new-session" aria-label="新建 AI 会话" title="新建 AI 会话"></button>' +
'<button type="button" class="wolai-page-ai-icon" data-page-ai-action="history" aria-label="历史会话" title="历史会话">⌕</button>' +
'<button type="button" class="wolai-page-ai-icon" data-page-ai-tab="skills" aria-label="能" title="能">' +
'<button type="button" class="wolai-page-ai-icon" data-page-ai-tab="skills" aria-label="能" title="能">' +
'<svg class="wolai-page-ai-icon-svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">' +
'<path d="M12 3l1.35 4.15L17.5 8.5l-4.15 1.35L12 14l-1.35-4.15L6.5 8.5l4.15-1.35L12 3z" />' +
'<path d="M18.5 13l.8 2.4 2.2.8-2.2.8-.8 2.4-.8-2.4-2.2-.8 2.2-.8.8-2.4z" />' +
@@ -856,17 +876,17 @@ export function createSidebarPageAiRenderRuntime(context) {
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Agent</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="skills" role="tab" aria-selected="true">Skills</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="skills" role="tab" aria-selected="true">能力</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">Runtime</button>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-skills-toolbar">' +
'<label class="wolai-page-ai-skill-search">' +
'<span>搜索能</span>' +
'<input type="search" data-page-ai-skill-search placeholder="搜索能…" />' +
'<span>搜索能</span>' +
'<input type="search" data-page-ai-skill-search placeholder="搜索能…" />' +
'</label>' +
'<label class="wolai-page-ai-profile-select" data-page-ai-skill-source-control>' +
'<span>能来源</span>' +
'<span>能来源</span>' +
'<select data-page-ai-skill-source-select></select>' +
'</label>' +
'<label class="wolai-page-ai-skill-filter-toggle">' +
@@ -882,7 +902,7 @@ export function createSidebarPageAiRenderRuntime(context) {
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Agent</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="skills" role="tab" aria-selected="false">Skills</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="skills" role="tab" aria-selected="false">能力</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="runtime" role="tab" aria-selected="true">Runtime</button>' +
'</div>' +
'</div>' +
@@ -911,9 +931,9 @@ export function createSidebarPageAiRenderRuntime(context) {
'</div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-queue-list></div>' +
'</div>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-panel" hidden>' +
'<div class="wolai-page-ai-tools-head">' +
'<span>mnote tools</span>' +
'<span>MNote 工具调试</span>' +
'<span data-page-ai-last-tool>暂无 tool call</span>' +
'</div>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-tool-error hidden></div>' +
@@ -982,6 +1002,231 @@ export function createSidebarPageAiRenderRuntime(context) {
}).join('');
}
function pageAiConversationShouldStickToBottom(conversation) {
var distance = conversation.scrollHeight - conversation.scrollTop - conversation.clientHeight;
return distance <= 48;
}
function pageAiCaptureOpenToolDetails(conversation) {
var openToolDetails = {};
conversation.querySelectorAll('[data-page-ai-collapse-card], [data-page-ai-tool-card]').forEach(function(card) {
if (!(card instanceof HTMLElement)) return;
var key = String(card.getAttribute('data-page-ai-collapse-id') || card.getAttribute('data-page-ai-tool-group-id') || card.getAttribute('data-page-ai-tool-call-id') || '').trim();
var details = card.querySelector('[data-page-ai-collapse-details], .wolai-page-ai-tool-details');
if (key && details instanceof HTMLDetailsElement && details.open) {
openToolDetails[key] = true;
}
});
return openToolDetails;
}
function pageAiRestoreOpenToolDetails(conversation, openToolDetails) {
if (!openToolDetails || typeof openToolDetails !== 'object') return;
conversation.querySelectorAll('[data-page-ai-collapse-card], [data-page-ai-tool-card]').forEach(function(card) {
if (!(card instanceof HTMLElement)) return;
var key = String(card.getAttribute('data-page-ai-collapse-id') || card.getAttribute('data-page-ai-tool-group-id') || card.getAttribute('data-page-ai-tool-call-id') || '').trim();
if (!key || openToolDetails[key] !== true) return;
var details = card.querySelector('[data-page-ai-collapse-details], .wolai-page-ai-tool-details');
if (details instanceof HTMLDetailsElement) details.open = true;
});
}
function pageAiRenderToolDetailRows(item) {
var locationRows = Array.isArray(item.locations) && item.locations.length
? '<div class="wolai-page-ai-tool-meta">位置:' + item.locations.map(function(loc) {
return '<span style="display:inline-flex;align-items:center;gap:4px;margin-right:8px">' +
'<span>' + escapeHtml(loc) + '</span>' +
'<button type="button" class="wolai-page-ai-ghost" style="font-size:0.85em;padding:0 4px;min-width:unset" data-page-ai-open-location="' + escapeHtml(loc) + '" data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" title="打开文件">打开</button>' +
'</span>';
}).join('') + '</div>'
: '';
return [
locationRows,
item.argsSummary ? '<div class="wolai-page-ai-tool-meta">参数 ' + escapeHtml(item.argsSummary) + '</div>' : '',
item.resultSummary ? '<div class="wolai-page-ai-tool-meta">结果 ' + escapeHtml(item.resultSummary) + '</div>' : '',
item.changedFiles && item.changedFiles.length ? '<pre class="wolai-page-ai-tool-meta wolai-page-ai-changed-files">' + escapeHtml(pageAiFormatChangedFiles(item.changedFiles)) + '</pre>' : '',
(item.traceId || item.auditId) ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml([item.traceId, item.auditId].filter(Boolean).join(' · ')) + '</div>' : ''
].filter(Boolean).join('');
}
function pageAiToolStatusLabel(status) {
if (status === 'completed') return '完成';
if (status === 'failed') return '失败';
return '运行中';
}
function pageAiToolGroupStatus(items) {
if (items.some(function(item) { return item.status === 'failed'; })) return 'failed';
if (items.some(function(item) { return item.status !== 'completed'; })) return 'running';
return 'completed';
}
function pageAiRenderToolGroup(items, groupIndex) {
var tools = pageAiNormalizeArray(items);
if (!tools.length) return '';
var status = pageAiToolGroupStatus(tools);
var completedCount = tools.filter(function(item) { return item.status === 'completed'; }).length;
var failedCount = tools.filter(function(item) { return item.status === 'failed'; }).length;
var runningCount = tools.length - completedCount - failedCount;
var countParts = [String(tools.length) + ' 个'];
if (completedCount) countParts.push('完成 ' + String(completedCount));
if (failedCount) countParts.push('失败 ' + String(failedCount));
if (runningCount) countParts.push('运行中 ' + String(runningCount));
var firstToolId = String(tools[0] && tools[0].toolCallId || '').trim();
var groupId = 'tool-group-' + String(groupIndex) + '-' + (firstToolId || String(tools.length));
var rows = tools.map(function(item) {
var statusLabel = pageAiToolStatusLabel(item.status);
var meta = [statusLabel, item.toolKind, item.toolCallId].filter(Boolean).join(' · ');
return '' +
'<div class="wolai-page-ai-tool-item" data-page-ai-tool-item data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" data-page-ai-tool-status="' + escapeHtml(item.status || '') + '">' +
'<div class="wolai-page-ai-tool-item-head">' +
'<strong>' + escapeHtml(item.toolName || item.content || 'tool') + '</strong>' +
'<span>' + escapeHtml(meta) + '</span>' +
'</div>' +
(pageAiRenderToolDetailRows(item) || '<div class="wolai-page-ai-tool-meta">暂无参数或结果详情。</div>') +
'</div>';
}).join('');
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-collapse-card data-page-ai-collapse-id="' + escapeHtml(groupId) + '" data-page-ai-tool-card data-page-ai-tool-group="true" data-page-ai-tool-group-id="' + escapeHtml(groupId) + '" data-page-ai-tool-status="' + escapeHtml(status) + '">' +
'<div class="wolai-page-ai-message-role">工具</div>' +
'<div class="wolai-page-ai-message-text">' +
'<details class="wolai-page-ai-tool-details wolai-page-ai-tool-group-details" data-page-ai-collapse-details>' +
'<summary>' +
'<strong>调用工具</strong>' +
'<span>' + escapeHtml(countParts.join(' · ')) + '</span>' +
'</summary>' +
'<div class="wolai-page-ai-tool-group-list">' + rows + '</div>' +
'</details>' +
'</div>' +
'</div>';
}
function pageAiRenderThoughtGroup(items, groupIndex) {
var thoughts = pageAiNormalizeArray(items).filter(function(item) {
return item && item.kind === 'thought' && String(item.content || '').trim();
});
if (!thoughts.length) return '';
var firstText = String(thoughts[0] && thoughts[0].content || '').slice(0, 24).replace(/\s+/g, ' ').trim();
var groupId = 'thought-group-' + String(groupIndex) + '-' + String(thoughts.length) + '-' + (firstText || 'delta');
var content = thoughts.map(function(item) {
return String(item.content || '').trim();
}).filter(Boolean).join('\n\n');
var countLabel = thoughts.length > 1 ? String(thoughts.length) + ' 段' : '1 段';
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-collapse-card data-page-ai-collapse-id="' + escapeHtml(groupId) + '" data-page-ai-thought-card="true">' +
'<div class="wolai-page-ai-message-role">AI</div>' +
'<div class="wolai-page-ai-message-text">' +
'<details class="wolai-page-ai-tool-details wolai-page-ai-thought-group-details" data-page-ai-collapse-details data-page-ai-thought-delta="true">' +
'<summary>' +
'<strong>思考过程</strong>' +
'<span>' + escapeHtml(countLabel) + '</span>' +
'</summary>' +
'<div class="wolai-page-ai-thought-group-list">' + escapeHtml(content) + '</div>' +
'</details>' +
'</div>' +
'</div>';
}
function pageAiRenderNonToolMessage(item) {
var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI');
if (item.kind === 'thought') {
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-collapse-card data-page-ai-collapse-id="thought-single">' +
'<div class="wolai-page-ai-message-role">AI</div>' +
'<div class="wolai-page-ai-message-text">' +
'<details class="wolai-page-ai-tool-details wolai-page-ai-thought-group-details" data-page-ai-collapse-details data-page-ai-thought-delta="true">' +
'<summary><strong>思考过程</strong><span>1 段</span></summary>' +
'<div class="wolai-page-ai-thought-group-list">' + escapeHtml(item.content || '') + '</div>' +
'</details>' +
'</div>' +
'</div>';
}
if (item.kind === 'permission') {
var permissionActions = item.resolved ? '' : (
'<div class="wolai-page-ai-message-actions">' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="allow" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">允许</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="deny" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">拒绝</button>' +
'</div>'
);
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-permission-request="' + escapeHtml(item.permissionId || '') + '">' +
'<div class="wolai-page-ai-message-role">权限</div>' +
'<div class="wolai-page-ai-message-text">' +
'<strong>' + escapeHtml(item.toolName || 'session/request_permission') + '</strong><br />' +
'<span>' + escapeHtml(item.argsSummary || item.content || '') + '</span>' +
permissionActions +
'</div>' +
'</div>';
}
if (item.kind === 'plan') {
var planEntries = Array.isArray(item.entries) ? item.entries : [];
var listHtml = planEntries.map(function(entry) {
return '<li style="margin:2px 0">' + escapeHtml(String(entry || '')) + '</li>';
}).join('');
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-plan="true">' +
'<details class="wolai-page-ai-plan-details" open>' +
'<summary class="wolai-page-ai-message-role" style="cursor:pointer;user-select:none;opacity:0.8;font-size:0.85em">执行计划 · ' + planEntries.length + ' 步</summary>' +
'<div class="wolai-page-ai-message-text" style="margin-top:4px;opacity:0.75;font-size:0.9em;border-left:2px solid var(--wolai-border-color, #ddd);padding-left:8px">' +
'<ol style="margin:4px 0;padding-left:20px">' + listHtml + '</ol>' +
'</div>' +
'</details>' +
'</div>';
}
var streamingAttr = item.streaming ? ' data-page-ai-streaming="true"' : '';
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--' + escapeHtml(item.role || 'assistant') + '"' + streamingAttr + '>' +
'<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
'<div class="wolai-page-ai-message-text">' + (item.role === 'assistant' ? renderPageAiMarkdown(item.content || '') : escapeHtml(item.content || '')) + '</div>' +
'</div>';
}
function pageAiConversationRenderHtml(messages) {
var turns = [];
var current = null;
function ensureTurn() {
if (!current) current = { user: null, tools: [], status: [], assistants: [], others: [] };
return current;
}
function flushTurn() {
if (!current) return;
turns.push(current);
current = null;
}
pageAiNormalizeArray(messages).forEach(function(item) {
if (item && item.role === 'user') {
flushTurn();
current = { user: item, tools: [], status: [], assistants: [], others: [] };
return;
}
var turn = ensureTurn();
if (item && item.role === 'tool' && item.kind !== 'permission') {
turn.tools.push(item);
} else if (item && (item.kind === 'thought' || item.kind === 'plan' || item.kind === 'permission')) {
turn.status.push(item);
} else if (item && item.role === 'assistant') {
turn.assistants.push(item);
} else {
turn.others.push(item);
}
});
flushTurn();
var toolGroupIndex = 0;
var thoughtGroupIndex = 0;
return turns.map(function(turn) {
var html = [];
if (turn.user) html.push(pageAiRenderNonToolMessage(turn.user));
if (turn.tools.length) html.push(pageAiRenderToolGroup(turn.tools, toolGroupIndex++));
var thoughts = turn.status.filter(function(item) { return item && item.kind === 'thought'; });
var otherStatus = turn.status.filter(function(item) { return !(item && item.kind === 'thought'); });
if (thoughts.length) html.push(pageAiRenderThoughtGroup(thoughts, thoughtGroupIndex++));
html = html.concat(otherStatus.map(pageAiRenderNonToolMessage));
html = html.concat(turn.assistants.map(pageAiRenderNonToolMessage));
html = html.concat(turn.others.map(pageAiRenderNonToolMessage));
return html.join('');
}).join('');
}
function renderPageAiConversation() {
var drawer = ensurePageAiDrawer();
renderPageAiSuggestions();
@@ -1022,86 +1267,17 @@ export function createSidebarPageAiRenderRuntime(context) {
conversation.innerHTML = '<div class="wolai-page-ai-empty">围绕当前页面提问,我会优先使用当前页内容、页面结构和已保存设置。</div>';
return;
}
conversation.innerHTML = pageUiState.pageAiMessages.map(function(item) {
var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI');
if (item.role === 'tool') {
var statusLabel = item.status === 'completed' ? '完成' : (item.status === 'failed' ? '失败' : '运行中');
var locationRows = Array.isArray(item.locations) && item.locations.length
? '<div class="wolai-page-ai-tool-meta">位置:' + item.locations.map(function(loc, idx) {
return '<span style="display:inline-flex;align-items:center;gap:4px;margin-right:8px">' +
'<span>' + escapeHtml(loc) + '</span>' +
'<button type="button" class="wolai-page-ai-ghost" style="font-size:0.85em;padding:0 4px;min-width:unset" data-page-ai-open-location="' + escapeHtml(loc) + '" data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" title="打开文件">打开</button>' +
'</span>';
}).join('') + '</div>'
: '';
var detailRows = [
locationRows,
item.argsSummary ? '<div class="wolai-page-ai-tool-meta">参数 ' + escapeHtml(item.argsSummary) + '</div>' : '',
item.resultSummary ? '<div class="wolai-page-ai-tool-meta">结果 ' + escapeHtml(item.resultSummary) + '</div>' : '',
item.changedFiles && item.changedFiles.length ? '<pre class="wolai-page-ai-tool-meta wolai-page-ai-changed-files">' + escapeHtml(pageAiFormatChangedFiles(item.changedFiles)) + '</pre>' : '',
(item.traceId || item.auditId) ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml([item.traceId, item.auditId].filter(Boolean).join(' · ')) + '</div>' : ''
].filter(Boolean).join('');
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-tool-card data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" data-page-ai-tool-status="' + escapeHtml(item.status || '') + '">' +
'<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
'<div class="wolai-page-ai-message-text">' +
'<details class="wolai-page-ai-tool-details">' +
'<summary>' +
'<strong>' + escapeHtml(item.toolName || item.content || 'tool') + '</strong>' +
'<span>' + escapeHtml([statusLabel, item.toolKind, item.toolCallId].filter(Boolean).join(' · ')) + '</span>' +
'</summary>' +
(detailRows || '<div class="wolai-page-ai-tool-meta">暂无参数或结果详情。</div>') +
'</details>' +
'</div>' +
'</div>';
}
if (item.kind === 'thought') {
return '' +
'<details class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-thought-delta="true">' +
'<summary class="wolai-page-ai-message-role" style="cursor:pointer;user-select:none;opacity:0.7;font-size:0.85em">思考过程</summary>' +
'<div class="wolai-page-ai-message-text" style="margin-top:4px;opacity:0.6;font-size:0.9em;border-left:2px solid var(--wolai-border-color, #ddd);padding-left:8px">' + escapeHtml(item.content || '') + '</div>' +
'</details>';
}
if (item.kind === 'permission') {
var permissionActions = item.resolved ? '' : (
'<div class="wolai-page-ai-message-actions">' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="allow" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">允许</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="deny" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">拒绝</button>' +
'</div>'
);
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-permission-request="' + escapeHtml(item.permissionId || '') + '">' +
'<div class="wolai-page-ai-message-role">权限</div>' +
'<div class="wolai-page-ai-message-text">' +
'<strong>' + escapeHtml(item.toolName || 'session/request_permission') + '</strong><br />' +
'<span>' + escapeHtml(item.argsSummary || item.content || '') + '</span>' +
permissionActions +
'</div>' +
'</div>';
}
if (item.kind === 'plan') {
var planEntries = Array.isArray(item.entries) ? item.entries : [];
var listHtml = planEntries.map(function(entry, idx) {
return '<li style="margin:2px 0">' + escapeHtml(String(entry || '')) + '</li>';
}).join('');
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--assistant" data-page-ai-plan="true">' +
'<details class="wolai-page-ai-plan-details" open>' +
'<summary class="wolai-page-ai-message-role" style="cursor:pointer;user-select:none;opacity:0.8;font-size:0.85em">执行计划 · ' + planEntries.length + ' 步</summary>' +
'<div class="wolai-page-ai-message-text" style="margin-top:4px;opacity:0.75;font-size:0.9em;border-left:2px solid var(--wolai-border-color, #ddd);padding-left:8px">' +
'<ol style="margin:4px 0;padding-left:20px">' + listHtml + '</ol>' +
'</div>' +
'</details>' +
'</div>';
}
var streamingAttr = item.streaming ? ' data-page-ai-streaming="true"' : '';
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--' + escapeHtml(item.role || 'assistant') + '"' + streamingAttr + '>' +
'<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
'<div class="wolai-page-ai-message-text">' + (item.role === 'assistant' ? renderPageAiMarkdown(item.content || '') : escapeHtml(item.content || '')) + '</div>' +
'</div>';
}).join('');
conversation.scrollTop = conversation.scrollHeight;
var shouldStickToBottom = pageAiConversationShouldStickToBottom(conversation);
var previousScrollTop = conversation.scrollTop;
var openToolDetails = pageAiCaptureOpenToolDetails(conversation);
conversation.innerHTML = pageAiConversationRenderHtml(pageUiState.pageAiMessages);
pageAiRestoreOpenToolDetails(conversation, openToolDetails);
if (shouldStickToBottom) {
conversation.scrollTop = conversation.scrollHeight;
} else {
var maxScrollTop = Math.max(0, conversation.scrollHeight - conversation.clientHeight);
conversation.scrollTop = Math.min(previousScrollTop, maxScrollTop);
}
}
return {
@@ -724,6 +724,89 @@ export function createSidebarPageAiRuntime(context) {
});
}
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 {
return decodeURIComponent(url.pathname.slice(prefix.length).split('/')[0] || '');
} catch (_error) {
return url.pathname.slice(prefix.length).split('/')[0] || '';
}
}
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.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 pageAiOpenCitationUrl(href) {
var url;
try {
url = new URL(String(href || ''), window.location.origin);
} catch (_error) {
return false;
}
if (url.origin !== window.location.origin || !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(),
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) {
@@ -786,10 +869,13 @@ export function createSidebarPageAiRuntime(context) {
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(),
skills: pageAiNormalizeArray(category && category.skills).map(function(skill) {
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(),
@@ -807,8 +893,18 @@ export function createSidebarPageAiRuntime(context) {
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(category && category.name || '').trim()
category: String(skill && skill.category || category && category.name || '').trim(),
categoryTitle: String(skill && skill.categoryTitle || category && category.title || category && category.name || '').trim()
};
})
};
@@ -831,6 +927,16 @@ export function createSidebarPageAiRuntime(context) {
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))
};
})
@@ -859,6 +965,16 @@ export function createSidebarPageAiRuntime(context) {
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)
});
});
@@ -1310,18 +1426,21 @@ export function createSidebarPageAiRuntime(context) {
function pageAiLoadSkillCatalog(runtime, profile) {
var params = new URLSearchParams();
params.set('runtime', runtime);
var endpoint = '/api/hermes/client/skills';
if (runtime === 'mnote') {
params.set('agentId', pageUiState.pageAiAgentId || 'reasonix');
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('/api/hermes/client/skills?' + params.toString(), {
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, 'skills_failed_' + response.status));
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'capabilities_failed_' + response.status));
return pageAiNormalizeSkills(payload);
});
});
@@ -1478,21 +1597,30 @@ export function createSidebarPageAiRuntime(context) {
}
if (skillGroup === 'mnote') {
try {
var mnoteResponse = await fetch('/api/hermes/client/skills/toggle', {
var mnoteResponse = await fetch('/api/hermes/client/capabilities/toggle', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
skillKind: 'mnote_builtin',
name: name,
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_skill_toggle_failed_' + mnoteResponse.status));
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);
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);
@@ -1612,8 +1740,6 @@ export function createSidebarPageAiRuntime(context) {
pageAiLoadBackendSessions()
]).then(function() {
renderPageAiControls();
}).catch(function() {}).then(function() {
return pageAiEnsureHermesSession();
}).then(function() {
return pageAiRestoreHermesSession();
}).then(function() {
@@ -2119,6 +2245,14 @@ export function createSidebarPageAiRuntime(context) {
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();
@@ -60,9 +60,9 @@ export function createSidebarPageAiSessionRuntime(context) {
acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
createdAt: now,
updatedAt: now,
source: 'local',
source: 'draft',
usage: null,
status: 'idle',
status: 'draft',
messages: []
};
}
@@ -117,6 +117,10 @@ export function createSidebarPageAiSessionRuntime(context) {
if (title.length > 28) title = title.slice(0, 28) + '…';
var persistence = String(row.persistence || payload.persistence || '').trim();
var sessionStorage = String(row.sessionStorage || row.session_storage || payload.sessionStorage || '').trim();
var status = String(row.status || (row.runtime && row.runtime.status) || '').trim();
var hasConversationSignal = String(payload.message || payload.input || row.snippet || '').trim()
|| pageAiNormalizeArray(row.messages).length > 0;
if (status === 'session.created' && !hasConversationSignal) return null;
var agentId = pageAiNormalizeAgentId(row.agentId || row.agent_id || payload.agentId || payload.agent_id);
var profileId = String(row.profileId || row.profile_id || payload.profileId || payload.profile_id || '').trim();
var profile = String(row.profile || payload.profile || pageAiRunProfile()).trim() || 'default';
@@ -143,7 +147,7 @@ export function createSidebarPageAiSessionRuntime(context) {
shareId: String(row.shareId || row.share_id || payload.shareId || '').trim(),
acpSessionId: String(row.acpSessionId || row.acp_session_id || payload.acpSessionId || payload.acp_session_id || '').trim(),
runId: String(row.runId || row.run_id || '').trim(),
status: String(row.status || (row.runtime && row.runtime.status) || '').trim(),
status: status,
usage: row.usage && typeof row.usage === 'object' ? row.usage : null,
preview: String(payload.message || row.snippet || '').trim(),
messages: []
@@ -164,11 +168,24 @@ export function createSidebarPageAiSessionRuntime(context) {
return pageAiNormalizeSessions(Object.keys(byId).map(function(id) { return byId[id]; }));
}
function pageAiDedupeSessions(sessions) {
var byId = {};
var ordered = [];
pageAiNormalizeSessions(sessions).forEach(function(session) {
var id = String(session && session.id || '').trim();
if (!id || byId[id]) return;
byId[id] = true;
ordered.push(session);
});
return ordered;
}
function pageAiSessionStorageLabel(session) {
var storage = String(session && (session.sessionStorage || session.session_storage) || '').trim();
var persistence = String(session && session.persistence || '').trim();
if (storage === 'local_shared') return '共享会话';
if (storage === 'local_private') return '本地私有';
if (storage === 'sqlite_control_plane' || persistence === 'sqlite_acp_runtime_store') return '账号会话';
if (storage === 'cloud' || persistence === 'convex_acp_runtime_store') return '云端会话';
if (persistence === 'local_ai_session_jsonl') return '本地私有';
return String(session && session.source || '').trim() === 'acp' ? '云端会话' : '本地私有';
@@ -179,30 +196,11 @@ export function createSidebarPageAiSessionRuntime(context) {
try {
var raw = win.localStorage.getItem(pageAiStorageKey());
var parsed = raw ? JSON.parse(raw) : null;
var activeId = String(parsed && parsed.activeSessionId || '').trim();
var activeProfile = String(parsed && parsed.activeProfileName || '').trim();
var activeAcpRuntime = String(parsed && parsed.activeAcpRuntime || '').trim();
var sessions = pageAiNormalizeSessions(parsed && parsed.sessions);
var storageVersion = Number(parsed && parsed.version || 0);
if (storageVersion >= sessionStorageVersion && activeAcpRuntime) pageUiState.pageAiAcpRuntime = activeAcpRuntime;
if (activeProfile) pageAiSetActiveProfile(activeProfile);
if (sessions.length) {
pageUiState.pageAiSessions = sessions;
var activeSession = sessions.find(function(session) { return session.id === activeId; }) || sessions[0];
pageUiState.pageAiActiveSessionId = activeSession.id;
if (activeSession.profile) pageAiSetActiveProfile(activeSession.profile);
if (activeSession.agentId) pageUiState.pageAiAgentId = pageAiNormalizeAgentId(activeSession.agentId);
if (!pageUiState.pageAiAcpRuntime && activeSession.acpRuntime) pageUiState.pageAiAcpRuntime = activeSession.acpRuntime;
pageUiState.pageAiMessages = Array.isArray(activeSession.messages) ? activeSession.messages.slice() : [];
return;
}
if (activeId) {
pageUiState.pageAiSessions = [pageAiNewSession('当前页问答')];
pageUiState.pageAiSessions[0].id = activeId;
pageUiState.pageAiActiveSessionId = activeId;
pageUiState.pageAiMessages = [];
return;
}
} catch (_) {}
var fresh = pageAiNewSession();
pageUiState.pageAiSessions = [fresh];
@@ -219,8 +217,24 @@ export function createSidebarPageAiSessionRuntime(context) {
throw new Error(pageAiErrorMessage(payload, 'session_list_failed_' + response.status));
}
var backendSessions = pageAiNormalizeArray(payload.sessions).map(pageAiNormalizeBackendSessionRow).filter(Boolean);
if (!backendSessions.length) return [];
pageUiState.pageAiSessions = pageAiMergeSessions(pageUiState.pageAiSessions, backendSessions);
var draftSessions = pageAiNormalizeArray(pageUiState.pageAiSessions).filter(function(session) {
var id = String(session && session.id || '').trim();
return id && !id.startsWith('mnote_')
&& (id === pageUiState.pageAiActiveSessionId || (Array.isArray(session.messages) && session.messages.length > 0));
});
if (!backendSessions.length) {
if (!draftSessions.length && !pageUiState.pageAiActiveSessionId) {
var fresh = pageAiNewSession();
pageUiState.pageAiSessions = [fresh];
pageUiState.pageAiActiveSessionId = fresh.id;
pageUiState.pageAiMessages = [];
}
pageUiState.pageAiSessionError = '';
renderPageAiConversation();
renderPageAiControls();
return [];
}
pageUiState.pageAiSessions = pageAiDedupeSessions(backendSessions.concat(draftSessions));
if (!pageUiState.pageAiActiveSessionId || !pageUiState.pageAiSessions.find(function(session) { return session.id === pageUiState.pageAiActiveSessionId; })) {
pageUiState.pageAiActiveSessionId = pageUiState.pageAiSessions[0].id;
}
@@ -242,7 +256,7 @@ export function createSidebarPageAiSessionRuntime(context) {
var eventType = String(event.eventType || event.event_type || event.event || '').trim();
var payload = event.payload && typeof event.payload === 'object' ? event.payload : event;
if (eventType === 'message.delta') {
var delta = String(payload.delta || payload.text || payload.output_text || '').trim();
var delta = String(payload.delta || payload.text || payload.output_text || '');
return delta ? { role: 'assistant', content: delta } : null;
}
if (eventType === 'thought.delta') {
@@ -301,10 +315,34 @@ export function createSidebarPageAiSessionRuntime(context) {
var userMessage = String(runPayload.message || runPayload.input || '').trim();
if (userMessage) messages.push({ role: 'user', content: userMessage });
var runId = String(run && (run.runId || run.run_id) || '').trim();
var assistantDelta = '';
var completedOutput = '';
function flushAssistantDelta() {
var content = assistantDelta.trim();
if (content) messages.push({ role: 'assistant', content: content });
assistantDelta = '';
}
pageAiNormalizeArray(eventsByRunId[runId]).forEach(function(event) {
var message = pageAiMessageFromRuntimeEvent(event);
if (message) messages.push(message);
if (!message) return;
if (message.role === 'assistant' && !message.kind) {
var eventType = String(event && (event.eventType || event.event_type || event.event) || '').trim();
if (eventType === 'run.completed') {
completedOutput = String(message.content || '').trim();
return;
}
assistantDelta += String(message.content || '');
return;
}
flushAssistantDelta();
messages.push(message);
});
flushAssistantDelta();
if (completedOutput && !messages.some(function(message) {
return message.role === 'assistant' && String(message.content || '').trim() === completedOutput;
})) {
messages.push({ role: 'assistant', content: completedOutput });
}
});
}
if (!messages.length) messages = storedMessages;
@@ -381,8 +419,7 @@ export function createSidebarPageAiSessionRuntime(context) {
version: sessionStorageVersion,
activeSessionId: pageUiState.pageAiActiveSessionId,
activeProfileName: pageAiCurrentProfile(),
activeAcpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
sessions: pageAiNormalizeSessions(pageUiState.pageAiSessions)
activeAcpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix'
}));
} catch (_) {}
}
@@ -427,7 +464,12 @@ export function createSidebarPageAiSessionRuntime(context) {
updatedAt: Date.now(),
messages: pageUiState.pageAiMessages.slice()
};
pageUiState.pageAiSessions = pageAiNormalizeSessions([session]);
var previousSessionId = String(current && current.id || '').trim();
var retainedSessions = pageAiNormalizeArray(pageUiState.pageAiSessions).filter(function(item) {
var itemId = String(item && item.id || '').trim();
return itemId && itemId !== session.id && itemId !== previousSessionId;
});
pageUiState.pageAiSessions = pageAiNormalizeSessions([session].concat(retainedSessions));
pageUiState.pageAiActiveSessionId = session.id;
pageAiPersistSessions();
renderPageAiConversation();
@@ -13,30 +13,39 @@ export function createSidebarPageAiSkillRuntime(context) {
function pageAiSkillSourceOptions() {
var options = [
{ value: 'mnote', group: 'mnote', label: 'mnote', profile: '' },
{ value: 'reasonix', group: 'reasonix', label: 'reasonix', profile: '' }
{ value: 'mnote', group: 'mnote', label: 'MNote 公共能力', profile: '' },
{ value: 'reasonix', group: 'reasonix', label: 'Reasonix skill(查看)', profile: '', readonly: true }
];
pageAiNormalizeArray(pageUiState.pageAiProfiles).forEach(function(profile) {
var profileId = pageAiProfileValue(profile);
if (!profileId) return;
var label = profile.kind === 'shared'
? (profile.baseProfile === 'lite' || profileId === 'shared_lite' ? 'hermes_lite' : 'Hermes_shared')
: 'Hermes_user';
var alias = String(profile.alias || '').trim();
if (!profileId || pageAiProfileIsChatOnlySkillSource(profile)) return;
var label = profile.kind === 'shared' ? 'Hermes 共享 skill(查看)' : 'Hermes skill(查看)';
var alias = String(profile.alias || profile.displayName || '').trim();
options.push({
value: 'hermes:' + profileId,
group: 'hermes',
profile: profileId,
label: alias && alias !== label ? label + ' · ' + alias : label,
readonly: profile.readonly === true
readonly: true
});
});
return options;
}
function pageAiProfileIsChatOnlySkillSource(profile) {
var profileId = String(pageAiProfileValue(profile) || '').trim().toLowerCase();
var baseProfile = String(profile && profile.baseProfile || '').trim().toLowerCase();
var label = String(profile && (profile.displayName || profile.alias || profile.name) || '').trim().toLowerCase();
var providerKind = String(profile && profile.providerKind || '').trim().toLowerCase();
return profileId.indexOf('chat') >= 0
|| baseProfile.indexOf('chat') >= 0
|| label.indexOf('chat') >= 0
|| providerKind.indexOf('chat') >= 0
|| profileId === 'shared_lite'
|| baseProfile === 'lite';
}
function pageAiDefaultSkillSource() {
if (pageAiCurrentAgentId() === 'hermes') return 'hermes:' + pageAiCurrentProfile();
if (pageAiCurrentAgentId() === 'reasonix') return 'reasonix';
return 'mnote';
}
@@ -172,21 +181,29 @@ export function createSidebarPageAiSkillRuntime(context) {
group: group,
profile: profile || '',
category: category.name,
categoryTitle: skill.categoryTitle || category.title || category.name || '',
id: id,
name: skill.name || id,
title: skill.title || skill.name || id,
description: skill.description || '',
enabled: group === 'hermes' ? skill.enabled !== false : (skill.enabled !== false && overrides[id] !== false),
toggleable: skill.toggleable !== false,
enabled: group === 'mnote' ? (skill.enabled !== false && overrides[id] !== false) : skill.enabled !== false,
toggleable: group === 'mnote' && skill.toggleable !== false,
source: skill.source || group,
origin: skill.origin || '',
readOnly: skill.readOnly === true || skill.readonly === true || skill.configurable === false,
readOnly: group !== 'mnote' || skill.readOnly === true || skill.readonly === true || skill.configurable === false,
builtin: pageAiSkillIsBuiltin(skill),
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 || []
});
});
@@ -198,21 +215,29 @@ export function createSidebarPageAiSkillRuntime(context) {
group: group,
profile: profile || '',
category: 'archived',
categoryTitle: skill.categoryTitle || 'archived',
id: id,
name: skill.name || id,
title: skill.title || skill.name || id,
description: skill.description || '',
enabled: group === 'hermes' ? skill.enabled !== false : (skill.enabled !== false && overrides[id] !== false),
toggleable: skill.toggleable !== false,
enabled: group === 'mnote' ? (skill.enabled !== false && overrides[id] !== false) : skill.enabled !== false,
toggleable: group === 'mnote' && skill.toggleable !== false,
source: skill.source || group,
origin: skill.origin || '',
readOnly: skill.readOnly === true || skill.readonly === true || skill.configurable === false,
readOnly: group !== 'mnote' || skill.readOnly === true || skill.readonly === true || skill.configurable === false,
builtin: pageAiSkillIsBuiltin(skill),
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 || []
});
});
@@ -59,13 +59,26 @@ export function createSidebarPageAiTargetRuntime(context) {
var kind = String(entry && (entry.editorKind || entry.kind) || '').trim().toLowerCase();
var assetId = String(entry && entry.assetId || '').trim();
var path = String(entry && entry.path || '').trim().toLowerCase();
var workspacePath = entry && entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : {};
var workspaceResourceKind = String(workspacePath.resourceKind || '').trim().toLowerCase();
var officeOpenMode = String(entry && entry.officeOpenMode || '').trim().toLowerCase();
var onlyofficeSessionId = String(entry && (entry.onlyofficeSessionId || entry.bridgeSessionId) || '').trim();
if (kind === 'page' || kind === 'markdown_page') return 'markdown_page';
if (kind === 'mindmap' || path.endsWith('.mindmap.json')) return 'mindmap';
if (kind === 'office' || kind === 'onlyoffice' || kind === 'only_office' || /\.(doc|docx|ppt|pptx|xls|xlsx)$/.test(path)) return 'only_office';
if (kind === 'office' || kind === 'onlyoffice' || kind === 'only_office' || workspaceResourceKind === 'only_office' || /\.(doc|docx|ppt|pptx|xls|xlsx)$/.test(path)) {
return officeOpenMode === 'onlyoffice_live' || onlyofficeSessionId ? 'only_office' : 'attachment';
}
if (kind === 'resource' && assetId) return 'resource';
return kind || 'markdown_page';
}
function pageAiIsOnlyOfficeLiveTarget(editorTarget) {
var workspacePath = editorTarget && editorTarget.workspacePath && typeof editorTarget.workspacePath === 'object' ? editorTarget.workspacePath : {};
var resourceKind = String(editorTarget && (editorTarget.resourceKind || editorTarget.editorKind) || workspacePath.resourceKind || '').trim().toLowerCase();
var officeOpenMode = String(editorTarget && editorTarget.officeOpenMode || '').trim().toLowerCase();
return resourceKind === 'only_office' || resourceKind === 'onlyoffice' || officeOpenMode === 'onlyoffice_live';
}
function pageAiTargetId(entry) {
if (!entry || typeof entry !== 'object') return '';
var workspacePath = entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : {};
@@ -113,6 +126,7 @@ export function createSidebarPageAiTargetRuntime(context) {
lastActiveAt: entry.lastActiveAt || 0,
assetId: entry.assetId || workspacePath.assetId || '',
path: entry.path || workspacePath.relativePath || '',
officeOpenMode: entry.officeOpenMode || '',
onlyofficeSessionId: entry.onlyofficeSessionId || entry.bridgeSessionId || '',
bridgeSessionId: entry.bridgeSessionId || entry.onlyofficeSessionId || '',
bridgeSessionReady: entry.bridgeSessionReady === true
@@ -137,6 +151,7 @@ export function createSidebarPageAiTargetRuntime(context) {
lastActiveAt: Number(entry.lastActiveAt || 0) || 0,
assetId: String(entry.assetId || '').trim(),
path: String(entry.path || '').trim(),
officeOpenMode: String(entry.officeOpenMode || '').trim(),
onlyofficeSessionId: String(entry.onlyofficeSessionId || entry.bridgeSessionId || '').trim(),
bridgeSessionId: String(entry.bridgeSessionId || entry.onlyofficeSessionId || '').trim(),
bridgeSessionReady: entry.bridgeSessionReady === true
@@ -661,7 +676,7 @@ export function createSidebarPageAiTargetRuntime(context) {
var workspacePath = editorTarget && editorTarget.workspacePath && typeof editorTarget.workspacePath === 'object' ? editorTarget.workspacePath : {};
var resourceKind = String(editorTarget && (editorTarget.resourceKind || editorTarget.editorKind) || workspacePath.resourceKind || '').trim();
var onlyofficeSessionId = String(editorTarget && (editorTarget.onlyofficeSessionId || editorTarget.bridgeSessionId) || '').trim();
if ((resourceKind === 'office' || resourceKind === 'only_office' || resourceKind === 'onlyoffice') && !onlyofficeSessionId) {
if (pageAiIsOnlyOfficeLiveTarget(editorTarget) && !onlyofficeSessionId) {
var sessionError = new Error('ONLYOFFICE 资源仍在连接 MNote bridge,请等待 Office 页面加载完成后再让 AI 操作。');
sessionError.code = 'page_ai_onlyoffice_bridge_not_ready';
throw sessionError;
@@ -740,8 +740,15 @@ export function createSidebarPageSettingsRuntime(context) {
var status = summary.status || {};
var settings = status.settings && typeof status.settings === 'object' ? status.settings : {};
var includePaths = Array.isArray(settings.includePaths) ? settings.includePaths : ['.'];
statusNode.textContent = '索引 ' + Number(status.documentCount || 0) + ' 个页面 / ' + Number(status.resourceCount || 0) + ' 个资源 / ' + Number(status.evidenceBlockCount || 0) + ' 个证据块' + (status.scheduledDue ? ',已到计划时间' : '');
var savedIncludePaths = Array.isArray(settings.includePaths) ? settings.includePaths : [];
var indexedPaths = Array.isArray(status.indexedPaths) ? status.indexedPaths : [];
var includePaths = savedIncludePaths.length ? savedIncludePaths : indexedPaths;
var hasIndexCache = status.indexExists === true || status.evidenceIndexExists === true;
if (!savedIncludePaths.length && !hasIndexCache) {
statusNode.textContent = '未设置索引范围';
} else {
statusNode.textContent = '索引 ' + Number(status.documentCount || 0) + ' 个页面 / ' + Number(status.resourceCount || 0) + ' 个资源 / ' + Number(status.evidenceBlockCount || 0) + ' 个证据块' + (status.scheduledDue ? ',已到计划时间' : '');
}
if (!(rangesNode instanceof HTMLElement) || !rangesNode.contains(document.activeElement)) {
renderLocalIndexRangeRows(popover, includePaths, localIndexStatusKind(status), false, status);
}
@@ -762,6 +769,9 @@ export function createSidebarPageSettingsRuntime(context) {
function localIndexStatusKind(status) {
if (!status || typeof status !== 'object') return 'fault';
var settings = status.settings && typeof status.settings === 'object' ? status.settings : {};
var includePaths = Array.isArray(settings.includePaths) ? settings.includePaths : [];
if (!includePaths.length && status.indexExists !== true && status.evidenceIndexExists !== true) return 'indexed';
if (status.indexExists !== true) return 'fault';
if (status.cacheMatchesSettings === true && status.scheduledDue !== true) return 'indexed';
return 'indexing';
@@ -774,10 +784,10 @@ export function createSidebarPageSettingsRuntime(context) {
}
function localIndexPathStatusKind(path, status, fallbackKind) {
if (fallbackKind === 'fault' || fallbackKind === 'indexing') return fallbackKind;
var indexedPaths = Array.isArray(status && status.indexedPaths) ? status.indexedPaths : [];
var normalizedPath = String(path || '').trim() || '.';
if (indexedPaths.indexOf(normalizedPath) >= 0 && status.cacheMatchesSettings === true) return 'indexed';
if (indexedPaths.indexOf(normalizedPath) >= 0 && status.indexExists === true) return 'indexed';
if (fallbackKind === 'fault' || fallbackKind === 'indexing') return fallbackKind;
if (status.indexExists === true) return 'indexing';
return 'fault';
}
@@ -789,13 +799,26 @@ export function createSidebarPageSettingsRuntime(context) {
return String(value || '').trim();
}) : [];
var paths = disabled ? rawPaths.filter(Boolean) : rawPaths;
if (!paths.length && !disabled) paths = ['.'];
var savedPaths = Array.isArray(status && status.settings && status.settings.includePaths)
? status.settings.includePaths.map(function(value) { return String(value || '').trim(); }).filter(Boolean)
: [];
var indexedPaths = Array.isArray(status && status.indexedPaths)
? status.indexedPaths.map(function(value) { return String(value || '').trim(); }).filter(Boolean)
: [];
rangesNode.innerHTML = paths.map(function(path, index) {
var isPersisted = savedPaths.indexOf(path) >= 0 || indexedPaths.indexOf(path) >= 0;
var inputDisabled = disabled || isPersisted;
var kind = localIndexPathStatusKind(path, status || {}, fallbackKind || 'fault');
var normalizedPath = String(path || '').trim() || '.';
var displayPath = inputDisabled && normalizedPath === '.' ? '工作区根目录(全部)' : path;
var rootScopeHint = normalizedPath === '.'
? '<span class="wolai-page-settings-index-root-hint">当前工作区全部</span>'
: '';
return '' +
'<div class="wolai-page-settings-index-range-row" data-local-index-range-row="true">' +
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + kind + '" title="' + escapeHtml(localIndexStatusLabel(kind)) + '"></span>' +
'<input type="text" class="wolai-page-settings-index-path" data-local-index-range-input="true" value="' + escapeHtml(path) + '" spellcheck="false"' + (disabled ? ' disabled' : '') + ' />' +
'<input type="text" class="wolai-page-settings-index-path" data-local-index-range-input="true" data-local-index-value="' + escapeHtml(normalizedPath) + '" value="' + escapeHtml(displayPath) + '" spellcheck="false"' + (inputDisabled ? ' disabled' : '') + (isPersisted ? ' data-local-index-persisted="true" title="已保存的索引目录不能直接修改;删除后重新新增范围"' : '') + ' />' +
rootScopeHint +
'<button type="button" class="wolai-page-settings-index-remove" data-local-index-action="remove-path" data-local-index-path-index="' + String(index) + '"' + (disabled ? ' disabled' : '') + ' aria-label="删除索引范围">×</button>' +
'</div>';
}).join('');
@@ -860,16 +883,17 @@ export function createSidebarPageSettingsRuntime(context) {
function localIndexIncludePathsFromForm() {
var popover = ensureLocalIndexSettingsPopover();
var values = currentLocalIndexRangeValues(popover).map(function(value) {
return currentLocalIndexRangeValues(popover).map(function(value) {
return value.trim();
}).filter(Boolean);
return values.length ? values : ['.'];
}
function currentLocalIndexRangeValues(popover) {
var root = popover || ensureLocalIndexSettingsPopover();
return Array.from(root.querySelectorAll('[data-local-index-range-input]')).map(function(input) {
return input instanceof HTMLInputElement ? input.value : '';
if (!(input instanceof HTMLInputElement)) return '';
if (input.disabled && input.dataset.localIndexValue) return input.dataset.localIndexValue;
return input.value;
});
}
@@ -901,7 +925,6 @@ export function createSidebarPageSettingsRuntime(context) {
var summary = pageUiState.localIndexSummary || {};
var values = currentLocalIndexRangeValues(popover);
values.splice(Number(index || 0), 1);
if (!values.length) values = [''];
renderLocalIndexRangeRows(popover, values, localIndexStatusKind(summary.status || {}), false, summary.status || {});
}
@@ -1162,6 +1162,8 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
var objectIdentity = fileObjectIdentity(item);
var ownerDocumentId = fileOwnerDocumentId(item, documentId, objectIdentity);
var iconKind = iconKindOf(item);
var indexStatus = String((item && item.indexStatus) || (item && item.resourceMeta && item.resourceMeta.indexStatus) || '').trim();
if (indexStatus !== 'indexed' && indexStatus !== 'failed') indexStatus = '';
var cachedChildren = relativePath ? cachedFileTreeRows(relativePath) : [];
var title = isFileTreeProjectionPageRow(rowKind, assetId)
? fileTreePageTitle(rawTitle)
@@ -1185,7 +1187,7 @@ export const createSidebarTreeLiveApplyRuntime = (dependencies = {}) => {
var childHtml = expandable && expanded && childRowsHtml
? '<ul class="tree-children">' + childRowsHtml + '</ul>'
: '';
return '<li class="tree-node" data-node-id="' + escapeHtml(nodeId) + '"><div class="tree-row" role="treeitem" aria-level="' + (depth + 1) + '" aria-expanded="' + String(expandable && expanded) + '" data-rust-rendered-row="filetree" data-testid="' + testId + '" data-row-id="' + escapeHtml(rowId) + '" data-row-kind="' + escapeHtml(rowKind) + '" data-node-id="' + escapeHtml(nodeId) + '"' + (parent ? ' data-parent-id="' + escapeHtml(parent) + '"' : '') + ' data-document-id="' + escapeHtml(documentId) + '" data-doc-id="' + escapeHtml(documentId) + '" data-owner-document-id="' + escapeHtml(ownerDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-local-relative-path="' + escapeHtml(relativePath) + '" data-object-identity="' + escapeHtml(objectIdentityAttr(objectIdentity)) + '" data-object-kind="' + escapeHtml(String(objectIdentity.objectKind || '')) + '" data-capabilities="' + escapeHtml(fileCapabilitiesAttr(item)) + '" data-shell-mode="filetree" data-selected="' + String(selected) + '" data-active="false" draggable="true">' + toggle + '<span class="tree-kind-badge" data-kind="' + escapeHtml(iconKind) + '" aria-hidden="true"></span><button type="button" class="tree-link" data-rust-action="open" data-row-id="' + escapeHtml(rowId) + '" data-document-id="' + escapeHtml(documentId) + '" data-owner-document-id="' + escapeHtml(ownerDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-local-relative-path="' + escapeHtml(relativePath) + '" title="' + escapeHtml(title) + '"><span class="tree-link-title" title="' + escapeHtml(title) + '">' + escapeHtml(title) + '</span></button><div class="tree-actions">' + createAction + '<button type="button" class="tree-action" data-testid="filetree-action-menu" data-rust-action="menu" data-row-id="' + escapeHtml(rowId) + '" aria-label="更多操作">…</button></div></div>' + childHtml + '</li>';
return '<li class="tree-node" data-node-id="' + escapeHtml(nodeId) + '"><div class="tree-row" role="treeitem" aria-level="' + (depth + 1) + '" aria-expanded="' + String(expandable && expanded) + '" data-rust-rendered-row="filetree" data-testid="' + testId + '" data-row-id="' + escapeHtml(rowId) + '" data-row-kind="' + escapeHtml(rowKind) + '" data-node-id="' + escapeHtml(nodeId) + '"' + (parent ? ' data-parent-id="' + escapeHtml(parent) + '"' : '') + ' data-document-id="' + escapeHtml(documentId) + '" data-doc-id="' + escapeHtml(documentId) + '" data-owner-document-id="' + escapeHtml(ownerDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-local-relative-path="' + escapeHtml(relativePath) + '" data-object-identity="' + escapeHtml(objectIdentityAttr(objectIdentity)) + '" data-object-kind="' + escapeHtml(String(objectIdentity.objectKind || '')) + '"' + (indexStatus ? ' data-index-status="' + escapeHtml(indexStatus) + '"' : '') + ' data-capabilities="' + escapeHtml(fileCapabilitiesAttr(item)) + '" data-shell-mode="filetree" data-selected="' + String(selected) + '" data-active="false" draggable="true">' + toggle + '<span class="tree-kind-badge" data-kind="' + escapeHtml(iconKind) + '" aria-hidden="true"></span><button type="button" class="tree-link" data-rust-action="open" data-row-id="' + escapeHtml(rowId) + '" data-document-id="' + escapeHtml(documentId) + '" data-owner-document-id="' + escapeHtml(ownerDocumentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-local-relative-path="' + escapeHtml(relativePath) + '" title="' + escapeHtml(title) + '"><span class="tree-link-title" title="' + escapeHtml(title) + '">' + escapeHtml(title) + '</span></button><div class="tree-actions">' + createAction + '<button type="button" class="tree-action" data-testid="filetree-action-menu" data-rust-action="menu" data-row-id="' + escapeHtml(rowId) + '" aria-label="更多操作">…</button></div></div>' + childHtml + '</li>';
}).join('');
}
@@ -874,6 +874,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
const inferOnlyOfficeFileType = (...args) => sidebarFileTreeOpen.inferOnlyOfficeFileType(...args);
const buildOnlyOfficeOpenUrl = (...args) => sidebarFileTreeOpen.buildOnlyOfficeOpenUrl(...args);
const buildOnlyOfficeOpenPath = (...args) => sidebarFileTreeOpen.buildOnlyOfficeOpenPath(...args);
const buildOfficePreviewOpenUrl = (...args) => sidebarFileTreeOpen.buildOfficePreviewOpenUrl(...args);
const buildPdfPreviewOpenUrl = (...args) => sidebarFileTreeOpen.buildPdfPreviewOpenUrl(...args);
const buildLocalOnlyOfficeOpenUrl = (...args) => sidebarFileTreeOpen.buildLocalOnlyOfficeOpenUrl(...args);
const openLocalOfficeFileInActiveTab = (...args) => sidebarFileTreeOpen.openLocalOfficeFileInActiveTab(...args);
@@ -2198,26 +2199,57 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
return searchText(shell && shell.getAttribute('data-document-id')) || searchText(bodyId);
}
function localResourceDocumentIdFromPath(path) {
var normalized = searchText(path).replace(/\\/g, '/');
return normalized ? 'local-resource:' + normalized.replace(/\//g, '~2F') : '';
}
function currentSearchDocumentId() {
var activePanel = document.querySelector('[data-mnote-resource-tab-panel][data-pane-role="primary"]:not([hidden])');
if (activePanel instanceof HTMLElement) {
var resourceKind = searchText(activePanel.getAttribute('data-resource-kind')).toLowerCase();
var resourcePath = searchText(activePanel.getAttribute('data-resource-path'));
if (resourceKind && resourceKind !== 'markdown' && resourcePath) return localResourceDocumentIdFromPath(resourcePath);
try {
var locator = JSON.parse(activePanel.getAttribute('data-mnote-evidence-locator') || 'null');
var locatorDoc = searchText(locator && (locator.ownerDocumentId || locator.owner_document_id));
if (locatorDoc) return locatorDoc;
} catch (_) {}
}
return currentDocumentId();
}
function searchSwitchValue(overlay, name) {
var button = overlay.querySelector('[data-search-switch="' + name + '"]');
return button instanceof HTMLElement && button.getAttribute('aria-checked') === 'true';
}
function highlightedHtml(value) {
return escapeHtml(value)
.replace(/&lt;mark&gt;/g, '<mark>')
.replace(/&lt;\/mark&gt;/g, '</mark>');
}
function highlightSearchTitle(title, query) {
var cleanTitle = searchText(title);
function highlightSearchText(value, query, exact) {
var text = searchText(value);
var cleanQuery = searchText(query);
if (!cleanQuery) return escapeHtml(cleanTitle);
var index = cleanTitle.toLowerCase().indexOf(cleanQuery.toLowerCase());
if (index < 0) return escapeHtml(cleanTitle);
return escapeHtml(cleanTitle.slice(0, index)) +
'<mark>' + escapeHtml(cleanTitle.slice(index, index + cleanQuery.length)) + '</mark>' +
escapeHtml(cleanTitle.slice(index + cleanQuery.length));
if (!text || !cleanQuery) return escapeHtml(text);
var lower = text.toLowerCase();
var lowerQuery = cleanQuery.toLowerCase();
var exactIndex = lower.indexOf(lowerQuery);
if (exact || exactIndex >= 0) {
if (exactIndex < 0) return escapeHtml(text);
return escapeHtml(text.slice(0, exactIndex)) +
'<mark>' + escapeHtml(text.slice(exactIndex, exactIndex + cleanQuery.length)) + '</mark>' +
escapeHtml(text.slice(exactIndex + cleanQuery.length));
}
var chars = Array.from(cleanQuery.replace(/\s+/g, '').toLowerCase());
if (!chars.length) return escapeHtml(text);
var next = 0;
var html = '';
Array.from(text).forEach(function(ch) {
if (next < chars.length && ch.toLowerCase() === chars[next]) {
html += '<mark>' + escapeHtml(ch) + '</mark>';
next += 1;
} else {
html += escapeHtml(ch);
}
});
return next >= chars.length ? html : escapeHtml(text);
}
function searchResultEvidenceLocator(item) {
@@ -2243,6 +2275,38 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
return searchText(action && action.url);
}
function evidenceLocatorLineRange(locator) {
return locator && (locator.lineRange || locator.line_range) || null;
}
function evidenceLocatorCharRange(locator) {
return locator && (locator.charRange || locator.char_range) || null;
}
function evidenceLocatorBlockId(locator) {
return searchText(locator && (locator.blockId || locator.block_id));
}
function resourceHrefForEvidenceLocator(resourceKind, resourcePath, fileName, ownerDocumentId, locator) {
var localFileUrl = buildLocalFileOpenUrl(resourcePath, false);
if (!localFileUrl) return '';
if (resourceKind === 'pdf') return buildPdfPreviewOpenUrl(localFileUrl, fileName);
if (resourceKind === 'office') {
var fileType = inferOnlyOfficeFileType(fileName, '') || (fileName.indexOf('.') >= 0 ? fileName.split('.').pop() : 'docx');
return buildOfficePreviewOpenUrl({
fileUrl: localFileUrl,
fileName: fileName,
fileType: fileType,
assetId: 'local-file:' + resourcePath,
documentId: ownerDocumentId || currentDocumentId() || '',
workspaceId: resolveWorkspaceId(document.body) || '',
sourceKind: currentSourceKind() || 'local_folder',
rootUri: searchText(locator && (locator.rootUri || locator.root_uri) || currentRootUri())
});
}
return localFileUrl;
}
async function openEvidenceSearchResult(item, event) {
var locator = searchResultEvidenceLocator(item);
if (!locator) {
@@ -2254,18 +2318,20 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
var ownerDocumentId = searchText(locator.ownerDocumentId || locator.owner_document_id || item.documentId || '');
var resourceKind = evidenceLocatorResourceKind(locator, item && item.resourceType);
var openTarget = event && event.altKey ? 'side' : 'active-tab';
if (resourcePath && resourceKind && resourceKind !== 'markdown') {
if (resourcePath && resourceKind) {
var fileName = resourcePath.split('/').filter(Boolean).pop() || resourcePath;
var localFileUrl = buildLocalFileOpenUrl(resourcePath, false);
var href = resourceKind === 'pdf' ? buildPdfPreviewOpenUrl(localFileUrl, fileName) : localFileUrl;
await openLocalResourceInActiveTab({
var normalizedKind = resourceKind === 'raw_file' || resourceKind === 'resource' ? fileTreeIconKindForFileName(fileName) || 'file' : resourceKind;
var hostDocumentId = currentDocumentId() || ownerDocumentId || '';
var href = resourceHrefForEvidenceLocator(normalizedKind, resourcePath, fileName, hostDocumentId, locator);
var openedInResourceTab = await openLocalResourceInActiveTab({
path: resourcePath,
title: fileName,
kind: resourceKind,
kind: normalizedKind,
href: href,
officeUrl: normalizedKind === 'office' ? href : '',
assetId: 'local-file:' + resourcePath,
documentId: ownerDocumentId || currentDocumentId() || '',
ownerDocumentId: ownerDocumentId || currentDocumentId() || '',
documentId: hostDocumentId,
ownerDocumentId: ownerDocumentId || hostDocumentId,
workspaceId: resolveWorkspaceId(document.body) || '',
sourceKind: currentSourceKind() || 'local_folder',
rootUri: searchText(locator.rootUri || locator.root_uri || currentRootUri()),
@@ -2274,10 +2340,11 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
page: locator.page,
bbox: locator.bbox,
sourceMapPath: searchText(locator.sourceMapPath || locator.source_map_path),
blockId: searchText(locator.blockId || locator.block_id),
lineRange: locator.lineRange || locator.line_range || null,
charRange: locator.charRange || locator.char_range || null
blockId: evidenceLocatorBlockId(locator),
lineRange: evidenceLocatorLineRange(locator),
charRange: evidenceLocatorCharRange(locator)
});
if (!openedInResourceTab) console.warn('mnote evidence 搜索结果无法在当前页面资源标签打开', locator);
closeSearchModal();
return;
}
@@ -2339,7 +2406,7 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
workspaceId: resolveWorkspaceId(document.body),
sourceKind: currentSourceKind() || null,
rootUri: currentRootUri() || null,
documentId: currentDocumentId() || null,
documentId: searchSwitchValue(overlay, 'page') ? (currentSearchDocumentId() || null) : (currentDocumentId() || null),
query: query,
limit: 30,
filters: {
@@ -2368,10 +2435,11 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
var locator = searchResultEvidenceLocator(item);
var locatorAttr = locator ? escapeHtml(JSON.stringify(locator)) : '';
var index = items.indexOf(item);
var exact = searchSwitchValue(overlay, 'exact');
return '<button type="button" class="wolai-search-result-row" data-testid="wolai-search-result-row" data-search-result-owner="rust-kernel" data-search-result-index="' + String(index) + '" data-document-id="' + escapeHtml(item.documentId || item.nodeId || item.id || '') + '" data-resource-type="' + escapeHtml(resourceType) + '"' + (locatorAttr ? ' data-evidence-locator="' + locatorAttr + '"' : '') + '>' +
'<span class="material-symbols-outlined wolai-search-result-icon" data-icon="article" aria-hidden="true"></span>' +
'<span class="wolai-search-result-main"><span class="wolai-search-result-title">' + highlightSearchTitle(title, query) + '</span>' +
'<span class="wolai-search-result-snippet">' + (snippet ? highlightedHtml(snippet) : escapeHtml(path)) + '</span>' +
'<span class="wolai-search-result-main"><span class="wolai-search-result-title">' + highlightSearchText(title, query, exact) + '</span>' +
'<span class="wolai-search-result-snippet">' + (snippet ? highlightSearchText(snippet, query, exact) : escapeHtml(path)) + '</span>' +
'<span class="wolai-search-result-path"><span>' + escapeHtml(path) + '</span><span class="wolai-search-result-type">' + escapeHtml(resourceType) + '</span></span></span>' +
'</button>';
}).join('');
@@ -2583,7 +2651,16 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
return;
}
var ocrSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-ocr-settings"], [data-mnote-action="toggle-ocr-tasks"]');
var ocrTaskTrigger = closestAction(e.target, '[data-mnote-action="toggle-ocr-tasks"]');
if (ocrTaskTrigger) {
e.preventDefault();
window.dispatchEvent(new CustomEvent('mnote:local-ocr-settings-action', {
detail: { action: 'tasks' }
}));
return;
}
var ocrSettingsTrigger = closestAction(e.target, '[data-mnote-action="open-ocr-settings"]');
if (ocrSettingsTrigger) {
e.preventDefault();
openLocalOcrSettingsPopover();