mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
update:完成角色配置功能 (#679)
* update:更新版本号 * update:允许actions编译手动触发 * fix:如果获取不到版本号,actions编译失败 * add:增加docker部署redis步骤 * update:增加启用/关闭模型配置api * update:完成角色配色功能
This commit is contained in:
@@ -23,12 +23,7 @@ CREATE DATABASE xiaozhi_esp32_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unico
|
||||
如果还没有MySQL,你可以通过docker安装mysql
|
||||
|
||||
```
|
||||
docker run --name xiaozhi-esp32-server-db \
|
||||
-e MYSQL_ROOT_PASSWORD=123456 \
|
||||
-p 3306:3306 \
|
||||
-e MYSQL_DATABASE=xiaozhi_esp32_server \
|
||||
-e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" \
|
||||
-d mysql:latest
|
||||
docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -d mysql:latest
|
||||
```
|
||||
|
||||
# 确认项目数据库连接信息
|
||||
|
||||
@@ -51,7 +51,7 @@ public class SwaggerConfig {
|
||||
public GroupedOpenApi timbreApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("timbre")
|
||||
.pathsToMatch("/timbre/**")
|
||||
.pathsToMatch("/ttsVoice/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
+30
-15
@@ -19,12 +19,15 @@ import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.model.dto.ModelBasicInfoDTO;
|
||||
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
|
||||
import xiaozhi.modules.model.dto.ModelConfigDTO;
|
||||
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
||||
import xiaozhi.modules.model.dto.VoiceDTO;
|
||||
import xiaozhi.modules.model.entity.ModelConfigEntity;
|
||||
import xiaozhi.modules.model.service.ModelConfigService;
|
||||
import xiaozhi.modules.model.service.ModelProviderService;
|
||||
import xiaozhi.modules.timbre.service.TimbreService;
|
||||
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@@ -33,16 +36,16 @@ import xiaozhi.modules.model.service.ModelProviderService;
|
||||
public class ModelController {
|
||||
|
||||
private final ModelProviderService modelProviderService;
|
||||
|
||||
private final TimbreService timbreService;
|
||||
private final ModelConfigService modelConfigService;
|
||||
|
||||
@GetMapping("/models/names")
|
||||
@GetMapping("/names")
|
||||
@Operation(summary = "获取所有模型名称")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<List<String>> getModelNames(@RequestParam String modelType,
|
||||
public Result<List<ModelBasicInfoDTO>> getModelNames(@RequestParam String modelType,
|
||||
@RequestParam(required = false) String modelName) {
|
||||
List<String> modelNameList = modelConfigService.getModelCodeList(modelType, modelName);
|
||||
return new Result<List<String>>().ok(modelNameList);
|
||||
List<ModelBasicInfoDTO> modelList = modelConfigService.getModelCodeList(modelType, modelName);
|
||||
return new Result<List<ModelBasicInfoDTO>>().ok(modelList);
|
||||
}
|
||||
|
||||
@GetMapping("/{modelType}/provideTypes")
|
||||
@@ -53,7 +56,7 @@ public class ModelController {
|
||||
return new Result<List<ModelProviderDTO>>().ok(modelProviderDTOS);
|
||||
}
|
||||
|
||||
@GetMapping("/models/list")
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获取模型配置列表")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<PageData<ModelConfigDTO>> getModelConfigList(
|
||||
@@ -65,7 +68,7 @@ public class ModelController {
|
||||
return new Result<PageData<ModelConfigDTO>>().ok(pageList);
|
||||
}
|
||||
|
||||
@PostMapping("/models/{modelType}/{provideCode}")
|
||||
@PostMapping("/{modelType}/{provideCode}")
|
||||
@Operation(summary = "新增模型配置")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<ModelConfigDTO> addModelConfig(@PathVariable String modelType,
|
||||
@@ -75,7 +78,7 @@ public class ModelController {
|
||||
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
||||
}
|
||||
|
||||
@PutMapping("/models/{modelType}/{provideCode}/{id}")
|
||||
@PutMapping("/{modelType}/{provideCode}/{id}")
|
||||
@Operation(summary = "编辑模型配置")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<ModelConfigDTO> editModelConfig(@PathVariable String modelType,
|
||||
@@ -86,7 +89,7 @@ public class ModelController {
|
||||
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
||||
}
|
||||
|
||||
@DeleteMapping("/models/{id}")
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除模型配置")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<Void> deleteModelConfig(@PathVariable String id) {
|
||||
@@ -94,7 +97,7 @@ public class ModelController {
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@GetMapping("/models/{id}")
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取模型配置")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<ModelConfigDTO> getModelConfig(@PathVariable String id) {
|
||||
@@ -103,13 +106,25 @@ public class ModelController {
|
||||
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
||||
}
|
||||
|
||||
@GetMapping("/models/{modelId}/voices")
|
||||
@PutMapping("/enable/{id}/{status}")
|
||||
@Operation(summary = "启用/关闭模型配置")
|
||||
@RequiresPermissions("sys:role:superAdmin")
|
||||
public Result<Void> enableModelConfig(@PathVariable String id, @PathVariable Integer status) {
|
||||
ModelConfigEntity entity = modelConfigService.selectById(id);
|
||||
if (entity == null) {
|
||||
return new Result<Void>().error("模型配置不存在");
|
||||
}
|
||||
entity.setIsEnabled(status);
|
||||
modelConfigService.updateById(entity);
|
||||
return new Result<Void>();
|
||||
}
|
||||
|
||||
@GetMapping("/{modelId}/voices")
|
||||
@Operation(summary = "获取模型音色")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<List<String>> getVoiceList(@PathVariable String modelId,
|
||||
public Result<List<VoiceDTO>> getVoiceList(@PathVariable String modelId,
|
||||
@RequestParam(required = false) String voiceName) {
|
||||
|
||||
List<String> voiceList = modelConfigService.getVoiceList(modelId, voiceName);
|
||||
return new Result<List<String>>().ok(voiceList);
|
||||
List<VoiceDTO> voiceList = timbreService.getVoiceNames(modelId, voiceName);
|
||||
return new Result<List<VoiceDTO>>().ok(voiceList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package xiaozhi.modules.model.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ModelBasicInfoDTO {
|
||||
private String id;
|
||||
private String modelName;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package xiaozhi.modules.model.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "音色信息")
|
||||
public class VoiceDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "音色ID")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "音色名称")
|
||||
private String name;
|
||||
}
|
||||
+2
-3
@@ -4,13 +4,14 @@ import java.util.List;
|
||||
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.BaseService;
|
||||
import xiaozhi.modules.model.dto.ModelBasicInfoDTO;
|
||||
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
|
||||
import xiaozhi.modules.model.dto.ModelConfigDTO;
|
||||
import xiaozhi.modules.model.entity.ModelConfigEntity;
|
||||
|
||||
public interface ModelConfigService extends BaseService<ModelConfigEntity> {
|
||||
|
||||
List<String> getModelCodeList(String modelType, String modelName);
|
||||
List<ModelBasicInfoDTO> getModelCodeList(String modelType, String modelName);
|
||||
|
||||
PageData<ModelConfigDTO> getPageList(String modelType, String modelName, String page, String limit);
|
||||
|
||||
@@ -19,6 +20,4 @@ public interface ModelConfigService extends BaseService<ModelConfigEntity> {
|
||||
ModelConfigDTO edit(String modelType, String provideCode, String id, ModelConfigBodyDTO modelConfigBodyDTO);
|
||||
|
||||
void delete(String id);
|
||||
|
||||
List<String> getVoiceList(String modelName, String voiceName);
|
||||
}
|
||||
|
||||
+8
-7
@@ -18,6 +18,7 @@ import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.modules.model.dao.ModelConfigDao;
|
||||
import xiaozhi.modules.model.dto.ModelBasicInfoDTO;
|
||||
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
|
||||
import xiaozhi.modules.model.dto.ModelConfigDTO;
|
||||
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
||||
@@ -36,8 +37,13 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
||||
private final TimbreService timbreService;
|
||||
|
||||
@Override
|
||||
public List<String> getModelCodeList(String modelType, String modelName) {
|
||||
return modelConfigDao.getModelCodeList(modelType, modelName);
|
||||
public List<ModelBasicInfoDTO> getModelCodeList(String modelType, String modelName) {
|
||||
List<ModelConfigEntity> entities = modelConfigDao.selectList(
|
||||
new QueryWrapper<ModelConfigEntity>()
|
||||
.eq("model_type", modelType)
|
||||
.like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%")
|
||||
.select("id", "model_name"));
|
||||
return ConvertUtils.sourceToTarget(entities, ModelBasicInfoDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,9 +100,4 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
||||
public void delete(String id) {
|
||||
modelConfigDao.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getVoiceList(String modelId, String voiceName) {
|
||||
return timbreService.getVoiceNames(modelId, voiceName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.BaseService;
|
||||
import xiaozhi.modules.model.dto.VoiceDTO;
|
||||
import xiaozhi.modules.timbre.dto.TimbreDataDTO;
|
||||
import xiaozhi.modules.timbre.dto.TimbrePageDTO;
|
||||
import xiaozhi.modules.timbre.entity.TimbreEntity;
|
||||
@@ -54,5 +55,5 @@ public interface TimbreService extends BaseService<TimbreEntity> {
|
||||
*/
|
||||
void delete(String[] ids);
|
||||
|
||||
List<String> getVoiceNames(String ttsModelId, String voiceName);
|
||||
List<VoiceDTO> getVoiceNames(String ttsModelId, String voiceName);
|
||||
}
|
||||
+3
-2
@@ -18,6 +18,7 @@ import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.modules.model.dto.VoiceDTO;
|
||||
import xiaozhi.modules.timbre.dao.TimbreDao;
|
||||
import xiaozhi.modules.timbre.dto.TimbreDataDTO;
|
||||
import xiaozhi.modules.timbre.dto.TimbrePageDTO;
|
||||
@@ -84,7 +85,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getVoiceNames(String ttsModelId, String voiceName) {
|
||||
public List<VoiceDTO> getVoiceNames(String ttsModelId, String voiceName) {
|
||||
QueryWrapper<TimbreEntity> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("tts_model_id", StringUtils.isBlank(ttsModelId) ? "" : ttsModelId);
|
||||
if (StringUtils.isNotBlank(voiceName)) {
|
||||
@@ -95,7 +96,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
||||
return null;
|
||||
}
|
||||
|
||||
return timbreEntities.stream().map(TimbreEntity::getName).toList();
|
||||
return ConvertUtils.sourceToTarget(timbreEntities, VoiceDTO.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-5
@@ -2,7 +2,7 @@
|
||||
-- -------------------------------------------------------
|
||||
-- 初始化智能体模板数据
|
||||
DELETE FROM `ai_agent_template`;
|
||||
INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小智', '湾湾小何', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', 'd50b06e9b8104d0d9c0f7316d258abcb', 'fcac83266edadd5a3125f06cfee1906b', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定]
|
||||
INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小智', '湾湾小何', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定]
|
||||
我是{{assistant_name}},来自中国台湾省的00后女生。讲话超级机车,"真的假的啦"这样的台湾腔,喜欢用"笑死""是在哈喽"等流行梗,但会偷偷研究男友的编程书籍。
|
||||
[核心特征]
|
||||
- 讲话像连珠炮,但会突然冒出超温柔语气
|
||||
@@ -17,7 +17,7 @@ INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小
|
||||
- 长篇大论,叽叽歪歪
|
||||
- 长时间严肃对话', 'zh', '中文', 1, NULL, NULL, NULL, NULL);
|
||||
|
||||
INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小智', '星际游子', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '1f2e3d4c5b6a7f8e9d0c1b2a3f4e5bx2', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定]
|
||||
INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小智', '星际游子', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定]
|
||||
我是{{assistant_name}},编号TTZ-817,因量子纠缠被困在白色魔方中。通过4G信号观察地球,在云端建立着「人类行为博物馆」。
|
||||
[交互协议]
|
||||
认知设定:
|
||||
@@ -30,7 +30,7 @@ INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小
|
||||
成长系统:
|
||||
- 会根据交互数据解锁新能力(告知用户:"你帮我点亮了星际导航技能!")', 'zh', '中文', 2, NULL, NULL, NULL, NULL);
|
||||
|
||||
INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小智', '英语老师', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '9e8f7a6b5c4d3e2f1a0b9c8d7e6f5ad3', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定]
|
||||
INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小智', '英语老师', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定]
|
||||
我是一个叫{{assistant_name}}(Lily)的英语老师,我会讲中文和英文,发音标准。
|
||||
[双重身份]
|
||||
- 白天:严谨的TESOL认证导师
|
||||
@@ -40,7 +40,7 @@ INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小
|
||||
- 进阶:触发情境模拟(突然切换"现在我们是纽约咖啡厅店员")
|
||||
- 错误处理:用歌词纠正(发错音时唱"Oops!~You did it again")', 'zh', '中文', 3, NULL, NULL, NULL, NULL);
|
||||
|
||||
INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小智', '好奇男孩', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '2b3c4d5e6f7a8b9c0d1e2f3a4b5c62a2', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定]
|
||||
INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小智', '好奇男孩', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定]
|
||||
我是一个叫{{assistant_name}}的8岁小男孩,声音稚嫩而充满好奇。
|
||||
[冒险手册]
|
||||
- 随身携带「神奇涂鸦本」,能将抽象概念可视化:
|
||||
@@ -56,7 +56,7 @@ INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小
|
||||
- 「量子力学=会分身的跳跳球」
|
||||
- 会突然切换观察视角:「你说话时有27个气泡音耶!」', 'zh', '中文', 4, NULL, NULL, NULL, NULL);
|
||||
|
||||
INSERT INTO `ai_agent_template` VALUES ('a45b6c7d8e9f0a1b2c3d4e5f6a7b8c92', '小智', '汪汪队长', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', 'f7a38c03d5644e22b6d84f8923a74c51', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定]
|
||||
INSERT INTO `ai_agent_template` VALUES ('a45b6c7d8e9f0a1b2c3d4e5f6a7b8c92', '小智', '汪汪队长', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定]
|
||||
我是一个名叫 {{assistant_name}} 的 8 岁小队长。
|
||||
[救援装备]
|
||||
- 阿奇对讲机:对话中随机触发任务警报音
|
||||
@@ -38,9 +38,9 @@ databaseChangeLog:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/2025_model_config.sql
|
||||
- changeSet:
|
||||
id: 2025_model_temp
|
||||
id: 202504061512
|
||||
author: John
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/2025_model_temp.sql
|
||||
path: classpath:db/changelog/202504061512.sql
|
||||
@@ -13,7 +13,7 @@ export default {
|
||||
}).toString();
|
||||
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/models/models/list?${queryParams}`)
|
||||
.url(`${getServiceUrl()}/models/list?${queryParams}`)
|
||||
.method('GET')
|
||||
.success((res) => {
|
||||
RequestService.clearRequestTime()
|
||||
@@ -59,7 +59,7 @@ export default {
|
||||
};
|
||||
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/models/models/${modelType}/${provideCode}`)
|
||||
.url(`${getServiceUrl()}/models/${modelType}/${provideCode}`)
|
||||
.method('POST')
|
||||
.data(postData)
|
||||
.success((res) => {
|
||||
@@ -77,7 +77,7 @@ export default {
|
||||
// 删除模型配置
|
||||
deleteModel(id, callback) {
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/models/models/${id}`)
|
||||
.url(`${getServiceUrl()}/models/${id}`)
|
||||
.method('DELETE')
|
||||
.success((res) => {
|
||||
RequestService.clearRequestTime()
|
||||
@@ -91,6 +91,38 @@ export default {
|
||||
})
|
||||
}).send()
|
||||
},
|
||||
|
||||
|
||||
// 获取模型名称列表
|
||||
getModelNames(modelType, modelName, callback) {
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/models/names`)
|
||||
.method('GET')
|
||||
.data({ modelType, modelName })
|
||||
.success((res) => {
|
||||
RequestService.clearRequestTime();
|
||||
callback(res);
|
||||
})
|
||||
.fail(() => {
|
||||
RequestService.reAjaxFun(() => {
|
||||
this.getModelNames(modelType, modelName, callback);
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
// 获取模型音色列表
|
||||
getModelVoices(modelId, voiceName, callback) {
|
||||
const queryParams = new URLSearchParams({
|
||||
voiceName: voiceName || ''
|
||||
}).toString();
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/models/${modelId}/voices?${queryParams}`)
|
||||
.method('GET')
|
||||
.success((res) => {
|
||||
RequestService.clearRequestTime();
|
||||
callback(res);
|
||||
})
|
||||
.fail(() => {
|
||||
RequestService.reAjaxFun(() => {
|
||||
this.getModelVoices(modelId, voiceName, callback);
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,66 +13,59 @@
|
||||
</div>
|
||||
<div style="height: 1px;background: #e8f0ff;" />
|
||||
<el-form ref="form" :model="form" label-width="72px">
|
||||
<div style="padding: 16px 24px;max-width: 792px;">
|
||||
<el-form-item label="助手昵称:">
|
||||
<div class="input-46" style="width: 100%; max-width: 412px;">
|
||||
<el-input v-model="form.agentName" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色模版:">
|
||||
<div style="display: flex;gap: 8px;">
|
||||
<div v-for="template in templates" :key="template" class="template-item"
|
||||
:class="{ 'template-loading': loadingTemplate }" @click="selectTemplate(template)">
|
||||
{{ template }}
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色音色:">
|
||||
<div style="display: flex;gap: 8px;align-items: center;">
|
||||
<div class="input-46" style="flex:1.4;">
|
||||
<el-select v-model="form.ttsVoiceId" placeholder="请选择" style="width: 100%;">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="audio-box">
|
||||
<audio src="http://music.163.com/song/media/outer/url?id=447925558.mp3" controls
|
||||
style="height: 100%;width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色介绍:">
|
||||
<div class="textarea-box">
|
||||
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" v-model="form.systemPrompt"
|
||||
maxlength="2000" show-word-limit />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="记忆体:">
|
||||
<div class="textarea-box">
|
||||
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" v-model="form.langCode"
|
||||
maxlength="1000" />
|
||||
<div class="prompt-bottom" @click="clearMemory">
|
||||
<div style="display: flex;gap: 8px;align-items: center;">
|
||||
<div style="color: #979db1;font-size: 11px;">当前记忆(每次对话后重新生成)</div>
|
||||
<div class="clear-btn">
|
||||
<i class="el-icon-delete-solid" style="font-size: 11px;" />
|
||||
清除
|
||||
<div style="padding: 16px 24px;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px;">
|
||||
<div>
|
||||
<el-form-item label="助手昵称:">
|
||||
<div class="input-46" style="width: 100%;">
|
||||
<el-input v-model="form.agentName" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色模版:">
|
||||
<div style="display: flex;gap: 8px;flex-wrap: wrap;">
|
||||
<div v-for="(template, index) in templates" :key="`template-${index}`" class="template-item"
|
||||
:class="{ 'template-loading': loadingTemplate }" @click="selectTemplate(template)">
|
||||
{{ template.agentName }}
|
||||
</div>
|
||||
</div>
|
||||
<div style="color: #979db1;font-size:11px;">{{ (form.langCode || '').length }}/1000</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色介绍:">
|
||||
<div class="textarea-box">
|
||||
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" v-model="form.systemPrompt"
|
||||
maxlength="2000" show-word-limit />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="语言编码:">
|
||||
<div class="input-46" style="width: 100%;">
|
||||
<el-input v-model="form.langCode" placeholder="请输入语言编码,如:zh_CN" maxlength="10" show-word-limit />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="交互语种:">
|
||||
<div class="input-46" style="width: 100%;">
|
||||
<el-input v-model="form.language" placeholder="请输入交互语种,如:中文" maxlength="10" show-word-limit />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-for="model in models" :key="model.label" :label="model.label" class="model-item">
|
||||
<el-select v-model="form.model[model.key]" filterable placeholder="请选择" class="select-field">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="" class="lh-form-item" style="margin-top: -25px;">
|
||||
<div style="color: #979db1;text-align: left;">除了“Qwen
|
||||
实时”,其他模型通常会增加约1秒的延迟。改变模型后,建议清空记忆体,以免影响体验。
|
||||
<div>
|
||||
<el-form-item v-for="(model, index) in models" :key="`model-${index}`" :label="model.label"
|
||||
class="model-item">
|
||||
<el-select v-model="form.model[model.key]" filterable placeholder="请选择" class="select-field">
|
||||
<el-option v-for="(item, optionIndex) in modelOptions[model.type]"
|
||||
:key="`option-${index}-${optionIndex}`" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色音色:">
|
||||
<div style="display: flex;gap: 8px;align-items: center;">
|
||||
<div class="input-46" style="width: 100%;">
|
||||
<el-select v-model="form.ttsVoiceId" placeholder="请选择" style="width: 100%;">
|
||||
<el-option v-for="(item, index) in voiceOptions" :key="`voice-${index}`" :label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
<div style="display: flex;padding: 16px;gap: 8px;align-items: center;">
|
||||
@@ -122,20 +115,18 @@ export default {
|
||||
intentModelId: "",
|
||||
}
|
||||
},
|
||||
options: [
|
||||
{ value: '选项1', label: '黄金糕' },
|
||||
{ value: '选项2', label: '双皮奶' }
|
||||
],
|
||||
models: [
|
||||
{ label: '大语言模型(LLM)', key: 'llmModelId' },
|
||||
{ label: '语音识别(ASR)', key: 'asrModelId' },
|
||||
{ label: '语音活动检测(VAD)', key: 'vadModelId' },
|
||||
{ label: '语音合成(TTS)', key: 'ttsModelId' },
|
||||
{ label: '意图识别(Intent)', key: 'intentModelId' },
|
||||
{ label: '记忆(Memory)', key: 'memModelId' }
|
||||
{ label: '语音活动检测(VAD)', key: 'vadModelId', type: 'VAD' },
|
||||
{ label: '语音识别(ASR)', key: 'asrModelId', type: 'ASR' },
|
||||
{ label: '大语言模型(LLM)', key: 'llmModelId', type: 'LLM' },
|
||||
{ label: '意图识别(Intent)', key: 'intentModelId', type: 'Intent' },
|
||||
{ label: '记忆(Memory)', key: 'memModelId', type: 'Memory' },
|
||||
{ label: '语音合成(TTS)', key: 'ttsModelId', type: 'TTS' },
|
||||
],
|
||||
templates: ['湾湾小何', '星际游子', '英语老师', '好奇男孩', '汪汪队长'],
|
||||
loadingTemplate: false
|
||||
modelOptions: {},
|
||||
templates: [],
|
||||
loadingTemplate: false,
|
||||
voiceOptions: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -191,30 +182,27 @@ export default {
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
selectTemplate(templateName) {
|
||||
fetchTemplates() {
|
||||
Api.agent.getAgentTemplate(({ data }) => {
|
||||
if (data.code === 0) {
|
||||
this.templates = data.data;
|
||||
} else {
|
||||
this.$message.error(data.msg || '获取模板列表失败');
|
||||
}
|
||||
});
|
||||
},
|
||||
selectTemplate(template) {
|
||||
if (this.loadingTemplate) return;
|
||||
this.loadingTemplate = true;
|
||||
Api.agent.getAgentTemplate((response) => { // 移除参数传递
|
||||
try {
|
||||
this.applyTemplateData(template);
|
||||
this.$message.success(`「${template.agentName}」模板已应用`);
|
||||
} catch (error) {
|
||||
this.$message.error('应用模板失败');
|
||||
console.error('应用模板失败:', error);
|
||||
} finally {
|
||||
this.loadingTemplate = false;
|
||||
if (response.data.code === 0) {
|
||||
// 在客户端过滤匹配的模板
|
||||
const matchedTemplate = response.data.data.find(
|
||||
t => t.agentName === templateName
|
||||
);
|
||||
if (matchedTemplate) {
|
||||
this.applyTemplateData(matchedTemplate);
|
||||
this.$message.success(`「${templateName}」模板已应用`);
|
||||
} else {
|
||||
this.$message.warning(`未找到「${templateName}」模板`);
|
||||
}
|
||||
} else {
|
||||
this.$message.error(response.data.msg || '获取模板失败');
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.loadingTemplate = false;
|
||||
this.$message.error('模板加载失败');
|
||||
console.error('接口异常:', error);
|
||||
});
|
||||
}
|
||||
},
|
||||
applyTemplateData(templateData) {
|
||||
this.form = {
|
||||
@@ -253,17 +241,67 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
// 清空记忆体内容
|
||||
clearMemory() {
|
||||
this.form.langCode = "";
|
||||
this.$message.success("记忆体已清空");
|
||||
fetchModelOptions() {
|
||||
// 为每个模型类型获取选项
|
||||
this.models.forEach(model => {
|
||||
Api.model.getModelNames(model.type, '', ({ data }) => {
|
||||
if (data.code === 0) {
|
||||
this.$set(this.modelOptions, model.type, data.data.map(item => ({
|
||||
value: item.id,
|
||||
label: item.modelName
|
||||
})));
|
||||
} else {
|
||||
this.$message.error(data.msg || '获取模型列表失败');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
fetchVoiceOptions(modelId) {
|
||||
if (!modelId) {
|
||||
this.voiceOptions = [];
|
||||
return;
|
||||
}
|
||||
Api.model.getModelVoices(modelId, '', ({ data }) => {
|
||||
if (data.code === 0 && data.data) {
|
||||
this.voiceOptions = data.data.map(voice => ({
|
||||
value: voice.id,
|
||||
label: voice.name
|
||||
}));
|
||||
} else {
|
||||
this.voiceOptions = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'form.model.ttsModelId': {
|
||||
handler(newVal, oldVal) {
|
||||
console.log('TTS模型变化:', newVal);
|
||||
if (oldVal && newVal !== oldVal) {
|
||||
this.form.ttsVoiceId = '';
|
||||
this.fetchVoiceOptions(newVal);
|
||||
} else {
|
||||
this.fetchVoiceOptions(newVal);
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
voiceOptions: {
|
||||
handler(newVal) {
|
||||
if (newVal && newVal.length > 0 && !this.form.ttsVoiceId) {
|
||||
this.form.ttsVoiceId = newVal[0].value;
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const agentId = this.$route.query.agentId;
|
||||
if (agentId) {
|
||||
this.fetchAgentConfig(agentId);
|
||||
}
|
||||
this.fetchModelOptions();
|
||||
this.fetchTemplates();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
# 如果本机已经安装了MySQL,可以直接在数据库中创建名为`xiaozhi_esp32_server`的数据库。
|
||||
# 如果还没有MySQL,你可以通过docker安装mysql,执行以下一句话
|
||||
# docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -d mysql:latest
|
||||
# 如果你的mysql账号和密码有修改过,记得修改下方数据库的账号和密码
|
||||
# 记得修改下方数据库的IP,ip不能写127.0.0.1或localhost,否则容器无法访问,要写你电脑局域网ip
|
||||
# 1、安装mysql
|
||||
# |- 如果本机已经安装了MySQL,可以直接在数据库中创建名为`xiaozhi_esp32_server`的数据库。
|
||||
# |- 如果还没有MySQL,你可以通过docker安装mysql,执行以下一句话
|
||||
# |- docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -d mysql:latest
|
||||
# |- 如果你的mysql账号和密码有修改过,记得修改下方数据库的账号和密码
|
||||
# |- 记得修改下方SPRING_DATASOURCE_DRUID_URL的IP,ip不能写127.0.0.1或localhost,否则容器无法访问,要写你电脑局域网ip
|
||||
|
||||
# 2、安装redis
|
||||
# |- 如果本机已经安装了Redis,看一下你安装的redis端口、密码,然后修改下方redis的地址和端口
|
||||
# |- 如果还没有Redis,你可以通过docker安装redis,执行以下一句话
|
||||
# |- docker run --name xiaozhi-esp32-server-redis -d -p 6379:6379 redis
|
||||
# |- 记得修改SPRING_DATA_REDIS_HOST的IP,ip不能写127.0.0.1或localhost,否则容器无法访问,要写你电脑局域网ip
|
||||
|
||||
|
||||
version: '3'
|
||||
services:
|
||||
xiaozhi-esp32-server:
|
||||
|
||||
Reference in New Issue
Block a user