c.ai X Swipes

A toggleable panel with the swipes of the current turn

  1. // ==UserScript==
  2. // @name c.ai X Swipes
  3. // @namespace c.ai X Swipes
  4. // @version 2.1
  5. // @description A toggleable panel with the swipes of the current turn
  6. // @author Vishanka via chatGPT
  7. // @license MIT
  8. // @match https://*.character.ai/chat*
  9. // @icon https://i.imgur.com/iH2r80g.png
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13.  
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. var original_prototype_open = XMLHttpRequest.prototype.open;
  19. const intercepted_data_object_swipes = {};
  20.  
  21. XMLHttpRequest.prototype.open = function(method, url, async) {
  22. if (
  23. url.startsWith('https://plus.character.ai/chat/history/continue/') ||
  24. url.startsWith('https://plus.character.ai/chat/character/info') ||
  25. url.startsWith('https://beta.character.ai/chat/history/continue/') ||
  26. url.startsWith('https://beta.character.ai/chat/character/info')
  27. ) {
  28. this.addEventListener('load', function() {
  29. let info1_swipes = JSON.parse(this.responseText);
  30. intercepted_data_object_swipes.external_id = info1_swipes.character.external_id;
  31. intercepted_data_object_swipes.name = info1_swipes.character.name;
  32. console.log("character_id:",intercepted_data_object_swipes.external_id);
  33.  
  34. // Only create the toggle button and the panel once
  35. if (!document.getElementById('NeoPanelSwipes')) {
  36. createToggleButton_NeoPanelSwipes();
  37. createNeoPanelSwipes();
  38. }
  39. });
  40.  
  41. } else if (url.startsWith(`https://neo.character.ai/chats/recent/${intercepted_data_object_swipes.external_id}`)) {
  42. this.addEventListener('load', function() {
  43. let info2_swipes = JSON.parse(this.responseText);
  44. intercepted_data_object_swipes.chat_id = info2_swipes.chats[0].chat_id;
  45. console.log("chat_id:",intercepted_data_object_swipes.chat_id);
  46. });
  47. } else if (url.startsWith(`https://neo.character.ai/turns/${intercepted_data_object_swipes.chat_id}`)) {
  48. this.addEventListener('load', function() {
  49. let info3_swipes = JSON.parse(this.responseText);
  50. intercepted_data_object_swipes.turn_id = info3_swipes.turns[0].turn_key.turn_id;
  51. intercepted_data_object_swipes.total_turns = info3_swipes.turns.length;
  52. console.log("turn_id:",intercepted_data_object_swipes.turn_id);
  53. console.log("total_turns:", intercepted_data_object_swipes.total_turns);
  54. // Extract data from the last turn_id if there are turns
  55. if (intercepted_data_object_swipes.total_turns > 0) {
  56. const lastTurnIndex = intercepted_data_object_swipes.total_turns - 1;
  57. const lastTurnData = info3_swipes.turns[lastTurnIndex];
  58. intercepted_data_object_swipes.lastTurnId = lastTurnData.turn_key.turn_id; // Store lastTurnId in intercepted_data_object_swipes
  59. console.log("Last turn_id:", intercepted_data_object_swipes.lastTurnId);
  60. }
  61. // Extract candidates for "turn 0", used for fetching limit
  62. if (intercepted_data_object_swipes.total_turns > 0) {
  63. const firstTurnData = info3_swipes.turns[0];
  64. intercepted_data_object_swipes.candidatesForTurn0 = firstTurnData.candidates;
  65. intercepted_data_object_swipes.numberOfCandidatesForTurn0 = intercepted_data_object_swipes.candidatesForTurn0.length;
  66. console.log("Number of candidates for turn 0:", intercepted_data_object_swipes.numberOfCandidatesForTurn0);
  67.  
  68. intercepted_data_object_swipes.rawContents = intercepted_data_object_swipes.candidatesForTurn0.map(candidate => candidate.raw_content);
  69. console.log("Raw contents of candidates for turn 0:", intercepted_data_object_swipes.rawContents);
  70.  
  71.  
  72. // All Styles and Functions of the List Elements
  73.  
  74. const swipes = document.createElement('div');
  75. swipes.style.textAlign = 'left';
  76. swipes.style.marginTop = '15px';
  77. swipes.style.overflowWrap = 'break-word';
  78. swipes.style.whiteSpace = 'pre-wrap';
  79. swipes.style.maxHeight = 'calc(100% - 12px)'; // Adjust the value as needed
  80. swipes.style.overflowY = 'auto';
  81. swipes.style.marginLeft = '-10px';
  82. swipes.style.scrollbarWidth = '1px';
  83. //swipes.style.scrollbarColor = 'transparent transparent';
  84.  
  85. if (intercepted_data_object_swipes.rawContents) {
  86. intercepted_data_object_swipes.rawContents.forEach((content, index) => {
  87. const contentContainer = document.createElement('div'); // Create a container for each content
  88. contentContainer.style.display = 'flex'; // Use flex layout
  89. contentContainer.style.alignItems = 'center'; // Center-align vertically
  90. contentContainer.style.marginBottom = '15px'; // Add some spacing between elements
  91. contentContainer.style.marginTop = '15px';
  92. contentContainer.style.direction = 'ltr';
  93. let isGreen = false; // Flag to track the background color state
  94.  
  95. const candidateNumber = index + 1; // Adding 1 to index to make it 1-based
  96. const numberElement = document.createElement('span'); // Create element for candidate number
  97. numberElement.textContent = `${candidateNumber}.`;
  98. numberElement.style.marginRight = '15px'; // Add spacing between number and text
  99. numberElement.style.direction = 'ltr';
  100. numberElement.style.marginLeft = '10px';
  101. // numberElement.style.alignItems = 'center';
  102. numberElement.style.marginBottom = '15px'
  103.  
  104. const contentElement = document.createElement('div'); // Create element for content
  105. contentElement.innerHTML = content;
  106. contentElement.style.direction = 'ltr';
  107. contentElement.style.color = '#878788';
  108.  
  109. // Changes the color of the text
  110. const formattedContent = content.replace(/(["“”«»].*?["“”«»])/g, '<span style="color: #FFFFFF">$1</span>');
  111. const finalContent1 = formattedContent.replace(/\*\*(.*?)\*\*/g, '<span style="font-weight: bold;">$1</span>');
  112. const finalContent = finalContent1.replace(/\*(.*?)\*/g, '<span style="font-style: italic; color: #E0DF7F;">$1</span>');
  113. // Use regular expressions to find text within backticks and apply formatting
  114. const formattedBackticks_3 = finalContent.replace(/```([^`]*)```/g, '<div style="display: inline-block; margin: 0px 10px; vertical-align: middle;"><div style="color: white; font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; font-size: 13px; tab-size: 4; hyphens: none; padding: 5px; margin: 0px; overflow: auto; background: rgb(1, 22, 39);"><code style="color: rgb(214, 222, 235); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; text-align: left; white-space: pre-wrap; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; font-size: 1em; tab-size: 4; hyphens: none;">$1</code></div></div>');
  115. const formattedBackticks_tilde = formattedBackticks_3.replace(/~~~([^`]*)~~~/g, '<div style="display: inline-block; margin: 0px 10px; vertical-align: middle;"><div style="color: white; font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; font-size: 13px; tab-size: 4; hyphens: none; padding: 5px; margin: 0px; overflow: auto; background: rgb(1, 22, 39);"><code style="color: rgb(214, 222, 235); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; text-align: left; white-space: pre-wrap; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; font-size: 1em; tab-size: 4; hyphens: none;">$1</code></div></div>');
  116.  
  117. const formattedBackticks_1 = formattedBackticks_tilde.replace(/`(?!`)(.*?)`/g, '<div style="display: inline-block; margin: 0px 10px; vertical-align: middle;"><div style="color: white; font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; font-size: 13px; tab-size: 4; hyphens: none; padding: 5px; margin: 0px; overflow: auto; background: rgb(1, 22, 39);"><code style="color: rgb(214, 222, 235); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; text-align: left; white-space: pre-wrap; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; font-size: 1em; tab-size: 4; hyphens: none;">$1</code></div></div>');
  118.  
  119. // Set the final formatted content as innerHTML
  120. contentElement.innerHTML = formattedBackticks_1;
  121.  
  122. contentContainer.appendChild(numberElement); // Append number element to container
  123. contentContainer.appendChild(contentElement); // Append content element to container
  124.  
  125.  
  126. contentContainer.addEventListener('click', (event) => {
  127. if (event.button === 0) {
  128. simulateArrowKeyPress(candidateNumber); // Call the function to simulate arrow key press
  129. }
  130. });
  131.  
  132. contentContainer.addEventListener('dblclick', (event) => {
  133. if (isGreen) {
  134. contentContainer.style.backgroundColor = ''; // Reset to default color
  135. } else {
  136. contentContainer.style.backgroundColor = 'green';
  137. }
  138. isGreen = !isGreen; // Toggle the flag
  139. });
  140.  
  141. swipes.appendChild(contentContainer); // Append the container to the swipes
  142.  
  143. // Add a horizontal line after each content (except for the last one)
  144. if (index < intercepted_data_object_swipes.rawContents.length - 1) {
  145. const divider = document.createElement('hr');
  146. swipes.appendChild(divider);
  147. }
  148. });
  149. } else {
  150. const errorMessage = document.createElement('p');
  151. errorMessage.textContent = "No raw contents available.";
  152. swipes.appendChild(errorMessage);
  153. }
  154.  
  155. // Function to simulate ArrowRight key press
  156. function simulateArrowKeyPress(steps) {
  157. for (let step = 0; step < 50; step++) {
  158. ArrowLeftKeyDown();
  159. }
  160. for (let step = 0; step < steps - 1; step++) {
  161. ArrowRightKeyDown();
  162. }
  163. }
  164.  
  165. // Your existing ArrowRightKeyDown function
  166. function ArrowRightKeyDown() {
  167. document.body.dispatchEvent(
  168. new KeyboardEvent('keydown', {
  169. bubbles: true,
  170. key: 'ArrowRight',
  171. })
  172. );
  173. console.log("Arrow right pressed");
  174. }
  175.  
  176. // Your existing ArrowLeftKeyDown function
  177. function ArrowLeftKeyDown() {
  178. document.body.dispatchEvent(
  179. new KeyboardEvent('keydown', {
  180. bubbles: true,
  181. key: 'ArrowLeft',
  182. })
  183. );
  184. console.log("Arrow left pressed");
  185. }
  186.  
  187. NeoPanelSwipes.appendChild(swipes);
  188.  
  189.  
  190.  
  191. }
  192.  
  193.  
  194.  
  195. XHR_interception_resolve(intercepted_data_object_swipes);
  196. });
  197. }
  198. original_prototype_open.apply(this, [method, url, async]);
  199. };
  200.  
  201. let XHR_interception_resolve;
  202. const XHR_interception_promise = new Promise(function(resolve, reject) {
  203. XHR_interception_resolve = resolve;
  204. });
  205.  
  206. XHR_interception_promise.then(function() {
  207. console.log("Intercepted Data:", intercepted_data_object_swipes);
  208.  
  209. });
  210.  
  211.  
  212.  
  213. function createToggleButton_NeoPanelSwipes() {
  214. const toggleButton_NeoPanelSwipes = document.createElement('button');
  215. toggleButton_NeoPanelSwipes.innerHTML = '<svg transform="rotate(90)" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16" height="22" width="22" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11.854 3.646a.5.5 0 0 1 0 .708L8.207 8l3.647 3.646a.5.5 0 0 1-.708.708l-4-4a.5.5 0 0 1 0-.708l4-4a.5.5 0 0 1 .708 0zM4.5 1a.5.5 0 0 0-.5.5v13a.5.5 0 0 0 1 0v-13a.5.5 0 0 0-.5-.5z"></path></svg>';
  216. toggleButton_NeoPanelSwipes.style.position = 'fixed';
  217. toggleButton_NeoPanelSwipes.style.bottom = '0px';
  218. toggleButton_NeoPanelSwipes.style.right = '3px';
  219. toggleButton_NeoPanelSwipes.style.backgroundColor = 'none';
  220. toggleButton_NeoPanelSwipes.style.color = 'white';
  221. toggleButton_NeoPanelSwipes.style.fontWeight = 'bold';
  222. toggleButton_NeoPanelSwipes.style.padding = '0px';
  223. toggleButton_NeoPanelSwipes.style.margin = '0px';
  224. // toggleButton_NeoPanelSwipes.style.width = '5%';
  225. toggleButton_NeoPanelSwipes.style.border = 'none';
  226. toggleButton_NeoPanelSwipes.style.borderRadius = '0px';
  227. toggleButton_NeoPanelSwipes.style.cursor = 'pointer';
  228. toggleButton_NeoPanelSwipes.style.userSelect = 'none';
  229. toggleButton_NeoPanelSwipes.style.zIndex = '101';
  230. toggleButton_NeoPanelSwipes.addEventListener('click', toggleNeoPanelSwipes);
  231.  
  232. document.body.appendChild(toggleButton_NeoPanelSwipes);
  233. }
  234. function createNeoPanelSwipes() {
  235.  
  236.  
  237.  
  238. const NeoPanelSwipes = document.createElement('div');
  239. NeoPanelSwipes.id = 'NeoPanelSwipes';
  240. NeoPanelSwipes.style.position = 'fixed';
  241. NeoPanelSwipes.style.bottom = '0px';
  242. NeoPanelSwipes.style.right = '0%';
  243. NeoPanelSwipes.style.width = '351px';
  244. NeoPanelSwipes.style.height = '100%';
  245. NeoPanelSwipes.style.backgroundColor = '#18181B';
  246. NeoPanelSwipes.style.borderLeft = '0.3px solid #3F3F46';
  247. NeoPanelSwipes.style.padding = '10px';
  248. NeoPanelSwipes.style.zIndex = '100';
  249. NeoPanelSwipes.style.resize = 'horizontal';
  250. NeoPanelSwipes.style.direction = 'rtl';
  251. NeoPanelSwipes.style.overflowX = 'auto';
  252. NeoPanelSwipes.style.overflowY = 'hidden';
  253. NeoPanelSwipes.style.display = 'none';
  254. NeoPanelSwipes.style.paddingBottom = '70px';
  255.  
  256. const otherElement = document.querySelector('.w-\\[786px\\]');
  257.  
  258. if (otherElement) {
  259. // Get the computed style of the other element
  260. const otherElementStyle = window.getComputedStyle(otherElement);
  261.  
  262. // Extract the width property from the computed style
  263. const otherElementWidth = parseFloat(otherElementStyle.width);
  264.  
  265. // Set the width of NeoPanelSwipes to match the other element
  266. NeoPanelSwipes.style.width = otherElementWidth/1.734 + 'px';
  267. }
  268.  
  269.  
  270. // Add Swipes header to the panel
  271. const swipes_headline = document.createElement('h5');
  272. swipes_headline.textContent = 'Swipes';
  273. swipes_headline.style.marginTop = '5px';
  274. swipes_headline.style.marginBottom = '15px';
  275. swipes_headline.style.textAlign = 'center';
  276. NeoPanelSwipes.appendChild(swipes_headline);
  277.  
  278. // Add a horizontal line (divider)
  279. const divider_swipes1 = document.createElement('hr');
  280. NeoPanelSwipes.appendChild(divider_swipes1);
  281.  
  282. document.body.appendChild(NeoPanelSwipes);
  283. }
  284.  
  285. function toggleNeoPanelSwipes() {
  286. const NeoPanelSwipes = document.getElementById('NeoPanelSwipes');
  287. if (NeoPanelSwipes.style.display === 'block') {
  288. NeoPanelSwipes.style.display = 'none';
  289. } else {
  290. NeoPanelSwipes.style.display = 'block';
  291. }
  292. }
  293.  
  294. })();