# EzVibeR+ 实施记录 — 2026-06-11/12 ## Tauri v2 升级 + 健康提醒 CRUD + 窗口交互修复 + Live2D 表情动作映射 --- ## 一、双击变大修复 ### 问题 GNOME/KDE 窗口管理器在透明窗口上双击会将窗口最大化/填充屏幕,导致桌面宠变形和点击失效。 ### 修复 | 文件 | 变更 | |------|------| | `src-tauri/tauri.conf.json` | 主窗口新增 `"maximizable": false`,`"maxWidth": 800`,`"maxHeight": 640` | | `src-tauri/src/main.rs` | `setup` 中调用 `main_window.set_maximizable(false)`;`show` 托盘处理中也设置 | --- ## 二、drag-region 逻辑反转(点击修复) ### 问题 `data-tauri-drag-region="true"` 默认设在 canvas 上,导致 GNOME 窗口管理器将所有 pointer 事件截获用于拖窗,PIXI 交互系统完全收不到点击。 ### 修复(`src/live2d/index.vue`) - **默认**:canvas 不设 drag-region → 点击正常到达 DOM → 手动桥接到模型 - **移动模式**(工具栏定位按钮):canvas 启用 drag-region → 窗口可拖 - `onFocusChanged` 不再恢复 drag-region ### DOM→模型桥接 由于 PIXI v6 在 Tauri 透明窗口下交互系统失效,改为直接在 canvas 的 `pointerdown` 事件中: 1. 计算点击坐标相对模型的位置 2. 上级 35% 为头部 → 切换表情 3. 其余为身体 → 播放动作 + 通知后端情绪引擎 ```typescript canvas.addEventListener("pointerdown", async (e) => { const px = (e.clientX - rect.left) * scaleX; const py = (e.clientY - rect.top) * scaleY; const isHead = (py - model.y) < model.height * 0.35; // ... }); ``` --- ## 三、健康提醒 CRUD 面板 ### 架构设计 **单一数据源**:所有提醒行为统一存储在 `AppConf.behaviors` 中,不再区分"内置"与"自定义"。 ### 新增文件 | 文件 | 用途 | |------|------| | `health.html` | 入口页面 | | `src/health/App.ts` | Vue 入口 | | `src/components/HealthReminders.vue` | CRUD 表格 + 模态表单 | ### 后端改动 | 文件 | 变更 | |------|------| | `src-tauri/src/app/config.rs` | 新增 `BehaviorDef` 结构体 + `default_behaviors()` + `ensure_default_behaviors()` + `write_behaviors()` 安全写入 | | `src-tauri/src/modules/scheduler.rs` | 移除硬编码 `register_builtin()`,新增 `reload_all_behaviors()` 全量替换 | | `src-tauri/src/app/commands.rs` | 新增 `list_behaviors`, `add_behavior`, `update_behavior`, `delete_behavior` | | `src-tauri/src/main.rs` | 启动时 `ensure_default_behaviors()` + `reload_all_behaviors()`;托盘新增 "健康提醒" 菜单项 | ### 安全写入 `write_behaviors()` 只修改 `behaviors` 字段,不影响 LLM 等配置: ```rust pub fn write_behaviors(behaviors: Vec) { let conf = Self::read(); let mut json = serde_json::to_value(&conf).unwrap(); json["behaviors"] = serde_json::to_value(&behaviors).unwrap(); std::fs::write(Self::file_path(), serde_json::to_string_pretty(&json).unwrap()); } ``` ### 用户使用 右键托盘 → **健康提醒** → 打开管理面板。支持增删改查实时同步到后端调度器。 --- ## 四、情绪引擎 → Live2D 动作/表情映射 ### 架构 ``` 后端 emotion.rs 前端 index.vue ┌─────────────────┐ ┌──────────────────────────┐ │ EmotionEngine │──心跳→ │ ezvibe:emotion/heartbeat │ │ Idle/Happy/... │ │ ↓ │ └─────────────────┘ │ applyEmotion(state) │ │ ├─ motion 动作 │ │ └─ expression 表情 │ └──────────────────────────┘ ``` ### 关键实现 | 功能 | 位置 | 说明 | |------|------|------| | 模型能力发现 | `discoverModelCapabilities()` | 从 model3.json 读取表达式列表和动作组 | | 情绪→表情映射 | `applyEmotion()` | 关键词匹配(Happy→星星/比耶, Annoyed→黑脸等) | | 动作匹配 | `fuzzyMatch()` | 精确/包含/反向三种策略 | | 事件监听 | `onMounted` | 监听 `ezvibe:emotion` + `ezvibe:heartbeat` | ### 表达式文件路径修复 March 7th 模型的表达式文件在 `exp/` 子目录,但 model3.json 引用路径不含子目录前缀。`pixi-live2d-display` v0.4.0 无法正确加载,导致 `model.expression()` 静默失败。 **修复**:用 `fetch` 直接从 `exp/{n}.exp3.json` 加载表达式文件,调用 `coreModel.setParameterValueById()` 直接操作 Cubism 4 参数。 ### 表达式重置 每个表达式只改 1 个参数(如 Param27),切换时需先清除上一个表达式的参数: ``` 上一个表达式 → setParameterValueById(Param27, 0) // 清掉 resetExpression() // 原生重置 新表达式 → setParameterValueById(Param28, 0.5) // 应用 ``` 记录 `lastExpressionParamIds` 数组,每次切前先清掉所有上一个涉及的参数 ID。 --- ## 五、前景遮罩修复 `tryAddFrame()` 中的前景 Sprite 使用 `stage.addChildAt(foreground, 0)` 添加到 index 0(最底层),确保模型始终在上方接收事件。 --- ## 六、配置窗口重建 托盘 "配置中心" / "健康提醒" 点击时先关闭旧窗口再重建,确保 Vue 组件重新挂载并从后端重新读取配置。 --- ## 七、新增 Tauri 命令 | 命令 | 功能 | |------|------| | `list_behaviors` | 列出所有提醒行为 | | `add_behavior` | 添加提醒行为 | | `update_behavior` | 更新提醒行为(支持改名) | | `delete_behavior` | 删除提醒行为 | | `debug_log` | 前端日志透传到终端 stderr |