您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
直接在 ChatGPT、DeepSeek、Google AI Studio、Qwen、Z.ai、Gemini 和 LMArena 中保存、编辑、删除、导入和导出您的自定义提示。
// ==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(); } })();