您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动分页抽取全部对话,按时间分组,支持选中删除和导出JSON备份,增加加载进度显示。
// ==UserScript== // @name ChatGPT 批量对话删除器 // @namespace https://chatgpt.com/ // @version 1.3 // @description 自动分页抽取全部对话,按时间分组,支持选中删除和导出JSON备份,增加加载进度显示。 // @author 猫猫 & AI Assistant // @match https://chatgpt.com/* // @grant none // ==/UserScript== (function () { 'use strict'; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); let cachedToken = null; let alreadyLoaded = false; const checkboxRefs = {}; let allConversationsRaw = []; let progressElement = null; // 用于存储显示进度的 DOM 元素 let panelContainer = null; // 存储面板容器的引用 let toggleButton = null; // 存储切换按钮的引用 function parseDateGroups(dateStr) { const date = new Date(dateStr); const now = new Date(); const diffMs = now - date; const oneDay = 24 * 60 * 60 * 1000; if (date.toDateString() === now.toDateString()) return "今天"; const yesterday = new Date(now); yesterday.setDate(now.getDate() - 1); if (date.toDateString() === yesterday.toDateString()) return "昨天"; if (diffMs <= 7 * oneDay) return "7天内"; if (diffMs <= 30 * oneDay) return "30天内"; return "更早"; } const categoryOrder = ["今天", "昨天", "7天内", "30天内", "更早"]; function formatDateLocal(isoStr) { const d = new Date(isoStr); return `${d.toLocaleDateString()} ${d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`; } const realFetch = window.fetch; window.fetch = async function (...args) { const [url, options] = args; if (typeof url === 'string' && url.includes('/backend-api/') && options?.headers?.Authorization && !cachedToken) { cachedToken = options.headers.Authorization; console.log(`🎯 抓到 token: ${cachedToken ? '成功' : '失败'} (来自: ${url})`); // !! 不再自动调用 main !! // Token 捕获后,可以更新切换按钮的状态或提示用户可以点击了(可选) if (toggleButton && cachedToken) { toggleButton.disabled = false; toggleButton.title = '打开对话管理器'; } } return realFetch.apply(this, args); }; function getCookie(name) { const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`)); return match ? match[2] : null; } function getAuthHeaders() { const token = cachedToken; const deviceId = getCookie("oai-did"); if (!token) { // 如果 token 仍然未获取,可以尝试从 localStorage 或其他地方获取,或者提示用户 console.warn("⚠️ Token 未通过 fetch 拦截获取,尝试其他方法或等待..."); // throw new Error("❌ 无法获取 Authorization token"); // 或者先不抛出错误,看后续是否能拿到 } if (!deviceId) throw new Error("❌ 缺少 oai-did cookie"); // 返回头部,即使 token 暂时为空,让 fetchAllConversations 内部处理重试或失败 return { "Authorization": token || '', // 提供空字符串如果未获取到 "OAI-Device-Id": deviceId, "Content-Type": "application/json" }; } // 新增:更新进度的函数 function updateProgressUI(loaded, total) { if (progressElement) { progressElement.textContent = `⏳ 正在加载... ${loaded}${total !== null ? ` / ${total}` : ''} 条对话`; } } // 修改 showFinalUI 确保面板显示 function showFinalUI(conversations, exportFn) { // 隐藏加载提示 if (progressElement) progressElement.style.display = 'none'; console.log("🎉 会话总数:", conversations.length); // 容器获取与样式设置 panelContainer = document.getElementById('chatgpt-cleaner-container'); if (!panelContainer) { panelContainer = document.createElement('div'); panelContainer.id = 'chatgpt-cleaner-container'; document.body.appendChild(panelContainer); // --- 容器基础样式 --- panelContainer.style.position = 'fixed'; panelContainer.style.top = '60px'; // 稍微往下一点,避免遮挡顶部元素 panelContainer.style.right = '15px'; panelContainer.style.width = '350px'; // 稍微宽一点 panelContainer.style.maxHeight = 'calc(100vh - 80px)'; // 限制最大高度 panelContainer.style.overflowY = 'auto'; panelContainer.style.background = '#ffffff'; // 明确白色背景 panelContainer.style.color = '#333333'; // 明确深色文字 panelContainer.style.border = '1px solid #e0e0e0'; panelContainer.style.borderRadius = '8px'; // 圆角 panelContainer.style.padding = '15px'; // 内边距 panelContainer.style.zIndex = '9998'; panelContainer.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; // 改进阴影 panelContainer.style.fontFamily = 'sans-serif'; // 使用通用字体 panelContainer.style.fontSize = '14px'; // panelContainer.style.display = 'none'; // 初始隐藏状态由 toggle 控制 } panelContainer.innerHTML = ''; // 清空旧内容 panelContainer.style.display = 'block'; // 确保显示 // --- 标题 --- const title = document.createElement('h2'); title.textContent = '对话批量管理'; title.style.fontSize = '1.2em'; title.style.fontWeight = '600'; title.style.color = '#111'; // 标题深色 title.style.marginTop = '0'; title.style.marginBottom = '15px'; title.style.borderBottom = '1px solid #eee'; title.style.paddingBottom = '10px'; panelContainer.appendChild(title); // --- 按钮区域 --- const buttonGroup = document.createElement('div'); buttonGroup.style.marginBottom = '15px'; buttonGroup.style.display = 'flex'; buttonGroup.style.flexWrap = 'wrap'; // 允许按钮换行 buttonGroup.style.gap = '8px'; // 按钮间距 // 统一样式函数 const styleButton = (btn, primary = true) => { btn.style.padding = '8px 12px'; btn.style.border = 'none'; btn.style.borderRadius = '5px'; btn.style.cursor = 'pointer'; btn.style.fontSize = '0.9em'; btn.style.transition = 'background-color 0.2s ease, box-shadow 0.2s ease'; if (primary) { btn.style.background = '#0d6efd'; // Bootstrap primary blue btn.style.color = 'white'; } else { btn.style.background = '#6c757d'; // Bootstrap secondary gray btn.style.color = 'white'; } btn.onmouseover = () => { btn.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)'; btn.style.filter = 'brightness(1.1)'; }; btn.onmouseout = () => { btn.style.boxShadow = 'none'; btn.style.filter = 'brightness(1)'; }; }; // 添加导出按钮 const exportButton = document.createElement('button'); exportButton.textContent = '导出 JSON'; exportButton.onclick = exportFn; styleButton(exportButton); buttonGroup.appendChild(exportButton); // 添加全选按钮 (占位) const selectAllButton = document.createElement('button'); selectAllButton.textContent = '全选'; selectAllButton.onclick = () => { Object.values(checkboxRefs).forEach(cb => cb.checked = true); console.log("全选操作 (待实现)"); }; styleButton(selectAllButton, false); // 使用次要样式 buttonGroup.appendChild(selectAllButton); // 添加取消全选按钮 (占位) const deselectAllButton = document.createElement('button'); deselectAllButton.textContent = '取消全选'; deselectAllButton.onclick = () => { Object.values(checkboxRefs).forEach(cb => cb.checked = false); console.log("取消全选操作 (待实现)"); }; styleButton(deselectAllButton, false); buttonGroup.appendChild(deselectAllButton); // 添加删除按钮 (占位) const deleteButton = document.createElement('button'); deleteButton.textContent = '删除选中'; deleteButton.style.background = '#dc3545'; // Bootstrap danger red deleteButton.onclick = async () => { // 改为 async 函数 const selectedCheckboxes = Object.values(checkboxRefs).filter(cb => cb.checked); const selectedIds = selectedCheckboxes.map(cb => cb.value); if (selectedIds.length === 0) { alert('请先选择要删除的对话。'); return; } // --- 重要:用户确认 --- if (!confirm(`确定要删除 ${selectedIds.length} 个选中的对话吗?\n(注意:这是永久删除,而不是将其设置为不可见)`)) { return; } // --- 开始删除流程 --- console.log("开始删除选中的对话:", selectedIds); // 禁用按钮防止重复点击 const allButtons = panelContainer.querySelectorAll('button'); allButtons.forEach(btn => btn.disabled = true); deleteButton.textContent = `正在删除(0/${selectedIds.length})...`; let successCount = 0; let failCount = 0; const failedIds = []; for (let i = 0; i < selectedIds.length; i++) { const id = selectedIds[i]; deleteButton.textContent = `正在删除(${i + 1}/${selectedIds.length})...`; try { await deleteSingleConversation(id); successCount++; // 从 UI 移除成功的条目 const checkbox = checkboxRefs[id]; if (checkbox) { checkbox.closest('div').remove(); // 移除包含 checkbox 的整行 div delete checkboxRefs[id]; // 从引用中删除 } } catch (error) { console.error(`删除对话 ${id} 失败:`, error); failCount++; failedIds.push(id); } // 稍微延时,避免请求过于频繁 await sleep(200); // 200ms 延迟 } // --- 删除完成,恢复 UI --- allButtons.forEach(btn => btn.disabled = false); deleteButton.textContent = '删除选中'; // 恢复按钮文字 // 显示结果 let message = `删除操作完成。 成功删除: ${successCount} 个`; if (failCount > 0) { message += ` 失败: ${failCount} 个 (ID: ${failedIds.join(', ')})\n请检查控制台获取详细错误信息。`; console.error("以下对话删除失败:", failedIds); } alert(message); // 可选:更新 allConversationsRaw (如果需要精确的备份) allConversationsRaw = allConversationsRaw.filter(convo => !selectedIds.includes(convo.id) || failedIds.includes(convo.id)); console.log("内存中的原始数据已更新 (移除了成功的对话)"); }; styleButton(deleteButton); deleteButton.style.background = '#dc3545'; // 覆盖默认主色 buttonGroup.appendChild(deleteButton); panelContainer.appendChild(buttonGroup); // --- 对话列表区域 --- const listContainer = document.createElement('div'); listContainer.id = 'chatgpt-cleaner-list'; // 可以给列表容器也加点样式,比如内边距 // listContainer.style.paddingTop = '10px'; // 按日期分组对话 const grouped = conversations.reduce((acc, convo) => { const group = parseDateGroups(convo.update_time); if (!acc[group]) acc[group] = []; acc[group].push(convo); return acc; }, {}); // 按预定顺序显示分组 let itemCount = 0; categoryOrder.forEach(groupName => { if (grouped[groupName] && grouped[groupName].length > 0) { itemCount += grouped[groupName].length; const groupDiv = document.createElement('div'); groupDiv.style.marginBottom = '15px'; // --- 分组标题 --- const groupTitle = document.createElement('h3'); groupTitle.textContent = groupName; groupTitle.style.fontSize = '1em'; groupTitle.style.fontWeight = '600'; groupTitle.style.color = '#555'; // 分组标题颜色 groupTitle.style.marginTop = '15px'; // 与上一组的间距 groupTitle.style.marginBottom = '8px'; groupTitle.style.paddingBottom = '5px'; groupTitle.style.borderBottom = '1px solid #f0f0f0'; // 分组标题下划线 groupDiv.appendChild(groupTitle); grouped[groupName].forEach(convo => { // --- 单个对话行 --- const convoDiv = document.createElement('div'); convoDiv.style.display = 'flex'; convoDiv.style.alignItems = 'center'; convoDiv.style.padding = '6px 4px'; // 行内上下间距 convoDiv.style.marginBottom = '2px'; convoDiv.style.cursor = 'pointer'; convoDiv.style.borderRadius = '4px'; convoDiv.style.transition = 'background-color 0.15s ease'; convoDiv.onmouseover = () => { convoDiv.style.backgroundColor = '#f0f0f0'; }; convoDiv.onmouseout = () => { convoDiv.style.backgroundColor = 'transparent'; }; // --- 复选框 --- const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.value = convo.id; checkbox.style.marginRight = '10px'; // 与文字间距 checkbox.style.marginLeft = '4px'; checkbox.style.flexShrink = '0'; checkbox.style.cursor = 'pointer'; checkboxRefs[convo.id] = checkbox; // 存储引用 convoDiv.appendChild(checkbox); // --- 标题和日期容器 --- const textWrapper = document.createElement('div'); textWrapper.style.flexGrow = '1'; textWrapper.style.overflow = 'hidden'; // 防止文字溢出容器 textWrapper.style.display = 'flex'; textWrapper.style.flexDirection = 'column'; // 标题和日期垂直排列 // --- 标题文字 --- const titleSpan = document.createElement('span'); titleSpan.textContent = `${convo.title || '无标题'}`; titleSpan.title = `ID: ${convo.id} 点击跳转到对话`; titleSpan.style.color = '#333'; // 确保标题文字是深色 titleSpan.style.overflow = 'hidden'; titleSpan.style.textOverflow = 'ellipsis'; titleSpan.style.whiteSpace = 'nowrap'; titleSpan.style.marginBottom = '3px'; // 与日期间距 titleSpan.onclick = (e) => { e.stopPropagation(); // 阻止事件冒泡到 convoDiv 的点击 window.location.href = `/c/${convo.id}`; }; textWrapper.appendChild(titleSpan); // --- 日期文字 --- const dateSpan = document.createElement('span'); dateSpan.textContent = `${formatDateLocal(convo.update_time)}`; dateSpan.style.fontSize = '0.85em'; dateSpan.style.color = '#666'; // 日期颜色稍浅 dateSpan.style.whiteSpace = 'nowrap'; textWrapper.appendChild(dateSpan); convoDiv.appendChild(textWrapper); // --- 整行点击切换复选框 --- convoDiv.onclick = (e) => { // 只有当点击目标不是 checkbox 或 titleSpan 时才切换 checkbox if (e.target !== checkbox && !titleSpan.contains(e.target)) { checkbox.checked = !checkbox.checked; } }; groupDiv.appendChild(convoDiv); }); listContainer.appendChild(groupDiv); } }); // 如果没有加载到任何对话,显示提示 if (itemCount === 0) { const noDataDiv = document.createElement('div'); noDataDiv.textContent = '未找到任何对话记录。'; noDataDiv.style.textAlign = 'center'; noDataDiv.style.padding = '20px'; noDataDiv.style.color = '#888'; listContainer.appendChild(noDataDiv); } panelContainer.appendChild(listContainer); // 添加一个关闭按钮到面板内部(可选,但更方便) const closeButton = document.createElement('button'); closeButton.textContent = '×'; // 使用乘号作为关闭图标 closeButton.style.position = 'absolute'; closeButton.style.top = '10px'; closeButton.style.right = '10px'; closeButton.style.background = 'transparent'; closeButton.style.border = 'none'; closeButton.style.fontSize = '1.5em'; closeButton.style.color = '#888'; closeButton.style.cursor = 'pointer'; closeButton.style.lineHeight = '1'; closeButton.style.padding = '5px'; closeButton.title = '关闭面板'; closeButton.onclick = () => { if (panelContainer) { panelContainer.style.display = 'none'; } if (toggleButton) { toggleButton.textContent = '管理'; // 或图标 toggleButton.title = '打开对话管理器'; } }; panelContainer.insertBefore(closeButton, panelContainer.firstChild); // 插入到标题前 console.log("✅ UI 渲染完成,样式已更新"); } // 修改 fetchAllConversations async function fetchAllConversations(progressCallback) { let headers = getAuthHeaders(); // 如果第一次获取 headers 时 token 为空,可能需要稍等并重试获取 if (!headers.Authorization) { console.log("⏳ Token 尚未捕获,等待 1 秒后重试获取 headers..."); await sleep(1000); headers = getAuthHeaders(); if (!headers.Authorization) { throw new Error("❌ 无法获取 Authorization token,请尝试刷新页面或重新登录。"); } } let conversations = []; let offset = 0; const limit = 50; // 稍微调大limit,减少请求次数,但注意不要超过API限制 let done = false; let total = null; // 初始化 total 为 null while (!done) { console.log(`⏳ 正在获取对话: offset=${offset}, limit=${limit}${total !== null ? `, total=${total}`:''}`); try { const res = await fetch(`/backend-api/conversations?offset=${offset}&limit=${limit}&order=updated`, { credentials: "include", headers // 使用更新后的 headers }); if (!res.ok) { const errorText = await res.text(); console.error(`❌ 获取对话失败: ${res.status} ${res.statusText}`, errorText); // 特别处理 401 Unauthorized if (res.status === 401) { throw new Error(`API 请求失败: 401 Unauthorized. Token 可能已过期,请刷新页面或重新登录。`); } throw new Error(`API 请求失败: ${res.status} ${res.statusText}`); } const json = await res.json(); // --- 关键修改:使用 'items' 和 'total' 字段 --- const page = json.items || []; // 假设对话列表在 'items' 字段 // 只有在 total 尚未确定时更新 total if (total === null && typeof json.total === 'number') { total = json.total; console.log(`ℹ️ 获取到对话总数: ${total}`); } // --- 结束修改 --- if (page.length === 0) { // 如果API不返回total,或者返回的total不准,这仍然是最后的保障 console.log("✅ 获取完成(API返回空列表或已达末页)"); done = true; } else { conversations = conversations.concat(page); offset += limit; // 按 limit 增加 offset 请求下一页 // 如果获取的数量已达到或超过 total(如果 total 可知),也视为完成 if (total !== null && conversations.length >= total) { console.log(`✅ 获取完成(已达到或超过声明的总数 ${total})`); // 可能获取了比 total 稍多的数据,如果 API 返回不精确或并发请求导致 // 为确保准确,最好还是依赖 page.length === 0 作为主要结束条件,或者 offset 足够大 // conversations = conversations.slice(0, total); // 暂时不裁剪,以防total不准 done = true; // 标记完成 } // 调用进度回调 if (progressCallback) { // 传递当前获取的数量和已知的总数 progressCallback(conversations.length, total); } await sleep(300); // 保持适当的 API 请求间隔 } } catch (error) { console.error("❌ 获取对话分页时出错:", error); // 通知用户并停止 if (progressElement) progressElement.textContent = `❌ 加载出错: ${error.message}. 请检查控制台。`; throw error; // 重新抛出错误,中断流程 } } allConversationsRaw = conversations; // 用于导出备份 console.log(`📊 最终获取 ${conversations.length} 条对话`); // 按更新时间降序排序 return conversations.sort((a, b) => new Date(b.update_time) - new Date(a.update_time)); } // 修改 __chatCleanerInject 以处理加载状态 window.__chatCleanerInject = function (conversations, exportFn, isLoading = false) { if (isLoading) { // 如果是加载状态,创建或显示进度元素 if (!progressElement) { progressElement = document.createElement('div'); progressElement.id = 'chatgpt-cleaner-progress'; // --- 设置进度条样式 --- progressElement.style.position = 'fixed'; progressElement.style.top = '10px'; progressElement.style.right = '10px'; progressElement.style.background = 'rgba(0,0,0,0.7)'; progressElement.style.color = 'white'; progressElement.style.padding = '5px 10px'; progressElement.style.borderRadius = '5px'; progressElement.style.zIndex = '9999'; progressElement.style.fontSize = '12px'; // 小一点 progressElement.style.fontFamily = 'sans-serif'; // Consistent font // --- 结束样式设置 --- document.body.appendChild(progressElement); } progressElement.style.display = 'block'; progressElement.style.backgroundColor = 'rgba(0,0,0,0.7)'; // Reset background color progressElement.textContent = '⏳ 正在准备加载...'; // 初始文本 } else { // 如果不是加载状态 (即加载完成),调用渲染最终 UI 的函数 if (Array.isArray(conversations)) { showFinalUI(conversations, exportFn); } else { console.error("❌ 渲染最终 UI 失败:对话数据不是数组。"); if (progressElement) { progressElement.textContent = '❌ 加载数据格式错误'; progressElement.style.backgroundColor = 'red'; } } } }; function downloadJSON() { if (!allConversationsRaw || allConversationsRaw.length === 0) { alert("没有对话数据可导出。"); return; } try { const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(allConversationsRaw, null, 2)); const a = document.createElement('a'); a.setAttribute("href", dataStr); // 更安全的文件名 const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); a.setAttribute("download", `chatgpt_backup_${timestamp}.json`); document.body.appendChild(a); // Required for Firefox a.click(); document.body.removeChild(a); // Clean up console.log("📄 JSON 文件已触发下载"); } catch (error) { console.error("❌ 导出 JSON 时出错:", error); alert(`导出失败: ${error.message}`); } } // 修改 main 函数,不再自动执行,由按钮触发 async function main() { // 防止重复加载数据 if (alreadyLoaded) { console.log("🚫 数据已加载,无需重复执行 main。"); // 如果面板已存在但被隐藏,直接显示它 if (panelContainer && panelContainer.style.display === 'none') { panelContainer.style.display = 'block'; if (toggleButton) { toggleButton.textContent = '隐藏'; // 更新按钮文本 toggleButton.title = '隐藏对话管理器'; } } return; } console.log("🚀 ChatGPT 清理器启动 main 函数 (由按钮触发)... "); // 1. 显示加载 UI window.__chatCleanerInject(null, downloadJSON, true); try { // 2. 获取所有对话 console.log("📡 开始获取对话列表..."); const conversations = await fetchAllConversations(updateProgressUI); alreadyLoaded = true; // 标记数据加载完成 if (toggleButton) toggleButton.disabled = false; // 确保按钮可用 // 3. 渲染最终的对话列表 UI console.log("🎨 准备渲染最终 UI..."); window.__chatCleanerInject(conversations, downloadJSON, false); // false 表示非加载状态 if (toggleButton) { toggleButton.textContent = '隐藏'; // 更新按钮文本 toggleButton.title = '隐藏对话管理器'; } } catch (error) { console.error("❌ 主流程执行失败:", error); if (progressElement) { progressElement.textContent = `❌ 加载失败: ${error.message}`; // 简化信息 progressElement.style.backgroundColor = 'red'; } // 出错时也解锁按钮,允许用户重试 if (toggleButton) toggleButton.disabled = false; alreadyLoaded = false; // 允许重试加载 } } // --- 创建切换按钮并添加到页面 --- function createToggleButton() { toggleButton = document.createElement('button'); toggleButton.id = 'chatgpt-cleaner-toggle'; toggleButton.textContent = '管理'; // 初始文本,或用图标 toggleButton.title = '等待 Token...'; // 初始提示 toggleButton.disabled = true; // 初始禁用,等待 Token // --- 切换按钮样式 --- toggleButton.style.position = 'fixed'; toggleButton.style.bottom = '20px'; toggleButton.style.right = '20px'; toggleButton.style.padding = '10px 15px'; toggleButton.style.background = '#0d6efd'; toggleButton.style.color = 'white'; toggleButton.style.border = 'none'; toggleButton.style.borderRadius = '50px'; // 圆形或胶囊形 toggleButton.style.cursor = 'pointer'; toggleButton.style.zIndex = '9997'; // 比面板低一点 toggleButton.style.boxShadow = '0 2px 8px rgba(0,0,0,0.3)'; toggleButton.style.fontSize = '14px'; toggleButton.style.transition = 'background-color 0.2s ease, transform 0.2s ease'; toggleButton.onmouseover = () => { toggleButton.style.filter = 'brightness(1.1)'; }; toggleButton.onmouseout = () => { toggleButton.style.filter = 'brightness(1)'; }; toggleButton.onclick = () => { toggleButton.disabled = true; // 防止快速重复点击 panelContainer = document.getElementById('chatgpt-cleaner-container'); if (!alreadyLoaded) { // 首次点击,执行 main 加载数据 console.log("🖱️ 切换按钮点击:首次加载数据..."); main().finally(() => { // 无论成功失败,最终都解锁按钮 // 状态更新由 main 内部处理 // toggleButton.disabled = false; // main内部处理了 }); } else if (panelContainer) { // 数据已加载,切换面板显示状态 const isHidden = panelContainer.style.display === 'none'; console.log(`🖱️ 切换按钮点击:切换面板显示为 ${isHidden ? '可见' : '隐藏'}`); panelContainer.style.display = isHidden ? 'block' : 'none'; toggleButton.textContent = isHidden ? '隐藏' : '管理'; toggleButton.title = isHidden ? '隐藏对话管理器' : '打开对话管理器'; toggleButton.disabled = false; // 操作完成,解锁按钮 } else { // 数据已加载但面板丢失?理论上不应发生,但作为保险 console.warn("面板容器丢失,尝试重新加载..."); alreadyLoaded = false; // 重置状态以允许重新加载 main().finally(() => { /* toggleButton.disabled = false; */ }); } }; document.body.appendChild(toggleButton); console.log("🔌 对话管理切换按钮已创建。"); } // 脚本主体逻辑:立即创建按钮,等待 Token console.log("ChatGPT Cleaner 脚本已注入,等待 API 请求以捕获 Token..."); // 确保 DOM 加载完成后创建按钮 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createToggleButton); } else { createToggleButton(); } // --- 新增:单个对话删除函数 --- async function deleteSingleConversation(conversationId) { if (!conversationId) { throw new Error("无效的 conversation ID"); } console.log(`⏳ 准备删除对话: ${conversationId}`); const headers = getAuthHeaders(); if (!headers.Authorization) { throw new Error("无法获取 Authorization token"); } const url = `/backend-api/conversation/${conversationId}`; const body = JSON.stringify({ is_visible: false }); try { const response = await fetch(url, { method: 'PATCH', headers: headers, body: body, credentials: 'include' // 确保包含 credentials }); if (!response.ok) { const errorData = await response.text(); console.error(`删除 ${conversationId} 请求失败: ${response.status} ${response.statusText}`, errorData); throw new Error(`API 请求失败: ${response.status} ${response.statusText}`); } const result = await response.json(); // 通常 PATCH 成功会返回 {success: true} console.log(`✅ 对话 ${conversationId} 删除成功:`, result); return result; } catch (error) { console.error(`❌ 执行删除对话 ${conversationId} 的 fetch 时出错:`, error); throw error; // 将错误向上抛出 } } })(); // 脚本立即执行函数结束