c.ai Background Image Library (Improved)

Customize the chat interface with an improved image library for backgrounds, with multiple rows of thumbnails and click-outside popup closure.

目前为 2024-11-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name c.ai Background Image Library (Improved)
  3. // @author LuxTallis
  4. // @namespace c.ai Background Image Library Improved
  5. // @match https://character.ai/*
  6. // @grant none
  7. // @license MIT
  8. // @version 1.0
  9. // @description Customize the chat interface with an improved image library for backgrounds, with multiple rows of thumbnails and click-outside popup closure.
  10. // @icon https://i.imgur.com/ynjBqKW.png
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. function saveLibrary(library) {
  15. localStorage.setItem('background_image_library', JSON.stringify(library));
  16. }
  17.  
  18. function getLibrary() {
  19. return JSON.parse(localStorage.getItem('background_image_library') || '[]');
  20. }
  21.  
  22. function addToLibrary(url) {
  23. const library = getLibrary();
  24. if (!url || library.includes(url)) return;
  25. library.push(url);
  26. saveLibrary(library);
  27. }
  28.  
  29. function removeFromLibrary(url) {
  30. const library = getLibrary().filter((image) => image !== url);
  31. saveLibrary(library);
  32. }
  33.  
  34. function applyBackgroundImage(url) {
  35. localStorage.setItem('background_image_url', url);
  36. const css = `
  37. body {
  38. background-image: url('${url}');
  39. background-size: cover;
  40. background-position: center;
  41. background-repeat: no-repeat;
  42. }
  43. `;
  44. let styleElement = document.getElementById('customBackgroundStyle');
  45. if (!styleElement) {
  46. styleElement = document.createElement('style');
  47. styleElement.id = 'customBackgroundStyle';
  48. document.head.appendChild(styleElement);
  49. }
  50. styleElement.innerHTML = css;
  51. }
  52.  
  53. function createCustomizationPanel() {
  54. const panel = document.createElement('div');
  55. panel.id = 'customizationPanel';
  56. panel.style.position = 'fixed';
  57. panel.style.top = '50%';
  58. panel.style.left = '50%';
  59. panel.style.transform = 'translate(-50%, -50%)';
  60. panel.style.backgroundColor = '#1e1e1e';
  61. panel.style.color = 'white';
  62. panel.style.borderRadius = '5px';
  63. panel.style.padding = '20px';
  64. panel.style.zIndex = '9999';
  65. panel.style.fontFamily = 'Montserrat, sans-serif';
  66. panel.style.maxWidth = '1250px'; // 2.5x wider
  67. panel.style.minWidth = '875px'; // 2.5x wider
  68. panel.style.width = 'auto'; // Ensure auto width based on content
  69.  
  70. const label = document.createElement('label');
  71. label.textContent = 'Add Image URL to Library:';
  72. label.style.display = 'block';
  73. label.style.marginBottom = '5px';
  74.  
  75. const input = document.createElement('input');
  76. input.type = 'text';
  77. input.placeholder = 'Enter image URL';
  78. input.style.width = '100%';
  79. input.style.marginBottom = '10px';
  80.  
  81. const addButton = document.createElement('button');
  82. addButton.textContent = 'Add';
  83. addButton.style.marginTop = '10px';
  84. addButton.style.padding = '5px 10px';
  85. addButton.style.border = 'none';
  86. addButton.style.borderRadius = '3px';
  87. addButton.style.backgroundColor = '#444';
  88. addButton.style.color = 'white';
  89. addButton.style.fontFamily = 'Montserrat, sans-serif';
  90. addButton.addEventListener('click', () => {
  91. const url = input.value.trim();
  92. if (url) {
  93. addToLibrary(url);
  94. input.value = '';
  95. renderLibrary();
  96. }
  97. });
  98.  
  99. const libraryContainer = document.createElement('div');
  100. libraryContainer.id = 'libraryContainer';
  101. libraryContainer.style.marginTop = '10px';
  102. libraryContainer.style.overflowX = 'auto';
  103. libraryContainer.style.display = 'flex';
  104. libraryContainer.style.flexWrap = 'wrap'; // Allow wrapping into multiple lines
  105. libraryContainer.style.gap = '15px'; // Increased gap between thumbnails
  106. libraryContainer.style.paddingBottom = '10px';
  107. libraryContainer.style.borderTop = '1px solid #555';
  108. libraryContainer.style.paddingTop = '10px';
  109. libraryContainer.style.whiteSpace = 'nowrap'; // Prevent wrapping of thumbnails
  110. libraryContainer.style.maxHeight = '380px'; // Ensure 3 rows of thumbnails
  111. libraryContainer.style.height = 'auto';
  112.  
  113. function renderLibrary() {
  114. libraryContainer.innerHTML = '';
  115. const library = getLibrary();
  116. library.forEach((url) => {
  117. const imgContainer = document.createElement('div');
  118. imgContainer.style.position = 'relative';
  119. imgContainer.style.flex = '0 0 auto'; // Ensures the thumbnail stays at its natural width
  120.  
  121. const img = document.createElement('img');
  122. img.src = url;
  123. img.alt = 'Preview';
  124. img.style.width = '99px'; // Half the size
  125. img.style.height = '64px'; // Half the size
  126. img.style.objectFit = 'cover';
  127. img.style.border = '1px solid #fff';
  128. img.style.borderRadius = '3px';
  129. img.style.cursor = 'pointer';
  130. img.title = url;
  131.  
  132. img.addEventListener('click', () => {
  133. applyBackgroundImage(url);
  134. });
  135.  
  136. const removeButton = document.createElement('button');
  137. removeButton.textContent = '×';
  138. removeButton.style.position = 'absolute';
  139. removeButton.style.top = '5px';
  140. removeButton.style.right = '5px';
  141. removeButton.style.backgroundColor = 'red';
  142. removeButton.style.color = 'white';
  143. removeButton.style.border = 'none';
  144. removeButton.style.borderRadius = '50%';
  145. removeButton.style.cursor = 'pointer';
  146. removeButton.style.width = '20px';
  147. removeButton.style.height = '20px';
  148. removeButton.style.textAlign = 'center';
  149. removeButton.style.fontSize = '12px';
  150. removeButton.addEventListener('click', (e) => {
  151. e.stopPropagation();
  152. removeFromLibrary(url);
  153. renderLibrary();
  154. });
  155.  
  156. imgContainer.appendChild(img);
  157. imgContainer.appendChild(removeButton);
  158. libraryContainer.appendChild(imgContainer);
  159. });
  160. }
  161.  
  162. panel.appendChild(label);
  163. panel.appendChild(input);
  164. panel.appendChild(addButton);
  165. panel.appendChild(libraryContainer);
  166. document.body.appendChild(panel);
  167.  
  168. renderLibrary();
  169.  
  170. // Close the panel when clicking outside of it
  171. document.addEventListener('click', function closeOnOutsideClick(event) {
  172. if (!panel.contains(event.target) && !mainButton.contains(event.target)) {
  173. panel.remove();
  174. document.removeEventListener('click', closeOnOutsideClick); // Remove the event listener
  175. }
  176. });
  177. }
  178.  
  179. function createButton(symbol, onClick) {
  180. const button = document.createElement('button');
  181. button.innerHTML = symbol;
  182. button.style.position = 'fixed';
  183. button.style.top = '82px';
  184. button.style.right = '5px';
  185. button.style.width = '22px';
  186. button.style.height = '22px';
  187. button.style.backgroundColor = '#444';
  188. button.style.color = 'white';
  189. button.style.border = 'none';
  190. button.style.borderRadius = '3px';
  191. button.style.cursor = 'pointer';
  192. button.style.fontFamily = 'Montserrat, sans-serif';
  193. button.addEventListener('click', onClick);
  194. return button;
  195. }
  196.  
  197. const mainButton = createButton('🖼️', () => {
  198. const panelExists = document.getElementById('customizationPanel');
  199. if (!panelExists) {
  200. createCustomizationPanel();
  201. }
  202. });
  203.  
  204. document.body.appendChild(mainButton);
  205.  
  206. const currentImageUrl = localStorage.getItem('background_image_url') || '';
  207. applyBackgroundImage(currentImageUrl);
  208. })();