Ver Fonte

📊 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.
t0ng7u há 5 meses atrás
pai
commit
d40fb68500
2 ficheiros alterados com 153 adições e 4 exclusões
  1. 5 1
      web/src/i18n/locales/en.json
  2. 148 3
      web/src/pages/Detail/index.js

+ 5 - 1
web/src/i18n/locales/en.json

@@ -876,7 +876,7 @@
   "加载token失败": "Failed to load token",
   "配置聊天": "Configure chat",
   "模型消耗分布": "Model consumption distribution",
-  "模型调用次数占比": "Proportion of model calls",
+  "模型调用次数占比": "Model call ratio",
   "用户消耗分布": "User consumption distribution",
   "时间粒度": "Time granularity",
   "天": "day",
@@ -1119,6 +1119,10 @@
   "平均TPM": "Average TPM",
   "消耗分布": "Consumption distribution",
   "调用次数分布": "Models call distribution",
+  "消耗趋势": "Consumption trend",
+  "模型消耗趋势": "Model consumption trend",
+  "调用次数排行": "Models call ranking",
+  "模型调用次数排行": "Model call ranking",
   "添加渠道": "Add channel",
   "测试所有通道": "Test all channels",
   "删除禁用通道": "Delete disabled channels",

+ 148 - 3
web/src/pages/Detail/index.js

@@ -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 ==========
   const performanceMetrics = useMemo(() => {
     const timeDiff = (Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000;
@@ -853,6 +933,46 @@ const Detail = (props) => {
       '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);
     setLineData(newLineData);
     setConsumeQuota(totalQuota);
@@ -1122,28 +1242,53 @@ const Detail = (props) => {
                         {t('消耗分布')}
                       </span>
                     } itemKey="1" />
+                    <TabPane tab={
+                      <span>
+                        <IconPulse />
+                        {t('消耗趋势')}
+                      </span>
+                    } itemKey="2" />
                     <TabPane tab={
                       <span>
                         <IconPieChart2Stroked />
                         {t('调用次数分布')}
                       </span>
-                    } itemKey="2" />
+                    } itemKey="3" />
+                    <TabPane tab={
+                      <span>
+                        <IconHistogram />
+                        {t('调用次数排行')}
+                      </span>
+                    } itemKey="4" />
                   </Tabs>
                 </div>
               }
             >
               <div style={{ height: 400 }}>
-                {activeChartTab === '1' ? (
+                {activeChartTab === '1' && (
                   <VChart
                     spec={spec_line}
                     option={CHART_CONFIG}
                   />
-                ) : (
+                )}
+                {activeChartTab === '2' && (
+                  <VChart
+                    spec={spec_model_line}
+                    option={CHART_CONFIG}
+                  />
+                )}
+                {activeChartTab === '3' && (
                   <VChart
                     spec={spec_pie}
                     option={CHART_CONFIG}
                   />
                 )}
+                {activeChartTab === '4' && (
+                  <VChart
+                    spec={spec_rank_bar}
+                    option={CHART_CONFIG}
+                  />
+                )}
               </div>
             </Card>