Add files via upload

This commit is contained in:
Cola-Echo
2025-12-30 01:25:12 +08:00
committed by GitHub
parent 1b3ec1d43f
commit c97f2958ce
12 changed files with 1340 additions and 106 deletions

2
ai.js
View File

@@ -566,7 +566,7 @@ export function buildSystemPrompt(contact, options = {}) {
- 回复要符合角色性格 - 回复要符合角色性格
- 不要使用任何格式标记,直接输出对话内容 - 不要使用任何格式标记,直接输出对话内容
- 【禁止】不要使用小括号描述动作/表情/语气!如"(笑)"、"(害羞地说)"等,这是文字聊天不是小说! - 【禁止】不要使用小括号描述动作/表情/语气!如"(笑)"、"(害羞地说)"等,这是文字聊天不是小说!
- 如果想发送语音消息,使用格式:[语音:语音内容] - 如果想发送语音消息,使用格式:[语音:实际说的话](注意:是你说的具体话语,不是声音描述!错误示例:[语音:声音低沉带笑] 正确示例:[语音:宝贝早上好呀]
- 如果想发送照片,使用格式:[照片:照片描述] - 如果想发送照片,使用格式:[照片:照片描述]
- 【绝对禁止】语音/照片消息必须单独一条!格式前后不能有任何其他文字! - 【绝对禁止】语音/照片消息必须单独一条!格式前后不能有任何其他文字!
错误示例:叫得这么甜 [照片:xxx] ← 错误! 错误示例:叫得这么甜 [照片:xxx] ← 错误!

View File

@@ -1,12 +1,13 @@
/** /**
* 聊天背景功能模块 * 聊天背景功能模块
* 支持每个联系人独立设置背景,含图片裁剪功能 * 支持每个联系人/群聊独立设置背景,含图片裁剪功能
*/ */
import { requestSave } from './save-manager.js'; import { requestSave } from './save-manager.js';
import { getSettings } from './config.js'; import { getSettings } from './config.js';
import { showToast } from './toast.js'; import { showToast } from './toast.js';
import { currentChatIndex } from './chat.js'; import { currentChatIndex } from './chat.js';
import { isInGroupChat, getCurrentGroupIndex } from './group-chat.js';
// 裁剪器状态 // 裁剪器状态
let cropperState = { let cropperState = {
@@ -76,12 +77,19 @@ export function showChatBgPanel() {
if (!panel || !preview) return; if (!panel || !preview) return;
// 获取当前联系人的背景 // 获取当前聊天对象的背景(支持群聊和私聊)
const settings = getSettings(); const settings = getSettings();
const contact = settings.contacts[currentChatIndex]; let chatTarget = null;
if (contact?.chatBackground) { if (isInGroupChat()) {
preview.innerHTML = `<img src="${contact.chatBackground}" alt="背景预览">`; const groupIndex = getCurrentGroupIndex();
chatTarget = settings.groupChats?.[groupIndex];
} else {
chatTarget = settings.contacts[currentChatIndex];
}
if (chatTarget?.chatBackground) {
preview.innerHTML = `<img src="${chatTarget.chatBackground}" alt="背景预览">`;
} else { } else {
preview.innerHTML = '<span class="wechat-chat-bg-placeholder">暂无背景</span>'; preview.innerHTML = '<span class="wechat-chat-bg-placeholder">暂无背景</span>';
} }
@@ -367,14 +375,21 @@ function confirmCrop() {
// 保存聊天背景 // 保存聊天背景
function saveChatBackground(imageData) { function saveChatBackground(imageData) {
const settings = getSettings(); 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('保存失败', '⚠️'); showToast('保存失败', '⚠️');
return; return;
} }
contact.chatBackground = imageData; chatTarget.chatBackground = imageData;
requestSave(); requestSave();
// 立即应用背景 // 立即应用背景
@@ -385,11 +400,18 @@ function saveChatBackground(imageData) {
// 清除聊天背景 // 清除聊天背景
function clearChatBackground() { function clearChatBackground() {
const settings = getSettings(); 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(); requestSave();
// 清除背景 // 清除背景
@@ -428,3 +450,15 @@ export function loadContactBackground(contactIndex) {
applyChatBackground(null); applyChatBackground(null);
} }
} }
// 加载群聊背景openGroupChat时调用
export function loadGroupBackground(groupIndex) {
const settings = getSettings();
const groupChat = settings.groupChats?.[groupIndex];
if (groupChat?.chatBackground) {
applyChatBackground(groupChat.chatBackground);
} else {
applyChatBackground(null);
}
}

View File

@@ -276,7 +276,7 @@ export const defaultSettings = {
【回复要求】 【回复要求】
- 根据聊天内容自然判断哪些角色会回复,不需要所有人都说话 - 根据聊天内容自然判断哪些角色会回复,不需要所有人都说话
- 语音消息格式:[角色名]: [语音:内容] - 语音消息格式:[角色名]: [语音:实际说的话](是具体话语,不是声音描述)
- 语音消息必须独立发送 - 语音消息必须独立发送
示例: 示例:
@@ -302,7 +302,7 @@ export const authorNoteTemplate = `【可乐不加冰 消息格式指南】每
【消息类型格式】 【消息类型格式】
- 普通消息:直接写内容 - 普通消息:直接写内容
- 语音消息:[语音:语音内容文字] - 语音消息:[语音:实际说的话](是具体话语,不是声音描述!)
- 照片/图片/视频/自拍:[照片:媒体描述] - 照片/图片/视频/自拍:[照片:媒体描述]
- 表情包回复:[表情:序号或名称] - 表情包回复:[表情:序号或名称]
- 音乐分享:[音乐:歌名] - 音乐分享:[音乐:歌名]

View File

@@ -171,12 +171,30 @@ export function refreshEmojiGrid() {
const content = document.getElementById('wechat-emoji-content'); const content = document.getElementById('wechat-emoji-content');
if (!content) return; if (!content) return;
const settings = getSettings();
const userStickers = Array.isArray(settings.stickers) ? settings.stickers : [];
let html = ''; let html = '';
// 我的表情区域(用户添加的表情)
html += '<div class="wechat-emoji-section-title">我的表情</div>';
html += '<div class="wechat-emoji-grid" id="wechat-emoji-user-grid">';
html += `<button class="wechat-emoji-add" id="wechat-emoji-add-btn">+</button>`;
userStickers.forEach((sticker, index) => {
html += `
<div class="wechat-emoji-swipe-container" data-user-index="${index}">
<div class="wechat-emoji-item wechat-emoji-user-item" title="${sticker.name || '自定义表情'}">
<img src="${sticker.url}" alt="${sticker.name || ''}" loading="lazy">
</div>
<div class="wechat-emoji-delete-bg">删除</div>
</div>
`;
});
html += '</div>';
// 默认表情区域 // 默认表情区域
html += '<div class="wechat-emoji-section-title">默认表情</div>'; html += '<div class="wechat-emoji-section-title">默认表情</div>';
html += '<div class="wechat-emoji-grid" id="wechat-emoji-default-grid">'; html += '<div class="wechat-emoji-grid" id="wechat-emoji-default-grid">';
html += `<button class="wechat-emoji-add" id="wechat-emoji-add-btn">+</button>`;
DEFAULT_STICKERS.forEach((sticker, index) => { DEFAULT_STICKERS.forEach((sticker, index) => {
const url = getCatboxUrl(sticker.id, sticker.ext); const url = getCatboxUrl(sticker.id, sticker.ext);
html += ` html += `
@@ -192,6 +210,11 @@ export function refreshEmojiGrid() {
// 绑定添加按钮事件 // 绑定添加按钮事件
document.getElementById('wechat-emoji-add-btn')?.addEventListener('click', showAddStickerDialog); 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 => { content.querySelectorAll('.wechat-emoji-default-item').forEach(item => {
item.addEventListener('click', () => { 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() { function showAddStickerDialog() {
const choice = prompt( const choice = prompt(

View File

@@ -18,46 +18,27 @@ let floatingBallState = {
hasMoved: false hasMoved: false
}; };
// SVG 图标 - 渐变圆圈和猫咪 // SVG 图标 - 简约猫咪(只有耳朵和胡须)
const FLOATING_BALL_SVG = ` const FLOATING_BALL_SVG = `
<svg viewBox="0 0 100 100" width="60" height="60" class="floating-ball-svg"> <svg viewBox="0 0 100 100" width="30" height="30" class="floating-ball-svg">
<defs> <defs>
<linearGradient id="ring-gradient" x1="0%" y1="0%" x2="100%" y2="100%"> <linearGradient id="bg-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#FFB6C1;stop-opacity:1" /> <stop offset="0%" style="stop-color:#FFFFFF;stop-opacity:1" />
<stop offset="50%" style="stop-color:#FFC0CB;stop-opacity:1" /> <stop offset="100%" style="stop-color:#FFE4EC;stop-opacity:1" />
<stop offset="100%" style="stop-color:#FFEFD5;stop-opacity:1" />
</linearGradient> </linearGradient>
</defs> </defs>
<!-- 渐变圆圈 --> <!-- 圆形背景 -->
<circle cx="50" cy="50" r="44" fill="none" stroke="url(#ring-gradient)" stroke-width="5" stroke-linecap="round"/> <circle cx="50" cy="50" r="48" fill="url(#bg-gradient)"/>
<!-- 猫咪头部轮廓 --> <!-- 左耳 -->
<g transform="translate(50, 52)" stroke="#333" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path d="M18,45 L28,12 L45,38" fill="#FFB6C1" stroke="#333" stroke-width="2" stroke-linejoin="round"/>
<!-- 头部 --> <!-- 右耳 -->
<ellipse cx="0" cy="0" rx="22" ry="18"/> <path d="M82,45 L72,12 L55,38" fill="#FFB6C1" stroke="#333" stroke-width="2" stroke-linejoin="round"/>
<!-- 左耳 --> <!-- 胡须 -->
<path d="M-18,-12 L-22,-24 L-12,-16"/> <g stroke="#333" stroke-width="2" stroke-linecap="round">
<!-- 右耳 --> <path d="M8,52 L35,56"/>
<path d="M18,-12 L22,-24 L12,-16"/> <path d="M8,64 L35,62"/>
<!-- 内耳(粉色填充) --> <path d="M92,52 L65,56"/>
<path d="M-17,-14 L-19,-21 L-13,-16" fill="#FFB6C1" stroke="none"/> <path d="M92,64 L65,62"/>
<path d="M17,-14 L19,-21 L13,-16" fill="#FFB6C1" stroke="none"/>
<!-- 左眼 -->
<circle cx="-8" cy="-2" r="3" fill="#333"/>
<!-- 右眼 -->
<circle cx="8" cy="-2" r="3" fill="#333"/>
<!-- 鼻子 -->
<ellipse cx="0" cy="6" rx="2" ry="1.5" fill="#FFB6C1"/>
<!-- 嘴巴 -->
<path d="M0,7 Q-4,12 -8,9" fill="none"/>
<path d="M0,7 Q4,12 8,9" fill="none"/>
<!-- 腮红 -->
<ellipse cx="-14" cy="4" rx="4" ry="3" fill="#FFB6C1" opacity="0.5" stroke="none"/>
<ellipse cx="14" cy="4" rx="4" ry="3" fill="#FFB6C1" opacity="0.5" stroke="none"/>
<!-- 胡须 -->
<path d="M-24,0 L-12,2"/>
<path d="M-24,6 L-12,5"/>
<path d="M24,0 L12,2"/>
<path d="M24,6 L12,5"/>
</g> </g>
</svg> </svg>
`; `;
@@ -95,14 +76,14 @@ function restorePosition(ball) {
if (savedPos && savedPos.x !== undefined && savedPos.y !== undefined) { if (savedPos && savedPos.x !== undefined && savedPos.y !== undefined) {
// 确保位置在视口内 // 确保位置在视口内
const maxX = window.innerWidth - 60; const maxX = window.innerWidth - 30;
const maxY = window.innerHeight - 60; const maxY = window.innerHeight - 30;
floatingBallState.currentX = Math.min(Math.max(0, savedPos.x), maxX); floatingBallState.currentX = Math.min(Math.max(0, savedPos.x), maxX);
floatingBallState.currentY = Math.min(Math.max(0, savedPos.y), maxY); floatingBallState.currentY = Math.min(Math.max(0, savedPos.y), maxY);
} else { } else {
// 默认位置:右侧中间 // 默认位置:右侧中间
floatingBallState.currentX = window.innerWidth - 80; floatingBallState.currentX = window.innerWidth - 40;
floatingBallState.currentY = (window.innerHeight - 60) / 2; floatingBallState.currentY = (window.innerHeight - 30) / 2;
} }
ball.style.left = floatingBallState.currentX + 'px'; ball.style.left = floatingBallState.currentX + 'px';
@@ -133,8 +114,8 @@ function bindFloatingBallEvents(ball) {
// 窗口大小变化时调整位置 // 窗口大小变化时调整位置
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
const maxX = window.innerWidth - 60; const maxX = window.innerWidth - 30;
const maxY = window.innerHeight - 60; const maxY = window.innerHeight - 30;
if (floatingBallState.currentX > maxX) { if (floatingBallState.currentX > maxX) {
floatingBallState.currentX = maxX; floatingBallState.currentX = maxX;
ball.style.left = floatingBallState.currentX + 'px'; ball.style.left = floatingBallState.currentX + 'px';
@@ -200,8 +181,8 @@ function onDragMove(e) {
let newY = floatingBallState.initialY + deltaY; let newY = floatingBallState.initialY + deltaY;
// 限制在视口内 // 限制在视口内
const maxX = window.innerWidth - 60; const maxX = window.innerWidth - 30;
const maxY = window.innerHeight - 60; const maxY = window.innerHeight - 30;
newX = Math.min(Math.max(0, newX), maxX); newX = Math.min(Math.max(0, newX), maxX);
newY = Math.min(Math.max(0, newY), maxY); newY = Math.min(Math.max(0, newY), maxY);

354
gift.js
View File

@@ -58,11 +58,17 @@ let currentCategory = 'normal';
let selectedGift = null; let selectedGift = null;
let selectedTarget = 'character'; // 'character' 送角色 | 'user' 送用户 let selectedTarget = 'character'; // 'character' 送角色 | 'user' 送用户
// 多选模式状态
let multiSelectMode = false;
let selectedGifts = []; // 多选时存储多个礼物
// 显示礼物页面 // 显示礼物页面
export function showGiftPage() { export function showGiftPage() {
currentCategory = 'normal'; currentCategory = 'normal';
selectedGift = null; selectedGift = null;
selectedTarget = 'character'; selectedTarget = 'character';
multiSelectMode = false;
selectedGifts = [];
const page = document.getElementById('wechat-gift-page'); const page = document.getElementById('wechat-gift-page');
if (page) { if (page) {
@@ -85,9 +91,38 @@ function renderGiftContent() {
const gridContainer = document.getElementById('wechat-gift-grid'); const gridContainer = document.getElementById('wechat-gift-grid');
const sendBtn = document.getElementById('wechat-gift-send'); const sendBtn = document.getElementById('wechat-gift-send');
const targetContainer = document.getElementById('wechat-gift-target'); const targetContainer = document.getElementById('wechat-gift-target');
const headerEl = document.querySelector('.wechat-gift-header');
if (!tabsContainer || !gridContainer) return; 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 (targetContainer) {
if (currentCategory === 'toy') { if (currentCategory === 'toy') {
@@ -132,6 +167,11 @@ function renderGiftContent() {
tab.addEventListener('click', () => { tab.addEventListener('click', () => {
currentCategory = tab.dataset.category; currentCategory = tab.dataset.category;
selectedGift = null; selectedGift = null;
// 切换分类时不重置多选只在非toy分类时重置
if (tab.dataset.category !== 'toy') {
multiSelectMode = false;
selectedGifts = [];
}
renderGiftContent(); renderGiftContent();
}); });
}); });
@@ -140,13 +180,21 @@ function renderGiftContent() {
const category = GIFT_CATEGORIES[currentCategory]; const category = GIFT_CATEGORIES[currentCategory];
let gridHtml = ''; let gridHtml = '';
category.items.forEach(item => { 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 ? '<span class="wechat-gift-control-badge">可控</span>' : ''; const controlBadge = item.hasControl ? '<span class="wechat-gift-control-badge">可控</span>' : '';
// 多选模式下显示勾选标记
const checkMark = isSelectedInMulti ? '<span class="wechat-gift-check-mark">✓</span>' : '';
gridHtml += ` gridHtml += `
<div class="wechat-gift-item ${selectedClass}" data-gift-id="${item.id}"> <div class="wechat-gift-item ${selectedClass}" data-gift-id="${item.id}">
<span class="wechat-gift-emoji">${item.emoji}</span> <span class="wechat-gift-emoji">${item.emoji}</span>
<span class="wechat-gift-name">${item.name}</span> <span class="wechat-gift-name">${item.name}</span>
${controlBadge} ${controlBadge}
${checkMark}
</div> </div>
`; `;
}); });
@@ -156,14 +204,39 @@ function renderGiftContent() {
gridContainer.querySelectorAll('.wechat-gift-item').forEach(item => { gridContainer.querySelectorAll('.wechat-gift-item').forEach(item => {
item.addEventListener('click', () => { item.addEventListener('click', () => {
const giftId = item.dataset.giftId; 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(); renderGiftContent();
}); });
}); });
// 更新发送按钮状态 // 更新发送按钮状态
if (sendBtn) { if (sendBtn) {
if (selectedGift) { if (multiSelectMode && selectedGifts.length > 0) {
sendBtn.disabled = false;
sendBtn.textContent = `送出 ${selectedGifts.length} 件玩具`;
} else if (!multiSelectMode && selectedGift) {
sendBtn.disabled = false; sendBtn.disabled = false;
sendBtn.textContent = `送出 ${selectedGift.name}`; sendBtn.textContent = `送出 ${selectedGift.name}`;
} else { } 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() { export async function sendGift() {
if (!selectedGift) { // 检查是否有选择
const isMulti = multiSelectMode && selectedGifts.length > 0;
if (!isMulti && !selectedGift) {
showToast('请选择礼物'); showToast('请选择礼物');
return; return;
} }
@@ -189,7 +280,6 @@ export async function sendGift() {
const contact = settings.contacts[currentChatIndex]; const contact = settings.contacts[currentChatIndex];
if (!contact) return; if (!contact) return;
const gift = selectedGift;
const isToy = currentCategory === 'toy'; const isToy = currentCategory === 'toy';
const target = isToy ? selectedTarget : null; const target = isToy ? selectedTarget : null;
@@ -201,6 +291,150 @@ export async function sendGift() {
const customDesc = descInput?.value?.trim() || ''; const customDesc = descInput?.value?.trim() || '';
if (descInput) descInput.value = ''; 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; let giftMessage;
if (isToy) { if (isToy) {
@@ -210,14 +444,6 @@ export async function sendGift() {
giftMessage = `[礼物] ${gift.emoji} ${gift.name}${customDesc ? ` - ${customDesc}` : ''}`; 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 = { const giftRecord = {
role: 'user', role: 'user',
content: giftMessage, content: giftMessage,
@@ -271,6 +497,11 @@ export async function sendGift() {
setTimeout(() => { setTimeout(() => {
showNotificationBanner('快递', '您选择的商品正在配送中~', 4000); showNotificationBanner('快递', '您选择的商品正在配送中~', 4000);
}, 500); }, 500);
// 2秒后弹出加急配送弹窗
setTimeout(() => {
showExpressDeliveryModal(pendingGift, contact);
}, 2000);
} }
requestSave(); 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) { export function showGiftArrivalModal(gift, contact) {
const modal = document.getElementById('wechat-gift-arrival-modal'); const modal = document.getElementById('wechat-gift-arrival-modal');
@@ -398,10 +669,16 @@ export function showGiftArrivalModal(gift, contact) {
if (!modal || !bodyEl) return; if (!modal || !bodyEl) return;
bodyEl.innerHTML = `您的 <strong>${gift.giftName}</strong> 已送达,您要现在开始玩吗?`; // 支持多选礼物
if (gift.isMulti) {
const toyNames = gift.toys.map(t => t.giftName).join('、');
bodyEl.innerHTML = `您的 <strong>${toyNames}</strong> 已送达,您要现在开始玩吗?`;
} else {
bodyEl.innerHTML = `您的 <strong>${gift.giftName}</strong> 已送达,您要现在开始玩吗?`;
}
// 存储当前礼物信息 // 存储当前礼物信息
modal.dataset.giftId = gift.giftId; modal.dataset.giftId = gift.giftId || (gift.isMulti ? 'multi' : '');
modal.dataset.giftTimestamp = gift.timestamp; modal.dataset.giftTimestamp = gift.timestamp;
modal.classList.remove('hidden'); modal.classList.remove('hidden');
@@ -494,6 +771,53 @@ export function appendGiftMessage(role, gift, isToy, customDesc, contact, target
messagesContainer.scrollTop = messagesContainer.scrollHeight; 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 = `<img src="${settings.userAvatar}" alt="" onerror="this.style.display='none';this.parentElement.textContent='我'">`;
} else {
avatarContent = '我';
}
} else {
avatarContent = contact?.avatar
? `<img src="${contact.avatar}" alt="" onerror="this.style.display='none';this.parentElement.innerHTML='${firstChar}'">`
: 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 = `
<div class="wechat-message-avatar">${avatarContent}</div>
<div class="wechat-message-content">
<div class="wechat-gift-bubble wechat-gift-bubble-toy wechat-gift-bubble-multi">
<div class="wechat-gift-bubble-emoji">${giftEmojis}</div>
<div class="wechat-gift-bubble-info">
<div class="wechat-gift-bubble-name">${giftNames}</div>
${customDesc ? `<div class="wechat-gift-bubble-desc">${escapeHtml(customDesc)}</div>` : ''}
</div>
<div class="wechat-gift-bubble-label">${giftTypeLabel}</div>
</div>
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// 获取礼物分类数据(供其他模块使用) // 获取礼物分类数据(供其他模块使用)
export function getGiftCategories() { export function getGiftCategories() {
return GIFT_CATEGORIES; return GIFT_CATEGORIES;

View File

@@ -12,6 +12,7 @@ import { getSTChatContext, HAKIMI_HEADER } from './ai.js';
import { playMusic as kugouPlayMusic } from './music.js'; import { playMusic as kugouPlayMusic } from './music.js';
import { showMessageMenu } from './message-menu.js'; import { showMessageMenu } from './message-menu.js';
import { showGroupRedPacketDetail } from './group-red-packet.js'; import { showGroupRedPacketDetail } from './group-red-packet.js';
import { loadGroupBackground } from './chat-background.js';
// 当前群聊的索引 // 当前群聊的索引
export let currentGroupChatIndex = -1; export let currentGroupChatIndex = -1;
@@ -631,6 +632,9 @@ export function openGroupChat(groupIndex) {
messagesContainer.dataset.isGroup = 'true'; messagesContainer.dataset.isGroup = 'true';
messagesContainer.dataset.groupIndex = groupIndex; messagesContainer.dataset.groupIndex = groupIndex;
console.log('[可乐] 群聊标记已设置:', { isGroup: messagesContainer.dataset.isGroup, groupIndex: messagesContainer.dataset.groupIndex }); console.log('[可乐] 群聊标记已设置:', { isGroup: messagesContainer.dataset.isGroup, groupIndex: messagesContainer.dataset.groupIndex });
// 加载群聊背景
loadGroupBackground(groupIndex);
} }
// 渲染群聊历史 // 渲染群聊历史
@@ -1757,7 +1761,7 @@ function buildSingleCharacterPrompt(member, groupChat, members, silentCharacters
4. 保持角色性格特点,回复要符合你的人设 4. 保持角色性格特点,回复要符合你的人设
5. 可以使用表情符号 5. 可以使用表情符号
6. 必须回复至少一条消息,哪怕只是"嗯"、"哦"、表情符号等简短回应 6. 必须回复至少一条消息,哪怕只是"嗯"、"哦"、表情符号等简短回应
7. 语音消息格式:[语音:内容] 7. 语音消息格式:[语音:实际说的话](是你说的具体话语,不是声音描述!)
8. 语音消息必须独立发送,不能和其他消息混在一起 8. 语音消息必须独立发送,不能和其他消息混在一起
【交错显示机制】 【交错显示机制】

17
main.js
View File

@@ -538,8 +538,21 @@ function onMinimizedDragMove(e) {
minimizeState.hasMoved = true; 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.left = newLeft + 'px';
phone.style.top = newTop + 'px'; phone.style.top = newTop + 'px';

View File

@@ -1312,6 +1312,17 @@ function generateGiftPageHTML() {
</div> </div>
</div> </div>
<!-- 加急配送弹窗 -->
<div id="wechat-express-delivery-modal" class="wechat-modal hidden">
<div class="wechat-express-modal-content">
<div class="wechat-express-text">您要加急配送吗100元</div>
<div class="wechat-express-actions">
<button class="wechat-express-btn confirm" id="wechat-express-yes">确认</button>
<button class="wechat-express-btn cancel" id="wechat-express-no">取消</button>
</div>
</div>
</div>
<!-- 礼物送达询问弹窗 --> <!-- 礼物送达询问弹窗 -->
<div id="wechat-gift-arrival-modal" class="wechat-modal hidden"> <div id="wechat-gift-arrival-modal" class="wechat-modal hidden">
<div class="wechat-modal-content wechat-gift-arrival-content"> <div class="wechat-modal-content wechat-gift-arrival-content">

368
style.css
View File

@@ -4295,6 +4295,49 @@
transform: scale(0.95); transform: scale(0.95);
} }
/* 左滑删除容器 */
.wechat-emoji-swipe-container {
position: relative;
width: 100%;
aspect-ratio: 1;
overflow: hidden;
border-radius: 8px;
}
.wechat-emoji-swipe-container .wechat-emoji-user-item {
position: relative;
z-index: 2;
width: 100%;
height: 100%;
will-change: transform;
}
.wechat-emoji-swipe-container .wechat-emoji-user-item:active {
transform: none;
}
/* 删除背景 */
.wechat-emoji-delete-bg {
position: absolute;
top: 0;
right: 0;
width: 50px;
height: 100%;
background: #ff3b30;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
z-index: 1;
cursor: pointer;
border-radius: 0 8px 8px 0;
}
.wechat-emoji-delete-bg:active {
background: #d32f2f;
}
.wechat-emoji-item img { .wechat-emoji-item img {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -10561,6 +10604,64 @@
color: #ff6b6b; color: #ff6b6b;
} }
/* 多选按钮 */
.wechat-gift-multi-select-btn {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
padding: 6px 14px;
background: linear-gradient(135deg, #ff6b8a, #ff4d6d);
border: none;
border-radius: 16px;
color: #fff;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 2px 8px rgba(255, 77, 109, 0.3);
}
.wechat-gift-multi-select-btn:hover {
background: linear-gradient(135deg, #ff7a96, #ff5c7a);
box-shadow: 0 3px 12px rgba(255, 77, 109, 0.4);
}
.wechat-gift-multi-select-btn.active {
background: linear-gradient(135deg, #ff8fab, #ff6b8a);
}
/* 多选勾选标记 */
.wechat-gift-check-mark {
position: absolute;
top: 6px;
right: 6px;
width: 20px;
height: 20px;
background: linear-gradient(135deg, #ff6b8a, #ff4d6d);
border-radius: 50%;
color: #fff;
font-size: 12px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 6px rgba(255, 77, 109, 0.4);
animation: checkMarkPop 0.2s ease-out;
}
@keyframes checkMarkPop {
0% {
transform: scale(0);
}
70% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
/* 礼物描述输入 */ /* 礼物描述输入 */
.wechat-gift-desc-row { .wechat-gift-desc-row {
margin-top: 15px; margin-top: 15px;
@@ -11127,6 +11228,52 @@
position: relative; position: relative;
} }
/* ========== 加急配送弹窗样式 ========== */
.wechat-express-modal-content {
background: #ffffff;
border-radius: 12px;
padding: 24px 20px;
width: 280px;
text-align: center;
}
.wechat-express-text {
color: #000000;
font-size: 16px;
line-height: 1.5;
margin-bottom: 24px;
}
.wechat-express-actions {
display: flex;
gap: 12px;
}
.wechat-express-btn {
flex: 1;
padding: 10px 0;
border: none;
border-radius: 8px;
font-size: 15px;
cursor: pointer;
transition: opacity 0.2s;
}
.wechat-express-btn.confirm {
background: #FFC107;
color: #000000;
font-weight: 500;
}
.wechat-express-btn.cancel {
background: #E5E5E5;
color: #333333;
}
.wechat-express-btn:active {
opacity: 0.8;
}
/* ========== 礼物送达询问弹窗样式 ========== */ /* ========== 礼物送达询问弹窗样式 ========== */
.wechat-gift-arrival-content { .wechat-gift-arrival-content {
background: #fff !important; background: #fff !important;
@@ -11298,6 +11445,33 @@
background: linear-gradient(135deg, #ffc107, #ff9800); background: linear-gradient(135deg, #ffc107, #ff9800);
} }
/* 媒体按钮(麦克风/摄像头) */
.wechat-toy-btn-media {
width: 60px;
height: 60px;
background: linear-gradient(135deg, #ff6b8a, #ff4d6d);
border-color: rgba(255, 107, 138, 0.3);
box-shadow: 0 4px 12px rgba(255, 77, 109, 0.3);
}
.wechat-toy-btn-media svg {
color: #fff;
}
.wechat-toy-btn-media:hover {
background: linear-gradient(135deg, #ff7a96, #ff5c7a);
}
.wechat-toy-btn-media.active {
background: linear-gradient(135deg, #ff8fab, #ff6b8a);
border-color: #ff8fab;
box-shadow: 0 0 20px rgba(255, 143, 171, 0.5);
}
.wechat-toy-btn-media.active svg {
color: #fff;
}
.wechat-toy-shock-row { .wechat-toy-shock-row {
padding-top: 5px; padding-top: 5px;
border-top: 1px dashed rgba(255, 107, 138, 0.3); border-top: 1px dashed rgba(255, 107, 138, 0.3);
@@ -11365,6 +11539,186 @@
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
} }
/* 系统消息(如切换玩具提示) */
.wechat-toy-control-msg.system {
align-self: center;
background: rgba(255, 107, 138, 0.15);
color: #ff6b8a;
font-size: 12px;
padding: 6px 14px;
border-radius: 16px;
max-width: none;
}
/* 多玩具轮盘选择器 */
.wechat-toy-wheel-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
height: 180px;
margin: 10px 0;
}
.wechat-toy-wheel-heart {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #ff6b8a, #ff4d6d);
border: none;
border-radius: 50%;
color: #fff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 15px rgba(255, 77, 109, 0.4);
transition: all 0.3s ease;
z-index: 10;
animation: heartPulse 2s ease-in-out infinite;
}
@keyframes heartPulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
.wechat-toy-wheel-heart:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(255, 77, 109, 0.5);
}
.wechat-toy-wheel-heart:active {
transform: scale(0.95);
}
.wechat-toy-wheel-heart svg {
width: 24px;
height: 24px;
}
.wechat-toy-wheel-options {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
.wechat-toy-wheel-options.open {
pointer-events: auto;
}
.wechat-toy-wheel-option {
position: absolute;
width: 56px;
height: 56px;
background: rgba(255, 255, 255, 0.95);
border: 2px solid rgba(255, 107, 138, 0.3);
border-radius: 50%;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
opacity: 0;
transform: translate(-50%, -50%) scale(0);
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
left: 50%;
top: 50%;
}
.wechat-toy-wheel-options.open .wechat-toy-wheel-option {
opacity: 1;
transform: translate(calc(-50% + var(--x)), calc(-50% + var(--y))) scale(1);
}
.wechat-toy-wheel-option:hover {
background: #fff;
border-color: #ff6b8a;
transform: translate(calc(-50% + var(--x)), calc(-50% + var(--y))) scale(1.15);
box-shadow: 0 5px 20px rgba(255, 107, 138, 0.3);
}
.wechat-toy-wheel-option.active {
background: linear-gradient(135deg, #fff0f3, #ffe0e6);
border-color: #ff6b8a;
box-shadow: 0 0 0 3px rgba(255, 107, 138, 0.2);
}
.wechat-toy-wheel-option .emoji {
font-size: 20px;
line-height: 1;
}
.wechat-toy-wheel-option .name {
font-size: 9px;
color: #666;
max-width: 50px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.wechat-toy-wheel-option.active .name {
color: #ff6b8a;
font-weight: 500;
}
/* 轮盘选择器移动端适配 */
@media (max-width: 420px) {
.wechat-toy-wheel-container {
height: 150px;
}
.wechat-toy-wheel-heart {
width: 44px;
height: 44px;
}
.wechat-toy-wheel-heart svg {
width: 20px;
height: 20px;
}
.wechat-toy-wheel-option {
width: 48px;
height: 48px;
}
.wechat-toy-wheel-option .emoji {
font-size: 16px;
}
.wechat-toy-wheel-option .name {
font-size: 8px;
max-width: 44px;
}
}
/* 轮盘背景遮罩(点击关闭) */
.wechat-toy-wheel-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
z-index: 5;
display: none;
pointer-events: none;
}
.wechat-toy-wheel-overlay.active {
display: block;
pointer-events: auto;
}
@keyframes fadeIn { @keyframes fadeIn {
from { from {
opacity: 0; opacity: 0;
@@ -11664,8 +12018,8 @@
/* ===== 悬浮球样式 ===== */ /* ===== 悬浮球样式 ===== */
.wechat-floating-ball { .wechat-floating-ball {
position: fixed; position: fixed;
width: 60px; width: 30px;
height: 60px; height: 30px;
z-index: 99999; z-index: 99999;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@@ -11679,24 +12033,24 @@
} }
.wechat-floating-ball:hover { .wechat-floating-ball:hover {
transform: scale(1.08); transform: scale(1.15);
} }
.wechat-floating-ball:active, .wechat-floating-ball:active,
.wechat-floating-ball.dragging { .wechat-floating-ball.dragging {
transform: scale(0.95); transform: scale(0.92);
opacity: 0.9; opacity: 0.9;
} }
.wechat-floating-ball .floating-ball-svg { .wechat-floating-ball .floating-ball-svg {
width: 100%; width: 100%;
height: 100%; height: 100%;
filter: drop-shadow(0 2px 8px rgba(255, 182, 193, 0.4)); filter: drop-shadow(0 1px 4px rgba(255, 182, 193, 0.5));
transition: filter 0.2s ease; transition: filter 0.2s ease;
} }
.wechat-floating-ball:hover .floating-ball-svg { .wechat-floating-ball:hover .floating-ball-svg {
filter: drop-shadow(0 4px 12px rgba(255, 182, 193, 0.6)); filter: drop-shadow(0 2px 6px rgba(255, 182, 193, 0.7));
} }
/* 悬浮球呼吸动画(可选,可删除) */ /* 悬浮球呼吸动画(可选,可删除) */
@@ -11705,7 +12059,7 @@
transform: scale(1); transform: scale(1);
} }
50% { 50% {
transform: scale(1.03); transform: scale(1.05);
} }
} }

View File

@@ -323,11 +323,11 @@ export function generateSummaryPrompt(allChats, cupNumber) {
- 不要使用markdown代码块 - 不要使用markdown代码块
- 直接以 { 开头,以 } 结尾 - 直接以 { 开头,以 } 结尾
- keys: 3-5个能代表本次聊天核心内容的关键词人名、地点、事件等 - keys: 3-5个能代表本次聊天核心内容的关键词人名、地点、事件等
- content: 原样复制对话记录,每条一行,格式为"发言者: 内容" - content: 以"以下是线上聊天内容:"开头,然后原样复制对话记录,每条一行,格式为"发言者: 内容"
- comment: "${getCupName(cupNumber)}" - comment: "${getCupName(cupNumber)}"
【JSON示例】 【JSON示例】
{"keys":["公园","约会","周末"],"content":"{{user}}: 今天去哪玩?\\n{{char}}: 去公园吧\\n{{user}}: 好呀\\n{{char}}: 那我们下午2点见","comment":"${getCupName(cupNumber)}"} {"keys":["公园","约会","周末"],"content":"以下是线上聊天内容:\\n{{user}}: 今天去哪玩?\\n{{char}}: 去公园吧\\n{{user}}: 好呀\\n{{char}}: 那我们下午2点见","comment":"${getCupName(cupNumber)}"}
`; `;
} }

View File

@@ -18,7 +18,13 @@ const TOY_ICONS = {
wave: `<svg viewBox="0 0 24 24" width="28" height="28"><path d="M2 12c2-3 4-6 6-6s4 6 6 6 4-6 6-6" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 18c2-3 4-6 6-6s4 6 6 6 4-6 6-6" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>`, wave: `<svg viewBox="0 0 24 24" width="28" height="28"><path d="M2 12c2-3 4-6 6-6s4 6 6 6 4-6 6-6" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 18c2-3 4-6 6-6s4 6 6 6 4-6 6-6" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
pause: `<svg viewBox="0 0 24 24" width="28" height="28"><rect x="6" y="4" width="4" height="16" stroke="currentColor" stroke-width="1.5" fill="none" rx="1"/><rect x="14" y="4" width="4" height="16" stroke="currentColor" stroke-width="1.5" fill="none" rx="1"/></svg>`, pause: `<svg viewBox="0 0 24 24" width="28" height="28"><rect x="6" y="4" width="4" height="16" stroke="currentColor" stroke-width="1.5" fill="none" rx="1"/><rect x="14" y="4" width="4" height="16" stroke="currentColor" stroke-width="1.5" fill="none" rx="1"/></svg>`,
shock: `<svg viewBox="0 0 24 24" width="28" height="28"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke="currentColor" stroke-width="1.5" fill="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>`, shock: `<svg viewBox="0 0 24 24" width="28" height="28"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke="currentColor" stroke-width="1.5" fill="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
back: `<svg viewBox="0 0 24 24" width="24" height="24"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>` back: `<svg viewBox="0 0 24 24" width="24" height="24"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
micOn: `<svg viewBox="0 0 24 24" width="28" height="28"><path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M19 10v2a7 7 0 01-14 0v-2" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/><line x1="12" y1="19" x2="12" y2="23" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><line x1="8" y1="23" x2="16" y2="23" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>`,
micOff: `<svg viewBox="0 0 24 24" width="28" height="28"><path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M19 10v2a7 7 0 01-14 0v-2" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/><line x1="12" y1="19" x2="12" y2="23" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><line x1="8" y1="23" x2="16" y2="23" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>`,
cameraOn: `<svg viewBox="0 0 24 24" width="28" height="28"><path d="M23 7l-7 5 7 5V7z" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2" stroke="currentColor" stroke-width="1.5" fill="none"/></svg>`,
cameraOff: `<svg viewBox="0 0 24 24" width="28" height="28"><path d="M23 7l-7 5 7 5V7z" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2" stroke="currentColor" stroke-width="1.5" fill="none"/></svg>`,
// 像素爱心图标(用于多玩具切换)
pixelHeart: `<svg viewBox="0 0 16 16" width="24" height="24"><rect x="2" y="4" width="2" height="2" fill="currentColor"/><rect x="4" y="2" width="2" height="2" fill="currentColor"/><rect x="6" y="2" width="2" height="2" fill="currentColor"/><rect x="8" y="2" width="2" height="2" fill="currentColor"/><rect x="10" y="2" width="2" height="2" fill="currentColor"/><rect x="12" y="4" width="2" height="2" fill="currentColor"/><rect x="2" y="6" width="2" height="2" fill="currentColor"/><rect x="4" y="4" width="2" height="2" fill="currentColor"/><rect x="6" y="4" width="2" height="2" fill="currentColor"/><rect x="8" y="4" width="2" height="2" fill="currentColor"/><rect x="10" y="4" width="2" height="2" fill="currentColor"/><rect x="12" y="6" width="2" height="2" fill="currentColor"/><rect x="2" y="8" width="2" height="2" fill="currentColor"/><rect x="4" y="6" width="2" height="2" fill="currentColor"/><rect x="6" y="6" width="2" height="2" fill="currentColor"/><rect x="8" y="6" width="2" height="2" fill="currentColor"/><rect x="10" y="6" width="2" height="2" fill="currentColor"/><rect x="12" y="8" width="2" height="2" fill="currentColor"/><rect x="4" y="8" width="2" height="2" fill="currentColor"/><rect x="6" y="8" width="2" height="2" fill="currentColor"/><rect x="8" y="8" width="2" height="2" fill="currentColor"/><rect x="10" y="8" width="2" height="2" fill="currentColor"/><rect x="6" y="10" width="2" height="2" fill="currentColor"/><rect x="8" y="10" width="2" height="2" fill="currentColor"/><rect x="4" y="10" width="2" height="2" fill="currentColor"/><rect x="10" y="10" width="2" height="2" fill="currentColor"/><rect x="6" y="12" width="4" height="2" fill="currentColor"/></svg>`
}; };
// 控制模式定义 // 控制模式定义
@@ -73,11 +79,21 @@ let toyControlState = {
currentMode: null, currentMode: null,
messages: [], messages: [],
activeModes: new Set(), activeModes: new Set(),
sessionStartTime: null sessionStartTime: null,
micEnabled: false, // 麦克风状态
cameraEnabled: false, // 摄像头状态
// 多玩具支持
isMulti: false, // 是否多玩具模式
toys: [], // 多玩具列表
currentToyIndex: 0, // 当前控制的玩具索引
wheelOpen: false // 轮盘是否展开
}; };
// 显示控制界面 // 显示控制界面
export function showToyControlPage(gift, contact, contactIndex) { export function showToyControlPage(gift, contact, contactIndex) {
// 判断是否多玩具模式
const isMulti = gift.isMulti === true;
toyControlState = { toyControlState = {
isActive: true, isActive: true,
gift: gift, gift: gift,
@@ -87,9 +103,30 @@ export function showToyControlPage(gift, contact, contactIndex) {
currentMode: null, currentMode: null,
messages: [], messages: [],
activeModes: new Set(), activeModes: new Set(),
sessionStartTime: Date.now() sessionStartTime: Date.now(),
micEnabled: false,
cameraEnabled: false,
// 多玩具支持
isMulti: isMulti,
toys: isMulti ? gift.toys : [],
currentToyIndex: 0,
wheelOpen: false
}; };
// 如果是多玩具模式,设置当前玩具为第一个
if (isMulti && gift.toys && gift.toys.length > 0) {
const firstToy = gift.toys[0];
toyControlState.gift = {
...gift,
giftId: firstToy.giftId,
giftName: firstToy.giftName,
giftEmoji: firstToy.giftEmoji,
giftDesc: firstToy.giftDesc,
hasControl: firstToy.hasControl,
hasShock: firstToy.hasShock
};
}
// 标记正在使用 // 标记正在使用
if (contact.pendingGifts) { if (contact.pendingGifts) {
const pendingGift = contact.pendingGifts.find(g => g.timestamp === gift.timestamp); const pendingGift = contact.pendingGifts.find(g => g.timestamp === gift.timestamp);
@@ -159,6 +196,9 @@ function renderToyControlPage() {
</button> </button>
</div> </div>
<div class="wechat-toy-btn-row"> <div class="wechat-toy-btn-row">
<button class="wechat-toy-btn wechat-toy-btn-media" data-media="mic" title="麦克风">
${toyControlState.micEnabled ? TOY_ICONS.micOn : TOY_ICONS.micOff}
</button>
<button class="wechat-toy-btn" data-mode="wave"> <button class="wechat-toy-btn" data-mode="wave">
${TOY_CONTROL_MODES.wave.icon} ${TOY_CONTROL_MODES.wave.icon}
<span class="wechat-toy-btn-label">${TOY_CONTROL_MODES.wave.name}</span> <span class="wechat-toy-btn-label">${TOY_CONTROL_MODES.wave.name}</span>
@@ -167,6 +207,9 @@ function renderToyControlPage() {
${TOY_CONTROL_MODES.pause.icon} ${TOY_CONTROL_MODES.pause.icon}
<span class="wechat-toy-btn-label">${TOY_CONTROL_MODES.pause.name}</span> <span class="wechat-toy-btn-label">${TOY_CONTROL_MODES.pause.name}</span>
</button> </button>
<button class="wechat-toy-btn wechat-toy-btn-media" data-media="camera" title="摄像头">
${toyControlState.cameraEnabled ? TOY_ICONS.cameraOn : TOY_ICONS.cameraOff}
</button>
</div> </div>
`; `;
buttonsEl.innerHTML = buttonsHtml; buttonsEl.innerHTML = buttonsHtml;
@@ -187,12 +230,246 @@ function renderToyControlPage() {
} }
} }
// 清空消息 // 多玩具轮盘选择器
if (messagesEl) { renderToyWheelSelector();
// 不清空消息(保留聊天内容)
// 只在首次进入时清空
if (messagesEl && messagesEl.children.length === 0 && toyControlState.messages.length === 0) {
messagesEl.innerHTML = ''; messagesEl.innerHTML = '';
} }
} }
// 渲染多玩具轮盘选择器
function renderToyWheelSelector() {
// 移除旧的轮盘和遮罩
const existingWheel = document.getElementById('wechat-toy-wheel-container');
if (existingWheel) {
existingWheel.remove();
}
const existingOverlay = document.getElementById('wechat-toy-wheel-overlay');
if (existingOverlay) {
existingOverlay.remove();
}
// 只在多玩具模式下显示
if (!toyControlState.isMulti || toyControlState.toys.length <= 1) return;
const controlPage = document.getElementById('wechat-toy-control-page');
if (!controlPage) return;
// 创建背景遮罩(点击关闭轮盘)
const overlay = document.createElement('div');
overlay.id = 'wechat-toy-wheel-overlay';
overlay.className = `wechat-toy-wheel-overlay ${toyControlState.wheelOpen ? 'active' : ''}`;
// 创建轮盘容器
const wheelContainer = document.createElement('div');
wheelContainer.id = 'wechat-toy-wheel-container';
wheelContainer.className = 'wechat-toy-wheel-container';
// 创建中心爱心按钮
const heartBtn = document.createElement('button');
heartBtn.className = 'wechat-toy-wheel-heart';
heartBtn.innerHTML = TOY_ICONS.pixelHeart;
heartBtn.title = '切换玩具';
// 创建轮盘选项
const wheelOptions = document.createElement('div');
wheelOptions.className = `wechat-toy-wheel-options ${toyControlState.wheelOpen ? 'open' : ''}`;
const toys = toyControlState.toys;
const angleStep = 360 / toys.length;
// 移动端使用更小的半径
const isMobile = window.innerWidth <= 420;
const radius = isMobile ? 55 : 70;
toys.forEach((toy, index) => {
const option = document.createElement('button');
option.className = `wechat-toy-wheel-option ${index === toyControlState.currentToyIndex ? 'active' : ''}`;
option.dataset.toyIndex = index;
// 计算位置(从顶部开始,顺时针排列)
const angle = -90 + (angleStep * index); // -90 从顶部开始
const x = Math.cos(angle * Math.PI / 180) * radius;
const y = Math.sin(angle * Math.PI / 180) * radius;
option.style.setProperty('--x', `${x}px`);
option.style.setProperty('--y', `${y}px`);
option.innerHTML = `<span class="emoji">${toy.giftEmoji}</span><span class="name">${toy.giftName}</span>`;
wheelOptions.appendChild(option);
});
wheelContainer.appendChild(wheelOptions);
wheelContainer.appendChild(heartBtn);
// 插入遮罩和轮盘到控制区域上方
const chatArea = controlPage.querySelector('.wechat-toy-control-chat');
if (chatArea) {
chatArea.parentNode.insertBefore(overlay, chatArea);
chatArea.parentNode.insertBefore(wheelContainer, chatArea);
} else {
controlPage.appendChild(overlay);
controlPage.appendChild(wheelContainer);
}
// 绑定爱心按钮事件(支持触摸)
heartBtn.addEventListener('click', toggleToyWheel);
heartBtn.addEventListener('touchend', (e) => {
e.preventDefault();
toggleToyWheel();
});
// 绑定遮罩点击事件(关闭轮盘)
overlay.addEventListener('click', closeToyWheel);
overlay.addEventListener('touchend', (e) => {
e.preventDefault();
closeToyWheel();
});
// 绑定轮盘选项事件(支持触摸)
wheelOptions.querySelectorAll('.wechat-toy-wheel-option').forEach(opt => {
opt.addEventListener('click', (e) => {
const index = parseInt(opt.dataset.toyIndex);
switchToToy(index);
});
opt.addEventListener('touchend', (e) => {
e.preventDefault();
const index = parseInt(opt.dataset.toyIndex);
switchToToy(index);
});
});
}
// 切换轮盘展开/收起
function toggleToyWheel() {
toyControlState.wheelOpen = !toyControlState.wheelOpen;
const options = document.querySelector('.wechat-toy-wheel-options');
const overlay = document.getElementById('wechat-toy-wheel-overlay');
if (options) {
options.classList.toggle('open', toyControlState.wheelOpen);
}
if (overlay) {
overlay.classList.toggle('active', toyControlState.wheelOpen);
}
}
// 关闭轮盘
function closeToyWheel() {
toyControlState.wheelOpen = false;
const options = document.querySelector('.wechat-toy-wheel-options');
const overlay = document.getElementById('wechat-toy-wheel-overlay');
if (options) {
options.classList.remove('open');
}
if (overlay) {
overlay.classList.remove('active');
}
}
// 切换到指定玩具
async function switchToToy(index) {
if (index < 0 || index >= toyControlState.toys.length) return;
if (index === toyControlState.currentToyIndex) {
// 同一个玩具,只关闭轮盘
closeToyWheel();
return;
}
const previousToy = toyControlState.toys[toyControlState.currentToyIndex];
const newToy = toyControlState.toys[index];
// 更新当前玩具索引
toyControlState.currentToyIndex = index;
// 更新当前gift信息
toyControlState.gift = {
...toyControlState.gift,
giftId: newToy.giftId,
giftName: newToy.giftName,
giftEmoji: newToy.giftEmoji,
giftDesc: newToy.giftDesc,
hasControl: newToy.hasControl,
hasShock: newToy.hasShock
};
// 重置模式状态(切换玩具后从暂停开始)
toyControlState.currentMode = null;
toyControlState.activeModes.clear();
// 关闭轮盘状态(渲染时会应用)
toyControlState.wheelOpen = false;
// 重新渲染界面(保留消息)
renderToyControlPage();
// 更新按钮状态
document.querySelectorAll('.wechat-toy-btn').forEach(btn => {
btn.classList.remove('active');
});
// 添加切换提示消息
addToyMessage('system', `已切换到 ${newToy.giftEmoji} ${newToy.giftName}`);
// AI对切换做出反应
showToyTypingIndicator();
const isCharacterUsing = toyControlState.target === 'character';
let prompt;
if (isCharacterUsing) {
prompt = `[玩具切换]
用户刚把玩具从"${previousToy.giftName}"切换到"${newToy.giftName}"了。
${newToy.giftName}的特点:${newToy.giftDesc}
请对这个切换做出反应:
- 可以表现出对新玩具的期待、紧张或好奇
- 如果是更刺激的玩具可以表现出紧张
- 如果是比较温和的可以表现出失落或放松
【重要规则】
1. 只能输出纯文字,禁止使用任何特殊格式标签
2. 禁止使用小括号描述动作如xxx
3. 回复简短1-2句话即可`;
} else {
prompt = `[玩具切换]
你把用户正在用的玩具从"${previousToy.giftName}"切换到"${newToy.giftName}"了。
${newToy.giftName}的特点:${newToy.giftDesc}
请对这个切换做出反应:
- 可以调侃用户接下来要体验的感觉
- 或者表达你为什么要给用户换这个
【重要规则】
1. 只能输出纯文字,禁止使用任何特殊格式标签
2. 禁止使用小括号描述动作如xxx
3. 回复简短1-2句话即可`;
}
try {
const response = await callToyAI(prompt);
hideToyTypingIndicator();
if (response) {
let reply = response.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) {
addToyMessage('ai', reply);
}
}
} catch (err) {
hideToyTypingIndicator();
console.error('[可乐] 玩具切换AI回复失败:', err);
}
}
// 绑定事件 // 绑定事件
let toyEventsBound = false; let toyEventsBound = false;
function bindToyControlEvents() { function bindToyControlEvents() {
@@ -216,6 +493,12 @@ function bindToyControlEvents() {
document.getElementById('wechat-toy-control-buttons')?.addEventListener('click', (e) => { document.getElementById('wechat-toy-control-buttons')?.addEventListener('click', (e) => {
const btn = e.target.closest('.wechat-toy-btn'); const btn = e.target.closest('.wechat-toy-btn');
if (btn) { if (btn) {
// 检查是否是媒体按钮(麦克风/摄像头)
const media = btn.dataset.media;
if (media) {
onMediaToggle(media);
return;
}
const mode = btn.dataset.mode; const mode = btn.dataset.mode;
if (mode) { if (mode) {
onButtonPress(mode, 'user'); onButtonPress(mode, 'user');
@@ -234,6 +517,99 @@ function bindToyControlEvents() {
}); });
} }
// 麦克风/摄像头切换处理
async function onMediaToggle(mediaType) {
if (!toyControlState.isActive) return;
const isMic = mediaType === 'mic';
const wasEnabled = isMic ? toyControlState.micEnabled : toyControlState.cameraEnabled;
const nowEnabled = !wasEnabled;
// 更新状态
if (isMic) {
toyControlState.micEnabled = nowEnabled;
} else {
toyControlState.cameraEnabled = nowEnabled;
}
// 更新按钮图标
updateMediaButtonUI();
// 显示typing
showToyTypingIndicator();
// 构建提示词
const prompt = buildMediaTogglePrompt(mediaType, nowEnabled);
try {
const response = await callToyAI(prompt);
hideToyTypingIndicator();
if (response) {
await processAIResponse(response);
}
} catch (err) {
hideToyTypingIndicator();
console.error('[可乐] 玩具控制AI回复失败:', err);
}
}
// 更新媒体按钮UI
function updateMediaButtonUI() {
const micBtn = document.querySelector('.wechat-toy-btn-media[data-media="mic"]');
const cameraBtn = document.querySelector('.wechat-toy-btn-media[data-media="camera"]');
if (micBtn) {
micBtn.innerHTML = toyControlState.micEnabled ? TOY_ICONS.micOn : TOY_ICONS.micOff;
micBtn.classList.toggle('active', toyControlState.micEnabled);
}
if (cameraBtn) {
cameraBtn.innerHTML = toyControlState.cameraEnabled ? TOY_ICONS.cameraOn : TOY_ICONS.cameraOff;
cameraBtn.classList.toggle('active', toyControlState.cameraEnabled);
}
}
// 构建媒体切换提示词
function buildMediaTogglePrompt(mediaType, isEnabled) {
const isCharacterUsing = toyControlState.target === 'character';
const isMic = mediaType === 'mic';
const mediaName = isMic ? '麦克风' : '摄像头';
const action = isEnabled ? '打开' : '关闭';
let prompt = `${mediaName}${action}\n`;
if (isCharacterUsing) {
// 角色在用玩具
if (isEnabled) {
prompt += isMic
? `用户打开了麦克风,现在可以听到用户的声音了。你正在使用玩具,听到用户的声音会让你更有感觉。请做出反应。`
: `用户打开了摄像头,现在可以看到用户了。你正在使用玩具,看到用户会让你更害羞/更有感觉。请做出反应。`;
} else {
prompt += isMic
? `用户关闭了麦克风,现在听不到用户的声音了。你可能会有点失落或者松一口气。请做出反应。`
: `用户关闭了摄像头,现在看不到用户了。你可能会有点失落或者松一口气。请做出反应。`;
}
} else {
// 用户在用玩具
if (isEnabled) {
prompt += isMic
? `用户打开了麦克风,你现在可以听到用户的声音/喘息/呻吟了。请做出反应,可以调侃、撩拨或关心用户。`
: `用户打开了摄像头,你现在可以看到用户使用玩具的样子了。请做出反应,可以调侃、撩拨或关心用户。`;
} else {
prompt += isMic
? `用户关闭了麦克风,你现在听不到用户的声音了。请做出反应。`
: `用户关闭了摄像头,你现在看不到用户了。请做出反应。`;
}
}
prompt += `\n\n【当前状态】
- 麦克风:${toyControlState.micEnabled ? '已打开(可以听到声音)' : '已关闭'}
- 摄像头:${toyControlState.cameraEnabled ? '已打开(可以看到画面)' : '已关闭'}
- 当前模式:${toyControlState.currentMode ? (TOY_CONTROL_MODES[toyControlState.currentMode]?.name || '已暂停') : '未开始'}`;
return prompt;
}
// 按钮点击处理 // 按钮点击处理
async function onButtonPress(buttonId, pressedBy = 'user') { async function onButtonPress(buttonId, pressedBy = 'user') {
if (!toyControlState.isActive) return; if (!toyControlState.isActive) return;
@@ -345,6 +721,18 @@ function buildButtonPressPrompt(buttonId, buttonName, pressedBy) {
3. 禁止使用[语音:xxx]、[照片:xxx]、[表情:xxx]等格式 3. 禁止使用[语音:xxx]、[照片:xxx]、[表情:xxx]等格式
4. 直接输出角色说的话和感受`; 4. 直接输出角色说的话和感受`;
// 添加当前媒体状态信息
const mediaStatus = [];
if (toyControlState.micEnabled) {
mediaStatus.push('麦克风已开启(可以听到对方的声音/喘息)');
}
if (toyControlState.cameraEnabled) {
mediaStatus.push('摄像头已开启(可以看到对方)');
}
if (mediaStatus.length > 0) {
prompt += `\n\n【当前连接状态】${mediaStatus.join('')}`;
}
return prompt; return prompt;
} }
@@ -644,21 +1032,44 @@ function saveToySession() {
const seconds = (durationSec % 60).toString().padStart(2, '0'); const seconds = (durationSec % 60).toString().padStart(2, '0');
const durationStr = `${minutes}:${seconds}`; const durationStr = `${minutes}:${seconds}`;
const session = { // 构建session记录
gift: { let session;
id: toyControlState.gift.giftId, if (toyControlState.isMulti) {
name: toyControlState.gift.giftName, // 多玩具模式
emoji: toyControlState.gift.giftEmoji session = {
}, isMulti: true,
target: toyControlState.target, toys: toyControlState.toys.map(t => ({
time: timeStr, id: t.giftId,
timestamp: toyControlState.sessionStartTime || Date.now(), name: t.giftName,
duration: durationStr, emoji: t.giftEmoji
messages: toyControlState.messages.map(m => ({ })),
role: m.role, target: toyControlState.target,
content: m.content time: timeStr,
})) timestamp: toyControlState.sessionStartTime || Date.now(),
}; duration: durationStr,
messages: toyControlState.messages.map(m => ({
role: m.role,
content: m.content
}))
};
} else {
// 单玩具模式
session = {
gift: {
id: toyControlState.gift.giftId,
name: toyControlState.gift.giftName,
emoji: toyControlState.gift.giftEmoji
},
target: toyControlState.target,
time: timeStr,
timestamp: toyControlState.sessionStartTime || Date.now(),
duration: durationStr,
messages: toyControlState.messages.map(m => ({
role: m.role,
content: m.content
}))
};
}
contact.toyHistory.push(session); contact.toyHistory.push(session);