Files
mnote/rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js
T
lix-2026 06327d59f5 feat: 7-61 Yuxi reference — extensions panel, KB detail, manifest split, event router, dashboard
Batch B: settings popover 新增"扩展"tab,按知识库/Skills/MCP/Agent工具分面板
Batch C: knowledge-rag popover 新增"资料源管理/检索测试/检索参数"三tab
Batch A: manifest.rs 按能力域拆为 10 个分组函数(context/doc/knowledge_rag/block/page/mindmap/office/onlyoffice/artifact)
Batch D: 新增 agent-stream-event-router.js 共享模块(事件状态机 + SSE 帧解析)
Batch E: settings popover 新增"仪表盘"tab — 知识库文件数/Skills数量/MCP连接状态/Agent run次数

836/838 Rust 测试通过(仅 2 个预存失败)
2026-06-13 22:59:24 +08:00

2674 lines
130 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export function createSidebarPageSettingsRuntime(context) {
const {
currentDocumentId,
currentPageAggregate,
currentRootUri,
currentSourceKind,
currentWorkspaceSourcePayload,
escapeHtml,
pageUiState,
resolveWorkspaceId,
searchText,
} = context;
const MNOTE_GLOBAL_SHOW_HEADING_NUMBERS_KEY = context.globalShowHeadingNumbersKey || 'mnote.global.showHeadingNumbers';
const PAGE_WIDTH_TYPES = ['default', 'markdown', 'word', 'pdf', 'excel', 'ppt', 'mindmap'];
const PAGE_WIDTH_LABELS = {
default: '默认',
markdown: 'Markdown',
word: 'Word',
pdf: 'PDF',
excel: 'Excel',
ppt: 'PPT',
mindmap: 'Mindmap'
};
const DEFAULT_PAGE_WIDTH_PREFERENCES = {
default: { mode: 'comfortable', resolvedMode: 'comfortable', cssMaxWidth: '980px', source: 'system' },
markdown: { mode: 'readable', resolvedMode: 'readable', cssMaxWidth: '760px', source: 'system' },
word: { mode: 'wide', resolvedMode: 'wide', cssMaxWidth: '1180px', source: 'system' },
pdf: { mode: 'wide', resolvedMode: 'wide', cssMaxWidth: '1180px', source: 'system' },
excel: { mode: 'full', resolvedMode: 'full', cssMaxWidth: 'none', source: 'system' },
ppt: { mode: 'wide', resolvedMode: 'wide', cssMaxWidth: '1180px', source: 'system' },
mindmap: { mode: 'full', resolvedMode: 'full', cssMaxWidth: 'none', source: 'system' }
};
var knowledgeRagPipelineEventSource = null;
var knowledgeRagPipelineWatcherScopeKey = '';
var knowledgeRagPipelineWatcherRefreshPending = false;
function defaultPageOptions() {
return {
wideLayout: false,
smallText: false,
layoutDensity: 'normal',
showHeadingNumbers: false,
showToc: false,
showStructure: false,
protectEditing: false,
showWordCount: true,
collapseBacklinks: false,
pageFont: 'default',
hideChildPages: false,
showBlockRefCount: false,
hideTitleHeader: false,
embedDefaultBlockId: null
};
}
function currentPageOptions() {
if (pageUiState.pageOptions) return pageUiState.pageOptions;
var aggregate = currentPageAggregate();
var current = aggregate && aggregate.layout && aggregate.layout.pageOptions && typeof aggregate.layout.pageOptions === 'object'
? aggregate.layout.pageOptions
: null;
if (!current) {
return defaultPageOptions();
}
pageUiState.pageOptions = Object.assign(defaultPageOptions(), current);
return pageUiState.pageOptions;
}
function currentPageWidthPreferences() {
return Object.assign({}, DEFAULT_PAGE_WIDTH_PREFERENCES, pageUiState.pageWidthPreferences || {});
}
function currentKnowledgeRagSummary() {
return pageUiState.knowledgeRagSummary || {};
}
function pageWidthModeLabel(mode) {
if (mode === 'inherit') return '继承默认';
if (mode === 'readable') return '阅读';
if (mode === 'comfortable') return '舒适';
if (mode === 'wide') return '宽版';
if (mode === 'full') return '全宽';
return '舒适';
}
function normalizePageWidthPreferences(value) {
var input = value && typeof value === 'object' ? value : {};
var output = {};
PAGE_WIDTH_TYPES.forEach(function(type) {
var base = DEFAULT_PAGE_WIDTH_PREFERENCES[type];
var current = input[type] && typeof input[type] === 'object' ? input[type] : {};
var mode = String(current.mode || base.mode || 'comfortable');
var resolvedMode = String(current.resolvedMode || current.resolved_mode || (mode === 'inherit' ? (input.default && input.default.resolvedMode) || 'comfortable' : mode));
output[type] = {
mode: mode,
custom: current.custom || null,
resolvedMode: resolvedMode,
resolvedCustom: current.resolvedCustom || current.resolved_custom || null,
cssMaxWidth: String(current.cssMaxWidth || current.css_max_width || pageWidthCssMaxWidth(resolvedMode)),
source: String(current.source || base.source || 'system')
};
});
return output;
}
function pageWidthCssMaxWidth(mode) {
if (mode === 'readable') return '760px';
if (mode === 'comfortable') return '980px';
if (mode === 'wide') return '1180px';
if (mode === 'full') return 'none';
return '980px';
}
function markdownPageWidthPreference(options) {
var preferences = currentPageWidthPreferences();
var markdown = preferences.markdown || DEFAULT_PAGE_WIDTH_PREFERENCES.markdown;
if (markdown.source === 'system' && options && options.wideLayout) {
return {
mode: 'comfortable',
resolvedMode: 'comfortable',
cssMaxWidth: '980px',
source: 'wideLayout'
};
}
return markdown;
}
function activeResourceWidthType() {
var raw = document.documentElement.getAttribute('data-mnote-active-resource-width-type') || '';
var type = String(raw).trim();
if (type === 'sheet') return 'excel';
if (PAGE_WIDTH_TYPES.indexOf(type) >= 0 && type !== 'default') return type;
return '';
}
function activeResourceWidthPreference(options) {
var type = activeResourceWidthType();
if (!type || type === 'markdown') return markdownPageWidthPreference(options);
var preferences = currentPageWidthPreferences();
return preferences[type] || DEFAULT_PAGE_WIDTH_PREFERENCES[type] || DEFAULT_PAGE_WIDTH_PREFERENCES.default;
}
function notifyPageWidthPreferenceChanged(type) {
var detail = {
type: String(type || ''),
preferences: currentPageWidthPreferences()
};
window.dispatchEvent(new CustomEvent('mnote:page-width-preference-changed', { detail: detail }));
document.querySelectorAll('iframe.mnote-resource-tab-frame').forEach(function(frame) {
if (!(frame instanceof HTMLIFrameElement) || !frame.contentWindow) return;
try {
frame.contentWindow.postMessage({
type: 'mnote:page-width-preference-changed',
contentType: detail.type
}, window.location.origin);
} catch (_) {}
});
}
function readGlobalShowHeadingNumbers() {
return Boolean(currentPageOptions().showHeadingNumbers);
}
function writeGlobalShowHeadingNumbers(value) {
void persistPageOptionsPatch({ showHeadingNumbers: Boolean(value) });
}
function effectiveShowHeadingNumbers(options) {
return Boolean(options && options.showHeadingNumbers);
}
function pageOptionIsSupported(name) {
return name === 'wideLayout'
|| name === 'smallText'
|| name === 'layoutDensity'
|| name === 'pageFont'
|| name === 'showHeadingNumbers'
|| (name === 'hideTitleHeader' && currentSourceKind() === 'local_folder');
}
function pageOptionDescription(name) {
if (name === 'wideLayout') return '自适应宽度';
if (name === 'smallText') return '小字体';
if (name === 'hideTitleHeader') return '隐藏本地 Markdown 文件标题';
if (name === 'showToc') return '标题目录';
if (name === 'showHeadingNumbers') return '标题自动编号';
if (name === 'protectEditing') return '编辑保护';
if (name === 'collapseBacklinks') return '折叠反向引用';
if (name === 'hideChildPages') return '隐藏子页面';
if (name === 'showBlockRefCount') return '显示块引用数字';
return name;
}
function pageOptionHint(name) {
if (name === 'wideLayout') return '已接通:主内容列宽度立即变化';
if (name === 'smallText') return '已接通:正文排版会更紧凑';
if (name === 'showHeadingNumbers') return '已接通:标题前显示顺序编号';
if (name === 'hideTitleHeader') return '已接通:本地 Markdown 页头标题立即隐藏或显示';
if (name === 'layoutDensity') return '已接通:段落与列表间距会变化';
if (name === 'pageFont') return '已接通:当前页面字体会切换';
if (name === 'showToc') return '待接线:当前 Rust 壳还没有正式目录面板';
if (name === 'protectEditing') return '待接线:当前主编辑器只显示降级说明';
if (name === 'collapseBacklinks') return '待接线:当前 Rust 壳未挂回链面板';
if (name === 'hideChildPages') return '待接线:当前页面壳还没有子页面块显隐';
if (name === 'showBlockRefCount') return '待接线:当前页面壳未显示块引用计数';
return '待接线';
}
function ensureHistorySnapshotsSeeded() {
if (pageUiState.historySnapshots.length > 0) return;
var aggregate = currentPageAggregate();
var stats = aggregate && aggregate.stats && typeof aggregate.stats === 'object' ? aggregate.stats : {};
pageUiState.historySnapshots = [{
id: 'snapshot-initial',
timestamp: Date.now(),
stats: {
wordCount: Number(stats.wordCount || 0),
characterCount: Number(stats.characterCount || 0),
blockCount: Number(stats.blockCount || 0),
todoTotal: Number(stats.todoTotal || 0),
todoDone: Number(stats.todoDone || 0)
}
}];
}
function computeLivePageStats() {
var aggregate = currentPageAggregate();
var fallbackStats = aggregate && aggregate.stats && typeof aggregate.stats === 'object' ? aggregate.stats : {};
var editor = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror');
if (!(editor instanceof HTMLElement)) {
return {
wordCount: Number(fallbackStats.wordCount || 0),
characterCount: Number(fallbackStats.characterCount || 0),
blockCount: Number(fallbackStats.blockCount || 0),
todoTotal: Number(fallbackStats.todoTotal || 0),
todoDone: Number(fallbackStats.todoDone || 0)
};
}
var text = (editor.textContent || '').trim();
var compact = text.replace(/\s+/g, ' ').trim();
var wordCount = compact ? compact.split(' ').filter(Boolean).length : 0;
var characterCount = text.replace(/\s/g, '').length;
var blockCount = editor.querySelectorAll(':scope > *').length;
var todos = Array.from(editor.querySelectorAll('input[type="checkbox"]'));
return {
wordCount: wordCount || Number(fallbackStats.wordCount || 0),
characterCount: characterCount || Number(fallbackStats.characterCount || 0),
blockCount: blockCount || Number(fallbackStats.blockCount || 0),
todoTotal: todos.length || Number(fallbackStats.todoTotal || 0),
todoDone: todos.filter(function(node){ return node.checked; }).length || Number(fallbackStats.todoDone || 0)
};
}
function recordPageHistorySnapshot(reason, stats) {
ensureHistorySnapshotsSeeded();
pageUiState.historySnapshots = [{
id: 'snapshot-' + Date.now(),
timestamp: Date.now(),
reason: reason || 'save',
stats: stats || computeLivePageStats()
}].concat(pageUiState.historySnapshots).slice(0, 15);
renderPageHistoryDrawer();
}
function applyPageOptionsToShell() {
var options = currentPageOptions();
var globalShowHeadingNumbers = Boolean(options.showHeadingNumbers);
var showHeadingNumbers = effectiveShowHeadingNumbers(options);
var shell = document.querySelector('.document-shell');
var editorRoot = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
var editorSurface = editorRoot && editorRoot.querySelector('.editor-surface');
var mindmapWidthPreference = currentPageWidthPreferences().mindmap || DEFAULT_PAGE_WIDTH_PREFERENCES.mindmap;
var mindmapMaxWidth = String(mindmapWidthPreference.cssMaxWidth || pageWidthCssMaxWidth(mindmapWidthPreference.resolvedMode));
var mindmapMaxWidthValue = mindmapMaxWidth === 'none' ? 'none' : mindmapMaxWidth;
if (shell instanceof HTMLElement) {
var widthPreference = activeResourceWidthPreference(options);
var cssMaxWidth = String(widthPreference.cssMaxWidth || pageWidthCssMaxWidth(widthPreference.resolvedMode));
shell.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout)));
shell.setAttribute('data-page-width-content-type', activeResourceWidthType() || 'markdown');
shell.setAttribute('data-page-width-mode', String(widthPreference.mode || 'readable'));
shell.setAttribute('data-page-width-resolved-mode', String(widthPreference.resolvedMode || widthPreference.mode || 'readable'));
shell.setAttribute('data-page-width-source', String(widthPreference.source || 'system'));
shell.setAttribute('data-page-small-text', String(Boolean(options.smallText)));
shell.setAttribute('data-layout-density', String(options.layoutDensity || 'normal'));
shell.setAttribute('data-page-font', String(options.pageFont || 'default'));
shell.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers));
shell.style.width = '100%';
shell.style.maxWidth = cssMaxWidth === 'none' ? 'none' : cssMaxWidth;
shell.style.setProperty('--mnote-mindmap-block-max-width', mindmapMaxWidthValue);
}
if (editorRoot instanceof HTMLElement) {
editorRoot.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout)));
editorRoot.setAttribute('data-page-small-text', String(Boolean(options.smallText)));
editorRoot.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers));
editorRoot.setAttribute('data-page-embed-default-block-id', options.embedDefaultBlockId == null ? '' : String(options.embedDefaultBlockId));
editorRoot.style.setProperty('--mnote-mindmap-block-max-width', mindmapMaxWidthValue);
}
if (editorSurface instanceof HTMLElement) {
editorSurface.setAttribute('data-layout-density', String(options.layoutDensity || 'normal'));
editorSurface.setAttribute('data-page-font', String(options.pageFont || 'default'));
editorSurface.setAttribute('data-show-heading-numbers', String(showHeadingNumbers));
}
document.documentElement.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout)));
var rootWidthPreference = activeResourceWidthPreference(options);
document.documentElement.setAttribute('data-page-width-content-type', activeResourceWidthType() || 'markdown');
document.documentElement.setAttribute('data-page-width-mode', String(rootWidthPreference.mode || 'readable'));
document.documentElement.setAttribute('data-page-width-resolved-mode', String(rootWidthPreference.resolvedMode || rootWidthPreference.mode || 'readable'));
document.documentElement.setAttribute('data-page-small-text', String(Boolean(options.smallText)));
document.documentElement.setAttribute('data-layout-density', String(options.layoutDensity || 'normal'));
document.documentElement.setAttribute('data-page-font', String(options.pageFont || 'default'));
document.documentElement.setAttribute('data-global-show-heading-numbers', String(globalShowHeadingNumbers));
document.documentElement.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers));
document.documentElement.setAttribute('data-page-hide-title-header', String(Boolean(options.hideTitleHeader)));
document.documentElement.style.setProperty('--mnote-mindmap-block-max-width', mindmapMaxWidthValue);
var titleHeader = document.querySelector('.document-pane[data-pane-role="primary"] .document-shell-header') || document.querySelector('.document-shell-header');
if (titleHeader instanceof HTMLElement) {
var hidden = Boolean(options.hideTitleHeader);
titleHeader.hidden = hidden;
titleHeader.setAttribute('data-page-title-hidden', String(hidden));
}
}
function updatePageSettingsTriggerState() {
var trigger = document.querySelector('[data-testid="wolai-page-settings-trigger"]');
if (!(trigger instanceof HTMLElement)) return;
trigger.setAttribute('data-state', pageUiState.pageSettingsOpen ? 'open' : 'closed');
trigger.setAttribute('aria-expanded', pageUiState.pageSettingsOpen ? 'true' : 'false');
}
function updateStandaloneSettingsTriggerState() {
var indexTrigger = document.querySelector('[data-testid="mnote-local-index-settings-toggle"]');
var indexOpen = isLocalIndexSettingsOpen();
if (indexTrigger instanceof HTMLElement) {
indexTrigger.setAttribute('data-state', indexOpen ? 'open' : 'closed');
indexTrigger.setAttribute('aria-expanded', indexOpen ? 'true' : 'false');
}
var ragTrigger = document.querySelector('[data-testid="mnote-knowledge-rag-settings-toggle"]');
var ragOpen = isKnowledgeRagSettingsOpen();
if (ragTrigger instanceof HTMLElement) {
ragTrigger.setAttribute('data-state', ragOpen ? 'open' : 'closed');
ragTrigger.setAttribute('aria-expanded', ragOpen ? 'true' : 'false');
}
}
function createPageOptionRow(key, type) {
var inputType = type || 'checkbox';
var supported = pageOptionIsSupported(key);
if (inputType === 'checkbox') {
return '' +
'<label class="wolai-page-setting-row' + (supported ? '' : ' is-pending') + '" data-page-setting-row="' + key + '">' +
'<span class="wolai-page-setting-copy">' +
'<span class="wolai-page-setting-label">' + escapeHtml(pageOptionDescription(key)) + '</span>' +
'<span class="wolai-page-setting-hint">' + escapeHtml(pageOptionHint(key)) + '</span>' +
'</span>' +
'<input type="checkbox" class="wolai-page-setting-checkbox" data-page-option-checkbox="' + key + '"' + (supported ? '' : ' data-setting-pending="true"') + ' />' +
'</label>';
}
return '' +
'<label class="wolai-page-setting-row" data-page-setting-row="' + key + '">' +
'<span class="wolai-page-setting-copy">' +
'<span class="wolai-page-setting-label">' + escapeHtml(pageOptionDescription(key)) + '</span>' +
'<span class="wolai-page-setting-hint">' + escapeHtml(pageOptionHint(key)) + '</span>' +
'</span>' +
'<select class="wolai-page-setting-select" data-page-option-select="' + key + '">' +
'<option value="compact">紧凑</option>' +
'<option value="normal">默认</option>' +
'<option value="spacious">宽松</option>' +
'</select>' +
'</label>';
}
function createGlobalHeadingNumbersRow() {
return '' +
'<label class="wolai-page-setting-row" data-page-setting-row="globalShowHeadingNumbers">' +
'<span class="wolai-page-setting-copy">' +
'<span class="wolai-page-setting-label">标题自动编号</span>' +
'</span>' +
'<input type="checkbox" class="wolai-page-setting-checkbox" data-global-option-checkbox="showHeadingNumbers" />' +
'</label>';
}
function createPageWidthSelectRow(type) {
var options = type === 'default'
? [
['readable', '阅读'],
['comfortable', '舒适'],
['wide', '宽版'],
['full', '全宽']
]
: [
['inherit', '继承默认'],
['readable', '阅读'],
['comfortable', '舒适'],
['wide', '宽版'],
['full', '全宽']
];
return '' +
'<label class="wolai-page-setting-row" data-page-width-row="' + type + '">' +
'<span class="wolai-page-setting-copy">' +
'<span class="wolai-page-setting-label">' + escapeHtml(PAGE_WIDTH_LABELS[type] || type) + '</span>' +
'<span class="wolai-page-setting-hint" data-page-width-hint="' + type + '"></span>' +
'</span>' +
'<select class="wolai-page-setting-select" data-page-width-select="' + type + '">' +
options.map(function(option) {
return '<option value="' + option[0] + '">' + option[1] + '</option>';
}).join('') +
'</select>' +
'</label>';
}
function createPageWidthRows() {
return '' +
'<div class="wolai-page-settings-global-note">页面宽度</div>' +
PAGE_WIDTH_TYPES.map(createPageWidthSelectRow).join('');
}
function renderGlobalOptions(popover) {
var globalHeadingNumbers = readGlobalShowHeadingNumbers();
popover.querySelectorAll('[data-global-option-checkbox="showHeadingNumbers"]').forEach(function(input) {
input.checked = globalHeadingNumbers;
});
var preferences = currentPageWidthPreferences();
popover.querySelectorAll('[data-page-width-select]').forEach(function(select) {
var type = select.getAttribute('data-page-width-select') || '';
var preference = preferences[type] || DEFAULT_PAGE_WIDTH_PREFERENCES[type] || DEFAULT_PAGE_WIDTH_PREFERENCES.default;
select.value = String(preference.mode || 'comfortable');
});
popover.querySelectorAll('[data-page-width-hint]').forEach(function(node) {
var type = node.getAttribute('data-page-width-hint') || '';
var preference = preferences[type] || DEFAULT_PAGE_WIDTH_PREFERENCES[type] || DEFAULT_PAGE_WIDTH_PREFERENCES.default;
var resolvedMode = String(preference.resolvedMode || preference.mode || 'comfortable');
node.textContent = pageWidthModeLabel(resolvedMode) + ' · ' + String(preference.cssMaxWidth || pageWidthCssMaxWidth(resolvedMode));
});
}
function markRetiredLocalOcrPreferenceSurface() {
document.documentElement.setAttribute('data-mnote-local-ocr-auto-retired', 'true');
}
function createPageFontRow() {
return '' +
'<label class="wolai-page-setting-row" data-page-setting-row="pageFont">' +
'<span class="wolai-page-setting-copy">' +
'<span class="wolai-page-setting-label">页面字体</span>' +
'<span class="wolai-page-setting-hint">已接通:仅对当前页面生效</span>' +
'</span>' +
'<select class="wolai-page-setting-select" data-page-option-select="pageFont">' +
'<option value="default">默认</option>' +
'<option value="song">宋体</option>' +
'<option value="kai">楷体</option>' +
'</select>' +
'</label>';
}
function ensurePageHistoryDrawer() {
var existing = document.querySelector('[data-testid="wolai-page-history-drawer"]');
if (existing instanceof HTMLElement) return existing;
var drawer = document.createElement('aside');
drawer.className = 'wolai-page-history-drawer';
drawer.setAttribute('data-testid', 'wolai-page-history-drawer');
drawer.setAttribute('data-mnote-surface', 'page-history');
drawer.hidden = true;
drawer.innerHTML = '' +
'<div class="wolai-page-history-panel">' +
'<div class="wolai-page-history-header">' +
'<div><div class="wolai-page-history-title">页面历史</div><div class="wolai-page-history-subtitle">当前会话内最近保存的 15 个快照。</div></div>' +
'<button type="button" class="wolai-surface-close" data-page-history-action="close" aria-label="关闭页面历史">×</button>' +
'</div>' +
'<div class="wolai-page-history-list" data-page-history-list></div>' +
'</div>';
drawer.addEventListener('click', function(event) {
if (event.target === drawer) closePageHistoryDrawer();
});
document.body.appendChild(drawer);
return drawer;
}
function renderPageHistoryDrawer() {
var drawer = ensurePageHistoryDrawer();
var list = drawer.querySelector('[data-page-history-list]');
if (!(list instanceof HTMLElement)) return;
ensureHistorySnapshotsSeeded();
list.innerHTML = pageUiState.historySnapshots.length
? pageUiState.historySnapshots.map(function(snapshot) {
var stats = snapshot.stats || {};
var label = new Date(snapshot.timestamp).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
return '' +
'<div class="wolai-page-history-item">' +
'<div class="wolai-page-history-item-copy">' +
'<div class="wolai-page-history-item-title">' + escapeHtml(label) + '</div>' +
'<div class="wolai-page-history-item-meta">字数 ' + Number(stats.wordCount || 0) + ' · 字符 ' + Number(stats.characterCount || 0) + ' · 块数 ' + Number(stats.blockCount || 0) + '</div>' +
'</div>' +
'<button type="button" class="wolai-page-history-item-ghost" data-page-history-action="noop">仅查看</button>' +
'</div>';
}).join('')
: '<div class="wolai-page-history-empty">尚未产生历史快照,编辑后会自动生成。</div>';
}
function openPageHistoryDrawer() {
renderPageHistoryDrawer();
var drawer = ensurePageHistoryDrawer();
drawer.hidden = false;
document.documentElement.setAttribute('data-mnote-page-history-open', 'true');
}
function closePageHistoryDrawer() {
var drawer = document.querySelector('[data-testid="wolai-page-history-drawer"]');
if (drawer instanceof HTMLElement) drawer.hidden = true;
document.documentElement.removeAttribute('data-mnote-page-history-open');
}
function ensurePageShareDialog() {
var existing = document.querySelector('[data-testid="wolai-page-share-dialog"]');
if (existing instanceof HTMLElement) return existing;
var dialog = document.createElement('div');
dialog.className = 'wolai-page-share-dialog';
dialog.setAttribute('data-testid', 'wolai-page-share-dialog');
dialog.setAttribute('data-mnote-surface', 'page-share');
dialog.hidden = true;
dialog.innerHTML = '' +
'<div class="wolai-page-share-card" role="dialog" aria-modal="true">' +
'<div class="wolai-page-share-header">' +
'<div><div class="wolai-page-share-title">公开分享页面</div><div class="wolai-page-share-subtitle">当前 3000 公开入口由 mnote-web 持有。</div></div>' +
'<button type="button" class="wolai-surface-close" data-page-share-action="close" aria-label="关闭公开分享页面">×</button>' +
'</div>' +
'<div class="wolai-page-share-state">' +
'<span class="wolai-public-pill wolai-public-pill--inline">全网公开</span>' +
'<span class="wolai-page-share-copy">任何拥有链接的人都可以访问当前页面。</span>' +
'</div>' +
'<div class="wolai-page-share-url-row">' +
'<input class="wolai-page-share-url" type="text" readonly data-page-share-url value="" />' +
'<button type="button" class="wolai-page-share-copy-button" data-page-share-action="copy-link">复制链接</button>' +
'</div>' +
'<div class="wolai-page-share-footer">更多共享者、群组公开和权限策略仍待接线。</div>' +
'</div>';
dialog.addEventListener('click', function(event) {
if (event.target === dialog) closePageShareDialog();
});
document.body.appendChild(dialog);
return dialog;
}
function openPageShareDialog() {
var dialog = ensurePageShareDialog();
var input = dialog.querySelector('[data-page-share-url]');
if (input instanceof HTMLInputElement) input.value = window.location.href;
dialog.hidden = false;
document.documentElement.setAttribute('data-mnote-page-share-open', 'true');
}
function closePageShareDialog() {
var dialog = document.querySelector('[data-testid="wolai-page-share-dialog"]');
if (dialog instanceof HTMLElement) dialog.hidden = true;
document.documentElement.removeAttribute('data-mnote-page-share-open');
}
function ensurePageSettingsPopover() {
var existing = document.querySelector('[data-testid="wolai-page-settings-popover"]');
if (existing instanceof HTMLElement) return existing;
var popover = document.createElement('div');
popover.className = 'wolai-page-settings-popover';
popover.setAttribute('data-testid', 'wolai-page-settings-popover');
popover.setAttribute('data-mnote-surface', 'page-settings');
popover.hidden = true;
popover.innerHTML = '' +
'<div class="wolai-page-settings-panel" role="dialog" aria-modal="false">' +
'<div class="wolai-page-settings-tabs" data-testid="wolai-page-settings-tabs" role="tablist" aria-label="页面设置分组">' +
'<button type="button" class="wolai-page-settings-tab is-active" role="tab" aria-selected="true" data-page-settings-tab="page">页面选项</button>' +
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="custom">自定义页面</button>' +
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="global">全局选项</button>' +
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="extensions">扩展</button>' +
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="dashboard">仪表盘</button>' +
'</div>' +
'<div class="wolai-page-settings-section" data-page-settings-panel="page">' +
createPageOptionRow('wideLayout', 'checkbox') +
createPageOptionRow('smallText', 'checkbox') +
createPageOptionRow('showToc', 'checkbox') +
createPageOptionRow('protectEditing', 'checkbox') +
createPageOptionRow('hideTitleHeader', 'checkbox') +
'</div>' +
'<div class="wolai-page-settings-section" data-page-settings-panel="custom" hidden>' +
createPageFontRow() +
createPageOptionRow('layoutDensity', 'select') +
createPageOptionRow('collapseBacklinks', 'checkbox') +
createPageOptionRow('hideChildPages', 'checkbox') +
createPageOptionRow('showBlockRefCount', 'checkbox') +
'</div>' +
'<div class="wolai-page-settings-section" data-page-settings-panel="global" hidden>' +
createGlobalHeadingNumbersRow() +
createPageWidthRows() +
'<div class="wolai-page-settings-section mnote-extensions-panel" data-page-settings-panel="extensions" hidden>' +
'<div class="mnote-extensions-subtabs" role="tablist" aria-label="扩展子面板">' +
'<button type="button" class="mnote-extensions-subtab is-active" role="tab" aria-selected="true" data-extensions-subtab="knowledge">知识库</button>' +
'<button type="button" class="mnote-extensions-subtab" role="tab" aria-selected="false" data-extensions-subtab="skills">Skills</button>' +
'<button type="button" class="mnote-extensions-subtab" role="tab" aria-selected="false" data-extensions-subtab="mcp">MCP</button>' +
'<button type="button" class="mnote-extensions-subtab" role="tab" aria-selected="false" data-extensions-subtab="tools">Agent 工具</button>' +
'</div>' +
'<div class="mnote-extensions-subpanel" data-extensions-panel="knowledge">' +
'<div class="mnote-extensions-status" data-extensions-status="knowledge"><span class="mnote-extensions-loading">加载中...</span></div>' +
'</div>' +
'<div class="mnote-extensions-subpanel" data-extensions-panel="skills" hidden>' +
'<div class="mnote-extensions-status" data-extensions-status="skills"><span class="mnote-extensions-loading">加载中...</span></div>' +
'</div>' +
'<div class="mnote-extensions-subpanel" data-extensions-panel="mcp" hidden>' +
'<div class="mnote-extensions-status" data-extensions-status="mcp"><span class="mnote-extensions-loading">加载中...</span></div>' +
'</div>' +
'<div class="mnote-extensions-subpanel" data-extensions-panel="tools" hidden>' +
'<div class="mnote-extensions-status" data-extensions-status="tools"><span class="mnote-extensions-loading">加载中...</span></div>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="wolai-page-settings-section mnote-dashboard-panel" data-page-settings-panel="dashboard" hidden>' +
'<div class="mnote-dashboard-grid">' +
'<div class="mnote-dashboard-card" data-dashboard-card="knowledge">' +
'<div class="mnote-dashboard-card-title">知识库</div>' +
'<div class="mnote-dashboard-card-value" data-dashboard-value="knowledge">--</div>' +
'<div class="mnote-dashboard-card-desc">已索引文件 / 状态</div>' +
'</div>' +
'<div class="mnote-dashboard-card" data-dashboard-card="skills">' +
'<div class="mnote-dashboard-card-title">Skills</div>' +
'<div class="mnote-dashboard-card-value" data-dashboard-value="skills">--</div>' +
'<div class="mnote-dashboard-card-desc">已安装 / 可用</div>' +
'</div>' +
'<div class="mnote-dashboard-card" data-dashboard-card="mcp">' +
'<div class="mnote-dashboard-card-title">MCP</div>' +
'<div class="mnote-dashboard-card-value" data-dashboard-value="mcp">--</div>' +
'<div class="mnote-dashboard-card-desc">服务器 / 已连接</div>' +
'</div>' +
'<div class="mnote-dashboard-card" data-dashboard-card="agent">' +
'<div class="mnote-dashboard-card-title">Agent Runs</div>' +
'<div class="mnote-dashboard-card-value" data-dashboard-value="agent">--</div>' +
'<div class="mnote-dashboard-card-desc">最近 7 天运行次数</div>' +
'</div>' +
'</div>' +
'<button type="button" class="wolai-page-settings-index-add" style="margin-top:10px" data-dashboard-action="refresh">刷新仪表盘</button>' +
'</div>' +
'<div class="wolai-page-settings-actions">' +
'<button type="button" class="wolai-page-settings-action" data-page-settings-action="history">页面历史...</button>' +
'<button type="button" class="wolai-page-settings-action" data-page-settings-action="share">公开分享页面...</button>' +
'<button type="button" class="wolai-page-settings-action is-disabled" data-page-settings-action="move" disabled>移动到...</button>' +
'<button type="button" class="wolai-page-settings-action is-disabled" data-page-settings-action="embed" disabled>嵌入到...</button>' +
'<button type="button" class="wolai-page-settings-action is-danger is-disabled" data-page-settings-action="delete" disabled>删除页面</button>' +
'</div>' +
'<div class="wolai-page-settings-stats" data-testid="wolai-page-settings-stats"></div>' +
'</div>';
document.body.appendChild(popover);
return popover;
}
function createLocalIndexSettingsPanelHtml() {
return '' +
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-local-index-settings-panel" role="dialog" aria-modal="false" aria-label="索引设置">' +
'<div class="mnote-settings-panel-head">' +
'<strong>索引设置</strong>' +
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭索引设置">×</button>' +
'</div>' +
'<div class="wolai-page-settings-index-panel" data-testid="wolai-page-settings-local-index-panel">' +
'<div class="wolai-page-settings-index-status" data-testid="wolai-page-settings-local-index-status"></div>' +
'<section class="wolai-page-settings-index-group">' +
'<div class="wolai-page-settings-index-title">索引范围</div>' +
'<div class="wolai-page-settings-index-range-list" data-testid="wolai-page-settings-local-index-ranges"></div>' +
'<button type="button" class="wolai-page-settings-index-add" data-local-index-action="add-path">新增范围</button>' +
'<div class="wolai-page-settings-index-schedule">' +
'<label><span>索引时间</span><select data-testid="wolai-page-settings-local-index-schedule-mode"><option value="daily">每日</option><option value="once">指定日期</option><option value="manual">手动</option></select></label>' +
'<label><span>时间</span><input type="time" data-testid="wolai-page-settings-local-index-schedule-time" value="02:00"></label>' +
'<label><span>日期</span><input type="date" data-testid="wolai-page-settings-local-index-schedule-date"></label>' +
'<label class="wolai-page-settings-index-inline"><input type="checkbox" data-testid="wolai-page-settings-local-index-run-on-change"><span>文档变化时立即索引</span></label>' +
'</div>' +
'<div class="wolai-page-settings-index-actions">' +
'<button type="button" data-local-index-action="save">保存设置</button>' +
'<button type="button" data-local-index-action="refresh">重建索引</button>' +
'</div>' +
'</section>' +
'</div>' +
'</div>';
}
function ensureLocalIndexSettingsPopover() {
var existing = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
if (existing instanceof HTMLElement) return existing;
var popover = document.createElement('div');
popover.className = 'wolai-page-settings-popover mnote-local-index-settings-popover';
popover.setAttribute('data-testid', 'mnote-local-index-settings-popover');
popover.setAttribute('data-mnote-surface', 'local-index-settings');
popover.hidden = true;
popover.innerHTML = createLocalIndexSettingsPanelHtml();
document.body.appendChild(popover);
return popover;
}
function createKnowledgeRagSettingsPanelHtml() {
return createKnowledgeRagSettingsPanelHtmlV2();
}
function createKnowledgeRagSettingsPanelHtmlV2() {
return '' +
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-knowledge-rag-settings-panel" role="dialog" aria-modal="false" aria-label="资料库问答设置">' +
'<div class="mnote-settings-panel-head">' +
'<strong>资料库问答</strong>' +
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭资料库问答设置">×</button>' +
'</div>' +
'<div class="mnote-knowledge-rag-detail-tabs" role="tablist" aria-label="资料库设置子面板">' +
'<button type="button" class="mnote-knowledge-rag-detail-tab is-active" role="tab" aria-selected="true" data-kb-rag-tab="sources">资料源管理</button>' +
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="search">检索测试</button>' +
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="params">检索参数</button>' +
'</div>' +
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="sources">' +
'<div class="wolai-page-settings-index-status" data-testid="mnote-knowledge-rag-status"></div>' +
'<div class="mnote-knowledge-rag-meta" data-testid="mnote-knowledge-rag-meta"></div>' +
'<section class="wolai-page-settings-index-group mnote-knowledge-rag-control-group">' +
'<div class="wolai-page-settings-index-title">资料操作</div>' +
'<div class="mnote-knowledge-rag-source-inputs" data-testid="mnote-knowledge-rag-source-inputs">' +
knowledgeRagSourceInputRow('') +
'</div>' +
'<div class="mnote-knowledge-rag-source-tools">' +
'<button type="button" class="wolai-page-settings-index-add" data-knowledge-rag-action="add-source">新增来源</button>' +
'<button type="button" class="wolai-page-settings-index-add" data-knowledge-rag-action="use-filetree-selection">使用选中文件树项</button>' +
'</div>' +
'<div class="wolai-page-settings-index-actions">' +
'<button type="button" data-knowledge-rag-action="refresh">刷新状态</button>' +
'<button type="button" data-knowledge-rag-action="ingest">索引/重建来源</button>' +
'<button type="button" data-knowledge-rag-action="prune">清除失效记录</button>' +
'<button type="button" data-knowledge-rag-action="open-dashboard">打开 LightRAG</button>' +
'</div>' +
'</section>' +
'<section class="wolai-page-settings-index-group">' +
'<div class="wolai-page-settings-index-title">资料来源</div>' +
'<div class="mnote-knowledge-rag-source-filters" data-testid="mnote-knowledge-rag-source-filters"></div>' +
'<div class="mnote-knowledge-rag-source-list" data-testid="mnote-knowledge-rag-sources"></div>' +
'</section>' +
'</div>' +
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="search" hidden>' +
'<div class="mnote-kb-search-form">' +
'<input type="text" class="mnote-kb-search-input" data-kb-rag-search-input="true" placeholder="输入检索关键词...">' +
'<button type="button" class="wolai-page-settings-index-add" data-kb-rag-action="search">搜索</button>' +
'</div>' +
'<div class="mnote-kb-search-results" data-kb-rag-search-results="true">' +
'<span class="mnote-extensions-loading">输入关键词后点击搜索</span>' +
'</div>' +
'</div>' +
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="params" hidden>' +
'<div class="mnote-kb-params-form">' +
'<div class="mnote-kb-params-row">' +
'<label class="mnote-kb-params-label">检索模式</label>' +
'<select class="mnote-kb-params-select" data-kb-rag-param="mode">' +
'<option value="hybrid" selected>混合 (Hybrid)</option>' +
'<option value="local">本地 (Local)</option>' +
'<option value="global">全局 (Global)</option>' +
'<option value="naive">朴素 (Naive)</option>' +
'<option value="mix">混合模式 (Mix)</option>' +
'</select>' +
'</div>' +
'<div class="mnote-kb-params-row">' +
'<label class="mnote-kb-params-label">返回数量 (Top-K)</label>' +
'<input type="number" class="mnote-kb-params-number" data-kb-rag-param="top_k" value="10" min="1" max="100">' +
'</div>' +
'<div class="mnote-kb-params-row">' +
'<label class="mnote-kb-params-label">相似度阈值</label>' +
'<input type="range" class="mnote-kb-params-range" data-kb-rag-param="similarity_threshold" value="0" min="0" max="1" step="0.05">' +
'<span class="mnote-kb-params-range-value" data-kb-rag-param-value="similarity_threshold">0.0</span>' +
'</div>' +
'<button type="button" class="wolai-page-settings-index-add" data-kb-rag-action="save-params">保存参数</button>' +
'</div>' +
'</div>' +
'</div>';
}
function ensureKnowledgeRagSettingsPopover() {
var existing = document.querySelector('[data-testid="mnote-knowledge-rag-settings-popover"]');
if (existing instanceof HTMLElement) return existing;
var popover = document.createElement('div');
popover.className = 'wolai-page-settings-popover mnote-knowledge-rag-settings-popover';
popover.setAttribute('data-testid', 'mnote-knowledge-rag-settings-popover');
popover.setAttribute('data-mnote-surface', 'knowledge-rag-settings');
popover.hidden = true;
popover.innerHTML = createKnowledgeRagSettingsPanelHtml();
document.body.appendChild(popover);
return popover;
}
function pageSettingsLocalIndexScopeKey() {
return [
currentSourceKind(),
currentRootUri(),
resolveWorkspaceId(document.body),
currentDocumentId()
].join('|');
}
function pageSettingsLocalIndexIsAvailable() {
return currentSourceKind() === 'local_folder' && Boolean(currentRootUri());
}
function pageSettingsLocalIndexEmpty(message) {
return '<div class="wolai-page-settings-index-empty">' + escapeHtml(message) + '</div>';
}
function renderPageSettingsLocalIndex(popover) {
popover = popover || ensureLocalIndexSettingsPopover();
var statusNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-status"]');
var rangesNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-ranges"]');
var scheduleModeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-mode"]');
var scheduleTimeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-time"]');
var scheduleDateNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-date"]');
var runOnChangeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-run-on-change"]');
if (!(statusNode instanceof HTMLElement)) return;
if (!pageSettingsLocalIndexIsAvailable()) {
statusNode.textContent = '本地索引仅在本地工作区可用';
renderLocalIndexRangeRows(popover, [], 'fault', true, {});
setLocalIndexScheduleControlsDisabled(true);
return;
}
var summary = pageUiState.localIndexSummary || {};
if (summary.loading) {
statusNode.textContent = '正在读取本地索引...';
renderLocalIndexRangeRows(popover, currentLocalIndexRangeValues(popover), 'indexing', true, summary.status || {});
setLocalIndexScheduleControlsDisabled(true);
return;
}
if (summary.error) {
statusNode.textContent = '本地索引读取失败';
renderLocalIndexRangeRows(popover, currentLocalIndexRangeValues(popover), 'fault', false, summary.status || {});
setLocalIndexScheduleControlsDisabled(false);
return;
}
if (summary.scopeKey !== pageSettingsLocalIndexScopeKey()) {
statusNode.textContent = '打开索引设置后读取本地索引';
renderLocalIndexRangeRows(popover, [], 'indexing', true, {});
setLocalIndexScheduleControlsDisabled(true);
return;
}
var status = summary.status || {};
var settings = status.settings && typeof status.settings === 'object' ? status.settings : {};
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);
}
if (scheduleModeNode instanceof HTMLSelectElement && document.activeElement !== scheduleModeNode) {
scheduleModeNode.value = String(settings.scheduleMode || 'daily');
}
if (scheduleTimeNode instanceof HTMLInputElement && document.activeElement !== scheduleTimeNode) {
scheduleTimeNode.value = String(settings.scheduleTime || '02:00');
}
if (scheduleDateNode instanceof HTMLInputElement && document.activeElement !== scheduleDateNode) {
scheduleDateNode.value = String(settings.scheduleDate || '');
}
if (runOnChangeNode instanceof HTMLInputElement) {
runOnChangeNode.checked = settings.runOnChange === true;
}
setLocalIndexScheduleControlsDisabled(false);
}
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';
}
function localIndexStatusLabel(kind) {
if (kind === 'indexed') return '已索引';
if (kind === 'indexing') return '索引中';
return '索引故障';
}
function localIndexPathStatusKind(path, status, fallbackKind) {
var indexedPaths = Array.isArray(status && status.indexedPaths) ? status.indexedPaths : [];
var normalizedPath = String(path || '').trim() || '.';
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';
}
function renderLocalIndexRangeRows(popover, includePaths, fallbackKind, disabled, status) {
var rangesNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-ranges"]');
if (!(rangesNode instanceof HTMLElement)) return;
var rawPaths = Array.isArray(includePaths) ? includePaths.map(function(value) {
return String(value || '').trim();
}) : [];
var paths = disabled ? rawPaths.filter(Boolean) : rawPaths;
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" 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('');
}
function setLocalIndexScheduleControlsDisabled(disabled) {
var popover = ensureLocalIndexSettingsPopover();
[
'[data-testid="wolai-page-settings-local-index-schedule-mode"]',
'[data-testid="wolai-page-settings-local-index-schedule-time"]',
'[data-testid="wolai-page-settings-local-index-schedule-date"]',
'[data-testid="wolai-page-settings-local-index-run-on-change"]'
].forEach(function(selector) {
var node = popover.querySelector(selector);
if (node instanceof HTMLInputElement || node instanceof HTMLSelectElement) node.disabled = disabled;
});
}
async function loadPageSettingsLocalIndex(force) {
if (!pageSettingsLocalIndexIsAvailable()) {
renderPageSettingsLocalIndex();
return;
}
var scopeKey = pageSettingsLocalIndexScopeKey();
var current = pageUiState.localIndexSummary || {};
if (!force && current.scopeKey === scopeKey && !current.error && !current.loading) {
renderPageSettingsLocalIndex();
return;
}
pageUiState.localIndexSummary = {
scopeKey: scopeKey,
loading: true,
error: ''
};
renderPageSettingsLocalIndex();
try {
var baseParams = new URLSearchParams();
baseParams.set('workspaceId', resolveWorkspaceId(document.body));
baseParams.set('rootUri', currentRootUri());
var statusUrl = '/api/search/local-index/status?' + baseParams.toString();
var response = await fetch(statusUrl, { headers: { accept: 'application/json' } });
var statusPayload = await response.json().catch(function(){ return null; });
if (!response.ok || !statusPayload || statusPayload.ok !== true) {
throw new Error('status_' + response.status);
}
pageUiState.localIndexSummary = {
scopeKey: scopeKey,
loading: false,
error: '',
status: statusPayload.result || {}
};
} catch (error) {
pageUiState.localIndexSummary = {
scopeKey: scopeKey,
loading: false,
error: error instanceof Error ? error.message : String(error),
status: {}
};
}
renderPageSettingsLocalIndex();
}
function localIndexIncludePathsFromForm() {
var popover = ensureLocalIndexSettingsPopover();
return currentLocalIndexRangeValues(popover).map(function(value) {
return value.trim();
}).filter(Boolean);
}
function currentLocalIndexRangeValues(popover) {
var root = popover || ensureLocalIndexSettingsPopover();
return Array.from(root.querySelectorAll('[data-local-index-range-input]')).map(function(input) {
if (!(input instanceof HTMLInputElement)) return '';
if (input.disabled && input.dataset.localIndexValue) return input.dataset.localIndexValue;
return input.value;
});
}
function localIndexSchedulePayloadFromForm() {
var popover = ensureLocalIndexSettingsPopover();
var modeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-mode"]');
var timeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-time"]');
var dateNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-date"]');
var runOnChangeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-run-on-change"]');
return {
scheduleMode: modeNode instanceof HTMLSelectElement ? modeNode.value : 'daily',
scheduleTime: timeNode instanceof HTMLInputElement ? (timeNode.value || '02:00') : '02:00',
scheduleDate: dateNode instanceof HTMLInputElement ? dateNode.value : '',
runOnChange: runOnChangeNode instanceof HTMLInputElement ? runOnChangeNode.checked : false
};
}
function addLocalIndexRange() {
var popover = ensureLocalIndexSettingsPopover();
var summary = pageUiState.localIndexSummary || {};
renderLocalIndexRangeRows(popover, currentLocalIndexRangeValues(popover).concat(['']), localIndexStatusKind(summary.status || {}), false, summary.status || {});
var inputs = Array.from(popover.querySelectorAll('[data-local-index-range-input]'));
var last = inputs[inputs.length - 1];
if (last instanceof HTMLInputElement) last.focus();
}
function removeLocalIndexRange(index) {
var popover = ensureLocalIndexSettingsPopover();
var summary = pageUiState.localIndexSummary || {};
var values = currentLocalIndexRangeValues(popover);
values.splice(Number(index || 0), 1);
renderLocalIndexRangeRows(popover, values, localIndexStatusKind(summary.status || {}), false, summary.status || {});
}
async function persistLocalIndexSettings() {
if (!pageSettingsLocalIndexIsAvailable()) return;
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
scopeKey: pageSettingsLocalIndexScopeKey(),
loading: true,
error: ''
});
renderPageSettingsLocalIndex();
try {
var response = await fetch('/api/search/local-index/settings', {
method: 'PUT',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify(Object.assign({
workspaceId: resolveWorkspaceId(document.body),
rootUri: currentRootUri(),
includePaths: localIndexIncludePathsFromForm()
}, localIndexSchedulePayloadFromForm()))
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'local_index_settings_' + response.status);
}
await loadPageSettingsLocalIndex(true);
} catch (error) {
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
scopeKey: pageSettingsLocalIndexScopeKey(),
loading: false,
error: error instanceof Error ? error.message : String(error)
});
renderPageSettingsLocalIndex();
}
}
function emitLocalIndexBackgroundTask(status, label, error) {
var now = Date.now();
window.dispatchEvent(new CustomEvent('mnote:local-background-task-updated', {
detail: {
task: {
taskKind: 'local_index',
taskId: 'local-index',
sourceRootRelativePath: '本地索引',
rootUri: currentRootUri(),
status: status,
stageLabel: label,
updatedAtMs: now,
finishedAtMs: status === 'running' ? null : now,
error: error || null
}
}
}));
}
async function refreshLocalIndex() {
if (!pageSettingsLocalIndexIsAvailable()) return;
emitLocalIndexBackgroundTask('running', '本地索引重建中', null);
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
scopeKey: pageSettingsLocalIndexScopeKey(),
loading: true,
error: ''
});
renderPageSettingsLocalIndex();
try {
var response = await fetch('/api/search/local-index/refresh', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
workspaceId: resolveWorkspaceId(document.body),
rootUri: currentRootUri()
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'local_index_refresh_' + response.status);
}
emitLocalIndexBackgroundTask('done', '本地索引已完成', null);
await loadPageSettingsLocalIndex(true);
} catch (error) {
emitLocalIndexBackgroundTask('failed', '本地索引失败', error instanceof Error ? error.message : String(error));
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
scopeKey: pageSettingsLocalIndexScopeKey(),
loading: false,
error: error instanceof Error ? error.message : String(error)
});
renderPageSettingsLocalIndex();
}
}
function knowledgeRagScopeKey() {
return [
currentSourceKind(),
currentRootUri(),
resolveWorkspaceId(document.body)
].join('|');
}
function knowledgeRagIsAvailable() {
return currentSourceKind() === 'local_folder' && Boolean(currentRootUri());
}
function knowledgeRagSourceInputRow(value) {
return '' +
'<div class="mnote-knowledge-rag-source-input-row" data-knowledge-rag-source-input-row="true">' +
'<input type="text" class="wolai-page-settings-index-path" data-knowledge-rag-source-input="true" value="' + escapeHtml(value || '') + '" placeholder="文件或目录,例如 docs/book.pdf / docs" spellcheck="false" />' +
'<span class="mnote-knowledge-rag-input-status" data-knowledge-rag-input-status-label="true" hidden></span>' +
'<button type="button" class="wolai-page-settings-index-remove" data-knowledge-rag-action="remove-source" aria-label="删除资料来源">×</button>' +
'</div>';
}
function knowledgeRagSourceInputValues() {
var popover = ensureKnowledgeRagSettingsPopover();
return Array.from(popover.querySelectorAll('[data-knowledge-rag-source-input]')).map(function(input) {
return input instanceof HTMLInputElement ? input.value.trim() : '';
}).filter(Boolean).filter(function(value, index, list) {
return list.indexOf(value) === index;
});
}
function knowledgeRagIndexedRootValues() {
var status = currentKnowledgeRagSummary().status || {};
var registry = status.registry && typeof status.registry === 'object' ? status.registry : {};
var roots = Array.isArray(registry.indexedRoots) ? registry.indexedRoots : [];
return roots.map(function(root) {
return String(root && (root.rootRelativePath || root.root_relative_path) || '').trim();
}).filter(Boolean).filter(function(value, index, list) {
return list.indexOf(value) === index;
});
}
function knowledgeRagCurrentFilter() {
return pageUiState.knowledgeRagSourceFilter || 'attention';
}
function setKnowledgeRagSourceFilter(filter) {
pageUiState.knowledgeRagSourceFilter = String(filter || 'attention').trim() || 'attention';
renderKnowledgeRagSettings();
}
function knowledgeRagRegistryEntryCategory(entry) {
var deleted = entry && entry.deletedAtMs;
var stale = entry && entry.stale;
var providerStatus = String(entry && entry.lightRagStatus || '').trim();
var failed = providerStatus === 'failed';
var deleting = providerStatus === 'delete_submitted';
var deletedDone = providerStatus === 'delete_completed';
var docId = String(entry && entry.lightRagDocId || '');
var indexed = entry && entry.indexedAtMs && docId && !deleted && !stale && !failed && !deleting && !deletedDone;
if (deletedDone) return 'removed';
if (failed || stale || deleted) return 'failed';
if (indexed) return 'indexed';
return 'indexing';
}
function knowledgeRagProviderDocuments(status) {
var documents = status && status.documents && typeof status.documents === 'object' ? status.documents : {};
return Array.isArray(documents.documents) ? documents.documents : [];
}
function knowledgeRagStatusHasInFlight(status) {
var pipeline = status && status.pipeline && typeof status.pipeline === 'object' ? status.pipeline : {};
if (pipeline.busy === true || pipeline.scanning === true || pipeline.destructiveBusy === true || pipeline.requestPending === true || pipeline.pendingRequests === true || Number(pipeline.pendingEnqueues || 0) > 0) return true;
var registry = status && status.registry && typeof status.registry === 'object' ? status.registry : {};
var entries = Array.isArray(registry.entries) ? registry.entries : [];
if (entries.some(function(entry) {
var providerStatus = String(entry && entry.lightRagStatus || '').trim();
return !entry?.deletedAtMs && !entry?.stale && !(entry?.indexedAtMs && entry?.lightRagDocId)
&& ['submitted', 'pending', 'scanning', 'parsing', 'parse', 'parsed', 'analyzing', 'analyze', 'processing', 'process', ''].includes(providerStatus);
})) return true;
var groups = status && status.documents && status.documents.rawStatusGroups && typeof status.documents.rawStatusGroups === 'object'
? status.documents.rawStatusGroups
: {};
return ['pending', 'scanning', 'parsing', 'parse', 'parsed', 'analyzing', 'analyze', 'processing', 'process'].some(function(key) {
return Number(groups[key] || 0) > 0;
});
}
function knowledgeRagPipelineHasInFlight(pipeline) {
pipeline = pipeline && typeof pipeline === 'object' ? pipeline : {};
return pipeline.busy === true
|| pipeline.scanning === true
|| pipeline.destructiveBusy === true
|| pipeline.requestPending === true
|| pipeline.pendingRequests === true
|| Number(pipeline.pendingEnqueues || 0) > 0;
}
function parseKnowledgeRagChunkProgress(message) {
var text = String(message || '');
var match = text.match(/Chunk\s+(\d+)\s+of\s+(\d+).*?\b(doc-[A-Za-z0-9_-]+?)(?=-(?:chunk|mm)-|\s|$)/i);
if (!match) return null;
var current = Number(match[1]);
var total = Number(match[2]);
if (!Number.isFinite(current) || !Number.isFinite(total) || current <= 0 || total <= 0) return null;
return {
current: Math.min(current, total),
total: total,
docId: String(match[3] || ''),
latestMessage: text
};
}
function normalizeKnowledgeRagPipelineEvent(payload) {
var input = payload && typeof payload === 'object' ? payload : {};
var latestMessage = String(input.latestMessage || input.latest_message || '');
var historyMessages = Array.isArray(input.historyMessages)
? input.historyMessages
: (Array.isArray(input.history_messages) ? input.history_messages : []);
var progress = input.progress && typeof input.progress === 'object'
? input.progress
: parseKnowledgeRagChunkProgress(latestMessage);
if (!progress) {
for (var index = historyMessages.length - 1; index >= 0; index -= 1) {
progress = parseKnowledgeRagChunkProgress(historyMessages[index]);
if (progress) break;
}
}
return {
ok: input.ok !== false,
busy: input.busy === true,
destructiveBusy: input.destructiveBusy === true || input.destructive_busy === true,
scanning: input.scanning === true,
scanningExclusive: input.scanningExclusive === true || input.scanning_exclusive === true,
requestPending: input.requestPending === true || input.request_pending === true,
pendingRequests: input.pendingRequests === true || input.pending_requests === true,
pendingEnqueues: Number(input.pendingEnqueues ?? input.pending_enqueues ?? 0) || 0,
docs: Number(input.docs || 0) || 0,
batches: Number(input.batches ?? input.batchs ?? 0) || 0,
currentBatch: Number(input.currentBatch ?? input.cur_batch ?? 0) || 0,
jobName: String(input.jobName || input.job_name || ''),
jobStart: input.jobStart || input.job_start || null,
latestMessage: latestMessage,
historyMessages: historyMessages.map(function(message) { return String(message || ''); }),
cancellationRequested: input.cancellationRequested === true || input.cancellation_requested === true,
cancellationReason: input.cancellationReason || input.cancellation_reason || null,
progress: progress || null
};
}
function knowledgeRagPipelineProgress(status) {
var pipeline = status && status.pipeline && typeof status.pipeline === 'object' ? status.pipeline : {};
var progress = pipeline.progress && typeof pipeline.progress === 'object' ? pipeline.progress : null;
if (!progress) return null;
var current = Number(progress.current || 0);
var total = Number(progress.total || 0);
if (!Number.isFinite(current) || !Number.isFinite(total) || current <= 0 || total <= 0) return null;
return {
current: Math.min(current, total),
total: total,
docId: String(progress.docId || ''),
latestMessage: String(pipeline.latestMessage || '')
};
}
function knowledgeRagSourceProgressHtml(progress) {
if (!progress) return '';
var current = Number(progress.current || 0);
var total = Number(progress.total || 0);
var determinate = Number.isFinite(current) && Number.isFinite(total) && current > 0 && total > 0;
var percent = determinate ? Math.max(0, Math.min(100, Math.round((current / total) * 100))) : 0;
var label = determinate ? current + ' / ' + total + ' · ' + percent + '%' : String(progress.label || '处理中');
var mode = determinate ? 'determinate' : 'indeterminate';
var ariaValues = determinate
? ' aria-valuemin="0" aria-valuemax="' + String(total) + '" aria-valuenow="' + String(current) + '"'
: ' aria-valuetext="' + escapeHtml(label) + '"';
return '' +
'<div class="mnote-knowledge-rag-source-progress" data-progress-mode="' + mode + '" role="progressbar"' + ariaValues + ' aria-label="' + escapeHtml('索引进度 ' + label) + '">' +
'<div><i' + (determinate ? ' style="width:' + String(percent) + '%"' : '') + '></i></div>' +
'<span>' + escapeHtml(label) + '</span>' +
'</div>';
}
function knowledgeRagDocumentIsInFlight(doc) {
var status = String(doc && (doc.statusGroup || doc.status) || '').toLowerCase();
return ['pending', 'scanning', 'parsing', 'parse', 'parsed', 'analyzing', 'analyze', 'processing', 'process'].includes(status);
}
function knowledgeRagProgressLabel(status, doc, fallback) {
var pipeline = status && status.pipeline && typeof status.pipeline === 'object' ? status.pipeline : {};
var latest = String(pipeline.latestMessage || '').trim();
if (latest) return '处理中';
var providerStatus = String(doc && (doc.statusGroup || doc.status) || fallback || '').toLowerCase();
if (providerStatus === 'pending') return '排队中';
if (providerStatus === 'scanning') return '扫描中';
if (providerStatus === 'parsing' || providerStatus === 'parse' || providerStatus === 'parsed') return '解析中';
if (providerStatus === 'analyzing' || providerStatus === 'analyze') return '分析中';
return '索引中';
}
function knowledgeRagIndeterminateProgress(status, doc, fallback) {
return { mode: 'indeterminate', label: knowledgeRagProgressLabel(status, doc, fallback) };
}
function stopKnowledgeRagPipelineWatcher() {
if (knowledgeRagPipelineEventSource && typeof knowledgeRagPipelineEventSource.close === 'function') {
knowledgeRagPipelineEventSource.close();
}
knowledgeRagPipelineEventSource = null;
knowledgeRagPipelineWatcherScopeKey = '';
}
function handleKnowledgeRagPipelineEvent(payload) {
var scopeKey = knowledgeRagScopeKey();
if (knowledgeRagPipelineWatcherScopeKey && knowledgeRagPipelineWatcherScopeKey !== scopeKey) return;
var current = currentKnowledgeRagSummary();
var currentStatus = current.status && typeof current.status === 'object' ? current.status : {};
var previousPipeline = currentStatus.pipeline && typeof currentStatus.pipeline === 'object' ? currentStatus.pipeline : {};
var previousInFlight = knowledgeRagPipelineHasInFlight(previousPipeline);
var pipeline = normalizeKnowledgeRagPipelineEvent(payload);
var nextStatus = Object.assign({}, currentStatus, { pipeline: pipeline });
pageUiState.knowledgeRagSummary = Object.assign({}, current, {
scopeKey: scopeKey,
loading: false,
error: '',
status: nextStatus
});
if (isKnowledgeRagSettingsOpen()) renderKnowledgeRagSettings();
var currentInFlight = knowledgeRagPipelineHasInFlight(pipeline);
if (currentInFlight) return;
var latest = String(pipeline.latestMessage || '').toLowerCase();
var finished = previousInFlight || latest.indexOf('completed') !== -1 || latest.indexOf('complete') !== -1;
if (!finished || knowledgeRagPipelineWatcherRefreshPending) return;
knowledgeRagPipelineWatcherRefreshPending = true;
void loadKnowledgeRagStatus(true, {
silentLoading: true,
notifyUpdated: true,
reason: 'pipeline-event-complete'
}).finally(function() {
knowledgeRagPipelineWatcherRefreshPending = false;
if (!knowledgeRagStatusHasInFlight(currentKnowledgeRagSummary().status || {})) {
stopKnowledgeRagPipelineWatcher();
}
});
}
function startKnowledgeRagPipelineWatcher(reason) {
document.documentElement.setAttribute('data-mnote-knowledge-rag-watcher-required', String(reason || 'source-updated'));
if (!knowledgeRagIsAvailable() || typeof window.EventSource !== 'function') return;
var scopeKey = knowledgeRagScopeKey();
if (knowledgeRagPipelineEventSource && knowledgeRagPipelineWatcherScopeKey === scopeKey) return;
stopKnowledgeRagPipelineWatcher();
var url = new URL('/api/knowledge-rag/pipeline-events', window.location.origin);
url.searchParams.set('workspaceId', resolveWorkspaceId(document.body));
url.searchParams.set('rootUri', currentRootUri());
var source = new EventSource(url.toString());
knowledgeRagPipelineEventSource = source;
knowledgeRagPipelineWatcherScopeKey = scopeKey;
source.addEventListener('pipeline_status', function(event) {
var payload = null;
try {
payload = JSON.parse(event.data || '{}');
} catch (_) {
return;
}
handleKnowledgeRagPipelineEvent(payload);
});
source.onerror = function() {
document.documentElement.setAttribute('data-mnote-knowledge-rag-watcher-error', 'true');
};
}
function scheduleKnowledgeRagStatusBridge(reason) {
startKnowledgeRagPipelineWatcher(reason);
}
function knowledgeRagUnmappedProviderRows(status, entries) {
var docs = knowledgeRagProviderDocuments(status);
if (!docs.length) return [];
var mappedDocIds = new Set();
var mappedFilePaths = new Set();
(Array.isArray(entries) ? entries : []).forEach(function(entry) {
var docId = String(entry && entry.lightRagDocId || '').trim();
var filePath = String(entry && entry.lightRagFilePath || '').trim();
if (docId) mappedDocIds.add(docId);
if (filePath) mappedFilePaths.add(filePath);
});
return docs.filter(function(doc) {
var docId = String(doc && doc.id || '').trim();
var filePath = String(doc && doc.filePath || '').trim();
return !(docId && mappedDocIds.has(docId)) && !(filePath && mappedFilePaths.has(filePath));
}).map(function(doc) {
return {
kind: 'provider',
category: String(doc.status || doc.statusGroup || '').toLowerCase().includes('fail') ? 'unmapped_failed' : 'unmapped',
doc: doc
};
});
}
function knowledgeRagVisibleForFilter(row, filter) {
if (filter === 'all') return true;
if (filter === 'attention') return row.category !== 'indexed';
if (filter === 'unmapped') return row.category === 'unmapped' || row.category === 'unmapped_failed';
if (filter === 'failed') return row.category === 'failed' || row.category === 'unmapped_failed';
return row.category === filter;
}
function renderKnowledgeRagSourceFilters(popover, rows) {
var filtersNode = popover.querySelector('[data-testid="mnote-knowledge-rag-source-filters"]');
if (!(filtersNode instanceof HTMLElement)) return;
var counts = rows.reduce(function(acc, row) {
acc.all += 1;
if (row.category !== 'indexed') acc.attention += 1;
if (row.category === 'indexing') acc.indexing += 1;
if (row.category === 'failed' || row.category === 'unmapped_failed') acc.failed += 1;
if (row.category === 'indexed') acc.indexed += 1;
if (row.category === 'removed') acc.removed += 1;
if (row.category === 'unmapped' || row.category === 'unmapped_failed') acc.unmapped += 1;
return acc;
}, { all: 0, attention: 0, indexing: 0, failed: 0, indexed: 0, removed: 0, unmapped: 0 });
var current = knowledgeRagCurrentFilter();
var options = [
['attention', '需处理'],
['indexing', '索引中'],
['failed', '失败/过期'],
['indexed', '已索引'],
['removed', '已移除'],
['unmapped', 'LightRAG 未映射'],
['all', '全部']
];
filtersNode.innerHTML = options.map(function(item) {
var key = item[0];
var label = item[1];
return '<button type="button" data-knowledge-rag-action="filter-sources" data-knowledge-rag-filter="' + key + '" data-active="' + (current === key ? 'true' : 'false') + '">' + escapeHtml(label) + ' <span>' + String(counts[key] || 0) + '</span></button>';
}).join('');
}
function renderKnowledgeRagSources(popover, entries, status) {
var sourcesNode = popover.querySelector('[data-testid="mnote-knowledge-rag-sources"]');
if (!(sourcesNode instanceof HTMLElement)) return;
var registryRows = (Array.isArray(entries) ? entries : []).map(function(entry) {
return { kind: 'registry', category: knowledgeRagRegistryEntryCategory(entry), entry: entry };
});
var rows = registryRows.concat(knowledgeRagUnmappedProviderRows(status || {}, entries));
renderKnowledgeRagSourceFilters(popover, rows);
var filter = knowledgeRagCurrentFilter();
var items = rows.filter(function(row) {
return knowledgeRagVisibleForFilter(row, filter);
});
if (!rows.length) {
sourcesNode.innerHTML = '<div class="wolai-page-settings-index-empty">还没有登记到 LightRAG 的资料来源</div>';
return;
}
if (!items.length) {
sourcesNode.innerHTML = '<div class="wolai-page-settings-index-empty">当前分类没有资料来源</div>';
return;
}
var pipelineProgress = knowledgeRagPipelineProgress(status || {});
var providerDocs = knowledgeRagProviderDocuments(status || {});
var providerDocsById = new Map();
var providerDocsByPath = new Map();
providerDocs.forEach(function(doc) {
var docId = String(doc && doc.id || '').trim();
var filePath = String(doc && doc.filePath || '').trim();
if (docId) providerDocsById.set(docId, doc);
if (filePath) providerDocsByPath.set(filePath, doc);
});
sourcesNode.innerHTML = items.map(function(row) {
if (row.kind === 'provider') {
var doc = row.doc || {};
var providerPath = String(doc.filePath || doc.id || '');
var docStatus = String(doc.status || doc.statusGroup || '');
var failedDoc = row.category === 'unmapped_failed';
var providerProgress = pipelineProgress && pipelineProgress.docId && pipelineProgress.docId === String(doc.id || '') ? pipelineProgress : null;
if (!providerProgress && !failedDoc && knowledgeRagDocumentIsInFlight(doc)) {
providerProgress = knowledgeRagIndeterminateProgress(status || {}, doc, docStatus);
}
return '' +
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-source-kind="provider" data-knowledge-rag-source-path="' + escapeHtml(providerPath) + '">' +
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + (failedDoc ? 'fault' : 'indexed') + '" title="' + escapeHtml(docStatus || 'LightRAG 文档') + '"></span>' +
'<div class="mnote-knowledge-rag-source-main">' +
'<strong title="' + escapeHtml(providerPath) + '">' + escapeHtml(providerPath || 'LightRAG 文档') + '</strong>' +
'<span>' + escapeHtml('LightRAG 未映射 · ' + (docStatus || 'unknown') + (doc.id ? ' · ' + doc.id : '')) + '</span>' +
knowledgeRagSourceProgressHtml(providerProgress) +
'</div>' +
'<div class="mnote-knowledge-rag-source-actions"><button type="button" disabled>只读</button></div>' +
'</div>';
}
var entry = row.entry || {};
var deleted = entry && entry.deletedAtMs;
var stale = entry && entry.stale;
var providerStatus = String(entry && entry.lightRagStatus || '').trim();
var failed = providerStatus === 'failed';
var deleting = providerStatus === 'delete_submitted';
var deletedDone = providerStatus === 'delete_completed';
var docId = String(entry && entry.lightRagDocId || '');
var indexed = entry && entry.indexedAtMs && docId && !deleted && !stale && !failed && !deleting && !deletedDone;
var kind = deletedDone ? '' : (deleting ? 'indexing' : (failed || stale || deleted ? 'fault' : (indexed ? 'indexed' : 'indexing')));
var title = String(entry && entry.sourceRootRelativePath || entry && entry.lightRagFilePath || '');
var statusLabel = deletedDone ? 'LightRAG 已移除'
: deleting ? '删除已提交'
: deleted ? '已删除'
: stale ? '已过期'
: failed ? '索引失败'
: indexed ? '已索引'
: (docId || providerStatus ? '正在索引' : '待提交');
var meta = docId ? statusLabel + ' · ' + docId : statusLabel;
var sourcePath = String(entry && entry.sourceRootRelativePath || '');
var canReindex = Boolean(sourcePath && !deleting);
var canDelete = Boolean(sourcePath && !deleted && !deletedDone && !deleting);
var entryProgress = pipelineProgress && pipelineProgress.docId && pipelineProgress.docId === docId && !indexed && !failed && !stale && !deleted && !deletedDone ? pipelineProgress : null;
var providerDoc = (docId && providerDocsById.get(docId)) || providerDocsByPath.get(String(entry && entry.lightRagFilePath || '').trim()) || null;
if (!entryProgress && !indexed && !failed && !stale && !deleted && !deletedDone && !deleting && (knowledgeRagDocumentIsInFlight(providerDoc) || docId || providerStatus)) {
entryProgress = knowledgeRagIndeterminateProgress(status || {}, providerDoc, providerStatus);
}
return '' +
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '">' +
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + kind + '" title="' + escapeHtml(statusLabel) + '"></span>' +
'<div class="mnote-knowledge-rag-source-main">' +
'<strong title="' + escapeHtml(title) + '">' + escapeHtml(title || '未命名来源') + '</strong>' +
'<span>' + escapeHtml(meta) + '</span>' +
knowledgeRagSourceProgressHtml(entryProgress) +
'</div>' +
'<div class="mnote-knowledge-rag-source-actions">' +
'<button type="button" data-knowledge-rag-action="reindex-source" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '"' + (canReindex ? '' : ' disabled') + '>重索引</button>' +
'<button type="button" data-knowledge-rag-action="delete-source" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '"' + (canDelete ? '' : ' disabled') + '>删除索引</button>' +
'</div>' +
'</div>';
}).join('');
}
function renderKnowledgeRagSettings(popover) {
popover = popover || ensureKnowledgeRagSettingsPopover();
var statusNode = popover.querySelector('[data-testid="mnote-knowledge-rag-status"]');
var metaNode = popover.querySelector('[data-testid="mnote-knowledge-rag-meta"]');
var dashboardButton = popover.querySelector('[data-knowledge-rag-action="open-dashboard"]');
if (!(statusNode instanceof HTMLElement)) return;
if (!knowledgeRagIsAvailable()) {
statusNode.textContent = '资料库问答仅在本地工作区可用';
if (metaNode instanceof HTMLElement) metaNode.innerHTML = '';
renderKnowledgeRagSourceFilters(popover, []);
renderKnowledgeRagSources(popover, [], {});
if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = true;
return;
}
var summary = currentKnowledgeRagSummary();
if (summary.loading) {
statusNode.textContent = '正在读取 LightRAG 状态...';
if (metaNode instanceof HTMLElement) metaNode.innerHTML = '';
renderKnowledgeRagSourceFilters(popover, []);
renderKnowledgeRagSources(popover, [], {});
if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = true;
return;
}
if (summary.error) {
statusNode.textContent = 'LightRAG 状态读取失败:' + summary.error;
if (metaNode instanceof HTMLElement) metaNode.innerHTML = '';
renderKnowledgeRagSourceFilters(popover, []);
renderKnowledgeRagSources(popover, [], {});
if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = true;
return;
}
var status = summary.status || {};
var health = status.health && typeof status.health === 'object' ? status.health : {};
var healthBody = health.health && typeof health.health === 'object' ? health.health : {};
var healthy = health.ok === true && String(healthBody.status || '') === 'healthy';
var registry = status.registry && typeof status.registry === 'object' ? status.registry : {};
var entries = Array.isArray(registry.entries) ? registry.entries : [];
var providerDocs = knowledgeRagProviderDocuments(status);
statusNode.textContent = healthy
? 'LightRAG healthyMNote 登记 ' + entries.length + ' 个来源,LightRAG 文档 ' + providerDocs.length + ' 个'
: 'LightRAG 未就绪';
if (metaNode instanceof HTMLElement) {
var dashboardUrl = publicKnowledgeRagDashboardUrl(status);
var inputDir = String(status.inputDir || '');
var workingDir = String(healthBody.working_directory || '');
var config = healthBody.configuration && typeof healthBody.configuration === 'object' ? healthBody.configuration : {};
var llmModel = String(config.llm_model || config.llmModel || '');
var vlmModel = String(config.vlm_model || config.vlmModel || '');
var rerank = status.rerank && typeof status.rerank === 'object' ? status.rerank : {};
var rerankStatus = String(rerank.status || 'unknown');
var rerankBinding = searchText(rerank.binding || '');
var rerankModel = searchText(rerank.model || '');
var rerankLabel = rerankStatus === 'disabled'
? 'disabled'
: rerankStatus === 'available'
? 'available'
: rerankStatus === 'unavailable'
? 'unavailable'
: 'unknown';
metaNode.innerHTML = '' +
'<div><span>Dashboard</span><code>' + escapeHtml(dashboardUrl || '未配置') + '</code></div>' +
'<div><span>Model</span><code>' + escapeHtml(llmModel || '未上报') + (vlmModel ? ' / VLM ' + escapeHtml(vlmModel) : '') + '</code></div>' +
'<div><span>Rerank</span><code>' + escapeHtml(rerankLabel + (rerankBinding ? ' · ' + rerankBinding : '') + (rerankModel ? ' · ' + rerankModel : '')) + '</code></div>' +
'<div><span>Input</span><code>' + escapeHtml(inputDir || '未配置') + '</code></div>' +
'<div><span>Storage</span><code>' + escapeHtml(workingDir || '未上报') + '</code></div>';
}
renderKnowledgeRagSources(popover, entries, status);
if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = !publicKnowledgeRagDashboardUrl(status);
}
function publicKnowledgeRagDashboardUrl(status) {
var url = String(status && (status.dashboardUrl || status.endpoint) || '').trim();
if (!url) return '';
try {
var parsed = new URL(url, window.location.href);
if (['127.0.0.1', 'localhost', '::1'].includes(parsed.hostname) && window.location.hostname && !['127.0.0.1', 'localhost', '::1'].includes(window.location.hostname)) {
parsed.hostname = window.location.hostname;
return parsed.toString();
}
} catch (_) {}
return url;
}
async function loadKnowledgeRagStatus(force, options) {
options = options || {};
if (!knowledgeRagIsAvailable()) {
renderKnowledgeRagSettings();
return;
}
var scopeKey = knowledgeRagScopeKey();
var current = currentKnowledgeRagSummary();
var previousStatus = current.status || {};
var previousDigest = knowledgeRagRegistryStatusDigest(previousStatus);
if (!force && current.scopeKey === scopeKey && !current.error && !current.loading) {
renderKnowledgeRagSettings();
return;
}
if (!options.silentLoading) {
pageUiState.knowledgeRagSummary = {
scopeKey: scopeKey,
loading: true,
error: ''
};
renderKnowledgeRagSettings();
}
try {
var params = new URLSearchParams();
params.set('workspaceId', resolveWorkspaceId(document.body));
params.set('rootUri', currentRootUri());
var response = await fetch('/api/knowledge-rag/status?' + params.toString(), {
headers: { accept: 'application/json' }
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_status_' + response.status);
}
var nextDigest = knowledgeRagRegistryStatusDigest(payload);
pageUiState.knowledgeRagSummary = {
scopeKey: scopeKey,
loading: false,
error: '',
status: payload
};
if (knowledgeRagStatusHasInFlight(payload)) {
startKnowledgeRagPipelineWatcher(options.reason || 'status-in-flight');
} else if (!knowledgeRagPipelineWatcherRefreshPending) {
stopKnowledgeRagPipelineWatcher();
}
if (options.notifyUpdated || previousDigest !== nextDigest) {
notifyKnowledgeRagSourceUpdated({ action: 'status-sync', reason: options.reason || 'registry-status-changed', status: payload });
}
} catch (error) {
pageUiState.knowledgeRagSummary = {
scopeKey: scopeKey,
loading: false,
error: error instanceof Error ? error.message : String(error),
status: {}
};
}
renderKnowledgeRagSettings();
}
function addKnowledgeRagSourceInput() {
var popover = ensureKnowledgeRagSettingsPopover();
var node = popover.querySelector('[data-testid="mnote-knowledge-rag-source-inputs"]');
if (!(node instanceof HTMLElement)) return;
node.insertAdjacentHTML('beforeend', knowledgeRagSourceInputRow(''));
var inputs = Array.from(node.querySelectorAll('[data-knowledge-rag-source-input]'));
var last = inputs[inputs.length - 1];
if (last instanceof HTMLInputElement) last.focus();
}
function knowledgeRagFileTreeSelectionPaths() {
var rows = Array.from(document.querySelectorAll(
'#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-selected="true"][data-local-relative-path]'
));
if (!rows.length) {
rows = Array.from(document.querySelectorAll(
'#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-active="true"][data-local-relative-path]'
));
}
return rows.map(function(row) {
return String(row.getAttribute('data-local-relative-path') || '').trim();
}).filter(Boolean).filter(function(value, index, list) {
return list.indexOf(value) === index;
});
}
function useKnowledgeRagFileTreeSelection() {
var selectedPaths = knowledgeRagFileTreeSelectionPaths();
if (!selectedPaths.length) {
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: '请先在文件树中选中要索引的文件或目录'
});
renderKnowledgeRagSettings();
return;
}
var popover = ensureKnowledgeRagSettingsPopover();
var node = popover.querySelector('[data-testid="mnote-knowledge-rag-source-inputs"]');
if (!(node instanceof HTMLElement)) return;
var existing = knowledgeRagSourceInputValues();
var next = existing.concat(selectedPaths).filter(function(value, index, list) {
return list.indexOf(value) === index;
});
node.innerHTML = next.map(knowledgeRagSourceInputRow).join('');
}
function notifyKnowledgeRagSourceUpdated(detail) {
window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-source-updated', {
detail: Object.assign({
rootUri: currentRootUri(),
workspaceId: resolveWorkspaceId(document.body)
}, detail || {})
}));
}
function knowledgeRagRegistryStatusDigest(status) {
var registry = status && status.registry && typeof status.registry === 'object' ? status.registry : {};
var entries = Array.isArray(registry.entries) ? registry.entries : [];
return entries.map(function(entry) {
return [
String(entry && entry.sourceRootRelativePath || ''),
String(entry && entry.lightRagDocId || ''),
String(entry && entry.lightRagStatus || ''),
String(entry && entry.indexedAtMs || ''),
String(entry && entry.deletedAtMs || ''),
entry && entry.stale ? 'stale' : 'fresh'
].join('|');
}).sort().join('\n');
}
function removeKnowledgeRagSourceInput(button) {
var row = button && button.closest ? button.closest('[data-knowledge-rag-source-input-row]') : null;
if (row instanceof HTMLElement) row.remove();
var popover = ensureKnowledgeRagSettingsPopover();
var node = popover.querySelector('[data-testid="mnote-knowledge-rag-source-inputs"]');
if (node instanceof HTMLElement && !node.querySelector('[data-knowledge-rag-source-input]')) {
node.innerHTML = knowledgeRagSourceInputRow('');
}
}
function resetKnowledgeRagSourceInputs() {
var popover = ensureKnowledgeRagSettingsPopover();
var node = popover.querySelector('[data-testid="mnote-knowledge-rag-source-inputs"]');
if (node instanceof HTMLElement) node.innerHTML = knowledgeRagSourceInputRow('');
}
async function ingestKnowledgeRagSources() {
if (!knowledgeRagIsAvailable()) return;
var sources = knowledgeRagSourceInputValues();
if (!sources.length) sources = knowledgeRagIndexedRootValues();
if (!sources.length) {
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: '请先填写要索引的资料路径,或保留至少一个已保存的索引范围'
});
renderKnowledgeRagSettings();
return;
}
setKnowledgeRagInputStatuses(Object.fromEntries(sources.map(function(path) {
return [path, 'running'];
})));
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: true,
error: ''
});
renderKnowledgeRagSettings();
try {
var response = await fetch('/api/knowledge-rag/ingest', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
workspaceId: resolveWorkspaceId(document.body),
rootUri: currentRootUri(),
sources: sources.map(function(path) { return { sourcePath: path }; })
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_ingest_' + response.status);
}
if (payload.retryRequired) {
setKnowledgeRagInputStatuses(Object.fromEntries(sources.map(function(path) {
return [path, 'retry'];
})));
await loadKnowledgeRagStatus(true);
notifyKnowledgeRagSourceUpdated({ action: 'ingest', sources: sources, result: payload, retryRequired: true });
scheduleKnowledgeRagStatusBridge('ingest-retry');
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: 'LightRAG 正在处理其他任务,请稍后刷新或重试'
});
renderKnowledgeRagSettings();
return;
}
var configured = Array.isArray(payload.configuredSources) ? payload.configuredSources : [];
var done = {};
configured.forEach(function(item) {
var requested = String(item && item.requestedSourcePath || item && item.sourceRootRelativePath || '').trim();
if (requested) done[requested] = 'done';
});
setKnowledgeRagInputStatuses(done, 'done');
await loadKnowledgeRagStatus(true);
resetKnowledgeRagSourceInputs();
notifyKnowledgeRagSourceUpdated({ action: 'ingest', sources: sources, result: payload });
scheduleKnowledgeRagStatusBridge('ingest');
} catch (error) {
setKnowledgeRagInputStatuses(Object.fromEntries(sources.map(function(path) {
return [path, 'failed'];
})));
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: error instanceof Error ? error.message : String(error)
});
renderKnowledgeRagSettings();
}
}
async function ingestSingleKnowledgeRagSource(sourcePath) {
sourcePath = String(sourcePath || '').trim();
if (!sourcePath || !knowledgeRagIsAvailable()) return;
setKnowledgeRagInputStatuses({ [sourcePath]: 'running' });
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: true,
error: ''
});
renderKnowledgeRagSettings();
try {
var response = await fetch('/api/knowledge-rag/ingest', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
workspaceId: resolveWorkspaceId(document.body),
rootUri: currentRootUri(),
sources: [{ sourcePath: sourcePath }],
force: true
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_reindex_' + response.status);
}
if (payload.retryRequired) {
await loadKnowledgeRagStatus(true);
notifyKnowledgeRagSourceUpdated({ action: 'reindex', sourceRootRelativePath: sourcePath, result: payload, retryRequired: true });
scheduleKnowledgeRagStatusBridge('reindex-retry');
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: 'LightRAG 正在处理其他任务,请稍后重试'
});
renderKnowledgeRagSettings();
return;
}
await loadKnowledgeRagStatus(true);
notifyKnowledgeRagSourceUpdated({ action: 'reindex', sourceRootRelativePath: sourcePath, result: payload });
scheduleKnowledgeRagStatusBridge('reindex');
} catch (error) {
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: error instanceof Error ? error.message : String(error)
});
renderKnowledgeRagSettings();
}
}
async function deleteKnowledgeRagSource(sourcePath) {
sourcePath = String(sourcePath || '').trim();
if (!sourcePath || !knowledgeRagIsAvailable()) return;
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: true,
error: ''
});
renderKnowledgeRagSettings();
try {
var response = await fetch('/api/knowledge-rag/delete-source', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
workspaceId: resolveWorkspaceId(document.body),
rootUri: currentRootUri(),
sourcePath: sourcePath
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_delete_' + response.status);
}
await loadKnowledgeRagStatus(true);
notifyKnowledgeRagSourceUpdated({ action: 'delete', sourceRootRelativePath: sourcePath, result: payload });
} catch (error) {
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: error instanceof Error ? error.message : String(error)
});
renderKnowledgeRagSettings();
}
}
async function pruneKnowledgeRagRegistry() {
if (!knowledgeRagIsAvailable()) return;
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: true,
error: ''
});
renderKnowledgeRagSettings();
try {
var response = await fetch('/api/knowledge-rag/prune-registry', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
workspaceId: resolveWorkspaceId(document.body),
rootUri: currentRootUri()
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.message ? payload.message : 'knowledge_rag_prune_' + response.status);
}
await loadKnowledgeRagStatus(true);
notifyKnowledgeRagSourceUpdated({ action: 'prune', result: payload });
} catch (error) {
pageUiState.knowledgeRagSummary = Object.assign({}, currentKnowledgeRagSummary(), {
scopeKey: knowledgeRagScopeKey(),
loading: false,
error: error instanceof Error ? error.message : String(error)
});
renderKnowledgeRagSettings();
}
}
function openKnowledgeRagDashboard() {
var status = currentKnowledgeRagSummary().status || {};
var url = publicKnowledgeRagDashboardUrl(status);
if (!url) return;
window.open(url, '_blank', 'noopener');
}
// --- 知识库详情 tab 切换 ---
function setActiveKnowledgeRagDetailTab(tabName) {
var popover = ensureKnowledgeRagSettingsPopover();
popover.querySelectorAll('[data-kb-rag-tab]').forEach(function(tab) {
var active = tab.getAttribute('data-kb-rag-tab') === tabName;
tab.classList.toggle('is-active', active);
tab.setAttribute('aria-selected', active ? 'true' : 'false');
});
popover.querySelectorAll('[data-kb-rag-panel]').forEach(function(panel) {
panel.hidden = panel.getAttribute('data-kb-rag-panel') !== tabName;
});
}
// --- 检索测试 ---
async function performKnowledgeRagSearch() {
var popover = ensureKnowledgeRagSettingsPopover();
var input = popover.querySelector('[data-kb-rag-search-input]');
var resultsEl = popover.querySelector('[data-kb-rag-search-results]');
if (!input || !resultsEl) return;
var query = (input.value || '').trim();
if (!query) {
resultsEl.innerHTML = '<span class="mnote-extensions-error">请输入检索关键词</span>';
return;
}
resultsEl.innerHTML = '<span class="mnote-extensions-loading">检索中...</span>';
try {
var params = new URLSearchParams();
params.set('query', query);
params.set('mode', currentKnowledgeRagSearchParams().mode || 'hybrid');
params.set('top_k', String(currentKnowledgeRagSearchParams().top_k || 10));
var resp = await fetch('/api/knowledge-rag/search?' + params.toString(), {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ query: query })
});
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
var results = Array.isArray(data.results) ? data.results : (Array.isArray(data) ? data : []);
if (!results.length) {
resultsEl.innerHTML = '<div class="mnote-extensions-item"><span>未找到匹配结果</span></div>';
return;
}
var html = '';
results.forEach(function(r, i) {
var score = typeof r.score === 'number' ? r.score.toFixed(3) : (typeof r.distance === 'number' ? r.distance.toFixed(3) : '-');
var content = r.content || r.text || r.chunk || '';
var source = r.file_path || r.source || r.document_id || '';
var citationMarkdown = r.citationMarkdown || r.citation_markdown || '';
var preview = String(content).substring(0, 200);
html += '<div class="mnote-kb-search-result-item">' +
'<div class="mnote-kb-search-result-header">' +
'<span class="mnote-kb-search-result-index">#' + (i + 1) + '</span>' +
'<span class="mnote-kb-search-result-score">相关度: ' + score + '</span>' +
'</div>' +
'<div class="mnote-kb-search-result-source">' + escapeHtml(String(source)) + '</div>' +
'<div class="mnote-kb-search-result-preview">' + escapeHtml(preview) + (content.length > 200 ? '...' : '') + '</div>' +
(citationMarkdown ? '<div class="mnote-kb-search-result-citation">' + escapeHtml(String(citationMarkdown).substring(0, 150)) + '</div>' : '') +
'</div>';
});
resultsEl.innerHTML = html;
} catch (err) {
resultsEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">检索失败: ' + escapeHtml(String(err.message || err)) + '</span></div>';
}
}
// --- 检索参数 ---
var _kbRagSearchParams = null;
function currentKnowledgeRagSearchParams() {
if (!_kbRagSearchParams) {
_kbRagSearchParams = { mode: 'hybrid', top_k: 10, similarity_threshold: 0.0 };
}
return _kbRagSearchParams;
}
function loadKnowledgeRagSearchParamsToForm() {
var popover = ensureKnowledgeRagSettingsPopover();
var params = currentKnowledgeRagSearchParams();
var modeSelect = popover.querySelector('[data-kb-rag-param="mode"]');
var topKInput = popover.querySelector('[data-kb-rag-param="top_k"]');
var thresholdRange = popover.querySelector('[data-kb-rag-param="similarity_threshold"]');
var thresholdValue = popover.querySelector('[data-kb-rag-param-value="similarity_threshold"]');
if (modeSelect) modeSelect.value = params.mode || 'hybrid';
if (topKInput) topKInput.value = String(params.top_k || 10);
if (thresholdRange) {
thresholdRange.value = String(params.similarity_threshold || 0);
if (thresholdValue) thresholdValue.textContent = String(params.similarity_threshold || 0);
}
}
function syncKnowledgeRagThresholdDisplay() {
var popover = ensureKnowledgeRagSettingsPopover();
var thresholdRange = popover.querySelector('[data-kb-rag-param="similarity_threshold"]');
var thresholdValue = popover.querySelector('[data-kb-rag-param-value="similarity_threshold"]');
if (thresholdRange && thresholdValue) {
var val = parseFloat(thresholdRange.value) || 0;
thresholdValue.textContent = val.toFixed(2);
}
}
function saveKnowledgeRagSearchParams() {
var popover = ensureKnowledgeRagSettingsPopover();
var modeSelect = popover.querySelector('[data-kb-rag-param="mode"]');
var topKInput = popover.querySelector('[data-kb-rag-param="top_k"]');
var thresholdRange = popover.querySelector('[data-kb-rag-param="similarity_threshold"]');
var params = {
mode: modeSelect ? modeSelect.value : 'hybrid',
top_k: topKInput ? (parseInt(topKInput.value, 10) || 10) : 10,
similarity_threshold: thresholdRange ? (parseFloat(thresholdRange.value) || 0) : 0
};
_kbRagSearchParams = params;
try {
fetch('/api/ui/preferences', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ updates: { knowledge_rag_search_params: params } })
}).catch(function(){});
} catch (_) {}
}
function knowledgeRagInputStatusText(status) {
if (status === 'running') return '索引中';
if (status === 'done') return '已提交';
if (status === 'retry') return '需重试';
if (status === 'failed') return '失败';
return '待索引';
}
function setKnowledgeRagInputStatuses(statusByPath, fallbackStatus) {
var popover = ensureKnowledgeRagSettingsPopover();
Array.from(popover.querySelectorAll('[data-knowledge-rag-source-input-row]')).forEach(function(row) {
var input = row.querySelector('[data-knowledge-rag-source-input]');
var label = row.querySelector('[data-knowledge-rag-input-status-label]');
if (!(input instanceof HTMLInputElement) || !(label instanceof HTMLElement)) return;
var value = input.value.trim();
var status = statusByPath && statusByPath[value] || fallbackStatus || 'idle';
label.textContent = knowledgeRagInputStatusText(status);
label.setAttribute('data-status', status);
label.hidden = status === 'idle';
});
}
function renderPageSettingsPopover() {
var popover = ensurePageSettingsPopover();
var options = currentPageOptions();
popover.querySelectorAll('[data-page-option-checkbox]').forEach(function(input) {
var key = input.getAttribute('data-page-option-checkbox');
input.checked = Boolean(options[key]);
input.disabled = !pageOptionIsSupported(key);
});
popover.querySelectorAll('[data-page-option-select]').forEach(function(select) {
var key = select.getAttribute('data-page-option-select');
var value = key === 'layoutDensity' ? String(options.layoutDensity || 'normal') : String(options.pageFont || 'default');
select.value = value;
});
renderGlobalOptions(popover);
var statsNode = popover.querySelector('[data-testid="wolai-page-settings-stats"]');
if (statsNode instanceof HTMLElement) {
var stats = computeLivePageStats();
statsNode.innerHTML = '' +
'<span>字数 ' + Number(stats.wordCount || 0) + '</span>' +
'<span>字符 ' + Number(stats.characterCount || 0) + '</span>' +
'<span>块数 ' + Number(stats.blockCount || 0) + '</span>' +
'<span>待办 ' + Number(stats.todoDone || 0) + '/' + Number(stats.todoTotal || 0) + '</span>';
}
}
function setActivePageSettingsTab(tabName) {
var popover = ensurePageSettingsPopover();
popover.querySelectorAll('[data-page-settings-tab]').forEach(function(tab) {
var active = tab.getAttribute('data-page-settings-tab') === tabName;
tab.classList.toggle('is-active', active);
tab.setAttribute('aria-selected', active ? 'true' : 'false');
});
popover.querySelectorAll('[data-page-settings-panel]').forEach(function(panel) {
if (tabName === 'extensions') {
refreshExtensionsPanel();
}
if (tabName === 'dashboard') {
refreshDashboardPanel();
}
panel.hidden = panel.getAttribute('data-page-settings-panel') !== tabName;
});
}
// --- 扩展面板子 tab 切换 ---
function setActiveExtensionsSubtab(subtabName) {
var popover = ensurePageSettingsPopover();
popover.querySelectorAll('[data-extensions-subtab]').forEach(function(tab) {
var active = tab.getAttribute('data-extensions-subtab') === subtabName;
tab.classList.toggle('is-active', active);
tab.setAttribute('aria-selected', active ? 'true' : 'false');
});
popover.querySelectorAll('[data-extensions-panel]').forEach(function(panel) {
panel.hidden = panel.getAttribute('data-extensions-panel') !== subtabName;
});
// 切换时懒加载对应面板数据
if (subtabName === 'knowledge') refreshKnowledgeExtensionsPanel();
else if (subtabName === 'skills') refreshSkillsExtensionsPanel();
else if (subtabName === 'mcp') refreshMcpExtensionsPanel();
else if (subtabName === 'tools') refreshToolsExtensionsPanel();
}
// 当切换到扩展 tab 时刷新当前子面板
function refreshExtensionsPanel() {
var popover = ensurePageSettingsPopover();
var activeSubtab = popover.querySelector('[data-extensions-subtab].is-active');
var subtabName = activeSubtab ? activeSubtab.getAttribute('data-extensions-subtab') : 'knowledge';
setActiveExtensionsSubtab(subtabName);
}
// --- 知识库子面板 ---
var _knowledgeRagExtensionsCache = null;
async function refreshKnowledgeExtensionsPanel() {
var statusEl = document.querySelector('[data-extensions-status="knowledge"]');
if (!statusEl) return;
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
try {
var resp = await fetch('/api/knowledge-rag/status');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
_knowledgeRagExtensionsCache = data;
var indexedCount = (data.files && data.files.length) || data.total_files || 0;
var statusText = data.ready ? '就绪' : '未就绪';
var statusClass = data.ready ? 'mnote-status-ready' : 'mnote-status-pending';
var lastIngest = data.last_ingest_time || '';
statusEl.innerHTML = '' +
'<div class="mnote-extensions-item">' +
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">LightRAG 状态</span><span class="' + statusClass + '">' + escapeHtml(String(statusText)) + '</span></div>' +
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">已索引文件</span><span>' + indexedCount + ' 个</span></div>' +
(lastIngest ? '<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">最近更新</span><span>' + escapeHtml(String(lastIngest)) + '</span></div>' : '') +
'<div class="mnote-extensions-item-actions">' +
'<button type="button" class="wolai-page-settings-action" data-knowledge-rag-action="manage">管理知识库...</button>' +
'<button type="button" class="wolai-page-settings-action" data-knowledge-rag-action="refresh-status">刷新状态</button>' +
'</div>' +
'</div>';
} catch (err) {
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法连接 LightRAG 服务</span></div>';
}
}
// --- Skills 子面板 ---
async function refreshSkillsExtensionsPanel() {
var statusEl = document.querySelector('[data-extensions-status="skills"]');
if (!statusEl) return;
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
try {
var resp = await fetch('/api/page-ai/agent-descriptors');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
var descriptors = data.descriptors || data || [];
// 从 agent descriptor 中收集 skills 信息
var allSkills = [];
descriptors.forEach(function(desc) {
var caps = desc.capabilities || desc.capabilityStates || {};
if (caps.skills && Array.isArray(caps.skills)) {
caps.skills.forEach(function(s) { allSkills.push({ name: s, source: desc.name || desc.id || 'unknown' }); });
}
});
if (!allSkills.length) {
allSkills.push({ name: 'skill-read (内置)', source: 'hermes' });
}
var html = '';
allSkills.forEach(function(skill) {
html += '<div class="mnote-extensions-item">' +
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">' + escapeHtml(skill.name) + '</span><span class="mnote-extensions-source">' + escapeHtml(skill.source) + '</span></div>' +
'</div>';
});
if (!html) html = '<div class="mnote-extensions-item"><span>暂无已安装 Skills</span></div>';
statusEl.innerHTML = html;
} catch (err) {
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法加载 Skills 信息</span></div>';
}
}
// --- MCP 子面板 ---
async function refreshMcpExtensionsPanel() {
var statusEl = document.querySelector('[data-extensions-status="mcp"]');
if (!statusEl) return;
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
try {
var resp = await fetch('/api/hermes/mcp/servers');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var servers = await resp.json();
var list = Array.isArray(servers) ? servers : (servers.servers || []);
var html = '';
if (list.length === 0) {
html = '<div class="mnote-extensions-item"><span>暂无已配置 MCP 服务器</span></div>';
} else {
list.forEach(function(srv) {
var name = srv.name || srv.id || '未命名';
var url = srv.url || srv.command || '';
var connected = srv.connected ? '已连接' : '未连接';
var connClass = srv.connected ? 'mnote-status-ready' : 'mnote-status-pending';
html += '<div class="mnote-extensions-item">' +
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">' + escapeHtml(name) + '</span><span class="' + connClass + '">' + connected + '</span></div>' +
(url ? '<div class="mnote-extensions-item-row"><span class="mnote-extensions-detail">' + escapeHtml(String(url)) + '</span></div>' : '') +
'</div>';
});
}
statusEl.innerHTML = html;
} catch (err) {
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法加载 MCP 配置</span></div>';
}
}
// --- Agent 工具子面板 ---
async function refreshToolsExtensionsPanel() {
var statusEl = document.querySelector('[data-extensions-status="tools"]');
if (!statusEl) return;
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
try {
var resp = await fetch('/api/page-ai/agent-descriptors');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
var descriptors = data.descriptors || data || [];
var html = '';
descriptors.forEach(function(desc) {
var tools = desc.tools || [];
var name = desc.name || desc.id || 'unknown';
if (!tools.length) return;
html += '<div class="mnote-extensions-item mnote-extensions-agent-group">' +
'<div class="mnote-extensions-agent-name">' + escapeHtml(name) + '</div>';
tools.forEach(function(tool) {
var toolName = tool.name || tool.function_name || '';
var disabled = tool.disabled ? ' (已禁用)' : '';
html += '<div class="mnote-extensions-tool-item">' +
'<span class="mnote-extensions-tool-name">' + escapeHtml(toolName) + '</span>' +
(disabled ? '<span class="mnote-extensions-tool-disabled">' + disabled + '</span>' : '') +
'</div>';
});
html += '</div>';
});
if (!html) html = '<div class="mnote-extensions-item"><span>暂无 Agent 工具信息</span></div>';
statusEl.innerHTML = html;
} catch (err) {
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法加载 Agent 工具信息</span></div>';
}
}
async function persistPageOptionsPatch(patch) {
var previous = Object.assign({}, currentPageOptions());
pageUiState.pageOptions = Object.assign({}, previous, patch);
var nextOptions = Object.assign({}, pageUiState.pageOptions);
applyPageOptionsToShell();
renderPageSettingsPopover();
try {
var response = await fetch('/api/ui/preferences', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
documentId: currentDocumentId(),
workspaceId: resolveWorkspaceId(document.body),
...currentWorkspaceSourcePayload(),
updates: nextOptions
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'page_settings_save_failed_' + response.status);
}
var savedOptions = payload && payload.result && payload.result.pageOptions && typeof payload.result.pageOptions === 'object'
? Object.assign(defaultPageOptions(), payload.result.pageOptions)
: nextOptions;
pageUiState.pageOptions = Object.assign({}, savedOptions);
nextOptions = Object.assign({}, pageUiState.pageOptions);
applyPageOptionsToShell();
renderPageSettingsPopover();
var script = document.getElementById('__MNOTE_PAGE_AGGREGATE__');
if (script) {
try {
var aggregate = JSON.parse(script.textContent || 'null');
if (aggregate && typeof aggregate === 'object') {
aggregate.layout = aggregate.layout && typeof aggregate.layout === 'object' ? aggregate.layout : {};
aggregate.layout.pageOptions = Object.assign({}, nextOptions);
aggregate.layout_options = Object.assign({}, nextOptions);
script.textContent = JSON.stringify(aggregate);
script.setAttribute('data-mnote-page-options-synced-at', String(Date.now()));
script.setAttribute('data-mnote-page-options-local-write-at', String(Date.now()));
}
} catch (_) {}
}
document.documentElement.setAttribute('data-mnote-page-options-saved', 'true');
} catch (error) {
pageUiState.pageOptions = previous;
applyPageOptionsToShell();
renderPageSettingsPopover();
document.documentElement.setAttribute('data-mnote-page-options-error', error instanceof Error ? error.message : String(error));
}
}
async function loadPageWidthPreferences() {
var params = new URLSearchParams();
if (currentDocumentId()) params.set('documentId', currentDocumentId());
params.set('workspaceId', resolveWorkspaceId(document.body));
var sourcePayload = currentWorkspaceSourcePayload();
Object.keys(sourcePayload || {}).forEach(function(key) {
if (sourcePayload[key] != null && sourcePayload[key] !== '') params.set(key, sourcePayload[key]);
});
try {
var response = await fetch('/api/ui/preferences/effective?' + params.toString(), {
headers: { accept: 'application/json' }
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) return;
pageUiState.pageWidthPreferences = normalizePageWidthPreferences(payload.result && payload.result.pageWidthPreferences);
markRetiredLocalOcrPreferenceSurface();
applyPageOptionsToShell();
if (isPageSettingsOpen()) renderPageSettingsPopover();
} catch (_) {}
}
async function persistPageWidthPreference(type, mode) {
if (PAGE_WIDTH_TYPES.indexOf(type) < 0) return;
var previous = pageUiState.pageWidthPreferences;
var next = normalizePageWidthPreferences(previous);
next[type] = Object.assign({}, next[type], {
mode: mode,
resolvedMode: mode === 'inherit' ? next.default.resolvedMode : mode,
cssMaxWidth: pageWidthCssMaxWidth(mode === 'inherit' ? next.default.resolvedMode : mode),
source: 'optimistic'
});
pageUiState.pageWidthPreferences = next;
applyPageOptionsToShell();
renderPageSettingsPopover();
notifyPageWidthPreferenceChanged(type);
try {
var updates = {};
updates['pageWidth.' + type] = { mode: mode, custom: null };
var response = await fetch('/api/ui/preferences', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
documentId: currentDocumentId(),
workspaceId: resolveWorkspaceId(document.body),
...currentWorkspaceSourcePayload(),
updates: updates
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'page_width_save_failed_' + response.status);
}
pageUiState.pageWidthPreferences = normalizePageWidthPreferences(payload.result && payload.result.pageWidthPreferences);
applyPageOptionsToShell();
renderPageSettingsPopover();
notifyPageWidthPreferenceChanged(type);
document.documentElement.setAttribute('data-mnote-page-width-saved', 'true');
} catch (error) {
pageUiState.pageWidthPreferences = previous;
applyPageOptionsToShell();
renderPageSettingsPopover();
document.documentElement.setAttribute('data-mnote-page-width-error', error instanceof Error ? error.message : String(error));
}
}
function isPageSettingsOpen() {
var popover = document.querySelector('[data-testid="wolai-page-settings-popover"]');
return popover instanceof HTMLElement && !popover.hidden;
}
function isLocalIndexSettingsOpen() {
var popover = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
return popover instanceof HTMLElement && !popover.hidden;
}
function isKnowledgeRagSettingsOpen() {
var popover = document.querySelector('[data-testid="mnote-knowledge-rag-settings-popover"]');
return popover instanceof HTMLElement && !popover.hidden;
}
function isAnySettingsOpen() {
return isPageSettingsOpen() || isLocalIndexSettingsOpen() || isKnowledgeRagSettingsOpen();
}
function openPageSettingsPopover(initialTab) {
if (!currentDocumentId()) return;
closeLocalIndexSettingsPopover();
closeKnowledgeRagSettingsPopover();
var popover = ensurePageSettingsPopover();
renderPageSettingsPopover();
setActivePageSettingsTab(initialTab || 'page');
popover.hidden = false;
pageUiState.pageSettingsOpen = true;
updatePageSettingsTriggerState();
}
function openPageIndexSettingsPopover() {
closePageSettingsPopover();
closeKnowledgeRagSettingsPopover();
var popover = ensureLocalIndexSettingsPopover();
renderPageSettingsLocalIndex(popover);
popover.hidden = false;
void loadPageSettingsLocalIndex(false);
updateStandaloneSettingsTriggerState();
}
function openKnowledgeRagSettingsPopover() {
closePageSettingsPopover();
closeLocalIndexSettingsPopover();
var popover = ensureKnowledgeRagSettingsPopover();
renderKnowledgeRagSettings(popover);
popover.hidden = false;
void loadKnowledgeRagStatus(false).then(function() {
if (knowledgeRagStatusHasInFlight(currentKnowledgeRagSummary().status || {})) {
scheduleKnowledgeRagStatusBridge('open-panel');
}
});
updateStandaloneSettingsTriggerState();
}
function closePageSettingsPopover() {
var popover = document.querySelector('[data-testid="wolai-page-settings-popover"]');
if (popover instanceof HTMLElement) popover.hidden = true;
pageUiState.pageSettingsOpen = false;
updatePageSettingsTriggerState();
}
function closeLocalIndexSettingsPopover() {
var popover = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
if (popover instanceof HTMLElement) popover.hidden = true;
updateStandaloneSettingsTriggerState();
}
function closeKnowledgeRagSettingsPopover() {
var popover = document.querySelector('[data-testid="mnote-knowledge-rag-settings-popover"]');
if (popover instanceof HTMLElement) popover.hidden = true;
updateStandaloneSettingsTriggerState();
}
function closeAllSettingsPopovers() {
closePageSettingsPopover();
closeLocalIndexSettingsPopover();
closeKnowledgeRagSettingsPopover();
}
function togglePageSettingsPopover() {
if (isPageSettingsOpen()) closePageSettingsPopover();
else openPageSettingsPopover();
}
window.addEventListener('mnote:active-resource-tab-changed', function() {
applyPageOptionsToShell();
});
window.addEventListener('mnote:knowledge-rag-source-updated', function() {
if (isKnowledgeRagSettingsOpen()) void loadKnowledgeRagStatus(true);
});
// --- 仪表盘 ---
function refreshDashboardPanel() {
var popover = ensurePageSettingsPopover();
// 知识库状态
refreshDashboardKnowledgeCard(popover);
// Skills
refreshDashboardSkillsCard(popover);
// MCP
refreshDashboardMcpCard(popover);
// Agent runs
refreshDashboardAgentCard(popover);
}
async function refreshDashboardKnowledgeCard(popover) {
var el = popover.querySelector('[data-dashboard-value="knowledge"]');
if (!el) return;
el.textContent = '加载中...';
try {
var resp = await fetch('/api/knowledge-rag/status');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
var count = (data.files && data.files.length) || data.total_files || 0;
var statusText = data.ready ? '就绪' : '未就绪';
el.textContent = count + ' 文件 / ' + statusText;
} catch (_) {
el.textContent = '不可用';
}
}
async function refreshDashboardSkillsCard(popover) {
var el = popover.querySelector('[data-dashboard-value="skills"]');
if (!el) return;
el.textContent = '加载中...';
try {
var resp = await fetch('/api/page-ai/agent-descriptors');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
var descriptors = data.descriptors || data || [];
var count = 0;
descriptors.forEach(function(d) {
var caps = d.capabilities || d.capabilityStates || {};
if (caps.skills && Array.isArray(caps.skills)) count += caps.skills.length;
});
if (!count) count = 1; // skill-read 内置
el.textContent = count + ' 个';
} catch (_) {
el.textContent = '--';
}
}
async function refreshDashboardMcpCard(popover) {
var el = popover.querySelector('[data-dashboard-value="mcp"]');
if (!el) return;
el.textContent = '加载中...';
try {
var resp = await fetch('/api/hermes/mcp/servers');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var servers = await resp.json();
var list = Array.isArray(servers) ? servers : (servers.servers || []);
var connected = list.filter(function(s) { return s.connected; }).length;
el.textContent = list.length + ' / ' + connected + ' 已连接';
} catch (_) {
el.textContent = '--';
}
}
async function refreshDashboardAgentCard(popover) {
var el = popover.querySelector('[data-dashboard-value="agent"]');
if (!el) return;
el.textContent = '加载中...';
try {
var resp = await fetch('/api/hermes/client/runs?limit=100');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var data = await resp.json();
var runs = Array.isArray(data) ? data : (data.runs || []);
var recentCount = runs.length;
el.textContent = recentCount + ' 次';
} catch (_) {
el.textContent = '--';
}
}
return {
addLocalIndexRange,
applyPageOptionsToShell,
closeAllSettingsPopovers,
closeKnowledgeRagSettingsPopover,
closePageHistoryDrawer,
closeLocalIndexSettingsPopover,
closePageSettingsPopover,
closePageShareDialog,
currentKnowledgeRagSummary,
currentPageOptions,
ensureHistorySnapshotsSeeded,
ensureKnowledgeRagSettingsPopover,
ensureLocalIndexSettingsPopover,
ensurePageHistoryDrawer,
ensurePageSettingsPopover,
ensurePageShareDialog,
isAnySettingsOpen,
isKnowledgeRagSettingsOpen,
isLocalIndexSettingsOpen,
isPageSettingsOpen,
openKnowledgeRagSettingsPopover,
openPageHistoryDrawer,
openPageSettingsPopover,
openPageIndexSettingsPopover,
openPageShareDialog,
pageOptionIsSupported,
persistLocalIndexSettings,
persistPageOptionsPatch,
persistPageWidthPreference,
recordPageHistorySnapshot,
removeLocalIndexRange,
renderPageSettingsPopover,
refreshExtensionsPanel,
refreshDashboardPanel,
refreshKnowledgeExtensionsPanel,
setActiveExtensionsSubtab,
setActivePageSettingsTab,
refreshLocalIndex,
addKnowledgeRagSourceInput,
deleteKnowledgeRagSource,
ingestKnowledgeRagSources,
ingestSingleKnowledgeRagSource,
loadKnowledgeRagStatus,
openKnowledgeRagDashboard,
performKnowledgeRagSearch,
saveKnowledgeRagSearchParams,
setActiveKnowledgeRagDetailTab,
pruneKnowledgeRagRegistry,
removeKnowledgeRagSourceInput,
setKnowledgeRagSourceFilter,
useKnowledgeRagFileTreeSelection,
togglePageSettingsPopover,
updatePageSettingsTriggerState,
writeGlobalShowHeadingNumbers,
loadPageWidthPreferences,
};
}