Files
abot/plugins/ai_auto_response/flow_manager.py

104 lines
4.1 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, time
from typing import Dict
@dataclass
class FlowState:
room_id: str
score: float = 0.0
state: str = "idle"
last_update: datetime = field(default_factory=datetime.now)
last_bot_reply_at: datetime | None = None
last_human_message_at: datetime | None = None
bot_reply_streak: int = 0
ignored_reply_count: int = 0
accepted_reply_count: int = 0
last_topic: str = ""
class FlowManager:
def __init__(self, config: Dict):
self.config = config or {}
self.states: Dict[str, FlowState] = {}
def get_state(self, room_id: str) -> FlowState:
if room_id not in self.states:
self.states[room_id] = FlowState(room_id=room_id)
return self.states[room_id]
def decay(self, room_id: str) -> FlowState:
state = self.get_state(room_id)
now = datetime.now()
elapsed_minutes = max((now - state.last_update).total_seconds() / 60.0, 0.0)
decay = elapsed_minutes * float(self.config.get("flow_decay_per_minute", 8))
state.score = max(0.0, state.score - decay)
state.last_update = now
state.state = self._score_to_state(state.score)
return state
def apply_message_event(self, room_id: str, event: Dict) -> FlowState:
state = self.decay(room_id)
now = datetime.now()
if self._is_night_silent(now.time()):
state.score = max(0.0, state.score - float(self.config.get("night_penalty", 30)))
if event.get("is_at"):
state.score += float(self.config.get("at_bot_boost", 40))
if event.get("is_question"):
state.score += float(self.config.get("question_boost", 30))
if event.get("is_followup"):
state.score += float(self.config.get("followup_boost", 20))
if event.get("topic_hit"):
state.score += float(self.config.get("topic_boost", 15))
if event.get("is_returning_member"):
state.score += float(self.config.get("returning_member_boost", 10))
if state.last_bot_reply_at:
since_reply = (now - state.last_bot_reply_at).total_seconds()
if since_reply <= 180 and event.get("message_after_bot"):
state.score += float(self.config.get("response_accepted_boost", 15))
state.accepted_reply_count += 1
state.last_human_message_at = now
state.last_topic = event.get("topic") or state.last_topic
state.state = self._score_to_state(state.score)
return state
def note_bot_reply(self, room_id: str) -> FlowState:
state = self.decay(room_id)
state.last_bot_reply_at = datetime.now()
state.bot_reply_streak += 1
max_streak = int(self.config.get("max_bot_reply_streak", 3))
if state.bot_reply_streak > max_streak:
state.score = max(0.0, state.score - float(self.config.get("over_reply_penalty", 15)))
state.state = self._score_to_state(state.score)
return state
def _score_to_state(self, score: float) -> str:
idle_threshold = float(self.config.get("idle_threshold", 20))
warming_threshold = float(self.config.get("warming_threshold", 40))
engaged_threshold = float(self.config.get("engaged_threshold", 70))
if score < idle_threshold:
return "idle"
if score < warming_threshold:
return "warming"
if score < engaged_threshold:
return "engaged"
return "deep_engaged"
def _is_night_silent(self, current_time: time) -> bool:
for window in self.config.get("night_silent_hours", []):
try:
start_str, end_str = window.split("-", 1)
start = time.fromisoformat(start_str)
end = time.fromisoformat(end_str)
if start <= end:
if start <= current_time <= end:
return True
else:
if current_time >= start or current_time <= end:
return True
except Exception:
continue
return False