Merge pull request #2051 from xinnan-tech/hot-fix

update:引导测试页面启动
This commit is contained in:
欣南科技
2025-08-16 14:37:04 +08:00
committed by GitHub
2 changed files with 1625 additions and 1550 deletions
@@ -9,7 +9,7 @@ logger = setup_logging()
class DefaultTTS(TTSProviderBase): class DefaultTTS(TTSProviderBase):
def __init__(self, config, delete_audio_file=True): def __init__(self, config, delete_audio_file=True):
super().__init__(config, delete_audio_file) super().__init__(config, delete_audio_file)
self.output_dir = config.get("output_dir", "output") self.output_dir = config.get("output_dir", "tmp")
if not os.path.exists(self.output_dir): if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir) os.makedirs(self.output_dir)
+98 -23
View File
@@ -6,9 +6,84 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小智服务器测试页面</title> <title>小智服务器测试页面</title>
<link rel="stylesheet" href="test_page.css"> <link rel="stylesheet" href="test_page.css">
<style>
#fileProtocolWarning {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
color: white;
padding: 20px;
box-sizing: border-box;
}
#fileProtocolWarning h2 {
color: #ff4d4d;
margin-bottom: 20px;
}
#fileProtocolWarning pre {
background-color: green;
font-size: 18px;
padding: 15px;
border-radius: 5px;
font-family: monospace;
overflow-x: auto;
margin: 15px 0;
}
#fileProtocolWarning button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 10px 2px;
cursor: pointer;
border-radius: 4px;
}
#fileProtocolWarning button:hover {
background-color: #45a049;
}
</style>
<script>
// 检测是否使用file://协议打开
if (window.location.protocol === 'file:') {
document.addEventListener('DOMContentLoaded', function () {
// 创建警告框
const warningDiv = document.createElement('div');
warningDiv.id = 'fileProtocolWarning';
warningDiv.innerHTML = `
<h2>⚠️ 警告:请使用HTTP服务器打开此页面</h2>
<p>您当前使用的是本地文件方式打开页面(file://协议),这可能导致页面功能异常。</p>
<p>您可以使用nginx映射启动测试页面,也可以请按照以下步骤使用python启动测试http服务:</p>
<ol>
<li>打开命令行终端</li>
<li>命令行进入到 xiaozhi-server/test 目录</li>
<li>执行以下命令启动HTTP服务器:</li>
</ol>
<pre>python -m http.server 8006</pre>
<p>然后在浏览器中访问:<strong>http://localhost:8006/test_page.html</strong></p>
`;
document.body.appendChild(warningDiv);
});
}
</script>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>小智服务器测试页面</h1> <h1>小智服务器测试页面</h1>
<div id="scriptStatus" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);"> <div id="scriptStatus" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);">
@@ -57,9 +132,9 @@
</h2> </h2>
<div class="connection-controls"> <div class="connection-controls">
<input type="text" id="otaUrl" value="http://127.0.0.1:8002/xiaozhi/ota/" <input type="text" id="otaUrl" value="http://127.0.0.1:8002/xiaozhi/ota/"
placeholder="OTA服务器地址,如:http://127.0.0.1:8002/xiaozhi/ota/"/> placeholder="OTA服务器地址,如:http://127.0.0.1:8002/xiaozhi/ota/" />
<input type="text" id="serverUrl" value="ws://127.0.0.1:8000/xiaozhi/v1/" <input type="text" id="serverUrl" value="ws://127.0.0.1:8000/xiaozhi/v1/"
placeholder="WebSocket服务器地址,如:ws://127.0.0.1:8000/xiaozhi/v1/"/> placeholder="WebSocket服务器地址,如:ws://127.0.0.1:8000/xiaozhi/v1/" />
<button id="connectButton">连接</button> <button id="connectButton">连接</button>
<button id="authTestButton">测试认证</button> <button id="authTestButton">测试认证</button>
</div> </div>
@@ -95,16 +170,16 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Opus解码库 --> <!-- Opus解码库 -->
<script src="libopus.js"></script> <script src="libopus.js"></script>
<script type="module"> <script type="module">
import {log} from './js/utils/logger.js'; import { log } from './js/utils/logger.js';
import {webSocketConnect} from './js/xiaoZhiConnect.js'; import { webSocketConnect } from './js/xiaoZhiConnect.js';
import {checkOpusLoaded, initOpusEncoder} from './js/opus.js'; import { checkOpusLoaded, initOpusEncoder } from './js/opus.js';
import {addMessage} from './js/document.js' import { addMessage } from './js/document.js'
import BlockingQueue from './js/utils/BlockingQueue.js' import BlockingQueue from './js/utils/BlockingQueue.js'
// 需要加载的脚本列表 - 移除Opus依赖 // 需要加载的脚本列表 - 移除Opus依赖
const scriptFiles = []; const scriptFiles = [];
@@ -282,7 +357,7 @@
// 获取正在播放已解码的PCM队列,单线程:在activeQueue一直更新的状态下不会出现安全问题 // 获取正在播放已解码的PCM队列,单线程:在activeQueue一直更新的状态下不会出现安全问题
getQueue: async function (minSamples) { getQueue: async function (minSamples) {
let TepArray = [] let TepArray = []
const num = minSamples - this.queue.length > 0 ? minSamples -this.queue.length : 1; const num = minSamples - this.queue.length > 0 ? minSamples - this.queue.length : 1;
// 原子交换 + 清空 // 原子交换 + 清空
[TepArray, this.activeQueue] = [await this.activeQueue.dequeue(num), new BlockingQueue()]; [TepArray, this.activeQueue] = [await this.activeQueue.dequeue(num), new BlockingQueue()];
this.queue.push(...TepArray) this.queue.push(...TepArray)
@@ -618,7 +693,7 @@
} }
// 创建完整的录音blob // 创建完整的录音blob
const blob = new Blob(audioChunks, {type: audioChunks[0].type}); const blob = new Blob(audioChunks, { type: audioChunks[0].type });
log(`已创建音频BlobMIME类型: ${audioChunks[0].type},大小: ${(blob.size / 1024).toFixed(2)} KB`, 'info'); log(`已创建音频BlobMIME类型: ${audioChunks[0].type},大小: ${(blob.size / 1024).toFixed(2)} KB`, 'info');
// 保存原始块,以防清空后需要调试 // 保存原始块,以防清空后需要调试
@@ -869,7 +944,7 @@
"tools": [{ "tools": [{
"name": "self.get_device_status", "name": "self.get_device_status",
"description": "Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\nUse this tool for: \n1. Answering questions about current condition (e.g. what is the current volume of the audio speaker?)\n2. As the first step to control the device (e.g. turn up / down the volume of the audio speaker, etc.)", "description": "Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\nUse this tool for: \n1. Answering questions about current condition (e.g. what is the current volume of the audio speaker?)\n2. As the first step to control the device (e.g. turn up / down the volume of the audio speaker, etc.)",
"inputSchema": {"type": "object", "properties": {}} "inputSchema": { "type": "object", "properties": {} }
}, { }, {
"name": "self.audio_speaker.set_volume", "name": "self.audio_speaker.set_volume",
"description": "Set the volume of the audio speaker. If the current volume is unknown, you must call `self.get_device_status` tool first and then call this tool.", "description": "Set the volume of the audio speaker. If the current volume is unknown, you must call `self.get_device_status` tool first and then call this tool.",
@@ -903,7 +978,7 @@
"description": "Set the theme of the screen. The theme can be 'light' or 'dark'.", "description": "Set the theme of the screen. The theme can be 'light' or 'dark'.",
"inputSchema": { "inputSchema": {
"type": "object", "type": "object",
"properties": {"theme": {"type": "string"}}, "properties": { "theme": { "type": "string" } },
"required": ["theme"] "required": ["theme"]
} }
}] }]
@@ -920,7 +995,7 @@
"payload": { "payload": {
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": payload.id, "id": payload.id,
"result": {"content": [{"type": "text", "text": "true"}], "isError": false} "result": { "content": [{ "type": "text", "text": "true" }], "isError": false }
} }
}) })
websocket.send(replay_message); websocket.send(replay_message);
@@ -1352,7 +1427,7 @@
// 检查是否支持AudioWorklet // 检查是否支持AudioWorklet
if (audioContext.audioWorklet) { if (audioContext.audioWorklet) {
// 注册音频处理器 // 注册音频处理器
const blob = new Blob([audioProcessorCode], {type: 'application/javascript'}); const blob = new Blob([audioProcessorCode], { type: 'application/javascript' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
await audioContext.audioWorklet.addModule(url); await audioContext.audioWorklet.addModule(url);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
@@ -1369,7 +1444,7 @@
}; };
log('使用AudioWorklet处理音频', 'success'); log('使用AudioWorklet处理音频', 'success');
return {node: audioProcessor, type: 'worklet'}; return { node: audioProcessor, type: 'worklet' };
} else { } else {
// 使用旧版ScriptProcessorNode作为回退方案 // 使用旧版ScriptProcessorNode作为回退方案
log('AudioWorklet不可用,使用ScriptProcessorNode作为回退方案', 'warning'); log('AudioWorklet不可用,使用ScriptProcessorNode作为回退方案', 'warning');
@@ -1400,7 +1475,7 @@
scriptProcessor.connect(silent); scriptProcessor.connect(silent);
silent.connect(audioContext.destination); silent.connect(audioContext.destination);
return {node: scriptProcessor, type: 'processor'}; return { node: scriptProcessor, type: 'processor' };
} }
} catch (error) { } catch (error) {
log(`创建音频处理器失败: ${error.message},尝试回退方案`, 'error'); log(`创建音频处理器失败: ${error.message},尝试回退方案`, 'error');
@@ -1429,7 +1504,7 @@
silent.connect(audioContext.destination); silent.connect(audioContext.destination);
log('使用ScriptProcessorNode作为回退方案成功', 'warning'); log('使用ScriptProcessorNode作为回退方案成功', 'warning');
return {node: scriptProcessor, type: 'processor'}; return { node: scriptProcessor, type: 'processor' };
} catch (fallbackError) { } catch (fallbackError) {
log(`回退方案也失败: ${fallbackError.message}`, 'error'); log(`回退方案也失败: ${fallbackError.message}`, 'error');
return null; return null;
@@ -1574,7 +1649,7 @@
// 启动音频处理器的录音 - 只有AudioWorklet才需要发送消息 // 启动音频处理器的录音 - 只有AudioWorklet才需要发送消息
if (audioProcessorType === 'worklet' && audioProcessor.port) { if (audioProcessorType === 'worklet' && audioProcessor.port) {
audioProcessor.port.postMessage({command: 'start'}); audioProcessor.port.postMessage({ command: 'start' });
} }
// 发送监听开始消息 // 发送监听开始消息
@@ -1631,7 +1706,7 @@
if (audioProcessor) { if (audioProcessor) {
// 只有AudioWorklet才需要发送停止消息 // 只有AudioWorklet才需要发送停止消息
if (audioProcessorType === 'worklet' && audioProcessor.port) { if (audioProcessorType === 'worklet' && audioProcessor.port) {
audioProcessor.port.postMessage({command: 'stop'}); audioProcessor.port.postMessage({ command: 'stop' });
} }
audioProcessor.disconnect(); audioProcessor.disconnect();
@@ -1734,7 +1809,7 @@
} }
initApp(); initApp();
</script> </script>
</body> </body>
</html> </html>