Chat GPT Code Export Button

Adds Export button to code blocks in ChatGPT responses, prompts user to save code as file with predefined filename based on coding language detected from the code block's class name.

Version au 04/07/2024. Voir la dernière version.

  1. // ==UserScript==
  2. // @name Chat GPT Code Export Button
  3. // @namespace http://tampermonkey.net/
  4. // @version 2024/07/03
  5. // @license MIT
  6. // @description Adds Export button to code blocks in ChatGPT responses, prompts user to save code as file with predefined filename based on coding language detected from the code block's class name.
  7. // @author Muffin & Arcadie
  8. // @match https://chatgpt.com/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Function to add "Export" button to existing code headers
  16. function addExportButtonToHeaders() {
  17. const codeHeaders = document.querySelectorAll('.flex.items-center.relative.text-token-text-secondary.bg-token-main-surface-secondary.px-4.py-2.text-xs.font-sans.justify-between.rounded-t-md');
  18.  
  19. codeHeaders.forEach(header => {
  20. // Check if "Export" button is already added
  21. if (header.querySelector('.export-button')) {
  22. return; // Skip if already added
  23. }
  24.  
  25. // Find the language label
  26. const languageLabel = header.querySelector('span');
  27.  
  28. // Create the "Export" button
  29. const exportButton = document.createElement('button');
  30. exportButton.innerText = 'Export';
  31. exportButton.classList.add('export-button'); // Add a class for styling
  32. exportButton.style.padding = '8px 16px';
  33. exportButton.style.marginRight = '8px'; // Adjust spacing if necessary
  34. exportButton.style.border = 'none';
  35. exportButton.style.borderRadius = '4px';
  36. exportButton.style.backgroundColor = '#e3e3e3';
  37. exportButton.style.color = '#333';
  38. exportButton.style.cursor = 'pointer';
  39. exportButton.style.transition = 'background-color 0.3s';
  40.  
  41. // Insert "Export" button after the language label
  42. languageLabel.parentNode.insertBefore(exportButton, languageLabel.nextSibling);
  43.  
  44. // Add click event listener for the "Export" button
  45. exportButton.addEventListener('click', () => {
  46. const codeBlock = header.parentElement.querySelector('pre code'); // Assuming structure, adjust as needed
  47. const language = languageLabel.textContent.trim().toLowerCase(); // Extract language
  48. exportCode(codeBlock, language); // Call export function
  49. });
  50. });
  51. }
  52.  
  53. // Function to open File Explorer for saving the code as file
  54. async function exportCode(codeBlock, language) {
  55. let fileName;
  56. let fileExtension;
  57. let mimeType;
  58.  
  59. // Determine filename, extension, and MIME type based on language
  60. switch (language) {
  61. case 'javascript':
  62. case 'js':
  63. fileName = 'script';
  64. fileExtension = '.js';
  65. mimeType = 'application/javascript';
  66. break;
  67. case 'html':
  68. fileName = 'index';
  69. fileExtension = '.html';
  70. mimeType = 'text/html';
  71. break;
  72. case 'css':
  73. fileName = 'styles';
  74. fileExtension = '.css';
  75. mimeType = 'text/css';
  76. break;
  77. case 'python':
  78. case 'py':
  79. fileName = 'main';
  80. fileExtension = '.py';
  81. mimeType = 'text/x-python';
  82. break;
  83. default:
  84. // If language cannot be determined from <span>, fallback to provided language
  85. switch (language.toLowerCase()) {
  86. case 'javascript':
  87. case 'js':
  88. fileName = 'script';
  89. fileExtension = '.js';
  90. mimeType = 'application/javascript';
  91. break;
  92. case 'html':
  93. fileName = 'index';
  94. fileExtension = '.html';
  95. mimeType = 'text/html';
  96. break;
  97. case 'css':
  98. fileName = 'styles';
  99. fileExtension = '.css';
  100. mimeType = 'text/css';
  101. break;
  102. case 'python':
  103. case 'py':
  104. fileName = 'main';
  105. fileExtension = '.py';
  106. mimeType = 'text/x-python';
  107. break;
  108. case 'java':
  109. fileName = 'Main';
  110. fileExtension = '.java';
  111. mimeType = 'text/x-java-source';
  112. break;
  113. case 'kotlin':
  114. fileName = 'Main';
  115. fileExtension = '.kt';
  116. mimeType = 'text/x-kotlin';
  117. break;
  118. case 'c++':
  119. case 'cpp':
  120. fileName = 'main';
  121. fileExtension = '.cpp';
  122. mimeType = 'text/x-c++src';
  123. break;
  124. case 'c#':
  125. case 'csharp':
  126. fileName = 'Program';
  127. fileExtension = '.cs';
  128. mimeType = 'text/x-csharp';
  129. break;
  130. case 'c':
  131. fileName = 'main';
  132. fileExtension = '.c';
  133. mimeType = 'text/x-csrc';
  134. break;
  135. case 'ruby':
  136. fileName = 'script';
  137. fileExtension = '.rb';
  138. mimeType = 'text/x-ruby';
  139. break;
  140. case 'rust':
  141. fileName = 'main';
  142. fileExtension = '.rs';
  143. mimeType = 'text/x-rustsrc';
  144. break;
  145. case 'php':
  146. fileName = 'script';
  147. fileExtension = '.php';
  148. mimeType = 'text/x-php';
  149. break;
  150. case 'swift':
  151. fileName = 'main';
  152. fileExtension = '.swift';
  153. mimeType = 'text/x-swift';
  154. break;
  155. case 'typescript':
  156. case 'ts':
  157. fileName = 'script';
  158. fileExtension = '.ts';
  159. mimeType = 'application/typescript';
  160. break;
  161. case 'go':
  162. fileName = 'main';
  163. fileExtension = '.go';
  164. mimeType = 'text/x-go';
  165. break;
  166. case 'perl':
  167. fileName = 'script';
  168. fileExtension = '.pl';
  169. mimeType = 'text/x-perl';
  170. break;
  171. case 'lua':
  172. fileName = 'script';
  173. fileExtension = '.lua';
  174. mimeType = 'text/x-lua';
  175. break;
  176. default:
  177. fileName = 'code';
  178. fileExtension = '.txt';
  179. mimeType = 'text/plain';
  180. break;
  181. }
  182. break;
  183. }
  184.  
  185. // Create a Blob object with the code content
  186. const blob = new Blob([codeBlock.innerText], { type: mimeType });
  187.  
  188. try {
  189. if (window.showSaveFilePicker) {
  190. // Use File System Access API if available
  191. const fileHandle = await window.showSaveFilePicker({
  192. suggestedName: fileName + fileExtension,
  193. types: [
  194. {
  195. description: language,
  196. accept: {
  197. [mimeType]: [fileExtension],
  198. },
  199. },
  200. ],
  201. });
  202.  
  203. const writable = await fileHandle.createWritable();
  204. await writable.write(blob);
  205. await writable.close();
  206. } else {
  207. // Fallback for browsers that do not support showSaveFilePicker
  208. const downloadLink = document.createElement('a');
  209. downloadLink.href = URL.createObjectURL(blob);
  210. downloadLink.download = fileName + fileExtension;
  211. downloadLink.style.display = 'none';
  212. document.body.appendChild(downloadLink);
  213. downloadLink.click();
  214. URL.revokeObjectURL(downloadLink.href);
  215. document.body.removeChild(downloadLink);
  216. }
  217. } catch (error) {
  218. console.error('Save file dialog was canceled or failed', error);
  219. }
  220. }
  221.  
  222. // Observe the document for changes and add "Export" button to new code headers
  223. const observer = new MutationObserver(addExportButtonToHeaders);
  224. observer.observe(document.body, { childList: true, subtree: true });
  225.  
  226. // Initial processing of existing code headers
  227. addExportButtonToHeaders();
  228.  
  229. // Add custom CSS styles if needed
  230. })();