Upload To Gitlab Button for Claude.ai Chat

Adds an "Upload To Gitlab" button before the chat controls on Claude.ai chat pages

  1. // ==UserScript==
  2. // @name Upload To Gitlab Button for Claude.ai Chat
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Adds an "Upload To Gitlab" button before the chat controls on Claude.ai chat pages
  6. // @match https://claude.ai/chat/*
  7. // @grant none
  8. // @license MIT
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. // Function to create the "Upload To Gitlab" button
  15. function createUploadToGitlabButton() {
  16. const uploadButton = document.createElement('button');
  17. uploadButton.textContent = 'Upload To Gitlab';
  18. uploadButton.id = 'upload-to-gitlab-btn';
  19. uploadButton.style.backgroundColor = 'blue';
  20. uploadButton.style.color = 'white';
  21. uploadButton.style.padding = '5px 10px';
  22. uploadButton.style.borderRadius = '5px';
  23. uploadButton.style.marginRight = '10px';
  24. uploadButton.addEventListener('click', openModal);
  25. return uploadButton;
  26. }
  27.  
  28. // Function to open the modal
  29. function openModal() {
  30. const modal = document.createElement('div');
  31. modal.id = 'gitlab-modal';
  32. modal.style.position = 'fixed';
  33. modal.style.top = '50%';
  34. modal.style.left = '50%';
  35. modal.style.transform = 'translate(-50%, -50%)';
  36. modal.style.backgroundColor = 'hsl(var(--bg-200)/var(--tw-bg-opacity))';
  37. modal.style.padding = '20px';
  38. modal.style.borderRadius = '5px';
  39. modal.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.3)';
  40. modal.style.zIndex = '9999';
  41. modal.innerHTML = `
  42. <h2>Upload To Gitlab</h2>
  43. <form id="gitlab-form">
  44. <label for="gitlab-domain">Gitlab Domain:</label>
  45. <input type="text" id="gitlab-domain" name="gitlab-domain" required style="background-color: hsl(var(--bg-200)/var(--tw-bg-opacity));"><br>
  46. <label for="gitlab-token">Gitlab Token:</label>
  47. <input type="text" id="gitlab-token" name="gitlab-token" required style="background-color: hsl(var(--bg-200)/var(--tw-bg-opacity));"><br>
  48. <label for="gitlab-project-id">Gitlab Project ID:</label>
  49. <input type="text" id="gitlab-project-id" name="gitlab-project-id" required style="background-color: hsl(var(--bg-200)/var(--tw-bg-opacity));"><br>
  50. <label for="project-branch">Project Branch:</label>
  51. <input type="text" id="project-branch" name="project-branch" required style="background-color: hsl(var(--bg-200)/var(--tw-bg-opacity));"><br>
  52. <label for="file-path">Path:</label>
  53. <input type="text" id="file-path" name="file-path" style="background-color: hsl(var(--bg-200)/var(--tw-bg-opacity));"><br>
  54. <div style="text-align: right;">
  55. <button type="button" id="close-modal-btn" style="margin-right: 10px;">X</button>
  56. <button type="submit" style="background-color: green; color: white;">Upload</button>
  57. </div>
  58. </form>
  59. `;
  60. document.body.appendChild(modal);
  61.  
  62. // Populate form fields with saved data
  63. const gitlabDomain = localStorage.getItem('gitlab-domain');
  64. const gitlabToken = localStorage.getItem('gitlab-token');
  65. const gitlabProjectId = localStorage.getItem('gitlab-project-id');
  66. const projectBranch = localStorage.getItem('project-branch');
  67. const filePath = localStorage.getItem('file-path');
  68. if (gitlabDomain) {
  69. document.getElementById('gitlab-domain').value = gitlabDomain;
  70. }
  71. if (gitlabToken) {
  72. document.getElementById('gitlab-token').value = gitlabToken;
  73. }
  74. if (gitlabProjectId) {
  75. document.getElementById('gitlab-project-id').value = gitlabProjectId;
  76. }
  77. if (projectBranch) {
  78. document.getElementById('project-branch').value = projectBranch;
  79. }
  80. if (filePath) {
  81. document.getElementById('file-path').value = filePath;
  82. }
  83.  
  84. // Handle form submission
  85. document.getElementById('gitlab-form').addEventListener('submit', function(e) {
  86. e.preventDefault();
  87. const gitlabDomain = document.getElementById('gitlab-domain').value.replace(/^https?:\/\//, '').replace(/\/$/, '');
  88. const gitlabToken = document.getElementById('gitlab-token').value;
  89. const gitlabProjectId = document.getElementById('gitlab-project-id').value;
  90. const projectBranch = document.getElementById('project-branch').value;
  91. const filePath = document.getElementById('file-path').value.trim();
  92.  
  93. // Save form data in localStorage
  94. localStorage.setItem('gitlab-domain', gitlabDomain);
  95. localStorage.setItem('gitlab-token', gitlabToken);
  96. localStorage.setItem('gitlab-project-id', gitlabProjectId);
  97. localStorage.setItem('project-branch', projectBranch);
  98. localStorage.setItem('file-path', filePath);
  99.  
  100. // Close the modal
  101. document.body.removeChild(modal);
  102.  
  103. // Fetch data from the API and upload to Gitlab
  104. uploadToGitlab(gitlabDomain, gitlabToken, gitlabProjectId, projectBranch, filePath);
  105. });
  106.  
  107. // Handle modal close button click
  108. document.getElementById('close-modal-btn').addEventListener('click', function() {
  109. document.body.removeChild(modal);
  110. });
  111. }
  112.  
  113. // Function to check if the file exists on Gitlab
  114. function checkFileExists(gitlabDomain, gitlabToken, gitlabProjectId, projectBranch, filePath) {
  115. const checkFileUrl = `https://${gitlabDomain}/api/v4/projects/${gitlabProjectId}/repository/files/${encodeURIComponent(filePath)}?ref=${projectBranch}`;
  116. return fetch(checkFileUrl, {
  117. method: 'GET',
  118. headers: {
  119. 'PRIVATE-TOKEN': gitlabToken
  120. }
  121. }).then(response => {
  122. if (response.status === 200) {
  123. return true;
  124. } else if (response.status === 404) {
  125. return false;
  126. } else {
  127. throw new Error(`Unexpected response status: ${response.status}`);
  128. }
  129. });
  130. }
  131.  
  132. // Function to upload data to Gitlab
  133. function uploadToGitlab(gitlabDomain, gitlabToken, gitlabProjectId, projectBranch, userFilePath) {
  134. const cacheName = 'apis';
  135. caches.open(cacheName).then(function(cache) {
  136. cache.keys().then(function(requests) {
  137. let organizationId = null;
  138. requests.forEach(function(request) {
  139. if (request.url.includes('https://claude.ai/api/organizations/')) {
  140. const parts = request.url.split('/');
  141. const index = parts.indexOf('organizations');
  142. if (index !== -1 && parts.length > index + 1) {
  143. organizationId = parts[index + 1];
  144. }
  145. }
  146. });
  147. console.log('Organization ID (from cache):', organizationId);
  148.  
  149. // Get the current chat ID from the browser URL
  150. const currentUrl = window.location.href;
  151. const chatId = currentUrl.split('/').pop();
  152. console.log('Current Chat ID (from URL):', chatId);
  153.  
  154. // Fetch JSON data from the API URL
  155. const apiUrl = `https://claude.ai/api/organizations/${organizationId}/chat_conversations/${chatId}`;
  156. fetch(apiUrl)
  157. .then(response => response.json())
  158. .then(data => {
  159. console.log('API Response:', data);
  160.  
  161. const filePath = userFilePath ? `${userFilePath.replace(/\/$/, '')}/${chatId}.json` : `${chatId}.json`;
  162. checkFileExists(gitlabDomain, gitlabToken, gitlabProjectId, projectBranch, filePath)
  163. .then(fileExists => {
  164. const action = fileExists ? 'update' : 'create';
  165.  
  166. // Prepare the payload for Gitlab API request
  167. const payload = {
  168. branch: projectBranch,
  169. commit_message: `${action === 'create' ? 'Add' : 'Update'} chat: ${data.name}`,
  170. actions: [
  171. {
  172. action: action,
  173. file_path: filePath,
  174. content: JSON.stringify(data, null, 2)
  175. }
  176. ]
  177. };
  178.  
  179. // Make the Gitlab API request to create or update the file
  180. const gitlabUrl = `https://${gitlabDomain}/api/v4/projects/${gitlabProjectId}/repository/commits`;
  181. fetch(gitlabUrl, {
  182. method: 'POST',
  183. headers: {
  184. 'PRIVATE-TOKEN': gitlabToken,
  185. 'Content-Type': 'application/json'
  186. },
  187. body: JSON.stringify(payload)
  188. })
  189. .then(response => {
  190. if (response.status === 401) {
  191. alert('Unauthorized: Invalid Gitlab token');
  192. } else if (response.status !== 201) {
  193. return response.json().then(data => {
  194. throw new Error(JSON.stringify(data));
  195. });
  196. } else {
  197. return response.json();
  198. }
  199. })
  200. .then(data => {
  201. console.log('Gitlab API Response:', data);
  202. window.open(data.web_url, '_blank');
  203. })
  204. .catch(error => {
  205. console.error('Error uploading to Gitlab:', error);
  206. alert(`Failed to upload chat to Gitlab. Error: ${error.message}`);
  207. });
  208. })
  209. .catch(error => {
  210. console.error('Error checking file existence:', error);
  211. alert('Failed to check file existence on Gitlab.');
  212. });
  213. })
  214. .catch(error => {
  215. console.error('Error fetching API data:', error);
  216. alert('Failed to fetch chat data from the API.');
  217. });
  218. });
  219. });
  220. }
  221.  
  222. // Function to insert the "Upload To Gitlab" button before the chat controls
  223. function insertUploadToGitlabButton() {
  224. const chatControls = document.querySelector('[data-testid="chat-controls"]');
  225. const existingButton = document.getElementById('upload-to-gitlab-btn');
  226. if (chatControls && !existingButton) {
  227. const uploadButton = createUploadToGitlabButton();
  228. chatControls.parentNode.insertBefore(uploadButton, chatControls);
  229. }
  230. }
  231.  
  232. // Observe changes in the DOM and insert the "Upload To Gitlab" button when the chat controls appear
  233. const observer = new MutationObserver(function(mutations) {
  234. mutations.forEach(function(mutation) {
  235. if (mutation.addedNodes.length) {
  236. insertUploadToGitlabButton();
  237. }
  238. });
  239. });
  240.  
  241. // Configure the observer to watch for changes in the entire document
  242. const config = { childList: true, subtree: true };
  243. observer.observe(document.body, config);
  244.  
  245. // Insert the "Upload To Gitlab" button initially if the chat controls are already present
  246. insertUploadToGitlabButton();
  247. })();