// ==UserScript==
// @name My Prompt
// @name:en My Prompt
// @name:pt-BR Meu Prompt
// @name:es Mi Prompt
// @name:zh-CN 我的提示
// @namespace https://github.com/0H4S
// @version 1.1
// @description Save, edit, delete, import, and export your custom prompts directly in ChatGPT, DeepSeek, Google AI Studio, Qwen, Z.ai, Gemini, and LMArena
// @description:en Save, edit, delete, import, and export your custom prompts directly in ChatGPT, DeepSeek, Google AI Studio, Qwen, Z.ai, Gemini, and LMArena
// @description:pt-BR Salve, edite, exclua, importe e exporte seus prompts personalizados diretamente no ChatGPT, DeepSeek, Google AI Studio, Qwen, Z.ai, Gemini e LMArena
// @description:es Guarda, edita, elimina, importa y exporta tus prompts personalizados directamente en ChatGPT, DeepSeek, Google AI Studio, Qwen, Z.ai, Gemini y LMArena
// @description:zh-CN 直接在 ChatGPT、DeepSeek、Google AI Studio、Qwen、Z.ai、Gemini 和 LMArena 中保存、编辑、删除、导入和导出您的自定义提示。
// @author OHAS
// @homepage https://github.com/0H4S
// @icon https://cdn-icons-png.flaticon.com/512/4997/4997543.png
// @license Copyright (c) 2025 OHAS. All Rights Reserved.
// @match https://chatgpt.com/*
// @match https://chat.deepseek.com/*
// @match https://aistudio.google.com/*
// @match https://chat.qwen.ai/*
// @match https://chat.z.ai/*
// @match https://gemini.google.com/*
// @match https://lmarena.ai/*
// @require https://update.greasyfork.org/scripts/549920/1662869/Script%20Notifier.js
// @connect gist.githubusercontent.com
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
/* globals ScriptNotifier */
if (window.top !== window.self) {
return;
}
const SCRIPT_CONFIG = {
notificationsUrl: 'https://gist.githubusercontent.com/0H4S/40b2a2feb2ba18d0bf63a1943ba5cec3/raw/my_prompt_notifications.json',
scriptVersion: '1.1',
currentLang: navigator.language || 'en'
};
const notifier = new ScriptNotifier(SCRIPT_CONFIG);
notifier.run();
const PROMPT_STORAGE_KEY = 'Prompts';
const LANG_STORAGE_KEY = 'Lang';
const translations = {
'en': {
'langName': 'English',
'prompt': 'Prompt',
'prompts': 'Prompts',
'newPrompt': 'New Prompt',
'editPrompt': 'Edit Prompt',
'title': 'Title',
'text': 'Prompt',
'save': 'Save',
'close': 'Close',
'edit': 'Edit',
'delete': 'Delete',
'noSavedPrompts': 'No saved prompts.',
'addPrompt': 'Add prompt',
'import': 'Import',
'export': 'Export',
'confirmDelete': 'Delete prompt "{title}"?',
'noPromptsToExport': 'No prompts to export.',
'promptsImported': '{count} prompts imported successfully!',
'errorImporting': 'Error importing file: {error}',
'requiredFields': 'Title and prompt are required.',
'editorNotFound': 'Could not find the text area for {platform}.',
'languageSettings': '🌐 Language',
'fileName': 'My Prompt.json'
},
'es': {
'langName': 'Español',
'prompt': 'Prompt',
'prompts': 'Prompts',
'newPrompt': 'Nuevo Prompt',
'editPrompt': 'Editar Prompt',
'title': 'Título',
'text': 'Prompt',
'save': 'Guardar',
'close': 'Cerrar',
'edit': 'Editar',
'delete': 'Eliminar',
'noSavedPrompts': 'No hay prompts guardados.',
'addPrompt': 'Añadir prompt',
'import': 'Importar',
'export': 'Exportar',
'confirmDelete': '¿Eliminar prompt "{title}"?',
'noPromptsToExport': 'No hay prompts para exportar.',
'promptsImported': '¡{count} prompts importados con éxito!',
'errorImporting': 'Error al importar el archivo: {error}',
'requiredFields': 'El título y el prompt son obligatorios.',
'editorNotFound': 'No se pudo encontrar el área de texto para {platform}.',
'languageSettings': '🌐 Idioma',
'fileName': 'Mi Prompt.json'
},
'pt-BR': {
'langName': 'Português (BR)',
'prompt': 'Prompt',
'prompts': 'Prompts',
'newPrompt': 'Novo Prompt',
'editPrompt': 'Editar Prompt',
'title': 'Título',
'text': 'Prompt',
'save': 'Salvar',
'close': 'Fechar',
'edit': 'Editar',
'delete': 'Excluir',
'noSavedPrompts': 'Nenhum prompt salvo.',
'addPrompt': 'Adicionar prompt',
'import': 'Importar',
'export': 'Exportar',
'confirmDelete': 'Excluir prompt "{title}"?',
'noPromptsToExport': 'Não há prompts para exportar.',
'promptsImported': '{count} prompts importados com sucesso!',
'errorImporting': 'Erro ao importar o arquivo: {error}',
'requiredFields': 'Título e prompt são obrigatórios.',
'editorNotFound': 'Não foi possível encontrar a área de texto para {platform}.',
'languageSettings': '🌐 Idioma',
'fileName': 'Meu Prompt.json'
},
'zh-CN': {
'langName': '简体中文',
'prompt': '提示',
'prompts': '提示',
'newPrompt': '新建提示',
'editPrompt': '编辑提示',
'title': '标题',
'text': '提示内容',
'save': '保存',
'close': '关闭',
'edit': '编辑',
'delete': '删除',
'noSavedPrompts': '没有已保存的提示。',
'addPrompt': '添加提示',
'import': '导入',
'export': '导出',
'confirmDelete': '确定要删除提示 "{title}" 吗?',
'noPromptsToExport': '沒有可導出的提示。',
'promptsImported': '成功导入 {count} 个提示!',
'errorImporting': '导入文件时出错: {error}',
'requiredFields': '标题和提示内容为必填项。',
'editorNotFound': '未能找到 {platform} 的文本输入区域。',
'languageSettings': '🌐 语言',
'fileName': '我的提示.json'
}
};
let currentLang = 'en';
function getTranslation(key, replacements = {}) {
let text = translations[currentLang]?.[key] || translations.en [key];
for (const placeholder in replacements) {
text = text.replace(`{${placeholder}}`, replacements[placeholder]);
}
return text;
}
async function determineLanguage() {
let savedLang = await GM_getValue(LANG_STORAGE_KEY);
if (savedLang && translations[savedLang]) {
currentLang = savedLang;
return;
}
let browserLang = (navigator.language || navigator.userLanguage).toLowerCase();
if (browserLang.startsWith('pt')) currentLang = 'pt-BR';
else if (browserLang.startsWith('es')) currentLang = 'es';
else if (browserLang.startsWith('zh')) currentLang = 'zh-CN';
else currentLang = 'en';
}
let isInitialized = false;
let isInitializing = false;
let currentButton = null;
let currentMenu = null;
let currentModal = null;
let languageModal = null;
let currentPlatform = null;
let pageObserver = null;
const scriptPolicy = window.trustedTypes
? window.trustedTypes.createPolicy('MyPromptPolicy', { createHTML: (input) => input })
: null;
function setSafeInnerHTML(element, html) {
if (!element) return;
if (scriptPolicy) {
element.innerHTML = scriptPolicy.createHTML(html);
} else {
element.innerHTML = html;
}
}
function detectPlatform() {
const hostname = window.location.hostname;
if (hostname.includes('chatgpt.com')) return 'chatgpt';
if (hostname.includes('deepseek.com')) return 'deepseek';
if (hostname.includes('aistudio.google.com')) return 'googlestudio';
if (hostname.includes('chat.qwen.ai')) return 'qwen';
if (hostname.includes('chat.z.ai')) return 'zai';
if (hostname.includes('gemini.google.com')) return 'gemini';
if (hostname.includes('lmarena.ai')) return 'lmarena';
return null;
}
function waitFor(selector, timeout = 8000) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) { resolve(el); return; }
const timer = setTimeout(() => {
obs.disconnect();
reject(`Timeout esperando por ${selector}`);
}, timeout);
const obs = new MutationObserver(() => {
const target = document.querySelector(selector);
if (target) {
clearTimeout(timer);
obs.disconnect();
resolve(target);
}
});
if (document.body) {
obs.observe(document.body, { childList: true, subtree: true });
} else {
document.addEventListener('DOMContentLoaded', () => obs.observe(document.body, { childList: true, subtree: true }));
}
});
}
const debounce = (func, wait) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
};
async function getAll() { return (await GM_getValue(PROMPT_STORAGE_KEY, [])); }
async function addItem(item) {
const prompts = await getAll();
prompts.push(item);
await GM_setValue(PROMPT_STORAGE_KEY, prompts);
return item;
}
async function update(index, itemToUpdate) {
let prompts = await getAll();
if (prompts[index]) {
prompts[index] = itemToUpdate;
await GM_setValue(PROMPT_STORAGE_KEY, prompts);
}
}
async function remove(index) {
let prompts = await getAll();
prompts.splice(index, 1);
await GM_setValue(PROMPT_STORAGE_KEY, prompts);
}
function createCustomTooltip(button, text, position = 'top') {
let tooltipElement = null;
const showTooltip = () => {
if (tooltipElement) return;
tooltipElement = document.createElement('div');
const innerDiv = document.createElement('div');
innerDiv.textContent = text;
innerDiv.style.cssText = "color: #1d1d20; text-align: center; font-family: 'Inter', sans-serif; font-size: 14px; font-weight: 400; line-height: 1;";
tooltipElement.appendChild(innerDiv);
tooltipElement.style.cssText = "position: fixed; z-index: 2147483647; background-color: #f4f4f6; border-radius: 6px; padding: 6px 12px; pointer-events: none; opacity: 0; transform: scale(0.95); transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1); white-space: nowrap;";
document.body.appendChild(tooltipElement);
const btnRect = button.getBoundingClientRect();
const tooltipRect = tooltipElement.getBoundingClientRect();
let top;
const margin = 8;
if (position === 'bottom') {
top = btnRect.bottom + margin;
if (top + tooltipRect.height > window.innerHeight) {
top = btnRect.top - tooltipRect.height - margin;
}
} else {
top = btnRect.top - tooltipRect.height - margin;
if (top < 0) {
top = btnRect.bottom + margin;
}
}
let left = btnRect.left + (btnRect.width / 2) - (tooltipRect.width / 2);
if (left < 0) { left = margin; }
if (left + tooltipRect.width > window.innerWidth) { left = window.innerWidth - tooltipRect.width - margin; }
tooltipElement.style.left = `${left}px`;
tooltipElement.style.top = `${top}px`;
requestAnimationFrame(() => {
tooltipElement.style.opacity = '1';
tooltipElement.style.transform = 'scale(1)';
});
};
const hideTooltip = () => {
if (!tooltipElement) return;
const el = tooltipElement;
tooltipElement = null;
el.style.opacity = '0';
el.style.transform = 'scale(0.95)';
setTimeout(() => {
if (document.body.contains(el)) {
document.body.removeChild(el);
}
}, 150);
};
button.addEventListener('mouseenter', showTooltip);
button.addEventListener('mouseleave', hideTooltip);
button.addEventListener('mousedown', hideTooltip);
}
function createChatGPTButton() {
const btn = document.createElement('button');
btn.type = 'button';
btn.setAttribute('data-testid', 'composer-button-prompts');
btn.setAttribute('aria-label', getTranslation('prompts'));
btn.setAttribute('title', getTranslation('prompt'));
btn.className = 'composer-btn';
setSafeInnerHTML(btn, `<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-label="" class="icon"><path d="M4 5h12M4 10h12M4 15h12" stroke="currentColor" stroke-width="2"/></svg>`);
return btn;
}
function createDeepSeekButton() {
const btn = document.createElement('button');
btn.setAttribute('role', 'button');
btn.setAttribute('tabindex', '0');
btn.setAttribute('data-testid', 'composer-button-prompts');
setSafeInnerHTML(btn, `<div class="ds-icon" style="font-size: 17px; width: 17px; height: 17px; color: currentColor; margin-right: 6px; visibility: visible;"><svg width="20" height="20" fill="currentColor" viewBox="0 0 20 20"><path d="M4 5h12M4 10h12M4 15h12" stroke="currentColor" stroke-width="2"/></svg></div><span class=""><span class="_6dbc175">${getTranslation('prompt')}</span></span>`);
return btn;
}
function createGoogleStudioButton() {
const btn = document.createElement('button');
btn.setAttribute('data-testid', 'composer-button-prompts');
btn.setAttribute('ms-button', '');
btn.setAttribute('variant', 'icon');
btn.className = 'mat-mdc-tooltip-trigger icon';
btn.setAttribute('aria-label', getTranslation('prompts'));
btn.setAttribute('title', getTranslation('prompt'));
const svgHTML = `<svg fill="currentColor" style="width: 24px; height: 24px; display: flex;" version="1.1" viewBox="0 0 100 100"><g><path d="M17.563,30.277h0.012c0,1.245,1.004,2.254,2.246,2.267v0.002h60.359v-0.001c1.248-0.006,2.259-1.018,2.259-2.268h0.01 l0-10.459h0c-0.002-1.251-1.017-2.265-2.269-2.265l0,0H19.821v0c0,0,0,0,0,0c-1.253,0-2.269,1.017-2.269,2.269 c0,0.039,0.01,0.076,0.012,0.115L17.563,30.277z"/><path d="M80.179,42.504L80.179,42.504H19.821v0c0,0,0,0,0,0c-1.253,0-2.269,1.017-2.269,2.269c0,0.039,0.01,0.076,0.012,0.115 l0,10.34h0.012c0,1.245,1.004,2.254,2.246,2.267v0.002h60.359v-0.001c1.248-0.006,2.259-1.018,2.259-2.268h0.01l0-10.459h0 C82.446,43.518,81.431,42.504,80.179,42.504z"/><path d="M80.179,67.454L80.179,67.454H19.821l0,0c0,0,0,0,0,0c-1.253,0-2.269,1.017-2.269,2.269c0,0.039,0.01,0.076,0.012,0.115 l0,10.34h0.012c0,1.245,1.004,2.254,2.246,2.267v0.002h60.359v-0.001c1.248-0.006,2.259-1.019,2.259-2.269h0.01l0-10.459h0 C82.446,68.468,81.431,67.454,80.179,67.454z"/></g></svg>`;
setSafeInnerHTML(btn, svgHTML);
return btn;
}
function createQwenButton() {
const btn = document.createElement('button');
btn.className = 'chat-input-feature-btn';
btn.setAttribute('data-testid', 'composer-button-prompts');
setSafeInnerHTML(btn, `<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="chat-input-feature-btn-icon" style="font-size: 16px;"><path d="M4 5h12M4 10h12M4 15h12" stroke="currentColor" stroke-width="2"/></svg><span class="chat-input-feature-btn-text">${getTranslation('prompt')}</span>`);
return btn;
}
function createZaiButton() {
const btnWrapper = document.createElement('div');
setSafeInnerHTML(btnWrapper, `<button type="button" class="px-2 @xl:px-3 py-1.5 flex gap-1.5 items-center text-sm rounded-lg border transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden bg-transparent dark:text-gray-300 border-[#E5E5E5] dark:border-[#3C3E3F] hover:bg-black/5 dark:hover:bg-white/5"><svg class=" size-4" stroke-width="2" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 17L3 12L9 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M15 17L21 12L15 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="hidden @sm:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5">${getTranslation('prompt')}</span></button>`);
const btn = btnWrapper.firstElementChild;
btn.setAttribute('data-testid', 'composer-button-prompts');
return btn;
}
function createGeminiButton() {
const btn = document.createElement('button');
btn.setAttribute('data-testid', 'composer-button-prompts');
btn.className = 'mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-primary mat-mdc-tooltip-trigger';
const svgHTML = `<span class="mat-mdc-button-persistent-ripple mdc-icon-button__ripple"></span>
<svg style="width: 24px; height: 24px; display: flex;" viewBox="0 0 20 20" fill="currentColor"><path d="M4 5h12M4 10h12M4 15h12" stroke="currentColor" stroke-width="2"/></svg>
<span class="mat-focus-indicator"></span><span class="mat-mdc-button-touch-target"></span><span class="mat-ripple mat-mdc-button-ripple"></span>`;
setSafeInnerHTML(btn, svgHTML);
// Adicionamos 'bottom' para especificar a posição
createCustomTooltip(btn, getTranslation('prompt'), 'bottom');
return btn;
}
function createLmarenaButton() {
const btn = document.createElement('button');
btn.setAttribute('data-testid', 'composer-button-prompts');
btn.className = 'inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ring-offset-2 focus-visible:ring-offset-surface-primary disabled:pointer-events-none disabled:opacity-50 text-interactive-active border border-border-faint bg-transparent hover:text-interactive-normal active:text-text-tertiary h-8 w-8 p-2 rounded-md active:scale-[0.96] active:transition-transform active:duration-75 transition-colors duration-150 ease-out hover:shadow-sm hover:bg-interactive-normal/10 hover:border-interactive-normal/10';
btn.type = 'button';
const svgHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4 transition-colors duration-150 ease-out text-interactive-normal"><path d="M4 5h12M4 10h12M4 15h12" stroke="currentColor" stroke-width="2"/></svg>`;
setSafeInnerHTML(btn, svgHTML);
createCustomTooltip(btn, getTranslation('prompt'));
return btn;
}
function createMenu() {
const m = document.createElement('div');
m.setAttribute('data-state', 'closed');
m.setAttribute('data-prompts-menu', 'true');
m.style.cssText = `position: fixed; display: none; min-width: 320px; max-width: 420px; background-color: #353535; border: 1px solid #626262; border-radius: 16px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); z-index: 2147483647; flex-direction: column; padding: 6px 0; user-select: none; color: #F8FAFF;`;
return m;
}
function createModal() {
const overlay = document.createElement('div');
overlay.id = '__ap_modal';
overlay.setAttribute('data-prompts-modal', 'true');
overlay.style.cssText = `position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); display: none; z-index: 2147483647; justify-content: center; align-items: center; backdrop-filter: blur(4px);`;
const box = document.createElement('div');
box.style.cssText = `position: relative; background-color: #2a2a2e; color: #e0e0e0; border-radius: 16px; padding: 28px; box-shadow: 0 4px 6px rgba(0,0,0,0.1), 0 10px 20px rgba(0,0,0,0.4); width: min(90vw, 520px); display: flex; flex-direction: column; border: 1px solid #444; transform: scale(0.95); opacity: 0; transition: transform 0.2s ease-out, opacity 0.2s ease-out;`;
setSafeInnerHTML(box, `
<style>
#__ap_modal input:focus, #__ap_modal textarea:focus { border-color: #22c55e; box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.4); }
</style>
<button id="__ap_close" aria-label="${getTranslation('close')}" style="position: absolute; top: 12px; right: 12px; background: none; border: none; color: #999; font-size: 22px; cursor: pointer; width: 32px; height: 32px; border-radius: 50%; transition: transform 0.3s ease, color 0.3s ease; display: flex; justify-content: center; align-items: center;">✕</button>
<h2 id="__ap_modal_title" style="font-size: 18px; font-weight: 600; margin: 0 0 20px; text-align: center; color: #f0f0f0; letter-spacing: 0.5px;">${getTranslation('newPrompt')}</h2>
<label for="__ap_title" style="margin-bottom: 6px; font-size: 13px; font-weight: 500; color: #bbb;">${getTranslation('title')}</label>
<input id="__ap_title" style="background-color: #1e1e22; color: #e0e0e0; border: 1px solid #555; border-radius: 8px; padding: 10px; margin-bottom: 16px; width: 100%; box-sizing: border-box; transition: all 0.2s; outline: none;" />
<label for="__ap_text" style="margin-bottom: 6px; font-size: 13px; font-weight: 500; color: #bbb;">${getTranslation('text')}</label>
<textarea id="__ap_text" style="background-color: #1e1e22; color: #e0e0e0; border: 1px solid #555; border-radius: 8px; padding: 10px; height: 120px; width: 100%; margin-bottom: 24px; box-sizing: border-box; resize: vertical; outline: none; font-family: inherit;"></textarea>
<div style="display: flex; justify-content: center;">
<button id="__ap_save" style="padding: 10px 28px; border-radius: 8px; background-color: #22c55e; color: white; border: none; font-weight: 600; cursor: pointer; transition: all 0.2s ease-in-out; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">${getTranslation('save')}</button>
</div>`);
const saveBtn = box.querySelector('#__ap_save');
saveBtn.addEventListener('mouseenter', () => { saveBtn.style.backgroundColor = '#16a34a'; saveBtn.style.transform = 'translateY(-1px)'; });
saveBtn.addEventListener('mouseleave', () => { saveBtn.style.backgroundColor = '#22c55e'; saveBtn.style.transform = 'translateY(0)'; });
const closeBtn = box.querySelector('#__ap_close');
closeBtn.addEventListener('mouseenter', () => { closeBtn.style.transform = 'rotate(90deg)'; closeBtn.style.color = '#ef4444'; });
closeBtn.addEventListener('mouseleave', () => { closeBtn.style.transform = 'rotate(0deg)'; closeBtn.style.color = '#999'; });
overlay.appendChild(box);
closeBtn.onclick = (e) => { e.stopPropagation(); hideModal(currentModal); };
return overlay;
}
function createLanguageModal() {
const overlay = document.createElement('div');
overlay.id = '__ap_lang_modal';
overlay.style.cssText = `position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); display: none; z-index: 2147483647; justify-content: center; align-items: center; backdrop-filter: blur(4px);`;
const box = document.createElement('div');
box.style.cssText = `position: relative; background-color: #2a2a2e; color: #e0e0e0; border-radius: 16px; padding: 40px 28px 28px; box-shadow: 0 4px 6px rgba(0,0,0,0.1), 0 10px 20px rgba(0,0,0,0.4); width: min(90vw, 380px); display: flex; flex-direction: column; border: 1px solid #444; transform: scale(0.95); opacity: 0; transition: transform 0.2s ease-out, opacity 0.2s ease-out;`;
const buttonsContainer = document.createElement('div');
buttonsContainer.style.cssText = 'display: flex; flex-direction: column; gap: 12px;';
Object.keys(translations).forEach(langKey => {
const btn = document.createElement('button');
btn.textContent = translations[langKey].langName;
btn.style.cssText = `padding: 12px 20px; border-radius: 8px; background-color: #3e3e42; color: white; border: 1px solid #555; font-weight: 500; cursor: pointer; transition: all 0.2s ease-in-out; text-align: center;`;
btn.addEventListener('mouseenter', () => { btn.style.borderColor = '#22c55e'; btn.style.backgroundColor = '#4a4a50'; });
btn.addEventListener('mouseleave', () => { btn.style.borderColor = '#555'; btn.style.backgroundColor = '#3e3e42'; });
btn.onclick = async () => {
await GM_setValue(LANG_STORAGE_KEY, langKey);
window.location.reload();
};
buttonsContainer.appendChild(btn);
});
setSafeInnerHTML(box, `
<button id="__ap_lang_close" aria-label="${getTranslation('close')}" style="position: absolute; top: 5px; right: 5px; background: none; border: none; color: #999; font-size: 22px; cursor: pointer; width: 32px; height: 32px; border-radius: 50%; transition: transform 0.3s ease, color 0.3s ease; display: flex; justify-content: center; align-items: center;">✕</button>
`);
box.appendChild(buttonsContainer);
overlay.appendChild(box);
const closeBtnLang = box.querySelector('#__ap_lang_close');
closeBtnLang.onclick = (e) => { e.stopPropagation(); hideModal(languageModal); };
closeBtnLang.addEventListener('mouseenter', () => { closeBtnLang.style.transform = 'rotate(90deg)'; closeBtnLang.style.color = '#ef4444'; });
closeBtnLang.addEventListener('mouseleave', () => { closeBtnLang.style.transform = 'rotate(0deg)'; closeBtnLang.style.color = '#999'; });
return overlay;
}
function positionMenu(menu, button) {
const btnRect = button.getBoundingClientRect();
const menuHeight = menu.offsetHeight;
const menuWidth = menu.offsetWidth;
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
const margin = 8;
let top = btnRect.bottom + margin;
const spaceBelow = viewportHeight - btnRect.bottom;
const spaceAbove = btnRect.top;
if (spaceBelow < menuHeight + margin && spaceAbove > menuHeight + margin) { top = btnRect.top - menuHeight - margin; }
let left = btnRect.left;
if (left + menuWidth > viewportWidth) { left = viewportWidth - menuWidth - margin; }
if (top < margin) { top = margin; }
if (left < margin) { left = margin; }
menu.style.top = `${top}px`;
menu.style.left = `${left}px`;
menu.style.visibility = 'visible';
}
function showModal(modal) {
if (!modal) return;
modal.style.display = 'flex';
setTimeout(() => {
const box = modal.querySelector('div');
box.style.opacity = '1';
box.style.transform = 'scale(1)';
}, 10);
}
function hideModal(modal) {
if (!modal) return;
const box = modal.querySelector('div');
box.style.opacity = '0';
box.style.transform = 'scale(0.95)';
setTimeout(() => { modal.style.display = 'none'; }, 200);
}
function openModal(item = null, index = -1) {
if (!currentModal) return;
const isEditing = !!item;
currentModal.setAttribute('data-index', index);
document.getElementById('__ap_modal_title').textContent = isEditing ? getTranslation('editPrompt') : getTranslation('newPrompt');
document.getElementById('__ap_title').value = item?.title || '';
document.getElementById('__ap_text').value = item?.text || '';
showModal(currentModal);
setTimeout(() => { document.getElementById('__ap_title').focus(); }, 100);
}
function ensureZaiToolsButtonIsVisible() {
if (currentPlatform !== 'zai') return;
setTimeout(() => {
const referenceIconPath = document.querySelector('svg path[d="M2.6499 4.48322H13.3166"]');
if (referenceIconPath) {
const toolsButton = referenceIconPath.closest('button[data-melt-popover-trigger]');
if (toolsButton && toolsButton.style.display === 'none') {
toolsButton.removeAttribute('style');
}
}
}, 0);
}
function closeMenu() {
if (currentMenu && currentMenu.getAttribute('data-state') === 'open') {
currentMenu.setAttribute('data-state', 'closed');
currentMenu.style.display = 'none';
currentMenu.style.visibility = 'hidden';
ensureZaiToolsButtonIsVisible();
}
}
function closeOtherMenus() {
document.querySelectorAll('[data-state="open"]:not([data-prompts-menu])').forEach(menu => {
if (menu.setAttribute) {
menu.setAttribute('data-state', 'closed');
if (menu.style) menu.style.display = 'none';
}
});
}
async function refreshMenu() {
if (!currentMenu) return;
setSafeInnerHTML(currentMenu, '');
const promptsListContainer = document.createElement('div');
promptsListContainer.style.cssText = 'max-height: 190px; overflow-y: auto;';
const items = await getAll();
if (!items.length) {
const empty = document.createElement('div');
empty.style.cssText = `padding: 8px 16px; color: #A0A0A0; font-size: 13px;`;
empty.textContent = getTranslation('noSavedPrompts');
promptsListContainer.appendChild(empty);
} else {
items.forEach((p, index) => {
const row = document.createElement('div');
row.style.cssText = `display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid #626262; cursor: pointer; transition: background-color 0.2s;`;
row.addEventListener('mouseenter', () => { row.style.backgroundColor = '#424451'; });
row.addEventListener('mouseleave', () => { row.style.backgroundColor = 'transparent'; });
const titleDiv = document.createElement('div');
titleDiv.style.cssText = `font-size: 14px; font-weight: 500; cursor: pointer; flex: 1; padding-right: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #F8FAFF;`;
titleDiv.textContent = p.title;
titleDiv.title = p.title;
titleDiv.onclick = (e) => { e.stopPropagation(); inserirPrompt(p, index); closeMenu(); };
row.appendChild(titleDiv);
const actionsDiv = document.createElement('div');
actionsDiv.style.cssText = `display: flex; align-items: center; gap: 8px; flex-shrink: 0;`;
const btnE = document.createElement('button');
btnE.textContent = getTranslation('edit');
btnE.title = getTranslation('edit');
btnE.style.cssText = `background: none; border: none; color: #fbbf24; cursor: pointer; font-size: 12px; padding: 4px 8px; border-radius: 4px; transition: background-color 0.2s;`;
btnE.addEventListener('mouseenter', () => { btnE.style.backgroundColor = '#92400e'; });
btnE.addEventListener('mouseleave', () => { btnE.style.backgroundColor = 'transparent'; });
btnE.onclick = (e) => { e.stopPropagation(); openModal(p, index); };
actionsDiv.appendChild(btnE);
const btnD = document.createElement('button');
btnD.textContent = getTranslation('delete');
btnD.title = getTranslation('delete');
btnD.style.cssText = `background: none; border: none; color: #ef4444; cursor: pointer; font-size: 12px; padding: 4px 8px; border-radius: 4px; transition: background-color 0.2s;`;
btnD.addEventListener('mouseenter', () => { btnD.style.backgroundColor = '#7f1d1d'; });
btnD.addEventListener('mouseleave', () => { btnD.style.backgroundColor = 'transparent'; });
btnD.onclick = (e) => { e.stopPropagation(); if (confirm(getTranslation('confirmDelete', { title: p.title }))) { remove(index).then(refreshMenu); } };
actionsDiv.appendChild(btnD);
row.appendChild(actionsDiv);
promptsListContainer.appendChild(row);
});
}
currentMenu.appendChild(promptsListContainer);
const add = document.createElement('div');
add.style.cssText = `display: flex; align-items: center; justify-content: center; padding: 8px 12px; cursor: pointer; transition: background-color 0.2s; color: #F8FAFF; border-top: 1px solid #626262;`;
add.addEventListener('mouseenter', () => { add.style.backgroundColor = '#424451'; });
add.addEventListener('mouseleave', () => { add.style.backgroundColor = 'transparent'; });
setSafeInnerHTML(add, `<svg width="16" height="16" fill="currentColor" viewBox="0 0 20 20" style="margin-right: 8px;"><path d="M10 5v10m5-5H5" stroke="currentColor" stroke-width="2"/></svg><span style="font-size: 13px;">${getTranslation('addPrompt')}</span>`);
add.title = getTranslation('addPrompt');
add.onclick = (e) => { e.stopPropagation(); openModal(); };
currentMenu.appendChild(add);
const importExportHr = document.createElement('hr');
importExportHr.style.cssText = "border: none; border-top: 1px solid #626262; margin: 0; padding: 0;";
currentMenu.appendChild(importExportHr);
const importExportContainer = document.createElement('div');
importExportContainer.style.cssText = `display: flex; justify-content: space-around; align-items: center;`;
const exportBtn = createMenuButton(getTranslation('export'));
exportBtn.onclick = (e) => { e.stopPropagation(); exportPrompts(); };
const importBtn = createMenuButton(getTranslation('import'));
importBtn.onclick = (e) => { e.stopPropagation(); importPrompts(); };
const divider = document.createElement('div');
divider.style.cssText = "border-left: 1px solid rgb(98, 98, 98); height: 24px;";
importExportContainer.appendChild(exportBtn);
importExportContainer.appendChild(divider);
importExportContainer.appendChild(importBtn);
currentMenu.appendChild(importExportContainer);
}
function createMenuButton(text) {
const btn = document.createElement('div');
btn.textContent = text;
btn.style.cssText = `flex: 1; text-align: center; padding: 8px 12px; cursor: pointer; transition: background-color 0.2s; color: #F8FAFF; font-size: 13px;`;
btn.addEventListener('mouseenter', () => { btn.style.backgroundColor = '#424451'; });
btn.addEventListener('mouseleave', () => { btn.style.backgroundColor = 'transparent'; });
return btn;
}
async function exportPrompts() {
const prompts = await getAll();
if (prompts.length === 0) { alert(getTranslation('noPromptsToExport')); return; }
const jsonString = JSON.stringify(prompts, null, 2);
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = getTranslation('fileName');
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
closeMenu();
}
function importPrompts() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json,application/json';
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (event) => {
try {
const importedPrompts = JSON.parse(event.target.result);
if (!Array.isArray(importedPrompts)) { throw new Error("O arquivo JSON não é um array válido."); }
const currentPrompts = await getAll();
const newPrompts = importedPrompts.map((p) => ({ title: p.title || 'Sem Título', text: p.text || '' }));
await GM_setValue(PROMPT_STORAGE_KEY, [...currentPrompts, ...newPrompts]);
await refreshMenu();
alert(getTranslation('promptsImported', { count: newPrompts.length }));
} catch (error) { alert(getTranslation('errorImporting', { error: error.message })); }
};
reader.readAsText(file);
};
input.click();
closeMenu();
}
async function inserirPrompt(promptItem, index) {
const platformSelectors = {
chatgpt: '#prompt-textarea',
deepseek: 'textarea[placeholder="Message DeepSeek"]',
googlestudio: 'ms-autosize-textarea textarea',
qwen: 'textarea#chat-input',
zai: 'textarea#chat-input',
gemini: 'div.ql-editor[contenteditable="true"]',
lmarena: 'textarea[name="message"]'
};
const selector = platformSelectors[currentPlatform];
const editor = document.querySelector(selector);
if (!editor) {
alert(getTranslation('editorNotFound', { platform: currentPlatform }));
return;
}
editor.focus();
setTimeout(() => {
if (currentPlatform === 'gemini') {
let p = editor.querySelector('p');
if (!p) {
p = document.createElement('p');
editor.appendChild(p);
}
p.textContent = promptItem.text;
editor.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
} else {
const dataTransfer = new DataTransfer();
dataTransfer.setData('text/plain', promptItem.text);
editor.dispatchEvent(new ClipboardEvent('paste', {
clipboardData: dataTransfer,
bubbles: true,
cancelable: true
}));
if (editor.value !== promptItem.text) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
nativeInputValueSetter.call(editor, promptItem.text);
editor.dispatchEvent(new Event('input', { bubbles: true }));
}
}
}, 50);
let prompts = await getAll();
if (index > 0) {
const item = prompts.splice(index, 1)[0];
prompts.unshift(item);
await GM_setValue(PROMPT_STORAGE_KEY, prompts);
}
}
function cleanup() {
if (currentButton) { currentButton.remove(); currentButton = null; }
if (currentMenu) { currentMenu.remove(); currentMenu = null; }
if (currentModal) { currentModal.remove(); currentModal = null; }
if (languageModal) { languageModal.remove(); languageModal = null; }
isInitialized = false;
}
async function initUI() {
if (pageObserver) pageObserver.disconnect();
cleanup();
currentPlatform = detectPlatform();
if (!currentPlatform) return;
try {
let btn, elementToInsert, insertionPoint, insertionMethod = 'before';
if (currentPlatform === 'chatgpt') {
insertionPoint = await waitFor('div[class*="[grid-area:leading]"]').catch(() => null);
if (insertionPoint) {
insertionPoint.style.display = 'flex';
insertionPoint.style.flexDirection = 'row';
insertionPoint.style.alignItems = 'center';
insertionPoint.style.gap = '8px';
btn = createChatGPTButton();
elementToInsert = btn;
insertionMethod = 'append';
}
} else if (currentPlatform === 'deepseek') {
const buttonsContainer = await waitFor('.ec4f5d61');
const referenceButton = buttonsContainer.querySelector('button:has(span._6dbc175)');
insertionPoint = buttonsContainer.querySelector('.bf38813a');
if (!referenceButton || !insertionPoint) { throw new Error('Elementos da UI do DeepSeek não encontrados.'); }
btn = createDeepSeekButton();
btn.className = referenceButton.className;
elementToInsert = btn;
insertionMethod = 'before';
} else if (currentPlatform === 'googlestudio') {
insertionPoint = await waitFor('ms-add-chunk-menu', 5000).then(el => el.closest('.button-wrapper')).catch(() => null);
if (insertionPoint) {
const wrapper = document.createElement('div');
wrapper.className = 'button-wrapper';
btn = createGoogleStudioButton();
wrapper.appendChild(btn);
elementToInsert = wrapper;
insertionMethod = 'before';
const parentContainer = insertionPoint.closest('.prompt-input-wrapper-container');
if (parentContainer) { parentContainer.style.alignItems = 'center'; }
}
} else if (currentPlatform === 'qwen') {
insertionPoint = await waitFor('button.websearch_button', 5000).catch(() => null);
if (insertionPoint) {
btn = createQwenButton();
elementToInsert = btn;
insertionMethod = 'after';
}
} else if (currentPlatform === 'zai') {
const referenceIconPath = await waitFor('svg path[d="M2.6499 4.48322H13.3166"]', 8000).catch(() => null);
if (referenceIconPath) {
insertionPoint = referenceIconPath.closest('button[data-melt-popover-trigger]');
if (insertionPoint) {
btn = createZaiButton();
elementToInsert = btn;
insertionMethod = 'after';
}
}
} else if (currentPlatform === 'gemini') {
insertionPoint = await waitFor('button.upload-card-button', 8000).catch(() => null);
if (insertionPoint) {
btn = createGeminiButton();
elementToInsert = btn;
insertionMethod = 'after';
const wrapper = insertionPoint.closest('div.leading-actions-wrapper');
if (wrapper) {
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.gap = '8px';
}
}
} else if (currentPlatform === 'lmarena') {
insertionPoint = await waitFor('div[data-sentry-component="SelectChatModality"]', 8000).catch(() => null);
if (insertionPoint) {
btn = createLmarenaButton();
elementToInsert = btn;
insertionMethod = 'append';
}
}
if (!btn || !insertionPoint) {
return;
}
currentButton = elementToInsert;
const clickableElement = btn;
currentMenu = createMenu();
currentModal = createModal();
languageModal = createLanguageModal();
if (insertionMethod === 'append') {
insertionPoint.appendChild(elementToInsert);
} else if (insertionMethod === 'before') {
insertionPoint.parentNode.insertBefore(elementToInsert, insertionPoint);
} else {
insertionPoint.parentNode.insertBefore(elementToInsert, insertionPoint.nextSibling);
}
document.body.appendChild(currentMenu);
document.body.appendChild(currentModal);
document.body.appendChild(languageModal);
clickableElement.addEventListener('click', e => {
e.stopPropagation();
e.preventDefault();
if (currentMenu.getAttribute('data-state') === 'open') {
closeMenu();
return;
}
closeOtherMenus();
ensureZaiToolsButtonIsVisible();
currentMenu.style.display = 'flex';
currentMenu.style.visibility = 'hidden';
refreshMenu().then(() => {
setTimeout(() => {
positionMenu(currentMenu, clickableElement);
currentMenu.setAttribute('data-state', 'open');
}, 10);
});
});
currentModal.querySelector('#__ap_save').onclick = (e) => {
e.stopPropagation();
const indexStr = currentModal.getAttribute('data-index');
const index = indexStr !== '-1' ? Number(indexStr) : -1;
const title = document.getElementById('__ap_title').value.trim();
const text = document.getElementById('__ap_text').value.trim();
if (!title || !text) { alert(getTranslation('requiredFields')); return; }
const item = { title, text };
const op = index > -1 ? update(index, item) : addItem(item);
op.then(() => { hideModal(currentModal); refreshMenu(); });
};
isInitialized = true;
} catch (error) {
cleanup();
} finally {
setupPageObserver();
}
}
function setupGlobalEventListeners() {
document.addEventListener('click', ev => {
if (!currentMenu || !currentButton) return;
if (ev.target.closest('[data-prompts-menu],[data-testid="composer-button-prompts"],[data-prompts-modal]')) return;
closeMenu();
});
document.addEventListener('keydown', ev => {
if (ev.key === 'Escape') {
closeMenu();
if (currentModal && currentModal.style.display === 'flex') { hideModal(currentModal); }
if (languageModal && languageModal.style.display === 'flex') { hideModal(languageModal); }
}
});
window.addEventListener('resize', debounce(() => {
if (currentMenu && currentMenu.getAttribute('data-state') === 'open') {
const clickable = currentButton.hasAttribute('data-testid') ? currentButton : currentButton.querySelector('[data-testid="composer-button-prompts"]');
if (clickable) positionMenu(currentMenu, clickable);
}
}, 100));
}
function registerLanguageMenu() {
GM_registerMenuCommand(getTranslation('languageSettings'), () => {
if (!languageModal) {
languageModal = createLanguageModal();
document.body.appendChild(languageModal);
}
showModal(languageModal);
});
}
const debouncedTryInit = debounce(tryInit, 500);
function setupPageObserver() {
if (pageObserver) pageObserver.disconnect();
pageObserver = new MutationObserver(() => {
if (!document.body.contains(currentButton)) {
debouncedTryInit();
}
});
pageObserver.observe(document.body, { childList: true, subtree: true });
}
function tryInit() {
if (isInitializing) return;
if (isInitialized && currentButton && document.body.contains(currentButton) && currentPlatform === detectPlatform()) {
return;
}
isInitializing = true;
initUI().catch(()=> {
}).finally(() => {
isInitializing = false;
});
}
async function start() {
await determineLanguage();
setupGlobalEventListeners();
registerLanguageMenu();
tryInit();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
}
})();