mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
update: 新增聊天输入框
This commit is contained in:
@@ -193,18 +193,55 @@ body {
|
||||
}
|
||||
|
||||
/* ==================== 聊天消息流(弹幕样式) ==================== */
|
||||
.chat-stream {
|
||||
.chat-container {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
bottom: 150px;
|
||||
right: 20px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.chat-stream {
|
||||
width: 300px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.chat-ipt {
|
||||
width: 100%;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.chat-ipt input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
background: rgba(35, 39, 42, 0.85);
|
||||
backdrop-filter: blur(10px);
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.chat-ipt input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.chat-ipt input:focus {
|
||||
outline: none;
|
||||
border-color: rgba(88, 101, 242, 0.6);
|
||||
box-shadow: 0 4px 16px rgba(88, 101, 242, 0.3);
|
||||
background: rgba(45, 49, 52, 0.9);
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
@@ -955,10 +992,10 @@ body {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.chat-stream {
|
||||
width: 250px;
|
||||
.chat-container {
|
||||
width: 230px;
|
||||
right: 10px;
|
||||
bottom: 80px;
|
||||
bottom: 105px;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
|
||||
@@ -40,15 +40,10 @@ class App {
|
||||
// 关闭加载loading
|
||||
this.setModelLoadingStatus(false);
|
||||
|
||||
log('应用初始化完成', 'success');
|
||||
}
|
||||
// 绑定页面卸载事件
|
||||
this.bindUnload();
|
||||
|
||||
// 设置model加载状态
|
||||
setModelLoadingStatus(isLoading) {
|
||||
const modelLoading = document.getElementById('modelLoading');
|
||||
if (modelLoading) {
|
||||
modelLoading.style.display = isLoading ? 'flex' : 'none';
|
||||
}
|
||||
log('应用初始化完成', 'success');
|
||||
}
|
||||
|
||||
// 初始化Live2D
|
||||
@@ -81,6 +76,24 @@ class App {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置model加载状态
|
||||
setModelLoadingStatus(isLoading) {
|
||||
const modelLoading = document.getElementById('modelLoading');
|
||||
if (modelLoading) {
|
||||
modelLoading.style.display = isLoading ? 'flex' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定页面卸载事件
|
||||
bindUnload() {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
// 销毁定时器
|
||||
if (this.uiController && this.uiController.wsTimer) {
|
||||
clearInterval(this.uiController.wsTimer);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 创建并启动应用
|
||||
@@ -89,17 +102,10 @@ const app = new App();
|
||||
// 将应用实例暴露到全局,供其他模块访问
|
||||
window.chatApp = app;
|
||||
|
||||
// DOM加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => app.init());
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 初始化应用
|
||||
app.init();
|
||||
window.addEventListener('beforeunload', () => {
|
||||
// 销毁定时器
|
||||
if (app.uiController && app.uiController.wsTimer) {
|
||||
clearInterval(app.uiController.wsTimer);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -58,12 +58,16 @@ export function saveConfig() {
|
||||
|
||||
// 获取配置值
|
||||
export function getConfig() {
|
||||
const deviceMac = document.getElementById('deviceMac').value.trim();
|
||||
// 从DOM获取值
|
||||
const deviceMac = document.getElementById('deviceMac')?.value.trim() || '';
|
||||
const deviceName = document.getElementById('deviceName')?.value.trim() || '';
|
||||
const clientId = document.getElementById('clientId')?.value.trim() || '';
|
||||
|
||||
return {
|
||||
deviceId: deviceMac, // 使用MAC地址作为deviceId
|
||||
deviceName: document.getElementById('deviceName').value.trim(),
|
||||
deviceMac: deviceMac,
|
||||
clientId: document.getElementById('clientId').value.trim()
|
||||
deviceName,
|
||||
deviceMac,
|
||||
clientId
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -307,14 +307,7 @@ export class AudioRecorder {
|
||||
|
||||
// 发送监听开始消息
|
||||
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
|
||||
const listenMessage = {
|
||||
type: 'listen',
|
||||
mode: 'manual',
|
||||
state: 'start'
|
||||
};
|
||||
|
||||
log(`发送录音开始消息: ${JSON.stringify(listenMessage)}`, 'info');
|
||||
this.websocket.send(JSON.stringify(listenMessage));
|
||||
log(`发送录音开始消息`, 'info');
|
||||
} else {
|
||||
log('WebSocket未连接,无法发送开始消息', 'error');
|
||||
return false;
|
||||
@@ -403,14 +396,6 @@ export class AudioRecorder {
|
||||
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
|
||||
const emptyOpusFrame = new Uint8Array(0);
|
||||
this.websocket.send(emptyOpusFrame);
|
||||
|
||||
const stopMessage = {
|
||||
type: 'listen',
|
||||
mode: 'manual',
|
||||
state: 'stop'
|
||||
};
|
||||
|
||||
this.websocket.send(JSON.stringify(stopMessage));
|
||||
log('已发送录音停止信号', 'info');
|
||||
}
|
||||
|
||||
|
||||
@@ -62,18 +62,6 @@ function validateConfig(config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断wsUrl路径是否存在错误
|
||||
function validateWsUrl(wsUrl) {
|
||||
if (wsUrl === '') return false;
|
||||
// 检查URL格式
|
||||
if (!wsUrl.startsWith('ws://') && !wsUrl.startsWith('wss://')) {
|
||||
log('URL格式错误,必须以ws://或wss://开头', 'error');
|
||||
return false;
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// OTA发送请求,验证状态,并返回响应数据
|
||||
async function sendOTA(otaUrl, config) {
|
||||
try {
|
||||
|
||||
@@ -421,7 +421,6 @@ export class WebSocketHandler {
|
||||
|
||||
const listenMessage = {
|
||||
type: 'listen',
|
||||
mode: 'manual',
|
||||
state: 'detect',
|
||||
text: text
|
||||
};
|
||||
|
||||
@@ -133,6 +133,21 @@ class UIController {
|
||||
});
|
||||
}
|
||||
|
||||
// 消息输入框事件
|
||||
const chatIpt = document.getElementById('chatIpt');
|
||||
if (chatIpt) {
|
||||
const wsHandler = getWebSocketHandler();
|
||||
chatIpt.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (e.target.value) {
|
||||
wsHandler.sendTextMessage(e.target.value);
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭按钮
|
||||
const closeButtons = document.querySelectorAll('.close-btn');
|
||||
closeButtons.forEach(btn => {
|
||||
@@ -247,7 +262,7 @@ class UIController {
|
||||
const recordBtn = document.getElementById('recordBtn');
|
||||
if (recordBtn) {
|
||||
if (isRecording) {
|
||||
recordBtn.querySelector('.btn-text').textContent = `录音中 ${seconds.toFixed(1)}秒`;
|
||||
recordBtn.querySelector('.btn-text').textContent = `录音中`;
|
||||
recordBtn.classList.add('recording');
|
||||
} else {
|
||||
recordBtn.querySelector('.btn-text').textContent = '录音';
|
||||
@@ -379,6 +394,11 @@ class UIController {
|
||||
// 显示连接中消息
|
||||
this.addChatMessage('正在连接服务器...', false);
|
||||
|
||||
const chatIpt = document.getElementById('chatIpt');
|
||||
if (chatIpt) {
|
||||
chatIpt.style.display = 'flex';
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取配置信息
|
||||
const config = getConfig();
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
// DOM元素
|
||||
const connectButton = document.getElementById('connectButton');
|
||||
const serverUrlInput = document.getElementById('serverUrl');
|
||||
const connectionStatus = document.getElementById('connectionStatus');
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
const sendTextButton = document.getElementById('sendTextButton');
|
||||
const recordButton = document.getElementById('recordButton');
|
||||
const stopButton = document.getElementById('stopButton');
|
||||
// 会话记录
|
||||
const conversationDiv = document.getElementById('conversation');
|
||||
const logContainer = document.getElementById('logContainer');
|
||||
let visualizerCanvas = document.getElementById('audioVisualizer');
|
||||
|
||||
// ota 是否连接成功,修改成对应的样式
|
||||
export function otaStatusStyle(flan) {
|
||||
const otaStatusElement = document.getElementById('otaStatus');
|
||||
@@ -25,11 +12,6 @@ export function otaStatusStyle(flan) {
|
||||
}
|
||||
}
|
||||
|
||||
// ota 是否连接成功,修改成对应的样式
|
||||
export function getLogContainer(flan) {
|
||||
return logContainer;
|
||||
}
|
||||
|
||||
// 更新Opus库状态显示
|
||||
export function updateScriptStatus(message, type) {
|
||||
const statusElement = document.getElementById('scriptStatus');
|
||||
@@ -41,12 +23,3 @@ export function updateScriptStatus(message, type) {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加消息到会话记录
|
||||
export function addMessage(text, isUser = false) {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message ${isUser ? 'user' : 'server'}`;
|
||||
messageDiv.textContent = text;
|
||||
conversationDiv.appendChild(messageDiv);
|
||||
conversationDiv.scrollTop = conversationDiv.scrollHeight;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,8 +68,13 @@
|
||||
<canvas id="live2d-stage"></canvas>
|
||||
|
||||
<!-- 聊天消息流(弹幕样式) -->
|
||||
<div class="chat-stream" id="chatStream">
|
||||
<!-- 聊天消息将动态插入这里 -->
|
||||
<div class="chat-container">
|
||||
<div class="chat-stream" id="chatStream">
|
||||
<!-- 聊天消息将动态插入这里 -->
|
||||
</div>
|
||||
<div class="chat-ipt" id="chatIpt">
|
||||
<input type="text" id="messageInput" autocomplete="off" placeholder="输入消息,按Enter发送">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部控制栏 -->
|
||||
|
||||
Reference in New Issue
Block a user