🏠 Home 

AI对话保存

AI对话保存,兼容ChatGPT和DeepSeek,安装后右下角出现一个悬浮窗按钮,点击即可保存当前对话,自动询问文件名,自动添加时间戳,保存为html格式


Install this script?
// ==UserScript==
// @name         AI对话保存
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  AI对话保存,兼容ChatGPT和DeepSeek,安装后右下角出现一个悬浮窗按钮,点击即可保存当前对话,自动询问文件名,自动添加时间戳,保存为html格式
// @author       thunder-sword
// @match        https://chat.openai.com/*
// @match        https://chat.deepseek.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=openai.com
// @license      MIT
// @grant        none
// ==/UserScript==
/* 作用:根据指定的html字符串下载html文件,可指定文件名,自动获取当前页面中的css,自动添加时间戳 */
function downloadHtml(html, fileName='page.html', getCSS=true, addTim###ffix=true){
var r###lt=`<head></head>`;
if(getCSS){
/* 获取当前页面 css */
const css = Array.from(document.styleSheets)
.filter(styleSheet => {
try {
return styleSheet.cssRules; // 尝试访问 cssRules,如果不抛出错误则说明不是跨域的
} catch (e) {
return false; // 跨域样式表,跳过
}
})
.map(styleSheet => Array.from(styleSheet.cssRules).map(rule => rule.cssText))
.flat()
.join("\n");
r###lt=`<head><style>\n${css}\n</style></head>`;
}
r###lt+='<body>'+html+'</body>';
const file = new File([r###lt], "page.html", { type: "text/html" });
const a = document.createElement("a");
a.href = URL.createObjectURL(file);
fileName = fileName.endsWith(".html") ? fileName : fileName + ".html";
if(addTim###ffix){
var currentTime = new Date();
fileName=fileName.slice(0,-5)+`-${currentTime.getFullYear()}${(currentTime.getMonth()+1).toString().padStart(2, "0")}${currentTime.getDate().toString().padStart(2, "0")}.html`;
}
a.download = fileName;
document.body.appendChild(a);
a.click();
}
var copyScript =`var cs = document.querySelectorAll('.bg-black > div > button');
for (let i = 0; i < cs.length; i++) {
/* 为按钮元素添加点击事件监听器 */
cs[i].addEventListener('click', function() {
/* 获取需要复制的文本内容 */
let text = cs[i].parentNode.parentNode.querySelector('div.p-4 > code').innerText;
/* 将文本内容复制到剪贴板 */
navigator.clipboard.writeText(text).then(function() {
/* 复制成功 */
/* alert('文本已复制到剪贴板!'); */
/* 更新按钮文字,提示复制成功 */
cs[i].innerHTML = '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="20 6 9 17 4 12"></polyline></svg>Copied!';
/* 设置定时器,延迟两秒钟恢复按钮的原状 */
setTimeout(function() {
cs[i].innerHTML = '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>Copy code';
}, 2000);
}, function() {
/* 复制失败 */
/* alert('文本复制失败!'); */
});
});
}`;
/* 作用:保存ai对话记录,可弹窗询问要保存的文件名,默认为false */
function savaAiRecording(fileName='page.html', askFileName=false){
/* askFileName为true时弹窗询问文件名 */
fileName=askFileName?prompt('输入要保存的文件名:'):fileName;
var body=document.createElement('body');
body.innerHTML=document.body.innerHTML;
/* 删除所有script标签 */
var ps = body.querySelectorAll('script');
for (var i = 0; i < ps.length; i++) {
ps[i].parentNode.removeChild(ps[i]);
}
/* 删除所有style标签,因为downloadHtml会自动再获取一次 */
ps = body.querySelectorAll('style');
for (var i = 0; i < ps.length; i++) {
ps[i].parentNode.removeChild(ps[i]);
}
/* 删除下边框 */
var element=body.querySelector('#__next > div > div > main > div.absolute');
element && element.remove();
/* 删除DeepSeek侧边框 */
element=body.querySelector('div#root > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div:nth-child(1)');
element && element.remove();
/* 删除侧边框 */
element=body.querySelector('#__next > div > div.hidden');
element && element.remove();
/* 删除侧边框间隔 */
element=body.querySelector('#__next > div > div');
if(element){element.className='';}
/* 添加script标签,用于修复一键复制 */
var script=document.createElement('script');
script.innerHTML=copyScript;
body.appendChild(script);
downloadHtml(body.innerHTML, fileName);
}
//版本号:v0.0.2
//作用:创建一个在右下角出现的悬浮窗按钮,多个按钮会自动排序,点击即可执行对应函数
function createFloatButton(name, func){
//没有容器则生成容器
let box=document.querySelector("body > div#ths_button_container");
if(!box){
box=document.createElement('div');
box.id="ths_button_container";
box.style.cssText = `
position: fixed;
bottom: 10px;
right: 10px;
min-height: 30px; /* 设置一个最小高度,确保容器有一定高度 */
display: flex;
z-index: 9999;
flex-direction: column;
`;
document.body.appendChild(box);
}
// 创建一个 div 元素
var floatWindow = document.createElement('div');
// 设置 div 的内容
//floatWindow.innerHTML = '点我执行代码';
floatWindow.innerHTML = name;
// 设置 div 的样式
floatWindow.style.cssText = `
padding: 5px;
background-color: #333;
color: #fff;
border-radius: 5px;
font-size: 16px;
text-align: center;
opacity: 1;
z-index: 9999;
margin: 5px;
cursor: pointer; /* 鼠标可以选中 */
`;
// 将悬浮窗的优先级提高
floatWindow.style.zIndex = "99999";
var isDragging = false;
var currentX;
var currentY;
var initialX;
var initialY;
var xOffset = 0;
var yOffset = 0;
var cursorX;
var cursorY;
floatWindow.addEventListener("mousedown", function(e) {
if (!isDragging) {
cursorX = e.clientX;
cursorY = e.clientY;
initialX = cursorX - xOffset;
initialY = cursorY - yOffset;
isDragging = true;
}
});
floatWindow.addEventListener("mousemove", function(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, floatWindow);
}
});
floatWindow.addEventListener("mouseup", async function(e) {
initialX = currentX;
initialY = currentY;
isDragging = false;
// 如果点击时鼠标的位置没有改变,就认为是真正的点击
if (cursorX === e.clientX && cursorY === e.clientY) {
await func();
}
});
// 为悬浮窗添加事件处理程序,用来监听触摸开始和触摸移动事件
// 这些事件处理程序的实现方式与上面的鼠标事件处理程序类似
floatWindow.addEventListener('touchstart', (event) => {
if (!isDragging) {
cursorX = event.touches[0].clientX;
cursorY = event.touches[0].clientY;
initialX = cursorX - xOffset;
initialY = cursorY - yOffset;
isDragging = true;
}
});
floatWindow.addEventListener('touchmove', (event) => {
if (isDragging) {
currentX = event.touches[0].clientX - initialX;
currentY = event.touches[0].clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, floatWindow);
}
});
// 为悬浮窗添加事件处理程序,用来监听触摸结束事件
// 这个事件处理程序的实现方式与上面的鼠标事件处理程序类似
floatWindow.addEventListener('touchend', async () => {
initialX = currentX;
initialY = currentY;
isDragging = false;
// 如果点击时鼠标的位置没有改变,就认为是真正的点击
if (cursorX === event.touches[0].clientX && cursorY === event.touches[0].clientY) {
await func();
}
});
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
//将悬浮窗添加到box元素中
box.appendChild(floatWindow);
}
createFloatButton("AI对话保存", ()=>{
savaAiRecording('',true);
});