feat: 7-61 Yuxi reference — extensions panel, KB detail, manifest split, event router, dashboard
Batch B: settings popover 新增"扩展"tab,按知识库/Skills/MCP/Agent工具分面板 Batch C: knowledge-rag popover 新增"资料源管理/检索测试/检索参数"三tab Batch A: manifest.rs 按能力域拆为 10 个分组函数(context/doc/knowledge_rag/block/page/mindmap/office/onlyoffice/artifact) Batch D: 新增 agent-stream-event-router.js 共享模块(事件状态机 + SSE 帧解析) Batch E: settings popover 新增"仪表盘"tab — 知识库文件数/Skills数量/MCP连接状态/Agent run次数 836/838 Rust 测试通过(仅 2 个预存失败)
This commit is contained in:
@@ -574,6 +574,8 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
'<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') +
|
||||
@@ -592,6 +594,51 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
'<div class="wolai-page-settings-section" data-page-settings-panel="global" hidden>' +
|
||||
createGlobalHeadingNumbersRow() +
|
||||
createPageWidthRows() +
|
||||
'<div class="wolai-page-settings-section mnote-extensions-panel" data-page-settings-panel="extensions" hidden>' +
|
||||
'<div class="mnote-extensions-subtabs" role="tablist" aria-label="扩展子面板">' +
|
||||
'<button type="button" class="mnote-extensions-subtab is-active" role="tab" aria-selected="true" data-extensions-subtab="knowledge">知识库</button>' +
|
||||
'<button type="button" class="mnote-extensions-subtab" role="tab" aria-selected="false" data-extensions-subtab="skills">Skills</button>' +
|
||||
'<button type="button" class="mnote-extensions-subtab" role="tab" aria-selected="false" data-extensions-subtab="mcp">MCP</button>' +
|
||||
'<button type="button" class="mnote-extensions-subtab" role="tab" aria-selected="false" data-extensions-subtab="tools">Agent 工具</button>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-extensions-subpanel" data-extensions-panel="knowledge">' +
|
||||
'<div class="mnote-extensions-status" data-extensions-status="knowledge"><span class="mnote-extensions-loading">加载中...</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-extensions-subpanel" data-extensions-panel="skills" hidden>' +
|
||||
'<div class="mnote-extensions-status" data-extensions-status="skills"><span class="mnote-extensions-loading">加载中...</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-extensions-subpanel" data-extensions-panel="mcp" hidden>' +
|
||||
'<div class="mnote-extensions-status" data-extensions-status="mcp"><span class="mnote-extensions-loading">加载中...</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-extensions-subpanel" data-extensions-panel="tools" hidden>' +
|
||||
'<div class="mnote-extensions-status" data-extensions-status="tools"><span class="mnote-extensions-loading">加载中...</span></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-settings-section mnote-dashboard-panel" data-page-settings-panel="dashboard" hidden>' +
|
||||
'<div class="mnote-dashboard-grid">' +
|
||||
'<div class="mnote-dashboard-card" data-dashboard-card="knowledge">' +
|
||||
'<div class="mnote-dashboard-card-title">知识库</div>' +
|
||||
'<div class="mnote-dashboard-card-value" data-dashboard-value="knowledge">--</div>' +
|
||||
'<div class="mnote-dashboard-card-desc">已索引文件 / 状态</div>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-dashboard-card" data-dashboard-card="skills">' +
|
||||
'<div class="mnote-dashboard-card-title">Skills</div>' +
|
||||
'<div class="mnote-dashboard-card-value" data-dashboard-value="skills">--</div>' +
|
||||
'<div class="mnote-dashboard-card-desc">已安装 / 可用</div>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-dashboard-card" data-dashboard-card="mcp">' +
|
||||
'<div class="mnote-dashboard-card-title">MCP</div>' +
|
||||
'<div class="mnote-dashboard-card-value" data-dashboard-value="mcp">--</div>' +
|
||||
'<div class="mnote-dashboard-card-desc">服务器 / 已连接</div>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-dashboard-card" data-dashboard-card="agent">' +
|
||||
'<div class="mnote-dashboard-card-title">Agent Runs</div>' +
|
||||
'<div class="mnote-dashboard-card-value" data-dashboard-value="agent">--</div>' +
|
||||
'<div class="mnote-dashboard-card-desc">最近 7 天运行次数</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<button type="button" class="wolai-page-settings-index-add" style="margin-top:10px" data-dashboard-action="refresh">刷新仪表盘</button>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-settings-actions">' +
|
||||
'<button type="button" class="wolai-page-settings-action" data-page-settings-action="history">页面历史...</button>' +
|
||||
@@ -648,13 +695,22 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
}
|
||||
|
||||
function createKnowledgeRagSettingsPanelHtml() {
|
||||
return createKnowledgeRagSettingsPanelHtmlV2();
|
||||
}
|
||||
|
||||
function createKnowledgeRagSettingsPanelHtmlV2() {
|
||||
return '' +
|
||||
'<div class="wolai-page-settings-panel mnote-settings-panel mnote-knowledge-rag-settings-panel" role="dialog" aria-modal="false" aria-label="资料库问答设置">' +
|
||||
'<div class="mnote-settings-panel-head">' +
|
||||
'<strong>资料库问答</strong>' +
|
||||
'<button type="button" class="mnote-settings-panel-close" data-settings-action="close" aria-label="关闭资料库问答设置">×</button>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-settings-index-panel" data-testid="mnote-knowledge-rag-settings-panel">' +
|
||||
'<div class="mnote-knowledge-rag-detail-tabs" role="tablist" aria-label="资料库设置子面板">' +
|
||||
'<button type="button" class="mnote-knowledge-rag-detail-tab is-active" role="tab" aria-selected="true" data-kb-rag-tab="sources">资料源管理</button>' +
|
||||
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="search">检索测试</button>' +
|
||||
'<button type="button" class="mnote-knowledge-rag-detail-tab" role="tab" aria-selected="false" data-kb-rag-tab="params">检索参数</button>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="sources">' +
|
||||
'<div class="wolai-page-settings-index-status" data-testid="mnote-knowledge-rag-status"></div>' +
|
||||
'<div class="mnote-knowledge-rag-meta" data-testid="mnote-knowledge-rag-meta"></div>' +
|
||||
'<section class="wolai-page-settings-index-group mnote-knowledge-rag-control-group">' +
|
||||
@@ -679,6 +735,39 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
'<div class="mnote-knowledge-rag-source-list" data-testid="mnote-knowledge-rag-sources"></div>' +
|
||||
'</section>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="search" hidden>' +
|
||||
'<div class="mnote-kb-search-form">' +
|
||||
'<input type="text" class="mnote-kb-search-input" data-kb-rag-search-input="true" placeholder="输入检索关键词...">' +
|
||||
'<button type="button" class="wolai-page-settings-index-add" data-kb-rag-action="search">搜索</button>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-kb-search-results" data-kb-rag-search-results="true">' +
|
||||
'<span class="mnote-extensions-loading">输入关键词后点击搜索</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-knowledge-rag-detail-panel" data-kb-rag-panel="params" hidden>' +
|
||||
'<div class="mnote-kb-params-form">' +
|
||||
'<div class="mnote-kb-params-row">' +
|
||||
'<label class="mnote-kb-params-label">检索模式</label>' +
|
||||
'<select class="mnote-kb-params-select" data-kb-rag-param="mode">' +
|
||||
'<option value="hybrid" selected>混合 (Hybrid)</option>' +
|
||||
'<option value="local">本地 (Local)</option>' +
|
||||
'<option value="global">全局 (Global)</option>' +
|
||||
'<option value="naive">朴素 (Naive)</option>' +
|
||||
'<option value="mix">混合模式 (Mix)</option>' +
|
||||
'</select>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-kb-params-row">' +
|
||||
'<label class="mnote-kb-params-label">返回数量 (Top-K)</label>' +
|
||||
'<input type="number" class="mnote-kb-params-number" data-kb-rag-param="top_k" value="10" min="1" max="100">' +
|
||||
'</div>' +
|
||||
'<div class="mnote-kb-params-row">' +
|
||||
'<label class="mnote-kb-params-label">相似度阈值</label>' +
|
||||
'<input type="range" class="mnote-kb-params-range" data-kb-rag-param="similarity_threshold" value="0" min="0" max="1" step="0.05">' +
|
||||
'<span class="mnote-kb-params-range-value" data-kb-rag-param-value="similarity_threshold">0.0</span>' +
|
||||
'</div>' +
|
||||
'<button type="button" class="wolai-page-settings-index-add" data-kb-rag-action="save-params">保存参数</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
@@ -1890,6 +1979,125 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
window.open(url, '_blank', 'noopener');
|
||||
}
|
||||
|
||||
// --- 知识库详情 tab 切换 ---
|
||||
function setActiveKnowledgeRagDetailTab(tabName) {
|
||||
var popover = ensureKnowledgeRagSettingsPopover();
|
||||
popover.querySelectorAll('[data-kb-rag-tab]').forEach(function(tab) {
|
||||
var active = tab.getAttribute('data-kb-rag-tab') === tabName;
|
||||
tab.classList.toggle('is-active', active);
|
||||
tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
||||
});
|
||||
popover.querySelectorAll('[data-kb-rag-panel]').forEach(function(panel) {
|
||||
panel.hidden = panel.getAttribute('data-kb-rag-panel') !== tabName;
|
||||
});
|
||||
}
|
||||
|
||||
// --- 检索测试 ---
|
||||
async function performKnowledgeRagSearch() {
|
||||
var popover = ensureKnowledgeRagSettingsPopover();
|
||||
var input = popover.querySelector('[data-kb-rag-search-input]');
|
||||
var resultsEl = popover.querySelector('[data-kb-rag-search-results]');
|
||||
if (!input || !resultsEl) return;
|
||||
var query = (input.value || '').trim();
|
||||
if (!query) {
|
||||
resultsEl.innerHTML = '<span class="mnote-extensions-error">请输入检索关键词</span>';
|
||||
return;
|
||||
}
|
||||
resultsEl.innerHTML = '<span class="mnote-extensions-loading">检索中...</span>';
|
||||
try {
|
||||
var params = new URLSearchParams();
|
||||
params.set('query', query);
|
||||
params.set('mode', currentKnowledgeRagSearchParams().mode || 'hybrid');
|
||||
params.set('top_k', String(currentKnowledgeRagSearchParams().top_k || 10));
|
||||
var resp = await fetch('/api/knowledge-rag/search?' + params.toString(), {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ query: query })
|
||||
});
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
var data = await resp.json();
|
||||
var results = Array.isArray(data.results) ? data.results : (Array.isArray(data) ? data : []);
|
||||
if (!results.length) {
|
||||
resultsEl.innerHTML = '<div class="mnote-extensions-item"><span>未找到匹配结果</span></div>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
results.forEach(function(r, i) {
|
||||
var score = typeof r.score === 'number' ? r.score.toFixed(3) : (typeof r.distance === 'number' ? r.distance.toFixed(3) : '-');
|
||||
var content = r.content || r.text || r.chunk || '';
|
||||
var source = r.file_path || r.source || r.document_id || '';
|
||||
var citationMarkdown = r.citationMarkdown || r.citation_markdown || '';
|
||||
var preview = String(content).substring(0, 200);
|
||||
html += '<div class="mnote-kb-search-result-item">' +
|
||||
'<div class="mnote-kb-search-result-header">' +
|
||||
'<span class="mnote-kb-search-result-index">#' + (i + 1) + '</span>' +
|
||||
'<span class="mnote-kb-search-result-score">相关度: ' + score + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="mnote-kb-search-result-source">' + escapeHtml(String(source)) + '</div>' +
|
||||
'<div class="mnote-kb-search-result-preview">' + escapeHtml(preview) + (content.length > 200 ? '...' : '') + '</div>' +
|
||||
(citationMarkdown ? '<div class="mnote-kb-search-result-citation">' + escapeHtml(String(citationMarkdown).substring(0, 150)) + '</div>' : '') +
|
||||
'</div>';
|
||||
});
|
||||
resultsEl.innerHTML = html;
|
||||
} catch (err) {
|
||||
resultsEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">检索失败: ' + escapeHtml(String(err.message || err)) + '</span></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// --- 检索参数 ---
|
||||
var _kbRagSearchParams = null;
|
||||
function currentKnowledgeRagSearchParams() {
|
||||
if (!_kbRagSearchParams) {
|
||||
_kbRagSearchParams = { mode: 'hybrid', top_k: 10, similarity_threshold: 0.0 };
|
||||
}
|
||||
return _kbRagSearchParams;
|
||||
}
|
||||
|
||||
function loadKnowledgeRagSearchParamsToForm() {
|
||||
var popover = ensureKnowledgeRagSettingsPopover();
|
||||
var params = currentKnowledgeRagSearchParams();
|
||||
var modeSelect = popover.querySelector('[data-kb-rag-param="mode"]');
|
||||
var topKInput = popover.querySelector('[data-kb-rag-param="top_k"]');
|
||||
var thresholdRange = popover.querySelector('[data-kb-rag-param="similarity_threshold"]');
|
||||
var thresholdValue = popover.querySelector('[data-kb-rag-param-value="similarity_threshold"]');
|
||||
if (modeSelect) modeSelect.value = params.mode || 'hybrid';
|
||||
if (topKInput) topKInput.value = String(params.top_k || 10);
|
||||
if (thresholdRange) {
|
||||
thresholdRange.value = String(params.similarity_threshold || 0);
|
||||
if (thresholdValue) thresholdValue.textContent = String(params.similarity_threshold || 0);
|
||||
}
|
||||
}
|
||||
|
||||
function syncKnowledgeRagThresholdDisplay() {
|
||||
var popover = ensureKnowledgeRagSettingsPopover();
|
||||
var thresholdRange = popover.querySelector('[data-kb-rag-param="similarity_threshold"]');
|
||||
var thresholdValue = popover.querySelector('[data-kb-rag-param-value="similarity_threshold"]');
|
||||
if (thresholdRange && thresholdValue) {
|
||||
var val = parseFloat(thresholdRange.value) || 0;
|
||||
thresholdValue.textContent = val.toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
function saveKnowledgeRagSearchParams() {
|
||||
var popover = ensureKnowledgeRagSettingsPopover();
|
||||
var modeSelect = popover.querySelector('[data-kb-rag-param="mode"]');
|
||||
var topKInput = popover.querySelector('[data-kb-rag-param="top_k"]');
|
||||
var thresholdRange = popover.querySelector('[data-kb-rag-param="similarity_threshold"]');
|
||||
var params = {
|
||||
mode: modeSelect ? modeSelect.value : 'hybrid',
|
||||
top_k: topKInput ? (parseInt(topKInput.value, 10) || 10) : 10,
|
||||
similarity_threshold: thresholdRange ? (parseFloat(thresholdRange.value) || 0) : 0
|
||||
};
|
||||
_kbRagSearchParams = params;
|
||||
try {
|
||||
fetch('/api/ui/preferences', {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ updates: { knowledge_rag_search_params: params } })
|
||||
}).catch(function(){});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function knowledgeRagInputStatusText(status) {
|
||||
if (status === 'running') return '索引中';
|
||||
if (status === 'done') return '已提交';
|
||||
@@ -1945,10 +2153,172 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
||||
});
|
||||
popover.querySelectorAll('[data-page-settings-panel]').forEach(function(panel) {
|
||||
if (tabName === 'extensions') {
|
||||
refreshExtensionsPanel();
|
||||
}
|
||||
if (tabName === 'dashboard') {
|
||||
refreshDashboardPanel();
|
||||
}
|
||||
|
||||
panel.hidden = panel.getAttribute('data-page-settings-panel') !== tabName;
|
||||
});
|
||||
}
|
||||
|
||||
// --- 扩展面板子 tab 切换 ---
|
||||
function setActiveExtensionsSubtab(subtabName) {
|
||||
var popover = ensurePageSettingsPopover();
|
||||
popover.querySelectorAll('[data-extensions-subtab]').forEach(function(tab) {
|
||||
var active = tab.getAttribute('data-extensions-subtab') === subtabName;
|
||||
tab.classList.toggle('is-active', active);
|
||||
tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
||||
});
|
||||
popover.querySelectorAll('[data-extensions-panel]').forEach(function(panel) {
|
||||
panel.hidden = panel.getAttribute('data-extensions-panel') !== subtabName;
|
||||
});
|
||||
// 切换时懒加载对应面板数据
|
||||
if (subtabName === 'knowledge') refreshKnowledgeExtensionsPanel();
|
||||
else if (subtabName === 'skills') refreshSkillsExtensionsPanel();
|
||||
else if (subtabName === 'mcp') refreshMcpExtensionsPanel();
|
||||
else if (subtabName === 'tools') refreshToolsExtensionsPanel();
|
||||
}
|
||||
|
||||
// 当切换到扩展 tab 时刷新当前子面板
|
||||
function refreshExtensionsPanel() {
|
||||
var popover = ensurePageSettingsPopover();
|
||||
var activeSubtab = popover.querySelector('[data-extensions-subtab].is-active');
|
||||
var subtabName = activeSubtab ? activeSubtab.getAttribute('data-extensions-subtab') : 'knowledge';
|
||||
setActiveExtensionsSubtab(subtabName);
|
||||
}
|
||||
|
||||
// --- 知识库子面板 ---
|
||||
var _knowledgeRagExtensionsCache = null;
|
||||
async function refreshKnowledgeExtensionsPanel() {
|
||||
var statusEl = document.querySelector('[data-extensions-status="knowledge"]');
|
||||
if (!statusEl) return;
|
||||
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
|
||||
try {
|
||||
var resp = await fetch('/api/knowledge-rag/status');
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
var data = await resp.json();
|
||||
_knowledgeRagExtensionsCache = data;
|
||||
var indexedCount = (data.files && data.files.length) || data.total_files || 0;
|
||||
var statusText = data.ready ? '就绪' : '未就绪';
|
||||
var statusClass = data.ready ? 'mnote-status-ready' : 'mnote-status-pending';
|
||||
var lastIngest = data.last_ingest_time || '';
|
||||
statusEl.innerHTML = '' +
|
||||
'<div class="mnote-extensions-item">' +
|
||||
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">LightRAG 状态</span><span class="' + statusClass + '">' + escapeHtml(String(statusText)) + '</span></div>' +
|
||||
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">已索引文件</span><span>' + indexedCount + ' 个</span></div>' +
|
||||
(lastIngest ? '<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">最近更新</span><span>' + escapeHtml(String(lastIngest)) + '</span></div>' : '') +
|
||||
'<div class="mnote-extensions-item-actions">' +
|
||||
'<button type="button" class="wolai-page-settings-action" data-knowledge-rag-action="manage">管理知识库...</button>' +
|
||||
'<button type="button" class="wolai-page-settings-action" data-knowledge-rag-action="refresh-status">刷新状态</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法连接 LightRAG 服务</span></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Skills 子面板 ---
|
||||
async function refreshSkillsExtensionsPanel() {
|
||||
var statusEl = document.querySelector('[data-extensions-status="skills"]');
|
||||
if (!statusEl) return;
|
||||
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
|
||||
try {
|
||||
var resp = await fetch('/api/page-ai/agent-descriptors');
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
var data = await resp.json();
|
||||
var descriptors = data.descriptors || data || [];
|
||||
// 从 agent descriptor 中收集 skills 信息
|
||||
var allSkills = [];
|
||||
descriptors.forEach(function(desc) {
|
||||
var caps = desc.capabilities || desc.capabilityStates || {};
|
||||
if (caps.skills && Array.isArray(caps.skills)) {
|
||||
caps.skills.forEach(function(s) { allSkills.push({ name: s, source: desc.name || desc.id || 'unknown' }); });
|
||||
}
|
||||
});
|
||||
if (!allSkills.length) {
|
||||
allSkills.push({ name: 'skill-read (内置)', source: 'hermes' });
|
||||
}
|
||||
var html = '';
|
||||
allSkills.forEach(function(skill) {
|
||||
html += '<div class="mnote-extensions-item">' +
|
||||
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">' + escapeHtml(skill.name) + '</span><span class="mnote-extensions-source">' + escapeHtml(skill.source) + '</span></div>' +
|
||||
'</div>';
|
||||
});
|
||||
if (!html) html = '<div class="mnote-extensions-item"><span>暂无已安装 Skills</span></div>';
|
||||
statusEl.innerHTML = html;
|
||||
} catch (err) {
|
||||
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法加载 Skills 信息</span></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// --- MCP 子面板 ---
|
||||
async function refreshMcpExtensionsPanel() {
|
||||
var statusEl = document.querySelector('[data-extensions-status="mcp"]');
|
||||
if (!statusEl) return;
|
||||
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
|
||||
try {
|
||||
var resp = await fetch('/api/hermes/mcp/servers');
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
var servers = await resp.json();
|
||||
var list = Array.isArray(servers) ? servers : (servers.servers || []);
|
||||
var html = '';
|
||||
if (list.length === 0) {
|
||||
html = '<div class="mnote-extensions-item"><span>暂无已配置 MCP 服务器</span></div>';
|
||||
} else {
|
||||
list.forEach(function(srv) {
|
||||
var name = srv.name || srv.id || '未命名';
|
||||
var url = srv.url || srv.command || '';
|
||||
var connected = srv.connected ? '已连接' : '未连接';
|
||||
var connClass = srv.connected ? 'mnote-status-ready' : 'mnote-status-pending';
|
||||
html += '<div class="mnote-extensions-item">' +
|
||||
'<div class="mnote-extensions-item-row"><span class="mnote-extensions-label">' + escapeHtml(name) + '</span><span class="' + connClass + '">' + connected + '</span></div>' +
|
||||
(url ? '<div class="mnote-extensions-item-row"><span class="mnote-extensions-detail">' + escapeHtml(String(url)) + '</span></div>' : '') +
|
||||
'</div>';
|
||||
});
|
||||
}
|
||||
statusEl.innerHTML = html;
|
||||
} catch (err) {
|
||||
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法加载 MCP 配置</span></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Agent 工具子面板 ---
|
||||
async function refreshToolsExtensionsPanel() {
|
||||
var statusEl = document.querySelector('[data-extensions-status="tools"]');
|
||||
if (!statusEl) return;
|
||||
statusEl.innerHTML = '<span class="mnote-extensions-loading">加载中...</span>';
|
||||
try {
|
||||
var resp = await fetch('/api/page-ai/agent-descriptors');
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
var data = await resp.json();
|
||||
var descriptors = data.descriptors || data || [];
|
||||
var html = '';
|
||||
descriptors.forEach(function(desc) {
|
||||
var tools = desc.tools || [];
|
||||
var name = desc.name || desc.id || 'unknown';
|
||||
if (!tools.length) return;
|
||||
html += '<div class="mnote-extensions-item mnote-extensions-agent-group">' +
|
||||
'<div class="mnote-extensions-agent-name">' + escapeHtml(name) + '</div>';
|
||||
tools.forEach(function(tool) {
|
||||
var toolName = tool.name || tool.function_name || '';
|
||||
var disabled = tool.disabled ? ' (已禁用)' : '';
|
||||
html += '<div class="mnote-extensions-tool-item">' +
|
||||
'<span class="mnote-extensions-tool-name">' + escapeHtml(toolName) + '</span>' +
|
||||
(disabled ? '<span class="mnote-extensions-tool-disabled">' + disabled + '</span>' : '') +
|
||||
'</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
});
|
||||
if (!html) html = '<div class="mnote-extensions-item"><span>暂无 Agent 工具信息</span></div>';
|
||||
statusEl.innerHTML = html;
|
||||
} catch (err) {
|
||||
statusEl.innerHTML = '<div class="mnote-extensions-item"><span class="mnote-extensions-error">无法加载 Agent 工具信息</span></div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function persistPageOptionsPatch(patch) {
|
||||
var previous = Object.assign({}, currentPageOptions());
|
||||
pageUiState.pageOptions = Object.assign({}, previous, patch);
|
||||
@@ -2158,6 +2528,88 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
if (isKnowledgeRagSettingsOpen()) void loadKnowledgeRagStatus(true);
|
||||
});
|
||||
|
||||
|
||||
// --- 仪表盘 ---
|
||||
function refreshDashboardPanel() {
|
||||
var popover = ensurePageSettingsPopover();
|
||||
// 知识库状态
|
||||
refreshDashboardKnowledgeCard(popover);
|
||||
// Skills
|
||||
refreshDashboardSkillsCard(popover);
|
||||
// MCP
|
||||
refreshDashboardMcpCard(popover);
|
||||
// Agent runs
|
||||
refreshDashboardAgentCard(popover);
|
||||
}
|
||||
|
||||
async function refreshDashboardKnowledgeCard(popover) {
|
||||
var el = popover.querySelector('[data-dashboard-value="knowledge"]');
|
||||
if (!el) return;
|
||||
el.textContent = '加载中...';
|
||||
try {
|
||||
var resp = await fetch('/api/knowledge-rag/status');
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
var data = await resp.json();
|
||||
var count = (data.files && data.files.length) || data.total_files || 0;
|
||||
var statusText = data.ready ? '就绪' : '未就绪';
|
||||
el.textContent = count + ' 文件 / ' + statusText;
|
||||
} catch (_) {
|
||||
el.textContent = '不可用';
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshDashboardSkillsCard(popover) {
|
||||
var el = popover.querySelector('[data-dashboard-value="skills"]');
|
||||
if (!el) return;
|
||||
el.textContent = '加载中...';
|
||||
try {
|
||||
var resp = await fetch('/api/page-ai/agent-descriptors');
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
var data = await resp.json();
|
||||
var descriptors = data.descriptors || data || [];
|
||||
var count = 0;
|
||||
descriptors.forEach(function(d) {
|
||||
var caps = d.capabilities || d.capabilityStates || {};
|
||||
if (caps.skills && Array.isArray(caps.skills)) count += caps.skills.length;
|
||||
});
|
||||
if (!count) count = 1; // skill-read 内置
|
||||
el.textContent = count + ' 个';
|
||||
} catch (_) {
|
||||
el.textContent = '--';
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshDashboardMcpCard(popover) {
|
||||
var el = popover.querySelector('[data-dashboard-value="mcp"]');
|
||||
if (!el) return;
|
||||
el.textContent = '加载中...';
|
||||
try {
|
||||
var resp = await fetch('/api/hermes/mcp/servers');
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
var servers = await resp.json();
|
||||
var list = Array.isArray(servers) ? servers : (servers.servers || []);
|
||||
var connected = list.filter(function(s) { return s.connected; }).length;
|
||||
el.textContent = list.length + ' / ' + connected + ' 已连接';
|
||||
} catch (_) {
|
||||
el.textContent = '--';
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshDashboardAgentCard(popover) {
|
||||
var el = popover.querySelector('[data-dashboard-value="agent"]');
|
||||
if (!el) return;
|
||||
el.textContent = '加载中...';
|
||||
try {
|
||||
var resp = await fetch('/api/hermes/client/runs?limit=100');
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
var data = await resp.json();
|
||||
var runs = Array.isArray(data) ? data : (data.runs || []);
|
||||
var recentCount = runs.length;
|
||||
el.textContent = recentCount + ' 次';
|
||||
} catch (_) {
|
||||
el.textContent = '--';
|
||||
}
|
||||
}
|
||||
return {
|
||||
addLocalIndexRange,
|
||||
applyPageOptionsToShell,
|
||||
@@ -2191,6 +2643,12 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
recordPageHistorySnapshot,
|
||||
removeLocalIndexRange,
|
||||
renderPageSettingsPopover,
|
||||
refreshExtensionsPanel,
|
||||
refreshDashboardPanel,
|
||||
|
||||
refreshKnowledgeExtensionsPanel,
|
||||
setActiveExtensionsSubtab,
|
||||
|
||||
setActivePageSettingsTab,
|
||||
refreshLocalIndex,
|
||||
addKnowledgeRagSourceInput,
|
||||
@@ -2199,6 +2657,10 @@ export function createSidebarPageSettingsRuntime(context) {
|
||||
ingestSingleKnowledgeRagSource,
|
||||
loadKnowledgeRagStatus,
|
||||
openKnowledgeRagDashboard,
|
||||
performKnowledgeRagSearch,
|
||||
saveKnowledgeRagSearchParams,
|
||||
setActiveKnowledgeRagDetailTab,
|
||||
|
||||
pruneKnowledgeRagRegistry,
|
||||
removeKnowledgeRagSourceInput,
|
||||
setKnowledgeRagSourceFilter,
|
||||
|
||||
Reference in New Issue
Block a user