// ==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.3 // @description Save, edit, delete, import, and export your custom prompts directly in ChatGPT, DeepSeek, Google AI Studio, Qwen, Z.ai, Gemini, and LMArena with a modern UI. // @description:en Save, edit, delete, import, and export your custom prompts directly in ChatGPT, DeepSeek, Google AI Studio, Qwen, Z.ai, Gemini, and LMArena with a modern UI. // @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 com uma UI moderna. // @description:es Guarda, edita, elimina, importa y exporta tus prompts personalizados directamente en ChatGPT, DeepSeek, Google AI Studio, Qwen, Z.ai, Gemini y LMArena con una UI moderna. // @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 CC-BY-NC-ND-4.0 // @copyright 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.icu/scripts/549920/Script%20Notifier.js // @connect gist.githubusercontent.com // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @run-at document-end // @downloadURL none // ==/UserScript== (function() { 'use strict'; if (window.top !== window.self) return; 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 Prompts.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: 'Mis Prompts.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: 'Meus Prompts.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' } }; const LANG_STORAGE_KEY = 'UserScriptLang'; let currentLang = 'en'; const SCRIPT_CONFIG = { notificationsUrl: 'https://gist.githubusercontent.com/0H4S/40b2a2feb2ba18d0bf63a1943ba5cec3/raw/my_prompt_notifications.json', scriptVersion: '1.3', }; const notifier = new ScriptNotifier(SCRIPT_CONFIG); notifier.run(); const PROMPT_STORAGE_KEY = 'Prompts'; let isInitialized = false; let isInitializing = false; let currentButton = null; let currentPlatform = null; let pageObserver = null; let currentMenu = null; let currentModal = null; let languageModal = 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 getTranslation(key, replacements = {}) { let text = translations[currentLang]?.[key] || translations.en[key]; Object.entries(replacements).forEach(([p, v]) => text = text.replace(`{${p}}`, v)); return text; } async function determineLanguage() { const savedLang = await GM_getValue(LANG_STORAGE_KEY); if (savedLang && translations[savedLang]) { currentLang = savedLang; return; } const 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'; } 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); } async function update(index, item) { let prompts = await getAll(); if (prompts[index]) { prompts[index] = item; 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'); tooltipElement.className = 'mp-tooltip'; tooltipElement.textContent = text; 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.classList.add('visible'); }); }; const hideTooltip = () => { if (!tooltipElement) return; const el = tooltipElement; tooltipElement = null; el.classList.remove('visible'); 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.className = 'composer-btn'; setSafeInnerHTML(btn, ``); createCustomTooltip(btn, getTranslation('prompts')); return btn; } function createDeepSeekButton() { const btn = document.createElement('button'); btn.setAttribute('data-testid', 'composer-button-prompts'); setSafeInnerHTML(btn, `
${getTranslation('prompt')}`); return btn; } function createGoogleStudioButton() { const styleId = 'my-prompt-gstudio-hover-fix'; if (!document.getElementById(styleId)) { const styleElement = document.createElement('style'); styleElement.id = styleId; setSafeInnerHTML(styleElement, ` button[data-testid="composer-button-prompts"]:hover { background-color: rgba(60, 64, 67, 0.08) !important; } @media (prefers-color-scheme: dark) { button[data-testid="composer-button-prompts"]:hover { background-color: rgba(232, 234, 237, 0.08) !important; } } `); document.head.appendChild(styleElement); } const btn = document.createElement('button'); btn.setAttribute('data-testid', 'composer-button-prompts'); btn.type = 'button'; btn.style.backgroundColor = 'transparent'; btn.style.border = 'none'; btn.style.boxShadow = 'none'; btn.style.borderRadius = '50%'; btn.style.width = '48px'; btn.style.height = '48px'; btn.style.padding = '0'; btn.style.margin = '0'; btn.style.cursor = 'pointer'; btn.style.display = 'inline-flex'; btn.style.alignItems = 'center'; btn.style.justifyContent = 'center'; btn.style.transition = 'background-color 150ms ease-in-out'; setSafeInnerHTML(btn, ``); createCustomTooltip(btn, getTranslation('prompts')); return btn; } function createQwenButton() { const btn = document.createElement('button'); btn.className = 'chat-input-feature-btn'; btn.setAttribute('data-testid', 'composer-button-prompts'); setSafeInnerHTML(btn, `${getTranslation('prompt')}`); return btn; } function createZaiButton() { const btnWrapper = document.createElement('div'); setSafeInnerHTML(btnWrapper, ``); 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 = ``; setSafeInnerHTML(btn, svgHTML); 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'; setSafeInnerHTML(btn, ``); createCustomTooltip(btn, getTranslation('prompt')); return btn; } function injectGlobalStyles() { const styleId='my-prompt-styles'; if (document.getElementById(styleId)) return; const styleElement=document.createElement('style'); styleElement.id=styleId; setSafeInnerHTML(styleElement, ` @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); :root { --mp-font-family-base: 'Inter', sans-serif; --mp-bg-primary: #fff; --mp-bg-secondary: #f8f9fa; --mp-bg-tertiary: #f1f3f5; --mp-bg-overlay: rgba(10, 10, 10, .5); --mp-text-primary: #212529; --mp-text-secondary: #495057; --mp-text-tertiary: #868e96; --mp-border-primary: #dee2e6; --mp-border-secondary: #ced4da; --mp-accent-primary: #7071fc; --mp-accent-primary-hover: #595ac9; --mp-accent-yellow: #fab005; --mp-accent-yellow-hover: #f08c00; --mp-accent-red: #f03e3e; --mp-accent-red-hover: #c92a2a; --mp-shadow-sm: 0 1px 2px rgba(0, 0, 0, .04); --mp-shadow-md: 0 4px 12px rgba(0, 0, 0, .1); --mp-shadow-lg: 0 10px 30px rgba(0, 0, 0, .1); --mp-border-radius-sm: 4px; --mp-border-radius-md: 8px; --mp-border-radius-lg: 16px; --mp-transition-fast: .2s cubic-bezier(.25, 1, .5, 1) } @media (prefers-color-scheme:dark) { :root { --mp-bg-primary: #212529; --mp-bg-secondary: #2c2c30; --mp-bg-tertiary: #343a40; --mp-bg-overlay: rgba(0, 0, 0, .6); --mp-text-primary: #f8f9fa; --mp-text-secondary: #e9ecef; --mp-text-tertiary: #adb5bd; --mp-border-primary: #495057; --mp-border-secondary: #868e96; --mp-shadow-sm: 0 1px 2px rgba(0, 0, 0, .15); --mp-shadow-md: 0 4px 12px rgba(0, 0, 0, .25); --mp-shadow-lg: 0 10px 30px rgba(0, 0, 0, .3) } } .mp-hidden { display: none !important; } .mp-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--mp-bg-overlay); z-index: 2147483647; display: flex; justify-content: center; align-items: center; backdrop-filter: blur(4px); opacity: 0; visibility: hidden; transition: opacity var(--mp-transition-fast), visibility var(--mp-transition-fast); } .mp-overlay.visible { opacity: 1; visibility: visible; } .mp-modal-box { font-family: var(--mp-font-family-base); background-color: var(--mp-bg-primary); color: var(--mp-text-primary); border-radius: var(--mp-border-radius-lg); padding: 24px; box-shadow: var(--mp-shadow-lg); width: min(90vw, 520px); border: 1px solid var(--mp-border-primary); transform: scale(.95) translateY(10px); opacity: 0; transition: transform var(--mp-transition-fast), opacity var(--mp-transition-fast); position: relative; } .mp-overlay.visible .mp-modal-box { transform: scale(1) translateY(0); opacity: 1; } .mp-modal-close-btn { position: absolute; top: 12px; right: 12px; background: none; border: none; color: var(--mp-text-tertiary); font-size: 22px; cursor: pointer; width: 32px; height: 32px; border-radius: 50%; transition: transform .3s ease, color .3s ease, background-color .3s ease; display: flex; justify-content: center; align-items: center; line-height: 1; } .mp-modal-close-btn:hover { transform: rotate(90deg); color: var(--mp-accent-red); background-color: color-mix(in srgb, var(--mp-accent-red) 15%, transparent); } .prompt-menu { position: fixed; min-width: 320px; max-width: 420px; background-color: var(--mp-bg-primary); border: 1px solid var(--mp-border-primary); border-radius: var(--mp-border-radius-lg); box-shadow: var(--mp-shadow-lg); z-index: 2147483647; display: flex; flex-direction: column; user-select: none; color: var(--mp-text-primary); font-family: var(--mp-font-family-base); overflow: hidden; opacity: 0; visibility: hidden; transform: scale(.95); transform-origin: top left; transition: opacity .2s ease, transform .2s ease, visibility 0s linear .2s; } .prompt-menu.visible { opacity: 1; visibility: visible; transform: scale(1); transition-delay: 0s; } .prompt-menu-list { max-height: 220px; overflow-y: auto; padding: 4px; } .prompt-item-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-radius: var(--mp-border-radius-md); cursor: pointer; transition: background-color .15s ease-in-out; } .prompt-item-row:hover { background-color: var(--mp-bg-tertiary); } .prompt-title { font-size: 14px; font-weight: 500; flex: 1; padding-right: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--mp-text-secondary); } .prompt-item-row:hover .prompt-title { color: var(--mp-text-primary); } .prompt-actions { display: flex; align-items: center; gap: 4px; } .action-btn { background: 0 0; border: none; cursor: pointer; font-size: 13px; font-weight: 500; padding: 4px 8px; border-radius: var(--mp-border-radius-sm); transition: background-color .15s ease-in-out, color .15s ease-in-out; font-family: var(--mp-font-family-base); } .action-btn.edit { color: var(--mp-accent-yellow); } .action-btn.edit:hover { background-color: var(--mp-accent-yellow); color: var(--mp-bg-primary); } .action-btn.delete { color: var(--mp-accent-red); } .action-btn.delete:hover { background-color: var(--mp-accent-red); color: var(--mp-bg-primary); } .menu-footer, .menu-section { border-top: 1px solid var(--mp-border-primary); padding: 4px; } .menu-button { display: flex; align-items: center; justify-content: center; padding: 8px 12px; cursor: pointer; transition: background-color .15s ease-in-out; color: var(--mp-text-secondary); border-radius: var(--mp-border-radius-md); font-size: 14px; font-weight: 500; } .menu-button:hover { background-color: var(--mp-bg-tertiary); color: var(--mp-text-primary); } .menu-button svg { margin-right: 8px; } .import-export-container { display: flex; } .import-export-container .menu-button { flex: 1; } .divider { border-left: 1px solid var(--mp-border-primary); height: 24px; align-self: center; } .empty-state { padding: 24px 16px; text-align: center; color: var(--mp-text-tertiary); font-size: 14px; } .form-group { display: flex; flex-direction: column; margin-bottom: 16px; } .form-label { margin-bottom: 6px; font-size: 13px; font-weight: 500; color: var(--mp-text-secondary); } .form-input { background-color: var(--mp-bg-secondary); color: var(--mp-text-primary); border: 1px solid var(--mp-border-primary) !important; border-radius: var(--mp-border-radius-md); padding: 10px; width: 100%; box-sizing: border-box; transition: border-color .2s, box-shadow .2s; outline: 0 !important; font-family: var(--mp-font-family-base); font-size: 14px } .form-textarea { background-color: var(--mp-bg-secondary); color: var(--mp-text-primary); border: 1px solid var(--mp-border-primary) !important; border-radius: var(--mp-border-radius-md); padding: 10px; width: 100%; box-sizing: border-box; outline: 0 !important; font-family: var(--mp-font-family-base); font-size: 14px; height: 140px; resize: vertical; transition: border-color .2s, box-shadow .2s; } .form-input:focus, .form-textarea:focus { border-color: var(--mp-accent-primary) !important; box-shadow: 0 0 0 3px color-mix(in srgb, var(--mp-accent-primary) 25%, transparent) !important; } .modal-title { font-size: 18px; font-weight: 600; margin: 0 0 24px; text-align: center; color: var(--mp-text-primary); } .modal-footer { display: flex; justify-content: flex-end; } .save-button { padding: 10px 28px; border-radius: var(--mp-border-radius-md); background-color: var(--mp-accent-primary); color: #fff; border: none; font-weight: 600; cursor: pointer; transition: all .2s ease-in-out; font-family: var(--mp-font-family-base); } .save-button:hover { background-color: var(--mp-accent-primary-hover); transform: translateY(-1px); } .lang-box { width: min(90vw, 320px); } .lang-buttons-container { display: flex; flex-direction: column; gap: 12px; } @keyframes mp-fade-in-up { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .lang-button { all: unset; box-sizing: border-box; display: block; width: 100%; padding: 12px 20px; border-radius: var(--mp-border-radius-md); background-color: var(--mp-bg-secondary); color: var(--mp-text-primary); border: 1px solid var(--mp-border-primary); font-weight: 500; cursor: pointer; text-align: center; opacity: 0; animation: mp-fade-in-up .4s ease forwards; transition: transform .2s ease, box-shadow .2s ease, background-color .2s ease; font-family: var(--mp-font-family-base); } .lang-button:hover { transform: translateY(-3px); box-shadow: var(--mp-shadow-md); background-color: var(--mp-bg-tertiary); } .lang-button:active { transform: translateY(0); transition-duration: .1s; } .prompt-menu-list { scrollbar-width: thin; scrollbar-color: var(--mp-border-secondary) var(--mp-bg-tertiary); } .prompt-menu-list::-webkit-scrollbar { width: 10px !important; height: 10px !important; } .prompt-menu-list::-webkit-scrollbar-track { background-color: var(--mp-bg-tertiary) !important; border-radius: 10px !important; border: none !important; } .prompt-menu-list::-webkit-scrollbar-thumb { background-color: var(--mp-border-secondary) !important; border-radius: 10px !important; border: 2px solid var(--mp-bg-primary) !important; } .prompt-menu-list::-webkit-scrollbar-thumb:hover { background-color: var(--mp-text-tertiary) !important; } .mp-tooltip { position: fixed; z-index: 2147483647; border-radius: var(--mp-border-radius-sm); padding: 6px 12px; pointer-events: none; white-space: nowrap; font-family: var(--mp-font-family-base); font-size: 14px; font-weight: 500; background-color: var(--mp-text-primary); color: var(--mp-bg-primary); box-shadow: var(--mp-shadow-md); border: 1px solid var(--mp-bg-tertiary); 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); } .mp-tooltip.visible { opacity: 1; transform: scale(1); } `); document.head.appendChild(styleElement); } function createPromptMenu() { const menu = document.createElement('div'); menu.className = 'prompt-menu'; menu.id = 'prompt-menu-container'; return menu; } function createPromptModal() { const overlay = document.createElement('div'); overlay.className = 'mp-overlay mp-hidden'; overlay.id = '__ap_modal_overlay'; const box = document.createElement('div'); box.className = 'mp-modal-box'; box.onclick = e => e.stopPropagation(); const modalContentHTML = `