diff --git a/README.md b/README.md
index 9eccfb4..4a65426 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,14 @@
# SD Webui Prompt Format
-[English|[中文](README_ZH.md)]
-
This is an Extension for the [Automatic1111 Webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui), which helps formatting prompts.
-> Also supports [SD.Next](https://github.com/vladmandic/automatic) and [Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) out of the box!
+> Compatible with [Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge)
-

+
+
+Sometimes, when you type too fast or copy prompts from all over the places, you end up with duplicated spaces and commas. This simple Extension helps removing them whenever you click Generate.
+
-Sometimes, when you type too fast or copy prompts from all over the places, you end up with duplicated **spaces** or **commas**. This simple Extension helps removing them whenever you click **Generate**.
+
## Features
- [x] Works in both `txt2img` and `img2img`
@@ -16,57 +17,48 @@ Sometimes, when you type too fast or copy prompts from all over the places, you
- [x] Fix misplaced **brackets** and **commas**
- [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
+ - **e.g.** `1girl, solo, smile, 1girl` will become `1girl, solo, smile`
+ - **e.g.** `a girl smiling, a girl standing` will not be changed
- [x] Enable `Remove Underscores` to replace `_` with `space`
- [x] Respect line breaks
- `Remove Duplicates` only checks within the same line
-- [x] Append a comma before a line break
+- [x] Append a comma every line break
- [x] Toggle between auto formatting and manual formatting
- 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] **New:** Trigger the formatting on the pasted text
+- [x] Pressing `Alt` + `Shift` + `F` can also manually trigger formatting
+- [x] Format the text pasted from clipboard
+
+
+
- [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
- [x] Exclude specific tags from `Remove Underscores`
-- [x] Click `Reload` to cache new cards
- - By default, the `ExtraNetwork` cards are cached once at the start, to be excluded from `Remove Underscores`. If you added more cards while the Webui is already running, click this button to re-cache again.
+- [x] Assign "[alias](#tag-alias)" that counts as duplicates for the specified tags
+- [x] Click `Reload` to refresh the 2 settings above
-### Tag Alias
-- 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:**
- ```
- 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. Though you may need to manually type something else for the prompt to get actually updated.
-
-
+> [!Note]
+> 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 settings to prevent this.
Booru
-- [x] Automatically clean up unwanted texts
+- [x] Clean up unwanted texts when pasting tags from Booru sites
- Currently supports [gelbooru](https://gelbooru.com/) and [danbooru](https://safebooru.donmai.us/)

+
+
+
+### Tag Alias
+- You can assign other tags that count as the same as the main tag, which then get removed during `Remove Duplicates`
+- The syntax is in the format of `main tag: alias1, alias2, alias3`
+ - **example:**
+ ```
+ 1girl: girl, woman, lady
+ ```
+ - If you type `girl`, it will get converted into `1girl`, which will get removed if the prompt already contains `1girl`
+
+- The pattern for alias uses **Regular Expression**, so certain symbols *(**e.g.** `(`, `)`)* will need to be escaped *(**i.e.** `\(`, `\)`)*
+ - Comma is not supported, as it is used to separate multiple patterns
diff --git a/README_ZH.md b/README_ZH.md
deleted file mode 100644
index 20476ec..0000000
--- a/README_ZH.md
+++ /dev/null
@@ -1,72 +0,0 @@
-# SD Webui Prompt Format
-[[English](README.md)|中文]
-
-這是一個[Automatic1111 Webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui)的插件,用來幫忙校正咒語。
-
-> 亦支援 [SD.Next](https://github.com/vladmandic/automatic) 與 [Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) !
-
-
-
-有時候如果打字太快或是從各處東拼西湊咒語,常會造成多個重複的空格或逗點。這個擴充可以幫忙移除它們。
-
-## 功能
-- [x] 在`txt2img`和`img2img`都有用
-- [x] 對`正面`和`負面`以及`Hires. fix`之咒語都有用
-- [x] 移除多餘的**空格**和**逗點**
-- [x] 修正錯誤的**括弧**
-- [x] 開啟`Remove Duplicates`會把咒語中重複的單字消除
- - **注意:** 只對單字類咒語有效
- - **例.** `1girl, solo, smile, 1girl` 會變成 `1girl, solo, smile`
- - **例.** `a girl smiling, a girl standing` 則不變
-- [x] 開啟`Remove Underscores`會將 `_` 換成 `空格`
-- [x] 保留咒語的換行
- - 上述的`Remove Duplicates`只在同一行中有效
-- [x] 在換行前加入逗點
-- [x] 按下`Auto Format`以在手動與自動間切換
- - `自動`: 每次按下 **生成 (Generate)** 時處裡
- - `手動`: 手動按下 **Format** 時才處裡
-- [x] **新功能:** 對貼上的咒語進行處裡
-- [x] 在 **Settings** 頁面 System 下的 `Prompt Format` 區可以 開啟/關閉 上述功能
-- [x] 按下 `Alt` + `Shift` + `F` 亦可觸發格式化
-- [x] 為指定單字新增 "[同義詞](#同義詞)"
-- [x] 將指定字詞除外 `Remove Underscores` 的影響
-- [x] 點擊 `Reload` 以緩存卡片
- - 在 Webui 剛開啟時, `ExtraNetwork` 中的卡片會被緩存一次以防被 `Remove Underscores` 影響。如果你在 Webui 已運行時加入更多的卡片,點擊此按鈕來重新緩存。
-
-### 同義詞
-- 在 `Prompt Format` 的設定裡,有個新的 **Tag Alias** 欄位
-- 你可以在此把其它字詞設為主單字的同義詞,使其在 `Remove Duplicates` 中被當作重複字而刪去
-- 格式為 `main tag: alias1, alias2, alias3`
- - **範例:**
- ```
- 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))* 追蹤文字的編輯事件,意即文字校正會導致它們啟動。你可以到設定關閉咒語的自動更新。
-
-
-
-
-Booru
-
-- [x] 自動清除多餘字詞
- - 目前支援 [gelbooru](https://gelbooru.com/) 及 [danbooru](https://safebooru.donmai.us/)
-
-
-
-
diff --git a/booru.png b/booru.png
index e7eedc3..a03c8ab 100644
Binary files a/booru.png and b/booru.png differ
diff --git a/javascript/pf_configs.js b/javascript/pf_configs.js
index faa15d9..59b283c 100644
--- a/javascript/pf_configs.js
+++ b/javascript/pf_configs.js
@@ -1,94 +1,51 @@
class pfConfigs {
-
constructor() {
- this.refresh = this.#shouldRefresh();
- this.autoRun = this.#defaultAuto();
- this.dedupe = this.#defaultDedupe();
- this.removeUnderscore = this.#defaultRemoveUnderscore();
- this.comma = this.#appendComma();
- this.paste = this.#onpaste();
- this.booru = this.#procBooru();
+ /** @type {HTMLTextAreaElement[]} */ this.promptFields = this.#getPromptFields();
- this.promptFields = this.#getPromptFields();
+ /** @type {boolean} */ this.refresh = !this.#getConfig("setting_pf_disableupdateinput");
+ /** @type {boolean} */ this.autoRun = this.#getConfig("setting_pf_startinauto");
+ /** @type {boolean} */ this.dedupe = this.#getConfig("setting_pf_startwithdedupe");
+ /** @type {boolean} */ this.rmUnderscore = this.#getConfig("setting_pf_startwithrmudscr");
+ /** @type {boolean} */ this.comma = this.#getConfig("setting_pf_appendcomma");
+ /** @type {boolean} */ this.paste = this.#getConfig("setting_pf_onpaste");
+ /** @type {boolean} */ this.booru = this.#getConfig("setting_pf_booru");
}
- /** @returns {boolean} */
- #shouldRefresh() {
- const config = document.getElementById('setting_pf_disableupdateinput').querySelector('input[type=checkbox]');
- return !config.checked;
- }
-
- /** @returns {boolean} */
- #defaultAuto() {
- const config = document.getElementById('setting_pf_startinauto').querySelector('input[type=checkbox]');
+ #getConfig(id) {
+ const config = document
+ .getElementById(id)
+ .querySelector("input[type=checkbox]");
return config.checked;
}
- /** @returns {boolean} */
- #defaultDedupe() {
- const config = document.getElementById('setting_pf_startwithdedupe').querySelector('input[type=checkbox]');
- return config.checked;
- }
-
- /** @returns {boolean} */
- #defaultRemoveUnderscore() {
- const config = document.getElementById('setting_pf_startwithrmudscr').querySelector('input[type=checkbox]');
- return config.checked;
- }
-
- /** @returns {boolean} */
- #appendComma() {
- const config = document.getElementById('setting_pf_appendcomma').querySelector('input[type=checkbox]');
- return config.checked;
- }
-
- /** @returns {boolean} */
- #onpaste() {
- const config = document.getElementById('setting_pf_onpaste').querySelector('input[type=checkbox]');
- return config.checked;
- }
-
- /** @returns {boolean} */
- #procBooru() {
- const config = document.getElementById('setting_pf_booru').querySelector('input[type=checkbox]');
- return config.checked;
- }
-
- /**
- * Cache All Prompt Fields
- * @returns {HTMLTextAreaElement[]}
- */
#getPromptFields() {
const textareas = [];
/** Expandable List of IDs in 1 place */
const IDs = [
- 'txt2img_prompt',
- 'txt2img_neg_prompt',
- 'img2img_prompt',
- 'img2img_neg_prompt',
- 'hires_prompt',
- 'hires_neg_prompt'
+ "txt2img_prompt",
+ "txt2img_neg_prompt",
+ "img2img_prompt",
+ "img2img_neg_prompt",
+ "hires_prompt",
+ "hires_neg_prompt",
];
for (const id of IDs) {
- const textArea = document.getElementById(id)?.querySelector('textarea');
- if (textArea != null)
- textareas.push(textArea);
+ const textArea = document.getElementById(id)?.querySelector("textarea");
+ if (textArea != null) textareas.push(textArea);
}
const ADetailer = [
"script_txt2img_adetailer_ad_main_accordion",
- "script_img2img_adetailer_ad_main_accordion"
+ "script_img2img_adetailer_ad_main_accordion",
];
for (const id of ADetailer) {
- const fields = document.getElementById(id)?.querySelectorAll('textarea');
- if (fields == null)
- continue;
+ const fields = document.getElementById(id)?.querySelectorAll("textarea");
+ if (fields == null) continue;
for (const textArea of fields) {
- if (textArea.placeholder.length > 0)
- textareas.push(textArea);
+ if (textArea.placeholder.length > 0) textareas.push(textArea);
}
}
@@ -97,40 +54,35 @@ class pfConfigs {
/** @returns {string[]} */
static cacheCards() {
- const extras = document.getElementById('txt2img_extra_tabs');
- if (!extras)
- return [];
+ const cards = document
+ .getElementById("pf_embeddings")
+ .querySelector("textarea")
+ .value.split("\n");
- const cards = [];
- for (const card of extras.querySelectorAll('span.name')) {
- if (card.textContent.includes('_'))
- cards.push(card.textContent);
- }
-
- const config = document.getElementById('setting_pf_exclusion').querySelector('input').value;
- if (config.trim()) {
- for (const tag of config.split(","))
- cards.push(tag.trim());
- }
+ const config = document
+ .getElementById("setting_pf_exclusion")
+ .querySelector("textarea").value;
+ for (const tag of config.split(",").map((t) => t.trim()))
+ if (tag) cards.push(tag);
return cards;
}
- /** @returns {Map} */
+ /** @returns {{RegExp: string}} */
static getTagAlias() {
const alias = new Map();
- const config = document.getElementById('setting_pf_alias').querySelector('textarea').value;
- if (!config.trim())
- return alias;
+ const config = document
+ .getElementById("setting_pf_alias")
+ .querySelector("textarea").value;
+ if (!config.includes(":")) return alias;
for (const line of config.split("\n")) {
const [tag, words] = line.split(":");
const mainTag = tag.trim();
- for (const word of words.split(",").map(part => part.trim())) {
- if (word === mainTag)
- continue;
+ for (const word of words.split(",").map((part) => part.trim())) {
+ if (word === mainTag) continue;
const pattern = this.#parseRegExp(word);
alias.set(pattern, mainTag);
@@ -142,9 +94,6 @@ class pfConfigs {
/** @param {string} input @returns {RegExp} */
static #parseRegExp(input) {
- const startAnchor = input.startsWith('^');
- const endAnchor = input.endsWith('$');
- return new RegExp(`${startAnchor ? '' : '^'}${input}${endAnchor ? '' : '$'}`);
+ return new RegExp(`^${input.trimStart("^").trimEnd("$")}$`);
}
-
}
diff --git a/javascript/pf_ui.js b/javascript/pf_ui.js
index b1916c8..505f8be 100644
--- a/javascript/pf_ui.js
+++ b/javascript/pf_ui.js
@@ -1,9 +1,8 @@
class pfUI {
-
/** @param {string} text @param {string} tip @returns {HTMLButtonElement} */
static #button(text, tip) {
- const button = document.createElement('button');
- button.classList.add(['lg', 'secondary', 'gradio-button']);
+ const button = document.createElement("button");
+ button.classList.add(["lg", "secondary", "gradio-button"]);
button.textContent = text;
if (tip) button.title = tip;
return button;
@@ -11,9 +10,12 @@ class pfUI {
/** @param {boolean} value @param {string} text @returns {HTMLLabelElement} */
static #checkbox(value, text) {
- const label = document.getElementById('tab_settings').querySelector('input[type=checkbox]').parentNode.cloneNode(true);
+ const label = document
+ .getElementById("pf_checkbox")
+ .querySelector("label")
+ .cloneNode(true);
label.classList.add("pf-checkbox");
- label.removeAttribute('id');
+ label.removeAttribute("id");
const checkbox = label.children[0];
checkbox.checked = value;
@@ -23,24 +25,19 @@ class pfUI {
return label;
}
- /**
- * @param {boolean} autoRun
- * @param {boolean} dedupe
- * @param {boolean} removeUnderscore
- * @returns {HTMLDivElement}
- */
- static setupUIs(autoRun, dedupe, removeUnderscore) {
- const formatter = document.createElement('div');
- formatter.id = 'le-formatter';
+ /** @param {boolean} autoRun @param {boolean} dedupe @param {boolean} rmUnderscore @returns {HTMLDivElement} */
+ static setupUIs(autoRun, dedupe, rmUnderscore) {
+ const formatter = document.createElement("div");
+ formatter.id = "le-formatter";
- const manualBtn = this.#button('Format', null);
- manualBtn.style.display = autoRun ? 'none' : 'flex';
- const refreshBtn = this.#button('Reload', 'Reload Cached Cards & Alias');
- refreshBtn.style.display = removeUnderscore ? 'flex' : 'none';
+ const manualBtn = this.#button("Format", "Manually Format the Prompts");
+ manualBtn.style.display = autoRun ? "none" : "flex";
+ const refreshBtn = this.#button("Reload", "Reload Exclusion & Alias");
+ refreshBtn.style.display = rmUnderscore ? "flex" : "none";
- const autoCB = this.#checkbox(autoRun, 'Auto Format');
- const dedupeCB = this.#checkbox(dedupe, 'Remove Duplicates');
- const underscoreCB = this.#checkbox(removeUnderscore, 'Remove Underscores');
+ const autoCB = this.#checkbox(autoRun, "Auto Format");
+ const dedupeCB = this.#checkbox(dedupe, "Remove Duplicates");
+ const underscoreCB = this.#checkbox(rmUnderscore, "Remove Underscores");
formatter.appendChild(autoCB);
formatter.appendChild(manualBtn);
@@ -56,5 +53,4 @@ class pfUI {
return formatter;
}
-
}
diff --git a/javascript/prompt_format.js b/javascript/prompt_format.js
index 2386464..c6a8859 100644
--- a/javascript/prompt_format.js
+++ b/javascript/prompt_format.js
@@ -1,76 +1,52 @@
class LeFormatter {
-
- static #_alias = null;
- static #_cachedCards = null;
+ static #aliasData = null;
+ static #cardsData = null;
static forceReload() {
- this.#_alias = null;
- this.#_cachedCards = null;
+ this.#aliasData = null;
+ this.#cardsData = null;
+ }
+
+ /** @returns {{RegExp: string}} */
+ static get #alias() {
+ return (this.#aliasData ??= pfConfigs.getTagAlias());
}
/** @returns {string[]} */
- static get #cachedCards() {
- if (this.#_cachedCards == null)
- this.#_cachedCards = pfConfigs.cacheCards();
- return this.#_cachedCards;
- }
-
- /** @returns {Map} */
- static get #alias() {
- if (this.#_alias == null)
- this.#_alias = pfConfigs.getTagAlias();
- return this.#_alias;
+ static get #cards() {
+ return (this.#cardsData ??= pfConfigs.cacheCards());
}
/**
* @param {HTMLTextAreaElement} textArea
* @param {boolean} dedupe
- * @param {boolean} removeUnderscore
+ * @param {boolean} rmUnderscore
* @param {boolean} autoRefresh
* @param {boolean} appendComma
*/
- static formatPipeline(textArea, dedupe, removeUnderscore, autoRefresh, appendComma) {
- const lines = textArea.value.split('\n');
+ static formatPipeline(textArea, dedupe, rmUnderscore, autoRefresh, appendComma) {
+ const lines = textArea.value.split("\n");
for (let i = 0; i < lines.length; i++)
- lines[i] = this.formatString(lines[i], dedupe, removeUnderscore);
+ lines[i] = this.formatString(lines[i], dedupe, rmUnderscore);
- if (!appendComma)
- textArea.value = lines.join('\n');
+ if (!appendComma) textArea.value = lines.join("\n");
else {
- const val = lines.join(',\n');
- textArea.value = val.replace(/\n,\n/g, '\n\n');
+ const val = lines.join(",\n");
+ textArea.value = val
+ .replace(/\n,\n/g, "\n\n")
+ .replace(/\s*,\s*$/g, "")
+ .replace(/\s*,\s*$/g, "");
}
- if (autoRefresh)
- updateInput(textArea);
- }
-
- /** @param {string[]} tags @returns {string[]} */
- static #joinExtraNet(tags) {
- let isNet = false;
- let i = 0;
-
- while (i < tags.length) {
- if (!isNet) {
- if (tags[i].startsWith('<'))
- isNet = true;
- i++;
- }
- else {
- isNet = !tags[i].endsWith('>');
- tags[i - 1] = `${tags[i - 1]}, ${tags.splice(i, 1)[0]}`;
- }
- }
-
- return tags;
+ if (autoRefresh) updateInput(textArea);
}
/** @param {string} input @returns {string} */
static #toExpression(input) {
return input
- .replace(/[,\n]\s*> <\s*[,\n]/g, ", $SHY$,")
- .replace(/[,\n]\s*:3\s*[,\n]/g, ", $CAT$,");
+ .replace(/[,\n^]\s*> <\s*[,\n$]/g, ", $SHY$,")
+ .replace(/[,\n^]\s*:3\s*[,\n$]/g, ", $CAT$,");
}
/** @param {string} input @returns {string} */
@@ -80,68 +56,99 @@ class LeFormatter {
.replace("$CAT$", ":3");
}
- /** @param {string} input @param {boolean} dedupe @param {boolean} removeUnderscore @returns {string} */
- static formatString(input, dedupe, removeUnderscore) {
+ /** @type {Map} */
+ static #networkDB = new Map();
+
+ /** @param {string} input @returns {string} */
+ static #toNetwork(input) {
+ this.#networkDB.clear();
+
+ const output = input.replace(/\s*<.+?>\s*/g, (match) => {
+ const UID = `@NET${this.#networkDB.size}WORK@`;
+ this.#networkDB.set(UID, match.trim());
+ return UID;
+ });
+
+ return output;
+ }
+
+ /** @param {string} input @returns {string} */
+ static #fromNetwork(input) {
+ const len = this.#networkDB.size;
+
+ for (let i = 0; i < len; i++) {
+ const UID = `@NET${i}WORK@`;
+ input = input.replace(UID, this.#networkDB.get(UID));
+ }
+
+ return input;
+ }
+
+ /** @param {string} input @param {boolean} dedupe @param {boolean} rmUnderscore @returns {string} */
+ static formatString(input, dedupe, rmUnderscore) {
+ // Substitute LoRAs
+ input = this.#toNetwork(input);
// Remove Underscore
- input = removeUnderscore ? this.#removeUnderscore(input) : input;
+ input = rmUnderscore ? this.#rmUnderscore(input) : input;
// Special Tags
input = this.#toExpression(input);
+ // Restore LoRAs
+ input = this.#fromNetwork(input);
+
// Fix Commas inside Brackets
input = input
- .replace(/,+\s*\)/g, '),')
- .replace(/,+\s*\]/g, '],')
- .replace(/,+\s*\>/g, '>,')
- .replace(/,+\s*\}/g, '},')
- .replace(/\(\s*,+/g, ',(')
- .replace(/\[\s*,+/g, ',[')
- .replace(/\<\s*,+/g, ',<')
- .replace(/\{\s*,+/g, ',{');
+ .replace(/,+\s*\)/g, "),")
+ .replace(/,+\s*\]/g, "],")
+ .replace(/,+\s*\>/g, ">,")
+ .replace(/,+\s*\}/g, "},")
+ .replace(/\(\s*,+/g, ",(")
+ .replace(/\[\s*,+/g, ",[")
+ .replace(/\<\s*,+/g, ",<")
+ .replace(/\{\s*,+/g, ",{");
// Fix Bracket & Space
input = input
- .replace(/\s+\)/g, ')')
- .replace(/\s+\]/g, ']')
- .replace(/\s+\>/g, '>')
- .replace(/\s+\}/g, '}')
- .replace(/\(\s+/g, '(')
- .replace(/\[\s+/g, '[')
- .replace(/\<\s+/g, '<')
- .replace(/\{\s+/g, '{');
+ .replace(/\s+\)/g, ")")
+ .replace(/\s+\]/g, "]")
+ .replace(/\s+\>/g, ">")
+ .replace(/\s+\}/g, "}")
+ .replace(/\(\s+/g, "(")
+ .replace(/\[\s+/g, "[")
+ .replace(/\<\s+/g, "<")
+ .replace(/\{\s+/g, "{");
// Remove Space around Syntax
- input = input
- .replace(/\s*\|\s*/g, '|')
- .replace(/\s*\:\s*/g, ':');
+ input = input.replace(/\s*\|\s*/g, "|").replace(/\s*\:\s*/g, ":");
// Sentence -> Tags
- let tags = input.split(',').map(word => word.trim());
-
- // [""] -> [""]
- tags = this.#joinExtraNet(tags);
+ let tags = input.split(",").map((word) => word.trim());
// Remove Duplicate
tags = dedupe ? this.#dedupe(tags) : tags;
// Remove extra Spaces
- input = tags.join(', ').replace(/\s+/g, ' ');
+ input = tags.join(", ").replace(/\s+/g, " ");
// Remove Empty Brackets
while (/\(\s*\)|\[\s*\]/.test(input))
- input = input.replace(/\(\s*\)|\[\s*\]/g, '');
+ input = input.replace(/\(\s*\)|\[\s*\]/g, "");
// Space after Comma in Escaped Brackets (for franchise)
- input = input.replace(/\\\(([^\\\)]+?):([^\\\)]+?)\\\)/g, '\\($1: $2\\)');
+ input = input.replace(/\\\(([^\\\)]+?):([^\\\)]+?)\\\)/g, "\\($1: $2\\)");
+
// Prune empty Chunks
- input = input.split(',').map(word => word.trim()).filter(word => word).join(', ')
+ input = input.split(",").map((word) => word.trim()).filter((word) => word).join(", ");
+
// LoRA Block Weights
- input = input.replace(/\<[^\>]+\>/g, (match) => {
- return match.replace(/\,\s+/g, ',');
+ input = input.replace(/<.+?>/g, (match) => {
+ return match.replace(/\,\s+/g, ",");
});
+
// Remove empty before Colon
- input = input.replace(/\,\s*\:(\d)/g, ':$1');
+ input = input.replace(/,\s*:(\d)/g, ":$1");
input = this.#fromExpression(input);
@@ -155,7 +162,7 @@ class LeFormatter {
const results = [];
for (const tag of input) {
- const cleanedTag = tag.replace(/\[|\]|\(|\)/g, '').replace(/\s+/g, ' ').trim();
+ const cleanedTag = tag.replace(/\[|\]|\(|\)/g, "").replace(/\s+/g, " ").trim();
if (KEYWORD.test(cleanedTag)) {
results.push(tag);
@@ -170,66 +177,62 @@ class LeFormatter {
}
}
- if ((substitute == null) && (!uniqueSet.has(cleanedTag))) {
+ if (substitute == null && !uniqueSet.has(cleanedTag)) {
uniqueSet.add(cleanedTag);
results.push(tag);
continue;
}
- if ((substitute != null) && (!uniqueSet.has(substitute))) {
+ if (substitute != null && !uniqueSet.has(substitute)) {
uniqueSet.add(substitute);
results.push(tag.replace(cleanedTag, substitute));
continue;
}
- results.push(tag.replace(cleanedTag, ''));
+ results.push(tag.replace(cleanedTag, ""));
}
return results;
}
/** @param {string} input @returns {string} */
- static #removeUnderscore(input) {
- if (!input.trim())
- return "";
+ static #rmUnderscore(input) {
+ if (!input.trim()) return "";
- const syntax = /\,\|\:\<\>\(\)\[\]\{\}/;
- const pattern = new RegExp(`([${syntax.source}]+|[^${syntax.source}]+)`, 'g');
- const parts = input.match(pattern);
+ for (let i = 0; i < this.#cards.length; i++)
+ input = input.replaceAll(this.#cards[i], `@TEXTUAL${i}INVERSION@`);
- const processed = parts.map((part) => {
- if (new RegExp(`[${syntax.source}]+`).test(part))
- return part;
- if (/^\s+$/.test(part))
- return part;
+ input = input.replaceAll("_", " ");
- if (!this.#cachedCards.includes(part.trim()))
- part = part.replaceAll('_', ' ');
+ for (let i = 0; i < this.#cards.length; i++)
+ input = input.replaceAll(`@TEXTUAL${i}INVERSION@`, this.#cards[i]);
- return part;
- });
-
- return processed.join('');
+ return input;
}
}
(function () {
onUiLoaded(() => {
-
const config = new pfConfigs();
- const formatter = pfUI.setupUIs(config.autoRun, config.dedupe, config.removeUnderscore);
+ const formatter = pfUI.setupUIs(config.autoRun, config.dedupe, config.rmUnderscore);
- document.addEventListener('keydown', (e) => {
- if (e.altKey && e.shiftKey && e.code === 'KeyF') {
+ 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.removeUnderscore, true, config.comma);
+ LeFormatter.formatPipeline(
+ field,
+ config.dedupe,
+ config.rmUnderscore,
+ config.refresh,
+ config.comma,
+ );
}
});
formatter.auto.addEventListener("change", () => {
config.autoRun = formatter.auto.checked;
- formatter.manual.style.display = config.autoRun ? 'none' : 'flex';
+ formatter.manual.style.display = config.autoRun ? "none" : "flex";
});
formatter.dedupe.addEventListener("change", () => {
@@ -237,52 +240,61 @@ class LeFormatter {
});
formatter.underscore.addEventListener("change", () => {
- config.removeUnderscore = formatter.underscore.checked;
- formatter.refresh.style.display = config.removeUnderscore ? 'flex' : 'none';
+ config.rmUnderscore = formatter.underscore.checked;
+ formatter.refresh.style.display = config.rmUnderscore ? "flex" : "none";
});
formatter.manual.addEventListener("click", () => {
for (const field of config.promptFields)
- LeFormatter.formatPipeline(field, config.dedupe, config.removeUnderscore, config.refresh, config.comma);
+ LeFormatter.formatPipeline(
+ field,
+ config.dedupe,
+ config.rmUnderscore,
+ config.refresh,
+ config.comma,
+ );
});
formatter.refresh.addEventListener("click", () => {
LeFormatter.forceReload();
});
- const tools = document.getElementById('quicksettings');
+ const tools = document.getElementById("quicksettings");
tools.after(formatter);
/** Expandable List of IDs in 1 place */
const IDs = [
- 'txt2img_generate',
- 'txt2img_enqueue',
- 'img2img_generate',
- 'img2img_enqueue'
+ "txt2img_generate",
+ "txt2img_enqueue",
+ "img2img_generate",
+ "img2img_enqueue",
];
for (const id of IDs) {
const button = document.getElementById(id);
- button?.addEventListener('click', () => {
- if (config.autoRun) {
- for (const field of config.promptFields)
- LeFormatter.formatPipeline(field, config.dedupe, config.removeUnderscore, config.refresh, config.comma);
- }
+ button?.addEventListener("click", () => {
+ if (!config.autoRun) return;
+ for (const field of config.promptFields)
+ LeFormatter.formatPipeline(
+ field,
+ config.dedupe,
+ config.rmUnderscore,
+ config.refresh,
+ config.comma,
+ );
});
}
- if (!config.paste)
- return;
+ 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;
+ 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
+ field.addEventListener("paste", (event) => {
+ /** @type {string} */ let paste = (event.clipboardData || window.clipboardData).getData("text");
+ if ([...paste.matchAll(paramPatterns)].length > 3) return; // Infotext
event.preventDefault();
@@ -292,25 +304,26 @@ class LeFormatter {
const multiline = !paste.includes(",");
if (config.booru) {
- paste = paste.replace(/\s*[\d.]+[kM]\s*|(?:^|,|\s+)\d{2,}(?:\s+|,|\?|$)|[\?\+\-]\s+/g, ", ");
+ 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, '\\$&');
+ return match.replace(/[()]/g, "\\$&");
});
}
if (multiline) {
const lines = [];
for (const line of paste.split("\n"))
- lines.push(LeFormatter.formatString(line, config.dedupe, config.removeUnderscore));
- paste = lines.join("\n");
+ lines.push(LeFormatter.formatString(line, config.dedupe, config.rmUnderscore));
+ paste = lines.filter((l) => l).join("\n");
+ if (!paste.includes(",")) paste = paste.replaceAll("\n", ", ");
} else
- paste = LeFormatter.formatString(paste, config.dedupe, config.removeUnderscore);
+ paste = LeFormatter.formatString(paste, config.dedupe, config.rmUnderscore);
- paste = `${commaStart ? ", " : ""}${paste}${commaEnd ? ", " : ""}`
+ paste = `${commaStart ? ", " : ""}${paste}${commaEnd ? ", " : ""}`;
const currentText = field.value;
const cursorPosition = field.selectionStart;
@@ -320,26 +333,8 @@ class LeFormatter {
field.selectionStart = field.selectionEnd = cursorPosition + paste.length;
updateInput(field);
+ return false;
});
}
-
- let refreshTimer;
- function onRefresh() { setTimeout(() => LeFormatter.forceReload(), 100); }
-
- const observer = new MutationObserver((mutationsList) => {
- for (const mutation of mutationsList) {
- if (mutation.type !== "childList")
- continue;
- if (refreshTimer) clearTimeout(refreshTimer);
- refreshTimer = setTimeout(onRefresh, 250);
- break;
- }
- });
-
- const lora = document.getElementById('txt2img_lora_cards_html');
- const ti = document.getElementById('txt2img_textual_inversion_cards_html');
-
- observer.observe(lora, { childList: true, subtree: true });
- observer.observe(ti, { childList: true, subtree: true });
});
})();
diff --git a/sample.jpg b/sample.jpg
deleted file mode 100644
index e72c063..0000000
Binary files a/sample.jpg and /dev/null differ
diff --git a/sample.png b/sample.png
new file mode 100644
index 0000000..09acc65
Binary files /dev/null and b/sample.png differ
diff --git a/scripts/pf_settings.py b/scripts/pf_settings.py
index 8ff9181..13ab61e 100644
--- a/scripts/pf_settings.py
+++ b/scripts/pf_settings.py
@@ -1,10 +1,54 @@
+from gradio import Checkbox, Column, Textbox
+
+from modules import scripts
from modules.script_callbacks import on_ui_settings
+from modules.shared import OptionInfo, cmd_opts, opts
+
+
+def get_embeddings() -> set[str]:
+ from pathlib import Path
+
+ EXTENSIONS = (".pt", ".pth", ".ckpt", ".safetensors", ".sft")
+ items: set[str] = set()
+
+ embeddings = Path(cmd_opts.embeddings_dir)
+ for ext in EXTENSIONS:
+ files = embeddings.glob(f"**/*{ext}")
+ for file in files:
+ items.add(file.stem)
+
+ return items
+
+
+class PFServer(scripts.Script):
+ def title(self):
+ return "Prompt Format"
+
+ def show(self, is_img2img):
+ return scripts.AlwaysVisible if is_img2img else None
+
+ def ui(self, is_img2img):
+ if not is_img2img:
+ return None
+
+ with Column(visible=False):
+ emb = get_embeddings()
+ link = Textbox(
+ value="\n".join(emb),
+ elem_id="pf_embeddings",
+ interactive=False,
+ )
+ dummy = Checkbox(
+ label="Enable",
+ elem_id="pf_checkbox",
+ interactive=True,
+ )
+
+ link.do_not_save_to_config = True
+ dummy.do_not_save_to_config = True
def on_settings():
- from modules.shared import OptionInfo, opts
- from gradio import Textbox
-
args = {"section": ("pf", "Prompt Format"), "category_id": "system"}
opts.add_option(
@@ -20,17 +64,23 @@ def on_settings():
opts.add_option(
"pf_startinauto",
- OptionInfo(True, "Launch in Auto Mode", **args),
+ OptionInfo(
+ True, "Launch the WebUI with Auto Format enabled", **args
+ ).needs_reload_ui(),
)
opts.add_option(
"pf_startwithdedupe",
- OptionInfo(True, "Launch with Remove Duplicates", **args),
+ OptionInfo(
+ True, "Launch the WebUI with Remove Duplicates enabled", **args
+ ).needs_reload_ui(),
)
opts.add_option(
"pf_startwithrmudscr",
- OptionInfo(True, "Launch with Remove Underscores", **args),
+ OptionInfo(
+ True, "Launch the WebUI with Remove Underscores enabled", **args
+ ).needs_reload_ui(),
)
opts.add_option(
@@ -42,22 +92,31 @@ def on_settings():
opts.add_option(
"pf_onpaste",
- OptionInfo(False, "Format the pasted text", **args).needs_reload_ui(),
+ OptionInfo(
+ False, "Format the texts pasted from clipboard", **args
+ ).needs_reload_ui(),
+ )
+
+ opts.add_option(
+ "pf_booru",
+ OptionInfo(False, 'Process the "Booru Structure"', **args)
+ .info("only take effect when the above option is enabled")
+ .needs_reload_ui(),
)
opts.add_option(
"pf_exclusion",
OptionInfo(
default="",
- label="Exclude Tags from Remove Underscores",
+ label="Tags excluded from Remove Underscores",
component=Textbox,
component_args={
"placeholder": "score_9, score_8_up, score_7_up",
- "max_lines": 1,
+ "max_lines": 4,
"lines": 1,
},
**args,
- ),
+ ).info("requires Reload button"),
)
opts.add_option(
@@ -67,23 +126,17 @@ def on_settings():
label="Tag Alias for Remove Duplicates",
component=Textbox,
component_args={
- "placeholder": "1girl: girl, woman, lady\nadult: \\d*\\s*(y\\.?o\\.?|[Yy]ear[s]? [Oo]ld)",
- "max_lines": 16,
- "lines": 4,
+ "placeholder": "1girl: girl, woman, lady\nadult: \\d+\\s*(y\\.?o\\.?|[Yy]ear[s]? [Oo]ld)",
+ "max_lines": 8,
+ "lines": 2,
},
**args,
)
- .info("treat tags on the right as duplicates of the main tag on the left")
- .link("RegExr", "https://regexr.com/"),
- )
-
- opts.add_option(
- "pf_booru",
- OptionInfo(
- False,
- 'Process the "Booru Structure"',
- **args,
- ).info("requires format on paste) (Experimental"),
+ .info("requires Reload button")
+ .link(
+ "Regexper",
+ "https://regexper.com/#%5Cd%2B%5Cs*%28y%5C.%3Fo%5C.%3F%7C%5BYy%5Dear%5Bs%5D%3F%20%5BOo%5Dld%29",
+ ),
)