Greasy Fork

来自缓存

Greasy Fork is available in English.

MiniMax Dialogue Exporter

导出MiniMax Agent对话内容为Markdown格式,包括对话、Task和Thinking

当前为 2025-11-27 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
})();