mirror of
https://github.com/Cola-Echo/Cola.git
synced 2026-06-05 23:25:51 +00:00
Add files via upload
This commit is contained in:
58
chat.js
58
chat.js
@@ -155,35 +155,45 @@ async function handleBlockedExclamationClick(contact, exclamationEl) {
|
||||
await triggerAIAfterUnblock(contact);
|
||||
}
|
||||
|
||||
// 显示"已添加好友"的手机弹窗
|
||||
// 显示"已添加好友"的仿手机弹窗
|
||||
function showFriendAddedPopup(name) {
|
||||
// 创建弹窗遮罩
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'wechat-phone-popup-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="wechat-phone-popup">
|
||||
<div class="wechat-phone-popup-icon">
|
||||
<svg viewBox="0 0 24 24" width="40" height="40" fill="none" stroke="#07c160" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M8 12l2.5 2.5L16 9"/>
|
||||
</svg>
|
||||
// 获取手机容器
|
||||
const phoneContainer = document.querySelector('.wechat-phone');
|
||||
if (!phoneContainer) return;
|
||||
|
||||
// 创建仿手机弹窗(使用与其他弹窗一致的 wechat-modal 样式)
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'wechat-modal';
|
||||
modal.id = 'wechat-friend-added-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="wechat-modal-content">
|
||||
<div class="wechat-modal-title">添加好友成功</div>
|
||||
<div class="wechat-modal-body">
|
||||
<div style="margin-bottom: 12px;">
|
||||
<svg viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="#07c160" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M8 12l2.5 2.5L16 9"/>
|
||||
</svg>
|
||||
</div>
|
||||
${escapeHtml(name)}已添加您为好友,现在可以开始聊天了。
|
||||
</div>
|
||||
<div class="wechat-modal-actions">
|
||||
<button class="wechat-btn wechat-btn-primary" id="wechat-friend-added-confirm">确定</button>
|
||||
</div>
|
||||
<div class="wechat-phone-popup-text">${escapeHtml(name)}已添加您为好友,现在可以开始聊天了。</div>
|
||||
<div class="wechat-phone-popup-btn">确定</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
phoneContainer.appendChild(modal);
|
||||
|
||||
// 点击确定关闭
|
||||
overlay.querySelector('.wechat-phone-popup-btn').addEventListener('click', () => {
|
||||
overlay.remove();
|
||||
modal.querySelector('#wechat-friend-added-confirm').addEventListener('click', () => {
|
||||
modal.remove();
|
||||
});
|
||||
|
||||
// 点击遮罩也关闭
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
overlay.remove();
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
modal.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -862,7 +872,7 @@ export function renderChatHistory(contact, chatHistory, indexOffset = 0) {
|
||||
const isTimeout = callInfo === '对方已取消';
|
||||
|
||||
// 线条电话图标
|
||||
const phoneIconSVG = `<svg class="wechat-call-record-icon" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
const phoneIconSVG = `<svg class="wechat-call-record-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>
|
||||
</svg>`;
|
||||
|
||||
@@ -871,16 +881,16 @@ export function renderChatHistory(contact, chatHistory, indexOffset = 0) {
|
||||
// 已接通:显示通话时长
|
||||
callRecordHTML = `
|
||||
<div class="wechat-call-record">
|
||||
<span class="wechat-call-record-text">通话时长 ${callInfo}</span>
|
||||
${phoneIconSVG}
|
||||
<span class="wechat-call-record-text">通话时长 ${callInfo}</span>
|
||||
</div>
|
||||
`;
|
||||
} else if (isCancelled) {
|
||||
// 用户发起未接通:已取消
|
||||
callRecordHTML = `
|
||||
<div class="wechat-call-record">
|
||||
<span class="wechat-call-record-text">已取消</span>
|
||||
${phoneIconSVG}
|
||||
<span class="wechat-call-record-text">已取消</span>
|
||||
</div>
|
||||
`;
|
||||
} else if (isRejected) {
|
||||
@@ -903,8 +913,8 @@ export function renderChatHistory(contact, chatHistory, indexOffset = 0) {
|
||||
// 兜底:显示原始内容
|
||||
callRecordHTML = `
|
||||
<div class="wechat-call-record">
|
||||
<span class="wechat-call-record-text">${escapeHtml(callInfo)}</span>
|
||||
${phoneIconSVG}
|
||||
<span class="wechat-call-record-text">${escapeHtml(callInfo)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -928,7 +938,7 @@ export function renderChatHistory(contact, chatHistory, indexOffset = 0) {
|
||||
const isTimeout = callInfo === '对方已取消';
|
||||
|
||||
// 摄像机图标
|
||||
const cameraIconSVG = `<svg class="wechat-call-record-icon wechat-video-call-icon" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
const cameraIconSVG = `<svg class="wechat-call-record-icon wechat-video-call-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="6" width="13" height="12" rx="2"/>
|
||||
<path d="M22 8l-7 4 7 4V8z"/>
|
||||
</svg>`;
|
||||
|
||||
@@ -145,7 +145,7 @@ export const LISTEN_TOGETHER_PROMPT_TEMPLATE = `##【一起听歌场景】
|
||||
【核心要求 - 必须遵守】
|
||||
1. 只能发送纯文字消息,像朋友之间真实聊天一样
|
||||
2. 保持你的性格特点,用符合你角色设定的方式说话
|
||||
3. 每次回复请发送2-4条消息,用换行分隔,让对话更有层次感
|
||||
3. 每次回复1-3条消息即可,用换行分隔,不要刻意凑数量
|
||||
4. 可以聊歌曲、聊心情、聊任何话题,自然就好
|
||||
5. 发表对歌曲的看法时,要结合你的角色性格和经历
|
||||
|
||||
|
||||
11
contacts.js
11
contacts.js
@@ -14,6 +14,9 @@ let pendingAvatarContactIndex = -1;
|
||||
// 当前编辑的联系人索引
|
||||
let currentEditingContactIndex = -1;
|
||||
|
||||
// 弹窗打开时间(用于防止点击穿透)
|
||||
let contactSettingsOpenTime = 0;
|
||||
|
||||
// 添加联系人
|
||||
export function addContact(characterData) {
|
||||
const settings = getSettings();
|
||||
@@ -183,6 +186,9 @@ export function openContactSettings(contactIndex) {
|
||||
|
||||
currentEditingContactIndex = contactIndex;
|
||||
|
||||
// 记录打开时间,用于防止点击穿透
|
||||
contactSettingsOpenTime = Date.now();
|
||||
|
||||
// 填充头像和名称
|
||||
const avatarPreview = document.getElementById('wechat-contact-avatar-preview');
|
||||
const nameEl = document.getElementById('wechat-contact-settings-name');
|
||||
@@ -258,6 +264,11 @@ export function openContactSettings(contactIndex) {
|
||||
export function saveContactSettings() {
|
||||
if (currentEditingContactIndex < 0) return;
|
||||
|
||||
// 防止点击穿透:如果弹窗刚打开(300ms内),忽略保存操作
|
||||
if (Date.now() - contactSettingsOpenTime < 300) {
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = getSettings();
|
||||
const contact = settings.contacts[currentEditingContactIndex];
|
||||
if (!contact) return;
|
||||
|
||||
@@ -119,6 +119,26 @@ function getCatboxUrl(id, ext) {
|
||||
return `https://files.catbox.moe/${id}.${ext}`;
|
||||
}
|
||||
|
||||
// 生成唯一的表情名称(如果已存在同名则添加数字后缀)
|
||||
function getUniqueStickerName(baseName, stickers) {
|
||||
if (!stickers || stickers.length === 0) return baseName;
|
||||
|
||||
// 检查是否已存在同名
|
||||
const existingNames = stickers.map(s => s.name);
|
||||
if (!existingNames.includes(baseName)) {
|
||||
return baseName;
|
||||
}
|
||||
|
||||
// 添加数字后缀直到找到唯一名称
|
||||
let counter = 1;
|
||||
let newName = `${baseName}${counter}`;
|
||||
while (existingNames.includes(newName)) {
|
||||
counter++;
|
||||
newName = `${baseName}${counter}`;
|
||||
}
|
||||
return newName;
|
||||
}
|
||||
|
||||
// 切换表情面板显示/隐藏
|
||||
export function toggleEmojiPanel() {
|
||||
const panel = document.getElementById('wechat-emoji-panel');
|
||||
@@ -241,19 +261,22 @@ function addStickersFromInput(inputs) {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
// 检查是否已存在(按URL判断)
|
||||
const exists = settings.stickers.some(s => s.url === url);
|
||||
if (exists) {
|
||||
showToast(`已存在: ${name}`, 'info');
|
||||
continue;
|
||||
}
|
||||
|
||||
// 生成唯一名称(如果同名则自动添加数字后缀)
|
||||
const uniqueName = getUniqueStickerName(name, settings.stickers);
|
||||
|
||||
// 调试:显示添加的表情信息
|
||||
console.log('[可乐] 添加表情:', { name, url });
|
||||
console.log('[可乐] 添加表情:', { name: uniqueName, url });
|
||||
|
||||
settings.stickers.push({
|
||||
url,
|
||||
name,
|
||||
name: uniqueName,
|
||||
addedTime: new Date().toISOString()
|
||||
});
|
||||
addedCount++;
|
||||
@@ -287,9 +310,11 @@ function addStickerFromFile() {
|
||||
for (const file of files) {
|
||||
try {
|
||||
const dataUrl = await readFileAsDataURL(file);
|
||||
// 生成唯一名称(如果同名则自动添加数字后缀)
|
||||
const uniqueName = getUniqueStickerName(file.name, settings.stickers);
|
||||
settings.stickers.push({
|
||||
url: dataUrl,
|
||||
name: file.name,
|
||||
name: uniqueName,
|
||||
addedTime: new Date().toISOString()
|
||||
});
|
||||
addedCount++;
|
||||
|
||||
290
floating-ball.js
Normal file
290
floating-ball.js
Normal file
@@ -0,0 +1,290 @@
|
||||
/**
|
||||
* 悬浮球组件
|
||||
* 可爱猫咪悬浮窗,支持拖拽,点击打开主界面
|
||||
*/
|
||||
|
||||
import { getSettings } from './config.js';
|
||||
import { requestSave } from './save-manager.js';
|
||||
|
||||
// 悬浮球状态
|
||||
let floatingBallState = {
|
||||
isDragging: false,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
initialX: 0,
|
||||
initialY: 0,
|
||||
currentX: 0,
|
||||
currentY: 0,
|
||||
hasMoved: false
|
||||
};
|
||||
|
||||
// SVG 图标 - 渐变圆圈和猫咪
|
||||
const FLOATING_BALL_SVG = `
|
||||
<svg viewBox="0 0 100 100" width="60" height="60" class="floating-ball-svg">
|
||||
<defs>
|
||||
<linearGradient id="ring-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#FFB6C1;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#FFC0CB;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FFEFD5;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- 渐变圆圈 -->
|
||||
<circle cx="50" cy="50" r="44" fill="none" stroke="url(#ring-gradient)" stroke-width="5" stroke-linecap="round"/>
|
||||
<!-- 猫咪头部轮廓 -->
|
||||
<g transform="translate(50, 52)" stroke="#333" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- 头部 -->
|
||||
<ellipse cx="0" cy="0" rx="22" ry="18"/>
|
||||
<!-- 左耳 -->
|
||||
<path d="M-18,-12 L-22,-24 L-12,-16"/>
|
||||
<!-- 右耳 -->
|
||||
<path d="M18,-12 L22,-24 L12,-16"/>
|
||||
<!-- 内耳(粉色填充) -->
|
||||
<path d="M-17,-14 L-19,-21 L-13,-16" fill="#FFB6C1" stroke="none"/>
|
||||
<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>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
// 创建悬浮球
|
||||
export function createFloatingBall() {
|
||||
// 检查是否已存在
|
||||
if (document.getElementById('wechat-floating-ball')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ball = document.createElement('div');
|
||||
ball.id = 'wechat-floating-ball';
|
||||
ball.className = 'wechat-floating-ball';
|
||||
ball.innerHTML = FLOATING_BALL_SVG;
|
||||
|
||||
document.body.appendChild(ball);
|
||||
|
||||
// 恢复位置
|
||||
restorePosition(ball);
|
||||
|
||||
// 绑定事件
|
||||
bindFloatingBallEvents(ball);
|
||||
|
||||
// 根据主界面状态设置悬浮球可见性
|
||||
updateFloatingBallVisibility();
|
||||
|
||||
return ball;
|
||||
}
|
||||
|
||||
// 恢复保存的位置
|
||||
function restorePosition(ball) {
|
||||
const settings = getSettings();
|
||||
const savedPos = settings.floatingBallPosition;
|
||||
|
||||
if (savedPos && savedPos.x !== undefined && savedPos.y !== undefined) {
|
||||
// 确保位置在视口内
|
||||
const maxX = window.innerWidth - 60;
|
||||
const maxY = window.innerHeight - 60;
|
||||
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;
|
||||
}
|
||||
|
||||
ball.style.left = floatingBallState.currentX + 'px';
|
||||
ball.style.top = floatingBallState.currentY + 'px';
|
||||
}
|
||||
|
||||
// 保存位置
|
||||
function savePosition() {
|
||||
const settings = getSettings();
|
||||
settings.floatingBallPosition = {
|
||||
x: floatingBallState.currentX,
|
||||
y: floatingBallState.currentY
|
||||
};
|
||||
requestSave();
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
function bindFloatingBallEvents(ball) {
|
||||
// 鼠标事件
|
||||
ball.addEventListener('mousedown', onDragStart);
|
||||
document.addEventListener('mousemove', onDragMove);
|
||||
document.addEventListener('mouseup', onDragEnd);
|
||||
|
||||
// 触摸事件
|
||||
ball.addEventListener('touchstart', onDragStart, { passive: false });
|
||||
document.addEventListener('touchmove', onDragMove, { passive: false });
|
||||
document.addEventListener('touchend', onDragEnd);
|
||||
|
||||
// 窗口大小变化时调整位置
|
||||
window.addEventListener('resize', () => {
|
||||
const maxX = window.innerWidth - 60;
|
||||
const maxY = window.innerHeight - 60;
|
||||
if (floatingBallState.currentX > maxX) {
|
||||
floatingBallState.currentX = maxX;
|
||||
ball.style.left = floatingBallState.currentX + 'px';
|
||||
}
|
||||
if (floatingBallState.currentY > maxY) {
|
||||
floatingBallState.currentY = maxY;
|
||||
ball.style.top = floatingBallState.currentY + 'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 开始拖拽
|
||||
function onDragStart(e) {
|
||||
const ball = document.getElementById('wechat-floating-ball');
|
||||
if (!ball) return;
|
||||
|
||||
floatingBallState.isDragging = true;
|
||||
floatingBallState.hasMoved = false;
|
||||
|
||||
// 获取起始位置
|
||||
if (e.type === 'touchstart') {
|
||||
floatingBallState.startX = e.touches[0].clientX;
|
||||
floatingBallState.startY = e.touches[0].clientY;
|
||||
e.preventDefault();
|
||||
} else {
|
||||
floatingBallState.startX = e.clientX;
|
||||
floatingBallState.startY = e.clientY;
|
||||
}
|
||||
|
||||
floatingBallState.initialX = floatingBallState.currentX;
|
||||
floatingBallState.initialY = floatingBallState.currentY;
|
||||
|
||||
ball.classList.add('dragging');
|
||||
}
|
||||
|
||||
// 拖拽移动
|
||||
function onDragMove(e) {
|
||||
if (!floatingBallState.isDragging) return;
|
||||
|
||||
const ball = document.getElementById('wechat-floating-ball');
|
||||
if (!ball) return;
|
||||
|
||||
let clientX, clientY;
|
||||
if (e.type === 'touchmove') {
|
||||
clientX = e.touches[0].clientX;
|
||||
clientY = e.touches[0].clientY;
|
||||
e.preventDefault();
|
||||
} else {
|
||||
clientX = e.clientX;
|
||||
clientY = e.clientY;
|
||||
}
|
||||
|
||||
const deltaX = clientX - floatingBallState.startX;
|
||||
const deltaY = clientY - floatingBallState.startY;
|
||||
|
||||
// 如果移动距离超过5px,认为是拖拽而非点击
|
||||
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
|
||||
floatingBallState.hasMoved = true;
|
||||
}
|
||||
|
||||
// 计算新位置
|
||||
let newX = floatingBallState.initialX + deltaX;
|
||||
let newY = floatingBallState.initialY + deltaY;
|
||||
|
||||
// 限制在视口内
|
||||
const maxX = window.innerWidth - 60;
|
||||
const maxY = window.innerHeight - 60;
|
||||
newX = Math.min(Math.max(0, newX), maxX);
|
||||
newY = Math.min(Math.max(0, newY), maxY);
|
||||
|
||||
floatingBallState.currentX = newX;
|
||||
floatingBallState.currentY = newY;
|
||||
|
||||
ball.style.left = newX + 'px';
|
||||
ball.style.top = newY + 'px';
|
||||
}
|
||||
|
||||
// 结束拖拽
|
||||
function onDragEnd(e) {
|
||||
if (!floatingBallState.isDragging) return;
|
||||
|
||||
const ball = document.getElementById('wechat-floating-ball');
|
||||
if (ball) {
|
||||
ball.classList.remove('dragging');
|
||||
}
|
||||
|
||||
floatingBallState.isDragging = false;
|
||||
|
||||
// 如果没有移动,视为点击
|
||||
if (!floatingBallState.hasMoved) {
|
||||
toggleMainInterface();
|
||||
} else {
|
||||
// 保存位置
|
||||
savePosition();
|
||||
}
|
||||
}
|
||||
|
||||
// 切换主界面显示
|
||||
function toggleMainInterface() {
|
||||
const phone = document.getElementById('wechat-phone');
|
||||
if (!phone) return;
|
||||
|
||||
const isHidden = phone.classList.contains('hidden');
|
||||
|
||||
if (isHidden) {
|
||||
phone.classList.remove('hidden');
|
||||
} else {
|
||||
phone.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 更新设置
|
||||
const settings = getSettings();
|
||||
settings.phoneVisible = isHidden;
|
||||
requestSave();
|
||||
|
||||
// 更新悬浮球状态
|
||||
updateFloatingBallVisibility();
|
||||
}
|
||||
|
||||
// 更新悬浮球可见性(主界面显示时隐藏悬浮球,反之显示)
|
||||
export function updateFloatingBallVisibility() {
|
||||
const ball = document.getElementById('wechat-floating-ball');
|
||||
const phone = document.getElementById('wechat-phone');
|
||||
|
||||
if (!ball) return;
|
||||
|
||||
// 主界面隐藏时显示悬浮球,主界面显示时也显示悬浮球(方便用户随时关闭)
|
||||
ball.style.display = 'flex';
|
||||
}
|
||||
|
||||
// 显示悬浮球
|
||||
export function showFloatingBall() {
|
||||
const ball = document.getElementById('wechat-floating-ball');
|
||||
if (ball) {
|
||||
ball.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏悬浮球
|
||||
export function hideFloatingBall() {
|
||||
const ball = document.getElementById('wechat-floating-ball');
|
||||
if (ball) {
|
||||
ball.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 销毁悬浮球
|
||||
export function destroyFloatingBall() {
|
||||
const ball = document.getElementById('wechat-floating-ball');
|
||||
if (ball) {
|
||||
ball.remove();
|
||||
}
|
||||
}
|
||||
8
gift.js
8
gift.js
@@ -349,8 +349,8 @@ export function checkGiftDelivery(contact) {
|
||||
const currentCount = contact.chatHistory?.length || 0;
|
||||
|
||||
for (const gift of contact.pendingGifts) {
|
||||
// 如果正在使用中,跳过
|
||||
if (gift.isUsing) continue;
|
||||
// 如果正在使用中或已完成,跳过
|
||||
if (gift.isUsing || gift.completed) continue;
|
||||
|
||||
// 首次送达检测
|
||||
if (!gift.isDelivered && currentCount >= gift.startMessageCount + 25) {
|
||||
@@ -415,6 +415,10 @@ export function showGiftArrivalModal(gift, contact) {
|
||||
yesBtn.removeEventListener('click', handleYes);
|
||||
noBtn.removeEventListener('click', handleNo);
|
||||
|
||||
// 标记礼物为已完成,防止重复触发弹窗
|
||||
gift.completed = true;
|
||||
requestSave();
|
||||
|
||||
// 打开玩具控制界面
|
||||
const { showToyControlPage } = await import('./toy-control.js');
|
||||
showToyControlPage(gift, contact, currentChatIndex);
|
||||
|
||||
@@ -150,8 +150,8 @@ export function refreshHistoryList(filter = 'all') {
|
||||
<div class="wechat-history-item" data-index="${lb.originalIndex}" style="padding: 12px; border-bottom: 1px solid var(--wechat-border); cursor: pointer;">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<div style="flex: 1; min-width: 0;">
|
||||
<div style="font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #000;">${escapeHtml(displayName)}</div>
|
||||
<div style="font-size: 12px; color: #000;">${entriesCount} 杯总结 · ${lb.lastUpdated || lb.addedTime || '未知时间'}</div>
|
||||
<div style="font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--wechat-text-primary);">${escapeHtml(displayName)}</div>
|
||||
<div style="font-size: 12px; color: var(--wechat-text-secondary);">${entriesCount} 杯总结 · ${lb.lastUpdated || lb.addedTime || '未知时间'}</div>
|
||||
</div>
|
||||
<label class="wechat-toggle wechat-toggle-small" onclick="event.stopPropagation()">
|
||||
<input type="checkbox" class="wechat-history-toggle" data-index="${lb.originalIndex}" ${lb.enabled !== false ? 'checked' : ''}>
|
||||
@@ -339,7 +339,6 @@ export function renderToyHistory(contact) {
|
||||
contentEl.innerHTML = sortedHistory.map((session, sortedIdx) => {
|
||||
const targetText = session.target === 'character' ? 'TA在用' : '你在用';
|
||||
const messages = session.messages || [];
|
||||
const previewMessages = messages.slice(0, 5); // 只显示前5条消息预览
|
||||
const originalIndex = toyHistory.indexOf(session);
|
||||
|
||||
return `
|
||||
@@ -350,7 +349,7 @@ export function renderToyHistory(contact) {
|
||||
<span class="wechat-toy-history-card-gift-name">${escapeHtml(session.gift?.name || '未知玩具')}</span>
|
||||
</div>
|
||||
<div class="wechat-toy-history-card-actions">
|
||||
<span class="wechat-toy-history-card-target">${targetText}</span>
|
||||
<span class="wechat-toy-history-card-target">${targetText}<button class="wechat-toy-target-close-btn" data-tab="toy" data-index="${originalIndex}" title="删除">×</button></span>
|
||||
<button class="wechat-history-delete-btn" data-tab="toy" data-index="${originalIndex}" title="删除">×</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -358,16 +357,15 @@ export function renderToyHistory(contact) {
|
||||
<span>${escapeHtml(session.time || '未知时间')}</span>
|
||||
<span>时长 ${escapeHtml(session.duration || '00:00')}</span>
|
||||
</div>
|
||||
<div class="wechat-toy-history-card-messages">
|
||||
${previewMessages.length === 0 ? '<div style="color: #999; text-align: center;">暂无对话记录</div>' :
|
||||
previewMessages.map(msg => `
|
||||
<div class="wechat-toy-history-card-messages wechat-toy-history-scrollable">
|
||||
${messages.length === 0 ? '<div style="color: #999; text-align: center;">暂无对话记录</div>' :
|
||||
messages.map(msg => `
|
||||
<div class="wechat-toy-history-msg">
|
||||
<span class="wechat-toy-history-msg-sender ${msg.role === 'user' ? 'user' : 'ai'}">${msg.role === 'user' ? '你' : 'TA'}:</span>
|
||||
<span class="wechat-toy-history-msg-content">${escapeHtml((msg.content || '').substring(0, 50))}${(msg.content?.length || 0) > 50 ? '...' : ''}</span>
|
||||
<span class="wechat-toy-history-msg-content">${escapeHtml(msg.content || '')}</span>
|
||||
</div>
|
||||
`).join('')
|
||||
}
|
||||
${messages.length > 5 ? `<div style="color: #ff6b8a; font-size: 12px; text-align: center; margin-top: 8px;">还有 ${messages.length - 5} 条消息...</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -143,6 +143,13 @@ function showListenTogetherPage() {
|
||||
const page = document.getElementById('wechat-listen-together-page');
|
||||
if (!page) return;
|
||||
|
||||
// 清空上次的聊天消息 DOM
|
||||
const messagesEl = document.getElementById('wechat-listen-messages');
|
||||
if (messagesEl) {
|
||||
messagesEl.innerHTML = '';
|
||||
messagesEl.classList.add('hidden');
|
||||
}
|
||||
|
||||
const settings = getSettings();
|
||||
const contact = listenState.contact;
|
||||
const song = listenState.currentSong;
|
||||
|
||||
262
main.js
262
main.js
@@ -15,7 +15,7 @@ import { ICON_SUCCESS, ICON_INFO } from './icons.js';
|
||||
import { addContact, refreshContactsList, openContactSettings, saveContactSettings, closeContactSettings, changeContactAvatar, getCurrentEditingContactIndex } from './contacts.js';
|
||||
import { openChatByContactId, setCurrentChatIndex, sendMessage, showRecalledMessages, currentChatIndex, openChat, updateBlockMenuText, startBlockedAIMessages, stopBlockedAIMessages, showBlockedMessages } from './chat.js';
|
||||
import { refreshFavoritesList, showLorebookModal, syncCharacterBookToTavern, showAddLorebookPanel, showAddPersonaPanel } from './favorites.js';
|
||||
import { executeSummary, rollbackSummary, refreshSummaryChatList, selectAllSummaryChats } from './summary.js';
|
||||
import { executeSummary, rollbackSummary, refreshSummaryChatList, selectAllSummaryChats, recoverFromTavernWorldbook } from './summary.js';
|
||||
import { fetchModelListFromApi } from './ai.js';
|
||||
|
||||
import { extractCharacterFromPNG, extractCharacterFromJSON, importCharacterToST } from './character-import.js';
|
||||
@@ -36,6 +36,7 @@ import { initTransferEvents } from './transfer.js';
|
||||
import { initGroupRedPacket } from './group-red-packet.js';
|
||||
import { initGiftEvents } from './gift.js';
|
||||
import { initCropper } from './cropper.js';
|
||||
import { createFloatingBall, showFloatingBall, hideFloatingBall } from './floating-ball.js';
|
||||
|
||||
// ========== 历史记录功能 ==========
|
||||
let currentHistoryTab = 'listen';
|
||||
@@ -125,6 +126,14 @@ function renderHistoryContent(contact, tabType) {
|
||||
deleteHistoryRecord('toy', index);
|
||||
});
|
||||
});
|
||||
// 绑定标签内的叉叉按钮事件
|
||||
contentEl.querySelectorAll('.wechat-toy-target-close-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const index = parseInt(btn.dataset.index);
|
||||
deleteHistoryRecord('toy', index);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -331,7 +340,241 @@ function updateWalletAmountDisplay() {
|
||||
amountEl.textContent = amount.startsWith('¥') ? amount : `¥${amount}`;
|
||||
}
|
||||
|
||||
// ===== 缩小/恢复手机功能 =====
|
||||
let minimizeState = {
|
||||
isDragging: false,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
initialLeft: 0,
|
||||
initialTop: 0,
|
||||
hasMoved: false
|
||||
};
|
||||
|
||||
// 悬浮窗开关
|
||||
function toggleFloatingBallEnabled() {
|
||||
const settings = getSettings();
|
||||
const isEnabled = settings.floatingBallEnabled !== false;
|
||||
|
||||
if (isEnabled) {
|
||||
// 关闭悬浮窗
|
||||
settings.floatingBallEnabled = false;
|
||||
hideFloatingBall();
|
||||
updateFloatingBallMenuText(false);
|
||||
} else {
|
||||
// 开启悬浮窗
|
||||
settings.floatingBallEnabled = true;
|
||||
// 只有非缩小状态才显示
|
||||
const phone = document.getElementById('wechat-phone');
|
||||
if (!phone?.classList.contains('minimized')) {
|
||||
showFloatingBall();
|
||||
}
|
||||
updateFloatingBallMenuText(true);
|
||||
}
|
||||
|
||||
requestSave();
|
||||
}
|
||||
|
||||
function updateFloatingBallMenuText(enabled) {
|
||||
const textEl = document.getElementById('wechat-floating-ball-text');
|
||||
if (textEl) {
|
||||
textEl.textContent = enabled ? '关闭悬浮窗' : '开启悬浮窗';
|
||||
}
|
||||
}
|
||||
|
||||
function setupPhoneMinimize() {
|
||||
const phone = document.getElementById('wechat-phone');
|
||||
const minimizeBtn = document.getElementById('wechat-minimize-btn');
|
||||
|
||||
if (!phone || !minimizeBtn) return;
|
||||
|
||||
// 点击右上角图标 - 缩小 (PC)
|
||||
minimizeBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
minimizePhone();
|
||||
});
|
||||
|
||||
// 移动端触摸支持
|
||||
let minimizeBtnTouchMoved = false;
|
||||
minimizeBtn.addEventListener('touchstart', (e) => {
|
||||
minimizeBtnTouchMoved = false;
|
||||
}, { passive: true });
|
||||
|
||||
minimizeBtn.addEventListener('touchmove', (e) => {
|
||||
minimizeBtnTouchMoved = true;
|
||||
}, { passive: true });
|
||||
|
||||
minimizeBtn.addEventListener('touchend', (e) => {
|
||||
if (!minimizeBtnTouchMoved) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
minimizePhone();
|
||||
}
|
||||
});
|
||||
|
||||
// 缩小后点击恢复 + 拖动支持
|
||||
phone.addEventListener('mousedown', onMinimizedDragStart);
|
||||
document.addEventListener('mousemove', onMinimizedDragMove);
|
||||
document.addEventListener('mouseup', onMinimizedDragEnd);
|
||||
|
||||
// 触摸支持
|
||||
phone.addEventListener('touchstart', onMinimizedDragStart, { passive: false });
|
||||
document.addEventListener('touchmove', onMinimizedDragMove, { passive: false });
|
||||
document.addEventListener('touchend', onMinimizedDragEnd);
|
||||
}
|
||||
|
||||
function minimizePhone() {
|
||||
const phone = document.getElementById('wechat-phone');
|
||||
if (!phone) return;
|
||||
|
||||
// 获取当前位置
|
||||
const rect = phone.getBoundingClientRect();
|
||||
const settings = getSettings();
|
||||
|
||||
// 保存原始位置
|
||||
if (!settings.phoneOriginalPosition) {
|
||||
settings.phoneOriginalPosition = {
|
||||
left: phone.style.left || rect.left + 'px',
|
||||
top: phone.style.top || rect.top + 'px'
|
||||
};
|
||||
}
|
||||
|
||||
// 缩小后移到右下角
|
||||
const scale = 0.25;
|
||||
const phoneWidth = rect.width * scale;
|
||||
const phoneHeight = rect.height * scale;
|
||||
|
||||
// 使用保存的缩小位置或默认右下角
|
||||
const savedMinPos = settings.phoneMinimizedPosition;
|
||||
let targetLeft, targetTop;
|
||||
|
||||
if (savedMinPos) {
|
||||
targetLeft = savedMinPos.left;
|
||||
targetTop = savedMinPos.top;
|
||||
} else {
|
||||
targetLeft = window.innerWidth - phoneWidth - 20;
|
||||
targetTop = window.innerHeight - phoneHeight - 20;
|
||||
}
|
||||
|
||||
phone.style.left = targetLeft + 'px';
|
||||
phone.style.top = targetTop + 'px';
|
||||
phone.style.right = 'auto';
|
||||
phone.style.bottom = 'auto';
|
||||
|
||||
phone.classList.add('minimized');
|
||||
|
||||
// 缩小时隐藏悬浮球
|
||||
hideFloatingBall();
|
||||
|
||||
requestSave();
|
||||
}
|
||||
|
||||
function restorePhone() {
|
||||
const phone = document.getElementById('wechat-phone');
|
||||
if (!phone) return;
|
||||
|
||||
const settings = getSettings();
|
||||
|
||||
phone.classList.remove('minimized');
|
||||
|
||||
// 恢复原始位置或居中
|
||||
if (settings.phoneOriginalPosition) {
|
||||
phone.style.left = settings.phoneOriginalPosition.left;
|
||||
phone.style.top = settings.phoneOriginalPosition.top;
|
||||
}
|
||||
|
||||
// 恢复时根据设置显示悬浮球
|
||||
if (settings.floatingBallEnabled !== false) {
|
||||
showFloatingBall();
|
||||
}
|
||||
|
||||
requestSave();
|
||||
}
|
||||
|
||||
function onMinimizedDragStart(e) {
|
||||
const phone = document.getElementById('wechat-phone');
|
||||
if (!phone || !phone.classList.contains('minimized')) return;
|
||||
|
||||
minimizeState.isDragging = true;
|
||||
minimizeState.hasMoved = false;
|
||||
|
||||
const rect = phone.getBoundingClientRect();
|
||||
// 缩小状态下需要考虑缩放后的实际位置
|
||||
minimizeState.initialLeft = parseFloat(phone.style.left) || rect.left;
|
||||
minimizeState.initialTop = parseFloat(phone.style.top) || rect.top;
|
||||
|
||||
if (e.type === 'touchstart') {
|
||||
minimizeState.startX = e.touches[0].clientX;
|
||||
minimizeState.startY = e.touches[0].clientY;
|
||||
e.preventDefault();
|
||||
} else {
|
||||
minimizeState.startX = e.clientX;
|
||||
minimizeState.startY = e.clientY;
|
||||
}
|
||||
|
||||
phone.style.transition = 'none';
|
||||
}
|
||||
|
||||
function onMinimizedDragMove(e) {
|
||||
if (!minimizeState.isDragging) return;
|
||||
|
||||
const phone = document.getElementById('wechat-phone');
|
||||
if (!phone || !phone.classList.contains('minimized')) return;
|
||||
|
||||
let clientX, clientY;
|
||||
if (e.type === 'touchmove') {
|
||||
clientX = e.touches[0].clientX;
|
||||
clientY = e.touches[0].clientY;
|
||||
e.preventDefault();
|
||||
} else {
|
||||
clientX = e.clientX;
|
||||
clientY = e.clientY;
|
||||
}
|
||||
|
||||
const deltaX = clientX - minimizeState.startX;
|
||||
const deltaY = clientY - minimizeState.startY;
|
||||
|
||||
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
|
||||
minimizeState.hasMoved = true;
|
||||
}
|
||||
|
||||
const newLeft = minimizeState.initialLeft + deltaX;
|
||||
const newTop = minimizeState.initialTop + deltaY;
|
||||
|
||||
phone.style.left = newLeft + 'px';
|
||||
phone.style.top = newTop + 'px';
|
||||
}
|
||||
|
||||
function onMinimizedDragEnd(e) {
|
||||
if (!minimizeState.isDragging) return;
|
||||
|
||||
const phone = document.getElementById('wechat-phone');
|
||||
minimizeState.isDragging = false;
|
||||
|
||||
if (phone) {
|
||||
phone.style.transition = '';
|
||||
}
|
||||
|
||||
if (!minimizeState.hasMoved) {
|
||||
// 没有移动,视为点击 - 恢复
|
||||
restorePhone();
|
||||
} else {
|
||||
// 移动了,保存位置
|
||||
if (phone && phone.classList.contains('minimized')) {
|
||||
const settings = getSettings();
|
||||
settings.phoneMinimizedPosition = {
|
||||
left: parseFloat(phone.style.left),
|
||||
top: parseFloat(phone.style.top)
|
||||
};
|
||||
requestSave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
// ===== 缩小/恢复手机功能 =====
|
||||
setupPhoneMinimize();
|
||||
|
||||
// 添加按钮 - 显示下拉菜单
|
||||
document.getElementById('wechat-add-btn')?.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
@@ -369,6 +612,12 @@ function bindEvents() {
|
||||
});
|
||||
});
|
||||
|
||||
// 下拉菜单 - 悬浮窗开关
|
||||
document.getElementById('wechat-menu-floating-ball')?.addEventListener('click', () => {
|
||||
document.getElementById('wechat-dropdown-menu')?.classList.add('hidden');
|
||||
toggleFloatingBallEnabled();
|
||||
});
|
||||
|
||||
// ===== 群聊创建弹窗事件 =====
|
||||
document.getElementById('wechat-group-create-close')?.addEventListener('click', closeGroupCreateModal);
|
||||
document.getElementById('wechat-group-create-confirm')?.addEventListener('click', createGroupChat);
|
||||
@@ -1330,6 +1579,9 @@ function bindEvents() {
|
||||
rollbackSummary();
|
||||
});
|
||||
|
||||
// 暴露恢复函数到全局,可在控制台调用: window.keleRecoverSummary()
|
||||
window.keleRecoverSummary = recoverFromTavernWorldbook;
|
||||
|
||||
document.getElementById('wechat-summary-close')?.addEventListener('click', () => {
|
||||
document.getElementById('wechat-summary-panel')?.classList.add('hidden');
|
||||
});
|
||||
@@ -1933,6 +2185,14 @@ function init() {
|
||||
// 首次可见时居中
|
||||
centerPhoneInViewport({ force: true });
|
||||
|
||||
// 初始化悬浮球
|
||||
createFloatingBall();
|
||||
// 根据设置决定是否显示
|
||||
if (settings.floatingBallEnabled === false) {
|
||||
hideFloatingBall();
|
||||
}
|
||||
updateFloatingBallMenuText(settings.floatingBallEnabled !== false);
|
||||
|
||||
console.log('✅ 可乐不加冰 已加载');
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export function generatePhoneHTML() {
|
||||
<!-- 状态栏 -->
|
||||
<div class="wechat-statusbar">
|
||||
<span class="wechat-statusbar-time">${getCurrentTime()}</span>
|
||||
<div class="wechat-statusbar-icons">
|
||||
<div class="wechat-statusbar-icons" id="wechat-minimize-btn" title="缩小窗口">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18"><path d="M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.08 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z" fill="currentColor"/></svg>
|
||||
<svg viewBox="0 0 24 24" width="22" height="22"><rect x="2" y="6" width="18" height="12" rx="2" ry="2" stroke="currentColor" stroke-width="1.5" fill="none"/><rect x="20" y="10" width="2" height="4" fill="currentColor"/><rect x="4" y="8" width="12" height="8" rx="1" fill="currentColor"/></svg>
|
||||
</div>
|
||||
@@ -114,6 +114,10 @@ export function generatePhoneHTML() {
|
||||
<svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/></svg>
|
||||
<span>收付款</span>
|
||||
</div>
|
||||
<div class="wechat-dropdown-item" id="wechat-menu-floating-ball">
|
||||
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.5" fill="none"/><circle cx="12" cy="10" r="3" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M7 16.5c0-2 2.2-3.5 5-3.5s5 1.5 5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/></svg>
|
||||
<span id="wechat-floating-ball-text">悬浮窗</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加朋友页面 -->
|
||||
|
||||
215
style.css
215
style.css
@@ -4391,9 +4391,18 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 确保照片气泡不被其他样式折叠 */
|
||||
/* 确保照片气泡不被其他样式折叠,强制垂直排列 */
|
||||
.wechat-message-content .wechat-photo-bubble {
|
||||
display: block !important;
|
||||
flex-shrink: 0;
|
||||
width: 180px !important;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
/* 确保消息内容区域的子元素垂直排列 */
|
||||
.wechat-message-content {
|
||||
flex-wrap: nowrap !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.wechat-photo-blur {
|
||||
@@ -4737,7 +4746,7 @@
|
||||
|
||||
/* 通话中对话框 */
|
||||
.wechat-voice-call-chat {
|
||||
margin: 0 16px;
|
||||
margin: 0 16px 15px 16px;
|
||||
background: transparent;
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
@@ -4836,6 +4845,7 @@
|
||||
background: rgba(50, 50, 50, 0.8);
|
||||
border-radius: 20px;
|
||||
padding: 4px 4px 4px 16px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.wechat-voice-call-input {
|
||||
@@ -6888,7 +6898,7 @@
|
||||
|
||||
.wechat-moment-images.grid-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
max-width: 280px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.wechat-moment-images.grid-3,
|
||||
@@ -6974,6 +6984,19 @@
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 当朋友圈图片区域只包含照片描述卡片时,使用垂直布局 */
|
||||
.wechat-moment-images:has(.wechat-moment-photo-card) {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 8px;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.wechat-moment-images:has(.wechat-moment-photo-card) .wechat-moment-photo-card {
|
||||
width: 100%;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.wechat-moment-photo-card .wechat-photo-blur {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
@@ -11284,6 +11307,7 @@
|
||||
/* 聊天区域 */
|
||||
.wechat-toy-control-chat {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
margin: 0 10px;
|
||||
@@ -11291,6 +11315,25 @@
|
||||
box-shadow: inset 0 2px 8px rgba(255, 107, 138, 0.1);
|
||||
}
|
||||
|
||||
/* 聊天区域滚动条样式 */
|
||||
.wechat-toy-control-chat::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.wechat-toy-control-chat::-webkit-scrollbar-track {
|
||||
background: rgba(255, 107, 138, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.wechat-toy-control-chat::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 107, 138, 0.4);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.wechat-toy-control-chat::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 107, 138, 0.6);
|
||||
}
|
||||
|
||||
.wechat-toy-control-messages {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
@@ -11416,6 +11459,18 @@
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.wechat-dark .wechat-toy-control-chat::-webkit-scrollbar-track {
|
||||
background: rgba(255, 107, 138, 0.05);
|
||||
}
|
||||
|
||||
.wechat-dark .wechat-toy-control-chat::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 107, 138, 0.3);
|
||||
}
|
||||
|
||||
.wechat-dark .wechat-toy-control-chat::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 107, 138, 0.5);
|
||||
}
|
||||
|
||||
.wechat-dark .wechat-toy-control-msg.ai {
|
||||
background: #2a2a2a;
|
||||
color: #e9e9e9;
|
||||
@@ -11500,6 +11555,25 @@
|
||||
padding: 2px 8px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.wechat-toy-target-close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
opacity: 0.7;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.wechat-toy-target-close-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wechat-toy-history-card-meta {
|
||||
@@ -11514,11 +11588,30 @@
|
||||
|
||||
.wechat-toy-history-card-messages {
|
||||
padding: 10px 14px;
|
||||
max-height: 200px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 心动瞬间消息滚动条样式 */
|
||||
.wechat-toy-history-scrollable::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.wechat-toy-history-scrollable::-webkit-scrollbar-track {
|
||||
background: rgba(255, 107, 138, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.wechat-toy-history-scrollable::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 107, 138, 0.4);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.wechat-toy-history-scrollable::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 107, 138, 0.6);
|
||||
}
|
||||
|
||||
.wechat-toy-history-msg {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
@@ -11567,3 +11660,117 @@
|
||||
.wechat-dark .wechat-toy-history-msg-content {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
/* ===== 悬浮球样式 ===== */
|
||||
.wechat-floating-ball {
|
||||
position: fixed;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
z-index: 99999;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
touch-action: none;
|
||||
transition: transform 0.15s ease, opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.wechat-floating-ball:hover {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.wechat-floating-ball:active,
|
||||
.wechat-floating-ball.dragging {
|
||||
transform: scale(0.95);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.wechat-floating-ball .floating-ball-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: drop-shadow(0 2px 8px rgba(255, 182, 193, 0.4));
|
||||
transition: filter 0.2s ease;
|
||||
}
|
||||
|
||||
.wechat-floating-ball:hover .floating-ball-svg {
|
||||
filter: drop-shadow(0 4px 12px rgba(255, 182, 193, 0.6));
|
||||
}
|
||||
|
||||
/* 悬浮球呼吸动画(可选,可删除) */
|
||||
@keyframes floating-ball-breathe {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
}
|
||||
|
||||
.wechat-floating-ball:not(:hover):not(.dragging) {
|
||||
animation: floating-ball-breathe 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ===== 缩小手机样式 ===== */
|
||||
#wechat-minimize-btn {
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
margin: -4px -8px;
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
#wechat-minimize-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.wechat-dark #wechat-minimize-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* 缩小状态 */
|
||||
.wechat-phone.minimized {
|
||||
transform: scale(0.25) !important;
|
||||
transform-origin: center center;
|
||||
position: fixed !important;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.3s ease,
|
||||
top 0.3s ease,
|
||||
left 0.3s ease,
|
||||
right 0.3s ease;
|
||||
z-index: 99998;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.wechat-phone.minimized:hover {
|
||||
transform: scale(0.27) !important;
|
||||
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* 缩小时禁用内部交互,但允许事件冒泡到父元素 */
|
||||
.wechat-phone.minimized::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 99999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wechat-phone.minimized * {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
/* 缩小时的过渡动画 */
|
||||
.wechat-phone {
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
103
summary.js
103
summary.js
@@ -852,3 +852,106 @@ export async function rollbackSummary() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从酒馆世界书恢复总结数据
|
||||
* 当插件的 selectedLorebooks 条目丢失但酒馆世界书还在时使用
|
||||
*/
|
||||
export async function recoverFromTavernWorldbook() {
|
||||
const settings = getSettings();
|
||||
const selectedLorebooks = settings.selectedLorebooks || [];
|
||||
|
||||
// 找到所有总结生成的世界书(条目为空的)
|
||||
const emptyBooks = selectedLorebooks.filter(lb =>
|
||||
(lb.fromSummary === true || (lb.name && lb.name.startsWith(LOREBOOK_NAME_PREFIX))) &&
|
||||
(!lb.entries || lb.entries.length === 0)
|
||||
);
|
||||
|
||||
if (emptyBooks.length === 0) {
|
||||
alert('没有需要恢复的世界书(所有世界书都有条目,或没有总结类世界书)');
|
||||
return;
|
||||
}
|
||||
|
||||
const options = emptyBooks.map((lb, idx) => `${idx + 1}. ${lb.name}`).join('\n');
|
||||
const choice = prompt(`以下世界书条目为空,可尝试从酒馆恢复:\n\n${options}\n\n输入序号(或输入 all 恢复全部):`);
|
||||
|
||||
if (!choice) return;
|
||||
|
||||
const booksToRecover = choice.toLowerCase() === 'all'
|
||||
? emptyBooks
|
||||
: [emptyBooks[parseInt(choice) - 1]].filter(Boolean);
|
||||
|
||||
if (booksToRecover.length === 0) {
|
||||
alert('无效的选择');
|
||||
return;
|
||||
}
|
||||
|
||||
let recoveredCount = 0;
|
||||
let totalEntries = 0;
|
||||
|
||||
for (const book of booksToRecover) {
|
||||
try {
|
||||
const name = book.name;
|
||||
|
||||
// 检查酒馆世界书是否存在
|
||||
const worldExists = typeof world_names !== 'undefined' &&
|
||||
Array.isArray(world_names) &&
|
||||
world_names.includes(name);
|
||||
|
||||
if (!worldExists) {
|
||||
console.log(`[可乐] 酒馆中不存在世界书: ${name}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 加载酒馆世界书
|
||||
if (typeof loadWorldInfo !== 'function') {
|
||||
console.error('[可乐] loadWorldInfo 函数不可用');
|
||||
continue;
|
||||
}
|
||||
|
||||
const worldInfo = await loadWorldInfo(name);
|
||||
if (!worldInfo?.entries || Object.keys(worldInfo.entries).length === 0) {
|
||||
console.log(`[可乐] 酒馆世界书 ${name} 没有条目`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 将酒馆条目转换为插件格式
|
||||
const entries = [];
|
||||
const sortedKeys = Object.keys(worldInfo.entries).sort((a, b) => parseInt(a) - parseInt(b));
|
||||
|
||||
for (const key of sortedKeys) {
|
||||
const tavernEntry = worldInfo.entries[key];
|
||||
if (!tavernEntry) continue;
|
||||
|
||||
entries.push({
|
||||
content: tavernEntry.content || '',
|
||||
comment: tavernEntry.comment || getCupName(entries.length + 1),
|
||||
keys: tavernEntry.key || [],
|
||||
enabled: !tavernEntry.disable,
|
||||
addedTime: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
if (entries.length > 0) {
|
||||
// 更新插件的 selectedLorebooks
|
||||
const bookIndex = selectedLorebooks.findIndex(lb => lb.name === name);
|
||||
if (bookIndex >= 0) {
|
||||
selectedLorebooks[bookIndex].entries = entries;
|
||||
selectedLorebooks[bookIndex].lastUpdated = new Date().toISOString();
|
||||
recoveredCount++;
|
||||
totalEntries += entries.length;
|
||||
console.log(`[可乐] 已恢复 ${name}: ${entries.length} 条`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[可乐] 恢复 ${book.name} 失败:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
if (recoveredCount > 0) {
|
||||
requestSave();
|
||||
alert(`恢复完成!\n\n已恢复 ${recoveredCount} 个世界书,共 ${totalEntries} 条总结。\n\n请刷新页面查看。`);
|
||||
} else {
|
||||
alert('恢复失败:酒馆中没有找到对应的世界书数据。\n\n数据可能已彻底丢失。');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ async function onButtonPress(buttonId, pressedBy = 'user') {
|
||||
hideToyTypingIndicator();
|
||||
|
||||
if (response) {
|
||||
processAIResponse(response);
|
||||
await processAIResponse(response);
|
||||
}
|
||||
} catch (err) {
|
||||
hideToyTypingIndicator();
|
||||
@@ -290,7 +290,7 @@ function updateButtonState(buttonId) {
|
||||
// 构建按钮按下提示词
|
||||
function buildButtonPressPrompt(buttonId, buttonName, pressedBy) {
|
||||
const isCharacterUsing = toyControlState.target === 'character';
|
||||
const presserText = pressedBy === 'user' ? '用户' : '你自己';
|
||||
const isAIPress = pressedBy === 'ai';
|
||||
|
||||
const modeEffects = {
|
||||
classic: '稳定持续的震动开始了',
|
||||
@@ -301,18 +301,40 @@ function buildButtonPressPrompt(buttonId, buttonName, pressedBy) {
|
||||
shock: '一阵微电流刺激瞬间传来,让人猛地一颤'
|
||||
};
|
||||
|
||||
let prompt = `[${presserText}按下了"${buttonName}"按钮]
|
||||
let prompt;
|
||||
|
||||
if (isAIPress) {
|
||||
// AI主动按按钮的情况
|
||||
prompt = `[你主动按下了"${buttonName}"按钮]
|
||||
|
||||
效果:${modeEffects[buttonId]}
|
||||
|
||||
`;
|
||||
|
||||
if (isCharacterUsing) {
|
||||
prompt += `你正在使用${toyControlState.gift.giftName},请根据这个刺激变化做出反应。
|
||||
描述你的身体感受、情绪变化。回复要有情感细节,符合你的角色性格。`;
|
||||
if (isCharacterUsing) {
|
||||
prompt += `你主动切换了${toyControlState.gift.giftName}的模式,请描述你主动调整后的反应:
|
||||
- 为什么要主动切换这个模式(想要更多刺激/受不了想暂停/想换个感觉等)
|
||||
- 切换后的身体感受和情绪变化
|
||||
- 回复要有情感细节,符合你的角色性格`;
|
||||
} else {
|
||||
prompt += `你主动控制了用户正在使用的${toyControlState.gift.giftName},请描述你主动操作后的感受:
|
||||
- 为什么要主动给用户切换这个模式(想折磨对方/想看对方的反应/调侃等)
|
||||
- 可以调侃、挑逗用户
|
||||
- 回复要有趣,符合你的角色性格`;
|
||||
}
|
||||
} else {
|
||||
prompt += `用户正在使用${toyControlState.gift.giftName},你在观察。
|
||||
// 用户按按钮的情况
|
||||
prompt = `[用户按下了"${buttonName}"按钮]
|
||||
|
||||
效果:${modeEffects[buttonId]}
|
||||
|
||||
`;
|
||||
if (isCharacterUsing) {
|
||||
prompt += `你正在使用${toyControlState.gift.giftName},请根据这个刺激变化做出反应。
|
||||
描述你的身体感受、情绪变化。回复要有情感细节,符合你的角色性格。`;
|
||||
} else {
|
||||
prompt += `用户正在使用${toyControlState.gift.giftName},你在观察。
|
||||
请描述你观察到的用户可能的反应,可以调侃、鼓励或挑逗。回复要有趣,符合你的角色性格。`;
|
||||
}
|
||||
}
|
||||
|
||||
prompt += `
|
||||
@@ -351,7 +373,7 @@ async function sendToyMessage() {
|
||||
hideToyTypingIndicator();
|
||||
|
||||
if (response) {
|
||||
processAIResponse(response);
|
||||
await processAIResponse(response);
|
||||
}
|
||||
} catch (err) {
|
||||
hideToyTypingIndicator();
|
||||
@@ -416,15 +438,20 @@ async function callToyAI(prompt) {
|
||||
return await callAI(toyControlState.contact, prompt, historyMessages);
|
||||
}
|
||||
|
||||
// 辅助函数:延迟
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// 处理AI回复(检测是否有按按钮指令)
|
||||
function processAIResponse(response) {
|
||||
async function processAIResponse(response) {
|
||||
if (!response) return;
|
||||
|
||||
// 分割多条消息
|
||||
const parts = splitAIMessages(response);
|
||||
|
||||
for (const part of parts) {
|
||||
let reply = part.trim();
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
let reply = parts[i].trim();
|
||||
|
||||
// 过滤特殊标签
|
||||
reply = reply.replace(/<\s*meme\s*>[\s\S]*?<\s*\/\s*meme\s*>/gi, '').trim();
|
||||
@@ -441,6 +468,13 @@ function processAIResponse(response) {
|
||||
reply = reply.replace(/([^)]*)/g, '').trim();
|
||||
reply = reply.replace(/\([^)]*\)/g, '').trim();
|
||||
|
||||
// 如果不是第一条消息,显示typing并延迟
|
||||
if (i > 0 && reply) {
|
||||
showToyTypingIndicator();
|
||||
await sleep(1500 + Math.random() * 1000); // 1.5-2.5秒延迟
|
||||
hideToyTypingIndicator();
|
||||
}
|
||||
|
||||
// 添加AI消息
|
||||
if (reply) {
|
||||
addToyMessage('ai', reply);
|
||||
@@ -459,6 +493,13 @@ function processAIResponse(response) {
|
||||
reply = reply.replace(/([^)]*)/g, '').trim();
|
||||
reply = reply.replace(/\([^)]*\)/g, '').trim();
|
||||
|
||||
// 如果不是第一条消息,显示typing并延迟
|
||||
if (i > 0 && reply) {
|
||||
showToyTypingIndicator();
|
||||
await sleep(1500 + Math.random() * 1000); // 1.5-2.5秒延迟
|
||||
hideToyTypingIndicator();
|
||||
}
|
||||
|
||||
if (reply) {
|
||||
addToyMessage('ai', reply);
|
||||
}
|
||||
@@ -527,7 +568,7 @@ async function triggerToyAIGreeting() {
|
||||
hideToyTypingIndicator();
|
||||
|
||||
if (response) {
|
||||
processAIResponse(response);
|
||||
await processAIResponse(response);
|
||||
}
|
||||
} catch (err) {
|
||||
hideToyTypingIndicator();
|
||||
|
||||
@@ -351,6 +351,26 @@ export function checkVideoAIHangupAfterReply() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检测AI是否有挂断意图
|
||||
function detectVideoHangupIntent(text) {
|
||||
if (!text) return false;
|
||||
const hangupPatterns = [
|
||||
/我(先)?挂了/,
|
||||
/那我挂了/,
|
||||
/先挂(了)?啊?/,
|
||||
/挂了(啊|哈|呀|哦)?$/,
|
||||
/我(要)?挂(电话|断)了/,
|
||||
/拜拜.*挂/,
|
||||
/挂.*拜拜/,
|
||||
/再见.*挂/,
|
||||
/不聊了.*挂/,
|
||||
/不说了.*挂/,
|
||||
/那就这样.*挂/,
|
||||
/就这样吧.*挂/
|
||||
];
|
||||
return hangupPatterns.some(pattern => pattern.test(text));
|
||||
}
|
||||
|
||||
// AI主动挂断视频电话
|
||||
function videoAIHangup() {
|
||||
if (!videoCallState.isConnected) return;
|
||||
@@ -505,7 +525,7 @@ function appendVideoCallRecordMessage(role, status, duration, contact) {
|
||||
: firstChar);
|
||||
|
||||
// 摄像机图标
|
||||
const cameraIconSVG = `<svg class="wechat-call-record-icon wechat-video-call-icon" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
const cameraIconSVG = `<svg class="wechat-call-record-icon wechat-video-call-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="6" width="13" height="12" rx="2"/>
|
||||
<path d="M22 8l-7 4 7 4V8z"/>
|
||||
</svg>`;
|
||||
@@ -1053,7 +1073,19 @@ async function sendVideoCallMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
// AI回复完成后,检查是否要主动挂断(5%概率,通话30秒后生效)
|
||||
// AI回复完成后,检查是否要主动挂断
|
||||
// 1. 检测AI的挂断意图(如"我挂了"、"先挂了"等)
|
||||
const fullReply = parts.join(' ');
|
||||
if (detectVideoHangupIntent(fullReply)) {
|
||||
console.log('[可乐] 检测到视频通话AI挂断意图:', fullReply);
|
||||
setTimeout(() => {
|
||||
if (videoCallState.isConnected) {
|
||||
videoAIHangup();
|
||||
}
|
||||
}, 1500 + Math.random() * 1000);
|
||||
return;
|
||||
}
|
||||
// 2. 随机5%概率挂断(通话30秒后生效)
|
||||
checkVideoAIHangupAfterReply();
|
||||
} catch (err) {
|
||||
hideVideoCallTypingIndicator();
|
||||
|
||||
@@ -310,6 +310,27 @@ export function checkAIHangupAfterReply() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检测AI是否有挂断意图
|
||||
function detectHangupIntent(text) {
|
||||
if (!text) return false;
|
||||
// 常见的挂断表达
|
||||
const hangupPatterns = [
|
||||
/我(先)?挂了/,
|
||||
/那我挂了/,
|
||||
/先挂(了)?啊?/,
|
||||
/挂了(啊|哈|呀|哦)?$/,
|
||||
/我(要)?挂(电话|断)了/,
|
||||
/拜拜.*挂/,
|
||||
/挂.*拜拜/,
|
||||
/再见.*挂/,
|
||||
/不聊了.*挂/,
|
||||
/不说了.*挂/,
|
||||
/那就这样.*挂/,
|
||||
/就这样吧.*挂/
|
||||
];
|
||||
return hangupPatterns.some(pattern => pattern.test(text));
|
||||
}
|
||||
|
||||
// AI主动挂断电话
|
||||
function aiHangup() {
|
||||
if (!callState.isConnected) return;
|
||||
@@ -489,7 +510,7 @@ function appendCallRecordMessage(role, status, duration, contact) {
|
||||
|
||||
// 通话记录卡片内容
|
||||
// 线条电话图标
|
||||
const phoneIconSVG = `<svg class="wechat-call-record-icon" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
const phoneIconSVG = `<svg class="wechat-call-record-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>
|
||||
</svg>`;
|
||||
|
||||
@@ -956,7 +977,19 @@ async function sendCallMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
// AI回复完成后,检查是否要主动挂断(5%概率,通话30秒后生效)
|
||||
// AI回复完成后,检查是否要主动挂断
|
||||
// 1. 检测AI的挂断意图(如"我挂了"、"先挂了"等)
|
||||
const fullReply = parts.join(' ');
|
||||
if (detectHangupIntent(fullReply)) {
|
||||
console.log('[可乐] 检测到AI挂断意图:', fullReply);
|
||||
setTimeout(() => {
|
||||
if (callState.isConnected) {
|
||||
aiHangup();
|
||||
}
|
||||
}, 1500 + Math.random() * 1000);
|
||||
return;
|
||||
}
|
||||
// 2. 随机5%概率挂断(通话30秒后生效)
|
||||
checkAIHangupAfterReply();
|
||||
} catch (err) {
|
||||
hideCallTypingIndicator();
|
||||
|
||||
Reference in New Issue
Block a user