// ==UserScript== // @name AI Prompt Saver - Minimal // @namespace http://tampermonkey.net/ // @version 6.5 // @description Simple prompt saver for AI chats with fixed edit functionality // @author You // @match https://chat.openai.com/* // @match https://claude.ai/* // @match https://chat.z.ai/* // @match https://gemini.google.com/app/* // @match https://grok.com/* // @match https://replit.com/* // @match https://huggingface.co/chat/* // @match https://www.perplexity.ai/* // @match https://stansa.ai/* // @match https://poe.com/* // @match https://chatgpt.com/* // @match https://chat.deepseek.com/* // @match https://chat.qwen.ai/* // @match https://huggingface.co/chat/* // @match https://chat.z.ai/ // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/563314/AI%20Prompt%20Saver%20-%20Minimal.user.js // @updateURL https://update.greasyfork.icu/scripts/563314/AI%20Prompt%20Saver%20-%20Minimal.meta.js // ==/UserScript== (function() { 'use strict'; // Storage const STORAGE_KEY = 'aiPromptSaverData'; let prompts = []; let isVisible = false; let currentFilter = 'all'; let searchTerm = ''; let editingId = null; // Categories const categories = [ { id: 'general', name: 'General', color: '#4a7fff' }, { id: 'coding', name: 'Coding', color: '#27ae60' }, { id: 'writing', name: 'Writing', color: '#e74c3c' }, { id: 'research', name: 'Research', color: '#f39c12' }, { id: 'creative', name: 'Creative', color: '#9b59b6' } ]; // CSS GM_addStyle(` #prompt-saver { position: fixed; top: 20px; right: 20px; width: 380px; max-height: 80vh; background: #1a1a1a; color: #fff; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); z-index: 10000; display: none; flex-direction: column; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; overflow: hidden; } #prompt-saver.show { display: flex; } .saver-header { padding: 16px 20px; background: linear-gradient(135deg, #4a7fff, #3a6ce5); display: flex; justify-content: space-between; align-items: center; } .saver-title { font-size: 18px; font-weight: 600; display: flex; align-items: center; gap: 8px; } .saver-close { width: 32px; height: 32px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 6px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s; } .saver-close:hover { background: rgba(255,255,255,0.3); } .saver-content { padding: 20px; overflow-y: auto; flex: 1; } .saver-search { width: 100%; padding: 10px 14px; border: 1px solid #333; border-radius: 8px; background: #2d2d2d; color: #fff; font-size: 14px; margin-bottom: 16px; } .saver-search:focus { outline: none; border-color: #4a7fff; } .saver-categories { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; } .category-btn { padding: 6px 12px; border: 1px solid #333; border-radius: 20px; background: #2d2d2d; color: #ccc; font-size: 12px; cursor: pointer; transition: all 0.2s; } .category-btn:hover { background: #333; } .category-btn.active { background: #4a7fff; color: white; border-color: #4a7fff; } .saver-prompts { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; } .prompt-item { background: #2d2d2d; border: 1px solid #333; border-radius: 8px; padding: 12px; cursor: pointer; transition: all 0.2s; position: relative; } .prompt-item:hover { background: #333; border-color: #4a7fff; transform: translateY(-2px); } .prompt-item.editing { border-color: #f39c12; background: #3a3a2d; } .prompt-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .prompt-title { font-weight: 600; font-size: 14px; flex: 1; word-wrap: break-word; } .prompt-category { padding: 2px 8px; border-radius: 10px; font-size: 10px; color: white; margin-left: 8px; white-space: nowrap; } .prompt-text { font-size: 13px; color: #ccc; margin-bottom: 8px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.4; } .prompt-text.editing { -webkit-line-clamp: unset; display: block; } .prompt-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; } .prompt-tag { padding: 2px 6px; background: #1a1a1a; border-radius: 4px; font-size: 10px; color: #999; } .prompt-actions { display: flex; gap: 6px; flex-wrap: wrap; } .prompt-btn { padding: 4px 8px; border: none; border-radius: 4px; font-size: 11px; font-weight: 500; cursor: pointer; transition: all 0.2s; flex: 1; min-width: 50px; text-align: center; } .prompt-btn:hover { transform: translateY(-1px); filter: brightness(1.1); } .insert-btn { background: #27ae60; color: white; } .copy-btn { background: #f39c12; color: white; } .edit-btn { background: #3498db; color: white; } .save-btn { background: #27ae60; color: white; } .cancel-btn { background: #95a5a6; color: white; } .delete-btn { background: #e74c3c; color: white; } .edit-form { margin-top: 8px; padding-top: 8px; border-top: 1px solid #444; } .edit-input, .edit-textarea, .edit-select { width: 100%; padding: 6px 10px; border: 1px solid #444; border-radius: 4px; background: #1a1a1a; color: #fff; font-size: 12px; margin-bottom: 6px; } .edit-textarea { min-height: 60px; resize: vertical; } .edit-row { display: flex; gap: 6px; margin-bottom: 6px; } .saver-form { background: #2d2d2d; border-radius: 8px; padding: 16px; border: 1px solid #333; } .form-group { margin-bottom: 12px; } .form-label { display: block; margin-bottom: 4px; font-size: 13px; font-weight: 500; } .form-input, .form-textarea, .form-select { width: 100%; padding: 8px 12px; border: 1px solid #333; border-radius: 6px; background: #1a1a1a; color: #fff; font-size: 13px; } .form-textarea { min-height: 80px; resize: vertical; } .form-row { display: flex; gap: 10px; } .form-col { flex: 1; } .save-btn-main { width: 100%; padding: 10px; background: #4a7fff; color: white; border: none; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; transition: background 0.2s; } .save-btn-main:hover { background: #3a6ce5; } .saver-toggle { position: fixed; top: 20px; right: 20px; width: 48px; height: 48px; background: #4a7fff; color: white; border: none; border-radius: 50%; cursor: pointer; z-index: 10001; box-shadow: 0 4px 12px rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; font-size: 20px; transition: all 0.2s; } .saver-toggle:hover { background: #3a6ce5; transform: scale(1.05); } .empty-state { text-align: center; padding: 40px 20px; color: #666; } .empty-icon { font-size: 48px; margin-bottom: 12px; opacity: 0.3; } .empty-text { font-size: 16px; margin-bottom: 4px; } .empty-subtext { font-size: 13px; } .notification { position: fixed; top: 20px; left: 50%; transform: translateX(-50%) translateY(-20px); background: #2d2d2d; color: white; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 10002; opacity: 0; transition: all 0.3s; border-left: 4px solid #4a7fff; max-width: 400px; } .notification.show { transform: translateX(-50%) translateY(0); opacity: 1; } .notification.error { border-left-color: #e74c3c; } .notification.warning { border-left-color: #f39c12; } .notification.success { border-left-color: #27ae60; } .copy-fallback { position: fixed; bottom: 20px; right: 20px; background: #f39c12; color: white; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 10001; display: none; align-items: center; gap: 10px; animation: slideUp 0.3s ease-out; } .copy-fallback.show { display: flex; } .copy-fallback-text { font-size: 14px; } .copy-fallback-btn { padding: 6px 12px; background: rgba(255,255,255,0.2); border: 1px solid rgba(255,255,255,0.3); border-radius: 4px; color: white; font-size: 12px; cursor: pointer; transition: background 0.2s; } .copy-fallback-btn:hover { background: rgba(255,255,255,0.3); } @keyframes slideUp { from { transform: translateY(100px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } `); // Load data function loadData() { const data = GM_getValue(STORAGE_KEY); if (data) { try { prompts = JSON.parse(data); } catch (e) { prompts = []; } } } // Save data function saveData() { GM_setValue(STORAGE_KEY, JSON.stringify(prompts)); } // Find AI input with comprehensive detection function findAIInput() { const hostname = window.location.hostname; // Comprehensive selectors for each platform const selectors = { 'chat.openai.com': [ '#prompt-textarea', 'textarea[data-id="prompt-textarea"]', 'textarea[id*="prompt"]', 'textarea[placeholder*="Message"]', 'textarea:not([readonly])' ], 'claude.ai': [ 'div[contenteditable="true"][data-message-author-role="user"]', 'div.ProseMirror', 'div[contenteditable="true"]', 'textarea' ], 'gemini.google.com': [ 'textarea[placeholder*="Enter a prompt"]', 'textarea[aria-label*="Enter prompt"]', 'textarea[placeholder*="prompt"]', 'textarea' ], 'www.perplexity.ai': [ 'textarea[aria-label*="Message"]', 'textarea[placeholder*="Ask anything"]', 'textarea[placeholder*="Ask"]', 'textarea[placeholder*="message"]', 'textarea', 'div[contenteditable="true"]', '[data-testid="prose-mirror-editor"]', '.ProseMirror', '[class*="editor"] textarea', '[class*="input"] textarea', 'div[role="textbox"]', '[data-test-id="prose-mirror-editor"]' ], 'poe.com': [ 'div[contenteditable="true"][data-id="root"]', 'div[contenteditable="true"]', 'textarea' ], 'chat.qwen.ai': [ 'textarea[placeholder*="请输入"]', 'textarea[placeholder*="Enter"]', 'textarea' ] }; // Get selectors for current site const siteSelectors = selectors[hostname] || ['textarea', 'div[contenteditable="true"]', 'div[role="textbox"]']; // Try each selector for (const selector of siteSelectors) { const elements = document.querySelectorAll(selector); for (const element of elements) { if (isValidInput(element)) { return element; } } } // Last resort: find any visible input const allInputs = document.querySelectorAll('textarea, div[contenteditable="true"], div[role="textbox"]'); for (const element of allInputs) { if (isValidInput(element)) { return element; } } return null; } // Check if element is a valid input function isValidInput(element) { // Skip our own elements if (element.closest('#prompt-saver')) return false; // Check visibility const style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { return false; } // Check size const rect = element.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) { return false; } // Must be reasonably sized if (rect.width < 100 || rect.height < 30) { return false; } // Additional validation for Perplexity if (window.location.hostname === 'www.perplexity.ai') { // Check if it's in the main chat area const chatArea = element.closest('[class*="chat"]') || element.closest('[class*="conversation"]') || element.closest('[class*="message"]') || element.closest('[class*="prose"]') || element.closest('[class*="editor"]') || element.closest('[class*="input"]'); if (!chatArea) { // Check if it's visible in viewport const isInViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth; if (!isInViewport) return false; } } return true; } // Robust text insertion with multiple methods function insertText(text, promptId = null) { const input = findAIInput(); if (!input) { showNotification('Could not find AI input. Please try again.', 'error'); return false; } try { // Focus the input first input.focus(); // Try multiple insertion methods setTimeout(() => { let success = false; // Method 1: Direct value/innerText assignment if (input.tagName.toLowerCase() === 'textarea') { success = insertIntoTextarea(input, text); } else if (input.contentEditable === 'true' || input.getAttribute('role') === 'textbox') { success = insertIntoContentEditable(input, text); } if (success) { showNotification('Prompt inserted', 'success'); // Update usage stats if (promptId) { const prompt = prompts.find(p => p.id === promptId); if (prompt) { prompt.lastUsed = new Date().toISOString(); prompt.usageCount++; saveData(); renderPrompts(); } } } else { // Show copy fallback showCopyFallback(text); showNotification('Failed to insert. Click "Copy" to copy manually.', 'warning'); } }, 100); return true; } catch (e) { console.error('Error inserting text:', e); showCopyFallback(text); showNotification('Failed to insert. Click "Copy" to copy manually.', 'error'); return false; } } // Insert into textarea with React support function insertIntoTextarea(textarea, text) { try { // Store original value const originalValue = textarea.value; // Get the native value setter descriptor const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; // Call the native setter nativeInputValueSetter.call(textarea, text); // Set cursor position textarea.setSelectionRange(text.length, text.length); // Create and dispatch events const event = new Event('input', { bubbles: true }); textarea.dispatchEvent(event); // Trigger React's internal update const reactEvent = new Event('input', { bubbles: true }); reactEvent.simulated = true; // Handle React's value tracker if (textarea._valueTracker) { textarea._valueTracker.setValue(originalValue); textarea._valueTracker.setValue(text); } textarea.dispatchEvent(reactEvent); textarea.dispatchEvent(new Event('change', { bubbles: true })); // Additional events for some frameworks setTimeout(() => { textarea.dispatchEvent(new Event('input', { bubbles: true })); }, 10); // Verify insertion worked setTimeout(() => { if (textarea.value !== text) { return false; } }, 50); return true; } catch (e) { console.error('Textarea insertion error:', e); return false; } } // Insert into contenteditable function insertIntoContentEditable(element, text) { try { // Clear existing content element.innerHTML = ''; element.focus(); // Create text node const textNode = document.createTextNode(text); element.appendChild(textNode); // Set cursor to end const range = document.createRange(); range.selectNodeContents(element); range.collapse(false); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); // Dispatch events element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('DOMSubtreeModified', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); // Verify insertion worked setTimeout(() => { if (element.textContent !== text) { return false; } }, 50); return true; } catch (e) { console.error('ContentEditable insertion error:', e); return false; } } // Show copy fallback UI function showCopyFallback(text) { // Remove existing fallback if any const existing = document.querySelector('.copy-fallback'); if (existing) { existing.remove(); } // Create fallback UI const fallback = document.createElement('div'); fallback.className = 'copy-fallback'; fallback.innerHTML = ` Insertion failed - copy manually? `; document.body.appendChild(fallback); // Show with animation setTimeout(() => fallback.classList.add('show'), 10); // Add copy functionality fallback.querySelector('.copy-fallback-btn').addEventListener('click', () => { navigator.clipboard.writeText(text).then(() => { showNotification('Copied to clipboard! Paste in chat.', 'success'); fallback.remove(); }).catch(() => { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); showNotification('Copied to clipboard! Paste in chat.', 'success'); fallback.remove(); }); }); // Auto-hide after 10 seconds setTimeout(() => { if (document.body.contains(fallback)) { fallback.remove(); } }, 10000); } // Show notification function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => notification.classList.add('show'), 10); setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 300); }, 3000); } // Add prompt function addPrompt(title, text, tags, category) { const prompt = { id: Date.now(), title: title || 'Untitled', text: text, tags: tags ? tags.split(',').map(t => t.trim()).filter(t => t) : [], category: category || 'general', created: new Date().toISOString(), lastUsed: new Date().toISOString(), usageCount: 0 }; prompts.unshift(prompt); saveData(); renderPrompts(); showNotification('Prompt saved', 'success'); } // Update prompt function updatePrompt(id, title, text, tags, category) { const prompt = prompts.find(p => p.id === id); if (prompt) { prompt.title = title || 'Untitled'; prompt.text = text; prompt.tags = tags ? tags.split(',').map(t => t.trim()).filter(t => t) : []; prompt.category = category || 'general'; saveData(); renderPrompts(); showNotification('Prompt updated', 'success'); } } // Delete prompt function deletePrompt(id) { prompts = prompts.filter(p => p.id !== id); saveData(); renderPrompts(); showNotification('Prompt deleted', 'info'); } // Use prompt function usePrompt(id) { const prompt = prompts.find(p => p.id === id); if (prompt) { insertText(prompt.text, id); } } // Copy prompt text function copyPrompt(id) { const prompt = prompts.find(p => p.id === id); if (prompt) { navigator.clipboard.writeText(prompt.text).then(() => { showNotification('Prompt copied to clipboard', 'success'); }).catch(() => { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = prompt.text; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); showNotification('Prompt copied to clipboard', 'success'); }); } } // Start editing prompt function startEditing(id) { editingId = id; renderPrompts(); } // Cancel editing function cancelEditing() { editingId = null; renderPrompts(); } // Save edited prompt function saveEditedPrompt(id) { const titleInput = document.getElementById(`edit-title-${id}`); const textInput = document.getElementById(`edit-text-${id}`); const tagsInput = document.getElementById(`edit-tags-${id}`); const categoryInput = document.getElementById(`edit-category-${id}`); if (titleInput && textInput) { const title = titleInput.value.trim(); const text = textInput.value.trim(); const tags = tagsInput.value.trim(); const category = categoryInput.value; if (!text) { showNotification('Prompt text cannot be empty', 'error'); return; } updatePrompt(id, title, text, tags, category); editingId = null; } } // Render prompts function renderPrompts() { const container = document.getElementById('prompts-container'); if (!container) return; let filtered = prompts; if (currentFilter !== 'all') { filtered = filtered.filter(p => p.category === currentFilter); } if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(p => p.title.toLowerCase().includes(term) || p.text.toLowerCase().includes(term) || p.tags.some(t => t.toLowerCase().includes(term)) ); } filtered.sort((a, b) => new Date(b.lastUsed) - new Date(a.lastUsed)); if (filtered.length === 0) { container.innerHTML = `
📝
No prompts found
Save your first prompt below
`; return; } container.innerHTML = filtered.map(prompt => { const category = categories.find(c => c.id === prompt.category) || categories[0]; const isEditing = editingId === prompt.id; if (isEditing) { return `
`; } else { return `
${prompt.title}
${category.name}
${prompt.text}
${prompt.tags.map(tag => `${tag}`).join('')}
`; } }).join(''); // Add event listeners using event delegation container.addEventListener('click', (e) => { const target = e.target; // Handle save button if (target.classList.contains('save-btn') && target.dataset.action === 'save') { e.stopPropagation(); const id = parseInt(target.dataset.id); saveEditedPrompt(id); } // Handle cancel button else if (target.classList.contains('cancel-btn') && target.dataset.action === 'cancel') { e.stopPropagation(); cancelEditing(); } // Handle other buttons else if (target.classList.contains('insert-btn')) { e.stopPropagation(); usePrompt(parseInt(target.dataset.id)); } else if (target.classList.contains('copy-btn')) { e.stopPropagation(); copyPrompt(parseInt(target.dataset.id)); } else if (target.classList.contains('edit-btn')) { e.stopPropagation(); startEditing(parseInt(target.dataset.id)); } else if (target.classList.contains('delete-btn')) { e.stopPropagation(); if (confirm('Delete this prompt?')) { deletePrompt(parseInt(target.dataset.id)); } } // Handle prompt item click (when not editing) else if (target.classList.contains('prompt-item') && !target.classList.contains('editing')) { const id = parseInt(target.dataset.id); usePrompt(id); } }); } // Update category filters function updateCategories() { const container = document.getElementById('categories-container'); if (!container) return; container.innerHTML = ` ${categories.map(cat => ` `).join('')} `; container.querySelectorAll('.category-btn').forEach(btn => { btn.addEventListener('click', () => { currentFilter = btn.dataset.category; renderPrompts(); updateCategories(); }); }); } // Create UI function createUI() { // Main container const saver = document.createElement('div'); saver.id = 'prompt-saver'; saver.innerHTML = `
📝 Prompt Saver
`; document.body.appendChild(saver); // Toggle button const toggle = document.createElement('button'); toggle.className = 'saver-toggle'; toggle.innerHTML = '💬'; toggle.title = 'Toggle Prompt Saver'; document.body.appendChild(toggle); // Event listeners toggle.addEventListener('click', () => { isVisible = !isVisible; saver.classList.toggle('show', isVisible); }); saver.querySelector('.saver-close').addEventListener('click', () => { isVisible = false; saver.classList.remove('show'); }); document.getElementById('search-input').addEventListener('input', (e) => { searchTerm = e.target.value; renderPrompts(); }); document.getElementById('save-btn').addEventListener('click', () => { const title = document.getElementById('prompt-title').value.trim(); const text = document.getElementById('prompt-text').value.trim(); const tags = document.getElementById('prompt-tags').value.trim(); const category = document.getElementById('prompt-category').value; if (!text) { showNotification('Please enter a prompt', 'error'); return; } addPrompt(title, text, tags, category); // Reset form document.getElementById('prompt-title').value = ''; document.getElementById('prompt-text').value = ''; document.getElementById('prompt-tags').value = ''; document.getElementById('prompt-category').value = 'general'; }); } // Initialize function init() { loadData(); createUI(); renderPrompts(); updateCategories(); } // Start if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();