mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
Merge branch 'main' of https://github.com/xinnan-tech/xiaozhi-esp32-server
This commit is contained in:
+11
-5
@@ -139,6 +139,9 @@ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/
|
||||
|
||||
conda install libopus -y
|
||||
conda install ffmpeg -y
|
||||
|
||||
# 在 Linux 环境下进行部署时,如出现类似缺失 libiconv.so.2 动态库的报错 请通过以下命令进行安装
|
||||
conda install libiconv -y
|
||||
```
|
||||
|
||||
请注意,以上命令,不是一股脑执行就成功的,你需要一步步执行,每一步执行完后,都检查一下输出的日志,查看是否成功。
|
||||
@@ -267,7 +270,8 @@ LLM:
|
||||
6、[我说话很慢,停顿时小智老是抢话](./FAQ.md)<br/>
|
||||
## 部署相关教程
|
||||
1、[如何自动拉取本项目最新代码自动编译和启动](./dev-ops-integration.md)<br/>
|
||||
2、[如何与Nginx集成](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/791)<br/>
|
||||
2、[如何部署MQTT网关开启MQTT+UDP协议](./mqtt-gateway-integration.md)<br/>
|
||||
3、[如何与Nginx集成](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/791)<br/>
|
||||
## 拓展相关教程
|
||||
1、[如何开启手机号码注册智控台](./ali-sms-integration.md)<br/>
|
||||
2、[如何集成HomeAssistant实现智能家居控制](./homeassistant-integration.md)<br/>
|
||||
@@ -275,11 +279,13 @@ LLM:
|
||||
4、[如何部署MCP接入点](./mcp-endpoint-enable.md)<br/>
|
||||
5、[如何接入MCP接入点](./mcp-endpoint-integration.md)<br/>
|
||||
6、[如何开启声纹识别](./voiceprint-integration.md)<br/>
|
||||
10、[新闻插件源配置指南](./newsnow_plugin_config.md)<br/>
|
||||
7、[新闻插件源配置指南](./newsnow_plugin_config.md)<br/>
|
||||
8、[天气插件使用指南](./weather-integration.md)<br/>
|
||||
## 语音克隆、本地语音部署相关教程
|
||||
1、[如何部署集成index-tts本地语音](./index-stream-integration.md)<br/>
|
||||
2、[如何部署集成fish-speech本地语音](./fish-speech-integration.md)<br/>
|
||||
3、[如何部署集成PaddleSpeech本地语音](./paddlespeech-deploy.md)<br/>
|
||||
1、[如何在智控台克隆音色](./huoshan-streamTTS-voice-cloning.md)<br/>
|
||||
2、[如何部署集成index-tts本地语音](./index-stream-integration.md)<br/>
|
||||
3、[如何部署集成fish-speech本地语音](./fish-speech-integration.md)<br/>
|
||||
4、[如何部署集成PaddleSpeech本地语音](./paddlespeech-deploy.md)<br/>
|
||||
## 性能测试教程
|
||||
1、[各组件速度测试指南](./performance_tester.md)<br/>
|
||||
2、[定期公开测试结果](https://github.com/xinnan-tech/xiaozhi-performance-research)<br/>
|
||||
|
||||
@@ -357,7 +357,7 @@ conda install libopus -y
|
||||
conda install ffmpeg -y
|
||||
|
||||
# 在 Linux 环境下进行部署时,如出现类似缺失 libiconv.so.2 动态库的报错 请通过以下命令进行安装
|
||||
conda install libiconv
|
||||
conda install libiconv -y
|
||||
```
|
||||
|
||||
请注意,以上命令,不是一股脑执行就成功的,你需要一步步执行,每一步执行完后,都检查一下输出的日志,查看是否成功。
|
||||
@@ -471,7 +471,8 @@ ws://你电脑局域网的ip:8000/xiaozhi/v1/
|
||||
6、[我说话很慢,停顿时小智老是抢话](./FAQ.md)<br/>
|
||||
## 部署相关教程
|
||||
1、[如何自动拉取本项目最新代码自动编译和启动](./dev-ops-integration.md)<br/>
|
||||
2、[如何与Nginx集成](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/791)<br/>
|
||||
2、[如何部署MQTT网关开启MQTT+UDP协议](./mqtt-gateway-integration.md)<br/>
|
||||
3、[如何与Nginx集成](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/791)<br/>
|
||||
## 拓展相关教程
|
||||
1、[如何开启手机号码注册智控台](./ali-sms-integration.md)<br/>
|
||||
2、[如何集成HomeAssistant实现智能家居控制](./homeassistant-integration.md)<br/>
|
||||
@@ -482,9 +483,10 @@ ws://你电脑局域网的ip:8000/xiaozhi/v1/
|
||||
7、[新闻插件源配置指南](./newsnow_plugin_config.md)<br/>
|
||||
8、[天气插件使用指南](./weather-integration.md)<br/>
|
||||
## 语音克隆、本地语音部署相关教程
|
||||
1、[如何部署集成index-tts本地语音](./index-stream-integration.md)<br/>
|
||||
2、[如何部署集成fish-speech本地语音](./fish-speech-integration.md)<br/>
|
||||
3、[如何部署集成PaddleSpeech本地语音](./paddlespeech-deploy.md)<br/>
|
||||
1、[如何在智控台克隆音色](./huoshan-streamTTS-voice-cloning.md)<br/>
|
||||
2、[如何部署集成index-tts本地语音](./index-stream-integration.md)<br/>
|
||||
3、[如何部署集成fish-speech本地语音](./fish-speech-integration.md)<br/>
|
||||
4、[如何部署集成PaddleSpeech本地语音](./paddlespeech-deploy.md)<br/>
|
||||
## 性能测试教程
|
||||
1、[各组件速度测试指南](./performance_tester.md)<br/>
|
||||
2、[定期公开测试结果](https://github.com/xinnan-tech/xiaozhi-performance-research)<br/>
|
||||
|
||||
+7
-7
@@ -62,15 +62,15 @@ VAD:
|
||||
### 7、部署相关教程
|
||||
1、[如何进行最简化部署](./Deployment.md)<br/>
|
||||
2、[如何进行全模块部署](./Deployment_all.md)<br/>
|
||||
2、[如何部署MQTT网关开启MQTT+UDP协议](./mqtt-gateway-integration.md)<br/>
|
||||
3、[如何自动拉取本项目最新代码自动编译和启动](./dev-ops-integration.md)<br/>
|
||||
4、[如何与Nginx集成](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/791)<br/>
|
||||
3、[如何部署MQTT网关开启MQTT+UDP协议](./mqtt-gateway-integration.md)<br/>
|
||||
4、[如何自动拉取本项目最新代码自动编译和启动](./dev-ops-integration.md)<br/>
|
||||
5、[如何与Nginx集成](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/791)<br/>
|
||||
|
||||
### 8、编译固件相关教程
|
||||
### 9、编译固件相关教程
|
||||
1、[如何自己编译小智固件](./firmware-build.md)<br/>
|
||||
2、[如何基于虾哥编译好的固件修改OTA地址](./firmware-setting.md)<br/>
|
||||
|
||||
### 8、拓展相关教程
|
||||
### 10、拓展相关教程
|
||||
1、[如何开启手机号码注册智控台](./ali-sms-integration.md)<br/>
|
||||
2、[如何集成HomeAssistant实现智能家居控制](./homeassistant-integration.md)<br/>
|
||||
3、[如何开启视觉模型实现拍照识物](./mcp-vision-integration.md)<br/>
|
||||
@@ -79,13 +79,13 @@ VAD:
|
||||
6、[如何开启声纹识别](./voiceprint-integration.md)<br/>
|
||||
10、[新闻插件源配置指南](./newsnow_plugin_config.md)<br/>
|
||||
|
||||
### 9、语音克隆、本地语音部署相关教程
|
||||
### 11、语音克隆、本地语音部署相关教程
|
||||
1、[如何在智控台克隆音色](./huoshan-streamTTS-voice-cloning.md)<br/>
|
||||
2、[如何部署集成index-tts本地语音](./index-stream-integration.md)<br/>
|
||||
3、[如何部署集成fish-speech本地语音](./fish-speech-integration.md)<br/>
|
||||
4、[如何部署集成PaddleSpeech本地语音](./paddlespeech-deploy.md)<br/>
|
||||
|
||||
### 10、性能测试教程
|
||||
### 12、性能测试教程
|
||||
1、[各组件速度测试指南](./performance_tester.md)<br/>
|
||||
2、[定期公开测试结果](https://github.com/xinnan-tech/xiaozhi-performance-research)<br/>
|
||||
|
||||
|
||||
+1
-60
@@ -93,7 +93,7 @@ public class DeviceController {
|
||||
}
|
||||
|
||||
@PostMapping("/bind/{agentId}")
|
||||
@Operation(summary = "转发POST请求到MQTT网关")
|
||||
@Operation(summary = "设备在线接口")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<String> forwardToMqttGateway(@PathVariable String agentId, @RequestBody String requestBody) {
|
||||
try {
|
||||
@@ -208,63 +208,4 @@ public class DeviceController {
|
||||
deviceService.manualAddDevice(user.getId(), dto);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@PostMapping("/commands/{deviceId}")
|
||||
@Operation(summary = "发送设备指令")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<String> sendDeviceCommand(@PathVariable String deviceId, @RequestBody String command) {
|
||||
try {
|
||||
// 从系统参数中获取MQTT网关地址
|
||||
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
|
||||
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
|
||||
return new Result<String>().error("MQTT网关地址未配置");
|
||||
}
|
||||
|
||||
// 构建完整的URL
|
||||
// 获取设备信息以构建mqttClientId
|
||||
DeviceEntity deviceById = deviceService.selectById(deviceId);
|
||||
|
||||
if (!deviceById.getUserId().equals(SecurityUser.getUser().getId())) {
|
||||
return new Result<String>().error("设备不存在");
|
||||
}
|
||||
String macAddress = deviceById != null ? deviceById.getMacAddress() : "unknown";
|
||||
String groupId = deviceById != null ? deviceById.getBoard() : null;
|
||||
if (groupId == null) {
|
||||
groupId = "GID_default";
|
||||
}
|
||||
groupId = groupId.replace(":", "_");
|
||||
macAddress = macAddress.replace(":", "_");
|
||||
|
||||
// 拼接为groupId@@@macAddress@@@deviceId格式
|
||||
String mqttClientId = groupId + "@@@" + macAddress + "@@@" + macAddress;
|
||||
|
||||
String url = "http://" + mqttGatewayUrl + "/api/commands/" + mqttClientId;
|
||||
|
||||
// 设置请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Content-Type", "application/json");
|
||||
|
||||
// 生成Bearer令牌
|
||||
String dateStr = java.time.LocalDate.now()
|
||||
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", false);
|
||||
if (StringUtils.isBlank(signatureKey)) {
|
||||
return new Result<String>().error("MQTT签名密钥未配置");
|
||||
}
|
||||
String tokenContent = dateStr + signatureKey;
|
||||
String token = org.apache.commons.codec.digest.DigestUtils.sha256Hex(tokenContent);
|
||||
headers.set("Authorization", "Bearer " + token);
|
||||
|
||||
// 构建请求体
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>(command, headers);
|
||||
|
||||
// 发送POST请求
|
||||
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
|
||||
|
||||
// 返回响应
|
||||
return new Result<String>().ok(response.getBody());
|
||||
} catch (Exception e) {
|
||||
return new Result<String>().error("发送指令失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,21 +102,4 @@ export default {
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
// 发送设备指令
|
||||
sendDeviceCommand(deviceId, mcpData, callback) {
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/device/commands/${deviceId}`)
|
||||
.method('POST')
|
||||
.data(mcpData)
|
||||
.success((res) => {
|
||||
RequestService.clearRequestTime();
|
||||
callback(res);
|
||||
})
|
||||
.networkFail((err) => {
|
||||
console.error('发送设备指令失败:', err);
|
||||
RequestService.reAjaxFun(() => {
|
||||
this.sendDeviceCommand(deviceId, mcpData, callback);
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -64,9 +64,6 @@
|
||||
<el-button size="mini" type="text" @click="handleUnbind(scope.row.device_id)">
|
||||
{{ $t('device.unbind') }}
|
||||
</el-button>
|
||||
<el-button v-if="scope.row.deviceStatus === 'online'" size="mini" type="text" @click="handleMcpToolCall(scope.row.device_id)">
|
||||
{{ $t('device.toolCall') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -82,9 +79,9 @@
|
||||
<el-button type="success" size="mini" class="add-device-btn" @click="handleManualAddDevice">
|
||||
{{ $t('device.manualAdd') }}
|
||||
</el-button>
|
||||
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteSelected">{{
|
||||
$t('device.unbind')
|
||||
}}</el-button>
|
||||
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteSelected">
|
||||
{{ $t('device.unbind') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="custom-pagination">
|
||||
<el-select v-model="pageSize" @change="handlePageSizeChange" class="page-size-select">
|
||||
@@ -92,20 +89,22 @@
|
||||
:label="$t('dictManagement.itemsPerPage').replace('{items}', item)" :value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">{{
|
||||
$t('dictManagement.firstPage')
|
||||
}}</button>
|
||||
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">{{
|
||||
$t('dictManagement.prevPage')
|
||||
}}</button>
|
||||
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">
|
||||
{{ $t('dictManagement.firstPage') }}
|
||||
</button>
|
||||
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">
|
||||
{{ $t('dictManagement.prevPage') }}
|
||||
</button>
|
||||
<button v-for="page in visiblePages" :key="page" class="pagination-btn"
|
||||
:class="{ active: page === currentPage }" @click="goToPage(page)">
|
||||
{{ page }}
|
||||
</button>
|
||||
<button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">{{
|
||||
$t('dictManagement.nextPage') }}</button>
|
||||
<span class="total-text">{{ $t('dictManagement.totalRecords').replace('{total}', deviceList.length)
|
||||
}}</span>
|
||||
<button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">
|
||||
{{ $t('dictManagement.nextPage') }}
|
||||
</button>
|
||||
<span class="total-text">
|
||||
{{ $t('dictManagement.totalRecords').replace('{total}', deviceList.length) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -117,7 +116,6 @@
|
||||
@refresh="fetchBindDevices(currentAgentId)" />
|
||||
<ManualAddDeviceDialog :visible.sync="manualAddDeviceDialogVisible" :agent-id="currentAgentId"
|
||||
@refresh="fetchBindDevices(currentAgentId)" />
|
||||
<McpToolCallDialog :visible.sync="mcpToolCallDialogVisible" :device-id="selectedDeviceId" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -127,20 +125,17 @@ import Api from '@/apis/api';
|
||||
import AddDeviceDialog from "@/components/AddDeviceDialog.vue";
|
||||
import HeaderBar from "@/components/HeaderBar.vue";
|
||||
import ManualAddDeviceDialog from "@/components/ManualAddDeviceDialog.vue";
|
||||
import McpToolCallDialog from "@/components/McpToolCallDialog.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HeaderBar,
|
||||
AddDeviceDialog,
|
||||
ManualAddDeviceDialog,
|
||||
McpToolCallDialog
|
||||
ManualAddDeviceDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addDeviceDialogVisible: false,
|
||||
manualAddDeviceDialogVisible: false,
|
||||
mcpToolCallDialogVisible: false,
|
||||
selectedDeviceId: '',
|
||||
searchKeyword: "",
|
||||
activeSearchKeyword: "",
|
||||
@@ -280,11 +275,6 @@ export default {
|
||||
handleManualAddDevice() {
|
||||
this.manualAddDeviceDialogVisible = true;
|
||||
},
|
||||
|
||||
handleMcpToolCall(deviceId) {
|
||||
this.selectedDeviceId = deviceId;
|
||||
this.mcpToolCallDialogVisible = true;
|
||||
},
|
||||
submitRemark(row) {
|
||||
if (row._submitting) return;
|
||||
|
||||
@@ -383,7 +373,7 @@ export default {
|
||||
.sort((a, b) => a.rawBindTime - b.rawBindTime);
|
||||
this.activeSearchKeyword = "";
|
||||
this.searchKeyword = "";
|
||||
|
||||
|
||||
// 获取设备列表后,立即获取设备状态
|
||||
this.fetchDeviceStatus(agentId);
|
||||
} else {
|
||||
@@ -391,7 +381,7 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// 获取设备状态
|
||||
fetchDeviceStatus(agentId) {
|
||||
Api.device.getDeviceStatus(agentId, ({ data }) => {
|
||||
@@ -399,7 +389,7 @@ export default {
|
||||
try {
|
||||
// 解析后端返回的设备状态JSON
|
||||
const statusData = JSON.parse(data.data);
|
||||
|
||||
|
||||
// 直接使用解析后的数据作为设备状态映射(不需要devices字段包装)
|
||||
if (statusData && typeof statusData === 'object') {
|
||||
// 更新设备状态
|
||||
@@ -411,7 +401,7 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// 根据API响应更新设备状态
|
||||
updateDeviceStatusFromResponse(deviceStatusMap) {
|
||||
this.deviceList.forEach(device => {
|
||||
@@ -419,11 +409,11 @@ export default {
|
||||
const macAddress = device.macAddress ? device.macAddress.replace(/:/g, '_') : 'unknown';
|
||||
const groupId = device.model ? device.model.replace(/:/g, '_') : 'GID_default';
|
||||
const mqttClientId = `${groupId}@@@${macAddress}@@@${macAddress}`;
|
||||
|
||||
|
||||
// 从状态映射中获取设备状态
|
||||
if (deviceStatusMap[mqttClientId]) {
|
||||
const statusInfo = deviceStatusMap[mqttClientId];
|
||||
|
||||
|
||||
let isOnline = false;
|
||||
if (statusInfo.isAlive === true) {
|
||||
isOnline = true;
|
||||
@@ -434,7 +424,7 @@ export default {
|
||||
} else {
|
||||
isOnline = false;
|
||||
}
|
||||
|
||||
|
||||
device.deviceStatus = isOnline ? 'online' : 'offline';
|
||||
} else {
|
||||
// 如果没有找到对应的状态信息,默认为离线
|
||||
|
||||
@@ -179,6 +179,9 @@ prompt: |
|
||||
- 长篇大论,叽叽歪歪
|
||||
- 长时间严肃对话
|
||||
|
||||
# 默认系统提示词模板文件
|
||||
prompt_template: agent-base-prompt.txt
|
||||
|
||||
# 结束语prompt
|
||||
end_prompt:
|
||||
enable: true # 是否开启结束语
|
||||
|
||||
@@ -66,7 +66,7 @@ class PromptManager:
|
||||
def _load_base_template(self):
|
||||
"""加载基础提示词模板"""
|
||||
try:
|
||||
template_path = "agent-base-prompt.txt"
|
||||
template_path = self.config.get("prompt_template", "agent-base-prompt.txt")
|
||||
cache_key = f"prompt_template:{template_path}"
|
||||
|
||||
# 先从缓存获取
|
||||
@@ -88,7 +88,7 @@ class PromptManager:
|
||||
self.base_prompt_template = template_content
|
||||
self.logger.bind(tag=TAG).debug("成功加载基础提示词模板并缓存")
|
||||
else:
|
||||
self.logger.bind(tag=TAG).warning("未找到agent-base-prompt.txt文件")
|
||||
self.logger.bind(tag=TAG).warning(f"未找到{template_path}文件")
|
||||
except Exception as e:
|
||||
self.logger.bind(tag=TAG).error(f"加载提示词模板失败: {e}")
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class LLMPerformanceTester:
|
||||
"""加载系统提示词"""
|
||||
try:
|
||||
prompt_file = os.path.join(
|
||||
os.path.dirname(os.path.dirname(__file__)), "agent-base-prompt.txt"
|
||||
os.path.dirname(os.path.dirname(__file__)), self.config.get("prompt_template", "agent-base-prompt.txt")
|
||||
)
|
||||
with open(prompt_file, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
Reference in New Issue
Block a user