Greasy Fork

来自缓存

Greasy Fork is available in English.

Lyra's Exporter Fetch

Claude、Gemini、NotebookLM、Google AI Studio对话管理工具的配套脚本:一键获取对话UUID和完整JSON数据,支持树形分支模式导出。轻松管理数百个对话窗口,导出完整时间线、思考过程、工具调用和Artifacts。配合Lyra's Exporter使用,让每次AI对话都成为您的数字资产!

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

// ==UserScript==
// @name         Lyra's Exporter Fetch
// @namespace    userscript://lyra-conversation-exporter
// @version      2.5
// @description  Claude、Gemini、NotebookLM、Google AI Studio对话管理工具的配套脚本:一键获取对话UUID和完整JSON数据,支持树形分支模式导出。轻松管理数百个对话窗口,导出完整时间线、思考过程、工具调用和Artifacts。配合Lyra's Exporter使用,让每次AI对话都成为您的数字资产!
// @description:en Claude conversation management companion script: One-click UUID extraction and complete JSON export with tree-branch mode support. Designed for power Claude users to easily manage hundreds of conversation windows, export complete timelines, thinking processes, tool calls and Artifacts. Works with Lyra's Exporter management app to turn every AI chat into your digital asset!
// @homepage     https://github.com/Yalums/Lyra-s-Claude-Exporter
// @supportURL   https://github.com/Yalums/Lyra-s-Claude-Exporter/issues
// @author       Yalums
// @match        https://claude.ai/*
// @match        https://gemini.google.com/app/*
// @match        https://notebooklm.google.com/*
// @match        https://aistudio.google.com/*
// @run-at       document-start
// @grant        none
// @license      GNU General Public License v3.0
// ==/UserScript==

