增加运维机器人单元

This commit is contained in:
qing committed 2026-07-27 21:39:53 +08:00
1 parent 479d263b88
commit 1b60ab0a2e
42 files changed
+20469

No files matched your search

+875
View File
@@ -0,0 +1,875 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>小猫AI · 智能喵语伙伴</title>
<!-- Font Awesome 6 (免费图标库) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- highlight.js 代码高亮 + marked markdown解析 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
<!-- Socket.IO 客户端库 -->
<script src="https://cdn.socket.io/4.5.0/socket.io.min.js"></script>
<style>
/* ========== 融合小猫风格 + WebSocket增强样式 ========== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", "Segoe UI", system-ui, -apple-system, 'Roboto', sans-serif;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
/* 整体背景及容器 - 可爱猫咪氛围 */
body {
background: #1a1a2f;
background-image: url('/imgs/background.jpeg');
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
background-attachment: fixed;
transition: background-image 0.3s ease, background-color 0.2s;
color: #f0ede8;
}
/* 主布局 flex,左侧聊天区,右侧猫咪头像+WS侧栏 */
.main-layout {
display: flex;
height: 100vh;
width: 100%;
position: relative;
}
/* 左侧内容区 (聊天框+输入区) */
.cat-chat-area {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
background: rgba(0,0,0,0.2);
backdrop-filter: blur(2px);
min-width: 0;
}
/* 角色模型区域 - 右侧面板内 */
#roleArea {
width: 140px;
height: 140px;
margin: 0 auto 16px auto;
filter: drop-shadow(0 8px 16px rgba(0,0,0,0.4));
pointer-events: auto;
background: rgba(0,0,0,0.2);
border-radius: 50%;
backdrop-filter: blur(4px);
padding: 8px;
}
#roleArea img {
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 50%;
transition: all 0.2s;
}
/* 聊天记录玻璃容器 */
.chat-box {
position: relative;
margin: 20px 20px 95px 20px;
background: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(12px);
border-radius: 28px;
padding: 16px 20px;
overflow-y: auto;
height: calc(100vh - 130px);
scroll-behavior: smooth;
border: 1px solid rgba(255, 220, 180, 0.3);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
.chat-box div {
margin: 10px 0;
animation: fadeSlideUp 0.2s ease;
}
.user {
text-align: right;
color: #b9f6ca;
font-weight: 500;
background: rgba(60, 50, 80, 0.7);
display: inline-block;
width: auto;
max-width: 85%;
margin-left: auto;
padding: 8px 16px;
border-radius: 26px;
border-bottom-right-radius: 4px;
word-break: break-word;
white-space: pre-wrap;
}
.ai {
text-align: left;
color: #fff0dd;
background: rgba(30, 30, 45, 0.8);
width: auto;
max-width: 85%;
padding: 8px 16px;
border-radius: 26px;
border-bottom-left-radius: 4px;
word-break: break-word;
white-space: pre-wrap;
}
/* 系统/错误/任务消息样式 */
.system-msg, .error-msg, .task-msg {
padding: 6px 12px;
border-radius: 20px;
font-size: 13px;
background: rgba(0,0,0,0.5);
margin: 6px 0;
}
.task-separator {
margin: 12px 0 8px;
padding: 6px 12px;
background: #ff980033;
border-left: 3px solid #ff9800;
border-radius: 12px;
font-size: 12px;
display: flex;
align-items: center;
gap: 8px;
color: #ffcd94;
}
/* 输入框区域 - 支持多行文本 */
.input-area {
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
display: flex;
gap: 12px;
background: transparent;
z-index: 30;
align-items: flex-end;
}
.input-wrapper {
flex: 1;
background: rgba(20, 20, 35, 0.9);
backdrop-filter: blur(6px);
border-radius: 24px;
border: 1px solid rgba(255, 200, 150, 0.5);
transition: all 0.2s ease;
}
.input-wrapper:focus-within {
border-color: #ff9a5a;
box-shadow: 0 0 8px rgba(255, 154, 90, 0.3);
}
#msgInput {
width: 100%;
padding: 12px 18px;
background: transparent;
color: #fce8cf;
font-size: 14px;
outline: none;
border: none;
resize: none;
font-family: inherit;
line-height: 1.5;
max-height: 150px;
overflow-y: auto;
}
#msgInput::placeholder {
color: rgba(252, 232, 207, 0.6);
}
#msgInput::-webkit-scrollbar {
width: 4px;
}
#msgInput::-webkit-scrollbar-track {
background: rgba(255,255,255,0.1);
border-radius: 4px;
}
#msgInput::-webkit-scrollbar-thumb {
background: rgba(255, 154, 90, 0.5);
border-radius: 4px;
}
#sendBtn {
padding: 0 28px;
height: 48px;
border-radius: 48px;
border: none;
background: linear-gradient(135deg, #ff9a5a, #ff6f61);
color: white;
font-weight: bold;
cursor: pointer;
transition: 0.2s;
flex-shrink: 0;
font-size: 16px;
display: flex;
align-items: center;
gap: 6px;
}
#sendBtn:hover {
transform: scale(1.02);
box-shadow: 0 4px 12px rgba(255, 111, 97, 0.4);
}
#sendBtn:active {
transform: scale(0.98);
}
/* 右侧整合面板 (猫咪头像 + WS控制台) */
.right-panel {
width: 320px;
background: rgba(30, 30, 40, 0.85);
backdrop-filter: blur(16px);
border-left: 1px solid rgba(255,255,200,0.2);
display: flex;
flex-direction: column;
overflow-y: auto;
z-index: 100;
padding: 24px 16px;
align-items: center;
}
/* 猫咪头像容器 */
.avatar-container {
text-align: center;
margin-bottom: 20px;
}
.avatar-name {
margin-top: 8px;
font-size: 18px;
font-weight: bold;
color: #ffd4a3;
letter-spacing: 1px;
}
/* WebSocket连接区域 */
.ws-card {
background: rgba(26, 188, 156, 0.75);
margin: 12px 0;
border-radius: 24px;
padding: 16px;
backdrop-filter: blur(4px);
width: 100%;
}
.ws-title {
font-weight: bold;
font-size: 14px;
margin-bottom: 10px;
border-bottom: 1px dashed rgba(255,255,240,0.5);
display: flex;
align-items: center;
gap: 6px;
}
.ws-control {
display: flex;
gap: 8px;
margin: 10px 0;
}
.ws-url-input {
width: 100%;
padding: 8px;
background: #2d2d3a;
border: 1px solid #5a5a6e;
border-radius: 20px;
color: white;
font-size: 12px;
margin-bottom: 10px;
}
.btn-sm {
padding: 6px 12px;
border-radius: 24px;
background: #4CAF50;
border: none;
color: white;
cursor: pointer;
font-size: 12px;
flex: 1;
}
.btn-sm:disabled { opacity: 0.5; }
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 6px;
}
.command-grid {
display: grid;
grid-template-columns: repeat(2,1fr);
gap: 6px;
margin-top: 8px;
}
.cmd-btn {
background: #3d3d4a;
padding: 6px;
border-radius: 20px;
font-size: 11px;
text-align: center;
cursor: pointer;
transition: 0.1s;
}
.cmd-btn:hover {
background: #5a5a6e;
transform: translateX(2px);
}
.stats-line {
font-size: 12px;
margin: 6px 0;
}
/* 流式消息游标动画 */
.streaming .message-text {
border-right: 2px solid #ffb347;
animation: blink 0.8s step-end infinite;
padding-right: 4px;
}
@keyframes blink { from,to { border-color: transparent; } 50% { border-color: #ffb347; } }
.markdown-body code { background: #1e1e2e; border-radius: 6px; }
.markdown-body pre { background: #0f0f1a; }
@keyframes fadeSlideUp {
from { opacity: 0; transform: translateY(8px);}
to { opacity: 1; transform: translateY(0);}
}
::-webkit-scrollbar { width: 5px; }
/* 更换背景按钮样式 */
.bg-btn {
background: rgba(255, 165, 100, 0.8);
border: none;
color: white;
padding: 8px 16px;
border-radius: 40px;
cursor: pointer;
font-size: 12px;
margin-top: 12px;
width: 100%;
transition: 0.2s;
}
.bg-btn:hover {
background: rgba(255, 140, 70, 1);
transform: scale(0.98);
}
/* 护眼模式开关样式 */
.eye-mode-btn {
background: rgba(100, 180, 120, 0.8);
border: none;
color: white;
padding: 8px 16px;
border-radius: 40px;
cursor: pointer;
font-size: 12px;
margin-top: 8px;
width: 100%;
transition: 0.2s;
}
.eye-mode-btn:hover {
background: rgba(80, 160, 100, 1);
}
.eye-protect {
background: #c8e6d9 !important;
background-image: none !important;
}
.eye-protect .chat-box {
background: rgba(180,200,150,0.85);
}
.eye-protect .right-panel {
background: rgba(80,100,70,0.9);
}
#sendBtn i {
font-size: 16px;
}
@media (max-width: 768px) {
.right-panel {
width: 280px;
}
.input-area {
gap: 8px;
}
#sendBtn {
padding: 0 20px;
font-size: 14px;
}
}
.proxy-info {
background: rgba(0,0,0,0.6);
padding: 4px 8px;
border-radius: 20px;
font-size: 10px;
margin-top: 8px;
text-align: center;
}
</style>
</head>
<body>
<div class="main-layout">
<!-- 左侧聊天区域 -->
<div class="cat-chat-area">
<div class="chat-box" id="chatBox">
<div class="ai">🐱 主人好呀~ 我是小猫AI,已整合智能引擎!点击右侧「连接服务器」开启AI对话 ✨<br>📝 提示:输入框支持多行文本,按 Enter 发送,Shift+Enter 换行<br>🔌 连接将通过 Socket.IO 代理服务转发</div>
</div>
<div class="input-area">
<div class="input-wrapper">
<textarea id="msgInput" placeholder="喵~ 跟小猫说说话吧... (Shift+Enter换行)" autocomplete="off" rows="1"></textarea>
</div>
<button id="sendBtn"><i class="fas fa-paper-plane"></i> 发送</button>
</div>
</div>
<!-- 右侧面板: 小猫头像 + WS控制台 + 背景/护眼功能 -->
<div class="right-panel">
<!-- 小猫头像区域 -->
<div class="avatar-container">
<div id="roleArea">
<img id="roleImg" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Ccircle cx='100' cy='100' r='80' fill='%23f9cfb0'/%3E%3Ccircle cx='70' cy='80' r='8' fill='%233b2b1f'/%3E%3Ccircle cx='130' cy='80' r='8' fill='%233b2b1f'/%3E%3Cpath d='M80 120 Q100 140 120 120' stroke='%23945c3a' fill='none' stroke-width='4'/%3E%3Cpolygon points='100,150 92,175 108,175' fill='%23ffaaa5'/%3E%3C/svg%3E" alt="小猫AI">
</div>
<div class="avatar-name">🐱 小猫 AI</div>
</div>
<!-- 背景与显示设置 -->
<div style="width: 100%; margin-bottom: 12px;">
<button id="changeBgBtn" class="bg-btn"><i class="fas fa-image"></i> 🖼️ 更换背景</button>
<button id="eyeModeBtn" class="eye-mode-btn"><i class="fas fa-eye"></i> 🌿 护眼模式</button>
</div>
<!-- WebSocket 智能连接卡片 -->
<div class="ws-card">
<div class="ws-title"><i class="fas fa-globe"></i> AI 智能引擎 (Socket.IO代理)</div>
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
<span class="status-dot" id="wsStatusDot" style="background:#f44336;"></span>
<span id="wsStatusText" style="font-size:12px;">未连接</span>
</div>
<div class="proxy-info">
<i class="fas fa-shield-alt"></i> 通过 Socket.IO 代理服务连接
</div>
<input type="text" id="wsUrlInput" class="ws-url-input" value="ws://localhost:8765" placeholder="目标 ws://地址:端口">
<div class="ws-control">
<button id="wsConnectBtn" class="btn-sm"><i class="fas fa-plug"></i> 连接</button>
<button id="wsDisconnectBtn" class="btn-sm" style="background:#6c757d;" disabled><i class="fas fa-power-off"></i> 断开</button>
</div>
<div style="margin-top: 12px;">
<div class="ws-title" style="font-size:12px;"><i class="fas fa-terminal"></i> 快捷命令</div>
<div class="command-grid">
<div class="cmd-btn" data-cmd="/help">/help</div>
<div class="cmd-btn" data-cmd="/list">/list</div>
<div class="cmd-btn" data-cmd="/memories">/memories</div>
<div class="cmd-btn" data-cmd="/tasks">/tasks</div>
<div class="cmd-btn" data-cmd="/new">/new</div>
<div class="cmd-btn" data-cmd="/config">/config</div>
</div>
</div>
<div id="statsPanel" style="margin-top: 12px; display:none;">
<div class="stats-line"><i class="fas fa-clock"></i> 连接时间: <span id="connectTime">-</span></div>
<div class="stats-line"><i class="fas fa-envelope"></i> 消息数: <span id="msgCount">0</span></div>
<div class="stats-line"><i class="fas fa-tasks"></i> 活跃任务: <span id="taskCount">0</span></div>
</div>
</div>
</div>
</div>
<!-- 隐藏文件上传 -->
<input type="file" id="bgFile" accept="image/*" style="display:none;">
<script>
(function(){
// ---------- DOM 元素 ----------
const bgFile = document.getElementById('bgFile');
const roleImg = document.getElementById('roleImg');
const chatBox = document.getElementById('chatBox');
const msgInput = document.getElementById('msgInput');
const sendBtn = document.getElementById('sendBtn');
const changeBgBtn = document.getElementById('changeBgBtn');
const eyeModeBtn = document.getElementById('eyeModeBtn');
let eyeProtectActive = false;
// ---------- Socket.IO 连接管理 ----------
let socket = null;
let reconnectAttempts = 0;
let reconnectTimer = null;
let manualDisconnect = false;
let markdownEnabled = true;
let globalMsgCount = 0;
let activeTaskCount = 0;
let connectStartTime = null;
let targetWsUrl = null;
// 流式消息管理
let streamingChat = { element: null, content: '', timer: null, active: false };
let streamingTasks = {};
function updateMsgStats() {
document.getElementById('msgCount').innerText = globalMsgCount;
document.getElementById('taskCount').innerText = activeTaskCount;
}
function addSystemMessage(text, isError=false) {
const div = document.createElement('div');
div.className = isError ? 'error-msg' : 'system-msg';
div.style.cssText = 'background:rgba(0,0,0,0.6); border-radius:16px; padding:6px 12px; margin:6px 0; font-size:12px;';
div.innerHTML = `<i class="fas ${isError ? 'fa-exclamation-triangle' : 'fa-info-circle'}"></i> ${text}`;
chatBox.appendChild(div);
chatBox.scrollTop = chatBox.scrollHeight;
}
function updateStreamingDisplay(type, execId=null) {
let stream = (type==='chat') ? streamingChat : (execId ? streamingTasks[execId] : null);
if(!stream || !stream.element) return;
const textDiv = stream.element.querySelector('.message-text');
if(textDiv) {
textDiv.setAttribute('data-raw', stream.content);
if(markdownEnabled) {
textDiv.innerHTML = marked.parse(stream.content);
textDiv.querySelectorAll('pre code').forEach(block => hljs.highlightElement(block));
} else textDiv.innerText = stream.content;
}
chatBox.scrollTop = chatBox.scrollHeight;
}
function handleStreamChunk(content, type='chat', taskMeta=null) {
if(type === 'chat') {
if(!streamingChat.element) {
const wrapper = document.createElement('div');
wrapper.className = 'ai streaming';
wrapper.innerHTML = `<div class="message-text markdown-body" data-raw=""></div>`;
streamingChat.element = wrapper;
chatBox.appendChild(wrapper);
streamingChat.content = '';
streamingChat.active = true;
}
streamingChat.content += content;
if(streamingChat.timer) clearTimeout(streamingChat.timer);
streamingChat.timer = setTimeout(() => updateStreamingDisplay('chat'), 50);
}
else if(type === 'task' && taskMeta && taskMeta.executionId) {
const eid = taskMeta.executionId;
if(!streamingTasks[eid]) {
const sep = document.createElement('div');
sep.className = 'task-separator';
sep.innerHTML = `<i class="fas fa-bolt"></i> 定时任务 #${taskMeta.taskId} (${taskMeta.taskTitle || '执行'}) 第${taskMeta.triggerCount||1}次执行`;
chatBox.appendChild(sep);
const taskDiv = document.createElement('div');
taskDiv.className = 'task-msg streaming';
taskDiv.style.cssText = 'background:#2d2a1e; border-radius:16px; margin:6px 0; padding:8px 12px;';
taskDiv.innerHTML = `<div class="message-text markdown-body"></div>`;
chatBox.appendChild(taskDiv);
streamingTasks[eid] = { element: taskDiv, content: '', timer: null };
activeTaskCount++;
updateMsgStats();
}
streamingTasks[eid].content += content;
if(streamingTasks[eid].timer) clearTimeout(streamingTasks[eid].timer);
streamingTasks[eid].timer = setTimeout(() => {
const st = streamingTasks[eid];
if(st && st.element) {
const textDiv = st.element.querySelector('.message-text');
if(textDiv) {
if(markdownEnabled) textDiv.innerHTML = marked.parse(st.content);
else textDiv.innerText = st.content;
textDiv.querySelectorAll('pre code').forEach(block => hljs.highlightElement(block));
}
chatBox.scrollTop = chatBox.scrollHeight;
}
}, 50);
}
}
function completeStream(type='chat', execId=null) {
if(type==='chat' && streamingChat.element) {
if(streamingChat.timer) clearTimeout(streamingChat.timer);
updateStreamingDisplay('chat');
streamingChat.element.classList.remove('streaming');
globalMsgCount++;
updateMsgStats();
streamingChat = { element: null, content: '', timer: null, active: false };
} else if(type==='task' && execId && streamingTasks[execId]) {
const task = streamingTasks[execId];
if(task.timer) clearTimeout(task.timer);
if(task.element) {
const txtDiv = task.element.querySelector('.message-text');
if(txtDiv && markdownEnabled) txtDiv.innerHTML = marked.parse(task.content);
task.element.classList.remove('streaming');
}
globalMsgCount++;
delete streamingTasks[execId];
activeTaskCount = Math.max(0, activeTaskCount-1);
updateMsgStats();
}
}
function handleWSMessage(data) {
if(data.type === 'chunk') handleStreamChunk(data.content, 'chat');
else if(data.type === 'complete') completeStream('chat');
else if(data.type === 'dialogue_ended') {
addSystemMessage("✨ 对话已结束,可开启新对话");
if(streamingChat.element) completeStream('chat');
}
else if(data.type === 'task_process') {
if(data.msg_type === 'chunk') {
handleStreamChunk(data.content, 'task', { taskId: data.task_id, executionId: data.execution_id, taskTitle: data.task_title, triggerCount: (data.execution_id?.split('_')[2]) });
} else if(data.msg_type === 'complete') completeStream('task', data.execution_id);
}
else if(data.type === 'task_status') addSystemMessage(`📌 任务${data.task_id}: ${data.title} - ${data.status}`);
else if(data.type === 'error') addSystemMessage(`❌ ${data.content}`, true);
else if(data.type === 'command_result') addSystemMessage(`📋 ${data.content}`);
else if(data.type === 'info') addSystemMessage(`ℹ️ ${data.content}`);
}
function sendWSMessage(message, isCommand=false) {
if(!socket || !socket.connected) {
addSystemMessage("未连接到AI服务器,请先点击连接", true);
return false;
}
const payload = isCommand ? { type: 'command', command: message } : { type: 'message', content: message };
socket.emit('ws_send', { message: JSON.stringify(payload) });
return true;
}
function connectWebSocket() {
if(socket && socket.connected) {
socket.disconnect();
}
targetWsUrl = document.getElementById('wsUrlInput').value.trim();
if(!targetWsUrl) {
addSystemMessage("请输入WebSocket服务器地址", true);
return;
}
if (!targetWsUrl.startsWith('ws://') && !targetWsUrl.startsWith('wss://')) {
addSystemMessage("WebSocket地址必须以 ws:// 或 wss:// 开头", true);
return;
}
manualDisconnect = false;
socket = io({
transports: ['websocket', 'polling'],
withCredentials: true
});
socket.on('connect', () => {
console.log('Socket.IO connected, SID:', socket.id);
updateConnectionUI(true);
reconnectAttempts = 0;
connectStartTime = new Date();
document.getElementById('connectTime').innerText = connectStartTime.toLocaleTimeString();
addSystemMessage(`✅ Socket.IO 连接成功!正在连接目标 WebSocket: ${targetWsUrl}`);
socket.emit('ws_connect', { target_url: targetWsUrl });
});
socket.on('connected', (data) => {
addSystemMessage(`✅ ${data.message}`);
});
socket.on('ws_connected', (data) => {
addSystemMessage(`✅ ${data.message}`);
if(streamingChat.element) completeStream('chat');
});
socket.on('ws_message', (data) => {
try {
const messageData = JSON.parse(data.data);
handleWSMessage(messageData);
} catch(err) {
handleStreamChunk(data.data, 'chat');
}
});
socket.on('ws_error', (data) => {
addSystemMessage(`❌ ${data.error}`, true);
updateConnectionUI(false);
});
socket.on('disconnect', (reason) => {
console.log('Socket.IO disconnected:', reason);
updateConnectionUI(false);
if(!manualDisconnect && reconnectAttempts < 3) {
reconnectAttempts++;
addSystemMessage(`⚠️ 连接断开 (${reason}),${reconnectAttempts}/3 次重试...`);
if(reconnectTimer) clearTimeout(reconnectTimer);
reconnectTimer = setTimeout(() => connectWebSocket(), 2000);
} else if(!manualDisconnect) {
addSystemMessage(`❌ 连接断开 (${reason}),请手动重连`);
} else {
addSystemMessage("已手动断开连接");
}
});
socket.on('connect_error', (error) => {
console.error('Socket.IO connection error:', error);
addSystemMessage(`❌ 连接错误: ${error.message}`, true);
});
}
function disconnectWebSocket() {
manualDisconnect = true;
if(socket) {
socket.disconnect();
socket = null;
}
targetWsUrl = null;
updateConnectionUI(false);
}
function updateConnectionUI(connected) {
const dot = document.getElementById('wsStatusDot');
const text = document.getElementById('wsStatusText');
const connectBtn = document.getElementById('wsConnectBtn');
const disconnectBtn = document.getElementById('wsDisconnectBtn');
const statsPanel = document.getElementById('statsPanel');
if(connected) {
dot.style.background = "#4CAF50";
text.innerText = "已连接 (Socket.IO)";
connectBtn.disabled = true;
disconnectBtn.disabled = false;
statsPanel.style.display = "block";
} else {
dot.style.background = "#f44336";
text.innerText = "未连接";
connectBtn.disabled = false;
disconnectBtn.disabled = true;
statsPanel.style.display = "none";
activeTaskCount = 0;
globalMsgCount = 0;
updateMsgStats();
Object.keys(streamingTasks).forEach(k => completeStream('task', k));
if(streamingChat.element) completeStream('chat');
}
}
function onSendMessage() {
let text = msgInput.value.trim();
if(!text) return;
const userDiv = document.createElement('div');
userDiv.className = 'user';
userDiv.innerText = text;
chatBox.appendChild(userDiv);
msgInput.value = '';
autoResizeTextarea();
msgInput.focus();
chatBox.scrollTop = chatBox.scrollHeight;
if(socket && socket.connected) {
if(text.startsWith('/')) sendWSMessage(text, true);
else sendWSMessage(text, false);
} else {
setTimeout(() => {
let reply = "喵~ 请先连接WebSocket服务器获取智能回复~ ✨\n💡 提示:多行文本也支持哦!\n🔌 连接将通过 Socket.IO 代理服务转发";
const aiDiv = document.createElement('div');
aiDiv.className = 'ai';
aiDiv.innerText = `🐱 小猫AI:${reply}`;
chatBox.appendChild(aiDiv);
chatBox.scrollTop = chatBox.scrollHeight;
}, 200);
}
}
function autoResizeTextarea() {
if (!msgInput) return;
msgInput.style.height = 'auto';
const newHeight = Math.min(msgInput.scrollHeight, 150);
msgInput.style.height = newHeight + 'px';
}
msgInput.addEventListener('input', autoResizeTextarea);
autoResizeTextarea();
changeBgBtn.onclick = () => bgFile.click();
bgFile.onchange = (e) => {
let f = e.target.files[0];
if(f) {
let url = URL.createObjectURL(f);
document.body.style.backgroundImage = `url(${url})`;
document.body.style.backgroundSize = "cover";
document.body.style.backgroundPosition = "center center";
document.body.style.backgroundRepeat = "no-repeat";
if(eyeProtectActive) {
eyeProtectActive = false;
document.body.classList.remove('eye-protect');
addSystemMessage("护眼模式已自动关闭(背景已更换)");
}
addSystemMessage("✨ 背景已更换");
}
};
eyeModeBtn.onclick = () => {
if(!eyeProtectActive) {
document.body.classList.add('eye-protect');
document.body.style.backgroundImage = "none";
eyeProtectActive=true;
addSystemMessage("护眼模式已开启(背景图片暂时隐藏)");
eyeModeBtn.style.background = "rgba(60, 140, 80, 0.9)";
}
else {
document.body.classList.remove('eye-protect');
document.body.style.backgroundImage = "url('/imgs/background.jpeg')";
document.body.style.backgroundSize = "cover";
document.body.style.backgroundPosition = "center center";
document.body.style.backgroundRepeat = "no-repeat";
eyeProtectActive=false;
addSystemMessage("护眼模式关闭,恢复背景");
eyeModeBtn.style.background = "rgba(100, 180, 120, 0.8)";
}
};
document.getElementById('wsConnectBtn').onclick = connectWebSocket;
document.getElementById('wsDisconnectBtn').onclick = disconnectWebSocket;
document.querySelectorAll('.cmd-btn').forEach(btn => {
btn.onclick = () => {
let cmd = btn.getAttribute('data-cmd');
msgInput.value = cmd + ' ';
autoResizeTextarea();
msgInput.focus();
};
});
sendBtn.onclick = onSendMessage;
msgInput.addEventListener('keydown', (e) => {
if(e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
onSendMessage();
}
});
document.body.style.backgroundImage = "url('/imgs/background.jpeg')";
document.body.style.backgroundSize = "cover";
document.body.style.backgroundPosition = "center center";
document.body.style.backgroundRepeat = "no-repeat";
msgInput.focus();
})();
</script>
</body>
</html>