From 5068b46702fe868bd2467a671a61c1cd205252db Mon Sep 17 00:00:00 2001 From: Cola-Echo Date: Wed, 31 Dec 2025 14:11:05 +0800 Subject: [PATCH] Add files via upload --- audio-storage.js | 48 +++++++++++ main.js | 213 ++++++++++++++++++++++++++++++++++++++++++++++- phone-html.js | 4 + 3 files changed, 264 insertions(+), 1 deletion(-) diff --git a/audio-storage.js b/audio-storage.js index a79c7a1..7093717 100644 --- a/audio-storage.js +++ b/audio-storage.js @@ -275,3 +275,51 @@ export async function getStorageStats() { }; }); } + +/** + * 获取所有语音记录,按联系人分组 + * @returns {Promise} { contactIndex: { count, totalDuration } } + */ +export async function getAllVoiceRecordingsGroupedByContact() { + await initAudioDB(); + + return new Promise((resolve, reject) => { + const transaction = db.transaction([STORE_NAME], 'readonly'); + const store = transaction.objectStore(STORE_NAME); + const request = store.getAll(); + + request.onsuccess = () => { + const records = request.result; + const grouped = {}; + + records.forEach(record => { + const idx = record.contactIndex; + if (!grouped[idx]) { + grouped[idx] = { count: 0, totalDuration: 0 }; + } + grouped[idx].count++; + grouped[idx].totalDuration += record.duration || 0; + }); + + resolve(grouped); + }; + + request.onerror = () => { + reject(request.error); + }; + }); +} + +/** + * 删除指定联系人的所有语音记录 + * @param {number} contactIndex - 联系人索引 + * @returns {Promise} 删除的记录数量 + */ +export async function deleteVoiceRecordingsByContact(contactIndex) { + const records = await getVoiceRecordingsByContact(contactIndex); + for (const record of records) { + await deleteVoiceRecording(record.id); + } + console.log(`[可乐] 已删除联系人 ${contactIndex} 的 ${records.length} 条语音记录`); + return records.length; +} diff --git a/main.js b/main.js index a103a93..9a73181 100644 --- a/main.js +++ b/main.js @@ -38,7 +38,7 @@ import { initGiftEvents } from './gift.js'; import { initCropper } from './cropper.js'; import { createFloatingBall, showFloatingBall, hideFloatingBall } from './floating-ball.js'; import { testSttApi, testTtsApi } from './voice-api.js'; -import { getVoiceRecordingsByContact, deleteVoiceRecording, playVoiceRecording } from './audio-storage.js'; +import { getVoiceRecordingsByContact, deleteVoiceRecording, playVoiceRecording, getAllVoiceRecordingsGroupedByContact, deleteVoiceRecordingsByContact } from './audio-storage.js'; // ========== 历史记录功能 ========== let currentHistoryTab = 'listen'; @@ -513,6 +513,211 @@ function updateFloatingBallMenuText(enabled) { } } +// 清除孤立缓存(已删除联系人/群聊的总结历史和语音记录) +async function clearOrphanedCache() { + const settings = getSettings(); + const contacts = settings.contacts || []; + const groupChats = settings.groupChats || []; + const lorebooks = settings.selectedLorebooks || []; + + // 获取当前有效的联系人ID和名称 + const validContactIds = new Set(contacts.map(c => c.id).filter(id => id)); + const validContactNames = new Set(contacts.map(c => c.name).filter(n => n)); + const validGroupNames = new Set(groupChats.map(g => g.name).filter(n => n)); + const validContactIndexes = new Set(contacts.map((_, idx) => idx)); + + // 查找孤立的总结世界书 + const orphanedSummaries = []; + + lorebooks.forEach((lb, idx) => { + const isSummaryBook = lb.fromSummary === true || + (lb.name?.startsWith('【可乐】和') && lb.name?.endsWith('的聊天')); + + if (isSummaryBook) { + const nameMatch = lb.name?.match(/^【可乐】和(.+)的聊天$/); + const linkedName = nameMatch ? nameMatch[1] : null; + + const contactExists = linkedName && validContactNames.has(linkedName); + const groupExists = linkedName && validGroupNames.has(linkedName); + const linkedById = lb.characterId && validContactIds.has(lb.characterId); + + if (!contactExists && !groupExists && !linkedById) { + const cupCount = lb.entries?.length || 0; + orphanedSummaries.push({ + type: 'summary', + name: lb.name, + linkedName: linkedName || '未知', + index: idx, + cupCount + }); + } + } + }); + + // 查找孤立的语音记录 + const orphanedVoices = []; + try { + const voiceGroups = await getAllVoiceRecordingsGroupedByContact(); + for (const [contactIdxStr, data] of Object.entries(voiceGroups)) { + const contactIdx = parseInt(contactIdxStr); + // 如果索引超出当前联系人范围,则为孤立数据 + if (!validContactIndexes.has(contactIdx)) { + orphanedVoices.push({ + type: 'voice', + contactIndex: contactIdx, + count: data.count, + totalDuration: data.totalDuration + }); + } + } + } catch (err) { + console.warn('[可乐] 获取语音记录失败:', err); + } + + // 如果没有孤立数据 + if (orphanedSummaries.length === 0 && orphanedVoices.length === 0) { + showToast('没有发现需要清理的缓存数据'); + return; + } + + // 显示选择弹窗 + showCacheCleanupModal(orphanedSummaries, orphanedVoices); +} + +// 显示缓存清理选择弹窗 +function showCacheCleanupModal(orphanedSummaries, orphanedVoices) { + document.getElementById('wechat-cache-cleanup-modal')?.remove(); + + const hasSummaries = orphanedSummaries.length > 0; + const hasVoices = orphanedVoices.length > 0; + + const modal = document.createElement('div'); + modal.className = 'wechat-modal'; + modal.id = 'wechat-cache-cleanup-modal'; + modal.innerHTML = ` +
+ +
清除缓存
+
+ 勾选要清除的项目: +
+
+ ${hasSummaries ? ` +
+ 📝 总结历史(已删除联系人/群聊) +
+ ${orphanedSummaries.map(item => ` + + `).join('')} + ` : ''} + ${hasVoices ? ` +
+ 🎙️ 语音通话记录(孤立数据) +
+ ${orphanedVoices.map(item => ` + + `).join('')} + ` : ''} +
+
+ +
+ + +
+
+
+ `; + + const phoneContainer = document.querySelector('.wechat-phone') || document.body; + phoneContainer.appendChild(modal); + + // 关闭按钮 + modal.querySelector('#wechat-cache-modal-close').addEventListener('click', () => modal.remove()); + modal.querySelector('#wechat-cache-cancel').addEventListener('click', () => modal.remove()); + + modal.addEventListener('click', (e) => { + if (e.target === modal) modal.remove(); + }); + + // 全选/取消全选 + const selectAllCheckbox = modal.querySelector('#wechat-cache-select-all'); + const itemCheckboxes = modal.querySelectorAll('.wechat-cache-checkbox'); + + selectAllCheckbox.addEventListener('change', () => { + itemCheckboxes.forEach(cb => cb.checked = selectAllCheckbox.checked); + }); + + itemCheckboxes.forEach(cb => { + cb.addEventListener('change', () => { + const allChecked = Array.from(itemCheckboxes).every(c => c.checked); + const noneChecked = Array.from(itemCheckboxes).every(c => !c.checked); + selectAllCheckbox.checked = allChecked; + selectAllCheckbox.indeterminate = !allChecked && !noneChecked; + }); + }); + + // 确认清除 + modal.querySelector('#wechat-cache-confirm').addEventListener('click', async () => { + const selectedSummaryIndexes = new Set(); + const selectedVoiceIndexes = []; + + itemCheckboxes.forEach(cb => { + if (cb.checked) { + if (cb.dataset.type === 'summary') { + selectedSummaryIndexes.add(parseInt(cb.dataset.index)); + } else if (cb.dataset.type === 'voice') { + selectedVoiceIndexes.push(parseInt(cb.dataset.contactIndex)); + } + } + }); + + if (selectedSummaryIndexes.size === 0 && selectedVoiceIndexes.length === 0) { + showToast('请至少选择一项'); + return; + } + + let clearedCount = 0; + + // 清除总结缓存 + if (selectedSummaryIndexes.size > 0) { + const settings = getSettings(); + settings.selectedLorebooks = settings.selectedLorebooks.filter((_, idx) => !selectedSummaryIndexes.has(idx)); + requestSave(); + clearedCount += selectedSummaryIndexes.size; + } + + // 清除语音缓存 + for (const contactIdx of selectedVoiceIndexes) { + try { + await deleteVoiceRecordingsByContact(contactIdx); + clearedCount++; + } catch (err) { + console.error('[可乐] 删除语音记录失败:', err); + } + } + + modal.remove(); + showToast(`已清除 ${clearedCount} 项缓存数据`); + console.log('[可乐] 已清除缓存,总结:', selectedSummaryIndexes.size, '语音:', selectedVoiceIndexes.length); + }); +} + function setupPhoneMinimize() { const phone = document.getElementById('wechat-phone'); const minimizeBtn = document.getElementById('wechat-minimize-btn'); @@ -763,6 +968,12 @@ function bindEvents() { toggleFloatingBallEnabled(); }); + // 下拉菜单 - 清除缓存 + document.getElementById('wechat-menu-clear-cache')?.addEventListener('click', () => { + document.getElementById('wechat-dropdown-menu')?.classList.add('hidden'); + clearOrphanedCache(); + }); + // ===== 群聊创建弹窗事件 ===== document.getElementById('wechat-group-create-close')?.addEventListener('click', closeGroupCreateModal); document.getElementById('wechat-group-create-confirm')?.addEventListener('click', createGroupChat); diff --git a/phone-html.js b/phone-html.js index c51030c..fea42bb 100644 --- a/phone-html.js +++ b/phone-html.js @@ -118,6 +118,10 @@ export function generatePhoneHTML() { 悬浮窗 +
+ + 清除缓存 +