carterlion13 / Homework AI Chat Helper Enhanced (v1.18)

// ==UserScript==
// @name         Homework AI Chat Helper Enhanced (v1.18)
// @namespace    http://tampermonkey.net/
// @version      1.18
// @license      MIT
// @author       ChatGPT [AI Made This]
// @description  Chat helper with history saving, markdown formatting, export (JSON, Markdown, or plain text), auto-detect API, response speed indicator, voice input, resizable container, tap-to-copy messages (copies only the message text), and customization of message colors and name.
// @match        *://*.r22.core.learn.edgenuity.com/player/*
// @match        *://*.thelearningodyssey.com/DLO/Player.aspx#/*
// @match        *://*.thelearningodyssey.com/assess/RuntimeQuestion.aspx?TaskId=0&LAId=*
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @connect      openrouter.ai
// @connect      generativelanguage.googleapis.com
// @connect      cdn.jsdelivr.net
// ==/UserScript==

(async function() {
    'use strict';

    // ======= CONFIG & LOCAL STORAGE KEYS ========
    const STORAGE_KEYS = {
        openRouterKey: 'aiHelper_openRouter_apiKey',
        geminiKey: 'aiHelper_gemini_apiKey',
        apiChoice: 'aiHelper_apiChoice', // "openrouter" or "gemini"
        darkMode: 'aiHelper_darkMode',
        openRouterModel: 'aiHelper_openRouterModel',
        chatHistory: 'aiHelper_chatHistory',
        customName: 'aiHelper_customName',
        customUserColor: 'aiHelper_customUserColor',
        customAIColor: 'aiHelper_customAIColor'
    };

    // Global customization variables (loaded later)
    let customName = "You";
    let customUserColor = "";
    let customAIColor = "";

    // ======= UTILITY FUNCTIONS ========
    function saveSetting(key, value) { localStorage.setItem(key, value); }
    function getSetting(key) { return localStorage.getItem(key); }

    // Chat history functions
    function saveChatHistory(history) { localStorage.setItem(STORAGE_KEYS.chatHistory, JSON.stringify(history)); }
    function loadChatHistory() {
        const history = localStorage.getItem(STORAGE_KEYS.chatHistory);
        return history ? JSON.parse(history) : [];
    }

    // Simple markdown formatting: **bold**, *italic*, and triple backtick code blocks.
    function formatResponse(text) {
        text = text.replace(/```([\s\S]*?)```/g, (match, p1) => `<pre><code>${p1.trim()}</code></pre>`);
        text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
        text = text.replace(/\*([^*]+)\*/g, '<em>$1</em>');
        return text;
    }

    // ======= API FUNCTIONS ========
    function fetchOpenRouterModels(apiKey, callback) {
        if (!apiKey) {
            callback(null, 'No OpenRouter API key provided.');
            return;
        }
        console.log('Fetching OpenRouter models...');
        GM_xmlhttpRequest({
            method: 'GET',
            url: 'https://openrouter.ai/api/v1/models',
            headers: { 'Authorization': `Bearer ${apiKey}` },
            onload: function(response) {
                if (response.status === 200) {
                    const data = JSON.parse(response.responseText);
                    callback(data.data, null);
                } else {
                    callback(null, `Error: ${response.status} - ${response.responseText}`);
                }
            },
            onerror: function() { callback(null, 'Failed to connect to OpenRouter API.'); }
        });
    }

    function fetchGeminiModels(apiKey, callback) {
        if (!apiKey) {
            callback(null, 'No Gemini API key provided.');
            return;
        }
        console.log('Fetching Google Gemini models...');
        GM_xmlhttpRequest({
            method: 'GET',
            url: 'https://generativelanguage.googleapis.com/v1beta/models?key=' + apiKey,
            headers: { 'Content-Type': 'application/json' },
            onload: function(response) {
                if (response.status === 200) {
                    const data = JSON.parse(response.responseText);
                    const models = data.models.filter(model => model.name.startsWith('models/gemini-2.0-'))
                        .map(model => ({
                            id: model.name.split('/').pop(),
                            name: model.displayName || model.name.split('/').pop()
                        }));
                    callback(models, null);
                } else {
                    console.error('Failed to fetch Gemini models:', response.status, response.responseText);
                    callback(null, `Error: ${response.status} - ${response.responseText}`);
                }
            },
            onerror: function() { callback(null, 'Failed to connect to Google Gemini API.'); }
        });
    }

    function generateOpenRouterText(apiKey, model, messages, callback) {
        const payload = { model, messages };
        console.log('Sending request to OpenRouter:', payload);
        const startTime = performance.now();
        GM_xmlhttpRequest({
            method: 'POST',
            url: 'https://openrouter.ai/api/v1/chat/completions',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': apiKey ? `Bearer ${apiKey}` : ''
            },
            data: JSON.stringify(payload),
            onload: function(response) {
                const duration = ((performance.now() - startTime) / 1000).toFixed(2);
                if (response.status === 200) {
                    const data = JSON.parse(response.responseText);
                    const generatedText = data.choices[0].message.content.trim();
                    callback(null, generatedText, duration);
                } else {
                    callback(`Error: ${response.status} - ${response.responseText}`, null, duration);
                }
            },
            onerror: function() { callback('Failed to connect to OpenRouter API.', null, null); }
        });
    }
    function generateGeminiText(apiKey, model, messages, temperature, callback) {
        const payload = {
            contents: [{ parts: messages.map(msg => ({ text: msg.content })) }],
            generationConfig: { temperature: parseFloat(temperature) || 1.0, maxOutputTokens: 2048 }
        };
        console.log('Sending request to Gemini:', payload);
        const startTime = performance.now();
        GM_xmlhttpRequest({
            method: 'POST',
            url: `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`,
            headers: { 'Content-Type': 'application/json' },
            data: JSON.stringify(payload),
            onload: function(response) {
                const duration = ((performance.now() - startTime) / 1000).toFixed(2);
                if (response.status === 200) {
                    const data = JSON.parse(response.responseText);
                    const generatedText = data.candidates[0].content.parts[0].text.trim();
                    callback(null, generatedText, duration);
                } else {
                    callback(`Error: ${response.status} - ${response.responseText}`, null, duration);
                }
            },
            onerror: function() { callback('Failed to connect to Google Gemini API.', null, null); }
        });
    }

    // ======= UI BUILDING & STYLING ========
    GM_addStyle(`
        /* Resizable Container */
        #aiHelperContainer {
            position: fixed;
            bottom: 10px;
            right: 10px;
            width: 360px;
            height: auto;
            max-height: 90vh;
            background: #fff;
            color: #000;
            border: 1px solid #ccc;
            z-index: 10000;
            font-family: Arial, sans-serif;
            box-shadow: 0 2px 8px rgba(0,0,0,0.3);
            border-radius: 5px;
            overflow: auto;
            resize: both;
            cursor: move;
        }
        #aiHelperContainer.dark { background: #2e2e2e !important; color: #fff !important; border: 1px solid #555 !important; }
        /* Header */
        #aiHelperHeader { background: #007acc; color: #fff; padding: 8px; cursor: move; }
        #aiHelperHeader.dark { background: #005a9e; }
        /* Tabs */
        #aiHelperTabs { display: flex; border-bottom: 1px solid #ccc; }
        #aiHelperTabs button { flex: 1; padding: 8px; border: none; background: #f0f0f0; cursor: pointer; color: #000; }
        #aiHelperTabs button.active { background: #ddd; }
        #aiHelperContainer.dark #aiHelperTabs button { background: #555; color: #fff; }
        #aiHelperContainer.dark #aiHelperTabs button.active { background: #666; }
        /* Content */
        #aiHelperContent { padding: 8px; }
        #aiHelperContent textarea, #aiHelperContent input, #aiHelperContent select {
            width: 100%;
            margin-bottom: 8px;
            padding: 4px;
            box-sizing: border-box;
            color: #000;
            background: #fff;
        }
        #aiHelperContainer.dark #aiHelperContent textarea,
        #aiHelperContainer.dark #aiHelperContent input,
        #aiHelperContainer.dark #aiHelperContent select { color: #fff !important; background: #555 !important; }
        #aiHelperContent button { width: 100%; padding: 8px; background: #007acc; color: #fff; border: none; cursor: pointer; margin-bottom: 8px; }
        #aiHelperContent .hidden { display: none; }
        /* Chat log styling */
        #aiChatLog { height: 200px; overflow-y: auto; border: 1px solid #ccc; margin-bottom: 8px; padding: 4px; background: #fafafa; color: #000; }
        #aiHelperContainer.dark #aiChatLog { background: #3a3a3a; border-color: #555; color: #fff; }
        .chatMessage { margin-bottom: 8px; padding: 6px; border-radius: 4px; cursor: pointer; position: relative; }
        .chatTimestamp { position: absolute; right: 6px; bottom: 4px; font-size: 0.7em; color: inherit; opacity: 0.7; }
        .chatUser { background: #e1f5fe; color: #000; }
        .chatAI { background: #f1f8e9; color: #000; }
        /* Dark mode chat colors */
        #aiHelperContainer.dark .chatUser { background: #2e4a7d; color: #fff; }
        #aiHelperContainer.dark .chatAI { background: #3a9d3a; color: #fff; }
        /* Dark Mode Toggle Button */
        #darkModeToggleBtn { width: 100%; padding: 8px; background: #007acc; color: #fff; border: none; cursor: pointer; margin-bottom: 8px; }
        #aiHelperContainer.dark #darkModeToggleBtn { background: #005a9e; }
        /* Clear History Button (in Settings only) */
        #clearHistoryBtn { width: 100%; padding: 8px; background: #d9534f; color: #fff; border: none; cursor: pointer; margin-bottom: 8px; }
        /* Download Button */
        #downloadBtn { width: 100%; padding: 8px; background: #5cb85c; color: #fff; border: none; cursor: pointer; margin-bottom: 8px; }
    `);

    // Create container and header
    const container = document.createElement('div');
    container.id = 'aiHelperContainer';
    document.body.appendChild(container);

    const header = document.createElement('div');
    header.id = 'aiHelperHeader';
    header.textContent = 'Homework AI Chat Helper';
    container.appendChild(header);

    // Tabs (Chat, Settings, Customization)
    const tabs = document.createElement('div');
    tabs.id = 'aiHelperTabs';
    container.appendChild(tabs);

    const tabMain = document.createElement('button');
    tabMain.textContent = 'Chat';
    tabMain.className = 'active';
    tabs.appendChild(tabMain);

    const tabSettings = document.createElement('button');
    tabSettings.textContent = 'Settings';
    tabs.appendChild(tabSettings);

    const tabCustomization = document.createElement('button');
    tabCustomization.textContent = 'Customization';
    tabs.appendChild(tabCustomization);

    // Content container
    const content = document.createElement('div');
    content.id = 'aiHelperContent';
    container.appendChild(content);

    // ======= MAIN TAB UI ========
    const chatLog = document.createElement('div');
    chatLog.id = 'aiChatLog';
    content.appendChild(chatLog);

    // Voice Input Button (only on Chat tab)
    const voiceBtn = document.createElement('button');
    voiceBtn.id = 'voiceBtn';
    voiceBtn.textContent = '🎤 Speak Your Question';
    content.appendChild(voiceBtn);

    const questionInput = document.createElement('textarea');
    questionInput.rows = 3;
    questionInput.placeholder = 'Enter your homework question here...';
    content.appendChild(questionInput);

    // Keyboard shortcut: Enter sends message (Shift+Enter for newline).
    questionInput.addEventListener('keydown', (e) => {
        if(e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            generateButton.click();
        }
    });

    // Remove image sending elements (not added)

    // Send button
    const generateButton = document.createElement('button');
    generateButton.textContent = 'Send';
    content.appendChild(generateButton);

    // Export Container: Only the Download button now.
    const exportContainer = document.createElement('div');
    exportContainer.style.overflow = 'hidden';
    content.appendChild(exportContainer);
    const downloadBtn = document.createElement('button');
    downloadBtn.id = 'downloadBtn';
    downloadBtn.textContent = 'To download chat';
    exportContainer.appendChild(downloadBtn);

    // Loading Bar (hidden by default)
    const loadingBar = document.createElement('div');
    loadingBar.id = 'loadingBar';
    loadingBar.style.display = 'none';
    content.appendChild(loadingBar);

    // ======= SETTINGS TAB UI ========
    const settingsDiv = document.createElement('div');
    settingsDiv.className = 'hidden';
    content.appendChild(settingsDiv);

    const apiChoiceLabel = document.createElement('label');
    apiChoiceLabel.textContent = 'Select API: ';
    settingsDiv.appendChild(apiChoiceLabel);

    // Only OpenRouter and Google Gemini options
    const selectAPI = document.createElement('select');
    const optionOpenRouter = document.createElement('option');
    optionOpenRouter.value = 'openrouter';
    optionOpenRouter.textContent = 'OpenRouter';
    selectAPI.appendChild(optionOpenRouter);
    const optionGemini = document.createElement('option');
    optionGemini.value = 'gemini';
    optionGemini.textContent = 'Google Gemini';
    selectAPI.appendChild(optionGemini);
    settingsDiv.appendChild(selectAPI);

    selectAPI.addEventListener('change', () => {
        saveSetting(STORAGE_KEYS.apiChoice, selectAPI.value);
        if(selectAPI.value === 'openrouter') {
            openRouterKeyInput.style.display = 'block';
            openRouterModelSelect.style.display = 'block';
            geminiKeyInput.style.display = 'none';
            const key = openRouterKeyInput.value.trim();
            fetchOpenRouterModels(key, (models, err) => {
                if(err){ console.error(err); return; }
                openRouterModelSelect.innerHTML = '';
                models.forEach(model => {
                    const option = document.createElement('option');
                    option.value = model.id;
                    option.textContent = model.id;
                    openRouterModelSelect.appendChild(option);
                });
            });
        } else if(selectAPI.value === 'gemini') {
            openRouterKeyInput.style.display = 'none';
            openRouterModelSelect.style.display = 'none';
            geminiKeyInput.style.display = 'block';
        }
    });

    // OpenRouter API key input
    const openRouterKeyInput = document.createElement('input');
    openRouterKeyInput.type = 'text';
    openRouterKeyInput.placeholder = 'Enter OpenRouter API Key';
    settingsDiv.appendChild(openRouterKeyInput);
    openRouterKeyInput.addEventListener('keypress', (e) => {
        if(e.key === 'Enter') {
            const key = openRouterKeyInput.value.trim();
            saveSetting(STORAGE_KEYS.openRouterKey, key);
            fetchOpenRouterModels(key, (models, err) => {
                if(err){ console.error(err); return; }
                openRouterModelSelect.innerHTML = '';
                models.forEach(model => {
                    const option = document.createElement('option');
                    option.value = model.id;
                    option.textContent = model.id;
                    openRouterModelSelect.appendChild(option);
                });
                const savedModel = getSetting(STORAGE_KEYS.openRouterModel);
                if(savedModel){ openRouterModelSelect.value = savedModel; }
            });
        }
    });

    // Dropdown for models (used for OpenRouter)
    const openRouterModelSelect = document.createElement('select');
    settingsDiv.appendChild(openRouterModelSelect);
    openRouterModelSelect.addEventListener('change', () => {
        saveSetting(STORAGE_KEYS.openRouterModel, openRouterModelSelect.value);
    });

    // Google Gemini API key input
    const geminiKeyInput = document.createElement('input');
    geminiKeyInput.type = 'text';
    geminiKeyInput.placeholder = 'Enter Google Gemini API Key';
    settingsDiv.appendChild(geminiKeyInput);

    // Dark Mode Toggle Button
    const darkModeToggleBtn = document.createElement('button');
    darkModeToggleBtn.id = 'darkModeToggleBtn';
    darkModeToggleBtn.textContent = 'Dark Mode: Off';
    settingsDiv.appendChild(darkModeToggleBtn);
    darkModeToggleBtn.addEventListener('click', () => {
        let isDark = getSetting(STORAGE_KEYS.darkMode) === 'true';
        isDark = !isDark;
        saveSetting(STORAGE_KEYS.darkMode, isDark ? 'true' : 'false');
        applyDarkMode();
    });

    // Clear History Button
    const clearHistoryBtn = document.createElement('button');
    clearHistoryBtn.id = 'clearHistoryBtn';
    clearHistoryBtn.textContent = 'Clear Chat History';
    settingsDiv.appendChild(clearHistoryBtn);
    clearHistoryBtn.addEventListener('click', () => {
        if (confirm("Are you sure you want to clear the chat history? This cannot be undone.")) {
            localStorage.removeItem(STORAGE_KEYS.chatHistory);
            chatLog.innerHTML = '';
            alert("Chat history deleted");
        }
    });

    // Save Settings Button
    const saveSettingsBtn = document.createElement('button');
    saveSettingsBtn.textContent = 'Save Settings';
    settingsDiv.appendChild(saveSettingsBtn);
    saveSettingsBtn.addEventListener('click', () => {
        saveSetting(STORAGE_KEYS.openRouterKey, openRouterKeyInput.value.trim());
        saveSetting(STORAGE_KEYS.geminiKey, geminiKeyInput.value.trim());
        saveSetting(STORAGE_KEYS.apiChoice, selectAPI.value);
        alert('Settings saved!');
        applyDarkMode();
    });

    // ======= CUSTOMIZATION TAB UI ========
    const customizationDiv = document.createElement('div');
    customizationDiv.className = 'hidden';
    content.appendChild(customizationDiv);

    const customNameLabel = document.createElement('label');
    customNameLabel.textContent = 'Your Name: ';
    customizationDiv.appendChild(customNameLabel);
    const customNameInput = document.createElement('input');
    customNameInput.type = 'text';
    customNameInput.placeholder = 'Enter your name';
    customizationDiv.appendChild(customNameInput);

    const userColorLabel = document.createElement('label');
    userColorLabel.textContent = 'Your Message Color: ';
    customizationDiv.appendChild(userColorLabel);
    const customUserColorInput = document.createElement('input');
    customUserColorInput.type = 'color';
    customizationDiv.appendChild(customUserColorInput);

    const aiColorLabel = document.createElement('label');
    aiColorLabel.textContent = 'AI Message Color: ';
    customizationDiv.appendChild(aiColorLabel);
    const customAIColorInput = document.createElement('input');
    customAIColorInput.type = 'color';
    customizationDiv.appendChild(customAIColorInput);

    const saveCustomizationBtn = document.createElement('button');
    saveCustomizationBtn.textContent = 'Save Customization';
    customizationDiv.appendChild(saveCustomizationBtn);

    saveCustomizationBtn.addEventListener('click', () => {
        saveSetting(STORAGE_KEYS.customName, customNameInput.value.trim());
        saveSetting(STORAGE_KEYS.customUserColor, customUserColorInput.value);
        saveSetting(STORAGE_KEYS.customAIColor, customAIColorInput.value);
        loadCustomization();
        alert('Customization saved!');
    });

    function loadCustomization() {
        customName = getSetting(STORAGE_KEYS.customName) || "You";
        customUserColor = getSetting(STORAGE_KEYS.customUserColor) || "";
        customAIColor = getSetting(STORAGE_KEYS.customAIColor) || "";
        customNameInput.value = customName;
        if(customUserColor) { customUserColorInput.value = customUserColor; }
        if(customAIColor) { customAIColorInput.value = customAIColor; }
    }

    // ======= DRAGGABLE FUNCTIONALITY ========
    let isDragging = false;
    header.addEventListener('mousedown', startDrag);
    header.addEventListener('dragstart', (e) => e.preventDefault());
    function startDrag(e) {
        isDragging = true;
        e.preventDefault();
        let startX = e.clientX;
        let startY = e.clientY;
        const rect = container.getBoundingClientRect();
        let origX = rect.left;
        let origY = rect.top;
        function onMouseMove(e) {
            let newX = origX + (e.clientX - startX);
            let newY = origY + (e.clientY - startY);
            container.style.left = newX + 'px';
            container.style.top = newY + 'px';
            container.style.right = 'auto';
            container.style.bottom = 'auto';
        }
        function onMouseUp() {
            isDragging = false;
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        }
        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);
    }
    header.addEventListener('dblclick', () => {
        container.style.height = container.style.height === '30px' ? 'auto' : '30px';
        content.style.display = content.style.display === 'none' ? 'block' : 'none';
        tabs.style.display = tabs.style.display === 'none' ? 'flex' : 'none';
    });

    // ======= TAB SWITCHING ========
    function showTab(tab) {
        // Hide all tab-specific elements.
        chatLog.style.display = 'none';
        questionInput.style.display = 'none';
        generateButton.style.display = 'none';
        exportContainer.style.display = 'none';
        voiceBtn.style.display = 'none';
        settingsDiv.classList.add('hidden');
        customizationDiv.classList.add('hidden');

        // Show elements for the selected tab.
        if(tab === 'chat') {
            chatLog.style.display = 'block';
            questionInput.style.display = 'block';
            generateButton.style.display = 'block';
            exportContainer.style.display = 'block';
            voiceBtn.style.display = 'block';
        } else if(tab === 'settings') {
            settingsDiv.classList.remove('hidden');
        } else if(tab === 'customization') {
            customizationDiv.classList.remove('hidden');
        }
    }
    tabMain.addEventListener('click', () => {
        tabMain.classList.add('active');
        tabSettings.classList.remove('active');
        tabCustomization.classList.remove('active');
        showTab('chat');
    });
    tabSettings.addEventListener('click', () => {
        tabSettings.classList.add('active');
        tabMain.classList.remove('active');
        tabCustomization.classList.remove('active');
        showTab('settings');
    });
    tabCustomization.addEventListener('click', () => {
        tabCustomization.classList.add('active');
        tabMain.classList.remove('active');
        tabSettings.classList.remove('active');
        showTab('customization');
    });

    // ======= INITIALIZE SETTINGS, CUSTOMIZATION & LOAD CHAT HISTORY ========
    function loadSettings() {
        const savedOpenRouterKey = getSetting(STORAGE_KEYS.openRouterKey) || '';
        const savedGeminiKey = getSetting(STORAGE_KEYS.geminiKey) || '';
        const savedApiChoice = getSetting(STORAGE_KEYS.apiChoice) || 'openrouter';
        const savedDarkMode = getSetting(STORAGE_KEYS.darkMode) || 'false';
        const savedModel = getSetting(STORAGE_KEYS.openRouterModel) || '';

        openRouterKeyInput.value = savedOpenRouterKey;
        geminiKeyInput.value = savedGeminiKey;
        selectAPI.value = savedApiChoice;
        darkModeToggleBtn.textContent = savedDarkMode === 'true' ? 'Dark Mode: On' : 'Dark Mode: Off';

        if(savedApiChoice === 'openrouter') {
            openRouterKeyInput.style.display = 'block';
            openRouterModelSelect.style.display = 'block';
            geminiKeyInput.style.display = 'none';
            fetchOpenRouterModels(savedOpenRouterKey, (models, err) => {
                if(err){ console.error(err); return; }
                openRouterModelSelect.innerHTML = '';
                models.forEach(model => {
                    const option = document.createElement('option');
                    option.value = model.id;
                    option.textContent = model.id;
                    openRouterModelSelect.appendChild(option);
                });
                if(savedModel){ openRouterModelSelect.value = savedModel; }
            });
        } else if(savedApiChoice === 'gemini') {
            openRouterKeyInput.style.display = 'none';
            openRouterModelSelect.style.display = 'none';
            geminiKeyInput.style.display = 'block';
        }
        applyDarkMode();
        loadCustomization();

        // Load and display chat history
        const history = loadChatHistory();
        chatLog.innerHTML = '';
        history.forEach(msg => appendMessage(msg.role, msg.text, false));
    }
    function applyDarkMode() {
        let isDark = getSetting(STORAGE_KEYS.darkMode) === 'true';
        if (isDark) {
            container.classList.add('dark');
            header.classList.add('dark');
            darkModeToggleBtn.textContent = 'Dark Mode: On';
        } else {
            container.classList.remove('dark');
            header.classList.remove('dark');
            darkModeToggleBtn.textContent = 'Dark Mode: Off';
        }
    }
    loadSettings();

    // ======= CHAT HISTORY FUNCTIONS, TIMESTAMPS & TAP-TO-COPY ========
    function appendMessage(role, text, save = true) {
        const messageDiv = document.createElement('div');
        messageDiv.className = 'chatMessage ' + ((role === 'user') ? 'chatUser' : 'chatAI');
        // Save original text (without name or timestamp) for tap-to-copy.
        messageDiv.dataset.original = text;
        // Create a timestamp element.
        const timestamp = document.createElement('div');
        timestamp.className = 'chatTimestamp';
        timestamp.textContent = new Date().toLocaleTimeString();
        // Set message content based on role and customization.
        if(role === 'user'){
            messageDiv.innerHTML = `${customName}: ${text}`;
            if(customUserColor) messageDiv.style.backgroundColor = customUserColor;
        } else {
            messageDiv.innerHTML = 'AI: ' + formatResponse(text);
            if(customAIColor) messageDiv.style.backgroundColor = customAIColor;
        }
        messageDiv.appendChild(timestamp);
        // Tap-to-copy functionality: copy only the original message (without name/timestamp)
        messageDiv.addEventListener('click', () => {
            navigator.clipboard.writeText(messageDiv.dataset.original)
                .then(() => alert('Message copied to clipboard!'))
                .catch(err => alert('Failed to copy message: ' + err));
        });
        chatLog.appendChild(messageDiv);
        chatLog.scrollTop = chatLog.scrollHeight;
        if (save) {
            const history = loadChatHistory();
            history.push({ role, text });
            saveChatHistory(history);
        }
    }

    // ======= EXPORT FUNCTION (Download Chat) ========
    downloadBtn.addEventListener('click', () => {
        const choice = prompt("How would you like your chat downloaded?\nEnter 'json', 'md', or 'text'");
        if (!choice) return;
        const history = loadChatHistory();
        let content = '';
        if(choice.toLowerCase() === 'json'){
            content = JSON.stringify(history, null, 2);
            downloadFile(content, 'chat_history.json', 'application/json');
        } else if(choice.toLowerCase() === 'md'){
            history.forEach(msg => {
                if(msg.role === 'user'){
                    content += `**${customName}:** ${msg.text}\n\n`;
                } else {
                    content += `**AI:** ${msg.text}\n\n`;
                }
            });
            downloadFile(content, 'chat_history.md', 'text/markdown');
        } else if(choice.toLowerCase() === 'text'){
            history.forEach(msg => {
                content += (msg.role === 'user' ? `${customName}: ` : 'AI: ') + msg.text + '\n\n';
            });
            downloadFile(content, 'chat_history.txt', 'text/plain');
        } else {
            alert("Invalid choice. Please enter 'json', 'md', or 'text'.");
        }
    });
    function downloadFile(content, fileName, mimeType) {
        const blob = new Blob([content], { type: mimeType });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = fileName;
        a.click();
        URL.revokeObjectURL(url);
    }

    // ======= VOICE INPUT (Web Speech API) ========
    if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
        const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
        const recognition = new SpeechRecognition();
        recognition.lang = 'en-US';
        recognition.continuous = false;
        recognition.interimResults = false;
        voiceBtn.addEventListener('click', () => {
            voiceBtn.textContent = 'Listening...';
            recognition.start();
        });
        recognition.onresult = (event) => {
            const transcript = event.results[0][0].transcript;
            questionInput.value = transcript;
            voiceBtn.textContent = '🎤 Speak Your Question';
        };
        recognition.onerror = (event) => {
            console.error('Voice recognition error', event.error);
            voiceBtn.textContent = '🎤 Speak Your Question';
        };
    } else {
        voiceBtn.style.display = 'none';
    }

    // ======= GENERATE ANSWER LOGIC, AUTO-DETECT API, & RESPONSE SPEED ========
    generateButton.addEventListener('click', () => {
        const question = questionInput.value.trim();
        if (!question) { alert('Please enter a question.'); return; }
        questionInput.value = '';
        const formattedQuestion = `Please answer this question. Give me an explanation and breakdown of this question If it's a normal question like "hi" or "how to make a pizza" Answer normal without Breaking it down.  if it sounds like a Homework question you can break it down And make the answer be at the end of your message Also don't say I understand please just answer question: ${question}`;
        appendMessage('user', question);

        loadingBar.style.display = 'block';

        const messages = [{ role: 'user', content: formattedQuestion }];
        const apiChoice = selectAPI.value;
        function processResponse(err, answer, duration) {
            loadingBar.style.display = 'none';
            if (err) {
                if(apiChoice === 'openrouter' && geminiKeyInput.value.trim()) {
                    appendMessage('chatAI', 'OpenRouter failed; switching to Google Gemini due to availability.');
                    generateGeminiText(geminiKeyInput.value.trim(), 'gemini-2.0-pro', messages, 1.0, (err2, answer2, duration2) => {
                        if (err2) {
                            appendMessage('chatAI', err2 + ' (Response time: ' + duration2 + 's)');
                        } else {
                            appendMessage('chatAI', answer2 + ' (Response in ' + duration2 + 's)');
                        }
                    });
                } else if(apiChoice === 'gemini' && openRouterKeyInput.value.trim()) {
                    appendMessage('chatAI', 'Google Gemini failed; switching to OpenRouter due to availability.');
                    generateOpenRouterText(openRouterKeyInput.value.trim(), openRouterModelSelect.value || 'gpt-3.5-turbo', messages, (err2, answer2, duration2) => {
                        if (err2) {
                            appendMessage('chatAI', err2 + ' (Response time: ' + duration2 + 's)');
                        } else {
                            appendMessage('chatAI', answer2 + ' (Response in ' + duration2 + 's)');
                        }
                    });
                } else {
                    appendMessage('chatAI', err + (duration ? ' (Response time: ' + duration + 's)' : ''));
                }
            } else {
                appendMessage('chatAI', answer + ' (Response in ' + duration + 's)');
            }
        }
        if(apiChoice === 'openrouter') {
            const apiKey = openRouterKeyInput.value.trim();
            const model = openRouterModelSelect.value || 'gpt-3.5-turbo';
            generateOpenRouterText(apiKey, model, messages, processResponse);
        } else if(apiChoice === 'gemini') {
            const apiKey = geminiKeyInput.value.trim();
            const model = 'gemini-2.0-pro';
            generateGeminiText(apiKey, model, messages, 1.0, processResponse);
        }
    });

    GM_registerMenuCommand("Toggle Homework AI Chat Helper", () => {
        container.style.display = container.style.display === 'none' ? 'block' : 'none';
    });
})();