Ask AI

Search selected text across AI tools (No popup warnings, no double-tabs)

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// @license      MIT
// ==UserScript==
// @name         Ask AI
// @namespace    http://tampermonkey.net/
// @version      1.0.4
// @description  Search selected text across AI tools (No popup warnings, no double-tabs)
// @match        *://*/*
// @run-at       document-end
// @grant        GM_setClipboard
// ==/UserScript==

(function () {
    "use strict";

    const buttonId = "ask-ai-button";
    const menuId = "ask-ai-menu";
    const promptId = "ask-ai-prompt";

    let mouseX = 0, mouseY = 0;
    let scrollBaseY = 0;
    let selectionTimeout = null;
    let activeSelectionText = "";
    let menuFiring = false;

    const AIs = [
        { name: "Perplexity",   url: t => `https://www.perplexity.ai/search?q=${t}` },
        { name: "Google Search", url: t => `https://www.google.com/search?q=${t}&udm=50` },
        { name: "DeepSeek",     url: () => `https://chat.deepseek.com/`, copy: true },
        { name: "Claude",       url: t => `https://claude.ai/new?q=${t}` },
        { name: "Grok",         url: t => `https://grok.com/?q=${t}` },
        { name: "ChatGPT",      url: t => `https://chatgpt.com/?q=${t}` },
        { name: "Gemini",       url: () => `https://gemini.google.com/app`, copy: true },
    ];

    const PRESETS = [
        "Explain", "Elaborate", "ELI5", "Synonyms",
        "Summarize", "Examples", "Translate", "Define",
        "Etymology", "Pros & Cons", "Counterarguments", "Fix grammar",
        "Rewrite formally", "Rewrite casually", "Shorter", "Longer",
        "Fact-check", "Historical context", "Real-world examples", "Compare & contrast",
        "Key takeaways", "Quiz me on this",
    ];

    function trackMouse(e) {
        mouseX = e.clientX;
        mouseY = e.clientY;
    }

    function handleKeyDown(e) {
        const tag = e.target && e.target.tagName;
        if (tag === "INPUT" || tag === "TEXTAREA" || (e.target && e.target.isContentEditable)) return;

        if (e.altKey && e.key.toLowerCase() === "q") {
            e.preventDefault();
            const text = getSelectionText();
            if (!text) return;
            const existing = document.getElementById(menuId);
            if (existing) { removeMenu(); return; }
            const bounds = getSelectionBounds();
            const px = bounds ? bounds.x + 6 : mouseX + 12;
            const py = bounds ? bounds.y - 6 : mouseY + 12;
            showMenu(px, py, text);
            return;
        }

        const digitMatch = e.code && e.code.match(/^Digit([1-7])$/);
        if (digitMatch && (e.shiftKey || document.getElementById(menuId))) {
            const index = parseInt(digitMatch[1], 10) - 1;
            const ai = AIs[index];
            const text = getSelectionText() || activeSelectionText;
            if (!ai || !text) return;

            if (e.shiftKey) {
                e.preventDefault();
                const bounds = getSelectionBounds();
                const px = bounds ? bounds.x + 6 : mouseX + 12;
                const py = bounds ? bounds.y - 6 : mouseY + 12;
                showPrompt(ai, text, px, py);
            } else if (document.getElementById(menuId)) {
                openAI(ai, text);
                removeMenu();
                hideButton();
            }
        }
    }

    function handleClick() {
        removeMenu();
        removePrompt();
        activeSelectionText = "";
        setTimeout(createButton, 0);
    }

    function handleScroll() {
        if (Math.abs(window.scrollY - scrollBaseY) > 50) {
            removeMenu();
            removePrompt();
            hideButton();
        }
    }

    function handleSelectionChange() {
        clearTimeout(selectionTimeout);
        selectionTimeout = setTimeout(createButton, 120);
    }

    function cleanup() {
        document.removeEventListener("mousemove", trackMouse);
        document.removeEventListener("keydown", handleKeyDown);
        document.removeEventListener("click", handleClick);
        window.removeEventListener("scroll", handleScroll);
        document.removeEventListener("selectionchange", handleSelectionChange);
        window.removeEventListener("load", createButton);
        clearTimeout(selectionTimeout);
        removeMenu();
        removePrompt();
        hideButton();
    }

    function getSelectionText() {
        const sel = document.getSelection();
        if (!sel) return null;
        const text = sel.toString().trim();
        return (!text || text.length > 1000) ? null : text;
    }

    function getSelectionBounds() {
        const sel = document.getSelection();
        if (!sel || sel.rangeCount === 0) return null;
        try {
            const range = sel.getRangeAt(0);
            const rect = range.getBoundingClientRect();
            return (rect.width > 0 || rect.height > 0) ? { x: rect.right, y: rect.top } : null;
        } catch (_) {
            return null;
        }
    }

    function removeMenu() {
        const m = document.getElementById(menuId);
        if (m) m.remove();
        activeSelectionText = "";
    }

    function removePrompt() {
        const p = document.getElementById(promptId);
        if (p) p.remove();
        activeSelectionText = "";
    }

    function hideButton() {
        const btn = document.getElementById(buttonId);
        if (btn) btn.style.display = "none";
    }

    function clampToViewport(el, preferredX, preferredY) {
        requestAnimationFrame(() => {
            if (!el.parentNode) return;
            const pad = 8;
            const rect = el.getBoundingClientRect();
            let x = Math.max(pad, Math.min(preferredX, window.innerWidth - rect.width - pad));
            let y = Math.max(pad, Math.min(preferredY, window.innerHeight - rect.height - pad));
            el.style.left = `${x}px`;
            el.style.top = `${y}px`;
        });
    }

    function blockMiddleClick(el) {
        ["mousedown", "mouseup", "click", "auxclick"].forEach(type => {
            el.addEventListener(type, (e) => {
                if (e.button === 1) {
                    e.preventDefault();
                    e.stopPropagation();
                }
            });
        });
    }

    async function copyText(text) {
        if (typeof GM_setClipboard === "function") {
            try {
                GM_setClipboard(text, "text");
                return true;
            } catch (_) {}
        }

        if (navigator.clipboard?.writeText) {
            try {
                await navigator.clipboard.writeText(text);
                return true;
            } catch (_) {}
        }

        try {
            const ta = document.createElement("textarea");
            ta.value = text;
            ta.style.cssText = "position:fixed;top:0;left:0;opacity:0;pointer-events:none;";
            document.body.appendChild(ta);
            ta.select();
            const success = document.execCommand("copy");
            document.body.removeChild(ta);
            return success;
        } catch (_) {
            return false;
        }
    }

    // --- SIMPLE openAI: Only opens in new tab, no fallbacks, no alerts ---
    function openAI(ai, text) {
        if (!text) return;
        if (ai.copy) {
            copyText(text).catch(() => {}); // Silently try to copy (no alerts)
        }
        const url = ai.copy ? ai.url() : ai.url(encodeURIComponent(text));
        window.open(url, "_blank", "noopener,noreferrer");
    }

    function showPrompt(ai, text, anchorX, anchorY) {
        removePrompt();
        scrollBaseY = window.scrollY;
        activeSelectionText = text;

        let includeContext = false;
        let firing = false;

        const box = document.createElement("div");
        box.id = promptId;
        box.tabIndex = -1;
        box.setAttribute("role", "dialog");
        box.setAttribute("aria-label", "Customize AI prompt");
        Object.assign(box.style, {
            position: "fixed",
            zIndex: "9999",
            left: `${anchorX}px`,
            top: `${anchorY}px`,
            backgroundColor: "#111",
            border: "1px solid #333",
            borderRadius: "10px",
            padding: "10px",
            boxShadow: "0 4px 16px rgba(0,0,0,0.5)",
            fontFamily: "system-ui, sans-serif",
            fontSize: "11px",
            width: "260px",
            userSelect: "none",
        });

        box.addEventListener("click", (e) => {
            e.stopPropagation();
            if (e.target !== input && !e.target.closest("button, input")) box.focus();
        });
        blockMiddleClick(box);

        const contextToggle = document.createElement("div");
        contextToggle.textContent = "🌐 Context: OFF (press C)";
        contextToggle.setAttribute("role", "button");
        contextToggle.setAttribute("aria-label", "Toggle page context");
        Object.assign(contextToggle.style, {
            fontSize: "10px", color: "#888", marginBottom: "6px",
            cursor: "pointer", textAlign: "center", padding: "3px",
            borderRadius: "4px", transition: "color 0.15s",
        });

        const refreshContextLabel = () => {
            contextToggle.textContent = includeContext ? "🌐 Context: ON (press C)" : "🌐 Context: OFF (press C)";
            contextToggle.style.color = includeContext ? "#4fc" : "#888";
        };

        contextToggle.addEventListener("mouseenter", () => contextToggle.style.color = "#ccc");
        contextToggle.addEventListener("mouseleave", refreshContextLabel);
        contextToggle.addEventListener("click", (e) => {
            e.stopPropagation();
            includeContext = !includeContext;
            refreshContextLabel();
        });
        box.appendChild(contextToggle);

        const grid = document.createElement("div");
        Object.assign(grid.style, {
            display: "grid", gridTemplateColumns: "1fr 1fr", gap: "4px", marginBottom: "8px"
        });
        PRESETS.forEach(preset => {
            const btn = document.createElement("button");
            btn.textContent = preset;
            btn.setAttribute("aria-label", `Use preset: ${preset}`);
            Object.assign(btn.style, {
                padding: "4px 6px", borderRadius: "5px", border: "1px solid #333",
                backgroundColor: "#1a1a1a", color: "#ccc", cursor: "pointer",
                fontSize: "11px", textAlign: "left", transition: "background 0.1s"
            });
            btn.addEventListener("mouseenter", () => btn.style.backgroundColor = "#2a2a2a");
            btn.addEventListener("mouseleave", () => btn.style.backgroundColor = "#1a1a1a");
            btn.addEventListener("click", (e) => {
                e.stopPropagation();
                input.value = preset + ": ";
                input.focus();
                input.setSelectionRange(input.value.length, input.value.length);
            });
            grid.appendChild(btn);
        });

        const input = document.createElement("input");
        input.type = "text";
        input.placeholder = "Custom prefix...";
        input.setAttribute("aria-label", "Prompt prefix");
        Object.assign(input.style, {
            width: "100%", padding: "6px 8px", borderRadius: "6px", border: "1px solid #333",
            backgroundColor: "#1a1a1a", color: "#eee", fontSize: "12px", outline: "none",
            boxSizing: "border-box", marginBottom: "8px"
        });
        blockMiddleClick(input);

        const send = document.createElement("button");
        send.textContent = "Send";
        send.setAttribute("aria-label", "Send to AI");
        Object.assign(send.style, {
            width: "100%", padding: "6px", borderRadius: "6px", border: "none",
            backgroundColor: "#222", color: "#eee", cursor: "pointer", fontSize: "12px"
        });

        function fire() {
            if (firing) return;
            firing = true;
            const prefix = input.value.trim();
            let query = prefix ? `${prefix}: ${text}` : text;
            if (includeContext) {
                query += `\n\n---\nSource: "${document.title}"\n${location.href}`;
            }
            openAI(ai, query);
            removePrompt();
            removeMenu();
            hideButton();
            setTimeout(() => { firing = false; }, 500);
        }

        send.addEventListener("click", (e) => { e.stopPropagation(); fire(); });
        input.addEventListener("keydown", (e) => {
            if (e.key === "Enter") { e.preventDefault(); fire(); }
            else if (e.key === "Escape") { e.preventDefault(); removePrompt(); }
            e.stopPropagation();
        });

        box.addEventListener("keydown", (e) => {
            if (e.key === "c" && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && document.activeElement !== input) {
                e.preventDefault();
                e.stopPropagation();
                includeContext = !includeContext;
                refreshContextLabel();
            }
        }, true);

        box.append(grid, input, send);
        document.body.appendChild(box);
        clampToViewport(box, anchorX, anchorY);
        input.focus();
    }

    function showMenu(anchorX, anchorY, text) {
        removeMenu();
        scrollBaseY = window.scrollY;
        activeSelectionText = text || getSelectionText();
        if (!activeSelectionText) return;

        const menu = document.createElement("div");
        menu.id = menuId;
        menu.setAttribute("role", "menu");
        menu.setAttribute("aria-label", "Select AI tool");
        Object.assign(menu.style, {
            position: "fixed", zIndex: "9999", left: `${anchorX}px`, top: `${anchorY}px`,
            backgroundColor: "#111", border: "1px solid #333", borderRadius: "8px",
            padding: "4px", boxShadow: "0 4px 16px rgba(0,0,0,0.5)",
            fontFamily: "system-ui, sans-serif", fontSize: "12px", minWidth: "150px", userSelect: "none"
        });

        menu.addEventListener("click", (e) => e.stopPropagation());
        blockMiddleClick(menu);

        AIs.forEach((ai, i) => {
            const item = document.createElement("div");
            item.textContent = `${i + 1}. ${ai.name}${ai.copy ? " 📋" : ""}`;
            item.setAttribute("role", "menuitem");
            item.setAttribute("tabindex", "0");
            item.setAttribute("aria-label", `Open ${ai.name}`);
            Object.assign(item.style, {
                padding: "5px 10px", borderRadius: "5px", color: "#eee",
                cursor: "pointer", transition: "background 0.1s", whiteSpace: "nowrap"
            });
            item.addEventListener("mouseenter", () => item.style.backgroundColor = "#222");
            item.addEventListener("mouseleave", () => item.style.backgroundColor = "transparent");

            item.addEventListener("click", (e) => {
                e.stopPropagation();
                if (menuFiring) return;
                menuFiring = true;
                const currentText = getSelectionText() || activeSelectionText;
                if (currentText) openAI(ai, currentText);
                removeMenu();
                hideButton();
                setTimeout(() => { menuFiring = false; }, 500);
            });

            item.addEventListener("auxclick", (e) => {
                if (e.button !== 1) return;
                e.preventDefault();
                e.stopPropagation();
                const r = item.getBoundingClientRect();
                showPrompt(ai, activeSelectionText, r.right + 6, r.top);
            });

            menu.appendChild(item);
        });

        document.body.appendChild(menu);
        clampToViewport(menu, anchorX, anchorY);
    }

    function createButton() {
        let btn = document.getElementById(buttonId);
        if (!btn) {
            btn = document.createElement("button");
            btn.id = buttonId;
            btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`;
            btn.title = "Ask AI (Alt+Q, Shift+1-7 for prompt)";
            btn.setAttribute("aria-label", "Ask AI");
            Object.assign(btn.style, {
                position: "fixed", zIndex: "9999", width: "32px", height: "32px",
                border: "none", borderRadius: "50%", backgroundColor: "#111",
                cursor: "pointer", outline: "none", display: "none",
                alignItems: "center", justifyContent: "center",
                boxShadow: "0 2px 8px rgba(0,0,0,0.5)", transition: "background 0.15s"
            });
            btn.addEventListener("mouseenter", () => btn.style.backgroundColor = "#333");
            btn.addEventListener("mouseleave", () => btn.style.backgroundColor = "#111");
            btn.addEventListener("click", (e) => {
                e.stopPropagation();
                if (document.getElementById(menuId)) { removeMenu(); return; }
                const r = btn.getBoundingClientRect();
                showMenu(r.right + 6, r.top);
            });
            blockMiddleClick(btn);
            document.body.appendChild(btn);
        }

        const text = getSelectionText();
        if (text) {
            const bounds = getSelectionBounds();
            const posX = bounds ? bounds.x + 6 : mouseX + 12;
            const posY = bounds ? bounds.y - 6 : mouseY + 12;
            btn.style.left = `${Math.max(8, Math.min(posX, window.innerWidth - 40))}px`;
            btn.style.top = `${Math.max(8, Math.min(posY, window.innerHeight - 40))}px`;
            btn.style.display = "flex";
        } else {
            btn.style.display = "none";
            removeMenu();
        }
    }

    document.addEventListener("mousemove", trackMouse);
    document.addEventListener("keydown", handleKeyDown);
    document.addEventListener("click", handleClick);
    window.addEventListener("scroll", handleScroll, { passive: true });
    document.addEventListener("selectionchange", handleSelectionChange);
    window.addEventListener("load", createButton);
    window.addEventListener("beforeunload", cleanup);
})();