mirror of
https://github.com/DominikDoom/a1111-sd-webui-tagcomplete.git
synced 2026-01-26 11:09:54 +00:00
Move the previously global util functions to TacUtils class & update references
(to prevent naming conflicts and have cleaner modularization)
This commit is contained in:
@@ -1,425 +1,566 @@
|
||||
// Utility functions for tag autocomplete
|
||||
|
||||
// Parse the CSV file into a 2D array. Doesn't use regex, so it is very lightweight.
|
||||
// We are ignoring newlines in quote fields since we expect one-line entries and parsing would break for unclosed quotes otherwise
|
||||
function parseCSV(str) {
|
||||
const arr = [];
|
||||
let quote = false; // 'true' means we're inside a quoted field
|
||||
class TacUtils {
|
||||
/**
|
||||
* Parses a CSV file into a 2D array. Doesn't use regex, so it is very lightweight.
|
||||
* We are ignoring newlines in quote fields since we expect one-line entries and parsing would break for unclosed quotes otherwise
|
||||
* @param {String} str - The CSV string to parse (likely from a file with multiple lines)
|
||||
* @returns {string[][]} A 2D array of CSV entries (rows and columns of that row)
|
||||
*/
|
||||
static parseCSV(str) {
|
||||
const arr = [];
|
||||
let quote = false; // 'true' means we're inside a quoted field
|
||||
|
||||
// Iterate over each character, keep track of current row and column (of the returned array)
|
||||
for (let row = 0, col = 0, c = 0; c < str.length; c++) {
|
||||
let cc = str[c], nc = str[c+1]; // Current character, next character
|
||||
arr[row] = arr[row] || []; // Create a new row if necessary
|
||||
arr[row][col] = arr[row][col] || ''; // Create a new column (start with empty string) if necessary
|
||||
// Iterate over each character, keep track of current row and column (of the returned array)
|
||||
for (let row = 0, col = 0, c = 0; c < str.length; c++) {
|
||||
let cc = str[c], nc = str[c+1]; // Current character, next character
|
||||
arr[row] = arr[row] || []; // Create a new row if necessary
|
||||
arr[row][col] = arr[row][col] || ''; // Create a new column (start with empty string) if necessary
|
||||
|
||||
// If the current character is a quotation mark, and we're inside a
|
||||
// quoted field, and the next character is also a quotation mark,
|
||||
// add a quotation mark to the current column and skip the next character
|
||||
if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
|
||||
// If the current character is a quotation mark, and we're inside a
|
||||
// quoted field, and the next character is also a quotation mark,
|
||||
// add a quotation mark to the current column and skip the next character
|
||||
if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
|
||||
|
||||
// If it's just one quotation mark, begin/end quoted field
|
||||
if (cc == '"') { quote = !quote; continue; }
|
||||
// If it's just one quotation mark, begin/end quoted field
|
||||
if (cc == '"') { quote = !quote; continue; }
|
||||
|
||||
// If it's a comma and we're not in a quoted field, move on to the next column
|
||||
if (cc == ',' && !quote) { ++col; continue; }
|
||||
// If it's a comma and we're not in a quoted field, move on to the next column
|
||||
if (cc == ',' && !quote) { ++col; continue; }
|
||||
|
||||
// If it's a newline (CRLF), skip the next character and move on to the next row and move to column 0 of that new row
|
||||
if (cc == '\r' && nc == '\n') { ++row; col = 0; ++c; quote = false; continue; }
|
||||
// If it's a newline (CRLF), skip the next character and move on to the next row and move to column 0 of that new row
|
||||
if (cc == '\r' && nc == '\n') { ++row; col = 0; ++c; quote = false; continue; }
|
||||
|
||||
// If it's a newline (LF or CR) move on to the next row and move to column 0 of that new row
|
||||
if (cc == '\n') { ++row; col = 0; quote = false; continue; }
|
||||
if (cc == '\r') { ++row; col = 0; quote = false; continue; }
|
||||
// If it's a newline (LF or CR) move on to the next row and move to column 0 of that new row
|
||||
if (cc == '\n') { ++row; col = 0; quote = false; continue; }
|
||||
if (cc == '\r') { ++row; col = 0; quote = false; continue; }
|
||||
|
||||
// Otherwise, append the current character to the current column
|
||||
arr[row][col] += cc;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
// Load file
|
||||
async function readFile(filePath, json = false, cache = false) {
|
||||
if (!cache)
|
||||
filePath += `?${new Date().getTime()}`;
|
||||
|
||||
let response = await fetch(`file=${filePath}`);
|
||||
|
||||
if (response.status != 200) {
|
||||
console.error(`Error loading file "${filePath}": ` + response.status, response.statusText);
|
||||
return null;
|
||||
// Otherwise, append the current character to the current column
|
||||
arr[row][col] += cc;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
if (json)
|
||||
/** Wrapper function to read a file from a path, using Gradio's "file="" accessor API
|
||||
* @param {String} filePath - The path to the file
|
||||
* @param {Boolean} json - Whether to parse the file as JSON
|
||||
* @param {Boolean} cache - Whether to cache the response
|
||||
* @returns {Promise<String | any>} The file content as a string or JSON object (if json is true)
|
||||
*/
|
||||
static async readFile(filePath, json = false, cache = false) {
|
||||
if (!cache)
|
||||
filePath += `?${new Date().getTime()}`;
|
||||
|
||||
let response = await fetch(`file=${filePath}`);
|
||||
|
||||
if (response.status != 200) {
|
||||
console.error(`Error loading file "${filePath}": ` + response.status, response.statusText);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (json)
|
||||
return await response.json();
|
||||
else
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
/** Wrapper function to read a file from the path and parse it as CSV
|
||||
* @param {String} path - The path to the CSV file
|
||||
* @returns {Promise<String[][]>} A 2D array of CSV entries
|
||||
*/
|
||||
static async loadCSV(path) {
|
||||
let text = await this.readFile(path);
|
||||
return this.parseCSV(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the TAC API for a GET request
|
||||
* @param {String} url - The URL to fetch from
|
||||
* @param {Boolean} json - Whether to parse the response as JSON or plain text
|
||||
* @param {Boolean} cache - Whether to cache the response
|
||||
* @returns {Promise<any | String>} JSON or text response from the API, depending on the "json" parameter
|
||||
*/
|
||||
static async fetchAPI(url, json = true, cache = false) {
|
||||
if (!cache) {
|
||||
const appendChar = url.includes("?") ? "&" : "?";
|
||||
url += `${appendChar}${new Date().getTime()}`
|
||||
}
|
||||
|
||||
let response = await fetch(url);
|
||||
|
||||
if (response.status != 200) {
|
||||
console.error(`Error fetching API endpoint "${url}": ` + response.status, response.statusText);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (json)
|
||||
return await response.json();
|
||||
else
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts to the TAC API
|
||||
* @param {String} url - The URL to post to
|
||||
* @param {String} body - (optional) The body of the POST request as a JSON string
|
||||
* @returns JSON response from the API
|
||||
*/
|
||||
static async postAPI(url, body = null) {
|
||||
let response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: body
|
||||
});
|
||||
|
||||
if (response.status != 200) {
|
||||
console.error(`Error posting to API endpoint "${url}": ` + response.status, response.statusText);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
else
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
// Load CSV
|
||||
async function loadCSV(path) {
|
||||
let text = await readFile(path);
|
||||
return parseCSV(text);
|
||||
}
|
||||
|
||||
// Fetch API
|
||||
async function fetchTacAPI(url, json = true, cache = false) {
|
||||
if (!cache) {
|
||||
const appendChar = url.includes("?") ? "&" : "?";
|
||||
url += `${appendChar}${new Date().getTime()}`
|
||||
}
|
||||
|
||||
let response = await fetch(url);
|
||||
/**
|
||||
* Puts to the TAC API
|
||||
* @param {String} url - The URL to post to
|
||||
* @param {String} body - (optional) The body of the PUT request as a JSON string
|
||||
* @returns JSON response from the API
|
||||
*/
|
||||
static async putAPI(url, body = null) {
|
||||
let response = await fetch(url, { method: "PUT", body: body });
|
||||
|
||||
if (response.status != 200) {
|
||||
console.error(`Error putting to API endpoint "${url}": ` + response.status, response.statusText);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.status != 200) {
|
||||
console.error(`Error fetching API endpoint "${url}": ` + response.status, response.statusText);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (json)
|
||||
return await response.json();
|
||||
else
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
async function postTacAPI(url, body = null) {
|
||||
let response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: body
|
||||
});
|
||||
|
||||
if (response.status != 200) {
|
||||
console.error(`Error posting to API endpoint "${url}": ` + response.status, response.statusText);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async function putTacAPI(url, body = null) {
|
||||
let response = await fetch(url, { method: "PUT", body: body });
|
||||
|
||||
if (response.status != 200) {
|
||||
console.error(`Error putting to API endpoint "${url}": ` + response.status, response.statusText);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
// Extra network preview thumbnails
|
||||
async function getTacExtraNetworkPreviewURL(filename, type) {
|
||||
const previewJSON = await fetchTacAPI(`tacapi/v1/thumb-preview/${filename}?type=${type}`, true, true);
|
||||
if (previewJSON?.url) {
|
||||
const properURL = `sd_extra_networks/thumb?filename=${previewJSON.url}`;
|
||||
if ((await fetch(properURL)).status == 200) {
|
||||
return properURL;
|
||||
/**
|
||||
* Get a preview image URL for a given extra network file.
|
||||
* Uses the official webui endpoint if available, otherwise creates a blob URL.
|
||||
* @param {String} filename - The filename of the extra network file
|
||||
* @param {String} type - One of "embed", "hyper", "lora", or "lyco", to determine the lookup location
|
||||
* @returns {Promise<String>} URL to a preview image for the extra network file, if available
|
||||
*/
|
||||
static async getExtraNetworkPreviewURL(filename, type) {
|
||||
const previewJSON = await this.fetchAPI(`tacapi/v1/thumb-preview/${filename}?type=${type}`, true, true);
|
||||
if (previewJSON?.url) {
|
||||
const properURL = `sd_extra_networks/thumb?filename=${previewJSON.url}`;
|
||||
if ((await fetch(properURL)).status == 200) {
|
||||
return properURL;
|
||||
} else {
|
||||
// create blob url
|
||||
const blob = await (await fetch(`tacapi/v1/thumb-preview-blob/${filename}?type=${type}`)).blob();
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
} else {
|
||||
// create blob url
|
||||
const blob = await (await fetch(`tacapi/v1/thumb-preview-blob/${filename}?type=${type}`)).blob();
|
||||
return URL.createObjectURL(blob);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
lastStyleRefresh = 0;
|
||||
// Refresh style file if needed
|
||||
async function refreshStyleNamesIfChanged() {
|
||||
// Only refresh once per second
|
||||
currentTimestamp = new Date().getTime();
|
||||
if (currentTimestamp - lastStyleRefresh < 1000) return;
|
||||
lastStyleRefresh = currentTimestamp;
|
||||
static lastStyleRefresh = 0;
|
||||
/**
|
||||
* Refreshes the styles.txt file if it has changed since the last check.
|
||||
* Checks at most once per second to prevent spamming the API.
|
||||
*/
|
||||
static async refreshStyleNamesIfChanged() {
|
||||
// Only refresh once per second
|
||||
let currentTimestamp = new Date().getTime();
|
||||
if (currentTimestamp - lastStyleRefresh < 1000) return;
|
||||
this.lastStyleRefresh = currentTimestamp;
|
||||
|
||||
const response = await fetch(`tacapi/v1/refresh-styles-if-changed?${new Date().getTime()}`)
|
||||
if (response.status === 304) {
|
||||
// Not modified
|
||||
} else if (response.status === 200) {
|
||||
// Reload
|
||||
QUEUE_FILE_LOAD.forEach(async fn => {
|
||||
if (fn.toString().includes("styleNames"))
|
||||
await fn.call(null, true);
|
||||
})
|
||||
} else {
|
||||
// Error
|
||||
console.error(`Error refreshing styles.txt: ` + response.status, response.statusText);
|
||||
const response = await fetch(`tacapi/v1/refresh-styles-if-changed?${new Date().getTime()}`)
|
||||
if (response.status === 304) {
|
||||
// Not modified
|
||||
} else if (response.status === 200) {
|
||||
// Reload
|
||||
QUEUE_FILE_LOAD.forEach(async fn => {
|
||||
if (fn.toString().includes("styleNames"))
|
||||
await fn.call(null, true);
|
||||
})
|
||||
} else {
|
||||
// Error
|
||||
console.error(`Error refreshing styles.txt: ` + response.status, response.statusText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debounce function to prevent spamming the autocomplete function
|
||||
var dbTimeOut;
|
||||
const debounce = (func, wait = 300) => {
|
||||
return function (...args) {
|
||||
if (dbTimeOut) {
|
||||
clearTimeout(dbTimeOut);
|
||||
static dbTimeOut;
|
||||
/**
|
||||
* Generic debounce function to prevent spamming the autocompletion during fast typing
|
||||
* @param {Function} func - The function to debounce
|
||||
* @param {Number} wait - The debounce time in milliseconds
|
||||
* @returns {Function} The debounced function
|
||||
*/
|
||||
static debounce = (func, wait = 300) => {
|
||||
return function (...args) {
|
||||
if (this.dbTimeOut) {
|
||||
clearTimeout(this.dbTimeOut);
|
||||
}
|
||||
|
||||
this.dbTimeOut = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, wait);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the difference between two arrays (order-sensitive).
|
||||
* Fixes duplicates not being seen as changes in a normal filter function.
|
||||
* @param {Array} a
|
||||
* @param {Array} b
|
||||
* @returns {Array} The difference between the two arrays
|
||||
*/
|
||||
static difference(a, b) {
|
||||
if (a.length == 0) {
|
||||
return b;
|
||||
}
|
||||
if (b.length == 0) {
|
||||
return a;
|
||||
}
|
||||
|
||||
dbTimeOut = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, wait);
|
||||
}
|
||||
}
|
||||
|
||||
// Difference function to fix duplicates not being seen as changes in normal filter
|
||||
function difference(a, b) {
|
||||
if (a.length == 0) {
|
||||
return b;
|
||||
}
|
||||
if (b.length == 0) {
|
||||
return a;
|
||||
return [...b.reduce((acc, v) => acc.set(v, (acc.get(v) || 0) - 1),
|
||||
a.reduce((acc, v) => acc.set(v, (acc.get(v) || 0) + 1), new Map())
|
||||
)].reduce((acc, [v, count]) => acc.concat(Array(Math.abs(count)).fill(v)), []);
|
||||
}
|
||||
|
||||
return [...b.reduce((acc, v) => acc.set(v, (acc.get(v) || 0) - 1),
|
||||
a.reduce((acc, v) => acc.set(v, (acc.get(v) || 0) + 1), new Map())
|
||||
)].reduce((acc, [v, count]) => acc.concat(Array(Math.abs(count)).fill(v)), []);
|
||||
}
|
||||
|
||||
// Object flatten function adapted from https://stackoverflow.com/a/61602592
|
||||
// $roots keeps previous parent properties as they will be added as a prefix for each prop.
|
||||
// $sep is just a preference if you want to seperate nested paths other than dot.
|
||||
function flatten(obj, roots = [], sep = ".") {
|
||||
return Object.keys(obj).reduce(
|
||||
(memo, prop) =>
|
||||
Object.assign(
|
||||
// create a new object
|
||||
{},
|
||||
// include previously returned object
|
||||
memo,
|
||||
Object.prototype.toString.call(obj[prop]) === "[object Object]"
|
||||
? // keep working if value is an object
|
||||
flatten(obj[prop], roots.concat([prop]), sep)
|
||||
: // include current prop and value and prefix prop with the roots
|
||||
{ [roots.concat([prop]).join(sep)]: obj[prop] }
|
||||
),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate biased tag score based on post count and frequent usage
|
||||
function calculateUsageBias(result, count, uses) {
|
||||
// Check setting conditions
|
||||
if (uses < TAC_CFG.frequencyMinCount) {
|
||||
uses = 0;
|
||||
} else if (uses != 0) {
|
||||
result.usageBias = true;
|
||||
/**
|
||||
* Object flatten function adapted from https://stackoverflow.com/a/61602592
|
||||
* @param {*} obj - The object to flatten
|
||||
* @param {Array} roots - Keeps previous parent properties as they will be added as a prefix for each prop.
|
||||
* @param {String} sep - Just a preference if you want to seperate nested paths other than dot.
|
||||
* @returns The flattened object
|
||||
*/
|
||||
static flatten(obj, roots = [], sep = ".") {
|
||||
return Object.keys(obj).reduce(
|
||||
(memo, prop) =>
|
||||
Object.assign(
|
||||
// create a new object
|
||||
{},
|
||||
// include previously returned object
|
||||
memo,
|
||||
Object.prototype.toString.call(obj[prop]) === "[object Object]"
|
||||
? // keep working if value is an object
|
||||
flatten(obj[prop], roots.concat([prop]), sep)
|
||||
: // include current prop and value and prefix prop with the roots
|
||||
{ [roots.concat([prop]).join(sep)]: obj[prop] }
|
||||
),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
switch (TAC_CFG.frequencyFunction) {
|
||||
case "Logarithmic (weak)":
|
||||
return Math.log(1 + count) + Math.log(1 + uses);
|
||||
case "Logarithmic (strong)":
|
||||
return Math.log(1 + count) + 2 * Math.log(1 + uses);
|
||||
case "Usage first":
|
||||
return uses;
|
||||
default:
|
||||
return count;
|
||||
/**
|
||||
* Calculate biased tag score based on post count and frequent usage
|
||||
* @param {AutocompleteResult} result - The unbiased result
|
||||
* @param {Number} count - The post count (or similar base metric)
|
||||
* @param {Number} uses - The usage count
|
||||
* @returns {Number} The biased score for sorting
|
||||
*/
|
||||
static calculateUsageBias(result, count, uses) {
|
||||
// Check setting conditions
|
||||
if (uses < TAC_CFG.frequencyMinCount) {
|
||||
uses = 0;
|
||||
} else if (uses != 0) {
|
||||
result.usageBias = true;
|
||||
}
|
||||
|
||||
switch (TAC_CFG.frequencyFunction) {
|
||||
case "Logarithmic (weak)":
|
||||
return Math.log(1 + count) + Math.log(1 + uses);
|
||||
case "Logarithmic (strong)":
|
||||
return Math.log(1 + count) + 2 * Math.log(1 + uses);
|
||||
case "Usage first":
|
||||
return uses;
|
||||
default:
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Beautify return type for easier parsing
|
||||
function mapUseCountArray(useCounts, posAndNeg = false) {
|
||||
return useCounts.map(useCount => {
|
||||
if (posAndNeg) {
|
||||
/**
|
||||
* Utility function to map the use count array from the database to a more readable format,
|
||||
* since FastAPI omits the field names in the response.
|
||||
* @param {Array} useCounts
|
||||
* @param {Boolean} posAndNeg - Whether to include negative counts
|
||||
*/
|
||||
static mapUseCountArray(useCounts, posAndNeg = false) {
|
||||
return useCounts.map(useCount => {
|
||||
if (posAndNeg) {
|
||||
return {
|
||||
"name": useCount[0],
|
||||
"type": useCount[1],
|
||||
"count": useCount[2],
|
||||
"negCount": useCount[3],
|
||||
"lastUseDate": useCount[4]
|
||||
}
|
||||
}
|
||||
return {
|
||||
"name": useCount[0],
|
||||
"type": useCount[1],
|
||||
"count": useCount[2],
|
||||
"negCount": useCount[3],
|
||||
"lastUseDate": useCount[4]
|
||||
"lastUseDate": useCount[3]
|
||||
}
|
||||
}
|
||||
return {
|
||||
"name": useCount[0],
|
||||
"type": useCount[1],
|
||||
"count": useCount[2],
|
||||
"lastUseDate": useCount[3]
|
||||
}
|
||||
});
|
||||
}
|
||||
// Call API endpoint to increase bias of tag in the database
|
||||
function increaseUseCount(tagName, type, negative = false) {
|
||||
postTacAPI(`tacapi/v1/increase-use-count?tagname=${tagName}&ttype=${type}&neg=${negative}`);
|
||||
}
|
||||
// Get use count of tag from the database
|
||||
async function getUseCount(tagName, type, negative = false) {
|
||||
return (await fetchTacAPI(`tacapi/v1/get-use-count?tagname=${tagName}&ttype=${type}&neg=${negative}`, true, false))["result"];
|
||||
}
|
||||
async function getUseCounts(tagNames, types, negative = false) {
|
||||
// While semantically weird, we have to use POST here for the body, as urls are limited in length
|
||||
const body = JSON.stringify({"tagNames": tagNames, "tagTypes": types, "neg": negative});
|
||||
const rawArray = (await postTacAPI(`tacapi/v1/get-use-count-list`, body))["result"]
|
||||
return mapUseCountArray(rawArray);
|
||||
}
|
||||
async function getAllUseCounts() {
|
||||
const rawArray = (await fetchTacAPI(`tacapi/v1/get-all-use-counts`))["result"];
|
||||
return mapUseCountArray(rawArray, true);
|
||||
}
|
||||
async function resetUseCount(tagName, type, resetPosCount, resetNegCount) {
|
||||
await putTacAPI(`tacapi/v1/reset-use-count?tagname=${tagName}&ttype=${type}&pos=${resetPosCount}&neg=${resetNegCount}`);
|
||||
}
|
||||
|
||||
function createTagUsageTable(tagCounts) {
|
||||
// Create table
|
||||
let tagTable = document.createElement("table");
|
||||
tagTable.innerHTML =
|
||||
`<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Type</td>
|
||||
<td>Count(+)</td>
|
||||
<td>Count(-)</td>
|
||||
<td>Last used</td>
|
||||
</tr>
|
||||
</thead>`;
|
||||
tagTable.id = "tac_tagUsageTable"
|
||||
|
||||
tagCounts.forEach(t => {
|
||||
let tr = document.createElement("tr");
|
||||
|
||||
// Fill values
|
||||
let values = [t.name, t.type-1, t.count, t.negCount, t.lastUseDate]
|
||||
values.forEach(v => {
|
||||
let td = document.createElement("td");
|
||||
td.innerText = v;
|
||||
tr.append(td);
|
||||
});
|
||||
// Add delete/reset button
|
||||
let delButton = document.createElement("button");
|
||||
delButton.innerText = "🗑️";
|
||||
delButton.title = "Reset count";
|
||||
tr.append(delButton);
|
||||
|
||||
tagTable.append(tr)
|
||||
});
|
||||
|
||||
return tagTable;
|
||||
}
|
||||
|
||||
// Sliding window function to get possible combination groups of an array
|
||||
function toNgrams(inputArray, size) {
|
||||
return Array.from(
|
||||
{ length: inputArray.length - (size - 1) }, //get the appropriate length
|
||||
(_, index) => inputArray.slice(index, index + size) //create the windows
|
||||
);
|
||||
}
|
||||
|
||||
function escapeRegExp(string, wildcardMatching = false) {
|
||||
if (wildcardMatching) {
|
||||
// Escape all characters except asterisks and ?, which should be treated separately as placeholders.
|
||||
return string.replace(/[-[\]{}()+.,\\^$|#\s]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
|
||||
}
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
function escapeHTML(unsafeText) {
|
||||
let div = document.createElement('div');
|
||||
div.textContent = unsafeText;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// For black/whitelisting
|
||||
function updateModelName() {
|
||||
let sdm = gradioApp().querySelector("#setting_sd_model_checkpoint");
|
||||
let modelDropdown = sdm?.querySelector("input") || sdm?.querySelector("select");
|
||||
if (modelDropdown) {
|
||||
currentModelName = modelDropdown.value;
|
||||
} else {
|
||||
// Fallback for intermediate versions
|
||||
modelDropdown = sdm?.querySelector("span.single-select");
|
||||
currentModelName = modelDropdown?.textContent || "";
|
||||
/**
|
||||
* Calls API endpoint to increase the count of a tag in the database.
|
||||
* Not awaited as it is non-critical and can be executed as fire-and-forget.
|
||||
* @param {String} tagName - The name of the tag
|
||||
* @param {ResultType} type - The type of the tag as mapped in {@link ResultType}
|
||||
* @param {Boolean} negative - Whether the tag was typed in a negative prompt field
|
||||
*/
|
||||
static increaseUseCount(tagName, type, negative = false) {
|
||||
this.postAPI(`tacapi/v1/increase-use-count?tagname=${tagName}&ttype=${type}&neg=${negative}`);
|
||||
}
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/61975440, how to detect JS value changes
|
||||
function observeElement(element, property, callback, delay = 0) {
|
||||
let elementPrototype = Object.getPrototypeOf(element);
|
||||
if (elementPrototype.hasOwnProperty(property)) {
|
||||
let descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
|
||||
Object.defineProperty(element, property, {
|
||||
get: function() {
|
||||
return descriptor.get.apply(this, arguments);
|
||||
},
|
||||
set: function () {
|
||||
let oldValue = this[property];
|
||||
descriptor.set.apply(this, arguments);
|
||||
let newValue = this[property];
|
||||
if (typeof callback == "function") {
|
||||
setTimeout(callback.bind(this, oldValue, newValue), delay);
|
||||
/**
|
||||
* Get the use count of a tag from the database
|
||||
* @param {String} tagName - The name of the tag
|
||||
* @param {ResultType} type - The type of the tag as mapped in {@link ResultType}
|
||||
* @param {Boolean} negative - Whether we are currently in a negative prompt field
|
||||
* @returns {Promise<Number>} The use count of the tag
|
||||
*/
|
||||
static async getUseCount(tagName, type, negative = false) {
|
||||
return (await this.fetchAPI(`tacapi/v1/get-use-count?tagname=${tagName}&ttype=${type}&neg=${negative}`, true, false))["result"];
|
||||
}
|
||||
/**
|
||||
* Retrieves the use counts of multiple tags at once from the database for improved performance
|
||||
* during typing.
|
||||
* @param {String[]} tagNames - An array of tag names
|
||||
* @param {ResultType[]} types - An array of tag types as mapped in {@link ResultType}
|
||||
* @param {Boolean} negative - Whether we are currently in a negative prompt field
|
||||
* @returns {Promise<Array>} The use count array mapped to named fields by {@link mapUseCountArray}
|
||||
*/
|
||||
static async getUseCounts(tagNames, types, negative = false) {
|
||||
// While semantically weird, we have to use POST here for the body, as urls are limited in length
|
||||
const body = JSON.stringify({"tagNames": tagNames, "tagTypes": types, "neg": negative});
|
||||
const rawArray = (await this.postAPI(`tacapi/v1/get-use-count-list`, body))["result"]
|
||||
return this.mapUseCountArray(rawArray);
|
||||
}
|
||||
/**
|
||||
* Gets all use counts existing in the database.
|
||||
* @returns {Array} The use count array mapped to named fields by {@link mapUseCountArray}
|
||||
*/
|
||||
static async getAllUseCounts() {
|
||||
const rawArray = (await this.fetchAPI(`tacapi/v1/get-all-use-counts`))["result"];
|
||||
return this.mapUseCountArray(rawArray, true);
|
||||
}
|
||||
/**
|
||||
* Resets the use count of the given tag back to zero.
|
||||
* @param {String} tagName - The name of the tag
|
||||
* @param {ResultType} type - The type of the tag as mapped in {@link ResultType}
|
||||
* @param {Boolean} resetPosCount - Whether to reset the positive count
|
||||
* @param {Boolean} resetNegCount - Whether to reset the negative count
|
||||
*/
|
||||
static async resetUseCount(tagName, type, resetPosCount, resetNegCount) {
|
||||
await TacUtils.putAPI(`tacapi/v1/reset-use-count?tagname=${tagName}&ttype=${type}&pos=${resetPosCount}&neg=${resetNegCount}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table to display an overview of tag usage statistics.
|
||||
* Currently unused.
|
||||
* @param {Array} tagCounts - The use count array to use, mapped to named fields by {@link mapUseCountArray}
|
||||
* @returns
|
||||
*/
|
||||
static createTagUsageTable(tagCounts) {
|
||||
// Create table
|
||||
let tagTable = document.createElement("table");
|
||||
tagTable.innerHTML =
|
||||
`<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Type</td>
|
||||
<td>Count(+)</td>
|
||||
<td>Count(-)</td>
|
||||
<td>Last used</td>
|
||||
</tr>
|
||||
</thead>`;
|
||||
tagTable.id = "tac_tagUsageTable"
|
||||
|
||||
tagCounts.forEach(t => {
|
||||
let tr = document.createElement("tr");
|
||||
|
||||
// Fill values
|
||||
let values = [t.name, t.type-1, t.count, t.negCount, t.lastUseDate]
|
||||
values.forEach(v => {
|
||||
let td = document.createElement("td");
|
||||
td.innerText = v;
|
||||
tr.append(td);
|
||||
});
|
||||
// Add delete/reset button
|
||||
let delButton = document.createElement("button");
|
||||
delButton.innerText = "🗑️";
|
||||
delButton.title = "Reset count";
|
||||
tr.append(delButton);
|
||||
|
||||
tagTable.append(tr)
|
||||
});
|
||||
|
||||
return tagTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sliding window function to get possible combination groups of an array
|
||||
* @param {Array} inputArray
|
||||
* @param {Number} size
|
||||
* @returns {Array[]} ngram permutations of the input array
|
||||
*/
|
||||
static toNgrams(inputArray, size) {
|
||||
return Array.from(
|
||||
{ length: inputArray.length - (size - 1) }, //get the appropriate length
|
||||
(_, index) => inputArray.slice(index, index + size) //create the windows
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string for use in a regular expression.
|
||||
* @param {String} string
|
||||
* @param {Boolean} wildcardMatching - Wildcard matching mode doesn't escape asterisks and question marks as they are handled separately there.
|
||||
* @returns {String} The escaped string
|
||||
*/
|
||||
static escapeRegExp(string, wildcardMatching = false) {
|
||||
if (wildcardMatching) {
|
||||
// Escape all characters except asterisks and ?, which should be treated separately as placeholders.
|
||||
return string.replace(/[-[\]{}()+.,\\^$|#\s]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
|
||||
}
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
/**
|
||||
* Escapes a string for use in HTML to not break formatting.
|
||||
* @param {String} unsafeText
|
||||
* @returns {String} The escaped HTML string
|
||||
*/
|
||||
static escapeHTML(unsafeText) {
|
||||
let div = document.createElement('div');
|
||||
div.textContent = unsafeText;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/** Updates {@link currentModelName} to the current model */
|
||||
static updateModelName() {
|
||||
let sdm = gradioApp().querySelector("#setting_sd_model_checkpoint");
|
||||
let modelDropdown = sdm?.querySelector("input") || sdm?.querySelector("select");
|
||||
if (modelDropdown) {
|
||||
currentModelName = modelDropdown.value;
|
||||
} else {
|
||||
// Fallback for intermediate versions
|
||||
modelDropdown = sdm?.querySelector("span.single-select");
|
||||
currentModelName = modelDropdown?.textContent || "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From https://stackoverflow.com/a/61975440.
|
||||
* Detects value changes in an element that were triggered programmatically
|
||||
* @param {HTMLElement} element - The DOM element to observe
|
||||
* @param {String} property - The object property to observe
|
||||
* @param {Function} callback - The callback function to call when the property changes
|
||||
* @param {Number} delay - The delay in milliseconds to wait before calling the callback
|
||||
*/
|
||||
static observeElement(element, property, callback, delay = 0) {
|
||||
let elementPrototype = Object.getPrototypeOf(element);
|
||||
if (elementPrototype.hasOwnProperty(property)) {
|
||||
let descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
|
||||
Object.defineProperty(element, property, {
|
||||
get: function() {
|
||||
return descriptor.get.apply(this, arguments);
|
||||
},
|
||||
set: function () {
|
||||
let oldValue = this[property];
|
||||
descriptor.set.apply(this, arguments);
|
||||
let newValue = this[property];
|
||||
if (typeof callback == "function") {
|
||||
setTimeout(callback.bind(this, oldValue, newValue), delay);
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
return newValue;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a matching sort function based on the current configuration
|
||||
* @returns {((a: any, b: any) => number)}
|
||||
*/
|
||||
static getSortFunction() {
|
||||
let criterion = TAC_CFG.modelSortOrder || "Name";
|
||||
|
||||
const textSort = (a, b, reverse = false) => {
|
||||
// Assign keys so next sort is faster
|
||||
if (!a.sortKey) {
|
||||
a.sortKey = a.type === ResultType.chant
|
||||
? a.aliases
|
||||
: a.text;
|
||||
}
|
||||
if (!b.sortKey) {
|
||||
b.sortKey = b.type === ResultType.chant
|
||||
? b.aliases
|
||||
: b.text;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort functions
|
||||
function getSortFunction() {
|
||||
let criterion = TAC_CFG.modelSortOrder || "Name";
|
||||
|
||||
const textSort = (a, b, reverse = false) => {
|
||||
// Assign keys so next sort is faster
|
||||
if (!a.sortKey) {
|
||||
a.sortKey = a.type === ResultType.chant
|
||||
? a.aliases
|
||||
: a.text;
|
||||
return reverse ? b.sortKey.localeCompare(a.sortKey) : a.sortKey.localeCompare(b.sortKey);
|
||||
}
|
||||
if (!b.sortKey) {
|
||||
b.sortKey = b.type === ResultType.chant
|
||||
? b.aliases
|
||||
: b.text;
|
||||
const numericSort = (a, b, reverse = false) => {
|
||||
const noKey = reverse ? "-1" : Number.MAX_SAFE_INTEGER;
|
||||
let aParsed = parseFloat(a.sortKey || noKey);
|
||||
let bParsed = parseFloat(b.sortKey || noKey);
|
||||
|
||||
if (aParsed === bParsed) {
|
||||
return textSort(a, b, false);
|
||||
}
|
||||
|
||||
return reverse ? bParsed - aParsed : aParsed - bParsed;
|
||||
}
|
||||
|
||||
return reverse ? b.sortKey.localeCompare(a.sortKey) : a.sortKey.localeCompare(b.sortKey);
|
||||
}
|
||||
const numericSort = (a, b, reverse = false) => {
|
||||
const noKey = reverse ? "-1" : Number.MAX_SAFE_INTEGER;
|
||||
let aParsed = parseFloat(a.sortKey || noKey);
|
||||
let bParsed = parseFloat(b.sortKey || noKey);
|
||||
|
||||
if (aParsed === bParsed) {
|
||||
return textSort(a, b, false);
|
||||
}
|
||||
|
||||
return reverse ? bParsed - aParsed : aParsed - bParsed;
|
||||
}
|
||||
|
||||
return (a, b) => {
|
||||
switch (criterion) {
|
||||
case "Date Modified (newest first)":
|
||||
return numericSort(a, b, true);
|
||||
case "Date Modified (oldest first)":
|
||||
return numericSort(a, b, false);
|
||||
default:
|
||||
return textSort(a, b);
|
||||
return (a, b) => {
|
||||
switch (criterion) {
|
||||
case "Date Modified (newest first)":
|
||||
return numericSort(a, b, true);
|
||||
case "Date Modified (oldest first)":
|
||||
return numericSort(a, b, false);
|
||||
default:
|
||||
return textSort(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Queue calling function to process global queues
|
||||
async function processQueue(queue, context, ...args) {
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
await queue[i].call(context, ...args);
|
||||
/**
|
||||
* Queue calling function to process global queues
|
||||
* @param {Array} queue - The queue to process
|
||||
* @param {*} context - The context to call the functions in (null for global)
|
||||
* @param {...any} args - Arguments to pass to the functions
|
||||
*/
|
||||
static async processQueue(queue, context, ...args) {
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
await queue[i].call(context, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The same but with return values
|
||||
async function processQueueReturn(queue, context, ...args)
|
||||
{
|
||||
let qeueueReturns = [];
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
let returnValue = await queue[i].call(context, ...args);
|
||||
if (returnValue)
|
||||
qeueueReturns.push(returnValue);
|
||||
}
|
||||
return qeueueReturns;
|
||||
}
|
||||
// Specific to tag completion parsers
|
||||
async function processParsers(textArea, prompt) {
|
||||
// Get all parsers that have a successful trigger condition
|
||||
let matchingParsers = PARSERS.filter(parser => parser.triggerCondition());
|
||||
// Guard condition
|
||||
if (matchingParsers.length === 0) {
|
||||
return null;
|
||||
/** The same as {@link processQueue}, but can accept and return results from the queued functions. */
|
||||
static async processQueueReturn(queue, context, ...args)
|
||||
{
|
||||
let qeueueReturns = [];
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
let returnValue = await queue[i].call(context, ...args);
|
||||
if (returnValue)
|
||||
qeueueReturns.push(returnValue);
|
||||
}
|
||||
return qeueueReturns;
|
||||
}
|
||||
/**
|
||||
* A queue processing function specific to tag completion parsers
|
||||
* @param {HTMLTextAreaElement} textArea - The current text area used by TAC
|
||||
* @param {String} prompt - The current prompt
|
||||
* @returns The results of the parsers
|
||||
*/
|
||||
static async processParsers(textArea, prompt) {
|
||||
// Get all parsers that have a successful trigger condition
|
||||
let matchingParsers = PARSERS.filter(parser => parser.triggerCondition());
|
||||
// Guard condition
|
||||
if (matchingParsers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let parseFunctions = matchingParsers.map(parser => parser.parse);
|
||||
// Process them and return the results
|
||||
return await processQueueReturn(parseFunctions, null, textArea, prompt);
|
||||
let parseFunctions = matchingParsers.map(parser => parser.parse);
|
||||
// Process them and return the results
|
||||
return await this.processQueueReturn(parseFunctions, null, textArea, prompt);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ class ChantParser extends BaseTagParser {
|
||||
if (tagword !== "<" && tagword !== "<c:") {
|
||||
let searchTerm = tagword.replace("<chant:", "").replace("<c:", "").replace("<", "");
|
||||
let filterCondition = x => {
|
||||
let regex = new RegExp(escapeRegExp(searchTerm, true), 'i');
|
||||
let regex = new RegExp(TacUtils.escapeRegExp(searchTerm, true), 'i');
|
||||
return regex.test(x.terms.toLowerCase()) || regex.test(x.name.toLowerCase());
|
||||
};
|
||||
tempResults = chants.filter(x => filterCondition(x)); // Filter by tagword
|
||||
@@ -33,7 +33,7 @@ class ChantParser extends BaseTagParser {
|
||||
async function load() {
|
||||
if (TAC_CFG.chantFile && TAC_CFG.chantFile !== "None") {
|
||||
try {
|
||||
chants = await readFile(`${tagBasePath}/${TAC_CFG.chantFile}?`, true);
|
||||
chants = await TacUtils.readFile(`${tagBasePath}/${TAC_CFG.chantFile}?`, true);
|
||||
} catch (e) {
|
||||
console.error("Error loading chants.json: " + e);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class EmbeddingParser extends BaseTagParser {
|
||||
}
|
||||
|
||||
let filterCondition = x => {
|
||||
let regex = new RegExp(escapeRegExp(searchTerm, true), 'i');
|
||||
let regex = new RegExp(TacUtils.escapeRegExp(searchTerm, true), 'i');
|
||||
return regex.test(x[0].toLowerCase()) || regex.test(x[0].toLowerCase().replaceAll(" ", "_"));
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ class EmbeddingParser extends BaseTagParser {
|
||||
async function load() {
|
||||
if (embeddings.length === 0) {
|
||||
try {
|
||||
embeddings = (await loadCSV(`${tagBasePath}/temp/emb.txt`))
|
||||
embeddings = (await TacUtils.loadCSV(`${tagBasePath}/temp/emb.txt`))
|
||||
.filter(x => x[0]?.trim().length > 0) // Remove empty lines
|
||||
.map(x => [x[0].trim(), x[1], x[2]]); // Return name, sortKey, hash tuples
|
||||
} catch (e) {
|
||||
|
||||
@@ -8,7 +8,7 @@ class HypernetParser extends BaseTagParser {
|
||||
if (tagword !== "<" && tagword !== "<h:" && tagword !== "<hypernet:") {
|
||||
let searchTerm = tagword.replace("<hypernet:", "").replace("<h:", "").replace("<", "");
|
||||
let filterCondition = x => {
|
||||
let regex = new RegExp(escapeRegExp(searchTerm, true), 'i');
|
||||
let regex = new RegExp(TacUtils.escapeRegExp(searchTerm, true), 'i');
|
||||
return regex.test(x.toLowerCase()) || regex.test(x.toLowerCase().replaceAll(" ", "_"));
|
||||
};
|
||||
tempResults = hypernetworks.filter(x => filterCondition(x[0])); // Filter by tagword
|
||||
@@ -32,7 +32,7 @@ class HypernetParser extends BaseTagParser {
|
||||
async function load() {
|
||||
if (hypernetworks.length === 0) {
|
||||
try {
|
||||
hypernetworks = (await loadCSV(`${tagBasePath}/temp/hyp.txt`))
|
||||
hypernetworks = (await TacUtils.loadCSV(`${tagBasePath}/temp/hyp.txt`))
|
||||
.filter(x => x[0]?.trim().length > 0) //Remove empty lines
|
||||
.map(x => [x[0]?.trim(), x[1]]); // Remove carriage returns and padding if it exists
|
||||
} catch (e) {
|
||||
|
||||
@@ -8,7 +8,7 @@ class LoraParser extends BaseTagParser {
|
||||
if (tagword !== "<" && tagword !== "<l:" && tagword !== "<lora:") {
|
||||
let searchTerm = tagword.replace("<lora:", "").replace("<l:", "").replace("<", "");
|
||||
let filterCondition = x => {
|
||||
let regex = new RegExp(escapeRegExp(searchTerm, true), 'i');
|
||||
let regex = new RegExp(TacUtils.escapeRegExp(searchTerm, true), 'i');
|
||||
return regex.test(x.toLowerCase()) || regex.test(x.toLowerCase().replaceAll(" ", "_"));
|
||||
};
|
||||
tempResults = loras.filter(x => filterCondition(x[0])); // Filter by tagword
|
||||
@@ -38,7 +38,7 @@ class LoraParser extends BaseTagParser {
|
||||
async function load() {
|
||||
if (loras.length === 0) {
|
||||
try {
|
||||
loras = (await loadCSV(`${tagBasePath}/temp/lora.txt`))
|
||||
loras = (await TacUtils.loadCSV(`${tagBasePath}/temp/lora.txt`))
|
||||
.filter(x => x[0]?.trim().length > 0) // Remove empty lines
|
||||
.map(x => [x[0]?.trim(), x[1], x[2]]); // Trim filenames and return the name, sortKey, hash pairs
|
||||
} catch (e) {
|
||||
@@ -50,7 +50,7 @@ async function load() {
|
||||
async function sanitize(tagType, text) {
|
||||
if (tagType === ResultType.lora) {
|
||||
let multiplier = TAC_CFG.extraNetworksDefaultMultiplier;
|
||||
let info = await fetchTacAPI(`tacapi/v1/lora-info/${text}`)
|
||||
let info = await TacUtils.fetchAPI(`tacapi/v1/lora-info/${text}`)
|
||||
if (info && info["preferred weight"]) {
|
||||
multiplier = info["preferred weight"];
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ class LycoParser extends BaseTagParser {
|
||||
if (tagword !== "<" && tagword !== "<l:" && tagword !== "<lyco:" && tagword !== "<lora:") {
|
||||
let searchTerm = tagword.replace("<lyco:", "").replace("<lora:", "").replace("<l:", "").replace("<", "");
|
||||
let filterCondition = x => {
|
||||
let regex = new RegExp(escapeRegExp(searchTerm, true), 'i');
|
||||
let regex = new RegExp(TacUtils.escapeRegExp(searchTerm, true), 'i');
|
||||
return regex.test(x.toLowerCase()) || regex.test(x.toLowerCase().replaceAll(" ", "_"));
|
||||
};
|
||||
tempResults = lycos.filter(x => filterCondition(x[0])); // Filter by tagword
|
||||
@@ -38,7 +38,7 @@ class LycoParser extends BaseTagParser {
|
||||
async function load() {
|
||||
if (lycos.length === 0) {
|
||||
try {
|
||||
lycos = (await loadCSV(`${tagBasePath}/temp/lyco.txt`))
|
||||
lycos = (await TacUtils.loadCSV(`${tagBasePath}/temp/lyco.txt`))
|
||||
.filter(x => x[0]?.trim().length > 0) // Remove empty lines
|
||||
.map(x => [x[0]?.trim(), x[1], x[2]]); // Trim filenames and return the name, sortKey, hash pairs
|
||||
} catch (e) {
|
||||
@@ -50,7 +50,7 @@ async function load() {
|
||||
async function sanitize(tagType, text) {
|
||||
if (tagType === ResultType.lyco) {
|
||||
let multiplier = TAC_CFG.extraNetworksDefaultMultiplier;
|
||||
let info = await fetchTacAPI(`tacapi/v1/lyco-info/${text}`)
|
||||
let info = await TacUtils.fetchAPI(`tacapi/v1/lyco-info/${text}`)
|
||||
if (info && info["preferred weight"]) {
|
||||
multiplier = info["preferred weight"];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
async function load() {
|
||||
let modelKeywordParts = (await readFile(`tmp/modelKeywordPath.txt`)).split(",")
|
||||
let modelKeywordParts = (await TacUtils.readFile(`tmp/modelKeywordPath.txt`)).split(",")
|
||||
modelKeywordPath = modelKeywordParts[0];
|
||||
let customFileExists = modelKeywordParts[1] === "True";
|
||||
|
||||
@@ -8,10 +8,10 @@ async function load() {
|
||||
let csv_lines = [];
|
||||
// Only add default keywords if wanted by the user
|
||||
if (TAC_CFG.modelKeywordCompletion !== "Only user list")
|
||||
csv_lines = (await loadCSV(`${modelKeywordPath}/lora-keyword.txt`));
|
||||
csv_lines = (await TacUtils.loadCSV(`${modelKeywordPath}/lora-keyword.txt`));
|
||||
// Add custom user keywords if the file exists
|
||||
if (customFileExists)
|
||||
csv_lines = csv_lines.concat((await loadCSV(`${modelKeywordPath}/lora-keyword-user.txt`)));
|
||||
csv_lines = csv_lines.concat((await TacUtils.loadCSV(`${modelKeywordPath}/lora-keyword-user.txt`)));
|
||||
|
||||
if (csv_lines.length === 0) return;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ var lastStyleVarIndex = "";
|
||||
class StyleParser extends BaseTagParser {
|
||||
async parse() {
|
||||
// Refresh if needed
|
||||
await refreshStyleNamesIfChanged();
|
||||
await TacUtils.refreshStyleNamesIfChanged();
|
||||
|
||||
// Show styles
|
||||
let tempResults = [];
|
||||
@@ -19,7 +19,7 @@ class StyleParser extends BaseTagParser {
|
||||
let searchTerm = tagword.replace(matchGroups[1], "");
|
||||
|
||||
let filterCondition = x => {
|
||||
let regex = new RegExp(escapeRegExp(searchTerm, true), 'i');
|
||||
let regex = new RegExp(TacUtils.escapeRegExp(searchTerm, true), 'i');
|
||||
return regex.test(x[0].toLowerCase()) || regex.test(x[0].toLowerCase().replaceAll(" ", "_"));
|
||||
};
|
||||
tempResults = styleNames.filter(x => filterCondition(x)); // Filter by tagword
|
||||
@@ -42,7 +42,7 @@ class StyleParser extends BaseTagParser {
|
||||
async function load(force = false) {
|
||||
if (styleNames.length === 0 || force) {
|
||||
try {
|
||||
styleNames = (await loadCSV(`${tagBasePath}/temp/styles.txt`))
|
||||
styleNames = (await TacUtils.loadCSV(`${tagBasePath}/temp/styles.txt`))
|
||||
.filter(x => x[0]?.trim().length > 0) // Remove empty lines
|
||||
.filter(x => x[0] !== "None") // Remove "None" style
|
||||
.map(x => [x[0].trim()]); // Trim name
|
||||
|
||||
@@ -117,7 +117,7 @@ class UmiParser extends BaseTagParser {
|
||||
if (umiTags.length > 0) {
|
||||
// Get difference for subprompt
|
||||
let tagCountChange = umiTags.length - umiPreviousTags.length;
|
||||
let diff = difference(umiTags, umiPreviousTags);
|
||||
let diff = TacUtils.difference(umiTags, umiPreviousTags);
|
||||
umiPreviousTags = umiTags;
|
||||
|
||||
// Show all condition
|
||||
@@ -136,7 +136,7 @@ class UmiParser extends BaseTagParser {
|
||||
originalTagword = tagword;
|
||||
tagword = umiTagword;
|
||||
let filteredWildcardsSorted = filteredWildcards(umiTagword);
|
||||
let searchRegex = new RegExp(`(^|[^a-zA-Z])${escapeRegExp(umiTagword)}`, 'i')
|
||||
let searchRegex = new RegExp(`(^|[^a-zA-Z])${TacUtils.escapeRegExp(umiTagword)}`, 'i')
|
||||
let baseFilter = x => x[0].toLowerCase().search(searchRegex) > -1;
|
||||
let spaceIncludeFilter = x => x[0].toLowerCase().replaceAll(" ", "_").search(searchRegex) > -1;
|
||||
tempResults = filteredWildcardsSorted.filter(x => baseFilter(x) || spaceIncludeFilter(x)) // Filter by tagword
|
||||
@@ -210,7 +210,7 @@ function updateUmiTags(tagType, sanitizedText, newPrompt, textArea) {
|
||||
async function load() {
|
||||
if (umiWildcards.length === 0) {
|
||||
try {
|
||||
let umiTags = (await readFile(`${tagBasePath}/temp/umi_tags.txt`)).split("\n");
|
||||
let umiTags = (await TacUtils.readFile(`${tagBasePath}/temp/umi_tags.txt`)).split("\n");
|
||||
// Split into tag, count pairs
|
||||
umiWildcards = umiTags.map(x => x
|
||||
.trim()
|
||||
|
||||
@@ -38,7 +38,7 @@ class WildcardParser extends BaseTagParser {
|
||||
}
|
||||
wildcards = wildcards.concat(getDescendantProp(yamlWildcards[basePath], fileName));
|
||||
} else {
|
||||
const fileContent = (await fetchTacAPI(`tacapi/v1/wildcard-contents?basepath=${basePath}&filename=${fileName}.txt`, false))
|
||||
const fileContent = (await TacUtils.fetchAPI(`tacapi/v1/wildcard-contents?basepath=${basePath}&filename=${fileName}.txt`, false))
|
||||
.split("\n")
|
||||
.filter(x => x.trim().length > 0 && !x.startsWith('#')); // Remove empty lines and comments
|
||||
wildcards = wildcards.concat(fileContent);
|
||||
@@ -92,7 +92,7 @@ class WildcardFileParser extends BaseTagParser {
|
||||
alreadyAdded.set(wcFile[1], true);
|
||||
});
|
||||
|
||||
finalResults.sort(getSortFunction());
|
||||
finalResults.sort(TacUtils.getSortFunction());
|
||||
|
||||
return finalResults;
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class WildcardFileParser extends BaseTagParser {
|
||||
async function load() {
|
||||
if (wildcardFiles.length === 0 && wildcardExtFiles.length === 0) {
|
||||
try {
|
||||
let wcFileArr = await loadCSV(`${tagBasePath}/temp/wc.txt`);
|
||||
let wcFileArr = await TacUtils.loadCSV(`${tagBasePath}/temp/wc.txt`);
|
||||
if (wcFileArr && wcFileArr.length > 0) {
|
||||
let wcBasePath = wcFileArr[0][0].trim(); // First line should be the base path
|
||||
wildcardFiles = wcFileArr.slice(1)
|
||||
@@ -110,7 +110,7 @@ async function load() {
|
||||
}
|
||||
|
||||
// To support multiple sources, we need to separate them using the provided "-----" strings
|
||||
let wcExtFileArr = await loadCSV(`${tagBasePath}/temp/wce.txt`);
|
||||
let wcExtFileArr = await TacUtils.loadCSV(`${tagBasePath}/temp/wce.txt`);
|
||||
let splitIndices = [];
|
||||
for (let index = 0; index < wcExtFileArr.length; index++) {
|
||||
if (wcExtFileArr[index][0].trim() === "-----") {
|
||||
@@ -134,11 +134,11 @@ async function load() {
|
||||
}
|
||||
|
||||
// Load the yaml wildcard json file and append it as a wildcard file, appending each key as a path component until we reach the end
|
||||
yamlWildcards = await readFile(`${tagBasePath}/temp/wc_yaml.json`, true);
|
||||
yamlWildcards = await TacUtils.readFile(`${tagBasePath}/temp/wc_yaml.json`, true);
|
||||
|
||||
// Append each key as a path component until we reach a leaf
|
||||
Object.keys(yamlWildcards).forEach(file => {
|
||||
const flattened = flatten(yamlWildcards[file], [], "/");
|
||||
const flattened = TacUtils.flatten(yamlWildcards[file], [], "/");
|
||||
Object.keys(flattened).forEach(key => {
|
||||
wildcardExtFiles.push([file, key]);
|
||||
});
|
||||
|
||||
@@ -151,7 +151,7 @@ async function loadTags(c) {
|
||||
// Load main tags and aliases
|
||||
if (allTags.length === 0 && c.tagFile && c.tagFile !== "None") {
|
||||
try {
|
||||
allTags = await loadCSV(`${tagBasePath}/${c.tagFile}`);
|
||||
allTags = await TacUtils.loadCSV(`${tagBasePath}/${c.tagFile}`);
|
||||
} catch (e) {
|
||||
console.error("Error loading tags file: " + e);
|
||||
return;
|
||||
@@ -163,7 +163,7 @@ async function loadTags(c) {
|
||||
async function loadExtraTags(c) {
|
||||
if (c.extra.extraFile && c.extra.extraFile !== "None") {
|
||||
try {
|
||||
extras = await loadCSV(`${tagBasePath}/${c.extra.extraFile}`);
|
||||
extras = await TacUtils.loadCSV(`${tagBasePath}/${c.extra.extraFile}`);
|
||||
// Add translations to the main translation map for extra tags that have them
|
||||
extras.forEach(e => {
|
||||
if (e[4]) translations.set(e[0], e[4]);
|
||||
@@ -178,7 +178,7 @@ async function loadExtraTags(c) {
|
||||
async function loadTranslations(c) {
|
||||
if (c.translation.translationFile && c.translation.translationFile !== "None") {
|
||||
try {
|
||||
let tArray = await loadCSV(`${tagBasePath}/${c.translation.translationFile}`);
|
||||
let tArray = await TacUtils.loadCSV(`${tagBasePath}/${c.translation.translationFile}`);
|
||||
tArray.forEach(t => {
|
||||
if (c.translation.oldFormat && t[2]) // if 2 doesn't exist, it's probably a new format file and the setting is on by mistake
|
||||
translations.set(t[0], t[2]);
|
||||
@@ -313,7 +313,7 @@ async function syncOptions() {
|
||||
TAC_CFG = newCFG;
|
||||
|
||||
// Callback
|
||||
await processQueue(QUEUE_AFTER_CONFIG_CHANGE, null);
|
||||
await TacUtils.processQueue(QUEUE_AFTER_CONFIG_CHANGE, null);
|
||||
}
|
||||
|
||||
// Create the result list div and necessary styling
|
||||
@@ -424,7 +424,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
var sanitizedText = text
|
||||
|
||||
// Run sanitize queue and use first result as sanitized text
|
||||
sanitizeResults = await processQueueReturn(QUEUE_SANITIZE, null, tagType, text);
|
||||
sanitizeResults = await TacUtils.processQueueReturn(QUEUE_SANITIZE, null, tagType, text);
|
||||
|
||||
if (sanitizeResults && sanitizeResults.length > 0) {
|
||||
sanitizedText = sanitizeResults[0];
|
||||
@@ -445,12 +445,12 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
&& TAC_CFG.wildcardCompletionMode !== "Always fully"
|
||||
&& sanitizedText.includes("/")) {
|
||||
if (TAC_CFG.wildcardCompletionMode === "To next folder level") {
|
||||
let regexMatch = sanitizedText.match(new RegExp(`${escapeRegExp(tagword)}([^/]*\\/?)`, "i"));
|
||||
let regexMatch = sanitizedText.match(new RegExp(`${TacUtils.escapeRegExp(tagword)}([^/]*\\/?)`, "i"));
|
||||
if (regexMatch) {
|
||||
let pathPart = regexMatch[0];
|
||||
// In case the completion would have just added a slash, try again one level deeper
|
||||
if (pathPart === `${tagword}/`) {
|
||||
pathPart = sanitizedText.match(new RegExp(`${escapeRegExp(tagword)}\\/([^/]*\\/?)`, "i"))[0];
|
||||
pathPart = sanitizedText.match(new RegExp(`${TacUtils.escapeRegExp(tagword)}\\/([^/]*\\/?)`, "i"))[0];
|
||||
}
|
||||
sanitizedText = pathPart;
|
||||
}
|
||||
@@ -503,7 +503,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
// Sanitize name for API call
|
||||
name = encodeURIComponent(name)
|
||||
// Call API & update db
|
||||
increaseUseCount(name, tagType, isNegative)
|
||||
TacUtils.increaseUseCount(name, tagType, isNegative)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,7 +513,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
let editStart = Math.max(cursorPos - tagword.length, 0);
|
||||
let editEnd = Math.min(cursorPos + tagword.length, prompt.length);
|
||||
let surrounding = prompt.substring(editStart, editEnd);
|
||||
let match = surrounding.match(new RegExp(escapeRegExp(`${tagword}`), "i"));
|
||||
let match = surrounding.match(new RegExp(TacUtils.escapeRegExp(`${tagword}`), "i"));
|
||||
let afterInsertCursorPos = editStart + match.index + sanitizedText.length;
|
||||
|
||||
var optionalSeparator = "";
|
||||
@@ -521,7 +521,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
let noCommaTypes = [ResultType.wildcardFile, ResultType.yamlWildcard, ResultType.umiWildcard].concat(extraNetworkTypes);
|
||||
if (!noCommaTypes.includes(tagType)) {
|
||||
// Append comma if enabled and not already present
|
||||
let beforeComma = surrounding.match(new RegExp(`${escapeRegExp(tagword)}[,:]`, "i")) !== null;
|
||||
let beforeComma = surrounding.match(new RegExp(`${TacUtils.escapeRegExp(tagword)}[,:]`, "i")) !== null;
|
||||
if (TAC_CFG.appendComma)
|
||||
optionalSeparator = beforeComma ? "" : ",";
|
||||
// Add space if enabled
|
||||
@@ -529,7 +529,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
optionalSeparator += " ";
|
||||
// If at end of prompt and enabled, override the normal setting if not already added
|
||||
if (!TAC_CFG.appendSpace && TAC_CFG.alwaysSpaceAtEnd)
|
||||
optionalSeparator += surrounding.match(new RegExp(`${escapeRegExp(tagword)}$`, "im")) !== null ? " " : "";
|
||||
optionalSeparator += surrounding.match(new RegExp(`${TacUtils.escapeRegExp(tagword)}$`, "im")) !== null ? " " : "";
|
||||
} else if (extraNetworkTypes.includes(tagType)) {
|
||||
// Use the dedicated separator for extra networks if it's defined, otherwise fall back to space
|
||||
optionalSeparator = TAC_CFG.extraNetworksSeparator || " ";
|
||||
@@ -552,7 +552,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
let keywords = null;
|
||||
// Check built-in activation words first
|
||||
if (tagType === ResultType.lora || tagType === ResultType.lyco) {
|
||||
let info = await fetchTacAPI(`tacapi/v1/lora-info/${result.text}`)
|
||||
let info = await TacUtils.fetchAPI(`tacapi/v1/lora-info/${result.text}`)
|
||||
if (info && info["activation text"]) {
|
||||
keywords = info["activation text"];
|
||||
}
|
||||
@@ -564,7 +564,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
|
||||
// No match, try to find a sha256 match from the cache file
|
||||
if (!nameDict) {
|
||||
const sha256 = await fetchTacAPI(`/tacapi/v1/lora-cached-hash/${result.text}`)
|
||||
const sha256 = await TacUtils.fetchAPI(`/tacapi/v1/lora-cached-hash/${result.text}`)
|
||||
if (sha256) {
|
||||
nameDict = modelKeywordDict.get(sha256);
|
||||
}
|
||||
@@ -630,7 +630,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
previousTags = tags;
|
||||
|
||||
// Callback
|
||||
let returns = await processQueueReturn(QUEUE_AFTER_INSERT, null, tagType, sanitizedText, newPrompt, textArea);
|
||||
let returns = await TacUtils.processQueueReturn(QUEUE_AFTER_INSERT, null, tagType, sanitizedText, newPrompt, textArea);
|
||||
// Return if any queue function returned true (has handled hide/show already)
|
||||
if (returns.some(x => x === true))
|
||||
return;
|
||||
@@ -680,7 +680,7 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
let displayText = "";
|
||||
// If the tag matches the tagword, we don't need to display the alias
|
||||
if(result.type === ResultType.chant) {
|
||||
displayText = escapeHTML(result.aliases);
|
||||
displayText = TacUtils.escapeHTML(result.aliases);
|
||||
} else if (result.aliases && !result.text.includes(tagword)) { // Alias
|
||||
let splitAliases = result.aliases.split(",");
|
||||
let bestAlias = splitAliases.find(a => a.toLowerCase().includes(tagword));
|
||||
@@ -696,7 +696,7 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
}
|
||||
}
|
||||
|
||||
displayText = escapeHTML(bestAlias);
|
||||
displayText = TacUtils.escapeHTML(bestAlias);
|
||||
|
||||
// Append translation for alias if it exists and is not what the user typed
|
||||
if (translations.has(bestAlias) && translations.get(bestAlias) !== bestAlias && bestAlias !== result.text)
|
||||
@@ -705,7 +705,7 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
if (!TAC_CFG.alias.onlyShowAlias && result.text !== bestAlias)
|
||||
displayText += " ➝ " + result.text;
|
||||
} else { // No alias
|
||||
displayText = escapeHTML(result.text);
|
||||
displayText = TacUtils.escapeHTML(result.text);
|
||||
}
|
||||
|
||||
// Append translation for result if it exists
|
||||
@@ -821,7 +821,7 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
// Add listener
|
||||
li.addEventListener("click", (e) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
resetUseCount(result.text, result.type, !isNegative, isNegative);
|
||||
TacUtils.resetUseCount(result.text, result.type, !isNegative, isNegative);
|
||||
flexDiv.querySelector(".acMetaText").classList.remove("biased");
|
||||
} else {
|
||||
insertTextAtCursor(textArea, result, tagword);
|
||||
@@ -887,7 +887,7 @@ async function updateSelectionStyle(textArea, newIndex, oldIndex) {
|
||||
|
||||
let img = previewDiv.querySelector("img");
|
||||
|
||||
let url = await getTacExtraNetworkPreviewURL(selectedFilename, shorthandType);
|
||||
let url = await TacUtils.getExtraNetworkPreviewURL(selectedFilename, shorthandType);
|
||||
if (url) {
|
||||
img.src = url;
|
||||
previewDiv.style.display = "block";
|
||||
@@ -933,17 +933,17 @@ function updateRuby(textArea, prompt) {
|
||||
|
||||
const translation = translations?.get(tag) || translations?.get(unsanitizedTag);
|
||||
|
||||
let escapedTag = escapeRegExp(tag);
|
||||
let escapedTag = TacUtils.escapeRegExp(tag);
|
||||
return { tag, escapedTag, translation };
|
||||
}
|
||||
|
||||
const replaceOccurences = (text, tuple) => {
|
||||
let { tag, escapedTag, translation } = tuple;
|
||||
let searchRegex = new RegExp(`(?<!<ruby>)(?:\\b)${escapedTag}(?:\\b|$|(?=[,|: \\t\\n\\r]))(?!<rt>)`, "g");
|
||||
return text.replaceAll(searchRegex, `<ruby>${escapeHTML(tag)}<rt>${translation}</rt></ruby>`);
|
||||
return text.replaceAll(searchRegex, `<ruby>${TacUtils.escapeHTML(tag)}<rt>${translation}</rt></ruby>`);
|
||||
}
|
||||
|
||||
let html = escapeHTML(prompt);
|
||||
let html = TacUtils.escapeHTML(prompt);
|
||||
|
||||
// First try to find direct matches
|
||||
[...rubyTags].forEach(tag => {
|
||||
@@ -972,11 +972,11 @@ function updateRuby(textArea, prompt) {
|
||||
}
|
||||
|
||||
// Perform n-gram sliding window search
|
||||
translateNgram(toNgrams(subTags, 3));
|
||||
translateNgram(toNgrams(subTags, 2));
|
||||
translateNgram(toNgrams(subTags, 1));
|
||||
translateNgram(TacUtils.toNgrams(subTags, 3));
|
||||
translateNgram(TacUtils.toNgrams(subTags, 1));
|
||||
translateNgram(TacUtils.toNgrams(subTags, 2));
|
||||
|
||||
let escapedTag = escapeRegExp(tuple.tag);
|
||||
let escapedTag = TacUtils.escapeRegExp(tuple.tag);
|
||||
|
||||
let searchRegex = new RegExp(`(?<!<ruby>)(?:\\b)${escapedTag}(?:\\b|$|(?=[,|: \\t\\n\\r]))(?!<rt>)`, "g");
|
||||
html = html.replaceAll(searchRegex, subHtml);
|
||||
@@ -1070,7 +1070,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
}
|
||||
|
||||
let tagCountChange = tags.length - previousTags.length;
|
||||
let diff = difference(tags, previousTags);
|
||||
let diff = TacUtils.difference(tags, previousTags);
|
||||
previousTags = tags;
|
||||
|
||||
// Guard for no difference / only whitespace remaining / last edited tag was fully removed
|
||||
@@ -1098,14 +1098,14 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
let normalTags = false;
|
||||
|
||||
// Process all parsers
|
||||
let resultCandidates = (await processParsers(textArea, prompt))?.filter(x => x.length > 0);
|
||||
let resultCandidates = (await TacUtils.processParsers(textArea, prompt))?.filter(x => x.length > 0);
|
||||
// If one ore more result candidates match, use their results
|
||||
if (resultCandidates && resultCandidates.length > 0) {
|
||||
// Flatten our candidate(s)
|
||||
results = resultCandidates.flat();
|
||||
// Sort results, but not if it's umi tags since they are sorted by count
|
||||
if (!(resultCandidates.length === 1 && results[0].type === ResultType.umiWildcard))
|
||||
results = results.sort(getSortFunction());
|
||||
results = results.sort(TacUtils.getSortFunction());
|
||||
}
|
||||
// Else search the normal tag list
|
||||
if (!resultCandidates || resultCandidates.length === 0
|
||||
@@ -1118,9 +1118,9 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
let searchRegex;
|
||||
if (tagword.startsWith("*")) {
|
||||
tagword = tagword.slice(1);
|
||||
searchRegex = new RegExp(`${escapeRegExp(tagword)}`, 'i');
|
||||
searchRegex = new RegExp(`${TacUtils.escapeRegExp(tagword)}`, 'i');
|
||||
} else {
|
||||
searchRegex = new RegExp(`(^|[^a-zA-Z])${escapeRegExp(tagword)}`, 'i');
|
||||
searchRegex = new RegExp(`(^|[^a-zA-Z])${TacUtils.escapeRegExp(tagword)}`, 'i');
|
||||
}
|
||||
|
||||
// Both normal tags and aliases/translations are included depending on the config
|
||||
@@ -1201,7 +1201,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
|
||||
// Request use counts from the DB
|
||||
const names = TAC_CFG.frequencyIncludeAlias ? tagNames.concat(aliasNames) : tagNames;
|
||||
const counts = await getUseCounts(names, types, isNegative);
|
||||
const counts = await TacUtils.getUseCounts(names, types, isNegative);
|
||||
|
||||
// Pre-calculate weights to prevent duplicate work
|
||||
const resultBiasMap = new Map();
|
||||
@@ -1212,7 +1212,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
const useStats = counts.find(c => c.name === name && c.type === type);
|
||||
const uses = useStats?.count || 0;
|
||||
// Calculate & set weight
|
||||
const weight = calculateUsageBias(result, result.count, uses)
|
||||
const weight = TacUtils.calculateUsageBias(result, result.count, uses)
|
||||
resultBiasMap.set(result, weight);
|
||||
});
|
||||
// Actual sorting with the pre-calculated weights
|
||||
@@ -1355,13 +1355,13 @@ async function refreshTacTempFiles(api = false) {
|
||||
loras = [];
|
||||
lycos = [];
|
||||
modelKeywordDict.clear();
|
||||
await processQueue(QUEUE_FILE_LOAD, null);
|
||||
await TacUtils.processQueue(QUEUE_FILE_LOAD, null);
|
||||
|
||||
console.log("TAC: Refreshed temp files");
|
||||
}
|
||||
|
||||
if (api) {
|
||||
await postTacAPI("tacapi/v1/refresh-temp-files");
|
||||
await TacUtils.postAPI("tacapi/v1/refresh-temp-files");
|
||||
await reload();
|
||||
} else {
|
||||
setTimeout(async () => {
|
||||
@@ -1371,9 +1371,9 @@ async function refreshTacTempFiles(api = false) {
|
||||
}
|
||||
|
||||
async function refreshEmbeddings() {
|
||||
await postTacAPI("tacapi/v1/refresh-embeddings", null);
|
||||
await TacUtils.postAPI("tacapi/v1/refresh-embeddings", null);
|
||||
embeddings = [];
|
||||
await processQueue(QUEUE_FILE_LOAD, null);
|
||||
await TacUtils.processQueue(QUEUE_FILE_LOAD, null);
|
||||
console.log("TAC: Refreshed embeddings");
|
||||
}
|
||||
|
||||
@@ -1403,11 +1403,11 @@ function addAutocompleteToArea(area) {
|
||||
if (!e.inputType && !tacSelfTrigger) return;
|
||||
tacSelfTrigger = false;
|
||||
|
||||
debounce(autocomplete(area, area.value), TAC_CFG.delayTime);
|
||||
TacUtils.debounce(autocomplete(area, area.value), TAC_CFG.delayTime);
|
||||
checkKeywordInsertionUndo(area, e);
|
||||
});
|
||||
// Add focusout event listener
|
||||
area.addEventListener('focusout', debounce(() => {
|
||||
area.addEventListener('focusout', TacUtils.debounce(() => {
|
||||
if (!hideBlocked)
|
||||
hideResults(area);
|
||||
}, 400));
|
||||
@@ -1428,7 +1428,7 @@ function addAutocompleteToArea(area) {
|
||||
// One-time setup, triggered from onUiUpdate
|
||||
async function setup() {
|
||||
// Load external files needed by completion extensions
|
||||
await processQueue(QUEUE_FILE_LOAD, null);
|
||||
await TacUtils.processQueue(QUEUE_FILE_LOAD, null);
|
||||
|
||||
// Find all textareas
|
||||
let textAreas = getTextAreas();
|
||||
@@ -1455,7 +1455,7 @@ async function setup() {
|
||||
});
|
||||
});
|
||||
quicksettings?.querySelectorAll(`[id^=setting_tac].gradio-dropdown input`).forEach(e => {
|
||||
observeElement(e, "value", () => {
|
||||
TacUtils.observeElement(e, "value", () => {
|
||||
setTimeout(async () => {
|
||||
await syncOptions();
|
||||
}, 500);
|
||||
@@ -1473,14 +1473,14 @@ async function setup() {
|
||||
|
||||
// Add mutation observer for the model hash text to also allow hash-based blacklist again
|
||||
let modelHashText = gradioApp().querySelector("#sd_checkpoint_hash");
|
||||
updateModelName();
|
||||
TacUtils.updateModelName();
|
||||
if (modelHashText) {
|
||||
currentModelHash = modelHashText.title
|
||||
let modelHashObserver = new MutationObserver((mutationList, observer) => {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.type === "attributes" && mutation.attributeName === "title") {
|
||||
currentModelHash = mutation.target.title;
|
||||
updateModelName();
|
||||
TacUtils.updateModelName();
|
||||
refreshEmbeddings();
|
||||
}
|
||||
}
|
||||
@@ -1524,7 +1524,7 @@ async function setup() {
|
||||
gradioApp().appendChild(acStyle);
|
||||
|
||||
// Callback
|
||||
await processQueue(QUEUE_AFTER_SETUP, null);
|
||||
await TacUtils.processQueue(QUEUE_AFTER_SETUP, null);
|
||||
}
|
||||
var tacLoading = false;
|
||||
onUiUpdate(async () => {
|
||||
@@ -1533,7 +1533,7 @@ onUiUpdate(async () => {
|
||||
if (TAC_CFG) return;
|
||||
tacLoading = true;
|
||||
// Get our tag base path from the temp file
|
||||
tagBasePath = await readFile(`tmp/tagAutocompletePath.txt`);
|
||||
tagBasePath = await TacUtils.readFile(`tmp/tagAutocompletePath.txt`);
|
||||
// Load config from webui opts
|
||||
await syncOptions();
|
||||
// Rest of setup
|
||||
|
||||
Reference in New Issue
Block a user