diff --git a/README.md b/README.md
index b59d9d45..191790bc 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@
-
+
@@ -95,6 +95,11 @@
|
+
+
+
+
+
|
@@ -185,7 +190,6 @@ server:
| LLM | FastgptLLM | fastgpt 接口调用 | 免费/消耗 token | 本地化部署,注意配置提示词需在 Fastgpt 控制台设置 |
| LLM | GeminiLLM | gemini 接口调用 | 免费 | [点击申请密钥](https://aistudio.google.com/apikey) |
| LLM | CozeLLM | coze 接口调用 | 消耗 token | 需提供 bot_id、user_id 及个人令牌 |
-| LLM | Home Assistant | homeassistant语音助手接口调用 | 免费 | 需提供home assistant令牌 |
实际上,任何支持 openai 接口调用的 LLM 均可接入使用。
@@ -370,7 +374,41 @@ VAD:
### 6、我想通过小智控制电灯、空调、远程开关机等操作 💡
-建议:在配置文件中将 `LLM` 设置为 `HomeAssistant`,通过 调用`HomeAssistant`接口实现相关控制。
+本项目,支持以工具调用的方式控制HomeAssistant设备
+
+1、首先选择一款支持function call支持的LLM,例如`ChatGLMLLM`。
+
+2、在配置文件中,将 `selected_module.Intent` 设置为 `function_call`。
+
+3、登录`HomeAssistant`,点击`左下角个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key。
+
+在配置文件中,配置好你的home assistant的`devices`(被控制的设备)和`api_key`和`base_url`等信息。例如:
+
+``` yaml
+plugins
+ home_assistant:
+ devices:
+ - 客厅,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1
+ - 卧室,台灯,switch.iot_cn_831898993_socn1_on_p_2_1
+ base_url: http://你的homeassistant地址:8123
+ api_key: 你的home assistant api访问令牌
+```
+
+最后,允许function_call 插件在配置文件中启用`hass_get_state`(必须)、`hass_set_state`(必须)、`hass_play_music`(不想用ha听音乐可以不启动),例如:
+
+``` yaml
+Intent:
+ ...
+ function_call:
+ type: nointent
+ functions:
+ - change_role
+ - get_weather
+ - get_news
+ - hass_get_state
+ - hass_set_state
+ - hass_play_music
+```
### 7、更多问题,可联系我们反馈 💬
diff --git a/docs/images/demo0.png b/docs/images/demo0.png
new file mode 100644
index 00000000..99592be6
Binary files /dev/null and b/docs/images/demo0.png differ
diff --git a/docs/images/demo5.png b/docs/images/demo5.png
index 8e03685b..c6da2997 100644
Binary files a/docs/images/demo5.png and b/docs/images/demo5.png differ
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java
index b7d6c278..6b04bb08 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java
@@ -19,6 +19,7 @@ import xiaozhi.modules.security.service.ShiroService;
import xiaozhi.modules.sys.entity.SysUserEntity;
import xiaozhi.modules.sys.enums.SuperAdminEnum;
+import java.util.HashSet;
import java.util.Set;
/**
@@ -47,7 +48,7 @@ public class Oauth2Realm extends AuthorizingRealm {
UserDetail user = (UserDetail) principals.getPrimaryPrincipal();
//用户权限列表
- Set permsSet = shiroService.getUserPermissions(user);
+ Set permsSet = new HashSet<>();
if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
permsSet.add("sys:role:superAdmin");
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/ShiroService.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/ShiroService.java
index 980645d6..b9d18889 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/ShiroService.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/ShiroService.java
@@ -1,22 +1,14 @@
package xiaozhi.modules.security.service;
-import xiaozhi.common.user.UserDetail;
import xiaozhi.modules.security.entity.SysUserTokenEntity;
import xiaozhi.modules.sys.entity.SysUserEntity;
-import java.util.List;
-import java.util.Set;
-
/**
* shiro相关接口
* Copyright (c) 人人开源 All rights reserved.
* Website: https://www.renren.io
*/
public interface ShiroService {
- /**
- * 获取用户权限列表
- */
- Set getUserPermissions(UserDetail user);
SysUserTokenEntity getByToken(String token);
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java
index 128d9b9d..97d16eaa 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java
@@ -19,22 +19,6 @@ public class ShiroServiceImpl implements ShiroService {
private final SysUserDao sysUserDao;
private final SysUserTokenDao sysUserTokenDao;
- @Override
- public Set getUserPermissions(UserDetail user) {
- //系统管理员,拥有最高权限
- // TODO: 暂时写死,后续改成从数据库查询
- List permissionsList = new ArrayList<>();
- //用户权限列表
- Set permsSet = new HashSet<>();
- for (String permissions : permissionsList) {
- if (StringUtils.isBlank(permissions)) {
- continue;
- }
- permsSet.addAll(Arrays.asList(permissions.trim().split(",")));
- }
-
- return permsSet;
- }
@Override
public SysUserTokenEntity getByToken(String token) {
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java
new file mode 100644
index 00000000..dc6ccca7
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java
@@ -0,0 +1,85 @@
+package xiaozhi.modules.timbre.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.web.bind.annotation.*;
+import xiaozhi.common.constant.Constant;
+import xiaozhi.common.page.PageData;
+import xiaozhi.common.utils.Result;
+import xiaozhi.common.validator.ValidatorUtils;
+import xiaozhi.modules.timbre.dto.TimbreDataDTO;
+import xiaozhi.modules.timbre.dto.TimbrePageDTO;
+import xiaozhi.modules.timbre.service.TimbreService;
+import xiaozhi.modules.timbre.vo.TimbreDetailsVO;
+
+import java.util.Map;
+
+/**
+ * 音色控制层
+ *
+ * @author zjy
+ * @since 2025-3-21
+ */
+@AllArgsConstructor
+@RestController
+@RequestMapping("/ttsVoice")
+@Tag(name = "音色管理")
+public class TimbreController {
+ private final TimbreService timbreService;
+
+ @GetMapping
+ @Operation(summary = "分页查找")
+ @RequiresPermissions("sys:role:superAdmin")
+ @Parameters({
+ @Parameter(name = "ttsModelId", description = "对应 TTS 模型主键", required = true),
+ @Parameter(name = "name", description = "音色名称"),
+ @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
+ @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
+ })
+ public Result> page(
+ @Parameter(hidden = true) @RequestParam Map params) {
+ TimbrePageDTO dto = new TimbrePageDTO();
+ dto.setTtsModelId((String) params.get("ttsModelId"));
+ dto.setName((String) params.get("name"));
+ dto.setLimit((String) params.get(Constant.LIMIT));
+ dto.setPage((String) params.get(Constant.PAGE));
+
+ ValidatorUtils.validateEntity(dto);
+ PageData page = timbreService.page(dto);
+ return new Result>().ok(page);
+ }
+
+ @PostMapping
+ @Operation(summary = "音色保存")
+ @RequiresPermissions("sys:role:superAdmin")
+ public Result save(@RequestBody TimbreDataDTO dto) {
+ ValidatorUtils.validateEntity(dto);
+ timbreService.save(dto);
+ return new Result<>();
+ }
+
+ @PutMapping("/{id}")
+ @Operation(summary = "音色修改")
+ @RequiresPermissions("sys:role:superAdmin")
+ public Result update(
+ @PathVariable Long id,
+ @RequestBody TimbreDataDTO dto) {
+ ValidatorUtils.validateEntity(dto);
+ timbreService.update(id, dto);
+ return new Result<>();
+ }
+
+ @DeleteMapping("/{id}")
+ @Operation(summary = "音色删除")
+ @RequiresPermissions("sys:role:superAdmin")
+ public Result delete(@PathVariable Long id) {
+ timbreService.delete(new Long[]{id});
+ return new Result<>();
+ }
+
+
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java
new file mode 100644
index 00000000..62afa358
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java
@@ -0,0 +1,14 @@
+package xiaozhi.modules.timbre.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import xiaozhi.modules.timbre.entity.TimbreEntity;
+
+/**
+ * 音色持久层定义
+ * @author zjy
+ * @since 2025-3-21
+ */
+@Mapper
+public interface TimbreDao extends BaseMapper {
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java
new file mode 100644
index 00000000..08ed9b18
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java
@@ -0,0 +1,45 @@
+package xiaozhi.modules.timbre.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+
+/**
+ * 音色表数据DTO
+ * @author zjy
+ * @since 2025-3-21
+ */
+@Data
+@Schema(description = "音色表信息")
+public class TimbreDataDTO {
+
+ @Schema(description = "语言")
+ @NotBlank(message = "{timbre.languages.require}")
+ private String languages;
+
+ @Schema(description = "音色名称")
+ @NotBlank(message = "{timbre.name.require}")
+ private String name;
+
+ @Schema(description = "备注")
+ @NotBlank(message = "{timbre.remark.require}")
+ private String remark;
+
+ @Schema(description = "排序")
+ @Min(value = 0, message = "{sort.number}")
+ private long sort;
+
+ @Schema(description = "对应 TTS 模型主键")
+ @NotBlank(message = "{timbre.ttsModelId.require}")
+ private String ttsModelId;
+
+ @Schema(description = "音色编码")
+ @NotBlank(message = "{timbre.ttsVoice.require}")
+ private String ttsVoice;
+
+ @Schema(description = "音频播放地址")
+ @NotBlank(message = "{timbre.voiceDemo.require}")
+ private String voiceDemo;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java
new file mode 100644
index 00000000..72ed68b1
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java
@@ -0,0 +1,28 @@
+package xiaozhi.modules.timbre.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+/**
+ * 音色分页参数DTO
+ * @author zjy
+ * @since 2025-3-21
+ */
+@Data
+@Schema(description = "音色分页参数")
+public class TimbrePageDTO {
+
+ @Schema(description = "对应 TTS 模型主键")
+ @NotBlank(message = "{timbre.ttsModelId.require}")
+ private String ttsModelId;
+
+ @Schema(description = "音色名称")
+ private String name;
+
+ @Schema(description = "页数")
+ private String page;
+
+ @Schema(description = "显示列数")
+ private String limit;
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java
new file mode 100644
index 00000000..71bd58a9
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java
@@ -0,0 +1,48 @@
+package xiaozhi.modules.timbre.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import xiaozhi.common.entity.BaseEntity;
+
+import java.util.Date;
+
+/**
+ * 音色表实体类
+ * @author zjy
+ * @since 2025-3-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ai_tts_voice")
+@Schema(description = "音色信息")
+public class TimbreEntity extends BaseEntity {
+
+ @Schema(description = "语言")
+ private String languages;
+
+ @Schema(description = "音色名称")
+ private String name;
+
+ @Schema(description = "备注")
+ private String remark;
+
+ @Schema(description = "排序")
+ private long sort;
+
+ @Schema(description = "对应 TTS 模型主键")
+ private String ttsModelId;
+
+ @Schema(description = "音色编码")
+ private String ttsVoice;
+
+ @Schema(description = "音频播放地址")
+ private String voiceDemo;
+
+ @Schema(description = "更新者")
+ private Long updater;
+
+ @Schema(description = "更新时间")
+ private Date updateDate;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java
new file mode 100644
index 00000000..9b6c73eb
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java
@@ -0,0 +1,49 @@
+package xiaozhi.modules.timbre.service;
+
+import xiaozhi.common.page.PageData;
+import xiaozhi.common.service.BaseService;
+import xiaozhi.modules.timbre.dto.TimbreDataDTO;
+import xiaozhi.modules.timbre.dto.TimbrePageDTO;
+import xiaozhi.modules.timbre.entity.TimbreEntity;
+import xiaozhi.modules.timbre.vo.TimbreDetailsVO;
+
+
+/**
+ * 音色的业务层的定义
+ * @author zjy
+ * @since 2025-3-21
+ */
+public interface TimbreService extends BaseService {
+ /**
+ * 分页获取音色指定tts的下的音色
+ * @param dto 分页查找参数
+ * @return 音色列表分页数据
+ */
+ PageData page(TimbrePageDTO dto);
+
+ /**
+ * 获取音色指定id的详情信息
+ * @param timbreId 音色表id
+ * @return 音色信息
+ */
+ TimbreDetailsVO get(Long timbreId);
+
+ /**
+ * 保存音色信息
+ * @param dto 需要保存数据
+ */
+ void save(TimbreDataDTO dto);
+
+ /**
+ * 保存音色信息
+ * @param timbreId 需要修改的id
+ * @param dto 需要修改的数据
+ */
+ void update(Long timbreId,TimbreDataDTO dto);
+
+ /**
+ * 批量删除音色
+ * @param ids 需要被删除的音色id列表
+ */
+ void delete(Long[] ids);
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java
new file mode 100644
index 00000000..ce3c2b7b
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java
@@ -0,0 +1,85 @@
+package xiaozhi.modules.timbre.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import xiaozhi.common.constant.Constant;
+import xiaozhi.common.page.PageData;
+import xiaozhi.common.service.impl.BaseServiceImpl;
+import xiaozhi.common.utils.ConvertUtils;
+import xiaozhi.modules.timbre.dao.TimbreDao;
+import xiaozhi.modules.timbre.dto.TimbreDataDTO;
+import xiaozhi.modules.timbre.dto.TimbrePageDTO;
+import xiaozhi.modules.timbre.service.TimbreService;
+import xiaozhi.modules.timbre.entity.TimbreEntity;
+import xiaozhi.modules.timbre.vo.TimbreDetailsVO;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 音色的业务层的实现
+ * @author zjy
+ * @since 2025-3-21
+ */
+@Service
+public class TimbreServiceImpl extends BaseServiceImpl implements TimbreService {
+
+
+ @Override
+ public PageData page(TimbrePageDTO dto) {
+ Map params = new HashMap();
+ params.put(Constant.PAGE, dto.getPage());
+ params.put(Constant.LIMIT,dto.getLimit());
+ IPage page = baseDao.selectPage(
+ getPage(params, "sort", true),
+ //定义查询条件
+ new QueryWrapper()
+ //必须按照ttsID查找
+ .eq("tts_model_id",dto.getTtsModelId())
+ //如果有音色名字,按照音色名模糊查找
+ .like(StringUtils.isNotBlank(dto.getName()),"name",dto.getName())
+ );
+
+ return getPageData(page, TimbreDetailsVO.class);
+ }
+
+ @Override
+ public TimbreDetailsVO get(Long timbreId) {
+ TimbreEntity entity = baseDao.selectById(timbreId);
+ return ConvertUtils.sourceToTarget(entity, TimbreDetailsVO.class);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void save(TimbreDataDTO dto) {
+ isTtsModelId(dto.getTtsModelId());
+ TimbreEntity timbreEntity = ConvertUtils.sourceToTarget(dto, TimbreEntity.class);
+ baseDao.insert(timbreEntity);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void update(Long timbreId, TimbreDataDTO dto) {
+ isTtsModelId(dto.getTtsModelId());
+ TimbreEntity timbreEntity = ConvertUtils.sourceToTarget(dto, TimbreEntity.class);
+ timbreEntity.setId(timbreId);
+ baseDao.updateById(timbreEntity);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void delete(Long[] ids) {
+ baseDao.deleteBatchIds(Arrays.asList(ids));
+ }
+
+ /**
+ * 处理是不是tts模型的id
+ */
+ private void isTtsModelId(String ttsModelId){
+ //等模型配置那边写好调用方法判断
+ }
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java
new file mode 100644
index 00000000..fc82a5d0
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java
@@ -0,0 +1,39 @@
+package xiaozhi.modules.timbre.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 音色详情展示VO
+ * @author zjy
+ * @since 2025-3-21
+ */
+@Data
+public class TimbreDetailsVO implements Serializable {
+ @Schema(description = "音色id")
+ private Long id;
+
+ @Schema(description = "语言")
+ private String languages;
+
+ @Schema(description = "音色名称")
+ private String name;
+
+ @Schema(description = "备注")
+ private String remark;
+
+ @Schema(description = "排序")
+ private long sort;
+
+ @Schema(description = "对应 TTS 模型主键")
+ private String ttsModelId;
+
+ @Schema(description = "音色编码")
+ private String ttsVoice;
+
+ @Schema(description = "音频播放地址")
+ private String voiceDemo;
+
+}
diff --git a/main/manager-api/src/main/resources/i18n/validation.properties b/main/manager-api/src/main/resources/i18n/validation.properties
index 947f89cc..e7452e47 100644
--- a/main/manager-api/src/main/resources/i18n/validation.properties
+++ b/main/manager-api/src/main/resources/i18n/validation.properties
@@ -20,3 +20,12 @@ sysuser.deptId.require=\u90E8\u95E8\u4E0D\u80FD\u4E3A\u7A7A
sysuser.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1
sysuser.captcha.require=\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
+
+
+timbre.languages.require=\u97F3\u8272\u7684\u8BED\u8A00\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+timbre.name.require=\u97F3\u8272\u7684\u540D\u79F0\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+timbre.remark.require=\u97F3\u8272\u7684\u5907\u6CE8\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+timbre.ttsModelId.require=\u97F3\u8272\u7684tts\u4E3B\u952E\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+timbre.ttsVoice.require=\u97F3\u8272\u7684\u7F16\u7801\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+timbre.voiceDemo.require=\u97F3\u8272\u7684\u64AD\u653EDemo\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+
diff --git a/main/manager-api/src/main/resources/i18n/validation_en_US.properties b/main/manager-api/src/main/resources/i18n/validation_en_US.properties
index f8bb6e94..37bebf18 100644
--- a/main/manager-api/src/main/resources/i18n/validation_en_US.properties
+++ b/main/manager-api/src/main/resources/i18n/validation_en_US.properties
@@ -20,3 +20,11 @@ sysuser.deptId.require=Departments cannot be empty
sysuser.status.range=State ranges from 0 to 1
sysuser.captcha.require=The captcha cannot be empty
sysuser.uuid.require=The unique identifier cannot be empty
+
+timbre.languages.require=The language of the timbre cannot be empty
+timbre.name.require=The name of the timbre cannot be empty
+timbre.remark.require=The remark of the timbre cannot be empty
+timbre.ttsModelId.require=The TTS model ID of the timbre cannot be empty
+timbre.ttsVoice.require=The TTS voice of the timbre cannot be empty
+timbre.voiceDemo.require=The voice demo of the timbre cannot be empty
+
diff --git a/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties b/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties
index ba350d05..41bf1e69 100644
--- a/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties
+++ b/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties
@@ -21,3 +21,10 @@ sysuser.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1
sysuser.captcha.require=\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
+timbre.languages.require=\u97F3\u8272\u7684\u8A9E\u8A00\u4E0D\u53EF\u4EE5\u70BA\u7A7A
+timbre.name.require=\u97F3\u8272\u7684\u540D\u7A31\u4E0D\u53EF\u4EE5\u70BA\u7A7A
+timbre.remark.require=\u97F3\u8272\u7684\u5099\u6CE8\u4E0D\u53EF\u4EE5\u70BA\u7A7A
+timbre.ttsModelId.require=\u97F3\u8272\u7684tts\u4E3B\u9375\u4E0D\u53EF\u4EE5\u70BA\u7A7A
+timbre.ttsVoice.require=\u97F3\u8272\u7684\u7DE8\u78BC\u4E0D\u53EF\u4EE5\u70BA\u7A7A
+timbre.voiceDemo.require=\u97F3\u8272\u7684\u64AD\u653E\u5730\u5740\u4E0D\u53EF\u4EE5\u70BA\u7A7A
+
diff --git a/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties b/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties
index 947f89cc..9241c97a 100644
--- a/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties
+++ b/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties
@@ -20,3 +20,10 @@ sysuser.deptId.require=\u90E8\u95E8\u4E0D\u80FD\u4E3A\u7A7A
sysuser.status.range=\u72B6\u6001\u53D6\u503C\u8303\u56F40~1
sysuser.captcha.require=\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A
sysuser.uuid.require=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A
+
+timbre.languages.require=\u97F3\u8272\u7684\u8BED\u8A00\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+timbre.name.require=\u97F3\u8272\u7684\u540D\u79F0\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+timbre.remark.require=\u97F3\u8272\u7684\u5907\u6CE8\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+timbre.ttsModelId.require=\u97F3\u8272\u7684tts\u4E3B\u952E\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+timbre.ttsVoice.require=\u97F3\u8272\u7684\u7F16\u7801\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
+timbre.voiceDemo.require=\u97F3\u8272\u7684\u64AD\u653EDemo\u4E0D\u53EF\u4EE5\u4E3A\u7A7A
diff --git a/main/manager-web/src/components/HeaderBar.vue b/main/manager-web/src/components/HeaderBar.vue
index cba41128..53946ed6 100644
--- a/main/manager-web/src/components/HeaderBar.vue
+++ b/main/manager-web/src/components/HeaderBar.vue
@@ -2,10 +2,10 @@