Add files via upload

This commit is contained in:
公明
2026-05-18 17:37:29 +08:00
committed by GitHub
parent ead2ce3ecc
commit 9a4f3c7d35
4 changed files with 288 additions and 32 deletions
+91 -9
View File
@@ -5931,14 +5931,41 @@ header {
flex-shrink: 0;
}
.mcp-stats-dist-donut {
position: relative;
.mcp-stats-dist-svg {
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
box-shadow:
0 2px 12px rgba(0, 0, 0, 0.08),
inset 0 0 0 1px rgba(255, 255, 255, 0.8);
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.08));
}
.mcp-stats-dist-segment {
cursor: pointer;
transition: opacity 0.15s ease, filter 0.15s ease;
outline: none;
}
.mcp-stats-dist-segment.is-others {
cursor: default;
}
.mcp-stats-dist-segment.is-dimmed {
opacity: 0.45;
}
.mcp-stats-dist-segment.is-highlighted {
opacity: 1;
filter: brightness(1.06);
}
.mcp-stats-dist-segment.is-active {
filter: brightness(1.08);
stroke: rgba(0, 102, 255, 0.55);
stroke-width: 0.6;
}
.mcp-stats-dist-segment:focus-visible {
outline: 2px solid rgba(0, 102, 255, 0.45);
outline-offset: 1px;
}
.mcp-stats-dist-donut-hole {
@@ -5952,17 +5979,34 @@ header {
justify-content: center;
gap: 2px;
box-shadow: inset 0 0 0 1px var(--border-color);
pointer-events: none;
}
.mcp-stats-dist-donut-label {
font-size: 0.6875rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
letter-spacing: 0.04em;
color: var(--text-muted);
max-width: 92%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.mcp-stats-dist-donut-label:not(.is-default) {
text-transform: none;
font-size: 0.625rem;
font-weight: 600;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.mcp-stats-dist-donut-value {
display: flex;
align-items: baseline;
justify-content: center;
gap: 1px;
font-size: 1.625rem;
font-weight: 800;
color: var(--text-primary);
@@ -5987,24 +6031,62 @@ header {
flex-shrink: 0;
}
.mcp-stats-dist-legend-item-wrap {
list-style: none;
margin: 0;
padding: 0;
}
.mcp-stats-dist-legend-item {
display: grid;
grid-template-columns: 6px minmax(0, 1fr);
grid-template-rows: auto auto;
gap: 2px 8px;
align-items: center;
width: 100%;
padding: 7px 8px;
border-radius: 8px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
transition: background 0.15s ease, border-color 0.15s ease;
transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
box-sizing: border-box;
font: inherit;
color: inherit;
text-align: left;
cursor: pointer;
}
.mcp-stats-dist-legend-item:hover {
.mcp-stats-dist-legend-item:hover,
.mcp-stats-dist-legend-item.is-highlighted {
border-color: rgba(0, 102, 255, 0.22);
background: rgba(0, 102, 255, 0.03);
}
.mcp-stats-dist-legend-item.is-active {
border-color: rgba(0, 102, 255, 0.35);
background: rgba(0, 102, 255, 0.05);
box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.1);
}
.mcp-stats-dist-legend-item.is-dimmed {
opacity: 0.5;
}
.mcp-stats-dist-legend-item:focus-visible {
outline: 2px solid rgba(0, 102, 255, 0.45);
outline-offset: 2px;
}
.mcp-stats-dist-legend-item.is-others {
cursor: default;
opacity: 0.72;
}
.mcp-stats-dist-legend-item.is-others:hover {
border-color: var(--border-color);
background: var(--bg-primary);
}
.mcp-stats-dist-swatch {
grid-row: 1 / 3;
align-self: stretch;
+4
View File
@@ -1321,6 +1321,10 @@
"statsSubtitle": "Refreshed {{time}} · {{count}} tools",
"distTitle": "Call distribution",
"distLegend": "Slice area shows share of all calls",
"distClickHint": "Click legend or slice to filter records",
"distHeaderHint": "{{n}} total calls",
"distSegmentAria": "{{name}}, {{pct}}% of calls, {{calls}} times",
"distOthersNoFilter": "Other tools cannot be filtered individually",
"distTotalCalls": "{{n}} total calls",
"distTop6Share": "Top {{n}} share of all calls",
"distOthers": "Other tools",
+4
View File
@@ -1310,6 +1310,10 @@
"statsSubtitle": "最后刷新 {{time}} · 共 {{count}} 个工具",
"distTitle": "调用分布",
"distLegend": "扇区面积为占全部调用比例",
"distClickHint": "点击图例或扇区筛选执行记录",
"distHeaderHint": "共 {{n}} 次调用",
"distSegmentAria": "{{name}},占 {{pct}}%{{calls}} 次",
"distOthersNoFilter": "其他工具无法单独筛选",
"distTotalCalls": "共 {{n}} 次调用",
"distTop6Share": "Top {{n}} 占全部调用",
"distOthers": "其他工具",
+189 -23
View File
@@ -3120,9 +3120,102 @@ function getMcpToolRateClass(rateNum) {
const MCP_STATS_DIST_COLORS = ['#3b82f6', '#22c55e', '#f59e0b', '#8b5cf6', '#14b8a6', '#ec4899'];
function renderMcpStatsInsightPanel(topTools, totals) {
function mcpStatsDescribeDonutSegment(startPct, endPct, outerR, innerR) {
if (endPct <= startPct) return '';
const span = endPct - startPct;
const cx = 50;
const cy = 50;
const point = (pct, r) => {
const rad = ((pct / 100) * 360 - 90) * Math.PI / 180;
return [cx + r * Math.cos(rad), cy + r * Math.sin(rad)];
};
if (span >= 99.995) {
const [x1, y1] = point(0, outerR);
const [x2, y2] = point(50, outerR);
const [x3, y3] = point(50, innerR);
const [x4, y4] = point(0, innerR);
const [x5, y5] = point(50, outerR);
const [x6, y6] = point(100, outerR);
const [x7, y7] = point(100, innerR);
const [x8, y8] = point(50, innerR);
return `M ${x1.toFixed(3)} ${y1.toFixed(3)} A ${outerR} ${outerR} 0 0 1 ${x2.toFixed(3)} ${y2.toFixed(3)} A ${outerR} ${outerR} 0 0 1 ${x6.toFixed(3)} ${y6.toFixed(3)} L ${x7.toFixed(3)} ${y7.toFixed(3)} A ${innerR} ${innerR} 0 0 0 ${x8.toFixed(3)} ${y8.toFixed(3)} A ${innerR} ${innerR} 0 0 0 ${x4.toFixed(3)} ${y4.toFixed(3)} Z`;
}
const large = span > 50 ? 1 : 0;
const [x1, y1] = point(startPct, outerR);
const [x2, y2] = point(endPct, outerR);
const [x3, y3] = point(endPct, innerR);
const [x4, y4] = point(startPct, innerR);
return `M ${x1.toFixed(3)} ${y1.toFixed(3)} A ${outerR} ${outerR} 0 ${large} 1 ${x2.toFixed(3)} ${y2.toFixed(3)} L ${x3.toFixed(3)} ${y3.toFixed(3)} A ${innerR} ${innerR} 0 ${large} 0 ${x4.toFixed(3)} ${y4.toFixed(3)} Z`;
}
function resetMcpStatsDistCenter(panel) {
if (!panel) return;
const label = panel.querySelector('.mcp-stats-dist-donut-label');
const value = panel.querySelector('.mcp-stats-dist-donut-value');
const unit = panel.querySelector('.mcp-stats-dist-donut-unit');
if (!label || !value) return;
label.textContent = panel.getAttribute('data-center-label') || '';
label.classList.add('is-default');
const centerVal = panel.getAttribute('data-center-value') || '';
const numEl = panel.querySelector('.mcp-stats-dist-donut-value-num');
if (numEl) numEl.textContent = centerVal;
else value.textContent = centerVal;
if (unit) {
unit.textContent = panel.getAttribute('data-center-suffix') || '%';
unit.hidden = false;
}
}
function previewMcpStatsDistCenter(panel, toolName, pct) {
if (!panel) return;
const label = panel.querySelector('.mcp-stats-dist-donut-label');
const value = panel.querySelector('.mcp-stats-dist-donut-value');
const unit = panel.querySelector('.mcp-stats-dist-donut-unit');
if (!label || !value) return;
const shortName = toolName.length > 14 ? `${toolName.slice(0, 13)}` : toolName;
label.textContent = shortName;
label.classList.remove('is-default');
const numEl = panel.querySelector('.mcp-stats-dist-donut-value-num');
if (numEl) numEl.textContent = pct;
else value.textContent = pct;
if (unit) unit.hidden = false;
}
function setMcpStatsDistHover(toolName) {
const panel = document.querySelector('.mcp-stats-dist-panel');
if (!panel) return;
const esc = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(toolName) : toolName.replace(/"/g, '\\"');
panel.querySelectorAll('.mcp-stats-dist-segment, .mcp-stats-dist-legend-item').forEach((el) => {
const t = el.getAttribute('data-tool-name') || '';
const match = toolName && t === toolName;
el.classList.toggle('is-highlighted', !!match);
el.classList.toggle('is-dimmed', !!toolName && !match && t);
});
if (toolName) {
const el = panel.querySelector(`[data-tool-name="${esc}"]`);
if (el) {
previewMcpStatsDistCenter(panel, toolName, el.getAttribute('data-pct') || '');
}
} else {
resetMcpStatsDistCenter(panel);
}
}
function handleMonitorStatsToolFilter(toolName) {
if (!toolName) return;
const toolFilter = document.getElementById('monitor-tool-filter');
if (toolFilter && toolFilter.value === toolName) {
clearMonitorToolFilter();
return;
}
filterMonitorByTool(toolName);
}
function renderMcpStatsInsightPanel(topTools, totals, activeToolFilter = '') {
const distTitle = mcpMonitorT('distTitle') || '调用分布';
const distLegend = mcpMonitorT('distLegend') || '扇区面积为占全部调用比例';
const distClickHint = mcpMonitorT('distClickHint') || '点击图例或扇区筛选执行记录';
const distOthersTitle = mcpMonitorT('distOthersNoFilter') || '其他工具无法单独筛选';
const top6ShareLabel = mcpMonitorT('distTop6Share', { n: MCP_STATS_TOP_N }) || `Top ${MCP_STATS_TOP_N} 占全部调用`;
const othersLabel = mcpMonitorT('distOthers') || '其他工具';
const callsUnit = (n) => mcpMonitorT('distCallsUnit', { n }) || `${n}`;
@@ -3144,6 +3237,7 @@ function renderMcpStatsInsightPanel(topTools, totals) {
name: tool.toolName || '',
calls,
pct: pct.toFixed(1),
isOthers: false,
});
acc += pct;
});
@@ -3156,19 +3250,51 @@ function renderMcpStatsInsightPanel(topTools, totals) {
name: othersLabel,
calls: otherCalls,
pct: pct.toFixed(1),
isOthers: true,
});
}
const conic = segments.length > 0
? segments.map(s => `${s.color} ${s.start.toFixed(2)}% ${s.end.toFixed(2)}%`).join(', ')
: '#e2e8f0 0% 100%';
const legendHtml = segments.map(s => `
<li class="mcp-stats-dist-legend-item">
const segmentPathsHtml = segments.map((s) => {
const pathD = mcpStatsDescribeDonutSegment(s.start, s.end, 48, 30);
if (!pathD) return '';
const isActive = !s.isOthers && activeToolFilter && activeToolFilter === s.name;
const segAria = s.isOthers
? escapeHtml(s.name)
: escapeHtml(mcpMonitorT('distSegmentAria', { name: s.name, pct: s.pct, calls: s.calls })
|| `${s.name},占 ${s.pct}%${s.calls}`);
return `<path class="mcp-stats-dist-segment${isActive ? ' is-active' : ''}${s.isOthers ? ' is-others' : ''}"
d="${pathD}"
fill="${s.color}"
data-tool-name="${s.isOthers ? '' : escapeHtml(s.name)}"
data-pct="${s.pct}"
data-calls="${s.calls}"
data-is-others="${s.isOthers ? '1' : '0'}"
tabindex="${s.isOthers ? '-1' : '0'}"
role="${s.isOthers ? 'presentation' : 'button'}"
aria-label="${segAria}" />`;
}).join('');
const legendHtml = segments.map((s) => {
const isActive = !s.isOthers && activeToolFilter && activeToolFilter === s.name;
const inner = `
<span class="mcp-stats-dist-swatch" style="--swatch-color:${s.color}"></span>
<span class="mcp-stats-dist-legend-name" title="${escapeHtml(s.name)}">${escapeHtml(s.name)}</span>
<span class="mcp-stats-dist-legend-meta"><em>${s.pct}%</em><span>${escapeHtml(callsUnit(s.calls))}</span></span>
</li>
`).join('');
<span class="mcp-stats-dist-legend-meta"><em>${s.pct}%</em><span>${escapeHtml(callsUnit(s.calls))}</span></span>`;
if (s.isOthers) {
return `<li class="mcp-stats-dist-legend-item is-others" title="${escapeHtml(distOthersTitle)}" data-is-others="1">${inner}</li>`;
}
const rowAria = mcpMonitorT('toolRowAriaLabel', { name: s.name, total: s.calls, rate: s.pct })
|| `${s.name}${s.calls} 次调用,占 ${s.pct}%`;
return `<li class="mcp-stats-dist-legend-item-wrap">
<button type="button" class="mcp-stats-dist-legend-item${isActive ? ' is-active' : ''}"
data-tool-name="${escapeHtml(s.name)}"
data-pct="${s.pct}"
data-calls="${s.calls}"
data-is-others="0"
aria-label="${escapeHtml(rowAria)}"
aria-pressed="${isActive ? 'true' : 'false'}">${inner}</button>
</li>`;
}).join('');
const centerLabel = `Top ${MCP_STATS_TOP_N}`;
const distHint = totals.total > 0
@@ -3176,25 +3302,30 @@ function renderMcpStatsInsightPanel(topTools, totals) {
: '';
return `
<div class="mcp-stats-tools-panel mcp-stats-dist-panel" aria-label="${escapeHtml(distTitle)}">
<div class="mcp-stats-tools-panel mcp-stats-dist-panel" aria-label="${escapeHtml(distTitle)}"
data-center-label="${escapeHtml(centerLabel)}"
data-center-value="${top6SharePct}"
data-center-suffix="%">
<div class="mcp-stats-tools-header">
<div class="mcp-stats-tools-heading">
<h4 class="mcp-stats-tools-title">${escapeHtml(distTitle)}</h4>
<span class="mcp-stats-tools-legend">${escapeHtml(distLegend)}</span>
<span class="mcp-stats-tools-legend">${escapeHtml(distLegend)} · ${escapeHtml(distClickHint)}</span>
</div>
${distHint ? `<span class="mcp-stats-tools-hint">${escapeHtml(distHint)}</span>` : ''}
<span class="mcp-stats-tools-hint">${escapeHtml(distHint)}</span>
</div>
<div class="mcp-stats-dist-body mcp-stats-dist-body--stacked">
<div class="mcp-stats-dist-chart-stage">
<div class="mcp-stats-dist-chart-wrap">
<div class="mcp-stats-dist-donut" style="background:conic-gradient(${conic})" role="img" aria-label="${escapeHtml(top6ShareLabel)} ${top6SharePct}%"></div>
<div class="mcp-stats-dist-donut-hole">
<span class="mcp-stats-dist-donut-label">${centerLabel}</span>
<span class="mcp-stats-dist-donut-value">${top6SharePct}<span class="mcp-stats-dist-donut-unit">%</span></span>
</div>
<div class="mcp-stats-dist-chart-stage">
<div class="mcp-stats-dist-chart-wrap">
<svg class="mcp-stats-dist-svg" viewBox="0 0 100 100" role="img" aria-label="${escapeHtml(top6ShareLabel)} ${top6SharePct}%">
<g class="mcp-stats-dist-segments">${segmentPathsHtml}</g>
</svg>
<div class="mcp-stats-dist-donut-hole" aria-hidden="true">
<span class="mcp-stats-dist-donut-label is-default">${centerLabel}</span>
<span class="mcp-stats-dist-donut-value"><span class="mcp-stats-dist-donut-value-num">${top6SharePct}</span><span class="mcp-stats-dist-donut-unit">%</span></span>
</div>
</div>
<ul class="mcp-stats-dist-legend mcp-stats-dist-legend--grid">${legendHtml}</ul>
</div>
<ul class="mcp-stats-dist-legend mcp-stats-dist-legend--grid">${legendHtml}</ul>
</div>
</div>
`;
@@ -3260,14 +3391,49 @@ function bindMonitorStatsPanelEvents() {
clearMonitorToolFilter();
return;
}
const row = e.target.closest('.mcp-stats-tool-row');
const distEl = e.target.closest('.mcp-stats-dist-segment[data-tool-name], .mcp-stats-dist-legend-item[data-tool-name]');
if (distEl && distEl.getAttribute('data-is-others') !== '1') {
const tool = distEl.getAttribute('data-tool-name');
if (tool) {
e.preventDefault();
handleMonitorStatsToolFilter(tool);
}
return;
}
const row = e.target.closest('.mcp-stats-tool-row');
if (!row) return;
const tool = row.getAttribute('data-tool-name');
if (tool) {
e.preventDefault();
filterMonitorByTool(tool);
handleMonitorStatsToolFilter(tool);
}
});
root.addEventListener('keydown', function (e) {
if (e.key !== 'Enter' && e.key !== ' ') return;
const distSeg = e.target.closest('.mcp-stats-dist-segment[data-tool-name]');
if (!distSeg || distSeg.getAttribute('data-is-others') === '1') return;
const tool = distSeg.getAttribute('data-tool-name');
if (tool) {
e.preventDefault();
handleMonitorStatsToolFilter(tool);
}
});
root.addEventListener('mouseover', function (e) {
const el = e.target.closest('.mcp-stats-dist-segment[data-tool-name], .mcp-stats-dist-legend-item[data-tool-name]');
if (!el || el.getAttribute('data-is-others') === '1') return;
const tool = el.getAttribute('data-tool-name');
if (tool) setMcpStatsDistHover(tool);
});
root.addEventListener('mouseout', function (e) {
const el = e.target.closest('.mcp-stats-dist-segment[data-tool-name], .mcp-stats-dist-legend-item[data-tool-name]');
if (!el) return;
const related = e.relatedTarget;
const next = related && related.closest
? related.closest('.mcp-stats-dist-segment[data-tool-name], .mcp-stats-dist-legend-item[data-tool-name]')
: null;
if (next) return;
setMcpStatsDistHover('');
});
monitorStatsPanelEventsBound = true;
}
@@ -3433,7 +3599,7 @@ function renderMonitorStats(statsMap = {}, lastFetchedAt = null) {
</div>
</div>
<div class="mcp-stats-split-right">
${renderMcpStatsInsightPanel(topTools, totals)}
${renderMcpStatsInsightPanel(topTools, totals, activeToolFilter)}
</div>
</div>
` : ''}