implement #9
parent
529ab30014
commit
951dc4bb5d
47
README.md
47
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 <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.
|
||||
|
|
|
|||
42
README_ZH.md
42
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** 頁面 <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))* 追蹤文字的編輯事件,意即文字校正會導致它們啟動。你可以到設定關閉咒語的自動更新。
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
// 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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 ? '' : '$'}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue