fix: add axes to sentinel memory chart

This commit is contained in:
Codex
2026-07-01 02:44:58 +00:00
parent c61ba69ce7
commit 5f00fe4cf3
4 changed files with 130 additions and 15 deletions
@@ -977,13 +977,12 @@ select {
.memory-chart {
display: block;
width: 100%;
height: 154px;
height: 192px;
border: 1px solid var(--line);
border-radius: 8px;
background: #ffffff;
}
.memory-axis,
.memory-legend {
display: flex;
min-width: 0;
@@ -993,6 +992,44 @@ select {
font-size: 12px;
}
.memory-grid-line {
stroke: #e5ecea;
stroke-width: 1;
}
.memory-axis-line {
stroke: #71817c;
stroke-width: 1.2;
}
.memory-tick-line {
stroke: #71817c;
stroke-width: 1;
}
.memory-axis-label {
fill: var(--muted);
font-size: 11px;
}
.memory-axis-label-y {
text-anchor: end;
}
.memory-axis-label-x {
text-anchor: middle;
}
.memory-axis-title {
fill: #4f625d;
font-size: 11px;
font-weight: 700;
}
.memory-axis-title-x {
text-anchor: middle;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -4,6 +4,15 @@ import { createApp, computed, onMounted, ref, watch } from "./vendor/vue.esm-bro
const bootstrap = readBootstrap();
const internalTextPattern = /水合|投影|Trace|trace|Shell|API|DOM|Console|console|Runner|runner|JSONL|steer|facts|分页|HTTP|http|requestfailed|pageerror|Final Response|Code Agent|web-probe|observe|analyzer|终态/u;
const memoryChartFrame = Object.freeze({
left: 64,
right: 696,
top: 20,
bottom: 132,
yLabelX: 56,
xLabelY: 154,
xTitleY: 172,
});
createApp({
setup() {
@@ -113,6 +122,18 @@ createApp({
});
const detailMemoryMax = computed(() => Math.max(1, ...detailMemorySeries.value.flatMap((series) => series.points.map((point) => number(point.memoryMb)))));
const detailMemoryDurationMax = computed(() => Math.max(1, ...detailMemorySeries.value.flatMap((series) => series.points.map((point) => number(point.elapsedMinutes)))));
const detailMemoryYAxisTicks = computed(() => memoryTickValues(detailMemoryMax.value).map((value) => ({
key: `memory-y-${value}`,
value,
y: memoryYValue(value),
label: formatMb(value),
})));
const detailMemoryXAxisTicks = computed(() => memoryTickValues(detailMemoryDurationMax.value).map((value) => ({
key: `memory-x-${value}`,
value,
x: memoryXValue(value),
label: formatMinuteTick(value),
})));
const scopedCheckFindings = computed(() => {
if (checkScope.value === "history") return historicalCheckFindings.value;
return runDetailCheckFindings.value;
@@ -318,12 +339,22 @@ createApp({
function memoryX(point) {
const elapsed = number(point?.elapsedMinutes);
return Math.round(24 + (elapsed / detailMemoryDurationMax.value) * 672);
return memoryXValue(elapsed);
}
function memoryY(point) {
const memory = number(point?.memoryMb);
return Math.round(126 - (memory / detailMemoryMax.value) * 102);
return memoryYValue(memory);
}
function memoryXValue(elapsedMinutes) {
const ratio = clamp(number(elapsedMinutes) / detailMemoryDurationMax.value, 0, 1);
return Math.round(memoryChartFrame.left + ratio * (memoryChartFrame.right - memoryChartFrame.left));
}
function memoryYValue(memoryMb) {
const ratio = clamp(number(memoryMb) / detailMemoryMax.value, 0, 1);
return Math.round(memoryChartFrame.bottom - ratio * (memoryChartFrame.bottom - memoryChartFrame.top));
}
function memoryLineClass(index) {
@@ -402,6 +433,9 @@ createApp({
detailMemorySeries,
detailMemoryMax,
detailMemoryDurationMax,
detailMemoryYAxisTicks,
detailMemoryXAxisTicks,
memoryFrame: memoryChartFrame,
scopedCheckFindings,
scopedCheckSummary,
visibleCheckFindings,
@@ -724,10 +758,23 @@ createApp({
<span class="tag">每个页面一条线 · 峰值 {{ formatMb(detailMemory.maxMemoryMb || detailMemoryMax) }}</span>
</div>
<div v-if="detailMemorySeries.length > 0" class="memory-chart-wrap">
<svg class="memory-chart" viewBox="0 0 720 142" role="img" aria-label="运行详情页面内存曲线">
<line class="trend-grid-line" x1="24" x2="696" y1="24" y2="24"></line>
<line class="trend-grid-line" x1="24" x2="696" y1="75" y2="75"></line>
<line class="trend-grid-line" x1="24" x2="696" y1="126" y2="126"></line>
<svg class="memory-chart" viewBox="0 0 720 178" role="img" aria-label="运行详情页面内存曲线,纵轴为 MB,横轴为运行分钟">
<g data-memory-axis-y="true">
<g v-for="tick in detailMemoryYAxisTicks" :key="tick.key" data-memory-axis-y-tick="true">
<line class="memory-grid-line" :x1="memoryFrame.left" :x2="memoryFrame.right" :y1="tick.y" :y2="tick.y"></line>
<text class="memory-axis-label memory-axis-label-y" :x="memoryFrame.yLabelX" :y="tick.y + 4">{{ tick.label }}</text>
</g>
<line class="memory-axis-line" :x1="memoryFrame.left" :x2="memoryFrame.left" :y1="memoryFrame.top" :y2="memoryFrame.bottom"></line>
<text class="memory-axis-title memory-axis-title-y" x="18" y="78" transform="rotate(-90 18 78)">内存 MB</text>
</g>
<g data-memory-axis-x="true">
<line class="memory-axis-line" :x1="memoryFrame.left" :x2="memoryFrame.right" :y1="memoryFrame.bottom" :y2="memoryFrame.bottom"></line>
<g v-for="tick in detailMemoryXAxisTicks" :key="tick.key" data-memory-axis-x-tick="true">
<line class="memory-tick-line" :x1="tick.x" :x2="tick.x" :y1="memoryFrame.bottom" :y2="memoryFrame.bottom + 6"></line>
<text class="memory-axis-label memory-axis-label-x" :x="tick.x" :y="memoryFrame.xLabelY">{{ tick.label }}</text>
</g>
<text class="memory-axis-title memory-axis-title-x" :x="(memoryFrame.left + memoryFrame.right) / 2" :y="memoryFrame.xTitleY">运行分钟</text>
</g>
<polyline
v-for="(series, index) in detailMemorySeries"
:key="series.key || series.label || index"
@@ -736,11 +783,6 @@ createApp({
:points="memoryPolyline(series)"
></polyline>
</svg>
<div class="memory-axis">
<span>0m</span>
<span>{{ formatMinutes(detailMemoryDurationMax) }}</span>
<span>峰值 {{ formatMb(detailMemory.maxMemoryMb || detailMemoryMax) }}</span>
</div>
<div class="memory-legend">
<span
v-for="(series, index) in detailMemorySeries"
@@ -1065,6 +1107,14 @@ function formatMinutes(value) {
return `${Number.isInteger(rounded) ? String(rounded) : String(rounded).replace(/0+$/u, "").replace(/\.$/u, "")} 分钟`;
}
function formatMinuteTick(value) {
const numberValue = optionalNumber(value);
if (numberValue === null) return "-";
const rounded = Math.round(numberValue * 100) / 100;
const text = Number.isInteger(rounded) ? String(rounded) : String(rounded).replace(/0+$/u, "").replace(/\.$/u, "");
return `${text}m`;
}
function formatMb(value) {
const numberValue = optionalNumber(value);
if (numberValue === null) return "-";
@@ -1073,6 +1123,11 @@ function formatMb(value) {
return `${text} MB`;
}
function memoryTickValues(maxValue) {
const max = Math.max(1, number(maxValue));
return [0, max / 2, max];
}
function runDurationText(run) {
const timing = run?.timing || {};
return formatMinutes(optionalNumber(run?.runDurationMinutes, run?.durationMinutes, timing.runDurationMinutes, timing.durationMinutes));