您需要先安装一个扩展,例如 篡改猴、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.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.gf.qytechs.cn/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 // ==/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, `<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M4 5h12M4 10h12M4 15h12" stroke="currentColor" stroke-width="2"/></svg>`); createCustomTooltip(btn, getTranslation('prompts')); return btn; } function createDeepSeekButton() { const btn = document.createElement('button'); 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;"><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><span class="_6dbc175">${getTranslation('prompt')}</span></span>`); 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, `<svg fill="currentColor" style="width: 24px; height: 24px;" 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>`); 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, `<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><span class="mat-icon notranslate" style="display: inline-flex; align-items: center; justify-content: center;"><svg style="width: 24px; height: 24px;" viewBox="0 0 20 20" fill="currentColor"><path d="M4 5h12M4 10h12M4 15h12" stroke="currentColor" stroke-width="2"></path></svg></span><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); 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, `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4"><path d="M4 5h12M4 10h12M4 15h12" stroke="currentColor" stroke-width="2"/></svg>`); 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 = ` <button id="__ap_close_prompt" class="mp-modal-close-btn" aria-label="${getTranslation('close')}">✕</button> <h2 class="modal-title">${getTranslation('newPrompt')}</h2> <div class="form-group"> <label for="__ap_title" class="form-label">${getTranslation('title')}</label> <input id="__ap_title" class="form-input" /> </div> <div class="form-group"> <label for="__ap_text" class="form-label">${getTranslation('text')}</label> <textarea id="__ap_text" class="form-textarea"></textarea> </div> <div class="modal-footer"> <button id="__ap_save" class="save-button">${getTranslation('save')}</button> </div> `; setSafeInnerHTML(box, modalContentHTML); overlay.appendChild(box); return overlay; } function createLanguageModal() { const overlay = document.createElement('div'); overlay.className = 'mp-overlay mp-hidden lang-overlay'; overlay.id = '__ap_lang_modal_overlay'; overlay.onclick = () => hideModal(overlay); const box = document.createElement('div'); box.className = 'mp-modal-box lang-box'; box.onclick = (e) => e.stopPropagation(); const buttonsContainer = document.createElement('div'); buttonsContainer.className = 'lang-buttons-container'; Object.keys(translations).forEach((langKey, index) => { const btn = document.createElement('button'); btn.className = 'lang-button'; btn.textContent = translations[langKey].langName; btn.style.animationDelay = `${index * 60}ms`; btn.onclick = async () => { await GM_setValue(LANG_STORAGE_KEY, langKey); window.location.reload(); }; buttonsContainer.appendChild(btn); }); box.appendChild(buttonsContainer); overlay.appendChild(box); return overlay; } function showModal(modal) { if (!modal) return; modal.classList.remove('mp-hidden'); setTimeout(() => modal.classList.add('visible'), 10); } function hideModal(modal) { if (!modal) return; modal.classList.remove('visible'); setTimeout(() => modal.classList.add('mp-hidden'), 200); } function openPromptModal(item = null, index = -1) { if (!currentModal) return; const isEditing = !!item; currentModal.dataset.index = index; currentModal.querySelector('.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 moveCursorToEnd(editor) { setTimeout(() => { try { editor.focus(); if (currentPlatform === 'gemini') { const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(editor); range.collapse(false); selection.removeAllRanges(); selection.addRange(range); editor.scrollTop = editor.scrollHeight; } else if (currentPlatform === 'chatgpt') { const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(editor); range.collapse(false); selection.removeAllRanges(); selection.addRange(range); const scrollContainer = editor.parentElement; if (scrollContainer) { scrollContainer.scrollTop = scrollContainer.scrollHeight; } } else if (currentPlatform === 'googlestudio') { const textLength = editor.value.length; editor.selectionStart = editor.selectionEnd = textLength; let scrollContainer = editor.parentElement; let i = 0; while (scrollContainer && i < 10) { const style = window.getComputedStyle(scrollContainer); if (style.overflowY === 'auto' || style.overflowY === 'scroll') { break; } scrollContainer = scrollContainer.parentElement; i++; } if (scrollContainer) { scrollContainer.scrollTop = scrollContainer.scrollHeight; } } else { const textLength = editor.value.length; editor.selectionStart = editor.selectionEnd = textLength; editor.scrollTop = editor.scrollHeight; } } catch (e) { } }, 10); } function closeMenu() { if (currentMenu && currentMenu.classList.contains('visible')) { currentMenu.classList.remove('visible'); } } 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, left; const spaceBelow = viewportHeight - btnRect.bottom - margin; const spaceAbove = btnRect.top - margin; if (spaceBelow >= menuHeight) { top = btnRect.bottom + margin; } else if (spaceAbove >= menuHeight) { top = btnRect.top - menuHeight - margin; } else { top = Math.max(margin, viewportHeight - menuHeight - margin); } const spaceRight = viewportWidth - btnRect.left - margin; const spaceLeft = btnRect.right - margin; if (spaceRight >= menuWidth) { left = btnRect.left; } else if (spaceLeft >= menuWidth) { left = btnRect.right - menuWidth; } else { left = (viewportWidth - menuWidth) / 2; } menu.style.top = `${Math.max(margin, Math.min(top, viewportHeight - menuHeight - margin))}px`; menu.style.left = `${Math.max(margin, Math.min(left, viewportWidth - menuWidth - margin))}px`; } async function refreshMenu() { if (!currentMenu) return; const listContainer = document.createElement('div'); listContainer.className = 'prompt-menu-list'; const items = await getAll(); if (!items.length) { setSafeInnerHTML(listContainer, `<div class="empty-state">${getTranslation('noSavedPrompts')}</div>`); } else { items.forEach((p, index) => { const row = document.createElement('div'); row.className = 'prompt-item-row'; const titleDiv = document.createElement('div'); titleDiv.className = 'prompt-title'; titleDiv.textContent = p.title; titleDiv.onclick = (e) => { e.stopPropagation(); insertPrompt(p, index); closeMenu(); }; const actionsDiv = document.createElement('div'); actionsDiv.className = 'prompt-actions'; const btnE = document.createElement('button'); btnE.textContent = getTranslation('edit'); btnE.className = 'action-btn edit'; btnE.onclick = (e) => { e.stopPropagation(); openPromptModal(p, index); }; const btnD = document.createElement('button'); btnD.textContent = getTranslation('delete'); btnD.className = 'action-btn delete'; btnD.onclick = (e) => { e.stopPropagation(); if (confirm(getTranslation('confirmDelete', { title: p.title }))) remove(index).then(refreshMenu); }; actionsDiv.appendChild(btnE); actionsDiv.appendChild(btnD); row.appendChild(titleDiv); row.appendChild(actionsDiv); listContainer.appendChild(row); }); } const addSection = document.createElement('div'); addSection.className = 'menu-section'; const addBtn = document.createElement('div'); addBtn.className = 'menu-button'; setSafeInnerHTML(addBtn, `<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /></svg>${getTranslation('addPrompt')}`); addBtn.onclick = (e) => { e.stopPropagation(); openPromptModal(); }; addSection.appendChild(addBtn); const footer = document.createElement('div'); footer.className = 'menu-footer'; const importExportContainer = document.createElement('div'); importExportContainer.className = 'import-export-container'; const exportBtn = document.createElement('div'); exportBtn.className = 'menu-button'; exportBtn.textContent = getTranslation('export'); exportBtn.onclick = (e) => { e.stopPropagation(); exportPrompts(); }; const importBtn = document.createElement('div'); importBtn.className = 'menu-button'; importBtn.textContent = getTranslation('import'); importBtn.onclick = (e) => { e.stopPropagation(); importPrompts(); }; const divider = document.createElement('div'); divider.className = 'divider'; importExportContainer.appendChild(exportBtn); importExportContainer.appendChild(divider); importExportContainer.appendChild(importBtn); footer.appendChild(importExportContainer); setSafeInnerHTML(currentMenu, ''); currentMenu.appendChild(listContainer); currentMenu.appendChild(addSection); currentMenu.appendChild(footer); } 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; } async function insertPrompt(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 editor = document.querySelector(platformSelectors[currentPlatform]); if (!editor) { alert(getTranslation('editorNotFound', { platform: currentPlatform })); return; } editor.focus(); setTimeout(() => { if (currentPlatform === 'gemini') { let p = editor.querySelector('p') || document.createElement('p'); setSafeInnerHTML(editor, ''); p.textContent = promptItem.text; editor.appendChild(p); editor.dispatchEvent(new Event('input', { bubbles: true, composed: true })); } else if (currentPlatform === 'chatgpt') { const isFirefox = navigator.userAgent.toLowerCase().includes('firefox'); if (isFirefox) { editor.innerHTML = ''; const p = document.createElement('p'); p.textContent = promptItem.text; editor.appendChild(p); editor.dispatchEvent(new Event('input', { bubbles: true, composed: true })); } else { const dt = new DataTransfer(); dt.setData('text/plain', promptItem.text); editor.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); } } else { const dt = new DataTransfer(); dt.setData('text/plain', promptItem.text); editor.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); if (editor.value !== promptItem.text) { const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; setter.call(editor, promptItem.text); editor.dispatchEvent(new Event('input', { bubbles: true })); } } moveCursorToEnd(editor); }, 100); let prompts = await getAll(); if (index > 0) { const item = prompts.splice(index, 1)[0]; prompts.unshift(item); await GM_setValue(PROMPT_STORAGE_KEY, prompts); } } async function exportPrompts() { const prompts = await getAll(); if(prompts.length===0){alert(getTranslation('noPromptsToExport'));return} const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([JSON.stringify(prompts,null,2)],{type:'application/json'})); a.download=getTranslation('fileName'); a.click(); URL.revokeObjectURL(a.href); closeMenu(); } function importPrompts() { const input=document.createElement('input'); input.type='file'; input.accept='.json'; input.onchange=e=>{ const file=e.target.files[0]; if(!file)return; const reader=new FileReader(); reader.onload=async event=>{ try{ const imported=JSON.parse(event.target.result); if(!Array.isArray(imported))throw new Error("Not an array."); const current=await getAll(); const newPrompts=imported.map(p=>({title:p.title||'No Title',text:p.text||''})); await GM_setValue(PROMPT_STORAGE_KEY,[...current,...newPrompts]); await refreshMenu(); alert(getTranslation('promptsImported',{count:newPrompts.length})); }catch(err){alert(getTranslation('errorImporting',{error:err.message}))} }; reader.readAsText(file); }; input.click(); closeMenu(); } 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]"]'); insertionPoint.style.display = 'flex'; insertionPoint.style.alignItems = 'center'; btn = createChatGPTButton(); elementToInsert = btn; insertionMethod = 'append'; } else if (currentPlatform === 'deepseek') { const container = await waitFor('.ec4f5d61'); insertionPoint = container.querySelector('.bf38813a'); const allButtons = Array.from(container.querySelectorAll('button')); const refBtn = allButtons.find(b => b.textContent.trim() === 'Search'); if (!refBtn || !insertionPoint) throw new Error('DeepSeek UI reference button "Search" not found.'); btn = createDeepSeekButton(); btn.className = refBtn.className; elementToInsert = btn; } else if (currentPlatform === 'googlestudio') { insertionPoint = await waitFor('ms-add-chunk-menu', 5000).then(el => el.closest('.button-wrapper')); const wrapper = document.createElement('div'); wrapper.className = 'button-wrapper'; btn = createGoogleStudioButton(); wrapper.appendChild(btn); elementToInsert = wrapper; const parent = insertionPoint.closest('.prompt-input-wrapper-container'); if (parent) parent.style.alignItems = 'center'; } else if (currentPlatform === 'qwen') { insertionPoint = await waitFor('button.websearch_button', 5000); btn = createQwenButton(); elementToInsert = btn; insertionMethod = 'after'; const buttonContainer = insertionPoint.parentElement; if (buttonContainer) { const qwenPositionObserver = new MutationObserver(() => { const myButton = buttonContainer.querySelector('button[data-testid="composer-button-prompts"]'); if (myButton && buttonContainer.lastElementChild !== myButton) { buttonContainer.appendChild(myButton); } }); qwenPositionObserver.observe(buttonContainer, { childList: true }); } } else if (currentPlatform === 'zai') { const referenceElement = await waitFor('svg path[d="M2.6499 4.48322H13.3166"]', 8000).then(p => p.closest('button')); if (!referenceElement) throw new Error('Z.ai reference element not found.'); insertionPoint = referenceElement.closest('div.flex.gap-\\[8px\\]'); if (!insertionPoint) throw new Error('Z.ai button container not found.'); btn = createZaiButton(); elementToInsert = btn; insertionMethod = 'append'; } else if (currentPlatform === 'gemini') { insertionPoint = await waitFor('uploader', 8000); btn = createGeminiButton(); elementToInsert = btn; insertionMethod = 'after'; const wrapper = insertionPoint.parentElement; if (wrapper) { wrapper.style.display = 'flex'; wrapper.style.alignItems = 'center'; wrapper.style.gap = '3px'; } } else if (currentPlatform === 'lmarena') { insertionPoint = await waitFor('div[data-sentry-component="SelectChatModality"]', 8000); btn = createLmarenaButton(); elementToInsert = btn; insertionMethod = 'append'; } if (!btn || !insertionPoint) return; currentButton = elementToInsert; const clickable = btn; if (insertionMethod === 'append') insertionPoint.appendChild(elementToInsert); else if (insertionMethod === 'before') insertionPoint.parentNode.insertBefore(elementToInsert, insertionPoint); else insertionPoint.parentNode.insertBefore(elementToInsert, insertionPoint.nextSibling); currentMenu = createPromptMenu(); currentModal = createPromptModal(); languageModal = createLanguageModal(); document.body.appendChild(currentMenu); document.body.appendChild(currentModal); document.body.appendChild(languageModal); clickable.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); const menu = currentMenu; if (menu.classList.contains('visible')) { closeMenu(); return; } refreshMenu().then(() => { positionMenu(menu, clickable); menu.classList.add('visible'); }); }); currentModal.querySelector('#__ap_save').onclick = async (e) => { e.stopPropagation(); const index = Number(currentModal.dataset.index) || -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 op = index > -1 ? update(index, { title, text }) : addItem({ title, text }); op.then(() => { hideModal(currentModal); refreshMenu(); }); }; currentModal.querySelector('#__ap_close_prompt').onclick = (e) => { e.stopPropagation(); hideModal(currentModal); }; isInitialized = true; } catch (error) { cleanup(); } finally { setupPageObserver(); } } 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 setupGlobalEventListeners() { document.addEventListener('click', ev => { if (!currentMenu || !currentButton) return; if (ev.target.closest('#prompt-menu-container, [data-testid="composer-button-prompts"]')) return; closeMenu(); }); document.addEventListener('keydown', ev => { if (ev.key === 'Escape') { closeMenu(); if (currentModal && currentModal.classList.contains('visible')) hideModal(currentModal); if (languageModal && languageModal.classList.contains('visible')) hideModal(languageModal); } }); window.addEventListener('resize', debounce(() => { if (currentMenu && currentMenu.classList.contains('visible')) { positionMenu(currentMenu, currentButton); } }, 100)); } function tryInit() { if (isInitializing) return; if (isInitialized && currentButton && document.body.contains(currentButton) && currentPlatform === detectPlatform()) { return; } isInitializing = true; initUI().finally(() => { isInitializing = false; }); } async function start() { await determineLanguage(); injectGlobalStyles(); setupGlobalEventListeners(); GM_registerMenuCommand(getTranslation('languageSettings'), () => { if (!languageModal) { languageModal = createLanguageModal(); document.body.appendChild(languageModal); } showModal(languageModal); }); tryInit(); } start(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址