Files
EzVibe/docs/v2_modify/CHANGES.md
e2hang 96cb28fe08 feat: multi-layer eventFilter for window drag + text input fix + Qt platform xcb
- Fix window drag: install eventFilter on live2d_container, central, and
  _live2d_widget; fix super() call in dynamic class
- Fix text input: remove WA_TransparentForMouseEvents from _chat_container
- Force QT_QPA_PLATFORM=xcb on Linux (wayland has mouse event issues)
- Add HealthTracker module, update AgentBrain with health integration
- Update scheduler and memory modules
- Add v5_modify documentation

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 23:36:58 +08:00

8.2 KiB
Raw Permalink Blame History

EzVibe v2 演进修改记录

本文档记录从 EVOLUTION_ROADMAP.md 出发的所有实际代码改动。 对应 Phase 1 和 Phase 2 的已完成项。


Phase 1底层重构与稳定性

Phase 1.1 — qasync 替换 QTimer 方案

改动文件main.py

现状(旧)

timer = QTimer(app)
timer.timeout.connect(_run_async)
timer.setInterval(20)  # 50 Hz
timer.start()
app.exec()

目标:引入 qasync实现 Qt 事件循环与 asyncio 的原生融合。

改动后

import qasync

qasync_loop = qasync.QEventLoop(app)
asyncio.set_event_loop(qasync_loop)

async def _run_scheduler_async():
    while getattr(self, "_running", False):
        # 异步调度循环,每 10 秒检查一次
        triggered = await self._scheduler.check_and_trigger(user_activity_level=activity)
        for action in triggered:
            self._window.show_reminder(...)
        await asyncio.sleep(10)

scheduler_task = qasync_loop.create_task(_run_scheduler_async())

with qasync_loop:
    qasync_loop.run_forever()

依赖requirements.txt 新增 qasync>=0.6.0


Phase 1.3 — 新增 HealthTracker 模块

新增文件agent/health_tracker.py

数据库 Schema

CREATE TABLE health_events (
    id          TEXT PRIMARY KEY,
    event_type  TEXT NOT NULL,      -- 'water' | 'stand' | 'stretch' | 'screen_break'
    timestamp   REAL NOT NULL,
    source      TEXT NOT NULL,      -- 'user_action' | 'reminder_confirmed' | 'implicit_detected'
    metadata    TEXT NOT NULL,      -- JSON
    created_at   REAL NOT NULL
);

CREATE TABLE daily_stats (
    date                TEXT PRIMARY KEY,   -- 'YYYY-MM-DD'
    water_count         INTEGER DEFAULT 0,
    stand_count         INTEGER DEFAULT 0,
    stretch_count       INTEGER DEFAULT 0,
    sedentary_minutes   INTEGER DEFAULT 0,
    screen_time_minutes INTEGER DEFAULT 0,
    last_stand_at       REAL,
    last_water_at       REAL,
    updated_at          REAL NOT NULL
);

CREATE TABLE sedentary_alerts (
    id              TEXT PRIMARY KEY,
    triggered_at    REAL NOT NULL,
    acknowledged    INTEGER DEFAULT 0,
    acknowledged_at REAL,
    message         TEXT
);

核心接口

  • record_water() / record_stand() / record_stretch() — 记录健康事件
  • get_today_stats() — 返回今日统计 dict
  • get_consecutive_sedentary_minutes() — 当前连续静坐分钟数
  • get_health_context_for_llm() — 生成注入 LLM System Prompt 的健康上下文
  • trigger_sedentary_alert() / acknowledge_alert() — 警报管理
  • get_weekly_report() / get_streak() — 统计聚合

与 main.py 集成

# main.py _init_components()
from agent.health_tracker import HealthTracker
self._health_tracker = HealthTracker(db_path="data/health.db")
self._brain = AgentBrain(
    ...,
    health_tracker=self._health_tracker,
)

Phase 2感知与决策升级

Phase 2.1 — DisplayMode 柔性降级

改动文件agent/scheduler.py

新增枚举

class DisplayMode(Enum):
    NORMAL = "normal"      # 正常打扰(气泡通知)
    QUIET = "quiet"        # 安静模式(缩角落举牌子)
    AGGRESSIVE = "aggressive"  # 强制弹窗(高优先级警告)

Behavior 数据结构变更

@dataclass
class Behavior:
    ...
    display_mode: DisplayMode = DisplayMode.NORMAL  # 新增字段

逻辑变更_is_activity_restricted

  • 原设计:annoyed 时完全屏蔽 P0 打扰
  • 新设计:annoyed 时 P0 降级为 QUIET 模式,不完全屏蔽
