Lyra's Exporter Fetch

Lyra's Exporter - AI对话导出器的配套脚本,专业的AI对话导出器 - 支持Claude、Gemini、NotebookLM等多平台,轻松管理数百个对话窗口,导出完整时间线、附加图片、思考过程、附件、工具调用和Artifacts。

当前为 2025-10-06 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Lyra's Exporter Fetch
// @name:en         Lyra's Exporter Fetch
// @namespace    userscript://lyra-conversation-exporter
// @version      6.3
// @description  Lyra's Exporter - AI对话导出器的配套脚本,专业的AI对话导出器 - 支持Claude、Gemini、NotebookLM等多平台,轻松管理数百个对话窗口,导出完整时间线、附加图片、思考过程、附件、工具调用和Artifacts。
// @description:en The essential companion script for Lyra's Exporter, designed for unified management and export of your conversation histories across multiple platforms, including Claude, Gemini, NotebookLM, and more.
// @homepage     https://github.com/Yalums/lyra-exporter/
// @supportURL   https://github.com/Yalums/lyra-exporter/issues
// @author       Yalums
// @match        https://pro.easychat.top/*
// @match        https://claude.ai/*
// @match        https://gemini.google.com/app/*
// @match        https://notebooklm.google.com/*
// @match        https://aistudio.google.com/*
// @include      *://gemini.google.com/*
// @include      *://notebooklm.google.com/*
// @include      *://aistudio.google.com/*
// @run-at       document-start
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @license      GNU General Public License v3.0
// ==/UserScript==

