- {previewResults.loss.map((result, idx) => (
-
-
- Loss - {result.fileName}
- ({result.count} 个匹配)
-
- {result.examples.length > 0 && (
-
- {result.examples.map((example, exIdx) => (
-
-
{example.value}
-
(第{example.line}行)
- {example.format && (
-
[{example.format}]
- )}
-
{example.text}
-
- ))}
+ {Object.entries(previewResults).map(([key, results]) => (
+ results.map((result, idx) => (
+
+
+ {key} - {result.fileName}
+ ({result.count} 个匹配)
- )}
-
- ))}
-
- {previewResults.gradNorm.map((result, idx) => (
-
-
-
Grad Norm - {result.fileName}
-
({result.count} 个匹配)
+ {result.examples.length > 0 && (
+
+ {result.examples.map((example, exIdx) => (
+
+
{example.value}
+
(第{example.line}行)
+ {example.format && (
+
[{example.format}]
+ )}
+
{example.text}
+
+ ))}
+
+ )}
- {result.examples.length > 0 && (
-
- {result.examples.map((example, exIdx) => (
-
-
{example.value}
-
(第{example.line}行)
- {example.format && (
-
[{example.format}]
- )}
-
{example.text}
-
- ))}
-
- )}
-
+ ))
))}
From 93ccf00d4ec267d4dbc68803f2bac6f4aa601a37 Mon Sep 17 00:00:00 2001
From: JavaZero <71128095+JavaZeroo@users.noreply.github.com>
Date: Tue, 22 Jul 2025 14:35:51 +0800
Subject: [PATCH 3/6] Restore sync pan and comparison stats
---
src/components/ChartContainer.jsx | 222 ++++++++++++++++++++++++------
1 file changed, 183 insertions(+), 39 deletions(-)
diff --git a/src/components/ChartContainer.jsx b/src/components/ChartContainer.jsx
index 35ed94e..da69020 100644
--- a/src/components/ChartContainer.jsx
+++ b/src/components/ChartContainer.jsx
@@ -67,6 +67,8 @@ export default function ChartContainer({
absoluteBaseline = 0.005,
showLoss = true,
showGradNorm = false,
+ xRange = { min: undefined, max: undefined },
+ onXRangeChange,
onMaxStepChange
}) {
const chartRefs = useRef(new Map());
@@ -214,6 +216,123 @@ export default function ChartContainer({
return result;
};
+ const chartOptions = useMemo(() => ({
+ responsive: true,
+ maintainAspectRatio: false,
+ animation: { duration: 0 },
+ animations: { colors: false, x: false, y: false },
+ hover: { animationDuration: 0 },
+ responsiveAnimationDuration: 0,
+ interaction: { mode: 'index', intersect: false },
+ plugins: {
+ zoom: {
+ pan: {
+ enabled: true,
+ mode: 'x',
+ onPanComplete: ({ chart }) => {
+ const { min, max } = chart.scales.x;
+ onXRangeChange({ min: Math.round(min), max: Math.round(max) });
+ }
+ },
+ zoom: {
+ drag: {
+ enabled: true,
+ borderColor: 'rgba(225,225,225,0.2)',
+ borderWidth: 1,
+ backgroundColor: 'rgba(225,225,225,0.2)',
+ modifierKey: 'shift'
+ },
+ wheel: { enabled: true },
+ pinch: { enabled: true },
+ mode: 'x',
+ onZoomComplete: ({ chart }) => {
+ const { min, max } = chart.scales.x;
+ onXRangeChange({ min: Math.round(min), max: Math.round(max) });
+ }
+ }
+ },
+ legend: {
+ position: 'top',
+ labels: {
+ boxWidth: 40,
+ boxHeight: 2,
+ padding: 10,
+ usePointStyle: false,
+ generateLabels: function (chart) {
+ const original = Chart.defaults.plugins.legend.labels.generateLabels;
+ const labels = original.call(this, chart);
+ labels.forEach((label, index) => {
+ const dataset = chart.data.datasets[index];
+ if (dataset && dataset.borderDash && dataset.borderDash.length > 0) {
+ label.lineDash = dataset.borderDash;
+ }
+ });
+ return labels;
+ }
+ }
+ },
+ tooltip: {
+ mode: 'index',
+ intersect: false,
+ animation: false,
+ backgroundColor: 'rgba(15, 23, 42, 0.92)',
+ titleColor: '#f1f5f9',
+ bodyColor: '#cbd5e1',
+ borderColor: 'rgba(71, 85, 105, 0.2)',
+ borderWidth: 1,
+ cornerRadius: 6,
+ displayColors: true,
+ usePointStyle: true,
+ titleFont: { size: 11, weight: '600', family: 'Inter, system-ui, sans-serif' },
+ bodyFont: { size: 10, weight: '400', family: 'Inter, system-ui, sans-serif' },
+ footerFont: { size: 9, weight: '300' },
+ padding: { top: 6, bottom: 6, left: 8, right: 8 },
+ caretPadding: 4,
+ caretSize: 4,
+ multiKeyBackground: 'transparent',
+ callbacks: {
+ title: function (context) {
+ return `Step ${context[0].parsed.x}`;
+ },
+ label: function (context) {
+ const value = Number(context.parsed.y.toPrecision(4));
+ return ` ${value}`;
+ },
+ labelColor: function (context) {
+ return {
+ borderColor: context.dataset.borderColor,
+ backgroundColor: context.dataset.borderColor,
+ borderWidth: 1,
+ borderRadius: 2
+ };
+ }
+ }
+ }
+ },
+ scales: {
+ x: {
+ type: 'linear',
+ display: true,
+ title: { display: true, text: 'Step' },
+ min: xRange.min,
+ max: xRange.max,
+ bounds: 'data'
+ },
+ y: {
+ type: 'linear',
+ display: true,
+ title: { display: true, text: 'Value' },
+ bounds: 'data',
+ ticks: {
+ callback: function (value) {
+ return Number(value.toPrecision(2));
+ }
+ }
+ }
+ },
+ elements: { point: { radius: 0 } }
+ }), [xRange, onXRangeChange]);
+
const createComparisonChartData = (item1, item2, title) => {
const comparisonData = getComparisonData(item1.data, item2.data, compareMode);
const baseline = compareMode === 'relative' ? relativeBaseline : compareMode === 'absolute' ? absoluteBaseline : 0;
@@ -299,49 +418,74 @@ export default function ChartContainer({
);
}
+ const stats = [];
+
+ const metricElements = metricsToShow.map((metric, idx) => {
+ const key = metric.name || metric.keyword || `metric${idx + 1}`;
+ const dataArray = metricDataArrays[key] || [];
+ const showComparison = dataArray.length === 2;
+
+ if (showComparison) {
+ const normalDiff = getComparisonData(dataArray[0].data, dataArray[1].data, 'normal');
+ const absDiff = getComparisonData(dataArray[0].data, dataArray[1].data, 'absolute');
+ const relDiff = getComparisonData(dataArray[0].data, dataArray[1].data, 'relative');
+ const mean = arr => (arr.reduce((s, p) => s + p.y, 0) / arr.length) || 0;
+ stats.push({
+ label: key,
+ meanNormal: mean(normalDiff),
+ meanAbsolute: mean(absDiff),
+ meanRelative: mean(relDiff)
+ });
+ }
+
+ return (
+
+
+
+
+ {showComparison && (
+
+
+
+ )}
+
+ );
+ });
+
return (
- {metricsToShow.map((metric, idx) => {
- const key = metric.name || metric.keyword || `metric${idx+1}`;
- const dataArray = metricDataArrays[key] || [];
- const showComparison = dataArray.length === 2;
- return (
-
-
-
-
- {showComparison && (
-
-
-
- )}
-
- );
- })}
+ {metricElements}
+ {stats.length > 0 && (
+
+
差值分析统计
+
+ {stats.map(s => (
+
+
{s.label} 差值统计
+
+
Mean Difference: {s.meanNormal.toFixed(6)}
+
Mean Absolute Error: {s.meanAbsolute.toFixed(6)}
+
Mean Relative Error: {s.meanRelative.toFixed(6)}
+
+
+ ))}
+
+
+ )}
);
}
From 5a61f9e06c7579bdcd4b4aaadb660c7578036980 Mon Sep 17 00:00:00 2001
From: JavaZero <71128095+JavaZeroo@users.noreply.github.com>
Date: Tue, 22 Jul 2025 14:43:15 +0800
Subject: [PATCH 4/6] Remove chart visibility toggles
---
src/App.jsx | 40 +------------------------------
src/components/ChartContainer.jsx | 12 ++--------
2 files changed, 3 insertions(+), 49 deletions(-)
diff --git a/src/App.jsx b/src/App.jsx
index 12856b7..6f3a265 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -31,8 +31,6 @@ function App() {
const [compareMode, setCompareMode] = useState('normal');
const [relativeBaseline, setRelativeBaseline] = useState(0.002);
const [absoluteBaseline, setAbsoluteBaseline] = useState(0.005);
- const [showLoss, setShowLoss] = useState(true);
- const [showGradNorm, setShowGradNorm] = useState(false);
const [configModalOpen, setConfigModalOpen] = useState(false);
const [configFile, setConfigFile] = useState(null);
const [globalDragOver, setGlobalDragOver] = useState(false);
@@ -319,41 +317,7 @@ function App() {
@@ -425,8 +389,6 @@ function App() {
compareMode={compareMode}
relativeBaseline={relativeBaseline}
absoluteBaseline={absoluteBaseline}
- showLoss={showLoss}
- showGradNorm={showGradNorm}
xRange={xRange}
onXRangeChange={setXRange}
onMaxStepChange={setMaxStep}
diff --git a/src/components/ChartContainer.jsx b/src/components/ChartContainer.jsx
index da69020..d61e729 100644
--- a/src/components/ChartContainer.jsx
+++ b/src/components/ChartContainer.jsx
@@ -65,8 +65,6 @@ export default function ChartContainer({
compareMode,
relativeBaseline = 0.002,
absoluteBaseline = 0.005,
- showLoss = true,
- showGradNorm = false,
xRange = { min: undefined, max: undefined },
onXRangeChange,
onMaxStepChange
@@ -402,13 +400,7 @@ export default function ChartContainer({
.map(file => ({ name: file.name, data: file.metricsData[name] }));
});
- const metricsToShow = metrics.filter((m, idx) => {
- if (idx === 0) return showLoss;
- if (idx === 1) return showGradNorm;
- return true;
- });
-
- if (metricsToShow.length === 0) {
+ if (metrics.length === 0) {
return (
@@ -420,7 +412,7 @@ export default function ChartContainer({
const stats = [];
- const metricElements = metricsToShow.map((metric, idx) => {
+ const metricElements = metrics.map((metric, idx) => {
const key = metric.name || metric.keyword || `metric${idx + 1}`;
const dataArray = metricDataArrays[key] || [];
const showComparison = dataArray.length === 2;
From 8ab18c29da4a3e08f3ca9015bd7bab1974e7aa7e Mon Sep 17 00:00:00 2001
From: JavaZero <71128095+JavaZeroo@users.noreply.github.com>
Date: Tue, 22 Jul 2025 15:04:14 +0800
Subject: [PATCH 5/6] Layout grid for metrics
---
src/components/ChartContainer.jsx | 43 ++++++++++++-------------------
1 file changed, 16 insertions(+), 27 deletions(-)
diff --git a/src/components/ChartContainer.jsx b/src/components/ChartContainer.jsx
index d61e729..d0fecf2 100644
--- a/src/components/ChartContainer.jsx
+++ b/src/components/ChartContainer.jsx
@@ -410,28 +410,26 @@ export default function ChartContainer({
);
}
- const stats = [];
-
const metricElements = metrics.map((metric, idx) => {
const key = metric.name || metric.keyword || `metric${idx + 1}`;
const dataArray = metricDataArrays[key] || [];
const showComparison = dataArray.length === 2;
+ let stats = null;
if (showComparison) {
const normalDiff = getComparisonData(dataArray[0].data, dataArray[1].data, 'normal');
const absDiff = getComparisonData(dataArray[0].data, dataArray[1].data, 'absolute');
const relDiff = getComparisonData(dataArray[0].data, dataArray[1].data, 'relative');
const mean = arr => (arr.reduce((s, p) => s + p.y, 0) / arr.length) || 0;
- stats.push({
- label: key,
+ stats = {
meanNormal: mean(normalDiff),
meanAbsolute: mean(absDiff),
meanRelative: mean(relDiff)
- });
+ };
}
return (
-
+
)}
+ {stats && (
+
+
{key} 差值统计
+
+
Mean Difference: {stats.meanNormal.toFixed(6)}
+
Mean Absolute Error: {stats.meanAbsolute.toFixed(6)}
+
Mean Relative Error: {stats.meanRelative.toFixed(6)}
+
+
+ )}
);
});
return (
-
-
- {metricElements}
-
- {stats.length > 0 && (
-
-
差值分析统计
-
- {stats.map(s => (
-
-
{s.label} 差值统计
-
-
Mean Difference: {s.meanNormal.toFixed(6)}
-
Mean Absolute Error: {s.meanAbsolute.toFixed(6)}
-
Mean Relative Error: {s.meanRelative.toFixed(6)}
-
-
- ))}
-
-
- )}
+
+ {metricElements}
);
}
From 32d5b00ba1507e66e7eceb926aaae58b04b8367a Mon Sep 17 00:00:00 2001
From: JavaZero <71128095+JavaZeroo@users.noreply.github.com>
Date: Tue, 22 Jul 2025 15:40:40 +0800
Subject: [PATCH 6/6] Improve metric titles and presets
---
src/components/ChartContainer.jsx | 10 +++++++-
src/components/FileConfigModal.jsx | 39 ++++++++++++++++++++++++++++--
src/components/RegexControls.jsx | 38 ++++++++++++++++++++++++++---
src/metricPresets.js | 6 +++++
4 files changed, 86 insertions(+), 7 deletions(-)
create mode 100644 src/metricPresets.js
diff --git a/src/components/ChartContainer.jsx b/src/components/ChartContainer.jsx
index d0fecf2..4622b9d 100644
--- a/src/components/ChartContainer.jsx
+++ b/src/components/ChartContainer.jsx
@@ -392,7 +392,15 @@ export default function ChartContainer({
);
}
- const metricNames = metrics.map(m => m.name || m.keyword);
+ const metricNames = metrics.map((m, idx) => {
+ if (m.name && m.name.trim()) return m.name.trim();
+ if (m.keyword) return m.keyword.replace(/[::]/g, '').trim();
+ if (m.regex) {
+ const sanitized = m.regex.replace(/[^a-zA-Z0-9_]/g, '').trim();
+ return sanitized || `metric${idx + 1}`;
+ }
+ return `metric${idx + 1}`;
+ });
const metricDataArrays = {};
metricNames.forEach(name => {
metricDataArrays[name] = parsedData
diff --git a/src/components/FileConfigModal.jsx b/src/components/FileConfigModal.jsx
index 0d0f17c..6428b20 100644
--- a/src/components/FileConfigModal.jsx
+++ b/src/components/FileConfigModal.jsx
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import { X, Settings, TrendingDown, TrendingUp, Sliders, BarChart3, Target, Code, Zap } from 'lucide-react';
+import { METRIC_PRESETS } from '../metricPresets.js';
// 匹配模式枚举
const MATCH_MODES = {
@@ -23,6 +24,16 @@ const MODE_CONFIG = {
}
};
+function getMetricTitle(metric, index) {
+ if (metric.name && metric.name.trim()) return metric.name.trim();
+ if (metric.keyword) return metric.keyword.replace(/[::]/g, '').trim();
+ if (metric.regex) {
+ const sanitized = metric.regex.replace(/[^a-zA-Z0-9_]/g, '').trim();
+ return sanitized || `Metric ${index + 1}`;
+ }
+ return `Metric ${index + 1}`;
+}
+
export function FileConfigModal({ file, isOpen, onClose, onSave, globalParsingConfig }) {
const [config, setConfig] = useState({
metrics: [],
@@ -62,6 +73,17 @@ export function FileConfigModal({ file, isOpen, onClose, onSave, globalParsingCo
}));
};
+ const applyPreset = (index, presetLabel) => {
+ const preset = METRIC_PRESETS.find(p => p.label === presetLabel);
+ if (!preset) return;
+ setConfig(prev => ({
+ ...prev,
+ metrics: prev.metrics.map((m, i) =>
+ i === index ? { ...m, ...preset } : m
+ )
+ }));
+ };
+
const handleRangeChange = (field, value) => {
setConfig(prev => ({
...prev,
@@ -83,9 +105,22 @@ export function FileConfigModal({ file, isOpen, onClose, onSave, globalParsingCo
// 渲染配置项的函数
const renderConfigPanel = (type, configItem, index) => {
const ModeIcon = MODE_CONFIG[configItem.mode].icon;
-
+
return (
+
+
+
+
{/* 模式选择 */}
{/* 模式选择 */}
@@ -430,9 +460,9 @@ export function RegexControls({
- {cfg.name || `Metric ${idx + 1}`} 解析配置
+ {getMetricTitle(cfg, idx)} 解析配置
- {renderConfigPanel(`metric-${idx}`, cfg, (field, value) => handleMetricChange(idx, field, value))}
+ {renderConfigPanel(`metric-${idx}`, cfg, (field, value) => handleMetricChange(idx, field, value), idx)}
))}