mirror of https://github.com/Mai-with-u/MaiBot.git
🤖 自动格式化代码 [skip ci]
parent
d6fbef7fc6
commit
a8f45e7c06
|
|
@ -116,12 +116,14 @@ class DefaultExpressor:
|
||||||
|
|
||||||
# 为 trigger_nickname_analysis 准备 bot_reply 参数
|
# 为 trigger_nickname_analysis 准备 bot_reply 参数
|
||||||
bot_reply_for_analysis = []
|
bot_reply_for_analysis = []
|
||||||
if reply: # reply 是 List[Tuple[str, str]]
|
if reply: # reply 是 List[Tuple[str, str]]
|
||||||
for seg_type, seg_data in reply:
|
for seg_type, seg_data in reply:
|
||||||
if seg_type == "text": # 只取文本类型的数据
|
if seg_type == "text": # 只取文本类型的数据
|
||||||
bot_reply_for_analysis.append(seg_data)
|
bot_reply_for_analysis.append(seg_data)
|
||||||
|
|
||||||
await nickname_manager.trigger_nickname_analysis(anchor_message, bot_reply_for_analysis, self.chat_stream)
|
await nickname_manager.trigger_nickname_analysis(
|
||||||
|
anchor_message, bot_reply_for_analysis, self.chat_stream
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(f"{self.log_prefix} 文本回复生成失败")
|
logger.warning(f"{self.log_prefix} 文本回复生成失败")
|
||||||
|
|
||||||
|
|
@ -260,7 +262,7 @@ class DefaultExpressor:
|
||||||
mark_head = False
|
mark_head = False
|
||||||
_first_bot_msg: Optional[MessageSending] = None
|
_first_bot_msg: Optional[MessageSending] = None
|
||||||
reply_message_ids = [] # 记录实际发送的消息ID
|
reply_message_ids = [] # 记录实际发送的消息ID
|
||||||
|
|
||||||
sent_msg_list = []
|
sent_msg_list = []
|
||||||
|
|
||||||
for i, msg_text in enumerate(response_set):
|
for i, msg_text in enumerate(response_set):
|
||||||
|
|
@ -300,7 +302,7 @@ class DefaultExpressor:
|
||||||
sent_msg = await self.heart_fc_sender.send_message(bot_message, has_thinking=True, typing=typing)
|
sent_msg = await self.heart_fc_sender.send_message(bot_message, has_thinking=True, typing=typing)
|
||||||
|
|
||||||
reply_message_ids.append(part_message_id) # 记录我们生成的ID
|
reply_message_ids.append(part_message_id) # 记录我们生成的ID
|
||||||
|
|
||||||
sent_msg_list.append((type, sent_msg))
|
sent_msg_list.append((type, sent_msg))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -310,7 +312,7 @@ class DefaultExpressor:
|
||||||
# 在尝试发送完所有片段后,完成原始的 thinking_id 状态
|
# 在尝试发送完所有片段后,完成原始的 thinking_id 状态
|
||||||
try:
|
try:
|
||||||
await self.heart_fc_sender.complete_thinking(chat_id, thinking_id)
|
await self.heart_fc_sender.complete_thinking(chat_id, thinking_id)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self.log_prefix}完成思考状态 {thinking_id} 时出错: {e}")
|
logger.error(f"{self.log_prefix}完成思考状态 {thinking_id} 时出错: {e}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -925,7 +925,9 @@ class HeartFChatting:
|
||||||
if action == "reply" and emoji:
|
if action == "reply" and emoji:
|
||||||
logger.debug(f"{self.log_prefix}[Planner] 大模型建议文字回复带表情: '{emoji}'")
|
logger.debug(f"{self.log_prefix}[Planner] 大模型建议文字回复带表情: '{emoji}'")
|
||||||
if random.random() > EMOJI_SEND_PRO:
|
if random.random() > EMOJI_SEND_PRO:
|
||||||
logger.info(f"{self.log_prefix}但是麦麦这次不想加表情 ({1 - EMOJI_SEND_PRO:.0%}),忽略表情 '{emoji}'")
|
logger.info(
|
||||||
|
f"{self.log_prefix}但是麦麦这次不想加表情 ({1 - EMOJI_SEND_PRO:.0%}),忽略表情 '{emoji}'"
|
||||||
|
)
|
||||||
action_data["emojis"] = "" # 清空表情请求
|
action_data["emojis"] = "" # 清空表情请求
|
||||||
else:
|
else:
|
||||||
logger.info(f"{self.log_prefix}好吧,加上表情 '{emoji}'")
|
logger.info(f"{self.log_prefix}好吧,加上表情 '{emoji}'")
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ class HeartFCSender:
|
||||||
|
|
||||||
sent_msg = await send_message(message)
|
sent_msg = await send_message(message)
|
||||||
await self.storage.store_message(message, message.chat_stream)
|
await self.storage.store_message(message, message.chat_stream)
|
||||||
|
|
||||||
if sent_msg:
|
if sent_msg:
|
||||||
return sent_msg
|
return sent_msg
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -876,4 +876,4 @@ def weighted_sample_no_replacement(items, weights, k) -> list:
|
||||||
|
|
||||||
|
|
||||||
init_prompt()
|
init_prompt()
|
||||||
prompt_builder = PromptBuilder()
|
prompt_builder = PromptBuilder()
|
||||||
|
|
|
||||||
|
|
@ -524,4 +524,4 @@ class NormalChat:
|
||||||
logger.info(f"[{self.stream_name}] 清理了 {len(thinking_messages)} 条未处理的思考消息。")
|
logger.info(f"[{self.stream_name}] 清理了 {len(thinking_messages)} 条未处理的思考消息。")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[{self.stream_name}] 清理思考消息时出错: {e}")
|
logger.error(f"[{self.stream_name}] 清理思考消息时出错: {e}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
|
||||||
|
|
@ -424,7 +424,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str:
|
||||||
|
|
||||||
# 分配占位符
|
# 分配占位符
|
||||||
person_map = {}
|
person_map = {}
|
||||||
current_char = ord('A')
|
current_char = ord("A")
|
||||||
output_lines = []
|
output_lines = []
|
||||||
|
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ def _format_online_time(online_seconds: int) -> str:
|
||||||
:param online_seconds: 在线时间(秒)
|
:param online_seconds: 在线时间(秒)
|
||||||
:return: 格式化后的在线时间字符串
|
:return: 格式化后的在线时间字符串
|
||||||
"""
|
"""
|
||||||
total_oneline_time = timedelta(seconds=int(online_seconds)) #确保是整数
|
total_oneline_time = timedelta(seconds=int(online_seconds)) # 确保是整数
|
||||||
|
|
||||||
days = total_oneline_time.days
|
days = total_oneline_time.days
|
||||||
hours = total_oneline_time.seconds // 3600
|
hours = total_oneline_time.seconds // 3600
|
||||||
|
|
@ -141,7 +141,7 @@ class StatisticOutputTask(AsyncTask):
|
||||||
记录文件路径
|
记录文件路径
|
||||||
"""
|
"""
|
||||||
|
|
||||||
now = datetime.now() # Renamed to avoid conflict with 'now' in methods
|
now = datetime.now() # Renamed to avoid conflict with 'now' in methods
|
||||||
if "deploy_time" in local_storage:
|
if "deploy_time" in local_storage:
|
||||||
# 如果存在部署时间,则使用该时间作为全量统计的起始时间
|
# 如果存在部署时间,则使用该时间作为全量统计的起始时间
|
||||||
deploy_time = datetime.fromtimestamp(local_storage["deploy_time"])
|
deploy_time = datetime.fromtimestamp(local_storage["deploy_time"])
|
||||||
|
|
@ -167,7 +167,7 @@ class StatisticOutputTask(AsyncTask):
|
||||||
:param now: 基准当前时间
|
:param now: 基准当前时间
|
||||||
"""
|
"""
|
||||||
# 输出最近一小时的统计数据
|
# 输出最近一小时的统计数据
|
||||||
last_hour_stats = stats.get("last_hour", {}) # Ensure 'last_hour' key exists
|
last_hour_stats = stats.get("last_hour", {}) # Ensure 'last_hour' key exists
|
||||||
|
|
||||||
output = [
|
output = [
|
||||||
self.SEP_LINE,
|
self.SEP_LINE,
|
||||||
|
|
@ -191,7 +191,7 @@ class StatisticOutputTask(AsyncTask):
|
||||||
stats = self._collect_all_statistics(now)
|
stats = self._collect_all_statistics(now)
|
||||||
|
|
||||||
# 输出统计数据到控制台
|
# 输出统计数据到控制台
|
||||||
if "last_hour" in stats: # Check if stats for last_hour were successfully collected
|
if "last_hour" in stats: # Check if stats for last_hour were successfully collected
|
||||||
self._statistic_console_output(stats, now)
|
self._statistic_console_output(stats, now)
|
||||||
else:
|
else:
|
||||||
logger.warning("无法输出最近一小时统计数据到控制台,因为数据缺失。")
|
logger.warning("无法输出最近一小时统计数据到控制台,因为数据缺失。")
|
||||||
|
|
@ -209,7 +209,7 @@ class StatisticOutputTask(AsyncTask):
|
||||||
|
|
||||||
:param collect_period: 统计时间段 [(period_key, start_datetime), ...]
|
:param collect_period: 统计时间段 [(period_key, start_datetime), ...]
|
||||||
"""
|
"""
|
||||||
if not collect_period:
|
if not collect_period:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
collect_period.sort(key=lambda x: x[1], reverse=True)
|
collect_period.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
|
@ -236,21 +236,20 @@ class StatisticOutputTask(AsyncTask):
|
||||||
}
|
}
|
||||||
for period_key, _ in collect_period
|
for period_key, _ in collect_period
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine the overall earliest start time for the database query
|
# Determine the overall earliest start time for the database query
|
||||||
# This assumes collect_period is not empty, which is checked at the beginning.
|
# This assumes collect_period is not empty, which is checked at the beginning.
|
||||||
overall_earliest_start_time = min(p[1] for p in collect_period)
|
overall_earliest_start_time = min(p[1] for p in collect_period)
|
||||||
|
|
||||||
for record in db.llm_usage.find({"timestamp": {"$gte": overall_earliest_start_time}}):
|
for record in db.llm_usage.find({"timestamp": {"$gte": overall_earliest_start_time}}):
|
||||||
record_timestamp = record.get("timestamp")
|
record_timestamp = record.get("timestamp")
|
||||||
if not isinstance(record_timestamp, datetime): # Ensure timestamp is a datetime object
|
if not isinstance(record_timestamp, datetime): # Ensure timestamp is a datetime object
|
||||||
try: # Attempt conversion if it's a number (e.g. Unix timestamp)
|
try: # Attempt conversion if it's a number (e.g. Unix timestamp)
|
||||||
record_timestamp = datetime.fromtimestamp(float(record_timestamp))
|
record_timestamp = datetime.fromtimestamp(float(record_timestamp))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
logger.warning(f"Skipping LLM usage record with invalid timestamp: {record.get('_id')}")
|
logger.warning(f"Skipping LLM usage record with invalid timestamp: {record.get('_id')}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
for idx, (_current_period_key, period_start_time) in enumerate(collect_period):
|
for idx, (_current_period_key, period_start_time) in enumerate(collect_period):
|
||||||
if record_timestamp >= period_start_time:
|
if record_timestamp >= period_start_time:
|
||||||
for period_key_to_update, _ in collect_period[idx:]:
|
for period_key_to_update, _ in collect_period[idx:]:
|
||||||
|
|
@ -285,7 +284,7 @@ class StatisticOutputTask(AsyncTask):
|
||||||
stats[period_key_to_update][COST_BY_TYPE][request_type] += cost
|
stats[period_key_to_update][COST_BY_TYPE][request_type] += cost
|
||||||
stats[period_key_to_update][COST_BY_USER][user_id] += cost
|
stats[period_key_to_update][COST_BY_USER][user_id] += cost
|
||||||
stats[period_key_to_update][COST_BY_MODEL][model_name] += cost
|
stats[period_key_to_update][COST_BY_MODEL][model_name] += cost
|
||||||
break
|
break
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
|
@ -308,7 +307,7 @@ class StatisticOutputTask(AsyncTask):
|
||||||
}
|
}
|
||||||
for period_key, _ in collect_period
|
for period_key, _ in collect_period
|
||||||
}
|
}
|
||||||
|
|
||||||
overall_earliest_start_time = min(p[1] for p in collect_period)
|
overall_earliest_start_time = min(p[1] for p in collect_period)
|
||||||
|
|
||||||
for record in db.online_time.find({"end_timestamp": {"$gte": overall_earliest_start_time}}):
|
for record in db.online_time.find({"end_timestamp": {"$gte": overall_earliest_start_time}}):
|
||||||
|
|
@ -324,13 +323,13 @@ class StatisticOutputTask(AsyncTask):
|
||||||
for idx, (_current_period_key, period_start_time) in enumerate(collect_period):
|
for idx, (_current_period_key, period_start_time) in enumerate(collect_period):
|
||||||
if record_start_timestamp < now and actual_end_timestamp > period_start_time:
|
if record_start_timestamp < now and actual_end_timestamp > period_start_time:
|
||||||
overlap_start = max(record_start_timestamp, period_start_time)
|
overlap_start = max(record_start_timestamp, period_start_time)
|
||||||
overlap_end = min(actual_end_timestamp, now)
|
overlap_end = min(actual_end_timestamp, now)
|
||||||
|
|
||||||
if overlap_end > overlap_start:
|
if overlap_end > overlap_start:
|
||||||
duration_seconds = (overlap_end - overlap_start).total_seconds()
|
duration_seconds = (overlap_end - overlap_start).total_seconds()
|
||||||
for period_key_to_update, _ in collect_period[idx:]:
|
for period_key_to_update, _ in collect_period[idx:]:
|
||||||
stats[period_key_to_update][ONLINE_TIME] += duration_seconds
|
stats[period_key_to_update][ONLINE_TIME] += duration_seconds
|
||||||
break
|
break
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
|
@ -354,37 +353,36 @@ class StatisticOutputTask(AsyncTask):
|
||||||
}
|
}
|
||||||
|
|
||||||
overall_earliest_start_timestamp_float = min(p[1].timestamp() for p in collect_period)
|
overall_earliest_start_timestamp_float = min(p[1].timestamp() for p in collect_period)
|
||||||
|
|
||||||
for message in db.messages.find({"time": {"$gte": overall_earliest_start_timestamp_float}}):
|
|
||||||
chat_info = message.get("chat_info", {})
|
|
||||||
user_info = message.get("user_info", {})
|
|
||||||
message_time_ts = message.get("time")
|
|
||||||
|
|
||||||
if message_time_ts is None:
|
for message in db.messages.find({"time": {"$gte": overall_earliest_start_timestamp_float}}):
|
||||||
|
chat_info = message.get("chat_info", {})
|
||||||
|
user_info = message.get("user_info", {})
|
||||||
|
message_time_ts = message.get("time")
|
||||||
|
|
||||||
|
if message_time_ts is None:
|
||||||
logger.warning(f"Skipping message record with no timestamp: {message.get('_id')}")
|
logger.warning(f"Skipping message record with no timestamp: {message.get('_id')}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message_datetime = datetime.fromtimestamp(float(message_time_ts))
|
message_datetime = datetime.fromtimestamp(float(message_time_ts))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
logger.warning(f"Skipping message record with invalid time format: {message.get('_id')}")
|
logger.warning(f"Skipping message record with invalid time format: {message.get('_id')}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
group_info = chat_info.get("group_info")
|
group_info = chat_info.get("group_info")
|
||||||
chat_id = None
|
chat_id = None
|
||||||
chat_name = None
|
chat_name = None
|
||||||
|
|
||||||
if group_info and group_info.get("group_id"):
|
if group_info and group_info.get("group_id"):
|
||||||
gid = group_info.get('group_id')
|
gid = group_info.get("group_id")
|
||||||
chat_id = f"g{gid}"
|
chat_id = f"g{gid}"
|
||||||
chat_name = group_info.get("group_name", f"群聊 {gid}")
|
chat_name = group_info.get("group_name", f"群聊 {gid}")
|
||||||
elif user_info and user_info.get("user_id"):
|
elif user_info and user_info.get("user_id"):
|
||||||
uid = user_info['user_id']
|
uid = user_info["user_id"]
|
||||||
chat_id = f"u{uid}"
|
chat_id = f"u{uid}"
|
||||||
chat_name = user_info.get("user_nickname", f"用户 {uid}")
|
chat_name = user_info.get("user_nickname", f"用户 {uid}")
|
||||||
|
|
||||||
if not chat_id:
|
if not chat_id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
current_mapping = self.name_mapping.get(chat_id)
|
current_mapping = self.name_mapping.get(chat_id)
|
||||||
|
|
@ -394,13 +392,12 @@ class StatisticOutputTask(AsyncTask):
|
||||||
else:
|
else:
|
||||||
self.name_mapping[chat_id] = (chat_name, message_time_ts)
|
self.name_mapping[chat_id] = (chat_name, message_time_ts)
|
||||||
|
|
||||||
|
|
||||||
for idx, (_current_period_key, period_start_time) in enumerate(collect_period):
|
for idx, (_current_period_key, period_start_time) in enumerate(collect_period):
|
||||||
if message_datetime >= period_start_time:
|
if message_datetime >= period_start_time:
|
||||||
for period_key_to_update, _ in collect_period[idx:]:
|
for period_key_to_update, _ in collect_period[idx:]:
|
||||||
stats[period_key_to_update][TOTAL_MSG_CNT] += 1
|
stats[period_key_to_update][TOTAL_MSG_CNT] += 1
|
||||||
stats[period_key_to_update][MSG_CNT_BY_CHAT][chat_id] += 1
|
stats[period_key_to_update][MSG_CNT_BY_CHAT][chat_id] += 1
|
||||||
break
|
break
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
|
@ -428,7 +425,7 @@ class StatisticOutputTask(AsyncTask):
|
||||||
("last_24_hours", timedelta(days=1), "最近24小时"),
|
("last_24_hours", timedelta(days=1), "最近24小时"),
|
||||||
("last_hour", timedelta(hours=1), "最近1小时"),
|
("last_hour", timedelta(hours=1), "最近1小时"),
|
||||||
]
|
]
|
||||||
self.stat_period = current_stat_periods_config # Update instance's stat_period if needed elsewhere
|
self.stat_period = current_stat_periods_config # Update instance's stat_period if needed elsewhere
|
||||||
|
|
||||||
stat_start_timestamp_config = []
|
stat_start_timestamp_config = []
|
||||||
for period_name, delta, _ in current_stat_periods_config:
|
for period_name, delta, _ in current_stat_periods_config:
|
||||||
|
|
@ -446,23 +443,39 @@ class StatisticOutputTask(AsyncTask):
|
||||||
final_stats[period_key].update(model_req_stat.get(period_key, {}))
|
final_stats[period_key].update(model_req_stat.get(period_key, {}))
|
||||||
final_stats[period_key].update(online_time_stat.get(period_key, {}))
|
final_stats[period_key].update(online_time_stat.get(period_key, {}))
|
||||||
final_stats[period_key].update(message_count_stat.get(period_key, {}))
|
final_stats[period_key].update(message_count_stat.get(period_key, {}))
|
||||||
|
|
||||||
for stat_field_key in [
|
for stat_field_key in [
|
||||||
TOTAL_REQ_CNT, REQ_CNT_BY_TYPE, REQ_CNT_BY_USER, REQ_CNT_BY_MODEL,
|
TOTAL_REQ_CNT,
|
||||||
IN_TOK_BY_TYPE, IN_TOK_BY_USER, IN_TOK_BY_MODEL,
|
REQ_CNT_BY_TYPE,
|
||||||
OUT_TOK_BY_TYPE, OUT_TOK_BY_USER, OUT_TOK_BY_MODEL,
|
REQ_CNT_BY_USER,
|
||||||
TOTAL_TOK_BY_TYPE, TOTAL_TOK_BY_USER, TOTAL_TOK_BY_MODEL,
|
REQ_CNT_BY_MODEL,
|
||||||
TOTAL_COST, COST_BY_TYPE, COST_BY_USER, COST_BY_MODEL,
|
IN_TOK_BY_TYPE,
|
||||||
ONLINE_TIME, TOTAL_MSG_CNT, MSG_CNT_BY_CHAT
|
IN_TOK_BY_USER,
|
||||||
|
IN_TOK_BY_MODEL,
|
||||||
|
OUT_TOK_BY_TYPE,
|
||||||
|
OUT_TOK_BY_USER,
|
||||||
|
OUT_TOK_BY_MODEL,
|
||||||
|
TOTAL_TOK_BY_TYPE,
|
||||||
|
TOTAL_TOK_BY_USER,
|
||||||
|
TOTAL_TOK_BY_MODEL,
|
||||||
|
TOTAL_COST,
|
||||||
|
COST_BY_TYPE,
|
||||||
|
COST_BY_USER,
|
||||||
|
COST_BY_MODEL,
|
||||||
|
ONLINE_TIME,
|
||||||
|
TOTAL_MSG_CNT,
|
||||||
|
MSG_CNT_BY_CHAT,
|
||||||
]:
|
]:
|
||||||
if stat_field_key not in final_stats[period_key]:
|
if stat_field_key not in final_stats[period_key]:
|
||||||
# Initialize with appropriate default type if key is missing
|
# Initialize with appropriate default type if key is missing
|
||||||
if "BY_" in stat_field_key: # These are usually defaultdicts
|
if "BY_" in stat_field_key: # These are usually defaultdicts
|
||||||
final_stats[period_key][stat_field_key] = defaultdict(int if "CNT" in stat_field_key or "TOK" in stat_field_key else float)
|
final_stats[period_key][stat_field_key] = defaultdict(
|
||||||
elif "CNT" in stat_field_key or "TOK" in stat_field_key :
|
int if "CNT" in stat_field_key or "TOK" in stat_field_key else float
|
||||||
final_stats[period_key][stat_field_key] = 0
|
)
|
||||||
|
elif "CNT" in stat_field_key or "TOK" in stat_field_key:
|
||||||
|
final_stats[period_key][stat_field_key] = 0
|
||||||
elif "COST" in stat_field_key or ONLINE_TIME == stat_field_key:
|
elif "COST" in stat_field_key or ONLINE_TIME == stat_field_key:
|
||||||
final_stats[period_key][stat_field_key] = 0.0
|
final_stats[period_key][stat_field_key] = 0.0
|
||||||
return final_stats
|
return final_stats
|
||||||
|
|
||||||
# -- 以下为统计数据格式化方法 --
|
# -- 以下为统计数据格式化方法 --
|
||||||
|
|
@ -517,7 +530,7 @@ class StatisticOutputTask(AsyncTask):
|
||||||
"""
|
"""
|
||||||
if stats.get(TOTAL_MSG_CNT, 0) > 0:
|
if stats.get(TOTAL_MSG_CNT, 0) > 0:
|
||||||
output = ["聊天消息统计:", " 联系人/群组名称 消息数量"]
|
output = ["聊天消息统计:", " 联系人/群组名称 消息数量"]
|
||||||
msg_cnt_by_chat = stats.get(MSG_CNT_BY_CHAT, {})
|
msg_cnt_by_chat = stats.get(MSG_CNT_BY_CHAT, {})
|
||||||
for chat_id, count in sorted(msg_cnt_by_chat.items()):
|
for chat_id, count in sorted(msg_cnt_by_chat.items()):
|
||||||
chat_name_display = self.name_mapping.get(chat_id, (f"未知 ({chat_id})", None))[0]
|
chat_name_display = self.name_mapping.get(chat_id, (f"未知 ({chat_id})", None))[0]
|
||||||
output.append(f"{chat_name_display[:32]:<32} {count:>10}")
|
output.append(f"{chat_name_display[:32]:<32} {count:>10}")
|
||||||
|
|
@ -539,21 +552,25 @@ class StatisticOutputTask(AsyncTask):
|
||||||
deploy_time_dt = datetime.fromtimestamp(local_storage["deploy_time"])
|
deploy_time_dt = datetime.fromtimestamp(local_storage["deploy_time"])
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
logger.error("Invalid deploy_time in local_storage for HTML report. Using default.")
|
logger.error("Invalid deploy_time in local_storage for HTML report. Using default.")
|
||||||
deploy_time_dt = datetime(2000,1,1) # Fallback
|
deploy_time_dt = datetime(2000, 1, 1) # Fallback
|
||||||
else:
|
else:
|
||||||
# This should ideally not happen if __init__ or _collect_all_statistics ran
|
# This should ideally not happen if __init__ or _collect_all_statistics ran
|
||||||
logger.warning("deploy_time not found in local_storage for HTML report. Using default.")
|
logger.warning("deploy_time not found in local_storage for HTML report. Using default.")
|
||||||
deploy_time_dt = datetime(2000, 1, 1) # Fallback
|
deploy_time_dt = datetime(2000, 1, 1) # Fallback
|
||||||
|
|
||||||
tab_list_html = []
|
tab_list_html = []
|
||||||
tab_content_html_list = []
|
tab_content_html_list = []
|
||||||
|
|
||||||
for period_key, period_delta, period_display_name in self.stat_period: # Use self.stat_period as defined by _collect_all_statistics
|
for (
|
||||||
|
period_key,
|
||||||
|
period_delta,
|
||||||
|
period_display_name,
|
||||||
|
) in self.stat_period: # Use self.stat_period as defined by _collect_all_statistics
|
||||||
tab_list_html.append(
|
tab_list_html.append(
|
||||||
f'<button class="tab-link" onclick="showTab(event, \'{period_key}\')">{period_display_name}</button>'
|
f'<button class="tab-link" onclick="showTab(event, \'{period_key}\')">{period_display_name}</button>'
|
||||||
)
|
)
|
||||||
|
|
||||||
current_period_stats = stat_collection.get(period_key, {})
|
current_period_stats = stat_collection.get(period_key, {})
|
||||||
|
|
||||||
if period_key == "all_time":
|
if period_key == "all_time":
|
||||||
start_time_dt_for_period = deploy_time_dt
|
start_time_dt_for_period = deploy_time_dt
|
||||||
|
|
@ -561,11 +578,12 @@ class StatisticOutputTask(AsyncTask):
|
||||||
# Ensure period_delta is a timedelta object
|
# Ensure period_delta is a timedelta object
|
||||||
if isinstance(period_delta, timedelta):
|
if isinstance(period_delta, timedelta):
|
||||||
start_time_dt_for_period = now - period_delta
|
start_time_dt_for_period = now - period_delta
|
||||||
else: # Fallback if period_delta is not as expected (e.g. from old self.stat_period)
|
else: # Fallback if period_delta is not as expected (e.g. from old self.stat_period)
|
||||||
logger.warning(f"period_delta for {period_key} is not a timedelta. Using 'now'. Type: {type(period_delta)}")
|
logger.warning(
|
||||||
|
f"period_delta for {period_key} is not a timedelta. Using 'now'. Type: {type(period_delta)}"
|
||||||
|
)
|
||||||
start_time_dt_for_period = now
|
start_time_dt_for_period = now
|
||||||
|
|
||||||
|
|
||||||
html_content_for_tab = f"""
|
html_content_for_tab = f"""
|
||||||
<div id="{period_key}" class="tab-content">
|
<div id="{period_key}" class="tab-content">
|
||||||
<p class="info-item">
|
<p class="info-item">
|
||||||
|
|
@ -630,7 +648,7 @@ class StatisticOutputTask(AsyncTask):
|
||||||
cost_by_user = current_period_stats.get(COST_BY_USER, defaultdict(float))
|
cost_by_user = current_period_stats.get(COST_BY_USER, defaultdict(float))
|
||||||
if req_cnt_by_user:
|
if req_cnt_by_user:
|
||||||
for user_id, count in sorted(req_cnt_by_user.items()):
|
for user_id, count in sorted(req_cnt_by_user.items()):
|
||||||
user_display_name = self.name_mapping.get(user_id, (user_id, None))[0]
|
user_display_name = self.name_mapping.get(user_id, (user_id, None))[0]
|
||||||
html_content_for_tab += (
|
html_content_for_tab += (
|
||||||
f"<tr>"
|
f"<tr>"
|
||||||
f"<td>{user_display_name}</td>"
|
f"<td>{user_display_name}</td>"
|
||||||
|
|
@ -645,7 +663,9 @@ class StatisticOutputTask(AsyncTask):
|
||||||
html_content_for_tab += "<tr><td colspan='6'>无数据</td></tr>"
|
html_content_for_tab += "<tr><td colspan='6'>无数据</td></tr>"
|
||||||
html_content_for_tab += "</tbody></table>"
|
html_content_for_tab += "</tbody></table>"
|
||||||
|
|
||||||
html_content_for_tab += "<h2>聊天消息统计</h2><table><thead><tr><th>联系人/群组名称</th><th>消息数量</th></tr></thead><tbody>"
|
html_content_for_tab += (
|
||||||
|
"<h2>聊天消息统计</h2><table><thead><tr><th>联系人/群组名称</th><th>消息数量</th></tr></thead><tbody>"
|
||||||
|
)
|
||||||
msg_cnt_by_chat = current_period_stats.get(MSG_CNT_BY_CHAT, {})
|
msg_cnt_by_chat = current_period_stats.get(MSG_CNT_BY_CHAT, {})
|
||||||
if msg_cnt_by_chat:
|
if msg_cnt_by_chat:
|
||||||
for chat_id, count in sorted(msg_cnt_by_chat.items()):
|
for chat_id, count in sorted(msg_cnt_by_chat.items()):
|
||||||
|
|
@ -653,11 +673,10 @@ class StatisticOutputTask(AsyncTask):
|
||||||
html_content_for_tab += f"<tr><td>{chat_name_display}</td><td>{count}</td></tr>"
|
html_content_for_tab += f"<tr><td>{chat_name_display}</td><td>{count}</td></tr>"
|
||||||
else:
|
else:
|
||||||
html_content_for_tab += "<tr><td colspan='2'>无数据</td></tr>"
|
html_content_for_tab += "<tr><td colspan='2'>无数据</td></tr>"
|
||||||
html_content_for_tab += "</tbody></table></div>"
|
html_content_for_tab += "</tbody></table></div>"
|
||||||
|
|
||||||
tab_content_html_list.append(html_content_for_tab)
|
tab_content_html_list.append(html_content_for_tab)
|
||||||
|
|
||||||
|
|
||||||
html_template = (
|
html_template = (
|
||||||
"""
|
"""
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,9 @@ from ...config.config import global_config
|
||||||
logger = get_module_logger("chat_utils")
|
logger = get_module_logger("chat_utils")
|
||||||
|
|
||||||
# 预编译正则表达式以提高性能
|
# 预编译正则表达式以提高性能
|
||||||
_L_REGEX = regex.compile(r'\p{L}') # 匹配任何Unicode字母
|
_L_REGEX = regex.compile(r"\p{L}") # 匹配任何Unicode字母
|
||||||
_HAN_CHAR_REGEX = regex.compile(r'\p{Han}') # 匹配汉字 (Unicode属性)
|
_HAN_CHAR_REGEX = regex.compile(r"\p{Han}") # 匹配汉字 (Unicode属性)
|
||||||
|
|
||||||
|
|
||||||
def is_letter_not_han(char_str: str) -> bool:
|
def is_letter_not_han(char_str: str) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
@ -40,16 +41,19 @@ def is_letter_not_han(char_str: str) -> bool:
|
||||||
is_han = _HAN_CHAR_REGEX.fullmatch(char_str) is not None
|
is_han = _HAN_CHAR_REGEX.fullmatch(char_str) is not None
|
||||||
return not is_han
|
return not is_han
|
||||||
|
|
||||||
|
|
||||||
def is_han_character(char_str: str) -> bool:
|
def is_han_character(char_str: str) -> bool:
|
||||||
"""检查字符是否为汉字 (使用 \p{Han} Unicode 属性)"""
|
"""检查字符是否为汉字 (使用 \p{Han} Unicode 属性)"""
|
||||||
if not isinstance(char_str, str) or len(char_str) != 1:
|
if not isinstance(char_str, str) or len(char_str) != 1:
|
||||||
return False
|
return False
|
||||||
return _HAN_CHAR_REGEX.fullmatch(char_str) is not None
|
return _HAN_CHAR_REGEX.fullmatch(char_str) is not None
|
||||||
|
|
||||||
|
|
||||||
def is_english_letter(char: str) -> bool:
|
def is_english_letter(char: str) -> bool:
|
||||||
"""检查字符是否为英文字母(忽略大小写)"""
|
"""检查字符是否为英文字母(忽略大小写)"""
|
||||||
return "a" <= char.lower() <= "z"
|
return "a" <= char.lower() <= "z"
|
||||||
|
|
||||||
|
|
||||||
def db_message_to_str(message_dict: dict) -> str:
|
def db_message_to_str(message_dict: dict) -> str:
|
||||||
logger.debug(f"message_dict: {message_dict}")
|
logger.debug(f"message_dict: {message_dict}")
|
||||||
time_str = time.strftime("%m-%d %H:%M:%S", time.localtime(message_dict["time"]))
|
time_str = time.strftime("%m-%d %H:%M:%S", time.localtime(message_dict["time"]))
|
||||||
|
|
@ -101,7 +105,8 @@ def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]:
|
||||||
if not is_mentioned:
|
if not is_mentioned:
|
||||||
# 判断是否被回复
|
# 判断是否被回复
|
||||||
if re.match(
|
if re.match(
|
||||||
f"\\[回复 [\\s\\S]*?\\({str(global_config.BOT_QQ)}\\):[\\s\\S]*?\\],说:", message.processed_plain_text
|
f"\\[回复 [\\s\\S]*?\\({str(global_config.BOT_QQ)}\\):[\\s\\S]*?\\],说:",
|
||||||
|
message.processed_plain_text,
|
||||||
):
|
):
|
||||||
is_mentioned = True
|
is_mentioned = True
|
||||||
else:
|
else:
|
||||||
|
|
@ -211,6 +216,7 @@ def split_into_sentences_w_remove_punctuation(text: str) -> list[str]:
|
||||||
# 2. 处理换行符和其他分隔符的组合 (增加了 . 和 —)
|
# 2. 处理换行符和其他分隔符的组合 (增加了 . 和 —)
|
||||||
text = regex.sub(r"\n\s*([—。.,,;\s\xa0])", r"\1", text)
|
text = regex.sub(r"\n\s*([—。.,,;\s\xa0])", r"\1", text)
|
||||||
text = regex.sub(r"([—。.,,;\s\xa0])\s*\n", r"\1", text)
|
text = regex.sub(r"([—。.,,;\s\xa0])\s*\n", r"\1", text)
|
||||||
|
|
||||||
# 3. 处理两个汉字中间的换行符
|
# 3. 处理两个汉字中间的换行符
|
||||||
def replace_han_newline(match):
|
def replace_han_newline(match):
|
||||||
char1 = match.group(1)
|
char1 = match.group(1)
|
||||||
|
|
@ -218,6 +224,7 @@ def split_into_sentences_w_remove_punctuation(text: str) -> list[str]:
|
||||||
if is_han_character(char1) and is_han_character(char2):
|
if is_han_character(char1) and is_han_character(char2):
|
||||||
return char1 + "。" + char2
|
return char1 + "。" + char2
|
||||||
return match.group(0)
|
return match.group(0)
|
||||||
|
|
||||||
text = regex.sub(r"(.)\n(.)", replace_han_newline, text)
|
text = regex.sub(r"(.)\n(.)", replace_han_newline, text)
|
||||||
|
|
||||||
len_text = len(text)
|
len_text = len(text)
|
||||||
|
|
@ -239,7 +246,7 @@ def split_into_sentences_w_remove_punctuation(text: str) -> list[str]:
|
||||||
if char in separators:
|
if char in separators:
|
||||||
can_split = True
|
can_split = True
|
||||||
|
|
||||||
if char == ' ' or char == '\xa0':
|
if char == " " or char == "\xa0":
|
||||||
if 0 < i < len(text) - 1:
|
if 0 < i < len(text) - 1:
|
||||||
prev_char = text[i - 1]
|
prev_char = text[i - 1]
|
||||||
next_char = text[i + 1]
|
next_char = text[i + 1]
|
||||||
|
|
@ -251,9 +258,9 @@ def split_into_sentences_w_remove_punctuation(text: str) -> list[str]:
|
||||||
if can_split:
|
if can_split:
|
||||||
if current_segment:
|
if current_segment:
|
||||||
segments.append((current_segment, char))
|
segments.append((current_segment, char))
|
||||||
elif char not in [' ', '\xa0']:
|
elif char not in [" ", "\xa0"]:
|
||||||
segments.append(("", char))
|
segments.append(("", char))
|
||||||
elif char in [' ', '\xa0']:
|
elif char in [" ", "\xa0"]:
|
||||||
segments.append(("", char))
|
segments.append(("", char))
|
||||||
current_segment = ""
|
current_segment = ""
|
||||||
else:
|
else:
|
||||||
|
|
@ -268,9 +275,9 @@ def split_into_sentences_w_remove_punctuation(text: str) -> list[str]:
|
||||||
temp_segments_for_filter = []
|
temp_segments_for_filter = []
|
||||||
for content, sep in segments:
|
for content, sep in segments:
|
||||||
if content.strip():
|
if content.strip():
|
||||||
temp_segments_for_filter.append((content,sep))
|
temp_segments_for_filter.append((content, sep))
|
||||||
elif sep and sep not in [' ', '\xa0']:
|
elif sep and sep not in [" ", "\xa0"]:
|
||||||
temp_segments_for_filter.append((content,sep))
|
temp_segments_for_filter.append((content, sep))
|
||||||
segments = temp_segments_for_filter
|
segments = temp_segments_for_filter
|
||||||
|
|
||||||
if not segments:
|
if not segments:
|
||||||
|
|
@ -280,7 +287,7 @@ def split_into_sentences_w_remove_punctuation(text: str) -> list[str]:
|
||||||
current_sentence_build = ""
|
current_sentence_build = ""
|
||||||
for content, sep in segments:
|
for content, sep in segments:
|
||||||
current_sentence_build += content
|
current_sentence_build += content
|
||||||
if sep and sep not in [' ', '\xa0']:
|
if sep and sep not in [" ", "\xa0"]:
|
||||||
current_sentence_build += sep
|
current_sentence_build += sep
|
||||||
if current_sentence_build.strip():
|
if current_sentence_build.strip():
|
||||||
preliminary_final_sentences.append(current_sentence_build.strip())
|
preliminary_final_sentences.append(current_sentence_build.strip())
|
||||||
|
|
@ -310,7 +317,7 @@ def split_into_sentences_w_remove_punctuation(text: str) -> list[str]:
|
||||||
temp_sentence = preliminary_final_sentences[0]
|
temp_sentence = preliminary_final_sentences[0]
|
||||||
for i in range(1, len(preliminary_final_sentences)):
|
for i in range(1, len(preliminary_final_sentences)):
|
||||||
if random.random() < merge_probability and temp_sentence:
|
if random.random() < merge_probability and temp_sentence:
|
||||||
temp_sentence += " " + preliminary_final_sentences[i]
|
temp_sentence += " " + preliminary_final_sentences[i]
|
||||||
else:
|
else:
|
||||||
if temp_sentence:
|
if temp_sentence:
|
||||||
final_sentences_merged.append(temp_sentence)
|
final_sentences_merged.append(temp_sentence)
|
||||||
|
|
@ -448,7 +455,7 @@ def calculate_typing_time(
|
||||||
|
|
||||||
total_time = 0
|
total_time = 0
|
||||||
for char in input_string:
|
for char in input_string:
|
||||||
if is_han_character(char): # 使用 is_han_character 进行判断
|
if is_han_character(char): # 使用 is_han_character 进行判断
|
||||||
total_time += chinese_time
|
total_time += chinese_time
|
||||||
else:
|
else:
|
||||||
total_time += english_time
|
total_time += english_time
|
||||||
|
|
@ -462,7 +469,6 @@ def calculate_typing_time(
|
||||||
return total_time
|
return total_time
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def cosine_similarity(v1, v2):
|
def cosine_similarity(v1, v2):
|
||||||
"""计算余弦相似度"""
|
"""计算余弦相似度"""
|
||||||
dot_product = np.dot(v1, v2)
|
dot_product = np.dot(v1, v2)
|
||||||
|
|
@ -578,7 +584,7 @@ def get_western_ratio(paragraph):
|
||||||
if not alnum_chars:
|
if not alnum_chars:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
western_count = sum(1 for char in alnum_chars if is_english_letter(char)) # 保持使用 is_english_letter
|
western_count = sum(1 for char in alnum_chars if is_english_letter(char)) # 保持使用 is_english_letter
|
||||||
return western_count / len(alnum_chars)
|
return western_count / len(alnum_chars)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,14 @@ from .heart_flow.sub_mind import SubMind
|
||||||
from .heart_flow.utils_chat import get_chat_type_and_target_info
|
from .heart_flow.utils_chat import get_chat_type_and_target_info
|
||||||
from src.manager.mood_manager import mood_manager
|
from src.manager.mood_manager import mood_manager
|
||||||
from src.chat.message_receive.chat_stream import ChatStream, chat_manager
|
from src.chat.message_receive.chat_stream import ChatStream, chat_manager
|
||||||
from src.chat.message_receive.message import MessageRecv, BaseMessageInfo, MessageThinking, MessageSending, Seg, UserInfo
|
from src.chat.message_receive.message import (
|
||||||
|
MessageRecv,
|
||||||
|
BaseMessageInfo,
|
||||||
|
MessageThinking,
|
||||||
|
MessageSending,
|
||||||
|
Seg,
|
||||||
|
UserInfo,
|
||||||
|
)
|
||||||
from src.chat.utils.utils import process_llm_response
|
from src.chat.utils.utils import process_llm_response
|
||||||
from src.chat.utils.utils_image import image_path_to_base64
|
from src.chat.utils.utils_image import image_path_to_base64
|
||||||
from src.chat.emoji_system.emoji_manager import emoji_manager
|
from src.chat.emoji_system.emoji_manager import emoji_manager
|
||||||
|
|
|
||||||
|
|
@ -567,7 +567,7 @@ class SubMind:
|
||||||
|
|
||||||
# ---------- 5. 构建最终提示词 ----------
|
# ---------- 5. 构建最终提示词 ----------
|
||||||
# --- Choose template based on chat type ---
|
# --- Choose template based on chat type ---
|
||||||
nickname_injection_str = "" # 初始化为空字符串
|
nickname_injection_str = "" # 初始化为空字符串
|
||||||
|
|
||||||
if is_group_chat:
|
if is_group_chat:
|
||||||
template_name = "sub_heartflow_prompt_before"
|
template_name = "sub_heartflow_prompt_before"
|
||||||
|
|
@ -584,7 +584,7 @@ class SubMind:
|
||||||
)
|
)
|
||||||
nickname_injection_str = await nickname_manager.get_nickname_prompt_injection(
|
nickname_injection_str = await nickname_manager.get_nickname_prompt_injection(
|
||||||
chat_stream, message_list_for_nicknames
|
chat_stream, message_list_for_nicknames
|
||||||
)
|
)
|
||||||
|
|
||||||
prompt = (await global_prompt_manager.get_prompt_async(template_name)).format(
|
prompt = (await global_prompt_manager.get_prompt_async(template_name)).format(
|
||||||
extra_info=self.structured_info_str,
|
extra_info=self.structured_info_str,
|
||||||
|
|
|
||||||
|
|
@ -264,8 +264,6 @@ class IdleChat:
|
||||||
# 获取关系值
|
# 获取关系值
|
||||||
relationship_value = 0
|
relationship_value = 0
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
|
||||||
# 尝试获取person_id
|
# 尝试获取person_id
|
||||||
person_id = None
|
person_id = None
|
||||||
try:
|
try:
|
||||||
|
|
@ -428,7 +426,6 @@ class IdleChat:
|
||||||
async def _get_chat_stream(self) -> Optional[ChatStream]:
|
async def _get_chat_stream(self) -> Optional[ChatStream]:
|
||||||
"""获取聊天流实例"""
|
"""获取聊天流实例"""
|
||||||
try:
|
try:
|
||||||
|
|
||||||
existing_chat_stream = chat_manager.get_stream(self.stream_id)
|
existing_chat_stream = chat_manager.get_stream(self.stream_id)
|
||||||
if existing_chat_stream:
|
if existing_chat_stream:
|
||||||
logger.debug(f"[私聊][{self.private_name}]从chat_manager找到现有聊天流")
|
logger.debug(f"[私聊][{self.private_name}]从chat_manager找到现有聊天流")
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ from src.chat.person_info.relationship_manager import relationship_manager
|
||||||
from src.chat.message_receive.chat_stream import ChatStream
|
from src.chat.message_receive.chat_stream import ChatStream
|
||||||
from src.chat.message_receive.message import MessageRecv
|
from src.chat.message_receive.message import MessageRecv
|
||||||
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
|
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
|
||||||
|
|
||||||
logger = get_logger("NicknameManager")
|
logger = get_logger("NicknameManager")
|
||||||
logger_helper = get_logger("AsyncLoopHelper") # 为辅助函数创建单独的 logger
|
logger_helper = get_logger("AsyncLoopHelper") # 为辅助函数创建单独的 logger
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue