/** * 可乐不加冰 - 主入口(模块化) */ console.log('[可乐] main.js 开始加载...'); import { requestSave, setupUnloadSave } from './save-manager.js'; import { loadSettings, getSettings, MEME_PROMPT_TEMPLATE } from './config.js'; import { generatePhoneHTML } from './phone-html.js'; import { showPage, refreshChatList, updateMePageInfo, getUserPersonaFromST, updateTabBadge } from './ui.js'; import { showToast } from './toast.js'; 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, recoverFromTavernWorldbook } from './summary.js'; import { fetchModelListFromApi } from './ai.js'; import { extractCharacterFromPNG, extractCharacterFromJSON, importCharacterToST } from './character-import.js'; import { setupPhoneAutoCentering, setupPhoneDrag, centerPhoneInViewport } from './phone.js'; import { showGroupCreateModal, closeGroupCreateModal, createGroupChat, sendGroupMessage, isInGroupChat, setCurrentGroupChatIndex, getCurrentGroupIndex, openGroupChat } from './group-chat.js'; import { toggleDarkMode, refreshContextTags } from './settings-ui.js'; import { initFuncPanel, toggleFuncPanel, hideFuncPanel, showExpandVoice, closeExpandPanel, sendExpandContent } from './chat-func-panel.js'; import { initEmojiPanel, toggleEmojiPanel, hideEmojiPanel } from './emoji-panel.js'; import { injectAuthorNote, setupMessageObserver, addExtensionButton } from './st-integration.js'; import { getCurrentTime } from './utils.js'; import { refreshHistoryList, refreshLogsList, clearErrorLogs, initErrorCapture, addErrorLog, renderToyHistory } from './history-logs.js'; import { initChatBackground } from './chat-background.js'; import { initMoments, openMomentsPage, clearContactMoments } from './moments.js'; import { initRedPacketEvents } from './red-packet.js'; 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'; let currentHistoryContactIndex = -1; function openHistoryPage(contactIndex) { const settings = getSettings(); const contact = settings.contacts?.[contactIndex]; if (!contact) return; currentHistoryContactIndex = contactIndex; currentHistoryTab = 'listen'; const page = document.getElementById('wechat-history-page'); if (page) { page.classList.remove('hidden'); // 重置标签状态 document.querySelectorAll('.wechat-history-tab').forEach(tab => { tab.classList.toggle('active', tab.dataset.tab === 'listen'); }); renderHistoryContent(contact, 'listen'); } } function closeHistoryPage() { const page = document.getElementById('wechat-history-page'); if (page) { page.classList.add('hidden'); } currentHistoryContactIndex = -1; } function deleteHistoryRecord(tabType, index) { const settings = getSettings(); const contact = settings.contacts?.[currentHistoryContactIndex]; if (!contact) return; if (tabType === 'listen') { if (contact.listenHistory && contact.listenHistory[index]) { contact.listenHistory.splice(index, 1); } } else if (tabType === 'voice' || tabType === 'video') { // 从 callHistory 中找到并删除对应类型的记录 const callHistory = contact.callHistory || []; const typeRecords = callHistory.filter(r => r.type === tabType); if (typeRecords[index]) { const originalIndex = callHistory.indexOf(typeRecords[index]); if (originalIndex >= 0) { contact.callHistory.splice(originalIndex, 1); } } } else if (tabType === 'toy') { if (contact.toyHistory && contact.toyHistory[index]) { contact.toyHistory.splice(index, 1); } } requestSave(); renderHistoryContent(contact, tabType); } function switchHistoryTab(tabType) { currentHistoryTab = tabType; document.querySelectorAll('.wechat-history-tab').forEach(tab => { tab.classList.toggle('active', tab.dataset.tab === tabType); }); const settings = getSettings(); const contact = settings.contacts?.[currentHistoryContactIndex]; if (contact) { renderHistoryContent(contact, tabType); } } function renderHistoryContent(contact, tabType) { const contentEl = document.getElementById('wechat-history-content'); if (!contentEl) return; // 心动瞬间使用专门的渲染函数 if (tabType === 'toy') { renderToyHistory(contact); // 绑定心动瞬间的删除按钮事件 contentEl.querySelectorAll('.wechat-history-delete-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const index = parseInt(btn.dataset.index); 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; } const context = window.SillyTavern?.getContext?.() || {}; const userName = context.name1 || '用户'; let records = []; if (tabType === 'listen') { records = contact.listenHistory || []; } else { // 从 callHistory 中筛选 voice 或 video const callHistory = contact.callHistory || []; records = callHistory.filter(r => r.type === tabType); } if (records.length === 0) { const emptyText = tabType === 'listen' ? '暂无一起听记录' : tabType === 'voice' ? '暂无语音通话记录' : '暂无视频通话记录'; contentEl.innerHTML = `
📭
${emptyText}
`; return; } // 按时间倒序排列 const sortedRecords = [...records].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); let html = ''; for (let i = 0; i < sortedRecords.length; i++) { const record = sortedRecords[i]; const time = record.time || '未知时间'; const duration = record.duration || ''; const messages = record.messages || []; const originalIndex = records.indexOf(record); html += `
`; html += `
`; html += `${escapeHtml(time)}`; html += `
`; html += ``; if (duration) { html += `${escapeHtml(duration)}`; } html += `
`; html += `
`; // 一起听显示歌曲信息 if (tabType === 'listen' && record.song) { const songName = record.song.name || '未知歌曲'; const songArtist = record.song.artist || '未知歌手'; html += `
[${escapeHtml(songName)} - ${escapeHtml(songArtist)}]
`; } // 消息列表 if (messages.length > 0) { html += `
`; for (const msg of messages) { const isUser = msg.role === 'user'; const senderName = isUser ? userName : contact.name; const senderClass = isUser ? 'user' : ''; html += `
`; html += `${escapeHtml(senderName)}: `; html += `${escapeHtml(msg.content || '')}`; html += `
`; } html += `
`; } html += `
`; } contentEl.innerHTML = html; // 绑定删除按钮事件 contentEl.querySelectorAll('.wechat-history-delete-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const tab = btn.dataset.tab; const index = parseInt(btn.dataset.index); deleteHistoryRecord(tab, index); }); }); } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function initHistoryEvents() { // 返回按钮 document.getElementById('wechat-history-back-btn')?.addEventListener('click', closeHistoryPage); // 标签切换 document.querySelectorAll('.wechat-history-tab').forEach(tab => { tab.addEventListener('click', () => { const tabType = tab.dataset.tab; if (tabType) { switchHistoryTab(tabType); } }); }); } // ========== 历史记录功能结束 ========== function normalizeModelListForSelect(models) { return (models || []).map(m => { if (typeof m === 'string') return { id: m, name: m }; return { id: m?.id || '', name: m?.name || m?.id || '' }; }).filter(m => m.id); } function restoreModelSelect() { // select 元素在 HTML 生成时已经包含了选项,无需额外恢复 } function restoreGroupModelSelect() { // select 元素在 HTML 生成时已经包含了选项,无需额外恢复 } function seedDefaultUserPersonaFromST(settings) { if (Array.isArray(settings.userPersonas) && settings.userPersonas.length > 0) return false; const stPersona = getUserPersonaFromST(); const content = stPersona?.description?.trim(); if (!content) return false; 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')}`; settings.userPersonas = [{ name: (stPersona?.name || '').trim() || '用户设定', content, enabled: true, addedTime: timeStr, }]; return true; } async function refreshModelSelect() { const select = document.getElementById('wechat-model-select'); const refreshBtn = document.getElementById('wechat-refresh-models'); if (!select) return; const settings = getSettings(); const apiUrl = document.getElementById('wechat-api-url')?.value?.trim() || settings.apiUrl || ''; const apiKey = document.getElementById('wechat-api-key')?.value?.trim() || settings.apiKey || ''; if (!apiUrl) { showToast('请先填写 API 地址', 'info'); return; } const originalText = refreshBtn?.textContent; if (refreshBtn) { refreshBtn.textContent = '加载中...'; refreshBtn.disabled = true; } try { const modelIds = await fetchModelListFromApi(apiUrl, apiKey); // 更新 select 选项 select.innerHTML = '' + modelIds.map(id => ``).join(''); settings.modelList = modelIds; requestSave(); showToast(`获取到 ${modelIds.length} 个模型`); } catch (err) { console.error('[可乐] 获取模型列表失败:', err); showToast(`获取失败,请手动输入模型名`, '⚠️'); } finally { if (refreshBtn) { refreshBtn.textContent = originalText; refreshBtn.disabled = false; } } } function syncContextEnabledUI(enabled) { const display = document.getElementById('wechat-context-level-display'); if (display) display.textContent = enabled ? '已开启' : '已关闭'; const settingsSection = document.getElementById('wechat-context-settings'); if (settingsSection) { settingsSection.style.opacity = enabled ? '1' : '0.5'; settingsSection.style.pointerEvents = enabled ? 'auto' : 'none'; } } function updateWalletAmountDisplay() { const settings = getSettings(); const amountEl = document.getElementById('wechat-wallet-amount'); if (!amountEl) return; const amount = settings.walletAmount || '5773.89'; 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 scale = 0.25; const rect = phone.getBoundingClientRect(); const scaledWidth = rect.width; const scaledHeight = rect.height; // 计算新位置:让缩小后的手机中心跟随手指 let newLeft = clientX - scaledWidth / 2; let newTop = clientY - scaledHeight / 2; // 限制在视口内 const maxX = window.innerWidth - scaledWidth; const maxY = window.innerHeight - scaledHeight; newLeft = Math.min(Math.max(0, newLeft), maxX); newTop = Math.min(Math.max(0, newTop), maxY); phone.style.left = newLeft + 'px'; phone.style.top = newTop + 'px'; } 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(); document.getElementById('wechat-dropdown-menu')?.classList.toggle('hidden'); }); // 点击其他地方关闭下拉菜单 document.getElementById('wechat-phone')?.addEventListener('click', (e) => { if (!e.target.closest('#wechat-add-btn') && !e.target.closest('#wechat-dropdown-menu')) { document.getElementById('wechat-dropdown-menu')?.classList.add('hidden'); } }); // 通讯录页面的添加按钮 - 直接进入添加朋友页面 document.getElementById('wechat-contacts-add-btn')?.addEventListener('click', () => { showPage('wechat-add-page'); }); // 下拉菜单 - 添加朋友 document.getElementById('wechat-menu-add-friend')?.addEventListener('click', () => { document.getElementById('wechat-dropdown-menu')?.classList.add('hidden'); showPage('wechat-add-page'); }); // 下拉菜单 - 发起群聊 document.getElementById('wechat-menu-group')?.addEventListener('click', () => { document.getElementById('wechat-dropdown-menu')?.classList.add('hidden'); showGroupCreateModal(); }); // 下拉菜单 - 其他选项(暂时只关闭菜单) ['wechat-menu-scan', 'wechat-menu-pay'].forEach(id => { document.getElementById(id)?.addEventListener('click', () => { document.getElementById('wechat-dropdown-menu')?.classList.add('hidden'); }); }); // 下拉菜单 - 悬浮窗开关 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); // 返回按钮 document.getElementById('wechat-back-btn')?.addEventListener('click', () => { showPage('wechat-main-content'); }); document.getElementById('wechat-chat-back-btn')?.addEventListener('click', () => { setCurrentChatIndex(-1); setCurrentGroupChatIndex(-1); // 清除群聊标记 const messagesContainer = document.getElementById('wechat-chat-messages'); if (messagesContainer) { messagesContainer.dataset.isGroup = 'false'; messagesContainer.dataset.groupIndex = '-1'; // 清除背景 messagesContainer.style.backgroundImage = ''; } // 关闭所有聊天页面板 document.getElementById('wechat-chat-menu')?.classList.add('hidden'); document.getElementById('wechat-recalled-panel')?.classList.add('hidden'); document.getElementById('wechat-chat-bg-panel')?.classList.add('hidden'); showPage('wechat-main-content'); refreshContactsList(); refreshChatList(); }); // ===== 聊天页菜单事件 ===== // 三个点按钮 - 显示聊天菜单 document.getElementById('wechat-chat-more-btn')?.addEventListener('click', (e) => { e.stopPropagation(); const menu = document.getElementById('wechat-chat-menu'); const recalledPanel = document.getElementById('wechat-recalled-panel'); const bgPanel = document.getElementById('wechat-chat-bg-panel'); recalledPanel?.classList.add('hidden'); bgPanel?.classList.add('hidden'); menu?.classList.toggle('hidden'); }); // 撤回消息菜单项 - 显示撤回消息区 document.getElementById('wechat-menu-recalled')?.addEventListener('click', () => { document.getElementById('wechat-chat-menu')?.classList.add('hidden'); showRecalledMessages(); }); // 关闭撤回消息区面板 document.getElementById('wechat-recalled-close')?.addEventListener('click', () => { document.getElementById('wechat-recalled-panel')?.classList.add('hidden'); }); // 查看TA的朋友圈 document.getElementById('wechat-menu-moments')?.addEventListener('click', () => { document.getElementById('wechat-chat-menu')?.classList.add('hidden'); if (currentChatIndex >= 0) { openMomentsPage(currentChatIndex); } }); // 查看历史记录 document.getElementById('wechat-menu-history')?.addEventListener('click', () => { document.getElementById('wechat-chat-menu')?.classList.add('hidden'); if (currentChatIndex >= 0) { openHistoryPage(currentChatIndex); } }); // 清空TA的朋友圈 document.getElementById('wechat-menu-clear-moments')?.addEventListener('click', () => { document.getElementById('wechat-chat-menu')?.classList.add('hidden'); if (currentChatIndex >= 0) { clearContactMoments(currentChatIndex); } }); // 清空当前聊天(支持单聊和群聊) document.getElementById('wechat-menu-clear-chat')?.addEventListener('click', () => { document.getElementById('wechat-chat-menu')?.classList.add('hidden'); const groupIndex = getCurrentGroupIndex(); const settings = getSettings(); // 群聊清空 if (groupIndex >= 0) { if (!confirm('确定要清空当前群聊记录吗?此操作不可恢复。')) return; const groupChat = settings.groupChats?.[groupIndex]; if (groupChat) { groupChat.chatHistory = []; groupChat.lastMessage = ''; requestSave(); openGroupChat(groupIndex); // 刷新群聊界面 showToast('群聊记录已清空'); } return; } // 单聊清空 if (currentChatIndex < 0) return; if (!confirm('确定要清空当前聊天记录吗?此操作不可恢复。')) return; const contact = settings.contacts[currentChatIndex]; if (contact) { contact.chatHistory = []; contact.lastMessage = ''; requestSave(); openChat(currentChatIndex); // 刷新聊天界面 showToast('聊天记录已清空'); } }); // 拉黑/取消拉黑功能 document.getElementById('wechat-menu-block')?.addEventListener('click', async () => { document.getElementById('wechat-chat-menu')?.classList.add('hidden'); // 群聊不支持拉黑 const groupIndex = getCurrentGroupIndex(); if (groupIndex >= 0) { showToast('群聊暂不支持此功能', 'info'); return; } if (currentChatIndex < 0) return; const settings = getSettings(); const contact = settings.contacts[currentChatIndex]; if (!contact) return; const isBlocked = contact.isBlocked === true; if (isBlocked) { // 取消拉黑 if (!confirm(`确定要取消拉黑"${contact.name}"吗?`)) return; contact.isBlocked = false; stopBlockedAIMessages(contact); requestSave(); refreshChatList(); updateBlockMenuText(false); showToast('已取消拉黑', '✓'); // 显示被拉黑期间AI发送的消息 await showBlockedMessages(contact); } else { // 拉黑 if (!confirm(`确定要拉黑"${contact.name}"吗?拉黑后对方将无法给你发消息。`)) return; contact.isBlocked = true; requestSave(); refreshChatList(); updateBlockMenuText(true); showToast('已拉黑', '🚫'); // 开始AI被拉黑期间发消息 startBlockedAIMessages(contact); } }); // 点击聊天页其他地方关闭菜单和面板 document.getElementById('wechat-chat-page')?.addEventListener('click', (e) => { if (!e.target.closest('#wechat-chat-more-btn') && !e.target.closest('#wechat-chat-menu')) { document.getElementById('wechat-chat-menu')?.classList.add('hidden'); } if (!e.target.closest('#wechat-chat-bg-panel') && !e.target.closest('#wechat-chat-menu')) { document.getElementById('wechat-chat-bg-panel')?.classList.add('hidden'); } }); document.getElementById('wechat-settings-back-btn')?.addEventListener('click', () => { showPage('wechat-me-page'); }); document.getElementById('wechat-favorites-back-btn')?.addEventListener('click', () => { showPage('wechat-me-page'); }); // 导入 PNG/JSON document.getElementById('wechat-import-png')?.addEventListener('click', () => { document.getElementById('wechat-file-png')?.click(); }); document.getElementById('wechat-import-json')?.addEventListener('click', () => { document.getElementById('wechat-file-json')?.click(); }); // PNG 文件选择 document.getElementById('wechat-file-png')?.addEventListener('change', async function (e) { const file = e.target.files[0]; if (!file) return; try { const charData = await extractCharacterFromPNG(file); charData.file = file; if (addContact(charData)) { showToast('导入成功'); try { await importCharacterToST(charData); } catch (err) { console.log('导入到酒馆失败(可忽略):', err.message); } // 同步角色卡内置世界书 const lorebookName = await syncCharacterBookToTavern(charData); if (lorebookName) { showToast(`角色书「${lorebookName}」已同步`); } showPage('wechat-main-content'); } } catch (err) { showToast(err.message, '⚠️'); } this.value = ''; }); // JSON 文件选择 document.getElementById('wechat-file-json')?.addEventListener('change', async function (e) { const file = e.target.files[0]; if (!file) return; try { const charData = await extractCharacterFromJSON(file); charData.file = file; if (addContact(charData)) { showToast('导入成功'); try { await importCharacterToST(charData); } catch (err) { console.log('导入到酒馆失败(可忽略):', err.message); } // 同步角色卡内置世界书 const lorebookName = await syncCharacterBookToTavern(charData); if (lorebookName) { showToast(`角色书「${lorebookName}」已同步`); } showPage('wechat-main-content'); } } catch (err) { showToast(err.message, '⚠️'); } this.value = ''; }); // 深色模式切换 document.getElementById('wechat-dark-toggle')?.addEventListener('click', toggleDarkMode); // 自动注入提示 document.getElementById('wechat-auto-inject-toggle')?.addEventListener('click', () => { const settings = getSettings(); settings.autoInjectPrompt = !settings.autoInjectPrompt; document.getElementById('wechat-auto-inject-toggle')?.classList.toggle('on', settings.autoInjectPrompt); // 展开/收起编辑区域 const contentDiv = document.getElementById('wechat-auto-inject-content'); if (contentDiv) { contentDiv.classList.toggle('hidden', !settings.autoInjectPrompt); } requestSave(); if (settings.autoInjectPrompt) injectAuthorNote(); }); // 保存作者注释模板 document.getElementById('wechat-save-author-note')?.addEventListener('click', () => { const settings = getSettings(); settings.authorNoteCustom = document.getElementById('wechat-author-note-content')?.value || ''; requestSave(); showToast('作者注释模板已保存'); }); // 哈基米破限开关 document.getElementById('wechat-hakimi-toggle')?.addEventListener('click', () => { const settings = getSettings(); settings.hakimiBreakLimit = !settings.hakimiBreakLimit; document.getElementById('wechat-hakimi-toggle')?.classList.toggle('on', settings.hakimiBreakLimit); // 展开/收起编辑区域 const contentDiv = document.getElementById('wechat-hakimi-content'); if (contentDiv) { contentDiv.classList.toggle('hidden', !settings.hakimiBreakLimit); } requestSave(); showToast(settings.hakimiBreakLimit ? '哈基米破限已开启' : '哈基米破限已关闭'); }); // 保存哈基米破限词 document.getElementById('wechat-save-hakimi')?.addEventListener('click', () => { const settings = getSettings(); settings.hakimiCustomPrompt = document.getElementById('wechat-hakimi-prompt')?.value || ''; requestSave(); showToast('破限提示词已保存'); }); // ===== Meme表情包事件 ===== // 关闭面板 document.getElementById('wechat-meme-stickers-close')?.addEventListener('click', () => { document.getElementById('wechat-meme-stickers-panel')?.classList.add('hidden'); }); // Meme开关 document.getElementById('wechat-meme-stickers-toggle')?.addEventListener('click', () => { const settings = getSettings(); settings.memeStickersEnabled = !settings.memeStickersEnabled; const toggle = document.getElementById('wechat-meme-stickers-toggle'); toggle?.classList.toggle('on', settings.memeStickersEnabled); requestSave(); showToast(settings.memeStickersEnabled ? 'Meme表情包已启用' : 'Meme表情包已禁用'); }); // 添加表情包 - 弹出文本输入框 document.getElementById('wechat-add-meme-sticker')?.addEventListener('click', () => { // 创建弹窗 const modal = document.createElement('div'); modal.className = 'wechat-modal'; modal.id = 'wechat-add-meme-modal'; modal.innerHTML = `
添加表情包
输入猫箱格式的文件名,每行一个
格式:名称+6位ID.扩展名
例如:是的主人yvrgdc.jpg
`; const phoneContainer = document.getElementById('wechat-phone'); if (phoneContainer) { phoneContainer.appendChild(modal); } else { document.body.appendChild(modal); } // 聚焦输入框 document.getElementById('wechat-meme-input')?.focus(); // 取消按钮 document.getElementById('wechat-meme-cancel')?.addEventListener('click', () => modal.remove()); // 点击背景关闭 modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); }); // 确认添加 document.getElementById('wechat-meme-confirm')?.addEventListener('click', () => { const input = document.getElementById('wechat-meme-input'); const text = input?.value?.trim(); if (!text) { modal.remove(); return; } // 解析输入的每一行 const lines = text.split('\n').map(s => s.trim()).filter(s => s); if (lines.length === 0) { modal.remove(); return; } // 添加到表情包列表 const textarea = document.getElementById('wechat-meme-stickers-list'); if (textarea) { const currentList = textarea.value.trim(); const updatedList = currentList ? currentList + '\n' + lines.join('\n') : lines.join('\n'); textarea.value = updatedList; showToast(`已添加 ${lines.length} 个表情包`); } modal.remove(); }); }); // ===== 角色设置弹窗事件 ===== // 关闭按钮 document.getElementById('wechat-contact-settings-close')?.addEventListener('click', closeContactSettings); // 保存按钮 document.getElementById('wechat-contact-settings-save')?.addEventListener('click', saveContactSettings); // 更换头像按钮 document.getElementById('wechat-change-avatar-btn')?.addEventListener('click', () => { const index = getCurrentEditingContactIndex(); if (index >= 0) changeContactAvatar(index); }); // 独立API开关 document.getElementById('wechat-contact-custom-api-toggle')?.addEventListener('click', () => { const toggle = document.getElementById('wechat-contact-custom-api-toggle'); const apiSettingsDiv = document.getElementById('wechat-contact-api-settings'); toggle?.classList.toggle('on'); const isOn = toggle?.classList.contains('on'); if (apiSettingsDiv) { if (isOn) { apiSettingsDiv.classList.remove('hidden'); apiSettingsDiv.style.display = 'flex'; } else { apiSettingsDiv.classList.add('hidden'); apiSettingsDiv.style.display = 'none'; } } }); // 角色独立哈基米开关 document.getElementById('wechat-contact-hakimi-toggle')?.addEventListener('click', () => { document.getElementById('wechat-contact-hakimi-toggle')?.classList.toggle('on'); }); // 角色独立API获取模型按钮 document.getElementById('wechat-contact-fetch-model')?.addEventListener('click', async () => { const apiUrl = document.getElementById('wechat-contact-api-url')?.value?.trim(); const apiKey = document.getElementById('wechat-contact-api-key')?.value?.trim(); const modelSelect = document.getElementById('wechat-contact-model-select'); const fetchBtn = document.getElementById('wechat-contact-fetch-model'); if (!apiUrl) { showToast('请先填写API地址', 'info'); return; } fetchBtn.textContent = '...'; fetchBtn.disabled = true; try { const { fetchModelListFromApi } = await import('./ai.js'); const models = await fetchModelListFromApi(apiUrl, apiKey); if (models.length > 0) { const currentValue = modelSelect?.value || ''; modelSelect.innerHTML = '' + models.map(m => ``).join(''); showToast(`获取到 ${models.length} 个模型`); } else { showToast('未找到可用模型', 'info'); } } catch (err) { console.error('[可乐] 获取模型失败:', err); showToast('获取失败: ' + err.message, '⚠️'); } finally { fetchBtn.textContent = '获取'; fetchBtn.disabled = false; } }); // 角色独立API手动输入按钮 document.getElementById('wechat-contact-model-manual')?.addEventListener('click', () => { const selectWrapper = document.getElementById('wechat-contact-model-select-wrapper'); const inputWrapper = document.getElementById('wechat-contact-model-input-wrapper'); const modelSelect = document.getElementById('wechat-contact-model-select'); const modelInput = document.getElementById('wechat-contact-model-input'); // 将当前选中的值复制到输入框 if (modelSelect?.value) { modelInput.value = modelSelect.value; } selectWrapper.style.display = 'none'; inputWrapper.style.display = 'flex'; modelInput?.focus(); }); // 角色独立API返回按钮 document.getElementById('wechat-contact-model-back')?.addEventListener('click', () => { const selectWrapper = document.getElementById('wechat-contact-model-select-wrapper'); const inputWrapper = document.getElementById('wechat-contact-model-input-wrapper'); const modelSelect = document.getElementById('wechat-contact-model-select'); const modelInput = document.getElementById('wechat-contact-model-input'); // 如果输入框有值,尝试在下拉列表中选中,或添加为新选项 const inputValue = modelInput?.value?.trim(); if (inputValue && modelSelect) { const existingOption = Array.from(modelSelect.options).find(opt => opt.value === inputValue); if (existingOption) { modelSelect.value = inputValue; } else { // 添加为新选项并选中 const newOption = document.createElement('option'); newOption.value = inputValue; newOption.textContent = inputValue; modelSelect.appendChild(newOption); modelSelect.value = inputValue; } } selectWrapper.style.display = 'flex'; inputWrapper.style.display = 'none'; }); // 角色独立API测试连接按钮 document.getElementById('wechat-contact-test-api')?.addEventListener('click', async () => { const apiUrl = document.getElementById('wechat-contact-api-url')?.value?.trim(); const apiKey = document.getElementById('wechat-contact-api-key')?.value?.trim(); // 优先从输入框获取,其次从下拉列表获取 const inputWrapper = document.getElementById('wechat-contact-model-input-wrapper'); const isManualMode = inputWrapper?.style.display === 'flex'; const model = isManualMode ? document.getElementById('wechat-contact-model-input')?.value?.trim() : document.getElementById('wechat-contact-model-select')?.value?.trim(); const testBtn = document.getElementById('wechat-contact-test-api'); if (!apiUrl) { showToast('请先填写API地址', 'info'); return; } if (!model) { showToast('请先填写或选择模型', 'info'); return; } testBtn.textContent = '测试中...'; testBtn.disabled = true; try { const chatUrl = apiUrl.replace(/\/+$/, '') + '/chat/completions'; const headers = { 'Content-Type': 'application/json' }; if (apiKey) { headers['Authorization'] = `Bearer ${apiKey}`; } const response = await fetch(chatUrl, { method: 'POST', headers, body: JSON.stringify({ model: model, messages: [{ role: 'user', content: '请回复"连接成功"' }], max_tokens: 8196 }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`${response.status}: ${errorText.substring(0, 100)}`); } const data = await response.json(); const reply = data.choices?.[0]?.message?.content || ''; showToast(`连接成功!回复: ${reply.substring(0, 20)}...`, 'success'); } catch (err) { console.error('[可乐] 测试连接失败:', err); showToast('❌ 连接失败: ' + err.message, '⚠️'); } finally { testBtn.textContent = '测试连接'; testBtn.disabled = false; } }); // ===== 群聊设置事件 ===== // 群聊提示词注入开关 document.getElementById('wechat-group-inject-toggle')?.addEventListener('click', () => { const settings = getSettings(); settings.groupAutoInjectPrompt = !settings.groupAutoInjectPrompt; document.getElementById('wechat-group-inject-toggle')?.classList.toggle('on', settings.groupAutoInjectPrompt); // 展开/收起编辑区域 const contentDiv = document.getElementById('wechat-group-inject-content'); if (contentDiv) { contentDiv.classList.toggle('hidden', !settings.groupAutoInjectPrompt); } requestSave(); showToast(settings.groupAutoInjectPrompt ? '群聊提示词注入已开启' : '群聊提示词注入已关闭'); }); // 保存群聊作者注释 document.getElementById('wechat-save-group-note')?.addEventListener('click', () => { const settings = getSettings(); settings.userGroupAuthorNote = document.getElementById('wechat-group-author-note')?.value || ''; requestSave(); showToast('群聊作者注释已保存'); }); // 聊天输入框发送消息(支持单聊和群聊) const chatInput = document.getElementById('wechat-input'); // 更新发送按钮状态(全局可用) window.updateSendButtonState = () => { const moreBtn = document.querySelector('.wechat-chat-input-more'); const sendText = moreBtn?.querySelector('.wechat-input-send-text'); const moreIcon = moreBtn?.querySelector('.wechat-input-more-icon'); if (!sendText || !moreIcon) return; const input = document.getElementById('wechat-input'); const hasText = input?.value?.trim(); if (hasText) { sendText.style.display = 'inline-block'; moreIcon.style.display = 'none'; } else { sendText.style.display = 'none'; moreIcon.style.display = 'inline-block'; } }; // 监听输入变化 chatInput?.addEventListener('input', window.updateSendButtonState); // 监听聚焦时也更新状态 chatInput?.addEventListener('focus', window.updateSendButtonState); chatInput?.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); const text = chatInput.value?.trim(); if (!text) return; // 调试日志 const messagesContainer = document.getElementById('wechat-chat-messages'); console.log('[可乐] Enter 键发送消息:', { text: text.substring(0, 20), isGroup: messagesContainer?.dataset?.isGroup, groupIndex: messagesContainer?.dataset?.groupIndex, isInGroupChatResult: isInGroupChat() }); if (isInGroupChat()) { console.log('[可乐] 调用 sendGroupMessage'); sendGroupMessage(text); } else { console.log('[可乐] 调用 sendMessage (单聊)'); sendMessage(text); } // 发送后更新按钮状态 setTimeout(window.updateSendButtonState, 50); } }); // 聊天输入区按钮 document.querySelector('.wechat-chat-input-more')?.addEventListener('click', () => { const text = chatInput?.value?.trim(); if (text) { // 有文字时发送消息 if (isInGroupChat()) { sendGroupMessage(text); } else { sendMessage(text); } // 发送后更新按钮状态 setTimeout(window.updateSendButtonState, 50); } else { // 无文字时切换功能面板 toggleFuncPanel(); } }); document.querySelector('.wechat-chat-input-voice')?.addEventListener('click', () => { hideFuncPanel(); hideEmojiPanel(); showExpandVoice(); }); // 表情按钮 document.querySelector('.wechat-chat-input-emoji')?.addEventListener('click', () => { hideFuncPanel(); toggleEmojiPanel(); }); initFuncPanel(); initEmojiPanel(); initChatBackground(); initMoments(); initRedPacketEvents(); initTransferEvents(); initGroupRedPacket(); initGiftEvents(); initCropper(); initHistoryEvents(); // 展开面板 document.getElementById('wechat-expand-close')?.addEventListener('click', closeExpandPanel); document.getElementById('wechat-expand-send')?.addEventListener('click', sendExpandContent); // 标签栏切换 document.querySelectorAll('.wechat-tab').forEach(tab => { tab.addEventListener('click', function () { document.querySelectorAll('.wechat-tab').forEach(t => { t.classList.toggle('active', t.dataset.tab === this.dataset.tab); }); const tabName = this.dataset.tab; if (tabName === 'me') { showPage('wechat-me-page'); return; } if (tabName === 'discover') { showPage('wechat-discover-page'); return; } if (tabName === 'chat') { showPage('wechat-main-content'); document.getElementById('wechat-chat-tab-content')?.classList.remove('hidden'); document.getElementById('wechat-contacts-tab-content')?.classList.add('hidden'); refreshChatList(); return; } if (tabName === 'contacts') { showPage('wechat-main-content'); document.getElementById('wechat-chat-tab-content')?.classList.add('hidden'); document.getElementById('wechat-contacts-tab-content')?.classList.remove('hidden'); refreshContactsList(); return; } showPage('wechat-main-content'); }); }); // 聊天列表项点击(支持单聊和群聊) document.getElementById('wechat-chat-list')?.addEventListener('click', (e) => { const chatItem = e.target.closest('.wechat-chat-item'); if (!chatItem) return; // 检查是否是群聊 if (chatItem.classList.contains('wechat-chat-item-group')) { const groupIndex = parseInt(chatItem.dataset.groupIndex); if (!isNaN(groupIndex)) { import('./group-chat.js').then(m => m.openGroupChat(groupIndex)); } } else { // 单聊 const contactId = chatItem.dataset.contactId; const index = parseInt(chatItem.dataset.index); if (contactId) openChatByContactId(contactId, index); } }); // “我”页面菜单 document.getElementById('wechat-menu-favorites')?.addEventListener('click', () => { showPage('wechat-favorites-page'); }); document.getElementById('wechat-menu-settings')?.addEventListener('click', () => { showPage('wechat-settings-page'); }); document.getElementById('wechat-menu-service')?.addEventListener('click', () => { showPage('wechat-service-page'); updateWalletAmountDisplay(); }); // 服务页返回 document.getElementById('wechat-service-back-btn')?.addEventListener('click', () => { showPage('wechat-me-page'); }); // 服务页面 - 钱包/上下文开关面板 document.getElementById('wechat-service-wallet')?.addEventListener('click', () => { document.getElementById('wechat-context-panel')?.classList.add('hidden'); document.getElementById('wechat-wallet-panel')?.classList.toggle('hidden'); }); document.getElementById('wechat-service-context')?.addEventListener('click', () => { document.getElementById('wechat-wallet-panel')?.classList.add('hidden'); document.getElementById('wechat-context-panel')?.classList.toggle('hidden'); }); // 上下文开关变化 document.getElementById('wechat-context-enabled')?.addEventListener('change', (e) => { const settings = getSettings(); settings.contextEnabled = e.target.checked; requestSave(); syncContextEnabledUI(settings.contextEnabled); }); // 上下文滑块变化 document.getElementById('wechat-context-slider')?.addEventListener('input', (e) => { const settings = getSettings(); settings.contextLevel = parseInt(e.target.value); requestSave(); document.getElementById('wechat-context-value').textContent = e.target.value; }); // 标签容器事件委托(添加/删除) document.getElementById('wechat-context-tags')?.addEventListener('click', (e) => { const settings = getSettings(); if (e.target.classList.contains('wechat-tag-del-btn')) { const index = parseInt(e.target.dataset.index); if (Array.isArray(settings.contextTags) && index >= 0 && index < settings.contextTags.length) { settings.contextTags.splice(index, 1); requestSave(); refreshContextTags(); } return; } if (e.target.classList.contains('wechat-tag-add-btn')) { const tagName = prompt('输入标签名(如 content、scene):'); if (tagName && tagName.trim()) { settings.contextTags = Array.isArray(settings.contextTags) ? settings.contextTags : []; if (!settings.contextTags.includes(tagName.trim())) { settings.contextTags.push(tagName.trim()); requestSave(); refreshContextTags(); } } } }); // 钱包金额保存(滑出面板) document.getElementById('wechat-wallet-save-slide')?.addEventListener('click', () => { const input = document.getElementById('wechat-wallet-input-slide'); const amount = input?.value || '0.00'; const settings = getSettings(); settings.walletAmount = amount; requestSave(); updateWalletAmountDisplay(); document.getElementById('wechat-wallet-panel')?.classList.add('hidden'); }); // 支付密码保存 document.getElementById('wechat-save-password-btn')?.addEventListener('click', () => { const input = document.getElementById('wechat-new-password-input'); const password = input?.value || ''; // 验证是否为6位数字 if (!/^\d{6}$/.test(password)) { showToast('请输入6位数字密码', 'info'); return; } const settings = getSettings(); settings.paymentPassword = password; requestSave(); showToast('密码已保存', '✓'); document.getElementById('wechat-change-password-panel')?.classList.add('hidden'); input.value = ''; }); // 密码输入框只允许数字 document.getElementById('wechat-new-password-input')?.addEventListener('input', (e) => { e.target.value = e.target.value.replace(/\D/g, '').slice(0, 6); }); // 总结模板保存 document.getElementById('wechat-summary-template-save')?.addEventListener('click', () => { const input = document.getElementById('wechat-summary-template-input'); const template = input?.value || ''; const settings = getSettings(); settings.customSummaryTemplate = template; requestSave(); showToast('模板已保存', '✓'); document.getElementById('wechat-summary-template-panel')?.classList.add('hidden'); }); // 总结模板恢复默认 document.getElementById('wechat-summary-template-reset')?.addEventListener('click', () => { const input = document.getElementById('wechat-summary-template-input'); if (input) input.value = ''; const settings = getSettings(); settings.customSummaryTemplate = ''; requestSave(); showToast('已恢复默认模板', '✓'); }); // 总结 API 配置 document.getElementById('wechat-summary-key-toggle')?.addEventListener('click', () => { const input = document.getElementById('wechat-summary-key'); if (input) input.type = input.type === 'password' ? 'text' : 'password'; }); document.getElementById('wechat-summary-fetch-models')?.addEventListener('click', async () => { const statusEl = document.getElementById('wechat-summary-status'); const url = document.getElementById('wechat-summary-url')?.value?.trim(); const key = document.getElementById('wechat-summary-key')?.value?.trim(); const modelSelect = document.getElementById('wechat-summary-model'); if (!url || !key) { if (statusEl) statusEl.innerHTML = `${ICON_INFO} 请先填写 URL 和 Key`; return; } if (statusEl) statusEl.textContent = '⏳ 正在获取模型列表...'; try { const models = await fetchModelListFromApi(url, key); if (models.length === 0) { if (statusEl) statusEl.innerHTML = `${ICON_INFO} 未找到可用模型`; return; } if (modelSelect) { modelSelect.innerHTML = '' + models.map(m => ``).join(''); } const settings = getSettings(); settings.summaryModelList = models; requestSave(); if (statusEl) statusEl.innerHTML = `${ICON_SUCCESS} 获取到 ${models.length} 个模型`; } catch (err) { console.error('[可乐] 获取模型列表失败:', err); if (statusEl) statusEl.textContent = `⚠️ 获取失败: ${err.message}`; } }); document.getElementById('wechat-summary-test')?.addEventListener('click', async () => { const statusEl = document.getElementById('wechat-summary-status'); const url = document.getElementById('wechat-summary-url')?.value?.trim(); const key = document.getElementById('wechat-summary-key')?.value?.trim(); const model = document.getElementById('wechat-summary-model')?.value; if (!url || !key) { if (statusEl) statusEl.innerHTML = `${ICON_INFO} 请先填写 URL 和 Key`; return; } if (!model) { if (statusEl) statusEl.innerHTML = `${ICON_INFO} 请先选择模型`; return; } if (statusEl) statusEl.textContent = '⏳ 正在测试连接...'; try { const chatUrl = url.replace(/\/+$/, '') + '/chat/completions'; const response = await fetch(chatUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model, messages: [{ role: 'user', content: 'Hi' }], max_tokens: 5 }) }); if (!response.ok) { const errData = await response.json().catch(() => ({})); throw new Error(errData.error?.message || `HTTP ${response.status}`); } if (statusEl) statusEl.innerHTML = `${ICON_SUCCESS} 连接成功!`; } catch (err) { console.error('[可乐] 测试连接失败:', err); if (statusEl) statusEl.textContent = `⚠️ 连接失败: ${err.message}`; } }); document.getElementById('wechat-summary-save')?.addEventListener('click', () => { const statusEl = document.getElementById('wechat-summary-status'); const urlInput = document.getElementById('wechat-summary-url'); const keyInput = document.getElementById('wechat-summary-key'); const modelSelect = document.getElementById('wechat-summary-model'); const settings = getSettings(); settings.summaryApiUrl = urlInput?.value?.trim() || ''; settings.summaryApiKey = keyInput?.value?.trim() || ''; settings.summarySelectedModel = modelSelect?.value || ''; requestSave(); if (statusEl) statusEl.innerHTML = `${ICON_SUCCESS} 配置已保存`; setTimeout(() => document.getElementById('wechat-summary-panel')?.classList.add('hidden'), 1500); }); document.getElementById('wechat-summary-model')?.addEventListener('change', (e) => { const settings = getSettings(); settings.summarySelectedModel = e.target.value; requestSave(); }); document.getElementById('wechat-summary-execute')?.addEventListener('click', () => { executeSummary(); }); document.getElementById('wechat-summary-rollback')?.addEventListener('click', () => { rollbackSummary(); }); // 暴露恢复函数到全局,可在控制台调用: window.keleRecoverSummary() window.keleRecoverSummary = recoverFromTavernWorldbook; document.getElementById('wechat-summary-close')?.addEventListener('click', () => { document.getElementById('wechat-summary-panel')?.classList.add('hidden'); }); // 总结面板 - 全选/取消全选 // 刷新按钮 document.getElementById('wechat-summary-refresh')?.addEventListener('click', () => { refreshSummaryChatList(); }); document.getElementById('wechat-summary-select-all')?.addEventListener('click', () => { selectAllSummaryChats(true); }); document.getElementById('wechat-summary-deselect-all')?.addEventListener('click', () => { selectAllSummaryChats(false); }); // 发现页面 - 朋友圈点击 document.getElementById('wechat-discover-moments')?.addEventListener('click', () => { openMomentsPage(); }); // 服务页面 - 服务项点击 document.querySelectorAll('.wechat-service-item').forEach(item => { item.addEventListener('click', () => { const service = item.dataset.service; // 关闭其他面板 const allPanels = ['wechat-context-panel', 'wechat-wallet-panel', 'wechat-summary-panel', 'wechat-history-panel', 'wechat-logs-panel', 'wechat-meme-stickers-panel', 'wechat-change-password-panel', 'wechat-summary-template-panel']; if (service === 'summary') { allPanels.filter(p => p !== 'wechat-summary-panel').forEach(p => document.getElementById(p)?.classList.add('hidden')); const panel = document.getElementById('wechat-summary-panel'); const isHidden = panel?.classList.contains('hidden'); panel?.classList.toggle('hidden'); if (isHidden) { refreshSummaryChatList(); } return; } if (service === 'history') { allPanels.filter(p => p !== 'wechat-history-panel').forEach(p => document.getElementById(p)?.classList.add('hidden')); const panel = document.getElementById('wechat-history-panel'); const isHidden = panel?.classList.contains('hidden'); panel?.classList.toggle('hidden'); if (isHidden) { refreshHistoryList('all'); } return; } if (service === 'logs') { allPanels.filter(p => p !== 'wechat-logs-panel').forEach(p => document.getElementById(p)?.classList.add('hidden')); const panel = document.getElementById('wechat-logs-panel'); const isHidden = panel?.classList.contains('hidden'); panel?.classList.toggle('hidden'); if (isHidden) { refreshLogsList(); } return; } if (service === 'meme-stickers') { allPanels.filter(p => p !== 'wechat-meme-stickers-panel').forEach(p => document.getElementById(p)?.classList.add('hidden')); const panel = document.getElementById('wechat-meme-stickers-panel'); panel?.classList.toggle('hidden'); return; } if (service === 'change-password') { allPanels.filter(p => p !== 'wechat-change-password-panel').forEach(p => document.getElementById(p)?.classList.add('hidden')); const panel = document.getElementById('wechat-change-password-panel'); panel?.classList.toggle('hidden'); return; } if (service === 'summary-template') { allPanels.filter(p => p !== 'wechat-summary-template-panel').forEach(p => document.getElementById(p)?.classList.add('hidden')); const panel = document.getElementById('wechat-summary-template-panel'); panel?.classList.toggle('hidden'); return; } const label = item.querySelector('span')?.textContent || '该'; showToast(`"${label}" 功能开发中...`, 'info'); }); }); // 收藏页面 - 添加按钮根据当前标签显示不同功能 document.getElementById('wechat-favorites-add-btn')?.addEventListener('click', (e) => { e.stopPropagation(); // 获取当前选中的标签 const activeTab = document.querySelector('.wechat-favorites-tab.active'); const currentFilter = activeTab?.dataset.tab || 'all'; // 根据标签执行不同操作 if (currentFilter === 'user') { // 用户标签:直接弹出添加用户设定 showAddPersonaPanel(); return; } if (currentFilter === 'character') { // 角色卡标签:显示导入选项 let menu = document.getElementById('wechat-favorites-add-menu'); if (!menu) { menu = document.createElement('div'); menu.id = 'wechat-favorites-add-menu'; menu.className = 'wechat-dropdown-menu'; menu.style.cssText = 'position: absolute; top: 45px; right: 10px; z-index: 100;'; document.getElementById('wechat-favorites-page')?.appendChild(menu); } menu.innerHTML = `
导入 PNG
导入 JSON
`; menu.classList.remove('hidden'); menu.querySelector('#wechat-add-menu-import-png')?.addEventListener('click', () => { menu.classList.add('hidden'); document.getElementById('wechat-file-png')?.click(); }); menu.querySelector('#wechat-add-menu-import-json')?.addEventListener('click', () => { menu.classList.add('hidden'); document.getElementById('wechat-file-json')?.click(); }); // 点击其他地方关闭菜单 const closeMenu = (ev) => { if (!ev.target.closest('#wechat-favorites-add-menu') && !ev.target.closest('#wechat-favorites-add-btn')) { menu.classList.add('hidden'); document.removeEventListener('click', closeMenu); } }; setTimeout(() => document.addEventListener('click', closeMenu), 0); return; } if (currentFilter === 'lorebook') { // 世界书标签:直接弹出添加世界书 showAddLorebookPanel(); return; } // 全部标签:显示完整菜单 let menu = document.getElementById('wechat-favorites-add-menu'); if (!menu) { menu = document.createElement('div'); menu.id = 'wechat-favorites-add-menu'; menu.className = 'wechat-dropdown-menu'; menu.style.cssText = 'position: absolute; top: 45px; right: 10px; z-index: 100;'; document.getElementById('wechat-favorites-page')?.appendChild(menu); } menu.innerHTML = `
添加用户设定
导入 PNG
导入 JSON
添加世界书
`; menu.classList.remove('hidden'); menu.querySelector('#wechat-add-menu-lorebook')?.addEventListener('click', () => { menu.classList.add('hidden'); showAddLorebookPanel(); }); menu.querySelector('#wechat-add-menu-persona')?.addEventListener('click', () => { menu.classList.add('hidden'); showAddPersonaPanel(); }); menu.querySelector('#wechat-add-menu-import-png')?.addEventListener('click', () => { menu.classList.add('hidden'); document.getElementById('wechat-file-png')?.click(); }); menu.querySelector('#wechat-add-menu-import-json')?.addEventListener('click', () => { menu.classList.add('hidden'); document.getElementById('wechat-file-json')?.click(); }); // 点击其他地方关闭菜单 const closeMenu = (ev) => { if (!ev.target.closest('#wechat-favorites-add-menu') && !ev.target.closest('#wechat-favorites-add-btn')) { menu.classList.add('hidden'); document.removeEventListener('click', closeMenu); } }; setTimeout(() => document.addEventListener('click', closeMenu), 0); }); document.getElementById('wechat-lorebook-cancel')?.addEventListener('click', () => { document.getElementById('wechat-lorebook-modal')?.classList.add('hidden'); }); document.querySelectorAll('.wechat-favorites-tab').forEach(tab => { tab.addEventListener('click', function () { document.querySelectorAll('.wechat-favorites-tab').forEach(t => t.classList.remove('active')); this.classList.add('active'); refreshFavoritesList(this.dataset.tab); }); }); // 清空联系人 document.getElementById('wechat-clear-contacts')?.addEventListener('click', () => { if (!confirm('确定要清空所有联系人吗?')) return; const settings = getSettings(); settings.contacts = []; requestSave(); refreshContactsList(); showToast('已清空所有联系人'); }); // 用户头像更换 document.getElementById('wechat-me-avatar')?.addEventListener('click', () => { document.getElementById('wechat-user-avatar-input')?.click(); }); document.getElementById('wechat-user-avatar-input')?.addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; try { const reader = new FileReader(); reader.onload = function (event) { const settings = getSettings(); settings.userAvatar = event.target.result; requestSave(); updateMePageInfo(); showToast('头像已更换'); }; reader.readAsDataURL(file); } catch (err) { console.error('[可乐] 更换头像失败:', err); showToast('更换头像失败: ' + err.message, '⚠️'); } e.target.value = ''; }); // API 配置:密钥可见性 document.getElementById('wechat-toggle-key-visibility')?.addEventListener('click', () => { const keyInput = document.getElementById('wechat-api-key'); const eyeBtn = document.getElementById('wechat-toggle-key-visibility'); if (!keyInput || !eyeBtn) return; if (keyInput.type === 'password') { keyInput.type = 'text'; eyeBtn.innerHTML = ''; } else { keyInput.type = 'password'; eyeBtn.innerHTML = ''; } }); // 保存 API 配置 document.getElementById('wechat-save-api')?.addEventListener('click', () => { const apiUrl = document.getElementById('wechat-api-url')?.value.trim() || ''; const apiKey = document.getElementById('wechat-api-key')?.value.trim() || ''; const selectedModel = document.getElementById('wechat-model-select')?.value || ''; const settings = getSettings(); settings.apiUrl = apiUrl; settings.apiKey = apiKey; settings.selectedModel = selectedModel; requestSave(); showToast('API 配置已保存'); }); // 刷新模型列表 document.getElementById('wechat-refresh-models')?.addEventListener('click', () => { refreshModelSelect(); }); // 模型选择变化(支持手动输入和从列表选择) const modelInput = document.getElementById('wechat-model-select'); if (modelInput) { modelInput.addEventListener('change', (e) => { const settings = getSettings(); settings.selectedModel = e.target.value.trim(); requestSave(); }); modelInput.addEventListener('input', (e) => { const settings = getSettings(); settings.selectedModel = e.target.value.trim(); requestSave(); }); } // 测试 API 连接 document.getElementById('wechat-test-api')?.addEventListener('click', async () => { const apiUrl = document.getElementById('wechat-api-url')?.value.trim() || ''; const apiKey = document.getElementById('wechat-api-key')?.value.trim() || ''; if (!apiUrl) { showToast('请先填写 API 地址', 'info'); return; } const testBtn = document.getElementById('wechat-test-api'); const originalText = testBtn?.textContent; if (testBtn) { testBtn.textContent = '测试中...'; testBtn.disabled = true; } try { await fetchModelListFromApi(apiUrl, apiKey); showToast('连接成功'); } catch (err) { showToast('连接失败:' + err.message, '⚠️'); } finally { if (testBtn) { testBtn.textContent = originalText; testBtn.disabled = false; } } }); // 自己填模型按钮 - 单聊 document.getElementById('wechat-manual-model')?.addEventListener('click', () => { const modelName = prompt('请输入模型名称:'); if (modelName && modelName.trim()) { const select = document.getElementById('wechat-model-select'); if (select) { // 添加一个新选项并选中 const option = document.createElement('option'); option.value = modelName.trim(); option.textContent = modelName.trim(); option.selected = true; select.appendChild(option); const settings = getSettings(); settings.selectedModel = modelName.trim(); requestSave(); showToast('模型已设置'); } } }); // ===== 群聊 API 配置事件 ===== // 群聊密钥可见性 document.getElementById('wechat-toggle-group-key-visibility')?.addEventListener('click', () => { const keyInput = document.getElementById('wechat-group-api-key'); const eyeBtn = document.getElementById('wechat-toggle-group-key-visibility'); if (!keyInput || !eyeBtn) return; if (keyInput.type === 'password') { keyInput.type = 'text'; eyeBtn.innerHTML = ''; } else { keyInput.type = 'password'; eyeBtn.innerHTML = ''; } }); // 群聊获取模型列表 document.getElementById('wechat-group-refresh-models')?.addEventListener('click', async () => { const settings = getSettings(); const apiUrl = document.getElementById('wechat-group-api-url')?.value?.trim() || settings.groupApiUrl || ''; const apiKey = document.getElementById('wechat-group-api-key')?.value?.trim() || settings.groupApiKey || ''; const refreshBtn = document.getElementById('wechat-group-refresh-models'); const select = document.getElementById('wechat-group-model-select'); if (!apiUrl) { showToast('请先填写群聊 API 地址', 'info'); return; } const originalText = refreshBtn?.textContent; if (refreshBtn) { refreshBtn.textContent = '加载中...'; refreshBtn.disabled = true; } try { const modelIds = await fetchModelListFromApi(apiUrl, apiKey); // 更新 select 选项 if (select) { select.innerHTML = '' + modelIds.map(id => ``).join(''); } settings.groupModelList = modelIds; requestSave(); showToast(`获取到 ${modelIds.length} 个模型`); } catch (err) { console.error('[可乐] 获取群聊模型列表失败:', err); showToast('获取失败,请手动输入模型名', '⚠️'); } finally { if (refreshBtn) { refreshBtn.textContent = originalText; refreshBtn.disabled = false; } } }); // 群聊自己填模型 document.getElementById('wechat-group-manual-model')?.addEventListener('click', () => { const modelName = prompt('请输入群聊模型名称:'); if (modelName && modelName.trim()) { const select = document.getElementById('wechat-group-model-select'); if (select) { // 添加一个新选项并选中 const option = document.createElement('option'); option.value = modelName.trim(); option.textContent = modelName.trim(); option.selected = true; select.appendChild(option); const settings = getSettings(); settings.groupSelectedModel = modelName.trim(); requestSave(); showToast('群聊模型已设置'); } } }); // 群聊测试连接 document.getElementById('wechat-group-test-api')?.addEventListener('click', async () => { const apiUrl = document.getElementById('wechat-group-api-url')?.value.trim() || ''; const apiKey = document.getElementById('wechat-group-api-key')?.value.trim() || ''; if (!apiUrl) { showToast('请先填写群聊 API 地址', 'info'); return; } const testBtn = document.getElementById('wechat-group-test-api'); const originalText = testBtn?.textContent; if (testBtn) { testBtn.textContent = '测试中...'; testBtn.disabled = true; } try { await fetchModelListFromApi(apiUrl, apiKey); showToast('群聊 API 连接成功'); } catch (err) { showToast('连接失败:' + err.message, '⚠️'); } finally { if (testBtn) { testBtn.textContent = originalText; testBtn.disabled = false; } } }); // 保存群聊 API 配置 document.getElementById('wechat-group-save-api')?.addEventListener('click', () => { const apiUrl = document.getElementById('wechat-group-api-url')?.value.trim() || ''; const apiKey = document.getElementById('wechat-group-api-key')?.value.trim() || ''; const selectedModel = document.getElementById('wechat-group-model-select')?.value || ''; const settings = getSettings(); settings.groupApiUrl = apiUrl; settings.groupApiKey = apiKey; settings.groupSelectedModel = selectedModel; requestSave(); showToast('群聊 API 配置已保存'); }); // 群聊模型选择变化 const groupModelInput = document.getElementById('wechat-group-model-select'); if (groupModelInput) { groupModelInput.addEventListener('change', (e) => { const settings = getSettings(); settings.groupSelectedModel = e.target.value.trim(); requestSave(); }); groupModelInput.addEventListener('input', (e) => { const settings = getSettings(); settings.groupSelectedModel = e.target.value.trim(); requestSave(); }); } // 总结 API - 自己填模型 document.getElementById('wechat-summary-manual-model')?.addEventListener('click', () => { const modelName = prompt('请输入总结模型名称:'); if (modelName && modelName.trim()) { const select = document.getElementById('wechat-summary-model'); if (select) { // 添加一个新选项并选中 const option = document.createElement('option'); option.value = modelName.trim(); option.textContent = modelName.trim(); option.selected = true; select.appendChild(option); const settings = getSettings(); settings.summarySelectedModel = modelName.trim(); requestSave(); showToast('总结模型已设置'); } } }); // ===== 历史回顾面板事件 ===== document.getElementById('wechat-history-close')?.addEventListener('click', () => { document.getElementById('wechat-history-panel')?.classList.add('hidden'); }); // 历史回顾标签切换 document.querySelectorAll('.wechat-history-tab').forEach(tab => { tab.addEventListener('click', () => { document.querySelectorAll('.wechat-history-tab').forEach(t => { t.classList.remove('active', 'wechat-btn-primary'); }); tab.classList.add('active', 'wechat-btn-primary'); refreshHistoryList(tab.dataset.tab); }); }); // ===== 日志面板事件 ===== document.getElementById('wechat-logs-close')?.addEventListener('click', () => { document.getElementById('wechat-logs-panel')?.classList.add('hidden'); }); document.getElementById('wechat-logs-clear')?.addEventListener('click', () => { if (confirm('确定清空所有日志?')) { clearErrorLogs(); refreshLogsList(); showToast('日志已清空'); } }); // 绑定联系人点击 refreshContactsList(); } function init() { loadSettings(); const settings = getSettings(); if (seedDefaultUserPersonaFromST(settings)) { requestSave(); } const phoneHTML = generatePhoneHTML(); document.body.insertAdjacentHTML('beforeend', phoneHTML); setupPhoneAutoCentering(); setupPhoneDrag(); bindEvents(); // 初始化发送按钮状态 window.updateSendButtonState?.(); // 初始化底部导航栏红点 updateTabBadge(); restoreModelSelect(); restoreGroupModelSelect(); // 同步上下文面板初始 UI syncContextEnabledUI(settings.contextEnabled); refreshContextTags(); updateWalletAmountDisplay(); if (settings.autoInjectPrompt) { injectAuthorNote(); } setupMessageObserver(); addExtensionButton(); // 初始化错误捕获 initErrorCapture(); // 初始化页面卸载保存 setupUnloadSave(); setInterval(() => { const phone = document.getElementById('wechat-phone'); if (!phone || phone.classList.contains('hidden')) return; const timeEl = document.querySelector('.wechat-statusbar-time'); if (timeEl) timeEl.textContent = getCurrentTime(); }, 60000); // 首次可见时居中 centerPhoneInViewport({ force: true }); // 初始化悬浮球 createFloatingBall(); // 根据设置决定是否显示 if (settings.floatingBallEnabled === false) { hideFloatingBall(); } updateFloatingBallMenuText(settings.floatingBallEnabled !== false); console.log('✅ 可乐不加冰 已加载'); } if (typeof jQuery === 'function') { jQuery(() => setTimeout(init, 500)); } else { document.addEventListener('DOMContentLoaded', () => setTimeout(init, 500), { once: true }); }