(function() {
    'use strict';

    // 检测当前平台
    let currentPlatform = '';
    const hostname = window.location.hostname;
    if (hostname.includes('easychat.top')) {
        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';
    }
    
    console.log(`[Lyra's Universal Exporter] Platform detected: ${currentPlatform}`);

    // Claude专用变量
    let capturedUserId = '';
    
    // 通用变量
    let isPanelCollapsed = localStorage.getItem('lyraExporterCollapsed') === 'true';
    const CONTROL_ID = "lyra-universal-exporter-container";
    const TOGGLE_ID = "lyra-toggle-button";
    const TREE_SWITCH_ID = "lyra-tree-mode";
    let panelInjected = false;

    // Claude专用:拦截请求获取用户ID
    if (currentPlatform === 'claude') {
        const originalXHROpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(method, url) {
            const organizationsMatch = url.match(/api\/organizations\/([a-zA-Z0-9-]+)/);
            if (organizationsMatch && organizationsMatch[1]) {
                capturedUserId = organizationsMatch[1];
                console.log("✨ Captured user ID:", capturedUserId);
            }
            return originalXHROpen.apply(this, arguments);
        };

        const originalFetch = window.fetch;
        window.fetch = function(resource, options) {
            if (typeof resource === 'string') {
                const organizationsMatch = resource.match(/api\/organizations\/([a-zA-Z0-9-]+)/);
                if (organizationsMatch && organizationsMatch[1]) {
                    capturedUserId = organizationsMatch[1];
                    console.log("✨ Captured user ID:", capturedUserId);
                }
            }
            return originalFetch.apply(this, arguments);
        };
    }

    // 注入样式
    function injectCustomStyle() {
        const style = document.createElement('style');
        style.textContent = `
            #${CONTROL_ID} {
                position: fixed !important;
                right: 15px !important;
                bottom: 25px !important;
                display: flex !important;
                flex-direction: column !important;
                gap: 8px !important;
                z-index: 2147483647 !important;
                transition: transform 0.3s ease, width 0.3s ease, padding 0.3s ease !important;
                background: white !important;
                border-radius: 12px !important;
                box-shadow: 0 4px 15px rgba(0,0,0,0.2) !important;
                padding: 12px !important;
                border: 1px solid #e0e0e0 !important;
                width: auto !important;
                min-width: 40px !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: 8px !important;
                width: 40px !important;
                height: 40px !important;
                justify-content: center !important;
                align-items: center !important;
                overflow: hidden !important;
            }
            
            #${TOGGLE_ID} {
                position: absolute !important;
                left: -14px !important;
                top: 50% !important;
                transform: translateY(-50%) !important;
                width: 28px !important;
                height: 28px !important;
                border-radius: 50% !important;
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
                background: #f1f3f4 !important;
                color: #1a73e8 !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: 10px !important;
                align-items: center !important;
            }
            
            .lyra-button {
                display: inline-flex !important;
                align-items: center !important;
                justify-content: center !important;
                padding: 8px 16px !important;
                border-radius: 18px !important;
                cursor: pointer !important;
                font-size: 14px !important;
                font-weight: 500 !important;
                background-color: #1a73e8 !important;
                color: white !important;
                border: none !important;
                transition: background-color 0.3s, box-shadow 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: 8px !important;
            }
            
            .lyra-button:hover {
                background-color: #1765c2 !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 svg {
                flex-shrink: 0 !important;
            }
            
            .lyra-title {
                font-size: 13px !important;
                font-weight: 500 !important;
                color: #1a73e8 !important;
                margin-bottom: 8px !important;
                text-align: center !important;
            }
            
            .lyra-toggle {
                display: flex !important;
                align-items: center !important;
                font-size: 13px !important;
                margin-bottom: 5px !important;
                gap: 5px !important;
                color: #5f6368 !important;
            }
            
            .lyra-switch {
                position: relative !important;
                display: inline-block !important;
                width: 32px !important;
                height: 16px !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: 12px !important;
                width: 12px !important;
                left: 2px !important;
                bottom: 2px !important;
                background-color: white !important;
                transition: .4s !important;
                border-radius: 50% !important;
            }
            
            input:checked + .lyra-slider {
                background-color: #1a73e8 !important;
            }
            
            input:checked + .lyra-slider:before {
                transform: translateX(16px) !important;
            }
            
            .lyra-toast {
                position: fixed !important;
                bottom: 60px !important;
                right: 20px !important;
                background-color: #323232 !important;
                color: white !important;
                padding: 12px 20px !important;
                border-radius: 4px !important;
                z-index: 2147483648 !important;
                opacity: 0 !important;
                transition: opacity 0.3s ease-in-out !important;
                font-size: 14px !important;
                font-family: 'Google Sans', Roboto, Arial, sans-serif !important;
            }
            
            .lyra-loading {
                display: inline-block !important;
                width: 20px !important;
                height: 20px !important;
                border: 2px solid rgba(26, 115, 232, 0.3) !important;
                border-radius: 50% !important;
                border-top-color: #1a73e8 !important;
                animation: lyra-spin 1s linear infinite !important;
            }
            
            @keyframes lyra-spin {
                to { transform: rotate(360deg); }
            }
            
            .lyra-progress {
                font-size: 12px !important;
                color: #5f6368 !important;
                margin-top: 5px !important;
                text-align: center !important;
            }
        `;
        document.head.appendChild(style);
    }

    // 通用工具函数
    function showToast(message, duration = 2000) {
        let toast = document.querySelector(".lyra-toast");
        if (!toast) {
            toast = document.createElement("div");
            toast.className = "lyra-toast";
            document.body.appendChild(toast);
        }
        toast.textContent = message;
        toast.style.opacity = "1";
        setTimeout(() => {
            toast.style.opacity = "0";
        }, duration);
    }

    function toggleCollapsed() {
        const container = document.getElementById(CONTROL_ID);
        const toggleButton = document.getElementById(TOGGLE_ID);
        if (container && toggleButton) {
            isPanelCollapsed = !isPanelCollapsed;
            container.classList.toggle('collapsed', isPanelCollapsed);
            toggleButton.innerHTML = isPanelCollapsed ? 
                '<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="m9 18 6-6-6-6"/></svg>' : 
                '<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="m15 18-6-6 6-6"/></svg>';
            localStorage.setItem('lyraExporterCollapsed', isPanelCollapsed);
        }
    }

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // 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() {
        if (!capturedUserId) {
            showToast("未能获取用户ID,请刷新页面");
            return null;
        }
        try {
            const apiUrl = `https://pro.easychat.top/api/organizations/${capturedUserId}/chat_conversations`;
            const response = await fetch(apiUrl);
            if (!response.ok) {
                throw new Error(`请求失败: ${response.status}`);
            }
            return await response.json();
        } catch (error) {
            console.error("获取对话列表失败:", error);
            showToast("获取对话列表失败: " + error.message);
            return null;
        }
    }

    async function getConversationDetails(uuid) {
        if (!capturedUserId) {
            return null;
        }
        try {
            const apiUrl = `https://pro.easychat.top/api/organizations/${capturedUserId}/chat_conversations/${uuid}?tree=True&rendering_mode=messages&render_all_tools=true`;
            const response = await fetch(apiUrl);
            if (!response.ok) {
                throw new Error(`请求失败: ${response.status}`);
            }
            return await response.json();
        } catch (error) {
            console.error(`获取对话 ${uuid} 详情失败:`, error);
            return null;
        }
    }

    // Gemini系列提取函数
    function extractGeminiConversationData() {
        const conversationTurns = document.querySelectorAll("div.conversation-turn");
        let conversationData = [];

        if (conversationTurns.length === 0) {
            const legacySelectors = ["div.single-turn", "div.conversation-container"];
            for (const sel of legacySelectors) {
                const legacyContainers = document.querySelectorAll(sel);
                if (legacyContainers.length > 0) {
                    legacyContainers.forEach(container => processGeminiContainer(container, conversationData));
                    break;
                }
            }
        } else {
            conversationTurns.forEach(turn => processGeminiContainer(turn, conversationData));
        }

        return conversationData;
    }

    function processGeminiContainer(container, conversationData) {
        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() : "";
        const answerText = modelResponseElement ? modelResponseElement.innerText.trim() : "";

        if (questionText || answerText) {
            conversationData.push({
                human: questionText,
                assistant: answerText
            });
        }
    }

    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 p, chat-message .from-user-container .message-text-content div.ng-star-inserted");
            if (userQueryEl) {
                questionText = userQueryEl.innerText.trim();
            }

            let answerText = "";
            const modelResponseContent = turnContainer.querySelector("chat-message .to-user-container .message-text-content");
            if (modelResponseContent) {
                let answerParts = [];
                const structuralElements = modelResponseContent.querySelectorAll('labs-tailwind-structural-element-view-v2');
                structuralElements.forEach(structEl => {
                    const textSpans = structEl.querySelectorAll('span[data-start-index].ng-star-inserted');
                    textSpans.forEach(span => {
                        if (!span.closest('button.citation-marker')) {
                            if (span.closest('.paragraph') || span.closest('.list-item') || span.innerText.length > 1 ) {
                                answerParts.push(span.innerText.trim());
                            }
                        }
                    });
                });
                answerText = answerParts.filter(part => part.length > 0).join(' ').replace(/\s\s+/g, ' ').trim();
            }

            if (questionText || answerText) {
                conversationData.push({
                    human: questionText,
                    assistant: answerText
                });
            }
        });

        if (conversationData.length === 0) {
            const emptyState = document.querySelector('.chat-panel-empty-state');
            if (emptyState) {
                const titleEl = emptyState.querySelector('h1.notebook-title');
                const summaryEl = emptyState.querySelector('.summary-content p');
                if (titleEl && summaryEl) {
                    conversationData.push({
                        notebook_title: titleEl.innerText.trim(),
                        notebook_summary: summaryEl.innerText.trim(),
                        type: "notebook_metadata"
                    });
                }
            }
        }

        return conversationData;
    }

    function extractAIStudioConversationData() {
        const turns = document.querySelectorAll('ms-chat-turn');
        const conversationData = [];
        let currentHuman = null;

        turns.forEach(turn => {
            const userElement = turn.querySelector('[data-turn-role="User"] ms-prompt-chunk ms-cmark-node');
            const modelElement = turn.querySelector('[data-turn-role="Model"] ms-prompt-chunk > ms-text-chunk ms-cmark-node');

            if (userElement) {
                currentHuman = userElement.innerText.trim();
            }

            if (modelElement && currentHuman !== null) {
                const assistant = modelElement.innerText.trim();
                conversationData.push({
                    human: currentHuman,
                    assistant: assistant
                });
                currentHuman = null;
            }
        });

        if (currentHuman !== null) {
            conversationData.push({
                human: currentHuman,
                assistant: "[Model response is pending or failed to load]"
            });
        }

        return conversationData;
    }

    // 创建面板
    function createFloatingPanel() {
        if (document.getElementById(CONTROL_ID) || panelInjected) {
            return false;
        }

        const container = document.createElement('div');
        container.id = CONTROL_ID;
        if (isPanelCollapsed) container.classList.add('collapsed');

        // 创建展开/折叠按钮
        const toggleButton = document.createElement('div');
        toggleButton.id = TOGGLE_ID;
        toggleButton.innerHTML = isPanelCollapsed ? 
            '<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="m9 18 6-6-6-6"/></svg>' : 
            '<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="m15 18-6-6 6-6"/></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 = 'Lyra\'s Claude Exporter';
                break;
            case 'gemini':
                title.textContent = 'Gemini JSON Exporter';
                break;
            case 'notebooklm':
                title.textContent = 'NotebookLM JSON Exporter';
                break;
            case 'aistudio':
                title.textContent = 'AI Studio JSON Exporter';
                break;
            default:
                title.textContent = 'Lyra\'s AI Exporter';
        }
        controlsArea.appendChild(title);

        // Claude特有功能
        if (currentPlatform === 'claude') {
            // 创建模式切换开关
            const toggleContainer = document.createElement('div');
            toggleContainer.className = 'lyra-toggle';
            toggleContainer.innerHTML = `
                <span>多分支模式</span>
                <label class="lyra-switch">
                    <input type="checkbox" id="${TREE_SWITCH_ID}" ${checkUrlForTreeMode() ? 'checked' : ''}>
                    <span class="lyra-slider"></span>
                </label>
            `;
            controlsArea.appendChild(toggleContainer);

            // 创建获取UUID按钮
            const uuidButton = document.createElement('button');
            uuidButton.className = 'lyra-button';
            uuidButton.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"/>
                </svg>
                获取对话UUID
            `;
            uuidButton.addEventListener('click', () => {
                const uuid = getCurrentChatUUID();
                if (uuid) {
                    if (!capturedUserId) {
                        showToast("未能获取用户ID,请刷新页面");
                        return;
                    }

                    navigator.clipboard.writeText(uuid).then(() => {
                        console.log("UUID已复制:", uuid);
                        showToast("UUID已复制!");
                    }).catch(err => {
                        console.error("复制失败:", err);
                        showToast("复制失败");
                    });

                    const treeMode = document.getElementById(TREE_SWITCH_ID).checked;
                    const jumpUrl = `https://pro.easychat.top/api/organizations/${capturedUserId}/chat_conversations/${uuid}${treeMode ? '?tree=True&rendering_mode=messages&render_all_tools=true' : ''}`;
                    window.open(jumpUrl, "_blank");
                } else {
                    showToast("未找到UUID!");
                }
            });
            controlsArea.appendChild(uuidButton);

            // 创建导出JSON按钮
            const exportButton = document.createElement('button');
            exportButton.className = 'lyra-button';
            exportButton.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20ZM8 15.01L9.41 16.42L11 14.84V19H13V14.84L14.59 16.43L16 15.01L12.01 11L8 15.01Z"/>
                </svg>
                导出对话JSON
            `;
            exportButton.addEventListener('click', async () => {
                const uuid = getCurrentChatUUID();
                if (uuid) {
                    if (!capturedUserId) {
                        showToast("未能获取用户ID,请刷新页面");
                        return;
                    }

                    try {
                        const treeMode = document.getElementById(TREE_SWITCH_ID).checked;
                        const apiUrl = `https://pro.easychat.top/api/organizations/${capturedUserId}/chat_conversations/${uuid}${treeMode ? '?tree=True&rendering_mode=messages&render_all_tools=true' : ''}`;
                        
                        showToast("正在获取数据...");
                        const response = await fetch(apiUrl);
                        if (!response.ok) {
                            throw new Error(`请求失败: ${response.status}`);
                        }
                        
                        const data = await response.json();
                        
                        // 创建下载
                        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)}_${new Date().toISOString().slice(0,10)}.json`;
                        document.body.appendChild(a);
                        a.click();
                        document.body.removeChild(a);
                        URL.revokeObjectURL(url);
                        
                        showToast("JSON导出成功!");
                    } catch (error) {
                        console.error("导出失败:", error);
                        showToast("导出失败: " + error.message);
                    }
                } else {
                    showToast("未找到对话UUID!");
                }
            });
            controlsArea.appendChild(exportButton);

            // 创建导出所有对话按钮
            const exportAllButton = document.createElement('button');
            exportAllButton.className = 'lyra-button';
            exportAllButton.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z"/>
                </svg>
                导出所有对话
            `;
            exportAllButton.addEventListener('click', async function(event) {
                if (!capturedUserId) {
                    showToast("未能获取用户ID,请刷新页面");
                    return;
                }

                // 创建进度显示
                const progressElem = document.createElement('div');
                progressElem.className = 'lyra-progress';
                progressElem.textContent = '准备中...';
                controlsArea.appendChild(progressElem);

                // 禁用按钮
                const originalContent = this.innerHTML;
                this.innerHTML = '<div class="lyra-loading"></div>';
                this.disabled = true;

                try {
                    showToast("正在获取所有对话列表...", 3000);
                    const allConversations = await getAllConversations();
                    
                    if (!allConversations || !Array.isArray(allConversations)) {
                        throw new Error("无法获取对话列表");
                    }

                    const result = {
                        exportedAt: new Date().toISOString(),
                        totalConversations: allConversations.length,
                        conversations: []
                    };

                    // 逐个获取对话详情
                    for (let i = 0; i < allConversations.length; i++) {
                        const conversation = allConversations[i];
                        progressElem.textContent = `获取对话 ${i + 1}/${allConversations.length}`;
                        
                        if (i > 0) {
                            await sleep(500);
                        }

                        const details = await getConversationDetails(conversation.uuid);
                        if (details) {
                            result.conversations.push(details);
                        } else {
                            console.warn(`跳过无法获取的对话: ${conversation.uuid}`);
                        }
                    }

                    // 创建并下载文件
                    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_${new Date().toISOString().slice(0,10)}.json`;
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                    URL.revokeObjectURL(url);

                    showToast(`成功导出 ${result.conversations.length} 个对话!`);
                } catch (error) {
                    console.error("导出所有对话失败:", error);
                    showToast("导出失败: " + error.message);
                } finally {
                    // 恢复按钮状态
                    this.innerHTML = originalContent;
                    this.disabled = false;
                    
                    // 移除进度显示
                    if (progressElem && progressElem.parentNode) {
                        progressElem.parentNode.removeChild(progressElem);
                    }
                }
            });
            controlsArea.appendChild(exportAllButton);
        } else {
            // 其他平台的导出按钮
            const exportButton = document.createElement('button');
            exportButton.className = 'lyra-button';
            exportButton.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20ZM8 15.01L9.41 16.42L11 14.84V19H13V14.84L14.59 16.43L16 15.01L12.01 11L8 15.01Z"/>
                </svg>
                Export to JSON
            `;
            exportButton.addEventListener('click', async function() {
                this.disabled = true;

                let conversationData = [];
                try {
                    switch (currentPlatform) {
                        case 'gemini':
                            conversationData = extractGeminiConversationData();
                            break;
                        case 'notebooklm':
                            conversationData = extractNotebookLMConversationData();
                            break;
                        case 'aistudio':
                            conversationData = extractAIStudioConversationData();
                            break;
                    }

                    if (conversationData.length > 0) {
                        const timestamp = new Date().toISOString().replace(/:/g, '-').slice(0, 19);
                        let filenamePrefix = 'Chat';
                        if (currentPlatform === 'gemini') filenamePrefix = 'Gemini';
                        if (currentPlatform === 'notebooklm') filenamePrefix = 'NotebookLM';
                        if (currentPlatform === 'aistudio') filenamePrefix = 'AIStudio';

                        const filename = `${filenamePrefix}_Chat_${timestamp}.json`;
                        const jsonData = JSON.stringify(conversationData, 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);
                        
                        showToast("Export successful!");
                    } else {
                        alert("No conversation content to export. Please ensure the conversation is visible.");
                    }
                } catch (error) {
                    console.error("Export error:", error);
                    alert(`An error occurred during export: ${error.message}`);
                } finally {
                    this.disabled = false;
                }
            });
            controlsArea.appendChild(exportButton);
        }

        container.appendChild(controlsArea);
        document.body.appendChild(container);
        panelInjected = true;
        
        return true;
    }

    // 初始化
    function initScript() {
        if (!currentPlatform) {
            console.error("[Lyra's Universal Exporter] Platform not supported");
            return;
        }

        injectCustomStyle();
        
        // 延迟创建面板,确保页面加载完成
        setTimeout(() => {
            // Claude需要在对话页面
            if (currentPlatform === 'claude') {
                if (/\/chat\/[a-zA-Z0-9-]+/.test(window.location.href)) {
                    createFloatingPanel();
                }
            } else {
                // 其他平台直接创建
                createFloatingPanel();
            }
        }, 1500);

        // 监听URL变化(Claude的SPA)
        if (currentPlatform === 'claude') {
            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 });
        }
    }

    // 启动脚本
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initScript);
    } else {
        initScript();
    }
})();