Chat UI Ctrl+Enter Sender

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

当前为 2024-07-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Chat UI Ctrl+Enter Sender
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description Send with Ctrl+Enter in ChatGPT, Claude, Gemini, Copilot, Perplexity, and others.
  6. // @author Chippppp
  7. // @license MIT
  8. // @match *://*/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=chatgpt.com
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_registerMenuCommand
  13. // ==/UserScript==
  14.  
  15. (() => {
  16. "use strict";
  17.  
  18. const defaultDomains = [
  19. "https://chatgpt.com",
  20. "https://claude.ai",
  21. "https://gemini.google.com",
  22. "https://www.bing.com",
  23. "https://www.perplexity.ai"
  24. ];
  25.  
  26. let domains = GM_getValue("domains", defaultDomains.slice());
  27.  
  28. GM_registerMenuCommand("Manage domains", () => {
  29. showDomainSettingsUI(domains);
  30. });
  31.  
  32. function showDomainSettingsUI(domains) {
  33. const dialog = document.createElement("dialog");
  34. dialog.style.width = "400px";
  35. dialog.style.height = "500px";
  36. dialog.style.overflowY = "auto";
  37. dialog.innerHTML = `
  38. <form method="dialog" style="display: flex; flex-direction: column; height: 100%; padding: 10px;">
  39. <h2 style="margin: 0 0 10px 0;">Domain Settings</h2>
  40. <div id="domain-list" style="margin-bottom: 10px; flex-grow: 1; overflow-y: auto; border: 1px solid #ccc; padding: 10px;"></div>
  41. <div style="margin-bottom: 10px; display: flex;">
  42. <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;">
  43. <button id="add-domain" type="button" style="padding: 5px;">Add domain</button>
  44. </div>
  45. <div style="margin-bottom: 10px;">
  46. <button id="reset-domains" type="button" style="padding: 5px;">Reset to default</button>
  47. </div>
  48. <div style="text-align: right;">
  49. <button id="close-dialog" type="submit" value="close" style="padding: 5px;">Close</button>
  50. </div>
  51. </form>
  52. `;
  53.  
  54. document.body.appendChild(dialog);
  55. dialog.showModal();
  56.  
  57. dialog.getElementById("add-domain").addEventListener("click", () => {
  58. const newDomainInput = dialog.querySelector("#new-domain-input").value;
  59. if (!newDomainInput) {
  60. alert("Invalid input")
  61. } else if (!domains.includes(newDomainInput)) {
  62. domains.push(newDomainInput);
  63. GM_setValue("domains", domains);
  64. alert(`Domain added: ${newDomainInput}`);
  65. updateDomainList(dialog, domains);
  66. dialog.querySelector("#new-domain-input").value = location.origin;
  67. } else {
  68. alert(`Domain already exists: ${newDomainInput}`);
  69. }
  70. });
  71.  
  72. dialog.getElementById("reset-domains").addEventListener("click", () => {
  73. if (confirm("Are you sure you want to reset domains to default?")) {
  74. domains = defaultDomains.slice();
  75. GM_setValue("domains", domains);
  76. alert("Domains have been reset to default.");
  77. updateDomainList(dialog, domains);
  78. dialog.querySelector("#new-domain-input").value = location.origin;
  79. }
  80. });
  81.  
  82. dialog.addEventListener("close", () => {
  83. dialog.remove();
  84. });
  85.  
  86. updateDomainList(dialog, domains);
  87. }
  88.  
  89. function createDomainItemHTML(domain) {
  90. return `
  91. <div class="domain-item" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
  92. <span>${domain}</span>
  93. <div>
  94. <button class="edit-domain" data-domain="${domain}" style="margin-right: 5px;">Edit</button>
  95. <button class="remove-domain" data-domain="${domain}">Remove</button>
  96. </div>
  97. </div>
  98. `;
  99. }
  100.  
  101. function updateDomainList(dialog, domains) {
  102. const domainList = dialog.querySelector("#domain-list");
  103. domainList.innerHTML = domains.map(domain => createDomainItemHTML(domain)).join("");
  104.  
  105. domainList.querySelectorAll(".remove-domain").forEach(button => {
  106. button.addEventListener("click", (event) => {
  107. const domainToRemove = event.target.getAttribute("data-domain");
  108. if (confirm(`Are you sure you want to remove ${domainToRemove}?`)) {
  109. const index = domains.indexOf(domainToRemove);
  110. if (index !== -1) {
  111. domains.splice(index, 1);
  112. GM_setValue("domains", domains);
  113. alert(`Domain removed: ${domainToRemove}`);
  114. } else {
  115. alert(`Domain not found: ${domainToRemove}`);
  116. }
  117. }
  118. updateDomainList(dialog, domains);
  119. });
  120. });
  121.  
  122. domainList.querySelectorAll(".edit-domain").forEach(button => {
  123. button.addEventListener("click", (event) => {
  124. const domainToEdit = event.target.getAttribute("data-domain");
  125. const newDomainName = prompt(`Edit domain: ${domainToEdit}`, domainToEdit);
  126. if (newDomainName === null) {
  127. updateDomainList(dialog, domains);
  128. return;
  129. }
  130. if (!newDomainName) {
  131. alert("Invalid input");
  132. } else if (!domains.includes(newDomainName)) {
  133. const index = domains.indexOf(domainToEdit);
  134. if (index !== -1) {
  135. domains[index] = newDomainName;
  136. GM_setValue("domains", domains);
  137. alert(`Domain edited: ${domainToEdit} to ${newDomainName}`);
  138. } else {
  139. alert(`Domain not found: ${domainToEdit}`);
  140. }
  141. } else {
  142. alert(`Domain already exists: ${newDomainName}`);
  143. }
  144. updateDomainList(dialog, domains);
  145. });
  146. });
  147. }
  148.  
  149. if (!domains.some(domain => location.origin === domain)) return;
  150.  
  151. console.log("Chat UI Ctrl+Enter Sender Enabled");
  152.  
  153. window.addEventListener("keydown", e => {
  154. if (!e.key === "Enter" || e.ctrlKey) return;
  155. let target = e.composedPath ? e.composedPath()[0] || e.target : e.target;
  156. if (/INPUT|TEXTAREA|SELECT|LABEL/.test(target.tagName) || target.getAttribute && target.getAttribute("contenteditable") === "true") {
  157. event.stopPropagation();
  158. }
  159. }, true);
  160. })();