// ==UserScript==
// @name 元宝 & DeepSeek 对话导出工具 | Yuanbao & DeepSeek Chat Exporter
// @namespace http://tampermonkey.net/
// @version 1.0.7
// @description 支持元宝和 DeepSeek 对话导出,支持 JSON 和 Markdown 格式。Export Yuanbao and DeepSeek chat history with JSON and Markdown formats.
// @author dst1213
// @license MIT
// @match https://yuanbao.tencent.com/chat/*
// @match https://*.deepseek.com/a/chat/s/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
let state = {
targetResponse: null,
lastUpdateTime: null,
convertedMd: null,
isYuanbao: window.location.href.includes('yuanbao.tencent.com'),
isDeepSeek: window.location.href.includes('deepseek.com')
};
const log = {
info: (msg) => console.log(`[Chat Exporter] ${msg}`),
error: (msg, e) => console.error(`[Chat Exporter] ${msg}`, e)
};
function processTargetResponse() {
try {
if (state.isYuanbao) {
const messages = extractYuanbaoMessages();
if (messages.length > 0) {
state.targetResponse = JSON.stringify({ messages }, null, 2);
state.lastUpdateTime = new Date().toLocaleTimeString();
state.convertedMd = convertJsonToMd({ messages });
updateButtonStatus();
log.info('成功提取元宝对话内容');
}
} else if (state.isDeepSeek) {
// DeepSeek 的逻辑保持不变
// ...
}
} catch (e) {
log.error('处理对话内容时出错:', e);
}
}
function extractYuanbaoMessages() {
const messages = [];
const messageElements = document.querySelectorAll('.agent-chat__list__item');
messageElements.forEach(el => {
const role = el.classList.contains('agent-chat__list__item--human') ? 'human' : 'ai';
const content = el.querySelector('.hyc-content-text, .hyc-content-md')?.innerText || '';
messages.push({ role, content });
});
return messages;
}
function convertJsonToMd(data) {
let mdContent = [];
if (state.isYuanbao) {
data.messages.forEach(msg => {
const role = msg.role === 'human' ? 'Human' : 'Assistant';
mdContent.push(`### ${role}`);
mdContent.push(msg.content + '\n');
});
} else if (state.isDeepSeek) {
// DeepSeek 的逻辑保持不变
// ...
}
return mdContent.join('\n');
}
function updateButtonStatus() {
const jsonButton = document.getElementById('downloadJsonButton');
const mdButton = document.getElementById('downloadMdButton');
if (jsonButton && mdButton) {
const hasResponse = state.targetResponse !== null;
jsonButton.style.backgroundColor = hasResponse ? '#28a745' : '#007bff';
mdButton.style.backgroundColor = state.convertedMd ? '#28a745' : '#007bff';
const statusText = hasResponse ? `最后更新: ${state.lastUpdateTime}\n数据已准备好` : '等待目标响应中...';
jsonButton.title = statusText;
mdButton.title = statusText;
}
}
function createDownloadButtons() {
const buttonContainer = document.createElement('div');
const jsonButton = document.createElement('button');
const mdButton = document.createElement('button');
Object.assign(buttonContainer.style, {
position: 'fixed',
top: '45%',
right: '10px',
zIndex: '9999',
display: 'flex',
flexDirection: 'column',
gap: '10px',
opacity: '0.5',
transition: 'opacity 0.3s ease',
cursor: 'move'
});
const buttonStyles = {
padding: '8px 12px',
backgroundColor: '#007bff',
color: '#ffffff',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
transition: 'all 0.3s ease',
fontFamily: 'Arial, sans-serif',
boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
whiteSpace: 'nowrap',
fontSize: '14px'
};
jsonButton.id = 'downloadJsonButton';
jsonButton.innerText = 'JSON';
mdButton.id = 'downloadMdButton';
mdButton.innerText = 'MD';
Object.assign(jsonButton.style, buttonStyles);
Object.assign(mdButton.style, buttonStyles);
buttonContainer.onmouseenter = () => buttonContainer.style.opacity = '1';
buttonContainer.onmouseleave = () => buttonContainer.style.opacity = '0.5';
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
buttonContainer.onmousedown = dragStart;
document.onmousemove = drag;
document.onmouseup = dragEnd;
function dragStart(e) {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
if (e.target === buttonContainer) {
isDragging = true;
}
}
function drag(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, buttonContainer);
}
}
function dragEnd() {
isDragging = false;
}
function setTranslate(xPos, yPos, el) {
el.style.transform = `translate(${xPos}px, ${yPos}px)`;
}
jsonButton.onclick = function () {
if (!state.targetResponse) {
alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。');
return;
}
try {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
const chatName = state.isYuanbao ? `Yuanbao - Chat` : `DeepSeek - Chat`;
const fileName = `${chatName}_${timestamp}.json`;
const blob = new Blob([state.targetResponse], { type: 'application/json' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
log.info(`成功下载文件: ${fileName}`);
} catch (e) {
log.error('下载过程中出错:', e);
alert('下载过程中发生错误,请查看控制台了解详情。');
}
};
mdButton.onclick = function () {
if (!state.convertedMd) {
alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。');
return;
}
try {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
const chatName = state.isYuanbao ? `Yuanbao - Chat` : `DeepSeek - Chat`;
const fileName = `${chatName}_${timestamp}.md`;
const blob = new Blob([state.convertedMd], { type: 'text/markdown' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
log.info(`成功下载文件: ${fileName}`);
} catch (e) {
log.error('下载过程中出错:', e);
alert('下载过程中发生错误,请查看控制台了解详情。');
}
};
buttonContainer.appendChild(jsonButton);
buttonContainer.appendChild(mdButton);
document.body.appendChild(buttonContainer);
updateButtonStatus();
}
window.addEventListener('load', function () {
createDownloadButtons();
const observer = new MutationObserver(() => {
processTargetResponse();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
log.info('对话导出脚本已启动');
});
})();