From 951dc4bb5deff700dab5fff386132783b1d29f38 Mon Sep 17 00:00:00 2001 From: Haoming Date: Mon, 2 Sep 2024 17:22:41 +0800 Subject: [PATCH] implement #9 --- README.md | 47 +++++++++++++----- README_ZH.md | 42 ++++++++++++---- javascript/prompt_format.js | 65 ++++++++++++++++++------- javascript/prompt_format_Configs.js | 63 +++++++++++++++++++++--- javascript/prompt_format_UI.js | 8 +-- scripts/pf_settings.py | 75 ++++++++++++++++++++++++++--- 6 files changed, 243 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index f2da99c..57df565 100644 --- a/README.md +++ b/README.md @@ -11,25 +11,48 @@ Sometimes, when you type too fast or copy prompts from all over the places, you ## Features - [x] Works in both `txt2img` and `img2img` -- [x] Remove duplicated **spaces** and **commas** +- [x] Works in both `Positive` and `Negative`, as well as `Hires. fix` prompts +- [x] Remove extra **spaces** and **commas** - [x] Fix misplaced **brackets** and **commas** -- [x] Toggle `Remove Duplicates` to remove identical tags found in the prompts +- [x] Enable `Remove Duplicates` to remove identical tags found in the prompts - **Note:** Only works for tag-based prompt, not sentence-based prompt - **eg.** `1girl, solo, smile, 1girl` will become `1girl, solo, smile` - **eg.** `a girl smiling, a girl standing` will not be changed -- [x] Toggle `Remove Underscores` to replace `_` with `space` - - Some newer anime checkpoints claim to eliminate the need of using underscores +- [x] Enable `Remove Underscores` to replace `_` with `space` - [x] Respect line breaks - `Remove Duplicates` only checks within the same line - [x] Toggle between auto formatting and manual formatting - - In `Auto`: The process is ran whenever you press **Generate** - - In `Manual`: The process is only ran when you press the **Format** button -- [x] Toggle which above features is enabled/disabled by default in `System` section of the **Settings** tab + - In `Auto` mode: The process is ran whenever you click on **Generate** + - In `Manual` mode: The process is only ran when you click the **Format** button +- [x] Toggle whether the above features are enabled / disabled by default in the `Prompt Format` section under the System category of the **Settings** tab - [x] Pressing `Alt` + `Shift` + `F` can also trigger formatting +- [x] Assign "[alias](#tag-alias)" that counts as duplicates for the specified tags -## Note -1. Since the formatting in `Auto` mode is triggered at the same time as the generation, -the immediate image will not have its metadata updated (unless you already did manual formatting beforehand). +### Tag Alias -2. Some Extensions *(**eg.** [tagcomplete](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete))* listen to the text editing event, -meaning the formatting will cause them to trigger. You can disable updating the actual prompts in the `System` section of the **Settings** tab. +

New 🔥

