diff --git a/ai.js b/ai.js index deaede9..f108a48 100644 --- a/ai.js +++ b/ai.js @@ -566,7 +566,7 @@ export function buildSystemPrompt(contact, options = {}) { - 回复要符合角色性格 - 不要使用任何格式标记,直接输出对话内容 - 【禁止】不要使用小括号描述动作/表情/语气!如"(笑)"、"(害羞地说)"等,这是文字聊天不是小说! -- 如果想发送语音消息,使用格式:[语音:语音内容] +- 如果想发送语音消息,使用格式:[语音:实际说的话](注意:是你说的具体话语,不是声音描述!错误示例:[语音:声音低沉带笑] 正确示例:[语音:宝贝早上好呀]) - 如果想发送照片,使用格式:[照片:照片描述] - 【绝对禁止】语音/照片消息必须单独一条!格式前后不能有任何其他文字! 错误示例:叫得这么甜 [照片:xxx] ← 错误! diff --git a/chat-background.js b/chat-background.js index 9d5bbd1..033c516 100644 --- a/chat-background.js +++ b/chat-background.js @@ -1,12 +1,13 @@ /** * 聊天背景功能模块 - * 支持每个联系人独立设置背景,含图片裁剪功能 + * 支持每个联系人/群聊独立设置背景,含图片裁剪功能 */ import { requestSave } from './save-manager.js'; import { getSettings } from './config.js'; import { showToast } from './toast.js'; import { currentChatIndex } from './chat.js'; +import { isInGroupChat, getCurrentGroupIndex } from './group-chat.js'; // 裁剪器状态 let cropperState = { @@ -76,12 +77,19 @@ export function showChatBgPanel() { if (!panel || !preview) return; - // 获取当前联系人的背景 + // 获取当前聊天对象的背景(支持群聊和私聊) const settings = getSettings(); - const contact = settings.contacts[currentChatIndex]; + let chatTarget = null; - if (contact?.chatBackground) { - preview.innerHTML = `背景预览`; + if (isInGroupChat()) { + const groupIndex = getCurrentGroupIndex(); + chatTarget = settings.groupChats?.[groupIndex]; + } else { + chatTarget = settings.contacts[currentChatIndex]; + } + + if (chatTarget?.chatBackground) { + preview.innerHTML = `背景预览`; } else { preview.innerHTML = '暂无背景'; } @@ -367,14 +375,21 @@ function confirmCrop() { // 保存聊天背景 function saveChatBackground(imageData) { const settings = getSettings(); - const contact = settings.contacts[currentChatIndex]; + let chatTarget = null; - if (!contact) { + if (isInGroupChat()) { + const groupIndex = getCurrentGroupIndex(); + chatTarget = settings.groupChats?.[groupIndex]; + } else { + chatTarget = settings.contacts[currentChatIndex]; + } + + if (!chatTarget) { showToast('保存失败', '⚠️'); return; } - contact.chatBackground = imageData; + chatTarget.chatBackground = imageData; requestSave(); // 立即应用背景 @@ -385,11 +400,18 @@ function saveChatBackground(imageData) { // 清除聊天背景 function clearChatBackground() { const settings = getSettings(); - const contact = settings.contacts[currentChatIndex]; + let chatTarget = null; - if (!contact) return; + if (isInGroupChat()) { + const groupIndex = getCurrentGroupIndex(); + chatTarget = settings.groupChats?.[groupIndex]; + } else { + chatTarget = settings.contacts[currentChatIndex]; + } - delete contact.chatBackground; + if (!chatTarget) return; + + delete chatTarget.chatBackground; requestSave(); // 清除背景 @@ -428,3 +450,15 @@ export function loadContactBackground(contactIndex) { applyChatBackground(null); } } + +// 加载群聊背景(openGroupChat时调用) +export function loadGroupBackground(groupIndex) { + const settings = getSettings(); + const groupChat = settings.groupChats?.[groupIndex]; + + if (groupChat?.chatBackground) { + applyChatBackground(groupChat.chatBackground); + } else { + applyChatBackground(null); + } +} diff --git a/config.js b/config.js index 4e53b8c..e434ba1 100644 --- a/config.js +++ b/config.js @@ -276,7 +276,7 @@ export const defaultSettings = { 【回复要求】 - 根据聊天内容自然判断哪些角色会回复,不需要所有人都说话 -- 语音消息格式:[角色名]: [语音:内容] +- 语音消息格式:[角色名]: [语音:实际说的话](是具体话语,不是声音描述) - 语音消息必须独立发送 示例: @@ -302,7 +302,7 @@ export const authorNoteTemplate = `【可乐不加冰 消息格式指南】每 【消息类型格式】 - 普通消息:直接写内容 -- 语音消息:[语音:语音内容文字] +- 语音消息:[语音:实际说的话](是具体话语,不是声音描述!) - 照片/图片/视频/自拍:[照片:媒体描述] - 表情包回复:[表情:序号或名称] - 音乐分享:[音乐:歌名] diff --git a/emoji-panel.js b/emoji-panel.js index 5aee4b7..0bf4fe3 100644 --- a/emoji-panel.js +++ b/emoji-panel.js @@ -171,12 +171,30 @@ export function refreshEmojiGrid() { const content = document.getElementById('wechat-emoji-content'); if (!content) return; + const settings = getSettings(); + const userStickers = Array.isArray(settings.stickers) ? settings.stickers : []; + let html = ''; + // 我的表情区域(用户添加的表情) + html += '
我的表情
'; + html += '
'; + html += ``; + userStickers.forEach((sticker, index) => { + html += ` +
+
+ ${sticker.name || ''} +
+
删除
+
+ `; + }); + html += '
'; + // 默认表情区域 html += '
默认表情
'; html += '
'; - html += ``; DEFAULT_STICKERS.forEach((sticker, index) => { const url = getCatboxUrl(sticker.id, sticker.ext); html += ` @@ -192,6 +210,11 @@ export function refreshEmojiGrid() { // 绑定添加按钮事件 document.getElementById('wechat-emoji-add-btn')?.addEventListener('click', showAddStickerDialog); + // 绑定用户表情左滑删除 + content.querySelectorAll('.wechat-emoji-swipe-container').forEach(container => { + setupSwipeToDelete(container); + }); + // 绑定默认表情点击事件 content.querySelectorAll('.wechat-emoji-default-item').forEach(item => { item.addEventListener('click', () => { @@ -201,6 +224,85 @@ export function refreshEmojiGrid() { }); } +// 设置左滑删除 +function setupSwipeToDelete(container) { + const item = container.querySelector('.wechat-emoji-user-item'); + const index = parseInt(container.dataset.userIndex); + let startX = 0; + let currentX = 0; + let isDragging = false; + let isOpen = false; + + const deleteThreshold = 50; // 滑动超过此距离触发删除状态 + + function onStart(e) { + // 如果已经打开,点击任意位置关闭 + if (isOpen) { + resetPosition(); + return; + } + isDragging = true; + startX = e.type.includes('mouse') ? e.clientX : e.touches[0].clientX; + item.style.transition = 'none'; + } + + function onMove(e) { + if (!isDragging) return; + currentX = e.type.includes('mouse') ? e.clientX : e.touches[0].clientX; + let diff = currentX - startX; + // 只允许左滑 + if (diff > 0) diff = 0; + // 限制最大滑动距离 + if (diff < -deleteThreshold - 20) diff = -deleteThreshold - 20; + item.style.transform = `translateX(${diff}px)`; + } + + function onEnd() { + if (!isDragging) return; + isDragging = false; + item.style.transition = 'transform 0.2s'; + + const diff = currentX - startX; + if (diff < -deleteThreshold) { + // 显示删除按钮 + item.style.transform = `translateX(-${deleteThreshold}px)`; + isOpen = true; + } else if (Math.abs(diff) < 5 && !isOpen) { + // 点击发送 + sendUserSticker(index); + } else { + resetPosition(); + } + } + + function resetPosition() { + item.style.transition = 'transform 0.2s'; + item.style.transform = 'translateX(0)'; + isOpen = false; + } + + // 触摸事件 + container.addEventListener('touchstart', onStart, { passive: true }); + container.addEventListener('touchmove', onMove, { passive: true }); + container.addEventListener('touchend', onEnd); + + // 鼠标事件 + container.addEventListener('mousedown', onStart); + container.addEventListener('mousemove', onMove); + container.addEventListener('mouseup', onEnd); + container.addEventListener('mouseleave', () => { + if (isDragging) { + isDragging = false; + resetPosition(); + } + }); + + // 删除按钮点击 + container.querySelector('.wechat-emoji-delete-bg').addEventListener('click', () => { + deleteSticker(index); + }); +} + // 显示添加表情对话框 function showAddStickerDialog() { const choice = prompt( diff --git a/floating-ball.js b/floating-ball.js index 2211a0c..5b47c8b 100644 --- a/floating-ball.js +++ b/floating-ball.js @@ -18,46 +18,27 @@ let floatingBallState = { hasMoved: false }; -// SVG 图标 - 渐变圆圈和猫咪 +// SVG 图标 - 简约猫咪(只有耳朵和胡须) const FLOATING_BALL_SVG = ` - + - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + `; @@ -95,14 +76,14 @@ function restorePosition(ball) { if (savedPos && savedPos.x !== undefined && savedPos.y !== undefined) { // 确保位置在视口内 - const maxX = window.innerWidth - 60; - const maxY = window.innerHeight - 60; + const maxX = window.innerWidth - 30; + const maxY = window.innerHeight - 30; floatingBallState.currentX = Math.min(Math.max(0, savedPos.x), maxX); floatingBallState.currentY = Math.min(Math.max(0, savedPos.y), maxY); } else { // 默认位置:右侧中间 - floatingBallState.currentX = window.innerWidth - 80; - floatingBallState.currentY = (window.innerHeight - 60) / 2; + floatingBallState.currentX = window.innerWidth - 40; + floatingBallState.currentY = (window.innerHeight - 30) / 2; } ball.style.left = floatingBallState.currentX + 'px'; @@ -133,8 +114,8 @@ function bindFloatingBallEvents(ball) { // 窗口大小变化时调整位置 window.addEventListener('resize', () => { - const maxX = window.innerWidth - 60; - const maxY = window.innerHeight - 60; + const maxX = window.innerWidth - 30; + const maxY = window.innerHeight - 30; if (floatingBallState.currentX > maxX) { floatingBallState.currentX = maxX; ball.style.left = floatingBallState.currentX + 'px'; @@ -200,8 +181,8 @@ function onDragMove(e) { let newY = floatingBallState.initialY + deltaY; // 限制在视口内 - const maxX = window.innerWidth - 60; - const maxY = window.innerHeight - 60; + const maxX = window.innerWidth - 30; + const maxY = window.innerHeight - 30; newX = Math.min(Math.max(0, newX), maxX); newY = Math.min(Math.max(0, newY), maxY); diff --git a/gift.js b/gift.js index 3246388..ae087d9 100644 --- a/gift.js +++ b/gift.js @@ -58,11 +58,17 @@ let currentCategory = 'normal'; let selectedGift = null; let selectedTarget = 'character'; // 'character' 送角色 | 'user' 送用户 +// 多选模式状态 +let multiSelectMode = false; +let selectedGifts = []; // 多选时存储多个礼物 + // 显示礼物页面 export function showGiftPage() { currentCategory = 'normal'; selectedGift = null; selectedTarget = 'character'; + multiSelectMode = false; + selectedGifts = []; const page = document.getElementById('wechat-gift-page'); if (page) { @@ -85,9 +91,38 @@ function renderGiftContent() { const gridContainer = document.getElementById('wechat-gift-grid'); const sendBtn = document.getElementById('wechat-gift-send'); const targetContainer = document.getElementById('wechat-gift-target'); + const headerEl = document.querySelector('.wechat-gift-header'); if (!tabsContainer || !gridContainer) return; + // 渲染多选按钮(仅情趣玩具分类显示) + let multiSelectBtn = document.getElementById('wechat-gift-multi-select-btn'); + if (currentCategory === 'toy') { + if (!multiSelectBtn && headerEl) { + multiSelectBtn = document.createElement('button'); + multiSelectBtn.id = 'wechat-gift-multi-select-btn'; + multiSelectBtn.className = 'wechat-gift-multi-select-btn'; + headerEl.appendChild(multiSelectBtn); + } + if (multiSelectBtn) { + if (multiSelectMode) { + multiSelectBtn.textContent = selectedGifts.length > 0 ? `完成(${selectedGifts.length})` : '取消'; + multiSelectBtn.classList.add('active'); + } else { + multiSelectBtn.textContent = '多选'; + multiSelectBtn.classList.remove('active'); + } + multiSelectBtn.onclick = toggleMultiSelectMode; + } + } else { + // 非情趣玩具分类,移除多选按钮并重置状态 + if (multiSelectBtn) { + multiSelectBtn.remove(); + } + multiSelectMode = false; + selectedGifts = []; + } + // 渲染送礼目标选择(仅情趣玩具显示) if (targetContainer) { if (currentCategory === 'toy') { @@ -132,6 +167,11 @@ function renderGiftContent() { tab.addEventListener('click', () => { currentCategory = tab.dataset.category; selectedGift = null; + // 切换分类时不重置多选,只在非toy分类时重置 + if (tab.dataset.category !== 'toy') { + multiSelectMode = false; + selectedGifts = []; + } renderGiftContent(); }); }); @@ -140,13 +180,21 @@ function renderGiftContent() { const category = GIFT_CATEGORIES[currentCategory]; let gridHtml = ''; category.items.forEach(item => { - const selectedClass = selectedGift?.id === item.id ? 'selected' : ''; + // 多选模式下检查是否在selectedGifts中 + const isSelectedInMulti = multiSelectMode && selectedGifts.some(g => g.id === item.id); + // 单选模式下检查是否是selectedGift + const isSelectedSingle = !multiSelectMode && selectedGift?.id === item.id; + const selectedClass = (isSelectedInMulti || isSelectedSingle) ? 'selected' : ''; const controlBadge = item.hasControl ? '可控' : ''; + // 多选模式下显示勾选标记 + const checkMark = isSelectedInMulti ? '' : ''; + gridHtml += `
${item.emoji} ${item.name} ${controlBadge} + ${checkMark}
`; }); @@ -156,14 +204,39 @@ function renderGiftContent() { gridContainer.querySelectorAll('.wechat-gift-item').forEach(item => { item.addEventListener('click', () => { const giftId = item.dataset.giftId; - selectedGift = category.items.find(g => g.id === giftId); + const gift = category.items.find(g => g.id === giftId); + + if (multiSelectMode && currentCategory === 'toy') { + // 多选模式:只能选择有控制功能的玩具 + if (!gift.hasControl) { + showToast('该玩具不支持多选控制'); + return; + } + // 切换选中状态 + const existingIndex = selectedGifts.findIndex(g => g.id === giftId); + if (existingIndex >= 0) { + selectedGifts.splice(existingIndex, 1); + } else { + if (selectedGifts.length >= 5) { + showToast('最多选择5个玩具'); + return; + } + selectedGifts.push(gift); + } + } else { + // 单选模式 + selectedGift = gift; + } renderGiftContent(); }); }); // 更新发送按钮状态 if (sendBtn) { - if (selectedGift) { + if (multiSelectMode && selectedGifts.length > 0) { + sendBtn.disabled = false; + sendBtn.textContent = `送出 ${selectedGifts.length} 件玩具`; + } else if (!multiSelectMode && selectedGift) { sendBtn.disabled = false; sendBtn.textContent = `送出 ${selectedGift.name}`; } else { @@ -173,9 +246,27 @@ function renderGiftContent() { } } +// 切换多选模式 +function toggleMultiSelectMode() { + if (multiSelectMode && selectedGifts.length > 0) { + // 如果已有选择,点击"完成"按钮触发发送 + sendGift(); + } else { + // 切换模式 + multiSelectMode = !multiSelectMode; + if (!multiSelectMode) { + selectedGifts = []; + } + selectedGift = null; + renderGiftContent(); + } +} + // 发送礼物 export async function sendGift() { - if (!selectedGift) { + // 检查是否有选择 + const isMulti = multiSelectMode && selectedGifts.length > 0; + if (!isMulti && !selectedGift) { showToast('请选择礼物'); return; } @@ -189,7 +280,6 @@ export async function sendGift() { const contact = settings.contacts[currentChatIndex]; if (!contact) return; - const gift = selectedGift; const isToy = currentCategory === 'toy'; const target = isToy ? selectedTarget : null; @@ -201,6 +291,150 @@ export async function sendGift() { const customDesc = descInput?.value?.trim() || ''; if (descInput) descInput.value = ''; + // 保存到聊天历史 + const now = new Date(); + const timeStr = `${now.getFullYear()}-${(now.getMonth()+1).toString().padStart(2,'0')}-${now.getDate().toString().padStart(2,'0')} ${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`; + + if (!contact.chatHistory) { + contact.chatHistory = []; + } + + // 多选模式处理 + if (isMulti) { + const giftsToSend = [...selectedGifts]; + const giftNames = giftsToSend.map(g => g.name).join('、'); + const giftEmojis = giftsToSend.map(g => g.emoji).join(' '); + const targetText = target === 'character' ? '送TA' : '送自己'; + const giftMessage = `[情趣礼物套装] ${giftEmojis} ${giftNames}(${targetText})${customDesc ? ` - ${customDesc}` : ''}`; + + const giftRecord = { + role: 'user', + content: giftMessage, + time: timeStr, + timestamp: Date.now(), + isGift: true, + isMultiGift: true, + giftInfo: { + gifts: giftsToSend.map(g => ({ + id: g.id, + name: g.name, + emoji: g.emoji, + desc: g.desc, + hasControl: g.hasControl, + hasShock: g.hasShock + })), + isToy: true, + target: target, + customDesc: customDesc + } + }; + + contact.chatHistory.push(giftRecord); + + // 显示礼物消息(多选版本) + appendMultiGiftMessage('user', giftsToSend, customDesc, contact, target); + + contact.lastMessage = giftMessage; + + // 添加到待配送列表(作为一个多玩具组合) + if (!contact.pendingGifts) { + contact.pendingGifts = []; + } + + const multiPendingGift = { + isMulti: true, + toys: giftsToSend.map(g => ({ + giftId: g.id, + giftName: g.name, + giftEmoji: g.emoji, + giftDesc: g.desc, + hasControl: g.hasControl, + hasShock: g.hasShock || false + })), + target: target, + startMessageCount: contact.chatHistory.length, + deliveredAt: null, + isDelivered: false, + isUsing: false, + timestamp: Date.now() + }; + + contact.pendingGifts.push(multiPendingGift); + + // 显示配送中弹窗 + setTimeout(() => { + showNotificationBanner('快递', `您选择的${giftsToSend.length}件商品正在配送中~`, 4000); + }, 500); + + // 2秒后弹出加急配送弹窗 + setTimeout(() => { + showExpressDeliveryModal(multiPendingGift, contact); + }, 2000); + + requestSave(); + refreshChatList(); + + // 显示打字指示器 + showTypingIndicator(contact); + + // 构建给AI的提示 + const targetTextAI = target === 'character' ? '你' : '用户'; + const aiPrompt = `[系统提示:用户刚刚购买了一套情趣玩具套装,包括:${giftNames},准备送给${targetTextAI}使用。商品正在配送中,预计很快就会送达。${customDesc ? `用户附言:${customDesc}` : ''} + +请根据你的角色性格,对这套即将到来的礼物做出反应: +- 如果是送给你的:可以表现出期待、害羞、紧张、好奇等情绪,可以问用户打算怎么用这些 +- 如果是送给用户的:可以表现出好奇、调侃、期待看到用户反应等 +- 根据你的人设和与用户的关系,反应可以是含蓄的、热情的、或者假装矜持的 +- 回复不要太短,请展现角色的内心活动和情绪变化 + +【重要】只能输出纯文字消息,禁止输出任何特殊格式标签]`; + + try { + const aiResponse = await callAI(contact, aiPrompt); + hideTypingIndicator(); + + if (aiResponse) { + const aiMessages = splitAIMessages(aiResponse); + + for (const msg of aiMessages) { + let reply = msg.trim(); + reply = reply.replace(/<\s*meme\s*>[\s\S]*?<\s*\/\s*meme\s*>/gi, '').trim(); + reply = reply.replace(/\[.*?\]/g, '').trim(); + reply = reply.replace(/([^)]*)/g, '').trim(); + reply = reply.replace(/\([^)]*\)/g, '').trim(); + + if (reply) { + contact.chatHistory.push({ + role: 'assistant', + content: reply, + time: timeStr, + timestamp: Date.now() + }); + appendMessage('assistant', reply, contact); + } + } + + const lastMsg = aiMessages[aiMessages.length - 1]?.trim()?.replace(/\[.*?\]/g, '').trim(); + if (lastMsg) { + contact.lastMessage = lastMsg.length > 20 ? lastMsg.substring(0, 20) + '...' : lastMsg; + } + requestSave(); + refreshChatList(); + } + } catch (err) { + hideTypingIndicator(); + console.error('[可乐] 礼物AI回复失败:', err); + } + + // 重置多选状态 + multiSelectMode = false; + selectedGifts = []; + return; + } + + // 单选模式处理(原有逻辑) + const gift = selectedGift; + // 构建礼物消息 let giftMessage; if (isToy) { @@ -210,14 +444,6 @@ export async function sendGift() { giftMessage = `[礼物] ${gift.emoji} ${gift.name}${customDesc ? ` - ${customDesc}` : ''}`; } - // 保存到聊天历史 - const now = new Date(); - const timeStr = `${now.getFullYear()}-${(now.getMonth()+1).toString().padStart(2,'0')}-${now.getDate().toString().padStart(2,'0')} ${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`; - - if (!contact.chatHistory) { - contact.chatHistory = []; - } - const giftRecord = { role: 'user', content: giftMessage, @@ -271,6 +497,11 @@ export async function sendGift() { setTimeout(() => { showNotificationBanner('快递', '您选择的商品正在配送中~', 4000); }, 500); + + // 2秒后弹出加急配送弹窗 + setTimeout(() => { + showExpressDeliveryModal(pendingGift, contact); + }, 2000); } requestSave(); @@ -391,6 +622,46 @@ export function checkGiftDelivery(contact) { } } +// 显示加急配送弹窗 +export function showExpressDeliveryModal(gift, contact) { + const modal = document.getElementById('wechat-express-delivery-modal'); + if (!modal) return; + + modal.classList.remove('hidden'); + + const yesBtn = document.getElementById('wechat-express-yes'); + const noBtn = document.getElementById('wechat-express-no'); + + const handleYes = () => { + modal.classList.add('hidden'); + yesBtn.removeEventListener('click', handleYes); + noBtn.removeEventListener('click', handleNo); + + // 标记为已送达 + gift.isDelivered = true; + gift.deliveredAt = Date.now(); + requestSave(); + + // 显示送达通知 + showNotificationBanner('快递', '您的商品已送达~', 3000); + + // 2秒后弹出"是否开始玩"弹窗 + setTimeout(() => { + showGiftArrivalModal(gift, contact); + }, 2000); + }; + + const handleNo = () => { + modal.classList.add('hidden'); + yesBtn.removeEventListener('click', handleYes); + noBtn.removeEventListener('click', handleNo); + // 什么都不做,走原有的25条消息检测逻辑 + }; + + yesBtn.addEventListener('click', handleYes); + noBtn.addEventListener('click', handleNo); +} + // 显示礼物送达询问弹窗 export function showGiftArrivalModal(gift, contact) { const modal = document.getElementById('wechat-gift-arrival-modal'); @@ -398,10 +669,16 @@ export function showGiftArrivalModal(gift, contact) { if (!modal || !bodyEl) return; - bodyEl.innerHTML = `您的 ${gift.giftName} 已送达,您要现在开始玩吗?`; + // 支持多选礼物 + if (gift.isMulti) { + const toyNames = gift.toys.map(t => t.giftName).join('、'); + bodyEl.innerHTML = `您的 ${toyNames} 已送达,您要现在开始玩吗?`; + } else { + bodyEl.innerHTML = `您的 ${gift.giftName} 已送达,您要现在开始玩吗?`; + } // 存储当前礼物信息 - modal.dataset.giftId = gift.giftId; + modal.dataset.giftId = gift.giftId || (gift.isMulti ? 'multi' : ''); modal.dataset.giftTimestamp = gift.timestamp; modal.classList.remove('hidden'); @@ -494,6 +771,53 @@ export function appendGiftMessage(role, gift, isToy, customDesc, contact, target messagesContainer.scrollTop = messagesContainer.scrollHeight; } +// 添加多选礼物消息到界面 +export function appendMultiGiftMessage(role, gifts, customDesc, contact, target = null) { + const messagesContainer = document.getElementById('wechat-chat-messages'); + if (!messagesContainer) return; + + const messageDiv = document.createElement('div'); + messageDiv.className = `wechat-message ${role === 'user' ? 'self' : ''}`; + + const firstChar = contact?.name ? contact.name.charAt(0) : '?'; + + // 获取用户头像 + let avatarContent; + if (role === 'user') { + const settings = getSettings(); + if (settings.userAvatar) { + avatarContent = ``; + } else { + avatarContent = '我'; + } + } else { + avatarContent = contact?.avatar + ? `` + : firstChar; + } + + const giftEmojis = gifts.map(g => g.emoji).join(' '); + const giftNames = gifts.map(g => escapeHtml(g.name)).join('、'); + const giftTypeLabel = target === 'character' ? '情趣套装·送TA' : '情趣套装·送自己'; + + messageDiv.innerHTML = ` +
${avatarContent}
+
+
+
${giftEmojis}
+
+
${giftNames}
+ ${customDesc ? `
${escapeHtml(customDesc)}
` : ''} +
+
${giftTypeLabel}
+
+
+ `; + + messagesContainer.appendChild(messageDiv); + messagesContainer.scrollTop = messagesContainer.scrollHeight; +} + // 获取礼物分类数据(供其他模块使用) export function getGiftCategories() { return GIFT_CATEGORIES; diff --git a/group-chat.js b/group-chat.js index a3940d8..cf4e50b 100644 --- a/group-chat.js +++ b/group-chat.js @@ -12,6 +12,7 @@ import { getSTChatContext, HAKIMI_HEADER } from './ai.js'; import { playMusic as kugouPlayMusic } from './music.js'; import { showMessageMenu } from './message-menu.js'; import { showGroupRedPacketDetail } from './group-red-packet.js'; +import { loadGroupBackground } from './chat-background.js'; // 当前群聊的索引 export let currentGroupChatIndex = -1; @@ -631,6 +632,9 @@ export function openGroupChat(groupIndex) { messagesContainer.dataset.isGroup = 'true'; messagesContainer.dataset.groupIndex = groupIndex; console.log('[可乐] 群聊标记已设置:', { isGroup: messagesContainer.dataset.isGroup, groupIndex: messagesContainer.dataset.groupIndex }); + + // 加载群聊背景 + loadGroupBackground(groupIndex); } // 渲染群聊历史 @@ -1757,7 +1761,7 @@ function buildSingleCharacterPrompt(member, groupChat, members, silentCharacters 4. 保持角色性格特点,回复要符合你的人设 5. 可以使用表情符号 6. 必须回复至少一条消息,哪怕只是"嗯"、"哦"、表情符号等简短回应 -7. 语音消息格式:[语音:内容] +7. 语音消息格式:[语音:实际说的话](是你说的具体话语,不是声音描述!) 8. 语音消息必须独立发送,不能和其他消息混在一起 【交错显示机制】 diff --git a/main.js b/main.js index e5933bc..a9ffa8b 100644 --- a/main.js +++ b/main.js @@ -538,8 +538,21 @@ function onMinimizedDragMove(e) { minimizeState.hasMoved = true; } - const newLeft = minimizeState.initialLeft + deltaX; - const newTop = minimizeState.initialTop + deltaY; + // 直接使用当前触摸/鼠标位置减去元素尺寸的一半,让元素中心跟随手指 + const scale = 0.25; + const rect = phone.getBoundingClientRect(); + const scaledWidth = rect.width; + const scaledHeight = rect.height; + + // 计算新位置:让缩小后的手机中心跟随手指 + let newLeft = clientX - scaledWidth / 2; + let newTop = clientY - scaledHeight / 2; + + // 限制在视口内 + const maxX = window.innerWidth - scaledWidth; + const maxY = window.innerHeight - scaledHeight; + newLeft = Math.min(Math.max(0, newLeft), maxX); + newTop = Math.min(Math.max(0, newTop), maxY); phone.style.left = newLeft + 'px'; phone.style.top = newTop + 'px'; diff --git a/phone-html.js b/phone-html.js index 059e675..669d5fe 100644 --- a/phone-html.js +++ b/phone-html.js @@ -1312,6 +1312,17 @@ function generateGiftPageHTML() {
+ + +