Greasy Fork is available in English.
导出MiniMax Agent对话内容为Markdown格式,包括对话、Task和Thinking
当前为
// ==UserScript==
// @name MiniMax Dialogue Exporter
// @namespace https://agent.minimaxi.com/
// @version 1.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 extractDialogueContent() {
const dialogueItems = [];
// 查找主对话容器 - 左侧对话区域
// 根据页面结构,对话内容在第一个主要的generic容器内
const mainContainer = document.querySelector('[ref="e164"]') ||
document.querySelector('[class*="chat"]') ||
findDialogueContainer();
if (!mainContainer) {
console.warn('未找到对话容器,尝试全局扫描');
return extractDialogueFromFullPage();
}
return extractDialogueFromContainer(mainContainer);
}
// 查找对话容器
function findDialogueContainer() {
// 查找包含对话内容的容器
// MiniMax页面结构:左侧是对话区,右侧是进程/文件
const allGenerics = document.querySelectorAll('[role="generic"]');
for (const el of allGenerics) {
// 查找包含"已思考"文本的容器作为对话区域的标识
if (el.textContent.includes('已思考') &&
el.textContent.length > 500) {
return el;
}
}
// 回退:查找最大的generic容器
let maxContainer = null;
let maxLength = 0;
allGenerics.forEach(el => {
if (el.textContent.length > maxLength) {
maxLength = el.textContent.length;
maxContainer = el;
}
});
return maxContainer;
}
// 从全页面提取对话
function extractDialogueFromFullPage() {
const items = [];
const processedTexts = new Set();
// 1. 提取用户消息 - 通常在包含特定样式的容器中
document.querySelectorAll('[role="generic"]').forEach(el => {
const text = el.textContent.trim();
if (text && !processedTexts.has(text)) {
// 检测用户消息特征
if (isUserMessage(el)) {
items.push({
type: 'user',
content: cleanText(text),
element: el
});
processedTexts.add(text);
}
}
});
// 2. 提取AI回复
document.querySelectorAll('paragraph, [role="paragraph"]').forEach(el => {
const text = el.textContent.trim();
if (text && !processedTexts.has(text) && text.length > 20) {
items.push({
type: 'assistant',
content: cleanText(text),
element: el
});
processedTexts.add(text);
}
});
// 3. 提取思考内容
document.querySelectorAll('[role="generic"]').forEach(el => {
const text = el.textContent;
if (text.includes('已思考') && text.includes('s')) {
const thinkingInfo = extractThinkingInfo(el);
if (thinkingInfo && !processedTexts.has(thinkingInfo.content)) {
items.push({
type: 'thinking',
duration: thinkingInfo.duration,
content: thinkingInfo.content,
element: el
});
processedTexts.add(thinkingInfo.content);
}
}
});
return items;
}
// 从容器提取对话
function extractDialogueFromContainer(container) {
const items = [];
const processedNodes = new WeakSet();
// 遍历容器的子元素
walkNode(container, (node) => {
if (processedNodes.has(node)) return;
// 1. 检测用户消息
if (isUserMessage(node)) {
const userText = extractUserMessageText(node);
if (userText) {
items.push({
type: 'user',
content: userText
});
processedNodes.add(node);
return;
}
}
// 2. 检测思考块
if (isThinkingBlock(node)) {
const thinking = extractThinkingInfo(node);
if (thinking) {
items.push({
type: 'thinking',
duration: thinking.duration,
content: thinking.content
});
processedNodes.add(node);
return;
}
}
// 3. 检测任务块
if (isTaskBlock(node)) {
const task = extractTaskInfo(node);
if (task) {
items.push({
type: 'task',
status: task.status,
action: task.action,
detail: task.detail
});
processedNodes.add(node);
return;
}
}
// 4. 检测AI回复段落
if (isAssistantParagraph(node)) {
const text = cleanText(node.textContent);
if (text && text.length > 10) {
items.push({
type: 'assistant',
content: text
});
processedNodes.add(node);
}
}
});
return items;
}
// 遍历节点
function walkNode(node, callback) {
callback(node);
node.childNodes.forEach(child => {
if (child.nodeType === Node.ELEMENT_NODE) {
walkNode(child, callback);
}
});
}
// 判断是否是用户消息
function isUserMessage(node) {
const text = node.textContent || '';
// 用户消息特征:
// 1. 包含用户输入框特征
// 2. 有复制按钮图标
// 3. 不包含"已思考"、"Completed"等AI特有标记
if (text.includes('已思考') ||
text.includes('Completed') ||
text.includes('Ongoing')) {
return false;
}
// 检查是否有编辑/复制按钮(用户消息特征)
const hasActionButton = node.querySelector('img[cursor="pointer"]');
const isShortMessage = text.length < 500 && text.length > 5;
// 检查父元素结构
const parent = node.parentElement;
if (parent && parent.querySelector('[role="img"]')) {
return isShortMessage;
}
return false;
}
// 提取用户消息文本
function extractUserMessageText(node) {
// 获取纯文本内容,排除子元素中的按钮等
let text = '';
node.childNodes.forEach(child => {
if (child.nodeType === Node.TEXT_NODE) {
text += child.textContent;
} else if (child.nodeType === Node.ELEMENT_NODE) {
const role = child.getAttribute('role');
if (role !== 'img' && !child.querySelector('img')) {
text += child.textContent;
}
}
});
return cleanText(text);
}
// 判断是否是思考块
function isThinkingBlock(node) {
const text = node.textContent || '';
return text.includes('已思考') && /\d+\.\d+s/.test(text);
}
// 提取思考信息
function extractThinkingInfo(node) {
const text = node.textContent || '';
const durationMatch = text.match(/(\d+\.\d+)s/);
const duration = durationMatch ? `${durationMatch[1]}s` : '';
// 获取思考内容(展开后的内容)
let content = '';
const paragraphs = node.querySelectorAll('paragraph, [role="paragraph"]');
paragraphs.forEach(p => {
const pText = p.textContent.trim();
if (pText && !pText.includes('已思考')) {
content += pText + '\n';
}
});
if (!content) {
// 尝试获取折叠内容的简要描述
content = text.replace(/已思考.*?s/, '').trim();
}
return content ? { duration, content: cleanText(content) } : null;
}
// 判断是否是任务块
function isTaskBlock(node) {
const text = node.textContent || '';
return text.includes('Completed') ||
text.includes('Ongoing') ||
text.includes('已完成') ||
text.includes('进行中');
}
// 提取任务信息
function extractTaskInfo(node) {
const text = node.textContent || '';
// 解析任务状态
let status = 'unknown';
if (text.includes('Completed') || text.includes('已完成')) {
status = 'completed';
} else if (text.includes('Ongoing') || text.includes('进行中')) {
status = 'ongoing';
}
// 解析任务动作
const actionPatterns = [
/Completed\s+(.+?)(?:\n|$)/,
/Ongoing\s+(.+?)(?:\n|$)/,
/已完成(.+?)(?:\n|$)/,
/进行中(.+?)(?:\n|$)/
];
let action = '';
for (const pattern of actionPatterns) {
const match = text.match(pattern);
if (match) {
action = match[1].trim();
break;
}
}
// 获取详细信息(如文件路径等)
let detail = '';
const paths = text.match(/\/[\w\-\/\.]+/g);
if (paths) {
detail = paths.join(', ');
}
return action ? { status, action, detail } : null;
}
// 判断是否是AI回复段落
function isAssistantParagraph(node) {
const tagName = node.tagName?.toLowerCase();
const role = node.getAttribute?.('role');
return tagName === 'paragraph' ||
role === 'paragraph' ||
(tagName === 'p' && node.closest('[role="generic"]'));
}
// 清理文本
function cleanText(text) {
return text
.replace(/\s+/g, ' ')
.replace(/^\s+|\s+$/g, '')
.trim();
}
// 智能提取对话内容(改进版)
function smartExtractDialogue() {
const items = [];
// 获取所有可能的对话元素
const allElements = document.body.querySelectorAll('*');
const textNodes = [];
// 收集所有有意义的文本节点
allElements.forEach(el => {
const text = el.textContent?.trim();
const directText = getDirectText(el);
if (directText && directText.length > 5) {
textNodes.push({
element: el,
text: directText,
rect: el.getBoundingClientRect()
});
}
});
// 按垂直位置排序
textNodes.sort((a, b) => a.rect.top - b.rect.top);
// 过滤和分类
const seen = new Set();
textNodes.forEach(({ element, text, rect }) => {
if (seen.has(text) || rect.width === 0) return;
// 只处理左侧区域(对话区)- 假设对话区在页面左半部分
const pageWidth = window.innerWidth;
if (rect.left > pageWidth * 0.6) return; // 排除右侧进程/文件区
// 分类
if (isThinkingText(text)) {
items.push({ type: 'thinking', content: text });
seen.add(text);
} else if (isTaskText(text)) {
items.push({ type: 'task', content: text });
seen.add(text);
} else if (isUserMessageByPosition(element, rect)) {
items.push({ type: 'user', content: text });
seen.add(text);
} else if (text.length > 30) {
items.push({ type: 'assistant', content: text });
seen.add(text);
}
});
return items;
}
// 获取元素的直接文本(排除子元素)
function getDirectText(element) {
let text = '';
element.childNodes.forEach(node => {
if (node.nodeType === Node.TEXT_NODE) {
text += node.textContent;
}
});
return text.trim();
}
// 判断是否是思考文本
function isThinkingText(text) {
return text.includes('已思考') && /\d+\.\d+s/.test(text);
}
// 判断是否是任务文本
function isTaskText(text) {
return /^(Completed|Ongoing|已完成|进行中)/.test(text);
}
// 根据位置判断是否是用户消息
function isUserMessageByPosition(element, rect) {
// 用户消息通常有特定的样式特征
const style = window.getComputedStyle(element);
const bgColor = style.backgroundColor;
// 检查是否有消息气泡样式
return rect.width < window.innerWidth * 0.5 &&
rect.height < 200;
}
// 转换为Markdown
function convertToMarkdown(title, items) {
let markdown = `# ${title}\n\n`;
markdown += `> 导出时间: ${new Date().toLocaleString('zh-CN')}\n\n`;
markdown += `---\n\n`;
let currentSection = '';
items.forEach((item, index) => {
switch (item.type) {
case 'user':
if (currentSection !== 'user') {
markdown += `## 👤 用户\n\n`;
currentSection = 'user';
}
markdown += `${item.content}\n\n`;
break;
case 'assistant':
if (currentSection !== 'assistant') {
markdown += `## 🤖 AI助手\n\n`;
currentSection = 'assistant';
}
markdown += `${item.content}\n\n`;
break;
case 'thinking':
markdown += `<details>\n`;
markdown += `<summary>💭 思考 ${item.duration || ''}</summary>\n\n`;
markdown += `${item.content || '(展开查看详情)'}\n\n`;
markdown += `</details>\n\n`;
currentSection = '';
break;
case 'task':
const statusIcon = item.status === 'completed' ? '✅' : '🔄';
markdown += `${statusIcon} **${item.action || item.content}**`;
if (item.detail) {
markdown += ` - \`${item.detail}\``;
}
markdown += `\n\n`;
currentSection = '';
break;
}
});
return markdown;
}
// 改进的提取方法
function extractDialogueAdvanced() {
const items = [];
// 获取对话容器 - 通常是页面左侧的主要区域
const dialogueArea = findMainDialogueArea();
if (!dialogueArea) {
console.warn('未找到对话区域');
return items;
}
// 遍历对话区域中的所有元素
const elements = dialogueArea.querySelectorAll('[role="generic"], paragraph, [role="paragraph"], [role="listitem"]');
elements.forEach(el => {
const text = el.textContent.trim();
if (!text || text.length < 3) return;
// 跳过重复内容
const existingContent = items.find(i => i.content === text);
if (existingContent) return;
// 识别元素类型
if (isThinkingElement(el)) {
const thinking = parseThinkingElement(el);
if (thinking) items.push(thinking);
} else if (isTaskElement(el)) {
const task = parseTaskElement(el);
if (task) items.push(task);
} else if (isUserMessageElement(el)) {
items.push({ type: 'user', content: text });
} else if (isAssistantElement(el)) {
items.push({ type: 'assistant', content: text });
}
});
return deduplicateAndSort(items);
}
// 查找主对话区域
function findMainDialogueArea() {
// 方法1: 查找包含对话特征的容器
const containers = document.querySelectorAll('[role="generic"]');
for (const container of containers) {
const hasThinking = container.textContent.includes('已思考');
const hasTask = container.textContent.includes('Completed') ||
container.textContent.includes('Ongoing');
const rect = container.getBoundingClientRect();
// 对话区通常在左侧,宽度较大
if (hasThinking && hasTask && rect.width > 300 && rect.left < window.innerWidth / 2) {
return container;
}
}
// 方法2: 查找ref属性
const refContainer = document.querySelector('[ref="e164"]');
if (refContainer) return refContainer;
// 方法3: 返回body
return document.body;
}
// 判断是否是思考元素
function isThinkingElement(el) {
const text = el.textContent;
return text.includes('已思考') && /\d+\.\d+s/.test(text);
}
// 解析思考元素
function parseThinkingElement(el) {
const text = el.textContent;
const durationMatch = text.match(/(\d+\.\d+)s/);
// 获取思考内容
let content = '';
const paragraphs = el.querySelectorAll('paragraph, [role="paragraph"]');
paragraphs.forEach(p => {
const pText = p.textContent.trim();
if (pText && !pText.includes('已思考') && pText.length > 10) {
content += pText + ' ';
}
});
return {
type: 'thinking',
duration: durationMatch ? `${durationMatch[1]}s` : '',
content: content.trim() || '(点击展开查看详情)'
};
}
// 判断是否是任务元素
function isTaskElement(el) {
const text = el.textContent.trim();
return /^(Completed|Ongoing|已完成|进行中)/.test(text) ||
text.includes('Completed') && el.querySelector('img');
}
// 解析任务元素
function parseTaskElement(el) {
const text = el.textContent.trim();
let status = 'unknown';
let action = text;
let detail = '';
if (text.includes('Completed') || text.includes('已完成')) {
status = 'completed';
action = text.replace(/^Completed\s*/, '').replace(/^已完成\s*/, '');
} else if (text.includes('Ongoing') || text.includes('进行中')) {
status = 'ongoing';
action = text.replace(/^Ongoing\s*/, '').replace(/^进行中\s*/, '');
}
// 提取文件路径
const pathMatch = text.match(/\/[\w\-\/\.]+/);
if (pathMatch) {
detail = pathMatch[0];
action = action.replace(pathMatch[0], '').trim();
}
return {
type: 'task',
status,
action: action.trim(),
detail
};
}
// 判断是否是用户消息元素
function isUserMessageElement(el) {
// 用户消息特征:
// 1. 相对较短
// 2. 有特定的容器结构
// 3. 后面跟着AI回复
const text = el.textContent.trim();
const rect = el.getBoundingClientRect();
// 排除系统元素
if (text.includes('已思考') ||
text.includes('Completed') ||
text.includes('Ongoing')) {
return false;
}
// 检查是否有用户输入的样式特征
const hasInputStyle = el.querySelector('img[cursor="pointer"]');
return text.length < 300 &&
text.length > 5 &&
rect.width > 100 &&
hasInputStyle;
}
// 判断是否是AI助手元素
function isAssistantElement(el) {
const tagName = el.tagName?.toLowerCase();
const role = el.getAttribute?.('role');
const text = el.textContent.trim();
// AI回复特征:
// 1. paragraph标签或role="paragraph"
// 2. 文本长度较长
// 3. 不是思考或任务元素
return (tagName === 'paragraph' || role === 'paragraph') &&
text.length > 20 &&
!text.includes('已思考') &&
!text.startsWith('Completed') &&
!text.startsWith('Ongoing');
}
// 去重和排序
function deduplicateAndSort(items) {
const seen = new Set();
return items.filter(item => {
const key = `${item.type}:${item.content || item.action}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
// 主导出函数
function exportDialogue() {
try {
const title = getDialogueTitle();
// 使用改进的提取方法
let items = extractDialogueAdvanced();
if (items.length === 0) {
// 回退到简单提取
items = simpleExtract();
}
if (items.length === 0) {
alert('未能提取到对话内容,请确保页面已完全加载。');
return;
}
const markdown = convertToMarkdown(title, items);
// 下载文件
downloadMarkdown(title, markdown);
// 同时复制到剪贴板
if (typeof GM_setClipboard !== 'undefined') {
GM_setClipboard(markdown, 'text');
}
console.log(`成功导出 ${items.length} 条对话内容`);
} catch (error) {
console.error('导出失败:', error);
alert('导出失败: ' + error.message);
}
}
// 简单提取方法(备用)
function simpleExtract() {
const items = [];
// 提取所有段落文本
document.querySelectorAll('paragraph, [role="paragraph"]').forEach(el => {
const text = el.textContent.trim();
if (text && text.length > 20) {
// 判断类型
if (text.includes('已思考')) {
items.push({ type: 'thinking', content: text, duration: '' });
} else if (text.includes('Completed') || text.includes('Ongoing')) {
items.push({ type: 'task', content: text, status: 'unknown', action: text, detail: '' });
} else {
items.push({ type: 'assistant', content: text });
}
}
});
return items;
}
// 下载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
});
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();
})();