feat:提供更多统计数据

This commit is contained in:
SengokuCola
2025-06-15 20:34:37 +08:00
parent ccf5092116
commit 222d4114fc
4 changed files with 408 additions and 25 deletions

View File

@@ -811,10 +811,10 @@ class NormalChat:
# 检查是否满足关系构建条件 # 检查是否满足关系构建条件
should_build_relation = ( should_build_relation = (
total_messages >= 50 # 50条消息必定满足 total_messages >= 40 # 40条消息必定满足
or (total_messages >= 35 and time_elapsed >= 600) # 35条且10分钟 or (total_messages >= 25 and time_elapsed >= 600) # 25条且10分钟
or (total_messages >= 25 and time_elapsed >= 1800) # 25条且30分钟 or (total_messages >= 20 and time_elapsed >= 900) # 20条且30分钟
or (total_messages >= 10 and time_elapsed >= 3600) # 10条且1小时 or (total_messages >= 10 and time_elapsed >= 1800) # 10条且1小时
) )
if should_build_relation: if should_build_relation:

View File

@@ -67,7 +67,7 @@ class NormalChatPlanner:
# LLM规划器配置 # LLM规划器配置
self.planner_llm = LLMRequest( self.planner_llm = LLMRequest(
model=global_config.model.planner, model=global_config.model.planner,
request_type="normal_chat.planner", # 用于normal_chat动作规划 request_type="normal.planner", # 用于normal_chat动作规划
) )
self.action_manager = action_manager self.action_manager = action_manager

View File

