feat: 新增设备互相通讯功能
feat: 新增通讯录页面
@@ -188,4 +188,11 @@ public class RedisKeys {
|
||||
return "ota:upload:count:" + username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备通讯录缓存Key
|
||||
*/
|
||||
public static String getAddressBookKey() {
|
||||
return "device:address_book:all";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<Object> getAddressBook(@PathVariable String macAddress) {
|
||||
return new Result<Object>().ok(deviceAddressBookService.getAddressBookList(macAddress));
|
||||
}
|
||||
|
||||
@GetMapping("/address-book/lookup")
|
||||
@Operation(summary = "根据昵称查找目标设备")
|
||||
public Result<Map<String, String>> lookupByNickname(String callerMac, String nickname) {
|
||||
Map<String, String> result = deviceAddressBookService.lookupByNickname(callerMac, nickname);
|
||||
if (result == null) {
|
||||
return new Result<Map<String, String>>().error("未找到对应设备");
|
||||
}
|
||||
return new Result<Map<String, String>>().ok(result);
|
||||
}
|
||||
|
||||
@PutMapping("/address-book/alias")
|
||||
@Operation(summary = "更新设备通讯录别名")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> updateAlias(@Valid @RequestBody DeviceAddressBookAliasDTO dto) {
|
||||
deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), dto.getAlias(), null);
|
||||
return new Result<Void>();
|
||||
}
|
||||
|
||||
@PutMapping("/address-book/permission")
|
||||
@Operation(summary = "更新设备通讯录权限")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> updatePermission(@Valid @RequestBody DeviceAddressBookPermissionDTO dto) {
|
||||
deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), null, dto.getHasPermission());
|
||||
return new Result<Void>();
|
||||
}
|
||||
}
|
||||
@@ -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<DeviceAddressBookEntity> {
|
||||
|
||||
/**
|
||||
* 获取设备通讯录列表
|
||||
*/
|
||||
List<DeviceAddressBookEntity> 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<DeviceAddressBookEntity> getAddressBookList(String macAddress);
|
||||
|
||||
/**
|
||||
* 获取所有设备的通讯录(全局缓存用)
|
||||
*/
|
||||
Map<String, Map<String, String>> 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<String, String> lookupByNickname(String callerMac, String nickname);
|
||||
}
|
||||
@@ -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<DeviceAddressBookEntity> getAddressBookList(String macAddress) {
|
||||
return deviceAddressBookDao.getAddressBookList(macAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Map<String, String>> getAllAddressBooks() {
|
||||
Object cached = redisUtils.get(RedisKeys.getAddressBookKey());
|
||||
if (cached != null) {
|
||||
return (Map<String, Map<String, String>>) cached;
|
||||
}
|
||||
refreshCache();
|
||||
return (Map<String, Map<String, String>>) redisUtils.get(RedisKeys.getAddressBookKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> lookupByNickname(String callerMac, String nickname) {
|
||||
Map<String, Map<String, String>> allBooks = getAllAddressBooks();
|
||||
Map<String, String> 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<String, String> targetBook = allBooks.get(targetMac.toLowerCase());
|
||||
if (targetBook == null) {
|
||||
return null;
|
||||
}
|
||||
// 检查双向关系:目标设备是否也添加了主叫方为联系人
|
||||
String callerNickname = targetBook.get(callerMac.toLowerCase());
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("targetMac", targetMac);
|
||||
result.put("callerNickname", callerNickname);
|
||||
result.put("hasPermission", hasPermission ? "true" : "false");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshCache() {
|
||||
Map<String, Map<String, String>> result = new HashMap<>();
|
||||
List<DeviceAddressBookEntity> allRecords = deviceAddressBookDao.selectList(null);
|
||||
Map<String, String> reverseMap = new HashMap<>();
|
||||
Map<String, Boolean> 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<DeviceAddressBookEntity> 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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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='设备通讯录表';
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="xiaozhi.modules.device.dao.DeviceAddressBookDao">
|
||||
|
||||
<select id="getAddressBookList" resultType="xiaozhi.modules.device.entity.DeviceAddressBookEntity">
|
||||
SELECT mac_address, target_mac, alias, has_permission, creator, create_date, updater, update_date
|
||||
FROM ai_device_address_book
|
||||
WHERE mac_address = #{macAddress}
|
||||
ORDER BY update_date DESC
|
||||
</select>
|
||||
|
||||
<update id="updateAlias">
|
||||
UPDATE ai_device_address_book
|
||||
SET alias = #{alias}, update_date = NOW()
|
||||
WHERE mac_address = #{macAddress} AND target_mac = #{targetMac}
|
||||
</update>
|
||||
|
||||
<update id="updatePermission">
|
||||
UPDATE ai_device_address_book
|
||||
SET has_permission = #{hasPermission}, update_date = NOW()
|
||||
WHERE mac_address = #{macAddress} AND target_mac = #{targetMac}
|
||||
</update>
|
||||
|
||||
<insert id="insert">
|
||||
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())
|
||||
</insert>
|
||||
</mapper>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<el-dialog :title="title" :visible="dialogVisible" width="600px" class="address-book-dialog" @close="handleClose">
|
||||
<el-form ref="addressBookForm" :model="form" :rules="rules" label-width="100px" size="medium">
|
||||
<el-form-item :label="$t('addressBookDialog.name')" prop="name">
|
||||
<el-input v-model="form.name" :placeholder="$t('addressBookDialog.namePlaceholder')" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('addressBookDialog.description')" prop="description">
|
||||
<el-input v-model="form.description" :placeholder="$t('addressBookDialog.descriptionPlaceholder')"
|
||||
type="textarea" :rows="4" maxlength="300" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('addressBookDialog.status')" prop="status">
|
||||
<el-switch
|
||||
v-model="form.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-color="#13ce66"
|
||||
inactive-color="#909399"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="handleClose">{{ $t('addressBookDialog.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">{{ $t('addressBookDialog.confirm') }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AddressBookDialog",
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
id: null,
|
||||
name: "",
|
||||
description: "",
|
||||
status: 1
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: this.visible,
|
||||
rules: {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t("addressBookDialog.nameRequired"),
|
||||
trigger: "blur"
|
||||
},
|
||||
{
|
||||
min: 1,
|
||||
max: 50,
|
||||
message: this.$t("addressBookDialog.nameLength"),
|
||||
trigger: "blur"
|
||||
},
|
||||
{
|
||||
pattern: /^[一-龥a-zA-Z0-9\s-_]+$/,
|
||||
message: this.$t("addressBookDialog.namePattern"),
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
description: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t("addressBookDialog.descriptionRequired"),
|
||||
trigger: "blur"
|
||||
},
|
||||
{
|
||||
max: 300,
|
||||
message: this.$t("addressBookDialog.descriptionLength"),
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
this.dialogVisible = val;
|
||||
if (val) {
|
||||
if (this.$refs.addressBookForm) {
|
||||
this.$refs.addressBookForm.clearValidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
if (this.$refs.addressBookForm) {
|
||||
this.$refs.addressBookForm.clearValidate();
|
||||
}
|
||||
this.dialogVisible = false;
|
||||
this.$emit("update:visible", false);
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.addressBookForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.$emit("submit", {
|
||||
...this.form
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.address-book-dialog {
|
||||
::v-deep .el-dialog {
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__body {
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__label {
|
||||
font-weight: 500;
|
||||
color: #34495e;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
::v-deep .el-input {
|
||||
.el-input__inner {
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:focus {
|
||||
border-color: #5f70f3;
|
||||
box-shadow: 0 0 0 2px rgba(95, 112, 243, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-textarea {
|
||||
.el-textarea__inner {
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:focus {
|
||||
border-color: #5f70f3;
|
||||
box-shadow: 0 0 0 2px rgba(95, 112, 243, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -81,6 +81,15 @@
|
||||
}" />
|
||||
<span class="nav-text">{{ $t("header.knowledgeBase") }}</span>
|
||||
</div>
|
||||
<div v-if="featureStatus.addressBook" class="equipment-management"
|
||||
:class="{ 'active-tab': $route.path === '/address-book-management' }"
|
||||
@click="handleRouter('addressBookManagement')">
|
||||
<img loading="lazy" alt="" src="@/assets/header/knowledge_base.png" :style="{
|
||||
filter:
|
||||
$route.path === '/address-book-management' ? 'brightness(0) invert(1)' : 'None',
|
||||
}" />
|
||||
<span class="nav-text">{{ $t("header.addressBook") }}</span>
|
||||
</div>
|
||||
<el-dropdown v-if="userInfo.superAdmin" trigger="click" class="equipment-management more-dropdown" :class="{
|
||||
'active-tab':
|
||||
$route.path === '/dict-management' ||
|
||||
@@ -228,6 +237,7 @@ export default {
|
||||
home: "/home",
|
||||
modelConfig: "/model-config",
|
||||
knowledgeBaseManagement: "/knowledge-base-management",
|
||||
addressBookManagement: "/address-book-management",
|
||||
voiceCloneManagement: "/voice-clone-management",
|
||||
voiceResourceManagement: "/voice-resource-management",
|
||||
paramManagement: "/params-management",
|
||||
@@ -247,6 +257,7 @@ export default {
|
||||
featureStatus: (state) => ({
|
||||
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,
|
||||
}),
|
||||
|
||||
@@ -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',
|
||||
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
}
|
||||
|
||||
@@ -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ạ',
|
||||
|
||||
}
|
||||
@@ -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}年前',
|
||||
|
||||
}
|
||||
@@ -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}年前',
|
||||
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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'])
|
||||
|
||||
// 恢复用户的选择状态(如果存在)
|
||||
|
||||
@@ -7,6 +7,7 @@ from config.manage_api_client import (
|
||||
get_server_config,
|
||||
get_agent_models,
|
||||
get_correct_words,
|
||||
lookup_address_book,
|
||||
DeviceNotFoundException,
|
||||
DeviceBindException,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
# 获取运行中的事件循环(必须在异步上下文中)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)}")
|
||||