This commit is contained in:
SengokuCola
2025-06-15 21:30:12 +08:00

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, ChatStreams # Import the Peewee model from ...common.database.database_model import OnlineTime, LLMUsage, Messages # 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")
@@ -253,9 +253,9 @@ 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 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
@@ -789,19 +789,19 @@ class StatisticOutputTask(AsyncTask):
"""生成图表数据""" """生成图表数据"""
now = datetime.now() now = datetime.now()
chart_data = {} chart_data = {}
# 支持多个时间范围 # 支持多个时间范围
time_ranges = [ time_ranges = [
("6h", 6, 10), # 6小时10分钟间隔 ("6h", 6, 10), # 6小时10分钟间隔
("12h", 12, 15), # 12小时15分钟间隔 ("12h", 12, 15), # 12小时15分钟间隔
("24h", 24, 15), # 24小时15分钟间隔 ("24h", 24, 15), # 24小时15分钟间隔
("48h", 48, 30), # 48小时30分钟间隔 ("48h", 48, 30), # 48小时30分钟间隔
] ]
for range_key, hours, interval_minutes in time_ranges: for range_key, hours, interval_minutes in time_ranges:
range_data = self._collect_interval_data(now, hours, interval_minutes) range_data = self._collect_interval_data(now, hours, interval_minutes)
chart_data[range_key] = range_data chart_data[range_key] = range_data
return chart_data return chart_data
def _collect_interval_data(self, now: datetime, hours: int, interval_minutes: int) -> dict: def _collect_interval_data(self, now: datetime, hours: int, interval_minutes: int) -> dict:
@@ -810,56 +810,56 @@ class StatisticOutputTask(AsyncTask):
start_time = now - timedelta(hours=hours) start_time = now - timedelta(hours=hours)
time_points = [] time_points = []
current_time = start_time current_time = start_time
while current_time <= now: while current_time <= now:
time_points.append(current_time) time_points.append(current_time)
current_time += timedelta(minutes=interval_minutes) current_time += timedelta(minutes=interval_minutes)
# 初始化数据结构 # 初始化数据结构
total_cost_data = [0] * len(time_points) total_cost_data = [0] * len(time_points)
cost_by_model = {} cost_by_model = {}
cost_by_module = {} cost_by_module = {}
message_by_chat = {} message_by_chat = {}
time_labels = [t.strftime("%H:%M") for t in time_points] time_labels = [t.strftime("%H:%M") for t in time_points]
interval_seconds = interval_minutes * 60 interval_seconds = interval_minutes * 60
# 查询LLM使用记录 # 查询LLM使用记录
query_start_time = start_time query_start_time = start_time
for record in LLMUsage.select().where(LLMUsage.timestamp >= query_start_time): for record in LLMUsage.select().where(LLMUsage.timestamp >= query_start_time):
record_time = record.timestamp record_time = record.timestamp
# 找到对应的时间间隔索引 # 找到对应的时间间隔索引
time_diff = (record_time - start_time).total_seconds() time_diff = (record_time - start_time).total_seconds()
interval_index = int(time_diff // interval_seconds) interval_index = int(time_diff // interval_seconds)
if 0 <= interval_index < len(time_points): if 0 <= interval_index < len(time_points):
# 累加总花费数据 # 累加总花费数据
cost = record.cost or 0.0 cost = record.cost or 0.0
total_cost_data[interval_index] += cost total_cost_data[interval_index] += cost
# 累加按模型分类的花费 # 累加按模型分类的花费
model_name = record.model_name or "unknown" model_name = record.model_name or "unknown"
if model_name not in cost_by_model: if model_name not in cost_by_model:
cost_by_model[model_name] = [0] * len(time_points) cost_by_model[model_name] = [0] * len(time_points)
cost_by_model[model_name][interval_index] += cost cost_by_model[model_name][interval_index] += cost
# 累加按模块分类的花费 # 累加按模块分类的花费
request_type = record.request_type or "unknown" request_type = record.request_type or "unknown"
module_name = request_type.split('.')[0] if '.' in request_type else request_type module_name = request_type.split(".")[0] if "." in request_type else request_type
if module_name not in cost_by_module: if module_name not in cost_by_module:
cost_by_module[module_name] = [0] * len(time_points) cost_by_module[module_name] = [0] * len(time_points)
cost_by_module[module_name][interval_index] += cost cost_by_module[module_name][interval_index] += cost
# 查询消息记录 # 查询消息记录
query_start_timestamp = start_time.timestamp() query_start_timestamp = start_time.timestamp()
for message in Messages.select().where(Messages.time >= query_start_timestamp): for message in Messages.select().where(Messages.time >= query_start_timestamp):
message_time_ts = message.time message_time_ts = message.time
# 找到对应的时间间隔索引 # 找到对应的时间间隔索引
time_diff = message_time_ts - query_start_timestamp time_diff = message_time_ts - query_start_timestamp
interval_index = int(time_diff // interval_seconds) interval_index = int(time_diff // interval_seconds)
if 0 <= interval_index < len(time_points): if 0 <= interval_index < len(time_points):
# 确定聊天流名称 # 确定聊天流名称
chat_name = None chat_name = None
@@ -869,81 +869,89 @@ class StatisticOutputTask(AsyncTask):
chat_name = message.user_nickname or f"用户{message.user_id}" chat_name = message.user_nickname or f"用户{message.user_id}"
else: else:
continue continue
if not chat_name: if not chat_name:
continue continue
# 累加消息数 # 累加消息数
if chat_name not in message_by_chat: if chat_name not in message_by_chat:
message_by_chat[chat_name] = [0] * len(time_points) message_by_chat[chat_name] = [0] * len(time_points)
message_by_chat[chat_name][interval_index] += 1 message_by_chat[chat_name][interval_index] += 1
return { return {
"time_labels": time_labels, "time_labels": time_labels,
"total_cost_data": total_cost_data, "total_cost_data": total_cost_data,
"cost_by_model": cost_by_model, "cost_by_model": cost_by_model,
"cost_by_module": cost_by_module, "cost_by_module": cost_by_module,
"message_by_chat": message_by_chat "message_by_chat": message_by_chat,
} }
def _generate_chart_tab(self, chart_data: dict) -> str: def _generate_chart_tab(self, chart_data: dict) -> str:
"""生成图表选项卡HTML内容""" """生成图表选项卡HTML内容"""
# 生成不同颜色的调色板 # 生成不同颜色的调色板
colors = [ colors = [
'#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', "#3498db",
'#1abc9c', '#34495e', '#e67e22', '#95a5a6', '#f1c40f' "#e74c3c",
"#2ecc71",
"#f39c12",
"#9b59b6",
"#1abc9c",
"#34495e",
"#e67e22",
"#95a5a6",
"#f1c40f",
] ]
# 默认使用24小时数据生成数据集 # 默认使用24小时数据生成数据集
default_data = chart_data['24h'] default_data = chart_data["24h"]
# 为每个模型生成数据集 # 为每个模型生成数据集
model_datasets = [] model_datasets = []
for i, (model_name, cost_data) in enumerate(default_data['cost_by_model'].items()): for i, (model_name, cost_data) in enumerate(default_data["cost_by_model"].items()):
color = colors[i % len(colors)] color = colors[i % len(colors)]
model_datasets.append(f'''{{ model_datasets.append(f"""{{
label: '{model_name}', label: '{model_name}',
data: {cost_data}, data: {cost_data},
borderColor: '{color}', borderColor: '{color}',
backgroundColor: '{color}20', backgroundColor: '{color}20',
tension: 0.4, tension: 0.4,
fill: false fill: false
}}''') }}""")
model_datasets_str = ',\n '.join(model_datasets) ",\n ".join(model_datasets)
# 为每个模块生成数据集 # 为每个模块生成数据集
module_datasets = [] module_datasets = []
for i, (module_name, cost_data) in enumerate(default_data['cost_by_module'].items()): for i, (module_name, cost_data) in enumerate(default_data["cost_by_module"].items()):
color = colors[i % len(colors)] color = colors[i % len(colors)]
module_datasets.append(f'''{{ module_datasets.append(f"""{{
label: '{module_name}', label: '{module_name}',
data: {cost_data}, data: {cost_data},
borderColor: '{color}', borderColor: '{color}',
backgroundColor: '{color}20', backgroundColor: '{color}20',
tension: 0.4, tension: 0.4,
fill: false fill: false
}}''') }}""")
module_datasets_str = ',\n '.join(module_datasets) ",\n ".join(module_datasets)
# 为每个聊天流生成消息数据集 # 为每个聊天流生成消息数据集
message_datasets = [] message_datasets = []
for i, (chat_name, message_data) in enumerate(default_data['message_by_chat'].items()): for i, (chat_name, message_data) in enumerate(default_data["message_by_chat"].items()):
color = colors[i % len(colors)] color = colors[i % len(colors)]
message_datasets.append(f'''{{ message_datasets.append(f"""{{
label: '{chat_name}', label: '{chat_name}',
data: {message_data}, data: {message_data},
borderColor: '{color}', borderColor: '{color}',
backgroundColor: '{color}20', backgroundColor: '{color}20',
tension: 0.4, tension: 0.4,
fill: false fill: false
}}''') }}""")
message_datasets_str = ',\n '.join(message_datasets) ",\n ".join(message_datasets)
return f''' return f"""
<div id="charts" class="tab-content"> <div id="charts" class="tab-content">
<h2>数据图表</h2> <h2>数据图表</h2>
@@ -1139,4 +1147,4 @@ class StatisticOutputTask(AsyncTask):
}}); }});
</script> </script>
</div> </div>
''' """