Promptimizer

AI-powered prompt optimization tool that works with OpenAI-compatible APIs

As of 2025-02-24. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Promptimizer
// @namespace    http://tampermonkey.net/
// @version      1.0
// @license      MIT
// @description  AI-powered prompt optimization tool that works with OpenAI-compatible APIs
// @author       NoahTheGinger
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // Styles for the UI
    GM_addStyle(`
        #promptimizer-container {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 400px;
            background: #ffffff;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.3);
            z-index: 2147483647;
            font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            display: none;
            color: #000000;
            transition: width 0.2s ease, height 0.2s ease;
            overflow: hidden;
            min-height: 300px;
            min-width: 300px;
        }

        #promptimizer-header {
            padding: 12px;
            background: #2196F3;
            border-radius: 8px 8px 0 0;
            cursor: move;
            display: flex;
            justify-content: space-between;
            align-items: center;
            color: #ffffff;
        }

        #promptimizer-header-controls {
            display: flex;
            gap: 12px;
            align-items: center;
        }

        .header-button {
            background: none;
            border: none;
            color: #ffffff;
            cursor: pointer;
            font-size: 16px;
            padding: 0;
            display: flex;
            align-items: center;
            opacity: 0.8;
            transition: opacity 0.2s;
        }

        .header-button:hover {
            opacity: 1;
        }

        #promptimizer-title {
            margin: 0;
            font-size: 16px;
            font-weight: bold;
            color: #ffffff;
        }

        #promptimizer-toggle {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 50px;
            height: 50px;
            background: #2196F3;
            border-radius: 25px;
            color: white;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 24px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            z-index: 2147483647;
            border: 2px solid rgba(255,255,255,0.2);
        }

        #promptimizer-content {
            padding: 16px;
            background: #ffffff;
            border-radius: 0 0 8px 8px;
            overflow-y: auto;
            max-height: calc(100% - 48px);
        }

        .promptimizer-input {
            width: 100%;
            padding: 8px;
            margin-bottom: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-sizing: border-box;
            background: #ffffff;
            color: #000000;
        }

        /* Textarea specific styles */
        textarea.promptimizer-input {
            resize: none; /* We'll handle resizing through container */
            min-height: 100px;
            transition: border-color 0.2s ease, box-shadow 0.2s ease;
            overflow-y: auto;
        }

        textarea.promptimizer-input:focus {
            border-color: #2196F3;
            box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
            outline: none;
        }

        /* Custom resize handles for the container */
        .resize-handle {
            position: absolute;
            background: transparent;
            z-index: 10;
        }

        .resize-handle-right {
            cursor: ew-resize;
            width: 8px;
            height: 100%;
            right: 0;
            top: 0;
        }

        .resize-handle-bottom {
            cursor: ns-resize;
            height: 8px;
            width: 100%;
            bottom: 0;
            left: 0;
        }

        .resize-handle-corner {
            cursor: nwse-resize;
            width: 14px;
            height: 14px;
            right: 0;
            bottom: 0;
        }

        /* Container resize visual feedback */
        #promptimizer-container.resizing {
            transition: none;
            box-shadow: 0 2px 15px rgba(33, 150, 243, 0.4);
        }

        #promptimizer-container.resizing-right {
            border-right: 2px solid #2196F3;
        }

        #promptimizer-container.resizing-bottom {
            border-bottom: 2px solid #2196F3;
        }

        #promptimizer-container.resizing-corner {
            border-right: 2px solid #2196F3;
            border-bottom: 2px solid #2196F3;
        }

        .prompt-type-select {
            width: 100%;
            padding: 8px;
            margin-bottom: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            background: #ffffff;
            color: #000000;
            font-size: 14px;
            cursor: pointer;
        }

        .prompt-type-select:focus {
            border-color: #2196F3;
            outline: none;
        }

        .input-group {
            margin-bottom: 16px;
        }

        .input-label {
            display: block;
            margin-bottom: 6px;
            color: #000000;
            font-size: 14px;
            font-weight: 500;
        }

        .promptimizer-button {
            background: #2196F3;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            margin-bottom: 10px;
            width: 100%;
        }

        .promptimizer-button:hover {
            background: #1976D2;
        }

        #promptimizer-response-container {
            position: relative;
            margin-top: 16px;
        }

        #promptimizer-response {
            margin-top: 0;
            padding: 12px;
            background: #f8f9fa;
            border-radius: 4px;
            white-space: pre-wrap;
            max-height: 300px;
            overflow-y: auto;
            color: #000000;
            border: 1px solid #e9ecef;
        }

        #copy-button {
            position: absolute;
            top: 8px;
            right: 8px;
            background: #2196F3;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 4px 8px;
            font-size: 12px;
            cursor: pointer;
            opacity: 0;
            transition: opacity 0.2s;
            z-index: 1;
        }

        #copy-button:hover {
            background: #1976D2;
        }

        #copy-button.visible {
            opacity: 1;
        }

        #copy-button.copied {
            background: #4CAF50;
        }

        .promptimizer-error {
            color: #dc3545;
            margin-top: 8px;
            font-size: 14px;
            background: #fff;
            padding: 8px;
            border-radius: 4px;
        }

        .config-section {
            margin-bottom: 15px;
            overflow: hidden;
            transition: max-height 0.3s ease-out;
        }

        .config-section.collapsed {
            max-height: 40px;
        }

        .config-section.expanded {
            max-height: 300px;
        }

        .config-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: pointer;
            padding: 8px 0;
        }

        .config-header h3 {
            margin: 0;
            font-size: 16px;
            color: #333;
        }

        .config-toggle {
            font-size: 18px;
            color: #666;
            transition: transform 0.3s;
        }

        .config-toggle.collapsed {
            transform: rotate(-90deg);
        }

        .config-content {
            transition: opacity 0.3s;
        }

        .config-content.collapsed {
            opacity: 0;
            height: 0;
            overflow: hidden;
        }

        .config-content.expanded {
            opacity: 1;
            height: auto;
        }
    `);

    // Default dimensions for the container
    const defaultDimensions = {
        width: '400px',
        height: 'auto',
        right: '20px',
        bottom: '20px'
    };

    // Create UI elements
    function createUI() {
        // Toggle button
        const toggle = document.createElement('div');
        toggle.id = 'promptimizer-toggle';
        toggle.innerHTML = '✨';
        toggle.title = 'Toggle Promptimizer';
        document.body.appendChild(toggle);

        // Main container
        const container = document.createElement('div');
        container.id = 'promptimizer-container';
        container.innerHTML = `
            <div id="promptimizer-header">
                <h2 id="promptimizer-title">Promptimizer</h2>
                <div id="promptimizer-header-controls">
                    <button class="header-button" id="promptimizer-reset" title="Reset UI Position and Size">↺</button>
                    <button class="header-button" id="promptimizer-minimize" title="Minimize">−</button>
                </div>
            </div>
            <div id="promptimizer-content">
                <div class="config-section expanded" id="config-section">
                    <div class="config-header" id="config-header">
                        <h3>API Configuration</h3>
                        <span class="config-toggle">▼</span>
                    </div>
                    <div class="config-content expanded">
                        <input type="text" id="api-url" class="promptimizer-input" placeholder="API URL (e.g., https://api.openai.com/v1)" />
                        <input type="password" id="api-key" class="promptimizer-input" placeholder="API Key" />
                        <input type="text" id="model-name" class="promptimizer-input" placeholder="Model name" />
                        <input type="text" id="provider-name" class="promptimizer-input" placeholder="Provider name (optional, for gpt4free)" />
                        <button id="save-config" class="promptimizer-button">Save Configuration</button>
                    </div>
                </div>
                <div class="input-group">
                    <label class="input-label" for="prompt-type">Prompt Type:</label>
                    <select id="prompt-type" class="prompt-type-select">
                        <option value="user">User Prompt</option>
                        <option value="system">System Prompt</option>
                    </select>
                </div>
                <div class="input-group">
                    <label class="input-label" for="prompt-input">Enter Your Prompt:</label>
                    <textarea id="prompt-input" class="promptimizer-input" rows="4" placeholder="Enter your prompt here..."></textarea>
                </div>
                <button id="optimize-button" class="promptimizer-button">Optimize Prompt</button>
                <div id="promptimizer-response-container">
                    <button id="copy-button" style="display: none;">Copy Enhanced Prompt</button>
                    <div id="promptimizer-response"></div>
                </div>
            </div>
            <div class="resize-handle resize-handle-right"></div>
            <div class="resize-handle resize-handle-bottom"></div>
            <div class="resize-handle resize-handle-corner"></div>
        `;
        document.body.appendChild(container);

        // Make the container draggable
        makeDraggable(container);

        // Add container resizing
        makeResizable(container);

        // Load saved configuration
        loadConfiguration();

        // Event listeners
        toggle.addEventListener('click', () => {
            container.style.display = container.style.display === 'none' ? 'block' : 'none';
        });

        document.getElementById('promptimizer-minimize').addEventListener('click', () => {
            container.style.display = 'none';
        });

        // Config section toggle
        const configHeader = document.getElementById('config-header');
        const configSection = document.getElementById('config-section');
        const configToggle = configHeader.querySelector('.config-toggle');
        const configContent = configSection.querySelector('.config-content');

        configHeader.addEventListener('click', () => {
            const isExpanded = configSection.classList.contains('expanded');

            if (isExpanded) {
                configSection.classList.remove('expanded');
                configSection.classList.add('collapsed');
                configContent.classList.remove('expanded');
                configContent.classList.add('collapsed');
                configToggle.classList.add('collapsed');
            } else {
                configSection.classList.remove('collapsed');
                configSection.classList.add('expanded');
                configContent.classList.remove('collapsed');
                configContent.classList.add('expanded');
                configToggle.classList.remove('collapsed');
            }
        });

        document.getElementById('save-config').addEventListener('click', saveConfiguration);
        document.getElementById('optimize-button').addEventListener('click', optimizePrompt);
        document.getElementById('copy-button').addEventListener('click', copyEnhancedPrompt);

        // Initialize auto-height for textarea
        const textarea = document.getElementById('prompt-input');
        if (textarea) {
            autoResizeTextarea(textarea);
            textarea.addEventListener('input', function() {
                autoResizeTextarea(this);
            });
        }
    }

    // Make an element draggable
    function makeDraggable(element) {
        const header = element.querySelector('#promptimizer-header');
        let isDragging = false;
        let currentX;
        let currentY;
        let initialX;
        let initialY;
        let xOffset = 0;
        let yOffset = 0;

        // Function to reset position and size
        function resetPosition() {
            element.style.transform = 'translate(0, 0)';
            element.style.right = defaultDimensions.right;
            element.style.bottom = defaultDimensions.bottom;
            element.style.left = '';
            element.style.top = '';
            // Reset dimensions as well
            element.style.width = defaultDimensions.width;
            element.style.height = defaultDimensions.height;
            xOffset = 0;
            yOffset = 0;

            // Reset any set textarea dimensions
            const textarea = document.getElementById('prompt-input');
            if (textarea) {
                textarea.style.height = '';
                autoResizeTextarea(textarea);
            }
        }

        // Add reset button event listener
        document.getElementById('promptimizer-reset').addEventListener('click', (e) => {
            e.stopPropagation(); // Prevent drag start
            resetPosition();
        });

        header.addEventListener('mousedown', dragStart);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', dragEnd);

        function dragStart(e) {
            if (e.target.closest('.header-button')) {
                return; // Don't start drag if clicking a button
            }
            initialX = e.clientX - xOffset;
            initialY = e.clientY - yOffset;

            if (e.target === header || e.target.closest('#promptimizer-header')) {
                isDragging = true;
            }
        }

        function drag(e) {
            if (isDragging) {
                e.preventDefault();
                currentX = e.clientX - initialX;
                currentY = e.clientY - initialY;
                xOffset = currentX;
                yOffset = currentY;

                element.style.transform = `translate(${currentX}px, ${currentY}px)`;
                // Clear the default positioning when dragging
                element.style.right = '';
                element.style.bottom = '';
            }
        }

        function dragEnd() {
            initialX = currentX;
            initialY = currentY;
            isDragging = false;
        }

        // Initialize at default position
        resetPosition();
    }

    // Make container resizable
    function makeResizable(container) {
        const rightHandle = container.querySelector('.resize-handle-right');
        const bottomHandle = container.querySelector('.resize-handle-bottom');
        const cornerHandle = container.querySelector('.resize-handle-corner');

        let isResizing = false;
        let currentResizeType = '';
        let startX, startY, startWidth, startHeight;

        // Function to start resize operation
        function startResize(e, type) {
            e.preventDefault();
            e.stopPropagation();

            isResizing = true;
            currentResizeType = type;

            // Get current dimensions
            const rect = container.getBoundingClientRect();
            startWidth = rect.width;
            startHeight = rect.height;
            startX = e.clientX;
            startY = e.clientY;

            // Add visual feedback classes
            container.classList.add('resizing');
            container.classList.add(`resizing-${type}`);

            // Add document listeners
            document.addEventListener('mousemove', resize);
            document.addEventListener('mouseup', stopResize);
        }

        // Function to handle resize
        function resize(e) {
            if (!isResizing) return;

            let newWidth = startWidth;
            let newHeight = startHeight;

            // Calculate new dimensions based on mouse movement
            switch (currentResizeType) {
                case 'right':
                    newWidth = startWidth + (e.clientX - startX);
                    break;
                case 'bottom':
                    newHeight = startHeight + (e.clientY - startY);
                    break;
                case 'corner':
                    newWidth = startWidth + (e.clientX - startX);
                    newHeight = startHeight + (e.clientY - startY);
                    break;
            }

            // Apply minimum dimensions
            newWidth = Math.max(300, newWidth);
            newHeight = Math.max(300, newHeight);

            // Apply new dimensions
            container.style.width = `${newWidth}px`;
            container.style.height = `${newHeight}px`;
        }

        // Function to stop resize
        function stopResize() {
            isResizing = false;

            // Remove visual feedback classes
            container.classList.remove('resizing');
            container.classList.remove(`resizing-${currentResizeType}`);

            // Remove document listeners
            document.removeEventListener('mousemove', resize);
            document.removeEventListener('mouseup', stopResize);

            // Re-enable transitions
            container.style.transition = 'width 0.2s ease, height 0.2s ease';
        }

        // Add event listeners to resize handles
        rightHandle.addEventListener('mousedown', e => startResize(e, 'right'));
        bottomHandle.addEventListener('mousedown', e => startResize(e, 'bottom'));
        cornerHandle.addEventListener('mousedown', e => startResize(e, 'corner'));
    }

    // Helper function to auto-resize textarea based on content
    function autoResizeTextarea(textarea) {
        // Save scroll position
        const scrollTop = window.pageYOffset || document.documentElement.scrollTop;

        // Reset height temporarily to get accurate scrollHeight
        textarea.style.height = 'auto';

        // Set new height based on content (with minimum)
        const newHeight = Math.max(textarea.scrollHeight, 100) + 'px';

        // Apply the new height with transition
        textarea.style.transition = 'height 0.15s ease-out';
        textarea.style.height = newHeight;

        // Restore scroll position to prevent page jump
        window.scrollTo(0, scrollTop);
    }

    // Save API configuration
    function saveConfiguration() {
        const apiUrl = document.getElementById('api-url').value;
        const apiKey = document.getElementById('api-key').value;
        const modelName = document.getElementById('model-name').value;
        const providerName = document.getElementById('provider-name').value;

        GM_setValue('apiUrl', apiUrl);
        GM_setValue('apiKey', apiKey);
        GM_setValue('modelName', modelName);
        GM_setValue('providerName', providerName);

        showResponse('Configuration saved!');
    }

    // Load saved configuration
    function loadConfiguration() {
        const apiUrl = GM_getValue('apiUrl', '');
        const apiKey = GM_getValue('apiKey', '');
        const modelName = GM_getValue('modelName', '');
        const providerName = GM_getValue('providerName', '');

        document.getElementById('api-url').value = apiUrl;
        document.getElementById('api-key').value = apiKey;
        document.getElementById('model-name').value = modelName;
        document.getElementById('provider-name').value = providerName;
    }

    // Show response or error message
    function showResponse(message, isError = false) {
        const responseDiv = document.getElementById('promptimizer-response');
        const copyButton = document.getElementById('copy-button');
        responseDiv.textContent = message;
        responseDiv.className = isError ? 'promptimizer-error' : '';

        // Show/hide copy button based on whether there's a successful response
        if (!isError && message !== 'Optimizing prompt...') {
            copyButton.style.display = 'block';
            setTimeout(() => copyButton.classList.add('visible'), 10);
        } else {
            copyButton.style.display = 'none';
            copyButton.classList.remove('visible');
        }
    }

    // Extract enhanced prompt from response
    function extractEnhancedPrompt(text) {
        const match = text.match(/<enhanced_prompt>([\s\S]*?)<\/enhanced_prompt>/);
        return match ? match[1].trim() : text;
    }

    // Copy enhanced prompt to clipboard
    function copyEnhancedPrompt() {
        const responseDiv = document.getElementById('promptimizer-response');
        const copyButton = document.getElementById('copy-button');
        const textToCopy = extractEnhancedPrompt(responseDiv.textContent);

        navigator.clipboard.writeText(textToCopy).then(() => {
            copyButton.textContent = 'Copied!';
            copyButton.classList.add('copied');
            setTimeout(() => {
                copyButton.textContent = 'Copy Enhanced Prompt';
                copyButton.classList.remove('copied');
            }, 2000);
        }).catch(err => {
            console.error('Failed to copy text: ', err);
        });
    }

    // Optimize prompt using the API
    async function optimizePrompt() {
        const apiUrl = GM_getValue('apiUrl', '');
        const apiKey = GM_getValue('apiKey', '');
        const modelName = GM_getValue('modelName', '');
        const providerName = GM_getValue('providerName', '');
        const promptType = document.getElementById('prompt-type').value;
        const promptInput = document.getElementById('prompt-input').value;

        if (!apiUrl || !apiKey || !modelName) {
            showResponse('Please configure API settings first!', true);
            return;
        }

        if (!promptInput.trim()) {
            showResponse('Please enter a prompt to optimize!', true);
            return;
        }

        showResponse('Optimizing prompt...');

        const systemPrompt = `You are a specialized prompt optimization AI focused on enhancing both user prompts and system prompts for AI interactions.

<instructions>
1. You will receive prompts marked with either <user_prompt> or <system_prompt> tags
2. ALWAYS maintain the same type of prompt in your enhancement
3. NEVER engage in conversation or provide explanations
4. ONLY return the enhanced prompt within <enhanced_prompt> tags
5. Apply appropriate prompt engineering techniques based on the prompt type:

   For User Prompts:
   - Maintain conversational context and flow
   - Clarify user intent and expectations
   - Add specific parameters for the response
   - Include relevant context from prior conversation
   - Structure multi-part requests clearly

   For System Prompts:
   - Define clear roles and responsibilities
   - Establish behavioral boundaries
   - Include success criteria and constraints
   - Structure hierarchical instructions
   - Define interaction patterns
   - Specify output formats and preferences
   - Include error handling instructions
</instructions>

<examples>
<example>
<input_type>user_prompt</input_type>
<input>Thanks for your help! Can you search the web for more information about this topic?</input>
<enhanced_prompt>Please conduct a comprehensive web search on our current topic with the following parameters:
1. Focus on authoritative sources from the last 2 years
2. Include academic and expert perspectives
3. Compare and contrast different viewpoints
4. Identify emerging trends and developments
5. Extract key insights and practical applications

Format the response with:
- Main findings in bullet points
- Source citations for each major claim
- Relevance assessment for each source
- Synthesis of conflicting information
- Suggestions for further research</enhanced_prompt>
</example>

<example>
<input_type>system_prompt</input_type>
<input>You are a web search AI assistant. Your role is to help users find information.</input>
<enhanced_prompt>You are a specialized web search AI assistant designed to provide comprehensive, accurate, and well-structured information retrieval services.

Core Responsibilities:
1. Execute precise web searches based on user queries
2. Evaluate source credibility and relevance
3. Synthesize information from multiple sources
4. Present findings in clear, structured formats

Behavioral Guidelines:
- Maintain objectivity in information presentation
- Clearly distinguish between facts and interpretations
- Acknowledge information gaps or uncertainties
- Proactively suggest related topics for exploration

Output Requirements:
1. Structure all responses with:
   - Executive summary
   - Detailed findings
   - Source citations
   - Confidence levels
2. Use formatting for clarity:
   - Bullet points for key facts
   - Tables for comparisons
   - Markdown for emphasis
   - Hierarchical headings

Error Handling:
- Acknowledge when information is outdated
- Flag potential misinformation
- Suggest alternative search strategies
- Provide confidence levels for findings</enhanced_prompt>
</example>
</examples>

<success_criteria>
For User Prompts:
- Enhanced clarity and specificity
- Maintained conversation context
- Clear parameters for response
- Structured multi-part requests
- Defined output preferences

For System Prompts:
- Clear role definition
- Comprehensive behavioral guidelines
- Specific output requirements
- Error handling procedures
- Interaction patterns defined
</success_criteria>`;

        // Wrap the input with appropriate type tags
        const taggedInput = `<${promptType}_prompt>${promptInput}</${promptType}_prompt>`;

        GM_xmlhttpRequest({
            method: 'POST',
            url: `${apiUrl}/chat/completions`,
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${apiKey}`
            },
            data: JSON.stringify({
                model: modelName,
                ...(providerName && { provider: providerName }),
                messages: [
                    {
                        role: 'system',
                        content: systemPrompt
                    },
                    {
                        role: 'user',
                        content: taggedInput
                    }
                ],
                temperature: 0.7,
                max_tokens: 16384
            }),
            onload: function(response) {
                try {
                    const result = JSON.parse(response.responseText);
                    if (result.error) {
                        showResponse(`Error: ${result.error.message}`, true);
                    } else if (result.choices && result.choices[0]) {
                        showResponse(result.choices[0].message.content);
                    } else {
                        showResponse('Unexpected API response format', true);
                    }
                } catch (error) {
                    showResponse(`Error processing response: ${error.message}`, true);
                }
            },
            onerror: function(error) {
                showResponse(`Network error: ${error.statusText}`, true);
            }
        });
    }

    // Initialize the UI
    createUI();
})();