@@ -7,7 +7,7 @@ from src.common.logger import get_logger
from src.manager.async_task_manager import AsyncTask from src.manager.async_task_manager import AsyncTask
from ...common.database.database import db # This db is the Peewee database instance from ...common.database.database import db # This db is the Peewee database instance
from ...common.database.database_model import OnlineTime, LLMUsage, Messages # Import the Peewee model from ...common.database.database_model import OnlineTime, LLMUsage, Messages, ChatStreams # Import the Peewee model
from src.manager.local_store_manager import local_storage from src.manager.local_store_manager import local_storage
logger = get_logger("maibot_statistic") logger = get_logger("maibot_statistic")
@@ -18,18 +18,23 @@ TOTAL_COST = "total_cost"
REQ_CNT_BY_TYPE = "requests_by_type" REQ_CNT_BY_TYPE = "requests_by_type"
REQ_CNT_BY_USER = "requests_by_user" REQ_CNT_BY_USER = "requests_by_user"
REQ_CNT_BY_MODEL = "requests_by_model" REQ_CNT_BY_MODEL = "requests_by_model"
REQ_CNT_BY_MODULE = "requests_by_module"
IN_TOK_BY_TYPE = "in_tokens_by_type" IN_TOK_BY_TYPE = "in_tokens_by_type"
IN_TOK_BY_USER = "in_tokens_by_user" IN_TOK_BY_USER = "in_tokens_by_user"
IN_TOK_BY_MODEL = "in_tokens_by_model" IN_TOK_BY_MODEL = "in_tokens_by_model"
IN_TOK_BY_MODULE = "in_tokens_by_module"
OUT_TOK_BY_TYPE = "out_tokens_by_type" OUT_TOK_BY_TYPE = "out_tokens_by_type"
OUT_TOK_BY_USER = "out_tokens_by_user" OUT_TOK_BY_USER = "out_tokens_by_user"
OUT_TOK_BY_MODEL = "out_tokens_by_model" OUT_TOK_BY_MODEL = "out_tokens_by_model"
OUT_TOK_BY_MODULE = "out_tokens_by_module"
TOTAL_TOK_BY_TYPE = "tokens_by_type" TOTAL_TOK_BY_TYPE = "tokens_by_type"
TOTAL_TOK_BY_USER = "tokens_by_user" TOTAL_TOK_BY_USER = "tokens_by_user"
TOTAL_TOK_BY_MODEL = "tokens_by_model" TOTAL_TOK_BY_MODEL = "tokens_by_model"
TOTAL_TOK_BY_MODULE = "tokens_by_module"
COST_BY_TYPE = "costs_by_type" COST_BY_TYPE = "costs_by_type"
COST_BY_USER = "costs_by_user" COST_BY_USER = "costs_by_user"
COST_BY_MODEL = "costs_by_model" COST_BY_MODEL = "costs_by_model"
COST_BY_MODULE = "costs_by_module"
ONLINE_TIME = "online_time" ONLINE_TIME = "online_time"
TOTAL_MSG_CNT = "total_messages" TOTAL_MSG_CNT = "total_messages"
MSG_CNT_BY_CHAT = "messages_by_chat" MSG_CNT_BY_CHAT = "messages_by_chat"
@@ -149,6 +154,7 @@ class StatisticOutputTask(AsyncTask):
("all_time", now - deploy_time, "自部署以来"), # 必须保留"all_time" ("all_time", now - deploy_time, "自部署以来"), # 必须保留"all_time"
("last_7_days", timedelta(days=7), "最近7天"), ("last_7_days", timedelta(days=7), "最近7天"),
("last_24_hours", timedelta(days=1), "最近24小时"), ("last_24_hours", timedelta(days=1), "最近24小时"),
("last_3_hours", timedelta(hours=3), "最近3小时"),
("last_hour", timedelta(hours=1), "最近1小时"), ("last_hour", timedelta(hours=1), "最近1小时"),
] ]
""" """
@@ -212,19 +218,24 @@ class StatisticOutputTask(AsyncTask):
REQ_CNT_BY_TYPE: defaultdict(int), REQ_CNT_BY_TYPE: defaultdict(int),
REQ_CNT_BY_USER: defaultdict(int), REQ_CNT_BY_USER: defaultdict(int),
REQ_CNT_BY_MODEL: defaultdict(int), REQ_CNT_BY_MODEL: defaultdict(int),
REQ_CNT_BY_MODULE: defaultdict(int),
IN_TOK_BY_TYPE: defaultdict(int), IN_TOK_BY_TYPE: defaultdict(int),
IN_TOK_BY_USER: defaultdict(int), IN_TOK_BY_USER: defaultdict(int),
IN_TOK_BY_MODEL: defaultdict(int), IN_TOK_BY_MODEL: defaultdict(int),
IN_TOK_BY_MODULE: defaultdict(int),
OUT_TOK_BY_TYPE: defaultdict(int), OUT_TOK_BY_TYPE: defaultdict(int),
OUT_TOK_BY_USER: defaultdict(int), OUT_TOK_BY_USER: defaultdict(int),
OUT_TOK_BY_MODEL: defaultdict(int), OUT_TOK_BY_MODEL: defaultdict(int),
OUT_TOK_BY_MODULE: defaultdict(int),
TOTAL_TOK_BY_TYPE: defaultdict(int), TOTAL_TOK_BY_TYPE: defaultdict(int),
TOTAL_TOK_BY_USER: defaultdict(int), TOTAL_TOK_BY_USER: defaultdict(int),
TOTAL_TOK_BY_MODEL: defaultdict(int), TOTAL_TOK_BY_MODEL: defaultdict(int),
TOTAL_TOK_BY_MODULE: defaultdict(int),
TOTAL_COST: 0.0, TOTAL_COST: 0.0,
COST_BY_TYPE: defaultdict(float), COST_BY_TYPE: defaultdict(float),
COST_BY_USER: defaultdict(float), COST_BY_USER: defaultdict(float),
COST_BY_MODEL: defaultdict(float), COST_BY_MODEL: defaultdict(float),
COST_BY_MODULE: defaultdict(float),
} }
for period_key, _ in collect_period for period_key, _ in collect_period
} }
@@ -242,10 +253,14 @@ class StatisticOutputTask(AsyncTask):
request_type = record.request_type or "unknown" request_type = record.request_type or "unknown"
user_id = record.user_id or "unknown" # user_id is TextField, already string user_id = record.user_id or "unknown" # user_id is TextField, already string
model_name = record.model_name or "unknown" model_name = record.model_name or "unknown"
# 提取模块名:如果请求类型包含".",取第一个"."之前的部分
module_name = request_type.split('.')[0] if '.' in request_type else request_type
stats[period_key][REQ_CNT_BY_TYPE][request_type] += 1 stats[period_key][REQ_CNT_BY_TYPE][request_type] += 1
stats[period_key][REQ_CNT_BY_USER][user_id] += 1 stats[period_key][REQ_CNT_BY_USER][user_id] += 1
stats[period_key][REQ_CNT_BY_MODEL][model_name] += 1 stats[period_key][REQ_CNT_BY_MODEL][model_name] += 1
stats[period_key][REQ_CNT_BY_MODULE][module_name] += 1
prompt_tokens = record.prompt_tokens or 0 prompt_tokens = record.prompt_tokens or 0
completion_tokens = record.completion_tokens or 0 completion_tokens = record.completion_tokens or 0
@@ -254,20 +269,24 @@ class StatisticOutputTask(AsyncTask):
stats[period_key][IN_TOK_BY_TYPE][request_type] += prompt_tokens stats[period_key][IN_TOK_BY_TYPE][request_type] += prompt_tokens
stats[period_key][IN_TOK_BY_USER][user_id] += prompt_tokens stats[period_key][IN_TOK_BY_USER][user_id] += prompt_tokens
stats[period_key][IN_TOK_BY_MODEL][model_name] += prompt_tokens stats[period_key][IN_TOK_BY_MODEL][model_name] += prompt_tokens
stats[period_key][IN_TOK_BY_MODULE][module_name] += prompt_tokens
stats[period_key][OUT_TOK_BY_TYPE][request_type] += completion_tokens stats[period_key][OUT_TOK_BY_TYPE][request_type] += completion_tokens
stats[period_key][OUT_TOK_BY_USER][user_id] += completion_tokens stats[period_key][OUT_TOK_BY_USER][user_id] += completion_tokens
stats[period_key][OUT_TOK_BY_MODEL][model_name] += completion_tokens stats[period_key][OUT_TOK_BY_MODEL][model_name] += completion_tokens
stats[period_key][OUT_TOK_BY_MODULE][module_name] += completion_tokens
stats[period_key][TOTAL_TOK_BY_TYPE][request_type] += total_tokens stats[period_key][TOTAL_TOK_BY_TYPE][request_type] += total_tokens
stats[period_key][TOTAL_TOK_BY_USER][user_id] += total_tokens stats[period_key][TOTAL_TOK_BY_USER][user_id] += total_tokens
stats[period_key][TOTAL_TOK_BY_MODEL][model_name] += total_tokens stats[period_key][TOTAL_TOK_BY_MODEL][model_name] += total_tokens
stats[period_key][TOTAL_TOK_BY_MODULE][module_name] += total_tokens
cost = record.cost or 0.0 cost = record.cost or 0.0
stats[period_key][TOTAL_COST] += cost stats[period_key][TOTAL_COST] += cost
stats[period_key][COST_BY_TYPE][request_type] += cost stats[period_key][COST_BY_TYPE][request_type] += cost
stats[period_key][COST_BY_USER][user_id] += cost stats[period_key][COST_BY_USER][user_id] += cost
stats[period_key][COST_BY_MODEL][model_name] += cost stats[period_key][COST_BY_MODEL][model_name] += cost
stats[period_key][COST_BY_MODULE][module_name] += cost
break break
return stats return stats
@@ -492,6 +511,8 @@ class StatisticOutputTask(AsyncTask):
f'<button class="tab-link" onclick="showTab(event, \'{period[0]}\')">{period[2]}</button>' f'<button class="tab-link" onclick="showTab(event, \'{period[0]}\')">{period[2]}</button>'
for period in self.stat_period for period in self.stat_period
] ]
# 添加图表选项卡
tab_list.append('<button class="tab-link" onclick="showTab(event, \'charts\')">数据图表</button>')
def _format_stat_data(stat_data: dict[str, Any], div_id: str, start_time: datetime) -> str: def _format_stat_data(stat_data: dict[str, Any], div_id: str, start_time: datetime) -> str:
""" """
@@ -530,20 +551,21 @@ class StatisticOutputTask(AsyncTask):
for req_type, count in sorted(stat_data[REQ_CNT_BY_TYPE].items()) for req_type, count in sorted(stat_data[REQ_CNT_BY_TYPE].items())
] ]
) )
# 按用户分类统计 # 按模块分类统计
user_rows = "\n".join( module_rows = "\n".join(
[ [
f"<tr>" f"<tr>"
f"<td>{user_id}</td>" f"<td>{module_name}</td>"
f"<td>{count}</td>" f"<td>{count}</td>"
f"<td>{stat_data[IN_TOK_BY_USER][user_id]}</td>" f"<td>{stat_data[IN_TOK_BY_MODULE][module_name]}</td>"
f"<td>{stat_data[OUT_TOK_BY_USER][user_id]}</td>" f"<td>{stat_data[OUT_TOK_BY_MODULE][module_name]}</td>"
f"<td>{stat_data[TOTAL_TOK_BY_USER][user_id]}</td>" f"<td>{stat_data[TOTAL_TOK_BY_MODULE][module_name]}</td>"
f"<td>{stat_data[COST_BY_USER][user_id]:.4f} ¥</td>" f"<td>{stat_data[COST_BY_MODULE][module_name]:.4f} ¥</td>"
f"</tr>" f"</tr>"
for user_id, count in sorted(stat_data[REQ_CNT_BY_USER].items()) for module_name, count in sorted(stat_data[REQ_CNT_BY_MODULE].items())
] ]
) )
# 聊天消息统计 # 聊天消息统计
chat_rows = "\n".join( chat_rows = "\n".join(
[ [
@@ -571,6 +593,16 @@ class StatisticOutputTask(AsyncTask):
</tbody> </tbody>
</table> </table>
<h2>按模块分类统计</h2>
<table>
<thead>
<tr><th>模块名称</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th></tr>
</thead>
<tbody>
{module_rows}
</tbody>
</table>
<h2>按请求类型分类统计</h2> <h2>按请求类型分类统计</h2>
<table> <table>
<thead> <thead>
@@ -581,16 +613,6 @@ class StatisticOutputTask(AsyncTask):
</tbody> </tbody>
</table> </table>
<h2>按用户分类统计</h2>
<table>
<thead>
<tr><th>用户名称</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th></tr>
</thead>
<tbody>
{user_rows}
</tbody>
</table>
<h2>聊天消息统计</h2> <h2>聊天消息统计</h2>
<table> <table>
<thead> <thead>
@@ -613,6 +635,10 @@ class StatisticOutputTask(AsyncTask):
_format_stat_data(stat["all_time"], "all_time", datetime.fromtimestamp(local_storage["deploy_time"])) _format_stat_data(stat["all_time"], "all_time", datetime.fromtimestamp(local_storage["deploy_time"]))
) )
# 添加图表内容
chart_data = self._generate_chart_data(stat)
tab_content_list.append(self._generate_chart_tab(chart_data))
joined_tab_list = "\n".join(tab_list) joined_tab_list = "\n".join(tab_list)
joined_tab_content = "\n".join(tab_content_list) joined_tab_content = "\n".join(tab_content_list)
@@ -624,6 +650,7 @@ class StatisticOutputTask(AsyncTask):
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MaiBot运行统计报告</title> <title>MaiBot运行统计报告</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style> <style>
body { body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
@@ -757,3 +784,359 @@ class StatisticOutputTask(AsyncTask):
with open(self.record_file_path, "w", encoding="utf-8") as f: with open(self.record_file_path, "w", encoding="utf-8") as f:
f.write(html_template) f.write(html_template)
def _generate_chart_data(self, stat: dict[str, Any]) -> dict:
"""生成图表数据"""
now = datetime.now()
chart_data = {}
# 支持多个时间范围
time_ranges = [
("6h", 6, 10), # 6小时10分钟间隔
("12h", 12, 15), # 12小时15分钟间隔
("24h", 24, 15), # 24小时15分钟间隔
("48h", 48, 30), # 48小时30分钟间隔
]
for range_key, hours, interval_minutes in time_ranges:
range_data = self._collect_interval_data(now, hours, interval_minutes)
chart_data[range_key] = range_data
return chart_data
def _collect_interval_data(self, now: datetime, hours: int, interval_minutes: int) -> dict:
"""收集指定时间范围内每个间隔的数据"""
# 生成时间点
start_time = now - timedelta(hours=hours)
time_points = []
current_time = start_time
while current_time <= now:
time_points.append(current_time)
current_time += timedelta(minutes=interval_minutes)
# 初始化数据结构
total_cost_data = [0] * len(time_points)
cost_by_model = {}
cost_by_module = {}
message_by_chat = {}
time_labels = [t.strftime("%H:%M") for t in time_points]
interval_seconds = interval_minutes * 60
# 查询LLM使用记录
query_start_time = start_time
for record in LLMUsage.select().where(LLMUsage.timestamp >= query_start_time):
record_time = record.timestamp
# 找到对应的时间间隔索引
time_diff = (record_time - start_time).total_seconds()
interval_index = int(time_diff // interval_seconds)
if 0 <= interval_index < len(time_points):
# 累加总花费数据
cost = record.cost or 0.0
total_cost_data[interval_index] += cost
# 累加按模型分类的花费
model_name = record.model_name or "unknown"
if model_name not in cost_by_model:
cost_by_model[model_name] = [0] * len(time_points)
cost_by_model[model_name][interval_index] += cost
# 累加按模块分类的花费
request_type = record.request_type or "unknown"
module_name = request_type.split('.')[0] if '.' in request_type else request_type
if module_name not in cost_by_module:
cost_by_module[module_name] = [0] * len(time_points)
cost_by_module[module_name][interval_index] += cost
# 查询消息记录
query_start_timestamp = start_time.timestamp()
for message in Messages.select().where(Messages.time >= query_start_timestamp):
message_time_ts = message.time
# 找到对应的时间间隔索引
time_diff = message_time_ts - query_start_timestamp
interval_index = int(time_diff // interval_seconds)
if 0 <= interval_index < len(time_points):
# 确定聊天流名称
chat_name = None
if message.chat_info_group_id:
chat_name = message.chat_info_group_name or f"{message.chat_info_group_id}"
elif message.user_id:
chat_name = message.user_nickname or f"用户{message.user_id}"
else:
continue
if not chat_name:
continue
# 累加消息数
if chat_name not in message_by_chat:
message_by_chat[chat_name] = [0] * len(time_points)
message_by_chat[chat_name][interval_index] += 1
return {
"time_labels": time_labels,
"total_cost_data": total_cost_data,
"cost_by_model": cost_by_model,
"cost_by_module": cost_by_module,
"message_by_chat": message_by_chat
}
def _generate_chart_tab(self, chart_data: dict) -> str:
"""生成图表选项卡HTML内容"""
# 生成不同颜色的调色板
colors = [
'#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6',
'#1abc9c', '#34495e', '#e67e22', '#95a5a6', '#f1c40f'
]
# 默认使用24小时数据生成数据集
default_data = chart_data['24h']
# 为每个模型生成数据集
model_datasets = []
for i, (model_name, cost_data) in enumerate(default_data['cost_by_model'].items()):
color = colors[i % len(colors)]
model_datasets.append(f'''{{
label: '{model_name}',
data: {cost_data},
borderColor: '{color}',
backgroundColor: '{color}20',
tension: 0.4,
fill: false
}}''')
model_datasets_str = ',\n '.join(model_datasets)
# 为每个模块生成数据集
module_datasets = []
for i, (module_name, cost_data) in enumerate(default_data['cost_by_module'].items()):
color = colors[i % len(colors)]
module_datasets.append(f'''{{
label: '{module_name}',
data: {cost_data},
borderColor: '{color}',
backgroundColor: '{color}20',
tension: 0.4,
fill: false
}}''')
module_datasets_str = ',\n '.join(module_datasets)
# 为每个聊天流生成消息数据集
message_datasets = []
for i, (chat_name, message_data) in enumerate(default_data['message_by_chat'].items()):
color = colors[i % len(colors)]
message_datasets.append(f'''{{
label: '{chat_name}',
data: {message_data},
borderColor: '{color}',
backgroundColor: '{color}20',
tension: 0.4,
fill: false
}}''')
message_datasets_str = ',\n '.join(message_datasets)
return f'''
<div id="charts" class="tab-content">
<h2>数据图表</h2>
<!-- 时间范围选择按钮 -->
<div style="margin: 20px 0; text-align: center;">
<label style="margin-right: 10px; font-weight: bold;">时间范围:</label>
<button class="time-range-btn" onclick="switchTimeRange('6h')">6小时</button>
<button class="time-range-btn" onclick="switchTimeRange('12h')">12小时</button>
<button class="time-range-btn active" onclick="switchTimeRange('24h')">24小时</button>
<button class="time-range-btn" onclick="switchTimeRange('48h')">48小时</button>
</div>
<div style="margin-top: 20px;">
<div style="margin-bottom: 40px;">
<canvas id="totalCostChart" width="800" height="400"></canvas>
</div>
<div style="margin-bottom: 40px;">
<canvas id="costByModuleChart" width="800" height="400"></canvas>
</div>
<div style="margin-bottom: 40px;">
<canvas id="costByModelChart" width="800" height="400"></canvas>
</div>
<div>
<canvas id="messageByChatChart" width="800" height="400"></canvas>
</div>
</div>
<style>
.time-range-btn {{
background-color: #ecf0f1;
border: 1px solid #bdc3c7;
color: #2c3e50;
padding: 8px 16px;
margin: 0 5px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}}
.time-range-btn:hover {{
background-color: #d5dbdb;
}}
.time-range-btn.active {{
background-color: #3498db;
color: white;
border-color: #2980b9;
}}
</style>
<script>
const allChartData = {chart_data};
let currentCharts = {{}};
// 图表配置模板
const chartConfigs = {{
totalCost: {{
id: 'totalCostChart',
title: '总花费',
yAxisLabel: '花费 (¥)',
dataKey: 'total_cost_data',
fill: true
}},
costByModule: {{
id: 'costByModuleChart',
title: '各模块花费',
yAxisLabel: '花费 (¥)',
dataKey: 'cost_by_module',
fill: false
}},
costByModel: {{
id: 'costByModelChart',
title: '各模型花费',
yAxisLabel: '花费 (¥)',
dataKey: 'cost_by_model',
fill: false
}},
messageByChat: {{
id: 'messageByChatChart',
title: '各聊天流消息数',
yAxisLabel: '消息数',
dataKey: 'message_by_chat',
fill: false
}}
}};
function switchTimeRange(timeRange) {{
// 更新按钮状态
document.querySelectorAll('.time-range-btn').forEach(btn => {{
btn.classList.remove('active');
}});
event.target.classList.add('active');
// 更新图表数据
const data = allChartData[timeRange];
updateAllCharts(data, timeRange);
}}
function updateAllCharts(data, timeRange) {{
// 销毁现有图表
Object.values(currentCharts).forEach(chart => {{
if (chart) chart.destroy();
}});
currentCharts = {{}};
// 重新创建图表
createChart('totalCost', data, timeRange);
createChart('costByModule', data, timeRange);
createChart('costByModel', data, timeRange);
createChart('messageByChat', data, timeRange);
}}
function createChart(chartType, data, timeRange) {{
const config = chartConfigs[chartType];
const colors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#34495e', '#e67e22', '#95a5a6', '#f1c40f'];
let datasets = [];
if (chartType === 'totalCost') {{
datasets = [{{
label: config.title,
data: data[config.dataKey],
borderColor: colors[0],
backgroundColor: 'rgba(52, 152, 219, 0.1)',
tension: 0.4,
fill: config.fill
}}];
}} else {{
let i = 0;
Object.entries(data[config.dataKey]).forEach(([name, chartData]) => {{
datasets.push({{
label: name,
data: chartData,
borderColor: colors[i % colors.length],
backgroundColor: colors[i % colors.length] + '20',
tension: 0.4,
fill: config.fill
}});
i++;
}});
}}
currentCharts[chartType] = new Chart(document.getElementById(config.id), {{
type: 'line',
data: {{
labels: data.time_labels,
datasets: datasets
}},
options: {{
responsive: true,
plugins: {{
title: {{
display: true,
text: timeRange + '' + config.title + '趋势',
font: {{ size: 16 }}
}},
legend: {{
display: chartType !== 'totalCost',
position: 'top'
}}
}},
scales: {{
x: {{
title: {{
display: true,
text: '时间'
}},
ticks: {{
maxTicksLimit: 12
}}
}},
y: {{
title: {{
display: true,
text: config.yAxisLabel
}},
beginAtZero: true
}}
}},
interaction: {{
intersect: false,
mode: 'index'
}}
}}
}});
}}
// 初始化图表默认24小时
document.addEventListener('DOMContentLoaded', function() {{
updateAllCharts(allChartData['24h'], '24h');
}});
</script>
</div>
'''

View File

@@ -326,7 +326,7 @@ MODULE_COLORS = {
"working_memory": "\033[38;5;22m", # 深绿色 "working_memory": "\033[38;5;22m", # 深绿色
"memory_activator": "\033[38;5;28m", # 绿色 "memory_activator": "\033[38;5;28m", # 绿色
# 插件系统 # 插件系统
"plugin_manager": "\033[38;5;196m", # 红色 "plugin_manager": "\033[38;5;208m", # 红色
"base_plugin": "\033[38;5;202m", # 橙红色 "base_plugin": "\033[38;5;202m", # 橙红色
"base_command": "\033[38;5;208m", # 橙色 "base_command": "\033[38;5;208m", # 橙色
"component_registry": "\033[38;5;214m", # 橙黄色 "component_registry": "\033[38;5;214m", # 橙黄色