better:美化统计界面

pull/1356/head
SengokuCola 2025-11-10 22:13:25 +08:00
parent 837ecf702b
commit fd19170543
4 changed files with 135 additions and 75 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@ data/
data1/ data1/
mongodb/ mongodb/
NapCat.Framework.Windows.Once/ NapCat.Framework.Windows.Once/
NapCat.Framework.Windows.OneKey/
log/ log/
logs/ logs/
out/ out/

View File

@ -430,7 +430,7 @@ class HeartFChatting:
asyncio.create_task(extract_and_store_jargon(self.stream_id)) asyncio.create_task(extract_and_store_jargon(self.stream_id))
# 添加聊天内容概括任务 - 累积、打包和压缩聊天记录 # 添加聊天内容概括任务 - 累积、打包和压缩聊天记录
# 注意后台循环已在start()中启动,这里作为额外触发点,在有思考时立即处理 # 注意后台循环已在start()中启动,这里作为额外触发点,在有思考时立即处理
asyncio.create_task(self.chat_history_summarizer.process()) # asyncio.create_task(self.chat_history_summarizer.process())
cycle_timers, thinking_id = self.start_cycle() cycle_timers, thinking_id = self.start_cycle()

View File

@ -104,7 +104,6 @@ class ChatHistorySummarizer:
if not new_messages: if not new_messages:
# 没有新消息,检查是否需要打包 # 没有新消息,检查是否需要打包
logger.info(f"{self.log_prefix} 无新增消息,尝试对现有批次执行打包检查")
if self.current_batch and self.current_batch.messages: if self.current_batch and self.current_batch.messages:
await self._check_and_package(current_time) await self._check_and_package(current_time)
self.last_check_time = current_time self.last_check_time = current_time
@ -113,17 +112,13 @@ class ChatHistorySummarizer:
# 有新消息,更新最后检查时间 # 有新消息,更新最后检查时间
self.last_check_time = current_time self.last_check_time = current_time
logger.info(
f"{self.log_prefix} 获取到 {len(new_messages)} 条新消息,最新消息时间戳: {new_messages[-1].time if new_messages else 'N/A'}"
)
# 如果有当前批次,添加新消息 # 如果有当前批次,添加新消息
if self.current_batch: if self.current_batch:
before_count = len(self.current_batch.messages) before_count = len(self.current_batch.messages)
self.current_batch.messages.extend(new_messages) self.current_batch.messages.extend(new_messages)
self.current_batch.end_time = current_time self.current_batch.end_time = current_time
logger.info( logger.info(
f"{self.log_prefix} 扩展现有批次: {before_count} -> {len(self.current_batch.messages)} 条消息,时间范围: {self.current_batch.start_time:.2f} - {self.current_batch.end_time:.2f}" f"{self.log_prefix} 批次更新: {before_count} -> {len(self.current_batch.messages)} 条消息"
) )
else: else:
# 创建新批次 # 创建新批次
@ -133,7 +128,7 @@ class ChatHistorySummarizer:
end_time=current_time, end_time=current_time,
) )
logger.info( logger.info(
f"{self.log_prefix} 创建新批次: 消息数 {len(new_messages)},时间范围: {self.current_batch.start_time:.2f} - {self.current_batch.end_time:.2f}" f"{self.log_prefix} 批次: {len(new_messages)} 条消息"
) )
# 检查是否需要打包 # 检查是否需要打包

View File

