您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhances Google Gemini with an advanced prompt editor (EN/IT, themes, smart formatting & more).
// ==UserScript== // @name Gemini PROmpt editor // @namespace http://tampermonkey.net/ // @version 1.1 // @description Enhances Google Gemini with an advanced prompt editor (EN/IT, themes, smart formatting & more). // @author Ustanojevic & Gemini 2.5 Pro // @match https://gemini.google.com/* // @grant GM_addStyle // @grant GM_info // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { 'use strict'; // --- LANGUAGE CONFIGURATION --- const LANG_STRINGS = { EN: { modalTitle: "Gemini PROmpt Editor", btnApply: "Apply Prompt & Close", btnCancel: "Cancel", btnSelectAll: "Select All", langSelectLabel: "Language:", // Non usata attualmente, ma pronta se serve ariaOpenEditor: "Open Gemini PROmpt Editor", sections: [ { header: "**### CONTEXT ###**", placeholder: "[Provide general context or background information here]", optional: true }, { header: "**### OBJECTIVE / SPECIFIC QUESTION ###**", placeholder: "[Clearly describe what you want Gemini to do, what question to answer, or task to perform]", optional: true }, { header: "**### OUTPUT FORMAT (Optional) ###**", placeholder: "[Specify desired output: e.g., bullet list, table, JSON, paragraph, email, etc.]", optional: true }, { header: "**### RULES / CONSTRAINTS (Optional) ###**", placeholder: "[Specify any restrictions, style, things to avoid, maximum length, etc.]", optional: true }, { header: "**### EXAMPLE (Optional) ###**", placeholder: "[Provide a brief example of desired input/output, if applicable]", optional: true } ] }, IT: { modalTitle: "Gemini PROmpt editor", btnApply: "Applica Prompt e Chiudi", btnCancel: "Annulla", btnSelectAll: "Seleziona Tutto", langSelectLabel: "Lingua:", ariaOpenEditor: "Apri Gemini PROmpt editor", sections: [ { header: "**### CONTESTO ###**", placeholder: "[Inserisci qui il contesto generale o le informazioni di base necessarie]", optional: true }, { header: "**### OBIETTIVO / DOMANDA SPECIFICA ###**", placeholder: "[Descrivi chiaramente cosa vuoi che Gemini faccia, quale domanda deve rispondere o quale compito deve svolgere]", optional: true }, { header: "**### FORMATO OUTPUT (Opzionale) ###**", placeholder: "[Indica come desideri l'output: es. lista puntata, tabella, codice JSON, paragrafo, email, ecc.]", optional: true }, { header: "**### REGOLE / VINCOLI (Opzionale) ###**", placeholder: "[Specifica eventuali restrizioni, stile da adottare, cose da evitare, lunghezza massima, ecc.]", optional: true }, { header: "**### ESEMPIO (Opzionale) ###**", placeholder: "[Fornisci un breve esempio di input/output desiderato, se applicabile]", optional: true } ] } }; let currentLang = GM_getValue('promptEditorLang', 'EN'); // Default a Inglese let STRINGS = LANG_STRINGS[currentLang] || LANG_STRINGS['EN']; let PROMPT_SECTIONS = STRINGS.sections; function generatePromptTemplate(sectionsArray) { return sectionsArray.map(s => `${s.header}\n${s.placeholder}`).join("\n\n"); } let PROMPT_TEMPLATE = generatePromptTemplate(PROMPT_SECTIONS); const GEMINI_DARK_MODE_CLASS = 'dark-theme'; const GEMINI_MAIN_CONTENT_SELECTOR = 'bard-sidenav-content'; // --- STILI CSS --- GM_addStyle(` #promptEditorButton { background-color: #1a73e8; color: white; padding: 0; width: 40px; height: 40px; border: none; border-radius: 50%; cursor: pointer; margin: 0 4px; display: inline-flex; align-items: center; justify-content: center; transition: background-color 0.2s ease-in-out; flex-shrink: 0; font-family: 'Google Symbols', 'Material Symbols Outlined', 'Material Icons', sans-serif; /* Per l'icona */ font-size: 20px; /* Dimensione icona Material */ font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; overflow: hidden; } #promptEditorButton:hover { background-color: #1765cc; } #promptEditorModalOverlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 10000; display: none; } #promptEditorModal { font-family: Roboto, Arial, sans-serif; width: 750px; max-width: 95%; background-color: #fff; color: #202124; border: 1px solid #dfe1e5; border-radius: 12px; box-shadow: 0 8px 16px rgba(0,0,0,0.15); padding: 28px; display: flex; flex-direction: column; gap: 20px; z-index: 10001; position: fixed; margin: 0; } #promptEditorModal h3 { margin-top: 0; margin-bottom: 4px; color: #3c4043; font-size: 22px; font-weight: 500; text-align: center; } #promptEditorTextarea { width: 100%; box-sizing: border-box; height: 450px; padding: 12px; border: 1px solid #dadce0; border-radius: 6px; font-family: "Google Sans", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-weight: 400; line-height: 1.6; resize: vertical; background-color: #f8f9fa; color: #3c4043; } #promptEditorTextarea:focus { border-color: #1a73e8; box-shadow: 0 0 0 1px #1a73e8; outline: none; background-color: #fff; } #promptEditorControls { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 8px; } #promptEditorControls .left-controls { display: flex; align-items: center; gap: 10px; } #promptEditorControls .right-buttons { display: flex; gap: 12px; } #promptEditorControls button { font-family: 'Google Sans', Roboto, Arial, sans-serif; padding: 10px 24px; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; font-size: 14px; text-transform: none; transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out; } #copyAndCloseBtn { background-color: #1a73e8; color: white; } #copyAndCloseBtn:hover { background-color: #1765cc; box-shadow: 0 1px 3px 0 rgba(60,64,67,0.302), 0 2px 6px 2px rgba(60,64,67,0.149); } #cancelBtn { background-color: #fff; color: #1a73e8; border: 1px solid #dadce0; } #cancelBtn:hover { background-color: rgba(26,115,232,0.04); border-color: #adcaf3; } #selectAllBtn { background-color: transparent; color: #5f6368; border: 1px solid #dadce0; padding: 9px 18px; } #selectAllBtn:hover { background-color: rgba(0,0,0,0.03); border-color: #c6c6c6; } #langSelect { padding: 8px 10px; border-radius: 4px; border: 1px solid #dadce0; background-color: #f8f9fa; color: #3c4043; font-size: 13px; font-family: 'Google Sans', Roboto, Arial, sans-serif; height: 38px; /* Per allineare con i bottoni */ } /* Stili Tema Scuro */ .prompt-editor-dark-theme #promptEditorModal { background-color: #202124; color: #e8eaed; border-color: #5f6368; } .prompt-editor-dark-theme #promptEditorModal h3 { color: #e8eaed; } .prompt-editor-dark-theme #promptEditorTextarea { background-color: #2d2e30; color: #e8eaed; border-color: #5f6368; } .prompt-editor-dark-theme #promptEditorTextarea:focus { border-color: #8ab4f8; box-shadow: 0 0 0 1px #8ab4f8; background-color: #202124; } .prompt-editor-dark-theme #copyAndCloseBtn { background-color: #8ab4f8; color: #202124; } .prompt-editor-dark-theme #copyAndCloseBtn:hover { background-color: #99c0fa; } .prompt-editor-dark-theme #cancelBtn { background-color: #2d2e30; color: #8ab4f8; border-color: #5f6368; } .prompt-editor-dark-theme #cancelBtn:hover { background-color: #3c4043; border-color: #8ab4f8;} .prompt-editor-dark-theme #selectAllBtn { background-color: transparent; color: #bdc1c6; border-color: #5f6368; } .prompt-editor-dark-theme #selectAllBtn:hover { background-color: rgba(255,255,255,0.08); border-color: #7f8286;} .prompt-editor-dark-theme #langSelect { background-color: #2d2e30; color: #e8eaed; border-color: #5f6368; } `); let editorButton = null; let modalOverlay = null; let modal = null; let promptTextarea = null; let geminiInputTarget = null; let resizeTimeout; let langSelectElement = null; function findGeminiInputArea() { const inputElement = document.querySelector('div.ql-editor[aria-label="Enter a prompt here"][contenteditable="true"]'); if (inputElement) { let buttonHostContainer = inputElement.closest('div.text-input-field'); if (!buttonHostContainer) buttonHostContainer = inputElement.closest('.text-input-field-main-area') || inputElement.closest('.text-input-field_textarea-wrapper'); if (!buttonHostContainer) { const richTextParent = inputElement.closest('rich-textarea')?.parentElement?.parentElement; buttonHostContainer = richTextParent || inputElement.parentElement?.parentElement || document.body; if (buttonHostContainer === document.body) console.warn("Gemini PROmpt editor: Usato document.body come fallback per buttonHostContainer."); } return { container: buttonHostContainer, input: inputElement, type: 'div' }; } return null; } function applyCurrentTheme() { if (modalOverlay) { if (document.body.classList.contains(GEMINI_DARK_MODE_CLASS) || document.documentElement.classList.contains(GEMINI_DARK_MODE_CLASS)) { modalOverlay.classList.add('prompt-editor-dark-theme'); } else { modalOverlay.classList.remove('prompt-editor-dark-theme'); } } } function positionModal() { if (!modal || !modalOverlay || modalOverlay.style.display === 'none') return; const modalWidth = modal.offsetWidth; const modalHeight = modal.offsetHeight; const targetElement = document.querySelector(GEMINI_MAIN_CONTENT_SELECTOR); let modalLeft, modalTop; if (targetElement) { const targetRect = targetElement.getBoundingClientRect(); modalLeft = targetRect.left + (targetRect.width / 2) - (modalWidth / 2); modalTop = (window.innerHeight / 2) - (modalHeight / 2); modalLeft = Math.max(10, Math.min(modalLeft, window.innerWidth - modalWidth - 10)); modalTop = Math.max(10, Math.min(modalTop, window.innerHeight - modalHeight - 10)); } else { console.warn(`Gemini PROmpt editor: Elemento target "${GEMINI_MAIN_CONTENT_SELECTOR}" non trovato. Uso centraggio viewport.`); modalLeft = (window.innerWidth / 2) - (modalWidth / 2); modalTop = (window.innerHeight / 2) - (modalHeight / 2); } modal.style.left = `${modalLeft}px`; modal.style.top = `${modalTop}px`; } function updateUIStrings() { STRINGS = LANG_STRINGS[currentLang] || LANG_STRINGS['EN']; // Assicura fallback a EN PROMPT_SECTIONS = STRINGS.sections; PROMPT_TEMPLATE = generatePromptTemplate(PROMPT_SECTIONS); if (modal) { modal.querySelector('h3').textContent = STRINGS.modalTitle; modal.querySelector('#selectAllBtn').textContent = STRINGS.btnSelectAll; modal.querySelector('#cancelBtn').textContent = STRINGS.btnCancel; modal.querySelector('#copyAndCloseBtn').textContent = STRINGS.btnApply; } if (editorButton) editorButton.setAttribute('aria-label', STRINGS.ariaOpenEditor); // Il valore del langSelectElement viene aggiornato dal suo event listener, non qui direttamente // per evitare loop se updateUIStrings fosse chiamato dall'event listener stesso. } function updatePromptTextareaOnLangChange(oldLangValue) { if (!promptTextarea) return; const oldTemplate = generatePromptTemplate(LANG_STRINGS[oldLangValue]?.sections || LANG_STRINGS['EN'].sections); if (promptTextarea.value.trim() === "" || promptTextarea.value === oldTemplate) { promptTextarea.value = PROMPT_TEMPLATE; // PROMPT_TEMPLATE è già stato aggiornato per currentLang } } function createEditorModal() { if (document.getElementById('promptEditorModalOverlay')) return; modalOverlay = document.createElement('div'); modalOverlay.id = 'promptEditorModalOverlay'; modal = document.createElement('div'); modal.id = 'promptEditorModal'; const titleElement = document.createElement('h3'); modal.appendChild(titleElement); promptTextarea = document.createElement('textarea'); promptTextarea.id = 'promptEditorTextarea'; modal.appendChild(promptTextarea); const controlsDiv = document.createElement('div'); controlsDiv.id = 'promptEditorControls'; const leftControlsDiv = document.createElement('div'); leftControlsDiv.className = 'left-controls'; const selectAllBtn = document.createElement('button'); selectAllBtn.id = 'selectAllBtn'; leftControlsDiv.appendChild(selectAllBtn); langSelectElement = document.createElement('select'); langSelectElement.id = 'langSelect'; for (const langCode in LANG_STRINGS) { const option = document.createElement('option'); option.value = langCode; option.textContent = langCode.toUpperCase(); if (langCode === currentLang) option.selected = true; langSelectElement.appendChild(option); } leftControlsDiv.appendChild(langSelectElement); controlsDiv.appendChild(leftControlsDiv); const rightButtonsDiv = document.createElement('div'); rightButtonsDiv.className = 'right-buttons'; const cancelEditorBtn = document.createElement('button'); cancelEditorBtn.id = 'cancelBtn'; rightButtonsDiv.appendChild(cancelEditorBtn); const copyAndCloseEditorBtn = document.createElement('button'); copyAndCloseEditorBtn.id = 'copyAndCloseBtn'; rightButtonsDiv.appendChild(copyAndCloseEditorBtn); controlsDiv.appendChild(rightButtonsDiv); modal.appendChild(controlsDiv); modalOverlay.appendChild(modal); document.body.appendChild(modalOverlay); updateUIStrings(); // Imposta testi iniziali selectAllBtn.addEventListener('click', () => { if (promptTextarea) { promptTextarea.focus(); promptTextarea.select(); } }); copyAndCloseEditorBtn.addEventListener('click', () => { let fullText = promptTextarea.value; let outputParts = []; const sectionRegex = /(\*\*### [^*]+ ###\*\*\s*[\s\S]*?(?=\n\n\*\*###|$))/g; let match; const localPromptSections = STRINGS.sections; // Usa le sezioni della lingua corrente const foundSections = []; while ((match = sectionRegex.exec(fullText)) !== null) { foundSections.push(match[0].trim()); } if (foundSections.length > 0) { for (const sectionText of foundSections) { let headerInfo = null; let sectionContent = ""; for (const def of localPromptSections) { if (sectionText.startsWith(def.header)) { headerInfo = def; sectionContent = sectionText.substring(def.header.length).trim(); break; } } if (headerInfo) { if (sectionContent !== "" && sectionContent !== headerInfo.placeholder.trim()) { outputParts.push(headerInfo.header + '\n' + sectionContent); } } else { outputParts.push(sectionText); } } } else if (fullText.trim() !== "" && fullText.trim() !== generatePromptTemplate(localPromptSections).trim()) { // Se non ci sono sezioni strutturate ma c'è testo diverso dal template vuoto, copia tutto outputParts.push(fullText.trim()); } // Altrimenti non copia nulla se il template è vuoto e non ci sono sezioni const textToCopy = outputParts.join("\n\n").trim(); if (geminiInputTarget && geminiInputTarget.input) { geminiInputTarget.input.focus(); geminiInputTarget.input.innerText = textToCopy; geminiInputTarget.input.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); geminiInputTarget.input.dispatchEvent(new Event('change', { bubbles: true, cancelable: true })); const placeholder = geminiInputTarget.input.parentElement?.querySelector('.input-placeholder, .ql-placeholder'); if (placeholder) placeholder.style.display = 'none'; setTimeout(() => { geminiInputTarget.input.dispatchEvent(new Event('keyup', { bubbles: true, cancelable: true })); }, 50); } else { console.error("Gemini PROmpt editor: Target input di Gemini non trovato per copiare."); } modalOverlay.style.display = 'none'; }); cancelEditorBtn.addEventListener('click', () => { modalOverlay.style.display = 'none'; }); modalOverlay.addEventListener('keydown', (event) => { if (event.key === 'Escape') modalOverlay.style.display = 'none'; }); modal.addEventListener('click', (event) => event.stopPropagation()); modalOverlay.addEventListener('click', () => modalOverlay.style.display = 'none'); langSelectElement.addEventListener('change', (event) => { const oldLang = currentLang; currentLang = event.target.value; GM_setValue('promptEditorLang', currentLang); updateUIStrings(); updatePromptTextareaOnLangChange(oldLang); }); } function addOpenEditorButton() { if (document.getElementById('promptEditorButton')) return; const inputInfo = findGeminiInputArea(); if (inputInfo && inputInfo.input && inputInfo.container) { geminiInputTarget = inputInfo; editorButton = document.createElement('button'); editorButton.id = 'promptEditorButton'; editorButton.classList.add('google-symbols'); // Classe per font Material Symbols // editorButton.classList.add('mat-ligature-font'); // Potrebbe non essere necessaria se 'google-symbols' è sufficiente editorButton.textContent = 'edit_note'; // Ligatura per l'icona editorButton.setAttribute('aria-label', STRINGS.ariaOpenEditor); editorButton.addEventListener('click', (e) => { e.stopPropagation(); if (!modalOverlay) createEditorModal(); modalOverlay.style.visibility = 'hidden'; modalOverlay.style.display = 'block'; if (modal) modal.style.display = 'flex'; applyCurrentTheme(); positionModal(); modalOverlay.style.visibility = 'visible'; if (promptTextarea) { // Assicurati che promptTextarea sia definito const currentTemplateForLang = generatePromptTemplate(STRINGS.sections); if (promptTextarea.value.trim() === "" || !promptTextarea.value.includes(STRINGS.sections[0].header) || // Controllo più generico basato sulla prima intestazione promptTextarea.value.length < currentTemplateForLang.length / 2 ) { // Se troppo corto promptTextarea.value = currentTemplateForLang; } promptTextarea.focus(); promptTextarea.scrollTop = 0; } }); if (!inputInfo.container || !(inputInfo.container instanceof Element)) { console.error("Gemini PROmpt editor: inputInfo.container non è un Elemento valido.", inputInfo.container); return; } const inputButtonsWrapperBottom = inputInfo.container.querySelector('div.input-buttons-wrapper-bottom'); if (inputButtonsWrapperBottom) { const micButtonContainer = inputButtonsWrapperBottom.querySelector('div.mic-button-container'); if (micButtonContainer) inputButtonsWrapperBottom.insertBefore(editorButton, micButtonContainer); else inputButtonsWrapperBottom.prepend(editorButton); } else { const trailingActionsWrapper = inputInfo.container.querySelector('div.trailing-actions-wrapper'); if (trailingActionsWrapper) trailingActionsWrapper.prepend(editorButton); else { const leadingActionsWrapper = inputInfo.container.querySelector('div.leading-actions-wrapper'); if (leadingActionsWrapper) { const uploaderContainer = leadingActionsWrapper.querySelector('div.uploader-button-container'); if (uploaderContainer) leadingActionsWrapper.insertBefore(editorButton, uploaderContainer); else leadingActionsWrapper.prepend(editorButton); } else { const mainInputArea = inputInfo.input.closest('.text-input-field-main-area'); if (mainInputArea) mainInputArea.appendChild(editorButton); else inputInfo.container.appendChild(editorButton); } } } } } // --- INIZIALIZZAZIONE --- console.log(`Gemini PROmpt editor: Script v${GM_info.script.version} avviato. Lingua: ${currentLang.toUpperCase()}`); const observer = new MutationObserver(() => { if (!document.getElementById('promptEditorButton')) addOpenEditorButton(); }); observer.observe(document.body, { childList: true, subtree: true }); window.addEventListener('load', () => { setTimeout(() => { if (!document.getElementById('promptEditorButton')) addOpenEditorButton(); }, 3000); }); window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(positionModal, 100); }); })();