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 '' + ''; } return '' + ''; } function createGlobalHeadingNumbersRow() { return '' + ''; } function createPageWidthSelectRow(type) { var options = type === 'default' ? [ ['readable', '阅读'], ['comfortable', '舒适'], ['wide', '宽版'], ['full', '全宽'] ] : [ ['inherit', '继承默认'], ['readable', '阅读'], ['comfortable', '舒适'], ['wide', '宽版'], ['full', '全宽'] ]; return '' + ''; } function createPageWidthRows() { return '' + '
页面宽度
' + 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 '' + ''; } 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 = '' + '
' + '
' + '
页面历史
当前会话内最近保存的 15 个快照。
' + '' + '
' + '
' + '
'; 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 '' + '
' + '
' + '
' + escapeHtml(label) + '
' + '
字数 ' + Number(stats.wordCount || 0) + ' · 字符 ' + Number(stats.characterCount || 0) + ' · 块数 ' + Number(stats.blockCount || 0) + '
' + '
' + '' + '
'; }).join('') : '
尚未产生历史快照,编辑后会自动生成。
'; } 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 = '' + ''; 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 = '' + ''; document.body.appendChild(popover); return popover; } function createLocalIndexSettingsPanelHtml() { return '' + ''; } 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 '' + ''; } 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 '
' + escapeHtml(message) + '
'; } 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 === '.' ? '当前工作区全部' : ''; return '' + '
' + '' + '' + rootScopeHint + '' + '
'; }).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 '' + '
' + '' + '' + '' + '
'; } function setKnowledgeRagSourceComposerVisible(visible) { var popover = ensureKnowledgeRagSettingsPopover(); var composer = popover.querySelector('[data-knowledge-rag-source-composer]'); if (composer instanceof HTMLElement) composer.hidden = !visible; } 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 || 'all'; } function setKnowledgeRagSourceFilter(filter) { pageUiState.knowledgeRagSourceFilter = String(filter || 'all').trim() || 'all'; renderKnowledgeRagSettings(); } function knowledgeRagRegistryEntryCategory(entry) { var deleted = entry && entry.deletedAtMs; var stale = entry && entry.stale; var providerStatus = knowledgeRagEntryProviderStatus(entry); var providerMissing = providerStatus === 'provider_missing'; var failed = providerStatus === 'failed' || providerMissing; var deleting = providerStatus === 'delete_submitted'; var deletedDone = providerStatus === 'delete_completed'; var docId = knowledgeRagEntryProviderDocId(entry); var indexed = entry && (entry.indexedAtMs || entry.providerStatus === 'provider_mapped' || entry.mappingStatus === 'provider_mapped') && docId && !deleted && !stale && !failed && !deleting && !deletedDone; if (deletedDone) return 'removed'; if (failed || stale || deleted) return 'failed'; if (indexed) return 'indexed'; return 'indexing'; } function knowledgeRagProviderName(status) { var config = status && status.providerConfig && typeof status.providerConfig === 'object' ? status.providerConfig : {}; return String(status && status.provider || config.active || 'lightrag_legacy').trim() || 'lightrag_legacy'; } function knowledgeRagProviderLabel(status) { var providerName = knowledgeRagProviderName(status); if (providerName === 'weknora') return 'WeKnora'; if (providerName === 'lightrag') return 'LightRAG'; if (providerName === 'lightrag_legacy') return 'LightRAG'; return providerName; } function knowledgeRagStatusValue(value) { var text = String(value || '').trim(); return text || 'unknown'; } function knowledgeRagEntryProviderStatus(entry) { return String(entry && ( entry.providerStatus || entry.provider_status || entry.mappingStatus || entry.mapping_status || entry.processingStatus || entry.processing_status || entry.lightRagStatus || '' ) || '').trim(); } function knowledgeRagEntryProviderDocId(entry) { return String(entry && ( entry.providerKnowledgeId || entry.provider_knowledge_id || entry.knowledgeId || entry.knowledge_id || entry.providerDocId || entry.provider_doc_id || entry.lightRagDocId || '' ) || '').trim(); } function knowledgeRagEntryProviderKnowledgeBaseId(entry) { return String(entry && ( entry.providerKnowledgeBaseId || entry.provider_knowledge_base_id || entry.knowledgeBaseId || entry.knowledge_base_id || '' ) || '').trim(); } function knowledgeRagEntryProviderIds(entry) { var ids = []; var providerIds = entry && entry.providerIds; if (Array.isArray(providerIds)) { providerIds.forEach(function(id) { var text = String(id || '').trim(); if (text && ids.indexOf(text) === -1) ids.push(text); }); } else if (providerIds && typeof providerIds === 'object') { Object.keys(providerIds).forEach(function(key) { var value = String(providerIds[key] || '').trim(); if (value && ids.indexOf(value) === -1) ids.push(value); }); } [ entry && entry.providerKnowledgeBaseId, entry && entry.provider_knowledge_base_id, entry && entry.knowledgeBaseId, entry && entry.knowledge_base_id, entry && entry.providerKnowledgeId, entry && entry.provider_knowledge_id, entry && entry.providerDocId, entry && entry.knowledgeId, entry && entry.knowledge_id, entry && entry.lightRagDocId ].forEach(function(id) { var text = String(id || '').trim(); if (text && ids.indexOf(text) === -1) ids.push(text); }); return ids; } function knowledgeRagProviderDocId(doc) { return String(doc && (doc.id || doc.knowledgeId || doc.knowledge_id || doc.documentId || doc.document_id) || '').trim(); } function knowledgeRagProviderKnowledgeId(doc) { return String(doc && (doc.knowledgeId || doc.knowledge_id || doc.id || doc.documentId || doc.document_id) || '').trim(); } function knowledgeRagProviderChunkCount(value) { var count = Number(value && (value.chunkCount ?? value.chunk_count ?? value.chunks ?? value.match_count ?? 0)); return Number.isFinite(count) && count > 0 ? count : 0; } function knowledgeRagProviderStatusText(status) { return knowledgeRagStatusValue(status); } function knowledgeRagUserStatusLabel(status) { var text = String(status || '').trim().toLowerCase(); if (!text) return ''; if (text === 'provider_missing') return '索引记录缺失'; if (text === 'failed' || text.includes('fail')) return '索引失败'; if (text === 'delete_submitted') return '正在移除索引'; if (text === 'delete_completed') return '已移除索引'; if (text === 'provider_mapped' || text === 'completed' || text === 'processed' || text === 'indexed') return '已索引'; if (['pending', 'submitted', 'scanning', 'parsing', 'parse', 'parsed', 'analyzing', 'analyze', 'processing', 'process'].includes(text)) return '处理中'; return text.replace(/_/g, ' '); } function knowledgeRagUserDescription(description) { var text = String(description || '').trim(); if (!text) return ''; if (/source registry|provider|sidecar|chunk|kb id|knowledge base id/i.test(text)) return ''; return text; } function knowledgeRagProviderKnowledgeBaseIds(status) { var config = status && status.providerConfig && typeof status.providerConfig === 'object' ? status.providerConfig : {}; var weknora = config.weknora && typeof config.weknora === 'object' ? config.weknora : {}; var ids = []; var raw = weknora.knowledgeBaseIds || weknora.knowledge_base_ids || status && status.knowledgeBaseIds || []; if (!Array.isArray(raw) && raw) raw = [raw]; (Array.isArray(raw) ? raw : []).forEach(function(id) { var text = String(id || '').trim(); if (text && ids.indexOf(text) === -1) ids.push(text); }); return ids; } function knowledgeRagBaseProviderKbId(base) { return String(base && ( base.providerKbId || base.provider_kb_id || base.providerKnowledgeBaseId || base.provider_knowledge_base_id || base.id || '' ) || '').trim(); } function knowledgeRagKnowledgeBases(status) { var registry = status && (status.knowledgeBases || status.knowledge_bases) && typeof (status.knowledgeBases || status.knowledge_bases) === 'object' ? (status.knowledgeBases || status.knowledge_bases) : {}; var bases = Array.isArray(registry.bases) ? registry.bases : []; var output = []; var seen = {}; bases.forEach(function(base) { var providerKbId = knowledgeRagBaseProviderKbId(base); if (!providerKbId || seen[providerKbId]) return; seen[providerKbId] = true; output.push({ providerKbId: providerKbId, name: String(base.name || base.title || providerKbId), description: String(base.description || base.summary || ''), sourceCount: Number(base.sourceCount || base.source_count || 0), chunkCount: Number(base.chunkCount || base.chunk_count || 0), updatedAt: String(base.updatedAt || base.updated_at || base.lastIndexedAt || base.last_indexed_at || ''), status: String(base.status || 'active') }); }); var optimisticBases = Array.isArray(pageUiState.knowledgeRagOptimisticBases) ? pageUiState.knowledgeRagOptimisticBases : []; optimisticBases.forEach(function(base) { var providerKbId = knowledgeRagBaseProviderKbId(base); if (!providerKbId || seen[providerKbId]) return; seen[providerKbId] = true; output.push({ providerKbId: providerKbId, name: String(base.name || base.title || providerKbId), description: String(base.description || ''), sourceCount: Number(base.sourceCount || 0), chunkCount: Number(base.chunkCount || 0), updatedAt: String(base.updatedAt || ''), status: String(base.status || 'active') }); }); knowledgeRagProviderKnowledgeBaseIds(status || {}).forEach(function(providerKbId) { if (!providerKbId || seen[providerKbId]) return; seen[providerKbId] = true; output.push({ providerKbId: providerKbId, name: 'Provider KB ' + providerKbId.slice(0, 8), description: '由知识库 provider 配置注入的资料库', sourceCount: 0, chunkCount: 0, updatedAt: '', status: 'configured' }); }); if (!seen['lightrag-default'] && ['lightrag', 'lightrag_legacy'].includes(knowledgeRagProviderName(status || {}))) { seen['lightrag-default'] = true; output.unshift({ providerKbId: 'lightrag-default', name: '默认资料库', description: '包含当前工作区的全部已索引资料', sourceCount: ((status && status.registry && Array.isArray(status.registry.entries)) ? status.registry.entries.length : 0), chunkCount: 0, updatedAt: '', status: 'active' }); } return output; } function selectedKnowledgeRagProviderKnowledgeBaseId(status) { var selected = String(pageUiState.knowledgeRagSelectedProviderKnowledgeBaseId || '').trim(); var bases = knowledgeRagKnowledgeBases(status || currentKnowledgeRagSummary().status || {}); if (selected && bases.some(function(base) { return base.providerKbId === selected; })) return selected; var first = bases.length ? bases[0].providerKbId : ''; pageUiState.knowledgeRagSelectedProviderKnowledgeBaseId = first; return first; } function setKnowledgeRagSelectedProviderKnowledgeBaseId(providerKnowledgeBaseId) { pageUiState.knowledgeRagSelectedProviderKnowledgeBaseId = String(providerKnowledgeBaseId || '').trim(); pageUiState.knowledgeRagSourceFilter = 'all'; renderKnowledgeRagSettings(); } function knowledgeRagEntriesForKb(entries, providerKnowledgeBaseId) { var selected = String(providerKnowledgeBaseId || '').trim(); if (selected === 'lightrag-default') return Array.isArray(entries) ? entries : []; return (Array.isArray(entries) ? entries : []).filter(function(entry) { return selected && knowledgeRagEntryProviderKnowledgeBaseId(entry) === selected; }); } function knowledgeRagProviderDocsForKb(providerDocs, providerKnowledgeBaseId) { var selected = String(providerKnowledgeBaseId || '').trim(); if (selected === 'lightrag-default') return Array.isArray(providerDocs) ? providerDocs : []; return (Array.isArray(providerDocs) ? providerDocs : []).filter(function(doc) { var id = String(doc && (doc.knowledgeBaseId || doc.knowledge_base_id || doc.providerKnowledgeBaseId || doc.provider_knowledge_base_id) || '').trim(); return selected && id === selected; }); } function knowledgeRagInFlightCount(entries, providerDocs) { return (Array.isArray(entries) ? entries : []).filter(function(entry) { var statusText = knowledgeRagEntryProviderStatus(entry).toLowerCase(); return ['pending', 'submitted', 'processing', 'provider_mapped', 'pending_provider_ingest_missing_kb'].includes(statusText) && !entry?.indexedAtMs && !entry?.deletedAtMs; }).length + (Array.isArray(providerDocs) ? providerDocs : []).filter(knowledgeRagDocumentIsInFlight).length; } function knowledgeRagBaseStats(base, entries, providerDocs) { var baseEntries = knowledgeRagEntriesForKb(entries, base && base.providerKbId); var baseProviderDocs = knowledgeRagProviderDocsForKb(providerDocs, base && base.providerKbId); var sourceCount = baseEntries.length || Number(base && base.sourceCount || 0); var providerDocCount = baseProviderDocs.length; var chunkCount = Number(base && base.chunkCount || 0); if (!chunkCount) { baseEntries.forEach(function(entry) { chunkCount += knowledgeRagProviderChunkCount(entry); }); baseProviderDocs.forEach(function(doc) { chunkCount += knowledgeRagProviderChunkCount(doc); }); } return { sourceCount: sourceCount, providerDocCount: providerDocCount, chunkCount: chunkCount, processingCount: knowledgeRagInFlightCount(baseEntries, baseProviderDocs) }; } function knowledgeRagBaseStatusLabel(status) { var text = String(status || 'active').trim().toLowerCase(); if (!text || text === 'active' || text === 'configured') return '可用'; if (text === 'processing' || text === 'indexing') return '处理中'; if (text === 'test' || text === 'preview' || text === 'local') return '测试'; if (text === 'failed' || text === 'error') return '异常'; return String(status || '可用').replace(/_/g, ' '); } function knowledgeRagOptimisticBases() { return Array.isArray(pageUiState.knowledgeRagOptimisticBases) ? pageUiState.knowledgeRagOptimisticBases : []; } function knowledgeRagBaseIsOptimistic(providerKnowledgeBaseId) { var selected = String(providerKnowledgeBaseId || '').trim(); return Boolean(selected && knowledgeRagOptimisticBases().some(function(base) { return knowledgeRagBaseProviderKbId(base) === selected; })); } function renderKnowledgeRagKnowledgeBaseCards(popover, status, entries, providerDocs) { var listNode = popover.querySelector('[data-testid="mnote-weknora-kb-card-list"]'); var countNode = popover.querySelector('[data-testid="mnote-weknora-kb-list-count"]'); var listPane = popover.querySelector('[data-testid="mnote-weknora-kb-list-pane"]'); var pageBody = popover.querySelector('.mnote-weknora-kb-page-body'); if (!(listNode instanceof HTMLElement)) return; var bases = knowledgeRagKnowledgeBases(status || {}); var selected = selectedKnowledgeRagProviderKnowledgeBaseId(status || {}); var providerName = knowledgeRagProviderName(status || {}); if (countNode instanceof HTMLElement) countNode.textContent = String(bases.length); if (listPane instanceof HTMLElement) listPane.hidden = bases.length <= 1; if (pageBody instanceof HTMLElement) pageBody.setAttribute('data-kb-list-hidden', bases.length <= 1 ? 'true' : 'false'); if (!bases.length) { listNode.innerHTML = '
还没有资料库
'; return; } listNode.innerHTML = bases.map(function(base) { var stats = knowledgeRagBaseStats(base, entries, providerDocs); var isActive = base.providerKbId === selected; var baseStatus = String(base.status || 'active'); var baseStatusLabel = knowledgeRagBaseStatusLabel(baseStatus); return '' + ''; }).join(''); } function renderKnowledgeRagKnowledgeBaseHero(popover, status, entries, providerDocs) { var heroNode = popover.querySelector('[data-testid="mnote-weknora-kb-detail-hero"]'); var titleNode = popover.querySelector('[data-testid="mnote-weknora-kb-detail-title"]'); var descNode = popover.querySelector('[data-testid="mnote-weknora-kb-detail-description"]'); var breadcrumbNode = popover.querySelector('[data-testid="mnote-weknora-kb-breadcrumb-current"]'); if (!(titleNode instanceof HTMLElement) || !(descNode instanceof HTMLElement)) return; var bases = knowledgeRagKnowledgeBases(status || {}); var selected = selectedKnowledgeRagProviderKnowledgeBaseId(status || {}); var base = bases.find(function(item) { return item.providerKbId === selected; }) || bases[0] || null; if (heroNode instanceof HTMLElement) heroNode.hidden = true; if (!base) { titleNode.textContent = '未配置知识库'; descNode.textContent = '加入资料来源后,可在这里管理 MNote 授权的本地文件和引用。'; if (breadcrumbNode instanceof HTMLElement) breadcrumbNode.textContent = '未配置'; return; } var stats = knowledgeRagBaseStats(base, entries, providerDocs); titleNode.textContent = base.name; if (breadcrumbNode instanceof HTMLElement) breadcrumbNode.textContent = base.name; var userDescription = knowledgeRagUserDescription(base.description); descNode.textContent = stats.sourceCount + ' 个资料 · ' + stats.processingCount + ' 个处理中 · ' + stats.chunkCount + ' 个片段' + (userDescription ? ' · ' + userDescription : ''); } function renderKnowledgeRagKnowledgeBaseControls(popover, status, entries, providerDocs) { var selects = Array.from(popover.querySelectorAll('[data-knowledge-rag-kb-select]')) .filter(function(select) { return select instanceof HTMLSelectElement; }); var deleteButton = popover.querySelector('[data-knowledge-rag-delete-selected-kb]'); var noteNode = popover.querySelector('[data-testid="mnote-knowledge-rag-kb-manager-note"]'); var bases = knowledgeRagKnowledgeBases(status || {}); var selected = selectedKnowledgeRagProviderKnowledgeBaseId(status || {}); if (!bases.length) { selects.forEach(function(select) { select.innerHTML = ''; select.value = ''; select.disabled = true; select.hidden = false; }); if (deleteButton instanceof HTMLButtonElement) { deleteButton.disabled = true; deleteButton.title = '没有可删除的资料库'; } if (noteNode instanceof HTMLElement) noteNode.textContent = '新建资料库后,可在这里切换当前工作区的资料范围。'; return; } var activeBase = bases.find(function(base) { return base.providerKbId === selected; }) || bases[0]; var selectedValue = activeBase.providerKbId; var activeStats = knowledgeRagBaseStats(activeBase, entries || [], providerDocs || []); var optionsHtml = bases.map(function(base) { var stats = knowledgeRagBaseStats(base, entries || [], providerDocs || []); var label = base.name + ' · ' + stats.sourceCount + ' 个资料'; return ''; }).join(''); selects.forEach(function(select) { select.disabled = false; select.hidden = false; select.innerHTML = optionsHtml; select.value = selectedValue; }); pageUiState.knowledgeRagSelectedProviderKnowledgeBaseId = selectedValue; if (deleteButton instanceof HTMLButtonElement) { var canDelete = selectedValue !== 'lightrag-default' && activeStats.sourceCount === 0 && activeStats.processingCount === 0; deleteButton.disabled = !canDelete; deleteButton.setAttribute('data-knowledge-rag-kb-id', selectedValue); deleteButton.title = canDelete ? '删除当前空资料库' : (selectedValue === 'lightrag-default' ? '默认资料库不能删除' : '请先移除资料后再删除资料库'); } if (noteNode instanceof HTMLElement) { var providerName = knowledgeRagProviderName(status || {}); var activeName = activeBase.name || '当前资料库'; if (selectedValue === 'lightrag-default') { noteNode.textContent = '默认资料库显示当前工作区全部已索引资料;新建资料库可管理独立资料范围。'; } else if (activeStats.sourceCount === 0 && activeStats.processingCount === 0) { noteNode.textContent = activeName + ':空资料库,可添加资料或直接删除。'; } else if (providerName === 'weknora') { noteNode.textContent = bases.length > 1 ? '已连接多个资料库,可用下拉框切换当前范围。' : '当前只有一个资料库,可新建后切换。'; } else { noteNode.textContent = bases.length > 1 ? '当前可切换资料库;删除前需要先移除其中资料。' : '当前使用默认资料库,也可以新建一个资料库管理独立范围。'; } } } function knowledgeRagKbSummaryHtml(status, entries, providerDocs) { var kbIds = knowledgeRagProviderKnowledgeBaseIds(status || {}); var activeKbIds = {}; (Array.isArray(entries) ? entries : []).forEach(function(entry) { var id = knowledgeRagEntryProviderKnowledgeBaseId(entry); if (id) activeKbIds[id] = true; }); (Array.isArray(providerDocs) ? providerDocs : []).forEach(function(doc) { var id = String(doc && (doc.knowledgeBaseId || doc.knowledge_base_id || doc.providerKnowledgeBaseId || doc.provider_knowledge_base_id) || '').trim(); if (id) activeKbIds[id] = true; }); var activeCount = Object.keys(activeKbIds).length || kbIds.length; var processingCount = knowledgeRagInFlightCount(entries, providerDocs); return '' + '
' + '
知识库' + escapeHtml(activeCount ? String(activeCount) + ' 个' : '未配置') + '
' + '
文档' + escapeHtml(String((entries || []).length)) + '
' + '
索引文档' + escapeHtml(String((providerDocs || []).length)) + '
' + '
处理中' + escapeHtml(String(processingCount)) + '
' + '
'; } 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 '' + '
' + '
' + '' + escapeHtml(label) + '' + '
'; } 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, providerDocs) { var docs = Array.isArray(providerDocs) ? providerDocs : knowledgeRagProviderDocuments(status); if (!docs.length) return []; var mappedDocIds = new Set(); var mappedFilePaths = new Set(); (Array.isArray(entries) ? entries : []).forEach(function(entry) { var docId = knowledgeRagEntryProviderDocId(entry); var filePath = String(entry && entry.lightRagFilePath || '').trim(); knowledgeRagEntryProviderIds(entry).forEach(function(id) { mappedDocIds.add(id); }); if (docId) mappedDocIds.add(docId); if (filePath) mappedFilePaths.add(filePath); }); return docs.filter(function(doc) { var docId = knowledgeRagProviderDocId(doc); 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"]'); var sourceTagsNode = popover.querySelector('[data-testid="mnote-weknora-doc-source-tags"]'); 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 = [ ['all', '全部'], ['attention', '需处理'], ['indexing', '处理中'], ['failed', '失败/过期'], ['indexed', '已索引'] ]; if (counts.removed) options.push(['removed', '已移除']); if (counts.unmapped) options.push(['unmapped', '未关联资料']); filtersNode.innerHTML = options.map(function(item) { var key = item[0]; var label = item[1]; return ''; }).join(''); if (sourceTagsNode instanceof HTMLElement) { sourceTagsNode.querySelectorAll('[data-knowledge-rag-action="filter-sources"]').forEach(function(button) { var key = button.getAttribute('data-knowledge-rag-filter'); var active = key === current; button.setAttribute('data-active', active ? 'true' : 'false'); }); } } function renderKnowledgeRagSources(popover, entries, status, providerDocs, activeBase) { var sourcesNode = popover.querySelector('[data-testid="mnote-knowledge-rag-sources"]'); if (!(sourcesNode instanceof HTMLElement)) return; var scopedProviderDocs = Array.isArray(providerDocs) ? providerDocs : knowledgeRagProviderDocuments(status || {}); var registryRows = (Array.isArray(entries) ? entries : []).map(function(entry) { return { kind: 'registry', category: knowledgeRagRegistryEntryCategory(entry), entry: entry }; }); var rows = registryRows.concat(knowledgeRagUnmappedProviderRows(status || {}, entries, scopedProviderDocs)); renderKnowledgeRagSourceFilters(popover, rows); var filter = knowledgeRagCurrentFilter(); var items = rows.filter(function(row) { return knowledgeRagVisibleForFilter(row, filter); }); if (!rows.length) { var emptyName = activeBase && activeBase.name ? activeBase.name : '当前资料库'; sourcesNode.innerHTML = '
' + escapeHtml(emptyName) + ' 还没有资料
'; return; } if (!items.length) { sourcesNode.innerHTML = '
当前分类没有资料
'; return; } var pipelineProgress = knowledgeRagPipelineProgress(status || {}); var providerName = knowledgeRagProviderName(status || {}); var providerDocsById = new Map(); var providerDocsByPath = new Map(); scopedProviderDocs.forEach(function(doc) { var docId = knowledgeRagProviderDocId(doc); var filePath = String(doc && doc.filePath || '').trim(); if (docId) providerDocsById.set(docId, doc); if (filePath) providerDocsByPath.set(filePath, doc); }); var listView = String(pageUiState.knowledgeRagDocumentView || 'list') === 'grid' ? 'grid' : 'list'; sourcesNode.setAttribute('data-weknora-document-view', listView); sourcesNode.innerHTML = items.map(function(row) { if (row.kind === 'provider') { var doc = row.doc || {}; var providerId = knowledgeRagProviderDocId(doc); var providerPath = String(doc.filePath || ''); var docStatus = knowledgeRagProviderStatusText(doc.status || doc.statusGroup || doc.processingStatus || doc.processing_status); var docStatusLabel = knowledgeRagUserStatusLabel(docStatus) || '已记录'; var failedDoc = row.category === 'unmapped_failed'; var providerProgress = pipelineProgress && pipelineProgress.docId && pipelineProgress.docId === providerId ? pipelineProgress : null; var knowledgeId = knowledgeRagProviderKnowledgeId(doc); var providerTitle = providerPath || providerId || '索引文档'; var chunkCount = knowledgeRagProviderChunkCount(doc); var providerMeta = '未关联本地资料 · ' + docStatusLabel + (chunkCount ? ' · ' + chunkCount + ' 个片段' : '') + (providerPath ? '' : ' · 无法直接定位原文件'); if (!providerProgress && !failedDoc && knowledgeRagDocumentIsInFlight(doc)) { providerProgress = knowledgeRagIndeterminateProgress(status || {}, doc, docStatus); } return '' + '
' + '' + '
' + '' + escapeHtml(providerTitle) + '' + '' + escapeHtml(providerMeta) + '' + '' + escapeHtml(knowledgeId || '索引记录') + '' + escapeHtml(providerPath ? '引用可回跳' : '无法直接定位') + '' + knowledgeRagSourceProgressHtml(providerProgress) + '
' + '
' + '
'; } var entry = row.entry || {}; var deleted = entry && entry.deletedAtMs; var stale = entry && entry.stale; var providerStatus = knowledgeRagEntryProviderStatus(entry); var providerMissing = providerStatus === 'provider_missing'; var failed = providerStatus === 'failed' || providerMissing; var deleting = providerStatus === 'delete_submitted'; var deletedDone = providerStatus === 'delete_completed'; var docId = knowledgeRagEntryProviderDocId(entry); var providerIds = knowledgeRagEntryProviderIds(entry); var indexed = entry && (entry.indexedAtMs || providerStatus === 'provider_mapped' || providerStatus === 'completed' || providerStatus === 'processed') && 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 rawProviderStatusLabel = knowledgeRagUserStatusLabel(providerStatus); var statusLabel = deletedDone ? '已移除索引' : deleting ? '删除已提交' : deleted ? '已删除' : stale ? '已过期' : providerMissing ? '索引记录缺失' : failed ? '索引失败' : indexed ? '已索引' : (docId || providerStatus ? '正在索引' : '待提交'); var providerDoc = (docId && providerDocsById.get(docId)) || providerDocsByPath.get(String(entry && entry.lightRagFilePath || '').trim()) || null; var chunkCount = knowledgeRagProviderChunkCount(entry) || knowledgeRagProviderChunkCount(providerDoc); var knowledgeId = docId; var kbId = knowledgeRagEntryProviderKnowledgeBaseId(entry); var metaParts = [ statusLabel, chunkCount ? chunkCount + ' 个片段' : '', rawProviderStatusLabel && !indexed && rawProviderStatusLabel !== statusLabel ? rawProviderStatusLabel : '', kbId ? '已绑定资料库' : '', providerIds.length ? providerIds.length + ' 个索引记录' : '' ].filter(Boolean); var meta = metaParts.join(' · '); 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; if (!entryProgress && !indexed && !failed && !stale && !deleted && !deletedDone && !deleting && (knowledgeRagDocumentIsInFlight(providerDoc) || docId || providerStatus)) { entryProgress = knowledgeRagIndeterminateProgress(status || {}, providerDoc, providerStatus); } return '' + '
' + '' + '
' + '' + escapeHtml(title || '未命名资料') + '' + '' + escapeHtml(meta) + '' + '' + escapeHtml(kbId || '未绑定 KB') + '' + escapeHtml(knowledgeId || '待生成 citation') + '' + knowledgeRagSourceProgressHtml(entryProgress) + '
' + '
' + '' + '' + '' + '
' + '
'; }).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, [], {}, [], null); if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = true; return; } var summary = currentKnowledgeRagSummary(); if (summary.loading) { statusNode.textContent = '正在读取知识库 provider 状态...'; if (metaNode instanceof HTMLElement) metaNode.innerHTML = ''; renderKnowledgeRagSourceFilters(popover, []); renderKnowledgeRagSources(popover, [], {}, [], null); if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = true; return; } if (summary.error) { statusNode.textContent = '知识库 provider 状态读取失败:' + summary.error; if (metaNode instanceof HTMLElement) metaNode.innerHTML = ''; renderKnowledgeRagSourceFilters(popover, []); renderKnowledgeRagSources(popover, [], {}, [], null); 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 providerLabel = knowledgeRagProviderLabel(status); if (metaNode instanceof HTMLElement) { metaNode.setAttribute('data-knowledge-rag-active-provider', knowledgeRagProviderName(status)); metaNode.setAttribute('data-knowledge-rag-legacy-fallback-provider', 'lightrag'); metaNode.setAttribute('data-knowledge-rag-reference-boundary', 'mnote-source-registry-lightrag-sidecar'); } var healthStatus = String(healthBody.status || ''); var healthy = health.ok === true && (!healthStatus || healthStatus === 'healthy' || healthStatus === 'ok'); var registry = status.registry && typeof status.registry === 'object' ? status.registry : {}; var entries = Array.isArray(registry.entries) ? registry.entries : []; var providerDocs = knowledgeRagProviderDocuments(status); var viewSwitchButtons = Array.from(popover.querySelectorAll('[data-knowledge-rag-view]')); var currentView = String(pageUiState.knowledgeRagDocumentView || 'list') === 'grid' ? 'grid' : 'list'; viewSwitchButtons.forEach(function(button) { var active = button.getAttribute('data-knowledge-rag-view') === currentView; button.classList.toggle('is-active', active); button.setAttribute('aria-pressed', active ? 'true' : 'false'); }); renderKnowledgeRagKnowledgeBaseControls(popover, status, entries, providerDocs); renderKnowledgeRagKnowledgeBaseCards(popover, status, entries, providerDocs); renderKnowledgeRagKnowledgeBaseHero(popover, status, entries, providerDocs); var processingCount = knowledgeRagInFlightCount(entries, providerDocs); var bases = knowledgeRagKnowledgeBases(status || {}); var selectedBaseId = selectedKnowledgeRagProviderKnowledgeBaseId(status || {}); var activeBase = bases.find(function(base) { return base.providerKbId === selectedBaseId; }) || bases[0] || null; var activeStats = activeBase ? knowledgeRagBaseStats(activeBase, entries, providerDocs) : null; var activeEntries = activeBase ? knowledgeRagEntriesForKb(entries, activeBase.providerKbId) : entries; var activeProviderDocs = activeBase ? knowledgeRagProviderDocsForKb(providerDocs, activeBase.providerKbId) : providerDocs; statusNode.textContent = healthy ? (activeBase ? activeBase.name + ' · 服务可用 · ' + activeStats.sourceCount + ' 个资料' + (activeStats.processingCount ? ' · ' + activeStats.processingCount + ' 个处理中' : '') : '知识库服务可用 · ' + entries.length + ' 个资料' + (processingCount ? ' · ' + processingCount + ' 个处理中' : '')) : '知识库服务不可用,页面仍可编辑'; if (metaNode instanceof HTMLElement) { var dashboardUrl = publicKnowledgeRagDashboardUrl(status); var inputDir = String((status.legacyFallback && status.legacyFallback.inputDir) || 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 = '' + knowledgeRagKbSummaryHtml(status, entries, providerDocs) + '
服务' + escapeHtml(providerLabel) + '
' + '
边界MNote source registry / LightRAG sidecar locator
' + '
控制台' + escapeHtml(dashboardUrl || '未配置') + '
' + '
模型' + escapeHtml(llmModel || '未上报') + (vlmModel ? ' / VLM ' + escapeHtml(vlmModel) : '') + '
' + '
Rerank' + escapeHtml(rerankLabel + (rerankBinding ? ' · ' + rerankBinding : '') + (rerankModel ? ' · ' + rerankModel : '')) + '
' + '
输入目录' + escapeHtml(inputDir || '未配置') + '
' + '
存储目录' + escapeHtml(workingDir || '未上报') + '
'; } renderKnowledgeRagSources(popover, activeEntries, status, activeProviderDocs, activeBase); if (dashboardButton instanceof HTMLButtonElement) dashboardButton.disabled = !publicKnowledgeRagDashboardUrl(status); } function publicKnowledgeRagDashboardUrl(status) { var config = status && status.providerConfig && typeof status.providerConfig === 'object' ? status.providerConfig : {}; var active = knowledgeRagProviderName(status || {}); var weknora = config.weknora && typeof config.weknora === 'object' ? config.weknora : {}; var lightrag = config.lightrag && typeof config.lightrag === 'object' ? config.lightrag : {}; var url = active === 'weknora' ? String((weknora && weknora.dashboardUrl) || (status && (status.dashboardUrl || status.endpoint)) || '').trim() : String((status && (status.dashboardUrl || status.endpoint)) || (lightrag && lightrag.dashboardUrl) || '').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"]'); var composer = popover.querySelector('[data-knowledge-rag-source-composer]'); if (!(node instanceof HTMLElement)) return; var shouldAppend = !(composer instanceof HTMLElement && composer.hidden); setKnowledgeRagSourceComposerVisible(true); if (shouldAppend) 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; setKnowledgeRagSourceComposerVisible(true); 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(''); } function setKnowledgeRagCreateCardVisible(visible) { var popover = ensureKnowledgeRagSettingsPopover(); var card = popover.querySelector('[data-testid="mnote-weknora-kb-controls"]'); var statusNode = popover.querySelector('[data-knowledge-rag-create-kb-status]'); if (card instanceof HTMLElement) card.hidden = !visible; if (statusNode instanceof HTMLElement && visible) statusNode.hidden = true; if (visible) { var input = popover.querySelector('[data-knowledge-rag-create-kb-name]'); if (input instanceof HTMLInputElement) input.focus(); } } function toggleKnowledgeRagCreateCard() { var popover = ensureKnowledgeRagSettingsPopover(); var card = popover.querySelector('[data-testid="mnote-weknora-kb-controls"]'); setKnowledgeRagCreateCardVisible(!(card instanceof HTMLElement) || card.hidden); } function setKnowledgeRagCreateStatus(text, status) { var popover = ensureKnowledgeRagSettingsPopover(); var statusNode = popover.querySelector('[data-knowledge-rag-create-kb-status]'); if (statusNode instanceof HTMLElement) { statusNode.textContent = String(text || ''); statusNode.setAttribute('data-status', status || 'done'); statusNode.hidden = !text; } } async function deleteKnowledgeRagKnowledgeBase() { var status = currentKnowledgeRagSummary().status || {}; var bases = knowledgeRagKnowledgeBases(status); var selected = selectedKnowledgeRagProviderKnowledgeBaseId(status); if (!selected || selected === 'lightrag-default') { setKnowledgeRagCreateStatus('默认资料库不能删除', 'failed'); return; } var activeBase = bases.find(function(base) { return base.providerKbId === selected; }) || null; var registry = status.registry && typeof status.registry === 'object' ? status.registry : {}; var entries = Array.isArray(registry.entries) ? registry.entries : []; var activeStats = activeBase ? knowledgeRagBaseStats(activeBase, entries, knowledgeRagProviderDocuments(status)) : null; if (activeStats && (activeStats.sourceCount > 0 || activeStats.processingCount > 0)) { setKnowledgeRagCreateStatus('请先移除资料后再删除资料库', 'failed'); return; } setKnowledgeRagCreateStatus('删除中', 'running'); try { var response = await fetch('/api/knowledge-rag/knowledge-bases', { method: 'DELETE', headers: { 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify({ workspaceId: resolveWorkspaceId(document.body), rootUri: currentRootUri(), providerKnowledgeBaseId: selected }) }); 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_kb_' + response.status); } pageUiState.knowledgeRagOptimisticBases = knowledgeRagOptimisticBases().filter(function(base) { return knowledgeRagBaseProviderKbId(base) !== selected; }); pageUiState.knowledgeRagSelectedProviderKnowledgeBaseId = 'lightrag-default'; setKnowledgeRagCreateStatus('已删除资料库', 'done'); await loadKnowledgeRagStatus(true, { reason: 'delete-kb', notifyUpdated: true }); renderKnowledgeRagSettings(); notifyKnowledgeRagSourceUpdated({ action: 'delete-kb', result: payload, providerKnowledgeBaseId: selected }); } catch (error) { setKnowledgeRagCreateStatus(error instanceof Error ? error.message : String(error), 'failed'); } } async function createKnowledgeRagKnowledgeBase() { if (!knowledgeRagIsAvailable()) return; var popover = ensureKnowledgeRagSettingsPopover(); var nameInput = popover.querySelector('[data-knowledge-rag-create-kb-name]'); var descriptionInput = popover.querySelector('[data-knowledge-rag-create-kb-description]'); var statusNode = popover.querySelector('[data-knowledge-rag-create-kb-status]'); var name = nameInput instanceof HTMLInputElement ? nameInput.value.trim() : ''; var description = descriptionInput instanceof HTMLInputElement ? descriptionInput.value.trim() : ''; if (!name) { if (statusNode instanceof HTMLElement) { statusNode.textContent = '请填写资料库名称'; statusNode.setAttribute('data-status', 'failed'); statusNode.hidden = false; } return; } if (statusNode instanceof HTMLElement) { statusNode.textContent = '创建中'; statusNode.setAttribute('data-status', 'running'); statusNode.hidden = false; } try { var response = await fetch('/api/knowledge-rag/knowledge-bases', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify({ workspaceId: resolveWorkspaceId(document.body), rootUri: currentRootUri(), name: name, description: description || null, defaultToolEnabled: true }) }); 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_create_kb_' + response.status); } var providerKnowledgeBaseId = String(payload.providerKnowledgeBaseId || payload.provider_knowledge_base_id || '').trim(); pageUiState.knowledgeRagSelectedProviderKnowledgeBaseId = providerKnowledgeBaseId; if (providerKnowledgeBaseId) { var optimisticBases = Array.isArray(pageUiState.knowledgeRagOptimisticBases) ? pageUiState.knowledgeRagOptimisticBases : []; pageUiState.knowledgeRagOptimisticBases = optimisticBases .filter(function(base) { return knowledgeRagBaseProviderKbId(base) !== providerKnowledgeBaseId; }) .concat([{ providerKbId: providerKnowledgeBaseId, name: name, description: description || '', sourceCount: 0, chunkCount: 0, status: 'active' }]); } if (nameInput instanceof HTMLInputElement) nameInput.value = ''; if (descriptionInput instanceof HTMLInputElement) descriptionInput.value = ''; if (statusNode instanceof HTMLElement) { statusNode.textContent = providerKnowledgeBaseId ? '已创建并选中' : '已创建'; statusNode.setAttribute('data-status', 'done'); statusNode.hidden = false; } setKnowledgeRagCreateCardVisible(false); await loadKnowledgeRagStatus(true, { reason: 'create-kb', notifyUpdated: true }); if (providerKnowledgeBaseId) { pageUiState.knowledgeRagSelectedProviderKnowledgeBaseId = providerKnowledgeBaseId; renderKnowledgeRagSettings(); } notifyKnowledgeRagSourceUpdated({ action: 'create-kb', result: payload, providerKnowledgeBaseId: providerKnowledgeBaseId }); } catch (error) { if (statusNode instanceof HTMLElement) { statusNode.textContent = error instanceof Error ? error.message : String(error); statusNode.setAttribute('data-status', 'failed'); statusNode.hidden = false; } } } 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 selectedProviderKnowledgeBaseId = selectedKnowledgeRagProviderKnowledgeBaseId(currentKnowledgeRagSummary().status || {}); 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(), providerKnowledgeBaseId: selectedProviderKnowledgeBaseId || undefined, 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: '知识库 provider 正在处理其他任务,请稍后刷新或重试' }); 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(); setKnowledgeRagSourceComposerVisible(false); 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: '知识库 provider 正在处理其他任务,请稍后重试' }); 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 url = publicKnowledgeRagDashboardUrl(currentKnowledgeRagSummary().status || {}); if (url && typeof window.open === 'function') { window.open(url, '_blank', 'noopener,noreferrer'); return; } if (url) window.location.href = url; } // --- 知识库详情 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; }); } // --- 检索测试 --- function knowledgeRagSearchResultSourcePath(result) { var candidates = [ result && result.sourceRootRelativePath, result && result.source_root_relative_path, result && result.filePath, result && result.file_path, result && result.openAction && result.openAction.params && result.openAction.params.path, result && result.locator && result.locator.sourceRootRelativePath, result && result.locator && result.locator.openAction && result.locator.openAction.params && result.locator.openAction.params.path ]; for (var index = 0; index < candidates.length; index += 1) { var value = candidates[index]; if (typeof value === 'string' && value.trim()) return value.trim(); } var source = result && result.source; if (source && typeof source === 'object') { return knowledgeRagSearchResultSourcePath(source); } if (typeof source === 'string' && source.trim()) return source.trim(); return ''; } function knowledgeRagSearchResultContent(result) { var candidates = [ result && result.content, result && result.text, result && result.chunk, result && result.displayQuote, result && result.display_quote, result && result.rawQuote, result && result.raw_quote, result && result.quote, result && result.reference && result.reference.content, result && result.reference && result.reference.matched_content ]; for (var index = 0; index < candidates.length; index += 1) { var value = candidates[index]; if (typeof value === 'string' && value.trim()) return value.trim(); } return ''; } 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 = '
请输入检索关键词
'; return; } resultsEl.innerHTML = '
检索中...
'; try { var params = new URLSearchParams(); params.set('query', query); params.set('mode', currentKnowledgeRagSearchParams().mode || 'hybrid'); params.set('top_k', String(currentKnowledgeRagSearchParams().top_k || 10)); // 获取当前 workspace 上下文 var workspaceId = resolveWorkspaceId(document.body) || ''; var rootUri = currentRootUri ? currentRootUri() : ''; var sourceKind = currentSourceKind ? currentSourceKind() : ''; var body = { query: query }; var selectedProviderKnowledgeBaseId = selectedKnowledgeRagProviderKnowledgeBaseId(currentKnowledgeRagSummary().status || {}); if (workspaceId) body.workspaceId = workspaceId; if (rootUri) body.rootUri = rootUri; if (sourceKind) body.sourceKind = sourceKind; if (selectedProviderKnowledgeBaseId) { body.providerKnowledgeBaseId = selectedProviderKnowledgeBaseId; params.set('providerKnowledgeBaseId', selectedProviderKnowledgeBaseId); } var resp = await fetch('/api/knowledge-rag/search?' + params.toString(), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }); 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.references) ? data.references : (Array.isArray(data) ? data : [])); if (!results.length) { resultsEl.innerHTML = '
未找到匹配结果
'; 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 = knowledgeRagSearchResultContent(r); var source = knowledgeRagSearchResultSourcePath(r); var citationMarkdown = r.citationMarkdown || r.citation_markdown || ''; var providerIds = r.providerIds && typeof r.providerIds === 'object' ? r.providerIds : {}; var providerKnowledgeBaseId = String(providerIds.knowledgeBaseId || r.providerKnowledgeBaseId || '').trim(); var providerKnowledgeId = String(providerIds.knowledgeId || r.providerKnowledgeId || '').trim(); var providerChunkId = String(providerIds.chunkId || r.providerChunkId || r.chunkId || '').trim(); var preview = String(content).substring(0, 200); html += '
' + '
' + '#' + (i + 1) + '' + '相关度 ' + score + '' + '
' + '
' + escapeHtml(String(source)) + '
' + '
' + escapeHtml(preview) + (content.length > 200 ? '...' : '') + '
' + (citationMarkdown ? '
' + escapeHtml(String(citationMarkdown).substring(0, 150)) + '
' : '') + '
' + '
'; }); resultsEl.innerHTML = html; } catch (err) { resultsEl.innerHTML = '
检索失败:' + escapeHtml(String(err.message || err)) + '
'; } } // --- 检索参数 --- 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"]'); if (modeSelect) modeSelect.value = params.mode || 'hybrid'; if (topKInput) topKInput.value = String(params.top_k || 10); } function syncKnowledgeRagThresholdDisplay() { return; } function localMarkdownDocumentIdFromKnowledgeRagPath(sourcePath) { var normalized = String(sourcePath || '').trim().replace(/^\/+/, ''); if (!normalized) return ''; return 'local-md:' + normalized.split('/').map(function(part) { return part.replace(/ /g, '~20'); }).join('~2F'); } function openKnowledgeRagSourceReference(sourcePath) { sourcePath = String(sourcePath || '').trim(); if (!sourcePath) return false; var documentId = localMarkdownDocumentIdFromKnowledgeRagPath(sourcePath); if (!documentId) return false; var url = new URL('/documents/' + encodeURIComponent(documentId), window.location.origin); url.searchParams.set('sourceKind', 'local_folder'); url.searchParams.set('rootUri', currentRootUri()); url.searchParams.set('workspaceId', resolveWorkspaceId(document.body)); url.searchParams.set('resourcePath', sourcePath); url.searchParams.set('resourceTab', 'resource:file:' + currentRootUri() + ':' + sourcePath); document.documentElement.setAttribute('data-mnote-knowledge-rag-open-reference-mode', 'source-registry-reference'); document.documentElement.setAttribute('data-mnote-knowledge-rag-open-reference-path', sourcePath); if (typeof window.__mnoteDocumentPaneRuntime?.openPrimaryDocument === 'function') { void window.__mnoteDocumentPaneRuntime.openPrimaryDocument({ documentId: documentId, workspaceId: resolveWorkspaceId(document.body), sourceKind: 'local_folder', rootUri: currentRootUri(), url: url }); return true; } window.location.href = url.toString(); return true; } 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 current = currentKnowledgeRagSearchParams(); var params = { mode: modeSelect ? modeSelect.value : 'hybrid', top_k: topKInput ? (parseInt(topKInput.value, 10) || 10) : 10, similarity_threshold: Number(current.similarity_threshold || 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 = '' + '字数 ' + Number(stats.wordCount || 0) + '' + '字符 ' + Number(stats.characterCount || 0) + '' + '块数 ' + Number(stats.blockCount || 0) + '' + '待办 ' + Number(stats.todoDone || 0) + '/' + Number(stats.todoTotal || 0) + ''; } } 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 = '加载中...'; 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 docs = knowledgeRagProviderDocuments(data); var registry = data && data.registry && typeof data.registry === 'object' ? data.registry : {}; var entries = Array.isArray(registry.entries) ? registry.entries : []; var indexedCount = entries.length || docs.length || data.total_files || 0; var health = data && data.health && typeof data.health === 'object' ? data.health : {}; var body = health.health && typeof health.health === 'object' ? health.health : {}; var ready = data && data.ok === true && health.ok === true && (!body.status || body.status === 'healthy' || body.status === 'ok'); var statusText = ready ? '就绪' : '未就绪'; var statusClass = ready ? 'mnote-status-ready' : 'mnote-status-pending'; var lastIngest = data.last_ingest_time || ''; statusEl.innerHTML = '' + '
' + '
知识库 provider 状态' + escapeHtml(String(statusText)) + '
' + '
已索引来源' + indexedCount + ' 个
' + (lastIngest ? '
最近更新' + escapeHtml(String(lastIngest)) + '
' : '') + '
' + '' + '' + '
' + '
'; } catch (err) { statusEl.innerHTML = '
无法连接知识库 provider 服务
'; } } // --- Skills 子面板 --- async function refreshSkillsExtensionsPanel() { var statusEl = document.querySelector('[data-extensions-status="skills"]'); if (!statusEl) return; statusEl.innerHTML = '加载中...'; 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 += '
' + '
' + escapeHtml(skill.name) + '' + escapeHtml(skill.source) + '
' + '
'; }); if (!html) html = '
暂无已安装 Skills
'; statusEl.innerHTML = html; } catch (err) { statusEl.innerHTML = '
无法加载 Skills 信息
'; } } // --- MCP 子面板 --- async function refreshMcpExtensionsPanel() { var statusEl = document.querySelector('[data-extensions-status="mcp"]'); if (!statusEl) return; statusEl.innerHTML = '加载中...'; 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 = '
暂无已配置 MCP 服务器
'; } 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 += '
' + '
' + escapeHtml(name) + '' + connected + '
' + (url ? '
' + escapeHtml(String(url)) + '
' : '') + '
'; }); } statusEl.innerHTML = html; } catch (err) { statusEl.innerHTML = '
无法加载 MCP 配置
'; } } // --- Agent 工具子面板 --- async function refreshToolsExtensionsPanel() { var statusEl = document.querySelector('[data-extensions-status="tools"]'); if (!statusEl) return; statusEl.innerHTML = '加载中...'; 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 += '
' + '
' + escapeHtml(name) + '
'; tools.forEach(function(tool) { var toolName = tool.name || tool.function_name || ''; var disabled = tool.disabled ? ' (已禁用)' : ''; html += '
' + '' + escapeHtml(toolName) + '' + (disabled ? '' + disabled + '' : '') + '
'; }); html += '
'; }); if (!html) html = '
暂无 Agent 工具信息
'; statusEl.innerHTML = html; } catch (err) { statusEl.innerHTML = '
无法加载 Agent 工具信息
'; } } 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); }); document.addEventListener('click', function(event) { var viewButton = event.target && event.target.closest ? event.target.closest('[data-knowledge-rag-view]') : null; if (!(viewButton instanceof HTMLElement)) return; var view = viewButton.getAttribute('data-knowledge-rag-view') === 'grid' ? 'grid' : 'list'; pageUiState.knowledgeRagDocumentView = view; renderKnowledgeRagSettings(); }); // --- 仪表盘 --- 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 docs = knowledgeRagProviderDocuments(data); var registry = data && data.registry && typeof data.registry === 'object' ? data.registry : {}; var entries = Array.isArray(registry.entries) ? registry.entries : []; var health = data && data.health && typeof data.health === 'object' ? data.health : {}; var body = health.health && typeof health.health === 'object' ? health.health : {}; var ready = data && data.ok === true && health.ok === true && (!body.status || body.status === 'healthy' || body.status === 'ok'); var count = entries.length || docs.length || data.total_files || 0; var statusText = 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, openKnowledgeRagSourceReference, openPageHistoryDrawer, openPageSettingsPopover, openPageIndexSettingsPopover, openPageShareDialog, pageOptionIsSupported, persistLocalIndexSettings, persistPageOptionsPatch, persistPageWidthPreference, recordPageHistorySnapshot, removeLocalIndexRange, renderPageSettingsPopover, refreshExtensionsPanel, refreshDashboardPanel, refreshKnowledgeExtensionsPanel, setActiveExtensionsSubtab, setActivePageSettingsTab, refreshLocalIndex, addKnowledgeRagSourceInput, deleteKnowledgeRagSource, ingestKnowledgeRagSources, ingestSingleKnowledgeRagSource, loadKnowledgeRagStatus, openKnowledgeRagDashboard, createKnowledgeRagKnowledgeBase, deleteKnowledgeRagKnowledgeBase, loadKnowledgeRagSearchParamsToForm, performKnowledgeRagSearch, saveKnowledgeRagSearchParams, setActiveKnowledgeRagDetailTab, syncKnowledgeRagThresholdDisplay, pruneKnowledgeRagRegistry, removeKnowledgeRagSourceInput, setKnowledgeRagSelectedProviderKnowledgeBaseId, setKnowledgeRagSourceFilter, useKnowledgeRagFileTreeSelection, toggleKnowledgeRagCreateCard, togglePageSettingsPopover, updatePageSettingsTriggerState, writeGlobalShowHeadingNumbers, loadPageWidthPreferences, }; }