(function() {
    'use strict';
    // 防止重复初始化
    if (window.lyraFetchInitialized) {
        return;
    }
    window.lyraFetchInitialized = true;
    // ===== i18n 国际化系统 =====
    const i18n = {
        languages: {
            'zh': {
                name: '中文',
                short: '简体中文',
                translations: {
                    loading: '加载中...',
                    exporting: '导出中...',
                    processing: '处理中...',
                    preparing: '准备中...',
                    scanning: '扫描中...',
                    scanComplete: '扫描完成,正在提取...',
                    scrollToTop: '正在滚动到顶部...',
                    startScanning: '开始向下扫描...',
                    exportSuccess: '导出成功!',
                    exportCancelled: '导出已取消。',
                    operationCancelled: '操作已取消。',
                    noContent: '没有可导出的对话内容。',
                    saveConversation: '导出JSON',
                    viewOnline: '预览对话',
                    exportCurrentJSON: '导出JSON',
                    exportAllConversations: '导出所有',
                    branchMode: '分支模式',
                    includeImages: '包含图片',
                    claudeTitle: 'Claude 会话导出',
                    geminiTitle: 'Gemini 会话导出',
                    notebooklmTitle: 'NotebookLM',
                    aistudioTitle: 'AI Studio 导出',
                    defaultTitle: 'AI 导出器',
                    enterTitle: '请输入对话标题:',
                    defaultChatTitle: '对话',
                    untitledChat: '未命名对话',
                    uuidNotFound: '未找到对话UUID!',
                    userIdNotFound: '未能自动获取用户ID,请手动输入或刷新页面重试',
                    fetchFailed: '获取对话数据失败',
                    exportFailed: '导出失败: ',
                    loadFailed: '加载失败: ',
                    cannotOpenExporter: '无法打开 Lyra Exporter,请检查',
                    connectionTimeout: '连接超时,请检查 Lyra Exporter 是否正常运行',
                    gettingConversation: '获取对话',
                    withImages: ' (处理图片中...)',
                    foundItems: '已发现',
                    items: '条',
                    successExported: '成功导出',
                    conversations: '个对话!',
                    manualUserId: '手动设置ID',
                    enterUserId: '请输入您的组织ID (settings/account):',
                    userIdSaved: '用户ID已保存!',
                    autoDetecting: '自动检测中...',
                    detectedUserId: '检测到用户: '
                }
            },
            'en': {
                name: 'English',
                short: 'English',
                translations: {
                    loading: 'Loading...',
                    exporting: 'Exporting...',
                    processing: 'Processing...',
                    preparing: 'Preparing...',
                    scanning: 'Scanning...',
                    scanComplete: 'Scan complete, extracting...',
                    scrollToTop: 'Scrolling to top...',
                    startScanning: 'Starting to scan...',
                    exportSuccess: 'Export successful!',
                    exportCancelled: 'Export cancelled.',
                    operationCancelled: 'Operation cancelled.',
                    noContent: 'No conversation content to export.',
                    saveConversation: 'Save File',
                    viewOnline: 'Preview',
                    exportCurrentJSON: 'Export JSON',
                    exportAllConversations: 'Export All',
                    branchMode: 'Branch',
                    includeImages: 'Images',
                    claudeTitle: 'Claude Exporter',
                    geminiTitle: 'Gemini Exporter',
                    notebooklmTitle: 'NotebookLM',
                    aistudioTitle: 'AI Studio',
                    defaultTitle: 'Exporter',
                    enterTitle: 'Enter conversation title:',
                    defaultChatTitle: 'Chat',
                    untitledChat: 'Untitled Chat',
                    uuidNotFound: 'UUID not found!',
                    userIdNotFound: 'Unable to auto-detect user ID, please enter manually or refresh the page',
                    fetchFailed: 'Failed to fetch conversation data',
                    exportFailed: 'Export failed: ',
                    loadFailed: 'Load failed: ',
                    cannotOpenExporter: 'Cannot open Lyra Exporter, please check popup blocker',
                    connectionTimeout: 'Connection timeout, please check if Lyra Exporter is running',
                    gettingConversation: 'Getting conversation',
                    withImages: ' (processing images...)',
                    foundItems: 'Found',
                    items: 'items',
                    successExported: 'Successfully exported',
                    conversations: 'conversations!',
                    manualUserId: 'Input Account ID',
                    enterUserId: 'Organization ID can be found in settings/account',
                    userIdSaved: 'User ID saved!',
                    autoDetecting: 'Auto-detecting...',
                    detectedUserId: 'Account '
                }
            }
        },
        currentLang: null,
        init() {
            const savedLang = localStorage.getItem('lyraExporterLanguage');
            if (savedLang && this.languages[savedLang]) {
                this.currentLang = savedLang;
            } else {
                const browserLang = navigator.language || navigator.userLanguage;
                this.currentLang = browserLang.startsWith('zh') ? 'zh' : 'en';
            }
        },
        t(key) {
            const translations = this.languages[this.currentLang]?.translations;
            return translations?.[key] || key;
        },
        setLanguage(lang) {
            if (this.languages[lang]) {
                this.currentLang = lang;
                localStorage.setItem('lyraExporterLanguage', lang);
                return true;
            }
            return false;
        },
        getLanguage() {
            return this.currentLang;
        },
        getLanguageShort() {
            return this.languages[this.currentLang]?.short || this.currentLang.toUpperCase();
        }
    };
    i18n.init();
    // ===== 平台检测 =====
    let currentPlatform = '';
    const hostname = window.location.hostname;
    if (hostname.includes('easychat.top') || hostname.includes('claude.ai')) {
        currentPlatform = 'claude';
    } else if (hostname.includes('gemini.google.com')) {
        currentPlatform = 'gemini';
    } else if (hostname.includes('notebooklm.google.com')) {
        currentPlatform = 'notebooklm';
    } else if (hostname.includes('aistudio.google.com')) {
        currentPlatform = 'aistudio';
    }
    // ===== 全局变量 =====
    const LYRA_EXPORTER_URL = 'https://yalums.github.io/lyra-exporter/';
    const LYRA_EXPORTER_ORIGIN = 'https://yalums.github.io';
    let capturedUserId = '';
    let isPanelCollapsed = localStorage.getItem('lyraExporterCollapsed') === 'true';
    let includeImages = localStorage.getItem('lyraIncludeImages') === 'true';
    const CONTROL_ID = "lyra-universal-exporter-container";
    const TOGGLE_ID = "lyra-toggle-button";
    const TREE_SWITCH_ID = "lyra-tree-mode";
    const IMAGE_SWITCH_ID = "lyra-image-mode";
    const LANG_SWITCH_ID = "lyra-lang-switch";
    const MANUAL_ID_BTN = "lyra-manual-id-btn";
    let panelInjected = false;
    const SCROLL_DELAY_MS = 250;
    const SCROLL_TOP_WAIT_MS = 1000;
    let collectedData = new Map();
    // ===== Claude用户ID捕获增强版 =====
    if (currentPlatform === 'claude') {
        const savedUserId = localStorage.getItem('lyraClaudeUserId');
        if (savedUserId) {
            capturedUserId = savedUserId;
        }
        // 方法1:解决沙箱隔离问题
        const interceptorScript = document.createElement('script');
        interceptorScript.textContent = `
            (function() {
                const patterns = [
                    /api\\/organizations\\/([a-zA-Z0-9-]+)/,
                    /api\\/accounts\\/([a-zA-Z0-9-]+)/,
                    /organizations\\/([a-zA-Z0-9-]+)\\/chat/,
                    /user\\/([a-zA-Z0-9-]+)/
                ];
                function captureUserId(url) {
                    for (const pattern of patterns) {
                        const match = url.match(pattern);
                        if (match && match[1]) {
                            const userId = match[1];
                            localStorage.setItem('lyraClaudeUserId', userId);
                            window.dispatchEvent(new CustomEvent('lyraUserIdCaptured', {
                                detail: { userId: userId }
                            }));
                            return userId;
                        }
                    }
                    return null;
                }
                const originalXHROpen = XMLHttpRequest.prototype.open;
                XMLHttpRequest.prototype.open = function(method, url) {
                    captureUserId(url);
                    return originalXHROpen.apply(this, arguments);
                };
                const originalFetch = window.fetch;
                window.fetch = function(resource, options) {
                    const url = typeof resource === 'string' ? resource : (resource.url || '');
                    if (url) {
                        captureUserId(url);
                    }
                    return originalFetch.apply(this, arguments);
                };
            })();
        `;
        (document.head || document.documentElement).appendChild(interceptorScript);
        interceptorScript.remove();
        // 监听来自页面的用户ID捕获事件
        window.addEventListener('lyraUserIdCaptured', (event) => {
            const userId = event.detail.userId;
            if (userId) {
                capturedUserId = userId;
            }
        });
        // 方法2:定期检查 localStorage(作为备用)
        const checkLocalStorage = () => {
            const storedId = localStorage.getItem('lyraClaudeUserId');
            if (storedId && storedId !== capturedUserId) {
                capturedUserId = storedId;
            }
        };
        setInterval(checkLocalStorage, 2000);
        // 方法3:DOM加载后尝试从页面提取
        document.addEventListener('DOMContentLoaded', () => {
            setTimeout(() => {
                if (!capturedUserId) {
                    checkLocalStorage();
                    const scripts = document.querySelectorAll('script');
                    scripts.forEach(script => {
                        const content = script.textContent;
                        if (content.includes('organization') || content.includes('account')) {
                            const match = content.match(/["']([a-zA-Z0-9-]{36})["']/);
                            if (match) {
                                capturedUserId = match[1];
                                localStorage.setItem('lyraClaudeUserId', capturedUserId);
                            }
                        }
                    });
                }
            }, 2000);
        });
        // 定期检查状态
        setInterval(() => {
            if (!capturedUserId) {
                checkLocalStorage();
            }
        }, 5000);
    }
    // ===== 手动输入用户ID功能 =====
    function promptForUserId() {
        const currentId = capturedUserId || localStorage.getItem('lyraClaudeUserId') || '';
        const newId = prompt(i18n.t('enterUserId'), currentId);
        if (newId && newId.trim()) {
            capturedUserId = newId.trim();
            localStorage.setItem('lyraClaudeUserId', capturedUserId);
            alert(i18n.t('userIdSaved'));
            const panel = document.getElementById(CONTROL_ID);
            if (panel) {
                panel.remove();
                panelInjected = false;
                createFloatingPanel();
            }
        }
    }
    // ===== 获取用户ID(带备用方案)=====
    async function ensureUserId() {
        if (capturedUserId) {
            return capturedUserId;
        }
        const savedId = localStorage.getItem('lyraClaudeUserId');
        if (savedId) {
            capturedUserId = savedId;
            return capturedUserId;
        }
        await sleep(1000);
        if (capturedUserId) {
            return capturedUserId;
        }
        const result = confirm(i18n.t('userIdNotFound') + '\n\n' + '是否手动输入组织ID?');
        if (result) {
            promptForUserId();
            return capturedUserId;
        }
        return null;
    }
    // ===== TrustedTypes处理 =====
    let trustedPolicy;
    if (typeof window.trustedTypes !== 'undefined' && window.trustedTypes.createPolicy) {
        try {
            trustedPolicy = window.trustedTypes.createPolicy('lyra-exporter-policy', {
                createHTML: (input) => input
            });
        } catch (e) {
            try {
                trustedPolicy = window.trustedTypes.defaultPolicy ||
                               window.trustedTypes.createPolicy('default', {
                                   createHTML: (input) => input
                               });
            } catch (e2) {
                // TrustedTypes not available
            }
        }
    }
    function safeSetInnerHTML(element, html) {
        if (trustedPolicy) {
            element.innerHTML = trustedPolicy.createHTML(html);
        } else {
            element.innerHTML = html;
        }
    }
    // ===== 样式注入 =====
    function injectCustomStyle() {
        let primaryColor = '#1a73e8';
        let buttonColor = '#1a73e8';
        let buttonHoverColor = '#1765c2';
        switch(currentPlatform) {
            case 'claude':
                primaryColor = '#d97706';
                buttonColor = '#EA580C';
                buttonHoverColor = '#DC2626';
                break;
            case 'notebooklm':
                primaryColor = '#374151';
                buttonColor = '#4B5563';
                buttonHoverColor = '#1F2937';
                break;
        }
        const styleContent = `
            #${CONTROL_ID} {
    position: fixed !important;
    right: ${currentPlatform === 'claude' ? '20px' : currentPlatform === 'gemini' ? '12px' : '12px'} !important;
    bottom: ${currentPlatform === 'claude' ? '80px' : currentPlatform === 'gemini' ? '120px' : '100px'} !important;  /* Claude往下移,Gemini往上移 */
    display: flex !important;
    flex-direction: column !important;
    gap: 6px !important;
    z-index: 2147483647 !important;
    transition: all 0.3s ease !important;
    background: white !important;
    border-radius: 10px !important;
    box-shadow: 0 3px 12px rgba(0,0,0,0.15) !important;
    padding: 8px !important;
    border: 1px solid ${currentPlatform === 'notebooklm' ? '#9CA3AF' : currentPlatform === 'claude' ? 'rgba(217, 119, 6, 0.3)' : '#e0e0e0'} !important;
    width: auto !important;
    min-width: ${currentPlatform === 'claude' ? '160px' : currentPlatform === 'gemini' ? '120px' : '140px'} !important;  /* Claude恢复原宽度,Gemini继续缩短 */
    max-width: ${currentPlatform === 'claude' ? '150px' : currentPlatform === 'gemini' ? '120px' : '120px'} !important;  /* 根据平台调整宽度 */
    font-family: 'Google Sans', Roboto, Arial, sans-serif !important;
    color: #3c4043 !important;
}
            #${CONTROL_ID}.collapsed .lyra-main-controls { display: none !important; }
            #${CONTROL_ID}.collapsed {
    padding: ${currentPlatform === 'claude' ? '5px' : currentPlatform === 'gemini' ? '3px' : '4px'} !important;
    width: ${currentPlatform === 'claude' ? '36px' : currentPlatform === 'gemini' ? '24px' : '28px'} !important;  /* Claude变大,Gemini变小 */
    height: ${currentPlatform === 'claude' ? '36px' : currentPlatform === 'gemini' ? '24px' : '28px'} !important;
    min-width: ${currentPlatform === 'claude' ? '36px' : currentPlatform === 'gemini' ? '24px' : '28px'} !important;
    justify-content: center !important;
    align-items: center !important;
    overflow: hidden !important;
}
            #${TOGGLE_ID} {
    position: absolute !important;
    left: ${currentPlatform === 'claude' ? '-22px' : '-18px'} !important;  /* Claude的按钮往左移 */
    top: 50% !important;
    transform: translateY(-50%) !important;
    width: ${currentPlatform === 'claude' ? '22px' : currentPlatform === 'gemini' ? '18px' : '20px'} !important;
    height: ${currentPlatform === 'claude' ? '22px' : currentPlatform === 'gemini' ? '18px' : '20px'} !important;
    border-radius: 50% !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    background: #f1f3f4 !important;
    color: #2C84DB !important;
    cursor: pointer !important;
    border: 1px solid #dadce0 !important;
    transition: all 0.3s !important;
    box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important;
}

#${CONTROL_ID}.collapsed #${TOGGLE_ID} {
    position: static !important;
    transform: none !important;
    left: auto !important;
    top: auto !important;
}
            #${TOGGLE_ID}:hover { background: #e8eaed !important; }
            .lyra-main-controls { display: flex !important; flex-direction: column !important; gap: 8px !important; align-items: stretch !important; width: 100% !important; }
            .lyra-button {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    padding: 6px 12px !important;
    border-radius: 16px !important;
    cursor: pointer !important;
    font-size: 12px !important;
    font-weight: 500 !important;
    background-color: ${buttonColor} !important;
    color: white !important;
    border: none !important;
    transition: all 0.3s !important;
    box-shadow: 0 1px 2px rgba(0,0,0,0.1) !important;
    font-family: 'Google Sans', Roboto, Arial, sans-serif !important;
    white-space: nowrap !important;
    width: 100% !important;
    gap: 6px !important;
    min-width: ${currentPlatform === 'claude' ? '116px' : currentPlatform === 'gemini' ? '100px' : '116px'} !important;
}
            .lyra-button:hover { background-color: ${buttonHoverColor} !important; box-shadow: 0 2px 4px rgba(0,0,0,0.15) !important; }
            .lyra-button:disabled { opacity: 0.6 !important; cursor: not-allowed !important; }
            .lyra-button.lyra-manual-btn {
                background-color: #6B7280 !important;
                font-size: 11px !important;
                padding: 4px 8px !important;
            }
            .lyra-button.lyra-manual-btn:hover {
                background-color: #4B5563 !important;
            }
            .lyra-button svg { flex-shrink: 0 !important; width: 16px !important; height: 16px !important; }
            .lyra-title { font-size: 11px !important; font-weight: 500 !important; color: ${primaryColor} !important; margin-bottom: 6px !important; text-align: center !important; }
            .lyra-status {
                font-size: 10px !important;
                color: #6B7280 !important;
                text-align: center !important;
                padding: 2px 4px !important;
                background: #F3F4F6 !important;
                border-radius: 4px !important;
                margin-bottom: 4px !important;
            }
            .lyra-status.success {
                background: #D1FAE5 !important;
                color: #065F46 !important;
            }
            .lyra-status.error {
                background: #FEE2E2 !important;
                color: #991B1B !important;
            }
            .lyra-toggle {
                display: flex !important; align-items: center !important; justify-content: space-between !important;
                font-size: 11px !important; margin-bottom: 4px !important; gap: 4px !important; color: #5f6368 !important;
                width: 100% !important; padding: 0 2px !important;
            }
            .lyra-toggle span { flex: 1 !important; text-align: left !important; }
            .lyra-switch { position: relative !important; display: inline-block !important; width: 28px !important; height: 14px !important; flex-shrink: 0 !important; }
            .lyra-switch input { opacity: 0 !important; width: 0 !important; height: 0 !important; }
            .lyra-slider { position: absolute !important; cursor: pointer !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; background-color: #ccc !important; transition: .4s !important; border-radius: 34px !important; }
            .lyra-slider:before { position: absolute !important; content: "" !important; height: 10px !important; width: 10px !important; left: 2px !important; bottom: 2px !important; background-color: white !important; transition: .4s !important; border-radius: 50% !important; }
            input:checked + .lyra-slider { background-color: ${buttonColor} !important; }
            input:checked + .lyra-slider:before { transform: translateX(14px) !important; }
            .lyra-loading {
                display: inline-block !important; width: 16px !important; height: 16px !important;
                border: 2px solid rgba(255, 255, 255, 0.3) !important; border-radius: 50% !important;
                border-top-color: #fff !important; animation: lyra-spin 1s linear infinite !important;
            }
            @keyframes lyra-spin { to { transform: rotate(360deg); } }
            .lyra-progress { font-size: 10px !important; color: #5f6368 !important; margin-top: 4px !important; text-align: center !important; width: 100%; }
            .lyra-lang-toggle {
                display: flex !important; align-items: center !important; justify-content: center !important;
                font-size: 10px !important; margin-top: 4px !important; gap: 4px !important;
                padding: 4px 8px !important; border-radius: 12px !important;
                background: #f1f3f4 !important; cursor: pointer !important; transition: all 0.2s !important;
                width: 100% !important; box-sizing: border-box !important;
                white-space: nowrap !important; overflow: hidden !important;
                text-overflow: ellipsis !important;
            }
            .lyra-lang-toggle:hover { background: #e8eaed !important; }
        `;
        if (typeof GM_addStyle === 'function') {
            GM_addStyle(styleContent);
        } else {
            const existingStyle = document.querySelector('style[data-lyra-styles]');
            if (!existingStyle) {
                const style = document.createElement('style');
                style.textContent = styleContent;
                style.setAttribute('data-lyra-styles', 'true');
                document.head.appendChild(style);
            }
        }
    }
    // ===== 工具函数 =====
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    function blobToBase64(blob) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onloadend = () => resolve(reader.result.split(',')[1]);
            reader.onerror = reject;
            reader.readAsDataURL(blob);
        });
    }
    function toggleCollapsed() {
        const container = document.getElementById(CONTROL_ID);
        const toggleButton = document.getElementById(TOGGLE_ID);
        if (container && toggleButton) {
            isPanelCollapsed = !isPanelCollapsed;
            container.classList.toggle('collapsed', isPanelCollapsed);
            safeSetInnerHTML(toggleButton, isPanelCollapsed ?
                '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>' :
                '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>');
            localStorage.setItem('lyraExporterCollapsed', isPanelCollapsed);
        }
    }
    // ===== HTML转Markdown函数 =====
    function htmlToMarkdown(element) {
        if (!element) return '';
        let result = '';
        function processNode(node) {
            if (node.nodeType === Node.TEXT_NODE) {
                return node.textContent;
            }
            if (node.nodeType !== Node.ELEMENT_NODE) {
                return '';
            }
            const tagName = node.tagName.toLowerCase();
            const children = Array.from(node.childNodes).map(processNode).join('');
            switch(tagName) {
                case 'h1': return `\n# ${children}\n`;
                case 'h2': return `\n## ${children}\n`;
                case 'h3': return `\n### ${children}\n`;
                case 'h4': return `\n#### ${children}\n`;
                case 'h5': return `\n##### ${children}\n`;
                case 'h6': return `\n###### ${children}\n`;
                case 'strong':
                case 'b': return `**${children}**`;
                case 'em':
                case 'i': return `*${children}*`;
                case 'code':
                    if (children.includes('\n')) {
                        return `\n\`\`\`\n${children}\n\`\`\`\n`;
                    }
                    return `\`${children}\``;
                case 'pre':
                    const codeChild = node.querySelector('code');
                    if (codeChild) {
                        const lang = codeChild.className.match(/language-(\w+)/)?.[1] || '';
                        const codeContent = codeChild.textContent;
                        return `\n\`\`\`${lang}\n${codeContent}\n\`\`\`\n`;
                    }
                    return `\n\`\`\`\n${children}\n\`\`\`\n`;
                case 'hr': return '\n---\n';
                case 'br': return '\n';
                case 'p': return `\n${children}\n`;
                case 'div': return `${children}\n`;
                case 'a':
                    const href = node.getAttribute('href');
                    if (href) {
                        return `[${children}](${href})`;
                    }
                    return children;
                case 'ul':
                case 'ol':
                    return `\n${children}\n`;
                case 'li':
                    const parent = node.parentElement;
                    if (parent && parent.tagName.toLowerCase() === 'ol') {
                        const index = Array.from(parent.children).indexOf(node) + 1;
                        return `${index}. ${children}\n`;
                    }
                    return `- ${children}\n`;
                case 'blockquote': return `\n> ${children.split('\n').join('\n> ')}\n`;
                case 'table': return `\n${children}\n`;
                case 'thead': return `${children}`;
                case 'tbody': return `${children}`;
                case 'tr': return `${children}|\n`;
                case 'th': return `| **${children}** `;
                case 'td': return `| ${children} `;
                default: return children;
            }
        }
        result = processNode(element);
        result = result.replace(/\n{3,}/g, '\n\n');
        result = result.trim();
        return result;
    }
    // ===== 图片获取函数 =====
    function fetchViaGM(url) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                responseType: "blob",
                onload: function(response) {
                    if (response.status >= 200 && response.status < 300) {
                        resolve(response.response);
                    } else {
                        reject(new Error(`GM_xmlhttpRequest failed with status: ${response.status}`));
                    }
                },
                onerror: function(error) {
                    reject(new Error(`GM_xmlhttpRequest network error: ${error.statusText}`));
                }
            });
        });
    }
    // ===== Gemini图片处理函数 =====
    async function processImageElement(imgElement) {
        if (!imgElement) return null;
        let imageUrlToFetch = null;
        const previewContainer = imgElement.closest('user-query-file-preview');
        if (previewContainer) {
            const lensLinkElement = previewContainer.querySelector('a[href*="lens.google.com"]');
            if (lensLinkElement && lensLinkElement.href) {
                try {
                    const urlObject = new URL(lensLinkElement.href);
                    const realImageUrl = urlObject.searchParams.get('url');
                    if (realImageUrl) {
                        imageUrlToFetch = realImageUrl;
                    }
                } catch (e) {
                    // Error parsing URL
                }
            }
        }
        if (!imageUrlToFetch) {
            const fallbackSrc = imgElement.src;
            if (fallbackSrc && !fallbackSrc.startsWith('data:')) {
                imageUrlToFetch = fallbackSrc;
            }
        }
        if (!imageUrlToFetch) {
            return null;
        }
        try {
            const blob = await fetchViaGM(imageUrlToFetch);
            const base64 = await blobToBase64(blob);
            return {
                type: 'image',
                format: blob.type,
                size: blob.size,
                data: base64,
                original_src: imageUrlToFetch
            };
        } catch (error) {
            return null;
        }
    }
    // ===== Gemini数据提取函数 =====
    async function processGeminiContainer(container) {
        const userQueryElement = container.querySelector("user-query .query-text") || container.querySelector(".query-text-line");
        const modelResponseContainer = container.querySelector("model-response") || container;
        const modelResponseElement = modelResponseContainer.querySelector("message-content .markdown-main-panel");
        const questionText = userQueryElement ? userQueryElement.innerText.trim() : "";
        let answerText = "";
        if (modelResponseElement) {
            answerText = htmlToMarkdown(modelResponseElement);
        }
        const userImageElements = container.querySelectorAll("user-query img");
        const modelImageElements = modelResponseContainer.querySelectorAll("model-response img");
        const userImagesPromises = Array.from(userImageElements).map(img => processImageElement(img));
        const modelImagesPromises = Array.from(modelImageElements).map(img => processImageElement(img));
        const userImages = (await Promise.all(userImagesPromises)).filter(Boolean);
        const modelImages = (await Promise.all(modelImagesPromises)).filter(Boolean);
        if (questionText || answerText || userImages.length > 0 || modelImages.length > 0) {
            return {
                human: { text: questionText, images: userImages },
                assistant: { text: answerText, images: modelImages }
            };
        }
        return null;
    }
    async function extractGeminiConversationData() {
        const conversationTurns = document.querySelectorAll("div.conversation-turn");
        let conversationData = [];
        if (conversationTurns.length > 0) {
            for (const turn of conversationTurns) {
                const data = await processGeminiContainer(turn);
                if (data) conversationData.push(data);
            }
        } else {
            const legacyContainers = document.querySelectorAll("div.single-turn, div.conversation-container");
            for (const container of legacyContainers) {
                const data = await processGeminiContainer(container);
                if (data) conversationData.push(data);
            }
        }
        return conversationData;
    }
    // ===== NotebookLM数据提取函数 =====
    function extractNotebookLMConversationData() {
        const conversationTurns = document.querySelectorAll("div.chat-message-pair");
        let conversationData = [];
        conversationTurns.forEach((turnContainer) => {
            let questionText = "";
            const userQueryEl = turnContainer.querySelector("chat-message .from-user-container .message-text-content");
            if (userQueryEl) {
                questionText = userQueryEl.innerText.trim();
                if (questionText.startsWith('[Preamble] ')) {
                    questionText = questionText.substring('[Preamble] '.length).trim();
                }
            }
            let answerText = "";
            const modelResponseContent = turnContainer.querySelector("chat-message .to-user-container .message-text-content");
            if (modelResponseContent) {
                const answerParts = [];
                const structuralElements = modelResponseContent.querySelectorAll('labs-tailwind-structural-element-view-v2');
                structuralElements.forEach(structEl => {
                    const bulletEl = structEl.querySelector('.bullet');
                    const paragraphEl = structEl.querySelector('.paragraph');
                    let lineText = '';
                    if (bulletEl) {
                        lineText += bulletEl.innerText.trim() + ' ';
                    }
                    if (paragraphEl) {
                        let paragraphText = '';
                        paragraphEl.childNodes.forEach(node => {
                            if (node.nodeType === Node.TEXT_NODE) {
                                paragraphText += node.textContent;
                            } else if (node.nodeType === Node.ELEMENT_NODE) {
                                if (node.querySelector && node.querySelector('button.citation-marker')) {
                                    return;
                                }
                                if (node.tagName === 'SPAN' && node.classList.contains('bold')) {
                                    paragraphText += `**${node.innerText}**`;
                                } else {
                                    paragraphText += node.innerText || node.textContent || '';
                                }
                            }
                        });
                        lineText += paragraphText;
                    }
                    if (lineText.trim()) {
                        answerParts.push(lineText.trim());
                    }
                });
                answerText = answerParts.join('\n\n');
            }
            if (questionText || answerText) {
                conversationData.push({
                    human: questionText,
                    assistant: answerText
                });
            }
        });
        return conversationData;
    }
    // ===== AI Studio相关函数 =====
    function getAIStudioScroller() {
        const selectors = [
            'ms-chat-session ms-autoscroll-container',
            'mat-sidenav-content',
            '.chat-view-container'
        ];
        for (const selector of selectors) {
            const el = document.querySelector(selector);
            if (el && (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth)) {
                return el;
            }
        }
        return document.documentElement;
    }
    function extractDataIncremental_AiStudio() {
        const turns = document.querySelectorAll('ms-chat-turn');
        turns.forEach(turn => {
            if (collectedData.has(turn)) { return; }
            const isUserTurn = turn.querySelector('.chat-turn-container.user');
            const isModelTurn = turn.querySelector('.chat-turn-container.model');
            let turnData = { type: 'unknown', text: '' };
            if (isUserTurn) {
                const userPromptNode = isUserTurn.querySelector('.user-prompt-container .turn-content');
                if (userPromptNode) {
                    const userText = userPromptNode.innerText.trim();
                    if (userText) {
                        turnData.type = 'user';
                        turnData.text = userText;
                    }
                }
            } else if (isModelTurn) {
                const responseChunks = isModelTurn.querySelectorAll('ms-prompt-chunk');
                let responseTexts = [];
                responseChunks.forEach(chunk => {
                    if (!chunk.querySelector('ms-thought-chunk')) {
                        const cmarkNode = chunk.querySelector('ms-cmark-node');
                        if (cmarkNode) {
                            const markdownText = htmlToMarkdown(cmarkNode);
                            if (markdownText) {
                                responseTexts.push(markdownText);
                            }
                        }
                    }
                });
                const responseText = responseTexts.join('\n\n').trim();
                if (responseText) {
                    turnData.type = 'model';
                    turnData.text = responseText;
                }
            }
            if (turnData.type !== 'unknown') {
                collectedData.set(turn, turnData);
            }
        });
    }
    async function autoScrollAndCaptureAIStudio(onProgress) {
        collectedData.clear();
        const scroller = getAIStudioScroller();
        onProgress(i18n.t('scrollToTop'), false);
        scroller.scrollTop = 0;
        await sleep(SCROLL_TOP_WAIT_MS);
        let lastScrollTop = -1;
        onProgress(i18n.t('startScanning'), false);
        while (true) {
            extractDataIncremental_AiStudio();
            onProgress(`${i18n.t('scanning')} ${Math.round((scroller.scrollTop + scroller.clientHeight) / scroller.scrollHeight * 100)}% (${i18n.t('foundItems')} ${collectedData.size} ${i18n.t('items')})`, false);
            if (scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 10) {
                break;
            }
            lastScrollTop = scroller.scrollTop;
            scroller.scrollTop += scroller.clientHeight * 0.85;
            await sleep(SCROLL_DELAY_MS);
            if (scroller.scrollTop === lastScrollTop) {
                break;
            }
        }
        onProgress(i18n.t('scanComplete'), false);
        extractDataIncremental_AiStudio();
        await sleep(500);
        const finalTurnsInDom = document.querySelectorAll('ms-chat-turn');
        let sortedData = [];
        finalTurnsInDom.forEach(turnNode => {
            if (collectedData.has(turnNode)) {
                sortedData.push(collectedData.get(turnNode));
            }
        });
        const pairedData = [];
        let lastHuman = null;
        sortedData.forEach(item => {
            if (item.type === 'user') {
                lastHuman = item.text;
            } else if (item.type === 'model' && lastHuman) {
                pairedData.push({ human: lastHuman, assistant: item.text });
                lastHuman = null;
            } else if (item.type === 'model' && !lastHuman) {
                pairedData.push({ human: "[No preceding user prompt found]", assistant: item.text });
            }
        });
        if (lastHuman) {
            pairedData.push({ human: lastHuman, assistant: "[Model response is pending]" });
        }
        return pairedData;
    }
    // ===== 获取对话标题函数 =====
    function getConversationTitle() {
        let title = '';
        try {
            switch (currentPlatform) {
                case 'gemini':
                    const defaultTitle = `Gemini ${i18n.t('defaultChatTitle')} ${new Date().toISOString().slice(0,10)}`;
                    title = prompt(i18n.t('enterTitle'), defaultTitle);
                    if (title === null) {
                        return null;
                    }
                    return title.trim() || defaultTitle;
                case 'notebooklm':
                    const nblmTitleInput = document.querySelector('input.title-input.mat-title-large') ||
                                         document.querySelector('.title-input.mat-title-large.ng-pristine.ng-valid.ng-touched') ||
                                         document.querySelector('h1.notebook-title');
                    if (nblmTitleInput) {
                        title = nblmTitleInput.value || nblmTitleInput.innerText || nblmTitleInput.textContent;
                        title = title.trim();
                    }
                    return title || `${i18n.t('untitledChat')} NotebookLM`;
                case 'aistudio':
                    const studioTitleEl = document.querySelector('div.page-title h1');
                    title = studioTitleEl ? studioTitleEl.innerText.trim() : `${i18n.t('untitledChat')} AI Studio`;
                    return title;
                default:
                    return `${i18n.t('defaultChatTitle')} ${new Date().toISOString().slice(0,10)}`;
            }
        } catch (error) {
            return `${i18n.t('untitledChat')} ${new Date().toISOString().slice(0,10)}`;
        }
    }
    // ===== Claude相关函数 =====
    function getCurrentChatUUID() {
        const url = window.location.href;
        const match = url.match(/\/chat\/([a-zA-Z0-9-]+)/);
        return match ? match[1] : null;
    }
    function checkUrlForTreeMode() {
        return window.location.href.includes('?tree=True&rendering_mode=messages&render_all_tools=true') ||
               window.location.href.includes('&tree=True&rendering_mode=messages&render_all_tools=true');
    }
    async function getAllConversations() {
        const userId = await ensureUserId();
        if (!userId) {
            return null;
        }
        try {
            const baseUrl = window.location.hostname.includes('claude.ai') ? 'https://claude.ai' : 'https://pro.easychat.top';
            const apiUrl = `${baseUrl}/api/organizations/${userId}/chat_conversations`;
            const response = await fetch(apiUrl);
            if (!response.ok) {
                throw new Error(`${i18n.t('fetchFailed')}: ${response.status}`);
            }
            return await response.json();
        } catch (error) {
            return null;
        }
    }
    async function processImageAttachment(imageUrl, debugInfo = '') {
        try {
            if (!imageUrl.startsWith('http')) {
                const baseUrl = window.location.hostname.includes('claude.ai') ?
                    'https://claude.ai' : 'https://pro.easychat.top';
                imageUrl = baseUrl + imageUrl;
            }
            const response = await fetch(imageUrl);
            if (!response.ok) {
                throw new Error(`Failed to fetch image: ${response.status}`);
            }
            const blob = await response.blob();
            const base64 = await blobToBase64(blob);
            return {
                type: 'image',
                format: blob.type,
                size: blob.size,
                data: base64,
                original_url: imageUrl
            };
        } catch (error) {
            return null;
        }
    }
    async function getConversationDetailsWithImages(uuid, includeImagesParam) {
        const userId = await ensureUserId();
        if (!userId) {
            return null;
        }
        try {
            const baseUrl = window.location.hostname.includes('claude.ai') ?
                'https://claude.ai' : 'https://pro.easychat.top';
            const treeMode = document.getElementById(TREE_SWITCH_ID) ?
                document.getElementById(TREE_SWITCH_ID).checked : false;
            const apiUrl = `${baseUrl}/api/organizations/${userId}/chat_conversations/${uuid}${treeMode ? '?tree=True&rendering_mode=messages&render_all_tools=true' : ''}`;
            const response = await fetch(apiUrl);
            if (!response.ok) {
                throw new Error(`${i18n.t('fetchFailed')}: ${response.status}`);
            }
            const data = await response.json();
            if (!includeImagesParam) {
                return data;
            }
            let processedImageCount = 0;
            if (data.chat_messages && Array.isArray(data.chat_messages)) {
                for (let msgIndex = 0; msgIndex < data.chat_messages.length; msgIndex++) {
                    const message = data.chat_messages[msgIndex];
                    const fileArrays = ['files', 'files_v2', 'attachments'];
                    for (const key of fileArrays) {
                        if (message[key] && Array.isArray(message[key])) {
                            for (let i = 0; i < message[key].length; i++) {
                                const file = message[key][i];
                                let imageUrl = null;
                                let isImage = false;
                                if (file.file_kind === 'image' ||
                                    (file.file_type && file.file_type.startsWith('image/'))) {
                                    isImage = true;
                                    imageUrl = file.preview_url || file.thumbnail_url || file.file_url;
                                }
                                if (isImage && imageUrl && !file.embedded_image) {
                                    const imageData = await processImageAttachment(
                                        imageUrl,
                                        `消息${msgIndex + 1}-${key}-${i + 1}`
                                    );
                                    if (imageData) {
                                        message[key][i].embedded_image = imageData;
                                        processedImageCount++;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            data._debug_info = {
                images_processed: processedImageCount,
                processing_time: new Date().toISOString()
            };
            return data;
        } catch (error) {
            return null;
        }
    }
    async function exportCurrentConversationWithImages() {
        const uuid = getCurrentChatUUID();
        if (!uuid) {
            alert(i18n.t('uuidNotFound'));
            return;
        }
        const userId = await ensureUserId();
        if (!userId) {
            return;
        }
        try {
            const shouldIncludeImages = document.getElementById(IMAGE_SWITCH_ID)?.checked || false;
            const data = await getConversationDetailsWithImages(uuid, shouldIncludeImages);
            if (!data) {
                throw new Error(i18n.t('fetchFailed'));
            }
            const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `claude_${uuid.substring(0, 8)}_${shouldIncludeImages ? 'with_images_' : ''}${new Date().toISOString().slice(0,10)}.json`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        } catch (error) {
            alert(`${i18n.t('exportFailed')} ${error.message}`);
        }
    }
    async function openLyraExporterWithData(jsonData, filename) {
        try {
            const exporterWindow = window.open(LYRA_EXPORTER_URL, '_blank');
            if (!exporterWindow) {
                alert(i18n.t('cannotOpenExporter'));
                return false;
            }
            const checkInterval = setInterval(() => {
                try {
                    exporterWindow.postMessage({
                        type: 'LYRA_HANDSHAKE',
                        source: 'lyra-fetch-script'
                    }, LYRA_EXPORTER_ORIGIN);
                } catch (e) {
                    // Error sending handshake
                }
            }, 1000);
            const handleMessage = (event) => {
                if (event.origin !== LYRA_EXPORTER_ORIGIN) {
                    return;
                }
                if (event.data && event.data.type === 'LYRA_READY') {
                    clearInterval(checkInterval);
                    const dataToSend = {
                        type: 'LYRA_LOAD_DATA',
                        source: 'lyra-fetch-script',
                        data: {
                            content: jsonData,
                            filename: filename || `${currentPlatform}_export_${new Date().toISOString().slice(0,10)}.json`
                        }
                    };
                    exporterWindow.postMessage(dataToSend, LYRA_EXPORTER_ORIGIN);
                    window.removeEventListener('message', handleMessage);
                }
            };
            window.addEventListener('message', handleMessage);
            setTimeout(() => {
                clearInterval(checkInterval);
                window.removeEventListener('message', handleMessage);
            }, 45000);
            return true;
        } catch (error) {
            alert(`${i18n.t('cannotOpenExporter')}: ${error.message}`);
            return false;
        }
    }
    // ===== 创建浮动面板 =====
    function createFloatingPanel() {
        if (document.getElementById(CONTROL_ID) || panelInjected) {
            return false;
        }
        let savedTreeMode = false;
        let savedImageMode = includeImages;
        const existingTreeSwitch = document.getElementById(TREE_SWITCH_ID);
        const existingImageSwitch = document.getElementById(IMAGE_SWITCH_ID);
        if (existingTreeSwitch) {
            savedTreeMode = existingTreeSwitch.checked;
        }
        if (existingImageSwitch) {
            savedImageMode = existingImageSwitch.checked;
        }
        const container = document.createElement('div');
        container.id = CONTROL_ID;
        if (isPanelCollapsed) container.classList.add('collapsed');
        const toggleButton = document.createElement('div');
        toggleButton.id = TOGGLE_ID;
        safeSetInnerHTML(toggleButton, isPanelCollapsed ?
            '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>' :
            '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>');
        toggleButton.addEventListener('click', toggleCollapsed);
        container.appendChild(toggleButton);
        const controlsArea = document.createElement('div');
        controlsArea.className = 'lyra-main-controls';
        const title = document.createElement('div');
        title.className = 'lyra-title';
        switch (currentPlatform) {
            case 'claude': title.textContent = i18n.t('claudeTitle'); break;
            case 'gemini': title.textContent = i18n.t('geminiTitle'); break;
            case 'notebooklm': title.textContent = i18n.t('notebooklmTitle'); break;
            case 'aistudio': title.textContent = i18n.t('aistudioTitle'); break;
            default: title.textContent = i18n.t('defaultTitle');
        }
        controlsArea.appendChild(title);
        // Claude专用功能
        if (currentPlatform === 'claude') {
            const statusDiv = document.createElement('div');
            statusDiv.className = 'lyra-status';
            if (capturedUserId) {
                statusDiv.classList.add('success');
                statusDiv.textContent = i18n.t('detectedUserId') + capturedUserId.substring(0, 8) + '...';
            } else {
                statusDiv.classList.add('error');
                statusDiv.textContent = i18n.t('autoDetecting');
            }
            controlsArea.appendChild(statusDiv);
            const manualIdBtn = document.createElement('button');
            manualIdBtn.id = MANUAL_ID_BTN;
            manualIdBtn.className = 'lyra-button lyra-manual-btn';
            manualIdBtn.textContent = i18n.t('manualUserId');
            manualIdBtn.addEventListener('click', promptForUserId);
            controlsArea.appendChild(manualIdBtn);
            const toggleContainer = document.createElement('div');
            toggleContainer.className = 'lyra-toggle';
            const treeModeChecked = savedTreeMode || checkUrlForTreeMode();
            safeSetInnerHTML(toggleContainer, `
                <span>${i18n.t('branchMode')}</span>
                <label class="lyra-switch">
                    <input type="checkbox" id="${TREE_SWITCH_ID}" ${treeModeChecked ? 'checked' : ''}>
                    <span class="lyra-slider"></span>
                </label>
            `);
            controlsArea.appendChild(toggleContainer);
            const imageToggleContainer = document.createElement('div');
            imageToggleContainer.className = 'lyra-toggle';
            safeSetInnerHTML(imageToggleContainer, `
                <span>${i18n.t('includeImages')}</span>
                <label class="lyra-switch">
                    <input type="checkbox" id="${IMAGE_SWITCH_ID}" ${savedImageMode ? 'checked' : ''}>
                    <span class="lyra-slider"></span>
                </label>
            `);
            controlsArea.appendChild(imageToggleContainer);
            document.addEventListener('change', function(e) {
                if (e.target.id === IMAGE_SWITCH_ID) {
                    includeImages = e.target.checked;
                    localStorage.setItem('lyraIncludeImages', includeImages);
                }
            });
            const uuidButton = document.createElement('button');
            uuidButton.className = 'lyra-button';
            safeSetInnerHTML(uuidButton, `
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="11" cy="11" r="8"></circle>
                    <path d="m21 21-4.35-4.35"></path>
                </svg>
                ${i18n.t('viewOnline')}
            `);
            uuidButton.addEventListener('click', async () => {
                const uuid = getCurrentChatUUID();
                if (!uuid) {
                    alert(i18n.t('uuidNotFound'));
                    return;
                }
                const userId = await ensureUserId();
                if (!userId) {
                    return;
                }
                const originalContent = uuidButton.innerHTML;
                safeSetInnerHTML(uuidButton, `<div class="lyra-loading"></div><span>${i18n.t('loading')}</span>`);
                uuidButton.disabled = true;
                try {
                    const shouldIncludeImages = document.getElementById(IMAGE_SWITCH_ID)?.checked || false;
                    const data = await getConversationDetailsWithImages(uuid, shouldIncludeImages);
                    if (!data) {
                        throw new Error(i18n.t('fetchFailed'));
                    }
                    const jsonString = JSON.stringify(data, null, 2);
                    const filename = `claude_${uuid.substring(0, 8)}_${shouldIncludeImages ? 'with_images_' : ''}${new Date().toISOString().slice(0,10)}.json`;
                    await openLyraExporterWithData(jsonString, filename);
                } catch (error) {
                    alert(`${i18n.t('loadFailed')} ${error.message}`);
                } finally {
                    safeSetInnerHTML(uuidButton, originalContent);
                    uuidButton.disabled = false;
                }
            });
            controlsArea.appendChild(uuidButton);
            const exportButton = document.createElement('button');
            exportButton.className = 'lyra-button';
            safeSetInnerHTML(exportButton, `
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                    <polyline points="7 10 12 15 17 10"></polyline>
                    <line x1="12" y1="15" x2="12" y2="3"></line>
                </svg>
                ${i18n.t('exportCurrentJSON')}
            `);
            exportButton.addEventListener('click', exportCurrentConversationWithImages);
            controlsArea.appendChild(exportButton);
            const exportAllButton = document.createElement('button');
            exportAllButton.className = 'lyra-button';
            safeSetInnerHTML(exportAllButton, `
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect>
                    <path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path>
                </svg>
                ${i18n.t('exportAllConversations')}
            `);
            exportAllButton.addEventListener('click', async function(event) {
                const userId = await ensureUserId();
                if (!userId) {
                    return;
                }
                const shouldIncludeImages = document.getElementById(IMAGE_SWITCH_ID)?.checked || false;
                const progressElem = document.createElement('div');
                progressElem.className = 'lyra-progress';
                progressElem.textContent = i18n.t('preparing');
                controlsArea.appendChild(progressElem);
                const originalContent = this.innerHTML;
                safeSetInnerHTML(this, `<div class="lyra-loading"></div><span>${i18n.t('exporting')}</span>`);
                this.disabled = true;
                try {
                    const allConversations = await getAllConversations();
                    if (!allConversations || !Array.isArray(allConversations)) {
                        throw new Error(i18n.t('fetchFailed'));
                    }
                    const result = {
                        exportedAt: new Date().toISOString(),
                        totalConversations: allConversations.length,
                        conversations: []
                    };
                    for (let i = 0; i < allConversations.length; i++) {
                        const conversation = allConversations[i];
                        progressElem.textContent = `${i18n.t('gettingConversation')} ${i + 1}/${allConversations.length}${shouldIncludeImages ? i18n.t('withImages') : ''}`;
                        if (i > 0) await sleep(500);
                        const details = await getConversationDetailsWithImages(conversation.uuid, shouldIncludeImages);
                        if (details) result.conversations.push(details);
                    }
                    const blob = new Blob([JSON.stringify(result, null, 2)], { type: 'application/json' });
                    const url = URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = `claude_all_conversations_${shouldIncludeImages ? 'with_images_' : ''}${new Date().toISOString().slice(0,10)}.json`;
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                    URL.revokeObjectURL(url);
                    alert(`${i18n.t('successExported')} ${result.conversations.length} ${i18n.t('conversations')}`);
                } catch (error) {
                    alert(`${i18n.t('exportFailed')} ${error.message}`);
                } finally {
                    safeSetInnerHTML(this, originalContent);
                    this.disabled = false;
                    if (progressElem.parentNode) progressElem.parentNode.removeChild(progressElem);
                }
            });
            controlsArea.appendChild(exportAllButton);
        } else {
            // 非Claude平台:Gemini, AI Studio, NotebookLM
            // 先添加预览按钮(除NotebookLM外)
            if (currentPlatform !== 'notebooklm') {
                const onlineViewButton = document.createElement('button');
                onlineViewButton.className = 'lyra-button';
                safeSetInnerHTML(onlineViewButton, `
                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <circle cx="11" cy="11" r="8"></circle>
                        <path d="m21 21-4.35-4.35"></path>
                    </svg>
                    ${i18n.t('viewOnline')}
                `);
                onlineViewButton.addEventListener('click', async function() {
                    const title = getConversationTitle();
                    if (title === null) {
                        return;
                    }
                    this.disabled = true;
                    const originalContent = this.innerHTML;
                    safeSetInnerHTML(this, `<div class="lyra-loading"></div><span>${i18n.t('loading')}</span>`);
                    let progressElem = null;
                    if (currentPlatform === 'aistudio') {
                        progressElem = document.createElement('div');
                        progressElem.className = 'lyra-progress';
                        controlsArea.appendChild(progressElem);
                    }
                    try {
                        let conversationData = [];
                        if (currentPlatform === 'aistudio') {
                            conversationData = await autoScrollAndCaptureAIStudio((message) => {
                                if (progressElem) progressElem.textContent = message;
                            });
                        } else if (currentPlatform === 'gemini') {
                            conversationData = await extractGeminiConversationData();
                        }
                        if (conversationData.length > 0) {
                            const finalJson = {
                                title: title,
                                platform: currentPlatform,
                                exportedAt: new Date().toISOString(),
                                conversation: conversationData
                            };
                            const timestamp = new Date().toISOString().replace(/:/g, '-').slice(0, 19);
                            const sanitizedTitle = title.replace(/[^a-z0-9\u4e00-\u9fa5]/gi, '_');
                            const filename = `${currentPlatform}_${sanitizedTitle}_${timestamp}.json`;
                            const jsonString = JSON.stringify(finalJson, null, 2);
                            await openLyraExporterWithData(jsonString, filename);
                        } else {
                            alert(i18n.t('noContent'));
                        }
                    } catch (error) {
                        alert(`${i18n.t('loadFailed')} ${error.message}`);
                    } finally {
                        this.disabled = false;
                        safeSetInnerHTML(this, originalContent);
                        if (progressElem && progressElem.parentNode) {
                            progressElem.parentNode.removeChild(progressElem);
                        }
                    }
                });
                controlsArea.appendChild(onlineViewButton);
            }
            // 然后添加保存按钮
            const exportButton = document.createElement('button');
            exportButton.className = 'lyra-button';
            safeSetInnerHTML(exportButton, `
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                    <polyline points="7 10 12 15 17 10"></polyline>
                    <line x1="12" y1="15" x2="12" y2="3"></line>
                </svg>
                ${i18n.t('saveConversation')}
            `);
            exportButton.addEventListener('click', async function() {
                const title = getConversationTitle();
                if (title === null) {
                    return;
                }
                this.disabled = true;
                const originalContent = this.innerHTML;
                safeSetInnerHTML(this, `<div class="lyra-loading"></div><span>${i18n.t('exporting')}</span>`);
                let progressElem = null;
                if (currentPlatform === 'aistudio') {
                    progressElem = document.createElement('div');
                    progressElem.className = 'lyra-progress';
                    controlsArea.appendChild(progressElem);
                }
                try {
                    let conversationData = [];
                    if (currentPlatform === 'aistudio') {
                        conversationData = await autoScrollAndCaptureAIStudio((message) => {
                            if (progressElem) progressElem.textContent = message;
                        });
                    } else if (currentPlatform === 'gemini') {
                        conversationData = await extractGeminiConversationData();
                    } else if (currentPlatform === 'notebooklm') {
                        conversationData = extractNotebookLMConversationData();
                    }
                    if (conversationData.length > 0) {
                        const finalJson = {
                            title: title,
                            platform: currentPlatform,
                            exportedAt: new Date().toISOString(),
                            conversation: conversationData
                        };
                        const timestamp = new Date().toISOString().replace(/:/g, '-').slice(0, 19);
                        const sanitizedTitle = title.replace(/[^a-z0-9\u4e00-\u9fa5]/gi, '_');
                        const filename = `${currentPlatform}_${sanitizedTitle}_${timestamp}.json`;
                        const jsonData = JSON.stringify(finalJson, null, 2);
                        const blob = new Blob([jsonData], { type: 'application/json;charset=utf-8' });
                        const link = document.createElement("a");
                        link.href = URL.createObjectURL(blob);
                        link.download = filename;
                        document.body.appendChild(link);
                        link.click();
                        document.body.removeChild(link);
                        URL.revokeObjectURL(link.href);
                    } else {
                        alert(i18n.t('noContent'));
                    }
                } catch (error) {
                    alert(`${i18n.t('exportFailed')} ${error.message}`);
                } finally {
                    this.disabled = false;
                    safeSetInnerHTML(this, originalContent);
                    if (progressElem && progressElem.parentNode) {
                        progressElem.parentNode.removeChild(progressElem);
                    }
                }
            });
            controlsArea.appendChild(exportButton);
        }
        // 语言切换按钮
        const langToggle = document.createElement('div');
        langToggle.className = 'lyra-lang-toggle';
        langToggle.id = LANG_SWITCH_ID;
        langToggle.textContent = `🌐 ${i18n.getLanguageShort()}`;
        langToggle.addEventListener('click', function() {
            const currentLang = i18n.getLanguage();
            const newLang = currentLang === 'zh' ? 'en' : 'zh';
            i18n.setLanguage(newLang);
            const existingPanel = document.getElementById(CONTROL_ID);
            if (existingPanel) {
                existingPanel.remove();
                panelInjected = false;
                createFloatingPanel();
            }
        });
        controlsArea.appendChild(langToggle);
        container.appendChild(controlsArea);
        document.body.appendChild(container);
        panelInjected = true;
        // 定期更新状态
        if (currentPlatform === 'claude') {
            setInterval(() => {
                const statusDiv = container.querySelector('.lyra-status');
                if (statusDiv) {
                    if (capturedUserId) {
                        statusDiv.classList.remove('error');
                        statusDiv.classList.add('success');
                        statusDiv.textContent = i18n.t('detectedUserId') + capturedUserId.substring(0, 8) + '...';
                    } else {
                        statusDiv.classList.remove('success');
                        statusDiv.classList.add('error');
                        statusDiv.textContent = i18n.t('autoDetecting');
                    }
                }
            }, 2000);
        }
        return true;
    }
    // ===== 脚本初始化 =====
    function initScript() {
        if (!currentPlatform) {
            return;
        }
        injectCustomStyle();
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                setTimeout(() => initializePanel(), 2000);
            });
        } else {
            setTimeout(() => initializePanel(), 2000);
        }
    }
    function initializePanel() {
        if (currentPlatform === 'claude') {
            if (/\/chat\/[a-zA-Z0-9-]+/.test(window.location.href)) {
                createFloatingPanel();
            }
            let lastUrl = window.location.href;
            const observer = new MutationObserver(() => {
                if (window.location.href !== lastUrl) {
                    lastUrl = window.location.href;
                    setTimeout(() => {
                        if (/\/chat\/[a-zA-Z0-9-]+/.test(lastUrl) && !document.getElementById(CONTROL_ID)) {
                            createFloatingPanel();
                        }
                    }, 1000);
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        } else {
            createFloatingPanel();
        }
    }
    initScript();
})();