diff --git a/src/chat/utils/report_generator.py b/src/chat/utils/report_generator.py
new file mode 100644
index 000000000..59467efaa
--- /dev/null
+++ b/src/chat/utils/report_generator.py
@@ -0,0 +1,322 @@
+"""
+该模块用于生成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[IN_TOK_BY_MODEL].get(model_name, 0)} | "
+ f"{stat_data[OUT_TOK_BY_MODEL].get(model_name, 0)} | "
+ f"{stat_data[TOTAL_TOK_BY_MODEL].get(model_name, 0)} | "
+ 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"{stat_data[STD_TIME_COST_BY_MODEL].get(model_name, 0):.3f} 秒 | "
+ f"
"
+ for model_name, count in sorted(stat_data[REQ_CNT_BY_MODEL].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())
+ ]
+ )
+ return f"""
+
+
+ 统计时段:
+ {start_time.strftime("%Y-%m-%d %H:%M:%S")} ~ {now.strftime("%Y-%m-%d %H:%M:%S")}
+
+
总在线时间: {format_online_time(int(stat_data.get(ONLINE_TIME, 0)))}
+
总消息数: {stat_data.get(TOTAL_MSG_CNT, 0)}
+
总请求数: {stat_data.get(TOTAL_REQ_CNT, 0)}
+
总花费: {stat_data.get(TOTAL_COST, 0):.4f} ¥
+
+
按模型分类统计
+
+ | 模型名称 | 调用次数 | 输入Token | 输出Token | Token总量 | 累计花费 | 平均耗时(秒) | 标准差(秒) |
+ {model_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)
diff --git a/src/chat/utils/statistic.py b/src/chat/utils/statistic.py
index aeef1f8b7..8043450d9 100644
--- a/src/chat/utils/statistic.py
+++ b/src/chat/utils/statistic.py
@@ -16,44 +16,8 @@ logger = get_logger("maibot_statistic")
# 彻底异步化:删除原同步包装器 _sync_db_get,所有数据库访问统一使用 await db_get。
-# 统计数据的键
-TOTAL_REQ_CNT = "total_requests"
-TOTAL_COST = "total_cost"
-REQ_CNT_BY_TYPE = "requests_by_type"
-REQ_CNT_BY_USER = "requests_by_user"
-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_USER = "in_tokens_by_user"
-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_USER = "out_tokens_by_user"
-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_USER = "tokens_by_user"
-TOTAL_TOK_BY_MODEL = "tokens_by_model"
-TOTAL_TOK_BY_MODULE = "tokens_by_module"
-COST_BY_TYPE = "costs_by_type"
-COST_BY_USER = "costs_by_user"
-COST_BY_MODEL = "costs_by_model"
-COST_BY_MODULE = "costs_by_module"
-ONLINE_TIME = "online_time"
-TOTAL_MSG_CNT = "total_messages"
-MSG_CNT_BY_CHAT = "messages_by_chat"
-TIME_COST_BY_TYPE = "time_costs_by_type"
-TIME_COST_BY_USER = "time_costs_by_user"
-TIME_COST_BY_MODEL = "time_costs_by_model"
-TIME_COST_BY_MODULE = "time_costs_by_module"
-AVG_TIME_COST_BY_TYPE = "avg_time_costs_by_type"
-AVG_TIME_COST_BY_USER = "avg_time_costs_by_user"
-AVG_TIME_COST_BY_MODEL = "avg_time_costs_by_model"
-AVG_TIME_COST_BY_MODULE = "avg_time_costs_by_module"
-STD_TIME_COST_BY_TYPE = "std_time_costs_by_type"
-STD_TIME_COST_BY_USER = "std_time_costs_by_user"
-STD_TIME_COST_BY_MODEL = "std_time_costs_by_model"
-STD_TIME_COST_BY_MODULE = "std_time_costs_by_module"
+from .report_generator import HTMLReportGenerator, format_online_time
+from .statistic_keys import *
class OnlineTimeRecordTask(AsyncTask):
@@ -95,13 +59,15 @@ class OnlineTimeRecordTask(AsyncTask):
if recent_records:
# 找到近期记录,更新它
- self.record_id = recent_records["id"]
- await db_query(
- model_class=OnlineTime,
- query_type="update",
- filters={"id": self.record_id},
- data={"end_timestamp": extended_end_time},
- )
+ record_to_use = recent_records[0] if isinstance(recent_records, list) else recent_records
+ self.record_id = record_to_use.get("id")
+ if self.record_id:
+ await db_query(
+ model_class=OnlineTime,
+ query_type="update",
+ filters={"id": self.record_id},
+ data={"end_timestamp": extended_end_time},
+ )
else:
# 创建新记录
new_record = await db_query(
@@ -115,8 +81,8 @@ class OnlineTimeRecordTask(AsyncTask):
},
)
if new_record:
- self.record_id = new_record["id"]
-
+ record_to_use = new_record[0] if isinstance(new_record, list) else new_record
+ self.record_id = record_to_use.get("id")
except Exception as e:
logger.error(f"在线时间记录失败,错误信息:{e}")
@@ -165,14 +131,14 @@ class StatisticOutputTask(AsyncTask):
"""
now = datetime.now()
- if "deploy_time" in local_storage:
+ deploy_time_ts = local_storage.get("deploy_time")
+ if deploy_time_ts:
# 如果存在部署时间,则使用该时间作为全量统计的起始时间
- deploy_time = datetime.fromtimestamp(local_storage["deploy_time"]) # type: ignore
+ deploy_time = datetime.fromtimestamp(deploy_time_ts) # type: ignore
else:
# 否则,使用最大时间范围,并记录部署时间为当前时间
deploy_time = datetime(2000, 1, 1)
local_storage["deploy_time"] = now.timestamp()
-
self.stat_period: list[tuple[str, timedelta, str]] = [
("all_time", now - deploy_time, "自部署以来"), # 必须保留"all_time"
("last_7_days", timedelta(days=7), "最近7天"),
@@ -211,11 +177,21 @@ class StatisticOutputTask(AsyncTask):
try:
now = datetime.now()
logger.info("正在收集统计数据(异步)...")
- stats = await asyncio.create_task(self._collect_all_statistics(now))
+ stats = await self._collect_all_statistics(now)
logger.info("统计数据收集完成")
+
self._statistic_console_output(stats, now)
- await asyncio.create_task(self._generate_html_report(stats, now))
- logger.info("统计数据输出完成")
+ # 使用新的 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}")
@@ -231,14 +207,23 @@ class StatisticOutputTask(AsyncTask):
logger.info("(后台) 正在收集统计数据(异步)...")
stats = await self._collect_all_statistics(now)
self._statistic_console_output(stats, now)
- await self._generate_html_report(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}")
# 创建后台任务,立即返回
asyncio.create_task(_async_collect_and_output()) # noqa: RUF006
-
# -- 以下为统计数据收集方法 --
@staticmethod
@@ -512,12 +497,12 @@ class StatisticOutputTask(AsyncTask):
continue
# Update name_mapping
- if chat_id in self.name_mapping:
- if chat_name != self.name_mapping[chat_id][0] and message_time_ts > self.name_mapping[chat_id][1]:
+ if chat_name:
+ if chat_id in self.name_mapping:
+ if chat_name != self.name_mapping[chat_id][0] and message_time_ts > self.name_mapping[chat_id][1]:
+ self.name_mapping[chat_id] = (chat_name, message_time_ts)
+ else:
self.name_mapping[chat_id] = (chat_name, message_time_ts)
- else:
- self.name_mapping[chat_id] = (chat_name, message_time_ts)
-
for idx, (_, period_start_dt) in enumerate(collect_period):
if message_time_ts >= period_start_dt.timestamp():
for period_key, _ in collect_period[idx:]:
@@ -673,341 +658,13 @@ class StatisticOutputTask(AsyncTask):
return ""
output = ["聊天消息统计:", " 联系人/群组名称 消息数量"]
output.extend(
- f"{self.name_mapping[chat_id][0][:32]:<32} {count:>10}"
+ f"{self.name_mapping.get(chat_id, (chat_id, 0))[0][:32]:<32} {count:>10}"
for chat_id, count in sorted(stats[MSG_CNT_BY_CHAT].items())
)
output.append("")
return "\n".join(output)
- @staticmethod
- def _get_chat_display_name_from_id(chat_id: str) -> str:
- """从chat_id获取显示名称"""
- try:
- # 首先尝试从chat_stream获取真实群组名称
- from src.chat.message_receive.chat_stream import get_chat_manager
-
- chat_manager = get_chat_manager()
-
- if chat_id in chat_manager.streams:
- stream = chat_manager.streams[chat_id]
- if stream.group_info and hasattr(stream.group_info, "group_name"):
- group_name = stream.group_info.group_name
- if group_name and group_name.strip():
- return group_name.strip()
- elif stream.user_info and hasattr(stream.user_info, "user_nickname"):
- user_name = stream.user_info.user_nickname
- if user_name and user_name.strip():
- return user_name.strip()
-
- # 如果从chat_stream获取失败,尝试解析chat_id格式
- if chat_id.startswith("g"):
- return f"群聊{chat_id[1:]}"
- elif chat_id.startswith("u"):
- return f"用户{chat_id[1:]}"
- else:
- return chat_id
- except Exception as e:
- logger.warning(f"获取聊天显示名称失败: {e}")
- return chat_id
-
- # 移除_generate_versions_tab方法
-
- async def _generate_html_report(self, stat: dict[str, Any], now: datetime):
- """
- 生成HTML格式的统计报告
- :param stat: 统计数据
- :param now: 基准当前时间
- :return: HTML格式的统计报告
- """
-
- # 移除版本对比内容相关tab和内容
- tab_list = [
- f''
- for period in self.stat_period
- ]
- tab_list.append('')
-
- def _format_stat_data(stat_data: dict[str, Any], div_id: str, start_time: datetime) -> str:
- """
- 格式化一个时间段的统计数据到html div块
- :param stat_data: 统计数据
- :param div_id: div的ID
- :param start_time: 统计时间段开始时间
- """
- # format总在线时间
-
- # 按模型分类统计
- model_rows = "\n".join(
- [
- f""
- f"| {model_name} | "
- f"{count} | "
- f"{stat_data[IN_TOK_BY_MODEL][model_name]} | "
- f"{stat_data[OUT_TOK_BY_MODEL][model_name]} | "
- f"{stat_data[TOTAL_TOK_BY_MODEL][model_name]} | "
- f"{stat_data[COST_BY_MODEL][model_name]:.4f} ¥ | "
- f"{stat_data[AVG_TIME_COST_BY_MODEL][model_name]:.3f} 秒 | "
- f"{stat_data[STD_TIME_COST_BY_MODEL][model_name]:.3f} 秒 | "
- f"
"
- for model_name, count in sorted(stat_data[REQ_CNT_BY_MODEL].items())
- ]
- )
- # 按请求类型分类统计
- type_rows = "\n".join(
- [
- f""
- f"| {req_type} | "
- f"{count} | "
- f"{stat_data[IN_TOK_BY_TYPE][req_type]} | "
- f"{stat_data[OUT_TOK_BY_TYPE][req_type]} | "
- f"{stat_data[TOTAL_TOK_BY_TYPE][req_type]} | "
- f"{stat_data[COST_BY_TYPE][req_type]:.4f} ¥ | "
- f"{stat_data[AVG_TIME_COST_BY_TYPE][req_type]:.3f} 秒 | "
- f"{stat_data[STD_TIME_COST_BY_TYPE][req_type]:.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][module_name]} | "
- f"{stat_data[OUT_TOK_BY_MODULE][module_name]} | "
- f"{stat_data[TOTAL_TOK_BY_MODULE][module_name]} | "
- f"{stat_data[COST_BY_MODULE][module_name]:.4f} ¥ | "
- f"{stat_data[AVG_TIME_COST_BY_MODULE][module_name]:.3f} 秒 | "
- f"{stat_data[STD_TIME_COST_BY_MODULE][module_name]:.3f} 秒 | "
- f"
"
- for module_name, count in sorted(stat_data[REQ_CNT_BY_MODULE].items())
- ]
- )
-
- # 聊天消息统计
- chat_rows = "\n".join(
- [
- f"| {self.name_mapping[chat_id][0]} | {count} |
"
- for chat_id, count in sorted(stat_data[MSG_CNT_BY_CHAT].items())
- ]
- )
- # 生成HTML
- return f"""
-
-
- 统计时段:
- {start_time.strftime("%Y-%m-%d %H:%M:%S")} ~ {now.strftime("%Y-%m-%d %H:%M:%S")}
-
-
总在线时间: {_format_online_time(stat_data[ONLINE_TIME])}
-
总消息数: {stat_data[TOTAL_MSG_CNT]}
-
总请求数: {stat_data[TOTAL_REQ_CNT]}
-
总花费: {stat_data[TOTAL_COST]:.4f} ¥
-
-
按模型分类统计
-
- | 模块名称 | 调用次数 | 输入Token | 输出Token | Token总量 | 累计花费 | 平均耗时(秒) | 标准差(秒) |
-
- {model_rows}
-
-
-
-
按模块分类统计
-
-
- | 模块名称 | 调用次数 | 输入Token | 输出Token | Token总量 | 累计花费 | 平均耗时(秒) | 标准差(秒) |
-
-
- {module_rows}
-
-
-
-
按请求类型分类统计
-
-
- | 请求类型 | 调用次数 | 输入Token | 输出Token | Token总量 | 累计花费 | 平均耗时(秒) | 标准差(秒) |
-
-
- {type_rows}
-
-
-
-
聊天消息统计
-
-
- | 联系人/群组名称 | 消息数量 |
-
-
- {chat_rows}
-
-
-
-
-
- """
-
- tab_content_list = [
- _format_stat_data(stat[period[0]], period[0], now - period[1])
- for period in self.stat_period
- if period[0] != "all_time"
- ]
-
- tab_content_list.append(
- _format_stat_data(stat["all_time"], "all_time", datetime.fromtimestamp(local_storage["deploy_time"])) # type: ignore
- )
-
- # 不再添加版本对比内容
- # 添加图表内容 (修正缩进)
- chart_data = await self._generate_chart_data(stat)
- 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 = (
- """
-
-
-
-
-
- MoFox-Bot运行统计报告
-
-
-
-
-"""
- + f"""
-
-
MoFox-Bot运行统计报告
-
统计截止时间: {now.strftime("%Y-%m-%d %H:%M:%S")}
-
-
- {joined_tab_list}
-
-
- {joined_tab_content}
-
-"""
- + """
-
-
-
- """
- )
-
- async with aiofiles.open(self.record_file_path, "w", encoding="utf-8") as f:
- await f.write(html_template)
-
- async def _generate_chart_data(self, stat: dict[str, Any]) -> dict:
+ async def _collect_chart_data(self, stat: dict[str, Any]) -> dict:
"""生成图表数据 (异步)"""
now = datetime.now()
chart_data: dict[str, Any] = {}
@@ -1088,15 +745,17 @@ class StatisticOutputTask(AsyncTask):
time_diff = msg_ts - start_time.timestamp()
idx = int(time_diff // interval_seconds)
if 0 <= idx < len(time_points):
+ chat_id = None
if msg.get("chat_info_group_id"):
- chat_name = msg.get("chat_info_group_name") or f"群{msg['chat_info_group_id']}"
+ chat_id = f"g{msg['chat_info_group_id']}"
elif msg.get("user_id"):
- chat_name = msg.get("user_nickname") or f"用户{msg['user_id']}"
- else:
- continue
- if chat_name not in message_by_chat:
- message_by_chat[chat_name] = [0] * len(time_points)
- message_by_chat[chat_name][idx] += 1
+ chat_id = f"u{msg['user_id']}"
+
+ if chat_id:
+ chat_name = self.name_mapping.get(chat_id, (chat_id, 0))[0]
+ if chat_name not in message_by_chat:
+ message_by_chat[chat_name] = [0] * len(time_points)
+ message_by_chat[chat_name][idx] += 1
return {
"time_labels": time_labels,
@@ -1105,282 +764,3 @@ class StatisticOutputTask(AsyncTask):
"cost_by_module": cost_by_module,
"message_by_chat": message_by_chat,
}
-
- @staticmethod
- def _generate_chart_tab(chart_data: dict) -> str:
- # sourcery skip: extract-duplicate-method, move-assign-in-block
- """生成图表选项卡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
- }}""")
-
- ",\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
- }}""")
-
- ",\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
- }}""")
-
- ",\n ".join(message_datasets)
-
- return f"""
-
-
数据图表
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- """
diff --git a/src/chat/utils/statistic_keys.py b/src/chat/utils/statistic_keys.py
new file mode 100644
index 000000000..41e96b1be
--- /dev/null
+++ b/src/chat/utils/statistic_keys.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+"""
+该模块用于存放统计数据相关的常量键名。
+"""
+
+# 统计数据的键
+TOTAL_REQ_CNT = "total_requests"
+TOTAL_COST = "total_cost"
+REQ_CNT_BY_TYPE = "requests_by_type"
+REQ_CNT_BY_USER = "requests_by_user"
+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_USER = "in_tokens_by_user"
+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_USER = "out_tokens_by_user"
+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_USER = "tokens_by_user"
+TOTAL_TOK_BY_MODEL = "tokens_by_model"
+TOTAL_TOK_BY_MODULE = "tokens_by_module"
+COST_BY_TYPE = "costs_by_type"
+COST_BY_USER = "costs_by_user"
+COST_BY_MODEL = "costs_by_model"
+COST_BY_MODULE = "costs_by_module"
+ONLINE_TIME = "online_time"
+TOTAL_MSG_CNT = "total_messages"
+MSG_CNT_BY_CHAT = "messages_by_chat"
+TIME_COST_BY_TYPE = "time_costs_by_type"
+TIME_COST_BY_USER = "time_costs_by_user"
+TIME_COST_BY_MODEL = "time_costs_by_model"
+TIME_COST_BY_MODULE = "time_costs_by_module"
+AVG_TIME_COST_BY_TYPE = "avg_time_costs_by_type"
+AVG_TIME_COST_BY_USER = "avg_time_costs_by_user"
+AVG_TIME_COST_BY_MODEL = "avg_time_costs_by_model"
+AVG_TIME_COST_BY_MODULE = "avg_time_costs_by_module"
+STD_TIME_COST_BY_TYPE = "std_time_costs_by_type"
+STD_TIME_COST_BY_USER = "std_time_costs_by_user"
+STD_TIME_COST_BY_MODEL = "std_time_costs_by_model"
+STD_TIME_COST_BY_MODULE = "std_time_costs_by_module"
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index a0e8572f6..f39d3f956 100644
--- a/src/main.py
+++ b/src/main.py
@@ -400,7 +400,7 @@ MoFox_Bot(第三方修改版)
base_init_tasks = [
async_task_manager.add_task(OnlineTimeRecordTask()),
async_task_manager.add_task(StatisticOutputTask()),
- async_task_manager.add_task(TelemetryHeartBeatTask()),
+ #async_task_manager.add_task(TelemetryHeartBeatTask()),
]
await asyncio.gather(*base_init_tasks, return_exceptions=True)
diff --git a/src/manager/local_store_manager.py b/src/manager/local_store_manager.py
index f5b5a28ca..bd94e7b56 100644
--- a/src/manager/local_store_manager.py
+++ b/src/manager/local_store_manager.py
@@ -21,6 +21,10 @@ class LocalStoreManager:
self.store = {}
self.load_local_store()
+ def __contains__(self, key: str) -> bool:
+ """检查键是否存在"""
+ return key in self.store
+
def __getitem__(self, item: str) -> str | list | dict | int | float | bool | None:
"""获取本地存储数据"""
return self.store.get(item)
@@ -38,9 +42,9 @@ class LocalStoreManager:
else:
logger.warning(f"尝试删除不存在的键: {key}")
- def __contains__(self, item: str) -> bool:
- """检查本地存储数据是否存在"""
- return item in self.store
+ def get(self, key: str, default=None):
+ """获取本地存储数据,支持默认值"""
+ return self.store.get(key, default)
def load_local_store(self):
"""加载本地存储数据"""