// ==UserScript==
// @name CivitAi Utilities
// @namespace http://tampermonkey.net/
// @version 0.11
// @description A script that adds new tools to the civitai image generator
// @author Pedro6159
// @match https://civitai.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=civitai.com
// @run-at document-end
// @license MIT
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function () {
'use strict';
// Editable variables for users
var delay = 500 // Milliseconds to wait for the script to start injecting into the website.
var enable_infolabels = true; // Creates labels that show positive and negative prompt size
var enable_taglist = true; // Enable tag list box
var tag_list_url = "https://drive.google.com/uc?id=1uR_hEn_6BQz3gP4gO0nhVfF9TY97JSNP"; // You must have a raw tag list url
// code
// Url
var u = window.document.URL;
var datalist = undefined;
var datalist_bkp = undefined;
function getElementByXpath(path) {
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}
// Ele filtra a string para retornar apenas a palavra, remove sintaxes do stable diffusion como "()", "[]", "{}", e os pesos ":x.x"
String.prototype.sdfilter = function () {
return this.toString().replace(/[()\[\]{}'"]|\W[:\d+(\.\d+)?]/g, "").trim().toLowerCase();
};
function addlabels(element) {
// Prompt Label Length
let _pll = document.createElement("span");
_pll.id = "promptlength"; _pll.innerText = "Prompt Length : 0/1024";
// Prompt Label Negativo
let _negpll = document.createElement("span");
_negpll.id = "negpromptlength"; _negpll.innerText = "Neg Prompt Length : 0/1024";
// Adiciona os dois na ordem
element.append(_pll);
element.insertBefore(_pll, element.childNodes[0]);
element.append(_negpll);
element.insertBefore(_negpll, element.childNodes[1]);
}
function initialize_gui(gui) {
const promptarea = getElementByXpath("(//textarea[contains(@class,'input')])[1]")
const negpromptarea = getElementByXpath("(//textarea[contains(@class,'input')])[2]")
promptarea.addEventListener('input', () => {
updatelabel()
});
negpromptarea.addEventListener('input', () => {
updatelabel()
});
if (enable_infolabels)
addlabels(gui.childNodes[0]);
if (enable_taglist)
initialize_autocomplete(gui.childNodes[0]);
console.log("Loaded UI");
}
// INICIA A CONSTRUÇÃO DO AUTO COMPLETE
function initialize_autocomplete(element) {
let infodiv = document.createElement("div");
infodiv.id = "infodiv";
element.append(infodiv);
element.insertBefore(infodiv, element.childNodes[2]);
// Infodiv pega o elemento criado pelo script com mesmo ID, então suporta ambas interfaces
// Começa a adicionar o div na interface
let ac_div = document.createElement("div");
ac_div.style = "border: 3px solid rgb(52 52 52); overflow: auto;min-height:100px;height:250px;resize: vertical;";
ac_div.id = "infoautocomplete";
ac_div.beforeunload = function () { alert("deletado") };
infodiv.append(ac_div);
var order_type = 1;
var btn_order = document.createElement("span");
btn_order.style = "font-weight: bold; display: inline-block;border:thin solid;margin: 0 1px;padding:5px;cursor:pointer;"
btn_order.innerText = "Sort (Normal)";
btn_order.onclick = () => {
if (order_type == 1) { // Mudar para ordem alfabetica (A-Z)
datalist.sort();
btn_order.innerText = "Sort (A-Z)";
btn_order.style.color = "#8f9cff";
order_type = 2;
}
else if (order_type == 2) {
datalist.reverse();
btn_order.innerText = "Sort (Z-A)";
btn_order.style.color = "#90ff8f";
order_type = 3;
}
else if (order_type == 3) {
shuffle(datalist);
btn_order.innerText = "Sort (Random)";
btn_order.style.color = "rgb(255 55 219)";
order_type = 4;
}
else if (order_type == 4) {
datalist = Array.from(datalist_bkp);
btn_order.innerText = "Sort (Normal)";
btn_order.style.color = "White";
order_type = 1;
}
autocompletelist.innerHTML = "";
let results = getResults(selectedword[1].sdfilter());
showtags(results);
};
ac_div.append(btn_order);
var order_type_2 = 1;
var btn_order2 = document.createElement("span");
btn_order2.style = "font-weight: bold; display: inline-block;border:thin solid;margin: 0 1px;padding:5px;cursor:pointer;"
btn_order2.innerText = "View (Inline)";
btn_order2.onclick = () => {
if (order_type_2 == 1) { // Mudar para ordem alfabetica (A-Z)
btn_order2.innerText = "View (Vertical)";
btn_order2.style.color = "#8f9cff";
autocompletelist.style.display = "grid";
order_type_2 = 2;
}
else if (order_type_2 == 2) {
datalist.reverse();
btn_order2.innerText = "View (Inline)";
btn_order2.style.color = "White";
autocompletelist.style.display = "inline";
order_type_2 = 1;
}
};
ac_div.append(btn_order2);
ac_div.append(document.createElement("br"));
if (datalist == undefined) {
// Label dizendo que esta carregando a lista
var labelloading = document.createElement("p");
labelloading.id = "loadlabel";
labelloading.style = "font-size:24px;font-weight: bold;color:Cyan"
labelloading.innerText = "Loading TagList...";
ac_div.append(labelloading);
}
// Elemento de lista de resultados
var autocompletelist = document.createElement("ul");
autocompletelist.style = "display:inline;padding-left: 0px;";
autocompletelist.id = "results";
ac_div.append(autocompletelist);
if (datalist == undefined) {
GM_xmlhttpRequest({
method: "GET",
// Url da lista de TAgs
url: tag_list_url,
onload: (ev) => {
// Foi carregado e atualiza o label
datalist = ev.responseText.match(/[^\r\n]+/g);
var versioninfo = datalist[0];
if (!versioninfo.startsWith("("))
versioninfo = "Customized";
labelloading.innerText = "Tag list loaded.\r\nCurrent version : " + versioninfo;
console.log("AutoComplete Tags Loaded | " + versioninfo);
labelloading.style.color = "#66FF99";
labelloading.style.cursor = "pointer";
datalist.shift();
datalist_bkp = Array.from(datalist);
// Avisar que foi carregado e fazer animação do label desaparecer lentamente
var o = 0;
var fade = setInterval(function () {
if (o < 100) {
labelloading.style.opacity = ((100 - o) + "%"); o = o + 0.11;
}
else {
clearInterval(fade); labelloading.remove();
}
}, 1);
labelloading.onclick = function () {
labelloading.remove();
clearInterval(fade);
}
},
onerror: () => {
console.error("List not loaded, invalid url.");
}
});
}
var promptarea = getElementByXpath("(//textarea[contains(@class,'input')])[1]");
// Pega a caixa de texto do prompt dependendo da interface
var selectedword;
promptarea.oninput = function () {
// Esse limpa tudo.
autocompletelist.innerHTML = "";
// Começa a progurar por tags
selectedword = getWord(promptarea);
let results = getResults(selectedword[1].sdfilter());
showtags(results);
};
function showtags(results) {
if (results.length > 0) {
// Ele exibe a lista de tags em relação aos caractere que tu escreveu
let i = 0;
for (i; i < results.length; i++) {
const tags = document.createElement("li");
tags.style = "display: inline-block;padding: 0.5rem;border-color:white;border:solid;border-width:thin";
tags.className = "tags";
if (results[i][1] == 1) {
tags.style["background-color"] = "#302D00";
tags.innerText = results[i][0];
autocompletelist.append(tags);
autocompletelist.insertBefore(tags, autocompletelist.childNodes[0]);
}
else {
tags.innerText = results[i][0];
autocompletelist.append(tags);
}
}
// Caso chegue até o final, não vai adicionar o botão de "Mostrar Mais"
if (i > 40) {
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>";
// Função de mostrar mais
document.getElementById("showmore").onclick = () => {
// O texto da ultima tag
const lasttagarea = autocompletelist.childNodes[autocompletelist.childNodes.length - 2].innerText;
// Começa a procurar mais tags
let results = getResults(selectedword[1].sdfilter(), lasttagarea);
// Remove o "Show More"
autocompletelist.childNodes[autocompletelist.childNodes.length - 1].remove();
showtags(results);
};
}
}
}
// Obtem o array a partir da palavra que você escreveu para auto completar, que tem relação a lista de tags
function getResults(input, last) {
input = input.replace(/[_+-]+/g, " ");
const results = [];
let i = 0;
// Caso você tiver clicado em "Mostrar Mais", ele pega a ultima tag da lista e continua a procurar por mais
if (last) {
i = searchStringInArray(last, datalist) + 1
}
// Pega palavras proximas
const l = datalist.length;
for (i; i < l; i++) {
if (input === datalist[i].slice(0, input.length)) {
if (input == datalist[i])
results.push([datalist[i], 1]); // 1 a palavra é igual a tag
else
results.push([datalist[i], 0]); // 0 sugestoes de tag
if (results.length > 40)
break;
}
}
return results;
}
// Quando você clicar em umas das tags para completar a palavra
autocompletelist.onclick = function (event) {
// Identificador tags, para não pensar que é aquele botão de 'mostrar mais'
if (event.target.className != "tags")
return;
const setValue = event.target.innerText;
// Separa o prompt em virgulas e remove espaços vazios
let valuetext = promptarea.value.split(",").map(function (word) {
return word.trim();
});
// Começa a procurar a palavra no array separado em virgula e depois substituir
for (let i = 0; i < valuetext.length; i++) {
if (valuetext[i].trim() == selectedword[1]) {
// Preserva os parenteses e a sintaxe de peso
const word = valuetext[i].sdfilter();
valuetext[i] = valuetext[i].replace(word, setValue);
}
}
promptarea.value = valuetext.join(", ");
this.innerHTML = "";
};
}
// Procura a string no array retornando apenas o index
function searchStringInArray(str, strArray) {
const l = strArray.length;
for (var j = 0; j < l; j++) {
if (strArray[j].match(str)) return j;
}
return -1;
};
function getWord(textarea) {
var cursorPos = textarea.selectionStart;
var text = textarea.value;
// Encontrar a palavra na posição do cursor
var start = text.lastIndexOf(',', cursorPos - 1) + 1;
var end = text.indexOf(',', cursorPos);
end = end === -1 ? text.length : end;
// Extrair a palavra
var selectedWord = text.substring(start, end).trim();
// Separar as palavras por vírgula
var wordsArray = text.split(',').map(function (word) {
return word.trim();
});
var selectedIndex = wordsArray.indexOf(selectedWord);
//console.log("Posição do cursor:", cursorPos);
//console.log("Palavra selecionada:", selectedWord);
//console.log("Array de palavras:", wordsArray);
return [selectedIndex, selectedWord.trim(), wordsArray];
};
// Aleatorizar Array
function shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex > 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
};
// Atualiza as informações do tamanho do prompt positivo e negativo
function updatelabel() {
document.getElementById("promptlength").innerText = "Prompt Length : " + getElementByXpath("(//textarea[contains(@class,'input')])[1]").value.length;
document.getElementById("negpromptlength").innerText = "Neg Prompt Length : " + getElementByXpath("(//textarea[contains(@class,'input')])[2]").value.length;
};
window.onload = function () {
if (document.readyState == 'complete') {
var gui;
console.log("Trying to find the interface to inject the script...");
var tempo = setInterval(function () {
if (!document.getElementById("infodiv")) {
gui = getElementByXpath("//form/div[1]/div[1]/div[not(@style)]/div[3]");
if (gui) {
initialize_gui(gui);
}
}
}, delay);
window.onload = null;
}
};
})();