🤖 自动格式化代码 [skip ci]

pull/937/head
github-actions[bot] 2025-05-14 14:41:11 +00:00
parent d6fbef7fc6
commit a8f45e7c06
12 changed files with 118 additions and 84 deletions

View File

@ -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}")

View File

@ -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}'")

View File

@ -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:

View File

@ -876,4 +876,4 @@ def weighted_sample_no_replacement(items, weights, k) -> list:
init_prompt() init_prompt()
prompt_builder = PromptBuilder() prompt_builder = PromptBuilder()

View File

@ -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())

View File

@ -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:

View File

@ -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>

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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找到现有聊天流")

View File

@ -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