feat: init

This commit is contained in:
2025-11-07 23:46:02 +08:00
parent b767041311
commit 6eb0c9c8dc
9 changed files with 96 additions and 47 deletions

1
.gitignore vendored
View File

@@ -32,3 +32,4 @@ application-local.properties
.vscode/ .vscode/
*.jar *.jar
/mysql

View File

@@ -0,0 +1,4 @@
-- 增加角色表模板ID字段若不存在
ALTER TABLE `xiaozhi`.`sys_role`
ADD COLUMN `templateId` int unsigned DEFAULT NULL COMMENT '模板ID' AFTER `modelId`;

View File

@@ -99,6 +99,7 @@ CREATE TABLE `xiaozhi`.`sys_role` (
`avatar` varchar(255) DEFAULT NULL COMMENT '角色头像', `avatar` varchar(255) DEFAULT NULL COMMENT '角色头像',
`ttsId` int DEFAULT NULL COMMENT 'TTS服务ID', `ttsId` int DEFAULT NULL COMMENT 'TTS服务ID',
`modelId` int unsigned DEFAULT NULL COMMENT '模型ID', `modelId` int unsigned DEFAULT NULL COMMENT '模型ID',
`templateId` int unsigned DEFAULT NULL COMMENT '模板ID',
`sttId` int unsigned DEFAULT NULL COMMENT 'STT服务ID', `sttId` int unsigned DEFAULT NULL COMMENT 'STT服务ID',
`vadSpeechTh` FLOAT DEFAULT 0.5 COMMENT '语音检测阈值', `vadSpeechTh` FLOAT DEFAULT 0.5 COMMENT '语音检测阈值',
`vadSilenceTh` FLOAT DEFAULT 0.3 COMMENT '静音检测阈值', `vadSilenceTh` FLOAT DEFAULT 0.3 COMMENT '静音检测阈值',

26
docker-compose-mysql.yml Normal file
View File

@@ -0,0 +1,26 @@
version: "3.8"
services:
mysql:
container_name: my-mysql
image: mysql:8.0
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root123 # root 用户密码
MYSQL_DATABASE: xiaozhi # 初始化数据库
MYSQL_USER: xiaozhi # 初始化用户
MYSQL_PASSWORD: 123456 # 用户密码
TZ: Asia/Shanghai # 时区
volumes:
- ./mysql/data:/var/lib/mysql # 持久化数据
- ./mysql/init:/docker-entrypoint-initdb.d # 初始化脚本
- /etc/localtime:/etc/localtime:ro
command:
[
"--character-set-server=utf8mb4",
"--collation-server=utf8mb4_general_ci",
"--lower_case_table_names=1",
"--default-authentication-plugin=mysql_native_password"
]

11
docker-compose-redis.yml Normal file
View File

@@ -0,0 +1,11 @@
version: "3.8"
services:
redis:
image: redis:7.2
container_name: local-redis
restart: unless-stopped
ports:
- "127.0.0.1:6379:6379"
command: >
redis-server --appendonly no --timeout 10 --tcp-keepalive 300

View File

@@ -61,6 +61,11 @@ public class SysRole extends Base<SysRole> {
*/ */
private String modelName; private String modelName;
/**
* 模板ID提示词模板
*/
private Integer templateId;
/** /**
* STT服务ID * STT服务ID
*/ */
@@ -115,4 +120,4 @@ public class SysRole extends Base<SysRole> {
* 总设备数 * 总设备数
*/ */
private Integer totalDevice; private Integer totalDevice;
} }

View File

@@ -4,7 +4,7 @@
<sql id="roleSql"> <sql id="roleSql">
sys_role.roleId, sys_role.avatar, sys_role.roleName, sys_role.roleDesc, sys_role.voiceName, sys_role.roleId, sys_role.avatar, sys_role.roleName, sys_role.roleDesc, sys_role.voiceName,
sys_role.modelId, sys_role.sttId, sys_role.ttsId, sys_role.modelId, sys_role.templateId, sys_role.sttId, sys_role.ttsId,
sys_role.vadSpeechTh, sys_role.vadSilenceTh, sys_role.vadEnergyTh, sys_role.vadSilenceMs, sys_role.vadSpeechTh, sys_role.vadSilenceTh, sys_role.vadEnergyTh, sys_role.vadSilenceMs,
sys_role.userId, sys_role.state, sys_role.isDefault, sys_role.createTime sys_role.userId, sys_role.state, sys_role.isDefault, sys_role.createTime
</sql> </sql>
@@ -50,6 +50,7 @@
<if test="voiceName != null and voiceName != ''">voiceName = #{voiceName},</if> <if test="voiceName != null and voiceName != ''">voiceName = #{voiceName},</if>
<if test="isDefault != null and isDefault != ''">isDefault = #{isDefault},</if> <if test="isDefault != null and isDefault != ''">isDefault = #{isDefault},</if>
<if test="modelId != null and modelId != ''">modelId = #{modelId},</if> <if test="modelId != null and modelId != ''">modelId = #{modelId},</if>
<if test="templateId != null">templateId = #{templateId},</if>
<if test="ttsId != null and ttsId != ''"> <if test="ttsId != null and ttsId != ''">
<choose> <choose>
<when test="ttsId == -1">ttsId = null,</when> <when test="ttsId == -1">ttsId = null,</when>
@@ -79,12 +80,13 @@
</update> </update>
<insert id="add" useGeneratedKeys="true" keyProperty="roleName" parameterType="com.xiaozhi.entity.SysRole"> <insert id="add" useGeneratedKeys="true" keyProperty="roleName" parameterType="com.xiaozhi.entity.SysRole">
INSERT INTO sys_role ( avatar, roleName, roleDesc, voiceName, modelId, ttsId, sttId, userId, isDefault ) VALUES ( INSERT INTO sys_role ( avatar, roleName, roleDesc, voiceName, modelId, templateId, ttsId, sttId, userId, isDefault ) VALUES (
#{avatar}, #{avatar},
#{roleName}, #{roleName},
#{roleDesc}, #{roleDesc},
#{voiceName}, #{voiceName},
#{modelId}, #{modelId},
#{templateId},
<choose> <choose>
<when test="ttsId == -1">null</when> <when test="ttsId == -1">null</when>
<otherwise>#{ttsId}</otherwise> <otherwise>#{ttsId}</otherwise>
@@ -107,4 +109,4 @@
roleId = #{roleId} roleId = #{roleId}
</select> </select>
</mapper> </mapper>

View File

@@ -51,7 +51,7 @@ public class AppRoleController extends BaseController {
public AjaxResult query(SysRole role, HttpServletRequest request) { public AjaxResult query(SysRole role, HttpServletRequest request) {
try { try {
PageFilter pageFilter = initPageFilter(request); PageFilter pageFilter = initPageFilter(request);
role.setUserId(Math.toIntExact(AppLoginCache.get().getUserId())); // role.setUserId(Math.toIntExact(AppLoginCache.get().getUserId()));
List<SysRole> roleList = roleService.query(role, pageFilter); List<SysRole> roleList = roleService.query(role, pageFilter);
AjaxResult result = AjaxResult.success(); AjaxResult result = AjaxResult.success();
result.put("data", new PageInfo<>(roleList)); result.put("data", new PageInfo<>(roleList));

View File

@@ -436,24 +436,18 @@
style="margin-bottom: 16px" /> style="margin-bottom: 16px" />
<template v-else> <template v-else>
<!-- 提示词编辑模式选择 --> <!-- 仅允许选择模板(禁用自定义) -->
<div style="margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center"> <div style="margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center">
<a-space> <a-space>
<a-radio-group v-model="promptEditorMode" button-style="solid" @change="handlePromptModeChange"> <a-tag color="blue">仅支持从模板选择</a-tag>
<a-radio-button value="template">使用模板</a-radio-button> <a-select style="width: 240px" placeholder="请选择模板" v-model="selectedTemplateId"
<a-radio-button value="custom">自定义</a-radio-button> @change="handleTemplateChange" :loading="templatesLoading" :allowClear="false">
</a-radio-group> <a-select-option v-for="template in promptTemplates" :key="template.templateId"
:value="template.templateId">
<template v-if="promptEditorMode === 'template'"> {{ template.templateName }}
<a-select style="width: 200px" placeholder="选择模板" v-model="selectedTemplateId" <a-tag v-if="template.isDefault == 1" color="green" size="small">默认</a-tag>
@change="handleTemplateChange" :loading="templatesLoading"> </a-select-option>
<a-select-option v-for="template in promptTemplates" :key="template.templateId" </a-select>
:value="template.templateId">
{{ template.templateName }}
<a-tag v-if="template.isDefault == 1" color="green" size="small">默认</a-tag>
</a-select-option>
</a-select>
</template>
</a-space> </a-space>
<!-- 模板管理按钮 --> <!-- 模板管理按钮 -->
@@ -470,7 +464,7 @@
{ {
rules: [], rules: [],
}, },
]" :disabled="selectedModelType === 'agent'" :rows="10" placeholder="请输入角色提示词,描述角色的特点、知识背景和行为方式等" /> ]" :disabled="true" :rows="10" placeholder="请选择模板,提示词将随模板自动填充" />
</a-form-item> </a-form-item>
<!-- 表单操作按钮 --> <!-- 表单操作按钮 -->
<a-form-item> <a-form-item>
@@ -600,7 +594,8 @@ export default {
// 表单相关 // 表单相关
roleForm: this.$form.createForm(this), roleForm: this.$form.createForm(this),
promptEditorMode: 'custom', // 仅模板模式
promptEditorMode: 'template',
selectedTemplateId: null, selectedTemplateId: null,
promptTemplates: [], promptTemplates: [],
templatesLoading: false, templatesLoading: false,
@@ -1284,12 +1279,19 @@ export default {
e.preventDefault(); e.preventDefault();
this.roleForm.validateFields((err, values) => { this.roleForm.validateFields((err, values) => {
if (!err) { if (!err) {
// 校验必须选择模板
if (!this.selectedTemplateId) {
this.$message.warning('请选择模板');
return;
}
this.submitLoading = true; this.submitLoading = true;
// 添加语音提供商信息 // 添加语音提供商信息
const formData = { const formData = {
...values, ...values,
avatar: this.avatarUrl, avatar: this.avatarUrl,
// 记录模板ID
templateId: this.selectedTemplateId,
// 将开关的布尔值转换为数字0或1 // 将开关的布尔值转换为数字0或1
isDefault: values.isDefault ? 1 : 0 isDefault: values.isDefault ? 1 : 0
}; };
@@ -1372,6 +1374,9 @@ export default {
this.selectedModelType = record.modelType || MODEL_TYPE.LLM; this.selectedModelType = record.modelType || MODEL_TYPE.LLM;
this.selectedModelProvider = record.modelProvider || ''; this.selectedModelProvider = record.modelProvider || '';
// 同步模板选中项
this.selectedTemplateId = record.templateId || this.selectedTemplateId || null;
// 设置表单值将isDefault从数字转为布尔值 // 设置表单值将isDefault从数字转为布尔值
roleForm.setFieldsValue({ roleForm.setFieldsValue({
roleName: record.roleName, roleName: record.roleName,
@@ -1406,6 +1411,13 @@ export default {
ttsId: this.selectedTtsId, ttsId: this.selectedTtsId,
voiceName: record.voiceName voiceName: record.voiceName
}); });
// 如果存在模板ID填充模板内容
if (this.selectedTemplateId && this.promptTemplates && this.promptTemplates.length > 0) {
const t = this.promptTemplates.find(t => t.templateId === this.selectedTemplateId);
if (t) {
roleForm.setFieldsValue({ roleDesc: t.templateContent });
}
}
}, 500); }, 500);
}); });
}, },
@@ -1468,7 +1480,7 @@ export default {
resetForm() { resetForm() {
this.roleForm.resetFields(); this.roleForm.resetFields();
this.editingRoleId = null; this.editingRoleId = null;
this.promptEditorMode = 'custom'; this.promptEditorMode = 'template';
this.audioUrl = ''; this.audioUrl = '';
this.avatarUrl = ''; // 重置头像 this.avatarUrl = ''; // 重置头像
this.avatarFile = null; // 清空文件对象 this.avatarFile = null; // 清空文件对象
@@ -1568,11 +1580,9 @@ export default {
// 如果有默认模板,应用默认模板 // 如果有默认模板,应用默认模板
if (this.promptTemplates && this.promptTemplates.length > 0) { if (this.promptTemplates && this.promptTemplates.length > 0) {
const defaultTemplate = this.promptTemplates.find(t => t.isDefault == 1); const defaultTemplate = this.promptTemplates.find(t => t.isDefault == 1) || this.promptTemplates[0];
if (defaultTemplate) { if (defaultTemplate) {
this.selectedTemplateId = defaultTemplate.templateId; this.selectedTemplateId = defaultTemplate.templateId;
this.promptEditorMode = 'template';
this.$nextTick(() => { this.$nextTick(() => {
this.roleForm.setFieldsValue({ this.roleForm.setFieldsValue({
roleDesc: defaultTemplate.templateContent roleDesc: defaultTemplate.templateContent
@@ -1593,15 +1603,13 @@ export default {
.then(res => { .then(res => {
if (res.code === 200) { if (res.code === 200) {
this.promptTemplates = res.data.list; this.promptTemplates = res.data.list;
// 如果有默认模板,自动选择 // 自动选择默认模板或第一个
const defaultTemplate = this.promptTemplates.find(t => t.isDefault == 1); const defaultTemplate = this.promptTemplates.find(t => t.isDefault == 1) || this.promptTemplates[0];
if (defaultTemplate) { if (!this.selectedTemplateId && defaultTemplate) {
this.selectedTemplateId = defaultTemplate.templateId; this.selectedTemplateId = defaultTemplate.templateId;
if (this.promptEditorMode === 'template') { this.roleForm.setFieldsValue({
this.roleForm.setFieldsValue({ roleDesc: defaultTemplate.templateContent
roleDesc: defaultTemplate.templateContent });
});
}
} }
} else { } else {
this.showError(res.message); this.showError(res.message);
@@ -1744,17 +1752,8 @@ export default {
this.selectedTtsId = value; this.selectedTtsId = value;
}, },
// 处理提示词模式变更 // 自定义模式已禁用(保留空实现,避免引用报错)
handlePromptModeChange(e) { handlePromptModeChange() {},
if (e.target.value === 'template' && this.selectedTemplateId) {
const template = this.promptTemplates.find(t => t.templateId === this.selectedTemplateId);
if (template) {
this.roleForm.setFieldsValue({
roleDesc: template.templateContent
});
}
}
},
// 处理模板选择变更 // 处理模板选择变更
handleTemplateChange(templateId) { handleTemplateChange(templateId) {
@@ -2031,4 +2030,4 @@ export default {
color: #8c8c8c; color: #8c8c8c;
font-size: 12px; font-size: 12px;
} }
</style> </style>