Merge branch 'main' into web-change-password

This commit is contained in:
CGD
2025-03-25 14:24:58 +08:00
committed by GitHub
7 changed files with 410 additions and 216 deletions
+32 -16
View File
@@ -112,22 +112,6 @@ export default {
}); });
}).send(); }).send();
}, },
// 获取设备配置
getDeviceConfig(device_id, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/user/configDevice/${device_id}`)
.method('GET')
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.fail((err) => {
console.error('获取配置失败:', err);
RequestService.reAjaxFun(() => {
this.getDeviceConfig(device_id, callback);
});
}).send();
},
// 获取所有模型名称 // 获取所有模型名称
getModelNames(callback) { getModelNames(callback) {
RequestService.sendRequest() RequestService.sendRequest()
@@ -241,4 +225,36 @@ export default {
}) })
.send(); .send();
}, },
// 获取智能体配置
getDeviceConfig(deviceId, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/user/agent/${deviceId}`)
.method('GET')
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.fail((err) => {
console.error('获取配置失败:', err);
RequestService.reAjaxFun(() => {
this.getDeviceConfig(deviceId, callback);
});
}).send();
},
// 配置智能体
updateAgentConfig(agentId, configData, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/user/agent/${agentId}`)
.method('PUT')
.data(configData)
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.fail(() => {
RequestService.reAjaxFun(() => {
this.updateAgentConfig(agentId, configData, callback);
});
}).send();
},
} }
@@ -17,7 +17,7 @@
音色模型{{ device.ttsVoiceName }} 音色模型{{ device.ttsVoiceName }}
</div> </div>
<div style="display: flex;gap: 10px;align-items: center;"> <div style="display: flex;gap: 10px;align-items: center;">
<div class="settings-btn" @click="$emit('configure')"> <div class="settings-btn" @click="handleConfigure">
配置角色 配置角色
</div> </div>
<div class="settings-btn"> <div class="settings-btn">
@@ -48,6 +48,9 @@ export default {
methods: { methods: {
handleDelete() { handleDelete() {
this.$emit('delete', this.device.agentId) this.$emit('delete', this.device.agentId)
},
handleConfigure() {
this.$router.push({ path: '/role-config', query: { agentId: this.device.agentId } });
} }
} }
} }
+6 -23
View File
@@ -4,14 +4,14 @@
<div style="display: flex;align-items: center;gap: 10px;"> <div style="display: flex;align-items: center;gap: 10px;">
<img alt="" src="@/assets/xiaozhi-logo.png" style="width: 42px;height: 42px;"/> <img alt="" src="@/assets/xiaozhi-logo.png" style="width: 42px;height: 42px;"/>
<img alt="" src="@/assets/xiaozhi-ai.png" style="width: 58px;height: 12px;"/> <img alt="" src="@/assets/xiaozhi-ai.png" style="width: 58px;height: 12px;"/>
<div class="equipment-management" @click="goHome"> <div class="equipment-management" :class="{ 'active-tab': $route.path === '/home' }" @click="goHome">
<img alt="" src="@/assets/home/equipment.png" style="width: 12px;height: 10px;"/> <img alt="" src="@/assets/home/equipment.png" style="width: 12px;height: 10px;"/>
智能体管理 智能体管理
</div> </div>
<div class="equipment-management2" :class="{ 'active-tab': $route.path === '/user-management' }" @click="goUserManagement"> <div class="equipment-management" :class="{ 'active-tab': $route.path === '/user-management' }" @click="goUserManagement">
用户管理 用户管理
</div> </div>
<div class="equipment-management2" :class="{ 'active-tab': $route.path === '/model-config' }" @click="goModelConfig"> <div class="equipment-management" :class="{ 'active-tab': $route.path === '/model-config' }" @click="goModelConfig">
模型配置 模型配置
</div> </div>
</div> </div>
@@ -111,26 +111,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.equipment-management,
.equipment-management2 {
cursor: pointer;
}
.equipment-management { .equipment-management {
width: 82px;
height: 24px;
border-radius: 12px;
background: #5778ff;
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
font-weight: 500;
color: #fff;
font-size: 10px;
}
.equipment-management2 {
width: 82px; width: 82px;
height: 24px; height: 24px;
border-radius: 12px; border-radius: 12px;
@@ -144,9 +125,11 @@ export default {
margin-left: 1px; margin-left: 1px;
align-items: center; align-items: center;
transition: all 0.3s ease; transition: all 0.3s ease;
cursor: pointer;
} }
.equipment-management2.active-tab { .equipment-management.active-tab {
background: #5778ff !important; background: #5778ff !important;
color: #fff !important; color: #fff !important;
} }
+158 -89
View File
@@ -1,68 +1,93 @@
<template> <template>
<div class="welcome"> <div class="welcome">
<HeaderBar /> <HeaderBar />
<div class="model-tabs-container"> <div class="model-container">
<el-tabs v-model="activeTab" class="model-tabs" > <el-menu :default-active="activeTab" class="el-menu-vertical-demo" @select="handleMenuSelect">
<el-tab-pane label="语音活动检测模型(VAD)" name="vad"></el-tab-pane> <el-menu-item index="vad">语音活动检测</el-menu-item>
<el-tab-pane label="语音识别(ASR)" name="asr"></el-tab-pane> <el-menu-item index="asr">语音识别</el-menu-item>
<el-tab-pane label="大语言模型(LLM)" name="llm"></el-tab-pane> <el-menu-item index="llm">大语言模型</el-menu-item>
<el-tab-pane label="意图识别模型(Intent)" name="intent"></el-tab-pane> <el-menu-item index="intent">意图识别</el-menu-item>
<el-tab-pane label="语音合成模型(TTS)" name="tts"></el-tab-pane> <el-menu-item index="tts">语音合成</el-menu-item>
<el-tab-pane label="记忆模型(Memory)" name="memory"></el-tab-pane> <el-menu-item index="memory">记忆</el-menu-item>
</el-tabs> </el-menu>
<div class="import-export-btn"> <div class="content-container">
<el-button v-if="activeTab === 'tts'" type="primary" @click="ttsDialogVisible = true" style="margin-right: 10px;">模型设置</el-button> <div class="import-export-btn">
<el-button size="small" @click="handleImportExport">导入导出配置</el-button> <el-button v-if="activeTab === 'tts'" type="primary" @click="ttsDialogVisible = true" style="margin-right: 10px;">
模型设置
</el-button>
<el-button size="small" @click="handleImportExport">导入导出配置</el-button>
</div>
<el-main style="padding: 20px; display: flex; flex-direction: column;">
<el-card class="model-card" shadow="always">
<div class="model-header">
<h2>大语言模型 (LLM)</h2>
<el-button type="primary" @click="addModel" class="add-btn">添加</el-button>
</div>
<div class="model-search-operate" style="margin-bottom: 20px;">
<el-input
placeholder="请输入模型名称查询"
v-model="search"
style="width: 300px; margin-right: 10px"
/>
<el-button @click="handleSearch">查询</el-button>
</div>
<el-table
:data="modelList"
style="width: 100%;"
border
stripe
header-cell-class-name="header-cell"
>
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="模型名称" prop="candidateName"></el-table-column>
<el-table-column label="模型编码" prop="code"></el-table-column>
<el-table-column label="提供商" prop="supplier"></el-table-column>
<el-table-column label="是否启用">
<template slot-scope="scope">
<el-switch v-model="scope.row.isApplied" active-color="#409EFF" inactive-color="#C0CCDA"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini"
@click="editModel(scope.row)"
style="margin-right: 5px;"
type="text"
class="action-btn"
>修改</el-button>
<el-button
size="mini"
type="danger"
@click="deleteModel(scope.row)"
class="action-btn"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="table-footer">
<el-button @click="selectAll" class="footer-btn">全选</el-button>
<el-button type="danger" @click="batchDelete" class="footer-btn">删除</el-button>
</div>
</el-card>
<div class="pagination-container">
<el-pagination
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 15]"
:page-size="pageSize"
layout="prev, pager, next"
:total="total"
/>
</div>
<div class="copyright">
©2025 xiaozhi-esp32-server
</div>
<ModelEditDialog :visible.sync="editDialogVisible" :modelData="editModelData" @save="handleModelSave"/>
<TtsModel :visible.sync="ttsDialogVisible" />
</el-main>
</div> </div>
</div> </div>
<el-main style="padding: 20px; display: flex; flex-direction: column;">
<el-card class="model-card" shadow="always">
<div class="model-search-operate" style="display: flex; align-items: center; margin-bottom: 20px;">
<el-input placeholder="请输入模型名称查询" v-model="search" style="width: 300px; margin-right: 10px" />
<el-button @click="handleSearch">查询</el-button>
<el-button type="primary" @click="addModel">增加模型</el-button>
<el-button type="danger" @click="batchDelete">批量删除</el-button>
</div>
<el-table :data="modelList" style="width: 100%;" border stripe>
<el-table-column label="模型编码" prop="code"></el-table-column>
<el-table-column label="模型名称" prop="candidateName"></el-table-column>
<el-table-column label="是否应用">
<template slot-scope="scope">
<el-tag :type="scope.row.isApplied ? 'success' : 'danger'">
{{ scope.row.isApplied ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="供应商名称" prop="supplier"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" @click="editModel(scope.row)" style="margin-right: 10px;">修改</el-button>
<el-button size="mini" type="danger" @click="deleteModel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<div class="pagination-container">
<el-pagination
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 15]"
:page-size="pageSize"
layout="prev, pager, next"
:total="total"
/>
</div>
<div style="font-size: 12px; font-weight: 400; margin-top: auto; padding-top: 30px; color: #979db1;">
©2025 xiaozhi-esp32-server
</div>
<ModelEditDialog :visible.sync="editDialogVisible" :modelData="editModelData" @save="handleModelSave"/>
<TtsModel :visible.sync="ttsDialogVisible" />
</el-main>
</div> </div>
</template> </template>
@@ -75,16 +100,15 @@ export default {
components: { HeaderBar, ModelEditDialog, TtsModel }, components: { HeaderBar, ModelEditDialog, TtsModel },
data() { data() {
return { return {
activeTab: 'vad', activeTab: 'llm',
search: '', search: '',
editDialogVisible: false, editDialogVisible: false,
editModelData: {}, editModelData: {},
ttsDialogVisible: false, ttsDialogVisible: false,
modelList: [ modelList: [
{ code: 'AILLM', candidateName: '阿里百炼', isApplied: true, supplier: 'openai' }, { code: 'DeepSeek', candidateName: '深度求索', isApplied: true, supplier: '硅基流动' },
{ code: 'DoubaoLLM', candidateName: '豆包大模型', isApplied: true, supplier: 'openai' }, { code: 'SmartAssist', candidateName: '智能助手', isApplied: false, supplier: '智脑科技' },
{ code: 'DeepSeekLLM', candidateName: '深度求索', isApplied: true, supplier: 'openai' }, { code: 'CogEngine', candidateName: '认知引擎', isApplied: true, supplier: '云智科技' },
{ code: 'DifyLLM', candidateName: 'DifChat', isApplied: true, supplier: 'dify' },
], ],
currentPage: 1, currentPage: 1,
pageSize: 4, pageSize: 4,
@@ -92,19 +116,18 @@ export default {
}; };
}, },
methods: { methods: {
// 查询 handleMenuSelect(index) {
this.activeTab = index;
},
handleSearch() { handleSearch() {
console.log('查询:', this.search); console.log('查询:', this.search);
}, },
// 批量删除
batchDelete() { batchDelete() {
console.log('批量删除'); console.log('批量删除');
}, },
// 增加
addModel() { addModel() {
console.log('增加模型'); console.log('增加模型');
}, },
// 修改
editModel(model) { editModel(model) {
this.editModelData = { this.editModelData = {
code: model.code, code: model.code,
@@ -113,7 +136,6 @@ export default {
}; };
this.editDialogVisible = true; this.editDialogVisible = true;
}, },
// 删除
deleteModel(model) { deleteModel(model) {
console.log('删除:', model); console.log('删除:', model);
}, },
@@ -121,14 +143,15 @@ export default {
this.currentPage = page; this.currentPage = page;
console.log('当前页码:', page); console.log('当前页码:', page);
}, },
// 导入导出
handleImportExport() { handleImportExport() {
console.log('导入导出'); console.log('导入导出');
}, },
handleModelSave(formData) { handleModelSave(formData) {
// 处理保存
console.log('保存的模型数据:', formData); console.log('保存的模型数据:', formData);
}, },
selectAll() {
console.log('全选');
}
} }
}; };
</script> </script>
@@ -147,8 +170,31 @@ export default {
-o-background-size: cover; -o-background-size: cover;
} }
.model-search-operate { .model-container {
display: flex;
margin-top: 25px;
}
.el-menu-vertical-demo {
width: 250px;
margin-right: 20px;
background-color: #f8f9fa;
}
.content-container {
flex: 1;
display: flex;
flex-direction: column;
}
.model-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
}
.model-search-operate {
display: flex; display: flex;
align-items: center; align-items: center;
} }
@@ -160,6 +206,7 @@ export default {
.el-table__header th { .el-table__header th {
background-color: #f5f7fa; background-color: #f5f7fa;
color: #606266; color: #606266;
font-weight: 500;
} }
.model-card { .model-card {
@@ -169,22 +216,11 @@ export default {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
} }
.model-tabs-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 25px;
}
.model-tabs {
flex-grow: 0;
margin-right: 20px;
}
.import-export-btn { .import-export-btn {
margin-right: 40px;
margin-bottom: 20px; margin-bottom: 20px;
display: flex;
justify-content: flex-end;
margin-right: 40px;
} }
.pagination-container { .pagination-container {
@@ -193,7 +229,40 @@ export default {
justify-content: flex-end; justify-content: flex-end;
} }
::v-deep .el-tabs { .table-footer {
margin-left: 70px; margin-top: 20px;
display: flex;
align-items: center;
}
.footer-btn {
margin-right: 10px;
}
.copyright {
font-size: 12px;
font-weight: 400;
color: #979db1;
margin-top: auto;
padding-top: 30px;
text-align: center;
}
.action-btn {
padding: 0 8px;
}
.header-cell {
font-weight: 500;
}
.add-btn {
margin-right: 830px;
padding: 8px 16px;
border-radius: 4px;
background-color: #409eff;
color: white;
border: none;
cursor: pointer;
} }
</style> </style>
+93 -35
View File
@@ -1,46 +1,68 @@
<template> <template>
<div class="welcome"> <div class="welcome">
<HeaderBar /> <HeaderBar />
<el-main style="padding: 20px; display: flex; flex-direction: column;"> <el-main class="main" style="padding: 20px; display: flex; flex-direction: column;">
<el-card class="user-card" shadow="always" > <div class="top-area">
<div class="user-search-operate" style="display: flex; align-items: center; margin-bottom: 20px;"> <div class="page-title">用户管理</div>
<el-input placeholder="请输入手机号码查询" v-model="searchPhone" style="width: 300px; margin-right: 10px" /> <div class="page-search">
<el-button @click="handleSearch">查询</el-button> <el-input placeholder="请输入手机号码查询" v-model="searchPhone" style="width: 300px; margin-right: 10px" />
<el-button type="danger" @click="batchDelete">批量删除</el-button> <el-button class="btn-search" @click="handleSearch">搜索</el-button>
<el-button type="danger" @click="batchDisable">批量禁用</el-button> <!-- <el-button type="danger" @click="batchDelete">批量删除</el-button>
<el-button type="danger" @click="batchDisable">批量禁用</el-button> -->
</div>
</div> </div>
<el-table :data="userList" style="width: 100%;" border stripe> <el-card class="user-card" shadow="never">
<el-table-column label="用户Id" prop="userId"></el-table-column> <!-- <div class="user-search-operate" style="display: flex; align-items: center; margin-bottom: 20px;">
<el-table-column label="手机号码" prop="phone"></el-table-column> <el-input placeholder="请输入手机号码查询" v-model="searchPhone" style="width: 300px; margin-right: 10px" />
<el-table-column label="Status" prop="status"></el-table-column> <el-button @click="handleSearch">查询</el-button>
<el-table-column label="设备数量" prop="deviceCount"></el-table-column> <el-button type="danger" @click="batchDelete">批量删除</el-button>
<el-table-column label="操作"> <el-button type="danger" @click="batchDisable">批量禁用</el-button>
<template slot-scope="scope"> </div> -->
<el-button size="mini" @click="resetPassword(scope.row)">重置密码</el-button> <el-table :data="userList" style="width: 100%;">
<el-button size="mini" <el-table-column label="选择"
type="selection"
width="55">
</el-table-column>
<el-table-column label="用户Id" prop="userId"></el-table-column>
<el-table-column label="手机号码" prop="phone"></el-table-column>
<el-table-column label="设备数量" prop="deviceCount"></el-table-column>
<el-table-column label="状态" prop="status"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="resetPassword(scope.row)">重置密码</el-button>
<el-button size="mini" type="text"
v-if="scope.row.status === '正常'" v-if="scope.row.status === '正常'"
@click="disableUser(scope.row)">禁用</el-button> @click="disableUser(scope.row)">禁用</el-button>
<el-button size="mini" <el-button size="mini" type="text"
v-if="scope.row.status === '禁用'" v-if="scope.row.status === '禁用'"
@click="restoreUser(scope.row)">恢复</el-button> @click="restoreUser(scope.row)">恢复</el-button>
<el-button size="mini" @click="deleteUser(scope.row)" style="color: #ff4949">删除用户</el-button> <el-button size="mini" type="text" @click="deleteUser(scope.row)" style="color: #ff4949">删除用户</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="table_bottom">
<div class="ctrl_btn">
<el-button size="mini" type="primary">全选</el-button>
<el-button size="mini" type="success" icon="el-icon-circle-check">启用</el-button>
<el-button size="mini" type="warning" icon="el-icon-circle-close">禁用</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
</div>
<div class="pagination-container">
<el-pagination
background
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 15]"
:page-size="pageSize"
layout="prev, pager, next"
:total="total"
/>
</div>
</div>
</el-card> </el-card>
<div class="pagination-container">
<el-pagination
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 15]"
:page-size="pageSize"
layout="prev, pager, next"
:total="total"
/>
</div>
<div style="font-size: 12px; font-weight: 400; margin-top: auto; padding-top: 30px; color: #979db1;"> <div style="font-size: 12px; font-weight: 400; margin-top: auto; padding-top: 30px; color: #979db1;">
©2025 xiaozhi-esp32-server ©2025 xiaozhi-esp32-server
</div> </div>
@@ -100,7 +122,32 @@ export default {
}; };
</script> </script>
<style scoped> <style lang="scss" scoped>
.main {
padding: 20px; display: flex; flex-direction: column;
background: linear-gradient(to bottom right, #dce8ff, #e4eeff, #e6cbfd);
}
.top-area {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.page-title {
font-size: 20px;
font-weight: 600;
color: #303133;
}
.page-search {
display: flex;
align-items: center;
.btn-search {
margin-left: 10px;
background: linear-gradient(to right, #5778ff, #c793f3);
width: 100px;
color: #fff;
}
}
}
.welcome { .welcome {
min-width: 900px; min-width: 900px;
min-height: 506px; min-height: 506px;
@@ -133,11 +180,22 @@ export default {
background: #fff; background: #fff;
border-radius: 12px; border-radius: 12px;
padding: 20px; padding: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); opacity: 0.9;
// box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.table_bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
.ctrl_btn {
display: flex;
align-items: center;
}
} }
.pagination-container { .pagination-container {
margin-top: 20px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
} }
+102 -37
View File
@@ -1,6 +1,5 @@
<template> <template>
<div class="welcome"> <div class="welcome">
<!-- 公共头部 -->
<HeaderBar/> <HeaderBar/>
<el-main style="padding: 16px;display: flex;flex-direction: column;"> <el-main style="padding: 16px;display: flex;flex-direction: column;">
<div style="border-radius: 16px;background: #fafcfe; border: 1px solid #e8f0ff;"> <div style="border-radius: 16px;background: #fafcfe; border: 1px solid #e8f0ff;">
@@ -17,7 +16,7 @@
<div style="padding: 16px 24px;max-width: 792px;"> <div style="padding: 16px 24px;max-width: 792px;">
<el-form-item label="助手昵称:"> <el-form-item label="助手昵称:">
<div class="input-46" style="width: 100%; max-width: 412px;"> <div class="input-46" style="width: 100%; max-width: 412px;">
<el-input v-model="form.name"/> <el-input v-model="form.agentName"/>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="角色模版:"> <el-form-item label="角色模版:">
@@ -30,7 +29,7 @@
<el-form-item label="角色音色:"> <el-form-item label="角色音色:">
<div style="display: flex;gap: 8px;align-items: center;"> <div style="display: flex;gap: 8px;align-items: center;">
<div class="input-46" style="flex:1.4;"> <div class="input-46" style="flex:1.4;">
<el-select v-model="form.timbre" placeholder="请选择" style="width: 100%;"> <el-select v-model="form.ttsVoiceId" placeholder="请选择" style="width: 100%;">
<el-option v-for="item in options" :key="item.value" :label="item.label" <el-option v-for="item in options" :key="item.value" :label="item.label"
:value="item.value"> :value="item.value">
</el-option> </el-option>
@@ -45,14 +44,14 @@
<el-form-item label="角色介绍:"> <el-form-item label="角色介绍:">
<div class="textarea-box"> <div class="textarea-box">
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" <el-input type="textarea" rows="5" resize="none" placeholder="请输入内容"
v-model="form.introduction" maxlength="2000" show-word-limit/> v-model="form.systemPrompt" maxlength="2000" show-word-limit/>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="记忆体:"> <el-form-item label="记忆体:">
<div class="textarea-box"> <div class="textarea-box">
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" <el-input type="textarea" rows="5" resize="none" placeholder="请输入内容"
v-model="form.prompt" maxlength="1000"/> v-model="form.langCode" maxlength="1000"/>
<div class="prompt-bottom"> <div class="prompt-bottom" @click="clearMemory">
<div style="display: flex;gap: 8px;align-items: center;"> <div style="display: flex;gap: 8px;align-items: center;">
<div style="color: #979db1;font-size: 11px;">当前记忆每次对话后重新生成</div> <div style="color: #979db1;font-size: 11px;">当前记忆每次对话后重新生成</div>
<div class="clear-btn"> <div class="clear-btn">
@@ -60,7 +59,7 @@
清除 清除
</div> </div>
</div> </div>
<div style="color: #979db1;font-size:11px;">{{ form.prompt.length }}/1000</div> <div style="color: #979db1;font-size:11px;">{{ form.langCode.length }}/1000</div>
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
@@ -106,39 +105,63 @@ export default {
return { return {
deviceMac: 'CC:ba:97:11:a6:ac', deviceMac: 'CC:ba:97:11:a6:ac',
form: { form: {
name: "", agentCode:"",
timbre: "", agentName: "",
introduction: "", ttsVoiceId: "",
prompt: "", systemPrompt: "",
langCode:"",
language:"",
sort:"",
model: { model: {
tts: "", ttsModelId: "",
vad: "", vadModelId: "",
asr: "", asrModelId:"",
llm: "", llmModelId: "",
memory:"", memModelId: "",
intent:"" intentModelId: "",
} }
}, },
options: [ options: [
{ value: '选项1', label: '黄金糕' }, { value: '选项1', label: '黄金糕' },
{ value: '选项2', label: '双皮奶' } { value: '选项2', label: '双皮奶' }
], ],
models: [ models: [
{ label: '大语言模型(LLM)', key: 'llm' }, { label: '大语言模型(LLM)', key: 'llmModelId' },
{ label: '语音识别(ASR)', key: 'asr' }, { label: '语音识别(ASR)', key: 'asrModelId' },
{ label: '语音活动检测模型(VAD)', key: 'vad' }, { label: '语音活动检测模型(VAD)', key: 'vadModelId' },
{ label: '语音合成模型(TTS)', key: 'tts' }, { label: '语音合成模型(TTS)', key: 'ttsModelId' },
{ label: '意图识别模型(Intent)', key: 'intent' }, { label: '意图识别模型(Intent)', key: 'intentModelId' },
{ label: '记忆模型(Memory)', key: 'memory' } { label: '记忆模型(Memory)', key: 'memModelId' }
], ],
templates: ['台湾女友', '土豆子', '英语老师', '好奇小男孩', '汪汪队队长'] templates: ['台湾女友', '土豆子', '英语老师', '好奇小男孩', '汪汪队队长']
} }
}, },
methods: { methods: {
saveConfig() { saveConfig() {
// 此处写保存配置逻辑 const configData = {
this.$message.success('配置已保存') agentCode: this.form.agentCode,
agentName: this.form.agentName,
asrModelId: this.form.model.asrModelId,
vadModelId: this.form.model.vadModelId,
llmModelId: this.form.model.llmModelId,
ttsModelId: this.form.model.ttsModelId,
ttsVoiceId: this.form.ttsVoiceId,
memModelId: this.form.model.memModelId,
intentModelId: this.form.model.intentModelId,
systemPrompt: this.form.systemPrompt,
langCode: this.form.langCode,
language: this.form.language,
sort: this.form.sort
};
import('@/apis/module/user').then(({ default: userApi }) => {
userApi.updateAgentConfig(this.$route.query.agentId, configData, ({data}) => {
if (data.code === 0) {
this.$message.success('配置保存成功');
} else {
this.$message.error(data.msg || '配置保存失败');
}
});
});
}, },
resetConfig() { resetConfig() {
this.$confirm('确定要重置配置吗?', '提示', { this.$confirm('确定要重置配置吗?', '提示', {
@@ -148,20 +171,62 @@ export default {
}).then(() => { }).then(() => {
// 重置表单 // 重置表单
this.form = { this.form = {
name: "", agentCode:"",
timbre: "", agentName: "",
introduction: "", ttsVoiceId: "",
prompt: "", systemPrompt: "",
model: "" langCode:"",
language:"",
sort:"",
model: {
ttsModelId: "",
vadModelId: "",
asrModelId:"",
llmModelId: "",
memModelId: "",
intentModelId: "",
}
} }
this.$message.success('配置已重置') this.$message.success('配置已重置')
}).catch(() => { }).catch(() => {
}) })
}, },
// 处理选择模板的逻辑
selectTemplate(template) { selectTemplate(template) {
this.form.name = template; this.form.name = template;
this.$message.success(`已选择模板:${template}`); this.$message.success(`已选择模板:${template}`);
},
fetchAgentConfig(agentId) {
import('@/apis/module/user').then(({ default: userApi }) => {
userApi.getDeviceConfig(agentId, ({ data }) => {
if (data.code === 0) {
this.form = {
...this.form,
...data.data,
model: {
ttsModelId: data.data.ttsModelId,
vadModelId: data.data.vadModelId,
asrModelId: data.data.asrModelId,
llmModelId: data.data.llmModelId,
memModelId: data.data.memModelId,
intentModelId: data.data.intentModelId
}
};
} else {
this.$message.error(data.msg || '获取配置失败');
}
});
});
},
// 清空记忆体内容
clearMemory() {
this.form.langCode = "";
this.$message.success("记忆体已清空");
},
},
mounted() {
const agentId = this.$route.query.agentId;
if (agentId) {
this.fetchAgentConfig(agentId);
} }
} }
} }
@@ -28,33 +28,33 @@ async def sendAudioMessage(conn, audios, text, text_index=0):
# 播放音频 # 播放音频
async def sendAudio(conn, audios): async def sendAudio(conn, audios):
# 流控参数优化 # 流控参数优化
original_frame_duration = 60 # 原始帧时长(毫秒) frame_duration = 60 # 帧时长(毫秒),匹配 Opus 编码
adjusted_frame_duration = int(original_frame_duration * 0.8) # 缩短20%
total_frames = len(audios) # 获取总帧数
compensation = total_frames * (original_frame_duration - adjusted_frame_duration) / 1000 # 补偿时间(秒)
start_time = time.perf_counter() start_time = time.perf_counter()
play_position = 0 # 已播放时长(毫秒) play_position = 0
for opus_packet in audios: # 预缓冲:发送前 3 帧
pre_buffer = min(3, len(audios))
for i in range(pre_buffer):
await conn.websocket.send(audios[i])
conn.logger.bind(tag=TAG).debug(f"预缓冲帧 {i}, 时间: {(time.perf_counter() - start_time) * 1000:.2f}ms")
# 正常播放剩余帧
for opus_packet in audios[pre_buffer:]:
if conn.client_abort: if conn.client_abort:
return return
# 计算带加速因子的预期时间 # 计算预期发送时间
expected_time = start_time + (play_position / 1000) expected_time = start_time + (play_position / 1000)
current_time = time.perf_counter() current_time = time.perf_counter()
# 流控等待(使用加速后的帧时长)
delay = expected_time - current_time delay = expected_time - current_time
if delay > 0: if delay > 0:
await asyncio.sleep(delay) await asyncio.sleep(delay)
await conn.websocket.send(opus_packet) await conn.websocket.send(opus_packet)
play_position += adjusted_frame_duration # 使用调整后的帧时长 conn.logger.bind(tag=TAG).debug(f"发送帧,位置: {play_position}ms, 实际间隔: {(time.perf_counter() - current_time) * 1000:.2f}ms")
play_position += frame_duration
# 补偿因加速损失的时长
if compensation > 0:
await asyncio.sleep(compensation)
async def send_tts_message(conn, state, text=None): async def send_tts_message(conn, state, text=None):