@ -820,48 +820,70 @@ class StatisticOutputTask(AsyncTask):
<strong>统计时段: </strong> <strong>统计时段: </strong>
{start_time.strftime("%Y-%m-%d %H:%M:%S")} ~ {now.strftime("%Y-%m-%d %H:%M:%S")} {start_time.strftime("%Y-%m-%d %H:%M:%S")} ~ {now.strftime("%Y-%m-%d %H:%M:%S")}
</p> </p>
<p class=\"info-item\"><strong>总在线时间: </strong>{_format_online_time(stat_data[ONLINE_TIME])}</p> <div class=\"kpi-cards\">
<p class=\"info-item\"><strong>总消息数: </strong>{stat_data[TOTAL_MSG_CNT]}</p> <div class=\"kpi-card\">
<p class=\"info-item\"><strong>总请求数: </strong>{stat_data[TOTAL_REQ_CNT]}</p> <div class=\"kpi-title\">总在线时间</div>
<p class=\"info-item\"><strong>总花费: </strong>{stat_data[TOTAL_COST]:.2f} ¥</p> <div class=\"kpi-value\">{_format_online_time(stat_data[ONLINE_TIME])}</div>
</div>
<div class=\"kpi-card\">
<div class=\"kpi-title\">总消息数</div>
<div class=\"kpi-value\">{stat_data[TOTAL_MSG_CNT]}</div>
</div>
<div class=\"kpi-card\">
<div class=\"kpi-title\">总请求数</div>
<div class=\"kpi-value\">{stat_data[TOTAL_REQ_CNT]}</div>
</div>
<div class=\"kpi-card\">
<div class=\"kpi-title\">总花费</div>
<div class=\"kpi-value\">{stat_data[TOTAL_COST]:.2f} ¥</div>
</div>
</div>
<h2>按模型分类统计</h2> <h2>按模型分类统计</h2>
<table> <div class=\"table-wrap\">
<thead><tr><th>模型名称</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th><th>平均耗时()</th><th>标准差()</th></tr></thead> <table>
<tbody> <thead><tr><th>模型名称</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th><th>平均耗时()</th><th>标准差()</th></tr></thead>
{model_rows} <tbody>
</tbody> {model_rows}
</table> </tbody>
</table>
</div>
<h2>按模块分类统计</h2> <h2>按模块分类统计</h2>
<table> <div class=\"table-wrap\">
<thead> <table>
<tr><th>模块名称</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th><th>平均耗时()</th><th>标准差()</th></tr> <thead>
</thead> <tr><th>模块名称</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th><th>平均耗时()</th><th>标准差()</th></tr>
<tbody> </thead>
{module_rows} <tbody>
</tbody> {module_rows}
</table> </tbody>
</table>
</div>
<h2>按请求类型分类统计</h2> <h2>按请求类型分类统计</h2>
<table> <div class=\"table-wrap\">
<thead> <table>
<tr><th>请求类型</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th><th>平均耗时()</th><th>标准差()</th></tr> <thead>
</thead> <tr><th>请求类型</th><th>调用次数</th><th>输入Token</th><th>输出Token</th><th>Token总量</th><th>累计花费</th><th>平均耗时()</th><th>标准差()</th></tr>
<tbody> </thead>
{type_rows} <tbody>
</tbody> {type_rows}
</table> </tbody>
</table>
</div>
<h2>聊天消息统计</h2> <h2>聊天消息统计</h2>
<table> <div class=\"table-wrap\">
<thead> <table>
<tr><th>联系人/群组名称</th><th>消息数量</th></tr> <thead>
</thead> <tr><th>联系人/群组名称</th><th>消息数量</th></tr>
<tbody> </thead>
{chat_rows_html} <tbody>
</tbody> {chat_rows_html}
</table> </tbody>
</table>
</div>
<h2>数据分布图表</h2> <h2>数据分布图表</h2>
<div style="display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px;"> <div style="display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px;">
@ -1085,21 +1107,22 @@ class StatisticOutputTask(AsyncTask):
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;
margin: 0; margin: 0;
padding: 20px; padding: 20px;
background-color: #f4f7f6; background-color: #faf7ff;
color: #333; color: #3a2f57;
line-height: 1.6; line-height: 1.6;
} }
.container { .container {
max-width: 900px; max-width: 900px;
margin: 20px auto; margin: 20px auto;
background-color: #fff; background-color: #ffffff;
padding: 25px; padding: 25px;
border-radius: 8px; border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); box-shadow: 0 10px 28px rgba(122, 98, 182, 0.12);
border: 1px solid #e5dcff;
} }
h1, h2 { h1, h2 {
color: #2c3e50; color: #473673;
border-bottom: 2px solid #3498db; border-bottom: 2px solid #9f8efb;
padding-bottom: 10px; padding-bottom: 10px;
margin-top: 0; margin-top: 0;
} }
@ -1115,33 +1138,62 @@ class StatisticOutputTask(AsyncTask):
margin-bottom: 10px; margin-bottom: 10px;
} }
.info-item { .info-item {
background-color: #ecf0f1; background-color: #f3eeff;
padding: 8px 12px; padding: 8px 12px;
border-radius: 4px; border-radius: 6px;
margin-bottom: 8px; margin-bottom: 8px;
font-size: 0.95em; font-size: 0.95em;
} }
.info-item strong { .info-item strong {
color: #2980b9; color: #7162bf;
} }
/* 新增顶部工具条与按钮 */
.toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.toolbar .right { display: flex; gap: 8px; align-items: center; }
.btn {
border: 1px solid #e3daff;
background-color: #fbf9ff;
color: #4a3c75;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
transition: all .2s ease;
}
.btn:hover { border-color: #9f8efb; color: #7c6bcf; background-color: #f1ecff; }
/* 新增KPI 卡片 */
.kpi-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 12px 0 6px; }
.kpi-card {
background: linear-gradient(145deg, #ffffff 0%, #f6f2ff 100%);
border: 1px solid #e3dbff;
border-radius: 10px;
padding: 14px 16px;
box-shadow: 0 6px 16px rgba(113, 98, 191, 0.1);
}
.kpi-title { font-size: 12px; color: #8579a6; letter-spacing: .3px; margin-bottom: 6px; }
.kpi-value { font-size: 20px; font-weight: 700; letter-spacing: .2px; color: #8b5cf6; }
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin-top: 15px; margin-top: 15px;
font-size: 0.9em; font-size: 0.9em;
} }
/* 新增表格包裹容器支持横向滚动 */
.table-wrap { width: 100%; overflow-x: auto; border-radius: 6px; }
th, td { th, td {
border: 1px solid #ddd; border: 1px solid #e6ddff;
padding: 10px; padding: 10px;
text-align: left; text-align: left;
} }
th { th {
background-color: #3498db; background-color: #9f8efb;
color: white; color: white;
font-weight: bold; font-weight: bold;
position: sticky;
top: 0;
z-index: 1;
} }
tr:nth-child(even) { tr:nth-child(even) {
background-color: #f9f9f9; background-color: #f6f1ff;
} }
.footer { .footer {
text-align: center; text-align: center;
@ -1151,25 +1203,32 @@ class StatisticOutputTask(AsyncTask):
} }
.tabs { .tabs {
overflow: hidden; overflow: hidden;
background: #ecf0f1; background: #f9f6ff;
display: flex; display: flex;
border: 1px solid #e4dcff;
border-radius: 10px;
box-shadow: 0 8px 18px rgba(120, 101, 179, 0.08);
} }
.tabs button { .tabs button {
background: inherit; border: none; outline: none; background: inherit; border: none; outline: none;
padding: 14px 16px; cursor: pointer; padding: 12px 14px; cursor: pointer;
transition: 0.3s; font-size: 16px; transition: 0.2s; font-size: 15px;
color: #52467a;
} }
.tabs button:hover { .tabs button:hover {
background-color: #d4dbdc; background-color: #efe9ff;
} }
.tabs button.active { .tabs button.active {
background-color: #b3bbbd; background-color: rgba(159, 142, 251, 0.25);
color: #6253a9;
} }
.tab-content { .tab-content {
display: none; display: none;
padding: 20px; padding: 20px;
background-color: #fff; background-color: #fefcff;
border: 1px solid #ccc; border: 1px solid #e4dcff;
border-top: none;
border-radius: 0 0 10px 10px;
} }
.tab-content.active { .tab-content.active {
display: block; display: block;
@ -1180,14 +1239,19 @@ class StatisticOutputTask(AsyncTask):
""" """
+ f""" + f"""
<div class="container"> <div class="container">
<h1>MaiBot运行统计报告</h1> <div class="toolbar">
<p class="info-item"><strong>统计截止时间:</strong> {now.strftime("%Y-%m-%d %H:%M:%S")}</p> <h1 style="margin: 0;">MaiBot运行统计报告</h1>
<div class="right">
<span class="info-item" style="margin: 0;"><strong>统计截止时间:</strong> {now.strftime("%Y-%m-%d %H:%M:%S")}</span>
</div>
</div>
<div class="tabs"> <div class="tabs">
{joined_tab_list} {joined_tab_list}
</div> </div>
{joined_tab_content} {joined_tab_content}
<div class="footer">Made with by MaiBot 本页会定期自动覆盖生成</div>
</div> </div>
""" """
+ """ + """
@ -1321,16 +1385,16 @@ class StatisticOutputTask(AsyncTask):
# 生成不同颜色的调色板 # 生成不同颜色的调色板
colors = [ colors = [
"#3498db", "#8b5cf6",
"#e74c3c", "#9f8efb",
"#2ecc71", "#b5a6ff",
"#f39c12", "#c7bbff",
"#9b59b6", "#d9ceff",
"#1abc9c", "#a78bfa",
"#34495e", "#9073d8",
"#e67e22", "#bfaefc",
"#95a5a6", "#cabdfd",
"#f1c40f", "#e6e0ff",
] ]
# 默认使用24小时数据生成数据集 # 默认使用24小时数据生成数据集
@ -1512,7 +1576,7 @@ class StatisticOutputTask(AsyncTask):
function createChart(chartType, data, timeRange) {{ function createChart(chartType, data, timeRange) {{
const config = chartConfigs[chartType]; const config = chartConfigs[chartType];
const colors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#34495e', '#e67e22', '#95a5a6', '#f1c40f']; const colors = ['#8b5cf6', '#9f8efb', '#b5a6ff', '#c7bbff', '#d9ceff', '#a78bfa', '#9073d8', '#bfaefc', '#cabdfd', '#e6e0ff'];
let datasets = []; let datasets = [];