Pamudu13/gemma-3-chat
0
1import asyncio2import time3import datetime4import logging5import random6from typing import Dict, List, Callable, Optional, Any, Union7from pydantic import BaseModel8 9# --- 数据模型定义 (与前端一致) ---10 11class BehaviorTriggerTime(BaseModel):12 timeValue: str # "HH:mm:ss"13 days: List[int] = [] # 1=Mon...6=Sat, 0=Sun14 15class BehaviorTriggerNoInput(BaseModel):16 latency: int17 18class BehaviorTriggerCycle(BaseModel):19 cycleValue: str # "HH:mm:ss"20 repeatNumber: int21 isInfiniteLoop: bool22 23class BehaviorTrigger(BaseModel):24 type: str # "time", "noInput", "cycle"25 time: Optional[BehaviorTriggerTime] = None26 noInput: Optional[BehaviorTriggerNoInput] = None27 cycle: Optional[BehaviorTriggerCycle] = None28 29class BehaviorRandomAction(BaseModel):30 events: List[str]31 type: str # "random", "order"32 orderIndex: int = 033 34class BehaviorAction(BaseModel):35 type: str # "prompt", "random", "topic"36 prompt: Optional[str] = ""37 random: Optional[BehaviorRandomAction] = None38 topicLimit: int = 139 40class BehaviorItem(BaseModel):41 enabled: bool42 trigger: BehaviorTrigger43 action: BehaviorAction44 platform: Optional[str] = "chat" # 保留字段以兼容旧版本45 platforms: List[str] = [] # 新字段:支持多选46 47class BehaviorSettings(BaseModel):48 enabled: bool49 behaviorList: List[BehaviorItem] = []50 51# --- 通用行为引擎 ---52 53class BehaviorEngine:54 _instance = None55 56 def __new__(cls):57 if cls._instance is None:58 cls._instance = super(BehaviorEngine, cls).__new__(cls)59 cls._instance._initialized = False60 return cls._instance61 62 def __init__(self):63 if self._initialized: return64 self._initialized = True65 66 self.settings: Optional[BehaviorSettings] = None67 self.is_running = False68 self._stop_event = None # 延迟初始化69 self.platform_activity: Dict[str, Dict[str, float]] = {} 70 self.platform_targets: Dict[str, List[str]] = {}71 self.handlers: Dict[str, Callable] = {}72 self.timers: Dict[str, float] = {}73 self.counters: Dict[str, int] = {}74 75 def register_handler(self, platform: str, handler: Callable):76 """注册平台的执行回调函数"""77 self.handlers[platform] = handler78 if platform not in self.platform_activity:79 self.platform_activity[platform] = {}80 81 # 关键修复:当新平台注册时,如果已经有配置,重置计时器82 # 这样即使“先开设置再开机器人”,机器人一上线就会重新计算触发时间83 if self.settings and self.settings.enabled:84 self.timers.clear()85 self.counters.clear()86 logging.info(f"[BehaviorEngine] 平台 {platform} 已上线,重置引擎计时器以激活任务")87 88 logging.info(f"[BehaviorEngine] Registered platform: {platform}")89 90 def update_config(self, settings: Union[BehaviorSettings, dict], platform_targets: Dict[str, List[str]] = None):91 """热更新配置"""92 if isinstance(settings, dict):93 try:94 self.settings = BehaviorSettings(**settings)95 except Exception as e:96 logging.error(f"[BehaviorEngine] 配置解析失败: {e}")97 return98 else:99 self.settings = settings100 101 if platform_targets:102 for platform, targets in platform_targets.items():103 self.platform_targets[platform] = targets104 105 self.timers.clear()106 self.counters.clear()107 logging.info("[BehaviorEngine] 配置已更新,计时器已重置")108 109 def report_activity(self, platform: str, chat_id: str):110 """平台层调用:上报活跃状态(重置无输入计时)"""111 if platform not in self.platform_activity:112 self.platform_activity[platform] = {}113 self.platform_activity[platform][chat_id] = time.time()114 115 async def start(self):116 """启动引擎循环"""117 # 确保 Event 对象在当前的 Loop 中创建118 self._stop_event = asyncio.Event()119 self.is_running = True120 logging.info("[BehaviorEngine] 监控任务已激活")121 122 try:123 while not self._stop_event.is_set():124 if not self.is_running: 125 break126 try:127 await self._tick()128 except Exception as e:129 logging.error(f"[BehaviorEngine] Tick 异常: {e}")130 131 # 必须使用 asyncio.sleep,不能用 time.sleep132 await asyncio.sleep(1)133 finally:134 self.is_running = False135 logging.info("[BehaviorEngine] 监控循环已安全退出")136 137 def stop(self):138 """停止引擎"""139 self.is_running = False140 if self._stop_event:141 self._stop_event.set()142 logging.info("[BehaviorEngine] 已发出停止信号")143 144 async def _tick(self):145 """核心逻辑:每秒检查一次"""146 if not self.settings or not self.settings.enabled:147 return148 149 now = time.time()150 dt_now = datetime.datetime.now()151 152 current_time_str = dt_now.strftime("%H:%M") 153 py_weekday = dt_now.weekday()154 current_day = (py_weekday + 1) if py_weekday < 6 else 0155 156 for idx, behavior in enumerate(self.settings.behaviorList):157 if not behavior.enabled: continue158 159 # 确定当前行为要分发到哪些平台160 effective_platforms = behavior.platforms if behavior.platforms else [behavior.platform]161 162 # 确定当前行为要分发到哪些具体的平台 Key163 target_platform_keys = []164 if "all" in effective_platforms:165 target_platform_keys = list(self.handlers.keys())166 else:167 # 过滤掉不支持的平台168 target_platform_keys = [p for p in effective_platforms if p in self.handlers]169 170 for platform in target_platform_keys:171 handler = self.handlers.get(platform)172 if not handler: continue173 174 trigger_chats = []175 static_targets = self.platform_targets.get(platform, [])176 177 # --- 逻辑 1: 无输入 (No Input) ---178 if behavior.trigger.type == "noInput" and behavior.trigger.noInput:179 latency = behavior.trigger.noInput.latency180 active_targets = list(self.platform_activity.get(platform, {}).keys())181 for chat_id in active_targets:182 last_active = self.platform_activity[platform].get(chat_id, now)183 if now - last_active >= latency:184 uniq_key = f"noInput_{idx}_{platform}_{chat_id}"185 if self.timers.get(uniq_key, 0) < now - latency - 5: # 防抖186 trigger_chats.append(chat_id)187 self.timers[uniq_key] = now188 189 # --- 逻辑 2: 定时 (Time) ---190 elif behavior.trigger.type == "time" and behavior.trigger.time:191 # 前端传的是 "HH:mm:ss",我们只比对 "HH:mm"192 if behavior.trigger.time.timeValue.startswith(current_time_str):193 if not behavior.trigger.time.days or current_day in behavior.trigger.time.days:194 uniq_key = f"time_{idx}_{platform}_{current_time_str}"195 if self.timers.get(uniq_key, 0) < now - 65:196 trigger_chats = static_targets197 self.timers[uniq_key] = now198 199 # --- 逻辑 3: 周期 (Cycle) ---200 elif behavior.trigger.type == "cycle" and behavior.trigger.cycle:201 try:202 t = behavior.trigger.cycle.cycleValue.split(':')203 cycle_sec = int(t[0])*3600 + int(t[1])*60 + int(t[2])204 except: cycle_sec = 60205 206 uniq_key = f"cycle_{idx}_{platform}"207 if self.timers.get(uniq_key, 0) == 0: # 首次运行208 self.timers[uniq_key] = now + cycle_sec209 elif now >= self.timers.get(uniq_key, 0):210 count_key = f"cycle_count_{idx}_{platform}"211 count = self.counters.get(count_key, 0)212 if behavior.trigger.cycle.isInfiniteLoop or count < behavior.trigger.cycle.repeatNumber:213 trigger_chats = static_targets214 self.timers[uniq_key] = now + cycle_sec215 self.counters[count_key] = count + 1216 217 # 执行触发218 if trigger_chats:219 for chat_id in set(trigger_chats):220 if chat_id:221 logging.info(f"[BehaviorEngine] 命中规则 {idx},准备推送到 {platform}:{chat_id}")222 asyncio.create_task(handler(chat_id, behavior))223 224# 全局单例225global_behavior_engine = BehaviorEngine()