import 'package:device_plugin_interface/device_plugin_interface.dart'; import 'package:get/get.dart'; import '../../devices/device_hub.dart'; import '../../devices/device_vendors.dart'; import '../models/agent_module.dart'; import '../models/device_category.dart'; import 'agent_module_service.dart'; /// 设备品类**首次连接成功**时,把该品类的默认功能铺到 AI 页。 /// /// 挂在 [DeviceHub.sessions] 上,谁上线就按厂商 → 品类映射铺一次; /// 「只铺一次」由 [AgentModuleService.applyDeviceDefaults] 记录。 class AgentDefaults { AgentDefaults._(); static const Map _vendorCategory = { DeviceVendors.bes: DeviceCategories.earphone, DeviceVendors.holder: DeviceCategories.phoneHolder, DeviceVendors.eaicar: DeviceCategories.carAroma, }; /// 厂商 → 品类。设备按键唤醒时用它找该接哪个 agent(见 /// `DeviceAiSessionService._resolveAgentAppId`)。 static String? categoryForVendor(String vendor) => _vendorCategory[vendor]; /// 这个品类默认该由哪个 agent 接管对话 —— 取默认编排里的第一个模块。 /// 找不到返回 null,调用方回退到全局默认(EMAI)。 static String? primaryModuleForVendor(String vendor) { final cat = _vendorCategory[vendor]; if (cat == null) return null; final list = AgentModules.defaultsForCategory(cat); return list.isEmpty ? null : list.first; } /// 底部中间「AI」按钮该打开哪个对话助手(2026-09-24)。 /// /// 按「当前连着的设备,没连就最近连过的那台」→ 品类 → 该品类默认编排里**第一个对话助手** /// ([AgentModuleService.assistantIds] 里的模块)。车载香薰返回 EaiCar 车载助手, /// 耳机/支架的默认编排里没有专属助手,返回 null = 走通用入口(EMAI,或后台配了移动精灵时走它)。 /// /// ⚠️ 与 [primaryModuleForVendor] 不同:那个取「默认编排第一个模块」,给设备按键唤醒用; /// 支架的第一个是语音纪要,拿它开对话页是不对的,所以这里只在助手里挑。 static String? assistantModuleForCurrentDevice() { if (!Get.isRegistered()) return null; final hub = DeviceHub.to; final String vendor = hub.sessions.isNotEmpty ? hub.sessions.first.vendor : hub.lastKnownVendor; final cat = _vendorCategory[vendor]; if (cat == null) return null; for (final id in AgentModules.defaultsForCategory(cat)) { if (AgentModuleService.assistantIds.contains(id)) return id; } return null; } static Worker? _worker; static void bind(DeviceHub hub) { _worker?.dispose(); _worker = ever>(hub.sessions, (_) => _apply(hub)); // ⚠️ 还要对**当前状态**跑一次:GetX 的 ever 只在之后的赋值上触发, // 拿不到绑定那一刻已经在的值。设备是被动回连上来的(原生扫到广播就接), // 完全可能赶在 bind 之前就绪 —— 那样 AI 页这一整轮都不会按设备铺编排, // 而且不报任何错,表现就是「设备明明连着,功能却是全量的」。 _apply(hub); } /// 按「当前连着的设备,没有就按最近连过的那台」铺默认编排。 /// /// ⚠️ 断开后必须回退到 [DeviceHub.lastKnownVendor],不能什么都不做: /// 设备页的产品图和「设备管理」在断连后仍然显示最近那台设备,AI 页却停在 /// 上一台设备铺下的编排里 —— 于是出现「图是 Echo-one 耳机、功能却是车载香薰 /// 那三个」。三处必须同一个口径,见 DeviceHub._lastKnown。 static void _apply(DeviceHub hub) { final list = hub.sessions; if (list.isNotEmpty) { for (final s in list) { final cat = _vendorCategory[s.vendor]; if (cat != null) applyAgentDefaultsFor(cat); } return; } final cat = _vendorCategory[hub.lastKnownVendor]; if (cat != null) applyAgentDefaultsFor(cat); } static void applyAgentDefaultsFor(String categoryId) { try { final service = Get.isRegistered() ? AgentModuleService.to : Get.put(AgentModuleService(), permanent: true); service.applyDeviceDefaults(categoryId); } catch (_) { // 铺默认功能失败不能影响连接本身 } } }