overhaul
parent
e77467fc42
commit
4135db4752
74
README.md
74
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)
|
||||
|
||||
<p align="center"><img src="sample.jpg" width=512></p>
|
||||
<p align="center">
|
||||
<img src="sample.png" width=768><br>
|
||||
Sometimes, when you type too fast or copy prompts from all over the places, you end up with duplicated <b>spaces</b> and <b>commas</b>. This simple Extension helps removing them whenever you click <b>Generate</b>.
|
||||
</p>
|
||||
|
||||
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**.
|
||||
<br>
|
||||
|
||||
## 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
|
||||
|
||||
<br>
|
||||
|
||||
- [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
|
||||
- [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`
|
||||
|
||||
<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. Though you may need to manually type something else for the prompt to get actually updated.
|
||||
|
||||
<hr>
|
||||
> [!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.
|
||||
|
||||
<details>
|
||||
<summary><b>Booru</b></summary>
|
||||
|
||||
- [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/)
|
||||
|
||||
<p align="center"><img src="booru.png" width=512></p>
|
||||
|
||||
</details>
|
||||
|
||||
<br>
|
||||
|
||||
### 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
|
||||
|
|
|
|||
72
README_ZH.md
72
README_ZH.md
|
|
@ -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) !
|
||||
|
||||
<p align="center"><img src="sample.jpg" width=512></p>
|
||||
|
||||
有時候如果打字太快或是從各處東拼西湊咒語,常會造成多個重複的空格或逗點。這個擴充可以幫忙移除它們。
|
||||
|
||||
## 功能
|
||||
- [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** 頁面 <ins>System</ins> 下的 `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`
|
||||
|
||||
<hr>
|
||||
|
||||
### 注意
|
||||
1. 由於 `自動`校正 和 生成 是同時觸發,當下所生產的第一張圖片之咒語可能不會是已更新的。
|
||||
|
||||
2. 有些擴充 *(如. [tagcomplete](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete))* 追蹤文字的編輯事件,意即文字校正會導致它們啟動。你可以到設定關閉咒語的自動更新。
|
||||
|
||||
<hr>
|
||||
|
||||
<details>
|
||||
<summary><b>Booru</b></summary>
|
||||
|
||||
- [x] 自動清除多餘字詞
|
||||
- 目前支援 [gelbooru](https://gelbooru.com/) 及 [danbooru](https://safebooru.donmai.us/)
|
||||
|
||||
<p align="center"><img src="booru.png" width=512></p>
|
||||
|
||||
</details>
|
||||
BIN
booru.png
BIN
booru.png
Binary file not shown.
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 22 KiB |
|
|
@ -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<RegExp, string>} */
|
||||
/** @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("$")}$`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<RegExp, string>} */
|
||||
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<string, string>} */
|
||||
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());
|
||||
|
||||
// ["<lora:name:weight:lbw=1", "2", "3>"] -> ["<lora:name:weight:lbw=1,2,3>"]
|
||||
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) {
|
||||
button?.addEventListener("click", () => {
|
||||
if (!config.autoRun) return;
|
||||
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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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 });
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
BIN
sample.jpg
BIN
sample.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -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 <b>Reload</b> 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) (<b>Experimental</b>"),
|
||||
.info("requires <b>Reload</b> button")
|
||||
.link(
|
||||
"Regexper",
|
||||
"https://regexper.com/#%5Cd%2B%5Cs*%28y%5C.%3Fo%5C.%3F%7C%5BYy%5Dear%5Bs%5D%3F%20%5BOo%5Dld%29",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue