diff --git a/javascript/prompt_format.js b/javascript/prompt_format.js index 497f2ee..0d8a5d7 100644 --- a/javascript/prompt_format.js +++ b/javascript/prompt_format.js @@ -1,4 +1,6 @@ class LeFormatter { + // #region Alias & Cards + static #aliasData = null; static #cardsData = null; @@ -17,6 +19,8 @@ class LeFormatter { return (this.#cardsData ??= pfConfigs.cacheCards()); } + // #region Pipeline + /** * @param {HTMLTextAreaElement} textArea * @param {boolean} dedupe @@ -34,15 +38,18 @@ class LeFormatter { else { const val = lines.join(",\n"); textArea.value = val - .replace(/\n,\n/g, "\n\n") - .replace(/\>,\n/g, ">\n") - .replace(/\s*,\s*$/g, "") - .replace(/\.,(\s)/g, ".$1"); + .replace(/\n,\n/g, "\n\n") // Empty Line + .replace(/[,\s]*$/g, "") // Last Line + .replace(/\>\s*,\n/g, ">\n") // Network Ending + .replace(/\.\s*,\n/g, ".\n") // Period Ending + .replace(/(AND|BREAK|ADDROW|ADDCOL)\s*,\n/g, "$1\n"); // Keyword } if (autoRefresh) updateInput(textArea); } + // #region Expression + /** @param {string} input @returns {string} */ static #toExpression(input) { return input @@ -57,11 +64,13 @@ class LeFormatter { .replace("$CAT$", ":3"); } + // #region Network + /** @type {Map} */ static #networkDB = new Map(); /** @param {string} input @returns {string} */ - static toNetwork(input) { + static #toNetwork(input) { this.#networkDB.clear(); const output = input @@ -80,7 +89,7 @@ class LeFormatter { } /** @param {string} input @returns {string} */ - static fromNetwork(input) { + static #fromNetwork(input) { const len = this.#networkDB.size; for (let i = len; i >= 0; i--) { @@ -91,13 +100,15 @@ class LeFormatter { return input; } + // #region Main + /** @param {string} input @param {boolean} dedupe @param {boolean} rmUnderscore @returns {string} */ static formatString(input, dedupe, rmUnderscore) { // Remove Whitespaces input = input.replace(/[^\S\n]/g, " "); // Substitute LoRAs - input = this.toNetwork(input); + input = this.#toNetwork(input); // Remove Underscore input = rmUnderscore ? this.#rmUnderscore(input) : input; @@ -106,7 +117,7 @@ class LeFormatter { input = this.#toExpression(input); // Restore LoRAs - input = this.fromNetwork(input); + input = this.#fromNetwork(input); // Fix Bracket & Space input = input.replace(/\s+(\)|\]|\>|\})/g, "$1").replace(/(\(|\[|\<|\{)\s+/g, "$1"); @@ -117,6 +128,12 @@ class LeFormatter { // Remove Space around Syntax input = input.replace(/\s*\|\s*/g, "|").replace(/\s*\:\s*/g, ":"); + // Remove Comma before Period + input = input.replace(/\,\s*\./g, "."); + + // Remove Space before last Period + input = input.replace(/\s*\.(\n|$)/g, ".$1"); + // Sentence -> Tags let tags = input.split(",").map((word) => word.trim()); @@ -129,7 +146,7 @@ class LeFormatter { // Remove Empty Brackets while (/\(\s*\)|\[\s*\]/.test(input)) input = input.replace(/\(\s*\)|\[\s*\]/g, ""); - // Space after Comma in Escaped Brackets (for franchise) + // Space after Colon in Escaped Brackets for Franchise (due to "Remove Space around Syntax") input = input.replace(/\\\(([^\\\)]+?):([^\\\)]+?)\\\)/g, "\\($1: $2\\)"); // Prune empty Chunks @@ -144,7 +161,7 @@ class LeFormatter { return match.replace(/\,\s+/g, ","); }); - // Remove empty before Colon + // Remove Space before Colon input = input.replace(/,\s*:(\d)/g, ":$1"); input = this.#fromExpression(input); @@ -152,9 +169,11 @@ class LeFormatter { return input; } + // #region Dedupe + /** @param {string[]} input @returns {string[]} */ static #dedupe(input) { - const KEYWORD = /^(AND|BREAK)$/; + const KEYWORD = /^(AND|BREAK|ADDROW|ADDCOL)$/; const uniqueSet = new Set(); const results = []; @@ -200,6 +219,8 @@ class LeFormatter { return results; } + // #region Underscore + /** @param {string} input @returns {string} */ static #rmUnderscore(input) { if (!input.trim()) return ""; @@ -212,21 +233,74 @@ class LeFormatter { return input; } + + // #region Paste + + /** @param {HTMLTextAreaElement} field @param {pfConfigs} config */ + static processPaste(field, config) { + /** https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.10.1/modules/infotext_utils.py#L16 */ + const paramPatterns = /\s*(\w[\w \-/]+):\s*("(?:\\.|[^\\"])+"|[^,]*)(?:,|$)/g; + + field.addEventListener("paste", (event) => { + /** @type {string} */ let paste = (event.clipboardData || window.clipboardData).getData("text"); + if ([...paste.matchAll(paramPatterns)].length > 3) return; // Infotext + + event.preventDefault(); + + /** @type {boolean} */ const commaStart = paste.match(/^\s*\,/); + /** @type {boolean} */ const commaEnd = paste.match(/\,\s*$/); + + /** @type {boolean} */ const onlyTags = !paste.includes(","); + + if (config.booru) { + paste = this.#toNetwork(paste); + paste = paste.replace(/\s*[\d.]+[kM]\s*|(?:^|,|\s+)\d+(?:\s+|,|\?|$)|[\?\+\-]\s+/g, ", "); + for (const excl of ["Artist", "Characters", "Character", "Copyright", "Tags", "Tag", "General"]) + paste = paste.replace(excl, ""); + + const franchise = /\w+(?:[\_\s]\(.+?\))+/g; + paste = paste.replace(franchise, (match) => { + return match.replace(/[()]/g, "\\$&"); + }); + paste = this.#fromNetwork(paste); + } + + if (onlyTags) paste = this.formatString(paste, config.dedupe, config.rmUnderscore); + else { + const lines = []; + for (const line of paste.split("\n")) lines.push(this.formatString(line, config.dedupe, config.rmUnderscore)); + paste = lines.join("\n"); + } + + paste = `${commaStart ? ", " : ""}${paste}${commaEnd ? ", " : ""}`; + + const currentText = field.value; + const cursorPosition = field.selectionStart; + + const newText = currentText.slice(0, cursorPosition) + paste + currentText.slice(field.selectionEnd); + field.value = newText; + field.selectionStart = field.selectionEnd = cursorPosition + paste.length; + + updateInput(field); + return false; + }); + } } +// #region Entry + (function () { onUiLoaded(() => { const config = new pfConfigs(); const formatter = pfUI.setupUIs(config.autoRun, config.dedupe, config.rmUnderscore); - for (const field of config.promptFields) { - field.addEventListener("keydown", (e) => { - if (e.altKey && e.shiftKey && e.code === "KeyF") { - e.preventDefault(); + document.addEventListener("keydown", (e) => { + if (e.altKey && e.shiftKey && e.code === "KeyF") { + e.preventDefault(); + for (const field of config.promptFields) LeFormatter.formatPipeline(field, config.dedupe, config.rmUnderscore, config.refresh, config.comma); - } - }); - } + } + }); formatter.auto.addEventListener("change", () => { config.autoRun = formatter.auto.checked; @@ -272,54 +346,6 @@ class LeFormatter { } if (!config.paste) return; - - /** https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.10.1/modules/infotext_utils.py#L16 */ - const paramPatterns = /\s*(\w[\w \-/]+):\s*("(?:\\.|[^\\"])+"|[^,]*)(?:,|$)/g; - - for (const field of config.promptFields) { - field.addEventListener("paste", (event) => { - /** @type {string} */ let paste = (event.clipboardData || window.clipboardData).getData("text"); - if ([...paste.matchAll(paramPatterns)].length > 3) return; // Infotext - - event.preventDefault(); - - /** @type {boolean} */ const commaStart = paste.match(/^\s*\,/); - /** @type {boolean} */ const commaEnd = paste.match(/\,\s*$/); - - /** @type {boolean} */ const onlyTags = !paste.includes(","); - - if (config.booru) { - paste = LeFormatter.toNetwork(paste); - paste = paste.replace(/\s*[\d.]+[kM]\s*|(?:^|,|\s+)\d+(?:\s+|,|\?|$)|[\?\+\-]\s+/g, ", "); - for (const excl of ["Artist", "Characters", "Character", "Copyright", "Tags", "Tag", "General"]) - paste = paste.replace(excl, ""); - - const name_franchise = /\w+?[\_\s]\(.*?\)/g; - paste = paste.replace(name_franchise, (match) => { - return match.replace(/[()]/g, "\\$&"); - }); - paste = LeFormatter.fromNetwork(paste); - } - - if (onlyTags) paste = LeFormatter.formatString(paste, config.dedupe, config.rmUnderscore); - else { - const lines = []; - for (const line of paste.split("\n")) lines.push(LeFormatter.formatString(line, config.dedupe, config.rmUnderscore)); - paste = lines.join("\n"); - } - - paste = `${commaStart ? ", " : ""}${paste}${commaEnd ? ", " : ""}`; - - const currentText = field.value; - const cursorPosition = field.selectionStart; - - const newText = currentText.slice(0, cursorPosition) + paste + currentText.slice(field.selectionEnd); - field.value = newText; - field.selectionStart = field.selectionEnd = cursorPosition + paste.length; - - updateInput(field); - return false; - }); - } + for (const field of config.promptFields) LeFormatter.processPaste(field, config); }); })();