您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在腾讯元宝对话中添加一键复制Markdown按钮(含思考过程),需点击右上角按钮激活
当前为
// ==UserScript== // @name Yuanbao Markdown Copy // @namespace http://tampermonkey.net/ // @version 0.6 // @description 在腾讯元宝对话中添加一键复制Markdown按钮(含思考过程),需点击右上角按钮激活 // @author LouisLUO // @match https://yuanbao.tencent.com/* // @icon https://cdn-bot.hunyuan.tencent.com/logo-v2.png // @grant none // @license MIT // ==/UserScript== // 鸣谢:本脚本部分思路和实现参考了 [腾讯元宝对话导出器 | Tencent Yuanbao Exporter](https://gf.qytechs.cn/zh-CN/scripts/532431-%E8%85%BE%E8%AE%AF%E5%85%83%E5%AE%9D%E5%AF%B9%E8%AF%9D%E5%AF%BC%E5%87%BA%E5%99%A8-tencent-yuanbao-exporter)(by Gao + Gemini 2.5 Pro),并受益于 GitHub Copilot 及 GPT-4.1 的辅助。 // Thanks: Some logic and implementation are inspired by [腾讯元宝对话导出器 | Tencent Yuanbao Exporter](https://gf.qytechs.cn/zh-CN/scripts/532431-%E8%85%BE%E8%AE%AF%E5%85%83%E5%AE%9D%E5%AF%B9%E8%AF%9D%E5%AF%BC%E5%87%BA%E5%99%A8-tencent-yuanbao-exporter) (by Gao + Gemini 2.5 Pro), with help from GitHub Copilot and GPT-4.1. (function () { // --- State Management --- let state = { latestDetailResponse: null, latestResponseSize: 0, latestResponseUrl: null, lastUpdateTime: null }; // --- Markdown 转换函数 --- function adjustHeaderLevels(text, increaseBy = 1) { if (!text) return ''; return text.replace(/^(#+)(\s*)(.*?)\s*$/gm, (match, hashes, existingSpace, content) => { return '#'.repeat(hashes.length + increaseBy) + ' ' + content.trim(); }); } function convertYuanbaoJsonToMarkdown(jsonData, onlyIndex = null) { // 转换单轮对话为 Markdown if (!jsonData || !jsonData.convs || !Array.isArray(jsonData.convs)) { return '# 错误:无效的JSON数据\n\n无法解析对话内容。'; } let markdownContent = ''; let refs = []; let refCount = 1; let convs = jsonData.convs; if (onlyIndex !== null) { convs = convs.filter(turn => turn.index === onlyIndex); } if (!convs.length) return ''; convs.forEach(turn => { if (turn.speaker === 'human') { let userTextMsg = ''; if (turn.speechesV2 && turn.speechesV2.length > 0 && turn.speechesV2[0].content) { const textBlock = turn.speechesV2[0].content.find(block => block.type === 'text'); if (textBlock && typeof textBlock.msg === 'string') { userTextMsg = textBlock.msg; } else if (typeof turn.displayPrompt === 'string') { userTextMsg = turn.displayPrompt; } } else if (typeof turn.displayPrompt === 'string') { userTextMsg = turn.displayPrompt; } markdownContent += userTextMsg + '\n'; if (turn.speechesV2 && turn.speechesV2.length > 0 && turn.speechesV2[0].content) { let uploadedMedia = []; turn.speechesV2[0].content.forEach(block => { if (block.type !== 'text' && block.fileName && block.url) { uploadedMedia.push(`[${block.fileName || '未知文件'}](${block.url || '#'})`); } }); if (uploadedMedia.length > 0) { markdownContent += `\n${uploadedMedia.join('\n')}\n`; } } } else if (turn.speaker === 'ai') { if (turn.speechesV2 && turn.speechesV2.length > 0) { turn.speechesV2.forEach(speech => { if (speech.content && speech.content.length > 0) { speech.content.forEach(block => { switch (block.type) { case 'text': // 处理引用格式 let msg = block.msg || ''; if (msg && msg.includes('(@ref)')) { msg = msg.replace(/\[(\d+)\]\(@ref\)/g, function (_, n) { return `[^${n}]`; }); } markdownContent += `${msg}\n\n`; break; case 'think': markdownContent += `<think>\n${block.content || ''}\n</think>\n\n`; break; case 'searchGuid': if (block.docs && block.docs.length > 0) { let localRefStart = refCount; block.docs.forEach((doc, docIndex) => { refs.push({ idx: refCount, title: doc.title || '无标题', url: doc.url || '#' }); refCount++; }); let text = block.msg || block.content || ''; let refIdx = localRefStart; let replaced = text.replace(/\[(\d+)\]\(@ref\)/g, function (_, n) { return `[^${refIdx++}]`; }); markdownContent += replaced + '\n\n'; } break; case 'image': case 'code': case 'pdf': markdownContent += `[${block.fileName || '未知文件'}](${block.url || '#'})\n\n`; break; default: } }); } }); } } }); markdownContent = markdownContent.trim(); // 追加引用链接 if (refs.length > 0) { markdownContent += '\n\n'; refs.forEach(ref => { markdownContent += `[^${ref.idx}]: [${ref.title}](${ref.url})\n`; }); } return markdownContent.trim(); } // --- 网络拦截,保存最新对话 JSON --- function processYuanbaoResponse(text, url) { // 拦截接口,保存最新对话 JSON 数据 if (!url || !url.includes('/api/user/agent/conversation/v1/detail')) { return; } try { if (text && text.includes('"convs":') && text.includes('"createTime":')) { state.latestDetailResponse = text; state.latestResponseSize = text.length; state.latestResponseUrl = url; state.lastUpdateTime = new Date().toLocaleTimeString(); } } catch (e) { } } // fetch 拦截 const originalFetch = window.fetch; window.fetch = async function (...args) { const url = args[0] instanceof Request ? args[0].url : args[0]; let response; try { response = await originalFetch.apply(this, args); if (typeof url === 'string' && url.includes('/api/user/agent/conversation/v1/detail')) { const clonedResponse = response.clone(); clonedResponse.text().then(text => { processYuanbaoResponse(text, url); }); } } catch (error) { throw error; } return response; }; // XMLHttpRequest 拦截 const originalXhrOpen = XMLHttpRequest.prototype.open; const originalXhrSend = XMLHttpRequest.prototype.send; const xhrUrlMap = new WeakMap(); XMLHttpRequest.prototype.open = function (method, url) { xhrUrlMap.set(this, url); if (typeof url === 'string' && url.includes('/api/user/agent/conversation/v1/detail')) { this.addEventListener('load', function () { if (this.readyState === 4 && this.status === 200) { const requestUrl = xhrUrlMap.get(this); try { processYuanbaoResponse(this.responseText, requestUrl); } catch (e) { } } }); } return originalXhrOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function () { return originalXhrSend.apply(this, arguments); }; // --- 注入每个对话泡的复制按钮 --- function injectCopyButtons() { // 遍历所有对话泡,注入“复制MD”按钮 document.querySelectorAll('.agent-chat__toolbar__copy').forEach(copyBtn => { if (copyBtn.parentElement.querySelector('.agent-chat__toolbar__copy-md')) return; const mdBtn = document.createElement('button'); mdBtn.className = 'agent-chat__toolbar__copy-md'; mdBtn.title = '复制Markdown(接口数据)'; mdBtn.textContent = '复制MD'; // 深灰背景,浅灰字体 mdBtn.style.marginLeft = '8px'; mdBtn.style.padding = '2px 8px'; mdBtn.style.border = 'none'; mdBtn.style.background = '#13172c'; mdBtn.style.color = '#efefef'; mdBtn.style.borderRadius = '8px'; mdBtn.style.cursor = 'pointer'; mdBtn.style.fontSize = '14px'; mdBtn.style.transition = 'background 0.2s'; mdBtn.onmouseover = () => { mdBtn.style.background = '#24293c'; }; mdBtn.onmouseout = () => { mdBtn.style.background = '#13172c'; }; mdBtn.onclick = function (e) { e.stopPropagation(); if (!state.latestDetailResponse) { alert('未捕获到对话数据,请刷新页面或重新进入对话。'); return; } let bubble = copyBtn.closest('.agent-chat__bubble'); if (!bubble) { alert('未找到对话泡'); return; } // 通过 DOM 顺序反向查找 JSON index const allBubbles = Array.from(document.querySelectorAll('.agent-chat__bubble')); const domIdx = allBubbles.indexOf(bubble); let jsonData; try { jsonData = JSON.parse(state.latestDetailResponse); } catch (e) { alert('JSON 解析失败'); return; } // JSON 顺序与 DOM 反向 const jsonConvs = jsonData && Array.isArray(jsonData.convs) ? jsonData.convs : []; const jsonIdx = jsonConvs.length - 1 - domIdx; let targetIndex = null; if (jsonConvs[jsonIdx]) { targetIndex = jsonConvs[jsonIdx].index; } if (targetIndex === null || targetIndex === undefined) { alert('无法匹配到正确的对话轮次'); return; } const md = convertYuanbaoJsonToMarkdown(jsonData, targetIndex); if (!md) return alert('未提取到Markdown内容'); navigator.clipboard.writeText(md).then(() => { mdBtn.title = '已复制!'; mdBtn.style.opacity = 0.5; setTimeout(() => { mdBtn.title = '复制Markdown(接口数据)'; mdBtn.style.opacity = 1; }, 1000); }); }; copyBtn.parentElement.insertBefore(mdBtn, copyBtn.nextSibling); }); } // --- MutationObserver 监听 agent-dialogue__tool 元素创建 --- function observeAgentDialogueTool() { // 监听 agent-dialogue__tool 元素的创建,注入导出按钮 const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && node.classList && node.classList.contains('agent-dialogue__tool')) { injectButtonToAgentDialogueTool(node); } if (node.nodeType === 1 && node.querySelectorAll) { node.querySelectorAll('.agent-dialogue__tool').forEach(el => { injectButtonToAgentDialogueTool(el); }); } }); } } }); observer.observe(document.body, { childList: true, subtree: true }); } // --- 注入导出按钮到 agent-dialogue__tool --- function injectButtonToAgentDialogueTool(el) { if (!el || el.dataset.mdInjected) return; el.innerHTML = ''; el.style.display = 'flex'; el.style.gap = '4px'; el.style.width = '150px'; el.style.alignItems = 'center'; el.style.height = '34px'; // SVG for ExportOutlined (Ant Design) const exportIconSvg = ` <svg viewBox="64 64 896 896" focusable="false" width="1em" height="1em" fill="currentColor" aria-hidden="true" style="margin-right:4px;"> <path d="M868 732h-70.3c-4.5 0-8.2 3.5-8.5 8-4.4 61.2-56.1 110-119.2 110H354c-65.2 0-118-53.2-118-118V354c0-63.1 48.8-114.8 110-119.2 4.5-0.3 8-4 8-8.5V156c0-4.4-3.6-8-8-8H156c-17.7 0-32 14.3-32 32v712c0 17.7 14.3 32 32 32h712c17.7 0 32-14.3 32-32V740c0-4.4-3.6-8-8-8z"></path> <path d="M534 352V136c0-4.4-3.6-8-8-8h-28c-4.4 0-8 3.6-8 8v216H296c-7.7 0-11.5 9.3-6.1 14.7l200 200c3.1 3.1 8.2 3.1 11.3 0l200-200c5.4-5.4 1.6-14.7-6.1-14.7H534z"></path> </svg> `; // 深灰背景,浅灰字体 const btnBg = '#13172c'; const btnBgHover = '#24293c'; const btnColor = '#efefef'; function createBtn(text, onclick) { const btn = document.createElement('button'); btn.type = 'button'; btn.innerHTML = exportIconSvg + text; btn.style.display = 'flex'; btn.style.alignItems = 'center'; btn.style.justifyContent = 'center'; btn.style.flex = '1 1 0'; btn.style.padding = '4px 0'; btn.style.margin = '4px'; btn.style.background = btnBg; btn.style.color = btnColor; btn.style.border = 'none'; btn.style.borderRadius = '8px'; btn.style.cursor = 'pointer'; btn.style.fontSize = '14px'; btn.style.gap = '4px'; btn.style.transition = 'background 0.2s'; btn.onmouseover = () => { btn.style.background = btnBgHover; }; btn.onmouseout = () => { btn.style.background = btnBg; }; btn.onclick = onclick; return btn; } // 导出全部对话,分割符 > # user 和 > # agent,顺序正序 function exportAllConversation() { if (!state.latestDetailResponse) { alert('未捕获到对话数据,请刷新页面或重新进入对话。'); return; } let jsonData; try { jsonData = JSON.parse(state.latestDetailResponse); } catch (e) { alert('JSON 解析失败'); return; } if (!jsonData || !Array.isArray(jsonData.convs)) { alert('无效的对话数据'); return; } let md = ''; // 正序导出 jsonData.convs.slice().reverse().forEach(turn => { if (turn.speaker === 'human') { md += '> # user\n'; let userTextMsg = ''; if (turn.speechesV2 && turn.speechesV2.length > 0 && turn.speechesV2[0].content) { const textBlock = turn.speechesV2[0].content.find(block => block.type === 'text'); if (textBlock && typeof textBlock.msg === 'string') { userTextMsg = textBlock.msg; } else if (typeof turn.displayPrompt === 'string') { userTextMsg = turn.displayPrompt; } } else if (typeof turn.displayPrompt === 'string') { userTextMsg = turn.displayPrompt; } md += (userTextMsg || '') + '\n\n'; } else if (turn.speaker === 'ai') { md += '> # agent\n'; if (turn.speechesV2 && turn.speechesV2.length > 0) { turn.speechesV2.forEach(speech => { if (speech.content && speech.content.length > 0) { speech.content.forEach(block => { if (block.type === 'text') { let msg = block.msg || ''; if (msg && msg.includes('(@ref)')) { msg = msg.replace(/\[(\d+)\]\(@ref\)/g, function (_, n) { return `[^${n}]`; }); } md += msg + '\n\n'; } else if (block.type === 'think') { md += `<think>\n${block.content || ''}\n</think>\n\n`; } else if (block.type === 'searchGuid') { let text = block.msg || block.content || ''; md += text + '\n\n'; } else if (block.type === 'image' || block.type === 'code' || block.type === 'pdf') { md += `[${block.fileName || '未知文件'}](${block.url || '#'})\n\n`; } }); } }); } } }); md = md.trim(); navigator.clipboard.writeText(md); } const btnAll = createBtn('全部', exportAllConversation); const btnDialogue = createBtn('对话', () => { injectCopyButtons(); }); el.appendChild(btnAll); el.appendChild(btnDialogue); el.dataset.mdInjected = '1'; } // --- 初始化 --- function init() { observeAgentDialogueTool(); } if (document.readyState === 'loading') { window.addEventListener('DOMContentLoaded', init); } else { init(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址