Chat UI Ctrl+Enter Sender

Send with Ctrl+Enter in ChatGPT, Claude, Gemini, Copilot, Perplexity, and others.

目前为 2024-06-30 提交的版本。查看 最新版本

// ==UserScript==
// @name         Chat UI Ctrl+Enter Sender
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Send with Ctrl+Enter in ChatGPT, Claude, Gemini, Copilot, Perplexity, and others.
// @author       Chippppp
// @license      MIT
// @match        *://*/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chatgpt.com
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(() => {
    "use strict";

    const defaultDomains = [
        "https://chatgpt.com",
        "https://claude.ai",
        "https://gemini.google.com",
        "https://www.bing.com",
        "https://www.perplexity.ai"
    ];

    let domains = GM_getValue("domains", defaultDomains.slice());

    GM_registerMenuCommand("Manage domains", () => {
        showDomainSettingsUI(domains);
    });

    function showDomainSettingsUI(domains) {
        const dialog = document.createElement("dialog");
        dialog.style.width = "400px";
        dialog.style.height = "500px";
        dialog.style.overflowY = "auto";
        dialog.innerHTML = `
            <form method="dialog" style="display: flex; flex-direction: column; height: 100%; padding: 10px;">
                <h2 style="margin: 0 0 10px 0;">Domain Settings</h2>
                <div id="domain-list" style="margin-bottom: 10px; flex-grow: 1; overflow-y: auto; border: 1px solid #ccc; padding: 10px;">
                    ${domains.map(domain => createDomainItemHTML(domain)).join("")}
                </div>
                <div style="margin-bottom: 10px; display: flex;">
                    <input id="new-domain-input" type="text" value="${location.origin}" style="flex-grow: 1; margin-right: 5px; padding: 5px; color: black; background-color: white; border: 1px solid #ccc;">
                    <button id="add-domain" type="button" style="padding: 5px;">Add domain</button>
                </div>
                <div style="margin-bottom: 10px;">
                    <button id="reset-domains" type="button" style="padding: 5px;">Reset to default</button>
                </div>
                <div style="text-align: right;">
                    <button id="close-dialog" type="submit" value="close" style="padding: 5px;">Close</button>
                </div>
            </form>
        `;

        document.body.appendChild(dialog);
        dialog.showModal();

        dialog.querySelector("#add-domain").addEventListener("click", () => {
            const newDomainInput = dialog.querySelector("#new-domain-input").value;
            if (!newDomainInput) {
                alert("Invalid input")
            } else if (!domains.includes(newDomainInput)) {
                domains.push(newDomainInput);
                GM_setValue("domains", domains);
                alert(`Domain added: ${newDomainInput}`);
                updateDomainList(dialog, domains);
                dialog.querySelector("#new-domain-input").value = location.origin;
            } else {
                alert(`Domain already exists: ${newDomainInput}`);
            }
        });

        dialog.querySelector("#reset-domains").addEventListener("click", () => {
            if (confirm("Are you sure you want to reset domains to default?")) {
                domains = defaultDomains.slice();
                GM_setValue("domains", domains);
                alert("Domains have been reset to default.");
                updateDomainList(dialog, domains);
                dialog.querySelector("#new-domain-input").value = location.origin;
            }
        });

        dialog.addEventListener("close", () => {
            dialog.remove();
        });

        updateDomainList(dialog, domains);
    }

    function createDomainItemHTML(domain) {
        return `
            <div class="domain-item" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
                <span>${domain}</span>
                <div>
                    <button class="edit-domain" data-domain="${domain}" style="margin-right: 5px;">Edit</button>
                    <button class="remove-domain" data-domain="${domain}">Remove</button>
                </div>
            </div>
        `;
    }

    function updateDomainList(dialog, domains) {
        const domainList = dialog.querySelector("#domain-list");
        domainList.innerHTML = domains.map(domain => createDomainItemHTML(domain)).join("");

        domainList.querySelectorAll(".remove-domain").forEach(button => {
            button.addEventListener("click", (event) => {
                const domainToRemove = event.target.getAttribute("data-domain");
                if (confirm(`Are you sure you want to remove ${domainToRemove}?`)) {
                    const index = domains.indexOf(domainToRemove);
                    if (index !== -1) {
                        domains.splice(index, 1);
                        GM_setValue("domains", domains);
                        alert(`Domain removed: ${domainToRemove}`);
                    } else {
                        alert(`Domain not found: ${domainToRemove}`);
                    }
                }
                updateDomainList(dialog, domains);
            });
        });

        domainList.querySelectorAll(".edit-domain").forEach(button => {
            button.addEventListener("click", (event) => {
                const domainToEdit = event.target.getAttribute("data-domain");
                const newDomainName = prompt(`Edit domain: ${domainToEdit}`, domainToEdit);
                if (newDomainName === null) {
                    updateDomainList(dialog, domains);
                    return;
                }
                if (!newDomainName) {
                    alert("Invalid input");
                } else if (!domains.includes(newDomainName)) {
                    const index = domains.indexOf(domainToEdit);
                    if (index !== -1) {
                        domains[index] = newDomainName;
                        GM_setValue("domains", domains);
                        alert(`Domain edited: ${domainToEdit} to ${newDomainName}`);
                    } else {
                        alert(`Domain not found: ${domainToEdit}`);
                    }
                } else {
                    alert(`Domain already exists: ${newDomainName}`);
                }
                updateDomainList(dialog, domains);
            });
        });
    }

    if (!domains.some(domain => location.origin === domain)) return;

    console.log("Chat UI Ctrl+Enter Sender Enabled");

    window.addEventListener("keydown", e => {
        if (!e.key === "Enter" || e.ctrlKey) return;
        let target = e.composedPath ? e.composedPath()[0] || e.target : e.target;
        if (/INPUT|TEXTAREA|SELECT|LABEL/.test(target.tagName) || target.getAttribute && target.getAttribute("contenteditable") === "true") {
            event.stopPropagation();
        }
    }, true);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址