Greasy Fork is available in English.
导出MiniMax Agent对话内容为Markdown格式,包括对话、Task和Thinking
当前为
// ==UserScript==
// @name MiniMax Dialogue Exporter
// @namespace https://agent.minimaxi.com/
// @version 3.0.0
// @description 导出MiniMax Agent对话内容为Markdown格式,包括对话、Task和Thinking
// @author AIPD01
// @match https://agent.minimaxi.com/*
// @icon https://agent.minimaxi.com/favicon.ico
// @grant GM_download
// @grant GM_setClipboard
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 配置
const CONFIG = {
buttonId: 'minimax-export-btn',
buttonStyle: {
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: '9999',
padding: '12px 20px',
backgroundColor: '#4F46E5',
color: '#ffffff',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '14px',
fontWeight: '600',
boxShadow: '0 4px 12px rgba(79, 70, 229, 0.4)',
transition: 'all 0.3s ease'
}
};
// 创建导出按钮
function createExportButton() {
if (document.getElementById(CONFIG.buttonId)) return;
const button = document.createElement('button');
button.id = CONFIG.buttonId;
button.textContent = '📥 导出对话';
Object.assign(button.style, CONFIG.buttonStyle);
button.addEventListener('mouseenter', () => {
button.style.backgroundColor = '#4338CA';
button.style.transform = 'translateY(-2px)';
});
button.addEventListener('mouseleave', () => {
button.style.backgroundColor = '#4F46E5';
button.style.transform = 'translateY(0)';
});
button.addEventListener('click', exportDialogue);
document.body.appendChild(button);
}
// 获取对话标题
function getDialogueTitle() {
// 从页面标题获取,格式通常是 "标题 - MiniMax Agent"
const pageTitle = document.title;
const titleMatch = pageTitle.match(/^(.+?)\s*-\s*MiniMax Agent$/);
if (titleMatch) {
return titleMatch[1].trim();
}
// 备用:从URL的share ID生成
const urlMatch = window.location.pathname.match(/\/share\/(\d+)/);
if (urlMatch) {
return `MiniMax对话_${urlMatch[1]}`;
}
return `MiniMax对话_${new Date().toISOString().slice(0, 10)}`;
}
// 清理文本
function cleanText(text) {
return text
.replace(/\s+/g, ' ')
.trim();
}
// 主提取函数 - 基于实际DOM结构
function extractDialogueFromDOM() {
const items = [];
// 找到主对话容器
const messagesContainer = document.querySelector('.messages-container');
if (!messagesContainer) {
console.warn('未找到 .messages-container');
return items;
}
// 获取所有消息块 - 直接子元素中包含 .message 的
const allMessages = messagesContainer.querySelectorAll('.message.sent, .message.received');
if (allMessages.length === 0) {
console.warn('未找到消息元素');
return items;
}
const processedTexts = new Set(); // 用于去重
allMessages.forEach((messageEl, index) => {
const isSent = messageEl.classList.contains('sent');
const isReceived = messageEl.classList.contains('received');
if (isSent) {
// 用户消息
const userContent = extractUserMessageContent(messageEl);
if (userContent && !processedTexts.has(userContent)) {
items.push({
type: 'user',
content: userContent
});
processedTexts.add(userContent);
}
} else if (isReceived) {
// AI响应 - 可能包含多种内容
const receivedItems = extractReceivedContent(messageEl, processedTexts);
items.push(...receivedItems);
}
});
return items;
}
// 提取用户消息内容
function extractUserMessageContent(messageEl) {
// 用户消息结构: .message.sent > .message-content > .text-pretty
const textPretty = messageEl.querySelector('.text-pretty');
if (textPretty) {
return cleanText(textPretty.textContent || '');
}
// 备用: 直接获取 .message-content 的文本
const messageContent = messageEl.querySelector('.message-content');
if (messageContent) {
return cleanText(messageContent.textContent || '');
}
return '';
}
// 提取AI响应内容(received消息)
function extractReceivedContent(messageEl, processedTexts) {
const items = [];
// 1. 检查是否是思考块
const thinkContainer = messageEl.querySelector('.think-container');
if (thinkContainer) {
const thinkingItem = extractThinkingBlock(thinkContainer, processedTexts);
if (thinkingItem) {
items.push(thinkingItem);
}
// 思考块后面可能还有正文内容
const matrixMarkdown = messageEl.querySelector('.matrix-markdown');
if (matrixMarkdown) {
const textItems = extractMarkdownContent(matrixMarkdown, processedTexts, true);
items.push(...textItems);
}
return items;
}
// 2. 检查是否是工具调用
const toolName = messageEl.querySelector('.tool-name');
if (toolName) {
const toolItem = extractToolBlock(messageEl, processedTexts);
if (toolItem) {
items.push(toolItem);
}
return items;
}
// 3. 普通AI响应 - 提取 matrix-markdown 内容
const matrixMarkdown = messageEl.querySelector('.matrix-markdown');
if (matrixMarkdown) {
const textItems = extractMarkdownContent(matrixMarkdown, processedTexts, false);
items.push(...textItems);
}
return items;
}
// 提取思考块内容
function extractThinkingBlock(thinkContainer, processedTexts) {
// 获取思考时间
let duration = '';
const durationSpan = thinkContainer.querySelector('span');
if (durationSpan) {
const timeText = durationSpan.textContent;
const timeMatch = timeText.match(/(\d+\.?\d*)s/);
if (timeMatch) {
duration = `${timeMatch[1]}s`;
}
}
// 获取思考内容 - 在 .hidden 内的 .relative.pl-5 中
let content = '';
const hiddenDiv = thinkContainer.querySelector('.hidden');
if (hiddenDiv) {
const contentDiv = hiddenDiv.querySelector('.relative.pl-5, [class*="pl-5"]');
if (contentDiv) {
content = extractTextFromElement(contentDiv);
} else {
// 备用:直接获取hidden div的文本
content = extractTextFromElement(hiddenDiv);
}
}
const key = `thinking:${duration}:${content.slice(0, 50)}`;
if (processedTexts.has(key)) return null;
processedTexts.add(key);
return {
type: 'thinking',
duration: duration,
content: content || null
};
}
// 提取工具调用块
function extractToolBlock(messageEl, processedTexts) {
const toolNameEl = messageEl.querySelector('.tool-name');
if (!toolNameEl) return null;
const fullText = toolNameEl.textContent.trim();
// 判断状态
const isCompleted = fullText.includes('已完成') || fullText.includes('Completed');
const isOngoing = fullText.includes('正在进行') || fullText.includes('Ongoing');
if (!isCompleted && !isOngoing) return null;
// 提取动作名称 - 在 span 中
let action = '';
const actionSpans = toolNameEl.querySelectorAll('span');
actionSpans.forEach(span => {
const spanText = span.textContent.trim();
if (spanText && !spanText.match(/^\d/) && spanText.length > 2) {
if (spanText.includes('已完成') || spanText.includes('正在进行')) {
action = spanText;
}
}
});
if (!action) {
// 从全文提取
action = fullText
.replace(/已完成|正在进行|Completed|Ongoing/g, '')
.trim()
.split('\n')[0]
.trim();
}
// 提取详细信息(如文件路径)
let detail = '';
const detailDiv = toolNameEl.querySelector('[class*="text-col_text01"]');
if (detailDiv) {
detail = detailDiv.textContent.trim();
} else {
// 从全文提取路径
const pathMatch = fullText.match(/(\/[\w\-\/\.]+)/);
if (pathMatch) {
detail = pathMatch[1];
}
}
// 清理action
action = action.replace(detail, '').trim();
if (!action || action.length < 2) {
action = fullText.split('\n')[0].replace(/已完成|正在进行/g, '').trim();
}
const key = `task:${action}:${detail}`;
if (processedTexts.has(key)) return null;
processedTexts.add(key);
return {
type: 'task',
status: isCompleted ? 'completed' : 'ongoing',
action: action,
detail: detail
};
}
// 提取Markdown内容
function extractMarkdownContent(matrixMarkdown, processedTexts, skipThinking) {
const items = [];
// 跳过思考容器内的重复内容
if (skipThinking) {
const thinkContainer = matrixMarkdown.querySelector('.think-container');
if (thinkContainer) {
thinkContainer.remove(); // 临时移除以避免重复
}
}
// 提取纯文本内容
const text = extractTextFromElement(matrixMarkdown);
if (text && text.length > 5) {
const key = `assistant:${text.slice(0, 100)}`;
if (!processedTexts.has(key)) {
items.push({
type: 'assistant',
content: text
});
processedTexts.add(key);
}
}
return items;
}
// 从元素提取文本(处理嵌套结构)
function extractTextFromElement(element) {
if (!element) return '';
let text = '';
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
{
acceptNode: function(node) {
// 跳过SVG内的文本
if (node.parentElement?.closest('svg')) {
return NodeFilter.FILTER_REJECT;
}
// 跳过空文本
if (!node.textContent.trim()) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
}
}
);
let node;
while (node = walker.nextNode()) {
const nodeText = node.textContent.trim();
// 跳过仅包含时间格式的节点
if (nodeText && !nodeText.match(/^\d+\.?\d*s$/)) {
text += nodeText + ' ';
}
}
return cleanText(text);
}
// 去重
function deduplicateItems(items) {
const seen = new Set();
return items.filter(item => {
const key = item.type + ':' + (item.content?.slice(0, 50) || item.action || '');
if (seen.has(key)) return false;
if (key.length < 5) return false;
seen.add(key);
return true;
});
}
// 转换为Markdown
function convertToMarkdown(title, items) {
let markdown = `# ${title}\n\n`;
markdown += `> 导出时间: ${new Date().toLocaleString('zh-CN')}\n`;
markdown += `> 来源: ${window.location.href}\n\n`;
markdown += `---\n\n`;
let currentRole = '';
let assistantContentBuffer = [];
const flushAssistantBuffer = () => {
if (assistantContentBuffer.length > 0) {
markdown += assistantContentBuffer.join('\n\n') + '\n\n';
assistantContentBuffer = [];
}
};
items.forEach((item, index) => {
switch (item.type) {
case 'user':
flushAssistantBuffer();
markdown += `## 👤 用户\n\n`;
markdown += `${item.content}\n\n`;
currentRole = 'user';
break;
case 'assistant':
if (currentRole !== 'assistant') {
flushAssistantBuffer();
markdown += `## 🤖 AI助手\n\n`;
currentRole = 'assistant';
}
assistantContentBuffer.push(item.content);
break;
case 'thinking':
flushAssistantBuffer();
markdown += `<details>\n`;
markdown += `<summary>💭 思考过程 ${item.duration || ''}</summary>\n\n`;
if (item.content) {
markdown += `${item.content}\n\n`;
} else {
markdown += `*(思考内容未展开)*\n\n`;
}
markdown += `</details>\n\n`;
currentRole = '';
break;
case 'task':
flushAssistantBuffer();
const statusIcon = item.status === 'completed' ? '✅' : '🔄';
markdown += `${statusIcon} **${item.action}**`;
if (item.detail) {
markdown += ` \`${item.detail}\``;
}
markdown += `\n\n`;
currentRole = '';
break;
}
});
flushAssistantBuffer();
return markdown;
}
// 备用提取方法 - 基于类名扫描
function extractDialogueFromClasses() {
const items = [];
const processedTexts = new Set();
// 1. 提取所有 .text-pretty 作为可能的用户消息
document.querySelectorAll('.message.sent .text-pretty').forEach(el => {
const text = cleanText(el.textContent || '');
if (text && text.length > 2 && !processedTexts.has(text)) {
items.push({ type: 'user', content: text });
processedTexts.add(text);
}
});
// 2. 提取思考块
document.querySelectorAll('.think-container').forEach(el => {
const item = extractThinkingBlock(el, processedTexts);
if (item) items.push(item);
});
// 3. 提取工具调用
document.querySelectorAll('.tool-name').forEach(el => {
const messageEl = el.closest('.message');
if (messageEl) {
const item = extractToolBlock(messageEl, processedTexts);
if (item) items.push(item);
}
});
// 4. 提取AI响应文本
document.querySelectorAll('.message.received .matrix-markdown').forEach(el => {
// 跳过思考块内的
if (el.closest('.think-container')) return;
const text = extractTextFromElement(el);
if (text && text.length > 10 && !processedTexts.has(text.slice(0, 100))) {
items.push({ type: 'assistant', content: text });
processedTexts.add(text.slice(0, 100));
}
});
return items;
}
// 主导出函数
function exportDialogue() {
try {
const title = getDialogueTitle();
// 首先尝试DOM结构提取
let items = extractDialogueFromDOM();
// 如果结果太少,使用备用方法
if (items.length < 3) {
console.log('DOM提取结果较少,尝试备用方法...');
items = extractDialogueFromClasses();
}
// 去重
items = deduplicateItems(items);
if (items.length === 0) {
alert('未能提取到对话内容。\n\n⚠️ 提示:\n1. 此页面是演示动画,请等待动画播放完成后再导出\n2. 确保页面已完全加载\n3. 如果仍无法导出,请尝试刷新页面');
return;
}
const markdown = convertToMarkdown(title, items);
// 下载文件
downloadMarkdown(title, markdown);
// 同时复制到剪贴板
if (typeof GM_setClipboard !== 'undefined') {
GM_setClipboard(markdown, 'text');
} else {
navigator.clipboard.writeText(markdown).catch(e => {
console.warn('复制到剪贴板失败:', e);
});
}
console.log(`✅ 成功导出 ${items.length} 条对话内容`);
// 显示简单的成功提示
showToast(`成功导出 ${items.length} 条内容`);
} catch (error) {
console.error('导出失败:', error);
alert('导出失败: ' + error.message);
}
}
// 显示提示
function showToast(message) {
const toast = document.createElement('div');
toast.textContent = message;
toast.style.cssText = `
position: fixed;
bottom: 80px;
right: 20px;
background: #10B981;
color: white;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
z-index: 10000;
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
animation: fadeInOut 2s ease forwards;
`;
// 添加动画
const style = document.createElement('style');
style.textContent = `
@keyframes fadeInOut {
0% { opacity: 0; transform: translateY(10px); }
15% { opacity: 1; transform: translateY(0); }
85% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-10px); }
}
`;
document.head.appendChild(style);
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
style.remove();
}, 2000);
}
// 下载Markdown文件
function downloadMarkdown(title, content) {
const filename = sanitizeFilename(title) + '.md';
// 方法1: 使用GM_download
if (typeof GM_download !== 'undefined') {
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
GM_download({
url: url,
name: filename,
saveAs: true
});
setTimeout(() => URL.revokeObjectURL(url), 1000);
return;
}
// 方法2: 使用原生下载
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// 清理文件名
function sanitizeFilename(name) {
return name
.replace(/[<>:"/\\|?*]/g, '_')
.replace(/\s+/g, '_')
.slice(0, 100);
}
// 初始化
function init() {
// 等待页面加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createExportButton);
} else {
createExportButton();
}
// 监听URL变化(SPA应用)
let lastUrl = location.href;
new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(createExportButton, 1000);
}
}).observe(document.body, { subtree: true, childList: true });
}
init();
})();