您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Save, manage and insert prompts across multiple AI chatbot platforms with improved input detection, fixed visibility, dark mode support, and better insertion
// ==UserScript== // @name Multi-Platform AI Prompt Manager // @namespace http://tampermonkey.net/ // @version 2.3 // @description Save, manage and insert prompts across multiple AI chatbot platforms with improved input detection, fixed visibility, dark mode support, and better insertion // @author skyline/imwaitingnow // @license MIT // @match https://chatgpt.com/* // @match https://chat.openai.com/* // @match https://claude.ai/* // @match https://x.com/i/grok* // @match https://twitter.com/i/grok* // @match https://chat.deepseek.com/* // @match https://chat.qwen.ai/* // @match https://qwen.alibaba.com/* // @match https://tongyi.aliyun.com/* // @match https://grok.com/* // @match https://gemini.google.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // ==/UserScript== (function() { 'use strict'; const PLATFORM_CONFIG = { chatgpt: { name: 'ChatGPT', domains: ['chatgpt.com', 'chat.openai.com'], storageKey: 'chatgpt_saved_prompts', theme: { primary: '#10a37f', primaryDark: '#0e8c6a', secondary: '#f7f7f8', surface: '#ffffff', surfaceSecondary: '#f7f7f8', text: '#353740', textSecondary: '#8e8ea0', border: '#e5e5e5', borderMedium: '#d1d5db', shadow: 'rgba(0,0,0,0.1)' }, darkTheme: { primary: '#10a37f', primaryDark: '#0e8c6a', secondary: '#202123', surface: '#343541', surfaceSecondary: '#40414f', text: '#ececf1', textSecondary: '#a3a3a3', border: '#4d4d4f', borderMedium: '#5e5e60', shadow: 'rgba(0,0,0,0.1)' }, icon: '🤖' }, claude: { name: 'Claude', domains: ['claude.ai'], storageKey: 'claude_saved_prompts', theme: { primary: '#CC785C', primaryDark: '#b8654a', secondary: '#f5f5f5', surface: '#ffffff', surfaceSecondary: '#f8f8f8', text: '#1f2937', textSecondary: '#6b7280', border: '#e5e7eb', borderMedium: '#d1d5db', shadow: 'rgba(204, 120, 92, 0.15)' }, icon: '🔶' }, grok: { name: 'Grok', domains: ['x.com', 'twitter.com', 'grok.com'], storageKey: 'grok_saved_prompts', theme: { primary: '#1d9bf0', primaryDark: '#1a8cd8', secondary: '#16181c', surface: '#000000', surfaceSecondary: '#16181c', text: '#e7e9ea', textSecondary: '#71767b', border: '#2f3336', borderMedium: '#3e4348', shadow: 'rgba(29, 155, 240, 0.15)' }, icon: '🚀' }, deepseek: { name: 'DeepSeek', domains: ['chat.deepseek.com'], storageKey: 'deepseek_saved_prompts', theme: { primary: '#2563eb', primaryDark: '#1d4ed8', secondary: '#f8fafc', surface: '#ffffff', surfaceSecondary: '#f1f5f9', text: '#0f172a', textSecondary: '#64748b', border: '#e2e8f0', borderMedium: '#cbd5e1', shadow: 'rgba(37, 99, 235, 0.15)' }, icon: '🌊' }, qwen: { name: 'Qwen', domains: ['chat.qwen.ai', 'qwen.alibaba.com', 'tongyi.aliyun.com'], storageKey: 'qwen_saved_prompts', theme: { primary: '#ff6a00', primaryDark: '#e55a00', secondary: '#fef7ed', surface: '#ffffff', surfaceSecondary: '#fef2e2', text: '#1c1c1c', textSecondary: '#666666', border: '#fed7aa', borderMedium: '#fdba74', shadow: 'rgba(255, 106, 0, 0.15)' }, icon: '🔥' }, gemini: { name: 'Gemini', domains: ['gemini.google.com'], storageKey: 'gemini_saved_prompts', theme: { primary: '#4285f4', primaryDark: '#3367d6', secondary: '#f8f9fa', surface: '#ffffff', surfaceSecondary: '#f1f3f4', text: '#202124', textSecondary: '#5f6368', border: '#dadce0', borderMedium: '#bdc1c6', shadow: 'rgba(66, 133, 244, 0.15)' }, icon: '✨' } }; let currentPlatform = null; let activePlatformTab = null; let allPlatformPrompts = {}; let isToolbarVisible = false; let isToolbarMinimized = false; let lastInsertedPrompt = ''; let undoTimeout = null; let inputDetectionCache = null; let inputDetectionObserver = null; let visibilityCheckInterval = null; let uiChangeObserver = null; let isDarkMode = false; function detectPlatform() { const hostname = window.location.hostname; for (const [key, config] of Object.entries(PLATFORM_CONFIG)) { if (config.domains.some(domain => hostname.includes(domain))) { return key; } } return 'chatgpt'; } function findChatGPTInput() { const selectors = [ '[data-testid="composer-text-input"]', 'textarea[placeholder*="Message"]', 'textarea[placeholder*="Send a message"]', 'div[contenteditable="true"][data-testid*="composer"]', 'form textarea', '#prompt-textarea' ]; for (const selector of selectors) { const element = document.querySelector(selector); if (element && isElementVisible(element)) { return element; } } const textareas = document.querySelectorAll('textarea'); for (const textarea of textareas) { if (isElementVisible(textarea) && textarea.offsetHeight > 30) { return textarea; } } return null; } function findClaudeInput() { const selectors = [ '[data-testid="chat-input"]', 'div[contenteditable="true"][data-testid*="chat"]', '.ProseMirror[contenteditable="true"]', 'div[role="textbox"]', 'div[contenteditable="true"]' ]; for (const selector of selectors) { const element = document.querySelector(selector); if (element && isElementVisible(element)) { return element; } } const editables = document.querySelectorAll('[contenteditable="true"]'); for (const editable of editables) { if (isElementVisible(editable) && editable.offsetHeight > 30) { return editable; } } return null; } function findGrokInput() { const selectors = [ 'textarea[placeholder*="Ask Grok"]', 'textarea[placeholder*="Message Grok"]', 'div[contenteditable="true"][data-testid*="grok"]', 'textarea[data-testid*="text-input"]', '.composer textarea', 'div[role="textbox"]' ]; for (const selector of selectors) { const element = document.querySelector(selector); if (element && isElementVisible(element)) { return element; } } const textareas = document.querySelectorAll('textarea'); for (const textarea of textareas) { const placeholder = textarea.placeholder?.toLowerCase() || ''; if (isElementVisible(textarea) && ( placeholder.includes('ask') || placeholder.includes('message') || placeholder.includes('chat') || textarea.offsetHeight > 40 )) { return textarea; } } return null; } function findDeepSeekInput() { const selectors = [ 'textarea[placeholder*="Send a message"]', 'div[contenteditable="true"]', '.chat-input textarea', 'textarea.form-control', '[data-testid="chat-input"]', '.input-area textarea', '.message-input textarea' ]; for (const selector of selectors) { const element = document.querySelector(selector); if (element && isElementVisible(element)) { return element; } } const textareas = document.querySelectorAll('textarea'); for (const textarea of textareas) { if (isElementVisible(textarea) && textarea.offsetHeight > 30) { return textarea; } } const editables = document.querySelectorAll('[contenteditable="true"]'); for (const editable of editables) { if (isElementVisible(editable) && editable.offsetHeight > 30) { return editable; } } return null; } function findQwenInput() { const selectors = [ 'textarea[placeholder*="请输入"]', 'textarea[placeholder*="输入消息"]', 'textarea[placeholder*="Send a message"]', 'div[contenteditable="true"]', '.chat-input textarea', '[data-testid="input"]', '.input-wrapper textarea', '.message-input textarea' ]; for (const selector of selectors) { const element = document.querySelector(selector); if (element && isElementVisible(element)) { return element; } } const textareas = document.querySelectorAll('textarea'); for (const textarea of textareas) { if (isElementVisible(textarea) && textarea.offsetHeight > 30) { return textarea; } } const editables = document.querySelectorAll('[contenteditable="true"]'); for (const editable of editables) { if (isElementVisible(editable) && editable.offsetHeight > 30) { return editable; } } return null; } function findGeminiInput() { const selectors = [ 'div[contenteditable="true"][data-testid*="input"]', 'div[contenteditable="true"][role="textbox"]', 'textarea[placeholder*="Enter a prompt"]', 'textarea[placeholder*="Message Gemini"]', '.input-area div[contenteditable="true"]', 'div[contenteditable="true"]', '.message-input-container textarea' ]; for (const selector of selectors) { const element = document.querySelector(selector); if (element && isElementVisible(element)) { return element; } } const editables = document.querySelectorAll('[contenteditable="true"]'); for (const editable of editables) { if (isElementVisible(editable) && editable.offsetHeight > 30) { const parent = editable.closest('[data-testid], .input, .message, .chat'); if (parent || editable.offsetHeight > 50) { return editable; } } } const textareas = document.querySelectorAll('textarea'); for (const textarea of textareas) { if (isElementVisible(textarea) && textarea.offsetHeight > 30) { return textarea; } } return null; } function isElementVisible(element) { if (!element) return false; const rect = element.getBoundingClientRect(); const style = window.getComputedStyle(element); return rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; } function findInputForCurrentPlatform() { if (inputDetectionCache && isElementVisible(inputDetectionCache)) { return inputDetectionCache; } let input = null; switch (currentPlatform) { case 'chatgpt': input = findChatGPTInput(); break; case 'claude': input = findClaudeInput(); break; case 'grok': input = findGrokInput(); break; case 'deepseek': input = findDeepSeekInput(); break; case 'qwen': input = findQwenInput(); break; case 'gemini': input = findGeminiInput(); break; default: input = findChatGPTInput(); } if (input) { inputDetectionCache = input; } return input; } function setupInputDetectionObserver() { if (inputDetectionObserver) { inputDetectionObserver.disconnect(); } inputDetectionObserver = new MutationObserver(() => { inputDetectionCache = null; }); inputDetectionObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); } function setupUIChangeObserver() { if (uiChangeObserver) { uiChangeObserver.disconnect(); } uiChangeObserver = new MutationObserver((mutations) => { let shouldCheckVisibility = false; mutations.forEach((mutation) => { if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { const computedStyle = window.getComputedStyle(node); const zIndex = parseInt(computedStyle.zIndex) || 0; if (zIndex > 5000 || node.classList.contains('modal') || node.classList.contains('overlay') || node.classList.contains('popup')) { shouldCheckVisibility = true; } } }); } }); if (shouldCheckVisibility) { setTimeout(ensureToggleButtonVisibility, 100); } }); uiChangeObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'z-index'] }); } function ensureToggleButtonVisibility() { const toggleButton = document.getElementById('prompt-manager-toggle'); if (!toggleButton) { createToggleButton(); return; } const rect = toggleButton.getBoundingClientRect(); const isVisible = rect.width > 0 && rect.height > 0; const computedStyle = window.getComputedStyle(toggleButton); if (!isVisible || computedStyle.display === 'none' || computedStyle.visibility === 'hidden') { toggleButton.remove(); createToggleButton(); } else { toggleButton.style.setProperty('z-index', '2147483647', 'important'); toggleButton.style.setProperty('position', 'fixed', 'important'); toggleButton.style.setProperty('display', 'flex', 'important'); toggleButton.style.setProperty('visibility', 'visible', 'important'); toggleButton.style.setProperty('opacity', '1', 'important'); } } function startVisibilityCheck() { if (visibilityCheckInterval) { clearInterval(visibilityCheckInterval); } visibilityCheckInterval = setInterval(ensureToggleButtonVisibility, 3000); } function stopVisibilityCheck() { if (visibilityCheckInterval) { clearInterval(visibilityCheckInterval); visibilityCheckInterval = null; } } function insertPromptIntoInput(prompt, textInput) { lastInsertedPrompt = textInput.tagName === 'TEXTAREA' ? textInput.value : textInput.textContent; if (textInput.tagName === 'TEXTAREA') { const setValue = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; setValue.call(textInput, prompt.content); const inputEvent = new Event('input', { bubbles: true }); textInput.dispatchEvent(inputEvent); textInput.dispatchEvent(new Event('change', { bubbles: true })); } else if (textInput.contentEditable === 'true') { textInput.textContent = prompt.content; const inputEvent = new InputEvent('input', { bubbles: true, cancelable: true, inputType: 'insertText', data: prompt.content }); textInput.dispatchEvent(inputEvent); textInput.dispatchEvent(new Event('change', { bubbles: true })); } textInput.focus(); setTimeout(() => textInput.focus(), 100); } function undoLastInsertion() { const textInput = findInputForCurrentPlatform(); const undoBtn = document.getElementById('prompt-undo-btn'); if (textInput && lastInsertedPrompt !== '') { if (textInput.tagName === 'TEXTAREA') { const setValue = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; setValue.call(textInput, lastInsertedPrompt); const inputEvent = new Event('input', { bubbles: true }); textInput.dispatchEvent(inputEvent); textInput.dispatchEvent(new Event('change', { bubbles: true })); } else if (textInput.contentEditable === 'true') { textInput.textContent = lastInsertedPrompt; const inputEvent = new InputEvent('input', { bubbles: true, cancelable: true, inputType: 'insertText', data: lastInsertedPrompt }); textInput.dispatchEvent(inputEvent); textInput.dispatchEvent(new Event('change', { bubbles: true })); } textInput.focus(); if (undoBtn) undoBtn.classList.remove('visible'); lastInsertedPrompt = ''; showNotification('Insertion undone!'); } } function loadAllPrompts() { allPlatformPrompts = {}; Object.entries(PLATFORM_CONFIG).forEach(([platform, config]) => { const storageKey = config.storageKey; let storedData = GM_getValue(storageKey, null); if (storedData === null) { const localStorageData = localStorage.getItem(storageKey); if (localStorageData) { storedData = localStorageData; GM_setValue(storageKey, storedData); } else { storedData = '[]'; } } allPlatformPrompts[platform] = JSON.parse(storedData); }); } function savePromptsForPlatform(platform, prompts) { const config = PLATFORM_CONFIG[platform]; if (config) { GM_setValue(config.storageKey, JSON.stringify(prompts)); allPlatformPrompts[platform] = prompts; } } function getCurrentPrompts() { const targetPlatform = activePlatformTab || currentPlatform; return allPlatformPrompts[targetPlatform] || []; } function setCurrentPrompts(prompts) { const targetPlatform = activePlatformTab || currentPlatform; savePromptsForPlatform(targetPlatform, prompts); } function generateStyles(platform) { const theme = isDarkMode && PLATFORM_CONFIG[platform].darkTheme ? PLATFORM_CONFIG[platform].darkTheme : PLATFORM_CONFIG[platform].theme; return ` :root { --primary: ${theme.primary}; --primary-dark: ${theme.primaryDark}; --surface-primary: ${theme.surface}; --surface-secondary: ${theme.secondary}; --text-primary: ${theme.text}; --text-secondary: ${theme.textSecondary}; --border-light: ${theme.border}; --border-medium: ${theme.borderMedium}; --shadow-color: ${theme.shadow}; } #prompt-manager-toggle { position: fixed !important; top: 50% !important; right: 10px !important; transform: translateY(-50%) !important; z-index: 2147483647 !important; background: var(--surface-primary) !important; border: 1px solid var(--border-light) !important; border-radius: 8px !important; width: 40px !important; height: 40px !important; cursor: pointer !important; display: flex !important; align-items: center !important; justify-content: center !important; box-shadow: 0 2px 8px var(--shadow-color) !important; transition: all 0.2s ease !important; visibility: visible !important; opacity: 1 !important; pointer-events: auto !important; } #prompt-manager-toggle:hover { background: var(--surface-secondary) !important; box-shadow: 0 4px 12px var(--shadow-color) !important; } .hamburger { width: 18px !important; height: 12px !important; position: relative !important; } .hamburger span { display: block !important; position: absolute !important; height: 2px !important; width: 100% !important; background: var(--text-primary) !important; border-radius: 1px !important; opacity: 1 !important; left: 0 !important; transform: rotate(0deg) !important; transition: 0.25s ease-in-out !important; } .hamburger span:nth-child(1) { top: 0px !important; } .hamburger span:nth-child(2) { top: 5px !important; } .hamburger span:nth-child(3) { top: 10px !important; } #prompt-manager-toolbar { position: fixed !important; top: 0 !important; right: -380px !important; width: 380px !important; height: 100vh !important; background: var(--surface-primary) !important; border-left: 1px solid var(--border-light) !important; box-shadow: -2px 0 15px var(--shadow-color) !important; z-index: 2147483646 !important; transition: right 0.3s ease !important; display: flex !important; flex-direction: column !important; } #prompt-manager-toolbar.visible { right: 0 !important; } #prompt-manager-toolbar.minimized { width: 60px !important; } .toolbar-header { padding: 16px; border-bottom: 1px solid var(--border-light); display: flex; align-items: center; justify-content: space-between; background: var(--surface-secondary); } .toolbar-title { font-weight: 600; color: var(--text-primary); font-size: 14px; display: flex; align-items: center; gap: 8px; } .platform-indicator { font-size: 16px; } .toolbar-controls { display: flex; gap: 8px; } .toolbar-btn { background: transparent; border: 1px solid var(--border-light); border-radius: 6px; padding: 6px 8px; cursor: pointer; color: var(--text-primary); font-size: 12px; transition: all 0.2s ease; } .toolbar-btn:hover { background: var(--surface-secondary); } .platform-tabs { display: flex; background: var(--surface-secondary); border-bottom: 1px solid var(--border-light); overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; } .platform-tabs::-webkit-scrollbar { display: none; } .platform-tab { flex: 1; min-width: 60px; padding: 8px 4px; text-align: center; cursor: pointer; border: none; background: transparent; color: var(--text-secondary); font-size: 11px; transition: all 0.2s ease; white-space: nowrap; display: flex; flex-direction: column; align-items: center; gap: 2px; } .platform-tab:hover { background: var(--surface-primary); color: var(--text-primary); } .platform-tab.active { background: var(--primary); color: white; } .platform-tab-icon { font-size: 14px; } .platform-tab-name { font-size: 9px; font-weight: 500; } .prompt-list { flex: 1; overflow-y: auto; padding: 16px; } .prompt-item { margin-bottom: 12px; border: 1px solid var(--border-light); border-radius: 8px; background: var(--surface-primary); transition: all 0.2s ease; } .prompt-item:hover { border-color: var(--border-medium); box-shadow: 0 2px 4px var(--shadow-color); } .prompt-header { padding: 12px; border-bottom: 1px solid var(--border-light); display: flex; align-items: center; justify-content: space-between; } .prompt-name { font-weight: 500; color: var(--text-primary); font-size: 13px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .prompt-actions { display: flex; gap: 4px; } .action-btn { background: transparent; border: none; padding: 4px 6px; border-radius: 4px; cursor: pointer; color: var(--text-secondary); font-size: 11px; transition: all 0.2s ease; } .action-btn:hover { background: var(--surface-secondary); color: var(--text-primary); } .action-btn.danger:hover { background: #fee; color: #d32f2f; } .action-btn.clone:hover { background: var(--primary); color: white; } .prompt-preview { padding: 12px; color: var(--text-secondary); font-size: 12px; line-height: 1.4; max-height: 60px; overflow: hidden; cursor: pointer; } .prompt-preview:hover { color: var(--text-primary); } .add-prompt-form { padding: 16px; border-top: 1px solid var(--border-light); background: var(--surface-secondary); } .form-group { margin-bottom: 12px; } .form-label { display: block; margin-bottom: 4px; color: var(--text-primary); font-size: 12px; font-weight: 500; } .form-input, .form-textarea { width: 100%; padding: 8px 12px; border: 1px solid var(--border-light); border-radius: 6px; background: var(--surface-primary); color: var(--text-primary); font-size: 13px; resize: vertical; box-sizing: border-box; } .form-textarea { min-height: 80px; font-family: inherit; } .form-input:focus, .form-textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 2px rgba(var(--primary), 0.2); } .form-actions { display: flex; gap: 6px; flex-wrap: wrap; } .btn-primary { background: var(--primary); color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 11px; font-weight: 500; transition: all 0.2s ease; } .btn-primary:hover { background: var(--primary-dark); } .btn-secondary { background: transparent; color: var(--text-primary); border: 1px solid var(--border-light); padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 11px; transition: all 0.2s ease; } .btn-secondary:hover { background: var(--surface-primary); } .clone-modal { position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background: rgba(0,0,0,0.5) !important; z-index: 2147483647 !important; display: flex !important; align-items: center !important; justify-content: center !important; } .clone-modal-content { background: var(--surface-primary); border-radius: 8px; padding: 20px; max-width: 400px; width: 90%; box-shadow: 0 10px 25px rgba(0,0,0,0.2); } .clone-modal h3 { margin: 0 0 16px 0; color: var(--text-primary); font-size: 16px; } .platform-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 16px; } .platform-option { padding: 12px; border: 1px solid var(--border-light); border-radius: 6px; cursor: pointer; text-align: center; transition: all 0.2s ease; display: flex; flex-direction: column; align-items: center; gap: 4px; } .platform-option:hover { border-color: var(--primary); background: var(--surface-secondary); } .platform-option.selected { border-color: var(--primary); background: var(--primary); color: white; } .clone-actions { display: flex; gap: 8px; justify-content: flex-end; } #prompt-undo-btn { position: fixed !important; right: 20px !important; bottom: 20px !important; background: #f59e0b !important; color: white !important; border: none !important; border-radius: 20px !important; padding: 8px 16px !important; cursor: pointer !important; font-size: 12px !important; font-weight: 500 !important; box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3) !important; opacity: 0 !important; transform: translateY(20px) !important; transition: all 0.3s ease !important; z-index: 2147483645 !important; } #prompt-undo-btn.visible { opacity: 1 !important; transform: translateY(0) !important; } #prompt-undo-btn:hover { background: #d97706 !important; transform: translateY(-2px) !important; box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4) !important; } .minimized .toolbar-header, .minimized .platform-tabs, .minimized .prompt-list, .minimized .add-prompt-form { display: none; } .minimized-controls { display: none; padding: 16px 8px; flex-direction: column; gap: 8px; align-items: center; } .minimized .minimized-controls { display: flex; } .minimized-btn { width: 40px; height: 40px; border: 1px solid var(--border-light); border-radius: 6px; background: var(--surface-primary); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; } .minimized-btn:hover { background: var(--surface-secondary); } .prompt-list::-webkit-scrollbar { width: 6px; } .prompt-list::-webkit-scrollbar-track { background: var(--surface-secondary); } .prompt-list::-webkit-scrollbar-thumb { background: var(--border-medium); border-radius: 3px; } .prompt-list::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } .sync-status { position: absolute; top: 4px; right: 4px; width: 8px; height: 8px; border-radius: 50%; background: #10b981; } @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .fade-in-up { animation: fadeInUp 0.3s ease; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .syncing { animation: pulse 1s infinite; } `; } function injectStyles() { const existingStyle = document.getElementById('prompt-manager-styles'); if (existingStyle) { existingStyle.remove(); } const styleSheet = document.createElement('style'); styleSheet.id = 'prompt-manager-styles'; styleSheet.textContent = generateStyles(currentPlatform); document.head.appendChild(styleSheet); } function createToggleButton() { const existingButton = document.getElementById('prompt-manager-toggle'); if (existingButton) { existingButton.remove(); } const button = document.createElement('div'); button.id = 'prompt-manager-toggle'; button.innerHTML = ` <div class="hamburger"> <span></span> <span></span> <span></span> </div> `; button.addEventListener('click', toggleToolbar); button.style.setProperty('z-index', '2147483647', 'important'); button.style.setProperty('position', 'fixed', 'important'); button.style.setProperty('display', 'flex', 'important'); button.style.setProperty('visibility', 'visible', 'important'); button.style.setProperty('opacity', '1', 'important'); button.style.setProperty('pointer-events', 'auto', 'important'); document.body.appendChild(button); return button; } function createPlatformTabs() { const tabsContainer = document.createElement('div'); tabsContainer.className = 'platform-tabs'; Object.entries(PLATFORM_CONFIG).forEach(([key, config]) => { const tab = document.createElement('button'); tab.className = 'platform-tab'; tab.dataset.platform = key; if (key === (activePlatformTab || currentPlatform)) { tab.classList.add('active'); } tab.innerHTML = ` <div class="platform-tab-icon">${config.icon}</div> <div class="platform-tab-name">${config.name}</div> `; tab.addEventListener('click', () => switchPlatformTab(key)); tabsContainer.appendChild(tab); }); return tabsContainer; } function switchPlatformTab(platform) { activePlatformTab = platform; document.querySelectorAll('.platform-tab').forEach(tab => { tab.classList.remove('active'); if (tab.dataset.platform === platform) { tab.classList.add('active'); } }); clearForm(); renderPromptList(); } function createToolbar() { const toolbar = document.createElement('div'); toolbar.id = 'prompt-manager-toolbar'; const currentConfig = PLATFORM_CONFIG[currentPlatform]; toolbar.innerHTML = ` <div class="toolbar-header"> <div class="toolbar-title"> <span class="platform-indicator">${currentConfig.icon}</span> Multi-Platform Prompt Manager <div class="sync-status" title="Synced"></div> </div> <div class="toolbar-controls"> <button class="toolbar-btn" id="minimize-btn">−</button> <button class="toolbar-btn" id="close-btn">×</button> </div> </div> <div class="platform-tabs-container"></div> <div class="prompt-list" id="prompt-list"></div> <div class="add-prompt-form"> <div class="form-group"> <label class="form-label">Name</label> <input type="text" class="form-input" id="prompt-name-input" placeholder="Enter prompt name..."> </div> <div class="form-group"> <label class="form-label">Content</label> <textarea class="form-textarea" id="prompt-content-input" placeholder="Enter your prompt..."></textarea> </div> <div class="form-actions"> <button class="btn-primary" id="save-btn">Save</button> <button class="btn-secondary" id="clear-btn">Clear</button> <button class="btn-secondary" id="sync-btn">Sync All</button> <button class="btn-secondary" id="import-btn">Import</button> <button class="btn-secondary" id="export-btn">Export</button> </div> </div> <div class="minimized-controls"> <div class="minimized-btn" id="expand-btn" title="Expand">${currentConfig.icon}</div> <div class="minimized-btn" id="add-btn" title="Add Prompt">+</div> </div> `; document.body.appendChild(toolbar); const tabsContainer = toolbar.querySelector('.platform-tabs-container'); tabsContainer.appendChild(createPlatformTabs()); document.getElementById('minimize-btn').addEventListener('click', minimizeToolbar); document.getElementById('close-btn').addEventListener('click', toggleToolbar); document.getElementById('save-btn').addEventListener('click', savePrompt); document.getElementById('clear-btn').addEventListener('click', clearForm); document.getElementById('sync-btn').addEventListener('click', syncAllPlatforms); document.getElementById('import-btn').addEventListener('click', importPrompts); document.getElementById('export-btn').addEventListener('click', exportPrompts); document.getElementById('expand-btn').addEventListener('click', toggleToolbar); document.getElementById('add-btn').addEventListener('click', showAddForm); return toolbar; } function createUndoButton() { const existingBtn = document.getElementById('prompt-undo-btn'); if (existingBtn) { existingBtn.remove(); } const button = document.createElement('button'); button.id = 'prompt-undo-btn'; button.textContent = '↶ Undo'; button.addEventListener('click', undoLastInsertion); document.body.appendChild(button); return button; } function createCloneModal(promptIndex) { const prompt = getCurrentPrompts()[promptIndex]; const modal = document.createElement('div'); modal.className = 'clone-modal'; const platformOptions = Object.entries(PLATFORM_CONFIG).map(([key, config]) => { const currentPlatformKey = activePlatformTab || currentPlatform; const isDisabled = key === currentPlatformKey; return ` <div class="platform-option ${isDisabled ? 'disabled' : ''}" data-platform="${key}" ${isDisabled ? 'style="opacity: 0.5; cursor: not-allowed;"' : ''}> <div style="font-size: 20px;">${config.icon}</div> <div style="font-size: 12px;">${config.name}</div> ${isDisabled ? '<div style="font-size: 10px; color: var(--text-secondary);">(Current)</div>' : ''} </div> `; }).join(''); modal.innerHTML = ` <div class="clone-modal-content"> <h3>Clone "${prompt.name}" to:</h3> <div class="platform-grid"> ${platformOptions} </div> <div class="clone-actions"> <button class="btn-secondary" id="clone-cancel">Cancel</button> <button class="btn-primary" id="clone-confirm" disabled>Clone</button> </div> </div> `; document.body.appendChild(modal); let selectedPlatforms = []; modal.querySelectorAll('.platform-option:not(.disabled)').forEach(option => { option.addEventListener('click', () => { const platform = option.dataset.platform; if (selectedPlatforms.includes(platform)) { selectedPlatforms = selectedPlatforms.filter(p => p !== platform); option.classList.remove('selected'); } else { selectedPlatforms.push(platform); option.classList.add('selected'); } document.getElementById('clone-confirm').disabled = selectedPlatforms.length === 0; }); }); document.getElementById('clone-confirm').addEventListener('click', () => { selectedPlatforms.forEach(platform => { const targetPrompts = allPlatformPrompts[platform] || []; const clonedPrompt = { ...prompt, name: `${prompt.name} (from ${PLATFORM_CONFIG[activePlatformTab || currentPlatform].name})` }; const existingIndex = targetPrompts.findIndex(p => p.name === clonedPrompt.name); if (existingIndex >= 0) { if (confirm(`A prompt named "${clonedPrompt.name}" already exists in ${PLATFORM_CONFIG[platform].name}. Replace it?`)) { targetPrompts[existingIndex] = clonedPrompt; } } else { targetPrompts.push(clonedPrompt); } savePromptsForPlatform(platform, targetPrompts); }); document.body.removeChild(modal); showNotification(`Prompt cloned to ${selectedPlatforms.length} platform(s)!`); }); document.getElementById('clone-cancel').addEventListener('click', () => { document.body.removeChild(modal); }); modal.addEventListener('click', (e) => { if (e.target === modal) { document.body.removeChild(modal); } }); } function showNotification(message, type = 'success') { const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${type === 'success' ? '#10b981' : '#ef4444'}; color: white; padding: 12px 16px; border-radius: 6px; font-size: 13px; z-index: 2147483647; opacity: 0; transform: translateY(-20px); transition: all 0.3s ease; `; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '1'; notification.style.transform = 'translateY(0)'; }, 100); setTimeout(() => { notification.style.opacity = '0'; notification.style.transform = 'translateY(-20px)'; setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 300); }, 3000); } function toggleToolbar() { const toolbar = document.getElementById('prompt-manager-toolbar'); isToolbarVisible = !isToolbarVisible; if (isToolbarVisible) { toolbar.classList.add('visible'); toolbar.classList.remove('minimized'); isToolbarMinimized = false; renderPromptList(); } else { toolbar.classList.remove('visible'); } } function minimizeToolbar() { const toolbar = document.getElementById('prompt-manager-toolbar'); isToolbarMinimized = !isToolbarMinimized; if (isToolbarMinimized) { toolbar.classList.add('minimized'); } else { toolbar.classList.remove('minimized'); } } function renderPromptList() { const list = document.getElementById('prompt-list'); if (!list) return; const prompts = getCurrentPrompts(); list.innerHTML = ''; if (prompts.length === 0) { const emptyState = document.createElement('div'); emptyState.style.cssText = ` text-align: center; padding: 40px 20px; color: var(--text-secondary); font-size: 13px; `; const activePlatformName = PLATFORM_CONFIG[activePlatformTab || currentPlatform].name; const isCurrentPlatform = (activePlatformTab || currentPlatform) === currentPlatform; emptyState.innerHTML = ` <div style="font-size: 32px; margin-bottom: 12px;">${PLATFORM_CONFIG[activePlatformTab || currentPlatform].icon}</div> <div>No prompts saved for ${activePlatformName}</div> <div style="margin-top: 8px; font-size: 11px;"> ${isCurrentPlatform ? 'Create your first prompt below!' : `Switch to other platform tabs to view their prompts!<br/>You can use prompts from any platform on ${PLATFORM_CONFIG[currentPlatform].name}.` } </div> `; list.appendChild(emptyState); return; } prompts.forEach((prompt, index) => { const item = document.createElement('div'); item.className = 'prompt-item fade-in-up'; const itemHeader = document.createElement('div'); itemHeader.className = 'prompt-header'; const promptName = document.createElement('div'); promptName.className = 'prompt-name'; promptName.textContent = prompt.name; promptName.title = prompt.name; const promptActions = document.createElement('div'); promptActions.className = 'prompt-actions'; const editBtn = document.createElement('button'); editBtn.className = 'action-btn'; editBtn.textContent = 'Edit'; editBtn.addEventListener('click', () => editPrompt(index)); const cloneBtn = document.createElement('button'); cloneBtn.className = 'action-btn clone'; cloneBtn.textContent = 'Clone'; cloneBtn.addEventListener('click', () => createCloneModal(index)); const deleteBtn = document.createElement('button'); deleteBtn.className = 'action-btn danger'; deleteBtn.textContent = 'Delete'; deleteBtn.addEventListener('click', () => deletePrompt(index)); promptActions.appendChild(editBtn); promptActions.appendChild(cloneBtn); promptActions.appendChild(deleteBtn); itemHeader.appendChild(promptName); itemHeader.appendChild(promptActions); const promptPreview = document.createElement('div'); promptPreview.className = 'prompt-preview'; promptPreview.textContent = prompt.content.substring(0, 120) + (prompt.content.length > 120 ? '...' : ''); promptPreview.addEventListener('click', () => insertPrompt(index)); item.appendChild(itemHeader); item.appendChild(promptPreview); list.appendChild(item); }); } function savePrompt() { const nameInput = document.getElementById('prompt-name-input'); const contentInput = document.getElementById('prompt-content-input'); const name = nameInput.value.trim(); const content = contentInput.value.trim(); if (!name || !content) { showNotification('Please enter both name and content for the prompt.', 'error'); return; } const prompts = getCurrentPrompts(); const editIndex = nameInput.dataset.editIndex; if (editIndex !== undefined) { prompts[parseInt(editIndex)] = { name, content }; delete nameInput.dataset.editIndex; showNotification('Prompt updated successfully!'); } else { if (prompts.some(p => p.name === name)) { if (!confirm(`A prompt named "${name}" already exists. Replace it?`)) { return; } const existingIndex = prompts.findIndex(p => p.name === name); prompts[existingIndex] = { name, content }; } else { prompts.push({ name, content }); } showNotification('Prompt saved successfully!'); } setCurrentPrompts(prompts); clearForm(); renderPromptList(); } function editPrompt(index) { const prompts = getCurrentPrompts(); const prompt = prompts[index]; const nameInput = document.getElementById('prompt-name-input'); const contentInput = document.getElementById('prompt-content-input'); nameInput.value = prompt.name; contentInput.value = prompt.content; nameInput.dataset.editIndex = index; nameInput.focus(); nameInput.scrollIntoView({ behavior: 'smooth', block: 'center' }); } function deletePrompt(index) { const prompts = getCurrentPrompts(); const prompt = prompts[index]; if (confirm(`Are you sure you want to delete the prompt "${prompt.name}"?`)) { prompts.splice(index, 1); setCurrentPrompts(prompts); renderPromptList(); showNotification('Prompt deleted successfully!'); } } function insertPrompt(index) { const prompts = getCurrentPrompts(); const prompt = prompts[index]; const textInput = findInputForCurrentPlatform(); if (textInput) { insertPromptIntoInput(prompt, textInput); showUndoButton(); const sourcePlatform = activePlatformTab || currentPlatform; const sourceConfig = PLATFORM_CONFIG[sourcePlatform]; const currentConfig = PLATFORM_CONFIG[currentPlatform]; if (sourcePlatform !== currentPlatform) { showNotification(`Prompt "${prompt.name}" from ${sourceConfig.name} inserted into ${currentConfig.name}!`); } else { showNotification(`Prompt "${prompt.name}" inserted!`); } } else { showNotification(`Could not find input field. Please try refreshing the page.`, 'error'); } } function showUndoButton() { const undoBtn = document.getElementById('prompt-undo-btn'); if (undoBtn) { undoBtn.classList.add('visible'); if (undoTimeout) { clearTimeout(undoTimeout); } undoTimeout = setTimeout(() => { undoBtn.classList.remove('visible'); }, 8000); } } function clearForm() { const nameInput = document.getElementById('prompt-name-input'); const contentInput = document.getElementById('prompt-content-input'); nameInput.value = ''; contentInput.value = ''; delete nameInput.dataset.editIndex; } function syncAllPlatforms() { const syncBtn = document.getElementById('sync-btn'); const originalText = syncBtn.textContent; syncBtn.textContent = 'Syncing...'; syncBtn.disabled = true; syncBtn.classList.add('syncing'); setTimeout(() => { const syncData = { version: '2.3', timestamp: new Date().toISOString(), platforms: allPlatformPrompts, metadata: { totalPrompts: Object.values(allPlatformPrompts).reduce((sum, prompts) => sum + prompts.length, 0), platformsWithData: Object.entries(allPlatformPrompts).filter(([_, prompts]) => prompts.length > 0).map(([platform, _]) => platform) } }; GM_setValue('prompt_manager_sync_backup', JSON.stringify(syncData)); Object.entries(allPlatformPrompts).forEach(([platform, prompts]) => { savePromptsForPlatform(platform, prompts); }); syncBtn.textContent = originalText; syncBtn.disabled = false; syncBtn.classList.remove('syncing'); showNotification('All platforms synced successfully!'); }, 1500); } function importPrompts() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = function(event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { try { const importedData = JSON.parse(e.target.result); if (Array.isArray(importedData)) { const currentPrompts = getCurrentPrompts(); const mergedPrompts = [...currentPrompts, ...importedData]; setCurrentPrompts(mergedPrompts); showNotification(`Successfully imported ${importedData.length} prompts!`); } else if (importedData.platforms) { let totalImported = 0; Object.entries(importedData.platforms).forEach(([platform, prompts]) => { if (PLATFORM_CONFIG[platform] && Array.isArray(prompts)) { const existingPrompts = allPlatformPrompts[platform] || []; const mergedPrompts = [...existingPrompts, ...prompts]; savePromptsForPlatform(platform, mergedPrompts); totalImported += prompts.length; } }); showNotification(`Successfully imported ${totalImported} prompts across platforms!`); } else { showNotification('Invalid file format. Please select a valid JSON file.', 'error'); } renderPromptList(); } catch (error) { showNotification('Error reading file. Please make sure it\'s a valid JSON file.', 'error'); } }; reader.readAsText(file); } }; input.click(); } function exportPrompts() { if (Object.values(allPlatformPrompts).every(prompts => prompts.length === 0)) { showNotification('No prompts to export!', 'error'); return; } const exportData = { version: '2.3', timestamp: new Date().toISOString(), currentPlatform: currentPlatform, activePlatformTab: activePlatformTab, platforms: allPlatformPrompts, metadata: { totalPrompts: Object.values(allPlatformPrompts).reduce((sum, prompts) => sum + prompts.length, 0), platformsWithData: Object.entries(allPlatformPrompts).filter(([_, prompts]) => prompts.length > 0).map(([platform, _]) => platform), exportedFrom: 'GM_Storage' } }; const dataStr = JSON.stringify(exportData, null, 2); const dataBlob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = `multi-platform-prompts-${new Date().toISOString().split('T')[0]}.json`; link.click(); URL.revokeObjectURL(url); showNotification('Prompts exported successfully!'); } function showAddForm() { if (isToolbarMinimized) { minimizeToolbar(); } const nameInput = document.getElementById('prompt-name-input'); if (nameInput) { nameInput.focus(); nameInput.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } function handlePageChange() { inputDetectionCache = null; setTimeout(() => { ensureToggleButtonVisibility(); if (!document.getElementById('prompt-undo-btn')) { createUndoButton(); } }, 2000); } function init() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); return; } currentPlatform = detectPlatform(); activePlatformTab = currentPlatform; isDarkMode = document.documentElement.classList.contains('dark') || document.body.classList.contains('dark'); loadAllPrompts(); injectStyles(); createToggleButton(); createToolbar(); setupInputDetectionObserver(); setupUIChangeObserver(); startVisibilityCheck(); setTimeout(() => { createUndoButton(); renderPromptList(); ensureToggleButtonVisibility(); }, 2000); let lastUrl = location.href; new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; handlePageChange(); } }).observe(document, { subtree: true, childList: true }); if (currentPlatform === 'chatgpt') { setTimeout(() => { ensureToggleButtonVisibility(); if (!document.getElementById('prompt-undo-btn')) { createUndoButton(); } }, 5000); setTimeout(() => { ensureToggleButtonVisibility(); }, 10000); } window.addEventListener('beforeunload', () => { stopVisibilityCheck(); if (inputDetectionObserver) { inputDetectionObserver.disconnect(); } if (uiChangeObserver) { uiChangeObserver.disconnect(); } }); } init(); })();