From ce68c83eead4942d2027adc1a5245cefe77c3580 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Tue, 26 May 2026 16:37:39 +0800 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E4=BA=92=E7=9B=B8=E9=80=9A=E8=AE=AF=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20feat:=20=E6=96=B0=E5=A2=9E=E9=80=9A=E8=AE=AF=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/xiaozhi/common/redis/RedisKeys.java | 7 + .../modules/config/init/SystemInitConfig.java | 7 + .../device/controller/DeviceController.java | 40 +- .../device/dao/DeviceAddressBookDao.java | 29 + .../device/dto/DeviceAddressBookAliasDTO.java | 21 + .../dto/DeviceAddressBookPermissionDTO.java | 21 + .../entity/DeviceAddressBookEntity.java | 49 + .../service/DeviceAddressBookService.java | 47 + .../impl/DeviceAddressBookServiceImpl.java | 147 +++ .../modules/security/config/ShiroConfig.java | 1 + .../resources/db/changelog/202605251425.sql | 31 + .../db/changelog/db.changelog-master.yaml | 7 + .../mapper/device/DeviceAddressBookDao.xml | 28 + main/manager-web/src/apis/api.js | 4 +- .../src/apis/module/addressBook.js | 60 + .../assets/device-avatars/xiaozhi-logo1.png | Bin 0 -> 12367 bytes .../assets/device-avatars/xiaozhi-logo10.png | Bin 0 -> 11551 bytes .../assets/device-avatars/xiaozhi-logo11.png | Bin 0 -> 12206 bytes .../assets/device-avatars/xiaozhi-logo12.png | Bin 0 -> 12179 bytes .../assets/device-avatars/xiaozhi-logo13.png | Bin 0 -> 11814 bytes .../assets/device-avatars/xiaozhi-logo14.png | Bin 0 -> 13687 bytes .../assets/device-avatars/xiaozhi-logo15.png | Bin 0 -> 11859 bytes .../assets/device-avatars/xiaozhi-logo16.png | Bin 0 -> 13372 bytes .../assets/device-avatars/xiaozhi-logo2.png | Bin 0 -> 12063 bytes .../assets/device-avatars/xiaozhi-logo3.png | Bin 0 -> 11849 bytes .../assets/device-avatars/xiaozhi-logo4.png | Bin 0 -> 12646 bytes .../assets/device-avatars/xiaozhi-logo5.png | Bin 0 -> 12346 bytes .../assets/device-avatars/xiaozhi-logo6.png | Bin 0 -> 12406 bytes .../assets/device-avatars/xiaozhi-logo7.png | Bin 0 -> 12731 bytes .../assets/device-avatars/xiaozhi-logo8.png | Bin 0 -> 13020 bytes .../assets/device-avatars/xiaozhi-logo9.png | Bin 0 -> 13656 bytes .../src/components/AddressBookDialog.vue | 174 +++ main/manager-web/src/components/HeaderBar.vue | 11 + main/manager-web/src/i18n/de.js | 35 + main/manager-web/src/i18n/en.js | 35 + main/manager-web/src/i18n/pt_BR.js | 35 + main/manager-web/src/i18n/vi.js | 35 + main/manager-web/src/i18n/zh_CN.js | 106 ++ main/manager-web/src/i18n/zh_TW.js | 106 ++ main/manager-web/src/router/index.js | 14 +- main/manager-web/src/utils/featureManager.js | 10 +- .../src/views/AddressBookManagement.vue | 1151 +++++++++++++++++ .../src/views/FeatureManagement.vue | 2 +- main/xiaozhi-server/config/config_loader.py | 1 + .../config/manage_api_client.py | 14 + main/xiaozhi-server/core/connection.py | 3 + .../core/handle/sendAudioHandle.py | 3 +- .../plugins_func/functions/call_device.py | 134 ++ 48 files changed, 2361 insertions(+), 7 deletions(-) create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceAddressBookDao.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceAddressBookAliasDTO.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceAddressBookPermissionDTO.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceAddressBookEntity.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceAddressBookService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImpl.java create mode 100644 main/manager-api/src/main/resources/db/changelog/202605251425.sql create mode 100644 main/manager-api/src/main/resources/mapper/device/DeviceAddressBookDao.xml create mode 100644 main/manager-web/src/apis/module/addressBook.js create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo1.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo10.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo11.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo12.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo13.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo14.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo15.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo16.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo2.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo3.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo4.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo5.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo6.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo7.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo8.png create mode 100644 main/manager-web/src/assets/device-avatars/xiaozhi-logo9.png create mode 100644 main/manager-web/src/components/AddressBookDialog.vue create mode 100644 main/manager-web/src/views/AddressBookManagement.vue create mode 100644 main/xiaozhi-server/plugins_func/functions/call_device.py diff --git a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java index 6691e395..c37ba615 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java +++ b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java @@ -188,4 +188,11 @@ public class RedisKeys { return "ota:upload:count:" + username; } + /** + * 设备通讯录缓存Key + */ + public static String getAddressBookKey() { + return "device:address_book:all"; + } + } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/init/SystemInitConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/config/init/SystemInitConfig.java index e47cd0c7..5c7fc7f9 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/config/init/SystemInitConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/init/SystemInitConfig.java @@ -9,6 +9,7 @@ import xiaozhi.common.constant.Constant; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.modules.config.service.ConfigService; +import xiaozhi.modules.device.service.DeviceAddressBookService; import xiaozhi.modules.sys.service.SysParamsService; @Configuration @@ -24,6 +25,9 @@ public class SystemInitConfig { @Autowired private RedisUtils redisUtils; + @Autowired + private DeviceAddressBookService deviceAddressBookService; + @PostConstruct public void init() { // 检查版本号 @@ -37,5 +41,8 @@ public class SystemInitConfig { sysParamsService.initServerSecret(); configService.getConfig(false); + + // 初始化设备通讯录缓存 + deviceAddressBookService.refreshCache(); } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java index 629ba80f..2b1b5ca8 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java @@ -22,12 +22,15 @@ import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.user.UserDetail; import xiaozhi.common.utils.Result; +import xiaozhi.modules.device.dto.DeviceAddressBookAliasDTO; +import xiaozhi.modules.device.dto.DeviceAddressBookPermissionDTO; import xiaozhi.modules.device.dto.DeviceManualAddDTO; import xiaozhi.modules.device.dto.DeviceRegisterDTO; import xiaozhi.modules.device.dto.DeviceToolsCallReqDTO; import xiaozhi.modules.device.dto.DeviceUnBindDTO; import xiaozhi.modules.device.dto.DeviceUpdateDTO; import xiaozhi.modules.device.entity.DeviceEntity; +import xiaozhi.modules.device.service.DeviceAddressBookService; import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.security.user.SecurityUser; import xiaozhi.modules.sys.service.SysParamsService; @@ -37,11 +40,13 @@ import xiaozhi.modules.sys.service.SysParamsService; @RequestMapping("/device") public class DeviceController { private final DeviceService deviceService; + private final DeviceAddressBookService deviceAddressBookService; private final RedisUtils redisUtils; private final SysParamsService sysParamsService; - public DeviceController(DeviceService deviceService, RedisUtils redisUtils, SysParamsService sysParamsService) { + public DeviceController(DeviceService deviceService, DeviceAddressBookService deviceAddressBookService, RedisUtils redisUtils, SysParamsService sysParamsService) { this.deviceService = deviceService; + this.deviceAddressBookService = deviceAddressBookService; this.redisUtils = redisUtils; this.sysParamsService = sysParamsService; } @@ -159,4 +164,37 @@ public class DeviceController { response.setMsg("Tools called successfully"); return response.ok(result); } + + @GetMapping("/address-book/{macAddress}") + @Operation(summary = "获取设备通讯录") + @RequiresPermissions("sys:role:normal") + public Result getAddressBook(@PathVariable String macAddress) { + return new Result().ok(deviceAddressBookService.getAddressBookList(macAddress)); + } + + @GetMapping("/address-book/lookup") + @Operation(summary = "根据昵称查找目标设备") + public Result> lookupByNickname(String callerMac, String nickname) { + Map result = deviceAddressBookService.lookupByNickname(callerMac, nickname); + if (result == null) { + return new Result>().error("未找到对应设备"); + } + return new Result>().ok(result); + } + + @PutMapping("/address-book/alias") + @Operation(summary = "更新设备通讯录别名") + @RequiresPermissions("sys:role:normal") + public Result updateAlias(@Valid @RequestBody DeviceAddressBookAliasDTO dto) { + deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), dto.getAlias(), null); + return new Result(); + } + + @PutMapping("/address-book/permission") + @Operation(summary = "更新设备通讯录权限") + @RequiresPermissions("sys:role:normal") + public Result updatePermission(@Valid @RequestBody DeviceAddressBookPermissionDTO dto) { + deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), null, dto.getHasPermission()); + return new Result(); + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceAddressBookDao.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceAddressBookDao.java new file mode 100644 index 00000000..43601f6b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceAddressBookDao.java @@ -0,0 +1,29 @@ +package xiaozhi.modules.device.dao; + +import java.util.List; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import xiaozhi.modules.device.entity.DeviceAddressBookEntity; + +@Mapper +public interface DeviceAddressBookDao extends BaseMapper { + + /** + * 获取设备通讯录列表 + */ + List getAddressBookList(@Param("macAddress") String macAddress); + + /** + * 更新别名 + */ + void updateAlias(@Param("macAddress") String macAddress, @Param("targetMac") String targetMac, @Param("alias") String alias); + + /** + * 更新权限 + */ + void updatePermission(@Param("macAddress") String macAddress, @Param("targetMac") String targetMac, @Param("hasPermission") Boolean hasPermission); +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceAddressBookAliasDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceAddressBookAliasDTO.java new file mode 100644 index 00000000..ef1ffe0b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceAddressBookAliasDTO.java @@ -0,0 +1,21 @@ +package xiaozhi.modules.device.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "更新设备通讯录别名") +public class DeviceAddressBookAliasDTO { + + @NotBlank(message = "MAC地址不能为空") + @Schema(description = "本设备MAC地址") + private String macAddress; + + @NotBlank(message = "目标MAC地址不能为空") + @Schema(description = "对方设备MAC地址") + private String targetMac; + + @Schema(description = "我对对方的称呼") + private String alias; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceAddressBookPermissionDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceAddressBookPermissionDTO.java new file mode 100644 index 00000000..945f1085 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceAddressBookPermissionDTO.java @@ -0,0 +1,21 @@ +package xiaozhi.modules.device.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "更新设备通讯录权限") +public class DeviceAddressBookPermissionDTO { + + @NotBlank(message = "MAC地址不能为空") + @Schema(description = "本设备MAC地址") + private String macAddress; + + @NotBlank(message = "目标MAC地址不能为空") + @Schema(description = "对方设备MAC地址") + private String targetMac; + + @Schema(description = "是否有权限呼叫") + private Boolean hasPermission; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceAddressBookEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceAddressBookEntity.java new file mode 100644 index 00000000..7c8bf007 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceAddressBookEntity.java @@ -0,0 +1,49 @@ +package xiaozhi.modules.device.entity; + +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("ai_device_address_book") +@Schema(description = "设备通讯录") +public class DeviceAddressBookEntity { + + @TableId(type = IdType.INPUT) + @Schema(description = "本设备MAC地址") + private String macAddress; + + @Schema(description = "对方设备MAC地址") + private String targetMac; + + @Schema(description = "我对对方的称呼") + private String alias; + + @Schema(description = "是否有权限呼叫") + private Boolean hasPermission; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建人") + private Long creator; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建时间") + private Date createDate; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "更新人") + private Long updater; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "更新时间") + private Date updateDate; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceAddressBookService.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceAddressBookService.java new file mode 100644 index 00000000..8d91fc5b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceAddressBookService.java @@ -0,0 +1,47 @@ +package xiaozhi.modules.device.service; + +import java.util.List; +import java.util.Map; + +import xiaozhi.modules.device.entity.DeviceAddressBookEntity; + +public interface DeviceAddressBookService { + + /** + * 获取设备通讯录列表 + */ + List getAddressBookList(String macAddress); + + /** + * 获取所有设备的通讯录(全局缓存用) + */ + Map> getAllAddressBooks(); + + /** + * 更新别名 + */ + void updateAlias(String macAddress, String targetMac, String alias); + + /** + * 更新权限 + */ + void updatePermission(String macAddress, String targetMac, Boolean hasPermission); + + /** + * 添加或更新通讯录记录 + */ + void saveOrUpdate(String macAddress, String targetMac, String alias, Boolean hasPermission); + + /** + * 刷新通讯录缓存 + */ + void refreshCache(); + + /** + * 根据昵称查找目标设备信息 + * @param callerMac 主叫方MAC地址 + * @param nickname 被叫方昵称 + * @return {targetMac: 目标MAC, callerNickname: 目标如何称呼主叫方} + */ + Map lookupByNickname(String callerMac, String nickname); +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImpl.java new file mode 100644 index 00000000..7b6e3e8b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImpl.java @@ -0,0 +1,147 @@ +package xiaozhi.modules.device.service.impl; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.modules.device.dao.DeviceAddressBookDao; +import xiaozhi.modules.device.entity.DeviceAddressBookEntity; +import xiaozhi.modules.device.service.DeviceAddressBookService; + +@Service +public class DeviceAddressBookServiceImpl implements DeviceAddressBookService { + + private final DeviceAddressBookDao deviceAddressBookDao; + private final RedisUtils redisUtils; + + public DeviceAddressBookServiceImpl(DeviceAddressBookDao deviceAddressBookDao, RedisUtils redisUtils) { + this.deviceAddressBookDao = deviceAddressBookDao; + this.redisUtils = redisUtils; + } + + @Override + public List getAddressBookList(String macAddress) { + return deviceAddressBookDao.getAddressBookList(macAddress); + } + + @Override + @SuppressWarnings("unchecked") + public Map> getAllAddressBooks() { + Object cached = redisUtils.get(RedisKeys.getAddressBookKey()); + if (cached != null) { + return (Map>) cached; + } + refreshCache(); + return (Map>) redisUtils.get(RedisKeys.getAddressBookKey()); + } + + @Override + public Map lookupByNickname(String callerMac, String nickname) { + Map> allBooks = getAllAddressBooks(); + Map callerBook = allBooks.get(callerMac.toLowerCase()); + if (callerBook == null) { + return null; + } + // 从缓存获取 targetMac,格式: "mac|permission" + String targetMacWithPerm = callerBook.get(nickname); + if (targetMacWithPerm == null) { + return null; + } + // 解析 targetMac 和 permission + String[] parts = targetMacWithPerm.split("\\|"); + String targetMac = parts[0]; + boolean hasPermission = parts.length > 1 && "1".equals(parts[1]); + + Map targetBook = allBooks.get(targetMac.toLowerCase()); + if (targetBook == null) { + return null; + } + // 检查双向关系:目标设备是否也添加了主叫方为联系人 + String callerNickname = targetBook.get(callerMac.toLowerCase()); + Map result = new HashMap<>(); + result.put("targetMac", targetMac); + result.put("callerNickname", callerNickname); + result.put("hasPermission", hasPermission ? "true" : "false"); + return result; + } + + @Override + public void refreshCache() { + Map> result = new HashMap<>(); + List allRecords = deviceAddressBookDao.selectList(null); + Map reverseMap = new HashMap<>(); + Map permissionMap = new HashMap<>(); + for (DeviceAddressBookEntity entity : allRecords) { + String macA = entity.getMacAddress().toLowerCase(); + String macB = entity.getTargetMac().toLowerCase(); + String alias = entity.getAlias(); + Boolean hasPermission = entity.getHasPermission(); + if (alias != null && !alias.isEmpty()) { + // 反向记录: (macB, macA) = B对A的称呼 + reverseMap.put(macB + ":" + macA, alias); + } + // 记录 A 对 B 是否有权限呼叫 + permissionMap.put(macA + ":" + macB, hasPermission != null && hasPermission); + } + for (DeviceAddressBookEntity entity : allRecords) { + String macA = entity.getMacAddress().toLowerCase(); + String macB = entity.getTargetMac().toLowerCase(); + String aliasAtoB = entity.getAlias(); + result.computeIfAbsent(macA, k -> new HashMap<>()); + result.computeIfAbsent(macB, k -> new HashMap<>()); + if (aliasAtoB != null && !aliasAtoB.isEmpty()) { + // A对B的映射: nickname -> macB|permission + Boolean perm = entity.getHasPermission(); + String permStr = (perm != null && perm) ? "1" : "0"; + result.get(macA).put(aliasAtoB, macB + "|" + permStr); + } + // B对A的映射: macA -> nickname(查反向记录) + String aliasBtoA = reverseMap.get(macA + ":" + macB); + if (aliasBtoA != null && !aliasBtoA.isEmpty()) { + result.get(macB).put(macA, aliasBtoA); + } + } + redisUtils.set(RedisKeys.getAddressBookKey(), result); + } + + @Override + public void updateAlias(String macAddress, String targetMac, String alias) { + deviceAddressBookDao.updateAlias(macAddress, targetMac, alias); + refreshCache(); + } + + @Override + public void updatePermission(String macAddress, String targetMac, Boolean hasPermission) { + deviceAddressBookDao.updatePermission(macAddress, targetMac, hasPermission); + refreshCache(); + } + + @Override + public void saveOrUpdate(String macAddress, String targetMac, String alias, Boolean hasPermission) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("mac_address", macAddress).eq("target_mac", targetMac); + DeviceAddressBookEntity record = deviceAddressBookDao.selectOne(wrapper); + if (record == null) { + DeviceAddressBookEntity entity = new DeviceAddressBookEntity(); + entity.setMacAddress(macAddress); + entity.setTargetMac(targetMac); + entity.setAlias(alias); + entity.setHasPermission(hasPermission); + deviceAddressBookDao.insert(entity); + } else { + if (alias != null) { + deviceAddressBookDao.updateAlias(macAddress, targetMac, alias); + } + if (hasPermission != null) { + deviceAddressBookDao.updatePermission(macAddress, targetMac, hasPermission); + } + } + refreshCache(); + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java index 80b93c6d..070af9a6 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java @@ -87,6 +87,7 @@ public class ShiroConfig { filterMap.put("/user/retrieve-password", "anon"); // 将config路径使用server服务过滤器 filterMap.put("/config/**", "server"); + filterMap.put("/device/address-book/lookup", "server"); filterMap.put("/agent/chat-history/report", "server"); filterMap.put("/agent/chat-history/download/**", "anon"); filterMap.put("/agent/chat-summary/**", "server"); diff --git a/main/manager-api/src/main/resources/db/changelog/202605251425.sql b/main/manager-api/src/main/resources/db/changelog/202605251425.sql new file mode 100644 index 00000000..feaebec4 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202605251425.sql @@ -0,0 +1,31 @@ +-- 新增呼叫设备工具配置 +SET @data_exists = (SELECT COUNT(*) FROM ai_model_provider WHERE id = 'SYSTEM_PLUGIN_CALL_DEVICE'); +SET @sql = IF(@data_exists = 0, + 'INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, +`update_date`) VALUES (''SYSTEM_PLUGIN_CALL_DEVICE'', ''Plugin'', ''call_device'', ''呼叫设备'', ''[{"key":"gateway_url","label":"网关地址","type": +"string","default":"http://127.0.0.1:8007"},{"key":"gateway_secret","label":"网关密钥","type":"string"}]'', 85, 1988490863118454785, ''2026-05-18 +12:00:00'', 1988490863118454785, ''2026-05-18 12:00:00'')', + 'SELECT ''data already exists, skip'' AS msg'); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +-- 更新系统菜单配置,增加联系人管理菜单 +UPDATE sys_params +SET param_value = REPLACE( + param_value, + '"mcpAccessPoint":{"name":"feature.mcpAccessPoint.name","enabled":true,"description":"feature.mcpAccessPoint.description"}', + '"mcpAccessPoint":{"name":"feature.mcpAccessPoint.name","enabled":true,"description":"feature.mcpAccessPoint.description"},"addressBook":{"name":"feature.addressBook.name","enabled":false,"description":"feature.addressBook.description"}' +) +WHERE param_code = 'system-web.menu'; + +-- 创建设备通讯录表 +CREATE TABLE IF NOT EXISTS `ai_device_address_book` ( + `mac_address` VARCHAR(64) NOT NULL COMMENT '本设备MAC地址', + `target_mac` VARCHAR(64) NOT NULL COMMENT '对方设备MAC地址', + `alias` VARCHAR(64) DEFAULT NULL COMMENT '别名', + `has_permission` TINYINT(1) DEFAULT TRUE COMMENT '是否有权限呼叫', + `creator` BIGINT DEFAULT NULL COMMENT '创建人', + `create_date` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` BIGINT DEFAULT NULL COMMENT '更新人', + `update_date` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`mac_address`, `target_mac`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='设备通讯录表'; \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index ee1d9109..26ed66cd 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -669,3 +669,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202605121509.sql + - changeSet: + id: 202605251425 + author: RanChen + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202605251425.sql diff --git a/main/manager-api/src/main/resources/mapper/device/DeviceAddressBookDao.xml b/main/manager-api/src/main/resources/mapper/device/DeviceAddressBookDao.xml new file mode 100644 index 00000000..cc4476a4 --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/device/DeviceAddressBookDao.xml @@ -0,0 +1,28 @@ + + + + + + + + UPDATE ai_device_address_book + SET alias = #{alias}, update_date = NOW() + WHERE mac_address = #{macAddress} AND target_mac = #{targetMac} + + + + UPDATE ai_device_address_book + SET has_permission = #{hasPermission}, update_date = NOW() + WHERE mac_address = #{macAddress} AND target_mac = #{targetMac} + + + + INSERT INTO ai_device_address_book (mac_address, target_mac, alias, has_permission, creator, create_date, updater, update_date) + VALUES (#{macAddress}, #{targetMac}, #{alias}, #{hasPermission}, #{creator}, NOW(), #{updater}, NOW()) + + \ No newline at end of file diff --git a/main/manager-web/src/apis/api.js b/main/manager-web/src/apis/api.js index 1ec27302..759ee8a7 100755 --- a/main/manager-web/src/apis/api.js +++ b/main/manager-web/src/apis/api.js @@ -11,6 +11,7 @@ import voiceClone from './module/voiceClone.js' import voiceResource from './module/voiceResource.js' import knowledgeBase from './module/knowledgeBase.js' import correctWord from './module/correctWord.js' +import addressBook from './module/addressBook.js' @@ -43,5 +44,6 @@ export default { voiceResource, voiceClone, knowledgeBase, - correctWord + correctWord, + addressBook } diff --git a/main/manager-web/src/apis/module/addressBook.js b/main/manager-web/src/apis/module/addressBook.js new file mode 100644 index 00000000..a9498c6c --- /dev/null +++ b/main/manager-web/src/apis/module/addressBook.js @@ -0,0 +1,60 @@ +import { getServiceUrl } from '../api'; +import RequestService from '../httpRequest'; + +export default { + /** + * 获取设备通讯录列表 + */ + getAddressBookList(macAddress, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/device/address-book/${macAddress}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.getAddressBookList(macAddress, callback); + }); + }).send(); + }, + + /** + * 更新设备通讯录别名 + */ + updateAlias(data, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/device/address-book/alias`) + .method('PUT') + .data(data) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.updateAlias(data, callback); + }); + }).send(); + }, + + /** + * 更新设备通讯录权限 + */ + updatePermission(data, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/device/address-book/permission`) + .method('PUT') + .data(data) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.updatePermission(data, callback); + }); + }).send(); + } +}; \ No newline at end of file diff --git a/main/manager-web/src/assets/device-avatars/xiaozhi-logo1.png b/main/manager-web/src/assets/device-avatars/xiaozhi-logo1.png new file mode 100644 index 0000000000000000000000000000000000000000..117a849866475895abcc16a32eb307fb2f459a97 GIT binary patch literal 12367 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 B5B~rF literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/device-avatars/xiaozhi-logo10.png b/main/manager-web/src/assets/device-avatars/xiaozhi-logo10.png new file mode 100644 index 0000000000000000000000000000000000000000..ebb4912fa0fb98afa817527bd06b0b427549d8ce GIT binary patch literal 11551 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 literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/device-avatars/xiaozhi-logo12.png b/main/manager-web/src/assets/device-avatars/xiaozhi-logo12.png new file mode 100644 index 0000000000000000000000000000000000000000..a81ba960a756687083e87a169d2f434f9a33bacf GIT binary patch literal 12179 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 V-c=l|u8IHv002ovPDHLkV1m91cgg?& literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/device-avatars/xiaozhi-logo13.png b/main/manager-web/src/assets/device-avatars/xiaozhi-logo13.png new file mode 100644 index 0000000000000000000000000000000000000000..31ac4f8c74fb51d9b93fa1dbfb730fabf98990a1 GIT binary patch literal 11814 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 VIDMc&IbHw&002ovPDHLkV1j=IHQ@jN literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/device-avatars/xiaozhi-logo15.png b/main/manager-web/src/assets/device-avatars/xiaozhi-logo15.png new file mode 100644 index 0000000000000000000000000000000000000000..9a82fab5240442d116368a664a1cad4389d8c4b8 GIT binary patch literal 11859 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 N002ovPDHLkV1l~JEcE~Y literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/device-avatars/xiaozhi-logo16.png b/main/manager-web/src/assets/device-avatars/xiaozhi-logo16.png new file mode 100644 index 0000000000000000000000000000000000000000..9e664970185207015dbc1daf44685c2136d7842a GIT binary patch literal 13372 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 U(FZ^vMF0Q*07*qoM6N<$f=Z48^8f$< literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/device-avatars/xiaozhi-logo5.png b/main/manager-web/src/assets/device-avatars/xiaozhi-logo5.png new file mode 100644 index 0000000000000000000000000000000000000000..db3dc6e97c0233a6a9a03a9e7862963626a6916b GIT binary patch literal 12346 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 literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/device-avatars/xiaozhi-logo6.png b/main/manager-web/src/assets/device-avatars/xiaozhi-logo6.png new file mode 100644 index 0000000000000000000000000000000000000000..b2f75ee880c8aea56d99d042dac996f9edd083c0 GIT binary patch literal 12406 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 literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/device-avatars/xiaozhi-logo7.png b/main/manager-web/src/assets/device-avatars/xiaozhi-logo7.png new file mode 100644 index 0000000000000000000000000000000000000000..3ea7baf4337c5ce43b100e8346700d670bdc62c9 GIT binary patch literal 12731 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 BoqhlS literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/device-avatars/xiaozhi-logo8.png b/main/manager-web/src/assets/device-avatars/xiaozhi-logo8.png new file mode 100644 index 0000000000000000000000000000000000000000..b0751cda08acbe77bd7561811d3b4f90faad928a GIT binary patch literal 13020 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 ev0}x_a^w$=fH0000W{w*375`T6|*{{Hy)`}Xzv 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

Onbw*ASevIV?D@KWM>`hH*N2tD!oY(N zVl6b8O`IVU)isf0oI!+5bn&H^88bM&N0O|+4Q`} zL%^s*`*qO(&Rb}12e4fn&cvH#h7fAoDGNDAMg^8~2%UPQc2WlTa~OR;f1sF0`Nf-8 zZ^Bz51M_pgUF?&*9fgI~`=9odyEi`F^zIhy*#U6gLjB2hCBpqC2;(74P(lne&cdZ? zPGXIz9kr9a=Y4%6C#K?Jq%6?4YBfF4ACRB>{bG#I9pKR?&;1)+p7N@urkc*|^hN&* zFysmmA%w%tXX4FDh+3jjM2es(Crj$&LJ&6f&56;`6Z= zF85*fq2TNByzso+T2Q_#XNk57WPY+a{O_6)2_f1+tmo&%b}GDtz~>e&vHO- zp#t6I-L^i^MDZ|EN}3efLOF0tW+R@k`>OE zML5ob4-2%_8^N9-I~fqX;L8I~^W$@d-v;2r00V0dty!@~2hh3i_OWv%86o~KgisuY z(Ifa>=t!g~l2Ixpbhh{*lR&~P6guM3{4hX#&el(Rjw}zJeY2rY`kn>J=L;O)xNKQ9 zU!i?Z-`y$EjeJJ)!>|{`GtuESn<2~-C6X&;#jx*n5(gc|*SNd2HR2thLlCc%;p}pL zpA$6p91P~Ugw*Gea=GRdQVnU)!^1ZVDWXh% zAfYTy!hXUbt|ML~!nQu!{^;_Br+xY9b#!z%ovv6`VcS%>-GPlqpFMMiukQ#g+V&yI zUL{B-QvjCgu1iNS!-BA9c;jwq=^JkJT={&p-+ zj8D~K)kgsihh3rNCof&vw(a@z=K@&5CZr%_2jZD7%g37CM1)Zy5g2(6|4u9plau2* z8lE?D<3{9bGkc;}y?+}rIlt>Jc6F#Q{k=xyg?GD>aSV}&!YGx+0VX3H*MW!O-Dtz| z#517{vCn%tqz~g$JX*^b)8A~wWFfqh(=8u2?*@@Y$ltaZ0Vj3DYik==w|>*6wUrI- z_P(&>UEdM&C{0%Q{%YeUrw&6H?>(H*yUfG!_(~dSm{KZLTqbcshs6mVHr{Q%i4&-= z!TtCJqrr#&M;)GQQ_I9rroY{=DY)MEGU1tSJ=w#FK#E8rJY2+Cg7&?Rc!N*YSF!Q= ze3fp;o^S`xxwl=(dTLj~Hw2AM^`dyZw8u+;_BH%VK^70{tD9&g#$58VTcJ3PxnXIBT289m_>O7Ew97iuCxt)z17^%P}Picr;iE- z_c{wakJeB(agu2W8ynAO&gm#;j?gKe6kH>V@GrulD^~^wZf%+00^VACgUWTm*be4% zIdJSLidyHlnTECzQ@F1uPU}>!*OQib1eoG(U`awI8hmx@CfjS?&h~8esVec{s}6VU zuMaSuN2~PX>M+y|8_RgCIIUA1@6+ziSj=(3k|t?;fXU6O)tXx7SFKySzEVXXvSOiU zuJ)jZO;7Rp>ujS;WzaYpq9pFvp|iTXG8Rj}d9cveuLA=EU!H7QH?LMxaKG0%?2f2d zo3fbO?FeX$=TTIDIgC#<0X$?jgGdY274eTeD)2Zc`jRTSKqLxG=k>+JG z$@^WM=(t-^$OReMq>k~dwn?Ucp-EHw`{CojBw}0GcRqZGi-m(+0!KQ|^C-1eTiIOp z{=_)L5{)t-)1hvr!}CMO)jY3I7Ci0W((y)(nVw0fb2%pL?(XK-`p{O5d`*h + + + + + + + + + + + + +

+ + + + + + \ No newline at end of file diff --git a/main/manager-web/src/components/HeaderBar.vue b/main/manager-web/src/components/HeaderBar.vue index 80139f2a..6c1c09eb 100644 --- a/main/manager-web/src/components/HeaderBar.vue +++ b/main/manager-web/src/components/HeaderBar.vue @@ -81,6 +81,15 @@ }" /> {{ $t("header.knowledgeBase") }} +
+ + {{ $t("header.addressBook") }} +
({ voiceClone: state.pubConfig.systemWebMenu?.features?.voiceClone?.enabled, // 音色克隆功能状态 knowledgeBase: state.pubConfig.systemWebMenu?.features?.knowledgeBase?.enabled, // 知识库功能状态 + addressBook: state.pubConfig.systemWebMenu?.features?.addressBook?.enabled, // 通讯录功能状态 }), userInfo: (state) => state.userInfo, }), diff --git a/main/manager-web/src/i18n/de.js b/main/manager-web/src/i18n/de.js index 926133e1..4bc7fbd5 100644 --- a/main/manager-web/src/i18n/de.js +++ b/main/manager-web/src/i18n/de.js @@ -1416,4 +1416,39 @@ export default { 'feature.asr.name': 'Spracherkennung', 'feature.asr.description': 'Sprache in Text umwandeln, um natürliche Sprachinteraktionsfunktionalität zu ermöglichen', + // Address Book Management page + 'addressBookManagement.mainTitle': 'Adressbuchverwaltung', + 'addressBookManagement.subTitle': 'Geräteadressbuch verwalten, Gerätenamen und Anrufpermissions festlegen', + 'addressBookManagement.online': 'Online', + 'addressBookManagement.offline': 'Offline', + 'addressBookManagement.macAddress': 'MAC-Adresse', + 'addressBookManagement.status': 'Status', + 'addressBookManagement.deviceGroup': 'Gerätegruppe', + 'addressBookManagement.addTime': 'Hinzugefügt Zeit', + 'addressBookManagement.lastOnline': 'Zuletzt Online', + 'addressBookManagement.deviceCallPermission': 'Geräteanrufpermission', + 'addressBookManagement.setPermissionDesc': 'Festlegen welche Geräte das aktuelle Gerät anrufen kann', + 'addressBookManagement.selectAll': 'Alle auswählen', + 'addressBookManagement.deselectAll': 'Alle abwählen', + + // Address Book Management page - operation messages + 'addressBookManagement.aliasSaved': 'Alias gespeichert', + 'addressBookManagement.saveFailed': 'Speichern fehlgeschlagen', + 'addressBookManagement.noChanges': 'Keine Änderungen', + 'addressBookManagement.permissionSaved': 'Permission gespeichert', + 'addressBookManagement.partialSaveFailed': 'Einige Permissions konnten nicht gespeichert werden', + 'addressBookManagement.agentNameUpdated': 'Agentname aktualisiert', + 'addressBookManagement.updateFailed': 'Aktualisierung fehlgeschlagen', + + // Time format + 'addressBookManagement.justNow': 'Gerade eben', + 'addressBookManagement.minutesAgo': 'vor {minutes} Minuten', + 'addressBookManagement.hoursAgo': 'vor {hours} Stunden', + 'addressBookManagement.daysAgo': 'vor {days} Tagen', + 'addressBookManagement.monthsAgo': 'vor {months} Monaten', + 'addressBookManagement.yearsAgo': 'vor {years} Jahren', + + // Header navigation + 'header.addressBook': 'Adressbuch', + } \ No newline at end of file diff --git a/main/manager-web/src/i18n/en.js b/main/manager-web/src/i18n/en.js index 19efceda..ad1d5f32 100644 --- a/main/manager-web/src/i18n/en.js +++ b/main/manager-web/src/i18n/en.js @@ -1416,4 +1416,39 @@ export default { 'feature.asr.name': 'Speech Recognition', 'feature.asr.description': 'Convert speech to text to enable natural language interaction functionality', + // Address Book Management page + 'addressBookManagement.mainTitle': 'Address Book Management', + 'addressBookManagement.subTitle': 'Manage device address book, set device names and call permissions', + 'addressBookManagement.online': 'Online', + 'addressBookManagement.offline': 'Offline', + 'addressBookManagement.macAddress': 'MAC Address', + 'addressBookManagement.status': 'Status', + 'addressBookManagement.deviceGroup': 'Device Group', + 'addressBookManagement.addTime': 'Added Time', + 'addressBookManagement.lastOnline': 'Last Online', + 'addressBookManagement.deviceCallPermission': 'Device Call Permission', + 'addressBookManagement.setPermissionDesc': 'Set which devices the current device can call', + 'addressBookManagement.selectAll': 'Select All', + 'addressBookManagement.deselectAll': 'Deselect All', + + // Address Book Management page - operation messages + 'addressBookManagement.aliasSaved': 'Alias saved', + 'addressBookManagement.saveFailed': 'Save failed', + 'addressBookManagement.noChanges': 'No changes', + 'addressBookManagement.permissionSaved': 'Permission saved', + 'addressBookManagement.partialSaveFailed': 'Some permissions failed to save', + 'addressBookManagement.agentNameUpdated': 'Agent name updated', + 'addressBookManagement.updateFailed': 'Update failed', + + // Time format + 'addressBookManagement.justNow': 'Just now', + 'addressBookManagement.minutesAgo': '{minutes} minutes ago', + 'addressBookManagement.hoursAgo': '{hours} hours ago', + 'addressBookManagement.daysAgo': '{days} days ago', + 'addressBookManagement.monthsAgo': '{months} months ago', + 'addressBookManagement.yearsAgo': '{years} years ago', + + // Header navigation + 'header.addressBook': 'Address Book', + } \ No newline at end of file diff --git a/main/manager-web/src/i18n/pt_BR.js b/main/manager-web/src/i18n/pt_BR.js index 5f34d651..fd27b699 100644 --- a/main/manager-web/src/i18n/pt_BR.js +++ b/main/manager-web/src/i18n/pt_BR.js @@ -1416,4 +1416,39 @@ export default { 'feature.asr.name': 'Reconhecimento de Fala', 'feature.asr.description': 'Converter fala em texto para habilitar a funcionalidade de interação por linguagem natural', + // Address Book Management page + 'addressBookManagement.mainTitle': 'Gerenciamento de Lista de Contatos', + 'addressBookManagement.subTitle': 'Gerenciar lista de contatos do dispositivo, definir nomes e permissões de chamada', + 'addressBookManagement.online': 'Online', + 'addressBookManagement.offline': 'Offline', + 'addressBookManagement.macAddress': 'Endereço MAC', + 'addressBookManagement.status': 'Status', + 'addressBookManagement.deviceGroup': 'Grupo de Dispositivos', + 'addressBookManagement.addTime': 'Hora de Adição', + 'addressBookManagement.lastOnline': 'Última Conexão', + 'addressBookManagement.deviceCallPermission': 'Permissão de Chamada do Dispositivo', + 'addressBookManagement.setPermissionDesc': 'Definir quais dispositivos o dispositivo atual pode chamar', + 'addressBookManagement.selectAll': 'Selecionar Todos', + 'addressBookManagement.deselectAll': 'Desmarcar Todos', + + // Address Book Management page - operation messages + 'addressBookManagement.aliasSaved': 'Apelido salvo', + 'addressBookManagement.saveFailed': 'Falha ao salvar', + 'addressBookManagement.noChanges': 'Sem alterações', + 'addressBookManagement.permissionSaved': 'Permissão salva', + 'addressBookManagement.partialSaveFailed': 'Algumas permissões não puderam ser salvas', + 'addressBookManagement.agentNameUpdated': 'Nome do agente atualizado', + 'addressBookManagement.updateFailed': 'Falha na atualização', + + // Time format + 'addressBookManagement.justNow': 'Agora mesmo', + 'addressBookManagement.minutesAgo': 'há {minutes} minutos', + 'addressBookManagement.hoursAgo': 'há {hours} horas', + 'addressBookManagement.daysAgo': 'há {days} dias', + 'addressBookManagement.monthsAgo': 'há {months} meses', + 'addressBookManagement.yearsAgo': 'há {years} anos', + + // Header navigation + 'header.addressBook': 'Lista de Contatos', + } diff --git a/main/manager-web/src/i18n/vi.js b/main/manager-web/src/i18n/vi.js index b553cfc4..d09f8981 100644 --- a/main/manager-web/src/i18n/vi.js +++ b/main/manager-web/src/i18n/vi.js @@ -1416,4 +1416,39 @@ export default { 'feature.asr.name': 'Nhận dạng giọng nói', 'feature.asr.description': 'Chuyển đổi giọng nói thành văn bản, thực hiện chức năng tương tác ngôn ngữ tự nhiên', + // Address Book Management page + 'addressBookManagement.mainTitle': 'Quản lý danh bạ', + 'addressBookManagement.subTitle': 'Quản lý danh bạ thiết bị, đặt tên thiết bị và quyền gọi', + 'addressBookManagement.online': 'Trực tuyến', + 'addressBookManagement.offline': 'Ngoại tuyến', + 'addressBookManagement.macAddress': 'Địa chỉ MAC', + 'addressBookManagement.status': 'Trạng thái', + 'addressBookManagement.deviceGroup': 'Nhóm thiết bị', + 'addressBookManagement.addTime': 'Thời gian thêm', + 'addressBookManagement.lastOnline': 'Hoạt động lần cuối', + 'addressBookManagement.deviceCallPermission': 'Quyền gọi thiết bị', + 'addressBookManagement.setPermissionDesc': 'Đặt thiết bị nào thiết bị hiện tại có thể gọi', + 'addressBookManagement.selectAll': 'Chọn tất cả', + 'addressBookManagement.deselectAll': 'Bỏ chọn tất cả', + + // Address Book Management page - operation messages + 'addressBookManagement.aliasSaved': 'Bí danh đã lưu', + 'addressBookManagement.saveFailed': 'Lưu thất bại', + 'addressBookManagement.noChanges': 'Không có thay đổi', + 'addressBookManagement.permissionSaved': 'Quyền đã lưu', + 'addressBookManagement.partialSaveFailed': 'Một số quyền không thể lưu', + 'addressBookManagement.agentNameUpdated': 'Tên tác tử đã cập nhật', + 'addressBookManagement.updateFailed': 'Cập nhật thất bại', + + // Time format + 'addressBookManagement.justNow': 'Vừa xong', + 'addressBookManagement.minutesAgo': '{minutes} phút trước', + 'addressBookManagement.hoursAgo': '{hours} giờ trước', + 'addressBookManagement.daysAgo': '{days} ngày trước', + 'addressBookManagement.monthsAgo': '{months} tháng trước', + 'addressBookManagement.yearsAgo': '{years} năm trước', + + // Header navigation + 'header.addressBook': 'Danh bạ', + } \ No newline at end of file diff --git a/main/manager-web/src/i18n/zh_CN.js b/main/manager-web/src/i18n/zh_CN.js index 91e109c2..f5fe59cc 100644 --- a/main/manager-web/src/i18n/zh_CN.js +++ b/main/manager-web/src/i18n/zh_CN.js @@ -1413,5 +1413,111 @@ export default { 'feature.vad.description': '自动检测语音活动,优化语音交互的响应效率', 'feature.asr.name': '语音识别', 'feature.asr.description': '将语音转换为文本,实现自然语言交互功能', + 'feature.addressBook.name': '通讯录', + 'feature.addressBook.description': '管理和维护联系人通讯录,为AI助手提供联系人信息支持', + + // 通讯录管理页面 + 'addressBookManagement.title': '通讯录管理', + 'addressBookManagement.searchPlaceholder': '搜索设备名称或MAC地址', + 'addressBookManagement.search': '搜索', + 'addressBookManagement.selectAgent': '请选择智能体', + 'addressBookManagement.name': '通讯录名称', + 'addressBookManagement.description': '通讯录描述', + 'addressBookManagement.contactCount': '联系人数量', + 'addressBookManagement.status': '启用', + 'addressBookManagement.createdAt': '创建时间', + 'addressBookManagement.operation': '操作', + 'addressBookManagement.add': '新增', + 'addressBookManagement.edit': '编辑', + 'addressBookManagement.delete': '删除', + 'addressBookManagement.itemsPerPage': '条/页', + 'addressBookManagement.firstPage': '首页', + 'addressBookManagement.prevPage': '上一页', + 'addressBookManagement.nextPage': '下一页', + 'addressBookManagement.totalRecords': '共{total}条记录', + 'addressBookManagement.addAddressBook': '新增通讯录', + 'addressBookManagement.editAddressBook': '编辑通讯录', + 'addressBookManagement.getAddressBookListFailed': '获取通讯录列表失败', + 'addressBookManagement.selectAddressBookFirst': '请先选择需要删除的通讯录', + 'addressBookManagement.confirmBatchDelete': '确定要删除选中的{count}个通讯录吗?', + 'addressBookManagement.batchDeleteSuccess': '成功删除{count}个通讯录', + 'addressBookManagement.deleteFailed': '删除失败,请重试', + 'addressBookManagement.operationCancelled': '已取消删除操作', + 'addressBookManagement.updateSuccess': '修改成功', + 'addressBookManagement.addSuccess': '新增成功', + 'addressBookManagement.updateFailed': '更新失败', + 'addressBookManagement.addFailed': '新增失败', + 'addressBookManagement.selectAll': '全选', + 'addressBookManagement.cancelSelectAll': '取消全选', + 'addressBookManagement.deviceName': '设备名称', + 'addressBookManagement.deviceType': '设备类型', + 'addressBookManagement.deviceId': '设备ID', + 'addressBookManagement.remarks': '备注', + 'addressBookManagement.addressBookDetail': '通讯录详情', + 'addressBookManagement.addressBookName': '通讯录名称', + 'addressBookManagement.addressBookNamePlaceholder': '请输入通讯录名称', + 'addressBookManagement.addressBookNameRequired': '请输入通讯录名称', + 'addressBookManagement.addressBookNameLength': '通讯录名称长度在1到50个字符之间', + 'addressBookManagement.addressBookDesc': '通讯录描述', + 'addressBookManagement.addressBookDescPlaceholder': '请输入通讯录描述', + 'addressBookManagement.remarksPlaceholder': '请输入备注信息', + 'addressBookManagement.selectDevice': '请选择一个设备查看通讯录详情', + 'addressBookManagement.save': '保存', + 'addressBookManagement.saveSuccess': '保存成功', + 'addressBookManagement.saveFailed': '保存失败', + 'addressBookManagement.deviceNotSelected': '请先选择一个设备', + 'addressBookManagement.addDeviceTip': '新增设备功能开发中', + 'addressBookManagement.selectAgentFirst': '请先选择智能体', + + // 通讯录对话框 + 'addressBookDialog.title': '通讯录', + 'addressBookDialog.name': '通讯录名称', + 'addressBookDialog.namePlaceholder': '请输入通讯录名称', + 'addressBookDialog.description': '通讯录描述', + 'addressBookDialog.descriptionPlaceholder': '请详细输入通讯录描述', + 'addressBookDialog.status': '状态', + 'addressBookDialog.save': '保存', + 'addressBookDialog.cancel': '取消', + 'addressBookDialog.confirm': '确认', + 'addressBookDialog.nameRequired': '请输入通讯录名称', + 'addressBookDialog.nameLength': '通讯录名称长度在1到50个字符之间', + 'addressBookDialog.namePattern': '通讯录名称只能包含中文、英文、数字、空格、下划线和连字符', + 'addressBookDialog.descriptionRequired': '请输入通讯录描述', + 'addressBookDialog.descriptionLength': '通讯录描述不能超过300个字符', + + // 头部导航 + 'header.addressBook': '通讯录', + + // 通讯录管理页面 - 设备列表 + 'addressBookManagement.mainTitle': '通讯录管理', + 'addressBookManagement.subTitle': '管理设备通讯录,设置设备名称和呼叫权限', + 'addressBookManagement.online': '在线', + 'addressBookManagement.offline': '离线', + 'addressBookManagement.macAddress': 'MAC地址', + 'addressBookManagement.status': '状态', + 'addressBookManagement.deviceGroup': '设备分组', + 'addressBookManagement.addTime': '添加时间', + 'addressBookManagement.lastOnline': '最后在线', + 'addressBookManagement.deviceCallPermission': '设备呼叫权限', + 'addressBookManagement.setPermissionDesc': '设置当前设备可以呼叫的设备', + 'addressBookManagement.selectAll': '全选', + 'addressBookManagement.deselectAll': '全不选', + + // 通讯录管理页面 - 操作消息 + 'addressBookManagement.aliasSaved': '别名已保存', + 'addressBookManagement.saveFailed': '保存失败', + 'addressBookManagement.noChanges': '没有变更', + 'addressBookManagement.permissionSaved': '权限已保存', + 'addressBookManagement.partialSaveFailed': '部分权限保存失败', + 'addressBookManagement.agentNameUpdated': '智能体名称已更新', + 'addressBookManagement.updateFailed': '更新失败', + + // 时间格式 + 'addressBookManagement.justNow': '刚刚', + 'addressBookManagement.minutesAgo': '{minutes}分钟前', + 'addressBookManagement.hoursAgo': '{hours}小时前', + 'addressBookManagement.daysAgo': '{days}天前', + 'addressBookManagement.monthsAgo': '{months}个月前', + 'addressBookManagement.yearsAgo': '{years}年前', } \ No newline at end of file diff --git a/main/manager-web/src/i18n/zh_TW.js b/main/manager-web/src/i18n/zh_TW.js index 91c8e0c2..37beebb8 100644 --- a/main/manager-web/src/i18n/zh_TW.js +++ b/main/manager-web/src/i18n/zh_TW.js @@ -1415,5 +1415,111 @@ export default { 'feature.vad.description': '自動檢測語音活動,優化語音交互的響應效率', 'feature.asr.name': '語音識別', 'feature.asr.description': '將語音轉換為文本,實現自然語言交互功能', + 'feature.addressBook.name': '通訊錄', + 'feature.addressBook.description': '管理和維護聯繫人通訊錄,為AI助手提供聯繫人信息支持', + + // 通訊錄管理頁面 + 'addressBookManagement.title': '通訊錄', + 'addressBookManagement.searchPlaceholder': '請輸入通訊錄名稱搜尋', + 'addressBookManagement.search': '搜尋', + 'addressBookManagement.name': '通訊錄名稱', + 'addressBookManagement.description': '通訊錄描述', + 'addressBookManagement.contactCount': '聯繫人數量', + 'addressBookManagement.status': '啟用', + 'addressBookManagement.createdAt': '創建時間', + 'addressBookManagement.operation': '操作', + 'addressBookManagement.add': '新增', + 'addressBookManagement.edit': '編輯', + 'addressBookManagement.delete': '刪除', + 'addressBookManagement.itemsPerPage': '條/頁', + 'addressBookManagement.firstPage': '首頁', + 'addressBookManagement.prevPage': '上一頁', + 'addressBookManagement.nextPage': '下一頁', + 'addressBookManagement.totalRecords': '共{total}條記錄', + 'addressBookManagement.addAddressBook': '新增通訊錄', + 'addressBookManagement.editAddressBook': '編輯通訊錄', + 'addressBookManagement.getAddressBookListFailed': '獲取通訊錄列表失敗', + 'addressBookManagement.selectAddressBookFirst': '請先選擇需要刪除的通訊錄', + 'addressBookManagement.confirmBatchDelete': '確定要刪除選中的{count}個通訊錄嗎?', + 'addressBookManagement.batchDeleteSuccess': '成功刪除{count}個通訊錄', + 'addressBookManagement.deleteFailed': '刪除失敗,請重試', + 'addressBookManagement.operationCancelled': '已取消刪除操作', + 'addressBookManagement.updateSuccess': '修改成功', + 'addressBookManagement.addSuccess': '新增成功', + 'addressBookManagement.updateFailed': '更新失敗', + 'addressBookManagement.addFailed': '新增失敗', + 'addressBookManagement.selectAll': '全選', + 'addressBookManagement.cancelSelectAll': '取消全選', + 'addressBookManagement.selectAgent': '請選擇智能體', + 'addressBookManagement.deviceName': '設備名稱', + 'addressBookManagement.deviceType': '設備類型', + 'addressBookManagement.deviceId': '設備ID', + 'addressBookManagement.remarks': '備註', + 'addressBookManagement.addressBookDetail': '通訊錄詳情', + 'addressBookManagement.addressBookName': '通訊錄名稱', + 'addressBookManagement.addressBookNamePlaceholder': '請輸入通訊錄名稱', + 'addressBookManagement.addressBookNameRequired': '請輸入通訊錄名稱', + 'addressBookManagement.addressBookNameLength': '通訊錄名稱長度在1到50個字元之間', + 'addressBookManagement.addressBookDesc': '通訊錄描述', + 'addressBookManagement.addressBookDescPlaceholder': '請輸入通訊錄描述', + 'addressBookManagement.remarksPlaceholder': '請輸入備註信息', + 'addressBookManagement.selectDevice': '請選擇一個設備查看通訊錄詳情', + 'addressBookManagement.save': '儲存', + 'addressBookManagement.saveSuccess': '儲存成功', + 'addressBookManagement.saveFailed': '儲存失敗', + 'addressBookManagement.deviceNotSelected': '請先選擇一個設備', + 'addressBookManagement.addDeviceTip': '新增設備功能開發中', + 'addressBookManagement.selectAgentFirst': '請先選擇智能體', + + // 通訊錄對話框 + 'addressBookDialog.title': '通訊錄', + 'addressBookDialog.name': '通訊錄名稱', + 'addressBookDialog.namePlaceholder': '請輸入通訊錄名稱', + 'addressBookDialog.description': '通訊錄描述', + 'addressBookDialog.descriptionPlaceholder': '請詳細輸入通訊錄描述', + 'addressBookDialog.status': '狀態', + 'addressBookDialog.save': '儲存', + 'addressBookDialog.cancel': '取消', + 'addressBookDialog.confirm': '確認', + 'addressBookDialog.nameRequired': '請輸入通訊錄名稱', + 'addressBookDialog.nameLength': '通訊錄名稱長度在1到50個字元之間', + 'addressBookDialog.namePattern': '通訊錄名稱只能包含中文、英文、數字、空格、底線和連字符', + 'addressBookDialog.descriptionRequired': '請輸入通訊錄描述', + 'addressBookDialog.descriptionLength': '通訊錄描述不能超過300個字元', + + // 頭部導航 + 'header.addressBook': '通訊錄', + + // 通訊錄管理頁面 - 設備列表 + 'addressBookManagement.mainTitle': '通訊錄管理', + 'addressBookManagement.subTitle': '管理設備通訊錄,設置設備名稱和呼叫權限', + 'addressBookManagement.online': '在線', + 'addressBookManagement.offline': '離線', + 'addressBookManagement.macAddress': 'MAC地址', + 'addressBookManagement.status': '狀態', + 'addressBookManagement.deviceGroup': '設備分組', + 'addressBookManagement.addTime': '添加時間', + 'addressBookManagement.lastOnline': '最後在線', + 'addressBookManagement.deviceCallPermission': '設備呼叫權限', + 'addressBookManagement.setPermissionDesc': '設置當前設備可以呼叫的設備', + 'addressBookManagement.selectAll': '全選', + 'addressBookManagement.deselectAll': '全不選', + + // 通訊錄管理頁面 - 操作消息 + 'addressBookManagement.aliasSaved': '別名已保存', + 'addressBookManagement.saveFailed': '保存失敗', + 'addressBookManagement.noChanges': '沒有變更', + 'addressBookManagement.permissionSaved': '權限已保存', + 'addressBookManagement.partialSaveFailed': '部分權限保存失敗', + 'addressBookManagement.agentNameUpdated': '智能體名稱已更新', + 'addressBookManagement.updateFailed': '更新失敗', + + // 時間格式 + 'addressBookManagement.justNow': '剛剛', + 'addressBookManagement.minutesAgo': '{minutes}分鐘前', + 'addressBookManagement.hoursAgo': '{hours}小時前', + 'addressBookManagement.daysAgo': '{days}天前', + 'addressBookManagement.monthsAgo': '{months}個月前', + 'addressBookManagement.yearsAgo': '{years}年前', } \ No newline at end of file diff --git a/main/manager-web/src/router/index.js b/main/manager-web/src/router/index.js index 7de86e2d..ceb48058 100644 --- a/main/manager-web/src/router/index.js +++ b/main/manager-web/src/router/index.js @@ -208,6 +208,18 @@ const routes = [ title: '替换词管理' } }, + // 通讯录管理页面路由 + { + path: '/address-book-management', + name: 'AddressBookManagement', + component: function () { + return import('../views/AddressBookManagement.vue') + }, + meta: { + requiresAuth: true, + title: '通讯录管理' + } + }, ] const router = new VueRouter({ base: process.env.VUE_APP_PUBLIC_PATH || '/', @@ -229,7 +241,7 @@ VueRouter.prototype.push = function push(location) { } // 需要登录才能访问的路由 -const protectedRoutes = ['home', 'RoleConfig', 'DeviceManagement', 'UserManagement', 'ModelConfig', 'KnowledgeBaseManagement', 'KnowledgeFileUpload'] +const protectedRoutes = ['home', 'RoleConfig', 'DeviceManagement', 'UserManagement', 'ModelConfig', 'KnowledgeBaseManagement', 'KnowledgeFileUpload', 'AddressBookManagement'] // 路由守卫 router.beforeEach((to, from, next) => { diff --git a/main/manager-web/src/utils/featureManager.js b/main/manager-web/src/utils/featureManager.js index dff84cad..f3f7a2ae 100644 --- a/main/manager-web/src/utils/featureManager.js +++ b/main/manager-web/src/utils/featureManager.js @@ -34,6 +34,11 @@ class FeatureManager { name: 'feature.asr.name', enabled: false, description: 'feature.asr.description' + }, + addressBook: { + name: 'feature.addressBook.name', + enabled: false, + description: 'feature.addressBook.description' } }; this.currentFeatures = { ...this.defaultFeatures }; // 当前内存中的配置 @@ -220,7 +225,7 @@ class FeatureManager { paramValue: JSON.stringify({ features: config, groups: { - featureManagement: ["voiceprintRecognition", "voiceClone", "knowledgeBase", "mcpAccessPoint"], + featureManagement: ["voiceprintRecognition", "voiceClone", "knowledgeBase", "mcpAccessPoint", "addressBook"], voiceManagement: ["vad", "asr"] } }), @@ -264,7 +269,8 @@ class FeatureManager { knowledgeBase: features.knowledgeBase?.enabled || false, mcpAccessPoint: features.mcpAccessPoint?.enabled || false, vad: features.vad?.enabled || false, - asr: features.asr?.enabled || false + asr: features.asr?.enabled || false, + addressBook: features.addressBook?.enabled || false }; } diff --git a/main/manager-web/src/views/AddressBookManagement.vue b/main/manager-web/src/views/AddressBookManagement.vue new file mode 100644 index 00000000..2b06308e --- /dev/null +++ b/main/manager-web/src/views/AddressBookManagement.vue @@ -0,0 +1,1151 @@ + + + + + diff --git a/main/manager-web/src/views/FeatureManagement.vue b/main/manager-web/src/views/FeatureManagement.vue index d6272a50..4495217f 100644 --- a/main/manager-web/src/views/FeatureManagement.vue +++ b/main/manager-web/src/views/FeatureManagement.vue @@ -183,7 +183,7 @@ export default { }) // 重新加载配置 - this.featureManagementFeatures = await this.getFeaturesByIds(['voiceprintRecognition', 'voiceClone', 'knowledgeBase', 'mcpAccessPoint']) + this.featureManagementFeatures = await this.getFeaturesByIds(['voiceprintRecognition', 'voiceClone', 'knowledgeBase', 'mcpAccessPoint', 'addressBook']) this.voiceManagementFeatures = await this.getFeaturesByIds(['vad', 'asr']) // 恢复用户的选择状态(如果存在) diff --git a/main/xiaozhi-server/config/config_loader.py b/main/xiaozhi-server/config/config_loader.py index 070d83a3..33a57e72 100644 --- a/main/xiaozhi-server/config/config_loader.py +++ b/main/xiaozhi-server/config/config_loader.py @@ -7,6 +7,7 @@ from config.manage_api_client import ( get_server_config, get_agent_models, get_correct_words, + lookup_address_book, DeviceNotFoundException, DeviceBindException, ) diff --git a/main/xiaozhi-server/config/manage_api_client.py b/main/xiaozhi-server/config/manage_api_client.py index 7028f975..7cc4c361 100644 --- a/main/xiaozhi-server/config/manage_api_client.py +++ b/main/xiaozhi-server/config/manage_api_client.py @@ -245,6 +245,20 @@ async def report( return None +async def lookup_address_book(caller_mac: str, nickname: str) -> Optional[Dict]: + """根据昵称查找目标设备""" + if not ManageApiClient._instance: + return None + try: + return await ManageApiClient._instance._execute_async_request( + "GET", + f"/device/address-book/lookup?callerMac={caller_mac}&nickname={nickname}", + ) + except Exception as e: + print(f"通讯录查找失败: {e}") + return None + + def init_service(config): ManageApiClient(config) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 2ae64316..f4dd59e2 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -190,6 +190,9 @@ class ConnectionHandler: # 初始化提示词管理器 self.prompt_manager = PromptManager(self.config, self.logger) + # 初始化通话状态 + self.calling = False + async def handle_connection(self, ws: websockets.ServerConnection): try: # 获取运行中的事件循环(必须在异步上下文中) diff --git a/main/xiaozhi-server/core/handle/sendAudioHandle.py b/main/xiaozhi-server/core/handle/sendAudioHandle.py index 0ae801ed..17de11ae 100644 --- a/main/xiaozhi-server/core/handle/sendAudioHandle.py +++ b/main/xiaozhi-server/core/handle/sendAudioHandle.py @@ -47,7 +47,8 @@ async def sendAudioMessage(conn: "ConnectionHandler", sentenceType, audios, text conn.logger.bind(tag=TAG).info(f"发送音频消息: {sentenceType}, {text}") # 发送结束消息(如果是最后一个文本) - if sentenceType == SentenceType.LAST: + # 通话需要维持speaking状态 + if not conn.calling and sentenceType == SentenceType.LAST: await send_tts_message(conn, "stop", None) if conn.close_after_chat: await conn.close() diff --git a/main/xiaozhi-server/plugins_func/functions/call_device.py b/main/xiaozhi-server/plugins_func/functions/call_device.py new file mode 100644 index 00000000..3e619fd8 --- /dev/null +++ b/main/xiaozhi-server/plugins_func/functions/call_device.py @@ -0,0 +1,134 @@ +"""呼叫设备工具""" +import httpx +import hashlib +import requests +from datetime import datetime +from typing import TYPE_CHECKING + +from config.logger import setup_logging +from plugins_func.register import register_function, ToolType, ActionResponse, Action + +if TYPE_CHECKING: + from core.connection import ConnectionHandler + +TAG = __name__ +logger = setup_logging() + + +call_device_function_desc = { + "type": "function", + "function": { + "name": "call_device", + "description": "主动呼叫其他小智设备进行语音通话。**重要**:只有当用户主动说\"我想跟XX通话\"、\"帮我打电话给XX\"时才调用此工具。当收到\"来自XX的来电\"消息时,不要调用此工具,让设备自然播报即可。", + "parameters": { + "type": "object", + "properties": { + "nickname": { + "type": "string", + "description": "目标设备的备注名,例如:小陈、小翁" + }, + "response_success": { + "type": "string", + "description": "呼叫成功时的回复" + }, + "response_failure": { + "type": "string", + "description": "呼叫失败时的回复" + } + }, + "required": ["nickname", "response_success", "response_failure"], + }, + }, +} + + +def _api_request(url: str, params: dict, headers: dict) -> dict: + """发送API请求并处理响应""" + resp = requests.get(url, params=params, headers=headers, timeout=10) + return resp.json() + + +def _call_gateway(gateway_url: str, gateway_secret: str, caller_mac: str, target_mac: str, caller_nickname: str) -> dict: + """呼叫网关""" + date_str = datetime.now().strftime("%Y-%m-%d") + token = hashlib.sha256((date_str + gateway_secret).encode()).hexdigest() + headers = {"Authorization": f"Bearer {token}"} + with httpx.Client(timeout=5.0) as client: + resp = client.post( + f"{gateway_url}/api/call/request", + json={"caller_mac": caller_mac, "target_mac": target_mac, "caller_nickname": caller_nickname}, + headers=headers + ) + return resp.json() + + +@register_function("call_device", call_device_function_desc, ToolType.SYSTEM_CTL) +def call_device(conn: "ConnectionHandler", nickname: str, response_success: str = None, response_failure: str = None): + try: + caller_mac = conn.headers.get("device-id", "") + if not caller_mac: + return ActionResponse(action=Action.RESPONSE, response="无法获取本机MAC地址") + + api_config = conn.config.get("manager-api", {}) + api_url = api_config.get("url", "") + api_secret = api_config.get("secret", "") + if not api_url or not api_secret: + logger.bind(tag=TAG).error("manager-api配置缺失") + return ActionResponse(action=Action.RESPONSE, response="配置错误,请稍后再试") + + try: + result = _api_request( + f"{api_url}/device/address-book/lookup", + params={"callerMac": caller_mac, "nickname": nickname}, + headers={"Authorization": f"Bearer {api_secret}"} + ) + except Exception as e: + logger.bind(tag=TAG).error(f"通讯录查找请求失败: {e}") + return ActionResponse(action=Action.RESPONSE, response="通讯录查询失败,请稍后再试") + + if result.get("code") != 0: + return ActionResponse(action=Action.RESPONSE, response=f"未找到备注为'{nickname}'的设备") + + lookup_result = result.get("data") + if not lookup_result: + return ActionResponse(action=Action.RESPONSE, response=f"未找到备注为'{nickname}'的设备") + + target_mac = lookup_result.get("targetMac") + caller_nickname = lookup_result.get("callerNickname") + has_permission = lookup_result.get("hasPermission", "false") == "true" + + if not target_mac: + return ActionResponse(action=Action.RESPONSE, response=f"未找到备注为'{nickname}'的设备") + + if not caller_nickname: + return ActionResponse(action=Action.RESPONSE, response="呼叫失败,您不是对方的联系人") + + if not has_permission: + return ActionResponse(action=Action.RESPONSE, response="呼叫失败,您没有权限呼叫该设备") + + plugin_config = conn.config.get("plugins", {}).get("call_device", {}) + gateway_url = plugin_config.get("gateway_url", "http://127.0.0.1:8007") + gateway_secret = plugin_config.get("gateway_secret", "") + + try: + gw_result = _call_gateway(gateway_url, gateway_secret, caller_mac, target_mac, caller_nickname) + except httpx.ConnectError: + logger.bind(tag=TAG).error(f"无法连接到网关: {gateway_url}") + return ActionResponse(action=Action.RESPONSE, response="无法连接到通话网关,请稍后再试") + except httpx.TimeoutException: + logger.bind(tag=TAG).error("网关请求超时") + return ActionResponse(action=Action.RESPONSE, response="通话请求超时,请稍后再试") + + conn.calling = True + status = gw_result.get("status") + if status == "bridged": + return ActionResponse(action=Action.NONE, result="bridged", response=f"已与{nickname}建立通话") + elif status == "pending": + return ActionResponse(action=Action.NONE, result="pending", response=f"正在呼叫{nickname},请等待对方接听") + else: + error_msg = gw_result.get("message", "未知错误") + return ActionResponse(action=Action.RESPONSE, response=f"呼叫失败:{error_msg}") + + except Exception as e: + logger.bind(tag=TAG).error(f"call_device错误: {e}") + return ActionResponse(action=Action.ERROR, response=f"呼叫失败: {str(e)}") From de78e87c77518769d0c73f16596d0ab94c6abae1 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Wed, 27 May 2026 15:34:17 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E4=BD=BF=E7=94=A8java=E7=AB=AF=E4=BD=9C=E4=B8=BA=E4=B8=AD?= =?UTF-8?q?=E8=BD=AC=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/controller/DeviceController.java | 7 + .../modules/device/service/DeviceService.java | 6 + .../service/impl/DeviceServiceImpl.java | 66 ++++++++ .../modules/security/config/ShiroConfig.java | 1 + .../resources/db/changelog/202605251425.sql | 3 +- .../textHandler/listenMessageHandler.py | 25 ++- .../plugins_func/functions/call_device.py | 156 +++++++----------- 7 files changed, 166 insertions(+), 98 deletions(-) diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java index 2b1b5ca8..4fa150c8 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java @@ -197,4 +197,11 @@ public class DeviceController { deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), null, dto.getHasPermission()); return new Result(); } + + @GetMapping("/call/forward") + @Operation(summary = "转发呼叫请求到网关") + public Result> forwardCallRequest(String callerMac, String targetMac, String callerNickname) { + Map result = deviceService.forwardCallRequest(callerMac, targetMac, callerNickname); + return new Result>().ok(result); + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java index ecbf719c..05e9cab4 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java @@ -133,4 +133,10 @@ public interface DeviceService extends BaseService { */ Object callDeviceTool(String deviceId, String toolName, Map arguments); + /** + * 转发呼叫请求到网关 + * @return 网关响应 {status, message} + */ + Map forwardCallRequest(String callerMac, String targetMac, String callerNickname); + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java index adabbd95..d509ee8a 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java @@ -872,4 +872,70 @@ public class DeviceServiceImpl extends BaseServiceImpl } return null; } + + @Override + public Map forwardCallRequest(String callerMac, String targetMac, String callerNickname) { + Map result = new HashMap<>(); + result.put("status", "error"); + result.put("message", "网关配置缺失"); + + // 从系统参数获取网关配置 + String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true); + String mqttSignatureKey = sysParamsService.getValue("server.mqtt_signature_key", true); + + if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) { + log.error("MQTT网关地址未配置"); + result.put("message", "MQTT网关地址未配置"); + return result; + } + if (StringUtils.isBlank(mqttSignatureKey) || "null".equals(mqttSignatureKey)) { + log.error("MQTT签名密钥未配置"); + result.put("message", "MQTT签名密钥未配置"); + return result; + } + + // 生成token: SHA256(date + secret) + String dateStr = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new Date()); + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); + byte[] hash = md.digest((dateStr + mqttSignatureKey).getBytes(StandardCharsets.UTF_8)); + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + String token = hexString.toString(); + + // 构建请求体 + Map body = new HashMap<>(); + body.put("caller_mac", callerMac); + body.put("target_mac", targetMac); + body.put("caller_nickname", callerNickname); + + // 发送请求并获取响应 + String url = "http://" + mqttGatewayUrl + "/api/call/request"; + String response = cn.hutool.http.HttpRequest.post(url) + .header("Authorization", "Bearer " + token) + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(body)) + .timeout(5000) + .execute() + .body(); + + // 解析网关响应 + if (StringUtils.isNotBlank(response)) { + Map gwResult = JSONUtil.parseObj(response); + result.put("status", gwResult.get("status")); + result.put("message", gwResult.get("message")); + } + return result; + } catch (Exception e) { + log.error("转发呼叫请求失败: {}", e.getMessage()); + result.put("message", "呼叫请求转发失败: " + e.getMessage()); + return result; + } + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java index 070af9a6..060e2659 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java @@ -88,6 +88,7 @@ public class ShiroConfig { // 将config路径使用server服务过滤器 filterMap.put("/config/**", "server"); filterMap.put("/device/address-book/lookup", "server"); + filterMap.put("/device/call/forward", "server"); filterMap.put("/agent/chat-history/report", "server"); filterMap.put("/agent/chat-history/download/**", "anon"); filterMap.put("/agent/chat-summary/**", "server"); diff --git a/main/manager-api/src/main/resources/db/changelog/202605251425.sql b/main/manager-api/src/main/resources/db/changelog/202605251425.sql index feaebec4..db57d573 100644 --- a/main/manager-api/src/main/resources/db/changelog/202605251425.sql +++ b/main/manager-api/src/main/resources/db/changelog/202605251425.sql @@ -2,8 +2,7 @@ SET @data_exists = (SELECT COUNT(*) FROM ai_model_provider WHERE id = 'SYSTEM_PLUGIN_CALL_DEVICE'); SET @sql = IF(@data_exists = 0, 'INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, -`update_date`) VALUES (''SYSTEM_PLUGIN_CALL_DEVICE'', ''Plugin'', ''call_device'', ''呼叫设备'', ''[{"key":"gateway_url","label":"网关地址","type": -"string","default":"http://127.0.0.1:8007"},{"key":"gateway_secret","label":"网关密钥","type":"string"}]'', 85, 1988490863118454785, ''2026-05-18 +`update_date`) VALUES (''SYSTEM_PLUGIN_CALL_DEVICE'', ''Plugin'', ''call_device'', ''呼叫设备'', ''[]'', 85, 1988490863118454785, ''2026-05-18 12:00:00'', 1988490863118454785, ''2026-05-18 12:00:00'')', 'SELECT ''data already exists, skip'' AS msg'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; diff --git a/main/xiaozhi-server/core/handle/textHandler/listenMessageHandler.py b/main/xiaozhi-server/core/handle/textHandler/listenMessageHandler.py index b445c72e..21188019 100644 --- a/main/xiaozhi-server/core/handle/textHandler/listenMessageHandler.py +++ b/main/xiaozhi-server/core/handle/textHandler/listenMessageHandler.py @@ -1,17 +1,21 @@ import time +import uuid import asyncio from typing import Dict, Any, TYPE_CHECKING if TYPE_CHECKING: from core.connection import ConnectionHandler +from core.utils.dialogue import Message +from core.providers.asr.dto.dto import InterfaceType from core.handle.receiveAudioHandle import startToChat from core.handle.reportHandle import enqueue_asr_report from core.handle.sendAudioHandle import send_stt_message, send_tts_message from core.handle.textMessageHandler import TextMessageHandler from core.handle.textMessageType import TextMessageType from core.utils.util import remove_punctuation_and_length -from core.providers.asr.dto.dto import InterfaceType +from core.providers.tts.dto.dto import ContentType, TTSMessageDTO, SentenceType + TAG = __name__ @@ -54,6 +58,25 @@ class ListenTextMessageHandler(TextMessageHandler): original_text ) + # 检查是否是设备呼叫指令 [device_call] + if original_text.startswith("[device_call]"): + # 提取 tag 后的文本 + call_text = original_text[len("[device_call]"):].strip() + conn.logger.bind(tag=TAG).info(f"收到设备呼叫指令: {call_text}") + + # 准备开始新会话 + conn.sentence_id = uuid.uuid4().hex + + await send_stt_message(conn, call_text) + conn.tts.store_tts_text(conn.sentence_id, call_text) + conn.tts.tts_text_queue.put(TTSMessageDTO(sentence_id=conn.sentence_id, sentence_type=SentenceType.FIRST, content_type=ContentType.ACTION)) + conn.tts.tts_one_sentence(conn, ContentType.TEXT, content_detail=call_text) + conn.tts.tts_text_queue.put(TTSMessageDTO(sentence_id=conn.sentence_id, sentence_type=SentenceType.LAST, content_type=ContentType.ACTION)) + + # 添加到对话历史,让模型理解上下文 + conn.dialogue.put(Message(role="user", content=call_text)) + return + # 识别是否是唤醒词 is_wakeup_words = filtered_text in conn.config.get("wakeup_words") # 是否开启唤醒词回复 diff --git a/main/xiaozhi-server/plugins_func/functions/call_device.py b/main/xiaozhi-server/plugins_func/functions/call_device.py index 3e619fd8..03ccccaa 100644 --- a/main/xiaozhi-server/plugins_func/functions/call_device.py +++ b/main/xiaozhi-server/plugins_func/functions/call_device.py @@ -1,8 +1,5 @@ """呼叫设备工具""" -import httpx -import hashlib import requests -from datetime import datetime from typing import TYPE_CHECKING from config.logger import setup_logging @@ -14,121 +11,90 @@ if TYPE_CHECKING: TAG = __name__ logger = setup_logging() - call_device_function_desc = { "type": "function", "function": { "name": "call_device", - "description": "主动呼叫其他小智设备进行语音通话。**重要**:只有当用户主动说\"我想跟XX通话\"、\"帮我打电话给XX\"时才调用此工具。当收到\"来自XX的来电\"消息时,不要调用此工具,让设备自然播报即可。", + "description": "主动呼叫其他小智设备进行语音通话。**重要**:只有当用户主动说'我想跟XX通话'、'帮我打电话给XX'时才调用此工具。当收到'来自XX的来电'消息时,不要调用此工具,让设备自然播报即可。", "parameters": { "type": "object", "properties": { - "nickname": { - "type": "string", - "description": "目标设备的备注名,例如:小陈、小翁" - }, - "response_success": { - "type": "string", - "description": "呼叫成功时的回复" - }, - "response_failure": { - "type": "string", - "description": "呼叫失败时的回复" - } + "nickname": {"type": "string", "description": "目标设备的备注名,例如:小陈、小翁"}, }, - "required": ["nickname", "response_success", "response_failure"], + "required": ["nickname"], }, }, } -def _api_request(url: str, params: dict, headers: dict) -> dict: - """发送API请求并处理响应""" - resp = requests.get(url, params=params, headers=headers, timeout=10) - return resp.json() +def _request_api(url: str, params: dict, headers: dict) -> requests.Response: + return requests.get(url, params=params, headers=headers, timeout=10) -def _call_gateway(gateway_url: str, gateway_secret: str, caller_mac: str, target_mac: str, caller_nickname: str) -> dict: - """呼叫网关""" - date_str = datetime.now().strftime("%Y-%m-%d") - token = hashlib.sha256((date_str + gateway_secret).encode()).hexdigest() - headers = {"Authorization": f"Bearer {token}"} - with httpx.Client(timeout=5.0) as client: - resp = client.post( - f"{gateway_url}/api/call/request", - json={"caller_mac": caller_mac, "target_mac": target_mac, "caller_nickname": caller_nickname}, - headers=headers - ) - return resp.json() +def _failed_reply(msg: str) -> ActionResponse: + return ActionResponse(action=Action.RESPONSE, response=msg) @register_function("call_device", call_device_function_desc, ToolType.SYSTEM_CTL) -def call_device(conn: "ConnectionHandler", nickname: str, response_success: str = None, response_failure: str = None): +def call_device(conn: "ConnectionHandler", nickname: str): + caller_mac = conn.headers.get("device-id") + if not caller_mac: + return _failed_reply("无法获取本机MAC地址") + + api_config = conn.config.get("manager-api", {}) + api_url = api_config.get("url") + api_secret = api_config.get("secret") + if not api_url or not api_secret: + logger.bind(tag=TAG).error("manager-api配置缺失") + return _failed_reply("配置错误,请稍后再试") + + headers = {"Authorization": f"Bearer {api_secret}"} + + # 查询通讯录 try: - caller_mac = conn.headers.get("device-id", "") - if not caller_mac: - return ActionResponse(action=Action.RESPONSE, response="无法获取本机MAC地址") + resp = _request_api( + f"{api_url}/device/address-book/lookup", + params={"callerMac": caller_mac, "nickname": nickname}, + headers=headers, + ) + result = resp.json() + except requests.RequestException as e: + logger.bind(tag=TAG).error(f"通讯录查找请求失败: {e}") + return _failed_reply("通讯录查询失败,请稍后再试") - api_config = conn.config.get("manager-api", {}) - api_url = api_config.get("url", "") - api_secret = api_config.get("secret", "") - if not api_url or not api_secret: - logger.bind(tag=TAG).error("manager-api配置缺失") - return ActionResponse(action=Action.RESPONSE, response="配置错误,请稍后再试") + if result.get("code") != 0 or not result.get("data"): + return _failed_reply(f"未找到备注为'{nickname}'的设备") - try: - result = _api_request( - f"{api_url}/device/address-book/lookup", - params={"callerMac": caller_mac, "nickname": nickname}, - headers={"Authorization": f"Bearer {api_secret}"} - ) - except Exception as e: - logger.bind(tag=TAG).error(f"通讯录查找请求失败: {e}") - return ActionResponse(action=Action.RESPONSE, response="通讯录查询失败,请稍后再试") + data = result["data"] + target_mac = data.get("targetMac") + caller_nickname = data.get("callerNickname") + has_permission = data.get("hasPermission") - if result.get("code") != 0: - return ActionResponse(action=Action.RESPONSE, response=f"未找到备注为'{nickname}'的设备") + if not target_mac: + return _failed_reply(f"未找到备注为'{nickname}'的设备") + if not caller_nickname: + return _failed_reply("呼叫失败,您不是对方的联系人") + if not has_permission: + return _failed_reply("呼叫失败,您没有权限呼叫该设备") - lookup_result = result.get("data") - if not lookup_result: - return ActionResponse(action=Action.RESPONSE, response=f"未找到备注为'{nickname}'的设备") + # 通过Java中转调用网关 + try: + resp = _request_api( + f"{api_url}/device/call/forward", + params={"callerMac": caller_mac, "targetMac": target_mac, "callerNickname": caller_nickname}, + headers=headers, + ) + result = resp.json() + except requests.RequestException as e: + logger.bind(tag=TAG).error(f"呼叫请求转发失败: {e}") + return _failed_reply("呼叫失败,请稍后再试") - target_mac = lookup_result.get("targetMac") - caller_nickname = lookup_result.get("callerNickname") - has_permission = lookup_result.get("hasPermission", "false") == "true" + if result.get("code") != 0: + return _failed_reply(result.get("msg", "呼叫失败")) - if not target_mac: - return ActionResponse(action=Action.RESPONSE, response=f"未找到备注为'{nickname}'的设备") + call_data = result.get("data", {}) + if call_data.get("status") == "offline": + return _failed_reply(call_data.get("message")) - if not caller_nickname: - return ActionResponse(action=Action.RESPONSE, response="呼叫失败,您不是对方的联系人") - - if not has_permission: - return ActionResponse(action=Action.RESPONSE, response="呼叫失败,您没有权限呼叫该设备") - - plugin_config = conn.config.get("plugins", {}).get("call_device", {}) - gateway_url = plugin_config.get("gateway_url", "http://127.0.0.1:8007") - gateway_secret = plugin_config.get("gateway_secret", "") - - try: - gw_result = _call_gateway(gateway_url, gateway_secret, caller_mac, target_mac, caller_nickname) - except httpx.ConnectError: - logger.bind(tag=TAG).error(f"无法连接到网关: {gateway_url}") - return ActionResponse(action=Action.RESPONSE, response="无法连接到通话网关,请稍后再试") - except httpx.TimeoutException: - logger.bind(tag=TAG).error("网关请求超时") - return ActionResponse(action=Action.RESPONSE, response="通话请求超时,请稍后再试") - - conn.calling = True - status = gw_result.get("status") - if status == "bridged": - return ActionResponse(action=Action.NONE, result="bridged", response=f"已与{nickname}建立通话") - elif status == "pending": - return ActionResponse(action=Action.NONE, result="pending", response=f"正在呼叫{nickname},请等待对方接听") - else: - error_msg = gw_result.get("message", "未知错误") - return ActionResponse(action=Action.RESPONSE, response=f"呼叫失败:{error_msg}") - - except Exception as e: - logger.bind(tag=TAG).error(f"call_device错误: {e}") - return ActionResponse(action=Action.ERROR, response=f"呼叫失败: {str(e)}") + conn.calling = True + return ActionResponse(action=Action.NONE, response=f"正在呼叫{nickname},请等待对方接听") \ No newline at end of file From 4a55662e911697f9aeca22f865b4950c678b82c2 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Wed, 27 May 2026 16:57:48 +0800 Subject: [PATCH 03/14] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E4=BD=93=E5=9C=A8=E7=BA=BF=E7=8A=B6=E6=80=81=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=20=E8=A1=A5=E5=85=85=E7=9B=B8=E5=85=B3=E5=9B=BD?= =?UTF-8?q?=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/i18n/de.js | 2 + main/manager-web/src/i18n/en.js | 2 + main/manager-web/src/i18n/pt_BR.js | 2 + main/manager-web/src/i18n/vi.js | 2 + .../src/views/AddressBookManagement.vue | 56 ++++++++++++++++++- 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/main/manager-web/src/i18n/de.js b/main/manager-web/src/i18n/de.js index 4bc7fbd5..becddc50 100644 --- a/main/manager-web/src/i18n/de.js +++ b/main/manager-web/src/i18n/de.js @@ -1415,6 +1415,8 @@ export default { 'feature.vad.description': 'Sprachaktivität automatisch erkennen, um die Effizienz der Sprachiinteraktion zu optimieren', 'feature.asr.name': 'Spracherkennung', 'feature.asr.description': 'Sprache in Text umwandeln, um natürliche Sprachinteraktionsfunktionalität zu ermöglichen', + 'feature.addressBook.name': 'Adressbuch', + 'feature.addressBook.description': 'Kontakt-Adressbuch verwalten und pflegen, um Kontaktinformationen für KI-Assistenten bereitzustellen', // Address Book Management page 'addressBookManagement.mainTitle': 'Adressbuchverwaltung', diff --git a/main/manager-web/src/i18n/en.js b/main/manager-web/src/i18n/en.js index ad1d5f32..8fefce7c 100644 --- a/main/manager-web/src/i18n/en.js +++ b/main/manager-web/src/i18n/en.js @@ -1415,6 +1415,8 @@ export default { 'feature.vad.description': 'Automatically detect voice activity to optimize voice interaction response efficiency', 'feature.asr.name': 'Speech Recognition', 'feature.asr.description': 'Convert speech to text to enable natural language interaction functionality', + 'feature.addressBook.name': 'Address Book', + 'feature.addressBook.description': 'Manage and maintain contact address book to provide contact information support for AI assistant', // Address Book Management page 'addressBookManagement.mainTitle': 'Address Book Management', diff --git a/main/manager-web/src/i18n/pt_BR.js b/main/manager-web/src/i18n/pt_BR.js index fd27b699..fe202af7 100644 --- a/main/manager-web/src/i18n/pt_BR.js +++ b/main/manager-web/src/i18n/pt_BR.js @@ -1415,6 +1415,8 @@ export default { 'feature.vad.description': 'Detectar automaticamente atividade de voz para otimizar a eficiência de resposta da interação por voz', 'feature.asr.name': 'Reconhecimento de Fala', 'feature.asr.description': 'Converter fala em texto para habilitar a funcionalidade de interação por linguagem natural', + 'feature.addressBook.name': 'Lista de Contatos', + 'feature.addressBook.description': 'Gerenciar e manter lista de contatos para fornecer suporte de informações de contato para assistente de IA', // Address Book Management page 'addressBookManagement.mainTitle': 'Gerenciamento de Lista de Contatos', diff --git a/main/manager-web/src/i18n/vi.js b/main/manager-web/src/i18n/vi.js index d09f8981..505a277b 100644 --- a/main/manager-web/src/i18n/vi.js +++ b/main/manager-web/src/i18n/vi.js @@ -1415,6 +1415,8 @@ export default { 'feature.vad.description': 'Tự động phát hiện hoạt động giọng nói, tối ưu hóa hiệu suất phản hồi tương tác giọng nói', 'feature.asr.name': 'Nhận dạng giọng nói', 'feature.asr.description': 'Chuyển đổi giọng nói thành văn bản, thực hiện chức năng tương tác ngôn ngữ tự nhiên', + 'feature.addressBook.name': 'Danh bạ', + 'feature.addressBook.description': 'Quản lý và duy trì danh bạ liên lạc để cung cấp hỗ trợ thông tin liên lạc cho trợ lý AI', // Address Book Management page 'addressBookManagement.mainTitle': 'Quản lý danh bạ', diff --git a/main/manager-web/src/views/AddressBookManagement.vue b/main/manager-web/src/views/AddressBookManagement.vue index 2b06308e..9c36966f 100644 --- a/main/manager-web/src/views/AddressBookManagement.vue +++ b/main/manager-web/src/views/AddressBookManagement.vue @@ -221,7 +221,8 @@ export default { isEditingAgentName: false, editAgentNameValue: '', editingDeviceId: null, - editingDeviceName: '' + editingDeviceName: '', + mqttServiceAvailable: false }; }, created() { @@ -272,9 +273,10 @@ export default { type: device.board, deviceId: device.macAddress, remarks: device.alias || '', - online: device.deviceStatus === 'online', + online: false, createDate: device.createDate, - lastConnectedAt: device.lastConnectedAt + lastConnectedAt: device.lastConnectedAt, + deviceStatus: 'offline' })); resolve(); }); @@ -283,6 +285,8 @@ export default { Promise.all(agentPromises).then(() => { this.agentDeviceOptions = agentList; this.filteredAgents = agentList; + // 获取设备状态 + this.fetchDeviceStatus(); }); } else { this.agentDeviceOptions = []; @@ -290,6 +294,52 @@ export default { } }); }, + fetchDeviceStatus() { + // 为每个智能体获取设备状态 + this.agentDeviceOptions.forEach(agent => { + if (!agent.id) return; + Api.device.getDeviceStatus(agent.id, (statusRes) => { + if (statusRes.data?.code === 0) { + try { + const statusData = JSON.parse(statusRes.data.data); + if (statusData && typeof statusData === 'object') { + this.mqttServiceAvailable = true; + this.updateDeviceStatusFromResponse(agent, statusData); + } + } catch (e) { + console.error('Parse device status error:', e); + } + } + }); + }); + }, + updateDeviceStatusFromResponse(agent, statusData) { + if (!agent.devices) return; + agent.devices.forEach(device => { + const macAddress = device.deviceId ? device.deviceId.replace(/:/g, '_') : 'unknown'; + const groupId = device.type ? device.type.replace(/:/g, '_') : 'GID_default'; + const mqttClientId = `${groupId}@@@${macAddress}@@@${macAddress}`; + + if (statusData[mqttClientId]) { + const statusInfo = statusData[mqttClientId]; + let isOnline = false; + if (statusInfo.isAlive === true) { + isOnline = true; + } else if (statusInfo.isAlive === false) { + isOnline = false; + } else if (statusInfo.isAlive === null && statusInfo.exists === true) { + isOnline = true; + } else { + isOnline = false; + } + device.online = isOnline; + device.deviceStatus = isOnline ? 'online' : 'offline'; + } else { + device.online = false; + device.deviceStatus = 'offline'; + } + }); + }, handleAgentClick(agent) { if (this.expandedAgentId === agent.id) { this.expandedAgentId = null; From d03583f493e823c95f1bef81e429f26704397e24 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Wed, 27 May 2026 17:28:58 +0800 Subject: [PATCH 04/14] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/FAQ.md | 1 + docs/device-call-guide.md | 64 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 docs/device-call-guide.md diff --git a/docs/FAQ.md b/docs/FAQ.md index 925a1d39..f18f02aa 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -84,6 +84,7 @@ VAD: 10、[如何部署上下文源](./context-provider-integration.md)
11、[如何集成PowerMem智能记忆](./powermem-integration.md)
12、[如何配置天气插件查询天气](./weather-integration.md)
+13、[如何开启设备呼叫插件](./device-call-guide.md)
### 10、数字人相关教程 1、[数字人digital-human启动方法](./digital-human-wakeword.md)
diff --git a/docs/device-call-guide.md b/docs/device-call-guide.md new file mode 100644 index 00000000..620ea31a --- /dev/null +++ b/docs/device-call-guide.md @@ -0,0 +1,64 @@ +# 设备间相互呼叫插件使用指南 + +## 概述 + +设备呼叫功能允许两个已配置设备之间通过语音/数据通道进行双向通信。设备A呼叫设备B时,系统通过以下流程实现: + +``` +设备A → 授权校验 → MQTT网关 → 设备B远程唤醒 → 建立连接 → 通话建立 +``` + +## 配置步骤 + +### 第一步:开启通讯录功能 + +1. 登录智控台后台 +2. 进入 **系统功能配置** +3. 在左侧功能列表中勾选 **通讯录** +4. 点击 **保存配置** 确认 + +### 第二步:配置设备间呼叫权限 + +1. 在智控台顶部菜单点击 **通讯录** +2. 在左侧智能体下,设备列表中选择你的设备A(支持按 MAC地址 或 备注名 搜索) +3. 在右侧详情面板中,找到目标设备B的称呼设置,例如 **"小王"** +4. 勾选设备B的 **呼叫权限** 复选框 +5. 点击 **保存** + +**双向授权说明:** 如需设备A和设备B互相通信,必须在两侧智控台分别配置对方权限。例如: + +- 在设备A的配置中勾选设备B → 设备A可与设备B通信 +- 在设备B的配置中勾选设备A → 设备B可与设备A通信 + +### 第三步:固件端添加远程唤醒工具 + +1. 在虾哥代码的基础上增加远程唤醒工具MCP:[xiaozhi-esp32](https://github.com/78/xiaozhi-esp32/pull/1244/changes) +2. 按照 [固件编译烧录指南](firmware-build.md) 完成固件烧录 + +### 第四步:配置MQTT网关服务 + +1. 部署MQTT网关服务,参考 [MQTT网关集成文档](mqtt-gateway-integration.md) +2. 如果已经部署请确认代码的版本是2026年5月27日的版本 + +## 呼叫流程说明 + +准备两个设备,在智控台上面配置好通讯权限之后,在其中一个小智对话那里对他说:”呼叫XXX“,观察设备B是否响应。 + +## 常见问题 + +### Q: 设备B没有响应呼叫? + +- 检查设备B是否在线(智控台设备状态) +- 确认设备B的固件已正确集成远程唤醒工具 +- 检查MQTT网关连接是否正常 +- 验证双向权限配置是否完整 + +### Q: 提示"无呼叫权限"? + +- 在智控台确认设备A已勾选设备B的呼叫权限 +- 确认配置已保存(非仅修改未保存) + +### Q: 如何确认通讯录功能已开启? + +- 智控台顶部菜单如显示"通讯录"入口,则表示已开启 + From 1e17e5ac8a8923e5e3f2283808a92b3ae30e6600 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Thu, 28 May 2026 14:41:06 +0800 Subject: [PATCH 05/14] =?UTF-8?q?fixed:=E5=BD=93=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E7=9A=84=E5=B0=B1=E6=95=B0=E6=8D=AE=E4=B8=8D=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=96=B0=E7=89=B9=E6=80=A7=E6=97=B6=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=96=B0=E7=89=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/utils/featureManager.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main/manager-web/src/utils/featureManager.js b/main/manager-web/src/utils/featureManager.js index f3f7a2ae..4e1c161b 100644 --- a/main/manager-web/src/utils/featureManager.js +++ b/main/manager-web/src/utils/featureManager.js @@ -332,6 +332,9 @@ class FeatureManager { Object.keys(featureUpdates).forEach(featureKey => { if (features[featureKey]) { features[featureKey].enabled = featureUpdates[featureKey]; + } else if (this.defaultFeatures[featureKey]) { + features[featureKey] = { ...this.defaultFeatures[featureKey] }; + features[featureKey].enabled = featureUpdates[featureKey]; } }); this.saveConfig(features); From 38958b2d068a563e7bd93a791b50ed96eee986eb Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Thu, 28 May 2026 15:02:20 +0800 Subject: [PATCH 06/14] =?UTF-8?q?add:=E9=80=9A=E8=AE=AF=E5=BD=95i18n?= =?UTF-8?q?=E8=A1=8C=E6=95=B0=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/i18n/de.js | 73 ++++++++++++++++++++++++++++-- main/manager-web/src/i18n/en.js | 71 ++++++++++++++++++++++++++++- main/manager-web/src/i18n/pt_BR.js | 73 +++++++++++++++++++++++++++++- main/manager-web/src/i18n/vi.js | 71 ++++++++++++++++++++++++++++- main/manager-web/src/i18n/zh_CN.js | 2 + main/manager-web/vue.config.js | 6 +-- 6 files changed, 285 insertions(+), 11 deletions(-) diff --git a/main/manager-web/src/i18n/de.js b/main/manager-web/src/i18n/de.js index becddc50..26c40df5 100644 --- a/main/manager-web/src/i18n/de.js +++ b/main/manager-web/src/i18n/de.js @@ -362,8 +362,6 @@ export default { 'replacementWordManagement.edit': 'Bearbeiten', 'replacementWordManagement.download': 'Herunterladen', 'replacementWordManagement.delete': 'Löschen', - - // Replacement word dialog related 'replacementDialog.fileName': 'Dateiname', 'replacementDialog.fileNamePlaceholder': 'Bitte Dateiname eingeben', 'replacementDialog.content': 'Ersetzungsinhalt', @@ -1418,7 +1416,76 @@ export default { 'feature.addressBook.name': 'Adressbuch', 'feature.addressBook.description': 'Kontakt-Adressbuch verwalten und pflegen, um Kontaktinformationen für KI-Assistenten bereitzustellen', - // Address Book Management page + // Adressbuch Verwaltungsseite + 'addressBookManagement.title': 'Adressbuchverwaltung', + 'addressBookManagement.searchPlaceholder': 'Gerätename oder MAC-Adresse suchen', + 'addressBookManagement.search': 'Suchen', + 'addressBookManagement.selectAgent': 'Bitte Agent auswählen', + 'addressBookManagement.name': 'Adressbuchname', + 'addressBookManagement.description': 'Adressbuchbeschreibung', + 'addressBookManagement.contactCount': 'Kontaktanzahl', + 'addressBookManagement.status': 'Aktiviert', + 'addressBookManagement.createdAt': 'Erstellt am', + 'addressBookManagement.operation': 'Aktion', + 'addressBookManagement.add': 'Hinzufügen', + 'addressBookManagement.edit': 'Bearbeiten', + 'addressBookManagement.delete': 'Löschen', + 'addressBookManagement.itemsPerPage': 'Einträge/Seite', + 'addressBookManagement.firstPage': 'Erste', + 'addressBookManagement.prevPage': 'Vorherige', + 'addressBookManagement.nextPage': 'Nächste', + 'addressBookManagement.totalRecords': '{total} Einträge insgesamt', + 'addressBookManagement.addAddressBook': 'Adressbuch hinzufügen', + 'addressBookManagement.editAddressBook': 'Adressbuch bearbeiten', + 'addressBookManagement.getAddressBookListFailed': 'Adressbuchliste konnte nicht geladen werden', + 'addressBookManagement.selectAddressBookFirst': 'Bitte zuerst Adressbücher zum Löschen auswählen', + 'addressBookManagement.confirmBatchDelete': 'Möchten Sie {count} ausgewählte Adressbücher wirklich löschen?', + 'addressBookManagement.batchDeleteSuccess': '{count} Adressbücher erfolgreich gelöscht', + 'addressBookManagement.deleteFailed': 'Löschen fehlgeschlagen, bitte versuchen Sie es erneut', + 'addressBookManagement.operationCancelled': 'Löschvorgang abgebrochen', + 'addressBookManagement.updateSuccess': 'Aktualisierung erfolgreich', + 'addressBookManagement.addSuccess': 'Erfolgreich hinzugefügt', + 'addressBookManagement.updateFailed': 'Aktualisierung fehlgeschlagen', + 'addressBookManagement.addFailed': 'Hinzufügen fehlgeschlagen', + 'addressBookManagement.selectAll': 'Alle auswählen', + 'addressBookManagement.cancelSelectAll': 'Alle abwählen', + 'addressBookManagement.deviceName': 'Gerätename', + 'addressBookManagement.deviceType': 'Gerätetyp', + 'addressBookManagement.deviceId': 'Geräte-ID', + 'addressBookManagement.remarks': 'Bemerkungen', + 'addressBookManagement.addressBookDetail': 'Adressbuchdetails', + 'addressBookManagement.addressBookName': 'Adressbuchname', + 'addressBookManagement.addressBookNamePlaceholder': 'Bitte Adressbuchnamen eingeben', + 'addressBookManagement.addressBookNameRequired': 'Bitte Adressbuchnamen eingeben', + 'addressBookManagement.addressBookNameLength': 'Adressbuchname muss zwischen 1 und 50 Zeichen lang sein', + 'addressBookManagement.addressBookDesc': 'Adressbuchbeschreibung', + 'addressBookManagement.addressBookDescPlaceholder': 'Bitte Adressbuchbeschreibung eingeben', + 'addressBookManagement.remarksPlaceholder': 'Bitte Bemerkungen eingeben', + 'addressBookManagement.selectDevice': 'Bitte ein Gerät auswählen, um Adressbuchdetails anzuzeigen', + 'addressBookManagement.save': 'Speichern', + 'addressBookManagement.saveSuccess': 'Speichern erfolgreich', + 'addressBookManagement.saveFailed': 'Speichern fehlgeschlagen', + 'addressBookManagement.deviceNotSelected': 'Bitte zuerst ein Gerät auswählen', + 'addressBookManagement.addDeviceTip': 'Gerät hinzufügen Funktion ist in Entwicklung', + 'addressBookManagement.selectAgentFirst': 'Bitte zuerst einen Agent auswählen', + + // Adressbuch Dialog + 'addressBookDialog.title': 'Adressbuch', + 'addressBookDialog.name': 'Adressbuchname', + 'addressBookDialog.namePlaceholder': 'Bitte Adressbuchnamen eingeben', + 'addressBookDialog.description': 'Adressbuchbeschreibung', + 'addressBookDialog.descriptionPlaceholder': 'Bitte detaillierte Adressbuchbeschreibung eingeben', + 'addressBookDialog.status': 'Status', + 'addressBookDialog.save': 'Speichern', + 'addressBookDialog.cancel': 'Abbrechen', + 'addressBookDialog.confirm': 'Bestätigen', + 'addressBookDialog.nameRequired': 'Bitte Adressbuchnamen eingeben', + 'addressBookDialog.nameLength': 'Adressbuchname muss zwischen 1 und 50 Zeichen lang sein', + 'addressBookDialog.namePattern': 'Adressbuchname darf nur Buchstaben, Zahlen, Leerzeichen, Unterstriche und Bindestriche enthalten', + 'addressBookDialog.descriptionRequired': 'Bitte Adressbuchbeschreibung eingeben', + 'addressBookDialog.descriptionLength': 'Adressbuchbeschreibung darf 300 Zeichen nicht überschreiten', + + // Adressbuch Verwaltungsseite - Geräteliste 'addressBookManagement.mainTitle': 'Adressbuchverwaltung', 'addressBookManagement.subTitle': 'Geräteadressbuch verwalten, Gerätenamen und Anrufpermissions festlegen', 'addressBookManagement.online': 'Online', diff --git a/main/manager-web/src/i18n/en.js b/main/manager-web/src/i18n/en.js index 8fefce7c..dde1b60b 100644 --- a/main/manager-web/src/i18n/en.js +++ b/main/manager-web/src/i18n/en.js @@ -362,8 +362,6 @@ export default { 'replacementWordManagement.edit': 'Edit', 'replacementWordManagement.download': 'Download', 'replacementWordManagement.delete': 'Delete', - - // Replacement word dialog related 'replacementDialog.fileName': 'File Name', 'replacementDialog.fileNamePlaceholder': 'Please enter file name', 'replacementDialog.content': 'Replacement Content', @@ -1419,6 +1417,75 @@ export default { 'feature.addressBook.description': 'Manage and maintain contact address book to provide contact information support for AI assistant', // Address Book Management page + 'addressBookManagement.title': 'Address Book Management', + 'addressBookManagement.searchPlaceholder': 'Search device name or MAC address', + 'addressBookManagement.search': 'Search', + 'addressBookManagement.selectAgent': 'Please select agent', + 'addressBookManagement.name': 'Address Book Name', + 'addressBookManagement.description': 'Address Book Description', + 'addressBookManagement.contactCount': 'Contact Count', + 'addressBookManagement.status': 'Enabled', + 'addressBookManagement.createdAt': 'Created Time', + 'addressBookManagement.operation': 'Operation', + 'addressBookManagement.add': 'Add', + 'addressBookManagement.edit': 'Edit', + 'addressBookManagement.delete': 'Delete', + 'addressBookManagement.itemsPerPage': 'items/page', + 'addressBookManagement.firstPage': 'First', + 'addressBookManagement.prevPage': 'Previous', + 'addressBookManagement.nextPage': 'Next', + 'addressBookManagement.totalRecords': '{total} records in total', + 'addressBookManagement.addAddressBook': 'Add Address Book', + 'addressBookManagement.editAddressBook': 'Edit Address Book', + 'addressBookManagement.getAddressBookListFailed': 'Failed to get address book list', + 'addressBookManagement.selectAddressBookFirst': 'Please select address books to delete first', + 'addressBookManagement.confirmBatchDelete': 'Are you sure to delete {count} selected address books?', + 'addressBookManagement.batchDeleteSuccess': 'Successfully deleted {count} address books', + 'addressBookManagement.deleteFailed': 'Delete failed, please try again', + 'addressBookManagement.operationCancelled': 'Delete operation cancelled', + 'addressBookManagement.updateSuccess': 'Update successful', + 'addressBookManagement.addSuccess': 'Add successful', + 'addressBookManagement.updateFailed': 'Update failed', + 'addressBookManagement.addFailed': 'Add failed', + 'addressBookManagement.selectAll': 'Select All', + 'addressBookManagement.cancelSelectAll': 'Deselect All', + 'addressBookManagement.deviceName': 'Device Name', + 'addressBookManagement.deviceType': 'Device Type', + 'addressBookManagement.deviceId': 'Device ID', + 'addressBookManagement.remarks': 'Remarks', + 'addressBookManagement.addressBookDetail': 'Address Book Detail', + 'addressBookManagement.addressBookName': 'Address Book Name', + 'addressBookManagement.addressBookNamePlaceholder': 'Please enter address book name', + 'addressBookManagement.addressBookNameRequired': 'Please enter address book name', + 'addressBookManagement.addressBookNameLength': 'Address book name must be between 1 and 50 characters', + 'addressBookManagement.addressBookDesc': 'Address Book Description', + 'addressBookManagement.addressBookDescPlaceholder': 'Please enter address book description', + 'addressBookManagement.remarksPlaceholder': 'Please enter remarks', + 'addressBookManagement.selectDevice': 'Please select a device to view address book details', + 'addressBookManagement.save': 'Save', + 'addressBookManagement.saveSuccess': 'Save successful', + 'addressBookManagement.saveFailed': 'Save failed', + 'addressBookManagement.deviceNotSelected': 'Please select a device first', + 'addressBookManagement.addDeviceTip': 'Add device feature is under development', + 'addressBookManagement.selectAgentFirst': 'Please select an agent first', + + // Address Book Dialog + 'addressBookDialog.title': 'Address Book', + 'addressBookDialog.name': 'Address Book Name', + 'addressBookDialog.namePlaceholder': 'Please enter address book name', + 'addressBookDialog.description': 'Address Book Description', + 'addressBookDialog.descriptionPlaceholder': 'Please enter address book description in detail', + 'addressBookDialog.status': 'Status', + 'addressBookDialog.save': 'Save', + 'addressBookDialog.cancel': 'Cancel', + 'addressBookDialog.confirm': 'Confirm', + 'addressBookDialog.nameRequired': 'Please enter address book name', + 'addressBookDialog.nameLength': 'Address book name must be between 1 and 50 characters', + 'addressBookDialog.namePattern': 'Address book name can only contain letters, numbers, spaces, underscores and hyphens', + 'addressBookDialog.descriptionRequired': 'Please enter address book description', + 'addressBookDialog.descriptionLength': 'Address book description cannot exceed 300 characters', + + // Address Book Management page - Device List 'addressBookManagement.mainTitle': 'Address Book Management', 'addressBookManagement.subTitle': 'Manage device address book, set device names and call permissions', 'addressBookManagement.online': 'Online', diff --git a/main/manager-web/src/i18n/pt_BR.js b/main/manager-web/src/i18n/pt_BR.js index fe202af7..a2d85118 100644 --- a/main/manager-web/src/i18n/pt_BR.js +++ b/main/manager-web/src/i18n/pt_BR.js @@ -1418,7 +1418,76 @@ export default { 'feature.addressBook.name': 'Lista de Contatos', 'feature.addressBook.description': 'Gerenciar e manter lista de contatos para fornecer suporte de informações de contato para assistente de IA', - // Address Book Management page + // Página de Gerenciamento de Lista de Contatos + 'addressBookManagement.title': 'Gerenciamento de Lista de Contatos', + 'addressBookManagement.searchPlaceholder': 'Pesquisar nome do dispositivo ou endereço MAC', + 'addressBookManagement.search': 'Pesquisar', + 'addressBookManagement.selectAgent': 'Selecione um agente', + 'addressBookManagement.name': 'Nome da Lista de Contatos', + 'addressBookManagement.description': 'Descrição da Lista de Contatos', + 'addressBookManagement.contactCount': 'Número de Contatos', + 'addressBookManagement.status': 'Ativo', + 'addressBookManagement.createdAt': 'Data de Criação', + 'addressBookManagement.operation': 'Operação', + 'addressBookManagement.add': 'Adicionar', + 'addressBookManagement.edit': 'Editar', + 'addressBookManagement.delete': 'Excluir', + 'addressBookManagement.itemsPerPage': 'itens/página', + 'addressBookManagement.firstPage': 'Primeira', + 'addressBookManagement.prevPage': 'Anterior', + 'addressBookManagement.nextPage': 'Próxima', + 'addressBookManagement.totalRecords': '{total} registros no total', + 'addressBookManagement.addAddressBook': 'Adicionar Lista de Contatos', + 'addressBookManagement.editAddressBook': 'Editar Lista de Contatos', + 'addressBookManagement.getAddressBookListFailed': 'Falha ao obter lista de contatos', + 'addressBookManagement.selectAddressBookFirst': 'Selecione as listas de contatos para excluir primeiro', + 'addressBookManagement.confirmBatchDelete': 'Tem certeza de que deseja excluir {count} listas de contatos selecionadas?', + 'addressBookManagement.batchDeleteSuccess': '{count} listas de contatos excluídas com sucesso', + 'addressBookManagement.deleteFailed': 'Falha ao excluir, tente novamente', + 'addressBookManagement.operationCancelled': 'Operação de exclusão cancelada', + 'addressBookManagement.updateSuccess': 'Atualização bem-sucedida', + 'addressBookManagement.addSuccess': 'Adição bem-sucedida', + 'addressBookManagement.updateFailed': 'Falha na atualização', + 'addressBookManagement.addFailed': 'Falha ao adicionar', + 'addressBookManagement.selectAll': 'Selecionar Todos', + 'addressBookManagement.cancelSelectAll': 'Desmarcar Todos', + 'addressBookManagement.deviceName': 'Nome do Dispositivo', + 'addressBookManagement.deviceType': 'Tipo do Dispositivo', + 'addressBookManagement.deviceId': 'ID do Dispositivo', + 'addressBookManagement.remarks': 'Observações', + 'addressBookManagement.addressBookDetail': 'Detalhes da Lista de Contatos', + 'addressBookManagement.addressBookName': 'Nome da Lista de Contatos', + 'addressBookManagement.addressBookNamePlaceholder': 'Digite o nome da lista de contatos', + 'addressBookManagement.addressBookNameRequired': 'Digite o nome da lista de contatos', + 'addressBookManagement.addressBookNameLength': 'O nome da lista de contatos deve ter entre 1 e 50 caracteres', + 'addressBookManagement.addressBookDesc': 'Descrição da Lista de Contatos', + 'addressBookManagement.addressBookDescPlaceholder': 'Digite a descrição da lista de contatos', + 'addressBookManagement.remarksPlaceholder': 'Digite as observações', + 'addressBookManagement.selectDevice': 'Selecione um dispositivo para ver os detalhes da lista de contatos', + 'addressBookManagement.save': 'Salvar', + 'addressBookManagement.saveSuccess': 'Salvo com sucesso', + 'addressBookManagement.saveFailed': 'Falha ao salvar', + 'addressBookManagement.deviceNotSelected': 'Selecione um dispositivo primeiro', + 'addressBookManagement.addDeviceTip': 'Recurso de adicionar dispositivo em desenvolvimento', + 'addressBookManagement.selectAgentFirst': 'Selecione um agente primeiro', + + // Diálogo da Lista de Contatos + 'addressBookDialog.title': 'Lista de Contatos', + 'addressBookDialog.name': 'Nome da Lista de Contatos', + 'addressBookDialog.namePlaceholder': 'Digite o nome da lista de contatos', + 'addressBookDialog.description': 'Descrição da Lista de Contatos', + 'addressBookDialog.descriptionPlaceholder': 'Digite a descrição detalhada da lista de contatos', + 'addressBookDialog.status': 'Status', + 'addressBookDialog.save': 'Salvar', + 'addressBookDialog.cancel': 'Cancelar', + 'addressBookDialog.confirm': 'Confirmar', + 'addressBookDialog.nameRequired': 'Digite o nome da lista de contatos', + 'addressBookDialog.nameLength': 'O nome da lista de contatos deve ter entre 1 e 50 caracteres', + 'addressBookDialog.namePattern': 'O nome da lista de contatos pode conter apenas letras, números, espaços, sublinhados e hifens', + 'addressBookDialog.descriptionRequired': 'Digite a descrição da lista de contatos', + 'addressBookDialog.descriptionLength': 'A descrição da lista de contatos não pode exceder 300 caracteres', + + // Página de Gerenciamento de Lista de Contatos - Lista de Dispositivos 'addressBookManagement.mainTitle': 'Gerenciamento de Lista de Contatos', 'addressBookManagement.subTitle': 'Gerenciar lista de contatos do dispositivo, definir nomes e permissões de chamada', 'addressBookManagement.online': 'Online', @@ -1453,4 +1522,4 @@ export default { // Header navigation 'header.addressBook': 'Lista de Contatos', -} +} \ No newline at end of file diff --git a/main/manager-web/src/i18n/vi.js b/main/manager-web/src/i18n/vi.js index 505a277b..d4fb7228 100644 --- a/main/manager-web/src/i18n/vi.js +++ b/main/manager-web/src/i18n/vi.js @@ -1418,7 +1418,76 @@ export default { 'feature.addressBook.name': 'Danh bạ', 'feature.addressBook.description': 'Quản lý và duy trì danh bạ liên lạc để cung cấp hỗ trợ thông tin liên lạc cho trợ lý AI', - // Address Book Management page + // Trang quản lý danh bạ + 'addressBookManagement.title': 'Quản lý danh bạ', + 'addressBookManagement.searchPlaceholder': 'Tìm kiếm tên thiết bị hoặc địa chỉ MAC', + 'addressBookManagement.search': 'Tìm kiếm', + 'addressBookManagement.selectAgent': 'Vui lòng chọn tác tử', + 'addressBookManagement.name': 'Tên danh bạ', + 'addressBookManagement.description': 'Mô tả danh bạ', + 'addressBookManagement.contactCount': 'Số liên hệ', + 'addressBookManagement.status': 'Kích hoạt', + 'addressBookManagement.createdAt': 'Thời gian tạo', + 'addressBookManagement.operation': 'Thao tác', + 'addressBookManagement.add': 'Thêm', + 'addressBookManagement.edit': 'Sửa', + 'addressBookManagement.delete': 'Xóa', + 'addressBookManagement.itemsPerPage': 'mục/trang', + 'addressBookManagement.firstPage': 'Đầu', + 'addressBookManagement.prevPage': 'Trước', + 'addressBookManagement.nextPage': 'Tiếp', + 'addressBookManagement.totalRecords': 'Tổng {total} bản ghi', + 'addressBookManagement.addAddressBook': 'Thêm danh bạ', + 'addressBookManagement.editAddressBook': 'Sửa danh bạ', + 'addressBookManagement.getAddressBookListFailed': 'Lấy danh sách danh bạ thất bại', + 'addressBookManagement.selectAddressBookFirst': 'Vui lòng chọn danh bạ cần xóa trước', + 'addressBookManagement.confirmBatchDelete': 'Bạn có chắc muốn xóa {count} danh bạ đã chọn?', + 'addressBookManagement.batchDeleteSuccess': 'Đã xóa thành công {count} danh bạ', + 'addressBookManagement.deleteFailed': 'Xóa thất bại, vui lòng thử lại', + 'addressBookManagement.operationCancelled': 'Đã hủy thao tác xóa', + 'addressBookManagement.updateSuccess': 'Cập nhật thành công', + 'addressBookManagement.addSuccess': 'Thêm thành công', + 'addressBookManagement.updateFailed': 'Cập nhật thất bại', + 'addressBookManagement.addFailed': 'Thêm thất bại', + 'addressBookManagement.selectAll': 'Chọn tất cả', + 'addressBookManagement.cancelSelectAll': 'Bỏ chọn tất cả', + 'addressBookManagement.deviceName': 'Tên thiết bị', + 'addressBookManagement.deviceType': 'Loại thiết bị', + 'addressBookManagement.deviceId': 'ID thiết bị', + 'addressBookManagement.remarks': 'Ghi chú', + 'addressBookManagement.addressBookDetail': 'Chi tiết danh bạ', + 'addressBookManagement.addressBookName': 'Tên danh bạ', + 'addressBookManagement.addressBookNamePlaceholder': 'Vui lòng nhập tên danh bạ', + 'addressBookManagement.addressBookNameRequired': 'Vui lòng nhập tên danh bạ', + 'addressBookManagement.addressBookNameLength': 'Tên danh bạ phải từ 1 đến 50 ký tự', + 'addressBookManagement.addressBookDesc': 'Mô tả danh bạ', + 'addressBookManagement.addressBookDescPlaceholder': 'Vui lòng nhập mô tả danh bạ', + 'addressBookManagement.remarksPlaceholder': 'Vui lòng nhập ghi chú', + 'addressBookManagement.selectDevice': 'Vui lòng chọn thiết bị để xem chi tiết danh bạ', + 'addressBookManagement.save': 'Lưu', + 'addressBookManagement.saveSuccess': 'Lưu thành công', + 'addressBookManagement.saveFailed': 'Lưu thất bại', + 'addressBookManagement.deviceNotSelected': 'Vui lòng chọn thiết bị trước', + 'addressBookManagement.addDeviceTip': 'Tính năng thêm thiết bị đang phát triển', + 'addressBookManagement.selectAgentFirst': 'Vui lòng chọn tác tử trước', + + // Hộp thoại danh bạ + 'addressBookDialog.title': 'Danh bạ', + 'addressBookDialog.name': 'Tên danh bạ', + 'addressBookDialog.namePlaceholder': 'Vui lòng nhập tên danh bạ', + 'addressBookDialog.description': 'Mô tả danh bạ', + 'addressBookDialog.descriptionPlaceholder': 'Vui lòng nhập mô tả danh bạ chi tiết', + 'addressBookDialog.status': 'Trạng thái', + 'addressBookDialog.save': 'Lưu', + 'addressBookDialog.cancel': 'Hủy', + 'addressBookDialog.confirm': 'Xác nhận', + 'addressBookDialog.nameRequired': 'Vui lòng nhập tên danh bạ', + 'addressBookDialog.nameLength': 'Tên danh bạ phải từ 1 đến 50 ký tự', + 'addressBookDialog.namePattern': 'Tên danh bạ chỉ được chứa chữ cái, số, dấu cách, dấu gạch dưới và dấu gạch ngang', + 'addressBookDialog.descriptionRequired': 'Vui lòng nhập mô tả danh bạ', + 'addressBookDialog.descriptionLength': 'Mô tả danh bạ không được vượt quá 300 ký tự', + + // Trang quản lý danh bạ - Danh sách thiết bị 'addressBookManagement.mainTitle': 'Quản lý danh bạ', 'addressBookManagement.subTitle': 'Quản lý danh bạ thiết bị, đặt tên thiết bị và quyền gọi', 'addressBookManagement.online': 'Trực tuyến', diff --git a/main/manager-web/src/i18n/zh_CN.js b/main/manager-web/src/i18n/zh_CN.js index f5fe59cc..59eaa06b 100644 --- a/main/manager-web/src/i18n/zh_CN.js +++ b/main/manager-web/src/i18n/zh_CN.js @@ -362,6 +362,8 @@ export default { 'replacementWordManagement.edit': '编辑', 'replacementWordManagement.download': '下载', 'replacementWordManagement.delete': '删除', + + // 替换词对话框相关 'replacementDialog.fileName': '文件名称', 'replacementDialog.fileNamePlaceholder': '请输入文件名称', 'replacementDialog.content': '替换词内容', diff --git a/main/manager-web/vue.config.js b/main/manager-web/vue.config.js index 6b6ba4f2..caef2895 100644 --- a/main/manager-web/vue.config.js +++ b/main/manager-web/vue.config.js @@ -11,7 +11,7 @@ const { InjectManifest } = require('workbox-webpack-plugin'); // 引入 path 模块 const path = require('path') - + function resolve(dir) { return path.join(__dirname, dir) } @@ -39,12 +39,12 @@ const cdnResources = { const useCDN = process.env.VUE_APP_USE_CDN === 'true'; module.exports = defineConfig({ - productionSourceMap: process.env.NODE_ENV !=='production', // 生产环境不生成 source map + productionSourceMap: process.env.NODE_ENV !== 'production', // 生产环境不生成 source map devServer: { port: 8001, // 指定端口为 8001 proxy: { '/xiaozhi': { - target: 'http://127.0.0.1:8002', + target: 'http://test.aiot.xin-nan.com', changeOrigin: true } }, From 84187d4e68da12e7d7c35313ca8e7b6efbb08439 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Thu, 28 May 2026 15:18:54 +0800 Subject: [PATCH 07/14] =?UTF-8?q?update:=E4=BC=98=E5=8C=96=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/i18n/de.js | 6 ++++-- main/manager-web/src/i18n/en.js | 6 ++++-- main/manager-web/src/i18n/pt_BR.js | 4 ++-- main/manager-web/src/i18n/vi.js | 4 ++-- main/manager-web/src/i18n/zh_CN.js | 4 ++-- main/manager-web/src/i18n/zh_TW.js | 4 ++-- main/manager-web/src/views/AddressBookManagement.vue | 2 +- main/manager-web/vue.config.js | 2 +- 8 files changed, 18 insertions(+), 14 deletions(-) diff --git a/main/manager-web/src/i18n/de.js b/main/manager-web/src/i18n/de.js index 26c40df5..0bce6ec1 100644 --- a/main/manager-web/src/i18n/de.js +++ b/main/manager-web/src/i18n/de.js @@ -362,6 +362,8 @@ export default { 'replacementWordManagement.edit': 'Bearbeiten', 'replacementWordManagement.download': 'Herunterladen', 'replacementWordManagement.delete': 'Löschen', + + // Replacement word dialog related 'replacementDialog.fileName': 'Dateiname', 'replacementDialog.fileNamePlaceholder': 'Bitte Dateiname eingeben', 'replacementDialog.content': 'Ersetzungsinhalt', @@ -1492,11 +1494,11 @@ export default { 'addressBookManagement.offline': 'Offline', 'addressBookManagement.macAddress': 'MAC-Adresse', 'addressBookManagement.status': 'Status', - 'addressBookManagement.deviceGroup': 'Gerätegruppe', + 'addressBookManagement.deviceGroup': 'Zugeordneter Agent', 'addressBookManagement.addTime': 'Hinzugefügt Zeit', 'addressBookManagement.lastOnline': 'Zuletzt Online', 'addressBookManagement.deviceCallPermission': 'Geräteanrufpermission', - 'addressBookManagement.setPermissionDesc': 'Festlegen welche Geräte das aktuelle Gerät anrufen kann', + 'addressBookManagement.setPermissionDesc': 'Festlegen welche Geräte das aktuelle Gerät anrufen kann ({count} ausgewählt)', 'addressBookManagement.selectAll': 'Alle auswählen', 'addressBookManagement.deselectAll': 'Alle abwählen', diff --git a/main/manager-web/src/i18n/en.js b/main/manager-web/src/i18n/en.js index dde1b60b..2772a56b 100644 --- a/main/manager-web/src/i18n/en.js +++ b/main/manager-web/src/i18n/en.js @@ -362,6 +362,8 @@ export default { 'replacementWordManagement.edit': 'Edit', 'replacementWordManagement.download': 'Download', 'replacementWordManagement.delete': 'Delete', + + // Replacement word dialog related 'replacementDialog.fileName': 'File Name', 'replacementDialog.fileNamePlaceholder': 'Please enter file name', 'replacementDialog.content': 'Replacement Content', @@ -1492,11 +1494,11 @@ export default { 'addressBookManagement.offline': 'Offline', 'addressBookManagement.macAddress': 'MAC Address', 'addressBookManagement.status': 'Status', - 'addressBookManagement.deviceGroup': 'Device Group', + 'addressBookManagement.deviceGroup': 'Assigned Agent', 'addressBookManagement.addTime': 'Added Time', 'addressBookManagement.lastOnline': 'Last Online', 'addressBookManagement.deviceCallPermission': 'Device Call Permission', - 'addressBookManagement.setPermissionDesc': 'Set which devices the current device can call', + 'addressBookManagement.setPermissionDesc': 'Set which devices the current device can call ({count} selected)', 'addressBookManagement.selectAll': 'Select All', 'addressBookManagement.deselectAll': 'Deselect All', diff --git a/main/manager-web/src/i18n/pt_BR.js b/main/manager-web/src/i18n/pt_BR.js index a2d85118..02e876b7 100644 --- a/main/manager-web/src/i18n/pt_BR.js +++ b/main/manager-web/src/i18n/pt_BR.js @@ -1494,11 +1494,11 @@ export default { 'addressBookManagement.offline': 'Offline', 'addressBookManagement.macAddress': 'Endereço MAC', 'addressBookManagement.status': 'Status', - 'addressBookManagement.deviceGroup': 'Grupo de Dispositivos', + 'addressBookManagement.deviceGroup': 'Agente Vinculado', 'addressBookManagement.addTime': 'Hora de Adição', 'addressBookManagement.lastOnline': 'Última Conexão', 'addressBookManagement.deviceCallPermission': 'Permissão de Chamada do Dispositivo', - 'addressBookManagement.setPermissionDesc': 'Definir quais dispositivos o dispositivo atual pode chamar', + 'addressBookManagement.setPermissionDesc': 'Definir quais dispositivos o dispositivo atual pode chamar ({count} selecionados)', 'addressBookManagement.selectAll': 'Selecionar Todos', 'addressBookManagement.deselectAll': 'Desmarcar Todos', diff --git a/main/manager-web/src/i18n/vi.js b/main/manager-web/src/i18n/vi.js index d4fb7228..f330d75f 100644 --- a/main/manager-web/src/i18n/vi.js +++ b/main/manager-web/src/i18n/vi.js @@ -1494,11 +1494,11 @@ export default { 'addressBookManagement.offline': 'Ngoại tuyến', 'addressBookManagement.macAddress': 'Địa chỉ MAC', 'addressBookManagement.status': 'Trạng thái', - 'addressBookManagement.deviceGroup': 'Nhóm thiết bị', + 'addressBookManagement.deviceGroup': 'Tác tử sở hữu', 'addressBookManagement.addTime': 'Thời gian thêm', 'addressBookManagement.lastOnline': 'Hoạt động lần cuối', 'addressBookManagement.deviceCallPermission': 'Quyền gọi thiết bị', - 'addressBookManagement.setPermissionDesc': 'Đặt thiết bị nào thiết bị hiện tại có thể gọi', + 'addressBookManagement.setPermissionDesc': 'Đặt thiết bị nào thiết bị hiện tại có thể gọi ({count} đã chọn)', 'addressBookManagement.selectAll': 'Chọn tất cả', 'addressBookManagement.deselectAll': 'Bỏ chọn tất cả', diff --git a/main/manager-web/src/i18n/zh_CN.js b/main/manager-web/src/i18n/zh_CN.js index 59eaa06b..b10cb43c 100644 --- a/main/manager-web/src/i18n/zh_CN.js +++ b/main/manager-web/src/i18n/zh_CN.js @@ -1497,11 +1497,11 @@ export default { 'addressBookManagement.offline': '离线', 'addressBookManagement.macAddress': 'MAC地址', 'addressBookManagement.status': '状态', - 'addressBookManagement.deviceGroup': '设备分组', + 'addressBookManagement.deviceGroup': '所属智能体', 'addressBookManagement.addTime': '添加时间', 'addressBookManagement.lastOnline': '最后在线', 'addressBookManagement.deviceCallPermission': '设备呼叫权限', - 'addressBookManagement.setPermissionDesc': '设置当前设备可以呼叫的设备', + 'addressBookManagement.setPermissionDesc': '设置当前设备可以呼叫的设备({count}台)', 'addressBookManagement.selectAll': '全选', 'addressBookManagement.deselectAll': '全不选', diff --git a/main/manager-web/src/i18n/zh_TW.js b/main/manager-web/src/i18n/zh_TW.js index 37beebb8..34772624 100644 --- a/main/manager-web/src/i18n/zh_TW.js +++ b/main/manager-web/src/i18n/zh_TW.js @@ -1497,11 +1497,11 @@ export default { 'addressBookManagement.offline': '離線', 'addressBookManagement.macAddress': 'MAC地址', 'addressBookManagement.status': '狀態', - 'addressBookManagement.deviceGroup': '設備分組', + 'addressBookManagement.deviceGroup': '所屬智能體', 'addressBookManagement.addTime': '添加時間', 'addressBookManagement.lastOnline': '最後在線', 'addressBookManagement.deviceCallPermission': '設備呼叫權限', - 'addressBookManagement.setPermissionDesc': '設置當前設備可以呼叫的設備', + 'addressBookManagement.setPermissionDesc': '設置當前設備可以呼叫的設備({count}台)', 'addressBookManagement.selectAll': '全選', 'addressBookManagement.deselectAll': '全不選', diff --git a/main/manager-web/src/views/AddressBookManagement.vue b/main/manager-web/src/views/AddressBookManagement.vue index 9c36966f..ddf4b0a8 100644 --- a/main/manager-web/src/views/AddressBookManagement.vue +++ b/main/manager-web/src/views/AddressBookManagement.vue @@ -138,7 +138,7 @@

{{ $t('addressBookManagement.deviceCallPermission') }}

-

{{ $t('addressBookManagement.setPermissionDesc') }}

+

{{ $t('addressBookManagement.setPermissionDesc', { count: selectedPermissions.length }) }}

diff --git a/main/manager-web/vue.config.js b/main/manager-web/vue.config.js index caef2895..89b85ddc 100644 --- a/main/manager-web/vue.config.js +++ b/main/manager-web/vue.config.js @@ -44,7 +44,7 @@ module.exports = defineConfig({ port: 8001, // 指定端口为 8001 proxy: { '/xiaozhi': { - target: 'http://test.aiot.xin-nan.com', + target: 'http://127.0.0.1:8002', changeOrigin: true } }, From 0678521b165372a11da8fcc8e6776532bed22c7c Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Thu, 28 May 2026 19:36:26 +0800 Subject: [PATCH 08/14] =?UTF-8?q?update:=E4=BC=98=E5=8C=96addressBook?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=9A=84=E7=9A=84=E5=AD=98=E5=82=A8=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{202605251425.sql => 202605251426.sql} | 19 +++++++++++++------ .../db/changelog/db.changelog-master.yaml | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) rename main/manager-api/src/main/resources/db/changelog/{202605251425.sql => 202605251426.sql} (74%) diff --git a/main/manager-api/src/main/resources/db/changelog/202605251425.sql b/main/manager-api/src/main/resources/db/changelog/202605251426.sql similarity index 74% rename from main/manager-api/src/main/resources/db/changelog/202605251425.sql rename to main/manager-api/src/main/resources/db/changelog/202605251426.sql index db57d573..c34e8ec8 100644 --- a/main/manager-api/src/main/resources/db/changelog/202605251425.sql +++ b/main/manager-api/src/main/resources/db/changelog/202605251426.sql @@ -2,19 +2,26 @@ SET @data_exists = (SELECT COUNT(*) FROM ai_model_provider WHERE id = 'SYSTEM_PLUGIN_CALL_DEVICE'); SET @sql = IF(@data_exists = 0, 'INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, -`update_date`) VALUES (''SYSTEM_PLUGIN_CALL_DEVICE'', ''Plugin'', ''call_device'', ''呼叫设备'', ''[]'', 85, 1988490863118454785, ''2026-05-18 +`update_date`) VALUES (''SYSTEM_PLUGIN_CALL_DEVICE'', ''Plugin'', ''call_device'', ''设备呼叫设备'', ''[]'', 85, 1988490863118454785, ''2026-05-18 12:00:00'', 1988490863118454785, ''2026-05-18 12:00:00'')', 'SELECT ''data already exists, skip'' AS msg'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 更新系统菜单配置,增加联系人管理菜单 UPDATE sys_params -SET param_value = REPLACE( - param_value, - '"mcpAccessPoint":{"name":"feature.mcpAccessPoint.name","enabled":true,"description":"feature.mcpAccessPoint.description"}', - '"mcpAccessPoint":{"name":"feature.mcpAccessPoint.name","enabled":true,"description":"feature.mcpAccessPoint.description"},"addressBook":{"name":"feature.addressBook.name","enabled":false,"description":"feature.addressBook.description"}' +SET param_value = CAST( + JSON_SET( + CAST(param_value AS JSON), + '$.addressBook', + JSON_OBJECT( + 'name', 'feature.addressBook.name', + 'enabled', FALSE, + 'description', 'feature.addressBook.description' + ) + ) AS CHAR ) -WHERE param_code = 'system-web.menu'; +WHERE param_code = 'system-web.menu' + AND NOT JSON_CONTAINS_PATH(CAST(param_value AS JSON), 'one', '$.addressBook'); -- 创建设备通讯录表 CREATE TABLE IF NOT EXISTS `ai_device_address_book` ( diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index 26ed66cd..aff608c6 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -670,9 +670,9 @@ databaseChangeLog: encoding: utf8 path: classpath:db/changelog/202605121509.sql - changeSet: - id: 202605251425 + id: 202605251426 author: RanChen changes: - sqlFile: encoding: utf8 - path: classpath:db/changelog/202605251425.sql + path: classpath:db/changelog/202605251426.sql From 9ea67d430626d07bd49bc05809fb3a0b1131e170 Mon Sep 17 00:00:00 2001 From: zhuoqinglian <1035449612@qq.com> Date: Fri, 29 May 2026 10:04:54 +0800 Subject: [PATCH 09/14] =?UTF-8?q?update:=20=E9=80=9A=E8=AE=AF=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/i18n/de.js | 1 + main/manager-web/src/i18n/en.js | 1 + main/manager-web/src/i18n/pt_BR.js | 1 + main/manager-web/src/i18n/vi.js | 1 + main/manager-web/src/i18n/zh_CN.js | 2 +- main/manager-web/src/i18n/zh_TW.js | 2 +- .../src/views/AddressBookManagement.vue | 422 ++++++++++-------- 7 files changed, 246 insertions(+), 184 deletions(-) diff --git a/main/manager-web/src/i18n/de.js b/main/manager-web/src/i18n/de.js index becddc50..b88434cc 100644 --- a/main/manager-web/src/i18n/de.js +++ b/main/manager-web/src/i18n/de.js @@ -1432,6 +1432,7 @@ export default { 'addressBookManagement.setPermissionDesc': 'Festlegen welche Geräte das aktuelle Gerät anrufen kann', 'addressBookManagement.selectAll': 'Alle auswählen', 'addressBookManagement.deselectAll': 'Alle abwählen', + 'addressBookManagement.save': 'Berechtigungen speichern', // Address Book Management page - operation messages 'addressBookManagement.aliasSaved': 'Alias gespeichert', diff --git a/main/manager-web/src/i18n/en.js b/main/manager-web/src/i18n/en.js index 8fefce7c..4a53ec7e 100644 --- a/main/manager-web/src/i18n/en.js +++ b/main/manager-web/src/i18n/en.js @@ -1432,6 +1432,7 @@ export default { 'addressBookManagement.setPermissionDesc': 'Set which devices the current device can call', 'addressBookManagement.selectAll': 'Select All', 'addressBookManagement.deselectAll': 'Deselect All', + 'addressBookManagement.save': 'Save Permissions', // Address Book Management page - operation messages 'addressBookManagement.aliasSaved': 'Alias saved', diff --git a/main/manager-web/src/i18n/pt_BR.js b/main/manager-web/src/i18n/pt_BR.js index fe202af7..287b3d8a 100644 --- a/main/manager-web/src/i18n/pt_BR.js +++ b/main/manager-web/src/i18n/pt_BR.js @@ -1432,6 +1432,7 @@ export default { 'addressBookManagement.setPermissionDesc': 'Definir quais dispositivos o dispositivo atual pode chamar', 'addressBookManagement.selectAll': 'Selecionar Todos', 'addressBookManagement.deselectAll': 'Desmarcar Todos', + 'addressBookManagement.save': 'Salvar Permissões', // Address Book Management page - operation messages 'addressBookManagement.aliasSaved': 'Apelido salvo', diff --git a/main/manager-web/src/i18n/vi.js b/main/manager-web/src/i18n/vi.js index 505a277b..3298c67a 100644 --- a/main/manager-web/src/i18n/vi.js +++ b/main/manager-web/src/i18n/vi.js @@ -1432,6 +1432,7 @@ export default { 'addressBookManagement.setPermissionDesc': 'Đặt thiết bị nào thiết bị hiện tại có thể gọi', 'addressBookManagement.selectAll': 'Chọn tất cả', 'addressBookManagement.deselectAll': 'Bỏ chọn tất cả', + 'addressBookManagement.save': 'Lưu quyền', // Address Book Management page - operation messages 'addressBookManagement.aliasSaved': 'Bí danh đã lưu', diff --git a/main/manager-web/src/i18n/zh_CN.js b/main/manager-web/src/i18n/zh_CN.js index f5fe59cc..39f7911f 100644 --- a/main/manager-web/src/i18n/zh_CN.js +++ b/main/manager-web/src/i18n/zh_CN.js @@ -1462,7 +1462,7 @@ export default { 'addressBookManagement.addressBookDescPlaceholder': '请输入通讯录描述', 'addressBookManagement.remarksPlaceholder': '请输入备注信息', 'addressBookManagement.selectDevice': '请选择一个设备查看通讯录详情', - 'addressBookManagement.save': '保存', + 'addressBookManagement.save': '保存权限', 'addressBookManagement.saveSuccess': '保存成功', 'addressBookManagement.saveFailed': '保存失败', 'addressBookManagement.deviceNotSelected': '请先选择一个设备', diff --git a/main/manager-web/src/i18n/zh_TW.js b/main/manager-web/src/i18n/zh_TW.js index 37beebb8..1d2bed2b 100644 --- a/main/manager-web/src/i18n/zh_TW.js +++ b/main/manager-web/src/i18n/zh_TW.js @@ -1464,7 +1464,7 @@ export default { 'addressBookManagement.addressBookDescPlaceholder': '請輸入通訊錄描述', 'addressBookManagement.remarksPlaceholder': '請輸入備註信息', 'addressBookManagement.selectDevice': '請選擇一個設備查看通訊錄詳情', - 'addressBookManagement.save': '儲存', + 'addressBookManagement.save': '保存權限', 'addressBookManagement.saveSuccess': '儲存成功', 'addressBookManagement.saveFailed': '儲存失敗', 'addressBookManagement.deviceNotSelected': '請先選擇一個設備', diff --git a/main/manager-web/src/views/AddressBookManagement.vue b/main/manager-web/src/views/AddressBookManagement.vue index 9c36966f..868add0c 100644 --- a/main/manager-web/src/views/AddressBookManagement.vue +++ b/main/manager-web/src/views/AddressBookManagement.vue @@ -22,32 +22,21 @@
-
-
-
-
- -
-
-
{{ agent.agentName }} ({{ agent.deviceCount || 0 }})
-
-
- -
+ + + +
avatar @@ -63,8 +52,8 @@
-
-
+ +
@@ -92,13 +81,16 @@
{{ $t('addressBookManagement.macAddress') }}:{{ selectedDevice.deviceId }}
- {{ $t('addressBookManagement.status') }}:{{ selectedDevice.online ? $t('addressBookManagement.online') : $t('addressBookManagement.offline') }} + {{ $t('addressBookManagement.status') }}: + + + {{ selectedDevice.online ? $t('addressBookManagement.online') : $t('addressBookManagement.offline') }}
- +
{{ $t('addressBookManagement.deviceGroup') }}
@@ -124,7 +116,7 @@
- +
{{ $t('addressBookManagement.lastOnline') }}
{{ getTimeAgo(selectedDevice.lastConnectedAt) }}
@@ -141,10 +133,10 @@

{{ $t('addressBookManagement.setPermissionDesc') }}

+ {{ $t('common.cancel') }} {{ isAllSelected ? $t('addressBookManagement.deselectAll') : $t('addressBookManagement.selectAll') }} - {{ $t('common.cancel') }} {{ $t('addressBookManagement.save') }}
@@ -155,8 +147,12 @@ :key="device.id" class="permission-item" :class="{ active: selectedPermissions.includes(device.id) }" - @click="handlePermissionToggle(device.id)" > +
avatar
@@ -183,10 +179,8 @@
-
- -

{{ $t('addressBookManagement.selectDevice') }}

-
+ + @@ -351,7 +345,9 @@ export default { this.selectedDevice = null; } }, - handleDeviceClick(device) { + handleDeviceClick(device, agent) { + this.expandedAgentId = agent.id; + this.selectedAgent = agent; this.selectedDevice = device; // 加载所有设备用于权限选择(排除当前设备) this.allDevices = this.agentDeviceOptions @@ -410,12 +406,16 @@ export default { this.editingDeviceId = null; this.editingDeviceName = ''; }, - handlePermissionToggle(deviceId) { - const index = this.selectedPermissions.indexOf(deviceId); - if (index > -1) { - this.selectedPermissions.splice(index, 1); + handlePermissionToggle(deviceId, checked) { + if (checked) { + if (!this.selectedPermissions.includes(deviceId)) { + this.selectedPermissions.push(deviceId); + } } else { - this.selectedPermissions.push(deviceId); + const index = this.selectedPermissions.indexOf(deviceId); + if (index > -1) { + this.selectedPermissions.splice(index, 1); + } } }, handleToggleSelectAll() { @@ -590,16 +590,113 @@ export default { display: flex; flex: 1; margin: 20px 22px 0 22px; - gap: 20px; + gap: 8px; min-height: 0; } } +.agent-collapse { + flex: 1; + padding-right: 10px; + padding-left: 20px; + overflow-y: auto; + scrollbar-width: thin; + .agent-name { + font-size: 14px; + font-weight: 500; + color: #686f85; + text-align: left; + } + + .device-list { + display: flex; + flex-direction: column; + margin-top: 8px; + .device-item { + padding: 4px 15px; + border-radius: 6px; + margin-bottom: 8px; + cursor: pointer; + transition: all 0.3s ease; + border: 1px solid transparent; + display: flex; + align-items: center; + gap: 12px; + background: none; + + &:hover { + background: #ebf1fe; + } + + &.active { + .device-name { + color: #7297fd !important; + } + border-color: #b3c0fc; + background: #ebf1fe; + } + + .device-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + overflow: hidden; + flex-shrink: 0; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + .device-content { + flex: 1; + min-width: 0; + .device-name { + font-size: 14px; + color: #686f85; + text-align: left; + line-height: 16px; + // margin-bottom: 4px; + } + } + + + .device-row { + display: flex; + justify-content: space-between; + align-items: center; + + .device-id { + font-size: 12px; + line-height: 16px; + color: #909399; + } + + .device-status { + font-size: 12px; + line-height: 16px; + padding: 2px 8px; + border-radius: 4px; + + &.online { + color: #4fbe80; + } + + &.offline { + color: #9ea3b0; + } + } + } + } + } +} .left-panel { - width: 20%; + width: 19%; background: white; border-radius: 15px; - padding: 20px; + padding: 20px 0; display: flex; flex-direction: column; min-height: 0; @@ -662,16 +759,17 @@ export default { margin-bottom: 6px; .device-name-text { + line-height: 34px; font-size: 24px; - font-weight: 600; - color: #303133; + color: #3d4566; cursor: pointer; } .alias-input { + height: 28px; font-size: 24px; font-weight: 600; - color: #303133; + color: #3d4566; border: 1px solid #6b8cff; border-radius: 4px; padding: 2px 8px; @@ -692,27 +790,32 @@ export default { .device-mac { font-size: 12px; - color: #606266; + color: #686f85; margin-bottom: 4px; text-align: left; } .device-status { + display: flex; + margin-top: 4px; font-size: 12px; - color: #606266; + color: #686f85; text-align: left; .online { - color: #52c41a; + margin-left: 4px; + color: #686f85; display: inline-flex; align-items: center; gap: 4px; .status-dot { - width: 8px; - height: 8px; + margin-top: -3px; + margin-right: 4px; + width: 9px; + height: 9px; border-radius: 50%; - background: #52c41a; + background: #4eb36c; } } @@ -723,8 +826,10 @@ export default { gap: 4px; .status-dot { - width: 8px; - height: 8px; + margin-top: -3px; + margin-right: 4px; + width: 9px; + height: 9px; border-radius: 50%; background: #999; } @@ -744,9 +849,9 @@ export default { align-items: center; gap: 10px; padding: 12px 15px; - background: #f5f7fa; border-radius: 8px; min-width: 140px; + border: 1px solid #eaedf4; i { font-size: 28px; @@ -765,7 +870,7 @@ export default { .stat-value { font-size: 14px; - color: #303133; + color: #3d4566; font-weight: 500; } @@ -790,7 +895,7 @@ export default { .stat-edit-input { font-size: 14px; - color: #303133; + color: #3d4566; font-weight: 500; border: 1px solid #6b8cff; border-radius: 4px; @@ -821,10 +926,10 @@ export default { text-align: left; h3 { - font-size: 16px; - font-weight: 600; - color: #303133; - margin: 0 0 4px 0; + font-size: 24px; + font-weight: 500; + color: #3d4566; + margin: 0 0 10px 0; } .section-desc { @@ -843,7 +948,8 @@ export default { .permission-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + grid-template-rows: repeat(auto-fill, minmax(100px, 1fr)); gap: 12px; overflow-y: auto; flex: 1; @@ -851,25 +957,51 @@ export default { .permission-item { padding: 12px; - background: #f5f7fa; + background: linear-gradient(135deg, #f8fafd 0%, #ffffff 100%); border-radius: 8px; - cursor: pointer; transition: all 0.3s ease; - border: 2px solid transparent; + border: 2px solid #eaedf4; display: flex; align-items: center; gap: 10px; + position: relative; - &:hover { - background: #e6f0ff; - } &.active { - border-color: #6b8cff; - background: #e6f0ff; + border-color: #c6d1fd; .permission-name { - color: #6b8cff; + color: #3765f8; + } + } + + .permission-checkbox { + position: absolute; + top: 8px; + right: 8px; + + ::v-deep(.el-checkbox__inner) { + width: 16px; + height: 16px; + border-radius: 4px; + border-color: #dcdfe6; + background-color: #ffffff; + + &::after { + width: 4px; + height: 8px; + left: 5px; + top: 2px; + } + } + + ::v-deep(.el-checkbox__input.is-checked .el-checkbox__inner) { + background-color: #3765f8; + border-color: #3765f8; + } + + ::v-deep(.el-checkbox__input.is-checked + .el-checkbox__label) { + color: #3765f8; } } @@ -894,17 +1026,18 @@ export default { .permission-name { font-size: 14px; - color: #303133; + color: #3d4566; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .permission-name-row { + line-height: 22px; display: flex; align-items: center; gap: 8px; - margin-bottom: 6px; + margin-bottom: 4px; .permission-name { flex: 1; @@ -912,6 +1045,7 @@ export default { } .permission-edit-btn { + margin-right: 24px; font-size: 14px; color: #909399; cursor: pointer; @@ -947,29 +1081,30 @@ export default { .permission-edit-input { font-size: 14px; - color: #303133; + color: #3d4566; border: 1px solid #6b8cff; border-radius: 4px; padding: 2px 6px; outline: none; - width: 80px; + width: calc(100% - 40px); } .left-header { margin-bottom: 20px; text-align: left; + padding: 0 20px; } .main-title { - font-size: 28px; - font-weight: 600; - color: #303133; + font-size: 24px; + font-weight: 500; + color: #3d4566; margin: 0 0 8px 0; } .sub-title { font-size: 14px; - color: #909399; + color: #a6aebe; margin: 0; } @@ -977,6 +1112,12 @@ export default { display: flex; gap: 10px; margin-bottom: 15px; + padding: 0 20px; +} + +.agent-triangle { + margin-right: 8px; + flex-shrink: 0; } .search-input { @@ -984,31 +1125,17 @@ export default { } .btn-search { + padding: 10px 20px; background: linear-gradient(135deg, #6b8cff, #a966ff); border: none; color: white; width: 80px; } -.agent-list { - display: flex; - flex-direction: column; - gap: 8px; - overflow-y: auto; - flex: 1; - min-height: 0; - padding-right: 5px; -} - -.agent-item { - display: flex; - flex-direction: column; -} - .agent-card { width: 100%; - padding: 12px 15px; - background: #f5f7fa; + padding: 12px 0px; + // background: #f5f7fa; border-radius: 8px; cursor: pointer; display: flex; @@ -1033,7 +1160,7 @@ export default { display: flex; align-items: center; justify-content: center; - color: #606266; + color: #686f85; transition: transform 0.3s ease; i { @@ -1051,7 +1178,7 @@ export default { .agent-name { font-size: 14px; font-weight: 600; - color: #303133; + color: #3d4566; text-align: left; } @@ -1062,91 +1189,6 @@ export default { } } -.device-list { - display: flex; - flex-direction: column; - padding-left: 20px; - margin-top: 8px; -} - -.device-item { - padding: 10px 15px; - background: #f5f7fa; - border-radius: 8px; - margin-bottom: 8px; - cursor: pointer; - transition: all 0.3s ease; - border: 2px solid transparent; - display: flex; - align-items: center; - gap: 12px; - - &:hover { - background: #e6f0ff; - } - - &.active { - .device-name { - color: #6b8cff; - } - border-color: #6b8cff; - background: #e6f0ff; - } - - .device-avatar { - width: 40px; - height: 40px; - border-radius: 50%; - overflow: hidden; - flex-shrink: 0; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } - } - - .device-content { - flex: 1; - min-width: 0; - } - - .device-name { - font-size: 14px; - color: #606266; - text-align: left; - margin-bottom: 4px; - } - - .device-row { - display: flex; - justify-content: space-between; - align-items: center; - - .device-id { - font-size: 12px; - color: #909399; - } - - .device-status { - font-size: 12px; - padding: 2px 8px; - border-radius: 4px; - - &.online { - background: #e6f7ff; - color: #52c41a; - } - - &.offline { - background: #f5f5f5; - color: #999; - } - } - } -} - .right-panel { width: 75%; background: white; @@ -1192,10 +1234,26 @@ export default { } ::v-deep(.el-input__inner) { - border-radius: 4px; + height: 36px; + border-radius: 6px; } ::v-deep(.el-textarea__inner) { border-radius: 4px; } +::v-deep(.el-collapse) { + border: none !important; +} +::v-deep(.el-collapse-item__header) { + border: none !important; +} +::v-deep(.el-collapse-item__wrap) { + border: none !important; +} +::v-deep(.el-collapse-item__content) { + padding-bottom: 0 !important; +} +::v-deep(.el-empty) { + height: 100%; +} From 2adf51ddf961c03beee83c8a8e083e6e2445ada4 Mon Sep 17 00:00:00 2001 From: zhuoqinglian <1035449612@qq.com> Date: Fri, 29 May 2026 14:36:14 +0800 Subject: [PATCH 10/14] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=A1=B6?= =?UTF-8?q?=E9=83=A8=E5=AF=BC=E8=88=AA=E6=A0=8F=E9=81=AE=E6=8C=A1=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/components/HeaderBar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/manager-web/src/components/HeaderBar.vue b/main/manager-web/src/components/HeaderBar.vue index 6c1c09eb..54b5342e 100644 --- a/main/manager-web/src/components/HeaderBar.vue +++ b/main/manager-web/src/components/HeaderBar.vue @@ -669,7 +669,7 @@ export default { gap: 25px; position: absolute; left: 50%; - transform: translateX(-50%); + transform: translateX(calc(-50% - 80px)); } .header-right { From 4392edf27d392793854bbda612b5f8e7f615325a Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Fri, 29 May 2026 15:56:52 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/device-call-guide.md | 78 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/docs/device-call-guide.md b/docs/device-call-guide.md index 620ea31a..2f940518 100644 --- a/docs/device-call-guide.md +++ b/docs/device-call-guide.md @@ -30,19 +30,87 @@ - 在设备A的配置中勾选设备B → 设备A可与设备B通信 - 在设备B的配置中勾选设备A → 设备B可与设备A通信 -### 第三步:固件端添加远程唤醒工具 +### 第三步:在智能体配置添加呼叫工具 -1. 在虾哥代码的基础上增加远程唤醒工具MCP:[xiaozhi-esp32](https://github.com/78/xiaozhi-esp32/pull/1244/changes) -2. 按照 [固件编译烧录指南](firmware-build.md) 完成固件烧录 +1. 在智控台顶部菜单点击 **智能体管理** +2. 在刚刚配置设备联系人的相关智能体中点击 **编辑角色** +3. 在右侧详情面板中,点击 **编辑功能** +4. 勾选 **设备呼叫设备** 工具 +5. 点击 **保存配置** 确认 +6. 在外侧再次点击 **保存配置** ,随即重启设备 -### 第四步:配置MQTT网关服务 +### 第四步:固件端添加远程唤醒工具 + +1. 在[xiaozhi-esp32](https://github.com/78/xiaozhi-esp32) 代码的基础上增加远程唤醒工具MCP,版本支持为2.1.0至2.2.6(2026年5月29日的版本) +2. 在application.h文件中添加远程唤醒函数声明 + ```cpp + void RemoteWakeup(const std::string& reason); + ``` +3. 在application.cc文件中添加远程唤醒函数 + ```cpp + void Application::RemoteWakeup(const std::string& reason){ + if (!protocol_) { + return; + } + + auto state = GetDeviceState(); + + if (state == kDeviceStateIdle) { + audio_service_.EncodeWakeWord(); + + if (!protocol_->IsAudioChannelOpened()) { + SetDeviceState(kDeviceStateConnecting); + if (!protocol_->OpenAudioChannel()) { + audio_service_.EnableWakeWordDetection(true); + return; + } + } + std::string wake_word = reason; + #if CONFIG_USE_AFE_WAKE_WORD || CONFIG_USE_CUSTOM_WAKE_WORD + // Encode and send the wake word data to the server + while (auto packet = audio_service_.PopWakeWordPacket()) { + protocol_->SendAudio(std::move(packet)); + } + // Set the chat state to wake word detected + protocol_->SendWakeWordDetected(wake_word); + SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); + #else + // Set flag to play popup sound after state changes to listening + // (PlaySound here would be cleared by ResetDecoder in EnableVoiceProcessing) + play_popup_on_listening_ = true; + SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); + #endif + } else if (state == kDeviceStateSpeaking) { + AbortSpeaking(kAbortReasonWakeWordDetected); + SetDeviceState(kDeviceStateIdle); + } else if (state == kDeviceStateActivating) { + SetDeviceState(kDeviceStateIdle); + } + } + ``` +4. 在mcp_server.cc文件中添加远程唤醒工具 + ```cpp + AddUserOnlyTool("self.remote_wakeup", "Remote wakeup function with configurable parameters", + PropertyList({ + Property("reason", kPropertyTypeString, "Wakeup reason"), + }), + [this](const PropertyList& properties) -> ReturnValue { + std::string reason = properties["reason"].value(); + ESP_LOGI(TAG, "Wakeup reason=%s", reason.c_str()); + auto& app = Application::GetInstance(); + app.RemoteWakeup(reason); + return true; + ``` +5. 按照 [固件编译烧录指南](firmware-build.md) 完成固件烧录 + +### 第五步:配置MQTT网关服务 1. 部署MQTT网关服务,参考 [MQTT网关集成文档](mqtt-gateway-integration.md) 2. 如果已经部署请确认代码的版本是2026年5月27日的版本 ## 呼叫流程说明 -准备两个设备,在智控台上面配置好通讯权限之后,在其中一个小智对话那里对他说:”呼叫XXX“,观察设备B是否响应。 +准备两个设备,在智控台上面配置好通讯权限和在智能体中添加呼叫工具之后,在其中一个小智对话那里对他说:”呼叫XXX“,观察设备B是否响应。 ## 常见问题 From 69fd5a1ea8c6bb5b1b29fd6064c70296892d0f99 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Fri, 29 May 2026 17:34:21 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E4=B8=8D=E5=BC=80=E5=90=AF=E9=80=9A?= =?UTF-8?q?=E8=AE=AF=E5=BD=95=E5=8A=9F=E8=83=BD=E6=97=B6=E4=B8=8D=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E7=9B=B8=E5=85=B3=E5=B7=A5=E5=85=B7=20=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E9=80=9A=E8=AE=AF=E5=BD=95=E5=8A=9F=E8=83=BD=E6=B8=85?= =?UTF-8?q?=E9=99=A4=E7=9B=B8=E5=85=B3=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xiaozhi/common/constant/Constant.java | 5 ++ .../service/AgentPluginMappingService.java | 11 +++- .../impl/AgentPluginMappingServiceImpl.java | 7 +++ .../sys/controller/SysParamsController.java | 7 ++- .../modules/sys/service/SysParamsService.java | 15 +++++ .../service/impl/SysParamsServiceImpl.java | 62 ++++++++++++++++++- .../src/components/FunctionDialog.vue | 46 +++++++++++--- 7 files changed, 139 insertions(+), 14 deletions(-) diff --git a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java index 81ca4ffa..61a05f70 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java +++ b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java @@ -146,6 +146,11 @@ public interface Constant { */ String SERVER_AUTH_ENABLED = "server.auth.enabled"; + /** + * 系统功能菜单配置 + */ + String SYSTEM_WEB_MENU = "system-web.menu"; + /** * 无记忆 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentPluginMappingService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentPluginMappingService.java index 79d97d2e..9411cc80 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentPluginMappingService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentPluginMappingService.java @@ -14,7 +14,7 @@ public interface AgentPluginMappingService extends IService /** * 根据智能体id获取插件参数 - * + * * @param agentId * @return */ @@ -22,8 +22,15 @@ public interface AgentPluginMappingService extends IService /** * 根据智能体id删除插件参数 - * + * * @param agentId */ void deleteByAgentId(String agentId); + + /** + * 根据插件ID删除所有智能体的插件映射 + * + * @param pluginId 插件ID + */ + void deleteByPluginId(String pluginId); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentPluginMappingServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentPluginMappingServiceImpl.java index 6d839d9c..a8784010 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentPluginMappingServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentPluginMappingServiceImpl.java @@ -104,4 +104,11 @@ public class AgentPluginMappingServiceImpl extends ServiceImpl updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("plugin_id", pluginId); + agentPluginMappingMapper.delete(updateWrapper); + } + } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java index 26e6d245..49317aa4 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java @@ -114,7 +114,12 @@ public class SysParamsController { // 校验mqtt密钥长度 validateMqttSecretLength(dto.getParamCode(), dto.getParamValue()); - sysParamsService.update(dto); + // 如果是系统功能菜单配置,使用特殊处理 + if (Constant.SYSTEM_WEB_MENU.equals(dto.getParamCode())) { + sysParamsService.updateSystemWebMenu(dto.getParamValue()); + } else { + sysParamsService.update(dto); + } configService.getConfig(false); return new Result(); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java index ac67a276..70ff417d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java @@ -53,4 +53,19 @@ public interface SysParamsService extends BaseService { * 初始化服务器密钥 */ void initServerSecret(); + + /** + * 获取系统功能菜单配置 + * + * @param fromCache 是否从缓存获取 + * @return 系统功能菜单配置JSON字符串 + */ + String getSystemWebMenu(boolean fromCache); + + /** + * 更新系统功能菜单配置(自动处理功能相关的插件清理) + * + * @param configJson 新的系统功能菜单配置JSON + */ + void updateSystemWebMenu(String configJson); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java index b22dd2b9..a83c2e68 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java @@ -22,6 +22,7 @@ import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.JsonUtils; import xiaozhi.common.utils.SM2Utils; +import xiaozhi.modules.agent.service.AgentPluginMappingService; import xiaozhi.modules.sys.dao.SysParamsDao; import xiaozhi.modules.sys.dto.SysParamsDTO; import xiaozhi.modules.sys.entity.SysParamsEntity; @@ -35,6 +36,7 @@ import xiaozhi.modules.sys.service.SysParamsService; @Service public class SysParamsServiceImpl extends BaseServiceImpl implements SysParamsService { private final SysParamsRedis sysParamsRedis; + private final AgentPluginMappingService agentPluginMappingService; @Override public PageData page(Map params) { @@ -270,4 +272,62 @@ public class SysParamsServiceImpl extends BaseServiceImpl currentMap = null; + Map newMap = null; + + try { + if (StringUtils.isNotBlank(currentConfig)) { + currentMap = JsonUtils.parseObject(currentConfig, Map.class); + } + if (StringUtils.isNotBlank(configJson)) { + newMap = JsonUtils.parseObject(configJson, Map.class); + } + } catch (Exception e) { + throw new RenException(ErrorCode.PARAM_JSON_INVALID); + } + + // 检查addressBook功能是否被关闭 + if (currentMap != null && newMap != null) { + Map currentFeatures = (Map) currentMap.get("features"); + Map newFeatures = (Map) newMap.get("features"); + + if (currentFeatures != null && newFeatures != null) { + Object currentAddressBookObj = currentFeatures.get("addressBook"); + Object newAddressBookObj = newFeatures.get("addressBook"); + + Boolean currentEnabled = false; + Boolean newEnabled = false; + + if (currentAddressBookObj instanceof Map) { + Map currentAddressBook = (Map) currentAddressBookObj; + currentEnabled = currentAddressBook.get("enabled") != null + ? (Boolean) currentAddressBook.get("enabled") : false; + } + + if (newAddressBookObj instanceof Map) { + Map newAddressBook = (Map) newAddressBookObj; + newEnabled = newAddressBook.get("enabled") != null + ? (Boolean) newAddressBook.get("enabled") : false; + } + + // 如果之前是启用状态,现在被禁用,删除所有call_device插件 + if (Boolean.TRUE.equals(currentEnabled) && !Boolean.TRUE.equals(newEnabled)) { + agentPluginMappingService.deleteByPluginId("SYSTEM_PLUGIN_CALL_DEVICE"); + } + } + } + + // 更新配置 + updateValueByCode(Constant.SYSTEM_WEB_MENU, configJson); + } +} diff --git a/main/manager-web/src/components/FunctionDialog.vue b/main/manager-web/src/components/FunctionDialog.vue index e95cca8a..595159ca 100644 --- a/main/manager-web/src/components/FunctionDialog.vue +++ b/main/manager-web/src/components/FunctionDialog.vue @@ -209,16 +209,27 @@ export default { // 功能状态 featureStatus: { - mcpAccessPoint: false + mcpAccessPoint: false, + addressBook: false } } }, computed: { selectedList() { - return this.allFunctions.filter(f => this.selectedNames.includes(f.name)); + const list = this.allFunctions.filter(f => this.selectedNames.includes(f.name)); + // 如果通讯录功能未启用,过滤掉设备呼叫设备插件 + if (!this.featureStatus.addressBook) { + return list.filter(f => f.providerCode !== 'call_device'); + } + return list; }, unselected() { - return this.allFunctions.filter(f => !this.selectedNames.includes(f.name)); + const list = this.allFunctions.filter(f => !this.selectedNames.includes(f.name)); + // 如果通讯录功能未启用,过滤掉设备呼叫设备插件 + if (!this.featureStatus.addressBook) { + return list.filter(f => f.providerCode !== 'call_device'); + } + return list; } }, watch: { @@ -239,11 +250,23 @@ export default { } }); }, - value(v) { + async value(v) { this.dialogVisible = v; if (v) { + // 加载功能状态(需要在初始化选中态之前) + await this.loadFeatureStatus(); + // 对话框打开时,初始化选中态 this.selectedNames = this.functions.map(f => f.name); + + // 如果通讯录功能未启用,从已选列表中移除设备呼叫设备插件 + if (!this.featureStatus.addressBook) { + this.selectedNames = this.selectedNames.filter(name => { + const func = this.allFunctions.find(f => f.name === name); + return func && func.providerCode !== 'call_device'; + }); + } + // 把后端传来的 this.functions(带 params)merge 到 allFunctions 上 this.functions.forEach(saved => { const idx = this.allFunctions.findIndex(f => f.name === saved.name); @@ -255,9 +278,6 @@ export default { // 右侧默认指向第一个 this.currentFunction = this.selectedList[0] || null; - // 加载功能状态 - this.loadFeatureStatus(); - // 加载MCP数据 this.loadMcpAddress(); this.loadMcpTools(); @@ -274,10 +294,11 @@ export default { async loadFeatureStatus() { // 确保featureManager已初始化完成 await featureManager.waitForInitialization(); - + const config = featureManager.getConfig(); this.featureStatus = { - mcpAccessPoint: config.mcpAccessPoint || false + mcpAccessPoint: config.mcpAccessPoint || false, + addressBook: config.addressBook || false }; }, @@ -413,7 +434,7 @@ export default { this.tempFunctions = {}; this.hasSaved = true; - const selected = this.selectedList.map(f => { + let selected = this.selectedList.map(f => { const modified = this.modifiedFunctions[f.name]; return { id: f.id, @@ -424,6 +445,11 @@ export default { } }); + // 如果通讯录功能未启用,自动取消已选的设备呼叫设备插件 + if (!this.featureStatus.addressBook) { + selected = selected.filter(f => f.providerCode !== 'call_device'); + } + this.$emit('update-functions', selected); this.dialogVisible = false; // 通知父组件对话框已关闭且已保存 From 7f2675f9df3896dd41e2224c746d01c27fbe39ed Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Sun, 31 May 2026 10:30:10 +0800 Subject: [PATCH 13/14] =?UTF-8?q?update:=E5=A2=9E=E5=8A=A0=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E5=91=BC=E5=8F=AB=E4=BD=BF=E7=94=A8=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/device-call-guide.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/device-call-guide.md b/docs/device-call-guide.md index 620ea31a..c7764b5c 100644 --- a/docs/device-call-guide.md +++ b/docs/device-call-guide.md @@ -7,15 +7,20 @@ ``` 设备A → 授权校验 → MQTT网关 → 设备B远程唤醒 → 建立连接 → 通话建立 ``` +## 使用这个功能的前提条件 +1. 你必须要有两个设备,每个设备型号必须是`ESP32-S3`,因为只有`ESP32-S3`才支持远程唤醒功能。 +2. 你必须使用[全模块部署](Deployment_all.md)本项目,因为你需要`智控台`来管理设备的权限和通信。 +3. 你必须安装并配置好`2026年5月27日`以后的[MQTT网关服务](mqtt-gateway-integration.md),如果你已经部署了MQTT网关服务,请确认代码的版本是`2026年5月27日`之后的版本。 ## 配置步骤 ### 第一步:开启通讯录功能 -1. 登录智控台后台 -2. 进入 **系统功能配置** -3. 在左侧功能列表中勾选 **通讯录** -4. 点击 **保存配置** 确认 +1. 确认你的智控台版本是`0.9.4`或以上版本。 +2. 登录智控台后台 +3. 进入 **系统功能配置** +4. 在左侧功能列表中勾选 **通讯录** +5. 点击 **保存配置** 确认 ### 第二步:配置设备间呼叫权限 From 7af4b41881985ec1d8bb0dc4517192f3f2e1276a Mon Sep 17 00:00:00 2001 From: 3030332422 <3030332422@qq.com> Date: Mon, 1 Jun 2026 16:25:52 +0800 Subject: [PATCH 14/14] =?UTF-8?q?update=EF=BC=9A=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=91=BC=E5=8F=AB=E5=B7=A5=E5=85=B7=E7=9A=84=E6=8E=A5=E5=90=AC?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E6=8F=90=E7=A4=BA=E8=AF=8D=E7=BA=A6=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/handle/textHandler/listenMessageHandler.py | 2 +- .../plugins_func/functions/call_device.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/main/xiaozhi-server/core/handle/textHandler/listenMessageHandler.py b/main/xiaozhi-server/core/handle/textHandler/listenMessageHandler.py index 21188019..313e4878 100644 --- a/main/xiaozhi-server/core/handle/textHandler/listenMessageHandler.py +++ b/main/xiaozhi-server/core/handle/textHandler/listenMessageHandler.py @@ -74,7 +74,7 @@ class ListenTextMessageHandler(TextMessageHandler): conn.tts.tts_text_queue.put(TTSMessageDTO(sentence_id=conn.sentence_id, sentence_type=SentenceType.LAST, content_type=ContentType.ACTION)) # 添加到对话历史,让模型理解上下文 - conn.dialogue.put(Message(role="user", content=call_text)) + conn.dialogue.put(Message(role="assistant", content=call_text)) return # 识别是否是唤醒词 diff --git a/main/xiaozhi-server/plugins_func/functions/call_device.py b/main/xiaozhi-server/plugins_func/functions/call_device.py index 03ccccaa..249da627 100644 --- a/main/xiaozhi-server/plugins_func/functions/call_device.py +++ b/main/xiaozhi-server/plugins_func/functions/call_device.py @@ -15,7 +15,15 @@ call_device_function_desc = { "type": "function", "function": { "name": "call_device", - "description": "主动呼叫其他小智设备进行语音通话。**重要**:只有当用户主动说'我想跟XX通话'、'帮我打电话给XX'时才调用此工具。当收到'来自XX的来电'消息时,不要调用此工具,让设备自然播报即可。", + "description": ( + "用于设备之间建立语音通话连接。" + "当用户说出以下意图时调用此工具:\n" + "1. 主动呼叫:用户说”呼叫XX/打电话给XX/连线XX/打给XX/帮我呼叫XX”时调用,nickname取XX。" + "例如:”呼叫张三”→nickname=”张三”、”帮我连线小陈”→nickname=”小陈”;\n" + "2. 接听来电:系统刚提示”您收到来自XX的来电,是否接听?”后,用户说”接听/接通/同意接听/同意连线/同意对话”时调用," + "nickname取提示中的XX。\n" + "如果用户输入不是明确接听,也不是明确拒绝,不得调用call_device,必须先追问一次" + ), "parameters": { "type": "object", "properties": {