Chat UI Ctrl+Enter Sender

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

当前为 2024-08-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Chat UI Ctrl+Enter Sender
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  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&pattern=chatgpt.com
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_registerMenuCommand
  13. // ==/UserScript==
  14.  
  15. (() => {
  16. "use strict";
  17.  
  18. const defaultPatterns = [
  19. "https://chatgpt.com/*",
  20. "https://claude.ai/*",
  21. "https://gemini.google.com/*",
  22. "https://www.perplexity.ai/*",
  23. "https://www.bing.com/chat*",
  24. "https://duckduckgo.com/*ia=chat*"
  25. ];
  26.  
  27. let patterns = GM_getValue("patterns", defaultPatterns.slice());
  28.  
  29. GM_registerMenuCommand("Manage URL patterns", () => {
  30. showPatternSettingsUI(patterns);
  31. });
  32.  
  33. function showPatternSettingsUI(patterns) {
  34. const dialog = document.createElement("dialog");
  35. dialog.style.width = "400px";
  36. dialog.style.height = "500px";
  37. dialog.style.overflowY = "auto";
  38. dialog.innerHTML = `
  39. <form method="dialog" style="display: flex; flex-direction: column; height: 100%; padding: 10px;">
  40. <h2 style="margin: 0 0 10px 0;">URL Pattern Settings</h2>
  41. <div id="pattern-list" style="margin-bottom: 10px; flex-grow: 1; overflow-y: auto; border: 1px solid #ccc; padding: 10px;"></div>
  42. <div style="margin-bottom: 10px; display: flex;">
  43. <input id="new-pattern-input" type="text" value="${location.origin}/*" style="flex-grow: 1; margin-right: 5px; padding: 5px; color: black; background-color: white; border: 1px solid #ccc;">
  44. <button id="add-pattern" type="button" style="padding: 5px;">Add URL pattern</button>
  45. </div>
  46. <div style="margin-bottom: 10px;">
  47. <button id="reset-patterns" type="button" style="padding: 5px;">Reset to default</button>
  48. </div>
  49. <div style="text-align: right;">
  50. <button id="close-dialog" type="submit" value="close" style="padding: 5px;">Close</button>
  51. </div>
  52. </form>
  53. `;
  54.  
  55. document.body.appendChild(dialog);
  56. dialog.showModal();
  57.  
  58. dialog.querySelector("#add-pattern").addEventListener("click", () => {
  59. const newPatternInput = dialog.querySelector("#new-pattern-input").value;
  60. if (!newPatternInput) {
  61. alert("Invalid input")
  62. } else if (!patterns.includes(newPatternInput)) {
  63. patterns.push(newPatternInput);
  64. GM_setValue("patterns", patterns);
  65. alert(`URL pattern added: ${newPatternInput}`);
  66. updatePatternList(dialog, patterns);
  67. dialog.querySelector("#new-pattern-input").value = location.origin + "/*";
  68. } else {
  69. alert(`URL pattern already exists: ${newPatternInput}`);
  70. }
  71. });
  72.  
  73. dialog.querySelector("#reset-patterns").addEventListener("click", () => {
  74. if (confirm("Are you sure you want to reset URL patterns to default?")) {
  75. patterns = defaultPatterns.slice();
  76. GM_setValue("patterns", patterns);
  77. alert("URL patterns have been reset to default.");
  78. updatePatternList(dialog, patterns);
  79. dialog.querySelector("#new-pattern-input").value = location.origin + "/*";
  80. }
  81. });
  82.  
  83. dialog.addEventListener("close", () => {
  84. dialog.remove();
  85. });
  86.  
  87. updatePatternList(dialog, patterns);
  88. }
  89.  
  90. function createPatternItemHTML(pattern) {
  91. return `
  92. <div class="pattern-item" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
  93. <span>${pattern}</span>
  94. <div>
  95. <button class="edit-pattern" data-pattern="${pattern}" style="margin-right: 5px;">Edit</button>
  96. <button class="remove-pattern" data-pattern="${pattern}">Remove</button>
  97. </div>
  98. </div>
  99. `;
  100. }
  101.  
  102. function updatePatternList(dialog, patterns) {
  103. const patternList = dialog.querySelector("#pattern-list");
  104. patternList.innerHTML = patterns.map(pattern => createPatternItemHTML(pattern)).join("");
  105.  
  106. patternList.querySelectorAll(".remove-pattern").forEach(button => {
  107. button.addEventListener("click", (event) => {
  108. const patternToRemove = event.target.getAttribute("data-pattern");
  109. if (confirm(`Are you sure you want to remove ${patternToRemove}?`)) {
  110. const index = patterns.indexOf(patternToRemove);
  111. if (index !== -1) {
  112. patterns.splice(index, 1);
  113. GM_setValue("patterns", patterns);
  114. alert(`URL pattern removed: ${patternToRemove}`);
  115. } else {
  116. alert(`URL pattern not found: ${patternToRemove}`);
  117. }
  118. }
  119. updatePatternList(dialog, patterns);
  120. });
  121. });
  122.  
  123. patternList.querySelectorAll(".edit-pattern").forEach(button => {
  124. button.addEventListener("click", (event) => {
  125. const patternToEdit = event.target.getAttribute("data-pattern");
  126. const newPatternName = prompt(`Edit URL pattern: ${patternToEdit}`, patternToEdit);
  127. if (newPatternName === null) {
  128. updatePatternList(dialog, patterns);
  129. return;
  130. }
  131. if (!newPatternName) {
  132. alert("Invalid input");
  133. } else if (!patterns.includes(newPatternName)) {
  134. const index = patterns.indexOf(patternToEdit);
  135. if (index !== -1) {
  136. patterns[index] = newPatternName;
  137. GM_setValue("patterns", patterns);
  138. alert(`URL pattern edited: ${patternToEdit} to ${newPatternName}`);
  139. } else {
  140. alert(`URL pattern not found: ${patternToEdit}`);
  141. }
  142. } else {
  143. alert(`URL pattern already exists: ${newPatternName}`);
  144. }
  145. updatePatternList(dialog, patterns);
  146. });
  147. });
  148. }
  149.  
  150. function matchPattern(url, pattern) {
  151. let regexPattern = pattern
  152. .replace(/[.+^${}()|[\]\\]/g, "\\$&")
  153. .replace(/\*/g, ".*")
  154. .replace(/\?/g, ".")
  155. .replace(/\|/g, "|");
  156.  
  157. return new RegExp(`^${regexPattern}$`).test(url);
  158.  
  159. }
  160.  
  161. if (!patterns.some(pattern => matchPattern(location.href, pattern))) return;
  162.  
  163. console.log("Chat UI Ctrl+Enter Sender Enabled");
  164.  
  165. window.addEventListener("keydown", e => {
  166. if (e.key !== "Enter" || e.ctrlKey || e.shiftKey) return;
  167. let target = e.composedPath ? e.composedPath()[0] || e.target : e.target;
  168. if (/INPUT|TEXTAREA|SELECT|LABEL/.test(target.tagName) || target.getAttribute && target.getAttribute("contenteditable") === "true") {
  169. e.stopPropagation();
  170. }
  171. }, true);
  172.  
  173. window.addEventListener("keypress", e => {
  174. if (e.key !== "Enter" || e.ctrlKey || e.shiftKey) return;
  175. let target = e.composedPath ? e.composedPath()[0] || e.target : e.target;
  176. if (/INPUT|TEXTAREA|SELECT|LABEL/.test(target.tagName) || target.getAttribute && target.getAttribute("contenteditable") === "true") {
  177. e.stopPropagation();
  178. }
  179. }, true);
  180. })();