📊 feat(detail): add model consumption trend & call ranking charts
Introduce two new visualizations to the “Model Data Analysis” panel: 1. Model Consumption Trend (line chart) • Added `spec_model_line` state and legend support. • Calculates per-model counts over time and updates via `updateChartData`. 2. Model Call Ranking (bar chart) • Added `spec_rank_bar` state with `seriesField` and legend enabled. • Ranks models by total call count. Additional changes: • Extended tab navigation with two new `TabPane`s and adjusted chart rendering logic. • Swapped icons/texts to match new chart purposes. • Reused existing color mapping to ensure consistent palette. No breaking changes; UI now offers richer insights into model usage patterns.
This commit is contained in:
@@ -876,7 +876,7 @@
|
|||||||
"加载token失败": "Failed to load token",
|
"加载token失败": "Failed to load token",
|
||||||
"配置聊天": "Configure chat",
|
"配置聊天": "Configure chat",
|
||||||
"模型消耗分布": "Model consumption distribution",
|
"模型消耗分布": "Model consumption distribution",
|
||||||
"模型调用次数占比": "Proportion of model calls",
|
"模型调用次数占比": "Model call ratio",
|
||||||
"用户消耗分布": "User consumption distribution",
|
"用户消耗分布": "User consumption distribution",
|
||||||
"时间粒度": "Time granularity",
|
"时间粒度": "Time granularity",
|
||||||
"天": "day",
|
"天": "day",
|
||||||
@@ -1119,6 +1119,10 @@
|
|||||||
"平均TPM": "Average TPM",
|
"平均TPM": "Average TPM",
|
||||||
"消耗分布": "Consumption distribution",
|
"消耗分布": "Consumption distribution",
|
||||||
"调用次数分布": "Models call distribution",
|
"调用次数分布": "Models call distribution",
|
||||||
|
"消耗趋势": "Consumption trend",
|
||||||
|
"模型消耗趋势": "Model consumption trend",
|
||||||
|
"调用次数排行": "Models call ranking",
|
||||||
|
"模型调用次数排行": "Model call ranking",
|
||||||
"添加渠道": "Add channel",
|
"添加渠道": "Add channel",
|
||||||
"测试所有通道": "Test all channels",
|
"测试所有通道": "Test all channels",
|
||||||
"删除禁用通道": "Delete disabled channels",
|
"删除禁用通道": "Delete disabled channels",
|
||||||
|
|||||||
@@ -366,6 +366,86 @@ const Detail = (props) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 模型消耗趋势折线图
|
||||||
|
const [spec_model_line, setSpecModelLine] = useState({
|
||||||
|
type: 'line',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: 'lineData',
|
||||||
|
values: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
xField: 'Time',
|
||||||
|
yField: 'Count',
|
||||||
|
seriesField: 'Model',
|
||||||
|
legends: {
|
||||||
|
visible: true,
|
||||||
|
selectMode: 'single',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
visible: true,
|
||||||
|
text: t('模型消耗趋势'),
|
||||||
|
subtext: '',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mark: {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
key: (datum) => datum['Model'],
|
||||||
|
value: (datum) => renderNumber(datum['Count']),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
specified: modelColorMap,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 模型调用次数排行柱状图
|
||||||
|
const [spec_rank_bar, setSpecRankBar] = useState({
|
||||||
|
type: 'bar',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: 'rankData',
|
||||||
|
values: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
xField: 'Model',
|
||||||
|
yField: 'Count',
|
||||||
|
seriesField: 'Model',
|
||||||
|
legends: {
|
||||||
|
visible: true,
|
||||||
|
selectMode: 'single',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
visible: true,
|
||||||
|
text: t('模型调用次数排行'),
|
||||||
|
subtext: '',
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
state: {
|
||||||
|
hover: {
|
||||||
|
stroke: '#000',
|
||||||
|
lineWidth: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mark: {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
key: (datum) => datum['Model'],
|
||||||
|
value: (datum) => renderNumber(datum['Count']),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
specified: modelColorMap,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// ========== Hooks - Memoized Values ==========
|
// ========== Hooks - Memoized Values ==========
|
||||||
const performanceMetrics = useMemo(() => {
|
const performanceMetrics = useMemo(() => {
|
||||||
const timeDiff = (Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000;
|
const timeDiff = (Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000;
|
||||||
@@ -853,6 +933,46 @@ const Detail = (props) => {
|
|||||||
'barData'
|
'barData'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ===== 模型调用次数折线图 =====
|
||||||
|
let modelLineData = [];
|
||||||
|
chartTimePoints.forEach((time) => {
|
||||||
|
const timeData = Array.from(uniqueModels).map((model) => {
|
||||||
|
const key = `${time}-${model}`;
|
||||||
|
const aggregated = aggregatedData.get(key);
|
||||||
|
return {
|
||||||
|
Time: time,
|
||||||
|
Model: model,
|
||||||
|
Count: aggregated?.count || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
modelLineData.push(...timeData);
|
||||||
|
});
|
||||||
|
modelLineData.sort((a, b) => a.Time.localeCompare(b.Time));
|
||||||
|
|
||||||
|
// ===== 模型调用次数排行柱状图 =====
|
||||||
|
const rankData = Array.from(modelTotals)
|
||||||
|
.map(([model, count]) => ({
|
||||||
|
Model: model,
|
||||||
|
Count: count,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.Count - a.Count);
|
||||||
|
|
||||||
|
updateChartSpec(
|
||||||
|
setSpecModelLine,
|
||||||
|
modelLineData,
|
||||||
|
`${t('总计')}:${renderNumber(totalTimes)}`,
|
||||||
|
newModelColors,
|
||||||
|
'lineData'
|
||||||
|
);
|
||||||
|
|
||||||
|
updateChartSpec(
|
||||||
|
setSpecRankBar,
|
||||||
|
rankData,
|
||||||
|
`${t('总计')}:${renderNumber(totalTimes)}`,
|
||||||
|
newModelColors,
|
||||||
|
'rankData'
|
||||||
|
);
|
||||||
|
|
||||||
setPieData(newPieData);
|
setPieData(newPieData);
|
||||||
setLineData(newLineData);
|
setLineData(newLineData);
|
||||||
setConsumeQuota(totalQuota);
|
setConsumeQuota(totalQuota);
|
||||||
@@ -1122,28 +1242,53 @@ const Detail = (props) => {
|
|||||||
{t('消耗分布')}
|
{t('消耗分布')}
|
||||||
</span>
|
</span>
|
||||||
} itemKey="1" />
|
} itemKey="1" />
|
||||||
|
<TabPane tab={
|
||||||
|
<span>
|
||||||
|
<IconPulse />
|
||||||
|
{t('消耗趋势')}
|
||||||
|
</span>
|
||||||
|
} itemKey="2" />
|
||||||
<TabPane tab={
|
<TabPane tab={
|
||||||
<span>
|
<span>
|
||||||
<IconPieChart2Stroked />
|
<IconPieChart2Stroked />
|
||||||
{t('调用次数分布')}
|
{t('调用次数分布')}
|
||||||
</span>
|
</span>
|
||||||
} itemKey="2" />
|
} itemKey="3" />
|
||||||
|
<TabPane tab={
|
||||||
|
<span>
|
||||||
|
<IconHistogram />
|
||||||
|
{t('调用次数排行')}
|
||||||
|
</span>
|
||||||
|
} itemKey="4" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div style={{ height: 400 }}>
|
<div style={{ height: 400 }}>
|
||||||
{activeChartTab === '1' ? (
|
{activeChartTab === '1' && (
|
||||||
<VChart
|
<VChart
|
||||||
spec={spec_line}
|
spec={spec_line}
|
||||||
option={CHART_CONFIG}
|
option={CHART_CONFIG}
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
|
{activeChartTab === '2' && (
|
||||||
|
<VChart
|
||||||
|
spec={spec_model_line}
|
||||||
|
option={CHART_CONFIG}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{activeChartTab === '3' && (
|
||||||
<VChart
|
<VChart
|
||||||
spec={spec_pie}
|
spec={spec_pie}
|
||||||
option={CHART_CONFIG}
|
option={CHART_CONFIG}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{activeChartTab === '4' && (
|
||||||
|
<VChart
|
||||||
|
spec={spec_rank_bar}
|
||||||
|
option={CHART_CONFIG}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user