Retire ACP/Hermes/OpenCode surfaces and rename hermes_tools to mnote_agent_tools so Page AI stays on Pi Lab only. Add Chrome vault extension + extension token route, pre-release purge design, and soft-retire legacy smokes for the small-group production cut.
3473 lines
179 KiB
JavaScript
3473 lines
179 KiB
JavaScript
export function createSidebarPageSettingsRuntime(context) {
|
|
const {
|
|
currentDocumentId,
|
|
currentPageAggregate,
|
|
currentRootUri,
|
|
currentSourceKind,
|
|
currentWorkspaceSourcePayload,
|
|
escapeHtml,
|
|
pageUiState,
|
|
resolveWorkspaceId,
|
|
searchText,
|
|
} = context;
|
|
const MNOTE_GLOBAL_SHOW_HEADING_NUMBERS_KEY = context.globalShowHeadingNumbersKey || 'mnote.global.showHeadingNumbers';
|
|
const PAGE_WIDTH_TYPES = ['default', 'markdown', 'word', 'pdf', 'excel', 'ppt', 'mindmap'];
|
|
const PAGE_WIDTH_LABELS = {
|
|
default: '默认',
|
|
markdown: 'Markdown',
|
|
word: 'Word',
|
|
pdf: 'PDF',
|
|
excel: 'Excel',
|
|
ppt: 'PPT',
|
|
mindmap: 'Mindmap'
|
|
};
|
|
const DEFAULT_PAGE_WIDTH_PREFERENCES = {
|
|
default: { mode: 'comfortable', resolvedMode: 'comfortable', cssMaxWidth: '980px', source: 'system' },
|
|
markdown: { mode: 'readable', resolvedMode: 'readable', cssMaxWidth: '760px', source: 'system' },
|
|
word: { mode: 'wide', resolvedMode: 'wide', cssMaxWidth: '1180px', source: 'system' },
|
|
pdf: { mode: 'wide', resolvedMode: 'wide', cssMaxWidth: '1180px', source: 'system' },
|
|
excel: { mode: 'full', resolvedMode: 'full', cssMaxWidth: 'none', source: 'system' },
|
|
ppt: { mode: 'wide', resolvedMode: 'wide', cssMaxWidth: '1180px', source: 'system' },
|
|
mindmap: { mode: 'full', resolvedMode: 'full', cssMaxWidth: 'none', source: 'system' }
|
|
};
|
|
var knowledgeRagPipelineEventSource = null;
|
|
var knowledgeRagPipelineWatcherScopeKey = '';
|
|
var knowledgeRagPipelineWatcherRefreshPending = false;
|
|
|
|
function defaultPageOptions() {
|
|
return {
|
|
wideLayout: false,
|
|
smallText: false,
|
|
layoutDensity: 'normal',
|
|
showHeadingNumbers: false,
|
|
showToc: false,
|
|
showStructure: false,
|
|
protectEditing: false,
|
|
showWordCount: true,
|
|
collapseBacklinks: false,
|
|
pageFont: 'default',
|
|
hideChildPages: false,
|
|
showBlockRefCount: false,
|
|
hideTitleHeader: false,
|
|
embedDefaultBlockId: null
|
|
};
|
|
}
|
|
|
|
function currentPageOptions() {
|
|
if (pageUiState.pageOptions) return pageUiState.pageOptions;
|
|
var aggregate = currentPageAggregate();
|
|
var current = aggregate && aggregate.layout && aggregate.layout.pageOptions && typeof aggregate.layout.pageOptions === 'object'
|
|
? aggregate.layout.pageOptions
|
|
: null;
|
|
if (!current) {
|
|
return defaultPageOptions();
|
|
}
|
|
pageUiState.pageOptions = Object.assign(defaultPageOptions(), current);
|
|
return pageUiState.pageOptions;
|
|
}
|
|
|
|
function currentPageWidthPreferences() {
|
|
return Object.assign({}, DEFAULT_PAGE_WIDTH_PREFERENCES, pageUiState.pageWidthPreferences || {});
|
|
}
|
|
|
|
function currentKnowledgeRagSummary() {
|
|
return pageUiState.knowledgeRagSummary || {};
|
|
}
|
|
|
|
function pageWidthModeLabel(mode) {
|
|
if (mode === 'inherit') return '继承默认';
|
|
if (mode === 'readable') return '阅读';
|
|
if (mode === 'comfortable') return '舒适';
|
|
if (mode === 'wide') return '宽版';
|
|
if (mode === 'full') return '全宽';
|
|
return '舒适';
|
|
}
|
|
|
|
function normalizePageWidthPreferences(value) {
|
|
var input = value && typeof value === 'object' ? value : {};
|
|
var output = {};
|
|
PAGE_WIDTH_TYPES.forEach(function(type) {
|
|
var base = DEFAULT_PAGE_WIDTH_PREFERENCES[type];
|
|
var current = input[type] && typeof input[type] === 'object' ? input[type] : {};
|
|
var mode = String(current.mode || base.mode || 'comfortable');
|
|
var resolvedMode = String(current.resolvedMode || current.resolved_mode || (mode === 'inherit' ? (input.default && input.default.resolvedMode) || 'comfortable' : mode));
|
|
output[type] = {
|
|
mode: mode,
|
|
custom: current.custom || null,
|
|
resolvedMode: resolvedMode,
|
|
resolvedCustom: current.resolvedCustom || current.resolved_custom || null,
|
|
cssMaxWidth: String(current.cssMaxWidth || current.css_max_width || pageWidthCssMaxWidth(resolvedMode)),
|
|
source: String(current.source || base.source || 'system')
|
|
};
|
|
});
|
|
return output;
|
|
}
|
|
|
|
function pageWidthCssMaxWidth(mode) {
|
|
if (mode === 'readable') return '760px';
|
|
if (mode === 'comfortable') return '980px';
|
|
if (mode === 'wide') return '1180px';
|
|
if (mode === 'full') return 'none';
|
|
return '980px';
|
|
}
|
|
|
|
function markdownPageWidthPreference(options) {
|
|
var preferences = currentPageWidthPreferences();
|
|
var markdown = preferences.markdown || DEFAULT_PAGE_WIDTH_PREFERENCES.markdown;
|
|
if (markdown.source === 'system' && options && options.wideLayout) {
|
|
return {
|
|
mode: 'comfortable',
|
|
resolvedMode: 'comfortable',
|
|
cssMaxWidth: '980px',
|
|
source: 'wideLayout'
|
|
};
|
|
}
|
|
return markdown;
|
|
}
|
|
|
|
function activeResourceWidthType() {
|
|
var raw = document.documentElement.getAttribute('data-mnote-active-resource-width-type') || '';
|
|
var type = String(raw).trim();
|
|
if (type === 'sheet') return 'excel';
|
|
if (PAGE_WIDTH_TYPES.indexOf(type) >= 0 && type !== 'default') return type;
|
|
return '';
|
|
}
|
|
|
|
function activeResourceWidthPreference(options) {
|
|
var type = activeResourceWidthType();
|
|
if (!type || type === 'markdown') return markdownPageWidthPreference(options);
|
|
var preferences = currentPageWidthPreferences();
|
|
return preferences[type] || DEFAULT_PAGE_WIDTH_PREFERENCES[type] || DEFAULT_PAGE_WIDTH_PREFERENCES.default;
|
|
}
|
|
|
|
function notifyPageWidthPreferenceChanged(type) {
|
|
var detail = {
|
|
type: String(type || ''),
|
|
preferences: currentPageWidthPreferences()
|
|
};
|
|
window.dispatchEvent(new CustomEvent('mnote:page-width-preference-changed', { detail: detail }));
|
|
document.querySelectorAll('iframe.mnote-resource-tab-frame').forEach(function(frame) {
|
|
if (!(frame instanceof HTMLIFrameElement) || !frame.contentWindow) return;
|
|
try {
|
|
frame.contentWindow.postMessage({
|
|
type: 'mnote:page-width-preference-changed',
|
|
contentType: detail.type
|
|
}, window.location.origin);
|
|
} catch (_) {}
|
|
});
|
|
}
|
|
|
|
function readGlobalShowHeadingNumbers() {
|
|
return Boolean(currentPageOptions().showHeadingNumbers);
|
|
}
|
|
|
|
function writeGlobalShowHeadingNumbers(value) {
|
|
void persistPageOptionsPatch({ showHeadingNumbers: Boolean(value) });
|
|
}
|
|
|
|
function effectiveShowHeadingNumbers(options) {
|
|
return Boolean(options && options.showHeadingNumbers);
|
|
}
|
|
|
|
function pageOptionIsSupported(name) {
|
|
return name === 'wideLayout'
|
|
|| name === 'smallText'
|
|
|| name === 'layoutDensity'
|
|
|| name === 'pageFont'
|
|
|| name === 'showHeadingNumbers'
|
|
|| (name === 'hideTitleHeader' && currentSourceKind() === 'local_folder');
|
|
}
|
|
|
|
function pageOptionDescription(name) {
|
|
if (name === 'wideLayout') return '自适应宽度';
|
|
if (name === 'smallText') return '小字体';
|
|
if (name === 'hideTitleHeader') return '隐藏本地 Markdown 文件标题';
|
|
if (name === 'showToc') return '标题目录';
|
|
if (name === 'showHeadingNumbers') return '标题自动编号';
|
|
if (name === 'protectEditing') return '编辑保护';
|
|
if (name === 'collapseBacklinks') return '折叠反向引用';
|
|
if (name === 'hideChildPages') return '隐藏子页面';
|
|
if (name === 'showBlockRefCount') return '显示块引用数字';
|
|
return name;
|
|
}
|
|
|
|
function pageOptionHint(name) {
|
|
if (name === 'wideLayout') return '已接通:主内容列宽度立即变化';
|
|
if (name === 'smallText') return '已接通:正文排版会更紧凑';
|
|
if (name === 'showHeadingNumbers') return '已接通:标题前显示顺序编号';
|
|
if (name === 'hideTitleHeader') return '已接通:本地 Markdown 页头标题立即隐藏或显示';
|
|
if (name === 'layoutDensity') return '已接通:段落与列表间距会变化';
|
|
if (name === 'pageFont') return '已接通:当前页面字体会切换';
|
|
if (name === 'showToc') return '待接线:当前 Rust 壳还没有正式目录面板';
|
|
if (name === 'protectEditing') return '待接线:当前主编辑器只显示降级说明';
|
|
if (name === 'collapseBacklinks') return '待接线:当前 Rust 壳未挂回链面板';
|
|
if (name === 'hideChildPages') return '待接线:当前页面壳还没有子页面块显隐';
|
|
if (name === 'showBlockRefCount') return '待接线:当前页面壳未显示块引用计数';
|
|
return '待接线';
|
|
}
|
|
|
|
function ensureHistorySnapshotsSeeded() {
|
|
if (pageUiState.historySnapshots.length > 0) return;
|
|
var aggregate = currentPageAggregate();
|
|
var stats = aggregate && aggregate.stats && typeof aggregate.stats === 'object' ? aggregate.stats : {};
|
|
pageUiState.historySnapshots = [{
|
|
id: 'snapshot-initial',
|
|
timestamp: Date.now(),
|
|
stats: {
|
|
wordCount: Number(stats.wordCount || 0),
|
|
characterCount: Number(stats.characterCount || 0),
|
|
blockCount: Number(stats.blockCount || 0),
|
|
todoTotal: Number(stats.todoTotal || 0),
|
|
todoDone: Number(stats.todoDone || 0)
|
|
}
|
|
}];
|
|
}
|
|
|
|
function computeLivePageStats() {
|
|
var aggregate = currentPageAggregate();
|
|
var fallbackStats = aggregate && aggregate.stats && typeof aggregate.stats === 'object' ? aggregate.stats : {};
|
|
var editor = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror');
|
|
if (!(editor instanceof HTMLElement)) {
|
|
return {
|
|
wordCount: Number(fallbackStats.wordCount || 0),
|
|
characterCount: Number(fallbackStats.characterCount || 0),
|
|
blockCount: Number(fallbackStats.blockCount || 0),
|
|
todoTotal: Number(fallbackStats.todoTotal || 0),
|
|
todoDone: Number(fallbackStats.todoDone || 0)
|
|
};
|
|
}
|
|
var text = (editor.textContent || '').trim();
|
|
var compact = text.replace(/\s+/g, ' ').trim();
|
|
var wordCount = compact ? compact.split(' ').filter(Boolean).length : 0;
|
|
var characterCount = text.replace(/\s/g, '').length;
|
|
var blockCount = editor.querySelectorAll(':scope > *').length;
|
|
var todos = Array.from(editor.querySelectorAll('input[type="checkbox"]'));
|
|
return {
|
|
wordCount: wordCount || Number(fallbackStats.wordCount || 0),
|
|
characterCount: characterCount || Number(fallbackStats.characterCount || 0),
|
|
blockCount: blockCount || Number(fallbackStats.blockCount || 0),
|
|
todoTotal: todos.length || Number(fallbackStats.todoTotal || 0),
|
|
todoDone: todos.filter(function(node){ return node.checked; }).length || Number(fallbackStats.todoDone || 0)
|
|
};
|
|
}
|
|
|
|
function recordPageHistorySnapshot(reason, stats) {
|
|
ensureHistorySnapshotsSeeded();
|
|
pageUiState.historySnapshots = [{
|
|
id: 'snapshot-' + Date.now(),
|
|
timestamp: Date.now(),
|
|
reason: reason || 'save',
|
|
stats: stats || computeLivePageStats()
|
|
}].concat(pageUiState.historySnapshots).slice(0, 15);
|
|
renderPageHistoryDrawer();
|
|
}
|
|
|
|
function applyPageOptionsToShell() {
|
|
var options = currentPageOptions();
|
|
var globalShowHeadingNumbers = Boolean(options.showHeadingNumbers);
|
|
var showHeadingNumbers = effectiveShowHeadingNumbers(options);
|
|
var shell = document.querySelector('.document-shell');
|
|
var editorRoot = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
|
|
var editorSurface = editorRoot && editorRoot.querySelector('.editor-surface');
|
|
var mindmapWidthPreference = currentPageWidthPreferences().mindmap || DEFAULT_PAGE_WIDTH_PREFERENCES.mindmap;
|
|
var mindmapMaxWidth = String(mindmapWidthPreference.cssMaxWidth || pageWidthCssMaxWidth(mindmapWidthPreference.resolvedMode));
|
|
var mindmapMaxWidthValue = mindmapMaxWidth === 'none' ? 'none' : mindmapMaxWidth;
|
|
if (shell instanceof HTMLElement) {
|
|
var widthPreference = activeResourceWidthPreference(options);
|
|
var cssMaxWidth = String(widthPreference.cssMaxWidth || pageWidthCssMaxWidth(widthPreference.resolvedMode));
|
|
shell.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout)));
|
|
shell.setAttribute('data-page-width-content-type', activeResourceWidthType() || 'markdown');
|
|
shell.setAttribute('data-page-width-mode', String(widthPreference.mode || 'readable'));
|
|
shell.setAttribute('data-page-width-resolved-mode', String(widthPreference.resolvedMode || widthPreference.mode || 'readable'));
|
|
shell.setAttribute('data-page-width-source', String(widthPreference.source || 'system'));
|
|
shell.setAttribute('data-page-small-text', String(Boolean(options.smallText)));
|
|
shell.setAttribute('data-layout-density', String(options.layoutDensity || 'normal'));
|
|
shell.setAttribute('data-page-font', String(options.pageFont || 'default'));
|
|
shell.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers));
|
|
shell.style.width = '100%';
|
|
shell.style.maxWidth = cssMaxWidth === 'none' ? 'none' : cssMaxWidth;
|
|
shell.style.setProperty('--mnote-mindmap-block-max-width', mindmapMaxWidthValue);
|
|
}
|
|
if (editorRoot instanceof HTMLElement) {
|
|
editorRoot.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout)));
|
|
editorRoot.setAttribute('data-page-small-text', String(Boolean(options.smallText)));
|
|
editorRoot.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers));
|
|
editorRoot.setAttribute('data-page-embed-default-block-id', options.embedDefaultBlockId == null ? '' : String(options.embedDefaultBlockId));
|
|
editorRoot.style.setProperty('--mnote-mindmap-block-max-width', mindmapMaxWidthValue);
|
|
}
|
|
if (editorSurface instanceof HTMLElement) {
|
|
editorSurface.setAttribute('data-layout-density', String(options.layoutDensity || 'normal'));
|
|
editorSurface.setAttribute('data-page-font', String(options.pageFont || 'default'));
|
|
editorSurface.setAttribute('data-show-heading-numbers', String(showHeadingNumbers));
|
|
}
|
|
document.documentElement.setAttribute('data-page-wide-layout', String(Boolean(options.wideLayout)));
|
|
var rootWidthPreference = activeResourceWidthPreference(options);
|
|
document.documentElement.setAttribute('data-page-width-content-type', activeResourceWidthType() || 'markdown');
|
|
document.documentElement.setAttribute('data-page-width-mode', String(rootWidthPreference.mode || 'readable'));
|
|
document.documentElement.setAttribute('data-page-width-resolved-mode', String(rootWidthPreference.resolvedMode || rootWidthPreference.mode || 'readable'));
|
|
document.documentElement.setAttribute('data-page-small-text', String(Boolean(options.smallText)));
|
|
document.documentElement.setAttribute('data-layout-density', String(options.layoutDensity || 'normal'));
|
|
document.documentElement.setAttribute('data-page-font', String(options.pageFont || 'default'));
|
|
document.documentElement.setAttribute('data-global-show-heading-numbers', String(globalShowHeadingNumbers));
|
|
document.documentElement.setAttribute('data-page-show-heading-numbers', String(showHeadingNumbers));
|
|
document.documentElement.setAttribute('data-page-hide-title-header', String(Boolean(options.hideTitleHeader)));
|
|
document.documentElement.style.setProperty('--mnote-mindmap-block-max-width', mindmapMaxWidthValue);
|
|
var titleHeader = document.querySelector('.document-pane[data-pane-role="primary"] .document-shell-header') || document.querySelector('.document-shell-header');
|
|
if (titleHeader instanceof HTMLElement) {
|
|
var hidden = Boolean(options.hideTitleHeader);
|
|
titleHeader.hidden = hidden;
|
|
titleHeader.setAttribute('data-page-title-hidden', String(hidden));
|
|
}
|
|
}
|
|
|
|
function updatePageSettingsTriggerState() {
|
|
var trigger = document.querySelector('[data-testid="wolai-page-settings-trigger"]');
|
|
if (!(trigger instanceof HTMLElement)) return;
|
|
trigger.setAttribute('data-state', pageUiState.pageSettingsOpen ? 'open' : 'closed');
|
|
trigger.setAttribute('aria-expanded', pageUiState.pageSettingsOpen ? 'true' : 'false');
|
|
}
|
|
|
|
function updateStandaloneSettingsTriggerState() {
|
|
var indexTrigger = document.querySelector('[data-testid="mnote-local-index-settings-toggle"]');
|
|
var indexOpen = isLocalIndexSettingsOpen();
|
|
if (indexTrigger instanceof HTMLElement) {
|
|
indexTrigger.setAttribute('data-state', indexOpen ? 'open' : 'closed');
|
|
indexTrigger.setAttribute('aria-expanded', indexOpen ? 'true' : 'false');
|
|
}
|
|
var ragTrigger = document.querySelector('[data-testid="mnote-knowledge-rag-settings-toggle"]');
|
|
var ragOpen = isKnowledgeRagSettingsOpen();
|
|
if (ragTrigger instanceof HTMLElement) {
|
|
ragTrigger.setAttribute('data-state', ragOpen ? 'open' : 'closed');
|
|
ragTrigger.setAttribute('aria-expanded', ragOpen ? 'true' : 'false');
|
|
}
|
|
}
|
|
|
|
function createPageOptionRow(key, type) {
|
|
var inputType = type || 'checkbox';
|
|
var supported = pageOptionIsSupported(key);
|
|
if (inputType === 'checkbox') {
|
|
return '' +
|
|
'<label class="wolai-page-setting-row' + (supported ? '' : ' is-pending') + '" data-page-setting-row="' + key + '">' +
|
|
'<span class="wolai-page-setting-copy">' +
|
|
'<span class="wolai-page-setting-label">' + escapeHtml(pageOptionDescription(key)) + '</span>' +
|
|
'<span class="wolai-page-setting-hint">' + escapeHtml(pageOptionHint(key)) + '</span>' +
|
|
'</span>' +
|
|
'<input type="checkbox" class="wolai-page-setting-checkbox" data-page-option-checkbox="' + key + '"' + (supported ? '' : ' data-setting-pending="true"') + ' />' +
|
|
'</label>';
|
|
}
|
|
return '' +
|
|
'<label class="wolai-page-setting-row" data-page-setting-row="' + key + '">' +
|
|
'<span class="wolai-page-setting-copy">' +
|
|
'<span class="wolai-page-setting-label">' + escapeHtml(pageOptionDescription(key)) + '</span>' +
|
|
'<span class="wolai-page-setting-hint">' + escapeHtml(pageOptionHint(key)) + '</span>' +
|
|
'</span>' +
|
|
'<select class="wolai-page-setting-select" data-page-option-select="' + key + '">' +
|
|
'<option value="compact">紧凑</option>' +
|
|
'<option value="normal">默认</option>' +
|
|
'<option value="spacious">宽松</option>' +
|
|
'</select>' +
|
|
'</label>';
|
|
}
|
|
|
|
function createGlobalHeadingNumbersRow() {
|
|
return '' +
|
|
'<label class="wolai-page-setting-row" data-page-setting-row="globalShowHeadingNumbers">' +
|
|
'<span class="wolai-page-setting-copy">' +
|
|
'<span class="wolai-page-setting-label">标题自动编号</span>' +
|
|
'</span>' +
|
|
'<input type="checkbox" class="wolai-page-setting-checkbox" data-global-option-checkbox="showHeadingNumbers" />' +
|
|
'</label>';
|
|
}
|
|
|
|
function createPageWidthSelectRow(type) {
|
|
var options = type === 'default'
|
|
? [
|
|
['readable', '阅读'],
|
|
['comfortable', '舒适'],
|
|
['wide', '宽版'],
|
|
['full', '全宽']
|
|
]
|
|
: [
|
|
['inherit', '继承默认'],
|
|
['readable', '阅读'],
|
|
['comfortable', '舒适'],
|
|
['wide', '宽版'],
|
|
['full', '全宽']
|
|
];
|
|
return '' +
|
|
'<label class="wolai-page-setting-row" data-page-width-row="' + type + '">' +
|
|
'<span class="wolai-page-setting-copy">' +
|
|
'<span class="wolai-page-setting-label">' + escapeHtml(PAGE_WIDTH_LABELS[type] || type) + '</span>' +
|
|
'<span class="wolai-page-setting-hint" data-page-width-hint="' + type + '"></span>' +
|
|
'</span>' +
|
|
'<select class="wolai-page-setting-select" data-page-width-select="' + type + '">' +
|
|
options.map(function(option) {
|
|
return '<option value="' + option[0] + '">' + option[1] + '</option>';
|
|
}).join('') +
|
|
'</select>' +
|
|
'</label>';
|
|
}
|
|
|
|
function createPageWidthRows() {
|
|
return '' +
|
|
'<div class="wolai-page-settings-global-note">页面宽度</div>' +
|
|
PAGE_WIDTH_TYPES.map(createPageWidthSelectRow).join('');
|
|
}
|
|
|
|
function renderGlobalOptions(popover) {
|
|
var globalHeadingNumbers = readGlobalShowHeadingNumbers();
|
|
popover.querySelectorAll('[data-global-option-checkbox="showHeadingNumbers"]').forEach(function(input) {
|
|
input.checked = globalHeadingNumbers;
|
|
});
|
|
var preferences = currentPageWidthPreferences();
|
|
popover.querySelectorAll('[data-page-width-select]').forEach(function(select) {
|
|
var type = select.getAttribute('data-page-width-select') || '';
|
|
var preference = preferences[type] || DEFAULT_PAGE_WIDTH_PREFERENCES[type] || DEFAULT_PAGE_WIDTH_PREFERENCES.default;
|
|
select.value = String(preference.mode || 'comfortable');
|
|
});
|
|
popover.querySelectorAll('[data-page-width-hint]').forEach(function(node) {
|
|
var type = node.getAttribute('data-page-width-hint') || '';
|
|
var preference = preferences[type] || DEFAULT_PAGE_WIDTH_PREFERENCES[type] || DEFAULT_PAGE_WIDTH_PREFERENCES.default;
|
|
var resolvedMode = String(preference.resolvedMode || preference.mode || 'comfortable');
|
|
node.textContent = pageWidthModeLabel(resolvedMode) + ' · ' + String(preference.cssMaxWidth || pageWidthCssMaxWidth(resolvedMode));
|
|
});
|
|
}
|
|
|
|
function markRetiredLocalOcrPreferenceSurface() {
|
|
document.documentElement.setAttribute('data-mnote-local-ocr-auto-retired', 'true');
|
|
}
|
|
|
|
function createPageFontRow() {
|
|
return '' +
|
|
'<label class="wolai-page-setting-row" data-page-setting-row="pageFont">' +
|
|
'<span class="wolai-page-setting-copy">' +
|
|
'<span class="wolai-page-setting-label">页面字体</span>' +
|
|
'<span class="wolai-page-setting-hint">已接通:仅对当前页面生效</span>' +
|
|
'</span>' +
|
|
'<select class="wolai-page-setting-select" data-page-option-select="pageFont">' +
|
|
'<option value="default">默认</option>' +
|
|
'<option value="song">宋体</option>' +
|
|
'<option value="kai">楷体</option>' +
|
|
'</select>' +
|
|
'</label>';
|
|
}
|
|
|
|
function ensurePageHistoryDrawer() {
|
|
var existing = document.querySelector('[data-testid="wolai-page-history-drawer"]');
|
|
if (existing instanceof HTMLElement) return existing;
|
|
var drawer = document.createElement('aside');
|
|
drawer.className = 'wolai-page-history-drawer';
|
|
drawer.setAttribute('data-testid', 'wolai-page-history-drawer');
|
|
drawer.setAttribute('data-mnote-surface', 'page-history');
|
|
drawer.hidden = true;
|
|
drawer.innerHTML = '' +
|
|
'<div class="wolai-page-history-panel">' +
|
|
'<div class="wolai-page-history-header">' +
|
|
'<div><div class="wolai-page-history-title">页面历史</div><div class="wolai-page-history-subtitle">当前会话内最近保存的 15 个快照。</div></div>' +
|
|
'<button type="button" class="wolai-surface-close" data-page-history-action="close" aria-label="关闭页面历史"><span class="material-symbols-outlined" data-icon="close" aria-hidden="true"></span></button>' +
|
|
'</div>' +
|
|
'<div class="wolai-page-history-list" data-page-history-list></div>' +
|
|
'</div>';
|
|
drawer.addEventListener('click', function(event) {
|
|
if (event.target === drawer) closePageHistoryDrawer();
|
|
});
|
|
document.body.appendChild(drawer);
|
|
return drawer;
|
|
}
|
|
|
|
function renderPageHistoryDrawer() {
|
|
var drawer = ensurePageHistoryDrawer();
|
|
var list = drawer.querySelector('[data-page-history-list]');
|
|
if (!(list instanceof HTMLElement)) return;
|
|
ensureHistorySnapshotsSeeded();
|
|
list.innerHTML = pageUiState.historySnapshots.length
|
|
? pageUiState.historySnapshots.map(function(snapshot) {
|
|
var stats = snapshot.stats || {};
|
|
var label = new Date(snapshot.timestamp).toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
return '' +
|
|
'<div class="wolai-page-history-item">' +
|
|
'<div class="wolai-page-history-item-copy">' +
|
|
'<div class="wolai-page-history-item-title">' + escapeHtml(label) + '</div>' +
|
|
'<div class="wolai-page-history-item-meta">字数 ' + Number(stats.wordCount || 0) + ' · 字符 ' + Number(stats.characterCount || 0) + ' · 块数 ' + Number(stats.blockCount || 0) + '</div>' +
|
|
'</div>' +
|
|
'<button type="button" class="wolai-page-history-item-ghost" data-page-history-action="noop">仅查看</button>' +
|
|
'</div>';
|
|
}).join('')
|
|
: '<div class="wolai-page-history-empty">尚未产生历史快照,编辑后会自动生成。</div>';
|
|
}
|
|
|
|
function openPageHistoryDrawer() {
|
|
renderPageHistoryDrawer();
|
|
var drawer = ensurePageHistoryDrawer();
|
|
drawer.hidden = false;
|
|
document.documentElement.setAttribute('data-mnote-page-history-open', 'true');
|
|
}
|
|
|
|
function closePageHistoryDrawer() {
|
|
var drawer = document.querySelector('[data-testid="wolai-page-history-drawer"]');
|
|
if (drawer instanceof HTMLElement) drawer.hidden = true;
|
|
document.documentElement.removeAttribute('data-mnote-page-history-open');
|
|
}
|
|
|
|
function ensurePageShareDialog() {
|
|
var existing = document.querySelector('[data-testid="wolai-page-share-dialog"]');
|
|
if (existing instanceof HTMLElement) return existing;
|
|
var dialog = document.createElement('div');
|
|
dialog.className = 'wolai-page-share-dialog';
|
|
dialog.setAttribute('data-testid', 'wolai-page-share-dialog');
|
|
dialog.setAttribute('data-mnote-surface', 'page-share');
|
|
dialog.hidden = true;
|
|
dialog.innerHTML = '' +
|
|
'<div class="wolai-page-share-card" role="dialog" aria-modal="true">' +
|
|
'<div class="wolai-page-share-header">' +
|
|
'<div><div class="wolai-page-share-title">公开分享页面</div><div class="wolai-page-share-subtitle">当前 3000 公开入口由 mnote-web 持有。</div></div>' +
|
|
'<button type="button" class="wolai-surface-close" data-page-share-action="close" aria-label="关闭公开分享页面"><span class="material-symbols-outlined" data-icon="close" aria-hidden="true"></span></button>' +
|
|
'</div>' +
|
|
'<div class="wolai-page-share-state">' +
|
|
'<span class="wolai-public-pill wolai-public-pill--inline">全网公开</span>' +
|
|
'<span class="wolai-page-share-copy">任何拥有链接的人都可以访问当前页面。</span>' +
|
|
'</div>' +
|
|
'<div class="wolai-page-share-url-row">' +
|
|
'<input class="wolai-page-share-url" type="text" readonly data-page-share-url value="" />' +
|
|
'<button type="button" class="wolai-page-share-copy-button" data-page-share-action="copy-link">复制链接</button>' +
|
|
'</div>' +
|
|
'<div class="wolai-page-share-footer">更多共享者、群组公开和权限策略仍待接线。</div>' +
|
|
'</div>';
|
|
dialog.addEventListener('click', function(event) {
|
|
if (event.target === dialog) closePageShareDialog();
|
|
});
|
|
document.body.appendChild(dialog);
|
|
return dialog;
|
|
}
|
|
|
|
function openPageShareDialog() {
|
|
var dialog = ensurePageShareDialog();
|
|
var input = dialog.querySelector('[data-page-share-url]');
|
|
if (input instanceof HTMLInputElement) input.value = window.location.href;
|
|
dialog.hidden = false;
|
|
document.documentElement.setAttribute('data-mnote-page-share-open', 'true');
|
|
}
|
|
|
|
function closePageShareDialog() {
|
|
var dialog = document.querySelector('[data-testid="wolai-page-share-dialog"]');
|
|
if (dialog instanceof HTMLElement) dialog.hidden = true;
|
|
document.documentElement.removeAttribute('data-mnote-page-share-open');
|
|
}
|
|
|
|
function ensurePageSettingsPopover() {
|
|
var existing = document.querySelector('[data-testid="wolai-page-settings-popover"]');
|
|
if (existing instanceof HTMLElement) return existing;
|
|
var popover = document.createElement('div');
|
|
popover.className = 'wolai-page-settings-popover';
|
|
popover.setAttribute('data-testid', 'wolai-page-settings-popover');
|
|
popover.setAttribute('data-mnote-surface', 'page-settings');
|
|
popover.hidden = true;
|
|
popover.innerHTML = '' +
|
|
'<div class="wolai-page-settings-panel" role="dialog" aria-modal="false">' +
|
|
'<div class="wolai-page-settings-tabs" data-testid="wolai-page-settings-tabs" role="tablist" aria-label="页面设置分组">' +
|
|
'<button type="button" class="wolai-page-settings-tab is-active" role="tab" aria-selected="true" data-page-settings-tab="page">页面选项</button>' +
|
|
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="custom">自定义页面</button>' +
|
|
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="global">全局选项</button>' +
|
|
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="extensions">扩展</button>' +
|
|
'<button type="button" class="wolai-page-settings-tab" role="tab" aria-selected="false" data-page-settings-tab="dashboard">仪表盘</button>' +
|
|
'</div>' +
|
|
'<div class="wolai-page-settings-section" data-page-settings-panel="page">' +
|
|
createPageOptionRow('wideLayout', 'checkbox') +
|
|
createPageOptionRow('smallText', 'checkbox') +
|
|
createPageOptionRow('showToc', 'checkbox') +
|
|
createPageOptionRow('protectEditing', 'checkbox') +
|
|
createPageOptionRow('hideTitleHeader', 'checkbox') +
|
|
'</div>' +
|
|
'<div class="wolai-page-settings-section" data-page-settings-panel="custom" hidden>' +
|
|
createPageFontRow() +
|
|
createPageOptionRow('layoutDensity', 'select') +
|
|
createPageOptionRow('collapseBacklinks', 'checkbox') +
|
|
createPageOptionRow('hideChildPages', 'checkbox') +
|
|
createPageOptionRow('showBlockRefCount', 'checkbox') +
|
|
'</div>' +
|
|
'<div class="wolai-page-settings-section" data-page-settings-panel="global" hidden>' +
|
|
createGlobalHeadingNumbersRow() +
|
|
createPageWidthRows() +
|
|
'</div>' +
|
|
'<div class="wolai-page-settings-section mnote-extensions-panel" data-page-settings-panel="extensions" hidden>' +
|
|
'<div class="mnote-extensions-subtabs" role="tablist" aria-label="扩展子面板">' +
|
|
'<button type="button" class="mnote-extensions-subtab is-active" role="tab" aria-selected="true" data-extensions-subtab="knowledge">知识库</button>' +
|
|
'<button type="button" class="mnote-extensions-subtab" role="tab" aria-selected="false" data-extensions-subtab="skills">Skills</button>' +
|
|
'<button type="button" class="mnote-extensions-subtab" role="tab" aria-selected="false" data-extensions-subtab="tools">Agent 工具</button>' +
|
|
'</div>' +
|
|
'<div class="mnote-extensions-subpanel" data-extensions-panel="knowledge">' +
|
|
'<div class="mnote-extensions-status" data-extensions-status="knowledge"><span class="mnote-extensions-loading">加载中...</span></div>' +
|
|
'</div>' +
|
|
'<div class="mnote-extensions-subpanel" data-extensions-panel="skills" hidden>' +
|
|
'<div class="mnote-extensions-status" data-extensions-status="skills"><span class="mnote-extensions-loading">加载中...</span></div>' +
|
|
'</div>' +
|
|
'<div class="mnote-extensions-subpanel" data-extensions-panel="tools" hidden>' +
|
|
'<div class="mnote-extensions-status" data-extensions-status="tools"><span class="mnote-extensions-loading">加载中...</span></div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="wolai-page-settings-section mnote-dashboard-panel" data-page-settings-panel="dashboard" hidden>' +
|
|
'<div class="mnote-dashboard-grid">' +
|
|
'<div class="mnote-dashboard-card" data-dashboard-card="knowledge">' +
|
|
'<div class="mnote-dashboard-card-title">知识库</div>' +
|
|
'<div class="mnote-dashboard-card-value" data-dashboard-value="knowledge">--</div>' +
|
|
'<div class="mnote-dashboard-card-desc">已索引文件 / 状态</div>' +
|
|
'</div>' +
|
|
'<div class="mnote-dashboard-card" data-dashboard-card="skills">' +
|
|
'<div class="mnote-dashboard-card-title">Skills</div>' +
|
|
'<div class="mnote-dashboard-card-value" data-dashboard-value="skills">--</div>' +
|
|
'<div class="mnote-dashboard-card-desc">工具清单中的 skill 能力</div>' +
|
|
'</div>' +
|
|
'<div class="mnote-dashboard-card" data-dashboard-card="tools">' +
|
|
'<div class="mnote-dashboard-card-title">Agent 工具</div>' +
|
|
'<div class="mnote-dashboard-card-value" data-dashboard-value="tools">--</div>' +
|
|
'<div class="mnote-dashboard-card-desc">/api/mnote/tools/manifest</div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<button type="button" class="wolai-page-settings-index-add" style="margin-top:10px" data-dashboard-action="refresh">刷新仪表盘</button>' +
|
|
'</div>' +
|
|
'<div class="wolai-page-settings-actions">' +
|
|
'<button type="button" class="wolai-page-settings-action" data-page-settings-action="history">页面历史...</button>' +
|
|
'<button type="button" class="wolai-page-settings-action" data-page-settings-action="share">公开分享页面...</button>' +
|
|
'<button type="button" class="wolai-page-settings-action is-disabled" data-page-settings-action="move" disabled>移动到...</button>' +
|
|
'<button type="button" class="wolai-page-settings-action is-disabled" data-page-settings-action="embed" disabled>嵌入到...</button>' +
|
|
'<button type="button" class="wolai-page-settings-action is-danger is-disabled" data-page-settings-action="delete" disabled>删除页面</button>' +
|
|
'</div>' +
|
|
'<div class="wolai-page-settings-stats" data-testid="wolai-page-settings-stats"></div>' +
|
|
'</div>';
|
|
document.body.appendChild(popover);
|
|
return popover;
|
|
}
|
|
|
|
function createLocalIndexSettingsPanelHtml() {
|
|
return '' +
|
|
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-local-index-settings-panel" role="dialog" aria-modal="false" aria-label="索引设置">' +
|
|
'<div class="mnote-settings-panel-head">' +
|
|
'<strong>索引设置</strong>' +
|
|
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭索引设置"><span class="material-symbols-outlined" data-icon="close" aria-hidden="true"></span></button>' +
|
|
'</div>' +
|
|
'<div class="wolai-page-settings-index-panel" data-testid="wolai-page-settings-local-index-panel">' +
|
|
'<div class="wolai-page-settings-index-status" data-testid="wolai-page-settings-local-index-status"></div>' +
|
|
'<section class="wolai-page-settings-index-group">' +
|
|
'<div class="wolai-page-settings-index-title">索引范围</div>' +
|
|
'<div class="wolai-page-settings-index-range-list" data-testid="wolai-page-settings-local-index-ranges"></div>' +
|
|
'<button type="button" class="wolai-page-settings-index-add" data-local-index-action="add-path">新增范围</button>' +
|
|
'<div class="wolai-page-settings-index-schedule">' +
|
|
'<label><span>索引时间</span><select data-testid="wolai-page-settings-local-index-schedule-mode"><option value="daily">每日</option><option value="once">指定日期</option><option value="manual">手动</option></select></label>' +
|
|
'<label><span>时间</span><input type="time" data-testid="wolai-page-settings-local-index-schedule-time" value="02:00"></label>' +
|
|
'<label><span>日期</span><input type="date" data-testid="wolai-page-settings-local-index-schedule-date"></label>' +
|
|
'<label class="wolai-page-settings-index-inline"><input type="checkbox" data-testid="wolai-page-settings-local-index-run-on-change"><span>文档变化时立即索引</span></label>' +
|
|
'</div>' +
|
|
'<div class="wolai-page-settings-index-actions">' +
|
|
'<button type="button" data-local-index-action="save">保存设置</button>' +
|
|
'<button type="button" data-local-index-action="refresh">重建索引</button>' +
|
|
'</div>' +
|
|
'</section>' +
|
|
'</div>' +
|
|
'</div>';
|
|
}
|
|
|
|
function ensureLocalIndexSettingsPopover() {
|
|
var existing = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
|
|
if (existing instanceof HTMLElement) return existing;
|
|
var popover = document.createElement('div');
|
|
popover.className = 'wolai-page-settings-popover mnote-local-index-settings-popover';
|
|
popover.setAttribute('data-testid', 'mnote-local-index-settings-popover');
|
|
popover.setAttribute('data-mnote-surface', 'local-index-settings');
|
|
popover.hidden = true;
|
|
popover.innerHTML = createLocalIndexSettingsPanelHtml();
|
|
document.body.appendChild(popover);
|
|
return popover;
|
|
}
|
|
|
|
function createKnowledgeRagSettingsPanelHtml() {
|
|
return createKnowledgeRagSettingsPanelHtmlV2();
|
|
}
|
|
|
|
function createKnowledgeRagSettingsPanelHtmlV2() {
|
|
return '' +
|
|
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-knowledge-rag-settings-panel mnote-weknora-kb-page" role="dialog" aria-modal="false" aria-label="LightRAG 知识库" data-testid="mnote-weknora-knowledge-settings-panel" data-knowledge-rag-default-provider="lightrag" data-knowledge-rag-legacy-fallback-provider="lightrag" data-knowledge-rag-source-registry="mnote" data-knowledge-rag-provider-index="lightrag-source-registry" data-mnote-weknora-page-replacement="false">' +
|
|
'<div class="mnote-weknora-kb-page-header">' +
|
|
'<div class="mnote-weknora-kb-page-title">' +
|
|
'<span class="mnote-weknora-kb-page-eyebrow">LightRAG</span>' +
|
|
'<strong>知识库</strong>' +
|
|
'<span>资料、索引状态和检索</span>' +
|
|
'</div>' +
|
|
'<div class="mnote-weknora-kb-page-actions">' +
|
|
'<button type="button" data-knowledge-rag-action="refresh" title="刷新"><span class="material-symbols-outlined" data-icon="refresh" aria-hidden="true"></span></button>' +
|
|
'<button type="button" data-knowledge-rag-action="open-dashboard" title="打开知识库" aria-label="打开知识库"><span class="material-symbols-outlined" data-icon="open_in_new" aria-hidden="true"></span></button>' +
|
|
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭知识库"><span class="material-symbols-outlined" data-icon="close" aria-hidden="true"></span></button>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="mnote-weknora-kb-page-status" data-testid="mnote-knowledge-rag-status"></div>' +
|
|
'<div class="mnote-weknora-kb-page-body" data-kb-list-hidden="true">' +
|
|
'<aside class="mnote-weknora-kb-list-pane" data-testid="mnote-weknora-kb-list-pane" hidden>' +
|
|
'<div class="mnote-weknora-kb-list-head">' +
|
|
'<div><strong>资料库</strong><span data-testid="mnote-weknora-kb-list-count">0</span></div>' +
|
|
'<button type="button" data-knowledge-rag-action="toggle-create-kb" title="新建资料库"><span class="material-symbols-outlined" data-icon="create_new_folder" aria-hidden="true"></span></button>' +
|
|
'</div>' +
|
|
'<div class="mnote-weknora-kb-card-wrap" data-testid="mnote-weknora-kb-card-list"></div>' +
|
|
'</aside>' +
|
|
'<section class="mnote-weknora-kb-detail-pane" data-testid="mnote-weknora-kb-detail-pane">' +
|
|
'<div class="mnote-weknora-kb-detail-hero" data-testid="mnote-weknora-kb-detail-hero">' +
|
|
'<div>' +
|
|
'<nav class="mnote-weknora-kb-breadcrumb" data-testid="mnote-weknora-kb-breadcrumb" aria-label="知识库面包屑"><span>知识库</span><span>/</span><strong data-testid="mnote-weknora-kb-breadcrumb-current">LightRAG 资料库</strong></nav>' +
|
|
'<span class="mnote-weknora-kb-type-pill"><span class="material-symbols-outlined" data-icon="folder" aria-hidden="true"></span>资料来源</span>' +
|
|
'<h2 data-testid="mnote-weknora-kb-detail-title">LightRAG 资料库</h2>' +
|
|
'<p data-testid="mnote-weknora-kb-detail-description">MNote 管理本地来源、引用定位和权限边界,LightRAG 负责索引与召回。</p>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<section class="mnote-knowledge-rag-kb-manager" data-testid="mnote-knowledge-rag-kb-manager">' +
|
|
'<div class="mnote-knowledge-rag-kb-manager-row">' +
|
|
'<label class="mnote-knowledge-rag-kb-select-field"><span>当前资料库</span><select class="mnote-kb-params-select" data-testid="mnote-weknora-kb-select" data-knowledge-rag-kb-select="true"><option value="">加载中...</option></select></label>' +
|
|
'<button type="button" data-knowledge-rag-action="toggle-create-kb"><span class="material-symbols-outlined" data-icon="folder_add" aria-hidden="true"></span>新建</button>' +
|
|
'<button type="button" data-knowledge-rag-action="delete-kb" data-knowledge-rag-delete-selected-kb="true"><span class="material-symbols-outlined" data-icon="delete" aria-hidden="true"></span>删除</button>' +
|
|
'</div>' +
|
|
'<section class="mnote-weknora-kb-create-card" data-testid="mnote-weknora-kb-controls" hidden>' +
|
|
'<div class="mnote-weknora-kb-create-title"><span class="material-symbols-outlined" data-icon="folder_add" aria-hidden="true"></span><strong>新建资料库</strong></div>' +
|
|
'<input type="text" class="wolai-page-settings-index-path" data-testid="mnote-weknora-create-kb-name" data-knowledge-rag-create-kb-name="true" placeholder="知识库名称" spellcheck="false" />' +
|
|
'<input type="text" class="wolai-page-settings-index-path" data-testid="mnote-weknora-create-kb-description" data-knowledge-rag-create-kb-description="true" placeholder="描述,可留空" spellcheck="false" />' +
|
|
'<button type="button" class="wolai-page-settings-index-add" data-knowledge-rag-action="create-kb">创建知识库</button>' +
|
|
'<span class="mnote-knowledge-rag-input-status" data-testid="mnote-weknora-create-kb-status" data-knowledge-rag-create-kb-status hidden></span>' +
|
|
'</section>' +
|
|
'<div class="mnote-knowledge-rag-kb-manager-note" data-testid="mnote-knowledge-rag-kb-manager-note"></div>' +
|
|
'</section>' +
|
|
'<details class="mnote-knowledge-rag-diagnostics" data-testid="mnote-knowledge-rag-diagnostics">' +
|
|
'<summary>诊断信息</summary>' +
|
|
'<div class="mnote-knowledge-rag-meta" data-testid="mnote-knowledge-rag-meta"></div>' +
|
|
'</details>' +
|
|
'<div class="mnote-knowledge-rag-detail-tabs" role="tablist" aria-label="当前资料库内容">' +
|
|
'<button type="button" class="mnote-knowledge-rag-detail-tab is-active" role="tab" aria-selected="true" data-kb-rag-tab="sources" data-testid="mnote-weknora-tab-documents">资料</button>' +
|
|
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="wiki" data-testid="mnote-weknora-tab-wiki" hidden>Wiki</button>' +
|
|
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="graph" data-testid="mnote-weknora-tab-graph" hidden>Graph</button>' +
|
|
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="search">检索</button>' +
|
|
'</div>' +
|
|
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="sources">' +
|
|
'<div class="mnote-weknora-doc-layout" data-testid="mnote-weknora-document-management">' +
|
|
'<aside class="mnote-weknora-doc-sidebar" data-testid="mnote-weknora-doc-source-sidebar" hidden>' +
|
|
'<div class="mnote-weknora-doc-sidebar-head"><strong>来源</strong><span>MNote registry</span></div>' +
|
|
'<div class="mnote-weknora-doc-source-tags" data-testid="mnote-weknora-doc-source-tags">' +
|
|
'<button type="button" data-knowledge-rag-action="filter-sources" data-knowledge-rag-filter="all" data-active="true"><span class="material-symbols-outlined" data-icon="folder" aria-hidden="true"></span>全部资料</button>' +
|
|
'<button type="button" data-knowledge-rag-action="filter-sources" data-knowledge-rag-filter="indexed"><span class="material-symbols-outlined" data-icon="check_circle" aria-hidden="true"></span>已索引</button>' +
|
|
'<button type="button" data-knowledge-rag-action="filter-sources" data-knowledge-rag-filter="indexing"><span class="material-symbols-outlined" data-icon="sync" aria-hidden="true"></span>处理中</button>' +
|
|
'</div>' +
|
|
'<div class="mnote-weknora-doc-sidebar-head"><strong>标签</strong><span>MNote views</span></div>' +
|
|
'<div class="mnote-weknora-doc-tags" data-testid="mnote-weknora-doc-tags"><span>PDF</span><span>Markdown</span><span>Folder</span><span>Citation</span></div>' +
|
|
'</aside>' +
|
|
'<section class="mnote-weknora-doc-main">' +
|
|
'<div class="mnote-weknora-doc-toolbar">' +
|
|
'<div class="mnote-weknora-doc-search"><span class="material-symbols-outlined" data-icon="search" aria-hidden="true"></span><input type="text" data-testid="mnote-weknora-doc-filter-input" data-knowledge-rag-doc-filter-input="true" placeholder="搜索或筛选当前资料库内容" spellcheck="false" /></div>' +
|
|
'<div class="mnote-weknora-view-switch" role="group" aria-label="文档视图切换" data-testid="mnote-weknora-view-switch">' +
|
|
'<button type="button" class="is-active" data-knowledge-rag-view="list" aria-pressed="true" title="列表视图"><span class="material-symbols-outlined" data-icon="view_list" aria-hidden="true"></span></button>' +
|
|
'<button type="button" data-knowledge-rag-view="grid" aria-pressed="false" title="网格视图"><span class="material-symbols-outlined" data-icon="grid_view" aria-hidden="true"></span></button>' +
|
|
'</div>' +
|
|
'<button type="button" data-knowledge-rag-action="use-filetree-selection"><span class="material-symbols-outlined" data-icon="account_tree" aria-hidden="true"></span>从文件树添加</button>' +
|
|
'<button type="button" class="mnote-knowledge-rag-primary-action" data-knowledge-rag-action="add-source"><span class="material-symbols-outlined" data-icon="upload_file" aria-hidden="true"></span>添加资料</button>' +
|
|
'<button type="button" data-knowledge-rag-action="ingest-active"><span class="material-symbols-outlined" data-icon="note_add" aria-hidden="true"></span>加入当前页</button>' +
|
|
'</div>' +
|
|
'<div class="mnote-knowledge-rag-source-composer" data-knowledge-rag-source-composer="true" hidden>' +
|
|
'<div class="mnote-knowledge-rag-source-inputs" data-testid="mnote-knowledge-rag-source-inputs">' +
|
|
knowledgeRagSourceInputRow('') +
|
|
'</div>' +
|
|
'<div class="mnote-weknora-doc-actions">' +
|
|
'<button type="button" class="mnote-knowledge-rag-primary-action" data-knowledge-rag-action="ingest">开始索引</button>' +
|
|
'<button type="button" data-knowledge-rag-action="prune">清除失效记录</button>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="mnote-knowledge-rag-source-filters" data-testid="mnote-knowledge-rag-source-filters"></div>' +
|
|
'<div class="mnote-weknora-doc-table" data-testid="mnote-knowledge-rag-sources" data-weknora-document-list-view="true"></div>' +
|
|
'</section>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="wiki" hidden>' +
|
|
'<div class="mnote-weknora-placeholder-panel" data-testid="mnote-weknora-wiki-panel">' +
|
|
'<strong>Wiki</strong>' +
|
|
'<span>后续可基于 LightRAG 召回结果生成知识页;当前以 MNote open-reference 边界为准。</span>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="graph" hidden>' +
|
|
'<div class="mnote-weknora-placeholder-panel" data-testid="mnote-weknora-graph-panel">' +
|
|
'<strong>Graph</strong>' +
|
|
'<span>展示 LightRAG 图谱和文档关系入口,本地文件真相仍由 MNote source registry 管理。</span>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="search" hidden>' +
|
|
'<div class="mnote-kb-search-form">' +
|
|
'<div class="mnote-kb-search-box"><span class="material-symbols-outlined" data-icon="search" aria-hidden="true"></span><input type="text" class="mnote-kb-search-input" data-kb-rag-search-input="true" placeholder="在当前资料库中检索关键词" spellcheck="false"></div>' +
|
|
'<button type="button" class="wolai-page-settings-index-add" data-kb-rag-action="search"><span class="material-symbols-outlined" data-icon="search" aria-hidden="true"></span>检索</button>' +
|
|
'</div>' +
|
|
'<div class="mnote-kb-search-results" data-kb-rag-search-results="true">' +
|
|
'<div class="mnote-kb-search-empty">输入关键词后检索当前资料库</div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="params" hidden>' +
|
|
'<div class="mnote-kb-params-form">' +
|
|
'<div class="mnote-kb-params-row">' +
|
|
'<label class="mnote-kb-params-label">Provider 检索策略</label>' +
|
|
'<select class="mnote-kb-params-select" data-kb-rag-param="mode">' +
|
|
'<option value="hybrid" selected>LightRAG hybrid</option>' +
|
|
'<option value="vector">向量召回</option>' +
|
|
'<option value="keyword">关键词召回</option>' +
|
|
'<option value="rerank">Hybrid + rerank</option>' +
|
|
'</select>' +
|
|
'</div>' +
|
|
'<div class="mnote-kb-params-row">' +
|
|
'<label class="mnote-kb-params-label">返回数量 (Top-K)</label>' +
|
|
'<input type="number" class="mnote-kb-params-number" data-kb-rag-param="top_k" value="10" min="1" max="100">' +
|
|
'</div>' +
|
|
'<div class="mnote-kb-params-row">' +
|
|
'<label class="mnote-kb-params-label">排序说明</label>' +
|
|
'<span class="mnote-kb-params-help">结果按相关度排序;高级阈值保留在请求层。</span>' +
|
|
'</div>' +
|
|
'<button type="button" class="wolai-page-settings-index-add" data-kb-rag-action="save-params">保存参数</button>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</section>' +
|
|
'</div>' +
|
|
'</div>';
|
|
}
|
|
|
|
function ensureKnowledgeRagSettingsPopover() {
|
|
var existing = document.querySelector('[data-testid="mnote-knowledge-rag-settings-popover"]');
|
|
if (existing instanceof HTMLElement) return existing;
|
|
var popover = document.createElement('div');
|
|
popover.className = 'wolai-page-settings-popover mnote-knowledge-rag-settings-popover';
|
|
popover.setAttribute('data-testid', 'mnote-knowledge-rag-settings-popover');
|
|
popover.setAttribute('data-mnote-surface', 'knowledge-rag-settings');
|
|
popover.hidden = true;
|
|
popover.innerHTML = createKnowledgeRagSettingsPanelHtml();
|
|
document.body.appendChild(popover);
|
|
return popover;
|
|
}
|
|
|
|
function pageSettingsLocalIndexScopeKey() {
|
|
return [
|
|
currentSourceKind(),
|
|
currentRootUri(),
|
|
resolveWorkspaceId(document.body),
|
|
currentDocumentId()
|
|
].join('|');
|
|
}
|
|
|
|
function pageSettingsLocalIndexIsAvailable() {
|
|
return currentSourceKind() === 'local_folder' && Boolean(currentRootUri());
|
|
}
|
|
|
|
function pageSettingsLocalIndexEmpty(message) {
|
|
return '<div class="wolai-page-settings-index-empty">' + escapeHtml(message) + '</div>';
|
|
}
|
|
|
|
function renderPageSettingsLocalIndex(popover) {
|
|
popover = popover || ensureLocalIndexSettingsPopover();
|
|
var statusNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-status"]');
|
|
var rangesNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-ranges"]');
|
|
var scheduleModeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-mode"]');
|
|
var scheduleTimeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-time"]');
|
|
var scheduleDateNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-date"]');
|
|
var runOnChangeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-run-on-change"]');
|
|
if (!(statusNode instanceof HTMLElement)) return;
|
|
|
|
if (!pageSettingsLocalIndexIsAvailable()) {
|
|
statusNode.textContent = '本地索引仅在本地工作区可用';
|
|
renderLocalIndexRangeRows(popover, [], 'fault', true, {});
|
|
setLocalIndexScheduleControlsDisabled(true);
|
|
return;
|
|
}
|
|
|
|
var summary = pageUiState.localIndexSummary || {};
|
|
if (summary.loading) {
|
|
statusNode.textContent = '正在读取本地索引...';
|
|
renderLocalIndexRangeRows(popover, currentLocalIndexRangeValues(popover), 'indexing', true, summary.status || {});
|
|
setLocalIndexScheduleControlsDisabled(true);
|
|
return;
|
|
}
|
|
if (summary.error) {
|
|
statusNode.textContent = '本地索引读取失败';
|
|
renderLocalIndexRangeRows(popover, currentLocalIndexRangeValues(popover), 'fault', false, summary.status || {});
|
|
setLocalIndexScheduleControlsDisabled(false);
|
|
return;
|
|
}
|
|
if (summary.scopeKey !== pageSettingsLocalIndexScopeKey()) {
|
|
statusNode.textContent = '打开索引设置后读取本地索引';
|
|
renderLocalIndexRangeRows(popover, [], 'indexing', true, {});
|
|
setLocalIndexScheduleControlsDisabled(true);
|
|
return;
|
|
}
|
|
|
|
var status = summary.status || {};
|
|
var settings = status.settings && typeof status.settings === 'object' ? status.settings : {};
|
|
var savedIncludePaths = Array.isArray(settings.includePaths) ? settings.includePaths : [];
|
|
var indexedPaths = Array.isArray(status.indexedPaths) ? status.indexedPaths : [];
|
|
var includePaths = savedIncludePaths.length ? savedIncludePaths : indexedPaths;
|
|
var hasIndexCache = status.indexExists === true || status.evidenceIndexExists === true;
|
|
if (!savedIncludePaths.length && !hasIndexCache) {
|
|
statusNode.textContent = '未设置索引范围';
|
|
} else {
|
|
statusNode.textContent = '索引 ' + Number(status.documentCount || 0) + ' 个页面 / ' + Number(status.resourceCount || 0) + ' 个资源 / ' + Number(status.evidenceBlockCount || 0) + ' 个证据块' + (status.scheduledDue ? ',已到计划时间' : '');
|
|
}
|
|
if (!(rangesNode instanceof HTMLElement) || !rangesNode.contains(document.activeElement)) {
|
|
renderLocalIndexRangeRows(popover, includePaths, localIndexStatusKind(status), false, status);
|
|
}
|
|
if (scheduleModeNode instanceof HTMLSelectElement && document.activeElement !== scheduleModeNode) {
|
|
scheduleModeNode.value = String(settings.scheduleMode || 'daily');
|
|
}
|
|
if (scheduleTimeNode instanceof HTMLInputElement && document.activeElement !== scheduleTimeNode) {
|
|
scheduleTimeNode.value = String(settings.scheduleTime || '02:00');
|
|
}
|
|
if (scheduleDateNode instanceof HTMLInputElement && document.activeElement !== scheduleDateNode) {
|
|
scheduleDateNode.value = String(settings.scheduleDate || '');
|
|
}
|
|
if (runOnChangeNode instanceof HTMLInputElement) {
|
|
runOnChangeNode.checked = settings.runOnChange === true;
|
|
}
|
|
setLocalIndexScheduleControlsDisabled(false);
|
|
}
|
|
|
|
function localIndexStatusKind(status) {
|
|
if (!status || typeof status !== 'object') return 'fault';
|
|
var settings = status.settings && typeof status.settings === 'object' ? status.settings : {};
|
|
var includePaths = Array.isArray(settings.includePaths) ? settings.includePaths : [];
|
|
if (!includePaths.length && status.indexExists !== true && status.evidenceIndexExists !== true) return 'indexed';
|
|
if (status.indexExists !== true) return 'fault';
|
|
if (status.cacheMatchesSettings === true && status.scheduledDue !== true) return 'indexed';
|
|
return 'indexing';
|
|
}
|
|
|
|
function localIndexStatusLabel(kind) {
|
|
if (kind === 'indexed') return '已索引';
|
|
if (kind === 'indexing') return '索引中';
|
|
return '索引故障';
|
|
}
|
|
|
|
function localIndexPathStatusKind(path, status, fallbackKind) {
|
|
var indexedPaths = Array.isArray(status && status.indexedPaths) ? status.indexedPaths : [];
|
|
var normalizedPath = String(path || '').trim() || '.';
|
|
if (indexedPaths.indexOf(normalizedPath) >= 0 && status.indexExists === true) return 'indexed';
|
|
if (fallbackKind === 'fault' || fallbackKind === 'indexing') return fallbackKind;
|
|
if (status.indexExists === true) return 'indexing';
|
|
return 'fault';
|
|
}
|
|
|
|
function renderLocalIndexRangeRows(popover, includePaths, fallbackKind, disabled, status) {
|
|
var rangesNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-ranges"]');
|
|
if (!(rangesNode instanceof HTMLElement)) return;
|
|
var rawPaths = Array.isArray(includePaths) ? includePaths.map(function(value) {
|
|
return String(value || '').trim();
|
|
}) : [];
|
|
var paths = disabled ? rawPaths.filter(Boolean) : rawPaths;
|
|
var savedPaths = Array.isArray(status && status.settings && status.settings.includePaths)
|
|
? status.settings.includePaths.map(function(value) { return String(value || '').trim(); }).filter(Boolean)
|
|
: [];
|
|
var indexedPaths = Array.isArray(status && status.indexedPaths)
|
|
? status.indexedPaths.map(function(value) { return String(value || '').trim(); }).filter(Boolean)
|
|
: [];
|
|
rangesNode.innerHTML = paths.map(function(path, index) {
|
|
var isPersisted = savedPaths.indexOf(path) >= 0 || indexedPaths.indexOf(path) >= 0;
|
|
var inputDisabled = disabled || isPersisted;
|
|
var kind = localIndexPathStatusKind(path, status || {}, fallbackKind || 'fault');
|
|
var normalizedPath = String(path || '').trim() || '.';
|
|
var displayPath = inputDisabled && normalizedPath === '.' ? '工作区根目录(全部)' : path;
|
|
var rootScopeHint = normalizedPath === '.'
|
|
? '<span class="wolai-page-settings-index-root-hint">当前工作区全部</span>'
|
|
: '';
|
|
return '' +
|
|
'<div class="wolai-page-settings-index-range-row" data-local-index-range-row="true">' +
|
|
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + kind + '" title="' + escapeHtml(localIndexStatusLabel(kind)) + '"></span>' +
|
|
'<input type="text" class="wolai-page-settings-index-path" data-local-index-range-input="true" data-local-index-value="' + escapeHtml(normalizedPath) + '" value="' + escapeHtml(displayPath) + '" spellcheck="false"' + (inputDisabled ? ' disabled' : '') + (isPersisted ? ' data-local-index-persisted="true" title="已保存的索引目录不能直接修改;删除后重新新增范围"' : '') + ' />' +
|
|
rootScopeHint +
|
|
'<button type="button" class="wolai-page-settings-index-remove" data-local-index-action="remove-path" data-local-index-path-index="' + String(index) + '"' + (disabled ? ' disabled' : '') + ' aria-label="删除索引范围"><span class="material-symbols-outlined" data-icon="close" aria-hidden="true"></span></button>' +
|
|
'</div>';
|
|
}).join('');
|
|
}
|
|
|
|
function setLocalIndexScheduleControlsDisabled(disabled) {
|
|
var popover = ensureLocalIndexSettingsPopover();
|
|
[
|
|
'[data-testid="wolai-page-settings-local-index-schedule-mode"]',
|
|
'[data-testid="wolai-page-settings-local-index-schedule-time"]',
|
|
'[data-testid="wolai-page-settings-local-index-schedule-date"]',
|
|
'[data-testid="wolai-page-settings-local-index-run-on-change"]'
|
|
].forEach(function(selector) {
|
|
var node = popover.querySelector(selector);
|
|
if (node instanceof HTMLInputElement || node instanceof HTMLSelectElement) node.disabled = disabled;
|
|
});
|
|
}
|
|
|
|
async function loadPageSettingsLocalIndex(force) {
|
|
if (!pageSettingsLocalIndexIsAvailable()) {
|
|
renderPageSettingsLocalIndex();
|
|
return;
|
|
}
|
|
var scopeKey = pageSettingsLocalIndexScopeKey();
|
|
var current = pageUiState.localIndexSummary || {};
|
|
if (!force && current.scopeKey === scopeKey && !current.error && !current.loading) {
|
|
renderPageSettingsLocalIndex();
|
|
return;
|
|
}
|
|
pageUiState.localIndexSummary = {
|
|
scopeKey: scopeKey,
|
|
loading: true,
|
|
error: ''
|
|
};
|
|
renderPageSettingsLocalIndex();
|
|
try {
|
|
var baseParams = new URLSearchParams();
|
|
baseParams.set('workspaceId', resolveWorkspaceId(document.body));
|
|
baseParams.set('rootUri', currentRootUri());
|
|
var statusUrl = '/api/search/local-index/status?' + baseParams.toString();
|
|
var response = await fetch(statusUrl, { headers: { accept: 'application/json' } });
|
|
var statusPayload = await response.json().catch(function(){ return null; });
|
|
if (!response.ok || !statusPayload || statusPayload.ok !== true) {
|
|
throw new Error('status_' + response.status);
|
|
}
|
|
pageUiState.localIndexSummary = {
|
|
scopeKey: scopeKey,
|
|
loading: false,
|
|
error: '',
|
|
status: statusPayload.result || {}
|
|
};
|
|
} catch (error) {
|
|
pageUiState.localIndexSummary = {
|
|
scopeKey: scopeKey,
|
|
loading: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
status: {}
|
|
};
|
|
}
|
|
renderPageSettingsLocalIndex();
|
|
}
|
|
|
|
function localIndexIncludePathsFromForm() {
|
|
var popover = ensureLocalIndexSettingsPopover();
|
|
return currentLocalIndexRangeValues(popover).map(function(value) {
|
|
return value.trim();
|
|
}).filter(Boolean);
|
|
}
|
|
|
|
function currentLocalIndexRangeValues(popover) {
|
|
var root = popover || ensureLocalIndexSettingsPopover();
|
|
return Array.from(root.querySelectorAll('[data-local-index-range-input]')).map(function(input) {
|
|
if (!(input instanceof HTMLInputElement)) return '';
|
|
if (input.disabled && input.dataset.localIndexValue) return input.dataset.localIndexValue;
|
|
return input.value;
|
|
});
|
|
}
|
|
|
|
function localIndexSchedulePayloadFromForm() {
|
|
var popover = ensureLocalIndexSettingsPopover();
|
|
var modeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-mode"]');
|
|
var timeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-time"]');
|
|
var dateNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-schedule-date"]');
|
|
var runOnChangeNode = popover.querySelector('[data-testid="wolai-page-settings-local-index-run-on-change"]');
|
|
return {
|
|
scheduleMode: modeNode instanceof HTMLSelectElement ? modeNode.value : 'daily',
|
|
scheduleTime: timeNode instanceof HTMLInputElement ? (timeNode.value || '02:00') : '02:00',
|
|
scheduleDate: dateNode instanceof HTMLInputElement ? dateNode.value : '',
|
|
runOnChange: runOnChangeNode instanceof HTMLInputElement ? runOnChangeNode.checked : false
|
|
};
|
|
}
|
|
|
|
function addLocalIndexRange() {
|
|
var popover = ensureLocalIndexSettingsPopover();
|
|
var summary = pageUiState.localIndexSummary || {};
|
|
renderLocalIndexRangeRows(popover, currentLocalIndexRangeValues(popover).concat(['']), localIndexStatusKind(summary.status || {}), false, summary.status || {});
|
|
var inputs = Array.from(popover.querySelectorAll('[data-local-index-range-input]'));
|
|
var last = inputs[inputs.length - 1];
|
|
if (last instanceof HTMLInputElement) last.focus();
|
|
}
|
|
|
|
function removeLocalIndexRange(index) {
|
|
var popover = ensureLocalIndexSettingsPopover();
|
|
var summary = pageUiState.localIndexSummary || {};
|
|
var values = currentLocalIndexRangeValues(popover);
|
|
values.splice(Number(index || 0), 1);
|
|
renderLocalIndexRangeRows(popover, values, localIndexStatusKind(summary.status || {}), false, summary.status || {});
|
|
}
|
|
|
|
async function persistLocalIndexSettings() {
|
|
if (!pageSettingsLocalIndexIsAvailable()) return;
|
|
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
|
|
scopeKey: pageSettingsLocalIndexScopeKey(),
|
|
loading: true,
|
|
error: ''
|
|
});
|
|
renderPageSettingsLocalIndex();
|
|
try {
|
|
var response = await fetch('/api/search/local-index/settings', {
|
|
method: 'PUT',
|
|
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
body: JSON.stringify(Object.assign({
|
|
workspaceId: resolveWorkspaceId(document.body),
|
|
rootUri: currentRootUri(),
|
|
includePaths: localIndexIncludePathsFromForm()
|
|
}, localIndexSchedulePayloadFromForm()))
|
|
});
|
|
var payload = await response.json().catch(function(){ return null; });
|
|
if (!response.ok || !payload || payload.ok !== true) {
|
|
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'local_index_settings_' + response.status);
|
|
}
|
|
await loadPageSettingsLocalIndex(true);
|
|
} catch (error) {
|
|
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
|
|
scopeKey: pageSettingsLocalIndexScopeKey(),
|
|
loading: false,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
});
|
|
renderPageSettingsLocalIndex();
|
|
}
|
|
}
|
|
|
|
function emitLocalIndexBackgroundTask(status, label, error) {
|
|
var now = Date.now();
|
|
window.dispatchEvent(new CustomEvent('mnote:local-background-task-updated', {
|
|
detail: {
|
|
task: {
|
|
taskKind: 'local_index',
|
|
taskId: 'local-index',
|
|
sourceRootRelativePath: '本地索引',
|
|
rootUri: currentRootUri(),
|
|
status: status,
|
|
stageLabel: label,
|
|
updatedAtMs: now,
|
|
finishedAtMs: status === 'running' ? null : now,
|
|
error: error || null
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
|
|
async function refreshLocalIndex() {
|
|
if (!pageSettingsLocalIndexIsAvailable()) return;
|
|
emitLocalIndexBackgroundTask('running', '本地索引重建中', null);
|
|
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
|
|
scopeKey: pageSettingsLocalIndexScopeKey(),
|
|
loading: true,
|
|
error: ''
|
|
});
|
|
renderPageSettingsLocalIndex();
|
|
try {
|
|
var response = await fetch('/api/search/local-index/refresh', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
body: JSON.stringify({
|
|
workspaceId: resolveWorkspaceId(document.body),
|
|
rootUri: currentRootUri()
|
|
})
|
|
});
|
|
var payload = await response.json().catch(function(){ return null; });
|
|
if (!response.ok || !payload || payload.ok !== true) {
|
|
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'local_index_refresh_' + response.status);
|
|
}
|
|
emitLocalIndexBackgroundTask('done', '本地索引已完成', null);
|
|
await loadPageSettingsLocalIndex(true);
|
|
} catch (error) {
|
|
emitLocalIndexBackgroundTask('failed', '本地索引失败', error instanceof Error ? error.message : String(error));
|
|
pageUiState.localIndexSummary = Object.assign({}, pageUiState.localIndexSummary || {}, {
|
|
scopeKey: pageSettingsLocalIndexScopeKey(),
|
|
loading: false,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
});
|
|
renderPageSettingsLocalIndex();
|
|
}
|
|
}
|
|
|
|
function knowledgeRagScopeKey() {
|
|
return [
|
|
currentSourceKind(),
|
|
currentRootUri(),
|
|
resolveWorkspaceId(document.body)
|
|
].join('|');
|
|
}
|
|
|
|
function knowledgeRagIsAvailable() {
|
|
return currentSourceKind() === 'local_folder' && Boolean(currentRootUri());
|
|
}
|
|
|
|
function knowledgeRagSourceInputRow(value) {
|
|
return '' +
|
|
'<div class="mnote-knowledge-rag-source-input-row" data-knowledge-rag-source-input-row="true">' +
|
|
'<input type="text" class="wolai-page-settings-index-path" data-knowledge-rag-source-input="true" data-knowledge-rag-source-picker="mnote-filetree" data-knowledge-rag-source-kind="local-file-or-folder" value="' + escapeHtml(value || '') + '" placeholder="本地文件或文件夹,例如 docs/book.pdf / docs" spellcheck="false" />' +
|
|
'<span class="mnote-knowledge-rag-input-status" data-knowledge-rag-input-status-label="true" hidden></span>' +
|
|
'<button type="button" class="wolai-page-settings-index-remove" data-knowledge-rag-action="remove-source" aria-label="删除资料来源"><span class="material-symbols-outlined" data-icon="close" aria-hidden="true"></span></button>' +
|
|
'</div>';
|
|
}
|
|
|
|
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 = '<div class="wolai-page-settings-index-empty">还没有资料库</div>';
|
|
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 '' +
|
|
'<button type="button" class="mnote-weknora-kb-card' + (isActive ? ' is-active' : '') + '" data-knowledge-rag-action="select-kb" data-knowledge-rag-kb-id="' + escapeHtml(base.providerKbId) + '" data-knowledge-rag-kb-provider="' + escapeHtml(providerName) + '" data-knowledge-rag-processing-count="' + escapeHtml(String(stats.processingCount)) + '">' +
|
|
'<span class="mnote-weknora-kb-card-star"><span class="material-symbols-outlined" data-icon="folder" aria-hidden="true"></span></span>' +
|
|
'<span class="mnote-weknora-kb-card-main">' +
|
|
'<strong title="' + escapeHtml(base.name) + '">' + escapeHtml(base.name) + '</strong>' +
|
|
'<em title="' + escapeHtml(base.description || '暂无描述') + '">' + escapeHtml(base.description || '暂无描述') + '</em>' +
|
|
'<span>' +
|
|
'<i><span class="material-symbols-outlined" data-icon="article" aria-hidden="true"></span>' + escapeHtml(String(stats.sourceCount)) + '</i>' +
|
|
'<i><span class="material-symbols-outlined" data-icon="hub" aria-hidden="true"></span>' + escapeHtml(String(stats.chunkCount || stats.providerDocCount || 0)) + '</i>' +
|
|
(stats.processingCount ? '<i class="is-processing"><span class="material-symbols-outlined" data-icon="sync" aria-hidden="true"></span>' + escapeHtml(String(stats.processingCount)) + '</i>' : '') +
|
|
'</span>' +
|
|
'</span>' +
|
|
'<span class="mnote-weknora-kb-card-status" data-status="' + escapeHtml(baseStatus) + '">' + escapeHtml(baseStatusLabel) + '</span>' +
|
|
'</button>';
|
|
}).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 = '<option value="">未配置资料库</option>';
|
|
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 '<option value="' + escapeHtml(base.providerKbId) + '">' + escapeHtml(label) + '</option>';
|
|
}).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 '' +
|
|
'<div class="mnote-weknora-kb-summary-card" data-testid="mnote-weknora-kb-summary" data-knowledge-rag-kb-id="' + escapeHtml(kbIds[0] || '') + '" data-knowledge-rag-kb-ids="' + escapeHtml(kbIds.join(',')) + '" data-knowledge-rag-kb-provider="' + escapeHtml(knowledgeRagProviderName(status || {})) + '" data-knowledge-rag-kb-status="configured">' +
|
|
'<div><span>知识库</span><strong>' + escapeHtml(activeCount ? String(activeCount) + ' 个' : '未配置') + '</strong></div>' +
|
|
'<div><span>文档</span><strong>' + escapeHtml(String((entries || []).length)) + '</strong></div>' +
|
|
'<div><span>索引文档</span><strong>' + escapeHtml(String((providerDocs || []).length)) + '</strong></div>' +
|
|
'<div><span>处理中</span><strong data-knowledge-rag-processing-count="' + escapeHtml(String(processingCount)) + '">' + escapeHtml(String(processingCount)) + '</strong></div>' +
|
|
'</div>';
|
|
}
|
|
|
|
function knowledgeRagProviderDocuments(status) {
|
|
var documents = status && status.documents && typeof status.documents === 'object' ? status.documents : {};
|
|
return Array.isArray(documents.documents) ? documents.documents : [];
|
|
}
|
|
|
|
function knowledgeRagStatusHasInFlight(status) {
|
|
var pipeline = status && status.pipeline && typeof status.pipeline === 'object' ? status.pipeline : {};
|
|
if (pipeline.busy === true || pipeline.scanning === true || pipeline.destructiveBusy === true || pipeline.requestPending === true || pipeline.pendingRequests === true || Number(pipeline.pendingEnqueues || 0) > 0) return true;
|
|
var registry = status && status.registry && typeof status.registry === 'object' ? status.registry : {};
|
|
var entries = Array.isArray(registry.entries) ? registry.entries : [];
|
|
if (entries.some(function(entry) {
|
|
var providerStatus = String(entry && entry.lightRagStatus || '').trim();
|
|
return !entry?.deletedAtMs && !entry?.stale && !(entry?.indexedAtMs && entry?.lightRagDocId)
|
|
&& ['submitted', 'pending', 'scanning', 'parsing', 'parse', 'parsed', 'analyzing', 'analyze', 'processing', 'process', ''].includes(providerStatus);
|
|
})) return true;
|
|
var groups = status && status.documents && status.documents.rawStatusGroups && typeof status.documents.rawStatusGroups === 'object'
|
|
? status.documents.rawStatusGroups
|
|
: {};
|
|
return ['pending', 'scanning', 'parsing', 'parse', 'parsed', 'analyzing', 'analyze', 'processing', 'process'].some(function(key) {
|
|
return Number(groups[key] || 0) > 0;
|
|
});
|
|
}
|
|
|
|
function knowledgeRagPipelineHasInFlight(pipeline) {
|
|
pipeline = pipeline && typeof pipeline === 'object' ? pipeline : {};
|
|
return pipeline.busy === true
|
|
|| pipeline.scanning === true
|
|
|| pipeline.destructiveBusy === true
|
|
|| pipeline.requestPending === true
|
|
|| pipeline.pendingRequests === true
|
|
|| Number(pipeline.pendingEnqueues || 0) > 0;
|
|
}
|
|
|
|
function parseKnowledgeRagChunkProgress(message) {
|
|
var text = String(message || '');
|
|
var match = text.match(/Chunk\s+(\d+)\s+of\s+(\d+).*?\b(doc-[A-Za-z0-9_-]+?)(?=-(?:chunk|mm)-|\s|$)/i);
|
|
if (!match) return null;
|
|
var current = Number(match[1]);
|
|
var total = Number(match[2]);
|
|
if (!Number.isFinite(current) || !Number.isFinite(total) || current <= 0 || total <= 0) return null;
|
|
return {
|
|
current: Math.min(current, total),
|
|
total: total,
|
|
docId: String(match[3] || ''),
|
|
latestMessage: text
|
|
};
|
|
}
|
|
|
|
function normalizeKnowledgeRagPipelineEvent(payload) {
|
|
var input = payload && typeof payload === 'object' ? payload : {};
|
|
var latestMessage = String(input.latestMessage || input.latest_message || '');
|
|
var historyMessages = Array.isArray(input.historyMessages)
|
|
? input.historyMessages
|
|
: (Array.isArray(input.history_messages) ? input.history_messages : []);
|
|
var progress = input.progress && typeof input.progress === 'object'
|
|
? input.progress
|
|
: parseKnowledgeRagChunkProgress(latestMessage);
|
|
if (!progress) {
|
|
for (var index = historyMessages.length - 1; index >= 0; index -= 1) {
|
|
progress = parseKnowledgeRagChunkProgress(historyMessages[index]);
|
|
if (progress) break;
|
|
}
|
|
}
|
|
return {
|
|
ok: input.ok !== false,
|
|
busy: input.busy === true,
|
|
destructiveBusy: input.destructiveBusy === true || input.destructive_busy === true,
|
|
scanning: input.scanning === true,
|
|
scanningExclusive: input.scanningExclusive === true || input.scanning_exclusive === true,
|
|
requestPending: input.requestPending === true || input.request_pending === true,
|
|
pendingRequests: input.pendingRequests === true || input.pending_requests === true,
|
|
pendingEnqueues: Number(input.pendingEnqueues ?? input.pending_enqueues ?? 0) || 0,
|
|
docs: Number(input.docs || 0) || 0,
|
|
batches: Number(input.batches ?? input.batchs ?? 0) || 0,
|
|
currentBatch: Number(input.currentBatch ?? input.cur_batch ?? 0) || 0,
|
|
jobName: String(input.jobName || input.job_name || ''),
|
|
jobStart: input.jobStart || input.job_start || null,
|
|
latestMessage: latestMessage,
|
|
historyMessages: historyMessages.map(function(message) { return String(message || ''); }),
|
|
cancellationRequested: input.cancellationRequested === true || input.cancellation_requested === true,
|
|
cancellationReason: input.cancellationReason || input.cancellation_reason || null,
|
|
progress: progress || null
|
|
};
|
|
}
|
|
|
|
function knowledgeRagPipelineProgress(status) {
|
|
var pipeline = status && status.pipeline && typeof status.pipeline === 'object' ? status.pipeline : {};
|
|
var progress = pipeline.progress && typeof pipeline.progress === 'object' ? pipeline.progress : null;
|
|
if (!progress) return null;
|
|
var current = Number(progress.current || 0);
|
|
var total = Number(progress.total || 0);
|
|
if (!Number.isFinite(current) || !Number.isFinite(total) || current <= 0 || total <= 0) return null;
|
|
return {
|
|
current: Math.min(current, total),
|
|
total: total,
|
|
docId: String(progress.docId || ''),
|
|
latestMessage: String(pipeline.latestMessage || '')
|
|
};
|
|
}
|
|
|
|
function knowledgeRagSourceProgressHtml(progress) {
|
|
if (!progress) return '';
|
|
var current = Number(progress.current || 0);
|
|
var total = Number(progress.total || 0);
|
|
var determinate = Number.isFinite(current) && Number.isFinite(total) && current > 0 && total > 0;
|
|
var percent = determinate ? Math.max(0, Math.min(100, Math.round((current / total) * 100))) : 0;
|
|
var label = determinate ? current + ' / ' + total + ' · ' + percent + '%' : String(progress.label || '处理中');
|
|
var mode = determinate ? 'determinate' : 'indeterminate';
|
|
var ariaValues = determinate
|
|
? ' aria-valuemin="0" aria-valuemax="' + String(total) + '" aria-valuenow="' + String(current) + '"'
|
|
: ' aria-valuetext="' + escapeHtml(label) + '"';
|
|
return '' +
|
|
'<div class="mnote-knowledge-rag-source-progress" data-progress-mode="' + mode + '" role="progressbar"' + ariaValues + ' aria-label="' + escapeHtml('索引进度 ' + label) + '">' +
|
|
'<div><i' + (determinate ? ' style="width:' + String(percent) + '%"' : '') + '></i></div>' +
|
|
'<span>' + escapeHtml(label) + '</span>' +
|
|
'</div>';
|
|
}
|
|
|
|
function knowledgeRagDocumentIsInFlight(doc) {
|
|
var status = String(doc && (doc.statusGroup || doc.status) || '').toLowerCase();
|
|
return ['pending', 'scanning', 'parsing', 'parse', 'parsed', 'analyzing', 'analyze', 'processing', 'process'].includes(status);
|
|
}
|
|
|
|
function knowledgeRagProgressLabel(status, doc, fallback) {
|
|
var pipeline = status && status.pipeline && typeof status.pipeline === 'object' ? status.pipeline : {};
|
|
var latest = String(pipeline.latestMessage || '').trim();
|
|
if (latest) return '处理中';
|
|
var providerStatus = String(doc && (doc.statusGroup || doc.status) || fallback || '').toLowerCase();
|
|
if (providerStatus === 'pending') return '排队中';
|
|
if (providerStatus === 'scanning') return '扫描中';
|
|
if (providerStatus === 'parsing' || providerStatus === 'parse' || providerStatus === 'parsed') return '解析中';
|
|
if (providerStatus === 'analyzing' || providerStatus === 'analyze') return '分析中';
|
|
return '索引中';
|
|
}
|
|
|
|
function knowledgeRagIndeterminateProgress(status, doc, fallback) {
|
|
return { mode: 'indeterminate', label: knowledgeRagProgressLabel(status, doc, fallback) };
|
|
}
|
|
|
|
function stopKnowledgeRagPipelineWatcher() {
|
|
if (knowledgeRagPipelineEventSource && typeof knowledgeRagPipelineEventSource.close === 'function') {
|
|
knowledgeRagPipelineEventSource.close();
|
|
}
|
|
knowledgeRagPipelineEventSource = null;
|
|
knowledgeRagPipelineWatcherScopeKey = '';
|
|
}
|
|
|
|
function handleKnowledgeRagPipelineEvent(payload) {
|
|
var scopeKey = knowledgeRagScopeKey();
|
|
if (knowledgeRagPipelineWatcherScopeKey && knowledgeRagPipelineWatcherScopeKey !== scopeKey) return;
|
|
var current = currentKnowledgeRagSummary();
|
|
var currentStatus = current.status && typeof current.status === 'object' ? current.status : {};
|
|
var previousPipeline = currentStatus.pipeline && typeof currentStatus.pipeline === 'object' ? currentStatus.pipeline : {};
|
|
var previousInFlight = knowledgeRagPipelineHasInFlight(previousPipeline);
|
|
var pipeline = normalizeKnowledgeRagPipelineEvent(payload);
|
|
var nextStatus = Object.assign({}, currentStatus, { pipeline: pipeline });
|
|
pageUiState.knowledgeRagSummary = Object.assign({}, current, {
|
|
scopeKey: scopeKey,
|
|
loading: false,
|
|
error: '',
|
|
status: nextStatus
|
|
});
|
|
if (isKnowledgeRagSettingsOpen()) renderKnowledgeRagSettings();
|
|
var currentInFlight = knowledgeRagPipelineHasInFlight(pipeline);
|
|
if (currentInFlight) return;
|
|
var latest = String(pipeline.latestMessage || '').toLowerCase();
|
|
var finished = previousInFlight || latest.indexOf('completed') !== -1 || latest.indexOf('complete') !== -1;
|
|
if (!finished || knowledgeRagPipelineWatcherRefreshPending) return;
|
|
knowledgeRagPipelineWatcherRefreshPending = true;
|
|
void loadKnowledgeRagStatus(true, {
|
|
silentLoading: true,
|
|
notifyUpdated: true,
|
|
reason: 'pipeline-event-complete'
|
|
}).finally(function() {
|
|
knowledgeRagPipelineWatcherRefreshPending = false;
|
|
if (!knowledgeRagStatusHasInFlight(currentKnowledgeRagSummary().status || {})) {
|
|
stopKnowledgeRagPipelineWatcher();
|
|
}
|
|
});
|
|
}
|
|
|
|
function startKnowledgeRagPipelineWatcher(reason) {
|
|
document.documentElement.setAttribute('data-mnote-knowledge-rag-watcher-required', String(reason || 'source-updated'));
|
|
if (!knowledgeRagIsAvailable() || typeof window.EventSource !== 'function') return;
|
|
var scopeKey = knowledgeRagScopeKey();
|
|
if (knowledgeRagPipelineEventSource && knowledgeRagPipelineWatcherScopeKey === scopeKey) return;
|
|
stopKnowledgeRagPipelineWatcher();
|
|
var url = new URL('/api/knowledge-rag/pipeline-events', window.location.origin);
|
|
url.searchParams.set('workspaceId', resolveWorkspaceId(document.body));
|
|
url.searchParams.set('rootUri', currentRootUri());
|
|
var source = new EventSource(url.toString());
|
|
knowledgeRagPipelineEventSource = source;
|
|
knowledgeRagPipelineWatcherScopeKey = scopeKey;
|
|
source.addEventListener('pipeline_status', function(event) {
|
|
var payload = null;
|
|
try {
|
|
payload = JSON.parse(event.data || '{}');
|
|
} catch (_) {
|
|
return;
|
|
}
|
|
handleKnowledgeRagPipelineEvent(payload);
|
|
});
|
|
source.onerror = function() {
|
|
document.documentElement.setAttribute('data-mnote-knowledge-rag-watcher-error', 'true');
|
|
};
|
|
}
|
|
|
|
function scheduleKnowledgeRagStatusBridge(reason) {
|
|
startKnowledgeRagPipelineWatcher(reason);
|
|
}
|
|
|
|
function knowledgeRagUnmappedProviderRows(status, entries, 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 '<button type="button" data-knowledge-rag-action="filter-sources" data-knowledge-rag-filter="' + key + '" data-active="' + (current === key ? 'true' : 'false') + '">' + escapeHtml(label) + ' <span>' + String(counts[key] || 0) + '</span></button>';
|
|
}).join('');
|
|
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 = '<div class="wolai-page-settings-index-empty">' + escapeHtml(emptyName) + ' 还没有资料</div>';
|
|
return;
|
|
}
|
|
if (!items.length) {
|
|
sourcesNode.innerHTML = '<div class="wolai-page-settings-index-empty">当前分类没有资料</div>';
|
|
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 '' +
|
|
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-source-kind="provider" data-knowledge-rag-provider="' + escapeHtml(providerName) + '" data-knowledge-rag-provider-id="' + escapeHtml(providerId) + '" data-knowledge-rag-knowledge-id="' + escapeHtml(knowledgeId) + '" data-knowledge-rag-chunk-count="' + escapeHtml(String(chunkCount || 0)) + '" data-knowledge-rag-processing-status="' + escapeHtml(docStatus) + '" data-knowledge-rag-open-reference-mode="provider-reference-fallback" data-knowledge-rag-source-path="' + escapeHtml(providerPath || providerId) + '">' +
|
|
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + (failedDoc ? 'fault' : 'indexed') + '" title="' + escapeHtml(docStatusLabel || '索引文档') + '"></span>' +
|
|
'<div class="mnote-knowledge-rag-source-main">' +
|
|
'<strong title="' + escapeHtml(providerTitle) + '">' + escapeHtml(providerTitle) + '</strong>' +
|
|
'<span>' + escapeHtml(providerMeta) + '</span>' +
|
|
'<span class="mnote-weknora-source-aux"><i>' + escapeHtml(knowledgeId || '索引记录') + '</i><i>' + escapeHtml(providerPath ? '引用可回跳' : '无法直接定位') + '</i></span>' +
|
|
knowledgeRagSourceProgressHtml(providerProgress) +
|
|
'</div>' +
|
|
'<div class="mnote-knowledge-rag-source-actions"></div>' +
|
|
'</div>';
|
|
}
|
|
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 '' +
|
|
'<div class="mnote-knowledge-rag-source-row" data-knowledge-rag-source-row="true" data-knowledge-rag-provider="' + escapeHtml(providerName) + '" data-knowledge-rag-provider-ids="' + escapeHtml(providerIds.join(',')) + '" data-knowledge-rag-kb-id="' + escapeHtml(kbId) + '" data-knowledge-rag-knowledge-id="' + escapeHtml(knowledgeId) + '" data-knowledge-rag-chunk-count="' + escapeHtml(String(chunkCount || 0)) + '" data-knowledge-rag-processing-status="' + escapeHtml(providerStatus || statusLabel) + '" data-knowledge-rag-open-reference-mode="source-registry-reference" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '">' +
|
|
'<span class="wolai-page-settings-index-status-dot" data-index-status="' + kind + '" title="' + escapeHtml(statusLabel) + '"></span>' +
|
|
'<div class="mnote-knowledge-rag-source-main">' +
|
|
'<strong title="' + escapeHtml(title) + '">' + escapeHtml(title || '未命名资料') + '</strong>' +
|
|
'<span>' + escapeHtml(meta) + '</span>' +
|
|
'<span class="mnote-weknora-source-aux"><i>' + escapeHtml(kbId || '未绑定 KB') + '</i><i>' + escapeHtml(knowledgeId || '待生成 citation') + '</i></span>' +
|
|
knowledgeRagSourceProgressHtml(entryProgress) +
|
|
'</div>' +
|
|
'<div class="mnote-knowledge-rag-source-actions">' +
|
|
'<button type="button" data-knowledge-rag-action="open-source-reference" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '"' + (sourcePath ? '' : ' disabled') + '>打开</button>' +
|
|
'<button type="button" data-knowledge-rag-action="reindex-source" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '"' + (canReindex ? '' : ' disabled') + '>重试</button>' +
|
|
'<button type="button" data-knowledge-rag-action="delete-source" data-knowledge-rag-delete-local-file-policy="preserve-local-file" data-knowledge-rag-source-path="' + escapeHtml(sourcePath) + '"' + (canDelete ? '' : ' disabled') + ' title="只移除索引记录,不删除本地文件">移除索引</button>' +
|
|
'</div>' +
|
|
'</div>';
|
|
}).join('');
|
|
}
|
|
|
|
function renderKnowledgeRagSettings(popover) {
|
|
popover = popover || ensureKnowledgeRagSettingsPopover();
|
|
var statusNode = popover.querySelector('[data-testid="mnote-knowledge-rag-status"]');
|
|
var metaNode = popover.querySelector('[data-testid="mnote-knowledge-rag-meta"]');
|
|
var dashboardButton = popover.querySelector('[data-knowledge-rag-action="open-dashboard"]');
|
|
if (!(statusNode instanceof HTMLElement)) return;
|
|
|
|
if (!knowledgeRagIsAvailable()) {
|
|
statusNode.textContent = '知识库仅在本地工作区可用';
|
|
if (metaNode instanceof HTMLElement) metaNode.innerHTML = '';
|
|
renderKnowledgeRagSourceFilters(popover, []);
|
|
renderKnowledgeRagSources(popover, [], {}, [], 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) +
|
|
'<div><span>服务</span><code>' + escapeHtml(providerLabel) + '</code></div>' +
|
|
'<div><span>边界</span><code>MNote source registry / LightRAG sidecar locator</code></div>' +
|
|
'<div><span>控制台</span><code>' + escapeHtml(dashboardUrl || '未配置') + '</code></div>' +
|
|
'<div><span>模型</span><code>' + escapeHtml(llmModel || '未上报') + (vlmModel ? ' / VLM ' + escapeHtml(vlmModel) : '') + '</code></div>' +
|
|
'<div><span>Rerank</span><code>' + escapeHtml(rerankLabel + (rerankBinding ? ' · ' + rerankBinding : '') + (rerankModel ? ' · ' + rerankModel : '')) + '</code></div>' +
|
|
'<div><span>输入目录</span><code>' + escapeHtml(inputDir || '未配置') + '</code></div>' +
|
|
'<div><span>存储目录</span><code>' + escapeHtml(workingDir || '未上报') + '</code></div>';
|
|
}
|
|
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 = '<div class="mnote-kb-search-empty">请输入检索关键词</div>';
|
|
return;
|
|
}
|
|
resultsEl.innerHTML = '<div class="mnote-kb-search-empty">检索中...</div>';
|
|
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 = '<div class="mnote-kb-search-empty">未找到匹配结果</div>';
|
|
return;
|
|
}
|
|
var html = '';
|
|
results.forEach(function(r, i) {
|
|
var score = typeof r.score === 'number' ? r.score.toFixed(3) : (typeof r.distance === 'number' ? r.distance.toFixed(3) : '-');
|
|
var content = 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 += '<div class="mnote-kb-search-result-item" data-kb-rag-search-result="true" data-knowledge-rag-provider="' + escapeHtml(knowledgeRagProviderName(currentKnowledgeRagSummary().status || {})) + '" data-knowledge-rag-kb-id="' + escapeHtml(providerKnowledgeBaseId) + '" data-knowledge-rag-knowledge-id="' + escapeHtml(providerKnowledgeId) + '" data-knowledge-rag-chunk-id="' + escapeHtml(providerChunkId) + '" data-knowledge-rag-source-path="' + escapeHtml(String(source)) + '">' +
|
|
'<div class="mnote-kb-search-result-header">' +
|
|
'<span class="mnote-kb-search-result-index">#' + (i + 1) + '</span>' +
|
|
'<span class="mnote-kb-search-result-score" data-kb-rag-score-label="relevance" data-kb-rag-score-is-similarity-percent="false">相关度 ' + score + '</span>' +
|
|
'</div>' +
|
|
'<div class="mnote-kb-search-result-source">' + escapeHtml(String(source)) + '</div>' +
|
|
'<div class="mnote-kb-search-result-preview">' + escapeHtml(preview) + (content.length > 200 ? '...' : '') + '</div>' +
|
|
(citationMarkdown ? '<div class="mnote-kb-search-result-citation">' + escapeHtml(String(citationMarkdown).substring(0, 150)) + '</div>' : '') +
|
|
'<div class="mnote-kb-search-result-actions"><button type="button" data-knowledge-rag-action="open-source-reference" data-knowledge-rag-source-path="' + escapeHtml(String(source)) + '" data-knowledge-rag-open-reference-mode="search-result-open-reference"' + (source ? '' : ' disabled') + '>打开引用</button></div>' +
|
|
'</div>';
|
|
});
|
|
resultsEl.innerHTML = html;
|
|
} catch (err) {
|
|
resultsEl.innerHTML = '<div class="mnote-kb-search-empty is-error">检索失败:' + escapeHtml(String(err.message || err)) + '</div>';
|
|
}
|
|
}
|
|
|
|
// --- 检索参数 ---
|
|
var _kbRagSearchParams = null;
|
|
function currentKnowledgeRagSearchParams() {
|
|
if (!_kbRagSearchParams) {
|
|
_kbRagSearchParams = { mode: 'hybrid', top_k: 10, similarity_threshold: 0.0 };
|
|
}
|
|
return _kbRagSearchParams;
|
|
}
|
|
|
|
function loadKnowledgeRagSearchParamsToForm() {
|
|
var popover = ensureKnowledgeRagSettingsPopover();
|
|
var params = currentKnowledgeRagSearchParams();
|
|
var modeSelect = popover.querySelector('[data-kb-rag-param="mode"]');
|
|
var topKInput = popover.querySelector('[data-kb-rag-param="top_k"]');
|
|
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 = '' +
|
|
'<span>字数 ' + Number(stats.wordCount || 0) + '</span>' +
|
|
'<span>字符 ' + Number(stats.characterCount || 0) + '</span>' +
|
|
'<span>块数 ' + Number(stats.blockCount || 0) + '</span>' +
|
|
'<span>待办 ' + Number(stats.todoDone || 0) + '/' + Number(stats.todoTotal || 0) + '</span>';
|
|
}
|
|
}
|
|
|
|
function setActivePageSettingsTab(tabName) {
|
|
var popover = ensurePageSettingsPopover();
|
|
popover.querySelectorAll('[data-page-settings-tab]').forEach(function(tab) {
|
|
var active = tab.getAttribute('data-page-settings-tab') === tabName;
|
|
tab.classList.toggle('is-active', active);
|
|
tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
});
|
|
popover.querySelectorAll('[data-page-settings-panel]').forEach(function(panel) {
|
|
if (tabName === 'extensions') {
|
|
refreshExtensionsPanel();
|
|
}
|
|
if (tabName === 'dashboard') {
|
|
refreshDashboardPanel();
|
|
}
|
|
|
|
panel.hidden = panel.getAttribute('data-page-settings-panel') !== tabName;
|
|
});
|
|
}
|
|
|
|
// --- 扩展面板子 tab 切换 ---
|
|
function setActiveExtensionsSubtab(subtabName) {
|
|
var popover = ensurePageSettingsPopover();
|
|
popover.querySelectorAll('[data-extensions-subtab]').forEach(function(tab) {
|
|
var active = tab.getAttribute('data-extensions-subtab') === subtabName;
|
|
tab.classList.toggle('is-active', active);
|
|
tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
});
|
|
popover.querySelectorAll('[data-extensions-panel]').forEach(function(panel) {
|
|
panel.hidden = panel.getAttribute('data-extensions-panel') !== subtabName;
|
|
});
|
|
// 切换时懒加载对应面板数据
|
|
if (subtabName === 'knowledge') refreshKnowledgeExtensionsPanel();
|
|
else if (subtabName === 'skills') refreshSkillsExtensionsPanel();
|
|
else if (subtabName === 'tools') refreshToolsExtensionsPanel();
|
|
}
|
|
|
|
async function fetchAgentToolsManifest() {
|
|
var resp = await fetch('/api/mnote/tools/manifest');
|
|
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
|
var data = await resp.json();
|
|
var tools = Array.isArray(data && data.tools) ? data.tools : [];
|
|
var capabilities = data && data.capabilities && typeof data.capabilities === 'object'
|
|
? data.capabilities
|
|
: {};
|
|
return { data: data, tools: tools, capabilities: capabilities };
|
|
}
|
|
|
|
function isSkillRelatedTool(tool) {
|
|
if (!tool || typeof tool !== 'object') return false;
|
|
var name = String(tool.name || tool.function_name || '');
|
|
if (/skill/i.test(name)) return true;
|
|
var scopes = tool.capabilityScope || tool.capabilities || [];
|
|
if (Array.isArray(scopes) && scopes.some(function(s) { return /skill/i.test(String(s)); })) return true;
|
|
return false;
|
|
}
|
|
|
|
// 当切换到扩展 tab 时刷新当前子面板
|
|
function refreshExtensionsPanel() {
|
|
var popover = ensurePageSettingsPopover();
|
|
var activeSubtab = popover.querySelector('[data-extensions-subtab].is-active');
|
|
var subtabName = activeSubtab ? activeSubtab.getAttribute('data-extensions-subtab') : 'knowledge';
|
|
setActiveExtensionsSubtab(subtabName);
|
|
}
|
|
|
|
// --- 知识库子面板 ---
|
|
var _knowledgeRagExtensionsCache = null;
|
|
async function refreshKnowledgeExtensionsPanel() {
|
|
var statusEl = document.querySelector('[data-extensions-status="knowledge"]');
|
|
if (!statusEl) return;
|
|
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
|
|
try {
|
|
var resp = await fetch('/api/knowledge-rag/status');
|
|
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
|
var data = await resp.json();
|
|
_knowledgeRagExtensionsCache = data;
|
|
var 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 = '' +
|
|
'<div class="mnote-extensions-item">' +
|
|
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">知识库 provider 状态</span><span class="' + statusClass + '">' + escapeHtml(String(statusText)) + '</span></div>' +
|
|
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">已索引来源</span><span>' + indexedCount + ' 个</span></div>' +
|
|
(lastIngest ? '<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">最近更新</span><span>' + escapeHtml(String(lastIngest)) + '</span></div>' : '') +
|
|
'<div class="mnote-extensions-item-actions">' +
|
|
'<button type="button" class="wolai-page-settings-action" data-knowledge-rag-action="manage">管理知识库...</button>' +
|
|
'<button type="button" class="wolai-page-settings-action" data-knowledge-rag-action="refresh-status">刷新状态</button>' +
|
|
'</div>' +
|
|
'</div>';
|
|
} catch (err) {
|
|
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法连接知识库 provider 服务</span></div>';
|
|
}
|
|
}
|
|
|
|
// --- Skills 子面板(来自 /api/mnote/tools/manifest,非历史 agent 网关) ---
|
|
async function refreshSkillsExtensionsPanel() {
|
|
var statusEl = document.querySelector('[data-extensions-status="skills"]');
|
|
if (!statusEl) return;
|
|
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
|
|
try {
|
|
var pack = await fetchAgentToolsManifest();
|
|
var allSkills = [];
|
|
pack.tools.forEach(function(tool) {
|
|
if (!isSkillRelatedTool(tool)) return;
|
|
allSkills.push({
|
|
name: tool.name || tool.function_name || 'skill',
|
|
source: 'mnote-agent-tools'
|
|
});
|
|
});
|
|
var caps = pack.capabilities;
|
|
Object.keys(caps).forEach(function(key) {
|
|
if (!/skill/i.test(key)) return;
|
|
var entry = caps[key];
|
|
var label = entry && (entry.name || entry.id) ? (entry.name || entry.id) : key;
|
|
allSkills.push({ name: String(label), source: 'capability' });
|
|
});
|
|
if (!allSkills.length) {
|
|
allSkills.push({ name: 'mnote.skill.read', source: 'builtin' });
|
|
}
|
|
var html = '';
|
|
allSkills.forEach(function(skill) {
|
|
html += '<div class="mnote-extensions-item">' +
|
|
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">' + escapeHtml(skill.name) + '</span><span class="mnote-extensions-source">' + escapeHtml(skill.source) + '</span></div>' +
|
|
'</div>';
|
|
});
|
|
if (!html) html = '<div class="mnote-extensions-item"><span>暂无 Skills</span></div>';
|
|
statusEl.innerHTML = html;
|
|
} catch (err) {
|
|
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法加载 Skills 信息</span></div>';
|
|
}
|
|
}
|
|
|
|
// --- Agent 工具子面板 ---
|
|
async function refreshToolsExtensionsPanel() {
|
|
var statusEl = document.querySelector('[data-extensions-status="tools"]');
|
|
if (!statusEl) return;
|
|
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
|
|
try {
|
|
var pack = await fetchAgentToolsManifest();
|
|
var html = '';
|
|
if (!pack.tools.length) {
|
|
html = '<div class="mnote-extensions-item"><span>暂无 Agent 工具信息</span></div>';
|
|
} else {
|
|
html += '<div class="mnote-extensions-item mnote-extensions-agent-group">' +
|
|
'<div class="mnote-extensions-agent-name">mnote agent tools</div>';
|
|
pack.tools.forEach(function(tool) {
|
|
var toolName = tool.name || tool.function_name || '';
|
|
if (!toolName) return;
|
|
var disabled = tool.disabled ? ' (已禁用)' : '';
|
|
html += '<div class="mnote-extensions-tool-item">' +
|
|
'<span class="mnote-extensions-tool-name">' + escapeHtml(toolName) + '</span>' +
|
|
(disabled ? '<span class="mnote-extensions-tool-disabled">' + disabled + '</span>' : '') +
|
|
'</div>';
|
|
});
|
|
html += '</div>';
|
|
}
|
|
statusEl.innerHTML = html;
|
|
} catch (err) {
|
|
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法加载 Agent 工具信息</span></div>';
|
|
}
|
|
}
|
|
|
|
async function persistPageOptionsPatch(patch) {
|
|
var previous = Object.assign({}, currentPageOptions());
|
|
pageUiState.pageOptions = Object.assign({}, previous, patch);
|
|
var nextOptions = Object.assign({}, pageUiState.pageOptions);
|
|
applyPageOptionsToShell();
|
|
renderPageSettingsPopover();
|
|
try {
|
|
var response = await fetch('/api/ui/preferences', {
|
|
method: 'PUT',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({
|
|
documentId: currentDocumentId(),
|
|
workspaceId: resolveWorkspaceId(document.body),
|
|
...currentWorkspaceSourcePayload(),
|
|
updates: nextOptions
|
|
})
|
|
});
|
|
var payload = await response.json().catch(function(){ return null; });
|
|
if (!response.ok || !payload || payload.ok !== true) {
|
|
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'page_settings_save_failed_' + response.status);
|
|
}
|
|
var savedOptions = payload && payload.result && payload.result.pageOptions && typeof payload.result.pageOptions === 'object'
|
|
? Object.assign(defaultPageOptions(), payload.result.pageOptions)
|
|
: nextOptions;
|
|
pageUiState.pageOptions = Object.assign({}, savedOptions);
|
|
nextOptions = Object.assign({}, pageUiState.pageOptions);
|
|
applyPageOptionsToShell();
|
|
renderPageSettingsPopover();
|
|
var script = document.getElementById('__MNOTE_PAGE_AGGREGATE__');
|
|
if (script) {
|
|
try {
|
|
var aggregate = JSON.parse(script.textContent || 'null');
|
|
if (aggregate && typeof aggregate === 'object') {
|
|
aggregate.layout = aggregate.layout && typeof aggregate.layout === 'object' ? aggregate.layout : {};
|
|
aggregate.layout.pageOptions = Object.assign({}, nextOptions);
|
|
aggregate.layout_options = Object.assign({}, nextOptions);
|
|
script.textContent = JSON.stringify(aggregate);
|
|
script.setAttribute('data-mnote-page-options-synced-at', String(Date.now()));
|
|
script.setAttribute('data-mnote-page-options-local-write-at', String(Date.now()));
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
document.documentElement.setAttribute('data-mnote-page-options-saved', 'true');
|
|
} catch (error) {
|
|
pageUiState.pageOptions = previous;
|
|
applyPageOptionsToShell();
|
|
renderPageSettingsPopover();
|
|
document.documentElement.setAttribute('data-mnote-page-options-error', error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
async function loadPageWidthPreferences() {
|
|
var params = new URLSearchParams();
|
|
if (currentDocumentId()) params.set('documentId', currentDocumentId());
|
|
params.set('workspaceId', resolveWorkspaceId(document.body));
|
|
var sourcePayload = currentWorkspaceSourcePayload();
|
|
Object.keys(sourcePayload || {}).forEach(function(key) {
|
|
if (sourcePayload[key] != null && sourcePayload[key] !== '') params.set(key, sourcePayload[key]);
|
|
});
|
|
try {
|
|
var response = await fetch('/api/ui/preferences/effective?' + params.toString(), {
|
|
headers: { accept: 'application/json' }
|
|
});
|
|
var payload = await response.json().catch(function(){ return null; });
|
|
if (!response.ok || !payload || payload.ok !== true) return;
|
|
pageUiState.pageWidthPreferences = normalizePageWidthPreferences(payload.result && payload.result.pageWidthPreferences);
|
|
markRetiredLocalOcrPreferenceSurface();
|
|
applyPageOptionsToShell();
|
|
if (isPageSettingsOpen()) renderPageSettingsPopover();
|
|
} catch (_) {}
|
|
}
|
|
|
|
async function persistPageWidthPreference(type, mode) {
|
|
if (PAGE_WIDTH_TYPES.indexOf(type) < 0) return;
|
|
var previous = pageUiState.pageWidthPreferences;
|
|
var next = normalizePageWidthPreferences(previous);
|
|
next[type] = Object.assign({}, next[type], {
|
|
mode: mode,
|
|
resolvedMode: mode === 'inherit' ? next.default.resolvedMode : mode,
|
|
cssMaxWidth: pageWidthCssMaxWidth(mode === 'inherit' ? next.default.resolvedMode : mode),
|
|
source: 'optimistic'
|
|
});
|
|
pageUiState.pageWidthPreferences = next;
|
|
applyPageOptionsToShell();
|
|
renderPageSettingsPopover();
|
|
notifyPageWidthPreferenceChanged(type);
|
|
try {
|
|
var updates = {};
|
|
updates['pageWidth.' + type] = { mode: mode, custom: null };
|
|
var response = await fetch('/api/ui/preferences', {
|
|
method: 'PUT',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({
|
|
documentId: currentDocumentId(),
|
|
workspaceId: resolveWorkspaceId(document.body),
|
|
...currentWorkspaceSourcePayload(),
|
|
updates: updates
|
|
})
|
|
});
|
|
var payload = await response.json().catch(function(){ return null; });
|
|
if (!response.ok || !payload || payload.ok !== true) {
|
|
throw new Error(payload && payload.error && payload.error.message ? payload.error.message : 'page_width_save_failed_' + response.status);
|
|
}
|
|
pageUiState.pageWidthPreferences = normalizePageWidthPreferences(payload.result && payload.result.pageWidthPreferences);
|
|
applyPageOptionsToShell();
|
|
renderPageSettingsPopover();
|
|
notifyPageWidthPreferenceChanged(type);
|
|
document.documentElement.setAttribute('data-mnote-page-width-saved', 'true');
|
|
} catch (error) {
|
|
pageUiState.pageWidthPreferences = previous;
|
|
applyPageOptionsToShell();
|
|
renderPageSettingsPopover();
|
|
document.documentElement.setAttribute('data-mnote-page-width-error', error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
function isPageSettingsOpen() {
|
|
var popover = document.querySelector('[data-testid="wolai-page-settings-popover"]');
|
|
return popover instanceof HTMLElement && !popover.hidden;
|
|
}
|
|
|
|
function isLocalIndexSettingsOpen() {
|
|
var popover = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
|
|
return popover instanceof HTMLElement && !popover.hidden;
|
|
}
|
|
|
|
function isKnowledgeRagSettingsOpen() {
|
|
var popover = document.querySelector('[data-testid="mnote-knowledge-rag-settings-popover"]');
|
|
return popover instanceof HTMLElement && !popover.hidden;
|
|
}
|
|
|
|
function isAnySettingsOpen() {
|
|
return isPageSettingsOpen() || isLocalIndexSettingsOpen() || isKnowledgeRagSettingsOpen();
|
|
}
|
|
|
|
function openPageSettingsPopover(initialTab) {
|
|
if (!currentDocumentId()) return;
|
|
closeLocalIndexSettingsPopover();
|
|
closeKnowledgeRagSettingsPopover();
|
|
var popover = ensurePageSettingsPopover();
|
|
renderPageSettingsPopover();
|
|
setActivePageSettingsTab(initialTab || 'page');
|
|
popover.hidden = false;
|
|
pageUiState.pageSettingsOpen = true;
|
|
updatePageSettingsTriggerState();
|
|
}
|
|
|
|
function openPageIndexSettingsPopover() {
|
|
closePageSettingsPopover();
|
|
closeKnowledgeRagSettingsPopover();
|
|
var popover = ensureLocalIndexSettingsPopover();
|
|
renderPageSettingsLocalIndex(popover);
|
|
popover.hidden = false;
|
|
void loadPageSettingsLocalIndex(false);
|
|
updateStandaloneSettingsTriggerState();
|
|
}
|
|
|
|
function openKnowledgeRagSettingsPopover() {
|
|
closePageSettingsPopover();
|
|
closeLocalIndexSettingsPopover();
|
|
var popover = ensureKnowledgeRagSettingsPopover();
|
|
renderKnowledgeRagSettings(popover);
|
|
popover.hidden = false;
|
|
void loadKnowledgeRagStatus(false).then(function() {
|
|
if (knowledgeRagStatusHasInFlight(currentKnowledgeRagSummary().status || {})) {
|
|
scheduleKnowledgeRagStatusBridge('open-panel');
|
|
}
|
|
});
|
|
updateStandaloneSettingsTriggerState();
|
|
}
|
|
|
|
function closePageSettingsPopover() {
|
|
var popover = document.querySelector('[data-testid="wolai-page-settings-popover"]');
|
|
if (popover instanceof HTMLElement) popover.hidden = true;
|
|
pageUiState.pageSettingsOpen = false;
|
|
updatePageSettingsTriggerState();
|
|
}
|
|
|
|
function closeLocalIndexSettingsPopover() {
|
|
var popover = document.querySelector('[data-testid="mnote-local-index-settings-popover"]');
|
|
if (popover instanceof HTMLElement) popover.hidden = true;
|
|
updateStandaloneSettingsTriggerState();
|
|
}
|
|
|
|
function closeKnowledgeRagSettingsPopover() {
|
|
var popover = document.querySelector('[data-testid="mnote-knowledge-rag-settings-popover"]');
|
|
if (popover instanceof HTMLElement) popover.hidden = true;
|
|
updateStandaloneSettingsTriggerState();
|
|
}
|
|
|
|
function closeAllSettingsPopovers() {
|
|
closePageSettingsPopover();
|
|
closeLocalIndexSettingsPopover();
|
|
closeKnowledgeRagSettingsPopover();
|
|
}
|
|
|
|
function togglePageSettingsPopover() {
|
|
if (isPageSettingsOpen()) closePageSettingsPopover();
|
|
else openPageSettingsPopover();
|
|
}
|
|
|
|
window.addEventListener('mnote:active-resource-tab-changed', function() {
|
|
applyPageOptionsToShell();
|
|
});
|
|
|
|
window.addEventListener('mnote:knowledge-rag-source-updated', function() {
|
|
if (isKnowledgeRagSettingsOpen()) void loadKnowledgeRagStatus(true);
|
|
});
|
|
|
|
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);
|
|
refreshDashboardSkillsCard(popover);
|
|
refreshDashboardToolsCard(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 pack = await fetchAgentToolsManifest();
|
|
var count = pack.tools.filter(isSkillRelatedTool).length;
|
|
if (!count) count = 1; // mnote.skill.read 内置
|
|
el.textContent = count + ' 个';
|
|
} catch (_) {
|
|
el.textContent = '--';
|
|
}
|
|
}
|
|
|
|
async function refreshDashboardToolsCard(popover) {
|
|
var el = popover.querySelector('[data-dashboard-value="tools"]');
|
|
if (!el) return;
|
|
el.textContent = '加载中...';
|
|
try {
|
|
var pack = await fetchAgentToolsManifest();
|
|
el.textContent = pack.tools.length + ' 个';
|
|
} 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,
|
|
};
|
|
}
|