From c3da3e937e564c78116721d69c2a2e34fcc13da0 Mon Sep 17 00:00:00 2001
From: lww155 <756794909@qq.com>
Date: Mon, 9 Feb 2026 16:50:23 +0800
Subject: [PATCH 1/3] =?UTF-8?q?add:=20=E5=89=8D=E5=90=8E=E6=91=84=E5=83=8F?=
=?UTF-8?q?=E5=A4=B4=E5=88=87=E6=8D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
main/xiaozhi-server/test/css/test_page.css | 27 +++++++++++++
main/xiaozhi-server/test/js/app.js | 42 +++++++++++++++++++-
main/xiaozhi-server/test/js/ui/controller.js | 10 +++++
main/xiaozhi-server/test/test_page.html | 9 +++++
4 files changed, 87 insertions(+), 1 deletion(-)
diff --git a/main/xiaozhi-server/test/css/test_page.css b/main/xiaozhi-server/test/css/test_page.css
index 1bfc3bd7..9935395a 100644
--- a/main/xiaozhi-server/test/css/test_page.css
+++ b/main/xiaozhi-server/test/css/test_page.css
@@ -1400,6 +1400,20 @@ body {
border-color: rgba(88, 101, 242, 0.5);
}
+@keyframes flipWithPosition {
+ 0% {
+ transform: var(--original-transform) rotateY(0deg);
+ }
+ 100% {
+ transform: var(--original-transform) rotateY(180deg);
+ }
+}
+
+
+.camera-container.flip {
+ animation: flipWithPosition 0.5s ease-in-out 1 forwards;
+}
+
#cameraVideo {
width: 100%;
height: 100%;
@@ -1407,4 +1421,17 @@ body {
background: #1a1a1a;
pointer-events: none;
transform: scaleX(-1);
+}
+
+.camera-switch {
+ display: none;
+ position: fixed;
+ bottom: 23px;
+ right: 20px;
+ z-index: 999;
+ color: #fff;
+}
+
+.camera-switch.active {
+ display: block;
}
\ No newline at end of file
diff --git a/main/xiaozhi-server/test/js/app.js b/main/xiaozhi-server/test/js/app.js
index bca7e3a7..91f60bf6 100644
--- a/main/xiaozhi-server/test/js/app.js
+++ b/main/xiaozhi-server/test/js/app.js
@@ -25,6 +25,7 @@ class App {
this.audioPlayer = null;
this.live2dManager = null;
this.cameraStream = null;
+ this.currentFacingMode = 'user';
}
// 初始化应用
@@ -127,6 +128,8 @@ class App {
async initCamera() {
const cameraContainer = document.getElementById('cameraContainer');
const cameraVideo = document.getElementById('cameraVideo');
+ const cameraSwitch = document.getElementById('cameraSwitch');
+ const dialBtn = document.getElementById('dialBtn');
if (!cameraContainer || !cameraVideo) {
log('摄像头元素未找到,跳过初始化', 'warning');
@@ -188,11 +191,24 @@ class App {
}
log('正在请求摄像头权限...', 'info');
this.cameraStream = await navigator.mediaDevices.getUserMedia({
- video: { width: 320, height: 240, facingMode: 'user' },
+ video: { width: 320, height: 240, facingMode: this.currentFacingMode },
audio: false
});
cameraVideo.srcObject = this.cameraStream;
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ const videoDevices = devices.filter(device => device.kind === 'videoinput');
+ if (videoDevices.length > 1) {
+ if (cameraSwitch) cameraSwitch.classList.add('active');
+ }
cameraContainer.classList.add('active');
+
+ // 切换时挂断情况
+ const hasActive = dialBtn.classList.contains('dial-active');
+ if (!hasActive) {
+ cameraContainer.classList.remove('active');
+ cameraSwitch.classList.remove('active');
+ window.stopCamera();
+ }
log('摄像头已启动', 'success');
return true;
} catch (error) {
@@ -217,6 +233,30 @@ class App {
}
};
+ window.switchCamera = async() => {
+ if (window.switchCameraTimer) return;
+ if (this.cameraStream) {
+ const currentTransform = window.getComputedStyle(cameraContainer).transform;
+ const originalTransform = currentTransform === 'none' ? 'translate(0px, 0px)' : currentTransform;
+ cameraContainer.style.setProperty('--original-transform', originalTransform);
+ cameraContainer.classList.add('flip');
+ this.currentFacingMode = this.currentFacingMode === 'user' ? 'environment' : 'user';
+ window.stopCamera();
+ window.startCamera();
+
+ window.switchCameraTimer = setTimeout(() => {
+ if (this.currentFacingMode === 'user') {
+ cameraVideo.style.transform = 'scaleX(-1)';
+ } else {
+ cameraVideo.style.transform = 'scaleX(1)';
+ }
+ window.switchCameraTimer = null;
+ cameraContainer.classList.remove('flip');
+ cameraContainer.style.removeProperty('--original-transform');
+ }, 500);
+ }
+ };
+
window.takePhoto = (question = '描述一下看到的物品') => {
return new Promise(async (resolve) => {
const canvas = document.createElement('canvas');
diff --git a/main/xiaozhi-server/test/js/ui/controller.js b/main/xiaozhi-server/test/js/ui/controller.js
index bc23f298..947783a3 100644
--- a/main/xiaozhi-server/test/js/ui/controller.js
+++ b/main/xiaozhi-server/test/js/ui/controller.js
@@ -98,6 +98,14 @@ class UIController {
});
}
+ // Camera switch button
+ const cameraSwitch = document.getElementById('cameraSwitch');
+ if (cameraSwitch) {
+ cameraSwitch.addEventListener('click', () => {
+ window.switchCamera();
+ })
+ }
+
// Dial button
const dialBtn = document.getElementById('dialBtn');
if (dialBtn) {
@@ -115,6 +123,7 @@ class UIController {
if (isConnected) {
wsHandler.disconnect();
this.updateDialButton(false);
+ if (cameraSwitch) cameraSwitch.classList.remove('active');
this.addChatMessage('Disconnected, see you next time~😊', false);
} else {
// Check if OTA URL is filled
@@ -153,6 +162,7 @@ class UIController {
if (isActive) {
// 关闭摄像头
if (typeof window.stopCamera === 'function') {
+ if (cameraSwitch) cameraSwitch.classList.remove('active');
window.stopCamera();
}
cameraContainer.classList.remove('active');
diff --git a/main/xiaozhi-server/test/test_page.html b/main/xiaozhi-server/test/test_page.html
index 22d4b0f6..7f85a127 100644
--- a/main/xiaozhi-server/test/test_page.html
+++ b/main/xiaozhi-server/test/test_page.html
@@ -44,6 +44,15 @@
+
+
+
+
From b22f430e982a4324acf11e7ad53ef462058124f3 Mon Sep 17 00:00:00 2001
From: rainv123 <2148537152@qq.com>
Date: Mon, 9 Feb 2026 17:22:28 +0800
Subject: [PATCH 2/3] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E6=91=84=E5=83=8F?=
=?UTF-8?q?=E5=A4=B4=E6=98=BE=E7=A4=BA=E5=8C=BA=E5=9F=9F=E4=B8=BA=E7=AB=96?=
=?UTF-8?q?=E5=B1=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
main/xiaozhi-server/test/css/test_page.css | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/main/xiaozhi-server/test/css/test_page.css b/main/xiaozhi-server/test/css/test_page.css
index 9935395a..17d00b00 100644
--- a/main/xiaozhi-server/test/css/test_page.css
+++ b/main/xiaozhi-server/test/css/test_page.css
@@ -1377,8 +1377,8 @@ body {
position: fixed;
top: 20px;
left: 20px;
- width: 240px;
- height: 180px;
+ width: 180px;
+ height: 240px;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
border-radius: 8px;
@@ -1402,10 +1402,11 @@ body {
@keyframes flipWithPosition {
0% {
- transform: var(--original-transform) rotateY(0deg);
+ transform: var(--original-transform) rotateY(0deg);
}
+
100% {
- transform: var(--original-transform) rotateY(180deg);
+ transform: var(--original-transform) rotateY(180deg);
}
}
@@ -1425,10 +1426,10 @@ body {
.camera-switch {
display: none;
- position: fixed;
- bottom: 23px;
- right: 20px;
- z-index: 999;
+ position: fixed;
+ bottom: 23px;
+ right: 20px;
+ z-index: 999;
color: #fff;
}
From 85fab6458882a8e9372b9f497d7e8b20c694292f Mon Sep 17 00:00:00 2001
From: lww155 <756794909@qq.com>
Date: Tue, 10 Feb 2026 09:54:39 +0800
Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96h5=E6=A0=B7?=
=?UTF-8?q?=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
main/xiaozhi-server/test/css/test_page.css | 301 +++++++++++----------
1 file changed, 157 insertions(+), 144 deletions(-)
diff --git a/main/xiaozhi-server/test/css/test_page.css b/main/xiaozhi-server/test/css/test_page.css
index 17d00b00..fb51457f 100644
--- a/main/xiaozhi-server/test/css/test_page.css
+++ b/main/xiaozhi-server/test/css/test_page.css
@@ -1201,150 +1201,6 @@ body {
z-index: 999;
}
-/* ==================== 响应式设计 ==================== */
-@media (max-width: 768px) {
- .container {
- padding: 10px;
- }
-
- .live2d-section {
- height: 60vh;
- margin-bottom: 10px;
- }
-
- .live2d-canvas {
- max-width: 300px;
- max-height: 450px;
- }
-
- .control-bar {
- bottom: 10px;
- padding: 8px 16px;
- gap: 12px;
- }
-
- .control-btn {
- width: 50px;
- height: 50px;
- }
-
- .btn-icon {
- width: 20px;
- height: 20px;
- }
-
- .btn-text {
- font-size: 10px;
- }
-
- .chat-container {
- width: 230px;
- right: 10px;
- bottom: 105px;
- }
-
- .message-bubble {
- max-width: 200px;
- font-size: 12px;
- }
-
- .modal-content {
- width: 95%;
- margin: 10px;
- }
-
- .config-row {
- flex-direction: column;
- gap: 12px;
- }
-
- .audio-visualizer {
- width: 150px;
- height: 50px;
- left: 10px;
- bottom: 80px;
- }
-}
-
-@media (max-width: 480px) {
- .live2d-section {
- height: 50vh;
- }
-
- .live2d-canvas {
- max-width: 250px;
- max-height: 350px;
- }
-
- .control-bar {
- gap: 8px;
- padding: 6px 12px;
- }
-
- .control-btn {
- width: 45px;
- height: 45px;
- }
-
- .btn-text {
- display: none;
- }
-
- .chat-stream {
- width: 200px;
- max-height: 150px;
- }
-
- .audio-visualizer {
- width: 120px;
- height: 40px;
- left: 5px;
- bottom: 70px;
- }
-}
-
-/* 竖屏优化 */
-@media (max-height: 700px) {
- .live2d-section {
- height: 55vh;
- }
-
- .live2d-canvas {
- max-height: 400px;
- }
-
- .chat-stream {
- bottom: 70px;
- max-height: 150px;
- }
-
- .audio-visualizer {
- bottom: 70px;
- }
-}
-
-/* 横屏优化 */
-@media (min-width: 1024px) and (min-height: 600px) {
- .live2d-section {
- height: 75vh;
- }
-
- .live2d-canvas {
- max-width: 500px;
- max-height: 650px;
- }
-
- .chat-stream {
- width: 350px;
- max-height: 250px;
- }
-
- .audio-visualizer {
- width: 250px;
- height: 80px;
- }
-}
-
/* 滚动条样式 */
.chat-stream::-webkit-scrollbar,
.modal-body::-webkit-scrollbar,
@@ -1435,4 +1291,161 @@ body {
.camera-switch.active {
display: block;
+}
+
+/* ==================== 响应式设计 ==================== */
+@media (max-width: 768px) {
+ .container {
+ padding: 10px;
+ }
+
+ .live2d-section {
+ height: 60vh;
+ margin-bottom: 10px;
+ }
+
+ .live2d-canvas {
+ max-width: 300px;
+ max-height: 450px;
+ }
+
+ .control-bar {
+ bottom: 10px;
+ padding: 8px 16px;
+ gap: 12px;
+ }
+
+ .control-btn {
+ width: 50px;
+ height: 50px;
+ }
+
+ .btn-icon {
+ width: 20px;
+ height: 20px;
+ }
+
+ .btn-text {
+ font-size: 10px;
+ }
+
+ .chat-container {
+ width: 230px;
+ right: 10px;
+ bottom: 105px;
+ }
+
+ .message-bubble {
+ max-width: 200px;
+ font-size: 12px;
+ }
+
+ .modal-content {
+ width: 95%;
+ margin: 10px;
+ }
+
+ .config-row {
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .audio-visualizer {
+ width: 150px;
+ height: 50px;
+ left: 10px;
+ bottom: 80px;
+ }
+
+ .chat-ipt input {
+ font-size: 12px;
+ }
+
+ .camera-container {
+ width: 130px;
+ height: 180px;
+ }
+
+ .status-indicator {
+ font-size: 12px;
+ }
+}
+
+@media (max-width: 480px) {
+ .live2d-section {
+ height: 50vh;
+ }
+
+ .live2d-canvas {
+ max-width: 250px;
+ max-height: 350px;
+ }
+
+ .control-bar {
+ gap: 8px;
+ padding: 6px 12px;
+ }
+
+ .control-btn {
+ width: 45px;
+ height: 45px;
+ }
+
+ .btn-text {
+ display: none;
+ }
+
+ .chat-stream {
+ width: 200px;
+ max-height: 150px;
+ }
+
+ .audio-visualizer {
+ width: 120px;
+ height: 40px;
+ left: 5px;
+ bottom: 70px;
+ }
+}
+
+/* 竖屏优化 */
+@media (max-height: 700px) {
+ .live2d-section {
+ height: 55vh;
+ }
+
+ .live2d-canvas {
+ max-height: 400px;
+ }
+
+ .chat-stream {
+ bottom: 70px;
+ max-height: 150px;
+ }
+
+ .audio-visualizer {
+ bottom: 70px;
+ }
+}
+
+/* 横屏优化 */
+@media (min-width: 1024px) and (min-height: 600px) {
+ .live2d-section {
+ height: 75vh;
+ }
+
+ .live2d-canvas {
+ max-width: 500px;
+ max-height: 650px;
+ }
+
+ .chat-stream {
+ width: 350px;
+ max-height: 250px;
+ }
+
+ .audio-visualizer {
+ width: 250px;
+ height: 80px;
+ }
}
\ No newline at end of file