// ==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);
});
})();