Enhanced Grok Export v2.4

Export Grok conversations with improved detection, share to X integration and more

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Enhanced Grok Export v2.4
// @description  Export Grok conversations with improved detection, share to X integration and more
// @version      2.4.0
// @author       iikoshteruu
// @grant        none
// @match        *://grok.com/*
// @match        *://x.com/*
// @license      MIT
// @namespace    https://github.com/iikoshteruu/enhanced-grok-export
// @homepageURL  https://github.com/iikoshteruu/enhanced-grok-export
// @supportURL   https://github.com/iikoshteruu/enhanced-grok-export/issues
// ==/UserScript==

(function() {
    'use strict';

    console.log('Enhanced Grok Export v2.2 starting...');

    // Configuration
    const CONFIG = {
        buttonText: 'Export Full',
        formats: ['txt', 'md', 'json', 'pdf'],
        defaultFormat: 'md',
        debug: true,
        autoScroll: true,
        scrollDelay: 1000,
        maxScrollAttempts: 50,
        shareToX: {
            enabled: true,
            maxLength: 280,
            hashtagSuggestions: ['#Grok', '#AI', '#XAI']
        }
    };

    let isExporting = false;

    function debugLog(message, data = null) {
        if (CONFIG.debug) {
            console.log('[Grok Export v2.2]', message, data || '');
        }
    }

    // Auto-scroll to load all conversation content
    async function loadFullConversation() {
        debugLog('Starting full conversation loading...');

        return new Promise((resolve) => {
            let scrollAttempts = 0;
            let lastScrollHeight = 0;
            let unchangedCount = 0;

            const scrollInterval = setInterval(() => {
                window.scrollTo(0, 0);

                const currentScrollHeight = document.body.scrollHeight;
                debugLog(`Scroll attempt ${scrollAttempts + 1}, Height: ${currentScrollHeight}`);

                if (currentScrollHeight === lastScrollHeight) {
                    unchangedCount++;
                } else {
                    unchangedCount = 0;
                    lastScrollHeight = currentScrollHeight;
                }

                scrollAttempts++;

                if (scrollAttempts >= CONFIG.maxScrollAttempts || unchangedCount >= 3) {
                    clearInterval(scrollInterval);
                    debugLog(`Scroll complete. Total attempts: ${scrollAttempts}`);

                    setTimeout(() => {
                        window.scrollTo(0, 0);
                        setTimeout(() => {
                            window.scrollTo(0, document.body.scrollHeight);
                            setTimeout(() => {
                                resolve();
                            }, 1000);
                        }, 500);
                    }, 500);
                }
            }, CONFIG.scrollDelay);
        });
    }

    // Enhanced conversation detection for Grok
    function getConversationData() {
        debugLog('Starting Grok conversation data extraction...');
        const messages = [];

        const strategies = [
            () => {
                const messageContainers = document.querySelectorAll('div[class*="css-146c3p1"]');
                debugLog(`Found ${messageContainers.length} containers with css-146c3p1`);
                return Array.from(messageContainers);
            },
            () => {
                const contentSpans = document.querySelectorAll('span[class*="css-1jxf684"]');
                debugLog(`Found ${contentSpans.length} content spans with css-1jxf684`);
                return Array.from(contentSpans).map(span => {
                    let parent = span.parentElement;
                    while (parent && !parent.classList.toString().includes('css-146c3p1')) {
                        parent = parent.parentElement;
                    }
                    return parent;
                }).filter(Boolean);
            },
            () => {
                const ltrDivs = document.querySelectorAll('div[dir="ltr"]');
                debugLog(`Found ${ltrDivs.length} divs with dir='ltr'`);
                return Array.from(ltrDivs).filter(div => {
                    const text = div.textContent?.trim() || '';
                    return text.length > 10 && text.length < 50000;
                });
            }
        ];

        let messageElements = [];

        for (let i = 0; i < strategies.length; i++) {
            try {
                messageElements = strategies[i]();
                debugLog(`Strategy ${i + 1} found ${messageElements.length} elements`);

                if (messageElements.length > 0) {
                    messageElements = messageElements.filter(el => {
                        const text = el.textContent?.trim() || '';
                        return text.length > 10 && text.length < 50000;
                    });

                    if (messageElements.length > 0) {
                        debugLog(`Using strategy ${i + 1} with ${messageElements.length} valid elements`);
                        break;
                    }
                }
            } catch (error) {
                debugLog(`Strategy ${i + 1} failed:`, error.message);
            }
        }

        debugLog(`Processing ${messageElements.length} message elements...`);

        const processedTexts = new Set();

        messageElements.forEach((element, index) => {
            try {
                const clone = element.cloneNode(true);

                const unwanted = clone.querySelectorAll(
                    'svg, button, input, select, nav, header, footer, script, style, ' +
                    '[aria-hidden="true"], [class*="icon"], [class*="button"]'
                );
                unwanted.forEach(el => el.remove());

                const text = clone.textContent?.trim() || '';

                if (text && text.length > 10 && !processedTexts.has(text)) {
                    processedTexts.add(text);

                    const speakerInfo = detectGrokSpeakerAdvanced(element, text, index, messageElements);

                    messages.push({
                        id: `msg_${index}`,
                        speaker: speakerInfo.speaker,
                        content: text,
                        mode: speakerInfo.mode,
                        timestamp: new Date().toISOString(),
                        index: index,
                        length: text.length,
                        element: element,
                        debugInfo: speakerInfo.debugInfo
                    });

                    debugLog(`Message ${index + 1}: ${speakerInfo.speaker} [${speakerInfo.mode}] (${text.length} chars)`, speakerInfo.debugInfo);
                }
            } catch (error) {
                debugLog(`Error processing element ${index}:`, error.message);
            }
        });

        messages.sort((a, b) => a.index - b.index);

        debugLog(`Extracted ${messages.length} unique messages`);
        return messages;
    }

    // REVISED: Speaker detection using position and content analysis
    function detectGrokSpeakerAdvanced(element, text, index, allElements) {
        let debugInfo = { scores: {}, reasoning: [] };

        // Mode detection for Grok
        let mode = 'standard';
        if (text.includes('🤔') || text.includes('Let me think') || text.includes('Step ') ||
            text.includes('First,') || text.includes('Then,') || text.includes('Finally,')) {
            mode = 'think';
        } else if (text.includes('😄') || text.includes('😂') || text.includes('LOL') ||
                   text.includes('haha') || text.includes('funny')) {
            mode = 'fun';
        } else if (text.includes('According to') || text.includes('Based on recent') ||
                   text.includes('Source:') || text.includes('https://')) {
            mode = 'deepsearch';
        }

        let grokScore = 0;
        let humanScore = 0;

        // 1. MESSAGE LENGTH ANALYSIS (Most reliable indicator)
        if (text.length > 400) {
            grokScore += 4;
            debugInfo.reasoning.push(`Very long message (${text.length} chars, likely GROK)`);
        } else if (text.length > 200) {
            grokScore += 2;
            debugInfo.reasoning.push(`Long message (${text.length} chars, likely GROK)`);
        } else if (text.length < 50) {
            humanScore += 2;
            debugInfo.reasoning.push(`Short message (${text.length} chars, likely HUMAN)`);
        }

        // 2. ENHANCED CONTENT PATTERN ANALYSIS
        const grokIndicators = [
            { pattern: /^(I'll|I can|I'd be happy|Here's|Let me|I understand|Certainly|Absolutely|Looking at)/i, score: 3, name: 'Grok response starter' },
            { pattern: /^(Yo, I'm right here|Hey there|What's up|Oof|Thanks for sharing)/i, score: 4, name: 'Grok casual phrases' },
            { pattern: /^(From your|Based on your|Looking at your|The error|This means|Why It's Happening)/i, score: 4, name: 'Grok analysis starters' },
            { pattern: /```/, score: 3, name: 'Code block' },
            { pattern: /(docker|container|build|error|issue|problem|fix|solution)/i, score: 2, name: 'Technical terms' },
            { pattern: /^(Based on|According to|The analysis|This approach|In summary|Overview)/i, score: 2, name: 'Analytical language' },
            { pattern: /(implementation|algorithm|analysis|explanation|methodology|digital realm)/i, score: 1, name: 'Technical/AI terms' },
            { pattern: /\n\n/, score: 1, name: 'Structured paragraphs' },
            { pattern: /(fully alive|kicking in the digital realm|locked in|squash|tackle this)/i, score: 4, name: 'Grok personality phrases' },
            { pattern: /^(Let's|Why|Steps to|Here's how)/i, score: 3, name: 'Instructional language' },
            { pattern: /(requirements\.txt|Dockerfile|app\.py|netstat|TrueNAS)/i, score: 2, name: 'Project-specific terms' }
        ];

        const humanIndicators = [
            { pattern: /^(hi|hello|hey|can you|could you|please|help|i need|i want)/i, score: 3, name: 'Human greeting/request' },
            { pattern: /^(grok|are you|do you remember)/i, score: 5, name: 'Addressing Grok directly' },
            { pattern: /\?$/, score: 3, name: 'Ends with question' },
            { pattern: /^(ok|okay|thanks|thank you|great|perfect|yes|no|good|nice)/i, score: 2, name: 'Acknowledgment' },
            { pattern: /^(let's|lets|now|next|alright|ready|this site can't be reached)/i, score: 2, name: 'Directive/status language' },
            { pattern: /\b(you|your)\b/i, score: 1, name: 'Addressing someone' },
            { pattern: /^(root@truenas|trying|nano)/i, score: 4, name: 'User commands/actions' }
        ];

        grokIndicators.forEach(({ pattern, score, name }) => {
            if (pattern.test(text)) {
                grokScore += score;
                debugInfo.reasoning.push(`${name} (+${score} GROK)`);
            }
        });

        humanIndicators.forEach(({ pattern, score, name }) => {
            if (pattern.test(text)) {
                humanScore += score;
                debugInfo.reasoning.push(`${name} (+${score} HUMAN)`);
            }
        });

        // 3. CONVERSATIONAL CONTEXT ANALYSIS
        if (index > 0 && allElements[index - 1]) {
            const prevText = allElements[index - 1].textContent?.trim() || '';

            // If previous message was a question and this is a long answer
            if (prevText.includes('?') && prevText.length < 200 && text.length > 150) {
                grokScore += 3;
                debugInfo.reasoning.push('Long response to question (likely GROK)');
            }

            // If this is a short follow-up to a long message
            if (prevText.length > 300 && text.length < 100) {
                humanScore += 2;
                debugInfo.reasoning.push('Short follow-up to long message (likely HUMAN)');
            }
        }

        // 4. QUESTION vs STATEMENT ANALYSIS
        const questionCount = (text.match(/\?/g) || []).length;
        if (questionCount > 0 && text.length < 150) {
            humanScore += questionCount * 2;
            debugInfo.reasoning.push(`Contains ${questionCount} questions (likely HUMAN)`);
        }

        // 5. CONVERSATION POSITION ANALYSIS
        const messagesSoFar = index + 1;

        // First message is typically human
        if (index === 0) {
            humanScore += 2;
            debugInfo.reasoning.push('First message (likely HUMAN)');
        }

        // Look at nearby message lengths for pattern
        const nearbyLengths = [];
        for (let i = Math.max(0, index - 2); i <= Math.min(allElements.length - 1, index + 2); i++) {
            if (i !== index && allElements[i]) {
                nearbyLengths.push(allElements[i].textContent?.length || 0);
            }
        }

        const avgNearbyLength = nearbyLengths.length > 0 ?
            nearbyLengths.reduce((sum, len) => sum + len, 0) / nearbyLengths.length : 0;

        if (text.length > avgNearbyLength * 2 && text.length > 200) {
            grokScore += 2;
            debugInfo.reasoning.push('Much longer than nearby messages (likely GROK)');
        }

        // 6. MODE BONUS
        if (mode !== 'standard') {
            grokScore += 3;
            debugInfo.reasoning.push(`${mode} mode detected (likely GROK)`);
        }

        // Store scores for debugging
        debugInfo.scores = { grokScore, humanScore };

        // FINAL DECISION with balanced thresholds
        let speaker;
        if (grokScore >= humanScore + 2) {  // Require clear Grok advantage
            speaker = 'Grok';
        } else if (humanScore >= grokScore + 1) {  // Easier for human detection
            speaker = 'Human';
        } else {
            // BALANCED FALLBACK based on conversation patterns

            // Strong human indicators
            if (text.startsWith('root@') || text.includes('nano ') || text.includes('ls -l') ||
                text.includes('docker run') || text.includes('docker build') || text.length < 25) {
                speaker = 'Human';
                debugInfo.reasoning.push('Fallback: Clear user command/input assumed HUMAN');
            }
            // Clear questions
            else if (text.includes('?') && text.length < 100) {
                speaker = 'Human';
                debugInfo.reasoning.push('Fallback: Short question assumed HUMAN');
            }
            // Long technical explanations
            else if (text.length > 300 && (text.includes('Fix:') || text.includes('Issue:') || text.includes('Solution:'))) {
                speaker = 'Grok';
                debugInfo.reasoning.push('Fallback: Long technical explanation assumed GROK');
            }
            // Medium length with technical terms
            else if (text.length > 150 && (text.includes('docker') || text.includes('container') || text.includes('error'))) {
                speaker = 'Grok';
                debugInfo.reasoning.push('Fallback: Medium technical content assumed GROK');
            }
            // Short technical references or status updates
            else if (text.length < 100 && !text.includes('Here\'s') && !text.includes('Let\'s')) {
                speaker = 'Human';
                debugInfo.reasoning.push('Fallback: Short non-explanatory content assumed HUMAN');
            }
            // Final alternating fallback
            else {
                speaker = index % 2 === 0 ? 'Human' : 'Grok';
                debugInfo.reasoning.push(`Final fallback: Alternating pattern (${speaker})`);
            }
        }

        debugInfo.finalDecision = `${speaker} (GROK: ${grokScore}, HUMAN: ${humanScore})`;

        return { speaker, mode, debugInfo };
    }

    // FIXED PDF GENERATION - No external dependencies
    function formatAsPDF(messages) {
        try {
            debugLog('Generating PDF document...');

            // Create a rich text document formatted like a PDF
            let content = '';

            // PDF-style header
            content += '═'.repeat(80) + '\n';
            content += '                    GROK CONVERSATION EXPORT\n';
            content += '═'.repeat(80) + '\n\n';

            // Document metadata
            content += `EXPORT INFORMATION:\n`;
            content += `${'─'.repeat(40)}\n`;
            content += `Generated: ${new Date().toLocaleString()}\n`;
            content += `Total Messages: ${messages.length}\n`;
            content += `Source URL: ${window.location.href}\n`;
            content += `Export Version: Enhanced Grok Export v2.2\n\n`;

            // Statistics section
            const stats = {
                humanMessages: messages.filter(m => m.speaker === 'Human').length,
                grokMessages: messages.filter(m => m.speaker === 'Grok').length,
                thinkMode: messages.filter(m => m.mode === 'think').length,
                funMode: messages.filter(m => m.mode === 'fun').length,
                deepSearchMode: messages.filter(m => m.mode === 'deepsearch').length,
                totalChars: messages.reduce((sum, m) => sum + m.content.length, 0),
                avgLength: Math.round(messages.reduce((sum, m) => sum + m.content.length, 0) / messages.length)
            };

            content += `CONVERSATION STATISTICS:\n`;
            content += `${'─'.repeat(40)}\n`;
            content += `┌─────────────────────────┬─────────┐\n`;
            content += `│ Human Messages          │ ${stats.humanMessages.toString().padStart(7)} │\n`;
            content += `│ Grok Messages           │ ${stats.grokMessages.toString().padStart(7)} │\n`;
            content += `├─────────────────────────┼─────────┤\n`;
            content += `│ Think Mode              │ ${stats.thinkMode.toString().padStart(7)} │\n`;
            content += `│ Fun Mode                │ ${stats.funMode.toString().padStart(7)} │\n`;
            content += `│ DeepSearch Mode         │ ${stats.deepSearchMode.toString().padStart(7)} │\n`;
            content += `├─────────────────────────┼─────────┤\n`;
            content += `│ Total Characters        │ ${stats.totalChars.toString().padStart(7)} │\n`;
            content += `│ Average Message Length  │ ${stats.avgLength.toString().padStart(7)} │\n`;
            content += `└─────────────────────────┴─────────┘\n\n`;

            content += '═'.repeat(80) + '\n';
            content += '                         CONVERSATION CONTENT\n';
            content += '═'.repeat(80) + '\n\n';

            // Message content
            messages.forEach((msg, index) => {
                const modeIndicator = msg.mode !== 'standard' ? ` [${msg.mode.toUpperCase()}]` : '';

                // Message header
                content += `${index + 1}. ${msg.speaker}${modeIndicator}\n`;
                content += `${'─'.repeat(Math.max(20, msg.speaker.length + modeIndicator.length + 4))}\n`;

                // Message content with proper wrapping
                const wrappedContent = wrapText(msg.content, 76);
                content += wrappedContent + '\n\n';

                // Visual separator between messages
                if (index < messages.length - 1) {
                    content += '▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪\n\n';
                }
            });

            // Document footer
            content += '\n' + '═'.repeat(80) + '\n';
            content += `                    End of Document - ${messages.length} Messages\n`;
            content += '═'.repeat(80) + '\n';

            return new Blob([content], { type: 'text/plain;charset=utf-8' });

        } catch (error) {
            debugLog('PDF generation failed:', error);
            throw new Error(`PDF generation failed: ${error.message}`);
        }
    }

    // Helper function for text wrapping
    function wrapText(text, maxWidth) {
        const words = text.split(' ');
        const lines = [];
        let currentLine = '';

        words.forEach(word => {
            if ((currentLine + word).length <= maxWidth) {
                currentLine += (currentLine ? ' ' : '') + word;
            } else {
                if (currentLine) lines.push(currentLine);
                currentLine = word;
            }
        });

        if (currentLine) lines.push(currentLine);
        return lines.join('\n');
    }

    // Create Share to X modal
    function createShareModal(messages) {
        const modal = document.createElement('div');
        modal.id = 'grok-share-modal';
        modal.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.5);
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        `;

        const content = document.createElement('div');
        content.style.cssText = `
            background: white;
            border-radius: 16px;
            padding: 24px;
            max-width: 500px;
            width: 90%;
            max-height: 80vh;
            overflow-y: auto;
            box-shadow: 0 20px 40px rgba(0,0,0,0.3);
        `;

        content.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
                <h3 style="margin: 0; color: #1DA1F2;">🐦 Share to X</h3>
                <button id="close-share-modal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #666;">×</button>
            </div>

            <div style="margin-bottom: 16px;">
                <label style="display: block; margin-bottom: 8px; font-weight: 600;">Select Messages to Share:</label>
                <div id="message-selector" style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; border-radius: 8px; padding: 12px;">
                    ${messages.map((msg, index) => `
                        <div style="margin-bottom: 12px; padding: 8px; border-radius: 6px; background: ${msg.speaker === 'Human' ? '#f0f8ff' : '#f8f9fa'};">
                            <label style="display: flex; align-items: flex-start; cursor: pointer;">
                                <input type="checkbox" data-msg-id="${msg.id}" style="margin-right: 8px; margin-top: 4px;">
                                <div>
                                    <strong>${msg.speaker}${msg.mode !== 'standard' ? ` [${msg.mode.toUpperCase()}]` : ''}:</strong>
                                    <div style="margin-top: 4px; font-size: 14px; color: #333;">${msg.content.substring(0, 100)}${msg.content.length > 100 ? '...' : ''}</div>
                                </div>
                            </label>
                        </div>
                    `).join('')}
                </div>
            </div>

            <div style="margin-bottom: 16px;">
                <label style="display: block; margin-bottom: 8px; font-weight: 600;">Your Commentary (optional):</label>
                <textarea id="share-commentary" placeholder="Add your thoughts about this Grok conversation..." style="width: 100%; height: 80px; border: 1px solid #ddd; border-radius: 8px; padding: 12px; resize: vertical; font-family: inherit;"></textarea>
            </div>

            <div style="margin-bottom: 16px;">
                <label style="display: block; margin-bottom: 8px; font-weight: 600;">Preview:</label>
                <div id="share-preview" style="border: 1px solid #ddd; border-radius: 8px; padding: 12px; background: #f8f9fa; min-height: 60px; font-size: 14px; color: #666;">
                    Select messages to see preview...
                </div>
                <div id="character-count" style="text-align: right; font-size: 12px; color: #666; margin-top: 4px;">0 / 280</div>
            </div>

            <div style="display: flex; gap: 12px; justify-content: flex-end;">
                <button id="cancel-share" style="padding: 10px 20px; border: 1px solid #ddd; background: white; border-radius: 8px; cursor: pointer;">Cancel</button>
                <button id="confirm-share" style="padding: 10px 20px; border: none; background: #1DA1F2; color: white; border-radius: 8px; cursor: pointer; font-weight: 600;" disabled>Share to X</button>
            </div>
        `;

        modal.appendChild(content);
        document.body.appendChild(modal);

        // Event handlers
        function updatePreview() {
            const selected = document.querySelectorAll('#message-selector input[type="checkbox"]:checked');
            const commentary = document.getElementById('share-commentary').value;

            let preview = '';
            if (commentary.trim()) {
                preview += commentary.trim() + '\n\n';
            }

            const selectedMessages = Array.from(selected).map(input => {
                const msgId = input.dataset.msgId;
                return messages.find(m => m.id === msgId);
            }).filter(Boolean);

            if (selectedMessages.length > 0) {
                preview += selectedMessages.map(msg => {
                    const modeIndicator = msg.mode !== 'standard' ? ` [${msg.mode.toUpperCase()}]` : '';
                    return `${msg.speaker}${modeIndicator}: ${msg.content}`;
                }).join('\n\n');
            }

            if (preview) {
                preview += '\n\n' + CONFIG.shareToX.hashtagSuggestions.join(' ');
            }

            if (preview.length > CONFIG.shareToX.maxLength) {
                preview = preview.substring(0, CONFIG.shareToX.maxLength - 3) + '...';
            }

            document.getElementById('share-preview').textContent = preview || 'Select messages to see preview...';
            document.getElementById('character-count').textContent = `${preview.length} / ${CONFIG.shareToX.maxLength}`;

            const shareButton = document.getElementById('confirm-share');
            shareButton.disabled = !preview || preview.length > CONFIG.shareToX.maxLength;
            shareButton.style.opacity = shareButton.disabled ? '0.5' : '1';
        }

        document.getElementById('close-share-modal').onclick = () => modal.remove();
        document.getElementById('cancel-share').onclick = () => modal.remove();

        document.getElementById('message-selector').addEventListener('change', updatePreview);
        document.getElementById('share-commentary').addEventListener('input', updatePreview);

        document.getElementById('confirm-share').onclick = () => {
            const preview = document.getElementById('share-preview').textContent;
            if (preview && preview !== 'Select messages to see preview...') {
                const tweetUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(preview)}`;
                window.open(tweetUrl, '_blank');
                modal.remove();
            }
        };

        modal.addEventListener('click', (e) => {
            if (e.target === modal) modal.remove();
        });

        return modal;
    }

    // Format functions
    function formatAsText(messages) {
        if (messages.length === 0) return 'No conversation found.';

        let output = `Grok.ai COMPLETE Conversation Export\n`;
        output += `Exported: ${new Date().toLocaleString()}\n`;
        output += `Total Messages: ${messages.length}\n`;
        output += `URL: ${window.location.href}\n`;
        output += '='.repeat(80) + '\n\n';

        messages.forEach((msg, index) => {
            const modeIndicator = msg.mode !== 'standard' ? ` [${msg.mode.toUpperCase()}]` : '';
            output += `${msg.speaker}${modeIndicator}:\n`;
            output += `${msg.content}\n\n`;

            if (index < messages.length - 1) {
                output += '-'.repeat(50) + '\n\n';
            }
        });

        return output;
    }

    function formatAsMarkdown(messages) {
        if (messages.length === 0) return '# No conversation found';

        let md = `# Grok.ai Complete Conversation Export\n\n`;
        md += `**Exported:** ${new Date().toLocaleString()}  \n`;
        md += `**Total Messages:** ${messages.length}  \n`;
        md += `**URL:** ${window.location.href}  \n`;
        md += `**Export Method:** Enhanced Grok Export v2.2\n\n`;
        md += `---\n\n`;

        messages.forEach(msg => {
            const modeIndicator = msg.mode !== 'standard' ? ` [${msg.mode.toUpperCase()}]` : '';
            md += `## ${msg.speaker}${modeIndicator}\n\n`;
            md += `${msg.content}\n\n`;
        });

        return md;
    }

    function formatAsJSON(messages) {
        const exportData = {
            exportDate: new Date().toISOString(),
            exportTimestamp: Date.now(),
            exportVersion: '2.2.0',
            platform: 'grok',
            messageCount: messages.length,
            url: window.location.href,
            userAgent: navigator.userAgent,
            conversation: messages.map(msg => ({
                id: msg.id,
                speaker: msg.speaker,
                content: msg.content,
                mode: msg.mode,
                timestamp: msg.timestamp,
                length: msg.length,
                debugInfo: msg.debugInfo
            })),
            statistics: {
                humanMessages: messages.filter(m => m.speaker === 'Human').length,
                grokMessages: messages.filter(m => m.speaker === 'Grok').length,
                totalCharacters: messages.reduce((sum, m) => sum + m.content.length, 0),
                averageMessageLength: Math.round(messages.reduce((sum, m) => sum + m.content.length, 0) / messages.length),
                modes: {
                    standard: messages.filter(m => m.mode === 'standard').length,
                    think: messages.filter(m => m.mode === 'think').length,
                    fun: messages.filter(m => m.mode === 'fun').length,
                    deepsearch: messages.filter(m => m.mode === 'deepsearch').length
                }
            }
        };
        return JSON.stringify(exportData, null, 2);
    }

    // Download file
    function downloadFile(content, filename, type = 'text/plain') {
        try {
            debugLog(`Downloading: ${filename} (${content instanceof Blob ? 'Blob' : content.length + ' chars'})`);

            let blob;
            if (content instanceof Blob) {
                blob = content;
            } else {
                blob = new Blob([content], { type: type + ';charset=utf-8' });
            }

            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');

            link.download = filename;
            link.href = url;
            link.style.display = 'none';

            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            setTimeout(() => URL.revokeObjectURL(url), 1000);

            debugLog('Download completed successfully');
            return true;
        } catch (error) {
            console.error('Download failed:', error);
            alert(`Download failed: ${error.message}`);
            return false;
        }
    }

    // Export conversation with full loading
    async function exportConversation(format) {
        if (isExporting) {
            alert('Export already in progress. Please wait...');
            return;
        }

        isExporting = true;

        try {
            debugLog(`Starting Grok export in ${format} format...`);

            showNotification('🔄 Loading full conversation...', 0);

            if (CONFIG.autoScroll) {
                await loadFullConversation();
                showNotification('📝 Extracting messages...', 3000);
            }

            const messages = getConversationData();

            if (messages.length === 0) {
                alert('No conversation content found! This might be a new chat or there could be a technical issue. Check the browser console for details.');
                return;
            }

            if (format === 'share') {
                createShareModal(messages);
                hideMenu();
                return;
            }

            const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
            let filename, content, mimeType;

            switch (format) {
                case 'md':
                    content = formatAsMarkdown(messages);
                    mimeType = 'text/markdown';
                    filename = `grok-FULL-conversation-${messages.length}msgs-${timestamp}.md`;
                    break;
                case 'json':
                    content = formatAsJSON(messages);
                    mimeType = 'application/json';
                    filename = `grok-FULL-conversation-${messages.length}msgs-${timestamp}.json`;
                    break;
                case 'pdf':
                    showNotification('📄 Generating document...', 0);
                    content = await formatAsPDF(messages);
                    mimeType = 'text/plain';
                    filename = `grok-FULL-conversation-${messages.length}msgs-${timestamp}.txt`;
                    break;
                default:
                    content = formatAsText(messages);
                    mimeType = 'text/plain';
                    filename = `grok-FULL-conversation-${messages.length}msgs-${timestamp}.txt`;
            }

            const success = downloadFile(content, filename, mimeType);
            if (success) {
                hideMenu();
                const formatName = format === 'pdf' ? 'Document' : format.toUpperCase();
                showNotification(`✅ Exported ${messages.length} messages as ${formatName}`, 5000);
            }

        } catch (error) {
            console.error('Export failed:', error);
            alert(`Export failed: ${error.message}\n\nCheck the browser console for more details.`);
        } finally {
            isExporting = false;
        }
    }

    // Show notification
    function showNotification(message, duration = 3000) {
        const existing = document.getElementById('grok-export-notification');
        if (existing) existing.remove();

        const notification = document.createElement('div');
        notification.id = 'grok-export-notification';
        notification.textContent = message;
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: #1DA1F2;
            color: white;
            padding: 12px 20px;
            border-radius: 8px;
            z-index: 10000;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
            max-width: 300px;
        `;

        document.body.appendChild(notification);

        if (duration > 0) {
            setTimeout(() => {
                if (notification.parentElement) {
                    notification.parentElement.removeChild(notification);
                }
            }, duration);
        }
    }

    // Create export menu
    function createExportMenu() {
        const menu = document.createElement('div');
        menu.id = 'grok-export-menu';
        menu.style.cssText = `
            position: fixed;
            bottom: 70px;
            right: 10px;
            background: #ffffff;
            border: 2px solid #1DA1F2;
            border-radius: 12px;
            box-shadow: 0 8px 25px rgba(0,0,0,0.2);
            padding: 12px;
            z-index: 1000;
            display: none;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            min-width: 220px;
            color: #333333 !important;
        `;

        // Add title
        const title = document.createElement('div');
        title.textContent = 'Export Grok Conversation';
        title.style.cssText = `
            font-weight: 600;
            color: #1DA1F2 !important;
            margin-bottom: 8px;
            padding-bottom: 8px;
            border-bottom: 1px solid #eee;
            font-size: 14px;
        `;
        menu.appendChild(title);

        const formats = [
            { ext: 'md', name: 'Markdown', icon: '📝', desc: 'Rich formatting' },
            { ext: 'txt', name: 'Plain Text', icon: '📄', desc: 'Universal format' },
            { ext: 'json', name: 'JSON Data', icon: '📊', desc: 'Structured data' },
            { ext: 'pdf', name: 'Document', icon: '📋', desc: 'Formatted text file' },
            { ext: 'share', name: 'Share to X', icon: '🐦', desc: 'Post snippets to X' }
        ];

        formats.forEach(format => {
            const button = document.createElement('button');
            button.innerHTML = `
                <div style="display: flex; align-items: center;">
                    <span style="font-size: 16px; margin-right: 8px;">${format.icon}</span>
                    <div>
                        <div style="font-weight: 500; color: #333333 !important;">${format.name}</div>
                        <div style="font-size: 11px; color: #666 !important;">${format.desc}</div>
                    </div>
                </div>
            `;
            button.style.cssText = `
                display: block;
                width: 100%;
                padding: 10px;
                margin: 4px 0;
                border: none;
                background: transparent;
                text-align: left;
                cursor: pointer;
                border-radius: 6px;
                font-size: 14px;
                color: #333333 !important;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
                transition: background-color 0.2s;
            `;

            button.onmouseover = () => {
                button.style.background = format.ext === 'pdf' ? '#fff3cd' :
                                        format.ext === 'share' ? '#e3f2fd' : '#f8f9ff';
            };
            button.onmouseout = () => {
                button.style.background = 'transparent';
            };

            button.onclick = () => exportConversation(format.ext);
            menu.appendChild(button);
        });

        // Debug button
        if (CONFIG.debug) {
            const debugButton = document.createElement('button');
            debugButton.innerHTML = '🔍 Debug Info';
            debugButton.style.cssText = `
                display: block;
                width: 100%;
                padding: 8px 12px;
                margin: 8px 0 4px 0;
                border: none;
                background: transparent;
                text-align: left;
                cursor: pointer;
                border-radius: 4px;
                font-size: 13px;
                border-top: 1px solid #eee;
                color: #666 !important;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            `;

            debugButton.onclick = () => {
                const messages = getConversationData();
                const speakerStats = {
                    human: messages.filter(m => m.speaker === 'Human').length,
                    grok: messages.filter(m => m.speaker === 'Grok').length
                };
                console.log('Grok Debug Info:', {
                    messagesFound: messages.length,
                    speakerDistribution: speakerStats,
                    url: window.location.href,
                    sampleMessages: messages.slice(0, 5).map(m => ({
                        speaker: m.speaker,
                        length: m.length,
                        preview: m.content.substring(0, 50) + '...',
                        debugInfo: m.debugInfo
                    }))
                });
                alert(`Found ${messages.length} messages\nHuman: ${speakerStats.human}, Grok: ${speakerStats.grok}\nCheck console for details.`);
                hideMenu();
            };
            menu.appendChild(debugButton);
        }

        return menu;
    }

    // Show/hide menu
    function toggleMenu() {
        const menu = document.getElementById('grok-export-menu');
        if (menu) {
            menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
        }
    }

    function hideMenu() {
        const menu = document.getElementById('grok-export-menu');
        if (menu) menu.style.display = 'none';
    }

    // Create main export button
    function createExportButton() {
        const button = document.createElement('button');
        button.innerHTML = `🤖 Export Grok`;
        button.id = 'grok-export-button';
        button.style.cssText = `
            position: fixed;
            bottom: 10px;
            right: 10px;
            padding: 10px 16px;
            background: linear-gradient(135deg, #1DA1F2 0%, #0084b4 100%);
            border: 2px solid rgba(255,255,255,0.3);
            color: white;
            text-align: center;
            display: inline-block;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            border-radius: 30px;
            z-index: 999;
            box-shadow: 0 4px 15px rgba(29,161,242,0.3);
            transition: all 0.3s ease;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        `;

        button.onmouseover = () => {
            button.style.transform = 'translateY(-3px) scale(1.05)';
            button.style.boxShadow = '0 6px 20px rgba(29,161,242,0.4)';
        };

        button.onmouseout = () => {
            button.style.transform = 'translateY(0) scale(1)';
            button.style.boxShadow = '0 4px 15px rgba(29,161,242,0.3)';
        };

        button.onclick = toggleMenu;

        return button;
    }

    // Initialize the script
    function init() {
        debugLog('Initializing Enhanced Grok Export v2.2...');

        // Remove existing elements
        const existingButton = document.getElementById('grok-export-button');
        const existingMenu = document.getElementById('grok-export-menu');
        if (existingButton) existingButton.remove();
        if (existingMenu) existingMenu.remove();

        // Create UI elements
        const button = createExportButton();
        const menu = createExportMenu();

        document.body.appendChild(button);
        document.body.appendChild(menu);

        // Close menu when clicking outside
        document.addEventListener('click', function(e) {
            if (!e.target.closest('#grok-export-menu') &&
                !e.target.closest('#grok-export-button')) {
                hideMenu();
            }
        });

        debugLog('Enhanced Grok Export v2.2 initialized successfully!');
        console.log('%c✅ Enhanced Grok Export v2.2 Ready!', 'color: green; font-weight: bold;');
        console.log('%c🔧 Fixed: Content-Based Speaker Detection & PDF Export', 'color: blue; font-weight: bold;');
    }

    // Wait for page to be ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        setTimeout(init, 1000);
    }

    // Re-initialize on navigation
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            setTimeout(init, 2000);
        }
    }).observe(document, { subtree: true, childList: true });

})();