Better LMarena (lmsys) Chat

make chat lmarena (lmsys) chat better and clean. Removes annoying startup alerts.

目前为 2024-12-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Better LMarena (lmsys) Chat
  3. // @namespace https://github.com/insign/better-lmsys-chat
  4. // @version 202412281425
  5. // @description make chat lmarena (lmsys) chat better and clean. Removes annoying startup alerts.
  6. // @match https://lmarena.ai/*
  7. // @match https://chat.lmsys.org/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=lmarena.ai
  9. // @author Hélio <open@helio.me>
  10. // @license WTFPL
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict'
  15.  
  16. // Override the default alert function
  17. window.alert = function() {
  18. console.log('Blocked an alert: ', arguments)
  19. }
  20.  
  21. const $ = document.querySelector.bind(document)
  22. const $$ = document.querySelectorAll.bind(document)
  23. const hide = el => el.style.display = 'none'
  24. const remove = el => el.remove()
  25. const click = el => el.click()
  26.  
  27. const rename = (el, text) => el.textContent = text
  28.  
  29. /**
  30. * Executes a function on selected elements repeatedly until the condition is met.
  31. *
  32. * @param {string|Element|NodeList|Array<string|Element|NodeList>} selector - The CSS selector, element, NodeList, or an array containing a combination of these types to select the elements.
  33. * @param {function(Element): boolean} check - A function that checks if the condition is met for an element. It should return `true` if the condition is met and `false` otherwise.
  34. * @param {function(Element): void} fn - The function to be executed on each element that meets the condition.
  35. * @param {number} [interval=50] - The interval in milliseconds between each check of the elements.
  36. *
  37. * @example
  38. * // Example usage with CSS selector
  39. * perma('.example-class', (el) => el.textContent !== 'Example', (el) => {
  40. * el.textContent = 'Example';
  41. * });
  42. *
  43. * @example
  44. * // Example usage with element
  45. * const element = document.querySelector('#example-id');
  46. * perma(element, (el) => el.classList.contains('active'), (el) => {
  47. * el.classList.add('active');
  48. * });
  49. *
  50. * @example
  51. * // Example usage with NodeList
  52. * const elements = document.querySelectorAll('.example-class');
  53. * perma(elements, (el) => el.dataset.status !== 'ready', (el) => {
  54. * el.dataset.status = 'ready';
  55. * });
  56. *
  57. * @example
  58. * // Example usage with an array of selectors
  59. * perma(['.example-class', '#example-id'], (el) => el.style.display !== 'none', (el) => {
  60. * el.style.display = 'none';
  61. * });
  62. */
  63. const perma = (selector, check, fn, interval = 1000) => {
  64. let intervalId = null
  65.  
  66. const checkAndExecute = () => {
  67. let elements = []
  68.  
  69. if (Array.isArray(selector)) {
  70. selector.forEach((item) => {
  71. if (typeof item === 'string') {
  72. elements = elements.concat(Array.from($$(item)))
  73. }
  74. else if (item instanceof Element) {
  75. elements.push(item)
  76. }
  77. else if (item instanceof NodeList) {
  78. elements = elements.concat(Array.from(item))
  79. }
  80. })
  81. }
  82. else if (typeof selector === 'string') {
  83. elements = $$(selector)
  84. }
  85. else if (selector instanceof Element) {
  86. elements = [ selector ]
  87. }
  88. else if (selector instanceof NodeList) {
  89. elements = Array.from(selector)
  90. }
  91.  
  92. elements.forEach((element) => {
  93. if (check(element)) {
  94. fn(element)
  95. }
  96. })
  97. }
  98.  
  99. const startInterval = () => {
  100. if (!intervalId) {
  101. intervalId = setInterval(checkAndExecute, interval)
  102. }
  103. }
  104.  
  105. const stopInterval = () => {
  106. if (intervalId) {
  107. clearInterval(intervalId)
  108. intervalId = null
  109. }
  110. }
  111.  
  112. document.addEventListener('visibilitychange', function() {
  113. if (document.hidden) {
  114. stopInterval()
  115. }
  116. else {
  117. startInterval()
  118. }
  119. })
  120.  
  121. startInterval() // Start initially
  122. }
  123. /**
  124. * Waits for specific elements to be present in the DOM and executes a callback function when they are found.
  125. *
  126. * @param {string|Element|NodeList|Array<string|Element|NodeList|function(): Element|null>} [selectors=['html']] - The CSS selector(s), element(s), NodeList(s), or array function(s) that return an element or null. Can be a single selector, element, NodeList, array function, or an array containing a combination of these types. Defaults to ['html'] if not provided.
  127. * @param {function(Element): void} [callback=null] - The callback function to be executed when the specified elements are found. It receives the first found element as an argument. If not provided, the function will resolve without executing a callback.
  128. * @param {number} [slow=0] - The delay in milliseconds before executing the callback function. If set to 0 (default), the callback will be executed immediately after the elements are found.
  129. *
  130. * @returns {Promise<void>} A promise that resolves when the specified elements are found and the callback function (if provided) has been executed.
  131. *
  132. * @example
  133. * // Example usage with a single CSS selector
  134. * when('.example-class', (element) => {
  135. * console.log('Element found:', element);
  136. * });
  137. *
  138. * @example
  139. * // Example usage with multiple CSS selectors
  140. * when(['.example-class', '#example-id'], (element) => {
  141. * console.log('Element found:', element);
  142. * });
  143. *
  144. * @example
  145. * // Example usage with an array function
  146. * when(() => document.querySelector('.example-class'), (element) => {
  147. * console.log('Element found:', element);
  148. * });
  149. *
  150. * @example
  151. * // Example usage with a delay before executing the callback
  152. * when('.example-class', (element) => {
  153. * console.log('Element found:', element);
  154. * }, 1000);
  155. *
  156. * @example
  157. * // Example usage without a callback function
  158. * when('.example-class').then(() => {
  159. * console.log('Element found');
  160. * });
  161. */
  162. const when = (selectors = [ 'html' ], callback = null, slow = 0) => {
  163. if (!Array.isArray(selectors)) {
  164. selectors = [ selectors ]
  165. }
  166.  
  167. return new Promise((resolve) => {
  168. const executeCallback = (element) => {
  169. if (callback) {
  170. if (slow > 0) {
  171. setTimeout(() => {
  172. callback(element)
  173. resolve()
  174. }, slow)
  175. }
  176. else {
  177. callback(element)
  178. resolve()
  179. }
  180. }
  181. else {
  182. resolve()
  183. }
  184. }
  185.  
  186. const checkSelectors = () => {
  187. for (const selector of selectors) {
  188. let element = null
  189.  
  190. if (typeof selector === 'string') {
  191. element = $(selector)
  192. }
  193. else if (selector instanceof Element) {
  194. element = selector
  195. }
  196. else if (selector instanceof NodeList) {
  197. element = selector[0]
  198. }
  199. else if (typeof selector === 'function') {
  200. element = selector()
  201. if (element === null) {
  202. continue
  203. }
  204. }
  205.  
  206. if (element) {
  207. executeCallback(element)
  208. return true
  209. }
  210. }
  211. return false
  212. }
  213.  
  214. if (checkSelectors()) {
  215. return
  216. }
  217.  
  218. const observer = new MutationObserver((mutations) => {
  219. mutations.forEach((mutation) => {
  220. Array.from(mutation.addedNodes).forEach((node) => {
  221. if (node.nodeType === Node.ELEMENT_NODE) {
  222. if (checkSelectors()) {
  223. observer.disconnect()
  224. }
  225. }
  226. })
  227. })
  228. })
  229.  
  230. observer.observe(document.body, { childList: true, subtree: true })
  231. })
  232. }
  233.  
  234. // when('.prose', el => !el.closest('.wrap'), remove)
  235.  
  236. perma('#component-18-button', el => el.textContent !== 'Battle', el => rename(el, 'Battle'), 100)
  237. perma('#component-63-button', el => el.textContent !== 'SbS', el => rename(el, 'Side-by-Side'), 100)
  238. perma('#component-107-button', el => el.textContent !== 'Chat', el => rename(el, 'Chat'), 100)
  239. perma('#component-108-button', el => el.textContent !== 'Vision Chat', el => rename(el, 'Vision Chat'), 100)
  240. perma('#component-140-button', el => el.textContent !== 'Ranking', el => rename(el, 'Ranking'), 100)
  241. perma('#component-231-button', el => el.textContent !== 'About', el => rename(el, 'About'), 100)
  242.  
  243. // ###notice_markdown > .svelte-1ed2p3z > .svelte-gq7qsu.prose > .prose.svelte-8tpqd2.md
  244. when([
  245. '#notice_markdown > .svelte-1ed2p3z > .svelte-gq7qsu.prose > .prose.svelte-8tpqd2.md', // top blocks of notice
  246. '#component-26 > .gap.svelte-vt1mxs > .hide-container.padded.svelte-12cmxck.block', // ToS
  247. '#component-139 > .gap.svelte-vt1mxs > .hide-container.padded.svelte-12cmxck.block', // ToS
  248. '#component-95 > .gap.svelte-vt1mxs > .hide-container.padded.svelte-12cmxck.block', // ToS
  249. '#leaderboard_markdown > .svelte-1ed2p3z > .svelte-gq7qsu.prose > .prose.svelte-8tpqd2.md', // top blocks of leaderboard
  250. ], remove)
  251.  
  252.  
  253. when([
  254. '#component-151-button', '#component-54', '#component-87', '#component-114', '#component-11',
  255. ], remove).then(() => { // all texts and about button :(
  256.  
  257. perma('.tab-nav button', el => el.style.padding !== 'var(--size-1) var(--size-3)', el => {
  258. console.info('padding', el.style.padding)
  259. el.style.padding = 'var(--size-1) var(--size-3)'
  260. }, 100)
  261.  
  262. perma('.tabitem', el => el.style.padding !== '0px', el => {
  263. console.info(el.style.padding)
  264.  
  265. el.style.padding = 0
  266. el.style.border = 0
  267. })
  268. })
  269.  
  270. when('.app', el => {
  271. el.style.margin = '0 auto'
  272. el.style.maxWidth = '100%'
  273. el.style.padding = 0
  274. })
  275.  
  276. when('.tab-nav', el => {
  277. el.style.display = 'flow'
  278. el.style.textAlign = 'center'
  279. })
  280.  
  281. perma('#chatbot', el => el.style.height !== '75vh', el => {
  282. console.info('height', el.style.height)
  283. el.style.height = '75vh'
  284. })
  285.  
  286. perma('.gap', el => el.style.gap !== '6px', el => {
  287. console.info('gap', el.style.gap)
  288. el.style.gap = '6px'
  289. })
  290.  
  291.  
  292. // no-radius
  293. perma([ 'button' ], el => el.style.borderRadius !== '0px', el => {
  294. console.info('border-radius', el.style.borderRadius)
  295. el.style.borderRadius = 0
  296. })
  297.  
  298. perma('#input_box', el => el.style.border !== '0px', el => {
  299. console.info('Found input_box parent')
  300. el.style.border = 0
  301. el.style.padding = 0
  302. el.parentNode.style.border = 0
  303. el.parentNode.style.borderRadius = 0
  304.  
  305. // run on the child textarea
  306. el.querySelector('textarea').style.borderRadius = 0
  307. })
  308.  
  309. // buttons send, 1123
  310. perma([
  311. '.submit-button',
  312. ], el => el.style.minWidth !== '35px', el => {
  313. console.info('buttons send', el.style.minWidth)
  314. el.style.minWidth = '35px'
  315. el.textContent = '⤴️'
  316. })
  317.  
  318. perma('#share-region-named', el => el.style.border !== '0px', el => {
  319. el.style.border = 0
  320. el.style.borderRadius = 0
  321. })
  322.  
  323.  
  324. // gapper
  325. perma('.svelte-15lo0d8', el => el.style.gap !== 'var(--spacing-md)', el => {
  326. console.info('gap', el.style.gap)
  327. el.style.gap = 'var(--spacing-md)'
  328. })
  329.  
  330. when('.built-with', remove, 1000)
  331.  
  332. // When appears "Model B" clicks on "Direct Chat" - I needed to use another setTimeout inside
  333. when('.svelte-nab2ao', () => setTimeout(() => $('#component-123-button').click(), 1000))
  334.  
  335.  
  336. })()