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)