/** * 联系人管理 */ import { saveSettingsDebounced } from '../../../../script.js'; import { getSettings, LOREBOOK_NAME_PREFIX, LOREBOOK_NAME_SUFFIX } from './config.js'; import { generateContactsList } from './ui.js'; import { showToast } from './toast.js'; // 当前换头像的联系人索引 let pendingAvatarContactIndex = -1; // 当前编辑的联系人索引 let currentEditingContactIndex = -1; // 添加联系人 export function addContact(characterData) { const settings = getSettings(); const now = new Date(); const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`; const exists = settings.contacts.some(c => c.name === characterData.name); if (exists) { showToast('该角色已在联系人列表中', '⚠️'); return false; } settings.contacts.push({ id: 'contact_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9), name: characterData.name, description: characterData.description?.substring(0, 50) + '...' || '', avatar: characterData.avatar, importTime: timeStr, rawData: characterData.rawData, // 角色独立配置 useCustomApi: false, customApiUrl: '', customApiKey: '', customModel: '', customHakimiBreakLimit: false }); saveSettingsDebounced(); refreshContactsList(); return true; } // 刷新联系人列表 export function refreshContactsList() { const contactsContainer = document.getElementById('wechat-contacts'); if (contactsContainer) { contactsContainer.innerHTML = generateContactsList(); bindContactsEvents(); } } // 删除联系人 export function deleteContact(index) { const settings = getSettings(); const contact = settings.contacts[index]; if (!contact) return; if (confirm(`确定要删除 ${contact.name} 吗?`)) { // 删除关联的世界书(角色卡世界书和总结世界书) deleteContactLorebooks(contact); settings.contacts.splice(index, 1); saveSettingsDebounced(); refreshContactsList(); } } // 删除联系人关联的世界书 function deleteContactLorebooks(contact) { const settings = getSettings(); if (!settings.selectedLorebooks) return; const contactName = contact.name; const contactId = contact.id; // 从后往前遍历删除,避免索引问题 for (let i = settings.selectedLorebooks.length - 1; i >= 0; i--) { const lb = settings.selectedLorebooks[i]; // 检查是否是该联系人的角色卡世界书 const isCharacterBook = lb.fromCharacter === true && (lb.characterName === contactName || lb.characterId === contactId); // 检查是否是该联系人的总结世界书 const summaryBookName = `${LOREBOOK_NAME_PREFIX}${contactName}${LOREBOOK_NAME_SUFFIX}`; const isSummaryBook = lb.name === summaryBookName; if (isCharacterBook || isSummaryBook) { console.log(`[可乐不加冰] 删除关联世界书: ${lb.name}`); settings.selectedLorebooks.splice(i, 1); } } } // 删除群聊 export function deleteGroupChat(groupIndex) { const settings = getSettings(); const groupChats = settings.groupChats || []; const group = groupChats[groupIndex]; if (!group) return; if (confirm(`确定要删除该群聊吗?`)) { // 删除群聊关联的总结世界书 deleteGroupLorebooks(group, settings); groupChats.splice(groupIndex, 1); saveSettingsDebounced(); refreshContactsList(); // 同时刷新聊天列表 import('./ui.js').then(m => m.refreshChatList()); showToast('群聊已删除'); } } // 删除群聊关联的世界书 function deleteGroupLorebooks(group, settings) { if (!settings.selectedLorebooks) return; // 获取群成员名称列表构建世界书名称 const memberNames = (group.memberIds || []).map(id => { const contact = settings.contacts?.find(c => c.id === id); return contact?.name || '未知'; }); const memberNamesStr = memberNames.join(','); const summaryBookName = `${LOREBOOK_NAME_PREFIX}${memberNamesStr}${LOREBOOK_NAME_SUFFIX}`; // 从后往前遍历删除,避免索引问题 for (let i = settings.selectedLorebooks.length - 1; i >= 0; i--) { const lb = settings.selectedLorebooks[i]; if (lb.name === summaryBookName) { console.log(`[可乐不加冰] 删除群聊关联世界书: ${lb.name}`); settings.selectedLorebooks.splice(i, 1); } } } // 更换角色头像(在设置弹窗中使用) export function changeContactAvatar(contactIndex) { pendingAvatarContactIndex = contactIndex; let input = document.getElementById('wechat-contact-avatar-input'); if (!input) { input = document.createElement('input'); input.type = 'file'; input.id = 'wechat-contact-avatar-input'; input.accept = 'image/*'; input.style.display = 'none'; document.body.appendChild(input); input.addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file || pendingAvatarContactIndex < 0) return; try { const reader = new FileReader(); reader.onload = function(event) { const settings = getSettings(); if (settings.contacts[pendingAvatarContactIndex]) { settings.contacts[pendingAvatarContactIndex].avatar = event.target.result; saveSettingsDebounced(); refreshContactsList(); // 更新弹窗中的头像预览 updateContactSettingsAvatar(pendingAvatarContactIndex); showToast('角色头像已更换'); } }; reader.readAsDataURL(file); } catch (err) { console.error('[可乐] 更换角色头像失败:', err); showToast('更换头像失败: ' + err.message, '❌'); } e.target.value = ''; }); } input.click(); } // 更新弹窗中的头像预览 function updateContactSettingsAvatar(contactIndex) { const settings = getSettings(); const contact = settings.contacts[contactIndex]; if (!contact) return; const avatarPreview = document.getElementById('wechat-contact-avatar-preview'); if (avatarPreview) { const firstChar = contact.name ? contact.name.charAt(0) : '?'; avatarPreview.innerHTML = contact.avatar ? `` : firstChar; } } // 打开角色设置弹窗 export function openContactSettings(contactIndex) { const settings = getSettings(); const contact = settings.contacts[contactIndex]; if (!contact) return; currentEditingContactIndex = contactIndex; // 填充头像和名称 const avatarPreview = document.getElementById('wechat-contact-avatar-preview'); const nameEl = document.getElementById('wechat-contact-settings-name'); if (avatarPreview) { const firstChar = contact.name ? contact.name.charAt(0) : '?'; avatarPreview.innerHTML = contact.avatar ? `` : firstChar; } if (nameEl) nameEl.textContent = contact.name; // 填充独立 API 配置 const useCustomApi = contact.useCustomApi || false; const customApiToggle = document.getElementById('wechat-contact-custom-api-toggle'); const apiSettingsDiv = document.getElementById('wechat-contact-api-settings'); if (customApiToggle) { customApiToggle.classList.toggle('on', useCustomApi); } if (apiSettingsDiv) { if (useCustomApi) { apiSettingsDiv.classList.remove('hidden'); apiSettingsDiv.style.display = 'flex'; } else { apiSettingsDiv.classList.add('hidden'); apiSettingsDiv.style.display = 'none'; } } document.getElementById('wechat-contact-api-url').value = contact.customApiUrl || ''; document.getElementById('wechat-contact-api-key').value = contact.customApiKey || ''; document.getElementById('wechat-contact-model').value = contact.customModel || ''; // 填充哈基米破限 const hakimiToggle = document.getElementById('wechat-contact-hakimi-toggle'); if (hakimiToggle) { hakimiToggle.classList.toggle('on', contact.customHakimiBreakLimit || false); } // 显示弹窗 document.getElementById('wechat-contact-settings-modal')?.classList.remove('hidden'); } // 保存角色设置 export function saveContactSettings() { if (currentEditingContactIndex < 0) return; const settings = getSettings(); const contact = settings.contacts[currentEditingContactIndex]; if (!contact) return; // 保存独立 API 配置 contact.useCustomApi = document.getElementById('wechat-contact-custom-api-toggle')?.classList.contains('on') || false; contact.customApiUrl = document.getElementById('wechat-contact-api-url')?.value?.trim() || ''; contact.customApiKey = document.getElementById('wechat-contact-api-key')?.value?.trim() || ''; contact.customModel = document.getElementById('wechat-contact-model')?.value?.trim() || ''; // 保存哈基米破限 contact.customHakimiBreakLimit = document.getElementById('wechat-contact-hakimi-toggle')?.classList.contains('on') || false; saveSettingsDebounced(); showToast('角色设置已保存'); // 关闭弹窗 document.getElementById('wechat-contact-settings-modal')?.classList.add('hidden'); currentEditingContactIndex = -1; } // 关闭角色设置弹窗 export function closeContactSettings() { document.getElementById('wechat-contact-settings-modal')?.classList.add('hidden'); currentEditingContactIndex = -1; } // 获取当前编辑的联系人索引 export function getCurrentEditingContactIndex() { return currentEditingContactIndex; } // 绑定联系人事件 export function bindContactsEvents() { // 导入 openChat 以避免循环依赖 import('./chat.js').then(chatModule => { // 单击卡片进入聊天 document.querySelectorAll('.wechat-contact-card:not(.wechat-group-card) .wechat-card-content').forEach(card => { card.addEventListener('click', function(e) { if (e.target.closest('.wechat-card-avatar')) return; const cardEl = this.closest('.wechat-contact-card'); const index = parseInt(cardEl.dataset.index); chatModule.openChat(index); }); }); }); // 群聊卡片点击进入群聊 import('./group-chat.js').then(groupModule => { document.querySelectorAll('.wechat-group-card .wechat-card-content').forEach(card => { card.addEventListener('click', function(e) { const cardEl = this.closest('.wechat-group-card'); const groupIndex = parseInt(cardEl.dataset.groupIndex); groupModule.openGroupChat(groupIndex); }); }); // 群聊头像点击也进入群聊 document.querySelectorAll('.wechat-group-avatar').forEach(avatar => { avatar.addEventListener('click', function(e) { e.stopPropagation(); const groupIndex = parseInt(this.dataset.groupIndex); groupModule.openGroupChat(groupIndex); }); }); }); // 群聊删除按钮 document.querySelectorAll('.wechat-group-delete').forEach(btn => { btn.addEventListener('click', function(e) { e.stopPropagation(); const groupIndex = parseInt(this.dataset.groupIndex); deleteGroupChat(groupIndex); }); }); // 头像事件绑定(长按删除 + 单击打开设置) document.querySelectorAll('.wechat-card-avatar').forEach(avatar => { let pressTimer = null; let isLongPress = false; // 长按开始 const handlePressStart = (e) => { isLongPress = false; pressTimer = setTimeout(() => { isLongPress = true; showDeleteBubble(avatar); }, 500); }; // 长按取消 const handlePressEnd = (e) => { clearTimeout(pressTimer); // 如果不是长按,则执行单击打开设置弹窗 if (!isLongPress) { const index = parseInt(avatar.dataset.index); openContactSettings(index); } }; // 移动时取消长按 const handlePressCancel = () => { clearTimeout(pressTimer); }; // 触摸设备 avatar.addEventListener('touchstart', handlePressStart, { passive: true }); avatar.addEventListener('touchend', handlePressEnd); avatar.addEventListener('touchmove', handlePressCancel, { passive: true }); avatar.addEventListener('touchcancel', handlePressCancel); // 鼠标设备 avatar.addEventListener('mousedown', handlePressStart); avatar.addEventListener('mouseup', handlePressEnd); avatar.addEventListener('mouseleave', handlePressCancel); // 阻止原有的click事件 avatar.addEventListener('click', (e) => { e.stopPropagation(); }); }); // 删除按钮点击 document.querySelectorAll('.wechat-card-delete').forEach(btn => { btn.addEventListener('click', function(e) { e.stopPropagation(); const index = parseInt(this.dataset.index); deleteContact(index); }); }); // 点击其他地方关闭删除气泡 document.addEventListener('click', hideDeleteBubble); document.addEventListener('touchstart', hideDeleteBubble, { passive: true }); // 初始化滑动删除功能 initSwipeToDelete(); } // 显示删除气泡 function showDeleteBubble(avatarEl) { // 先移除已有的气泡 hideDeleteBubble(); const index = parseInt(avatarEl.dataset.index); const settings = getSettings(); const contact = settings.contacts[index]; if (!contact) return; // 创建删除气泡 const bubble = document.createElement('div'); bubble.className = 'wechat-delete-bubble'; bubble.dataset.index = index; bubble.innerHTML = `🗑️ 删除`; // 添加到头像元素 avatarEl.style.position = 'relative'; avatarEl.classList.add('has-bubble'); avatarEl.appendChild(bubble); // 绑定删除事件 bubble.addEventListener('click', (e) => { e.stopPropagation(); const idx = parseInt(bubble.dataset.index); deleteContactDirect(idx); hideDeleteBubble(); }); // 触摸设备 bubble.addEventListener('touchend', (e) => { e.stopPropagation(); e.preventDefault(); const idx = parseInt(bubble.dataset.index); deleteContactDirect(idx); hideDeleteBubble(); }); } // 隐藏删除气泡 function hideDeleteBubble(e) { // 如果点击的是气泡本身,不关闭 if (e && e.target.closest('.wechat-delete-bubble')) return; const bubbles = document.querySelectorAll('.wechat-delete-bubble'); bubbles.forEach(bubble => bubble.remove()); document.querySelectorAll('.wechat-card-avatar.has-bubble').forEach(avatar => { avatar.classList.remove('has-bubble'); }); } // 直接删除联系人(不需要确认) function deleteContactDirect(index) { const settings = getSettings(); const contact = settings.contacts[index]; if (!contact) return; // 删除关联的世界书(角色卡世界书和总结世界书) deleteContactLorebooks(contact); settings.contacts.splice(index, 1); saveSettingsDebounced(); refreshContactsList(); } // 初始化滑动删除功能 function initSwipeToDelete() { const cards = document.querySelectorAll('.wechat-contact-card'); cards.forEach(card => { const wrapper = card.querySelector('.wechat-card-swipe-wrapper'); if (!wrapper || wrapper.dataset.swipeInit) return; wrapper.dataset.swipeInit = 'true'; const isGroupCard = card.classList.contains('wechat-group-card'); let startX = 0; let currentX = 0; let isDragging = false; let hasMoved = false; // 是否真的发生了移动 let isOpen = false; const deleteWidth = 70; const moveThreshold = 10; // 移动阈值,超过此距离才算拖动 const handleStart = (e) => { // 群聊卡片不需要跳过头像 if (!isGroupCard && e.target.closest('.wechat-card-avatar')) return; isDragging = true; hasMoved = false; startX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX; wrapper.style.transition = 'none'; }; const handleMove = (e) => { if (!isDragging) return; const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX; const diff = clientX - startX; // 只有移动超过阈值才算真正的拖动 if (Math.abs(diff) > moveThreshold) { hasMoved = true; } if (!hasMoved) return; let newX = isOpen ? -deleteWidth + diff : diff; newX = Math.max(-deleteWidth, Math.min(0, newX)); currentX = newX; wrapper.style.transform = `translateX(${newX}px)`; }; const handleEnd = () => { if (!isDragging) return; isDragging = false; wrapper.style.transition = 'transform 0.3s ease'; // 如果没有真正移动,不做任何处理,让点击事件正常触发 if (!hasMoved) { return; } if (currentX < -deleteWidth / 2) { wrapper.style.transform = `translateX(-${deleteWidth}px)`; isOpen = true; } else { wrapper.style.transform = 'translateX(0)'; isOpen = false; } }; const closeOthers = () => { cards.forEach(otherCard => { if (otherCard !== card) { const otherWrapper = otherCard.querySelector('.wechat-card-swipe-wrapper'); if (otherWrapper) { otherWrapper.style.transition = 'transform 0.3s ease'; otherWrapper.style.transform = 'translateX(0)'; } } }); }; wrapper.addEventListener('touchstart', (e) => { closeOthers(); handleStart(e); }, { passive: true }); wrapper.addEventListener('touchmove', handleMove, { passive: true }); wrapper.addEventListener('touchend', handleEnd); const onMouseMove = (e) => handleMove(e); const onMouseUp = (e) => { handleEnd(); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; wrapper.addEventListener('mousedown', (e) => { if (!isGroupCard && e.target.closest('.wechat-card-avatar')) return; closeOthers(); handleStart(e); document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); // 不再 preventDefault,让点击事件可以正常触发 }); }); }