您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a visible 'Copy' button to each Gemini response's action bar and to the Canvas toolbar for easy one-click copying.
// ==UserScript== // @name Gemini - Direct Copy Button // @name:es Gemini - Botón de Copiar Directo // @namespace http://tampermonkey.net/ // @version 2.1.0 // @description Adds a visible 'Copy' button to each Gemini response's action bar and to the Canvas toolbar for easy one-click copying. // @description:es Agrega un botón de 'Copiar' visible en la barra de acciones de cada respuesta de Gemini y en la barra de herramientas del Canvas para copiar con un solo clic. // @author Gemini // @match https://gemini.google.com/app* // @icon https://www.google.com/s2/favicons?sz=64&domain=gemini.google.com // @grant GM_addStyle // @license MIT // ==/UserScript== (function() { 'use strict'; // Inyecta el CSS necesario para los nuevos botones y sus estados. GM_addStyle(` .copiar-script-button { margin-right: 8px; /* Espacio para el botón en las respuestas del chat */ } .copiar-script-button .copied-icon, .copiar-canvas-button .copied-icon { color: #6dd58c; /* Color verde para el ícono de "check" de confirmación */ font-variation-settings: 'FILL' 1; } .copiar-canvas-button { margin-right: 8px; /* Espacio para el botón en el Canvas */ } `); /** * Agrega el botón de copiar a un contenedor de respuesta de chat. * @param {HTMLElement} responseContainer - El elemento <div> que contiene la respuesta del modelo. */ function agregarBotonDeCopiaChat(responseContainer) { const actionsContainer = responseContainer.querySelector('.buttons-container-v2'); const shareButtonWrapper = responseContainer.querySelector('share-button'); if (!actionsContainer || !shareButtonWrapper) return; const botonCopiar = document.createElement('button'); botonCopiar.setAttribute('mat-icon-button', ''); botonCopiar.setAttribute('mattooltip', 'Copiar contenido'); botonCopiar.setAttribute('aria-label', 'Copiar contenido'); botonCopiar.className = 'mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-unthemed copiar-script-button'; const icono = document.createElement('mat-icon'); icono.className = 'mat-icon notranslate google-symbols mat-ligature-font mat-icon-no-color'; icono.textContent = 'content_copy'; botonCopiar.appendChild(icono); botonCopiar.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const contentElement = responseContainer.querySelector('.markdown'); if (contentElement) { navigator.clipboard.writeText(contentElement.innerText).then(() => { icono.textContent = 'check'; icono.classList.add('copied-icon'); setTimeout(() => { icono.textContent = 'content_copy'; icono.classList.remove('copied-icon'); }, 2000); }).catch(err => console.error('Error al copiar el texto del chat: ', err)); } }); actionsContainer.insertBefore(botonCopiar, shareButtonWrapper); } /** * Agrega el botón de copiar a la barra de herramientas del panel de código (Canvas). * @param {HTMLElement} panelCanvas - El elemento <code-immersive-panel>. */ function agregarBotonDeCopiaCanvas(panelCanvas) { const actionsContainer = panelCanvas.querySelector('toolbar .action-buttons'); const shareButtonTrigger = panelCanvas.querySelector('toolbar share-button button'); if (!actionsContainer || !shareButtonTrigger) return; const botonCopiar = document.createElement('button'); botonCopiar.setAttribute('mat-icon-button', ''); botonCopiar.setAttribute('mattooltip', 'Copiar código'); botonCopiar.setAttribute('aria-label', 'Copiar código'); botonCopiar.className = 'mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger icon-button copiar-canvas-button mat-unthemed'; const icono = document.createElement('mat-icon'); icono.setAttribute('role', 'img'); icono.className = 'mat-icon notranslate google-symbols mat-ligature-font mat-icon-no-color'; icono.textContent = 'content_copy'; botonCopiar.appendChild(icono); botonCopiar.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); // Simula un clic en el botón "Compartir" para abrir el menú. shareButtonTrigger.click(); // Espera a que el menú se renderice en el DOM. setTimeout(() => { // Busca el botón de copiar original dentro del panel del menú. const menuPanel = document.querySelector('.mat-mdc-menu-panel.mat-mdc-menu-panel'); if (menuPanel) { const originalCopyButton = menuPanel.querySelector('copy-button button'); if (originalCopyButton) { // Simula un clic en el botón de copiar original. originalCopyButton.click(); // Feedback visual de éxito en nuestro botón. icono.textContent = 'check'; icono.classList.add('copied-icon'); setTimeout(() => { icono.textContent = 'content_copy'; icono.classList.remove('copied-icon'); }, 2000); } } }, 50); // Un pequeño delay es suficiente. }); // Inserta el nuevo botón antes del contenedor del botón de compartir. actionsContainer.insertBefore(botonCopiar, shareButtonTrigger.parentElement.parentElement); } /** * Busca nuevos elementos en la página para agregarles los botones. */ function procesarNuevosNodos() { // Lógica para las respuestas del chat. document.querySelectorAll('model-response:not([data-copy-button-added])').forEach(container => { if (container.querySelector('.markdown')) { agregarBotonDeCopiaChat(container); container.dataset.copyButtonAdded = 'true'; } }); // Lógica para el panel de código (Canvas). document.querySelectorAll('code-immersive-panel:not([data-canvas-copy-button-added])').forEach(panel => { agregarBotonDeCopiaCanvas(panel); panel.dataset.canvasCopyButtonAdded = 'true'; }); } // El MutationObserver vigila por cambios en el DOM para ejecutar nuestro código. const observer = new MutationObserver(() => { setTimeout(procesarNuevosNodos, 500); }); // Empezamos a observar el cuerpo del documento. observer.observe(document.body, { childList: true, subtree: true }); // Ejecutamos la función una vez al inicio. setTimeout(procesarNuevosNodos, 1000); })();