diff --git a/src/chat/utils/report_generator.py b/src/chat/utils/report_generator.py
deleted file mode 100644
index 5526781fa..000000000
--- a/src/chat/utils/report_generator.py
+++ /dev/null
@@ -1,446 +0,0 @@
-"""
-该模块用于生成HTML格式的统计报告。
-"""
-from datetime import datetime, timedelta
-from typing import Any
-
-import aiofiles
-
-from .statistic_keys import * # noqa: F403
-
-
-def format_online_time(online_seconds: int) -> str:
- """
- 格式化在线时间。
-
- :param online_seconds: 在线时间(秒)。
- :return: 格式化后的在线时间字符串。
- """
- total_online_time = timedelta(seconds=online_seconds)
- days = total_online_time.days
- hours = total_online_time.seconds // 3600
- minutes = (total_online_time.seconds // 60) % 60
- seconds = total_online_time.seconds % 60
- if days > 0:
- return f"{days}天{hours}小时{minutes}分钟{seconds}秒"
- elif hours > 0:
- return f"{hours}小时{minutes}分钟{seconds}秒"
- else:
- return f"{minutes}分钟{seconds}秒"
-
-
-class HTMLReportGenerator:
- """生成HTML统计报告"""
-
- def __init__(
- self,
- name_mapping: dict,
- stat_period: list,
- deploy_time: datetime,
- ):
- """
- 初始化报告生成器。
-
- :param name_mapping: 聊天ID到名称的映射。
- :param stat_period: 统计时间段配置。
- :param deploy_time: 系统部署时间。
- """
- self.name_mapping = name_mapping
- self.stat_period = stat_period
- self.deploy_time = deploy_time
-
- def _format_stat_data_div(self, stat_data: dict[str, Any], div_id: str, start_time: datetime, now: datetime) -> str:
- """
- 将单个时间段的统计数据格式化为HTML div块。
-
- :param stat_data: 统计数据。
- :param div_id: div的ID。
- :param start_time: 统计时间段的开始时间。
- :param now: 当前时间。
- :return: HTML字符串。
- """
- # 按模型分类统计
- model_rows = "\n".join(
- [
- f"
"
- f"| {model_name} | "
- f"{count} | "
- f"{stat_data[AVG_TOK_BY_MODEL].get(model_name, 0)} | "
- f"{stat_data[TOTAL_TOK_BY_MODEL].get(model_name, 0)} | "
- f"{stat_data[TPS_BY_MODEL].get(model_name, 0):.2f} | "
- f"{stat_data[COST_PER_KTOK_BY_MODEL].get(model_name, 0):.4f} ¥ | "
- f"{stat_data[COST_BY_MODEL].get(model_name, 0):.4f} ¥ | "
- f"{stat_data[AVG_TIME_COST_BY_MODEL].get(model_name, 0):.3f} 秒 | "
- f"
"
- for model_name, count in sorted(stat_data[REQ_CNT_BY_MODEL].items())
- ]
- )
- # 按供应商分类统计
- provider_rows = "\n".join(
- [
- f""
- f"| {provider_name} | "
- f"{count} | "
- f"{stat_data[TOTAL_TOK_BY_PROVIDER].get(provider_name, 0)} | "
- 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"
"
- for provider_name, count in sorted(stat_data[REQ_CNT_BY_PROVIDER].items())
- ]
- )
- # 按请求类型分类统计
- type_rows = "\n".join(
- [
- f""
- f"| {req_type} | "
- f"{count} | "
- f"{stat_data[IN_TOK_BY_TYPE].get(req_type, 0)} | "
- f"{stat_data[OUT_TOK_BY_TYPE].get(req_type, 0)} | "
- f"{stat_data[TOTAL_TOK_BY_TYPE].get(req_type, 0)} | "
- f"{stat_data[COST_BY_TYPE].get(req_type, 0):.4f} ¥ | "
- f"{stat_data[AVG_TIME_COST_BY_TYPE].get(req_type, 0):.3f} 秒 | "
- f"{stat_data[STD_TIME_COST_BY_TYPE].get(req_type, 0):.3f} 秒 | "
- f"
"
- for req_type, count in sorted(stat_data[REQ_CNT_BY_TYPE].items())
- ]
- )
- # 按模块分类统计
- module_rows = "\n".join(
- [
- f""
- f"| {module_name} | "
- f"{count} | "
- f"{stat_data[IN_TOK_BY_MODULE].get(module_name, 0)} | "
- f"{stat_data[OUT_TOK_BY_MODULE].get(module_name, 0)} | "
- f"{stat_data[TOTAL_TOK_BY_MODULE].get(module_name, 0)} | "
- f"{stat_data[COST_BY_MODULE].get(module_name, 0):.4f} ¥ | "
- f"{stat_data[AVG_TIME_COST_BY_MODULE].get(module_name, 0):.3f} 秒 | "
- f"{stat_data[STD_TIME_COST_BY_MODULE].get(module_name, 0):.3f} 秒 | "
- f"
"
- for module_name, count in sorted(stat_data[REQ_CNT_BY_MODULE].items())
- ]
- )
- # 聊天消息统计
- chat_rows = "\n".join(
- [
- f"| {self.name_mapping.get(chat_id, ('未知', 0))[0]} | {count} |
"
- for chat_id, count in sorted(stat_data[MSG_CNT_BY_CHAT].items())
- ]
- )
- summary_cards = f"""
-
-
-
总花费
-
{stat_data.get(TOTAL_COST, 0):.4f} ¥
-
-
-
总请求数
-
{stat_data.get(TOTAL_REQ_CNT, 0)}
-
-
-
总Token数
-
{sum(stat_data.get(TOTAL_TOK_BY_MODEL, {}).values())}
-
-
-
总消息数
-
{stat_data.get(TOTAL_MSG_CNT, 0)}
-
-
-
总在线时间
-
{format_online_time(int(stat_data.get(ONLINE_TIME, 0)))}
-
-
- """
-
- # 增加饼图和条形图
- static_charts = self._generate_static_charts_div(stat_data, div_id) # 该功能尚未实现
- return f"""
-
-
- 统计时段:
- {start_time.strftime("%Y-%m-%d %H:%M:%S")} ~ {now.strftime("%Y-%m-%d %H:%M:%S")}
-
- {summary_cards}
- {static_charts}
-
-
按模型分类统计
-
- | 模型名称 | 调用次数 | 平均Token数 | Token总量 | TPS | 每K Token成本 | 累计花费 | 平均耗时(秒) |
- {model_rows}
-
-
-
按供应商分类统计
-
- | 供应商名称 | 调用次数 | Token总量 | TPS | 每K Token成本 | 累计花费 |
- {provider_rows}
-
-
-
按模块分类统计
-
-
- | 模块名称 | 调用次数 | 输入Token | 输出Token | Token总量 | 累计花费 | 平均耗时(秒) | 标准差(秒) |
-
- {module_rows}
-
-
-
按请求类型分类统计
-
-
- | 请求类型 | 调用次数 | 输入Token | 输出Token | Token总量 | 累计花费 | 平均耗时(秒) | 标准差(秒) |
-
- {type_rows}
-
-
-
聊天消息统计
-
-
- | 联系人/群组名称 | 消息数量 |
-
- {chat_rows}
-
-
- """
- def _generate_chart_tab(self, chart_data: dict) -> str:
- """生成图表选项卡的HTML内容。"""
- return f"""
-
-
数据图表
-
-
-
-
-
-
-
-
-
-
-
- """
-
- async def generate_report(self, stat: dict[str, Any], chart_data: dict, now: datetime, output_path: str):
- """
- 生成并写入完整的HTML报告文件。
-
- :param stat: 所有时间段的统计数据。
- :param chart_data: 用于图表的数据。
- :param now: 当前时间。
- :param output_path: 输出文件路径。
- """
- tab_list = [
- f''
- for period in self.stat_period
- ]
- tab_list.append('')
-
- tab_content_list = [
- self._format_stat_data_div(stat[period[0]], period[0], now - period[1], now)
- for period in self.stat_period
- if period[0] != "all_time"
- ]
- tab_content_list.append(
- self._format_stat_data_div(stat["all_time"], "all_time", self.deploy_time, now)
- )
- tab_content_list.append(self._generate_chart_tab(chart_data))
-
- joined_tab_list = "\n".join(tab_list)
- joined_tab_content = "\n".join(tab_content_list)
-
- html_template = f"""
-
-
-
-
-
- MoFox-Bot运行统计报告
-
-
-
-
-
-
MoFox-Bot运行统计报告
-
统计截止时间: {now.strftime("%Y-%m-%d %H:%M:%S")}
-
{joined_tab_list}
- {joined_tab_content}
-
-
-
-
- """
- async with aiofiles.open(output_path, "w", encoding="utf-8") as f:
- await f.write(html_template)
- def _generate_static_charts_div(self, stat_data: dict[str, Any], period_id: str) -> str:
- """生成静态图表(饼图、条形图)的HTML和JS。"""
- provider_cost_data = stat_data.get(PIE_CHART_COST_BY_PROVIDER, {})
- model_cost_data = stat_data.get(BAR_CHART_COST_BY_MODEL, {})
-
- if not provider_cost_data and not model_cost_data:
- return "数据总览
当前时段暂无足够数据生成图表。
"
-
- provider_labels = provider_cost_data.get('labels', [])
- provider_data = provider_cost_data.get('data', [])
- model_labels = model_cost_data.get('labels', [])
- model_data = model_cost_data.get('data', [])
-
- return f"""
- 数据总览
-
-
- """
diff --git a/src/chat/utils/statistic.py b/src/chat/utils/statistic.py
index 08d2ed8be..8007f8f97 100644
--- a/src/chat/utils/statistic.py
+++ b/src/chat/utils/statistic.py
@@ -16,7 +16,6 @@ logger = get_logger("maibot_statistic")
# 彻底异步化:删除原同步包装器 _sync_db_get,所有数据库访问统一使用 await db_get。
-from .report_generator import HTMLReportGenerator, format_online_time
from .statistic_keys import *
@@ -181,16 +180,6 @@ class StatisticOutputTask(AsyncTask):
logger.info("统计数据收集完成")
self._statistic_console_output(stats, now)
- # 使用新的 HTMLReportGenerator 生成报告
- chart_data = await self._collect_chart_data(stats)
- deploy_time = datetime.fromtimestamp(local_storage.get("deploy_time", now.timestamp()))
- report_generator = HTMLReportGenerator(
- name_mapping=self.name_mapping,
- stat_period=self.stat_period,
- deploy_time=deploy_time,
- )
- await report_generator.generate_report(stats, chart_data, now, self.record_file_path)
- logger.info("统计数据HTML报告输出完成")
except Exception as e:
logger.exception(f"输出统计数据过程中发生异常,错误信息:{e}")
@@ -207,18 +196,6 @@ class StatisticOutputTask(AsyncTask):
logger.info("(后台) 正在收集统计数据(异步)...")
stats = await self._collect_all_statistics(now)
self._statistic_console_output(stats, now)
-
- # 使用新的 HTMLReportGenerator 生成报告
- chart_data = await self._collect_chart_data(stats)
- deploy_time = datetime.fromtimestamp(local_storage.get("deploy_time", now.timestamp()))
- report_generator = HTMLReportGenerator(
- name_mapping=self.name_mapping,
- stat_period=self.stat_period,
- deploy_time=deploy_time,
- )
- await report_generator.generate_report(stats, chart_data, now, self.record_file_path)
-
- logger.info("统计数据后台输出完成")
except Exception as e:
logger.exception(f"后台统计数据输出过程中发生异常:{e}")