您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.2 // @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 y específicos para Perplexity const SELECTORS = { INPUT_TEXTAREA: 'textarea[placeholder="Ask anything…"]', PERPLEXITY_MESSAGES: 'div[class*="bg-offset"][class*="px-md py-sm"]', USER_MESSAGES: 'div[class*="bg-base"][class*="px-md py-sm"][class*="inline-block"]', MESSAGE_TEXT_PROSE_P: '.prose p[class*="my-2"]', CONTROLS_AREA: 'div[class*="gap-x-sm flex items-center"]', CLEAR_BUTTON: 'button[aria-label="Clear Chat"]', STOP_BUTTON: 'button[aria-label="Stop Generating"]', SUBMIT_BUTTON: 'button[aria-label="Submit"]', CODE_BLOCKS: 'div[class*="-mt-xl"] code', CODE_CONTAINERS: 'div[data-testid="code-language-indicator"]', CODE_CONTENT: 'code[style*="white-space: pre-wrap"]' }; 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; 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> `; // ========================================================= // Funciones principales y de utilidad - Definidas al inicio para hoisting // ========================================================= function extractCleanCodeContent(codeElement) { try { // Método 1: Intentar obtener texto limpio let codeText = codeElement.innerText || codeElement.textContent || ''; // Método 2: Si el código tiene estructura compleja con spans, extraer de cada span if (!codeText || codeText.length < 10) { const spans = codeElement.querySelectorAll('span'); if (spans.length > 0) { codeText = Array.from(spans) .map(span => span.innerText || span.textContent || '') .join(''); } } // Método 3: Obtener todo el textContent del elemento padre si es necesario if (!codeText || codeText.length < 10) { const codeContainer = codeElement.closest('div[class*="pr-lg"]'); if (codeContainer) { codeText = codeContainer.innerText || codeContainer.textContent || ''; } } // Limpiar el texto del código codeText = codeText .replace(/^\s*[\d\s]+/gm, '') // Remover números de línea al inicio .replace(/\s*\n\s*\n\s*/g, '\n') // Normalizar saltos de línea múltiples .trim(); // Verificar que el contenido sea código válido (no solo espacios o números) if (codeText.length > 5 && /[a-zA-Z(){};=]/.test(codeText)) { return codeText; } return null; } catch (error) { console.warn('⚠️ Error extrayendo código:', error); return null; } } function findElement(selectors) { const specificSelectors = [SELECTORS.INPUT_TEXTAREA, SELECTORS.CONTROLS_AREA, SELECTORS.CLEAR_BUTTON]; if (typeof selectors === 'string') { const element = document.querySelector(selectors); if (element) return element; } const allSelectors = Array.isArray(selectors) ? [...specificSelectors, ...selectors] : specificSelectors; for (const selector of allSelectors) { 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 []; } 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') { 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; } 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)'; }); if (onClick) { button.addEventListener('click', onClick); } return button; } function createPerplexityButton(iconHtml, tooltip, onClick) { const button = document.createElement('button'); button.innerHTML = `<div class="flex items-center min-w-0 gap-two justify-center"><span style="font-size: 16px;">${iconHtml}</span></div>`; button.title = tooltip; button.className = `focus-visible:bg-offsetPlus hover:bg-offsetPlus text-quiet hover:text-foreground dark:hover:bg-offsetPlus font-sans focus:outline-none outline-none outline-transparent transition duration-300 ease-out select-none items-center relative group/button font-semimedium justify-center text-center items-center rounded-full cursor-pointer active:scale-[0.97] active:duration-150 active:ease-outExpo origin-center whitespace-nowrap inline-flex text-base h-10 aspect-square`.replace(/\s+/g, ' ').trim(); if (onClick) { button.addEventListener('click', onClick); } return button; } 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', height: '40px', appearance: 'none', backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E")`, backgroundRepeat: 'no-repeat', backgroundPosition: 'right 8px center', backgroundSize: '16px', paddingRight: '30px' }); 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 = ""; setTimeout(() => { defaultOption.selected = true; }, 0); } }); return select; } function getCurrentChatContent() { const chatContent = []; console.log('🔍 Buscando mensajes con selectores específicos de Perplexity...'); // Buscar todos los mensajes (método universal) const allMessageContainers = document.querySelectorAll([ 'div[class*="bg-offset"]', 'div[class*="bg-base"]', 'div[class*="px-md"]' ].join(',')); allMessageContainers.forEach((container, index) => { let fullText = ''; // Capturar texto normal const textElements = container.querySelectorAll('p, div, span'); textElements.forEach(el => { const text = el.innerText || el.textContent || ''; if (text.trim() && !fullText.includes(text.trim())) { fullText += text.trim() + '\n'; } }); // 🆕 Capturar bloques de código de manera agresiva const codeElements = container.querySelectorAll('code, pre, [class*="code"], [data-testid*="code"]'); codeElements.forEach(codeEl => { const codeText = extractCleanCodeContent(codeEl); if (codeText && !fullText.includes(codeText)) { // Intentar obtener el indicador de lenguaje const languageIndicator = codeEl.closest('div[class*="-mt-xl"]') ?.querySelector('[data-testid="code-language-indicator"]'); const language = languageIndicator?.textContent?.trim() || ''; fullText += `\n\`\`\`${language}\n${codeText}\n\`\`\`\n`; } }); if (fullText.trim()) { const messageType = container.matches('[class*="bg-base"]') ? 'User' : 'Perplexity'; chatContent.push({ type: messageType, text: fullText.trim(), timestamp: new Date().toISOString(), index: index }); } }); console.log(`✅ Mensajes extraídos correctamente: ${chatContent.length}`); if (chatContent.length > 0) { console.log('📝 Primeros mensajes encontrados:'); chatContent.slice(0, 3).forEach((msg, i) => { console.log(`${i + 1}. [${msg.type}] ${msg.text.substring(0, Math.min(msg.text.length, 100))}...`); }); } return chatContent; } 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); }); } 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); }); } async function processDroppedFiles(files) { const textarea = findElement(SELECTORS.INPUT_TEXTAREA); if (!textarea) { console.error('❌ No se encontró el área de texto'); return; } let allContent = ''; const importButton = document.getElementById('perplexity-import-button'); const originalTitle = importButton?.title || ''; const updateButtonStatus = (title, iconHtml = UPLOAD_ICON_SVG) => { if (importButton) { importButton.title = title; const iconSpan = importButton.querySelector('span'); if (iconSpan) iconSpan.innerHTML = iconHtml; } }; 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', 'vue']); 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) { updateButtonStatus(`🔍 OCR: ${file.name}...`, '📸'); await loadTesseractJs(); const { data: { text } } = await Tesseract.recognize(file, 'spa+eng', { logger: m => { if (m.status === 'recognizing') { updateButtonStatus(`📸 OCR ${file.name}: ${Math.round(m.progress * 100)}%`, '📸'); } } }); allContent += text.trim(); } else if (isPdf) { updateButtonStatus(`📄 PDF: ${file.name}...`, '📄'); await loadPdfJs(); const text = await extractTextFromPdf(file); allContent += text.trim(); } else if (isDocx) { updateButtonStatus(`📝 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); } } updateButtonStatus(originalTitle, UPLOAD_ICON_SVG); if (allContent.trim()) { insertTextIntoTextarea(textarea.value + (textarea.value ? '\n\n' : '') + allContent); } } function saveCurrentChat() { const chatContent = getCurrentChatContent(); console.log('💾 Intentando guardar chat...', chatContent); if (chatContent.length === 0) { alert('❌ No hay conversación para guardar. Asegúrate de que hay mensajes en el chat.'); return; } const chatName = prompt("💾 Introduce un nombre para esta conversación:", `Chat ${new Date().toLocaleString()}`); if (chatName && chatName.trim()) { try { const savedChats = JSON.parse(localStorage.getItem('perplexity_playground_chats') || '[]'); const newChat = { name: chatName.trim(), timestamp: new Date().toISOString(), messages: chatContent, messageCount: chatContent.length, url: window.location.href }; savedChats.push(newChat); localStorage.setItem('perplexity_playground_chats', JSON.stringify(savedChats)); alert(`✅ Conversación "${chatName}" guardada con éxito.\n📊 ${chatContent.length} mensajes guardados.`); console.log('✅ Chat guardado exitosamente:', newChat); } catch (e) { console.error("❌ Error al guardar:", e); alert("❌ Error al guardar la conversación: " + e.message); } } } // === FUNCIONES DE EXPORTACIÓN === function exportChatToText() { console.log('📤 Iniciando exportación específica de Perplexity...'); const chatContent = getCurrentChatContent(); // Usar la nueva función if (chatContent.length === 0) { alert('❌ No se encontraron mensajes para exportar.'); return; } try { let exportText = `=== Conversación Perplexity Playground ===\n`; exportText += `Fecha: ${new Date().toLocaleString()}\n`; exportText += `URL: ${window.location.href}\n`; exportText += `Mensajes: ${chatContent.length}\n\n`; chatContent.forEach((msg, index) => { exportText += `[${index + 1}] ${msg.type}:\n`; exportText += `${msg.text}\n\n`; exportText += `${'='.repeat(80)}\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.\n📊 ${chatContent.length} mensajes exportados.`); console.log('✅ Exportación completada'); } catch (error) { console.error('❌ Error al exportar:', error); alert('❌ Error al exportar la conversación: ' + error.message); } } function insertTextIntoTextarea(text) { const textarea = findElement(SELECTORS.INPUT_TEXTAREA); if (textarea) { const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set; nativeInputValueSetter.call(textarea, text); 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; } function handlePromptSelection(promptText) { if (!insertTextIntoTextarea(promptText)) { 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.'); }); } } function createImportButton() { 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'; const importButton = createPerplexityButton(UPLOAD_ICON_SVG, '📎 Importar archivos (texto, imagen con OCR, PDF, DOCX) - Arrastra y suelta o haz clic', () => fileInput.click()); importButton.id = 'perplexity-import-button'; ['dragover', 'dragenter'].forEach(eventName => { importButton.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); importButton.classList.add('bg-offsetPlus'); importButton.style.border = '2px dashed #6366f1'; }); }); ['dragleave', 'dragend'].forEach(eventName => { importButton.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); importButton.classList.remove('bg-offsetPlus'); importButton.style.border = 'none'; }); }); importButton.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); importButton.classList.remove('bg-offsetPlus'); importButton.style.border = 'none'; if (e.dataTransfer.files.length > 0) { processDroppedFiles(e.dataTransfer.files); } }); fileInput.addEventListener('change', (event) => { if (event.target.files.length > 0) { processDroppedFiles(event.target.files); event.target.value = ''; } }); document.body.appendChild(fileInput); return importButton; } function setupCharacterCounter() { const textarea = findElement(SELECTORS.INPUT_TEXTAREA); if (!textarea) return; 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(); } function addControlsToUI() { const buttonContainer = document.querySelector(SELECTORS.CONTROLS_AREA); if (!buttonContainer) { console.warn('⚠️ No se encontró el contenedor de botones específico, creando controles flotantes.'); createFloatingControls(); return; } if (buttonContainer.querySelector('[data-perplexity-controls]')) { return; } const controlsContainer = document.createElement('div'); controlsContainer.setAttribute('data-perplexity-controls', 'true'); // GRID más compacto y sin margen lateral excesivo controlsContainer.style.cssText = ` display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: auto auto auto; gap: 4px; /* Menor separación */ margin-left: 4px; max-width: 320px; /* Más compacto aún */ grid-template-areas: "dropdown dropdown" "save load" "export import"; align-items: center; justify-items: stretch; `; // Dropdown más delgado const promptsDropdown = createCategorizedDropdown(ALL_CATEGORIZED_PROMPTS, handlePromptSelection, "🎯 Prompts"); promptsDropdown.style.gridArea = 'dropdown'; promptsDropdown.style.width = '100%'; promptsDropdown.style.margin = '0'; promptsDropdown.style.fontSize = '13px'; promptsDropdown.style.height = '30px'; promptsDropdown.style.padding = '3px 8px'; // Botones más pequeños function buttonCompact(button) { button.style.width = '100%'; button.style.margin = '0'; button.style.fontSize = '13px'; button.style.height = '28px'; button.style.padding = '3px 0px'; button.style.minWidth = '0'; button.style.minHeight = '0'; return button; } const saveButton = buttonCompact(createPerplexityButton('💾', 'Guardar Chat', saveCurrentChat)); saveButton.style.gridArea = 'save'; const loadButton = buttonCompact(createPerplexityButton('📂', 'Cargar Chat', loadSavedChatsMain)); loadButton.style.gridArea = 'load'; const exportButton = buttonCompact(createPerplexityButton('📤', 'Exportar Chat', exportChatToText)); exportButton.style.gridArea = 'export'; const importButton = buttonCompact(createImportButton()); importButton.style.gridArea = 'import'; // Agrega controles compactos controlsContainer.appendChild(promptsDropdown); controlsContainer.appendChild(saveButton); controlsContainer.appendChild(loadButton); controlsContainer.appendChild(exportButton); controlsContainer.appendChild(importButton); // Remueve margen/padding en el CONTROLS_AREA si lo hubiera (libera espacio) buttonContainer.style.marginBottom = '0'; buttonContainer.style.marginTop = '0'; buttonContainer.style.padding = '0'; buttonContainer.appendChild(controlsContainer); console.log('✅ Controles compactos GRID añadidos'); } function createFloatingControls() { const floatingContainer = document.createElement('div'); floatingContainer.style.cssText = `position: fixed; top: 80px; right: 20px; z-index: 1000; background: rgba(42, 42, 48, 0.95); padding: 12px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); display: flex; flex-direction: column; gap: 8px; min-width: 200px;`; const title = document.createElement('div'); title.textContent = '🛠️ Controles Avanzados'; title.style.cssText = `color: #6366f1; font-weight: bold; text-align: center; margin-bottom: 8px; font-size: 14px;`; floatingContainer.appendChild(title); const promptsDropdown = createCategorizedDropdown(ALL_CATEGORIZED_PROMPTS, handlePromptSelection, "🎯 Prompts"); floatingContainer.appendChild(promptsDropdown); const controls = [ { text: '💾 Guardar', func: saveCurrentChat }, { text: '📂 Cargar', func: loadSavedChatsMain }, // Usar el nuevo método principal { text: '📤 Exportar', func: exportChatToText }, { text: '📎 Importar', func: () => { const input = document.createElement('input'); input.type = 'file'; input.multiple = true; input.accept = '.txt,.pdf,.docx,.png,.jpg,.jpeg'; input.onchange = (e) => processDroppedFiles(e.target.files); input.click(); } } ]; controls.forEach(control => { const btn = createButton(control.text, control.func, { width: '100%', margin: '0', fontSize: '13px' }); floatingContainer.appendChild(btn); }); document.body.appendChild(floatingContainer); console.log('✅ Controles flotantes creados'); } function initializeFeatures() { if (featuresInitialized) return; console.log('🚀 Iniciando Perplexity Playground Advanced...'); try { setTimeout(() => setDefaultModel(), 1000); addControlsToUI(); setupCharacterCounter(); featuresInitialized = true; console.log('✅ Perplexity Playground Advanced inicializado correctamente'); } catch (error) { console.error('❌ Error durante la inicialización:', error); } } function checkPageReady() { const textarea = findElement(SELECTORS.INPUT_TEXTAREA); const hasContent = document.body.children.length > 5; const buttonContainer = document.querySelector(SELECTORS.CONTROLS_AREA); if (textarea && hasContent && buttonContainer) { console.log('✅ Página lista, inicializando funciones...'); return true; } return false; } function startInitialization() { if (checkPageReady()) { initializeFeatures(); return; } retryCount++; if (retryCount >= CONFIG.MAX_RETRIES) { console.warn('⚠️ Máximo de reintentos alcanzado. La página podría no estar completamente cargada.'); if (!document.querySelector(SELECTORS.CONTROLS_AREA)) { console.log('Forzando inicialización de controles flotantes.'); createFloatingControls(); } initializeFeatures(); return; } console.log(`🔄 Reintento ${retryCount}/${CONFIG.MAX_RETRIES}...`); setTimeout(startInitialization, CONFIG.RETRY_INTERVAL); } // ========================================================= // Gestor de Chats en Nueva Ventana (CORE DE LA SOLUCIÓN) // ========================================================= function loadSavedChatsMain() { console.log('📂 === CARGANDO CHATS EN NUEVA VENTANA (MÉTODO ROBUSTO) ==='); try { const savedChatsString = localStorage.getItem('perplexity_playground_chats'); if (!savedChatsString || savedChatsString.trim() === '') { alert('📂 No hay conversaciones guardadas.\n\n💡 Primero guarda una conversación con "💾 Guardar".'); return; } let savedChats; try { savedChats = JSON.parse(savedChatsString); } catch (parseError) { console.error('❌ Error parseando JSON de chats guardados:', parseError); alert('❌ Datos corruptos en el almacenamiento local.\n\n🔧 Ejecuta `window.clearAllSavedChats()` en la consola para limpiar.'); return; } if (!Array.isArray(savedChats) || savedChats.length === 0) { alert('📂 No hay conversaciones guardadas.'); return; } const validChats = savedChats.filter(chat => chat && typeof chat.name === 'string' && chat.messages && Array.isArray(chat.messages)); if (validChats.length === 0) { alert('❌ Todas las conversaciones están corruptas.\n\n🔧 Ejecuta `window.clearAllSavedChats()` para limpiar.'); return; } if (validChats.length !== savedChats.length) { localStorage.setItem('perplexity_playground_chats', JSON.stringify(validChats)); console.warn('🧹 Se eliminaron chats corruptos automáticamente.'); } openChatsManagerWindow(validChats); } catch (error) { console.error('❌ Error en loadSavedChatsMain:', error); alert(`❌ Error al cargar conversaciones: ${error.message}`); } } function openChatsManagerWindow(savedChats) { console.log('🪟 === ABRIENDO GESTOR DE CHATS EN NUEVA VENTANA (VERSIÓN CORREGIDA) ==='); const newWindow = window.open('', '_blank', 'width=1300,height=900,scrollbars=yes,resizable=yes,menubar=no,toolbar=no,location=no'); if (!newWindow) { alert('❌ El navegador bloqueó la ventana emergente.\n\n🔧 Solución:\n1. Permite ventanas emergentes para playground.perplexity.ai\n2. O usa Ctrl+Click en el botón "📂 Cargar Chat"'); return; } // Construir el HTML y CSS en la nueva ventana newWindow.document.open(); newWindow.document.write('<!DOCTYPE html><html><head>'); newWindow.document.write('<title>📚 Perplexity Chats Manager - ' + savedChats.length + ' conversaciones</title>'); newWindow.document.write('<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">'); const cssContent = ` * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif; background: linear-gradient(135deg, #0f0f14 0%, #1a1a1f 50%, #2a2a30 100%); color: white; line-height: 1.6; min-height: 100vh; overflow-x: hidden; } .header { background: linear-gradient(90deg, #6366f1 0%, #8b5cf6 100%); padding: 30px 20px; text-align: center; box-shadow: 0 4px 20px rgba(99, 102, 241, 0.3); position: sticky; top: 0; z-index: 100; } .header h1 { font-size: 28px; font-weight: 700; margin-bottom: 10px; text-shadow: 0 2px 4px rgba(0,0,0,0.3); } .header .stats { font-size: 16px; opacity: 0.9; font-weight: 500; } .controls-bar { background: #2a2a30; padding: 20px; display: flex; justify-content: center; gap: 15px; flex-wrap: wrap; border-bottom: 1px solid #404040; } .btn { background: #4a4a50; color: white; border: none; padding: 12px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.3s ease; display: flex; align-items: center; gap: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); } .btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.3); } .btn-primary { background: #6366f1; } .btn-primary:hover { background: #5856eb; } .btn-success { background: #10b981; } .btn-success:hover { background: #059669; } .btn-danger { background: #ef4444; } .btn-danger:hover { background: #dc2626; } .btn-warning { background: #f59e0b; } .btn-warning:hover { background: #d97706; } .container { max-width: 1200px; margin: 0 auto; padding: 30px 20px; } .chats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 20px; margin-top: 20px; } .chat-card { background: linear-gradient(145deg, #2a2a30 0%, #1f1f24 100%); border: 1px solid #404040; border-radius: 12px; padding: 20px; transition: all 0.3s ease; position: relative; overflow: hidden; } .chat-card:hover { transform: translateY(-5px); border-color: #6366f1; box-shadow: 0 10px 30px rgba(99, 102, 241, 0.2); } .chat-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: linear-gradient(90deg, #6366f1, #8b5cf6, #ec4899); } .chat-name { font-size: 18px; font-weight: 700; color: #f8fafc; margin-bottom: 10px; word-wrap: break-word; line-height: 1.4; } .chat-meta { color: #94a3b8; font-size: 13px; margin-bottom: 15px; display: flex; flex-direction: column; gap: 5px; } .chat-preview { background: #1a1a1f; padding: 12px; border-radius: 8px; border-left: 3px solid #6366f1; margin: 15px 0; font-size: 13px; color: #cbd5e1; max-height: 80px; overflow: hidden; position: relative; } .chat-preview::after { content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 20px; background: linear-gradient(transparent, #1a1a1f); } .chat-actions { display: flex; gap: 10px; margin-top: 15px; } .action-btn { flex: 1; padding: 10px; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 600; transition: all 0.2s ease; text-align: center; } .view-btn { background: #6366f1; color: white; } .view-btn:hover { background: #5856eb; } .delete-btn { background: #ef4444; color: white; } .delete-btn:hover { background: #dc2626; } .export-btn { background: #10b981; color: white; } .export-btn:hover { background: #059669; } .empty-state { text-align: center; padding: 60px 20px; color: #94a3b8; } .empty-state h2 { font-size: 24px; margin-bottom: 15px; color: #6366f1; } @media (max-width: 768px) { .chats-grid { grid-template-columns: 1fr; } .controls-bar { flex-direction: column; align-items: center; } } `; newWindow.document.write('<style>' + cssContent + '</style>'); newWindow.document.write('</head><body>'); newWindow.document.write('<div class="header">'); newWindow.document.write('<h1>📚 Gestor de Conversaciones Perplexity</h1>'); newWindow.document.write('<div class="stats">💬 ' + savedChats.length + ' conversaciones guardadas • 📊 ' + savedChats.reduce((total, chat) => total + (chat.messages?.length || 0), 0) + ' mensajes totales</div>'); newWindow.document.write('</div>'); newWindow.document.write('<div class="controls-bar">'); newWindow.document.write('<button class="btn btn-success" onclick="exportAllChats()">📤 Exportar Todas las Conversaciones</button>'); newWindow.document.write('<button class="btn btn-warning" onclick="refreshFromParent()">🔄 Actualizar Lista</button>'); newWindow.document.write('<button class="btn btn-danger" onclick="clearAllChats()">🗑️ Eliminar Todas</button>'); newWindow.document.write('<button class="btn btn-primary" onclick="window.close()">❌ Cerrar Ventana</button>'); newWindow.document.write('</div>'); newWindow.document.write('<div class="container">'); if (savedChats.length === 0) { newWindow.document.write('<div class="empty-state"><h2>📂 No hay conversaciones guardadas</h2><p>Regresa a Perplexity Playground y usa el botón <strong>"💾 Guardar"</strong> para guardar tu conversación actual.</p></div>'); } else { newWindow.document.write('<div class="chats-grid">'); savedChats.forEach((chat, index) => { const previewText = chat.messages && chat.messages.length > 0 ? (chat.messages[0].text.length > 150 ? chat.messages[0].text.substring(0, 150) + '...' : chat.messages[0].text) : 'Sin contenido de vista previa'; const safeChatName = escapeHTML(chat.name); const safePreviewText = escapeHTML(previewText); newWindow.document.write('<div class="chat-card" data-chat-index="' + index + '">'); newWindow.document.write('<div class="chat-name">' + safeChatName + '</div>'); newWindow.document.write('<div class="chat-meta">'); newWindow.document.write('<span>🕒 ' + new Date(chat.timestamp).toLocaleString() + '</span>'); newWindow.document.write('<span>💬 ' + (chat.messages?.length || 0) + ' mensajes</span>'); newWindow.document.write('</div>'); newWindow.document.write('<div class="chat-preview">' + safePreviewText + '</div>'); newWindow.document.write('<div class="chat-actions">'); newWindow.document.write('<button class="action-btn view-btn" onclick="viewChat(' + index + ')">👁️ Ver Completo</button>'); newWindow.document.write('<button class="action-btn export-btn" onclick="exportSingleChat(' + index + ')">📤 Exportar</button>'); newWindow.document.write('<button class="action-btn delete-btn" onclick="deleteChat(' + index + ')">🗑️ Eliminar</button>'); newWindow.document.write('</div>'); newWindow.document.write('</div>'); }); newWindow.document.write('</div>'); } newWindow.document.write('</div>'); // End container // ========================================================= // INYECCIÓN DE JAVASCRIPT DINÁMICO (VERSION SEGURA) // ========================================================= const scriptElement = newWindow.document.createElement('script'); const scriptContent = ` const savedChatsData = ${JSON.stringify(savedChats)}; const parentOrigin = '${window.location.origin}'; function escapeHTML(str) { const div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; } function viewChat(index) { const chat = savedChatsData[index]; if (!chat) { alert('❌ Chat no encontrado'); return; } const chatWindow = window.open('', '_blank', 'width=1000,height=800,scrollbars=yes,resizable=yes'); if (!chatWindow) { alert('❌ Ventana emergente bloqueada para la vista del chat.'); return; } chatWindow.document.open(); chatWindow.document.write('<!DOCTYPE html><html><head>'); chatWindow.document.write('<title>' + escapeHTML(chat.name) + ' - Vista Completa</title>'); chatWindow.document.write('<meta charset="UTF-8">'); chatWindow.document.write('<style>'); chatWindow.document.write(\` body { font-family: system-ui, sans-serif; background: #1a1a1f; color: white; margin: 0; padding: 20px; line-height: 1.6; } .header { background: #2a2a30; padding: 25px; border-radius: 15px; margin-bottom: 25px; border: 2px solid #6366f1; text-align: center; } .header h1 { color: #6366f1; margin-bottom: 10px; font-size: 24px; } .header .meta { color: #94a3b8; font-size: 14px; } .message { margin: 20px 0; padding: 18px; border-radius: 12px; max-width: 85%; word-wrap: break-word; white-space: pre-wrap; box-shadow: 0 2px 8px rgba(0,0,0,0.3); } .user { background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); margin-left: auto; text-align: right; } .perplexity { background: linear-gradient(135deg, #374151 0%, #111827 100%); margin-right: auto; text-align: left; } .type-label { font-weight: bold; font-size: 11px; text-transform: uppercase; margin-bottom: 10px; opacity: 0.7; letter-spacing: 1px; } .controls { position: fixed; top: 20px; right: 20px; background: rgba(42, 42, 48, 0.95); padding: 15px; border-radius: 10px; border: 1px solid #444; backdrop-filter: blur(10px); } .control-btn { background: #6366f1; color: white; border: none; padding: 10px 15px; border-radius: 6px; cursor: pointer; margin: 0 5px 5px 0; font-size: 13px; transition: all 0.2s ease; } .control-btn:hover { background: #5856eb; transform: translateY(-1px); } .export-btn { background: #10b981; } .export-btn:hover { background: #059669; } .close-btn { background: #ef4444; } .close-btn:hover { background: #dc2626; } .back-btn { background: #64748b; } .back-btn:hover { background: #475569; } .message-counter { position: absolute; top: 5px; right: 5px; background: rgba(0,0,0,0.6); padding: 4px 8px; border-radius: 4px; font-size: 10px; } \`); chatWindow.document.write('</style></head><body>'); chatWindow.document.write('<div class="controls">'); chatWindow.document.write('<button class="control-btn back-btn" onclick="history.back()">⬅️ Volver</button>'); chatWindow.document.write('<button class="control-btn export-btn" onclick="exportThisChat()">📤 Exportar</button>'); chatWindow.document.write('<button class="control-btn close-btn" onclick="window.close()">❌ Cerrar</button>'); chatWindow.document.write('</div>'); chatWindow.document.write('<div class="header">'); chatWindow.document.write('<h1>' + escapeHTML(chat.name) + '</h1>'); chatWindow.document.write('<div class="meta">🕒 ' + new Date(chat.timestamp).toLocaleString() + ' • 💬 ' + (chat.messages?.length || 0) + ' mensajes • 📊 ' + ((chat.messages || []).reduce((total, msg) => total + msg.text.length, 0)).toLocaleString() + ' caracteres</div>'); chatWindow.document.write('</div>'); chatWindow.document.write('<div class="container" style="max-width: 900px; margin: 0 auto;">'); if (chat.messages && chat.messages.length > 0) { chat.messages.forEach((msg, i) => { const isUser = msg.type === 'User'; const className = isUser ? 'user' : 'perplexity'; const labelColor = isUser ? '#bfdbfe' : '#a7f3d0'; chatWindow.document.write('<div class="message ' + className + '">'); chatWindow.document.write('<div class="message-counter">' + (i + 1) + '/' + chat.messages.length + '</div>'); chatWindow.document.write('<div class="type-label" style="color: ' + labelColor + ';">' + msg.type + '</div>'); chatWindow.document.write('<div>' + escapeHTML(msg.text) + '</div>'); chatWindow.document.write('</div>'); }); } else { chatWindow.document.write('<div style="text-align: center; color: #94a3b8; padding: 60px 20px; font-size: 16px;">📭 Esta conversación no tiene mensajes</div>'); } chatWindow.document.write('</div>'); // End container // JS for individual chat export chatWindow.document.write('<script>'); chatWindow.document.write('const currentChatData = ' + JSON.stringify(chat) + ';'); chatWindow.document.write(\` function exportThisChat() { let content = '=== ' + currentChatData.name + ' ===\\\\n'; content += 'Fecha: ' + new Date(currentChatData.timestamp).toLocaleString() + '\\\\n'; content += 'Mensajes: ' + (currentChatData.messages?.length || 0) + '\\\\n\\\\n'; if (currentChatData.messages) { currentChatData.messages.forEach((msg, i) => { content += '[' + (i + 1) + '] ' + msg.type + ':\\\\n'; content += msg.text + '\\\\n'; content += '============================================================\\\\n\\\\n'; }); } const blob = new Blob([content], { type: 'text/plain; charset=utf-8' }); const url = URL.createObjectURL(blob); const a = chatWindow.document.createElement('a'); // Use chatWindow.document a.href = url; a.download = currentChatData.name.replace(/[^a-zA-Z0-9\\\\s]/g, '_').replace(/\\\\s+/g, '_') + '_' + new Date().toISOString().slice(0,10) + '.txt'; chatWindow.document.body.appendChild(a); // Use chatWindow.document.body a.click(); chatWindow.document.body.removeChild(a); // Use chatWindow.document.body URL.revokeObjectURL(url); alert('✅ Chat exportado exitosamente como archivo de texto.'); } \`); chatWindow.document.write('</script>'); chatWindow.document.write('</body></html>'); chatWindow.document.close(); chatWindow.focus(); } function exportSingleChat(index) { const chat = savedChatsData[index]; if (!chat) { alert('❌ Chat no encontrado'); return; } let content = '=== ' + chat.name + ' ===\\n'; content += 'Fecha: ' + new Date(chat.timestamp).toLocaleString() + '\\n'; content += 'Mensajes: ' + (chat.messages?.length || 0) + '\\n\\n'; if (chat.messages) { chat.messages.forEach((msg, i) => { content += '[' + (i + 1) + '] ' + msg.type + ':\\n'; content += msg.text + '\\n'; content += '============================================================\\n\\n'; }); } const blob = new Blob([content], { type: 'text/plain; charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = chat.name.replace(/[^a-zA-Z0-9\\s]/g, '_').replace(/\\s+/g, '_') + '_' + new Date().toISOString().slice(0,10) + '.txt'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); alert('✅ "' + chat.name + '" exportado exitosamente.'); } function deleteChat(index) { const chat = savedChatsData[index]; if (!chat) { alert('❌ Chat no encontrado'); return; } if (confirm('⚠️ ¿Eliminar "' + chat.name + '"?\\n\\nEsta acción no se puede deshacer.\\n\\n📊 Se perderán ' + (chat.messages?.length || 0) + ' mensajes.')) { try { if (window.opener && !window.opener.closed) { window.opener.postMessage({ action: 'deleteChat', index: index, chatName: chat.name }, parentOrigin); } savedChatsData.splice(index, 1); alert('✅ "' + chat.name + '" eliminado exitosamente.'); setTimeout(() => { window.location.reload(); }, 500); } catch (error) { console.error('❌ Error eliminando chat:', error); alert('❌ Error al eliminar el chat: ' + error.message); } } } function exportAllChats() { if (savedChatsData.length === 0) { alert('❌ No hay chats para exportar'); return; } let content = '=== EXPORTACIÓN COMPLETA DE CONVERSACIONES PERPLEXITY ===\\n'; content += 'Fecha de exportación: ' + new Date().toLocaleString() + '\\n'; content += 'Total de conversaciones: ' + savedChatsData.length + '\\n'; content += 'Total de mensajes: ' + savedChatsData.reduce((total, chat) => total + (chat.messages?.length || 0), 0) + '\\n\\n'; content += '================================================================================\\n\\n'; savedChatsData.forEach((chat, chatIndex) => { content += '[CONVERSACIÓN ' + (chatIndex + 1) + '] ' + chat.name + '\\n'; content += 'Fecha: ' + new Date(chat.timestamp).toLocaleString() + '\\n'; content += 'Mensajes: ' + (chat.messages?.length || 0) + '\\n\\n'; if (chat.messages) { chat.messages.forEach((msg, msgIndex) => { content += ' [' + (msgIndex + 1) + '] ' + msg.type + ':\\n ' + msg.text.replace(/\\n/g, '\\n ') + '\\n\\n'; }); } content += '================================================================================\\n\\n'; }); const blob = new Blob([content], { type: 'text/plain; charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'perplexity_all_conversations_' + new Date().toISOString().slice(0,19).replace(/[:.]/g, '-') + '.txt'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); alert('✅ Todas las conversaciones exportadas exitosamente.\\n\\n📊 Archivo generado con ' + savedChatsData.length + ' conversaciones.'); } function clearAllChats() { if (confirm('⚠️ ¿ELIMINAR TODAS LAS CONVERSACIONES?\\n\\nEsta acción es PERMANENTE y eliminará:\\n\\n📊 ' + savedChatsData.length + ' conversaciones\\n💬 ' + savedChatsData.reduce((total, chat) => total + (chat.messages?.length || 0), 0) + ' mensajes totales\\n\\n¿Estás completamente seguro?')) { if (confirm('🔴 CONFIRMACIÓN FINAL:\\n\\nEsta es tu última oportunidad.\\n\\n¿Eliminar TODAS las conversaciones?')) { try { if (window.opener && !window.opener.closed) { window.opener.postMessage({ action: 'clearAllChats' }, parentOrigin); } alert('✅ Todas las conversaciones eliminadas exitosamente.'); window.close(); } catch (error) { console.error('❌ Error limpiando chats:', error); alert('❌ Error al limpiar conversaciones: ' + error.message); } } } } function refreshFromParent() { alert('🔄 Para actualizar la lista, cierra esta ventana y vuelve a abrir el gestor de chats desde Perplexity.'); } window.addEventListener('load', () => { document.body.focus(); }); `; scriptElement.textContent = scriptContent; newWindow.document.body.appendChild(scriptElement); newWindow.document.close(); newWindow.focus(); const messageListener = (event) => { if (event.origin !== window.location.origin) return; if (event.data && event.data.action) { if (event.data.action === 'deleteChat') { try { const currentChats = JSON.parse(localStorage.getItem('perplexity_playground_chats') || '[]'); const chatIndexToDelete = currentChats.findIndex(c => c.name === event.data.chatName); if (chatIndexToDelete !== -1) { currentChats.splice(chatIndexToDelete, 1); localStorage.setItem('perplexity_playground_chats', JSON.stringify(currentChats)); console.log(`✅ Chat "${event.data.chatName}" eliminado desde ventana hija.`); } else { console.warn(`⚠️ Chat "${event.data.chatName}" no encontrado en localStorage al intentar eliminar.`); } } catch (error) { console.error('❌ Error eliminando chat desde ventana hija:', error); } } else if (event.data.action === 'clearAllChats') { try { localStorage.removeItem('perplexity_playground_chats'); console.log('✅ Todos los chats eliminados desde ventana hija.'); } catch (error) { console.error('❌ Error limpiando chats desde ventana hija:', error); } } } }; window.addEventListener('message', messageListener); const checkClosed = setInterval(() => { if (newWindow.closed) { window.removeEventListener('message', messageListener); clearInterval(checkClosed); console.log('🧹 Listener de comunicación limpiado.'); } }, 1000); console.log('✅ Gestor de chats abierto en nueva ventana con JavaScript funcional.'); } // ========================================================= // Funciones auxiliares para debugging (definidas al final del closure) // ========================================================= function insertTextIntoTextarea(text) { const textarea = findElement(SELECTORS.INPUT_TEXTAREA); if (textarea) { const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set; nativeInputValueSetter.call(textarea, text); 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; } function testChatExtraction() { console.log('🧪 === TESTING EXTRACCIÓN DE CHAT ==='); const chatContent = getCurrentChatContent(); console.log('📊 Resultado de extracción:', chatContent); if (chatContent.length === 0) { alert('❌ No se encontraron mensajes en el chat actual.\n\n🔍 Abre la consola (F12) para ver los detalles del debugging.'); } else { alert(`✅ Extracción exitosa: ${chatContent.length} mensajes encontrados.\n\n📝 Primeros 2 mensajes:\n\n1. [${chatContent[0].type}]: ${chatContent[0].text.substring(0, Math.min(chatContent[0].text.length, 100))}...\n\n${chatContent[1] ? `2. [${chatContent[1].type}]: ${chatContent[1].text.substring(0, Math.min(chatContent[1].text.length, 100))}...` : ''}`); } return chatContent; } function testLoadSaveSystem() { console.log('🧪 === TESTING SISTEMA DE GUARDADO Y CARGA ==='); console.log('1️⃣ Verificando acceso a localStorage...'); try { const testKey = 'perplexity_test_' + Date.now(); localStorage.setItem(testKey, 'test_value'); const retrieved = localStorage.getItem(testKey); localStorage.removeItem(testKey); console.log('✅ localStorage funciona correctamente'); } catch (e) { console.error('❌ Error con localStorage:', e); alert('❌ Error con localStorage: ' + e.message); return false; } console.log('2️⃣ Verificando chats guardados existentes...'); const savedChatsString = localStorage.getItem('perplexity_playground_chats'); const savedChats = savedChatsString ? JSON.parse(savedChatsString) : []; console.log(`📊 Chats guardados encontrados: ${savedChats.length}`); console.log('3️⃣ Simulando proceso de guardado...'); const testChat = { name: 'Chat de Prueba del Sistema', timestamp: new Date().toISOString(), messages: [{ type: 'User', text: 'Mensaje de prueba del usuario' }, { type: 'Perplexity', text: 'Respuesta de prueba de Perplexity' }], messageCount: 2, url: window.location.href }; try { const updatedChats = [...savedChats, testChat]; localStorage.setItem('perplexity_playground_chats', JSON.stringify(updatedChats)); console.log('✅ Simulación de guardado exitosa'); localStorage.setItem('perplexity_playground_chats', JSON.stringify(savedChats)); console.log('🧹 Chat de prueba limpiado'); } catch (e) { console.error('❌ Error simulando guardado:', e); alert('❌ Error en simulación de guardado: ' + e.message); return false; } alert(`✅ Sistema de guardado y carga funcionando correctamente.\n\n📊 Estadísticas:\n- Chats guardados: ${savedChats.length}\n- localStorage: ✅ Operacional\n- Guardado: ✅ Funcional\n- Carga: ✅ Funcional`); return true; } function emergencyModal() { // Simplified placeholder for a true emergency modal alert("🚨 Método de Emergencia: Si la ventana principal del gestor de chats no se abre, este alert muestra que el JS de la página principal funciona. \n\nPor favor, asegúrate de permitir los popups para esta página."); console.log("🆘 emergencyModal triggered. JS on main page is alive."); } function highlightModal() { const panel = document.querySelector('[data-perplexity-sidepanel]'); const targetModal = panel; if (targetModal) { console.log("🎯 Panel lateral encontrado en el DOM. Aplicando resaltado."); targetModal.style.outline = '5px solid yellow !important'; targetModal.style.boxShadow = '0 0 50px 20px rgba(255,255,0,0.8) !important'; targetModal.style.backgroundColor = 'rgba(0,0,0,0.95) !important'; if (!targetModal.querySelector('.highlight-text')) { const debugText = document.createElement('div'); debugText.className = 'highlight-text'; debugText.textContent = '¡PANEL AQUÍ!'; debugText.style.cssText = `position: absolute !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; font-size: 50px !important; color: yellow !important; background: black !important; padding: 20px !important; border-radius: 10px !important; z-index: 2147483647 !important; pointer-events: none !important;`; targetModal.appendChild(debugText); } setTimeout(() => { targetModal.style.outline = ''; targetModal.style.boxShadow = ''; targetModal.style.backgroundColor = ''; const debugText = targetModal.querySelector('.highlight-text'); if (debugText) debugText.remove(); console.log("Resaltado del panel desactivado."); }, 10000); } else { console.error("❌ No se encontró ningún panel lateral activo ([data-perplexity-sidepanel]) en el DOM."); alert("No se encontró el panel lateral en el DOM. Revisa los logs de 'loadSavedChatsMain' para ver si hay errores al crearlo."); } } function clearAllSavedChats() { if (confirm('⚠️ ¿Estás seguro de que quieres eliminar TODAS las conversaciones guardadas?\n\nEsta acción es PERMANENTE y no se puede deshacer.')) { try { localStorage.removeItem('perplexity_playground_chats'); alert('✅ Todas las conversaciones han sido eliminadas del almacenamiento local.'); console.log('🧹 localStorage limpiado de "perplexity_playground_chats".'); } catch (e) { console.error('❌ Error limpiando localStorage:', e); alert('❌ Error al limpiar el almacenamiento: ' + e.message); } } } function loadChatsEmergencyMethod() { // Fallback para abrir todos los chats en una nueva ventana si el gestor principal falla console.log('🆘 Método de emergencia - ventana nueva (todos los chats)'); try { const savedChatsString = localStorage.getItem('perplexity_playground_chats'); const savedChats = savedChatsString ? JSON.parse(savedChatsString) : []; if (savedChats.length === 0) { alert('No hay chats guardados'); return; } let htmlContent = `<!DOCTYPE html><html><head><title>Perplexity Chats Guardados (Emergencia)</title><style>body { font-family: Arial, sans-serif; background: #1a1a1f; color: white; margin: 0; padding: 20px;} .chat { background: #2a2a30; padding: 15px; margin: 10px 0; border-radius: 10px; } .message { margin: 10px 0; padding: 10px; border-radius: 8px; white-space: pre-wrap; word-break: break-word; } .user { background: #007bff; text-align: right; } .perplexity { background: #333; text-align: left; } h1, h2 { color: #6366f1; } .controls { position: fixed; top: 20px; right: 20px; background: #2a2a30; padding: 10px; border-radius: 8px; border: 1px solid #444; z-index: 1000; } button { background: #6366f1; color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; margin: 0 5px; } button:hover { background: #5856eb; } .export-btn { background: #28a745; } .close-btn { background: #dc3545; }</style></head><body><div class="controls"><button class="export-btn" onclick="exportAllChatsEmergency()">📤 Exportar Todo</button><button class="close-btn" onclick="window.close()">❌ Cerrar</button></div><h1>📚 Conversaciones Guardadas (Emergencia)</h1>`; savedChats.forEach((chat, i) => { htmlContent += `<div class="chat"><h2>${escapeHTML(chat.name)}</h2><p><small>${new Date(chat.timestamp).toLocaleString()}</small></p>`; if (chat.messages) { chat.messages.forEach(msg => { const className = msg.type === 'User' ? 'user' : 'perplexity'; htmlContent += `<div class="message ${className}"><strong>${msg.type}:</strong><br>${escapeHTML(msg.text)}</div>`; }); } htmlContent += `</div>`; }); htmlContent += `<script>function escapeHTML(str) { const div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; } function exportAllChatsEmergency() { const savedChatData = ${JSON.stringify(savedChats)}; let content = ''; savedChatData.forEach((chat, i) => { content += \`=== \${escapeHTML(chat.name)} ===\\nFecha: \${new Date(chat.timestamp).toLocaleString()}\\nMensajes: \${chat.messages?.length || 0}\\n\\n\`; chat.messages.forEach((msg, j) => { content += \`[\${j + 1}] \${msg.type}:\\n\${escapeHTML(msg.text)}\\n\${'='.repeat(50)}\\n\\n\`; }); }); const blob = new Blob([content], { type: 'text/plain; charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = \`perplexity_all_chats_export_emergency_\${new Date().toISOString().slice(0,19).replace(/[:.]/g, '-')}.txt\`; document.body.appendChild(a); a.click(); URL.revokeObjectURL(url); }}</script></body></html>`; const newWindow = window.open('', '_blank', 'width=900,height=700,scrollbars=yes,resizable=yes'); if (newWindow) { newWindow.document.write(htmlContent); newWindow.document.close(); newWindow.focus(); console.log('✅ Chats abiertos en ventana nueva (emergencia)'); } else { alert('❌ El navegador bloqueó la ventana emergente. Por favor, permite las ventanas emergentes para playground.perplexity.ai para usar esta función.'); } } catch (error) { console.error('❌ Error en método de emergencia:', error); alert('❌ Error: ' + error.message); } } function simpleModalAlert() { // Fallback para mostrar chats en un textarea en la misma página const savedChats = JSON.parse(localStorage.getItem('perplexity_playground_chats') || '[]'); if (savedChats.length === 0) { alert('No hay chats guardados'); return; } let chatListText = '📚 CONVERSACIONES GUARDADAS:\n\n'; savedChats.forEach((chat, i) => { chatListText += `${i + 1}. ${escapeHTML(chat.name)} (${new Date(chat.timestamp).toLocaleString()})\n`; }); const selection = prompt(chatListText + '\nIngresa el número del chat que quieres ver (1-' + savedChats.length + '):'); if (selection && !isNaN(selection)) { const index = parseInt(selection) - 1; if (index >= 0 && index < savedChats.length) { const chat = savedChats[index]; let content = `💬 ${escapeHTML(chat.name)} (ID: ${index + 1})\n\n`; if (chat.messages) { chat.messages.forEach((msg, i) => { content += `[${i + 1}] ${msg.type}:\n${escapeHTML(msg.text)}\n\n${'='.repeat(50)}\n\n`; }); } const textareaId = 'perplexity-simple-view-textarea-' + Date.now(); const closeBtnId = 'perplexity-simple-view-closebtn-' + Date.now(); const textareaHtml = `<textarea id="${textareaId}" style="position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 90vw !important; height: 90vh !important; max-width: 1000px !important; max-height: 800px !important; z-index: 2147483647 !important; background: #1a1a1f !important; color: white !important; border: 2px solid #6366f1 !important; padding: 20px !important; font-family: monospace !important; font-size: 14px !important; resize: both !important; overflow: auto !important; opacity: 1 !important; visibility: visible !important;" readonly>${escapeHTML(content)}</textarea>`; const closeButtonHtml = `<button id="${closeBtnId}" style="position: fixed !important; top: 5% !important; right: 5% !important; z-index: 2147483647 !important; background: red !important; color: white !important; border: none !important; padding: 10px 20px !important; border-radius: 5px !important; cursor: pointer !important; font-size: 16px !important; opacity: 1 !important; visibility: visible !important;">❌ Cerrar</button>`; document.body.insertAdjacentHTML('beforeend', textareaHtml); document.body.insertAdjacentHTML('beforeend', closeButtonHtml); const textareaEl = document.getElementById(textareaId); const closeBtnEl = document.getElementById(closeBtnId); if (textareaEl && closeBtnEl) { closeBtnEl.addEventListener('click', () => { textareaEl.remove(); closeBtnEl.remove(); }); } else { alert('❌ Error al mostrar contenido con simpleModalAlert.'); } } else { alert('Número de chat inválido.'); } } } // Helper para escapar HTML (para usar dentro de document.write) function escapeHTML(str) { const div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; } // ========================================================= // Inicialización y Observadores // ========================================================= if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startInitialization); } else { startInitialization(); } const observer = new MutationObserver((mutations) => { if (!featuresInitialized || !document.querySelector(SELECTORS.CONTROLS_AREA + ' [data-perplexity-controls]')) { console.log('Detectado cambio en el DOM, verificando estado de inicialización...'); startInitialization(); } }); observer.observe(document.body, { childList: true, subtree: true, attributes: true }); window.addEventListener('beforeunload', () => { document.querySelectorAll('[data-perplexity-sidepanel]').forEach(el => { if (el.__escapeListener) { document.removeEventListener('keydown', el.__escapeListener); } el.remove(); }); observer.disconnect(); }); console.log('📋 Perplexity Playground Advanced v2.1 cargado'); // ========================================================= // Exponer funciones globales para debugging // ========================================================= window.testChatExtraction = testChatExtraction; window.testLoadSaveSystem = testLoadSaveSystem; window.clearAllSavedChats = clearAllSavedChats; // testModal y emergencyModal no son directamente relevantes con el nuevo enfoque principal, pero se mantienen si fueran útiles. window.emergencyModal = emergencyModal; window.highlightModal = highlightModal; // Highlight no aplicará a la nueva ventana, solo a elementos en la página principal. window.loadSavedChatsMain = loadSavedChatsMain; window.loadChatsEmergencyMethod = loadChatsEmergencyMethod; window.simpleModalAlert = simpleModalAlert; window.openChatsManagerWindow = openChatsManagerWindow; // Exponer la función principal del gestor de ventanas })();