再次修复

This commit is contained in:
Flatlogic Bot 2026-02-20 08:04:38 +00:00
parent 88f258181d
commit 10e38bc178
16 changed files with 244 additions and 112 deletions

View File

@ -203,6 +203,8 @@ ob_start();
let selectedUser = null;
let selectedIp = null;
let lastMsgId = 0;
let lastChatIds = new Set();
let currentUserContext = '';
async function refreshUsers() {
const r = await fetch('/api/chat.php?action=admin_get_all');
@ -301,50 +303,73 @@ async function deleteUser() {
async function fetchMessages() {
if (!selectedIp && !selectedUser) return;
const r = await fetch(`/api/chat.php?action=get_messages&user_id=${selectedUser}&ip=${selectedIp}`);
const msgs = await r.json();
const area = document.getElementById('messages-area');
// For registered users, we show all their messages. For guests, we filter by IP.
const filtered = msgs.filter(m => {
if (selectedUser != 0) {
return m.user_id == selectedUser;
} else {
return m.ip_address === selectedIp && m.user_id == 0;
try {
const r = await fetch(`/api/chat.php?action=get_messages&user_id=${selectedUser}&ip=${selectedIp}`);
const msgs = await r.json();
if (!msgs || !Array.isArray(msgs)) return;
// If user changed, clear everything
const context = selectedUser + '_' + selectedIp;
if (currentUserContext !== context) {
document.getElementById('messages-area').innerHTML = '';
lastChatIds.clear();
currentUserContext = context;
}
});
if (filtered.length > 0) {
const isAtBottom = area.scrollTop + area.clientHeight >= area.scrollHeight - 50;
const area = document.getElementById('messages-area');
let hasNew = false;
let html = '';
filtered.forEach(m => {
const msgDate = new Date(m.created_at.replace(/-/g, "/"));
const timeStr = msgDate.toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit', second: '2-digit'});
const recallHtml = m.sender === 'admin' ? `<span class="recall-btn text-white-50" onclick="recallMessage(${m.id})">撤回</span>` : '';
const isImage = m.message.indexOf('<img') !== -1;
html += `
<div class="msg ${m.sender === 'admin' ? 'msg-admin' : 'msg-user'}" style="${isImage ? 'padding: 5px; background: ' + (m.sender === 'admin' ? '#007bff' : '#f0f0f0') + ';' : ''}">
<div class="msg-content">${m.message}</div>
<div class="msg-time" style="${isImage ? 'position: absolute; bottom: 4px; right: 10px; background: rgba(0,0,0,0.3); color: #fff; padding: 0 4px; border-radius: 4px;' : ''}">${timeStr} ${recallHtml}</div>
</div>
`;
msgs.forEach(m => {
if (!lastChatIds.has(m.id)) {
appendMessageHTML(m);
lastChatIds.add(m.id);
hasNew = true;
}
});
if (area.innerHTML !== html) {
area.innerHTML = html;
if (isAtBottom || lastMsgId === 0) {
area.scrollTop = area.scrollHeight;
if (hasNew) {
area.scrollTop = area.scrollHeight;
}
if (msgs.length > 0) {
const lastMsg = msgs[msgs.length - 1];
if (lastMsg.created_at) {
document.getElementById('info-time').innerText = new Date(lastMsg.created_at.replace(/-/g, "/")).toLocaleString('zh-CN');
}
}
lastMsgId = filtered.length;
const lastMsg = filtered[filtered.length - 1];
document.getElementById('info-time').innerText = new Date(lastMsg.created_at.replace(/-/g, "/")).toLocaleString('zh-CN');
} catch (err) {
console.error('Fetch messages error:', err);
}
}
function appendMessageHTML(m) {
const area = document.getElementById('messages-area');
if (!area || area.querySelector(`[data-id="${m.id}"]`)) return;
const time = m.created_at || new Date().toISOString();
const msgDate = time.includes('-') ? new Date(time.replace(/-/g, "/")) : new Date(time);
const timeStr = msgDate.toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit', second: '2-digit'});
const recallHtml = m.sender === 'admin' ? `<span class="recall-btn text-white-50 ms-2" style="cursor:pointer; text-decoration:underline;" onclick="recallMessage(${m.id})">撤回</span>` : '';
const isImage = typeof m.message === 'string' && m.message.indexOf('<img') !== -1;
const div = document.createElement('div');
div.className = `msg ${m.sender === 'admin' ? 'msg-admin' : 'msg-user'}`;
div.setAttribute('data-id', m.id);
if (isImage) {
div.style.padding = '5px';
div.style.background = m.sender === 'admin' ? '#007bff' : '#f0f0f0';
div.style.lineHeight = '0';
}
div.innerHTML = `
<div class="msg-content">${m.message}</div>
<div class="msg-time" style="${isImage ? 'position: absolute; bottom: 8px; right: 10px; background: rgba(0,0,0,0.4); color: #fff; padding: 0 6px; border-radius: 4px; font-size: 9px; line-height: 1.5;' : ''}">${timeStr} ${recallHtml}</div>
`;
area.appendChild(div);
}
document.getElementById('plus-btn').addEventListener('click', () => {
document.getElementById('image-input').click();
});
@ -353,22 +378,50 @@ document.getElementById('image-input').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
// Local preview for "0 latency"
const localUrl = URL.createObjectURL(file);
const tempId = 'temp_img_' + Date.now();
const localMsgHtml = `<img src="${localUrl}" class="img-fluid rounded" style="max-width: 100%; max-height: 250px; object-fit: contain; margin: 5px 0; opacity: 0.6;">`;
appendMessageHTML({
id: tempId,
sender: 'admin',
message: localMsgHtml,
created_at: new Date().toISOString()
});
const area = document.getElementById('messages-area');
area.scrollTop = area.scrollHeight;
const formData = new FormData();
formData.append('file', file);
formData.append('user_id', selectedUser);
formData.append('ip_address', selectedIp);
formData.append('user_id', selectedUser || 0);
formData.append('ip_address', selectedIp || '');
const r = await fetch('/api/chat.php?action=upload_image', {
method: 'POST',
body: formData
});
const res = await r.json();
if (res.success) {
fetchMessages();
} else {
alert('上传失败: ' + res.error);
try {
const r = await fetch('/api/chat.php?action=upload_image', {
method: 'POST',
body: formData
});
const res = await r.json();
// Remove temp
const tempMsg = document.querySelector(`[data-id="${tempId}"]`);
if (tempMsg) tempMsg.remove();
if (res.success && res.message) {
appendMessageHTML(res.message);
area.scrollTop = area.scrollHeight;
fetchMessages();
} else {
alert('上传失败: ' + res.error);
}
} catch(err) {
const tempMsg = document.querySelector(`[data-id="${tempId}"]`);
if (tempMsg) tempMsg.remove();
}
e.target.value = ''; // Reset
setTimeout(() => URL.revokeObjectURL(localUrl), 5000);
});
document.getElementById('chat-form').addEventListener('submit', async (e) => {
@ -377,14 +430,37 @@ document.getElementById('chat-form').addEventListener('submit', async (e) => {
const msg = input.value.trim();
if (!msg) return;
input.value = '';
// Optimistic UI
const tempId = 'temp_msg_' + Date.now();
appendMessageHTML({
id: tempId,
sender: 'admin',
message: msg,
created_at: new Date().toISOString()
});
const area = document.getElementById('messages-area');
area.scrollTop = area.scrollHeight;
const fd = new URLSearchParams();
fd.append('message', msg);
fd.append('user_id', selectedUser);
fd.append('ip_address', selectedIp);
await fetch('/api/chat.php?action=admin_send', { method: 'POST', body: fd });
input.value = '';
fetchMessages();
try {
const r = await fetch('/api/chat.php?action=admin_send', { method: 'POST', body: fd });
const res = await r.json();
const tempMsg = document.querySelector(`[data-id="${tempId}"]`);
if (tempMsg) tempMsg.remove();
if (res.success && res.message) {
appendMessageHTML(res.message);
area.scrollTop = area.scrollHeight;
fetchMessages();
}
} catch(err) {}
});
document.getElementById('save-remark-btn').addEventListener('click', async () => {

View File

@ -5,7 +5,7 @@ if (session_status() === PHP_SESSION_NONE) session_start();
// Force simplified Chinese for admin
$lang = 'zh';
$_SESSION['lang'] = 'zh';
// $_SESSION['lang'] = 'zh'; // Do not persist to session to avoid affecting front-end default language
// Admin check
$admin = null;

View File

@ -1,6 +1,7 @@
<?php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/lang.php';
$lang = 'zh'; // Force Simplified Chinese for admin login
if (session_status() === PHP_SESSION_NONE) session_start();

View File

@ -28,24 +28,35 @@ if ($action === 'upload_image' || (isset($_POST['action']) && $_POST['action'] =
$targetPath = $targetDir . $filename;
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
$imageUrl = '/assets/images/chat/' . $filename;
$message = '<img src="' . $imageUrl . '" class="img-fluid rounded cursor-pointer" style="max-width: 100%; max-height: 250px; object-fit: contain; margin: 5px 0;" onclick="window.open(\'' . $imageUrl . '\')">';
if (isset($_SESSION['admin_id'])) {
$user_id = $_POST['user_id'] ?? 0;
$user_id = (int)($_POST['user_id'] ?? 0);
$ip = $_POST['ip_address'] ?? '';
$sender = 'admin';
$admin_id = $_SESSION['admin_id'];
$stmt = db()->prepare("INSERT INTO messages (user_id, admin_id, sender, message, ip_address) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$user_id, $admin_id, $sender, $message, $ip]);
} else {
$user_id = $_SESSION['user_id'] ?? 0;
$user_id = (int)($_SESSION['user_id'] ?? 0);
$ip = getRealIP();
$sender = 'user';
$stmt = db()->prepare("INSERT INTO messages (user_id, sender, message, ip_address) VALUES (?, ?, ?, ?)");
$stmt->execute([$user_id, 'user', $message, $ip]);
$stmt->execute([$user_id, $sender, $message, $ip]);
}
echo json_encode(['success' => true, 'url' => $imageUrl]);
$newId = db()->lastInsertId();
$createdAt = date('Y-m-d H:i:s');
echo json_encode([
'success' => true,
'url' => $imageUrl,
'message' => [
'id' => $newId,
'sender' => $sender,
'message' => $message,
'created_at' => $createdAt
]
]);
} else {
echo json_encode(['success' => false, 'error' => 'Failed to move uploaded file']);
}
@ -76,19 +87,19 @@ if ($action === 'send_message') {
$message = $_POST['message'] ?? '';
if (!$message) exit(json_encode(['success' => false]));
$user_id = $_SESSION['user_id'] ?? 0;
$user_id = (int)($_SESSION['user_id'] ?? 0);
$ip = getRealIP();
$stmt = db()->prepare("INSERT INTO messages (user_id, sender, message, ip_address) VALUES (?, ?, ?, ?)");
$stmt->execute([$user_id, 'user', $message, $ip]);
$newId = db()->lastInsertId();
echo json_encode(['success' => true, 'id' => $newId]);
echo json_encode(['success' => true, 'id' => $newId, 'message' => ['id' => $newId, 'sender' => 'user', 'message' => $message, 'created_at' => date('Y-m-d H:i:s')]]);
exit;
}
if ($action === 'admin_send') {
$message = $_POST['message'] ?? '';
$user_id = $_POST['user_id'] ?? 0;
$user_id = (int)($_POST['user_id'] ?? 0);
$target_ip = $_POST['ip_address'] ?? '';
if (!$message) exit(json_encode(['success' => false]));
@ -99,10 +110,11 @@ if ($action === 'admin_send') {
$stmt = db()->prepare("INSERT INTO messages (user_id, admin_id, sender, message, ip_address) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$user_id, $admin_id, $sender, $message, $target_ip]);
$newId = db()->lastInsertId();
echo json_encode(['success' => true, 'id' => $newId]);
echo json_encode(['success' => true, 'id' => $newId, 'message' => ['id' => $newId, 'sender' => $sender, 'message' => $message, 'created_at' => date('Y-m-d H:i:s')]]);
exit;
}
if ($action === 'ping') {
$user_id = $_SESSION['user_id'] ?? 0;
$ip = getRealIP();
@ -118,7 +130,7 @@ if ($action === 'admin_get_all') {
SELECT
v.user_id,
v.ip_address,
CASE WHEN m.message LIKE '<img%' THEN '[图片]' ELSE COALESCE(m.message, '用户已进入聊天室') END as message,
CASE WHEN m.message LIKE '<img%' THEN '[Image]' ELSE COALESCE(m.message, 'User joined') END as message,
COALESCE(m.created_at, v.last_activity) as created_at,
CASE WHEN u.email LIKE '%@user.byro' THEN u.username ELSE COALESCE(u.email, u.username) END as username,
u.uid,
@ -131,20 +143,20 @@ if ($action === 'admin_get_all') {
MAX(last_activity) as last_activity,
MAX(user_time) as user_time
FROM (
SELECT user_id, ip_address, MAX(created_at) as last_activity, NULL as user_time FROM messages GROUP BY user_id, ip_address
SELECT COALESCE(user_id, 0) as user_id, ip_address, MAX(created_at) as last_activity, NULL as user_time FROM messages GROUP BY COALESCE(user_id, 0), ip_address
UNION
SELECT user_id, ip_address, MAX(last_ping) as last_activity, MAX(user_time) as user_time FROM chat_visitors GROUP BY user_id, ip_address
SELECT COALESCE(user_id, 0) as user_id, ip_address, MAX(last_ping) as last_activity, MAX(user_time) as user_time FROM chat_visitors GROUP BY COALESCE(user_id, 0), ip_address
) t1
GROUP BY user_id, (CASE WHEN user_id = 0 THEN ip_address ELSE '0' END)
) v
LEFT JOIN (
SELECT m1.* FROM messages m1
INNER JOIN (
SELECT MAX(id) as max_id FROM messages GROUP BY user_id, (CASE WHEN user_id = 0 THEN ip_address ELSE '0' END)
SELECT MAX(id) as max_id FROM messages GROUP BY COALESCE(user_id, 0), (CASE WHEN COALESCE(user_id, 0) = 0 THEN ip_address ELSE '0' END)
) m2 ON m1.id = m2.max_id
) m ON (v.user_id = m.user_id AND (v.user_id != 0 OR v.ip_address = m.ip_address))
) m ON (v.user_id = COALESCE(m.user_id, 0) AND (v.user_id != 0 OR v.ip_address = m.ip_address))
LEFT JOIN users u ON (v.user_id = u.id AND v.user_id != 0)
LEFT JOIN chat_remarks r ON (v.user_id = r.user_id AND (v.user_id != 0 OR v.ip_address = r.ip_address))
LEFT JOIN chat_remarks r ON (v.user_id = COALESCE(r.user_id, 0) AND (v.user_id != 0 OR v.ip_address = r.ip_address))
WHERE v.last_activity > DATE_SUB(NOW(), INTERVAL 48 HOUR)
ORDER BY created_at DESC
");

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -36,7 +36,7 @@ $site_certificate = getSetting('site_certificate', '');
</div>
<div class="mt-3">
<button class="btn btn-primary btn-sm rounded-pill px-3" onclick="window.open('<?= htmlspecialchars($cert) ?>', '_blank')">
<i class="bi bi-zoom-in me-2"></i>查看高清原图
<i class="bi bi-zoom-in me-2"></i><?= __('view_full_image') ?>
</button>
</div>
</div>

View File

@ -1,5 +1,6 @@
<?php
// Generated by setup_mariadb_project.sh — edit as needed.
// Baota / Local Deployment Settings - Change these to match your database
define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'app_38451');
define('DB_USER', 'app_38451');

View File

@ -1,3 +1,9 @@
/*
BYRO Digital Asset Platform - Full Database Dump
Date: 2026-02-20
Instructions: Import this file into your MySQL/MariaDB database.
*/
/*M!999999\- enable the sandbox mode */
-- MariaDB dump 10.19 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64)
--

View File

@ -225,43 +225,69 @@ csUploadBtn.addEventListener('click', () => csFileInput.click());
csFileInput.addEventListener('change', async () => {
if (!csFileInput.files[0]) return;
const file = csFileInput.files[0];
// Create local preview for "0 latency"
const localUrl = URL.createObjectURL(file);
const tempId = 'temp_img_' + Date.now();
const localMsgHtml = `<img src="${localUrl}" class="img-fluid rounded" style="max-width: 100%; max-height: 250px; object-fit: contain; margin: 5px 0; opacity: 0.6;">`;
appendMessageHTML({
id: tempId,
sender: 'user',
message: localMsgHtml,
created_at: new Date().toISOString()
});
scrollToBottom();
const formData = new FormData();
formData.append('file', file);
formData.append('action', 'upload_image');
appendMessage('user', '<i class="bi bi-image"></i> <?= __("uploading") ?>');
try {
const resp = await fetch('/api/chat.php', {
method: 'POST',
body: formData
});
const data = await resp.json();
if (data.success) {
// Remove local preview
const tempMsg = document.querySelector(`[data-id="${tempId}"]`);
if (tempMsg) tempMsg.remove();
if (data.success && data.message) {
appendMessageHTML(data.message);
scrollToBottom();
pollMessages();
} else {
alert(data.error || '<?= __("send_failed") ?>');
}
} catch (err) {
console.error('Upload error:', err);
const tempMsg = document.querySelector(`[data-id="${tempId}"]`);
if (tempMsg) tempMsg.remove();
}
csFileInput.value = '';
// Clean up object URL
setTimeout(() => URL.revokeObjectURL(localUrl), 5000);
});
csToggle.addEventListener('click', () => {
csBox.classList.toggle('d-none');
scrollToBottom();
if (!csBox.classList.contains('d-none')) {
const now = new Date();
const timeStr = now.toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit', second: '2-digit'});
fetch('/api/chat.php?action=ping&user_time=' + encodeURIComponent(timeStr));
scrollToBottom();
pollMessages();
}
});
csClose.addEventListener('click', () => csBox.classList.add('d-none'));
function scrollToBottom() {
csMessages.scrollTop = csMessages.scrollHeight;
setTimeout(() => {
csMessages.scrollTop = csMessages.scrollHeight;
}, 50);
}
csForm.addEventListener('submit', async (e) => {
@ -270,6 +296,16 @@ csForm.addEventListener('submit', async (e) => {
if (!msg) return;
csInput.value = '';
// Optimistic UI update
const tempId = 'temp_msg_' + Date.now();
appendMessageHTML({
id: tempId,
sender: 'user',
message: msg,
created_at: new Date().toISOString()
});
scrollToBottom();
try {
const resp = await fetch('/api/chat.php?action=send_message', {
@ -278,7 +314,14 @@ csForm.addEventListener('submit', async (e) => {
body: `message=${encodeURIComponent(msg)}`
});
const data = await resp.json();
// Remove temp message and wait for poll to bring the real one
const tempMsg = document.querySelector(`[data-id="${tempId}"]`);
if (tempMsg) tempMsg.remove();
if (data.success) {
appendMessageHTML(data.message);
scrollToBottom();
pollMessages();
}
} catch (err) {
@ -286,38 +329,43 @@ csForm.addEventListener('submit', async (e) => {
}
});
function appendMessage(sender, text, time = null) {
const div = document.createElement('div');
div.className = `mb-3 d-flex flex-column ${sender === 'user' ? 'align-items-end' : 'align-items-start'}`;
function appendMessageHTML(m) {
if (document.querySelector(`[data-id="${m.id}"]`)) return;
const sender = m.sender;
const text = m.message;
const time = m.created_at;
const isImage = text.indexOf('<img') !== -1;
let dateObj;
if (time) {
if (typeof time === 'string' && time.includes('-')) {
dateObj = new Date(time.replace(/-/g, "/"));
} else {
dateObj = new Date();
dateObj = new Date(time);
}
const timeStr = dateObj.toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit', second: '2-digit'});
div.innerHTML = `
<div class="p-2 px-3 rounded-4 small ${sender === 'user' ? 'bg-primary text-white' : 'bg-dark text-white border border-secondary'}" style="max-width: 80%; color: #ffffff !important; word-break: break-all; position: relative; padding-bottom: 20px !important;">
${text}
<div style="font-size: 9px; opacity: 0.6; position: absolute; bottom: 4px; ${sender === 'user' ? 'right: 10px;' : 'left: 10px;'}">${timeStr}</div>
const msgHtml = `
<div class="mb-3 d-flex flex-column ${sender === 'user' ? 'align-items-end' : 'align-items-start'} message-item" data-id="${m.id}">
<div class="p-2 px-3 rounded-4 small ${sender === 'user' ? 'bg-primary text-white' : 'bg-dark text-white border border-secondary'}" style="max-width: 80%; color: #ffffff !important; word-break: break-all; position: relative; padding-bottom: 20px !important; ${isImage ? 'padding: 5px !important; padding-bottom: 5px !important; line-height: 0;' : ''}">
${text}
<div style="font-size: 9px; opacity: 0.6; position: absolute; bottom: 4px; ${sender === 'user' ? 'right: 10px;' : 'left: 10px;'} ${isImage ? 'background: rgba(0,0,0,0.4); padding: 0 4px; border-radius: 4px; bottom: 8px;' : ''}">${timeStr}</div>
</div>
</div>
`;
csMessages.appendChild(div);
scrollToBottom();
csMessages.insertAdjacentHTML('beforeend', msgHtml);
}
// Polling for new messages
let lastMsgId = 0;
let lastPingTime = 0;
let lastChatHtml = '';
let lastChatIds = new Set();
async function pollMessages() {
if (csBox.classList.contains('d-none')) return;
// Ping every 10 seconds to update user time
const now = Date.now();
if (typeof lastPingTime === 'undefined') window.lastPingTime = 0;
if (now - lastPingTime > 10000) {
const timeStr = new Date().toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit', second: '2-digit'});
fetch('/api/chat.php?action=ping&user_time=' + encodeURIComponent(timeStr));
@ -327,37 +375,23 @@ async function pollMessages() {
try {
const resp = await fetch('/api/chat.php?action=get_messages');
const data = await resp.json();
if (data) {
let html = '<div class="text-center text-muted small mb-3"><?= __("welcome_support") ?></div>';
if (data && Array.isArray(data)) {
let hasNew = false;
data.forEach(m => {
const sender = m.sender;
const text = m.message;
const time = m.created_at;
const isImage = text.indexOf('<img') !== -1;
let dateObj = new Date(time.replace(/-/g, "/"));
const timeStr = dateObj.toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit', second: '2-digit'});
html += `
<div class="mb-3 d-flex flex-column ${sender === 'user' ? 'align-items-end' : 'align-items-start'}">
<div class="p-2 px-3 rounded-4 small ${sender === 'user' ? 'bg-primary text-white' : 'bg-dark text-white border border-secondary'}" style="max-width: 80%; color: #ffffff !important; word-break: break-all; position: relative; padding-bottom: 20px !important; ${isImage ? 'padding: 5px !important;' : ''}">
${text}
<div style="font-size: 9px; opacity: 0.6; position: absolute; bottom: 4px; ${sender === 'user' ? 'right: 10px;' : 'left: 10px;'} ${isImage ? 'background: rgba(0,0,0,0.4); padding: 0 4px; border-radius: 4px;' : ''}">${timeStr}</div>
</div>
</div>
`;
if (parseInt(m.id) > lastMsgId) lastMsgId = parseInt(m.id);
if (!lastChatIds.has(m.id)) {
appendMessageHTML(m);
lastChatIds.add(m.id);
hasNew = true;
}
});
if (csMessages.innerHTML !== html) {
const isAtBottom = csMessages.scrollTop + csMessages.clientHeight >= csMessages.scrollHeight - 50;
csMessages.innerHTML = html;
if (isAtBottom) scrollToBottom();
if (hasNew) {
scrollToBottom();
}
}
} catch (err) {}
}
setInterval(pollMessages, 300);
setInterval(pollMessages, 300); // 300ms polling for "zero delay" feel
</script>
<style>

View File

@ -1,9 +1,9 @@
<?php
session_start();
$lang = $_SESSION['lang'] ?? 'en';
$lang = $_SESSION['lang'] ?? 'en'; // Default to English
if (isset($_GET['lang'])) {
$lang = $_GET['lang'] === 'en' ? 'en' : 'zh';
$lang = $_GET['lang'] === 'zh' ? 'zh' : 'en';
$_SESSION['lang'] = $lang;
}
@ -371,6 +371,7 @@ $translations = [
'platform_certificate' => '平台注册证书',
'no_certificate_yet' => '暂无证书',
'trade' => '交易',
'view_full_image' => '查看高清原图',
'failed' => '失败',
'success' => '成功',
'direction' => '方向',
@ -891,6 +892,7 @@ $translations = [
'platform_certificate' => 'Platform Registration Certificate',
'no_certificate_yet' => 'No certificate available yet',
'trade' => 'Trade',
'view_full_image' => 'View Full Image',
'failed' => 'Failed',
'success' => 'Success',
'direction' => 'Direction',