optimization

main
Haoming 2024-12-25 18:09:13 +08:00
parent a3f9c96cd4
commit bed7a348d4
7 changed files with 156 additions and 186 deletions

View File

@ -29,13 +29,10 @@ Sometimes, when you type too fast or copy prompts from all over the places, you
- [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 Cached Cards & Alias` to force a reload
- By default, the `ExtraNetwork` cards are cached once at the start, to be excluded from `Remove Underscores`. If you add more cards while the Webui is still running, you may click this to re-cache again.
- [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.
### Tag Alias
<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`

View File

@ -29,13 +29,10 @@
- [x] 按下 `Alt` + `Shift` + `F` 亦可觸發格式化
- [x] 為指定單字新增 "[同義詞](#同義詞)"
- [x] 將指定字詞除外 `Remove Underscores` 的影響
- [x] 點擊 `Reload Cached Cards & Alias` 以重新載入
- 在一開始 `ExtraNetwork` 中的卡片會被緩存一次以防被 `Remove Underscores` 影響。如果你在 Webui 仍在運行時加入更多的卡片,點擊此按鈕來重新緩存。
- [x] 點擊 `Reload` 以緩存卡片
- 在 Webui 剛開啟時 `ExtraNetwork` 中的卡片會被緩存一次以防被 `Remove Underscores` 影響。如果你在 Webui 運行時加入更多的卡片,點擊此按鈕來重新緩存。
### 同義詞
<p align="right"><i><b>新功能</b> 🔥</i></p>
- 在 `Prompt Format` 的設定裡,有個新的 **Tag Alias** 欄位
- 你可以在此把其它字詞設為主單字的同義詞,使其在 `Remove Duplicates` 中被當作重複字而刪去
- 格式為 `main tag: alias1, alias2, alias3`

View File

@ -1,4 +1,4 @@
class LeFormatterConfig {
class pfConfigs {
constructor() {
this.refresh = this.#shouldRefresh();
@ -7,7 +7,6 @@ class LeFormatterConfig {
this.removeUnderscore = this.#defaultRemoveUnderscore();
this.comma = this.#appendComma();
this.promptFields = this.#getPromptFields();
this.button = this.#createReloadButton();
}
/** @returns {boolean} */
@ -40,56 +39,47 @@ class LeFormatterConfig {
return config.checked;
}
// ===== Cache All Prompt Fields =====
/** @returns {HTMLTextAreaElement[]} */
/**
* Cache All Prompt Fields
* @returns {HTMLTextAreaElement[]}
*/
#getPromptFields() {
const textareas = [];
// Expandable ID List in 1 place
[
/** Expandable List of IDs in 1 place */
const IDs = [
'txt2img_prompt',
'txt2img_neg_prompt',
'img2img_prompt',
'img2img_neg_prompt',
'hires_prompt',
'hires_neg_prompt'
].forEach((id) => {
];
for (const id of IDs) {
const textArea = document.getElementById(id)?.querySelector('textarea');
if (textArea != null)
textareas.push(textArea);
});
}
// ADetailer
[
const ADetailer = [
"script_txt2img_adetailer_ad_main_accordion",
"script_img2img_adetailer_ad_main_accordion"
].forEach((id) => {
];
for (const id of ADetailer) {
const fields = document.getElementById(id)?.querySelectorAll('textarea');
if (fields != null)
fields.forEach((textArea) => {
if (textArea.placeholder.length > 0)
textareas.push(textArea);
});
});
if (fields == null)
continue;
for (const textArea of fields) {
if (textArea.placeholder.length > 0)
textareas.push(textArea);
}
}
return textareas;
}
/** @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";
button.style.borderRadius = "1em";
button.style.margin = "1em 0em 0em 0em";
page.appendChild(button);
return button;
}
/** @returns {string[]} */
static cacheCards() {
const extras = document.getElementById('txt2img_extra_tabs');
@ -97,16 +87,15 @@ class LeFormatterConfig {
return [];
const cards = [];
extras.querySelectorAll('span.name').forEach((card) => {
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()) {
config.split(",").forEach((tag) => {
for (const tag of config.split(","))
cards.push(tag.trim());
});
}
return cards;
@ -117,22 +106,21 @@ class LeFormatterConfig {
const alias = new Map();
const config = document.getElementById('setting_pf_alias').querySelector('textarea').value;
if (!config.trim())
return alias;
config.split("\n").forEach((line) => {
for (const line of config.split("\n")) {
const [tag, words] = line.split(":");
const mainTag = tag.trim();
words.split(",").map(part => part.trim()).forEach((word) => {
for (const word of words.split(",").map(part => part.trim())) {
if (word === mainTag)
return;
continue;
const pattern = this.#parseRegExp(word);
alias.set(pattern, mainTag);
});
});
}
}
return alias;
}
@ -141,7 +129,6 @@ class LeFormatterConfig {
static #parseRegExp(input) {
const startAnchor = input.startsWith('^');
const endAnchor = input.endsWith('$');
return new RegExp(`${startAnchor ? '' : '^'}${input}${endAnchor ? '' : '$'}`);
}

View File

@ -1,26 +1,22 @@
class LeFormatterUI {
class pfUI {
/** @param {Function} onClick @returns {HTMLButtonElement} */
static #button(onClick) {
/** @param {string} text @param {string} tip @returns {HTMLButtonElement} */
static #button(text, tip) {
const button = document.createElement('button');
button.textContent = 'Format';
button.id = 'manual-format';
button.classList.add(['lg', 'secondary', 'gradio-button']);
button.addEventListener('click', onClick);
button.textContent = text;
if (tip) button.title = tip;
return button;
}
/** @param {boolean} default_value @param {string} text @returns {HTMLDivElement} */
static #checkbox(default_value, text) {
/** @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);
label.removeAttribute('id');
label.classList.add("pf-checkbox");
label.removeAttribute('id');
const checkbox = label.children[0];
checkbox.checked = default_value;
checkbox.checked = value;
const span = label.children[1];
span.textContent = text;
@ -28,16 +24,19 @@ class LeFormatterUI {
}
/**
* @param {Function} onManual
* @param {boolean} autoRun @param {boolean} dedupe @param {boolean} removeUnderscore
* @param {boolean} autoRun
* @param {boolean} dedupe
* @param {boolean} removeUnderscore
* @returns {HTMLDivElement}
* */
static setupUIs(onManual, autoRun, dedupe, removeUnderscore) {
*/
static setupUIs(autoRun, dedupe, removeUnderscore) {
const formatter = document.createElement('div');
formatter.id = 'le-formatter';
const manualBtn = this.#button(onManual);
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 autoCB = this.#checkbox(autoRun, 'Auto Format');
const dedupeCB = this.#checkbox(dedupe, 'Remove Duplicates');
@ -47,13 +46,13 @@ class LeFormatterUI {
formatter.appendChild(manualBtn);
formatter.appendChild(dedupeCB);
formatter.appendChild(underscoreCB);
formatter.appendChild(refreshBtn);
formatter.btn = manualBtn;
formatter.checkboxs = [
autoCB.children[0],
dedupeCB.children[0],
underscoreCB.children[0]
];
formatter.manual = manualBtn;
formatter.refresh = refreshBtn;
formatter.auto = autoCB.children[0];
formatter.dedupe = dedupeCB.children[0];
formatter.underscore = underscoreCB.children[0];
return formatter;
}

View File

@ -1,11 +1,27 @@
class LeFormatter {
static #cachedCards = null;
static #alias = null;
static #cachedCardsInternal = null;
static #aliasInternal = null;
static forceReload() {
this.#alias = LeFormatterConfig.getTagAlias();
this.#cachedCards = LeFormatterConfig.cacheCards();
this.#cachedCardsInternal = null;
this.#aliasInternal = null;
}
/** @returns {string[]} */
static get #cachedCards() {
if (this.#cachedCardsInternal == null)
this.#cachedCardsInternal = pfConfigs.cacheCards();
return this.#cachedCardsInternal;
}
/** @returns {Map<RegExp, string>} */
static get #alias() {
if (this.#aliasInternal == null)
this.#aliasInternal = pfConfigs.getTagAlias();
return this.#aliasInternal;
}
/**
@ -25,7 +41,7 @@ class LeFormatter {
textArea.value = lines.join('\n');
else {
const val = lines.join(',\n');
textArea.value = val.replace(/\n,\n/g, "\n\n");
textArea.value = val.replace(/\n,\n/g, '\n\n');
}
if (autoRefresh)
@ -35,12 +51,7 @@ class LeFormatter {
/** @param {string} input @param {boolean} dedupe @param {boolean} removeUnderscore @returns {string} */
static #formatString(input, dedupe, removeUnderscore) {
// Remove Duplicate
if (dedupe) {
if (this.#alias == null)
this.#alias = LeFormatterConfig.getTagAlias();
input = this.#dedupe(input);
}
input = dedupe ? this.#dedupe(input) : input;
// Fix Commas inside Brackets
input = input
@ -50,22 +61,17 @@ class LeFormatter {
.replace(/\[\s*,+/g, ',[');
// Sentence -> Tags
var tags = input.split(',');
let tags = input.split(',');
// Remove Underscore
if (removeUnderscore) {
if (this.#cachedCards == null)
this.#cachedCards = LeFormatterConfig.cacheCards();
tags = this.#removeUnderscore(tags);
}
tags = removeUnderscore ? this.#removeUnderscore(tags) : tags;
// Remove Stray Brackets
const patterns = /^\(+$|^\)+$|^\[+$|^\]+$/;
tags = tags.filter(word => !patterns.test(word));
// Remove extra Spaces
input = tags.join(', ').replace(/\s{2,}/g, ' ');
input = tags.join(', ').replace(/\s+/g, ' ');
// Fix Bracket & Space
input = input
@ -85,22 +91,22 @@ class LeFormatter {
static #dedupe(input) {
const chunks = input.split(',');
const KEYWORD = /^(AND|BREAK)$/;
const uniqueSet = new Set();
const resultArray = [];
const KEYWORD = /^(AND|BREAK)$/;
chunks.forEach((tag) => {
for (const tag of chunks) {
const cleanedTag = tag.replace(/\[|\]|\(|\)/g, '').replace(/\s+/g, ' ').trim();
if (KEYWORD.test(cleanedTag)) {
resultArray.push(tag);
return;
continue;
}
var substitute = null;
let substitute = null;
for (const [pattern, mainTag] of this.#alias) {
if (substitute != null)
return;
continue;
if (pattern.test(cleanedTag))
substitute = mainTag;
}
@ -108,39 +114,39 @@ class LeFormatter {
if ((substitute == null) && (!uniqueSet.has(cleanedTag))) {
uniqueSet.add(cleanedTag);
resultArray.push(tag);
return;
continue;
}
if ((substitute != null) && (!uniqueSet.has(substitute))) {
uniqueSet.add(substitute);
resultArray.push(tag.replace(cleanedTag, substitute));
return;
continue;
}
resultArray.push(tag.replace(cleanedTag, ''));
});
}
return resultArray.join(', ');
}
/** @param {Array<string} tags @returns {Array<string} */
/** @param {string[]} tags @returns {string[]} */
static #removeUnderscore(tags) {
const result = [];
tags.forEach((tag) => {
for (const tag of tags) {
if (!tag.trim())
return;
continue;
// [start:end:step] OR <lora:name:str>
const chucks = tag.split(':').map(c => c.trim());
for (let i = 0; i < chucks.length; i++) {
if (!this.#cachedCards.includes(chucks[i]))
chucks[i] = chucks[i].replace(/_/g, ' ');
chucks[i] = chucks[i].replaceAll('_', ' ');
}
result.push(chucks.join(':').trim());
});
}
return result;
}
@ -148,51 +154,59 @@ class LeFormatter {
onUiLoaded(() => {
const config = new LeFormatterConfig();
config.button.onclick = () => { LeFormatter.forceReload(); }
const config = new pfConfigs();
const formatter = pfUI.setupUIs(config.autoRun, config.dedupe, config.removeUnderscore);
document.addEventListener('keydown', (e) => {
if (e.altKey && e.shiftKey && e.code === 'KeyF') {
e.preventDefault();
config.promptFields.forEach((field) => LeFormatter.formatPipeline(field, config.dedupe, config.removeUnderscore, true, config.comma));
for (const field of config.promptFields)
LeFormatter.formatPipeline(field, config.dedupe, config.removeUnderscore, true, config.comma);
}
});
const formatter = LeFormatterUI.setupUIs(
() => {
config.promptFields.forEach((field) => LeFormatter.formatPipeline(field, config.dedupe, config.removeUnderscore, true, config.comma));
},
config.autoRun, config.dedupe, config.removeUnderscore
);
formatter.checkboxs[0].addEventListener("change", (e) => {
config.autoRun = e.target.checked;
formatter.btn.style.display = config.autoRun ? 'none' : 'flex';
formatter.auto.addEventListener("change", () => {
config.autoRun = formatter.auto.checked;
formatter.manual.style.display = config.autoRun ? 'none' : 'flex';
});
formatter.checkboxs[1].addEventListener("change", (e) => {
config.dedupe = e.target.checked;
formatter.dedupe.addEventListener("change", () => {
config.dedupe = formatter.dedupe.checked;
});
formatter.checkboxs[2].addEventListener("change", (e) => {
config.removeUnderscore = e.target.checked;
formatter.underscore.addEventListener("change", () => {
config.removeUnderscore = formatter.underscore.checked;
formatter.refresh.style.display = config.removeUnderscore ? 'flex' : 'none';
});
formatter.manual.addEventListener("click", () => {
for (const field of config.promptFields)
LeFormatter.formatPipeline(field, config.dedupe, config.removeUnderscore, config.refresh, config.comma);
});
formatter.refresh.addEventListener("click", () => {
LeFormatter.forceReload();
});
const tools = document.getElementById('quicksettings');
tools.after(formatter);
['txt', 'img'].forEach((mode) => {
// 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, config.comma));
});
/** Expandable List of IDs in 1 place */
const IDs = [
'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);
}
});
});
}
});

View File

@ -1,61 +1,39 @@
from modules.script_callbacks import on_ui_settings
from modules.shared import OptionInfo, opts
import gradio as gr
section = ("pf", "Prompt Format")
def on_settings():
from modules.shared import OptionInfo, opts
import gradio as gr
args = {"section": ("pf", "Prompt Format"), "category_id": "system"}
opts.add_option(
"pf_disableupdateinput",
OptionInfo(
False,
"Disable automatic input updates",
section=section,
category_id="system",
).html(
"""
<span class='info'>(enable this if you have Extension,
such as <a href="https://github.com/DominikDoom/a1111-sd-webui-tagcomplete">tagcomplete</a>,
that subscribes to text editing event)</span>
"""
OptionInfo(False, "Disable the automatic updates of the prompts", **args).info(
'enable this if you have Extensions, such as <a href="https://github.com/DominikDoom/a1111-sd-webui-tagcomplete">tagcomplete</a>, that subscribe to text editing events'
),
)
opts.add_option(
"pf_startinauto",
OptionInfo(True, "Start in Auto Mode", section=section, category_id="system"),
OptionInfo(True, "Launch in Auto Mode", **args),
)
opts.add_option(
"pf_startwithdedupe",
OptionInfo(
True,
"Launch with Remove Duplicates",
section=section,
category_id="system",
),
OptionInfo(True, "Launch with Remove Duplicates", **args),
)
opts.add_option(
"pf_startwithrmudscr",
OptionInfo(
False,
"Launch with Remove Underscores",
section=section,
category_id="system",
),
OptionInfo(True, "Launch with Remove Underscores", **args),
)
opts.add_option(
"pf_appendcomma",
OptionInfo(
True,
"Append a comma at the end of a line",
section=section,
category_id="system",
).info("only active when there are more than one line"),
OptionInfo(True, "Append a comma at the end of each line", **args).info(
"only active when there are multiple lines"
),
)
opts.add_option(
@ -66,11 +44,10 @@ def on_settings():
component=gr.Textbox,
component_args={
"placeholder": "score_9, score_8_up, score_7_up",
"lines": 1,
"max_lines": 1,
"lines": 1,
},
section=section,
category_id="system",
**args
),
)
@ -82,18 +59,17 @@ def on_settings():
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,
"max_lines": 16,
"lines": 4,
},
section=section,
category_id="system",
**args
)
.link("RegExr", "https://regexr.com/")
.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/"),
"""treat tags on the right as duplicates of the main tag on the left)
(based on regular expression, meaning you need to escape special characters)
(comma is not allowed"""
),
)

View File

@ -4,7 +4,7 @@
user-select: none;
}
#manual-format {
#le-formatter button {
height: 24px;
padding: 2px 8px;
border-radius: 0.2em;
@ -16,7 +16,7 @@
align-items: center;
}
.pf-checkbox {
#le-formatter .pf-checkbox {
display: flex;
align-items: center;
margin: 2px 8px;