# 烦躁时 P0 提醒降级为 QUIET不屏蔽改展示模式
if emotion == "annoyed" and behavior.priority == 0:
    behavior.display_mode = DisplayMode.QUIET

return False  # 不再直接屏蔽,而是降级

行为触发返回时附加 display_mode

result["display_mode"] = behavior.display_mode.value

Phase 2.2 — LLM Prompt 工程升级HealthTracker 上下文注入)

改动文件agent/brain.py

System Prompt 变更

DEFAULT_SYSTEM_PROMPT = """...
【情绪驱动行为规则】
...

{health_context}  <!-- 新增占位符 -->

【主动行为能力】
...
"""

AgentBrain 初始化变更

def __init__(
    self,
    ...,
    health_tracker: Any = None,  # 新增参数
) -> None:
    ...
    self._health_tracker = health_tracker

think() 方法中注入健康上下文

async def think(self, user_input: str, ...):
    # 2. 获取健康上下文Phase 2.2 — HealthTracker 数据注入)
    health_context = ""
    if self._health_tracker:
        try:
            health_context = await self._health_tracker.get_health_context_for_llm()
        except Exception as exc:
            logger.warning("[Brain] 健康数据获取失败: %s", exc)
            health_context = "【用户健康状态】暂无数据"

    # 3. 构建系统提示词(注入情绪 + 健康上下文)
    system_prompt = self._system_prompt.format(
        emotion_display=emotion_display,
        emotion_state=emotion,
        health_context=health_context,  # 注入
    )

main.py 集成

self._health_tracker = HealthTracker(db_path="data/health.db")
self._brain = AgentBrain(
    llm_backend=self._llm_backend,
    llm_config=self._llm_config,
    emotion_engine=self._emotion,
    memory=self._memory,
    health_tracker=self._health_tracker,  # 传入
)

Phase 2.3 — 解耦"动作触发"与"文案生成"FallbackMessageCache

改动文件agent/brain.py

新增类

class FallbackMessageCache:
    """
    P0 健康提醒的 Fallback 文案缓存。

    设计文档decide_action() 等待 LLM 生成文案后才执行 UI 动作。
    改进P0 健康提醒立即执行 UI 动作LLM 文案异步加载或使用 Fallback 缓存。
    """

    FALLBACK_MESSAGES: dict[str, list[str]] = {
        "remind_water": [
            "记得喝水哦~",
            "该补充水分了!",
            "喝水时间到!",
            "滴—— 喝水提醒!💧",
            "身体需要水份~来一杯吧!",
        ],
        "remind_stretch": [
            "起来伸展一下吧!",
            "坐了好久啦,站起来动动!",
            "伸个懒腰吧~身体需要活动!",
            "⚠️ 久坐提醒:起身动一动!",
            "站起来伸展一下,缓解疲劳~",
        ],
        "nudge_idle": [...],
        "nudge_happy": [...],
    }

    def get(self, action_type: str) -> str:
        messages = self.FALLBACK_MESSAGES.get(action_type, [])
        if not messages:
            return f"[{action_type}]"
        return self._rng.choice(messages)

AgentBrain 集成

def __init__(...):
    ...
    self._fallback_cache = FallbackMessageCache()

decide_action 变更_decide_proactive_action

# P0 规则:高频工作 + 非烦躁状态 → 强制健康提醒
# Phase 2.3:使用 Fallback 文案缓存,立即返回不等待 LLM
if activity < 0.15 and emotion != "annoyed":
    if self._check_cooldown("remind_health"):
        return {
            "type": "remind_stretch",
            "message": self._fallback_cache.get("remind_stretch"),
            "priority": 0,
        }

# 喝水提醒(更低优先级)
if activity < 0.4 and self._check_cooldown("remind_water"):
    return {
        "type": "remind_water",
        "message": self._fallback_cache.get("remind_water"),
        "priority": 1,
    }

文件变更清单

文件 变更类型 对应 Phase
requirements.txt 修改 1.1
main.py 修改 1.1, 1.3, 2.2
agent/health_tracker.py 新增 1.3
agent/scheduler.py 修改 2.1
agent/brain.py 修改 2.2, 2.3

运行命令

# 使用 conda ai 环境
conda activate ai

# Dummy 模式(无 GUI
/home/e2hang/miniforge3/envs/ai/bin/python main.py --dummy

# 完整 GUI 模式
/home/e2hang/miniforge3/envs/ai/bin/python main.py