- 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>
8.2 KiB
8.2 KiB
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 模块
数据库 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()— 返回今日统计 dictget_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