feat(ai): 将小班长升级为完全独立的实体用户并支持随机金币发放及持续在线刷级,设定为女兵人设并使用自定义头像

This commit is contained in:
2026-03-26 11:15:11 +08:00
parent c13bb5f35c
commit 4d60893dbe
16 changed files with 523 additions and 101 deletions
+33 -35
View File
@@ -257,20 +257,7 @@
};
targetContainer.appendChild(allDiv);
// ── AI 小助手(仅当全局开关开启时显示,与普通用户风格一致)──
if (window.chatContext.chatBotEnabled) {
let botDiv = document.createElement('div');
botDiv.className = 'user-item';
botDiv.innerHTML = `
<img class="user-head" src="/images/ai_bot.png" onerror="this.src='/images/headface/1.gif'">
<span class="user-name">AI小班长</span><span style="font-size:12px; margin-left:2px;" title="聊天机器人">🤖</span>
`;
botDiv.onclick = () => {
toUserSelect.value = 'AI小班长';
document.getElementById('content').focus();
};
targetContainer.appendChild(botDiv);
}
// ── AI 小助手(已在 onlineUsers 中动态维护,移除硬编码)──
// 构建用户数组并排序
let userArr = [];
@@ -374,19 +361,16 @@
// 调用核心渲染(桌面端名单容器)
_renderUserListToContainer(userList, sortBy, keyword);
// 重新填充发言对象下拉框(不过滤关键词,始终显示全部用户)
toUserSelect.innerHTML = '<option value="大家">大家</option>';
if (window.chatContext.chatBotEnabled) {
let botOption = document.createElement('option');
botOption.value = 'AI小班长';
botOption.textContent = '🤖 AI小班长';
toUserSelect.appendChild(botOption);
}
// 下拉框里如果 AI在场,可以直接选
for (let username in onlineUsers) {
if (username !== window.chatContext.username) {
let option = document.createElement('option');
option.value = username;
option.textContent = username;
let text = username;
if (username === 'AI小班长') {
text = '🤖 AI小班长';
}
option.textContent = text;
toUserSelect.appendChild(option);
}
}
@@ -445,7 +429,7 @@
let timeStrOverride = false;
// 系统用户名列表(不可被选为聊天对象)
const systemUsers = ['钓鱼播报', '星海小博士', '系统传音', '系统公告', 'AI小班长', '送花播报', '系统', '欢迎'];
const systemUsers = ['钓鱼播报', '星海小博士', '系统传音', '系统公告', '送花播报', '系统', '欢迎'];
// 动作文字映射表:情绪型(着/地,放"对"之前)和动作型(了,替换"对X说"
const actionTextMap = {
@@ -522,7 +506,7 @@
// 用户名(单击切换发言对象,双击查看资料;系统用户或游戏标签仅显示文本)
const clickableUser = (uName, color) => {
if (uName === 'AI小班长') {
return `<span class="msg-user" data-u="${uName}" style="color: ${color}; cursor: pointer;" onclick="switchTarget('${uName}')">${uName}</span>`;
return `<span class="msg-user" data-u="${uName}" style="color: ${color}; cursor: pointer;" onclick="switchTarget('${uName}')" ondblclick="openUserCard('${uName}')">${uName}</span>`;
}
if (systemUsers.includes(uName) || isGameLabel(uName)) {
return `<span class="msg-user" style="color: ${color};">${uName}</span>`;
@@ -530,14 +514,11 @@
return `<span class="msg-user" data-u="${uName}" style="color: ${color}; cursor: pointer;" onclick="switchTarget('${uName}')" ondblclick="openUserCard('${uName}')">${uName}</span>`;
};
// 系统播报用户(不包含 AI小班长)使用军号图标,AI小班长用专属图,普通用户用头像
const buggleUsers = ['钓鱼播报', '星海小博士', '送花播报', '系统传音', '系统公告'];
// 普通用户(包括 AI小班长)用数据库头像,播报类用特殊喇叭图标
const senderInfo = onlineUsers[msg.from_user];
const senderHead = ((senderInfo && senderInfo.headface) || '1.gif').toLowerCase();
let headImgSrc = senderHead.startsWith('storage/') ? '/' + senderHead : `/images/headface/${senderHead}`;
if (msg.from_user === 'AI小班长') {
headImgSrc = '/images/ai_bot.png';
} else if (buggleUsers.includes(msg.from_user)) {
if (msg.from_user.endsWith('播报') || msg.from_user === '星海小博士' || msg.from_user === '系统传音' || msg.from_user === '系统公告') {
headImgSrc = '/images/bugle.png';
}
const headImg =
@@ -733,6 +714,12 @@
users.forEach(u => {
onlineUsers[u.username] = u;
});
// 初始加载时,如果全局且开启,注入 AI
if (window.chatContext.chatBotEnabled && window.chatContext.botUser) {
onlineUsers['AI小班长'] = window.chatContext.botUser;
}
renderUserList();
// 管理员自己进房时,在本地播放烟花(服务端广播可能在 WS 连上前已发出)
@@ -743,6 +730,21 @@
}
});
// 监听机器人动态开关
window.addEventListener('chat:bot-toggled', (e) => {
const detail = e.detail;
window.chatContext.chatBotEnabled = detail.isOnline;
if (detail.isOnline && detail.user && detail.user.username) {
onlineUsers[detail.user.username] = detail.user;
window.chatContext.botUser = detail.user;
} else {
delete onlineUsers['AI小班长'];
window.chatContext.botUser = null;
}
renderUserList();
});
window.addEventListener('chat:joining', (e) => {
const user = e.detail;
onlineUsers[user.username] = user;
@@ -1192,14 +1194,10 @@
return;
}
// 如果发言对象是 AI 小助手,专用机器人 API
// 如果发言对象是 AI 小助手,也发送一份给专用机器人 API,不打断正常的发消息流程
const toUser = formData.get('to_user');
if (toUser === 'AI小班长') {
contentInput.value = '';
contentInput.focus();
_isSending = false;
sendToChatBot(content); // 异步调用,不阻塞全局发送
return;
}
// ── 神秘箱子暗号拦截 ────────────────────────────────────