+ +- In the `Prompt Format` settings, there is a new field for **Tag Alias** +- You can assign other tags that count as the same as the main tag, and thus get removed during `Remove Duplicates` +- The syntax is in the format of `main tag: alias1, alias2, alias3` + - **example:** + ```json + 1girl: girl, woman, lady + ``` + - If you type `girl`, it will get converted into `1girl`, and if you already have `1girl`, then the future ones will get removed. + +- The pattern for alias uses **Regular Expression**, so certain symbols *(**eg.** `(`, `)`)* will need to be escaped *(**ie.** `\(`, `\)`)* + - Comma is not supported, as it is used to separate multiple patterns + - Check out [RegExr](https://regexr.com/) for cheatsheet + - **example:** + ```regex + adult: \d*\s*(y\.?o\.?|[Yy]ear[s]? [Oo]ld) + ``` + - It will convert `15 yo`, `20 y.o.`, `25 years old`, `30 Year Old` all into `adult` + +
+ +### Note +1. Since the formatting in `Auto` mode is triggered at the same time as the generation, the immediate image might not have its prompts updated. + +2. Some Extensions *(**eg.** [tagcomplete](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete))* listen to the text editing event, meaning the formatting will cause them to be triggered. You can disable updating the actual prompts in the `Prompt Format` settings to prevent this. diff --git a/README_ZH.md b/README_ZH.md index 15bf870..6b9889a 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -11,24 +11,48 @@ ## 功能 - [x] 在`txt2img`和`img2img`都有用 +- [x] 對`正面`和`負面`以及`Hires. fix`之咒語都有用 - [x] 移除多餘的**空格**和**逗點** -- [x] 修改錯誤的**括弧** -- [x] 開啟`Remove Duplicates`會把咒語中重複的單詞消除 - - **注意:** 只對單詞類咒語有效 +- [x] 修正錯誤的**括弧** +- [x] 開啟`Remove Duplicates`會把咒語中重複的單字消除 + - **注意:** 只對單字類咒語有效 - **例.** `1girl, solo, smile, 1girl` 會變成 `1girl, solo, smile` - **例.** `a girl smiling, a girl standing` 則不變 - [x] 開啟`Remove Underscores`會將 `_` 換成 `空格` - - 一些較新的動漫模型聲稱不用再加底線 - [x] 保留咒語的換行 - 上述的`Remove Duplicates`只在同一行中有效 - [x] 按下`Auto Format`以在手動與自動間切換 - `自動`: 每次按下 **生成 (Generate)** 時處裡 - `手動`: 手動按下 **Format** 時才處裡 -- [x] 在 **Settings** 頁面的 `System` 區 開啟/關閉 上述預設功能 +- [x] 在 **Settings** 頁面 System 下的 `Prompt Format` 區可以 開啟/關閉 上述功能 - [x] 按下 `Alt` + `Shift` + `F` 亦可觸發格式化 +- [x] 為指定單字新增 "[同義詞](#同義詞)" -## 注意 -1. 由於`自動`校正和生成是同時觸發,除非有先用過`手動`校正,不然當下所產的第一張圖片之內部資料並不會被更新。 +### 同義詞 -2. 有些擴充 *(如. [tagcomplete](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete))* 追蹤文字的編輯事件,意即文字校正會導致它們啟動。 -你可以到 **Settings** 頁面的 `System` 區關閉咒語的自動編輯。 +

新功能 🔥

+ +- 在 `Prompt Format` 的設定裡,有個新的 **Tag Alias** 欄位 +- 你可以在此把其它字詞設為主單字的同義詞,使其在 `Remove Duplicates` 中被當作重複字而刪去 +- 格式為 `main tag: alias1, alias2, alias3` + - **範例:** + ```json + 1girl: girl, woman, lady + ``` + - 如果輸入 `girl`, 便會轉換成 `1girl`; 而如果 `1girl` 已經存在,多餘的便會被刪除。 + +- 同義詞判斷使用 **Regular Expression**,故特定文字 *(**如.** `(`, `)`)* 便需要被跳脫 *(**即.** `\(`, `\)`)* + - 逗號用來分開多個同義詞,故無法用於同義詞 + - 可參考 [RegExr](https://regexr.com/) 以便學習 + - **範例:** + ```regex + adult: \d*\s*(y\.?o\.?|[Yy]ear[s]? [Oo]ld) + ``` + - 此便會將 `15 yo`, `20 y.o.`, `25 years old`, `30 Year Old` 都轉為 `adult` + +
+ +### 注意 +1. 由於 `自動`校正 和 生成 是同時觸發,當下所生產的第一張圖片之咒語可能不會是已更新的。 + +2. 有些擴充 *(如. [tagcomplete](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete))* 追蹤文字的編輯事件,意即文字校正會導致它們啟動。你可以到設定關閉咒語的自動更新。 diff --git a/javascript/prompt_format.js b/javascript/prompt_format.js index 705e111..fb566f7 100644 --- a/javascript/prompt_format.js +++ b/javascript/prompt_format.js @@ -1,8 +1,19 @@ class LeFormatter { static #cachedCards = null; + static #alias = null; - /** @param {Element} textArea @param {boolean} dedupe @param {boolean} removeUnderscore @param {boolean} autoRefresh */ + static forceReload() { + this.#alias = LeFormatterConfig.getTagAlias(); + this.#cachedCards = LeFormatterConfig.cacheCards(); + } + + /** + * @param {HTMLTextAreaElement} textArea + * @param {boolean} dedupe + * @param {boolean} removeUnderscore + * @param {boolean} autoRefresh + */ static formatPipeline(textArea, dedupe, removeUnderscore, autoRefresh) { const lines = textArea.value.split('\n'); @@ -18,8 +29,12 @@ class LeFormatter { /** @param {string} input @param {boolean} dedupe @param {boolean} removeUnderscore @returns {string} */ static #formatString(input, dedupe, removeUnderscore) { // Remove Duplicate - if (dedupe) + if (dedupe) { + if (this.#alias == null) + this.#alias = LeFormatterConfig.getTagAlias(); + input = this.#dedupe(input); + } // Fix Commas inside Brackets input = input @@ -35,6 +50,7 @@ class LeFormatter { if (removeUnderscore) { if (this.#cachedCards == null) this.#cachedCards = LeFormatterConfig.cacheCards(); + tags = this.#removeUnderscore(tags); } @@ -68,16 +84,30 @@ class LeFormatter { const KEYWORD = /^(AND|BREAK)$/; chunks.forEach((tag) => { - const cleanedTag = tag.replace(/\[|\]|\(|\)|\s+/g, '').trim(); + const cleanedTag = tag.replace(/\[|\]|\(|\)/g, '').replace(/\s+/g, ' ').trim(); if (KEYWORD.test(cleanedTag)) { resultArray.push(tag); return; } - if (!uniqueSet.has(cleanedTag)) { + var substitute = null; + for (const [pattern, mainTag] of this.#alias) { + if (substitute != null) + return; + if (pattern.test(cleanedTag)) + substitute = mainTag; + } + + if ((substitute == null) && (!uniqueSet.has(cleanedTag))) { uniqueSet.add(cleanedTag); - resultArray.push(tag); + resultArray.push(cleanedTag); + return; + } + + if ((substitute != null) && (!uniqueSet.has(substitute))) { + uniqueSet.add(substitute); + resultArray.push(tag.replace(cleanedTag, substitute)); return; } @@ -110,9 +140,10 @@ class LeFormatter { } } -onUiLoaded(async () => { +onUiLoaded(() => { const config = new LeFormatterConfig(); + config.button.onclick = () => { LeFormatter.forceReload(); } document.addEventListener('keydown', (e) => { if (e.altKey && e.shiftKey && e.code === 'KeyF') { @@ -145,17 +176,17 @@ onUiLoaded(async () => { tools.after(formatter); ['txt', 'img'].forEach((mode) => { - const generateButton = gradioApp().getElementById(`${mode}2img_generate`); - const enqueueButton = gradioApp().getElementById(`${mode}2img_enqueue`); - - generateButton?.addEventListener('click', () => { - if (config.autoRun) - config.promptFields.forEach((field) => LeFormatter.formatPipeline(field, config.dedupe, config.removeUnderscore, config.refresh)); - }); - - enqueueButton?.addEventListener('click', () => { - if (config.autoRun) - config.promptFields.forEach((field) => LeFormatter.formatPipeline(field, config.dedupe, config.removeUnderscore, config.refresh)); + // Expandable ID List in 1 place + [ + `${mode}2img_generate`, + `${mode}2img_enqueue`, + ].forEach((id) => { + const button = document.getElementById(id); + button?.addEventListener('click', () => { + if (config.autoRun) + config.promptFields.forEach((field) => LeFormatter.formatPipeline(field, config.dedupe, config.removeUnderscore, config.refresh)); + }); }); }); + }); diff --git a/javascript/prompt_format_Configs.js b/javascript/prompt_format_Configs.js index 3d6ee5e..d090e25 100644 --- a/javascript/prompt_format_Configs.js +++ b/javascript/prompt_format_Configs.js @@ -6,34 +6,35 @@ class LeFormatterConfig { this.dedupe = this.#defaultDedupe(); this.removeUnderscore = this.#defaultRemoveUnderscore(); this.promptFields = this.#getPromptFields(); + this.button = this.#createReloadButton(); } /** @returns {boolean} */ #shouldRefresh() { - const config = gradioApp().getElementById('setting_pf_disableupdateinput').querySelector('input[type=checkbox]'); + const config = document.getElementById('setting_pf_disableupdateinput').querySelector('input[type=checkbox]'); return !config.checked; } /** @returns {boolean} */ #defaultAuto() { - const config = gradioApp().getElementById('setting_pf_startinauto').querySelector('input[type=checkbox]'); + const config = document.getElementById('setting_pf_startinauto').querySelector('input[type=checkbox]'); return config.checked; } /** @returns {boolean} */ #defaultDedupe() { - const config = gradioApp().getElementById('setting_pf_startwithdedupe').querySelector('input[type=checkbox]'); + const config = document.getElementById('setting_pf_startwithdedupe').querySelector('input[type=checkbox]'); return config.checked; } /** @returns {boolean} */ #defaultRemoveUnderscore() { - const config = gradioApp().getElementById('setting_pf_startwithrmudscr').querySelector('input[type=checkbox]'); + const config = document.getElementById('setting_pf_startwithrmudscr').querySelector('input[type=checkbox]'); return config.checked; } // ===== Cache All Prompt Fields ===== - /** @returns {Array} */ + /** @returns {HTMLTextAreaElement[]} */ #getPromptFields() { const textareas = []; @@ -46,7 +47,7 @@ class LeFormatterConfig { 'hires_prompt', 'hires_neg_prompt' ].forEach((id) => { - const textArea = gradioApp().getElementById(id)?.querySelector('textarea'); + const textArea = document.getElementById(id)?.querySelector('textarea'); if (textArea != null) textareas.push(textArea); }); @@ -54,9 +55,21 @@ class LeFormatterConfig { return textareas; } - /** @returns {Array} */ + /** @returns {HTMLButtonElement} */ + #createReloadButton() { + const button = document.getElementById('settings_show_all_pages').cloneNode(false); + const page = document.getElementById('column_settings_pf'); + + button.id = "setting_pf_reload"; + button.textContent = "Reload Cached Cards & Alias"; + + page.appendChild(button); + return button; + } + + /** @returns {string[]} */ static cacheCards() { - const extras = gradioApp().getElementById('txt2img_extra_tabs'); + const extras = document.getElementById('txt2img_extra_tabs'); if (!extras) return []; @@ -68,4 +81,38 @@ class LeFormatterConfig { return cards; } + + /** @returns {Map} */ + static getTagAlias() { + const alias = new Map(); + + const config = document.getElementById('setting_pf_alias').querySelector('textarea').value; + + if (!config.trim()) + return alias; + + config.split("\n").forEach((line) => { + const [tag, words] = line.split(":"); + const mainTag = tag.trim(); + + words.split(",").map(part => part.trim()).forEach((word) => { + if (word === mainTag) + return; + + const pattern = this.#parseRegExp(word); + alias.set(pattern, mainTag); + }); + }); + + return alias; + } + + /** @param {string} input @returns {RegExp} */ + static #parseRegExp(input) { + const startAnchor = input.startsWith('^'); + const endAnchor = input.endsWith('$'); + + return new RegExp(`${startAnchor ? '' : '^'}${input}${endAnchor ? '' : '$'}`); + } + } diff --git a/javascript/prompt_format_UI.js b/javascript/prompt_format_UI.js index 8700d37..0d18016 100644 --- a/javascript/prompt_format_UI.js +++ b/javascript/prompt_format_UI.js @@ -1,6 +1,6 @@ class LeFormatterUI { - /** @param {Function} onClick @returns {Element} */ + /** @param {Function} onClick @returns {HTMLButtonElement} */ static #button(onClick) { const button = document.createElement('button'); button.textContent = 'Format'; @@ -12,9 +12,9 @@ class LeFormatterUI { return button; } - /** @param {boolean} default_value @param {string} text @returns {Element} */ + /** @param {boolean} default_value @param {string} text @returns {HTMLDivElement} */ static #checkbox(default_value, text) { - const label = gradioApp().getElementById('tab_settings').querySelector('input[type=checkbox]').parentNode.cloneNode(true); + const label = document.getElementById('tab_settings').querySelector('input[type=checkbox]').parentNode.cloneNode(true); label.removeAttribute('id'); label.classList.add("pf-checkbox"); @@ -30,7 +30,7 @@ class LeFormatterUI { /** * @param {Function} onManual * @param {boolean} autoRun @param {boolean} dedupe @param {boolean} removeUnderscore - * @returns {Element} + * @returns {HTMLDivElement} * */ static setupUIs(onManual, autoRun, dedupe, removeUnderscore) { const formatter = document.createElement('div'); diff --git a/scripts/pf_settings.py b/scripts/pf_settings.py index 0764bd9..a0c36d7 100644 --- a/scripts/pf_settings.py +++ b/scripts/pf_settings.py @@ -1,9 +1,70 @@ -from modules import script_callbacks, shared +from modules.script_callbacks import on_ui_settings +from modules.shared import OptionInfo, opts +import gradio as gr -def on_ui_settings(): - shared.opts.add_option("pf_disableupdateinput", shared.OptionInfo(False, "Prompt Format - Disable Update Input", section=("system", "System")).info("Enable this if you have Extensions, such as [tagcomplete], that subscribe to text editing event.")) - shared.opts.add_option("pf_startinauto", shared.OptionInfo(True, "Prompt Format - Start in Auto Mode", section=("system", "System"))) - shared.opts.add_option("pf_startwithdedupe", shared.OptionInfo(True, "Prompt Format - Start with Remove Duplicates", section=("system", "System"))) - shared.opts.add_option("pf_startwithrmudscr", shared.OptionInfo(False, "Prompt Format - Start with Remove Underscores", section=("system", "System"))) +section = ("pf", "Prompt Format") -script_callbacks.on_ui_settings(on_ui_settings) + +def on_settings(): + + opts.add_option( + "pf_disableupdateinput", + OptionInfo( + False, + "Disable automatic input updates", + section=section, + category_id="system", + ).info( + "check this if you have Extensions, such as [tagcomplete], that subscribe to text editing event" + ), + ) + + opts.add_option( + "pf_startinauto", + OptionInfo(True, "Start in Auto Mode", section=section, category_id="system"), + ) + + opts.add_option( + "pf_startwithdedupe", + OptionInfo( + True, + "Launch with Remove Duplicates", + section=section, + category_id="system", + ), + ) + + opts.add_option( + "pf_startwithrmudscr", + OptionInfo( + False, + "Launch with Remove Underscores", + section=section, + category_id="system", + ), + ) + + opts.add_option( + "pf_alias", + OptionInfo( + default="", + label="Tag Alias for Remove Duplicates", + component=gr.Textbox, + component_args={ + "placeholder": "1girl: girl, woman, lady\nadult: \\d*\\s*(y\\.?o\\.?|[Yy]ear[s]? [Oo]ld)", + "lines": 8, + "max_lines": 64, + }, + section=section, + category_id="system", + ) + .info( + """treat tags on the right as duplicates, and substitute them with the main tag on the left) + (based on regular expression, meaning you may need to escape some symbols) + (comma is not supported in pattern""" + ) + .link("RegExr", "https://regexr.com/"), + ) + + +on_ui_settings(on_settings)