CivitAi Utilities

A script that adds new tools to the civitai image generator

  1. // ==UserScript==
  2. // @name CivitAi Utilities
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.11
  5. // @description A script that adds new tools to the civitai image generator
  6. // @author Pedro6159
  7. // @match https://civitai.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=civitai.com
  9. // @run-at document-end
  10. // @license MIT
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @grant GM_xmlhttpRequest
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18. // Editable variables for users
  19. var delay = 500 // Milliseconds to wait for the script to start injecting into the website.
  20. var enable_infolabels = true; // Creates labels that show positive and negative prompt size
  21. var enable_taglist = true; // Enable tag list box
  22. var tag_list_url = "https://drive.google.com/uc?id=1uR_hEn_6BQz3gP4gO0nhVfF9TY97JSNP"; // You must have a raw tag list url
  23.  
  24. // code
  25.  
  26. // Url
  27. var u = window.document.URL;
  28. var datalist = undefined;
  29. var datalist_bkp = undefined;
  30.  
  31.  
  32. function getElementByXpath(path) {
  33. return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  34. }
  35. // Ele filtra a string para retornar apenas a palavra, remove sintaxes do stable diffusion como "()", "[]", "{}", e os pesos ":x.x"
  36. String.prototype.sdfilter = function () {
  37. return this.toString().replace(/[()\[\]{}'"]|\W[:\d+(\.\d+)?]/g, "").trim().toLowerCase();
  38. };
  39. function addlabels(element) {
  40. // Prompt Label Length
  41. let _pll = document.createElement("span");
  42. _pll.id = "promptlength"; _pll.innerText = "Prompt Length : 0/1024";
  43. // Prompt Label Negativo
  44. let _negpll = document.createElement("span");
  45. _negpll.id = "negpromptlength"; _negpll.innerText = "Neg Prompt Length : 0/1024";
  46. // Adiciona os dois na ordem
  47. element.append(_pll);
  48. element.insertBefore(_pll, element.childNodes[0]);
  49. element.append(_negpll);
  50. element.insertBefore(_negpll, element.childNodes[1]);
  51. }
  52. function initialize_gui(gui) {
  53. const promptarea = getElementByXpath("(//textarea[contains(@class,'input')])[1]")
  54. const negpromptarea = getElementByXpath("(//textarea[contains(@class,'input')])[2]")
  55. promptarea.addEventListener('input', () => {
  56. updatelabel()
  57. });
  58. negpromptarea.addEventListener('input', () => {
  59. updatelabel()
  60. });
  61. if (enable_infolabels)
  62. addlabels(gui.childNodes[0]);
  63. if (enable_taglist)
  64. initialize_autocomplete(gui.childNodes[0]);
  65. console.log("Loaded UI");
  66. }
  67.  
  68. // INICIA A CONSTRUÇÃO DO AUTO COMPLETE
  69. function initialize_autocomplete(element) {
  70. let infodiv = document.createElement("div");
  71. infodiv.id = "infodiv";
  72. element.append(infodiv);
  73. element.insertBefore(infodiv, element.childNodes[2]);
  74. // Infodiv pega o elemento criado pelo script com mesmo ID, então suporta ambas interfaces
  75. // Começa a adicionar o div na interface
  76. let ac_div = document.createElement("div");
  77. ac_div.style = "border: 3px solid rgb(52 52 52); overflow: auto;min-height:100px;height:250px;resize: vertical;";
  78. ac_div.id = "infoautocomplete";
  79. ac_div.beforeunload = function () { alert("deletado") };
  80. infodiv.append(ac_div);
  81. var order_type = 1;
  82. var btn_order = document.createElement("span");
  83. btn_order.style = "font-weight: bold; display: inline-block;border:thin solid;margin: 0 1px;padding:5px;cursor:pointer;"
  84. btn_order.innerText = "Sort (Normal)";
  85. btn_order.onclick = () => {
  86. if (order_type == 1) { // Mudar para ordem alfabetica (A-Z)
  87. datalist.sort();
  88. btn_order.innerText = "Sort (A-Z)";
  89. btn_order.style.color = "#8f9cff";
  90. order_type = 2;
  91. }
  92. else if (order_type == 2) {
  93. datalist.reverse();
  94. btn_order.innerText = "Sort (Z-A)";
  95. btn_order.style.color = "#90ff8f";
  96. order_type = 3;
  97. }
  98. else if (order_type == 3) {
  99. shuffle(datalist);
  100. btn_order.innerText = "Sort (Random)";
  101. btn_order.style.color = "rgb(255 55 219)";
  102. order_type = 4;
  103. }
  104. else if (order_type == 4) {
  105. datalist = Array.from(datalist_bkp);
  106. btn_order.innerText = "Sort (Normal)";
  107. btn_order.style.color = "White";
  108. order_type = 1;
  109. }
  110. autocompletelist.innerHTML = "";
  111. let results = getResults(selectedword[1].sdfilter());
  112. showtags(results);
  113. };
  114. ac_div.append(btn_order);
  115. var order_type_2 = 1;
  116. var btn_order2 = document.createElement("span");
  117. btn_order2.style = "font-weight: bold; display: inline-block;border:thin solid;margin: 0 1px;padding:5px;cursor:pointer;"
  118. btn_order2.innerText = "View (Inline)";
  119. btn_order2.onclick = () => {
  120. if (order_type_2 == 1) { // Mudar para ordem alfabetica (A-Z)
  121. btn_order2.innerText = "View (Vertical)";
  122. btn_order2.style.color = "#8f9cff";
  123. autocompletelist.style.display = "grid";
  124. order_type_2 = 2;
  125. }
  126. else if (order_type_2 == 2) {
  127. datalist.reverse();
  128. btn_order2.innerText = "View (Inline)";
  129. btn_order2.style.color = "White";
  130. autocompletelist.style.display = "inline";
  131. order_type_2 = 1;
  132. }
  133. };
  134. ac_div.append(btn_order2);
  135. ac_div.append(document.createElement("br"));
  136.  
  137. if (datalist == undefined) {
  138. // Label dizendo que esta carregando a lista
  139. var labelloading = document.createElement("p");
  140. labelloading.id = "loadlabel";
  141. labelloading.style = "font-size:24px;font-weight: bold;color:Cyan"
  142. labelloading.innerText = "Loading TagList...";
  143. ac_div.append(labelloading);
  144. }
  145. // Elemento de lista de resultados
  146. var autocompletelist = document.createElement("ul");
  147. autocompletelist.style = "display:inline;padding-left: 0px;";
  148. autocompletelist.id = "results";
  149. ac_div.append(autocompletelist);
  150.  
  151.  
  152.  
  153. if (datalist == undefined) {
  154. GM_xmlhttpRequest({
  155. method: "GET",
  156. // Url da lista de TAgs
  157. url: tag_list_url,
  158. onload: (ev) => {
  159. // Foi carregado e atualiza o label
  160. datalist = ev.responseText.match(/[^\r\n]+/g);
  161. var versioninfo = datalist[0];
  162. if (!versioninfo.startsWith("("))
  163. versioninfo = "Customized";
  164. labelloading.innerText = "Tag list loaded.\r\nCurrent version : " + versioninfo;
  165. console.log("AutoComplete Tags Loaded | " + versioninfo);
  166. labelloading.style.color = "#66FF99";
  167. labelloading.style.cursor = "pointer";
  168. datalist.shift();
  169. datalist_bkp = Array.from(datalist);
  170. // Avisar que foi carregado e fazer animação do label desaparecer lentamente
  171. var o = 0;
  172. var fade = setInterval(function () {
  173. if (o < 100) {
  174. labelloading.style.opacity = ((100 - o) + "%"); o = o + 0.11;
  175. }
  176. else {
  177. clearInterval(fade); labelloading.remove();
  178. }
  179. }, 1);
  180. labelloading.onclick = function () {
  181. labelloading.remove();
  182. clearInterval(fade);
  183. }
  184. },
  185. onerror: () => {
  186. console.error("List not loaded, invalid url.");
  187. }
  188. });
  189. }
  190.  
  191. var promptarea = getElementByXpath("(//textarea[contains(@class,'input')])[1]");
  192. // Pega a caixa de texto do prompt dependendo da interface
  193. var selectedword;
  194. promptarea.oninput = function () {
  195. // Esse limpa tudo.
  196. autocompletelist.innerHTML = "";
  197. // Começa a progurar por tags
  198. selectedword = getWord(promptarea);
  199. let results = getResults(selectedword[1].sdfilter());
  200. showtags(results);
  201. };
  202. function showtags(results) {
  203. if (results.length > 0) {
  204. // Ele exibe a lista de tags em relação aos caractere que tu escreveu
  205. let i = 0;
  206. for (i; i < results.length; i++) {
  207. const tags = document.createElement("li");
  208. tags.style = "display: inline-block;padding: 0.5rem;border-color:white;border:solid;border-width:thin";
  209. tags.className = "tags";
  210. if (results[i][1] == 1) {
  211. tags.style["background-color"] = "#302D00";
  212. tags.innerText = results[i][0];
  213. autocompletelist.append(tags);
  214. autocompletelist.insertBefore(tags, autocompletelist.childNodes[0]);
  215. }
  216. else {
  217. tags.innerText = results[i][0];
  218. autocompletelist.append(tags);
  219. }
  220.  
  221. }
  222. // Caso chegue até o final, não vai adicionar o botão de "Mostrar Mais"
  223. if (i > 40) {
  224. autocompletelist.innerHTML += "<li id='showmore' style='display: inline-block;padding: 0.5rem;border-color:white;border:solid;border-width:thin;background-color:darkblue'>Show More</li>";
  225. // Função de mostrar mais
  226. document.getElementById("showmore").onclick = () => {
  227. // O texto da ultima tag
  228. const lasttagarea = autocompletelist.childNodes[autocompletelist.childNodes.length - 2].innerText;
  229. // Começa a procurar mais tags
  230. let results = getResults(selectedword[1].sdfilter(), lasttagarea);
  231. // Remove o "Show More"
  232. autocompletelist.childNodes[autocompletelist.childNodes.length - 1].remove();
  233. showtags(results);
  234. };
  235. }
  236. }
  237. }
  238. // Obtem o array a partir da palavra que você escreveu para auto completar, que tem relação a lista de tags
  239. function getResults(input, last) {
  240. input = input.replace(/[_+-]+/g, " ");
  241. const results = [];
  242. let i = 0;
  243. // Caso você tiver clicado em "Mostrar Mais", ele pega a ultima tag da lista e continua a procurar por mais
  244. if (last) {
  245. i = searchStringInArray(last, datalist) + 1
  246. }
  247.  
  248. // Pega palavras proximas
  249. const l = datalist.length;
  250. for (i; i < l; i++) {
  251. if (input === datalist[i].slice(0, input.length)) {
  252. if (input == datalist[i])
  253. results.push([datalist[i], 1]); // 1 a palavra é igual a tag
  254. else
  255. results.push([datalist[i], 0]); // 0 sugestoes de tag
  256.  
  257. if (results.length > 40)
  258. break;
  259. }
  260.  
  261. }
  262. return results;
  263. }
  264. // Quando você clicar em umas das tags para completar a palavra
  265. autocompletelist.onclick = function (event) {
  266. // Identificador tags, para não pensar que é aquele botão de 'mostrar mais'
  267. if (event.target.className != "tags")
  268. return;
  269. const setValue = event.target.innerText;
  270. // Separa o prompt em virgulas e remove espaços vazios
  271. let valuetext = promptarea.value.split(",").map(function (word) {
  272. return word.trim();
  273. });
  274. // Começa a procurar a palavra no array separado em virgula e depois substituir
  275. for (let i = 0; i < valuetext.length; i++) {
  276. if (valuetext[i].trim() == selectedword[1]) {
  277. // Preserva os parenteses e a sintaxe de peso
  278. const word = valuetext[i].sdfilter();
  279. valuetext[i] = valuetext[i].replace(word, setValue);
  280. }
  281. }
  282. promptarea.value = valuetext.join(", ");
  283. this.innerHTML = "";
  284. };
  285. }
  286.  
  287. // Procura a string no array retornando apenas o index
  288. function searchStringInArray(str, strArray) {
  289. const l = strArray.length;
  290. for (var j = 0; j < l; j++) {
  291. if (strArray[j].match(str)) return j;
  292. }
  293. return -1;
  294. };
  295. function getWord(textarea) {
  296. var cursorPos = textarea.selectionStart;
  297. var text = textarea.value;
  298.  
  299. // Encontrar a palavra na posição do cursor
  300. var start = text.lastIndexOf(',', cursorPos - 1) + 1;
  301. var end = text.indexOf(',', cursorPos);
  302. end = end === -1 ? text.length : end;
  303.  
  304. // Extrair a palavra
  305. var selectedWord = text.substring(start, end).trim();
  306.  
  307. // Separar as palavras por vírgula
  308. var wordsArray = text.split(',').map(function (word) {
  309. return word.trim();
  310. });
  311. var selectedIndex = wordsArray.indexOf(selectedWord);
  312. //console.log("Posição do cursor:", cursorPos);
  313. //console.log("Palavra selecionada:", selectedWord);
  314. //console.log("Array de palavras:", wordsArray);
  315. return [selectedIndex, selectedWord.trim(), wordsArray];
  316. };
  317. // Aleatorizar Array
  318. function shuffle(array) {
  319. let currentIndex = array.length, randomIndex;
  320.  
  321. // While there remain elements to shuffle.
  322. while (currentIndex > 0) {
  323.  
  324. // Pick a remaining element.
  325. randomIndex = Math.floor(Math.random() * currentIndex);
  326. currentIndex--;
  327.  
  328. // And swap it with the current element.
  329. [array[currentIndex], array[randomIndex]] = [
  330. array[randomIndex], array[currentIndex]];
  331. }
  332.  
  333. return array;
  334. };
  335. // Atualiza as informações do tamanho do prompt positivo e negativo
  336. function updatelabel() {
  337. document.getElementById("promptlength").innerText = "Prompt Length : " + getElementByXpath("(//textarea[contains(@class,'input')])[1]").value.length;
  338. document.getElementById("negpromptlength").innerText = "Neg Prompt Length : " + getElementByXpath("(//textarea[contains(@class,'input')])[2]").value.length;
  339. };
  340. window.onload = function () {
  341. if (document.readyState == 'complete') {
  342. var gui;
  343. console.log("Trying to find the interface to inject the script...");
  344. var tempo = setInterval(function () {
  345. if (!document.getElementById("infodiv")) {
  346. gui = getElementByXpath("//form/div[1]/div[1]/div[not(@style)]/div[3]");
  347. if (gui) {
  348. initialize_gui(gui);
  349. }
  350. }
  351. }, delay);
  352. window.onload = null;
  353. }
  354. };
  355. })();