Perplexity Playground Advanced

Añade funcionalidades avanzadas a Perplexity Playground: guardar/exportar/cargar conversaciones, un único menú de prompts, contador de caracteres, la capacidad de editar y reenviar mensajes anteriores (resaltado), y una importación de archivos de texto/imagen/PDF/Word versátil con OCR.

// ==UserScript==
// @name         Perplexity Playground Advanced
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Añade funcionalidades avanzadas a Perplexity Playground: guardar/exportar/cargar conversaciones, un único menú de prompts, contador de caracteres, la capacidad de editar y reenviar mensajes anteriores (resaltado), y una importación de archivos de texto/imagen/PDF/Word versátil con OCR.
// @match        https://playground.perplexity.ai/*
// @author       YouTubeDrawaria
// @grant        none
// @license      MIT
// @icon         https://playground.perplexity.ai/favicon.ico
// ==/UserScript==

(function() {
    'use strict';

    // Configuración actualizada para detectores más robustos
    const CONFIG = {
        DEFAULT_MODEL_VALUE: 'sonar-pro',
        INIT_TIMEOUT: 10000, // 10 segundos máximo para inicializar
        RETRY_INTERVAL: 500,  // Verificar cada 500ms
        MAX_RETRIES: 20
    };

    // Selectores CSS actualizados para ser más robustos
    const SELECTORS = {
        // Buscar textareas con placeholder específico o atributos comunes
        INPUT_TEXTAREA: 'textarea[placeholder*="Ask"], textarea[placeholder*="anything"], textarea[data-testid*="input"], textarea[aria-label*="message"]',
        // Contenedores de chat más genéricos
        CHAT_CONTAINER: '[data-testid*="chat"], [class*="chat"], [class*="conversation"], main, [role="main"]',
        CHAT_MESSAGES: '[class*="message"], [data-testid*="message"], [class*="bubble"]',
        // Burbujas de mensaje más genéricas
        MESSAGE_BUBBLE: '[class*="message"], [class*="bubble"], [class*="chat-message"]',
        MESSAGE_TEXT: '.prose, [class*="text"], [class*="content"], p',
        // Header y controles
        HEADER_CONTAINER: 'header, [data-testid*="header"], [class*="header"], nav',
        INPUT_CONTAINER: '[class*="input"], [class*="textarea"], form',
        CLEAR_BUTTON: 'button[aria-label*="clear"], button[title*="clear"], [data-testid*="clear"]',
        // Select de modelo
        MODEL_SELECT: 'select, [role="combobox"], [data-testid*="model"], [class*="select"]'
    };

    // Prompts categorizados (mantenido igual)
    const ALL_CATEGORIZED_PROMPTS = {
         "Prompts de Juego": [
            { name: "Juego Simple HTML", text: `Crea un juego en un solo archivo HTML. No uses data:image/png;base64. Genera los gráficos usando formas y SVG.` },
            { name: "Juego Completo", text: `Genera recursos, sprites, assets, sfx, música, mecánicas, conceptos, diseños de juego, ideas y características para un juego completo. Sé preciso, inteligente y conciso.` },
            { name: "Recrear Juego", text: `Crea un prompt detallado para que una IA recree un juego existente. Explica paso a paso cómo debe abordar la recreación, incluyendo el análisis del juego original, la identificación de mecánicas clave, la creación de assets, la implementación del código y las fases de prueba. Sé minucioso en cada detalle.` },
            { name: "Juego Complejo HTML", text: `Crea un juego en un solo archivo HTML con un mapa grande, añade elementos, objetos, detalles y los mejores gráficos. Sé preciso, inteligente y conciso. Usa solo formas y SVG para todos los gráficos (sin base64encoded o imágenes PNG). Todos los gráficos deben ser creados usando formas y trazados SVG, sin recursos externos, con animaciones y transiciones fluidas, mecánicas de batalla por turnos adecuadas, elementos de UI responsivos, un sistema de gestión de salud, cuatro movimientos diferentes con cálculo de daño aleatorio, IA enemiga con lógica de ataque básica, y retroalimentación visual para ataques y daño.` },
            { name: "Juego Detallado", text: `Mejora, expande y perfecciona un juego existente. El juego debe tener un mapa grande, y debe incluir elementos, objetos, detalles y los mejores gráficos, junto con personajes mejorados y detallados. Quiero que todo el juego esté contenido en un solo archivo. No uses imágenes base64encoded o PNG; debes crear los gráficos con la máxima complejidad, detalle y mejora posible, utilizando únicamente formas y SVG. Haz que el juego sea lo mejor y más grande que pueda ser. Además, añade más tipos de plataformas, crea más tipos de enemigos, implementa diferentes efectos de power-up, establece un sistema de niveles, diseña distintos entornos, desarrolla una IA enemiga más compleja, haz que los movimientos de los jugadores sean más suaves y mejora la interfaz de usuario tanto para el jugador como para los enemigos.` }
        ],
        "Prompts Web": [
            { name: "Web Moderna", text: `Crea el código para una landing page de un sitio web moderno que . Make the code for landing page. Make sure it looks nice and well designed` }
        ],
        "Prompts Personaje": [
            { name: "Descripción Personaje", text: `Haz una descripción larga describiendo todo sobre el personaje con información extra detallada. Haz una descripción profesional describiendo detalladamente todo sobre la imagen con información más detallada.` }
        ],
        "Prompts Canción": [
            { name: "Atributos de Canción", text: `Dame los atributos de la canción separados por comas. Atributos de la canción separados por comas.` }
        ],
        "Prompts Gemini": [
            { name: "Generar 4 Imágenes X", text: `Genera 4 nuevas [X] diferentes en 4 imágenes cada una.` }
        ],
        "Prompts de Scripting/Desarrollo": [
            { name: "Crear Script Drawaria", text: `Crea un script tampermonkey completo para drawaria.online con la siguiente estructura inicial:\n // ==UserScript==\n// @name New Userscript\n// @namespace http://tampermonkey.net/\n// @version 1.0\n// @description try to take over the world!\n// @author YouTubeDrawaria\n// @match https://drawaria.online/*\n// @grant none\n// @license MIT\n// @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online\n// ==/UserScript==\n\n(function() {\n    'use strict';\n\n    // Your code here...\n})();\n` },
            { name: "Script Drawaria Avanzado", text: "Crea un script tampermonkey completo para drawaria.online con funcionalidades avanzadas: efectos visuales, partículas, animaciones, interfaz mejorada, y características especiales. No uses placeholders ni archivos externos." },
            { name: "Mejorar Script Drawaria", text: `Mejora, actualiza, maximiza, sorprende, crea realismo y alto nivel de detalle en el script para drawaria.online. Quiero elementos de X en pantalla, música, efectos, partículas, brillos y una interfaz bien animada y detallada con todo. No uses placeholders, .mp3 ni data:image/png;base64. Debes crear los gráficos tú mismo, sin archivos reemplazables.` },
            { name: "Atributos de Juego", text: `Dame los atributos de un juego. Incluye: icono del juego (<link rel="icon" href="https://drawaria.online/avatar/cache/ab53c430-1b2c-11f0-af95-072f6d4ed084.1749767757401.jpg" type="image/x-icon">) y música de fondo con reproducción automática al hacer clic: (<audio id="bg-music" src="https://www.myinstants.com/media/sounds/super-mini-juegos-2.mp3" loop></audio><script>const music = document.getElementById('bg-music'); document.body.addEventListener('click', () => { if (music.paused) { music.play(); } });</script>).` },
            { name: "API Cubic Engine Info", text: `Proporciona información sobre APIs ampliamente utilizadas que no estén alojadas en Vercel, no presenten problemas con CORS al usarlas desde navegadores/shell, se puedan integrar rápidamente en Cubic Engine / Drawaria, y sean gratuitas y de uso inmediato.` },
            { name: "Integrar Función Cubic Engine", text: `Para integrar una nueva adición a un módulo de Cubic Engine, necesito el código completo actualizado de la función. Esto incluye el botón con todas sus propiedades, los activadores con sus IDs, los listeners de este evento y los archivos que lo ejecutan. Solo proporciona el código de la función actualizada, no el código de Cubic Engine desde cero.` }
        ]
    };

    let featuresInitialized = false;
    let retryCount = 0;

    // Iconos SVG
    const DOWNLOAD_ICON_SVG = `
        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
            <path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"></path>
            <polyline points="7 11 12 16 17 11"></polyline>
            <line x1="12" y1="4" x2="12" y2="16"></line>
        </svg>
    `;

    const UPLOAD_ICON_SVG = `
        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
            <path d="M4 7v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-10"></path>
            <polyline points="17 7 12 2 7 7"></polyline>
            <line x1="12" y1="4" x2="12" y2="14"></line>
        </svg>
    `;

    /**
     * Función de utilidad para encontrar elementos de manera más robusta
     */
    function findElement(selectors) {
        if (typeof selectors === 'string') {
            return document.querySelector(selectors);
        }

        for (const selector of selectors) {
            const element = document.querySelector(selector);
            if (element) return element;
        }
        return null;
    }

    function findElements(selectors) {
        if (typeof selectors === 'string') {
            return document.querySelectorAll(selectors);
        }

        for (const selector of selectors) {
            const elements = document.querySelectorAll(selector);
            if (elements.length > 0) return elements;
        }
        return [];
    }

    /**
     * Establecer modelo por defecto de manera más robusta
     */
    function setDefaultModel() {
        const possibleSelectors = [
            '#lamma-select',
            'select[value*="sonar"]',
            'select option[value*="sonar"]',
            '[data-testid*="model"] select',
            'select'
        ];

        for (const selector of possibleSelectors) {
            const element = document.querySelector(selector);
            if (element && element.tagName === 'SELECT') {
                // Buscar opción sonar-pro
                const sonarOption = Array.from(element.options).find(option =>
                    option.value.includes('sonar') || option.textContent.includes('sonar')
                );

                if (sonarOption) {
                    element.value = sonarOption.value;
                    element.dispatchEvent(new Event('change', { bubbles: true }));
                    console.log(`✅ Modelo establecido a: ${sonarOption.value}`);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Crear botón con estilos mejorados
     */
    function createButton(text, onClick, styles = {}) {
        const button = document.createElement('button');
        button.textContent = text;

        const defaultStyles = {
            backgroundColor: '#4a4a50',
            color: 'white',
            padding: '8px 16px',
            border: 'none',
            borderRadius: '8px',
            cursor: 'pointer',
            fontSize: '14px',
            fontWeight: '500',
            marginLeft: '8px',
            transition: 'all 0.2s ease',
            whiteSpace: 'nowrap',
            ...styles
        };

        Object.assign(button.style, defaultStyles);

        button.addEventListener('mouseenter', () => {
            button.style.backgroundColor = '#5c5c63';
            button.style.transform = 'translateY(-1px)';
        });

        button.addEventListener('mouseleave', () => {
            button.style.backgroundColor = defaultStyles.backgroundColor;
            button.style.transform = 'translateY(0)';
        });

        button.addEventListener('click', onClick);
        return button;
    }

    /**
     * Crear botón de icono con estilos predefinidos
     */
    function createIconButton(svgContent, title, onClick, styles = {}) {
        const button = document.createElement('button');
        button.innerHTML = svgContent;
        button.title = title;

        const defaultStyles = {
            backgroundColor: '#4a4a50',
            color: 'white',
            padding: '10px',
            border: 'none',
            borderRadius: '8px',
            cursor: 'pointer',
            transition: 'all 0.2s ease',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            marginLeft: '8px',
            ...styles
        };

        Object.assign(button.style, defaultStyles);

        button.addEventListener('mouseenter', () => {
            button.style.backgroundColor = '#5c5c63';
            button.style.transform = 'translateY(-1px)';
        });

        button.addEventListener('mouseleave', () => {
            button.style.backgroundColor = defaultStyles.backgroundColor;
            button.style.transform = 'translateY(0)';
        });

        button.addEventListener('click', onClick);
        return button;
    }

    /**
     * Crear dropdown categorizado mejorado
     */
    function createCategorizedDropdown(categorizedOptions, onSelect, placeholder = "Seleccionar Prompt") {
        const select = document.createElement('select');

        Object.assign(select.style, {
            backgroundColor: '#4a4a50',
            color: 'white',
            padding: '8px 12px',
            border: 'none',
            borderRadius: '8px',
            cursor: 'pointer',
            fontSize: '14px',
            marginLeft: '8px',
            minWidth: '180px',
            transition: 'all 0.2s ease'
        });

        const defaultOption = document.createElement('option');
        defaultOption.value = "";
        defaultOption.textContent = placeholder;
        defaultOption.disabled = true;
        defaultOption.selected = true;
        select.appendChild(defaultOption);

        for (const category in categorizedOptions) {
            const optgroup = document.createElement('optgroup');
            optgroup.label = category;
            optgroup.style.color = '#cccccc';

            categorizedOptions[category].forEach(opt => {
                const option = document.createElement('option');
                option.value = opt.text;
                option.textContent = opt.name;
                option.style.color = 'white';
                optgroup.appendChild(option);
            });
            select.appendChild(optgroup);
        }

        select.addEventListener('change', (event) => {
            if (event.target.value) {
                onSelect(event.target.value);
                event.target.value = "";
            }
        });

        return select;
    }

    /**
     * Obtener contenido del chat de manera más robusta
     */
    function getCurrentChatContent() {
        const chatContent = [];

        // Intentar múltiples selectores para encontrar mensajes
        const messageSelectors = [
            '[class*="message"]',
            '[data-testid*="message"]',
            '[class*="bubble"]',
            '[class*="chat-item"]'
        ];

        let messages = [];
        for (const selector of messageSelectors) {
            messages = document.querySelectorAll(selector);
            if (messages.length > 0) break;
        }

        messages.forEach((message, index) => {
            let messageText = '';
            let isUserMessage = false;

            // Detectar si es mensaje del usuario
            const parent = message.closest('[class*="justify-end"], [class*="user"], [data-role="user"]');
            const hasUserClass = message.classList.toString().includes('user') ||
                                message.getAttribute('data-role') === 'user';

            isUserMessage = !!(parent || hasUserClass);

            // Extraer texto del mensaje
            const textElement = message.querySelector('.prose, [class*="text"], [class*="content"], p') || message;
            messageText = textElement.innerText?.trim() || textElement.textContent?.trim() || '';

            if (messageText && messageText.length > 0) {
                chatContent.push({
                    type: isUserMessage ? 'User' : 'Perplexity',
                    text: messageText,
                    timestamp: new Date().toISOString()
                });
            }
        });

        return chatContent;
    }

    // --- FUNCIONES DE IMPORTACIÓN DE ARCHIVOS ---

    /**
     * Cargar librerías externas dinámicamente
     */
    function loadTesseractJs() {
        return new Promise((resolve, reject) => {
            if (window.Tesseract) {
                resolve();
                return;
            }
            console.log("📷 Cargando Tesseract.js para OCR...");
            const script = document.createElement('script');
            script.src = "https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js";
            script.onload = () => window.Tesseract ? resolve() : reject(new Error("Tesseract.js no disponible"));
            script.onerror = reject;
            document.head.appendChild(script);
        });
    }

    function loadPdfJs() {
        return new Promise((resolve, reject) => {
            if (window.pdfjsLib) {
                resolve();
                return;
            }
            console.log("📄 Cargando PDF.js...");
            const script = document.createElement('script');
            script.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.2.67/pdf.min.js";
            script.onload = () => {
                if (window.pdfjsLib) {
                    window.pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.2.67/pdf.worker.min.js";
                    resolve();
                } else {
                    reject(new Error("PDF.js no disponible"));
                }
            };
            script.onerror = reject;
            document.head.appendChild(script);
        });
    }

    function loadMammothJs() {
        return new Promise((resolve, reject) => {
            if (window.mammoth) {
                resolve();
                return;
            }
            console.log("📝 Cargando Mammoth.js para DOCX...");
            const script = document.createElement('script');
            script.src = "https://unpkg.com/mammoth/mammoth.browser.min.js";
            script.onload = () => window.mammoth ? resolve() : reject(new Error("Mammoth.js no disponible"));
            script.onerror = reject;
            document.head.appendChild(script);
        });
    }

    /**
     * Funciones de procesamiento de archivos
     */
    function readFileAsText(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = (e) => resolve(e.target.result);
            reader.onerror = reject;
            reader.readAsText(file);
        });
    }

    async function extractTextFromPdf(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = async (e) => {
                try {
                    const typedarray = new Uint8Array(e.target.result);
                    const loadingTask = window.pdfjsLib.getDocument({ data: typedarray });
                    const pdf = await loadingTask.promise;
                    let text = '';
                    for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
                        const page = await pdf.getPage(pageNum);
                        const content = await page.getTextContent();
                        text += content.items.map(item => item.str).join(' ') + '\n';
                    }
                    resolve(text);
                } catch (err) {
                    reject(new Error(`Error al procesar PDF: ${err.message}`));
                }
            };
            reader.onerror = reject;
            reader.readAsArrayBuffer(file);
        });
    }

    async function extractTextFromDocx(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = async (e) => {
                try {
                    const arrayBuffer = e.target.result;
                    const result = await mammoth.extractRawText({ arrayBuffer });
                    resolve(result.value.trim());
                } catch (err) {
                    reject(new Error(`Error al procesar DOCX: ${err.message}`));
                }
            };
            reader.onerror = reject;
            reader.readAsArrayBuffer(file);
        });
    }

    /**
     * Procesar archivos importados
     */
    async function processDroppedFiles(files) {
        const textarea = findElement(SELECTORS.INPUT_TEXTAREA);
        if (!textarea) {
           // alert('❌ No se encontró el área de texto');
            return;
        }

        let allContent = '';
        const importButton = document.getElementById('perplexity-import-button');

        // Mostrar progreso en el botón
        const originalTitle = importButton?.title || '';
        const originalBg = importButton?.style.backgroundColor || '';

        for (const file of files) {
            const textExtensions = new Set([
                'txt', 'html', 'htm', 'css', 'js', 'json', 'csv', 'xml', 'md', 'log',
                'yaml', 'yml', 'py', 'java', 'c', 'cpp', 'h', 'hpp', 'go', 'php',
                'rb', 'sh', 'bat', 'ps1', 'ini', 'cfg', 'conf', 'rs', 'ts', 'jsx', 'tsx'
            ]);

            const imageExtensions = new Set(['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp']);

            const fileExt = file.name.split('.').pop()?.toLowerCase() || '';
            const isTextFile = textExtensions.has(fileExt) || file.type.startsWith('text/');
            const isImage = imageExtensions.has(fileExt) || file.type.startsWith('image/');
            const isPdf = fileExt === 'pdf' || file.type === 'application/pdf';
            const isDocx = fileExt === 'docx' || file.type.includes('wordprocessingml');

            try {
                if (allContent) allContent += `\n\n--- ${file.name} ---\n\n`;

                if (isTextFile) {
                    const content = await readFileAsText(file);
                    allContent += content;
                } else if (isImage) {
                    if (importButton) {
                        importButton.style.backgroundColor = '#6366f1';
                        importButton.title = `🔍 OCR: ${file.name}...`;
                    }
                    await loadTesseractJs();
                    const { data: { text } } = await Tesseract.recognize(
                        file,
                        'spa+eng',
                        {
                            logger: m => {
                                if (importButton && m.status === 'recognizing') {
                                    importButton.title = `📸 OCR ${file.name}: ${Math.round(m.progress * 100)}%`;
                                }
                            }
                        }
                    );
                    allContent += text.trim();
                } else if (isPdf) {
                    if (importButton) {
                        importButton.style.backgroundColor = '#6366f1';
                        importButton.title = `📄 PDF: ${file.name}...`;
                    }
                    await loadPdfJs();
                    const text = await extractTextFromPdf(file);
                    allContent += text.trim();
                } else if (isDocx) {
                    if (importButton) {
                        importButton.style.backgroundColor = '#6366f1';
                        importButton.title = `📝 DOCX: ${file.name}...`;
                    }
                    await loadMammothJs();
                    const text = await extractTextFromDocx(file);
                    allContent += text;
                } else {
                    console.warn(`⚠️ Tipo de archivo no soportado: ${file.name}`);
                    continue;
                }
            } catch (error) {
                console.error(`❌ Error procesando ${file.name}:`, error);
              //  alert(`❌ Error al procesar ${file.name}: ${error.message}`);
            }
        }

        // Restaurar botón
        if (importButton) {
            importButton.title = originalTitle;
            importButton.style.backgroundColor = originalBg;
        }

        if (allContent.trim()) {
            insertTextIntoTextarea(textarea.value + (textarea.value ? '\n\n' : '') + allContent);
          //  alert(`✅ Archivos importados exitosamente`);
        }
    }

    /**
     * Funciones de guardado y carga mejoradas
     */
    function saveCurrentChat() {
        const chatContent = getCurrentChatContent();
        if (chatContent.length === 0) {
            //alert('❌ No hay conversación para guardar.');
            return;
        }

        const chatName = prompt("💾 Introduce un nombre para esta conversación:",
                               `Chat ${new Date().toLocaleString()}`);
        if (chatName) {
            try {
                const savedChats = JSON.parse(localStorage.getItem('perplexity_playground_chats') || '[]');
                savedChats.push({
                    name: chatName,
                    timestamp: new Date().toISOString(),
                    messages: chatContent
                });
                localStorage.setItem('perplexity_playground_chats', JSON.stringify(savedChats));
                //alert(`✅ Conversación "${chatName}" guardada con éxito.`);
            } catch (e) {
                console.error("Error al guardar:", e);
                //alert("❌ Error al guardar la conversación.");
            }
        }
    }

    function loadSavedChats() {
        const savedChats = JSON.parse(localStorage.getItem('perplexity_playground_chats') || '[]');
        if (savedChats.length === 0) {
            //alert('📂 No hay conversaciones guardadas.');
            return;
        }

        // Crear modal simple para mostrar chats
        showChatModal(savedChats);
    }

    function showChatModal(savedChats) {
        const modal = document.createElement('div');
        modal.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.8);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 10000;
        `;

        const content = document.createElement('div');
        content.style.cssText = `
            background: #2b2b30;
            color: white;
            padding: 20px;
            border-radius: 12px;
            max-width: 80%;
            max-height: 80%;
            overflow-y: auto;
        `;

        let html = '<h3 style="color: #6366f1; margin-bottom: 20px;">📚 Conversaciones Guardadas</h3>';

        savedChats.forEach((chat, index) => {
            html += `
                <div style="background: #3a3a40; padding: 15px; margin: 10px 0; border-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
                    <span>${chat.name} (${new Date(chat.timestamp).toLocaleString()})</span>
                    <div>
                        <button onclick="viewChat(${index})" style="background: #6366f1; color: white; border: none; padding: 8px 12px; border-radius: 6px; margin-right: 5px; cursor: pointer;">👁️ Ver</button>
                        <button onclick="deleteChat(${index})" style="background: #dc3545; color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer;">🗑️ Eliminar</button>
                    </div>
                </div>
            `;
        });

        content.innerHTML = html + '<button onclick="this.closest(\'[style*=fixed]\').remove()" style="background: #666; color: white; border: none; padding: 10px 20px; border-radius: 6px; margin-top: 20px; cursor: pointer;">❌ Cerrar</button>';

        // Añadir funciones globales temporales
        window.viewChat = (index) => {
            const chat = savedChats[index];
            let chatHtml = `<h3 style="color: #6366f1;">💬 ${chat.name}</h3><div style="background: #1a1a1a; padding: 15px; border-radius: 8px; max-height: 400px; overflow-y: auto;">`;

            chat.messages.forEach(msg => {
                const isUser = msg.type === 'User';
                chatHtml += `
                    <div style="text-align: ${isUser ? 'right' : 'left'}; margin: 10px 0;">
                        <div style="display: inline-block; background: ${isUser ? '#007bff' : '#333'}; padding: 10px; border-radius: 10px; max-width: 80%;">
                            <strong style="color: ${isUser ? '#cceeff' : '#aaffaa'};">${msg.type}:</strong><br>
                            ${msg.text.replace(/\n/g, '<br>')}
                        </div>
                    </div>
                `;
            });

            chatHtml += '</div><button onclick="this.closest(\'[style*=fixed]\').remove()" style="background: #666; color: white; border: none; padding: 10px 20px; border-radius: 6px; margin-top: 20px; cursor: pointer;">⬅️ Volver</button>';
            content.innerHTML = chatHtml;
        };

        window.deleteChat = (index) => {
            if (confirm(`¿Eliminar "${savedChats[index].name}"?`)) {
                savedChats.splice(index, 1);
                localStorage.setItem('perplexity_playground_chats', JSON.stringify(savedChats));
                modal.remove();
                if (savedChats.length > 0) {
                    showChatModal(savedChats);
                } else {
                    //alert('✅ Todas las conversaciones eliminadas.');
                }
            }
        };

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

        // Cerrar con clic fuera
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                modal.remove();
                delete window.viewChat;
                delete window.deleteChat;
            }
        });
    }

    function exportChatToText() {
        const chatContent = getCurrentChatContent();
        if (chatContent.length === 0) {
            //alert('❌ No hay conversación para exportar.');
            return;
        }

        let exportText = `=== Conversación Perplexity Playground ===\n`;
        exportText += `Fecha: ${new Date().toLocaleString()}\n`;
        exportText += `Mensajes: ${chatContent.length}\n\n`;

        chatContent.forEach((msg, index) => {
            exportText += `[${index + 1}] ${msg.type}:\n`;
            exportText += `${msg.text}\n\n`;
            exportText += `${'='.repeat(50)}\n\n`;
        });

        const blob = new Blob([exportText], { type: 'text/plain; charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `perplexity_chat_${new Date().toISOString().slice(0,19).replace(/[:.]/g, '-')}.txt`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
        //alert('✅ Conversación exportada exitosamente.');
    }

    /**
     * Función mejorada para insertar texto en el textarea
     */
    function insertTextIntoTextarea(text) {
        const textareaSelectors = [
            'textarea[placeholder*="Ask"]',
            'textarea[placeholder*="anything"]',
            'textarea[data-testid*="input"]',
            'textarea[aria-label*="message"]',
            'textarea'
        ];

        let textarea = null;
        for (const selector of textareaSelectors) {
            textarea = document.querySelector(selector);
            if (textarea) break;
        }

        if (textarea) {
            // Método más robusto para establecer valor
            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
                window.HTMLTextAreaElement.prototype,
                "value"
            ).set;

            nativeInputValueSetter.call(textarea, text);

            // Disparar eventos necesarios
            textarea.dispatchEvent(new Event('input', { bubbles: true }));
            textarea.dispatchEvent(new Event('change', { bubbles: true }));
            textarea.focus();

            return true;
        }

        console.warn('⚠️ No se encontró el textarea');
        return false;
    }

    /**
     * Manejador de selección de prompts
     */
    function handlePromptSelection(promptText) {
        if (!insertTextIntoTextarea(promptText)) {
            // Fallback: copiar al portapapeles
            navigator.clipboard.writeText(promptText).then(() => {
               // alert('📋 Prompt copiado al portapapeles. Pégalo manualmente en el chat.');
            }).catch(() => {
               // alert('❌ No se pudo insertar el prompt automáticamente. Inténtalo de nuevo.');
            });
        }
    }

    /**
     * Crear botón de importación con drag & drop
     */
    function createImportButton() {
        // Crear input de archivo oculto
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.multiple = true;
        fileInput.accept = `
            .txt,.html,.htm,.css,.js,.json,.csv,.xml,.md,.log,.yaml,.yml,.py,.java,.c,.cpp,.h,.hpp,.go,.php,.rb,.sh,.bat,.ps1,.ini,.cfg,.conf,.rs,.ts,.jsx,.tsx,.vue,
            .png,.jpg,.jpeg,.bmp,.gif,.webp,
            .pdf,application/pdf,
            .docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document,
            text/*,application/json,application/xml,application/javascript,image/*
        `.replace(/\s/g, '');
        fileInput.style.display = 'none';

        // Crear botón de icono
        const importButton = createIconButton(
            UPLOAD_ICON_SVG,
            '📎 Importar archivos (texto, imagen con OCR, PDF, DOCX) - Arrastra y suelta o haz clic',
            () => fileInput.click()
        );
        importButton.id = 'perplexity-import-button';

        // Eventos de drag & drop
        ['dragover', 'dragenter'].forEach(eventName => {
            importButton.addEventListener(eventName, (e) => {
                e.preventDefault();
                e.stopPropagation();
                importButton.style.backgroundColor = '#6366f1';
                importButton.style.border = '2px dashed #fff';
            });
        });

        ['dragleave', 'dragend'].forEach(eventName => {
            importButton.addEventListener(eventName, (e) => {
                e.preventDefault();
                e.stopPropagation();
                importButton.style.backgroundColor = '#4a4a50';
                importButton.style.border = 'none';
            });
        });

        importButton.addEventListener('drop', (e) => {
            e.preventDefault();
            e.stopPropagation();
            importButton.style.backgroundColor = '#4a4a50';
            importButton.style.border = 'none';

            if (e.dataTransfer.files.length > 0) {
                processDroppedFiles(e.dataTransfer.files);
            }
        });

        // Evento para selección de archivos
        fileInput.addEventListener('change', (event) => {
            if (event.target.files.length > 0) {
                processDroppedFiles(event.target.files);
                event.target.value = '';
            }
        });

        document.body.appendChild(fileInput);
        return importButton;
    }

    /**
     * Configurar contador de caracteres mejorado
     */
    function setupCharacterCounter() {
        const textarea = findElement(SELECTORS.INPUT_TEXTAREA);
        if (!textarea) return;

        // Evitar duplicados
        if (document.getElementById('char-counter-perplexity')) return;

        const counter = document.createElement('div');
        counter.id = 'char-counter-perplexity';
        counter.style.cssText = `
            position: absolute;
            bottom: 8px;
            right: 12px;
            font-size: 11px;
            color: #888;
            background: rgba(0,0,0,0.7);
            padding: 2px 6px;
            border-radius: 4px;
            pointer-events: none;
            z-index: 1000;
        `;

        const container = textarea.closest('[class*="relative"], div') || textarea.parentElement;
        if (container) {
            if (getComputedStyle(container).position === 'static') {
                container.style.position = 'relative';
            }
            container.appendChild(counter);
        }

        const updateCounter = () => {
            const text = textarea.value;
            const chars = text.length;
            const words = text.trim() ? text.trim().split(/\s+/).length : 0;
            counter.textContent = `${chars} chars | ${words} words`;
        };

        textarea.addEventListener('input', updateCounter);
        updateCounter();
    }

    /**
     * Agregar funcionalidades a la interfaz
     */
    function addControlsToUI() {
        // Buscar área de header/controles
        const headerSelectors = [
            'header',
            '[data-testid*="header"]',
            '[class*="header"]',
            'nav',
            '[class*="flex"][class*="items-center"]:not([class*="message"])'
        ];

        let headerContainer = null;
        for (const selector of headerSelectors) {
            const elements = document.querySelectorAll(selector);
            for (const element of elements) {
                // Verificar que no sea un contenedor de mensaje
                if (!element.closest('[class*="message"]') &&
                    element.offsetWidth > 200 &&
                    element.offsetHeight < 100) {
                    headerContainer = element;
                    break;
                }
            }
            if (headerContainer) break;
        }

        if (!headerContainer) {
            // Crear nuestro propio contenedor si no encontramos uno
            headerContainer = document.createElement('div');
            headerContainer.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                z-index: 1000;
                background: rgba(0,0,0,0.8);
                padding: 10px;
                border-radius: 10px;
                display: flex;
                gap: 8px;
                flex-wrap: wrap;
            `;
            document.body.appendChild(headerContainer);
        }

        // Evitar duplicados
        if (headerContainer.querySelector('[data-perplexity-controls]')) {
            return;
        }

        const controlsContainer = document.createElement('div');
        controlsContainer.setAttribute('data-perplexity-controls', 'true');
        controlsContainer.style.cssText = `
            display: flex;
            gap: 8px;
            align-items: center;
            flex-wrap: wrap;
        `;

        // Crear controles
        const promptsDropdown = createCategorizedDropdown(
            ALL_CATEGORIZED_PROMPTS,
            handlePromptSelection,
            "🎯 Prompts"
        );

        const saveButton = createButton('💾 Guardar', saveCurrentChat);
        const loadButton = createButton('📂 Cargar', loadSavedChats);
        const exportButton = createButton('📤 Exportar', exportChatToText);
        const importButton = createImportButton(); // ¡AQUÍ ESTÁ EL BOTÓN DE IMPORTAR!

        controlsContainer.appendChild(promptsDropdown);
        controlsContainer.appendChild(saveButton);
        controlsContainer.appendChild(loadButton);
        controlsContainer.appendChild(exportButton);
        controlsContainer.appendChild(importButton); // Junto al exportar

        headerContainer.appendChild(controlsContainer);
        console.log('✅ Controles añadidos a la interfaz (incluyendo importar)');
    }

    /**
     * Inicialización principal mejorada
     */
    function initializeFeatures() {
        if (featuresInitialized) return;

        console.log('🚀 Iniciando Perplexity Playground Advanced...');

        try {
            // Establecer modelo por defecto
            setTimeout(() => setDefaultModel(), 1000);

            // Añadir controles a la UI (incluyendo importar)
            addControlsToUI();

            // Configurar contador de caracteres
            setupCharacterCounter();

            featuresInitialized = true;
            console.log('✅ Perplexity Playground Advanced inicializado correctamente');

        } catch (error) {
            console.error('❌ Error durante la inicialización:', error);
        }
    }

    /**
     * Verificar si la página está lista
     */
    function checkPageReady() {
        // Verificar elementos clave
        const textarea = findElement(SELECTORS.INPUT_TEXTAREA);
        const hasContent = document.body.children.length > 5;

        if (textarea && hasContent) {
            console.log('✅ Página lista, inicializando funciones...');
            initializeFeatures();
            return true;
        }

        return false;
    }

    /**
     * Sistema de reintentos mejorado
     */
    function startInitialization() {
        if (checkPageReady()) {
            return;
        }

        retryCount++;
        if (retryCount >= CONFIG.MAX_RETRIES) {
            console.warn('⚠️ Máximo de reintentos alcanzado. La página podría no estar completamente cargada.');
            // Intentar inicialización forzada
            setTimeout(initializeFeatures, 1000);
            return;
        }

        console.log(`🔄 Reintento ${retryCount}/${CONFIG.MAX_RETRIES}...`);
        setTimeout(startInitialization, CONFIG.RETRY_INTERVAL);
    }

    // Inicialización cuando el DOM esté listo
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', startInitialization);
    } else {
        startInitialization();
    }

    // Observer para detectar cambios en la página
    const observer = new MutationObserver((mutations) => {
        if (!featuresInitialized) {
            // Verificar si ahora podemos inicializar
            if (checkPageReady()) {
                observer.disconnect();
            }
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: false
    });

    // Cleanup al cambiar de página
    window.addEventListener('beforeunload', () => {
        if (window.viewChat) delete window.viewChat;
        if (window.deleteChat) delete window.deleteChat;
        observer.disconnect();
    });

    console.log('📋 Perplexity Playground Advanced v2.1 cargado');

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址