CoolFace
Apppublic

Pamudu13/gemma-3-chat

sourceHugging Faceupdated 25d agoView on Hugging Face
0likes
behavior_engine.py225 linesDownload Raw Back to py
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()