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

303 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EzVibe v2 演进修改记录
> 本文档记录从 EVOLUTION_ROADMAP.md 出发的所有实际代码改动。
> 对应 Phase 1 和 Phase 2 的已完成项。
---
## Phase 1底层重构与稳定性
### Phase 1.1 — qasync 替换 QTimer 方案
**改动文件**[main.py](main.py#L320-L360)
**现状(旧)**
```python
timer = QTimer(app)
timer.timeout.connect(_run_async)
timer.setInterval(20) # 50 Hz
timer.start()
app.exec()
```
**目标**:引入 qasync实现 Qt 事件循环与 asyncio 的原生融合。
**改动后**
```python
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](agent/health_tracker.py)
**数据库 Schema**
```sql
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 集成**
```python
# 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](agent/scheduler.py#L35-L52)
**新增枚举**
```python
class DisplayMode(Enum):
NORMAL = "normal" # 正常打扰(气泡通知)
QUIET = "quiet" # 安静模式(缩角落举牌子)
AGGRESSIVE = "aggressive" # 强制弹窗(高优先级警告)
```
**Behavior 数据结构变更**
```python
@dataclass
class Behavior:
...
display_mode: DisplayMode = DisplayMode.NORMAL # 新增字段
```
**逻辑变更**`_is_activity_restricted`
- 原设计:`annoyed` 时完全屏蔽 P0 打扰
- 新设计:`annoyed` 时 P0 降级为 QUIET 模式,不完全屏蔽
```python
# 烦躁时 P0 提醒降级为 QUIET不屏蔽改展示模式
if emotion == "annoyed" and behavior.priority == 0:
behavior.display_mode = DisplayMode.QUIET
return False # 不再直接屏蔽,而是降级
```
**行为触发返回时附加 display_mode**
```python
result["display_mode"] = behavior.display_mode.value
```
---
### Phase 2.2 — LLM Prompt 工程升级HealthTracker 上下文注入)
**改动文件**[agent/brain.py](agent/brain.py#L36-L60)
**System Prompt 变更**
```python
DEFAULT_SYSTEM_PROMPT = """...
【情绪驱动行为规则】
...
{health_context} <!-- 新增占位符 -->
【主动行为能力】
...
"""
```
**AgentBrain 初始化变更**
```python
def __init__(
self,
...,
health_tracker: Any = None, # 新增参数
) -> None:
...
self._health_tracker = health_tracker
```
**think() 方法中注入健康上下文**
```python
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 集成**
```python
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](agent/brain.py#L196-L248)
**新增类**
```python
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 集成**
```python
def __init__(...):
...
self._fallback_cache = FallbackMessageCache()
```
**decide_action 变更**`_decide_proactive_action`
```python
# 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](requirements.txt) | 修改 | 1.1 |
| [main.py](main.py) | 修改 | 1.1, 1.3, 2.2 |
| [agent/health_tracker.py](agent/health_tracker.py) | 新增 | 1.3 |
| [agent/scheduler.py](agent/scheduler.py) | 修改 | 2.1 |
| [agent/brain.py](agent/brain.py) | 修改 | 2.2, 2.3 |
---
## 运行命令
```bash
# 使用 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
```