update:完成角色配置功能 (#679)

* update:更新版本号

* update:允许actions编译手动触发

* fix:如果获取不到版本号,actions编译失败

* add:增加docker部署redis步骤

* update:增加启用/关闭模型配置api

* update:完成角色配色功能
This commit is contained in:
hrz
2025-04-06 15:45:18 +08:00
committed by GitHub
parent e57137b101
commit 50897e97af
14 changed files with 267 additions and 145 deletions
+1 -6
View File
@@ -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();
}
@@ -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;
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
/**
@@ -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-8174G信号观察地球
[]
@@ -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
+37 -5
View File
@@ -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();
},
}
+131 -93
View File
@@ -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>
+14 -5
View File
@@ -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账号和密码有修改过,记得修改下方数据库的账号和密码
# 记得修改下方数据库的IPip不能写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的IPip不能写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的IPip不能写127.0.0.1或localhost,否则容器无法访问,要写你电脑局域网ip
version: '3'
services:
xiaozhi-esp32-server: