Gemini Keyboard Shortcuts

This userscript enhances your Gemini experience by adding a wide range of keyboard shortcuts for streamlined navigation and interaction, as well as cleaning up Gemini's UI.

  1. // ==UserScript==
  2. // @name Gemini Keyboard Shortcuts
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2.5
  5. // @description This userscript enhances your Gemini experience by adding a wide range of keyboard shortcuts for streamlined navigation and interaction, as well as cleaning up Gemini's UI.
  6. // @license MIT
  7. // @author Henry Getz
  8. // @match https://gemini.google.com/u/*
  9. // @match https://gemini.google.com/app*
  10. // @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE0IDI4QzE0IDI2LjA2MzMgMTMuNjI2NyAyNC4yNDMzIDEyLjg4IDIyLjU0QzEyLjE1NjcgMjAuODM2NyAxMS4xNjUgMTkuMzU1IDkuOTA1IDE4LjA5NUM4LjY0NSAxNi44MzUgNy4xNjMzMyAxNS44NDMzIDUuNDYgMTUuMTJDMy43NTY2NyAxNC4zNzMzIDEuOTM2NjcgMTQgMCAxNEMxLjkzNjY3IDE0IDMuNzU2NjcgMTMuNjM4MyA1LjQ2IDEyLjkxNUM3LjE2MzMzIDEyLjE2ODMgOC42NDUgMTEuMTY1IDkuOTA1IDkuOTA1QzExLjE2NSA4LjY0NSAxMi4xNTY3IDcuMTYzMzMgMTIuODggNS40NkMxMy42MjY3IDMuNzU2NjcgMTQgMS45MzY2NyAxNCAwQzE0IDEuOTM2NjcgMTQuMzYxNyAzLjc1NjY3IDE1LjA4NSA1LjQ2QzE1LjgzMTcgNy4xNjMzMyAxNi44MzUgOC42NDUgMTguMDk1IDkuOTA1QzE5LjM1NSAxMS4xNjUgMjAuODM2NyAxMi4xNjgzIDIyLjU0IDEyLjkxNUMyNC4yNDMzIDEzLjYzODMgMjYuMDYzMyAxNCAyOCAxNEMyNi4wNjMzIDE0IDI0LjI0MzMgMTQuMzczMyAyMi41NCAxNS4xMkMyMC44MzY3IDE1Ljg0MzMgMTkuMzU1IDE2LjgzNSAxOC4wOTUgMTguMDk1QzE2LjgzNSAxOS4zNTUgMTUuODMxNyAyMC44MzY3IDE1LjA4NSAyMi41NEMxNC4zNjE3IDI0LjI0MzMgMTQgMjYuMDYzMyAxNCAyOFoiIGZpbGw9InVybCgjcGFpbnQwX3JhZGlhbF8xNjc3MV81MzIxMikiLz4KPGRlZnM+CjxyYWRpYWxHcmFkaWVudCBpZD0icGFpbnQwX3JhZGlhbF8xNjc3MV81MzIxMiIgY3g9IjAiIGN5PSIwIiByPSIxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSgyLjc3ODc2IDExLjM3OTUpIHJvdGF0ZSgxOC42ODMyKSBzY2FsZSgyOS44MDI1IDIzOC43MzcpIj4KPHN0b3Agb2Zmc2V0PSIwLjA2NzEyNDYiIHN0b3AtY29sb3I9IiM5MTY4QzAiLz4KPHN0b3Agb2Zmc2V0PSIwLjM0MjU1MSIgc3RvcC1jb2xvcj0iIzU2ODREMSIvPgo8c3RvcCBvZmZzZXQ9IjAuNjcyMDc2IiBzdG9wLWNvbG9yPSIjMUJBMUUzIi8+CjwvcmFkaWFsR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==
  11. // @supportURL https://github.com/HenryGetz/GeminiPilot/issues
  12. // @grant none
  13. // @run-at document-start
  14. // ==/UserScript==
  15. /*
  16.  
  17. #New Feature: URL Parameters!
  18.  
  19. Empower your automation workflows! Directly open Gemini with pre-populated prompts by using query parameters in the URL (e.g., `https://gemini.google.com/app?q=YOURTESTPROMPT`).
  20.  
  21.  
  22. # Included Keyboard Shortcuts:
  23.  
  24.  
  25. ## Chat Management
  26.  
  27. | Shortcut (Mac/Windows) | Action |
  28. |:--------------------------:|:--------------:|
  29. | ⌘/Ctrl + Shift + O | Open new chat |
  30. | ⌘/Ctrl + Shift + Backspace | Delete chat |
  31. | ⌘/Ctrl + Shift + F | Toggle sidebar |
  32. | ⌥/Alt + 1-9 | Go to nth chat |
  33. | ⌘/Ctrl + Shift + = | Next chat |
  34. | ⌘/Ctrl + Shift + – | Previous chat |
  35.  
  36.  
  37. ## Text Input and Editing
  38.  
  39. | Shortcut (Mac/Windows) | Action |
  40. |:----------------------:|:-----------------------------:|
  41. | Shift + Esc | Focus chat input |
  42. | ⌘/Ctrl + Shift + E | Edit text |
  43. | ⌘/Ctrl + Shift + ; | Copy last code block |
  44. | ⌘/Ctrl + Shift + ' |Copy second-to-last code block |
  45. | ⌘/Ctrl + Shift + C | Copy response |
  46. | ⌘/Ctrl + Shift + K | Stop/start generation |
  47.  
  48.  
  49. ## Draft Navigation
  50.  
  51. | Shortcut (Mac/Windows) | Action |
  52. |:----------------------:|:--------------------:|
  53. | ⌘/Ctrl + Shift + D | Generate more drafts |
  54. | ⌘/Ctrl + Shift + , | Next draft |
  55. | ⌘/Ctrl + Shift + . | Previous draft |
  56.  
  57.  
  58. ## Sharing and Linking
  59.  
  60. | Shortcut (Mac/Windows) | Action |
  61. |:----------------------:|:-------------------------:|
  62. | ⌘/Ctrl + Shift + L | Copy prompt/response link |
  63. | ⌘/Ctrl + Shift + M | Copy chat link |
  64.  
  65.  
  66. ## Audio and File Shortcuts
  67.  
  68. | Shortcut (Mac/Windows) | Action |
  69. |:----------------------:|:---------------------:|
  70. | ⌘/Ctrl + Shift + K | Stop/start generation |
  71. | ⌘/Ctrl + Shift + Y | Play/pause audio |
  72. | ⌘/Ctrl + Shift + S | Voice to text |
  73. | ⌘/Ctrl + O | Open file |
  74.  
  75.  
  76.  
  77. */
  78.  
  79. //With this false, it will copy from the response in the viewport.
  80.  
  81. const assumeLastResponse = false;
  82.  
  83. //This setting allows you to delete chats in succession, like browser tabs, instead of beign forced to go to a new one. Perfect if doing Gemini housekeeping
  84.  
  85. const goToNextChatOnDelete = true;
  86.  
  87.  
  88.  
  89. const hasQuery = window.location.href.includes("?q=");
  90. let url = new URL(window.location.href);
  91. let params = new URLSearchParams(url.search);
  92. let query = unescape(params.get('q'));
  93.  
  94. window.onload = onLoad;
  95.  
  96. function onLoad(){
  97. //This code makes the prompt take up the full width of the screen, and moves the heading
  98. let s = document.createElement("style");
  99. document.head.append(s);
  100. s.textContent = `
  101.  
  102. .conversation-container, .input-area-container, .bottom-container {
  103. max-width: -webkit-fill-available !important;
  104. }
  105.  
  106. .capabilities-disclaimer, #gbwa, .cdk-overlay-backdrop {
  107. display: none !important;
  108. }
  109.  
  110. .code-block-decoration.footer, .code-block-decoration.header {
  111. user-select: none; /* Standard syntax */
  112. -webkit-user-select: none; /* WebKit (Safari, Chrome) browsers */
  113. -moz-user-select: none; /* Firefox */
  114. -ms-user-select: none; /* Internet Explorer/Edge */
  115.  
  116. }
  117.  
  118. .bottom-container {
  119. padding-bottom: 20px;
  120. }
  121.  
  122. bard-mode-switcher {
  123. position: fixed;
  124. top: 0px;
  125. right: 64px;
  126. z-index: 1000;
  127. background: var(--bard-color-surface-container);
  128. border: solid var(--bard-color-surface-container) 4px;
  129. border-right: solid var(--bard-color-surface-container) 100px;
  130. transform: translate(100px, -4px);
  131. border-radius: 100px;
  132. box-shadow: 0 0 20px 12px rgba(var(--bard-color-main-container-background-rgb), 77%)
  133. }
  134.  
  135. .mat-mdc-focus-indicator::before {
  136. border: none !important;
  137. }
  138.  
  139. * > .conversation-container:first-child {
  140. border-top: solid transparent 60px !important;
  141. }
  142.  
  143. `;
  144.  
  145. const nums = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth"];
  146. const rapidClickDelayMS = 100;
  147. const capitalize = word => word.charAt(0).toUpperCase() + word.slice(1);
  148.  
  149.  
  150. //This code makes sure that the 'more chats' feature is selected without user interaction (so that you can select chats 6-9 with alt as well.)
  151.  
  152. //This code also allows for query parameters in the URL.
  153.  
  154. let showMoreClicked = false;
  155. let inputBarClicked = false;
  156. const observer = new MutationObserver((_, observer) => {
  157. const showMore = document.querySelector('[data-test-id="show-more-button"]');
  158. const inputBar = document.querySelector('.text-input-field');
  159. const textInput = document.querySelector('[aria-label="Enter a prompt here"]');
  160.  
  161. if (showMore && !showMoreClicked) {
  162. showMoreClicked = true;
  163. simulateClick(showMore);
  164. }
  165. if (hasQuery && inputBar && !inputBarClicked) {
  166. if (textInput && !inputBarClicked) {
  167.  
  168.  
  169. inputBarClicked = true;
  170. console.log(query);
  171. params.delete('q');
  172. window.history.pushState(null,"",url.origin + url.pathname);
  173.  
  174. setTimeout(function(){
  175. inputBar.click();
  176.  
  177. setTimeout(function(){
  178. textInput.firstChild.remove();
  179. query = query.split("\n");
  180. for (let line of query) {
  181. let p = document.createElement("p");
  182. p.innerText = line;
  183. textInput.append(p);
  184. }
  185.  
  186. //This waits to also change the url when the drafts generate. Google is weird and changes it back
  187. const observer = new MutationObserver((_, observer) => {
  188. let showDrafts = document.querySelector('[data-test-id="generate-more-drafts-button"]');
  189. if (showDrafts) {
  190. observer.disconnect();
  191.  
  192. setTimeout(function(){
  193. url = new URL(window.location.href);
  194. params = new URLSearchParams(url.search);
  195. window.history.pushState(null,"",url.origin + url.pathname);
  196. }, 2000)
  197. }
  198. });
  199. observer.observe(document.body, {childList: true, subtree: true});
  200.  
  201. setTimeout(function(){
  202. document.querySelector('[aria-label="Send message"]').click();
  203. }, rapidClickDelayMS)
  204. } ,rapidClickDelayMS)
  205. }, rapidClickDelayMS)
  206.  
  207. }
  208. } else if (inputBar && !inputBarClicked) {
  209. console.log(hasQuery)
  210. inputBarClicked = true;
  211. setTimeout(() => inputBar.click(), rapidClickDelayMS)
  212. }
  213.  
  214. if (showMoreClicked && inputBarClicked) {
  215. observer.disconnect();
  216. }
  217. });
  218. observer.observe(document.body, {childList: true, subtree: true});
  219.  
  220. let c = null;
  221. function getLastElement(querySelector) {
  222. const containers = document.querySelectorAll('.conversation-container');
  223. c = containers[containers.length - 1];
  224. if (!assumeLastResponse) {
  225. let mostVisibleElement = null;
  226. let maxVisibleArea = 0;
  227.  
  228. containers.forEach(container => {
  229. const rect = container.getBoundingClientRect();
  230. const viewportHeight = window.innerHeight;
  231.  
  232. // Calculate visible area (only consider area within the viewport)
  233. const visibleArea = Math.max(0, Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0));
  234.  
  235. if (visibleArea > maxVisibleArea && visibleArea !== 0) {
  236. maxVisibleArea = visibleArea;
  237. mostVisibleElement = container;
  238. }
  239. });
  240. c = mostVisibleElement;
  241. }
  242. return c.querySelectorAll(querySelector)[c.querySelectorAll(querySelector).length - 1];
  243. }
  244.  
  245. function copy(text) {
  246. const textarea = document.createElement('textarea');
  247. textarea.value = text;
  248. document.body.appendChild(textarea);
  249. textarea.select();
  250. document.execCommand('copy');
  251. document.body.removeChild(textarea);
  252. }
  253.  
  254. function copyRichTextFromDiv(element) {
  255. const div = element;
  256.  
  257. if (!div) {
  258. console.error("Div not found.");
  259. return;
  260. }
  261.  
  262. document.querySelectorAll('.code-block-decoration.footer, .code-block-decoration.header, .table-footer').forEach(el => el.style.display = 'none');
  263.  
  264. const selection = window.getSelection();
  265. const range = document.createRange();
  266. range.selectNodeContents(div);
  267. selection.removeAllRanges();
  268. selection.addRange(range);
  269.  
  270. try {
  271. const successful = document.execCommand('copy');
  272. } catch (err) {
  273. console.error('Failed to copy rich text: ', err);
  274. }
  275.  
  276. selection.removeAllRanges();
  277. setTimeout(function(){
  278. document.querySelectorAll('.code-block-decoration.footer, .code-block-decoration.header').forEach(el => el.style.display = '');
  279. }, rapidClickDelayMS)
  280.  
  281. }
  282.  
  283.  
  284.  
  285. function clearNotifications() {
  286. for (let ele of document.querySelectorAll(".gemini-key-notification")) {
  287. ele.remove();
  288. }
  289. }
  290.  
  291. function notify(text) {
  292. clearNotifications();
  293. for (let ele of document.querySelectorAll(".gmat-mdc-dialog")) {
  294. ele.remove();
  295. }
  296.  
  297. var div = document.createElement('div');
  298. div.classList.add("gemini-key-notification");
  299. div.innerText = text;
  300. let tDuration = 125;
  301. let nDuration = 3000;
  302. let tLeft = nDuration - tDuration;
  303. div.style.cssText = `position: absolute;bottom: 26px;left: 26px;font-family: var(--mdc-snackbar-supporting-text-font);font-size: var(--mdc-snackbar-supporting-text-size);font-weight: var(--mdc-snackbar-supporting-text-weight);line-height: var(--mdc-snackbar-supporting-text-line-height);color: var(--mdc-snackbar-supporting-text-color);border-radius: var(--mdc-snackbar-container-shape);background-color: var(--mdc-snackbar-container-color);z-index: 2147483647;padding: 16px;line-height: 20px;transition-property: opacity, scale;transition-duration: ${tDuration}ms;transform-origin: center;scale: 0.6;opacity: 0;`;
  304. document.body.append(div);
  305. setTimeout(function(){div.style.opacity = 1; div.style.scale = 1;}, rapidClickDelayMS)
  306. setTimeout(function(){
  307. div.style.opacity = 0;
  308. setTimeout(function(){div.remove()}, tDuration)
  309. }, tLeft);
  310. }
  311.  
  312.  
  313. function simulateClick(element) {
  314. element.click();
  315. }
  316.  
  317. let draftIndex = 0;
  318. let googleDraftCount = 3;
  319. let waitOnGeneration = false;
  320.  
  321. function changeDraft(direction) {
  322. let draftButtons = document.querySelectorAll(".draft-preview-button");
  323. if (!waitOnGeneration) {
  324. draftIndex = (draftIndex + direction + googleDraftCount) % googleDraftCount; // Ensure index stays within 0-2
  325. }
  326.  
  327. if (!waitOnGeneration && draftButtons[draftIndex]) {
  328. simulateClick(draftButtons[draftIndex]);
  329. //notify(`${capitalize(nums[draftIndex])} draft`)
  330. } else if (!waitOnGeneration) {
  331. draftIndex = 0;
  332. draftIndex = (draftIndex + direction + googleDraftCount) % googleDraftCount;
  333. simulateClick(getLastElement('[data-test-id="generate-more-drafts-button"]'));
  334. notify(`Generating ${nums[draftIndex]} draft`)
  335. waitOnGeneration = true;
  336.  
  337. const observer = new MutationObserver((_, observer) => {
  338. draftButtons = document.querySelectorAll(".draft-preview-button");
  339. if (draftButtons[draftIndex]) {
  340. observer.disconnect();
  341. setTimeout(function(){
  342. waitOnGeneration = false;
  343. simulateClick(draftButtons[draftIndex]);
  344. //notify(`${capitalize(nums[draftIndex])} draft`)
  345. },rapidClickDelayMS * 2)
  346. }
  347. });
  348. observer.observe(document.body, {childList: true, subtree: true});
  349. } else {
  350. notify("Waiting on generation");
  351. }
  352. }
  353.  
  354. const nextDraft = () => changeDraft(1);
  355. const previousDraft = () => changeDraft(-1);
  356.  
  357. let chatIndex = 0;
  358. let waitOnLoadingMore = false;
  359.  
  360. function changeChat(direction) {
  361. chatIndex = Array.from(document.querySelectorAll('[data-test-id="conversation"]')).indexOf(document.querySelector('.selected[data-test-id="conversation"]'));
  362. let chatButtons = document.querySelectorAll('[data-test-id="conversation"]');
  363.  
  364. if (!waitOnLoadingMore) {
  365. chatIndex = Math.max(0, chatIndex + direction);
  366. }
  367.  
  368. if (!waitOnLoadingMore && chatButtons[chatIndex]) {
  369. simulateClick(chatButtons[chatIndex]);
  370. notify(`"${chatButtons[chatIndex].querySelector(".conversation-title").innerHTML.trim()}"`)
  371. } else if (!waitOnLoadingMore) {
  372. simulateClick(document.querySelector('[data-test-id="load-more-button"]'));
  373. notify(`Loading chats`)
  374. waitOnLoadingMore = true;
  375.  
  376. const observer = new MutationObserver((_, observer) => {
  377. chatButtons = document.querySelectorAll('[data-test-id="conversation"]');
  378. if (chatButtons[chatIndex]) {
  379. observer.disconnect();
  380. setTimeout(function(){
  381. waitOnLoadingMore = false;
  382. simulateClick(chatButtons[chatIndex]);
  383. //notify(`${capitalize(nums[draftIndex])} draft`)
  384. notify(`"${chatButtons[chatIndex].querySelector(".conversation-title").innerHTML.trim()}"`)
  385. },rapidClickDelayMS * 2)
  386. }
  387. });
  388. observer.observe(document.body, {childList: true, subtree: true});
  389. } else {
  390. notify("Chats loading");
  391. }
  392. }
  393.  
  394. const nextChat = () => changeChat(1);
  395. const previousChat = () => changeChat(-1);
  396.  
  397.  
  398.  
  399. var isMac = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
  400.  
  401. document.addEventListener('keydown', function(event) {
  402. // Check for Command or Control key
  403.  
  404. if (event.shiftKey && event.key === "Escape") {
  405. simulateClick(document.querySelector('.text-input-field'));
  406. event.preventDefault();
  407. }
  408.  
  409. let keyNumber = parseInt(event.code.replace("Digit",""));
  410. keyNumber = keyNumber === 0 ? 10 : keyNumber;
  411.  
  412. if (event.altKey && keyNumber) {
  413. document.querySelectorAll('[data-test-id="conversation"]')[keyNumber - 1].click();
  414. chatIndex = Array.from(document.querySelectorAll('[data-test-id="conversation"]')).indexOf(document.querySelector('.selected[data-test-id="conversation"]'));
  415. notify(`"${document.querySelectorAll('[data-test-id="conversation"]')[chatIndex].querySelector(".conversation-title").innerHTML.trim()}"`)
  416. //notify(`${capitalize(nums[keyNumber-1])} conversation`)
  417. event.preventDefault();
  418. }
  419.  
  420.  
  421. if (event.key === "Escape" && document.activeElement.getAttribute("aria-label").includes("Edit prompt")) {
  422. simulateClick(getLastElement('[aria-label*="Cancel"]'));
  423. event.preventDefault();
  424. }
  425.  
  426. const isCmdOrCtrl = (isMac && event.metaKey) || (!isMac && event.ctrlKey);
  427.  
  428. if (!isCmdOrCtrl) return;
  429.  
  430. if (isCmdOrCtrl && event.key === 'o' && !event.shiftKey) {
  431. event.preventDefault();
  432. simulateClick(document.querySelector('.upload-button button'));
  433. simulateClick(document.querySelector('[aria-label*="Upload files"]'))
  434. }
  435.  
  436. switch (event.key) {
  437. case 'o':
  438. if (event.shiftKey) {
  439. simulateClick(document.querySelector('[aria-label*="New chat"] button'));
  440. simulateClick(document.querySelector('.text-input-field'));
  441. //notify("New chat created");
  442. event.preventDefault();
  443. } else {
  444. document.querySelector('[aria-label*="upload file"]').click(); setTimeout(function(){document.body.querySelector('[aria-label*="Upload files"]').click()}, rapidClickDelayMS);
  445. }
  446. break;
  447. //BELOW NEEDS MORE TIME
  448. case 'c':
  449. if (event.shiftKey) {
  450. event.preventDefault();
  451. getLastElement();
  452. copyRichTextFromDiv(c.querySelector(".model-response-text"));
  453. notify("Copied response")
  454.  
  455.  
  456. /* All of the below code was me desperately trying to do it through Google's menus, and failing for 2+ hours. Good riddance
  457.  
  458. simulateClick(getLastElement('[aria-label*="options"]'));
  459. setTimeout(function(){simulateClick(document.querySelector('[aria-label*="Copy"]'))},rapidClickDelayMS*2)
  460. simulateClick(getLastElement('[aria-label*="options"]'));
  461. simulateClick(document.querySelector('#overflow-container'))
  462. setTimeout(function(){document.querySelector('.cdk-overlay-pane').style.top = "99999999px"; c.focus()},rapidClickDelayMS)
  463. clearNotifications();
  464. */
  465. }
  466. break;
  467. case 'i':
  468. if (event.shiftKey) {
  469. // Implement custom instructions if Gemini supports them
  470. event.preventDefault();
  471. }
  472. break;
  473. case 'f':
  474. if (event.shiftKey) {
  475. simulateClick(document.querySelector('[aria-label*="Main menu"]'));
  476. event.preventDefault();
  477. }
  478. break;
  479. case 'Backspace':
  480. if (event.shiftKey) {
  481. event.preventDefault();
  482. chatIndex = Array.from(document.querySelectorAll('[data-test-id="conversation"]')).indexOf(document.querySelector('.selected[data-test-id="conversation"]'));
  483. document.querySelector('.conversation.selected').parentElement.querySelector('[data-test-id="actions-menu-button"]').click(); setTimeout(function(){document.body.querySelector('[data-test-id="delete-button"]').click()}, rapidClickDelayMS); setTimeout(function(){document.body.querySelector('[data-test-id="confirm-button"]').click(); setTimeout(function(){if(goToNextChatOnDelete){simulateClick(document.querySelectorAll('[data-test-id="conversation"]')[chatIndex])}}, rapidClickDelayMS)}, rapidClickDelayMS)
  484. }
  485. break;
  486. case 'd':
  487. if (event.shiftKey) {
  488. let element = getLastElement('[data-test-id="generate-more-drafts-button"]');
  489. if (!element) {
  490. element = getLastElement('[mattooltip="Regenerate drafts"]');
  491. }
  492. simulateClick(element);
  493. event.preventDefault();
  494. }
  495. break;
  496. case 'e':
  497. if (event.shiftKey) {
  498. simulateClick(getLastElement('[mattooltip="Edit text"]'));
  499. event.preventDefault();
  500. }
  501. break;
  502. case ';':
  503. if (event.shiftKey) {
  504. event.preventDefault();
  505. // simulateClick(getLastElement('[mattooltip="Copy code"]'));
  506. getLastElement();
  507. copyRichTextFromDiv(c.querySelectorAll("code-block")[c.querySelectorAll("code-block").length - 1]);
  508. notify("Copied last code block to clipboard");
  509. }
  510. break;
  511. case '\'':
  512. if (event.shiftKey) {
  513. event.preventDefault();
  514. // simulateClick(getLastElement('[mattooltip="Copy code"]'));
  515. getLastElement();
  516. copyRichTextFromDiv(c.querySelectorAll("code-block")[c.querySelectorAll("code-block").length - 2]);
  517. notify("Copied second-last code block to clipboard");
  518. }
  519. break;
  520. case 'm':
  521. if (event.shiftKey) {
  522. simulateClick(getLastElement('[aria-label*="Share"]'));
  523. setTimeout(function(){simulateClick(document.querySelector('[aria-label*="Share response"]'))},rapidClickDelayMS)
  524. setTimeout(function(){simulateClick(document.querySelector('[data-test-id="share-mode-radio-button-full"] label'))},rapidClickDelayMS*2)
  525. setTimeout(function(){simulateClick(document.querySelector('[data-test-id="create-button"]'))},rapidClickDelayMS*3)
  526.  
  527. //below waits until the link menu loads, then copies it and closes the menu
  528. const observer = new MutationObserver((_, observer) => {
  529. const element = document.querySelector('[aria-label="Copy public link"]');
  530. if (element) {
  531. observer.disconnect();
  532. simulateClick(element);
  533. setTimeout(function(){
  534. simulateClick(document.querySelector('[aria-label="Close"]'))
  535. notify("Chat link copied");
  536. },rapidClickDelayMS)
  537. }
  538. });
  539. observer.observe(document.body, {childList: true, subtree: true});
  540.  
  541.  
  542. clearNotifications();
  543. //notify("Last response copied to clipboard");
  544. event.preventDefault();
  545. }
  546. break;
  547. case 'l':
  548. if (event.shiftKey) {
  549. simulateClick(getLastElement('[aria-label*="Share"]'));
  550. setTimeout(function(){simulateClick(document.querySelector('[aria-label*="Share response"]'))},rapidClickDelayMS)
  551. setTimeout(function(){simulateClick(document.querySelector('[data-test-id="create-button"]'))},rapidClickDelayMS*2)
  552.  
  553. //below waits until the link menu loads, then copies it and closes the menu
  554. const observer = new MutationObserver((_, observer) => {
  555. const element = document.querySelector('[aria-label="Copy public link"]');
  556. if (element) {
  557. observer.disconnect();
  558. simulateClick(element);
  559. setTimeout(function(){
  560. simulateClick(document.querySelector('[aria-label="Close"]'));
  561. notify("Prompt/response link copied");
  562. },rapidClickDelayMS)
  563. }
  564. });
  565. observer.observe(document.body, {childList: true, subtree: true});
  566. //notify("Last response copied to clipboard");
  567. event.preventDefault();
  568. }
  569. break;
  570. case ',':
  571. if (event.shiftKey) {
  572. previousDraft();
  573. }
  574. break;
  575. case '.':
  576. if (event.shiftKey) {
  577. nextDraft();
  578. }
  579. break;
  580. case '-':
  581. if (event.shiftKey) {
  582. event.preventDefault();
  583. previousChat();
  584. }
  585. break;
  586. case '=':
  587. if (event.shiftKey) {
  588. event.preventDefault();
  589. nextChat();
  590. }
  591. break;
  592. case 'k':
  593. event.preventDefault();
  594. if (event.shiftKey) {
  595. simulateClick(document.querySelector('[aria-label="Send message"]'));
  596. //notify("Last response copied to clipboard");
  597. }
  598. break;
  599. case 'y':
  600. if (event.shiftKey) {
  601. simulateClick(getLastElement('.response-tts-container button'));
  602. event.preventDefault();
  603. }
  604. break;
  605. case 's':
  606. if (event.shiftKey) {
  607. simulateClick(document.querySelector('[mattooltip="Use microphone"]'));
  608. event.preventDefault();
  609. }
  610. break;
  611. }
  612. });
  613. }