feat(sleep): 实现睡眠唤醒与重新入睡机制

引入了更完善的睡眠唤醒和重新入睡逻辑,以处理在睡眠期间被消息打扰的情况。

- **唤醒机制**: 当在睡眠时间内收到消息并达到唤醒阈值时,角色会被唤醒并进入愤怒状态。唤醒后,将保持清醒状态处理消息,而不是立即重新入睡。
- **状态持久化**: 新增 `_is_woken_up` 状态到 `schedule_manager`,并将其持久化,以确保在重启后能记住唤醒状态。
- **重新入睡**: 如果角色被吵醒后,在配置的一段时间内(`re_sleep_delay_minutes`)没有收到新消息,系统将自动尝试重新进入睡眠状态,以模拟更自然的行为。
- **上下文同步**: 在唤醒时,`wakeup_manager` 会通知 `schedule_manager` 更新其内部状态,确保系统各模块之间的睡眠状态一致。
This commit is contained in:
tt-P607
2025-08-28 08:48:19 +08:00
parent 910b0db5d2
commit 829ff4cd4f
4 changed files with 49 additions and 5 deletions

View File

@@ -242,10 +242,16 @@ class HeartFChatting:
# 处理唤醒度逻辑
if is_sleeping:
self._handle_wakeup_messages(recent_messages)
# 如果处于失眠状态,则无视睡眠时间,继续处理消息
# 否则,如果仍然在睡眠(没被吵醒),则跳过本轮处理
if not self.context.is_in_insomnia and schedule_manager.is_sleeping(self.wakeup_manager):
# 再次检查睡眠状态因为_handle_wakeup_messages可能会触发唤醒
current_is_sleeping = schedule_manager.is_sleeping(self.wakeup_manager)
if not self.context.is_in_insomnia and current_is_sleeping:
# 仍然在睡眠,跳过本轮的消息处理
return has_new_messages
else:
# 从睡眠中被唤醒,需要继续处理本轮消息
logger.info(f"{self.context.log_prefix} 从睡眠中被唤醒,将处理积压的消息。")
self.context.last_wakeup_time = time.time()
# 根据聊天模式处理新消息
if self.context.loop_mode == ChatMode.FOCUS:
@@ -266,6 +272,15 @@ class HeartFChatting:
# 更新上一帧的睡眠状态
self.context.was_sleeping = is_sleeping
# --- 重新入睡逻辑 ---
# 如果被吵醒了,并且在一定时间内没有新消息,则尝试重新入睡
if schedule_manager._is_woken_up and not has_new_messages:
re_sleep_delay = global_config.wakeup_system.re_sleep_delay_minutes * 60
# 使用 last_message_time 来判断空闲时间
if time.time() - self.context.last_message_time > re_sleep_delay:
logger.info(f"{self.context.log_prefix} 已被唤醒且超过 {re_sleep_delay / 60} 分钟无新消息,尝试重新入睡。")
schedule_manager.reset_wakeup_state()
# 保存HFC上下文状态
self.context.save_context_state()

View File

@@ -49,6 +49,7 @@ class HfcContext:
# 失眠状态
self.is_in_insomnia: bool = False
self.insomnia_end_time: float = 0.0
self.last_wakeup_time: float = 0.0 # 被吵醒的时间
self.last_message_time = time.time()
self.last_read_time = time.time() - 10
@@ -92,6 +93,7 @@ class HfcContext:
"sleep_pressure": self.sleep_pressure,
"is_in_insomnia": self.is_in_insomnia,
"insomnia_end_time": self.insomnia_end_time,
"last_wakeup_time": self.last_wakeup_time,
}
local_storage[self._get_storage_key()] = state
logger = get_logger("hfc_context")

View File

@@ -188,6 +188,10 @@ class WakeUpManager:
from src.mood.mood_manager import mood_manager
mood_manager.set_angry_from_wakeup(self.context.stream_id)
# 通知日程管理器重置睡眠状态
from src.schedule.schedule_manager import schedule_manager
schedule_manager.reset_sleep_state_after_wakeup()
logger.info(f"{self.context.log_prefix} 唤醒度达到阈值({self.wakeup_threshold}),被吵醒进入愤怒状态!")
def get_angry_prompt_addition(self) -> str:

View File

@@ -138,6 +138,7 @@ class ScheduleManager:
self._last_sleep_check_date: Optional[datetime.date] = None
self._last_fully_slept_log_time: float = 0
self._is_in_voluntary_delay: bool = False # 新增:标记是否处于主动延迟睡眠状态
self._is_woken_up: bool = False # 新增:标记是否被吵醒
self._load_sleep_state()
@@ -453,14 +454,15 @@ class ScheduleManager:
self._is_preparing_sleep = False
self._sleep_buffer_end_time = None
self._is_in_voluntary_delay = False
self._is_woken_up = False # 离开睡眠时间,重置唤醒状态
self._save_sleep_state()
return False
# --- 处理唤醒状态 ---
if wakeup_manager and wakeup_manager.is_in_angry_state():
if self._is_woken_up:
current_timestamp = now.timestamp()
if current_timestamp - self.last_sleep_log_time > self.sleep_log_interval:
logger.info(f"在休眠活动 '{activity}' 期间,但已被唤醒。")
logger.info(f"在休眠活动 '{activity}' 期间,但已被唤醒,保持清醒状态")
self.last_sleep_log_time = current_timestamp
return False
@@ -502,6 +504,16 @@ class ScheduleManager:
self.last_sleep_log_time = current_timestamp
return True
def reset_sleep_state_after_wakeup(self):
"""被唤醒后重置睡眠状态"""
if self._is_preparing_sleep or self.is_sleeping():
logger.info("被唤醒,重置所有睡眠准备状态,恢复清醒!")
self._is_preparing_sleep = False
self._sleep_buffer_end_time = None
self._is_in_voluntary_delay = False
self._is_woken_up = True # 标记为已被唤醒
self._save_sleep_state()
def _is_in_theoretical_sleep_time(self, now_time: time) -> (bool, Optional[str]):
"""检查当前时间是否落在日程表的任何一个睡眠活动中"""
sleep_keywords = ["休眠", "睡觉", "梦乡"]
@@ -600,6 +612,7 @@ class ScheduleManager:
"total_delayed_minutes_today": self._total_delayed_minutes_today,
"last_sleep_check_date_str": self._last_sleep_check_date.isoformat() if self._last_sleep_check_date else None,
"is_in_voluntary_delay": self._is_in_voluntary_delay,
"is_woken_up": self._is_woken_up,
}
local_storage["schedule_sleep_state"] = state
logger.debug(f"已保存睡眠状态: {state}")
@@ -619,6 +632,7 @@ class ScheduleManager:
self._total_delayed_minutes_today = state.get("total_delayed_minutes_today", 0)
self._is_in_voluntary_delay = state.get("is_in_voluntary_delay", False)
self._is_woken_up = state.get("is_woken_up", False)
date_str = state.get("last_sleep_check_date_str")
if date_str:
@@ -628,6 +642,15 @@ class ScheduleManager:
except Exception as e:
logger.warning(f"加载睡眠状态失败,将使用默认值: {e}")
def reset_wakeup_state(self):
"""重置被唤醒的状态,允许重新尝试入睡"""
if self._is_woken_up:
logger.info("重置唤醒状态,将重新尝试入睡。")
self._is_woken_up = False
self._is_preparing_sleep = False # 允许重新进入弹性睡眠判断
self._sleep_buffer_end_time = None
self._save_sleep_state()
def _validate_schedule_with_pydantic(self, schedule_data) -> bool:
"""使用Pydantic验证日程数据格式和完整性"""
try: