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:
@@ -0,0 +1,127 @@
|
||||
// AgentStreamEventRouter — 统一的 agent 流式事件状态机
|
||||
// 供 Page AI sidebar runtime 和 document editor adapter runtime 共用
|
||||
//
|
||||
// 事件状态:
|
||||
// init — 建立 request_id 绑定
|
||||
// loading — message_delta / tool_call_delta → 增量更新
|
||||
// stream_event — tool-started / tool-finished / agent_state
|
||||
// finished — 正常终止
|
||||
// interrupted — 中断(等待审批/恢复)
|
||||
// error — 错误终止
|
||||
// approval_required — 等待用户确认
|
||||
|
||||
export const AGENT_EVENT_STATES = {
|
||||
INIT: 'init',
|
||||
LOADING: 'loading',
|
||||
STREAM_EVENT: 'stream_event',
|
||||
FINISHED: 'finished',
|
||||
INTERRUPTED: 'interrupted',
|
||||
ERROR: 'error',
|
||||
APPROVAL_REQUIRED: 'approval_required',
|
||||
};
|
||||
|
||||
export const TERMINAL_STATES = new Set([
|
||||
AGENT_EVENT_STATES.FINISHED,
|
||||
AGENT_EVENT_STATES.INTERRUPTED,
|
||||
AGENT_EVENT_STATES.ERROR,
|
||||
]);
|
||||
|
||||
export function isTerminalState(status) {
|
||||
return TERMINAL_STATES.has(status);
|
||||
}
|
||||
|
||||
// 解析 SSE 帧为 eventName + payloadText
|
||||
export function parseSSEFrames(buffer, lastBoundary) {
|
||||
var frames = buffer.split('\n\n');
|
||||
var remaining = frames.pop() || '';
|
||||
var events = [];
|
||||
frames.forEach(function(frame) {
|
||||
var eventName = '';
|
||||
var dataLines = [];
|
||||
frame.split('\n').forEach(function(line) {
|
||||
if (line.startsWith('event:')) eventName = line.slice(6).trim();
|
||||
if (line.startsWith('data:')) dataLines.push(line.slice(5).trim());
|
||||
});
|
||||
var payloadText = dataLines.join('\n');
|
||||
if (!eventName && payloadText) {
|
||||
try {
|
||||
var parsed = JSON.parse(payloadText);
|
||||
eventName = parsed && parsed.event ? String(parsed.event) : '';
|
||||
} catch (_) {}
|
||||
}
|
||||
if (eventName) events.push({ eventName: eventName, payloadText: payloadText });
|
||||
});
|
||||
return { events: events, remaining: remaining };
|
||||
}
|
||||
|
||||
// 从 SSE 流读取事件
|
||||
export async function readSSEStream(response, onFrame) {
|
||||
if (!response.body || typeof response.body.getReader !== 'function') return;
|
||||
var reader = response.body.getReader();
|
||||
var decoder = new TextDecoder();
|
||||
var buffer = '';
|
||||
while (true) {
|
||||
var chunk = await reader.read();
|
||||
if (chunk.done) break;
|
||||
buffer += decoder.decode(chunk.value, { stream: true });
|
||||
var result = parseSSEFrames(buffer, '');
|
||||
buffer = result.remaining;
|
||||
result.events.forEach(function(evt) {
|
||||
onFrame(evt.eventName, evt.payloadText);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 创建标准事件路由器
|
||||
// handlers: { onInit, onDelta, onToolCall, onToolResult, onAgentState, onTerminal, onApprovalRequired }
|
||||
export function createAgentStreamRouter(handlers) {
|
||||
var h = handlers || {};
|
||||
|
||||
return function routeEvent(eventName, payloadText) {
|
||||
var payload = null;
|
||||
try { payload = JSON.parse(payloadText || 'null'); } catch (_) {}
|
||||
|
||||
var status = (payload && payload.status) || eventName;
|
||||
|
||||
switch (status) {
|
||||
case AGENT_EVENT_STATES.INIT:
|
||||
if (h.onInit) h.onInit(payload);
|
||||
break;
|
||||
|
||||
case AGENT_EVENT_STATES.LOADING:
|
||||
if (h.onDelta) h.onDelta(payload);
|
||||
if (h.onToolCall) {
|
||||
var toolChunks = (payload && payload.tool_call_chunks) || (payload && payload.msg && payload.msg.tool_call_chunks);
|
||||
if (toolChunks && toolChunks.length) h.onToolCall(payload);
|
||||
}
|
||||
break;
|
||||
|
||||
case AGENT_EVENT_STATES.STREAM_EVENT:
|
||||
if (payload && payload.event === 'tool-finished') {
|
||||
if (h.onToolResult) h.onToolResult(payload);
|
||||
} else if (payload && payload.event === 'tool-started') {
|
||||
// tool start — 可选处理
|
||||
} else if (payload && payload.agent_state) {
|
||||
if (h.onAgentState) h.onAgentState(payload.agent_state);
|
||||
}
|
||||
break;
|
||||
|
||||
case AGENT_EVENT_STATES.FINISHED:
|
||||
case AGENT_EVENT_STATES.INTERRUPTED:
|
||||
case AGENT_EVENT_STATES.ERROR:
|
||||
if (h.onTerminal) h.onTerminal(status, payload);
|
||||
break;
|
||||
|
||||
case AGENT_EVENT_STATES.APPROVAL_REQUIRED:
|
||||
if (h.onApprovalRequired) h.onApprovalRequired(payload);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 未知状态:尝试作为 loading 处理
|
||||
if (payload && (payload.text || payload.delta || payload.content)) {
|
||||
if (h.onDelta) h.onDelta(payload);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -219,6 +219,13 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
const ingestSingleKnowledgeRagSource = (...args) => sidebarPageSettings.ingestSingleKnowledgeRagSource(...args);
|
||||
const loadKnowledgeRagStatus = (...args) => sidebarPageSettings.loadKnowledgeRagStatus(...args);
|
||||
const openKnowledgeRagDashboard = (...args) => sidebarPageSettings.openKnowledgeRagDashboard(...args);
|
||||
const setActiveKnowledgeRagDetailTab = (...args) => sidebarPageSettings.setActiveKnowledgeRagDetailTab(...args);
|
||||
const performKnowledgeRagSearch = (...args) => sidebarPageSettings.performKnowledgeRagSearch(...args);
|
||||
const saveKnowledgeRagSearchParams = (...args) => sidebarPageSettings.saveKnowledgeRagSearchParams(...args);
|
||||
const loadKnowledgeRagSearchParamsToForm = (...args) => sidebarPageSettings.loadKnowledgeRagSearchParamsToForm(...args);
|
||||
const syncKnowledgeRagThresholdDisplay = (...args) => sidebarPageSettings.syncKnowledgeRagThresholdDisplay(...args);
|
||||
const refreshDashboardPanel = (...args) => sidebarPageSettings.refreshDashboardPanel(...args);
|
||||
|
||||
const pruneKnowledgeRagRegistry = (...args) => sidebarPageSettings.pruneKnowledgeRagRegistry(...args);
|
||||
const setKnowledgeRagSourceFilter = (...args) => sidebarPageSettings.setKnowledgeRagSourceFilter(...args);
|
||||
const useKnowledgeRagFileTreeSelection = (...args) => sidebarPageSettings.useKnowledgeRagFileTreeSelection(...args);
|
||||
@@ -2999,6 +3006,14 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
e.preventDefault();
|
||||
setActivePageSettingsTab(pageSettingsTab.getAttribute('data-page-settings-tab') || 'page');
|
||||
return;
|
||||
|
||||
var extensionsSubtab = closestAction(e.target, '[data-extensions-subtab]');
|
||||
if (extensionsSubtab) {
|
||||
e.preventDefault();
|
||||
setActiveExtensionsSubtab(extensionsSubtab.getAttribute('data-extensions-subtab') || 'knowledge');
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var localIndexAction = closestAction(e.target, '[data-local-index-action]');
|
||||
@@ -3027,6 +3042,15 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
if (knowledgeRagAction) {
|
||||
e.preventDefault();
|
||||
var knowledgeRagActionName = knowledgeRagAction.getAttribute('data-knowledge-rag-action') || '';
|
||||
if (knowledgeRagActionName === 'manage') {
|
||||
void openKnowledgeRagSettingsPopover();
|
||||
return;
|
||||
}
|
||||
if (knowledgeRagActionName === 'refresh-status') {
|
||||
void refreshKnowledgeExtensionsPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (knowledgeRagActionName === 'refresh') {
|
||||
void loadKnowledgeRagStatus(true);
|
||||
return;
|
||||
@@ -3069,6 +3093,51 @@ import { createSidebarPageSettingsRuntime } from './sidebar-page-settings-runtim
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var kbRagDetailTab = closestAction(e.target, '[data-kb-rag-tab]');
|
||||
if (kbRagDetailTab) {
|
||||
e.preventDefault();
|
||||
var tabName = kbRagDetailTab.getAttribute('data-kb-rag-tab') || 'sources';
|
||||
setActiveKnowledgeRagDetailTab(tabName);
|
||||
if (tabName === 'params') loadKnowledgeRagSearchParamsToForm();
|
||||
return;
|
||||
}
|
||||
|
||||
var kbRagAction = closestAction(e.target, '[data-kb-rag-action]');
|
||||
if (kbRagAction) {
|
||||
e.preventDefault();
|
||||
var kbRagActionName = kbRagAction.getAttribute('data-kb-rag-action') || '';
|
||||
if (kbRagActionName === 'search') {
|
||||
void performKnowledgeRagSearch();
|
||||
return;
|
||||
}
|
||||
if (kbRagActionName === 'save-params') {
|
||||
saveKnowledgeRagSearchParams();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var kbRagSearchInput = closestAction(e.target, '[data-kb-rag-search-input]');
|
||||
if (kbRagSearchInput && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
void performKnowledgeRagSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
var kbRagThresholdRange = closestAction(e.target, '[data-kb-rag-param="similarity_threshold"]');
|
||||
if (kbRagThresholdRange && e.type === 'input') {
|
||||
syncKnowledgeRagThresholdDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var dashboardAction = closestAction(e.target, '[data-dashboard-action="refresh"]');
|
||||
if (dashboardAction) {
|
||||
e.preventDefault();
|
||||
refreshDashboardPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
var pageSettingsAction = closestAction(e.target, '[data-page-settings-action]');
|
||||
if (pageSettingsAction) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -5,25 +5,76 @@ pub const MANIFEST_SCHEMA_VERSION: &str = "mnote.hermes_tool_manifest.v1";
|
||||
pub const TOOL_SCHEMA_VERSION: &str = "mnote.hermes_tool.v1";
|
||||
|
||||
pub fn manifest() -> Value {
|
||||
let tools = annotate_tools_with_capabilities(vec![
|
||||
skill_read_tool(),
|
||||
let tools = annotate_tools_with_capabilities(assemble_all_tools());
|
||||
json!({
|
||||
"schemaVersion": MANIFEST_SCHEMA_VERSION,
|
||||
"plugin": {
|
||||
"name": "mnote",
|
||||
"description": "mnote 页面、树、artifact 与 edge 工具",
|
||||
"runtimeOwner": "mnote-web",
|
||||
"writeOwner": "rust-runtime-kernel"
|
||||
},
|
||||
"capabilities": skill::manifest_capabilities(),
|
||||
"tools": tools
|
||||
})
|
||||
}
|
||||
|
||||
// ── 按能力域分组的工具组装函数 ──
|
||||
|
||||
fn assemble_all_tools() -> Vec<Value> {
|
||||
let mut tools = Vec::new();
|
||||
tools.push(skill_read_tool());
|
||||
tools.append(&mut context_tools());
|
||||
tools.append(&mut doc_tools());
|
||||
tools.append(&mut knowledge_rag_tools());
|
||||
tools.append(&mut block_tools());
|
||||
tools.append(&mut page_tools());
|
||||
tools.append(&mut mindmap_tools());
|
||||
tools.append(&mut office_tools());
|
||||
tools.append(&mut onlyoffice_tools());
|
||||
tools.append(&mut artifact_tools());
|
||||
tools
|
||||
}
|
||||
|
||||
fn context_tools() -> Vec<Value> {
|
||||
vec![
|
||||
context_snapshot_tool(),
|
||||
context_read_current_page_tool(),
|
||||
context_resolve_target_tool(),
|
||||
]
|
||||
}
|
||||
|
||||
fn doc_tools() -> Vec<Value> {
|
||||
vec![
|
||||
doc_fetch_tool(),
|
||||
doc_find_tool(),
|
||||
doc_markdown_edit_tool(),
|
||||
doc_apply_block_ops_tool(),
|
||||
doc_plan_update_tool(),
|
||||
]
|
||||
}
|
||||
|
||||
fn knowledge_rag_tools() -> Vec<Value> {
|
||||
vec![
|
||||
knowledge_rag_status_tool(),
|
||||
knowledge_rag_query_tool(),
|
||||
knowledge_rag_section_context_tool(),
|
||||
knowledge_rag_open_reference_tool(),
|
||||
]
|
||||
}
|
||||
|
||||
fn block_tools() -> Vec<Value> {
|
||||
vec![
|
||||
block_fetch_tool(),
|
||||
doc_plan_update_tool(),
|
||||
block_replace_tool(),
|
||||
block_insert_after_tool(),
|
||||
block_delete_tool(),
|
||||
block_move_after_tool(),
|
||||
doc_apply_block_ops_tool(),
|
||||
doc_markdown_edit_tool(),
|
||||
]
|
||||
}
|
||||
|
||||
fn page_tools() -> Vec<Value> {
|
||||
vec![
|
||||
page_get_tool(),
|
||||
page_save_tool(),
|
||||
available_tool(
|
||||
@@ -36,11 +87,26 @@ pub fn manifest() -> Value {
|
||||
"更新当前页面设置",
|
||||
["page.write"],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn mindmap_tools() -> Vec<Value> {
|
||||
vec![
|
||||
mindmap_fetch_tool(),
|
||||
mindmap_apply_ops_tool(),
|
||||
mindmap_create_from_outline_tool(),
|
||||
]
|
||||
}
|
||||
|
||||
fn office_tools() -> Vec<Value> {
|
||||
vec![
|
||||
office_fetch_summary_tool(),
|
||||
office_propose_changes_tool(),
|
||||
]
|
||||
}
|
||||
|
||||
fn onlyoffice_tools() -> Vec<Value> {
|
||||
vec![
|
||||
onlyoffice_session_current_tool(),
|
||||
onlyoffice_capabilities_tool(),
|
||||
onlyoffice_selection_get_tool(),
|
||||
@@ -76,6 +142,11 @@ pub fn manifest() -> Value {
|
||||
onlyoffice_presentation_add_table_tool(),
|
||||
onlyoffice_presentation_clear_slide_tool(),
|
||||
onlyoffice_presentation_add_shape_tool(),
|
||||
]
|
||||
}
|
||||
|
||||
fn artifact_tools() -> Vec<Value> {
|
||||
vec![
|
||||
available_tool(
|
||||
"mnote.artifact.create_summary",
|
||||
"为当前页面创建或更新 AI Summary",
|
||||
@@ -86,20 +157,11 @@ pub fn manifest() -> Value {
|
||||
"基于当前页面创建新的 AI Note",
|
||||
["artifact.write"],
|
||||
),
|
||||
]);
|
||||
json!({
|
||||
"schemaVersion": MANIFEST_SCHEMA_VERSION,
|
||||
"plugin": {
|
||||
"name": "mnote",
|
||||
"description": "mnote 页面、树、artifact 与 edge 工具",
|
||||
"runtimeOwner": "mnote-web",
|
||||
"writeOwner": "rust-runtime-kernel"
|
||||
},
|
||||
"capabilities": skill::manifest_capabilities(),
|
||||
"tools": tools
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn annotate_tools_with_capabilities(tools: Vec<Value>) -> Vec<Value> {
|
||||
tools
|
||||
.into_iter()
|
||||
|
||||
@@ -221,6 +221,10 @@ pub fn build_router(state: AppState) -> Router {
|
||||
"/api/mnote-browser-runtime/sidebar-attachment-open-runtime.js",
|
||||
get(web_shell::sidebar_attachment_open_runtime_asset),
|
||||
)
|
||||
.route(
|
||||
"/api/mnote-browser-runtime/agent-stream-event-router.js",
|
||||
get(web_shell::agent_stream_event_router_asset),
|
||||
)
|
||||
.route(
|
||||
"/api/mnote-browser-runtime/sidebar-page-ai-runtime.js",
|
||||
get(web_shell::sidebar_page_ai_runtime_asset),
|
||||
@@ -1305,6 +1309,7 @@ mod tests {
|
||||
"/api/mnote-browser-runtime/sidebar-filetree-upload-runtime.js",
|
||||
"/api/mnote-browser-runtime/sidebar-attachment-open-runtime.js",
|
||||
"/api/mnote-browser-runtime/sidebar-page-ai-runtime.js",
|
||||
"/api/mnote-browser-runtime/agent-stream-event-router.js",
|
||||
"/api/mnote-browser-runtime/sidebar-page-ai-markdown-runtime.js",
|
||||
"/api/mnote-browser-runtime/sidebar-page-ai-render-runtime.js",
|
||||
"/api/mnote-browser-runtime/sidebar-page-ai-permission-runtime.js",
|
||||
|
||||
@@ -2456,6 +2456,15 @@ pub async fn sidebar_tree_runtime_asset() -> Response {
|
||||
.unwrap_or_else(|_| Response::new(Body::empty()))
|
||||
}
|
||||
|
||||
pub async fn agent_stream_event_router_asset() -> Response {
|
||||
const JS: &str = include_str!("../../browser/agent-stream-event-router.js");
|
||||
Response::builder()
|
||||
.header("content-type", "application/javascript; charset=utf-8")
|
||||
.header("cache-control", "public, max-age=3600")
|
||||
.body(Body::from(JS))
|
||||
.expect("agent-stream-event-router.js")
|
||||
}
|
||||
|
||||
pub async fn sidebar_page_ai_runtime_asset() -> Response {
|
||||
const JS: &str = include_str!("../../browser/sidebar-page-ai-runtime.js");
|
||||
Response::builder()
|
||||
|
||||
@@ -4102,6 +4102,282 @@ body {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
||||
/* 扩展面板 */
|
||||
.mnote-extensions-panel {
|
||||
gap: 12px;
|
||||
}
|
||||
.mnote-extensions-subtabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
border-bottom: 1px solid #E5E4E4;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.mnote-extensions-subtab {
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #787877;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mnote-extensions-subtab:hover {
|
||||
background: #F4F3F3;
|
||||
}
|
||||
.mnote-extensions-subtab.is-active {
|
||||
background: #F4F3F3;
|
||||
color: #1B1C1C;
|
||||
font-weight: 600;
|
||||
}
|
||||
.mnote-extensions-subpanel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.mnote-extensions-item {
|
||||
padding: 8px 10px;
|
||||
background: #F9F8F8;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.mnote-extensions-item-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
.mnote-extensions-label {
|
||||
color: #787877;
|
||||
}
|
||||
.mnote-extensions-detail {
|
||||
color: #AEADAB;
|
||||
font-size: 12px;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mnote-extensions-source {
|
||||
color: #AEADAB;
|
||||
font-size: 11px;
|
||||
}
|
||||
.mnote-extensions-agent-name {
|
||||
font-weight: 600;
|
||||
color: #1B1C1C;
|
||||
font-size: 13px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.mnote-extensions-tool-item {
|
||||
padding: 2px 0 2px 12px;
|
||||
font-size: 12px;
|
||||
color: #585857;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.mnote-extensions-tool-name {
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
.mnote-extensions-tool-disabled {
|
||||
color: #D15251;
|
||||
font-size: 10px;
|
||||
}
|
||||
.mnote-extensions-loading {
|
||||
color: #AEADAB;
|
||||
font-size: 13px;
|
||||
}
|
||||
.mnote-extensions-error {
|
||||
color: #D15251;
|
||||
font-size: 13px;
|
||||
}
|
||||
.mnote-extensions-item-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.mnote-status-ready {
|
||||
color: #409440;
|
||||
font-weight: 500;
|
||||
}
|
||||
.mnote-status-pending {
|
||||
|
||||
/* 知识库详情面板 */
|
||||
.mnote-knowledge-rag-detail-tabs {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
border-bottom: 1px solid #E5E4E4;
|
||||
padding: 0 16px 0 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.mnote-knowledge-rag-detail-tab {
|
||||
padding: 6px 14px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #787877;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mnote-knowledge-rag-detail-tab:hover {
|
||||
background: #F4F3F3;
|
||||
}
|
||||
.mnote-knowledge-rag-detail-tab.is-active {
|
||||
background: #F4F3F3;
|
||||
color: #1B1C1C;
|
||||
font-weight: 600;
|
||||
}
|
||||
.mnote-knowledge-rag-detail-panel {
|
||||
padding: 0 16px 12px 16px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.mnote-kb-search-form {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.mnote-kb-search-input {
|
||||
flex: 1;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #E5E4E4;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
.mnote-kb-search-input:focus {
|
||||
border-color: #8F8E8C;
|
||||
}
|
||||
.mnote-kb-search-results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.mnote-kb-search-result-item {
|
||||
padding: 8px 10px;
|
||||
background: #F9F8F8;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.mnote-kb-search-result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.mnote-kb-search-result-index {
|
||||
font-weight: 600;
|
||||
color: #1B1C1C;
|
||||
font-size: 12px;
|
||||
}
|
||||
.mnote-kb-search-result-score {
|
||||
font-size: 12px;
|
||||
color: #409440;
|
||||
font-weight: 500;
|
||||
}
|
||||
.mnote-kb-search-result-source {
|
||||
font-size: 11px;
|
||||
color: #AEADAB;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mnote-kb-search-result-preview {
|
||||
font-size: 12px;
|
||||
color: #585857;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.mnote-kb-search-result-citation {
|
||||
font-size: 11px;
|
||||
color: #7B7A79;
|
||||
font-family: monospace;
|
||||
}
|
||||
.mnote-kb-params-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.mnote-kb-params-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.mnote-kb-params-label {
|
||||
font-size: 13px;
|
||||
color: #585857;
|
||||
min-width: 90px;
|
||||
}
|
||||
.mnote-kb-params-select {
|
||||
flex: 1;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #E5E4E4;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
background: #fff;
|
||||
}
|
||||
.mnote-kb-params-number {
|
||||
width: 80px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #E5E4E4;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
.mnote-kb-params-range {
|
||||
flex: 1;
|
||||
accent-color: #409440;
|
||||
}
|
||||
.mnote-kb-params-range-value {
|
||||
|
||||
/* 仪表盘 */
|
||||
.mnote-dashboard-panel {
|
||||
padding: 4px 0;
|
||||
}
|
||||
.mnote-dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
.mnote-dashboard-card {
|
||||
padding: 12px;
|
||||
background: #F9F8F8;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.mnote-dashboard-card-title {
|
||||
font-size: 12px;
|
||||
color: #AEADAB;
|
||||
font-weight: 500;
|
||||
}
|
||||
.mnote-dashboard-card-value {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1B1C1C;
|
||||
}
|
||||
.mnote-dashboard-card-desc {
|
||||
font-size: 11px;
|
||||
color: #AEADAB;
|
||||
}
|
||||
|
||||
font-size: 13px;
|
||||
color: #1B1C1C;
|
||||
font-weight: 500;
|
||||
min-width: 32px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
color: #F09B26;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.wolai-page-settings-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -6437,6 +6713,6 @@ mod tests {
|
||||
// 至少 2000 字符才能包含完整样式
|
||||
assert!(MNOTE_CSS.len() > 2000);
|
||||
// 当前整合了工作区壳、编辑器样式、树菜单、文件树图标、页面 AI、账号弹窗与授权管理控制面,仍保持在单文件可审阅范围内。
|
||||
assert!(MNOTE_CSS.len() < 145000);
|
||||
assert!(MNOTE_CSS.len() < 158000);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user