main
Haoming 2024-09-02 17:22:41 +08:00
parent 529ab30014
commit 951dc4bb5d
6 changed files with 243 additions and 57 deletions

View File

@ -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 <ins>System</ins> 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.
<p align="right"><i><b>New</b> 🔥</i></p>
- 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`
<hr>
### 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.

View File

@ -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** 頁面 <ins>System</ins> 下的 `Prompt Format` 區可以 開啟/關閉 上述功能
- [x] 按下 `Alt` + `Shift` + `F` 亦可觸發格式化
- [x] 為指定單字新增 "[同義詞](#同義詞)"
## 注意
1. 由於`自動`校正和生成是同時觸發,除非有先用過`手動`校正,不然當下所產的第一張圖片之內部資料並不會被更新。
### 同義詞
2. 有些擴充 *(如. [tagcomplete](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete))* 追蹤文字的編輯事件,意即文字校正會導致它們啟動。
你可以到 **Settings** 頁面的 `System` 區關閉咒語的自動編輯。
<p align="right"><i><b>新功能</b> 🔥</i></p>
- 在 `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`
<hr>
### 注意
1. 由於 `自動`校正 和 生成 是同時觸發,當下所生產的第一張圖片之咒語可能不會是已更新的。
2. 有些擴充 *(如. [tagcomplete](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete))* 追蹤文字的編輯事件,意即文字校正會導致它們啟動。你可以到設定關閉咒語的自動更新。

View File

@ -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));
});
});
});
});

View File

@ -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<Element>} */
/** @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<string>} */
/** @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<RegExp, string>} */
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 ? '' : '$'}`);
}
}

View File

@ -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');

View File

@ -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)