Claude API Exporter 1.1

Export Claude conversations using API

// ==UserScript==
// @name         Claude API Exporter 1.1
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Export Claude conversations using API
// @author       MRL
// @match        https://claude.ai/*
// @grant        GM_registerMenuCommand
// @grant        GM_download
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // =============================================
    // CONSTANTS AND DEFAULT SETTINGS
    // =============================================

    /**
     * Default settings configuration
     */
    const defaultSettings = {
        conversationTemplate: '{timestamp}_{conversationId}__{title}.md',
        artifactTemplate: '{timestamp}_{conversationId}_{artifactId}_branch{branch}{main_suffix}_v{version}_{title}{extension}',
        dateFormat: 'YYYYMMDDHHMMSS', // YYYYMMDDHHMMSS, YYYY-MM-DD_HH-MM-SS, ISO
        
        artifactExportMode: 'files', // Flexible artifact export settings: 'embed', 'files', 'both'
        includeArtifactsInConversationOnly: false, // For "Export Conversation Only" button
        includeArtifactMetadata: true, // Include metadata comments in artifact files
        
        // Content formatting settings
        excludeAttachments: false, // Exclude attachments from conversation export
        removeDoubleNewlinesFromConversation: false, // Remove \n\n from conversation content
        removeDoubleNewlinesFromMarkdown: false // Remove \n\n from markdown artifact content
    };

    /**
     * Available variables for filename templates
     */
    const availableVariables = {
        // Common variables
        '{timestamp}': 'Current timestamp (format depends on dateFormat setting)',
        '{conversationId}': 'Unique conversation identifier',
        '{title}': 'Sanitized conversation or artifact title',
        
        // Artifact-specific variables
        '{artifactId}': 'Unique artifact identifier',
        '{version}': 'Artifact version number',
        '{branch}': 'Branch number (e.g., 1, 2, 3)',
        '{main_suffix}': 'Suffix "_main" for main branch, empty for others',
        '{extension}': 'File extension based on artifact type',
        
        // Date/time variables
        '{date}': 'Current date in YYYY-MM-DD format',
        '{time}': 'Current time in HH-MM-SS format',
        '{year}': 'Current year (YYYY)',
        '{month}': 'Current month (MM)',
        '{day}': 'Current day (DD)',
        '{hour}': 'Current hour (HH)',
        '{minute}': 'Current minute (MM)',
        '{second}': 'Current second (SS)'
    };

    // =============================================
    // SETTINGS MANAGEMENT
    // =============================================

    /**
     * Load settings from storage
     */
    function loadSettings() {
        const settings = {};
        for (const [key, defaultValue] of Object.entries(defaultSettings)) {
            settings[key] = GM_getValue(key, defaultValue);
        }
        return settings;
    }

    /**
     * Save settings to storage
     */
    function saveSettings(settings) {
        for (const [key, value] of Object.entries(settings)) {
            GM_setValue(key, value);
        }
    }

    /**
     * Apply variables to template string
     */
    function applyTemplate(template, variables) {
        let result = template;
        
        for (const [placeholder, value] of Object.entries(variables)) {
            // Replace all occurrences of the placeholder
            result = result.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), value || '');
        }
        
        return result;
    }

    // =============================================
    // SETTINGS UI
    // =============================================

    /**
     * Creates and shows the settings interface
     */
    function showSettingsUI() {
        // Remove existing settings UI if present
        document.getElementById('claude-exporter-settings')?.remove();
        const currentSettings = loadSettings();

        // Create modal overlay
        const overlay = document.createElement('div');
        overlay.id = 'claude-exporter-settings';
        overlay.innerHTML = `
            <div class="claude-settings-overlay">
                <div class="claude-settings-modal">
                    <div class="claude-settings-header">
                        <h2>🔧 Claude Exporter Settings</h2>
                        <button class="claude-settings-close" type="button">×</button>
                    </div>
                    
                    <!-- Tab Navigation -->
                    <div class="claude-tabs-nav">
                        <button class="claude-tab-btn active" data-tab="general">⚙️ General Settings</button>
                        <button class="claude-tab-btn" data-tab="filenames">📁 Filename Templates</button>
                    </div>
                    
                    <div class="claude-settings-content">
                        <!-- Tab 1: General Settings -->
                        <div class="claude-tab-content active" id="tab-general">
                            <div class="claude-settings-section">
                                <h3>📦 Artifact Export Settings</h3>
                                
                                <div class="claude-setting-group">
                                    <label for="artifactExportMode">Default artifact export mode:</label>
                                    <select id="artifactExportMode">
                                        ${['embed', 'files', 'both'].map(mode => 
                                            `<option value="${mode}" ${currentSettings.artifactExportMode === mode ? 'selected' : ''}>
                                                ${mode === 'embed' ? '📄 Embed Only - Include artifacts in conversation file only' : 
                                                  mode === 'files' ? '📁 Files Only - Export artifacts as separate files only' : 
                                                  '📄📁 Both - Include in conversation and export as separate files'}
                                            </option>`).join('')}
                                    </select>
                                    <p class="claude-settings-help">This setting affects the "Export Conversation + Final Artifacts" and "Export Conversation + All Artifacts" buttons.</p>
                                </div>

                                <div class="claude-setting-group">
                                    <div class="claude-checkbox-group">
                                        <input type="checkbox" id="includeArtifactsInConversationOnly" ${currentSettings.includeArtifactsInConversationOnly ? 'checked' : ''}>
                                        <label for="includeArtifactsInConversationOnly">Include all artifacts in "Export Conversation Only"</label>
                                    </div>
                                    <p class="claude-settings-help">When enabled, the "Export Conversation Only" button will embed all artifact versions in the conversation file (but never export separate artifact files).</p>
                                </div>

                                <div class="claude-export-behavior-info">
                                    <h4>📋 Export Behavior Summary</h4>
                                    <div class="claude-behavior-grid">
                                        ${['conversationOnlyBehavior', 'finalArtifactsBehavior', 'allArtifactsBehavior'].map(id => 
                                            `<div class="claude-behavior-item"><strong>${id.replace(/([A-Z])/g, ' $1').replace('Behavior', '').replace(/([a-z])([A-Z])/g, '$1 $2')}:</strong><span id="${id}"></span></div>`).join('')}
                                    </div>
                                </div>
                            </div>

                            <div class="claude-settings-section">
                                <h3>📝 Content Formatting</h3>
                                <div class="claude-setting-group">
                                    <div class="claude-checkbox-group">
                                        <input type="checkbox" id="excludeAttachments" ${currentSettings.excludeAttachments ? 'checked' : ''}>
                                        <label for="excludeAttachments">Exclude attachments content from conversation export</label>
                                    </div>
                                    <p class="claude-settings-help">When enabled, the extracted content of attachments will not be included in the exported conversation markdown.</p>
                                </div>
                                
                                ${['removeDoubleNewlinesFromConversation', 'removeDoubleNewlinesFromMarkdown'].map(setting => `
                                    <div class="claude-setting-group">
                                        <div class="claude-checkbox-group">
                                            <input type="checkbox" id="${setting}" ${currentSettings[setting] ? 'checked' : ''}>
                                            <label for="${setting}">Remove double newlines (\\n\\n) from ${setting.includes('Conversation') ? 'conversation content' : 'markdown artifact content'}</label>
                                        </div>
                                        <p class="claude-settings-help">When enabled, replaces multiple consecutive newlines with single newlines in ${setting.includes('Conversation') ? 'conversation text content only. Does not affect markdown structure or metadata.' : 'markdown artifact content only. Does not affect artifact metadata or other file types.'}</p>
                                    </div>
                                `).join('')}
                            </div>

                            <div class="claude-settings-section">
                                <h3>📝 Artifact File Settings</h3>
                                <div class="claude-setting-group">
                                    <div class="claude-checkbox-group">
                                        <input type="checkbox" id="includeArtifactMetadata" ${currentSettings.includeArtifactMetadata ? 'checked' : ''}>
                                        <label for="includeArtifactMetadata">Include metadata comments in artifact files</label>
                                    </div>
                                    <p class="claude-settings-help">When enabled, artifact files will include metadata comments at the top (ID, branch, version, etc.). When disabled, files will contain only the pure artifact content.</p>
                                </div>
                            </div>
                        </div>

                        <!-- Tab 2: Filename Templates -->
                        <div class="claude-tab-content" id="tab-filenames">
                            <div class="claude-settings-section">
                                <h3>📁 Filename Templates</h3>
                                <p class="claude-settings-description">Configure how exported files are named using variables. <a href="#claude-variables-panel" class="claude-variables-toggle">Show available variables</a></p>
                                
                                <div class="claude-variables-panel" style="display: none;">
                                    <h4>Available Variables:</h4>
                                    <div class="claude-variables-grid">
                                        ${Object.entries(availableVariables).map(([variable, description]) => 
                                            `<div class="claude-variable-item"><code class="claude-variable-name">${variable}</code><span class="claude-variable-desc">${description}</span></div>`).join('')}
                                    </div>
                                </div>

                                ${['conversation', 'artifact'].map(type => `
                                    <div class="claude-setting-group">
                                        <label for="${type}Template">${type.charAt(0).toUpperCase() + type.slice(1)} Files:</label>
                                        <input type="text" id="${type}Template" value="${currentSettings[type + 'Template']}" placeholder="${defaultSettings[type + 'Template']}">
                                        <div class="claude-preview" id="${type}Preview"></div>
                                    </div>
                                `).join('')}
                            </div>

                            <div class="claude-settings-section">
                                <h3>📅 Date Format</h3>
                                <div class="claude-setting-group">
                                    <label for="dateFormat">Timestamp Format:</label>
                                    <select id="dateFormat">
                                        ${[
                                            ['YYYYMMDDHHMMSS', 'Compact (YYYYMMDDHHMMSS) - e.g., 20250710143022'],
                                            ['YYYY-MM-DD_HH-MM-SS', 'Readable (YYYY-MM-DD_HH-MM-SS) - e.g., 2025-07-10_14-30-22'],
                                            ['ISO', 'ISO (YYYY-MM-DDTHH-MM-SS) - e.g., 2025-07-10T14-30-22']
                                        ].map(([value, text]) => 
                                            `<option value="${value}" ${currentSettings.dateFormat === value ? 'selected' : ''}>${text}</option>`).join('')}
                                    </select>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="claude-settings-footer">
                        <button class="claude-btn claude-btn-secondary" type="button" id="resetDefaults">🔄 Reset to Defaults</button>
                        <div class="claude-btn-group">
                            <button class="claude-btn claude-btn-secondary" type="button" id="cancelSettings">Cancel</button>
                            <button class="claude-btn claude-btn-primary" type="button" id="saveSettings">💾 Save Settings</button>
                        </div>
                    </div>
                </div>
            </div>
        `;

        // Add CSS styles (compressed)
        const styles = `<style id="claude-exporter-styles">
            .claude-settings-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.75);display:flex;align-items:center;justify-content:center;z-index:99999;font-family:system-ui,-apple-system,sans-serif}
            .claude-settings-modal{background:#fff;border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,0.3);width:90%;max-width:800px;max-height:90vh;overflow:hidden;display:flex;flex-direction:column}
            .claude-settings-header{background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:white;padding:20px 24px;display:flex;align-items:center;justify-content:space-between}
            .claude-settings-header h2{margin:0;font-size:20px;font-weight:600}
            .claude-settings-close{background:none;border:none;color:white;font-size:24px;cursor:pointer;padding:0;width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;transition:background-color 0.2s}
            .claude-settings-close:hover{background:rgba(255,255,255,0.2)}
            .claude-tabs-nav{display:flex;background:#f8fafc;border-bottom:1px solid #e2e8f0}
            .claude-tab-btn{background:none;border:none;padding:16px 20px;font-size:14px;font-weight:500;color:#64748b;cursor:pointer;border-bottom:3px solid transparent;transition:all 0.2s;flex:1;text-align:center}
            .claude-tab-btn:hover{background:rgba(102,126,234,0.1);color:#475569}
            .claude-tab-btn.active{color:#667eea;background:white;border-bottom-color:#667eea}
            .claude-tab-content{display:none}.claude-tab-content.active{display:block}
            .claude-settings-content{flex:1;overflow-y:auto;padding:24px}
            .claude-settings-section{margin-bottom:32px}.claude-settings-section:last-child{margin-bottom:0}
            .claude-settings-section h3{margin:0 0 12px 0;font-size:18px;font-weight:600;color:#2d3748}
            .claude-settings-description{margin:0 0 20px 0;color:#718096;font-size:14px;line-height:1.5}
            .claude-variables-toggle{color:#667eea;text-decoration:none;font-weight:500}.claude-variables-toggle:hover{text-decoration:underline}
            .claude-variables-panel{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin:16px 0}
            .claude-variables-panel h4{margin:0 0 12px 0;font-size:14px;font-weight:600;color:#2d3748}
            .claude-variables-grid{display:grid;gap:8px}
            .claude-variable-item{display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:start}
            .claude-variable-name{background:#e2e8f0;color:#2d3748;padding:2px 6px;border-radius:4px;font-family:'Monaco','Menlo',monospace;font-size:13px;white-space:nowrap}
            .claude-variable-desc{font-size:13px;color:#718096;line-height:1.4}
            .claude-setting-group{margin-bottom:20px}
            .claude-setting-group label{display:block;margin-bottom:6px;font-weight:500;color:#2d3748;font-size:14px}
            .claude-setting-group input,.claude-setting-group select{width:100%;padding:10px 12px;border:2px solid #e2e8f0;border-radius:6px;font-size:14px;transition:border-color 0.2s,box-shadow 0.2s;box-sizing:border-box}
            .claude-setting-group input[type="text"]{font-family:'Monaco','Menlo',monospace}
            .claude-checkbox-group{display:flex;align-items:center;gap:10px;margin-bottom:8px}
            .claude-checkbox-group input[type="checkbox"]{width:auto;margin:0;transform:scale(1.2)}
            .claude-checkbox-group label{margin:0;cursor:pointer;font-weight:500}
            .claude-settings-help{margin:8px 0 0 0;font-size:13px;color:#718096;line-height:1.4}
            .claude-setting-group input:focus,.claude-setting-group select:focus{outline:none;border-color:#667eea;box-shadow:0 0 0 3px rgba(102,126,234,0.1)}
            .claude-preview{margin-top:8px;padding:8px 12px;background:#f0f9ff;border:1px solid #bae6fd;border-radius:6px;font-family:'Monaco','Menlo',monospace;font-size:13px;color:#0369a1;word-break:break-all}
            .claude-export-behavior-info{background:#f0f9ff;border:1px solid #bae6fd;border-radius:8px;padding:16px;margin-top:20px}
            .claude-export-behavior-info h4{margin:0 0 12px 0;font-size:14px;font-weight:600;color:#0369a1}
            .claude-behavior-grid{display:grid;gap:8px}
            .claude-behavior-item{display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:start}
            .claude-behavior-item strong{font-size:13px;color:#0369a1;white-space:nowrap}
            .claude-behavior-item span{font-size:13px;color:#374151;line-height:1.4}
            .claude-settings-footer{background:#f8fafc;padding:20px 24px;border-top:1px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between}
            .claude-btn{padding:10px 20px;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:all 0.2s;text-decoration:none;display:inline-flex;align-items:center;gap:6px}
            .claude-btn-primary{background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:white}
            .claude-btn-primary:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(102,126,234,0.4)}
            .claude-btn-secondary{background:#e2e8f0;color:#2d3748}.claude-btn-secondary:hover{background:#cbd5e0}
            .claude-btn-group{display:flex;gap:12px}
            @media (max-width:600px){.claude-settings-modal{width:95%;margin:20px}.claude-settings-footer{flex-direction:column;gap:12px}.claude-btn-group{width:100%}.claude-btn{flex:1;justify-content:center}.claude-tabs-nav{flex-direction:column}.claude-tab-btn{flex:none}}
        </style>`;

        // Add styles to head and modal to body
        document.head.insertAdjacentHTML('beforeend', styles);
        document.body.appendChild(overlay);

        // Initialize functionality
        initTabs();
        initEventListeners();
        updatePreviews();
    }

        // Tab switching functionality
        function initTabs() {
            document.querySelectorAll('.claude-tab-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    const targetTab = btn.dataset.tab;
                    document.querySelectorAll('.claude-tab-btn, .claude-tab-content').forEach(el => el.classList.remove('active'));
                    btn.classList.add('active');
                    document.getElementById(`tab-${targetTab}`).classList.add('active');
                });
            });
        }
    
        // Initialize all event listeners
        function initEventListeners() {
            const formElements = ['conversationTemplate', 'artifactTemplate', 'dateFormat', 'artifactExportMode', 
                                'includeArtifactsInConversationOnly', 'includeArtifactMetadata', 
                                'excludeAttachments', 'removeDoubleNewlinesFromConversation', 'removeDoubleNewlinesFromMarkdown'];
            
            formElements.forEach(id => {
                const element = document.getElementById(id);
                if (element) {
                    element.addEventListener(element.type === 'checkbox' ? 'change' : 'input', updatePreviews);
                }
            });
    
            // Variables panel toggle
            document.querySelector('.claude-variables-toggle').addEventListener('click', (e) => {
                e.preventDefault();
                const panel = document.querySelector('.claude-variables-panel');
                const isVisible = panel.style.display !== 'none';
                panel.style.display = isVisible ? 'none' : 'block';
                e.target.textContent = isVisible ? 'Show available variables' : 'Hide available variables';
            });
    
            // Reset defaults
            document.getElementById('resetDefaults').addEventListener('click', () => {
                if (confirm('Reset all settings to defaults?')) {
                    Object.entries(defaultSettings).forEach(([key, value]) => {
                        const element = document.getElementById(key);
                        if (element) {
                            element.type === 'checkbox' ? element.checked = value : element.value = value;
                        }
                    });
                    updatePreviews();
                }
            });
    
            // Close modal functionality
            const closeModal = () => {
                document.getElementById('claude-exporter-settings')?.remove();
                document.getElementById('claude-exporter-styles')?.remove();
            };
    
            document.getElementById('cancelSettings').addEventListener('click', closeModal);
            document.querySelector('.claude-settings-close').addEventListener('click', closeModal);
            
            // Close on overlay click
            document.querySelector('.claude-settings-overlay').addEventListener('click', (e) => {
                if (e.target.classList.contains('claude-settings-overlay')) closeModal();
            });
    
            // Save settings
            document.getElementById('saveSettings').addEventListener('click', () => {
                const newSettings = {};
                Object.keys(defaultSettings).forEach(key => {
                    const element = document.getElementById(key);
                    if (element) {
                        newSettings[key] = element.type === 'checkbox' ? element.checked : element.value;
                    }
                });
    
                saveSettings(newSettings);
                showNotification('Settings saved successfully!', 'success');
                closeModal();
            });
        }
    
        // Update previews function
        function updatePreviews() {
            const conversationTemplate = document.getElementById('conversationTemplate').value;
            const artifactTemplate = document.getElementById('artifactTemplate').value;
            const dateFormat = document.getElementById('dateFormat').value;
            const artifactExportMode = document.getElementById('artifactExportMode').value;
            const includeArtifactsInConversationOnly = document.getElementById('includeArtifactsInConversationOnly').checked;
    
            // Sample data for preview
            const sampleVariables = createTemplateVariables({
                '{timestamp}': formatTimestamp(new Date(), dateFormat),
                '{conversationId}': '12dasdh1-fa1j-f213-da13-dfa3124123ff',
                '{title}': 'Title',
                '{artifactId}': 'artifact2',
                '{version}': '3',
                '{branch}': '2',
                '{main_suffix}': '',
                '{extension}': '.js'
            });
    
            document.getElementById('conversationPreview').textContent = 'Preview: ' + applyTemplate(conversationTemplate, sampleVariables);
            document.getElementById('artifactPreview').textContent = 'Preview: ' + applyTemplate(artifactTemplate, sampleVariables);
            
            // Behavior descriptions
            const behaviors = {
                conversationOnlyBehavior: includeArtifactsInConversationOnly ? 'Embeds all artifacts in conversation file' : 'Pure conversation without artifacts',
                finalArtifactsBehavior: {
                    'embed': 'Embeds final artifacts in conversation file only',
                    'files': 'Pure conversation + final artifacts as separate files',
                    'both': 'Embeds final artifacts in conversation + exports as separate files'
                }[artifactExportMode],
                allArtifactsBehavior: {
                    'embed': 'Embeds all artifacts in conversation file only',
                    'files': 'Pure conversation + all artifacts as separate files',
                    'both': 'Embeds all artifacts in conversation + exports as separate files'
                }[artifactExportMode]
            };
    
            Object.entries(behaviors).forEach(([id, text]) => {
                document.getElementById(id).textContent = text;
            });
        }

    // =============================================
    // SETTINGS UTILITY FUNCTIONS
    // =============================================

    /**
     * Formats timestamp according to the selected format
     */
    function formatTimestamp(dateInput, format) {
        const d = typeof dateInput === 'string' ? new Date(dateInput) : dateInput;
        
        if (isNaN(d.getTime())) {
            return formatTimestamp(new Date(), format);
        }
        
        const components = {
            year: d.getFullYear(),
            month: String(d.getMonth() + 1).padStart(2, '0'),
            day: String(d.getDate()).padStart(2, '0'),
            hour: String(d.getHours()).padStart(2, '0'),
            minute: String(d.getMinutes()).padStart(2, '0'),
            second: String(d.getSeconds()).padStart(2, '0')
        };
        
        switch (format) {
            case 'YYYY-MM-DD_HH-MM-SS':
                return `${components.year}-${components.month}-${components.day}_${components.hour}-${components.minute}-${components.second}`;
            case 'ISO':
                return `${components.year}-${components.month}-${components.day}T${components.hour}-${components.minute}-${components.second}`;
            case 'YYYYMMDDHHMMSS':
            default:
                return `${components.year}${components.month}${components.day}${components.hour}${components.minute}${components.second}`;
        }
    }

    /**
     * Generates timestamp using current settings
     */
    function generateTimestamp(dateInput) {
        const settings = loadSettings();
        const date = dateInput ? 
            (typeof dateInput === 'string' ? new Date(dateInput) : dateInput) : 
            new Date();
        
        return formatTimestamp(date, settings.dateFormat);
    }

    /**
     * Creates template variables object for filenames
     */
    function createTemplateVariables(baseData) {
        const now = new Date();
        const base = {
            '{date}': now.toISOString().split('T')[0],
            '{time}': now.toTimeString().split(' ')[0].replace(/:/g, '-'),
            '{year}': now.getFullYear().toString(),
            '{month}': String(now.getMonth() + 1).padStart(2, '0'),
            '{day}': String(now.getDate()).padStart(2, '0'),
            '{hour}': String(now.getHours()).padStart(2, '0'),
            '{minute}': String(now.getMinutes()).padStart(2, '0'),
            '{second}': String(now.getSeconds()).padStart(2, '0')
        };
        
        return { ...base, ...baseData };
    }

    /**
     * Generates conversation filename using template
     */
    function generateConversationFilename(conversationData) {
        const settings = loadSettings();
        const variables = createTemplateVariables({
            '{timestamp}': generateTimestamp(conversationData.updated_at),
            '{conversationId}': conversationData.uuid,
            '{title}': sanitizeFileName(conversationData.name)
        });
        
        return applyTemplate(settings.conversationTemplate, variables);
    }

    /**
     * Generates artifact filename using template
     */
    function generateArtifactFilename(version, conversationData, branchLabel, isMain, artifactId) {
        const settings = loadSettings();
        const variables = createTemplateVariables({
            '{timestamp}': generateTimestamp(version.content_stop_timestamp),
            '{conversationId}': conversationData.uuid,
            '{artifactId}': artifactId,
            '{version}': version.version.toString(),
            '{branch}': branchLabel,
            '{main_suffix}': isMain ? '_main' : '',
            '{title}': sanitizeFileName(version.title),
            '{extension}': getFileExtension(version.finalType, version.finalLanguage)
        });
        
        return applyTemplate(settings.artifactTemplate, variables);
    }
    
    /**
     * Removes double newlines from text content while preserving markdown structure
     * @param {string} content - Text content to process
     * @param {boolean} removeDoubleNewlines - Whether to remove \n\n
     * @returns {string} Processed content
     */
    function processTextContent(content, removeDoubleNewlines) {
        return processContent(content, removeDoubleNewlines);
    }

    /**
     * Processes artifact content based on type and settings
     * @param {string} content - Artifact content
     * @param {string} type - Artifact type
     * @param {boolean} removeDoubleNewlines - Whether to remove \n\n
     * @returns {string} Processed content
     */
    function processArtifactContent(content, type, removeDoubleNewlines) {
        return processContent(content, removeDoubleNewlines, type);
    }

    /**
     * Unified content processing function
     */
    function processContent(content, removeDoubleNewlines, type = null) {
        if (!removeDoubleNewlines || !content) {
            return content;
        }
        
        // Only apply to markdown artifacts or general content
        if (!type || type === 'text/markdown') {
            return content.replace(/\n\n+/g, '\n');
        }
        
        return content;
    }

    // =============================================
    // UTILITY FUNCTIONS
    // =============================================

    /**
     * Sanitizes filename by removing invalid characters and limiting length
     */
    function sanitizeFileName(name) {
        return name.replace(/[\\/:*?"<>|]/g, '_')
                  .replace(/\s+/g, '_')
                  .replace(/__+/g, '_')
                  .replace(/^_+|_+$/g, '')
                  .slice(0, 100);
    }

    /**
     * Formats date string to localized format
     */
    function formatDate(dateInput) {
        if (!dateInput) return '';
        const date = typeof dateInput === 'string' ? new Date(dateInput) : dateInput;
        return date.toLocaleString();
    }

    /**
     * Downloads content as a file using browser's download functionality
     */
    function downloadFile(filename, content) {
        const blob = new Blob([content], { type: 'text/plain' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = filename;
        link.click();
        setTimeout(() => {
            URL.revokeObjectURL(url);
        }, 100);
    }

    /**
     * Shows temporary notification to the user
     * @param {string} message - Message to display
     * @param {string} type - Type of notification (info, success, error)
     */
    function showNotification(message, type = "info") {
        const notification = document.createElement('div');
        const colors = {
            error: '#f44336',
            success: '#4CAF50',
            info: '#2196F3'
        };
        
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px 20px;
            border-radius: 5px;
            color: white;
            font-family: system-ui, -apple-system, sans-serif;
            font-size: 14px;
            z-index: 10000;
            max-width: 400px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
            background-color: ${colors[type] || colors.info};
        `;

        notification.textContent = message;
        document.body.appendChild(notification);

        setTimeout(() => {
            if (notification.parentNode) {
                notification.parentNode.removeChild(notification);
            }
        }, 5000);
    }

    // =============================================
    // API FUNCTIONS
    // =============================================

    /**
     * Extracts conversation ID from current URL
     * @returns {string|null} Conversation ID or null if not found
     */
    function getConversationId() {
        const match = window.location.pathname.match(/\/chat\/([^/?]+)/);
        return match ? match[1] : null;
    }

    /**
     * Gets organization ID from browser cookies
     * @returns {string} Organization ID
     * @throws {Error} If organization ID not found
     */
    function getOrgId() {
        const cookies = document.cookie.split(';');
        for (const cookie of cookies) {
            const [name, value] = cookie.trim().split('=');
            if (name === 'lastActiveOrg') {
                return value;
            }
        }
        throw new Error('Could not find organization ID');
    }

    /**
     * Fetches conversation data from Claude API
     * @returns {Promise<Object>} Complete conversation data including messages and metadata
     */
    async function getConversationData() {
        const conversationId = getConversationId();
        if (!conversationId) {
            throw new Error('Not in a conversation');
        }

        const orgId = getOrgId();
        const response = await fetch(`/api/organizations/${orgId}/chat_conversations/${conversationId}?tree=true&rendering_mode=messages&render_all_tools=true`);

        if (!response.ok) {
            throw new Error(`API request failed: ${response.status}`);
        }

        return await response.json();
    }

    // =============================================
    // FILE EXTENSION FUNCTIONS
    // =============================================

    /**
     * Gets appropriate file extension based on artifact type and language
     * @param {string} type - Artifact MIME type
     * @param {string} language - Programming language (for code artifacts)
     * @returns {string} File extension including the dot
     */
    function getFileExtension(type, language) {
        switch (type) {
            case 'application/vnd.ant.code':
                return getCodeExtension(language);
            case 'text/html':
                return '.html';
            case 'text/markdown':
                return '.md';
            case 'image/svg+xml':
                return '.svg';
            case 'application/vnd.ant.mermaid':
                return '.mmd';
            case 'application/vnd.ant.react':
                return '.jsx';
            case undefined:
            default:
                return '.txt';
        }
    }

    /**
     * Maps programming language names to file extensions
     * @param {string} language - Programming language name
     * @returns {string} File extension including the dot
     */
    function getCodeExtension(language) {
        const extensionMap = {
            // Web languages
            'javascript': '.js', 'typescript': '.ts', 'html': '.html', 'css': '.css', 'scss': '.scss', 'sass': '.sass', 'less': '.less', 'jsx': '.jsx', 'tsx': '.tsx', 'vue': '.vue',

            // Languages
            'python': '.py', 'java': '.java', 'csharp': '.cs', 'c#': '.cs', 'cpp': '.cpp', 'c++': '.cpp', 'c': '.c', 'go': '.go', 'rust': '.rs', 'swift': '.swift', 'kotlin': '.kt', 'dart': '.dart', 'php': '.php', 'ruby': '.rb', 'perl': '.pl', 'lua': '.lua',

            // Functional languages
            'haskell': '.hs', 'clojure': '.clj', 'erlang': '.erl', 'elixir': '.ex', 'fsharp': '.fs', 'f#': '.fs', 'ocaml': '.ml', 'scala': '.scala', 'lisp': '.lisp',

            // Data and config
            'json': '.json', 'yaml': '.yaml', 'yml': '.yml', 'xml': '.xml', 'toml': '.toml', 'ini': '.ini', 'csv': '.csv',

            // Query languages
            'sql': '.sql', 'mysql': '.sql', 'postgresql': '.sql', 'sqlite': '.sql', 'plsql': '.sql',

            // Shell and scripting
            'bash': '.sh', 'shell': '.sh', 'sh': '.sh', 'zsh': '.zsh', 'fish': '.fish', 'powershell': '.ps1', 'batch': '.bat', 'cmd': '.cmd',

            // Scientific and specialized
            'r': '.r', 'matlab': '.m', 'julia': '.jl', 'fortran': '.f90', 'cobol': '.cob', 'assembly': '.asm', 'vhdl': '.vhd', 'verilog': '.v',

            // Build and config files
            'dockerfile': '.dockerfile', 'makefile': '.mk', 'cmake': '.cmake', 'gradle': '.gradle', 'maven': '.xml',

            // Markup and documentation
            'markdown': '.md', 'latex': '.tex', 'restructuredtext': '.rst', 'asciidoc': '.adoc',

            // Other
            'regex': '.regex', 'text': '.txt', 'plain': '.txt'
        };

        const normalizedLanguage = language ? language.toLowerCase().trim() : '';
        return extensionMap[normalizedLanguage] || '.txt';
    }

    /**
     * Gets comment style for a programming language
     * @param {string} type - Artifact MIME type
     * @param {string} language - Programming language
     * @returns {Object} Comment style object with start and end strings
     */
    function getCommentStyle(type, language) {
        if (type === 'text/html' || type === 'image/svg+xml') {
            return { start: '<!-- ', end: ' -->' };
        }

        if (type !== 'application/vnd.ant.code') {
            return { start: '# ', end: '' }; // Default to hash comments
        }

        const normalizedLanguage = language ? language.toLowerCase().trim() : '';
        
        // Define comment patterns
        const commentPatterns = {
            // Languages with // comments
            slash: ['javascript', 'typescript', 'java', 'csharp', 'c#', 'cpp', 'c++', 'c', 'go', 'rust', 'swift', 'kotlin', 'dart', 'php', 'scala', 'jsx', 'tsx'],
                   
            // Languages with # comments
            hash: ['python', 'ruby', 'perl', 'bash', 'shell', 'sh', 'zsh', 'fish', 'yaml', 'yml', 'r', 'julia', 'toml', 'ini', 'powershell'],
                  
            // Languages with -- comments
            dash: ['sql', 'mysql', 'postgresql', 'sqlite', 'plsql', 'haskell', 'lua']
        };

        if (commentPatterns.slash.includes(normalizedLanguage)) {
            return { start: '// ', end: '' };
        } else if (commentPatterns.hash.includes(normalizedLanguage)) {
            return { start: '# ', end: '' };
        } else if (commentPatterns.dash.includes(normalizedLanguage)) {
            return { start: '-- ', end: '' };
        }
        
        return { start: '# ', end: '' }; // Default to hash comments
    }

    /**
     * Gets language identifier for markdown syntax highlighting
     */
    function getLanguageForHighlighting(type, language) {
        const typeMap = {
            'text/html': 'html',
            'text/markdown': 'markdown', 
            'image/svg+xml': 'xml',
            'application/vnd.ant.mermaid': 'mermaid',
            'application/vnd.ant.react': 'jsx'
        };
        
        if (typeMap[type]) return typeMap[type];
        
        if (type === 'application/vnd.ant.code' && language) {
            const normalizedLanguage = language.toLowerCase().trim();
            
            // Map some languages to their markdown equivalents
            const languageMap = {
                'c++': 'cpp', 'c#': 'csharp', 'f#': 'fsharp',
                'objective-c': 'objc', 'shell': 'bash', 'sh': 'bash'
            };
            
            return languageMap[normalizedLanguage] || normalizedLanguage;
        }
        
        return '';
    }

    // =============================================
    // BRANCH HANDLING FUNCTIONS
    // =============================================

    /**
     * Builds conversation tree structure to understand message branches
     * @param {Array} messages - Array of chat messages
     * @returns {Object} Tree structure with branch information
     */
    function buildConversationTree(messages) {
        const messageMap = new Map();
        const rootMessages = [];
        
        // First pass: create message map
        messages.forEach(message => {
            messageMap.set(message.uuid, {
                ...message,
                children: [],
                branchId: null,
                branchIndex: null
            });
        });
        
        // Second pass: build parent-child relationships
        messages.forEach(message => {
            const messageNode = messageMap.get(message.uuid);
            if (message.parent_message_uuid && messageMap.has(message.parent_message_uuid)) {
                const parent = messageMap.get(message.parent_message_uuid);
                parent.children.push(messageNode);
            } else {
                rootMessages.push(messageNode);
            }
        });
        
        return {
            messageMap,
            rootMessages
        };
    }

    /**
     * Gets all branch information including branch points
     * @param {Object} tree - Tree structure from buildConversationTree
     * @returns {Array} Array of branch information
     */
    function getAllBranchInfo(tree) {
        const branches = [];
        let branchCounter = 0;
        
        function traverseBranches(node, currentPath = [], branchStartIndex = 0) {
            const newPath = [...currentPath, node];
            
            if (node.children.length === 0) {
                // Leaf node - this is a complete branch
                branchCounter++;
                branches.push({
                    branchId: node.uuid,
                    branchIndex: branchCounter,
                    fullPath: newPath,
                    branchStartIndex: branchStartIndex, // Index in fullPath where this branch starts
                    isMainBranch: branchStartIndex === 0
                });
            } else if (node.children.length === 1) {
                // Single child - continue same branch
                traverseBranches(node.children[0], newPath, branchStartIndex);
            } else {
                // Multiple children - branch point
                node.children.forEach((child, childIndex) => {
                    // For first child, continue current branch
                    // For other children, start new branches from this point
                    const newBranchStart = childIndex === 0 ? branchStartIndex : newPath.length;
                    traverseBranches(child, newPath, newBranchStart);
                });
            }
        }
        
        tree.rootMessages.forEach(root => {
            traverseBranches(root, [], 0);
        });
        
        return branches;
    }

    // =============================================
    // TEXT PROCESSING FUNCTIONS
    // =============================================

    /**
     * Recursively extracts text content from nested content structures
     * @param {Object} content - Content object to process
     * @returns {Promise<Array<string>>} Array of text pieces found
     */
    async function getTextFromContent(content) {
        let textPieces = [];

        if (content.text) {
            textPieces.push(content.text);
        }
        if (content.input) {
            textPieces.push(JSON.stringify(content.input));
        }
        if (content.content) {
            if (Array.isArray(content.content)) {
                for (const nestedContent of content.content) {
                    textPieces = textPieces.concat(await getTextFromContent(nestedContent));
                }
            } else if (typeof content.content === 'object') {
                textPieces = textPieces.concat(await getTextFromContent(content.content));
            }
        }
        return textPieces;
    }

    // =============================================
    // ARTIFACT PROCESSING FUNCTIONS
    // =============================================

    /**
     * Extracts artifacts from messages, respecting branch boundaries
     * @param {Array} branchPath - Full path from root to leaf
     * @param {number} branchStartIndex - Index where this branch starts (for split branches)
     * @param {string} branchId - Unique identifier for this branch
     * @param {boolean} isMainBranch - Whether this is the main branch
     * @returns {Object} {ownArtifacts: Map, inheritedStates: Map}
     */
    function extractArtifacts(branchPath, branchStartIndex, branchId, isMainBranch) {
        const ownArtifacts = new Map(); // Artifacts created/modified in this branch
        const inheritedStates = new Map(); // Final states of artifacts from parent branch
        
        // For non-main branches, first collect inherited states from parent path
        if (!isMainBranch && branchStartIndex > 0) {
            const parentPath = branchPath.slice(0, branchStartIndex);
            const parentArtifacts = new Map();
            
            // Extract artifacts from parent path
            parentPath.forEach((message, messageIndex) => {
                message.content.forEach(content => {
                    if (content.type === 'tool_use' && content.name === 'artifacts' && content.input) {
                        const input = content.input;
                        const artifactId = input.id;
                        
                        if (!parentArtifacts.has(artifactId)) {
                            parentArtifacts.set(artifactId, []);
                        }
                        
                        const versions = parentArtifacts.get(artifactId);
                        versions.push({
                            type: input.type,
                            title: input.title || `Artifact ${artifactId}`,
                            command: input.command,
                            content: input.content || '',
                            new_str: input.new_str || '',
                            old_str: input.old_str || '',
                            language: input.language || '',
                            timestamp_created_at: message.created_at,
                            timestamp_updated_at: message.updated_at
                        });
                    }
                });
            });
            
            // Build final states from parent artifacts
            parentArtifacts.forEach((versions, artifactId) => {
                let currentContent = '';
                let currentTitle = `Artifact ${artifactId}`;
                let currentType = undefined;
                let currentLanguage = '';
                let versionCount = 0;
                
                versions.forEach(version => {
                    versionCount++;
                    switch (version.command) {
                        case 'create':
                            currentContent = version.content;
                            currentTitle = version.title;
                            currentType = version.type;
                            currentLanguage = version.language;
                            break;
                        case 'rewrite':
                            currentContent = version.content;
                            currentTitle = version.title;
                            // Keep type and language from create
                            break;
                        case 'update':
                            const updateResult = applyUpdate(currentContent, version.old_str, version.new_str);
                            currentContent = updateResult.content;
                            break;
                    }
                });
                
                inheritedStates.set(artifactId, {
                    content: currentContent,
                    title: currentTitle,
                    type: currentType,
                    language: currentLanguage,
                    versionCount: versionCount
                });
            });
        }
        
        // Now extract artifacts from this branch only (starting from branchStartIndex)
        const branchMessages = branchPath.slice(branchStartIndex);
        
        branchMessages.forEach((message, relativeIndex) => {
            message.content.forEach(content => {
                if (content.type === 'tool_use' && content.name === 'artifacts' && content.input) {
                    const input = content.input;
                    const artifactId = input.id;

                    if (!ownArtifacts.has(artifactId)) {
                        ownArtifacts.set(artifactId, []);
                    }

                    const versions = ownArtifacts.get(artifactId);
                    
                    // Calculate version number based on inherited versions
                    let versionNumber;
                    if (isMainBranch) {
                        // Main branch: start from 1
                        versionNumber = versions.length + 1;
                    } else {
                        // Split branch: continue from parent version count
                        const inheritedCount = inheritedStates.has(artifactId) ? inheritedStates.get(artifactId).versionCount : 0;
                        versionNumber = inheritedCount + versions.length + 1;
                    }
                    
                    versions.push({
                        messageUuid: message.uuid,
                        messageText: message.text,
                        version: versionNumber,
                        content_start_timestamp: content.start_timestamp,
                        content_stop_timestamp: content.stop_timestamp,
                        content_type: content.type,
                        type: input.type,
                        title: input.title || `Artifact ${artifactId}`,
                        command: input.command,
                        old_str: input.old_str || '',
                        new_str: input.new_str || '',
                        content: input.content || '',
                        uuid: input.version_uuid,
                        language: input.language || '',
                        messageIndex: branchStartIndex + relativeIndex,
                        stop_reason: message.stop_reason,
                        timestamp_created_at: message.created_at,
                        timestamp_updated_at: message.updated_at,
                        branchId: branchId
                    });
                }
            });
        });

        return { ownArtifacts, inheritedStates };
    }

    /**
     * Applies update command to previous content by replacing old_str with new_str
     * @param {string} previousContent - Content before update
     * @param {string} oldStr - String to be replaced
     * @param {string} newStr - String to replace with
     * @returns {Object} {success: boolean, content: string, info: string}
     */
    function applyUpdate(previousContent, oldStr, newStr) {
        if (!previousContent || !oldStr) {
            return {
                success: false,
                content: previousContent || '',
                info: 'Cannot apply update: missing previousContent or oldStr'
            };
        }

        // Apply the string replacement
        const updatedContent = previousContent.replace(oldStr, newStr);

        if (updatedContent === previousContent) {
            // Try to find similar strings for debugging
            const lines = previousContent.split('\n');
            const oldLines = oldStr.split('\n');
            let debugInfo = 'Update did not change content - old string not found';
            
            if (oldLines.length > 0) {
                const firstOldLine = oldLines[0].trim();
                const foundLine = lines.find(line => line.includes(firstOldLine));
                if (foundLine) {
                    debugInfo += ` | Found similar line: "${foundLine.trim()}"`;
                }
            }
            
            return {
                success: false,
                content: previousContent,
                info: debugInfo
            };
        }

        return {
            success: true,
            content: updatedContent,
            info: `Successfully applied update`
        };
    }

    /**
     * Creates change description for artifact commands
     */
    function createChangeDescription(version, oldContent, currentContent) {
        switch (version.command) {
            case 'create':
                return 'Created';
            case 'rewrite':
                return 'Rewritten';
            case 'update':
                const oldPreview = version.old_str ? version.old_str.substring(0, 50).replace(/\n/g, '\\n') + '...' : '';
                const newPreview = version.new_str ? version.new_str.substring(0, 50).replace(/\n/g, '\\n') + '...' : '';
                let changeDescription = `Updated: "${oldPreview}" → "${newPreview}"`;
                
                // Add information about character count changes
                const lengthDiff = currentContent.length - oldContent.length;
                if (lengthDiff > 0) {
                    changeDescription += ` (+${lengthDiff} chars)`;
                } else if (lengthDiff < 0) {
                    changeDescription += ` (${lengthDiff} chars)`;
                }
                
                return changeDescription;
            default:
                return `Unknown command: ${version.command}`;
        }
    }

    /**
     * Builds complete artifact versions for a specific branch
     * @param {Map} ownArtifacts - Artifacts created/modified in this branch
     * @param {Map} inheritedStates - Final states from parent branch
     * @param {string} branchId - Branch identifier
     * @param {boolean} isMainBranch - Whether this is the main branch
     * @returns {Map} Map of artifact ID to processed versions with full content
     */
    function buildArtifactVersions(ownArtifacts, inheritedStates, branchId, isMainBranch) {
        const processedArtifacts = new Map();

        ownArtifacts.forEach((versions, artifactId) => {
            const processedVersions = [];
            
            // Start with inherited content if this is a branch
            let currentContent = '';
            let currentTitle = `Artifact ${artifactId}`;
            let currentType = undefined;
            let currentLanguage = '';
            
            if (!isMainBranch && inheritedStates.has(artifactId)) {
                const inherited = inheritedStates.get(artifactId);
                currentContent = inherited.content;
                currentTitle = inherited.title;
                currentType = inherited.type;
                currentLanguage = inherited.language;
            }

            versions.forEach((version, index) => {
                let updateInfo = '';
                let versionStartContent = currentContent;
                const oldContent = currentContent;
                
                switch (version.command) {
                    case 'create':
                        currentContent = version.content;
                        currentTitle = version.title;
                        currentType = version.type;
                        currentLanguage = version.language;
                        break;
                    case 'rewrite':
                        currentContent = version.content;
                        currentTitle = version.title;
                        // Keep type and language from create
                        break;
                    case 'update':
                        const updateResult = applyUpdate(currentContent, version.old_str, version.new_str);
                        
                        currentContent = updateResult.content;
                        updateInfo = updateResult.info;
                        
                        if (!updateResult.success) {
                            updateInfo = `[WARNING: ${updateResult.info}]`;
                        }
                        break;
                    default:
                        console.warn(`Unknown command: ${version.command}`);
                        break;
                }

                const changeDescription = createChangeDescription(version, oldContent, currentContent);

                processedVersions.push({
                    ...version,
                    fullContent: currentContent,
                    changeDescription: updateInfo ? changeDescription + ` ${updateInfo}` : changeDescription,
                    updateInfo: updateInfo,
                    branchId: branchId,
                    isMainBranch: isMainBranch,
                    inheritedContent: versionStartContent,
                    finalType: currentType,
                    finalLanguage: currentLanguage
                });
            });

            processedArtifacts.set(artifactId, processedVersions);
        });

        return processedArtifacts;
    }

    /**
     * Extracts and processes all artifacts from all branches with proper inheritance
     * @param {Object} conversationData - Complete conversation data
     * @returns {Object} {branchArtifacts: Map, branchInfo: Array}
     */
    function extractAllArtifacts(conversationData) {
        // Build conversation tree
        const tree = buildConversationTree(conversationData.chat_messages);
        const branches = getAllBranchInfo(tree);
        
        console.log(`Found ${branches.length} conversation branches`);
        
        const branchArtifacts = new Map(); // branchId -> Map<artifactId, versions>
        const branchInfo = [];
        
        branches.forEach((branch) => {
            const { ownArtifacts, inheritedStates } = extractArtifacts(
                branch.fullPath, 
                branch.branchStartIndex, 
                branch.branchId, 
                branch.isMainBranch
            );
            
            if (ownArtifacts.size > 0) {
                // Process artifacts for this branch
                const processedArtifacts = buildArtifactVersions(
                    ownArtifacts, 
                    inheritedStates, 
                    branch.branchId, 
                    branch.isMainBranch
                );
                branchArtifacts.set(branch.branchId, processedArtifacts);
                
                const leafMessage = branch.fullPath[branch.fullPath.length - 1];
                branchInfo.push({
                    branchId: branch.branchId,
                    branchIndex: branch.branchIndex,
                    messageCount: branch.fullPath.length,
                    branchMessageCount: branch.fullPath.length - branch.branchStartIndex,
                    artifactCount: ownArtifacts.size,
                    inheritedArtifactCount: inheritedStates.size,
                    lastMessageTime: leafMessage.created_at,
                    lastMessageUuid: leafMessage.uuid,
                    isMainBranch: branch.isMainBranch,
                    branchStartIndex: branch.branchStartIndex
                });
            }
        });
        
        return {
            branchArtifacts,
            branchInfo
        };
    }

    // =============================================
    // VERSION TRACKING FUNCTIONS
    // =============================================

    /**
     * Builds version information for messages with alternatives (same parent)
     * @param {Array} messages - Array of chat messages
     * @returns {Map} Map of message UUID to version info {version, total}
     */
    function buildVersionInfo(messages) {
        const versionInfo = new Map();
        
        // Group messages by parent_message_uuid
        const parentGroups = new Map();
        
        messages.forEach(message => {
            if (message.parent_message_uuid) {
                if (!parentGroups.has(message.parent_message_uuid)) {
                    parentGroups.set(message.parent_message_uuid, []);
                }
                parentGroups.get(message.parent_message_uuid).push(message);
            }
        });
        
        // Process groups with more than one message (alternatives)
        parentGroups.forEach((siblings, parentUuid) => {
            if (siblings.length > 1) {
                // Sort by created_at to determine version numbers
                siblings.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
                
                siblings.forEach((message, index) => {
                    versionInfo.set(message.uuid, {
                        version: index + 1,
                        total: siblings.length
                    });
                });
            }
        });
        
        return versionInfo;
    }

    // =============================================
    // EXPORT FUNCTIONS
    // =============================================

    /**
     * Finds the artifact content for a specific artifact ID and message
     */
    function findArtifactContent(artifactId, messageUuid, branchArtifacts, includeMode = 'final') {
        let allVersionsOfArtifact = [];
        let messageVersion = null;
        
        // Collect all versions of this artifact from all branches
        for (const [branchId, artifactsMap] of branchArtifacts) {
            if (artifactsMap.has(artifactId)) {
                const versions = artifactsMap.get(artifactId);
                allVersionsOfArtifact = allVersionsOfArtifact.concat(versions);
                
                // Also find if current message has any version of this artifact
                const msgVersion = versions.find(v => v.messageUuid === messageUuid);
                if (msgVersion) {
                    messageVersion = msgVersion;
                }
            }
        }
        
        if (allVersionsOfArtifact.length === 0) {
            return null;
        }
        
        if (includeMode === 'all') {
            // Show the version that was created in this specific message
            return messageVersion;
        } else if (includeMode === 'final') {
            // Sort all versions by creation time to find the truly latest one
            allVersionsOfArtifact.sort((a, b) => {
                const timeA = new Date(a.content_stop_timestamp || a.timestamp_created_at);
                const timeB = new Date(b.content_stop_timestamp || b.timestamp_created_at);
                return timeA - timeB;
            });
            
            const globalLatestVersion = allVersionsOfArtifact[allVersionsOfArtifact.length - 1];
            
            // Show artifact ONLY if this message contains the globally final version
            if (globalLatestVersion.messageUuid === messageUuid) {
                return globalLatestVersion;
            }
            
            return null;
        }
        
        return null;
    }

    /**
     * Generates markdown content for the entire conversation
     * @param {Object} conversationData - Complete conversation data from API
     * @returns {string} Formatted markdown content
     */
    function generateConversationMarkdown(conversationData, includeArtifacts = 'none', branchArtifacts = null, branchInfo = null) {
        const settings = loadSettings();
        let markdown = '';

        // Header with conversation metadata
        markdown += `# ${conversationData.name}\n`;
        
        // Project info (if available)
        if (conversationData.project) {
            markdown += `*Project:* [${conversationData.project.name}] (https://claude.ai/project/${conversationData.project.uuid})\n`;
        }
        
        markdown += `*URL:* https://claude.ai/chat/${conversationData.uuid}\n`;
        
        // Format dates properly
        markdown += `*Created:* ${formatDate(conversationData.created_at)}\n`;
        markdown += `*Updated:* ${formatDate(conversationData.updated_at)}\n`;
        markdown += `*Exported:* ${formatDate(new Date())}\n`;
        
        if (conversationData.model) {
            markdown += `*Model:* \`${conversationData.model}\`\n`;
        }
        
        markdown += `\n`;

        // Build version info for messages with alternatives
        const versionInfo = buildVersionInfo(conversationData.chat_messages);

        // Process each message
        conversationData.chat_messages.forEach(message => {
            const role = message.sender === 'human' ? 'Human' : 'Claude';
            markdown += `__________\n\n`;
            markdown += `## ${role}\n`;
            markdown += `*UUID:* \`${message.uuid}\`\n`;
            markdown += `*Created:* ${formatDate(message.created_at)}\n`;
            
            // Add version info if this message has alternatives
            if (versionInfo.has(message.uuid)) {
                const info = versionInfo.get(message.uuid);
                markdown += `*Version:* ${info.version} of ${info.total}\n`;
            }
            
            markdown += `\n`;

            // Process message content
            message.content.forEach(content => {
                if (content.type === 'text') {
                    const processedText = processTextContent(content.text, settings.removeDoubleNewlinesFromConversation);
                    markdown += processedText + '\n\n';
                } else if (content.type === 'tool_use' && content.name === 'artifacts') {
                    const input = content.input;
                    if (input.title) {
                        markdown += `**Artifact Created:** ${input.title}\n`;
                    }
                    markdown += `*ID:* \`${input.id}\`\n`;
                    markdown += `*Command:* \`${input.command}\`\n`;
                    
                    // Add version, branch and timestamp info if available
                    if (branchArtifacts) {
                        // Find the specific version for this operation (by timestamp if available)
                        let artifactVersion = null;
                        
                        for (const [branchId, artifactsMap] of branchArtifacts) {
                            if (artifactsMap.has(input.id)) {
                                const versions = artifactsMap.get(input.id);
                                
                                if (content.stop_timestamp) {
                                    // Try to find by exact timestamp
                                    artifactVersion = versions.find(v => 
                                        v.messageUuid === message.uuid && 
                                        v.content_stop_timestamp === content.stop_timestamp
                                    );
                                }
                                
                                // Fallback: find any version in this message
                                if (!artifactVersion) {
                                    artifactVersion = versions.find(v => v.messageUuid === message.uuid);
                                }
                                
                                if (artifactVersion) break;
                            }
                        }
                        
                        if (artifactVersion) {
                            // Find branch info for proper branch label  
                            const branchData = branchInfo ? branchInfo.find(b => b.branchId === artifactVersion.branchId) : null;
                            let branchLabel;
                            
                            if (branchData) {
                                if (branchData.isMainBranch) {
                                    branchLabel = `branch${branchData.branchIndex} (main) (${artifactVersion.branchId.substring(0, 8)}...)`;
                                } else {
                                    branchLabel = `branch${branchData.branchIndex} (${artifactVersion.branchId.substring(0, 8)}...)`;
                                }
                            } else {
                                branchLabel = `unknown (${artifactVersion.branchId.substring(0, 8)}...)`;
                            }
                            
                            markdown += `*Version:* ${artifactVersion.version}\n`;
                            markdown += `*Branch:* ${branchLabel}\n`;
                            markdown += `*Created:* ${formatDate(artifactVersion.content_stop_timestamp || artifactVersion.timestamp_created_at)}\n`;
                            
                            // Show change description if available
                            if (artifactVersion.changeDescription) {
                                markdown += `*Change:* ${artifactVersion.changeDescription}\n`;
                            }
                        }
                    }
                    
                    // Include artifact content based on mode
                    if (includeArtifacts !== 'none' && branchArtifacts) {
                        const artifactContent = findArtifactContent(input.id, message.uuid, branchArtifacts, includeArtifacts);
                        if (artifactContent) {
                            markdown += `\n### Artifact Content\n\n`;
                            
                            // Determine the language for syntax highlighting
                            const language = getLanguageForHighlighting(artifactContent.finalType, artifactContent.finalLanguage);
                            
                            // Process artifact content based on settings
                            let processedArtifactContent = artifactContent.fullContent;
                            if (artifactContent.finalType === 'text/markdown' && settings.removeDoubleNewlinesFromMarkdown) {
                                processedArtifactContent = processArtifactContent(artifactContent.fullContent, artifactContent.finalType, true);
                            }
                            
                            markdown += '```' + language + '\n';
                            markdown += processedArtifactContent + '\n';
                            markdown += '```\n';
                        }
                    }
                    
                    markdown += `\n`;
                } else if (content.type === 'thinking') {
                    if (content.thinking) {
                        markdown += `*[Claude thinking...]*\n\n`;
                        markdown += `<details>\n<summary>Thinking process</summary>\n\n`;
                        
                        const processedThinking = processTextContent(content.thinking, settings.removeDoubleNewlinesFromConversation);
                        markdown += processedThinking + '\n\n';
                        markdown += `</details>\n\n`;
                    } else {
                        markdown += `*[Claude thinking...]*\n\n`;
                    }
                }
            });

            // Process files if present (files or files_v2)
            const attachedFiles = message.files_v2 || message.files || [];
            if (attachedFiles.length > 0) {
                attachedFiles.forEach(file => {
                    markdown += `**File:** ${file.file_name}\n`;
                    markdown += `*ID:* \`${file.file_uuid}\`\n`;
                    if (file.file_kind === 'image') {
                        markdown += `*Preview:* https://claude.ai${file.preview_url}\n`;
                    }
                    
                    markdown += `\n`;
                    
                });
            
            }

            // Process attachments if present
            if (message.attachments && message.attachments.length > 0) {
                message.attachments.forEach(attachment => {
                    markdown += `**Attachment:** ${attachment.file_name}\n`;
                    markdown += `*ID:* \`${attachment.id}\`\n\n`;
                    
                    if (!settings.excludeAttachments && attachment.extracted_content) {
                        markdown += `<details>\n\n`;
                        markdown += '```\n';
                        
                        const processedAttachment = processTextContent(attachment.extracted_content, settings.removeDoubleNewlinesFromConversation);
                        markdown += processedAttachment + '\n';
                        markdown += '```\n\n';
                        markdown += `</details>\n\n`;
                    }
                });
            }
        });

        return markdown;
    }

    /**
     * Formats artifact metadata as comments in the appropriate style
     * @param {Object} version - Version object with metadata
     * @param {string} artifactId - Artifact ID
     * @param {string} branchLabel - Branch label
     * @param {boolean} isMain - Whether this is the main branch
     * @returns {string} Formatted metadata comments
     */
    function formatArtifactMetadata(version, artifactId, branchLabel, isMain) {
        const settings = loadSettings();
        
        // Return empty string if metadata is disabled
        if (!settings.includeArtifactMetadata) {
            return '';
        }

        const metadataInfo = [
            `Artifact ID: ${artifactId}`,
            `Branch: ${branchLabel}${isMain ? ' (main)' : ''} (${version.branchId.substring(0, 8)}...)`,
            `Version: ${version.version}`,
            `Command: ${version.command}`,
            `UUID: ${version.uuid}`,
            `Created: ${formatDate(version.content_stop_timestamp)}`
        ];

        if (version.changeDescription) {
            metadataInfo.push(`Change: ${version.changeDescription}`);
        }
        
        if (version.updateInfo) {
            metadataInfo.push(`Update Info: ${version.updateInfo}`);
        }
        
        if (!isMain && version.inheritedContent && version.command === 'update') {
            metadataInfo.push(`Started from inherited content: ${version.inheritedContent.length} chars`);
        }

        // Special formatting for markdown files
        if (version.finalType === 'text/markdown') {
            let metadata = metadataInfo.map(info => `*${info}*`).join('\n') + '\n\n---\n';
            return metadata;
        }
        
        // For all other file types, use comments
        const commentStyle = getCommentStyle(version.finalType, version.finalLanguage);
        const { start, end } = commentStyle;
        
        let metadata = metadataInfo.map(info => `${start}${info}${end}`).join('\n') + '\n';
        
        // Add separator based on language
        const separators = {
            '// ': '\n// ---\n',
            '-- ': '\n-- ---\n', 
            '<!-- ': '\n<!-- --- -->\n'
        };
        
        metadata += separators[start] || '\n# ---\n';
        
        return metadata;
    }

    /**
     * Helper to count artifacts across branches
     */
    function countArtifacts(branchArtifacts) {
        return Array.from(branchArtifacts.values())
            .reduce((total, artifactsMap) => total + artifactsMap.size, 0);
    }

    /**
     * Exports conversation with artifacts (all versions or final versions only)
     * @param {boolean} finalVersionsOnly - If true, exports only final artifact versions
     */
    async function exportConversation(finalVersionsOnly = false) {
        try {
            showNotification('Fetching conversation data...', 'info');

            const conversationData = await getConversationData();
            const settings = loadSettings();

            // Extract and process artifacts from all branches
            const { branchArtifacts, branchInfo } = extractAllArtifacts(conversationData);

            let conversationMarkdown;
            let shouldExportSeparateFiles = false;

            // Determine behavior based on setting
            switch (settings.artifactExportMode) {
                case 'embed':
                    // Only embed in conversation file
                    const includeMode = finalVersionsOnly ? 'final' : 'all';
                    conversationMarkdown = generateConversationMarkdown(conversationData, includeMode, branchArtifacts, branchInfo);
                    shouldExportSeparateFiles = false;
                    break;
                    
                case 'files':
                    // Only separate files, no embedding
                    conversationMarkdown = generateConversationMarkdown(conversationData, 'none', branchArtifacts, branchInfo);
                    shouldExportSeparateFiles = true;
                    break;
                    
                case 'both':
                    // Both embed and separate files
                    const includeModeBoth = finalVersionsOnly ? 'final' : 'all';
                    conversationMarkdown = generateConversationMarkdown(conversationData, includeModeBoth, branchArtifacts, branchInfo);
                    shouldExportSeparateFiles = true;
                    break;
            }
            
            // Always download conversation file
            const conversationFilename = generateConversationFilename(conversationData);
            downloadFile(conversationFilename, conversationMarkdown);

            // Export separate artifact files if needed
            if (shouldExportSeparateFiles && branchArtifacts.size > 0) {
                let totalExported = 0;

                // Export artifacts for each branch
                branchArtifacts.forEach((artifactsMap, branchId) => {
                    const branchData = branchInfo.find(b => b.branchId === branchId);
                    const branchLabel = branchData ? branchData.branchIndex.toString() : 'unknown';
                    const isMain = branchData ? branchData.isMainBranch : false;
                    
                    artifactsMap.forEach((versions, artifactId) => {
                        const versionsToExport = finalVersionsOnly ?
                            [versions[versions.length - 1]] : // Only last version
                            versions; // All versions

                        versionsToExport.forEach(version => {
                            const filename = generateArtifactFilename(version, conversationData, branchLabel, isMain, artifactId);
                            
                            // Format metadata as comments
                            const metadata = formatArtifactMetadata(version, artifactId, branchLabel, isMain);
                            
                            // Process artifact content based on settings
                            let processedContent = version.fullContent;
                            if (version.finalType === 'text/markdown' && settings.removeDoubleNewlinesFromMarkdown) {
                                processedContent = processArtifactContent(version.fullContent, version.finalType, true);
                            }
                            
                            // Combine metadata and content
                            const content = metadata ? metadata + '\n' + processedContent : processedContent;

                            downloadFile(filename, content);
                            totalExported++;
                        });
                    });
                });

                const mode = finalVersionsOnly ? 'final versions' : 'all versions';
                const modeText = settings.artifactExportMode === 'both' ? 'embedded + separate files' : 'separate files';
                showNotification(`Export completed! Downloaded conversation + ${totalExported} artifacts as ${modeText} (${mode})`, 'success');
            } else {
                // No separate files exported
                const artifactCount = countArtifacts(branchArtifacts);
                
                if (artifactCount > 0) {
                    const mode = finalVersionsOnly ? 'final versions' : 'all versions';
                    showNotification(`Export completed! Downloaded conversation with ${artifactCount} embedded artifacts (${mode})`, 'success');
                } else {
                    showNotification('Export completed! No artifacts found in conversation', 'info');
                }
            }

        } catch (error) {
            console.error('Export failed:', error);
            showNotification(`Export failed: ${error.message}`, 'error');
        }
    }

    /**
     * Exports only the conversation, with optional artifact inclusion based on settings
     */
    async function exportConversationOnly() {
        try {
            showNotification('Fetching conversation data...', 'info');

            const conversationData = await getConversationData();
            const settings = loadSettings();

            // Extract artifacts to get metadata for conversation display
            const { branchArtifacts, branchInfo } = extractAllArtifacts(conversationData);
            
            const includeArtifacts = settings.includeArtifactsInConversationOnly ? 'all' : 'none';
            const conversationMarkdown = generateConversationMarkdown(conversationData, includeArtifacts, branchArtifacts, branchInfo);
            
            const conversationFilename = generateConversationFilename(conversationData);
            downloadFile(conversationFilename, conversationMarkdown);

            if (settings.includeArtifactsInConversationOnly && branchArtifacts.size > 0) {
                const artifactCount = countArtifacts(branchArtifacts);
                showNotification(`Conversation exported with ${artifactCount} embedded artifacts!`, 'success');
            } else {
                showNotification('Conversation exported successfully!', 'success');
            }

        } catch (error) {
            console.error('Export failed:', error);
            showNotification(`Export failed: ${error.message}`, 'error');
        }
    }

    // =============================================
    // INITIALIZATION
    // =============================================

    /**
     * Initializes the script and registers menu commands
     */
    function init() {
        console.log('[Claude API Exporter] Initializing...');

        // Register menu commands
        GM_registerMenuCommand('⚙️ Settings', showSettingsUI);
        GM_registerMenuCommand('📄 Export Conversation Only', exportConversationOnly);
        GM_registerMenuCommand('📁 Export Conversation + Final Artifacts', () => exportConversation(true));
        GM_registerMenuCommand('📁 Export Conversation + All Artifacts', () => exportConversation(false));
    }

    // Start when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();