mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-31 03:43:57 +08:00
update:优化测试web工具
This commit is contained in:
+3
@@ -19,6 +19,7 @@ import xiaozhi.common.page.TokenDTO;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.common.validator.AssertUtils;
|
||||
import xiaozhi.common.validator.ValidatorUtils;
|
||||
import xiaozhi.modules.security.dto.LoginDTO;
|
||||
import xiaozhi.modules.security.password.PasswordUtils;
|
||||
import xiaozhi.modules.security.service.CaptchaService;
|
||||
@@ -104,6 +105,8 @@ public class LoginController {
|
||||
@PutMapping("/change-password")
|
||||
@Operation(summary = "修改用户密码")
|
||||
public Result<?> changePassword(@RequestBody PasswordDTO passwordDTO) {
|
||||
// 判断非空
|
||||
ValidatorUtils.validateEntity(passwordDTO);
|
||||
Long userId = SecurityUser.getUserId();
|
||||
sysUserTokenService.changePassword(userId, passwordDTO);
|
||||
return new Result<>();
|
||||
|
||||
@@ -90,7 +90,6 @@ public class Oauth2Filter extends AuthenticatingFilter {
|
||||
String json = JsonUtils.toJsonString(r);
|
||||
httpResponse.getWriter().print(json);
|
||||
} catch (IOException e1) {
|
||||
logger.error("onLoginFailure:登录失败! msg:{}", e1.getMessage(), e1);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -11,36 +12,45 @@
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
margin-top: 0;
|
||||
color: #444;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 15px;
|
||||
border: none;
|
||||
@@ -50,36 +60,43 @@
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #3367d6;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #cccccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#serverUrl {
|
||||
flex-grow: 1;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#messageInput {
|
||||
flex-grow: 1;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#nfcCardId {
|
||||
flex-grow: 1;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.conversation {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
@@ -87,36 +104,45 @@
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.user {
|
||||
background-color: #e2f2ff;
|
||||
margin-left: auto;
|
||||
margin-right: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.server {
|
||||
background-color: #f0f0f0;
|
||||
margin-right: auto;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.audio-controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.audio-visualizer {
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
@@ -125,45 +151,72 @@
|
||||
border-radius: 5px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.record-button {
|
||||
background-color: #db4437;
|
||||
}
|
||||
|
||||
.record-button:hover {
|
||||
background-color: #c53929;
|
||||
}
|
||||
|
||||
.record-button.recording {
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { background-color: #db4437; }
|
||||
50% { background-color: #ff6659; }
|
||||
100% { background-color: #db4437; }
|
||||
0% {
|
||||
background-color: #db4437;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: #ff6659;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: #db4437;
|
||||
}
|
||||
}
|
||||
|
||||
#logContainer {
|
||||
margin-top: 20px;
|
||||
margin-top: 0;
|
||||
padding: 10px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 5px;
|
||||
font-family: monospace;
|
||||
height: 150px;
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
margin: 5px 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
.log-info { color: #333; }
|
||||
.log-error { color: #db4437; }
|
||||
.log-success { color: #0f9d58; }
|
||||
.info-box {
|
||||
background-color: #e8f4ff;
|
||||
border-left: 4px solid #4285f4;
|
||||
width: auto;
|
||||
display: none;
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
|
||||
.log-info {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.log-error {
|
||||
color: #db4437;
|
||||
}
|
||||
|
||||
.log-success {
|
||||
color: #0f9d58;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -60%);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.script-status {
|
||||
@@ -174,9 +227,17 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.script-loaded { background-color: #0f9d58; }
|
||||
.script-loading { background-color: #f4b400; }
|
||||
.script-error { background-color: #db4437; }
|
||||
.script-loaded {
|
||||
background-color: #0f9d58;
|
||||
}
|
||||
|
||||
.script-loading {
|
||||
background-color: #f4b400;
|
||||
}
|
||||
|
||||
.script-error {
|
||||
background-color: #db4437;
|
||||
}
|
||||
|
||||
.script-list {
|
||||
margin: 10px 0;
|
||||
@@ -187,14 +248,6 @@
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* 状态消息样式 */
|
||||
#scriptStatus {
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#scriptStatus.success {
|
||||
background-color: #e6f4ea;
|
||||
color: #0f9d58;
|
||||
@@ -212,57 +265,106 @@
|
||||
color: #f4b400;
|
||||
border-left: 4px solid #f4b400;
|
||||
}
|
||||
|
||||
/* 标签页样式 */
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: #4285f4;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: #4285f4;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #4285f4;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>小智服务器测试页面</h1>
|
||||
|
||||
<div id="scriptStatus" class="info-box"></div>
|
||||
<div id="scriptStatus" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);">
|
||||
正在加载Opus库...</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>WebSocket连接</h2>
|
||||
<h2>WebSocket连接 <span id="connectionStatus" class="status">未连接</span></h2>
|
||||
<div class="control-panel">
|
||||
<input type="text" id="serverUrl" value="ws://localhost:8000" placeholder="WebSocket服务器地址">
|
||||
<input type="text" id="serverUrl" value="ws://127.0.0.1:8000/xiaozhi/v1/" placeholder="WebSocket服务器地址">
|
||||
<button id="connectButton">连接</button>
|
||||
<button id="disconnectButton" disabled>断开</button>
|
||||
<button id="authTestButton">测试认证</button>
|
||||
</div>
|
||||
<div id="connectionStatus" class="status">未连接</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>文本消息</h2>
|
||||
<div class="message-input">
|
||||
<input type="text" id="messageInput" placeholder="输入消息..." disabled>
|
||||
<button id="sendTextButton" disabled>发送</button>
|
||||
<h2>消息发送</h2>
|
||||
<div class="tabs">
|
||||
<button class="tab active" data-tab="text">文本消息</button>
|
||||
<button class="tab" data-tab="voice">语音消息</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>NFC卡片模拟</h2>
|
||||
<div class="message-input">
|
||||
<input type="text" id="nfcCardId" placeholder="输入NFC卡片ID..." value="ABCD1234EFGH5678" disabled>
|
||||
<button id="sendNfcButton" disabled>模拟NFC卡片</button>
|
||||
<div class="tab-content active" id="textTab">
|
||||
<div class="message-input">
|
||||
<input type="text" id="messageInput" placeholder="输入消息..." disabled>
|
||||
<button id="sendTextButton" disabled>发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>语音消息</h2>
|
||||
<div class="audio-controls">
|
||||
<button id="recordButton" class="record-button" disabled>开始录音</button>
|
||||
<button id="stopButton" disabled>停止录音并发送</button>
|
||||
<div class="tab-content" id="voiceTab">
|
||||
<div class="audio-controls">
|
||||
<button id="recordButton" class="record-button" disabled>开始录音</button>
|
||||
</div>
|
||||
<canvas id="audioVisualizer" class="audio-visualizer"></canvas>
|
||||
</div>
|
||||
<canvas id="audioVisualizer" class="audio-visualizer"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>会话记录</h2>
|
||||
<div id="conversation" class="conversation"></div>
|
||||
</div>
|
||||
|
||||
<div id="logContainer">
|
||||
<div class="log-entry log-info">准备就绪,请连接服务器开始测试...</div>
|
||||
<div class="flex-container">
|
||||
<div id="conversation" class="conversation"></div>
|
||||
<div id="logContainer">
|
||||
<div class="log-entry log-info">准备就绪,请连接服务器开始测试...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -283,48 +385,42 @@
|
||||
|
||||
// 检查Opus库是否已加载
|
||||
function checkOpusLoaded() {
|
||||
// setTimeout(() => {
|
||||
try {
|
||||
// 检查Module是否存在(本地库导出的全局变量)
|
||||
if (typeof Module === 'undefined') {
|
||||
throw new Error('Opus库未加载,Module对象不存在');
|
||||
}
|
||||
|
||||
// 尝试先使用Module.instance(libopus.js最后一行导出方式)
|
||||
if (typeof Module.instance !== 'undefined' && typeof Module.instance._opus_decoder_get_size === 'function') {
|
||||
// 使用Module.instance对象替换全局Module对象
|
||||
window.ModuleInstance = Module.instance;
|
||||
log('Opus库加载成功(使用Module.instance)', 'success');
|
||||
updateScriptStatus('Opus库加载成功', 'success');
|
||||
|
||||
// 3秒后隐藏状态
|
||||
setTimeout(() => {
|
||||
const statusElement = document.getElementById('scriptStatus');
|
||||
if (statusElement) statusElement.style.display = 'none';
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果没有Module.instance,检查全局Module函数
|
||||
if (typeof Module._opus_decoder_get_size === 'function') {
|
||||
window.ModuleInstance = Module;
|
||||
log('Opus库加载成功(使用全局Module)', 'success');
|
||||
updateScriptStatus('Opus库加载成功', 'success');
|
||||
|
||||
// 3秒后隐藏状态
|
||||
setTimeout(() => {
|
||||
const statusElement = document.getElementById('scriptStatus');
|
||||
if (statusElement) statusElement.style.display = 'none';
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('Opus解码函数未找到,可能Module结构不正确');
|
||||
} catch (err) {
|
||||
log(`Opus库加载失败,请检查libopus.js文件是否存在且正确: ${err.message}`, 'error');
|
||||
updateScriptStatus('Opus库加载失败,请检查libopus.js文件是否存在且正确', 'error');
|
||||
try {
|
||||
// 检查Module是否存在(本地库导出的全局变量)
|
||||
if (typeof Module === 'undefined') {
|
||||
throw new Error('Opus库未加载,Module对象不存在');
|
||||
}
|
||||
// }, 1000); // 延迟1秒检查,确保脚本有时间加载
|
||||
|
||||
// 尝试先使用Module.instance(libopus.js最后一行导出方式)
|
||||
if (typeof Module.instance !== 'undefined' && typeof Module.instance._opus_decoder_get_size === 'function') {
|
||||
// 使用Module.instance对象替换全局Module对象
|
||||
window.ModuleInstance = Module.instance;
|
||||
log('Opus库加载成功(使用Module.instance)', 'success');
|
||||
updateScriptStatus('Opus库加载成功', 'success');
|
||||
|
||||
// 3秒后隐藏状态
|
||||
const statusElement = document.getElementById('scriptStatus');
|
||||
if (statusElement) statusElement.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果没有Module.instance,检查全局Module函数
|
||||
if (typeof Module._opus_decoder_get_size === 'function') {
|
||||
window.ModuleInstance = Module;
|
||||
log('Opus库加载成功(使用全局Module)', 'success');
|
||||
updateScriptStatus('Opus库加载成功', 'success');
|
||||
|
||||
// 3秒后隐藏状态
|
||||
const statusElement = document.getElementById('scriptStatus');
|
||||
if (statusElement) statusElement.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('Opus解码函数未找到,可能Module结构不正确');
|
||||
} catch (err) {
|
||||
log(`Opus库加载失败,请检查libopus.js文件是否存在且正确: ${err.message}`, 'error');
|
||||
updateScriptStatus('Opus库加载失败,请检查libopus.js文件是否存在且正确', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新脚本状态显示
|
||||
@@ -358,13 +454,10 @@
|
||||
|
||||
// DOM元素
|
||||
const connectButton = document.getElementById('connectButton');
|
||||
const disconnectButton = document.getElementById('disconnectButton');
|
||||
const serverUrlInput = document.getElementById('serverUrl');
|
||||
const connectionStatus = document.getElementById('connectionStatus');
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
const sendTextButton = document.getElementById('sendTextButton');
|
||||
const nfcCardIdInput = document.getElementById('nfcCardId');
|
||||
const sendNfcButton = document.getElementById('sendNfcButton');
|
||||
const recordButton = document.getElementById('recordButton');
|
||||
const stopButton = document.getElementById('stopButton');
|
||||
const conversationDiv = document.getElementById('conversation');
|
||||
@@ -433,7 +526,6 @@
|
||||
conversationDiv.scrollTop = conversationDiv.scrollHeight;
|
||||
}
|
||||
|
||||
|
||||
// 播放Opus音频数据
|
||||
function playOpusData(opusBuffers) {
|
||||
if (!opusBuffers || opusBuffers.length === 0) {
|
||||
@@ -482,7 +574,7 @@
|
||||
module: window.ModuleInstance, // 保存模块实例的引用
|
||||
|
||||
// 使用ModuleInstance提供的函数来解码
|
||||
decode_float: function(opusData) {
|
||||
decode_float: function (opusData) {
|
||||
try {
|
||||
const mod = this.module; // 简写引用
|
||||
|
||||
@@ -527,7 +619,7 @@
|
||||
// 从内存中复制PCM数据
|
||||
const pcmData = new Float32Array(samplesDecoded);
|
||||
for (let i = 0; i < samplesDecoded; i++) {
|
||||
pcmData[i] = mod.HEAPF32[pcmPtr/4 + i];
|
||||
pcmData[i] = mod.HEAPF32[pcmPtr / 4 + i];
|
||||
}
|
||||
|
||||
// 释放内存
|
||||
@@ -562,7 +654,7 @@
|
||||
const opusBuffer = opusBuffers[i];
|
||||
// 确保这是Uint8Array
|
||||
const opusArray = opusBuffer instanceof Uint8Array ?
|
||||
opusBuffer : new Uint8Array(opusBuffer);
|
||||
opusBuffer : new Uint8Array(opusBuffer);
|
||||
|
||||
// 解码Opus帧为PCM数据
|
||||
const decoded = opusDecoder.decode_float(opusArray);
|
||||
@@ -571,7 +663,7 @@
|
||||
successCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
log(`Opus帧 #${i+1} 解码错误: ${error.message}`, 'error');
|
||||
log(`Opus帧 #${i + 1} 解码错误: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,7 +708,7 @@
|
||||
};
|
||||
|
||||
source.start();
|
||||
log(`开始播放PCM音频,时长: ${(combinedPCM.length/16000).toFixed(2)}秒`, 'debug');
|
||||
log(`开始播放PCM音频,时长: ${(combinedPCM.length / 16000).toFixed(2)}秒`, 'debug');
|
||||
|
||||
} catch (error) {
|
||||
log(`播放Opus音频错误: ${error.message},尝试使用简单播放`, 'error');
|
||||
@@ -911,12 +1003,10 @@
|
||||
// 连接成功后发送hello消息
|
||||
await sendHelloMessage();
|
||||
|
||||
connectButton.disabled = true;
|
||||
disconnectButton.disabled = false;
|
||||
connectButton.textContent = '断开';
|
||||
connectButton.onclick = disconnectFromServer;
|
||||
messageInput.disabled = false;
|
||||
sendTextButton.disabled = false;
|
||||
nfcCardIdInput.disabled = false;
|
||||
sendNfcButton.disabled = false;
|
||||
|
||||
const audioInitialized = await initAudio();
|
||||
if (audioInitialized) {
|
||||
@@ -929,14 +1019,12 @@
|
||||
connectionStatus.textContent = '已断开';
|
||||
connectionStatus.style.color = 'red';
|
||||
|
||||
connectButton.disabled = false;
|
||||
disconnectButton.disabled = true;
|
||||
connectButton.textContent = '连接';
|
||||
connectButton.onclick = connectToServer;
|
||||
messageInput.disabled = true;
|
||||
sendTextButton.disabled = true;
|
||||
recordButton.disabled = true;
|
||||
stopButton.disabled = true;
|
||||
nfcCardIdInput.disabled = true;
|
||||
sendNfcButton.disabled = true;
|
||||
};
|
||||
|
||||
websocket.onerror = (error) => {
|
||||
@@ -945,7 +1033,7 @@
|
||||
connectionStatus.style.color = 'red';
|
||||
};
|
||||
|
||||
websocket.onmessage = function(event) {
|
||||
websocket.onmessage = function (event) {
|
||||
try {
|
||||
// 检查是否为文本消息
|
||||
if (typeof event.data === 'string') {
|
||||
@@ -989,14 +1077,6 @@
|
||||
if (message.text && message.text !== '😊') {
|
||||
addMessage(message.text);
|
||||
}
|
||||
} else if (message.type === 'nfc_card_response') {
|
||||
// NFC卡片响应
|
||||
log(`NFC卡片响应: ${message.status}`, message.status === 'success' ? 'success' : 'error');
|
||||
if (message.content) {
|
||||
addMessage(`[NFC卡片内容] ${message.content}`);
|
||||
} else if (message.message) {
|
||||
addMessage(`[NFC卡片错误] ${message.message}`);
|
||||
}
|
||||
} else {
|
||||
// 未知消息类型
|
||||
log(`未知消息类型: ${message.type}`, 'info');
|
||||
@@ -1104,41 +1184,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 发送NFC卡片消息
|
||||
function sendNfcCardMessage() {
|
||||
const cardId = nfcCardIdInput.value.trim();
|
||||
if (cardId === '' || !websocket || websocket.readyState !== WebSocket.OPEN) return;
|
||||
|
||||
try {
|
||||
// 构造NFC卡片消息
|
||||
const nfcMessage = {
|
||||
type: 'nfc_card_detected',
|
||||
card_id: cardId
|
||||
};
|
||||
|
||||
websocket.send(JSON.stringify(nfcMessage));
|
||||
log(`发送NFC卡片消息, 卡片ID: ${cardId}`, 'info');
|
||||
addMessage(`[NFC卡片] ${cardId}`, true);
|
||||
} catch (error) {
|
||||
log(`发送NFC卡片消息错误: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化事件监听器
|
||||
function initEventListeners() {
|
||||
connectButton.addEventListener('click', connectToServer);
|
||||
disconnectButton.addEventListener('click', disconnectFromServer);
|
||||
document.getElementById('authTestButton').addEventListener('click', testAuthentication);
|
||||
|
||||
// 标签页切换
|
||||
const tabs = document.querySelectorAll('.tab');
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
// 移除所有标签页的active类
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||||
|
||||
// 添加当前标签页的active类
|
||||
tab.classList.add('active');
|
||||
document.getElementById(`${tab.dataset.tab}Tab`).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
sendTextButton.addEventListener('click', sendTextMessage);
|
||||
messageInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') sendTextMessage();
|
||||
});
|
||||
|
||||
sendNfcButton.addEventListener('click', sendNfcCardMessage);
|
||||
|
||||
recordButton.addEventListener('click', startRecording);
|
||||
stopButton.addEventListener('click', stopRecording);
|
||||
recordButton.addEventListener('click', () => {
|
||||
if (isRecording) {
|
||||
stopRecording();
|
||||
} else {
|
||||
startRecording();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('resize', initVisualizer);
|
||||
}
|
||||
@@ -1253,8 +1329,6 @@
|
||||
return window.btoa(binary);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 使用libopus创建一个Opus编码器
|
||||
let opusEncoder = null;
|
||||
function initOpusEncoder() {
|
||||
@@ -1283,7 +1357,7 @@
|
||||
module: mod,
|
||||
|
||||
// 初始化编码器
|
||||
init: function() {
|
||||
init: function () {
|
||||
try {
|
||||
// 获取编码器大小
|
||||
const encoderSize = mod._opus_encoder_get_size(this.channels);
|
||||
@@ -1329,7 +1403,7 @@
|
||||
},
|
||||
|
||||
// 编码PCM数据为Opus
|
||||
encode: function(pcmData) {
|
||||
encode: function (pcmData) {
|
||||
if (!this.encoderPtr) {
|
||||
if (!this.init()) {
|
||||
return null;
|
||||
@@ -1381,7 +1455,7 @@
|
||||
},
|
||||
|
||||
// 销毁编码器
|
||||
destroy: function() {
|
||||
destroy: function () {
|
||||
if (this.encoderPtr) {
|
||||
this.module._free(this.encoderPtr);
|
||||
this.encoderPtr = null;
|
||||
@@ -1733,15 +1807,14 @@
|
||||
let recordingSeconds = 0;
|
||||
const recordingTimer = setInterval(() => {
|
||||
recordingSeconds += 0.1;
|
||||
recordButton.textContent = `录音中... ${recordingSeconds.toFixed(1)}秒`;
|
||||
recordButton.textContent = `停止录音 ${recordingSeconds.toFixed(1)}秒`;
|
||||
}, 100);
|
||||
|
||||
// 保存计时器,以便在停止时清除
|
||||
window.recordingTimer = recordingTimer;
|
||||
|
||||
recordButton.classList.add('recording');
|
||||
recordButton.disabled = true;
|
||||
stopButton.disabled = false;
|
||||
recordButton.disabled = false;
|
||||
|
||||
log('开始PCM直接录音', 'success');
|
||||
return true;
|
||||
@@ -1813,7 +1886,6 @@
|
||||
recordButton.textContent = '开始录音';
|
||||
recordButton.classList.remove('recording');
|
||||
recordButton.disabled = false;
|
||||
stopButton.disabled = true;
|
||||
|
||||
log('停止PCM直接录音', 'success');
|
||||
return true;
|
||||
@@ -1858,4 +1930,5 @@
|
||||
initApp();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user