diff --git a/src/chat/utils/report_generator.py b/src/chat/utils/report_generator.py
index 48914f2fc..ec6136f49 100644
--- a/src/chat/utils/report_generator.py
+++ b/src/chat/utils/report_generator.py
@@ -92,6 +92,7 @@ class HTMLReportGenerator:
f"
{stat_data[TPS_BY_PROVIDER].get(provider_name, 0):.2f} | "
f"{stat_data[COST_PER_KTOK_BY_PROVIDER].get(provider_name, 0):.4f} ¥ | "
f"{stat_data[COST_BY_PROVIDER].get(provider_name, 0):.4f} ¥ | "
+ f"{stat_data.get(AVG_TIME_COST_BY_PROVIDER, {}).get(provider_name, 0):.3f} 秒 | "
f""
for provider_name, count in sorted(stat_data[REQ_CNT_BY_PROVIDER].items())
]
@@ -316,10 +317,12 @@ class HTMLReportGenerator:
period_id = period[0]
static_chart_data[period_id] = {
"provider_cost_data": stat[period_id].get(PIE_CHART_COST_BY_PROVIDER, {}),
+ "module_cost_data": stat[period_id].get(PIE_CHART_COST_BY_MODULE, {}),
"model_cost_data": stat[period_id].get(BAR_CHART_COST_BY_MODEL, {}),
}
static_chart_data["all_time"] = {
"provider_cost_data": stat["all_time"].get(PIE_CHART_COST_BY_PROVIDER, {}),
+ "module_cost_data": stat["all_time"].get(PIE_CHART_COST_BY_MODULE, {}),
"model_cost_data": stat["all_time"].get(BAR_CHART_COST_BY_MODEL, {}),
}
diff --git a/src/chat/utils/statistic.py b/src/chat/utils/statistic.py
index a94278b04..c8a93a614 100644
--- a/src/chat/utils/statistic.py
+++ b/src/chat/utils/statistic.py
@@ -299,6 +299,7 @@ class StatisticOutputTask(AsyncTask):
# Chart data
PIE_CHART_COST_BY_PROVIDER: {},
PIE_CHART_REQ_BY_PROVIDER: {},
+ PIE_CHART_COST_BY_MODULE: {},
BAR_CHART_COST_BY_MODEL: {},
BAR_CHART_REQ_BY_MODEL: {},
}
@@ -457,6 +458,15 @@ class StatisticOutputTask(AsyncTask):
"data": [round(item[1], 4) for item in sorted_providers],
}
+ # 按模块花费饼图
+ module_costs = period_stats[COST_BY_MODULE]
+ if module_costs:
+ sorted_modules = sorted(module_costs.items(), key=lambda item: item[1], reverse=True)
+ period_stats[PIE_CHART_COST_BY_MODULE] = {
+ "labels": [item[0] for item in sorted_modules],
+ "data": [round(item[1], 4) for item in sorted_modules],
+ }
+
# 按模型花费条形图
model_costs = period_stats[COST_BY_MODEL]
if model_costs:
diff --git a/src/chat/utils/statistic_keys.py b/src/chat/utils/statistic_keys.py
index 9630d081b..1f27ffbe9 100644
--- a/src/chat/utils/statistic_keys.py
+++ b/src/chat/utils/statistic_keys.py
@@ -59,6 +59,7 @@ STD_TIME_COST_BY_PROVIDER = "std_time_costs_by_provider"
# 新增饼图和条形图数据
PIE_CHART_COST_BY_PROVIDER = "pie_chart_cost_by_provider"
PIE_CHART_REQ_BY_PROVIDER = "pie_chart_req_by_provider"
+PIE_CHART_COST_BY_MODULE = "pie_chart_cost_by_module"
BAR_CHART_COST_BY_MODEL = "bar_chart_cost_by_model"
BAR_CHART_REQ_BY_MODEL = "bar_chart_req_by_model"
diff --git a/src/chat/utils/templates/report.css b/src/chat/utils/templates/report.css
index ff8930f23..0cb096514 100644
--- a/src/chat/utils/templates/report.css
+++ b/src/chat/utils/templates/report.css
@@ -33,6 +33,7 @@ body {
border-radius: 28px;
box-shadow: 0 4px 16px var(--md-sys-color-shadow);
animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
}
@keyframes slideIn {
@@ -48,18 +49,43 @@ body {
/* Dashboard Layout */
.dashboard-layout {
display: flex;
- flex-direction: column;
gap: 32px;
+ align-items: start;
+ position: relative;
}
.main-content {
- width: 100%;
+ flex: 1;
min-width: 0;
}
.sidebar-content {
- width: 100%;
- min-width: 0;
+ width: 380px;
+ min-width: 380px;
+ flex-shrink: 0;
+ padding: 24px;
+ background: linear-gradient(135deg, #FAFAFA 0%, #FFFFFF 100%);
+ border-radius: 20px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
+ border: 1px solid var(--md-sys-color-outline);
+}
+
+/* 自定义侧边栏滚动条 */
+.sidebar-content::-webkit-scrollbar {
+ width: 6px;
+}
+
+.sidebar-content::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.sidebar-content::-webkit-scrollbar-thumb {
+ background: var(--md-sys-color-outline);
+ border-radius: 3px;
+}
+
+.sidebar-content::-webkit-scrollbar-thumb:hover {
+ background: var(--md-sys-color-primary);
}
/* Typography - Material Design 3 */
@@ -378,6 +404,24 @@ tbody tr:hover {
}
/* Responsive Design */
+@media (max-width: 1200px) {
+ .dashboard-layout {
+ flex-direction: column;
+ }
+
+ .main-content {
+ margin-right: 0;
+ }
+
+ .sidebar-content {
+ position: static;
+ width: 100%;
+ min-width: 0;
+ max-height: none;
+ right: auto;
+ }
+}
+
@media (max-width: 768px) {
.container {
padding: 20px;
diff --git a/src/chat/utils/templates/report.js b/src/chat/utils/templates/report.js
index 7d03a3192..16b574ea1 100644
--- a/src/chat/utils/templates/report.js
+++ b/src/chat/utils/templates/report.js
@@ -178,8 +178,21 @@ document.addEventListener('DOMContentLoaded', function () {
Object.keys(staticChartData).forEach(period_id => {
const providerCostData = staticChartData[period_id].provider_cost_data;
+ const moduleCostData = staticChartData[period_id].module_cost_data;
const modelCostData = staticChartData[period_id].model_cost_data;
- const colors = ['#3498db', '#2ecc71', '#f1c40f', '#e74c3c', '#9b59b6', '#1abc9c', '#34495e', '#e67e22'];
+ // 扩展的Material Design调色板 - 包含多种蓝色系和其他配色
+ const colors = [
+ '#1976D2', '#42A5F5', '#2196F3', '#64B5F6', '#90CAF9', '#BBDEFB', // 蓝色系
+ '#1565C0', '#0D47A1', '#82B1FF', '#448AFF', // 深蓝色系
+ '#00BCD4', '#26C6DA', '#4DD0E1', '#80DEEA', // 青色系
+ '#009688', '#26A69A', '#4DB6AC', '#80CBC4', // 青绿色系
+ '#4CAF50', '#66BB6A', '#81C784', '#A5D6A7', // 绿色系
+ '#FF9800', '#FFA726', '#FFB74D', '#FFCC80', // 橙色系
+ '#FF5722', '#FF7043', '#FF8A65', '#FFAB91', // 深橙色系
+ '#9C27B0', '#AB47BC', '#BA68C8', '#CE93D8', // 紫色系
+ '#E91E63', '#EC407A', '#F06292', '#F48FB1', // 粉色系
+ '#607D8B', '#78909C', '#90A4AE', '#B0BEC5' // 蓝灰色系
+ ];
// Provider Cost Pie Chart
const providerCtx = document.getElementById(`providerCostPieChart_${period_id}`);
@@ -200,28 +213,95 @@ document.addEventListener('DOMContentLoaded', function () {
options: {
responsive: true,
maintainAspectRatio: true,
- aspectRatio: 1.5,
+ aspectRatio: 1.3,
plugins: {
title: {
display: true,
- text: '按供应商花费分布',
- font: { size: 14, weight: '500' },
+ text: '🏢 按供应商花费分布',
+ font: { size: 13, weight: '500' },
color: '#1C1B1F',
- padding: { top: 8, bottom: 16 }
+ padding: { top: 4, bottom: 12 }
},
legend: {
- position: 'right',
+ position: 'bottom',
labels: {
usePointStyle: true,
- padding: 15,
- font: { size: 12 }
+ padding: 10,
+ font: { size: 11 }
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
- titleFont: { size: 14 },
- bodyFont: { size: 13 },
+ titleFont: { size: 13 },
+ bodyFont: { size: 12 },
+ cornerRadius: 8,
+ callbacks: {
+ label: function(context) {
+ let label = context.label || '';
+ if (label) {
+ label += ': ';
+ }
+ label += context.parsed.toFixed(4) + ' ¥';
+ const total = context.dataset.data.reduce((a, b) => a + b, 0);
+ const percentage = ((context.parsed / total) * 100).toFixed(2);
+ label += ` (${percentage}%)`;
+ return label;
+ }
+ }
+ }
+ },
+ animation: {
+ animateRotate: true,
+ animateScale: true,
+ duration: 1000,
+ easing: 'easeInOutQuart'
+ }
+ }
+ });
+ }
+
+ // Module Cost Pie Chart
+ const moduleCtx = document.getElementById(`moduleCostPieChart_${period_id}`);
+ if (moduleCtx && moduleCostData && moduleCostData.data && moduleCostData.data.length > 0) {
+ new Chart(moduleCtx, {
+ type: 'doughnut',
+ data: {
+ labels: moduleCostData.labels,
+ datasets: [{
+ label: '按模块花费',
+ data: moduleCostData.data,
+ backgroundColor: colors,
+ borderColor: '#FFFFFF',
+ borderWidth: 2,
+ hoverOffset: 8
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: true,
+ aspectRatio: 1.3,
+ plugins: {
+ title: {
+ display: true,
+ text: '🔧 按模块使用分布',
+ font: { size: 13, weight: '500' },
+ color: '#1C1B1F',
+ padding: { top: 4, bottom: 12 }
+ },
+ legend: {
+ position: 'bottom',
+ labels: {
+ usePointStyle: true,
+ padding: 10,
+ font: { size: 11 }
+ }
+ },
+ tooltip: {
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
+ padding: 12,
+ titleFont: { size: 13 },
+ bodyFont: { size: 12 },
cornerRadius: 8,
callbacks: {
label: function(context) {
@@ -261,28 +341,28 @@ document.addEventListener('DOMContentLoaded', function () {
backgroundColor: colors,
borderColor: colors,
borderWidth: 2,
- borderRadius: 8,
+ borderRadius: 6,
hoverBackgroundColor: colors.map(c => c + 'dd')
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
- aspectRatio: 1.5,
+ aspectRatio: 1.2,
plugins: {
title: {
display: true,
- text: '按模型花费排行',
- font: { size: 14, weight: '500' },
+ text: '🤖 按模型花费排行',
+ font: { size: 13, weight: '500' },
color: '#1C1B1F',
- padding: { top: 8, bottom: 16 }
+ padding: { top: 4, bottom: 12 }
},
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
- titleFont: { size: 14 },
- bodyFont: { size: 13 },
+ titleFont: { size: 13 },
+ bodyFont: { size: 12 },
cornerRadius: 8,
callbacks: {
label: function(context) {
@@ -293,16 +373,22 @@ document.addEventListener('DOMContentLoaded', function () {
},
scales: {
x: {
- grid: { display: false }
+ grid: { display: false },
+ ticks: {
+ font: { size: 10 }
+ }
},
y: {
beginAtZero: true,
title: {
display: true,
text: '💰 花费 (¥)',
- font: { size: 13, weight: 'bold' }
+ font: { size: 11, weight: 'bold' }
},
- grid: { color: 'rgba(0, 0, 0, 0.05)' }
+ grid: { color: 'rgba(0, 0, 0, 0.05)' },
+ ticks: {
+ font: { size: 10 }
+ }
}
},
animation: {
diff --git a/src/chat/utils/templates/static_charts.html b/src/chat/utils/templates/static_charts.html
index d22cbfc02..4b9e7cd07 100644
--- a/src/chat/utils/templates/static_charts.html
+++ b/src/chat/utils/templates/static_charts.html
@@ -1,9 +1,16 @@
-📊 数据总览
-
-
+
+
+ 📊 数据可视化
+
+
+
+
-
\ No newline at end of file
diff --git a/src/chat/utils/templates/tab_content.html b/src/chat/utils/templates/tab_content.html
index b9094aae1..7e6566a3a 100644
--- a/src/chat/utils/templates/tab_content.html
+++ b/src/chat/utils/templates/tab_content.html
@@ -1,4 +1,25 @@
+
+
📖 名词解释
+
+
+ 🎯 Token: AI处理文本的基本单位,约等于0.75个英文单词或0.5个中文字
+
+
+ 💸 TPS: Token Per Second,每秒处理的Token数量,衡量AI响应速度
+
+
+ 📊 每K Token成本: 每1000个Token的成本,用于比较不同模型的性价比
+
+
+ ⚡ Token效率: 输出Token与输入Token的比率,反映模型输出丰富度
+
+
+ 🔄 消息/请求比: 平均每次AI请求处理的用户消息数量
+
+
+
+
@@ -15,7 +36,9 @@
🏢 按供应商分类统计
- | 供应商名称 | 调用次数 | Token总量 | TPS | 每K Token成本 | 累计花费 |
+
+ | 供应商名称 | 调用次数 | Token总量 | TPS | 每K Token成本 | 累计花费 | 平均耗时(秒) |
+
{{ provider_rows }}
@@ -58,9 +81,9 @@
{{ efficiency_rows }}
-
-
-
\ No newline at end of file