JanitorAI Enhanced UI

Adds useful UI controls for JanitorAI, hides buttons on all chat pages

目前为 2025-04-09 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name JanitorAI Enhanced UI
  3. // @namespace http://tampermonkey.net/
  4. // @version 3
  5. // @description Adds useful UI controls for JanitorAI, hides buttons on all chat pages
  6. // @author Fefnik
  7. // @match https://janitorai.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. // Settings
  15. let MIN_TOKENS = localStorage.getItem('janitorAITokenFilter') ? parseInt(localStorage.getItem('janitorAITokenFilter')) : 500;
  16. let isSidebarHidden = localStorage.getItem('janitorAISidebarHidden') === 'true';
  17. let isAutoScrollEnabled = localStorage.getItem('janitorAIAutoScroll') !== 'false';
  18. let isMenuVisible = localStorage.getItem('janitorAIMenuVisible') === 'true';
  19.  
  20. // Elements
  21. let sliderElement = null;
  22. let sliderContainer = null;
  23. let controlPanel = null;
  24. let controlsContainer = null;
  25. let emblaSlide = null; // Переменная для хранения ссылки на элемент .is-in-view.is-snapped.embla__slide
  26.  
  27. function isAllowedPage() {
  28. const path = window.location.pathname;
  29. return (path === '/' || path.startsWith('/search') || path === '/my_characters' || path.startsWith('/profiles/'));
  30. }
  31.  
  32. function isChatsPage() {
  33. const path = window.location.pathname;
  34. return path.startsWith('/chats');
  35. }
  36.  
  37. function parseTokens(tokenText) {
  38. try {
  39. let cleanText = tokenText.replace(/<!--[\s\S]*?-->/g, '').replace('tokens', '').trim();
  40. if (cleanText.includes('k')) {
  41. return parseFloat(cleanText.replace('k', '')) * 1000;
  42. }
  43. return parseInt(cleanText, 10) || 0;
  44. } catch (error) {
  45. return 0;
  46. }
  47. }
  48.  
  49. function filterCards() {
  50. const cards = document.querySelectorAll('.chakra-stack.css-1s5evre, .css-1s5evre');
  51. cards.forEach(card => {
  52. const tokenElement = card.querySelector('.chakra-text.css-jccmq6, .css-jccmq6');
  53. if (!tokenElement) return;
  54.  
  55. const tokenCount = parseTokens(tokenElement.textContent);
  56. const parentContainer = card.closest('.css-1sxhvxh, .css-1dbw1r8');
  57. if (parentContainer) {
  58. parentContainer.style.display = tokenCount < MIN_TOKENS ? 'none' : '';
  59. }
  60. });
  61. }
  62.  
  63. function setupPaginationScroll() {
  64. const paginationButtons = document.querySelectorAll('.css-kzd6o0');
  65. paginationButtons.forEach(button => {
  66. button.removeEventListener('click', handlePaginationClick);
  67. button.addEventListener('click', handlePaginationClick);
  68. });
  69. }
  70.  
  71. function handlePaginationClick() {
  72. if (isAutoScrollEnabled) {
  73. setTimeout(() => {
  74. window.scrollTo({
  75. top: 0,
  76. behavior: 'smooth'
  77. });
  78. }, 300);
  79. }
  80. }
  81.  
  82. function toggleSidebar() {
  83. const sidebar = document.querySelector('.css-h988mi');
  84. const css70qvj9 = document.querySelector('.css-70qvj9'); // Элемент .css-70qvj9
  85.  
  86. if (sidebar) {
  87. isSidebarHidden = !isSidebarHidden;
  88. sidebar.style.display = isSidebarHidden ? 'none' : '';
  89.  
  90. // Управление видимостью элемента .is-in-view.is-snapped.embla__slide
  91. if (!emblaSlide && isSidebarHidden) {
  92. emblaSlide = document.querySelector('.is-in-view.is-snapped.embla__slide');
  93. }
  94. if (emblaSlide) {
  95. emblaSlide.style.display = isSidebarHidden ? 'none' : '';
  96. }
  97.  
  98. // Управление видимостью элемента .css-70qvj9
  99. if (css70qvj9) {
  100. css70qvj9.style.display = isSidebarHidden ? 'none' : '';
  101. }
  102.  
  103. localStorage.setItem('janitorAISidebarHidden', isSidebarHidden);
  104. updateControlText();
  105. }
  106. }
  107.  
  108. function toggleAutoScroll() {
  109. isAutoScrollEnabled = !isAutoScrollEnabled;
  110. localStorage.setItem('janitorAIAutoScroll', isAutoScrollEnabled);
  111. updateControlText();
  112. }
  113.  
  114. function toggleMenu() {
  115. isMenuVisible = !isMenuVisible;
  116. localStorage.setItem('janitorAIMenuVisible', isMenuVisible);
  117. updateElementsVisibility();
  118. updateControlText();
  119. }
  120.  
  121. function updateControlText() {
  122. const sidebarText = document.getElementById('sidebar-toggle-text');
  123. const scrollText = document.getElementById('auto-scroll-text');
  124.  
  125. if (sidebarText) {
  126. sidebarText.textContent = isSidebarHidden ? 'Show Topbar' : 'Hide Topbar';
  127. sidebarText.style.color = isSidebarHidden ? '#fff' : '#ccc';
  128. }
  129.  
  130. if (scrollText) {
  131. scrollText.textContent = isAutoScrollEnabled ? 'Auto-Scroll: ON' : 'Auto-Scroll: OFF';
  132. scrollText.style.color = isAutoScrollEnabled ? '#fff' : '#ccc';
  133. }
  134. }
  135.  
  136. function createControlPanel() {
  137. if (controlPanel) return;
  138.  
  139. controlPanel = document.createElement('div');
  140. controlPanel.id = 'janitor-control-panel';
  141. controlPanel.style.position = 'fixed';
  142. controlPanel.style.top = '75px';
  143. controlPanel.style.left = '10px';
  144. controlPanel.style.zIndex = '100000';
  145. controlPanel.style.display = 'flex';
  146. controlPanel.style.flexDirection = 'column';
  147. controlPanel.style.gap = '5px';
  148. controlPanel.style.alignItems = 'flex-start';
  149.  
  150. const settingsButton = document.createElement('button');
  151. settingsButton.id = 'token-filter-toggle';
  152. settingsButton.textContent = '⚙️';
  153. settingsButton.title = 'Toggle settings';
  154. settingsButton.style.width = '30px';
  155. settingsButton.style.height = '30px';
  156. settingsButton.style.padding = '0';
  157. settingsButton.style.backgroundColor = 'rgba(74, 74, 74, 0.7)';
  158. settingsButton.style.color = '#fff';
  159. settingsButton.style.border = 'none';
  160. settingsButton.style.borderRadius = '5px';
  161. settingsButton.style.cursor = 'pointer';
  162. settingsButton.style.display = 'flex';
  163. settingsButton.style.alignItems = 'center';
  164. settingsButton.style.justifyContent = 'center';
  165. settingsButton.style.fontSize = '16px';
  166. settingsButton.style.transition = 'background-color 0.2s';
  167.  
  168. settingsButton.addEventListener('click', toggleMenu);
  169.  
  170. controlsContainer = document.createElement('div');
  171. controlsContainer.id = 'controls-container';
  172. controlsContainer.style.display = 'none';
  173. controlsContainer.style.flexDirection = 'column';
  174. controlsContainer.style.gap = '5px';
  175. controlsContainer.style.backgroundColor = 'rgba(74, 74, 74, 0.7)';
  176. controlsContainer.style.padding = '5px';
  177. controlsContainer.style.borderRadius = '5px';
  178.  
  179. const sidebarText = document.createElement('span');
  180. sidebarText.id = 'sidebar-toggle-text';
  181. sidebarText.style.cursor = 'pointer';
  182. sidebarText.style.fontSize = '12px';
  183. sidebarText.addEventListener('click', toggleSidebar);
  184.  
  185. const scrollText = document.createElement('span');
  186. scrollText.id = 'auto-scroll-text';
  187. scrollText.style.cursor = 'pointer';
  188. scrollText.style.fontSize = '12px';
  189. scrollText.addEventListener('click', toggleAutoScroll);
  190.  
  191. controlsContainer.appendChild(sidebarText);
  192. controlsContainer.appendChild(scrollText);
  193.  
  194. controlPanel.appendChild(settingsButton);
  195. controlPanel.appendChild(controlsContainer);
  196. document.body.appendChild(controlPanel);
  197. updateControlText();
  198. }
  199.  
  200. function createOrUpdateSlider() {
  201. if (!sliderElement) {
  202. sliderContainer = document.createElement('div');
  203. sliderContainer.id = 'token-filter-container';
  204. sliderContainer.style.position = 'fixed';
  205. sliderContainer.style.top = '75px';
  206. sliderContainer.style.left = '50px';
  207. sliderContainer.style.zIndex = '99999';
  208. sliderContainer.style.display = 'none';
  209. sliderContainer.style.flexDirection = 'row';
  210. sliderContainer.style.alignItems = 'center';
  211. sliderContainer.style.gap = '10px';
  212. sliderContainer.style.padding = '5px';
  213. sliderContainer.style.backgroundColor = 'rgba(74, 74, 74, 0.7)';
  214. sliderContainer.style.borderRadius = '5px';
  215.  
  216. sliderElement = document.createElement('input');
  217. sliderElement.type = 'range';
  218. sliderElement.id = 'token-filter-slider';
  219. sliderElement.min = '0';
  220. sliderElement.max = '6000';
  221. sliderElement.step = '100';
  222. sliderElement.value = MIN_TOKENS;
  223. sliderElement.style.width = '150px';
  224. sliderElement.style.height = '20px';
  225. sliderElement.style.backgroundColor = '#4a4a4a';
  226. sliderElement.style.cursor = 'pointer';
  227. sliderElement.style.appearance = 'none';
  228. sliderElement.style.outline = 'none';
  229. sliderElement.style.borderRadius = '5px';
  230. sliderElement.style.padding = '0';
  231.  
  232. const style = document.createElement('style');
  233. style.textContent = `
  234. #token-filter-slider {
  235. -webkit-appearance: none;
  236. appearance: none;
  237. background: #4a4a4a;
  238. border-radius: 5px;
  239. }
  240. #token-filter-slider::-webkit-slider-thumb {
  241. -webkit-appearance: none;
  242. appearance: none;
  243. width: 20px;
  244. height: 20px;
  245. background: #ffffff;
  246. cursor: pointer;
  247. border-radius: 50%;
  248. border: 2px solid #000;
  249. box-shadow: 0 0 2px rgba(0,0,0,0.5);
  250. transform: translateY(-5px);
  251. }
  252. #token-filter-slider::-moz-range-thumb {
  253. width: 20px;
  254. height: 20px;
  255. background: #ffffff;
  256. cursor: pointer;
  257. border-radius: 50%;
  258. border: 2px solid #000;
  259. box-shadow: 0 0 2px rgba(0,0,0,0.5);
  260. transform: translateY(-5px);
  261. }
  262. #token-filter-slider::-webkit-slider-runnable-track {
  263. height: 10px;
  264. background: #4a4a4a;
  265. border-radius: 5px;
  266. }
  267. #token-filter-slider::-moz-range-track {
  268. height: 10px;
  269. background: #4a4a4a;
  270. border-radius: 5px;
  271. }
  272. `;
  273. document.head.appendChild(style);
  274.  
  275. const label = document.createElement('span');
  276. label.id = 'token-filter-label';
  277. label.style.color = '#fff';
  278. label.style.fontSize = '12px';
  279. label.style.minWidth = '60px';
  280. label.textContent = `${MIN_TOKENS} tokens`;
  281.  
  282. sliderElement.addEventListener('input', (e) => {
  283. MIN_TOKENS = parseInt(e.target.value);
  284. label.textContent = `${MIN_TOKENS} tokens`;
  285. localStorage.setItem('janitorAITokenFilter', MIN_TOKENS);
  286. filterCards();
  287. });
  288.  
  289. sliderContainer.appendChild(sliderElement);
  290. sliderContainer.appendChild(label);
  291. document.body.appendChild(sliderContainer);
  292. }
  293. }
  294.  
  295. function updateElementsVisibility() {
  296. const shouldShow = isAllowedPage() && !isChatsPage();
  297. if (controlPanel) {
  298. controlPanel.style.display = shouldShow ? 'flex' : 'none';
  299. }
  300. if (sliderContainer) {
  301. sliderContainer.style.display = shouldShow && isMenuVisible ? 'flex' : 'none';
  302. }
  303. if (controlsContainer) {
  304. controlsContainer.style.display = shouldShow && isMenuVisible ? 'flex' : 'none';
  305. }
  306. }
  307.  
  308. function initialize() {
  309. createControlPanel();
  310. createOrUpdateSlider();
  311. updateElementsVisibility();
  312.  
  313. if (isAllowedPage() && !isChatsPage()) {
  314. filterCards();
  315. setupPaginationScroll();
  316.  
  317. const sidebar = document.querySelector('.css-h988mi');
  318. if (sidebar && isSidebarHidden) {
  319. sidebar.style.display = 'none';
  320. emblaSlide = document.querySelector('.is-in-view.is-snapped.embla__slide');
  321. if (emblaSlide) {
  322. emblaSlide.style.display = 'none';
  323. }
  324. const css70qvj9 = document.querySelector('.css-70qvj9');
  325. if (css70qvj9) {
  326. css70qvj9.style.display = 'none'; // Скрываем при инициализации, если сайдбар скрыт
  327. }
  328. }
  329.  
  330. const observer = new MutationObserver(() => {
  331. filterCards();
  332. setupPaginationScroll();
  333. });
  334. observer.observe(document.body, { childList: true, subtree: true });
  335. }
  336. }
  337.  
  338. const tryInitialize = () => {
  339. if (document.body) {
  340. initialize();
  341.  
  342. let lastPath = window.location.pathname;
  343. setInterval(() => {
  344. if (lastPath !== window.location.pathname) {
  345. lastPath = window.location.pathname;
  346. updateElementsVisibility();
  347. if (isAllowedPage() && !isChatsPage()) {
  348. filterCards();
  349. setupPaginationScroll();
  350. }
  351. }
  352. }, 500);
  353. } else {
  354. setTimeout(tryInitialize, 1000);
  355. }
  356. };
  357.  
  358. tryInitialize();
  359. })();