add lora user metadata editor dialog inspired by MrKuenning's mockup from #7458
This commit is contained in:
parent
5decbf184b
commit
11f339733d
187
extensions-builtin/Lora/ui_edit_user_metadata.py
Normal file
187
extensions-builtin/Lora/ui_edit_user_metadata.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import html
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
|
||||||
|
import gradio as gr
|
||||||
|
import re
|
||||||
|
|
||||||
|
from modules import ui_extra_networks_user_metadata
|
||||||
|
|
||||||
|
|
||||||
|
def is_non_comma_tagset(tags):
|
||||||
|
average_tag_length = sum(len(x) for x in tags.keys()) / len(tags)
|
||||||
|
|
||||||
|
return average_tag_length >= 16
|
||||||
|
|
||||||
|
|
||||||
|
re_word = re.compile(r"[-_\w']+")
|
||||||
|
re_comma = re.compile(r" *, *")
|
||||||
|
|
||||||
|
|
||||||
|
def build_tags(metadata):
|
||||||
|
tags = {}
|
||||||
|
|
||||||
|
for _, tags_dict in metadata.get("ss_tag_frequency", {}).items():
|
||||||
|
for tag, tag_count in tags_dict.items():
|
||||||
|
tag = tag.strip()
|
||||||
|
tags[tag] = tags.get(tag, 0) + int(tag_count)
|
||||||
|
|
||||||
|
if tags and is_non_comma_tagset(tags):
|
||||||
|
new_tags = {}
|
||||||
|
|
||||||
|
for text, text_count in tags.items():
|
||||||
|
for word in re.findall(re_word, text):
|
||||||
|
if len(word) < 3:
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_tags[word] = new_tags.get(word, 0) + text_count
|
||||||
|
|
||||||
|
tags = new_tags
|
||||||
|
|
||||||
|
ordered_tags = sorted(tags.keys(), key=tags.get, reverse=True)
|
||||||
|
|
||||||
|
return [(tag, tags[tag]) for tag in ordered_tags]
|
||||||
|
|
||||||
|
|
||||||
|
class LoraUserMetadataEditor(ui_extra_networks_user_metadata.UserMetadataEditor):
|
||||||
|
def __init__(self, ui, tabname, page):
|
||||||
|
super().__init__(ui, tabname, page)
|
||||||
|
|
||||||
|
self.taginfo = None
|
||||||
|
self.edit_activation_text = None
|
||||||
|
self.slider_preferred_weight = None
|
||||||
|
self.edit_notes = None
|
||||||
|
|
||||||
|
def save_lora_user_metadata(self, name, desc, activation_text, preferred_weight, notes):
|
||||||
|
user_metadata = self.get_user_metadata(name)
|
||||||
|
user_metadata["description"] = desc
|
||||||
|
user_metadata["activation text"] = activation_text
|
||||||
|
user_metadata["preferred weight"] = preferred_weight
|
||||||
|
user_metadata["notes"] = notes
|
||||||
|
|
||||||
|
self.write_user_metadata(name, user_metadata)
|
||||||
|
|
||||||
|
def get_metadata_table(self, name):
|
||||||
|
table = super().get_metadata_table(name)
|
||||||
|
item = self.page.items.get(name, {})
|
||||||
|
metadata = json.loads(item.get("metadata") or '{}')
|
||||||
|
|
||||||
|
keys = [
|
||||||
|
('ss_sd_model_name', "Model:"),
|
||||||
|
('ss_resolution', "Resolution:"),
|
||||||
|
('ss_clip_skip', "Clip skip:"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for key, label in keys:
|
||||||
|
value = metadata.get(key, None)
|
||||||
|
if value is not None and str(value) != "None":
|
||||||
|
table.append((label, html.escape(value)))
|
||||||
|
|
||||||
|
image_count = 0
|
||||||
|
for _, params in metadata.get("ss_dataset_dirs", {}).items():
|
||||||
|
image_count += int(params.get("img_count", 0))
|
||||||
|
|
||||||
|
if image_count:
|
||||||
|
table.append(("Dataset size:", image_count))
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
def put_values_into_components(self, name):
|
||||||
|
user_metadata = self.get_user_metadata(name)
|
||||||
|
values = super().put_values_into_components(name)
|
||||||
|
|
||||||
|
item = self.page.items.get(name, {})
|
||||||
|
metadata = json.loads(item.get("metadata") or '{}')
|
||||||
|
|
||||||
|
tags = build_tags(metadata)
|
||||||
|
gradio_tags = [(tag, str(count)) for tag, count in tags[0:24]]
|
||||||
|
|
||||||
|
return [
|
||||||
|
*values[0:4],
|
||||||
|
gr.HighlightedText.update(value=gradio_tags, visible=True if tags else False),
|
||||||
|
user_metadata.get('activation text', ''),
|
||||||
|
float(user_metadata.get('preferred weight', 0.0)),
|
||||||
|
user_metadata.get('notes', ''),
|
||||||
|
gr.update(visible=True if tags else False),
|
||||||
|
gr.update(value=self.generate_random_prompt_from_tags(tags), visible=True if tags else False),
|
||||||
|
]
|
||||||
|
|
||||||
|
def generate_random_prompt(self, name):
|
||||||
|
item = self.page.items.get(name, {})
|
||||||
|
metadata = json.loads(item.get("metadata") or '{}')
|
||||||
|
tags = build_tags(metadata)
|
||||||
|
|
||||||
|
return self.generate_random_prompt_from_tags(tags)
|
||||||
|
|
||||||
|
def generate_random_prompt_from_tags(self, tags):
|
||||||
|
max_count = None
|
||||||
|
res = []
|
||||||
|
for tag, count in tags:
|
||||||
|
if not max_count:
|
||||||
|
max_count = count
|
||||||
|
|
||||||
|
v = random.random() * max_count
|
||||||
|
if count > v:
|
||||||
|
res.append(tag)
|
||||||
|
|
||||||
|
return ", ".join(sorted(res))
|
||||||
|
|
||||||
|
def create_editor(self):
|
||||||
|
self.create_default_editor_elems()
|
||||||
|
|
||||||
|
self.taginfo = gr.HighlightedText(label="Tags")
|
||||||
|
self.edit_activation_text = gr.Text(label='Activation text', info="Will be added to prompt along with Lora")
|
||||||
|
self.slider_preferred_weight = gr.Slider(label='Preferred weight', info="Set to 0 to disable", minimum=0.0, maximum=2.0, step=0.01)
|
||||||
|
|
||||||
|
with gr.Row() as row_random_prompt:
|
||||||
|
with gr.Column(scale=8):
|
||||||
|
random_prompt = gr.Textbox(label='Random prompt', lines=4, max_lines=4, interactive=False)
|
||||||
|
|
||||||
|
with gr.Column(scale=1, min_width=120):
|
||||||
|
generate_random_prompt = gr.Button('Generate').style(full_width=True, size="lg")
|
||||||
|
|
||||||
|
self.edit_notes = gr.TextArea(label='Notes', lines=4)
|
||||||
|
|
||||||
|
generate_random_prompt.click(fn=self.generate_random_prompt, inputs=[self.edit_name_input], outputs=[random_prompt])
|
||||||
|
|
||||||
|
def select_tag(activation_text, evt: gr.SelectData):
|
||||||
|
tag = evt.value[0]
|
||||||
|
|
||||||
|
words = re.split(re_comma, activation_text)
|
||||||
|
if tag in words:
|
||||||
|
words = [x for x in words if x != tag and x.strip()]
|
||||||
|
return ", ".join(words)
|
||||||
|
|
||||||
|
return activation_text + ", " + tag if activation_text else tag
|
||||||
|
|
||||||
|
self.taginfo.select(fn=select_tag, inputs=[self.edit_activation_text], outputs=[self.edit_activation_text], show_progress=False)
|
||||||
|
|
||||||
|
self.create_default_buttons()
|
||||||
|
|
||||||
|
viewed_components = [
|
||||||
|
self.edit_name,
|
||||||
|
self.edit_description,
|
||||||
|
self.html_filedata,
|
||||||
|
self.html_preview,
|
||||||
|
self.taginfo,
|
||||||
|
self.edit_activation_text,
|
||||||
|
self.slider_preferred_weight,
|
||||||
|
self.edit_notes,
|
||||||
|
row_random_prompt,
|
||||||
|
random_prompt,
|
||||||
|
]
|
||||||
|
|
||||||
|
self.button_edit\
|
||||||
|
.click(fn=self.put_values_into_components, inputs=[self.edit_name_input], outputs=viewed_components)\
|
||||||
|
.then(fn=lambda: gr.update(visible=True), inputs=[], outputs=[self.box])
|
||||||
|
|
||||||
|
edited_components = [
|
||||||
|
self.edit_description,
|
||||||
|
self.edit_activation_text,
|
||||||
|
self.slider_preferred_weight,
|
||||||
|
self.edit_notes,
|
||||||
|
]
|
||||||
|
|
||||||
|
self.button_save\
|
||||||
|
.click(fn=self.save_lora_user_metadata, inputs=[self.edit_name_input, *edited_components], outputs=[]) \
|
||||||
|
.then(fn=None, _js="extraNetworksReloadAll")
|
@ -3,6 +3,7 @@ import os
|
|||||||
import lora
|
import lora
|
||||||
|
|
||||||
from modules import shared, ui_extra_networks
|
from modules import shared, ui_extra_networks
|
||||||
|
from ui_edit_user_metadata import LoraUserMetadataEditor
|
||||||
|
|
||||||
|
|
||||||
class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
|
class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
|
||||||
@ -18,19 +19,29 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
|
|||||||
|
|
||||||
alias = lora_on_disk.get_alias()
|
alias = lora_on_disk.get_alias()
|
||||||
|
|
||||||
yield {
|
item = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"filename": lora_on_disk.filename,
|
"filename": lora_on_disk.filename,
|
||||||
"preview": self.find_preview(path),
|
"preview": self.find_preview(path),
|
||||||
"description": self.find_description(path),
|
"description": self.find_description(path),
|
||||||
"search_term": self.search_terms_from_path(lora_on_disk.filename),
|
"search_term": self.search_terms_from_path(lora_on_disk.filename),
|
||||||
"prompt": json.dumps(f"<lora:{alias}:") + " + opts.extra_networks_default_multiplier + " + json.dumps(">"),
|
|
||||||
"local_preview": f"{path}.{shared.opts.samples_format}",
|
"local_preview": f"{path}.{shared.opts.samples_format}",
|
||||||
"metadata": json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None,
|
"metadata": json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None,
|
||||||
"sort_keys": {'default': index, **self.get_sort_keys(lora_on_disk.filename)},
|
"sort_keys": {'default': index, **self.get_sort_keys(lora_on_disk.filename)},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.read_user_metadata(item)
|
||||||
|
activation_text = item["user_metadata"].get("activation text")
|
||||||
|
preferred_weight = item["user_metadata"].get("preferred weight", 0.0)
|
||||||
|
item["prompt"] = json.dumps(f"<lora:{alias}:") + " + " + (str(preferred_weight) if preferred_weight else "opts.extra_networks_default_multiplier") + " + " + json.dumps(">")
|
||||||
|
|
||||||
|
if activation_text:
|
||||||
|
item["prompt"] += " + " + json.dumps(" " + activation_text)
|
||||||
|
|
||||||
|
yield item
|
||||||
|
|
||||||
def allowed_directories_for_previews(self):
|
def allowed_directories_for_previews(self):
|
||||||
return [shared.cmd_opts.lora_dir]
|
return [shared.cmd_opts.lora_dir]
|
||||||
|
|
||||||
|
def create_user_metadata_editor(self, ui, tabname):
|
||||||
|
return LoraUserMetadataEditor(ui, tabname, self)
|
||||||
|
@ -113,7 +113,7 @@ function setupExtraNetworks() {
|
|||||||
|
|
||||||
onUiLoaded(setupExtraNetworks);
|
onUiLoaded(setupExtraNetworks);
|
||||||
|
|
||||||
var re_extranet = /<([^:]+:[^:]+):[\d.]+>/;
|
var re_extranet = /<([^:]+:[^:]+):[\d.]+>(.*)/;
|
||||||
var re_extranet_g = /\s+<([^:]+:[^:]+):[\d.]+>/g;
|
var re_extranet_g = /\s+<([^:]+:[^:]+):[\d.]+>/g;
|
||||||
|
|
||||||
function tryToRemoveExtraNetworkFromPrompt(textarea, text) {
|
function tryToRemoveExtraNetworkFromPrompt(textarea, text) {
|
||||||
@ -121,15 +121,22 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text) {
|
|||||||
var replaced = false;
|
var replaced = false;
|
||||||
var newTextareaText;
|
var newTextareaText;
|
||||||
if (m) {
|
if (m) {
|
||||||
|
var extraTextAfterNet = m[2];
|
||||||
var partToSearch = m[1];
|
var partToSearch = m[1];
|
||||||
newTextareaText = textarea.value.replaceAll(re_extranet_g, function(found) {
|
var foundAtPosition = -1;
|
||||||
|
newTextareaText = textarea.value.replaceAll(re_extranet_g, function(found, net, pos) {
|
||||||
m = found.match(re_extranet);
|
m = found.match(re_extranet);
|
||||||
if (m[1] == partToSearch) {
|
if (m[1] == partToSearch) {
|
||||||
replaced = true;
|
replaced = true;
|
||||||
|
foundAtPosition = pos;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (foundAtPosition >= 0 && newTextareaText.substr(foundAtPosition, extraTextAfterNet.length) == extraTextAfterNet) {
|
||||||
|
newTextareaText = newTextareaText.substr(0, foundAtPosition) + newTextareaText.substr(foundAtPosition + extraTextAfterNet.length);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newTextareaText = textarea.value.replaceAll(new RegExp(text, "g"), function(found) {
|
newTextareaText = textarea.value.replaceAll(new RegExp(text, "g"), function(found) {
|
||||||
if (found == text) {
|
if (found == text) {
|
||||||
@ -288,3 +295,10 @@ function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) {
|
|||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extraNetworksReloadAll() {
|
||||||
|
closePopup();
|
||||||
|
|
||||||
|
gradioApp().getElementById('txt2img_extra_refresh').click();
|
||||||
|
gradioApp().getElementById('img2img_extra_refresh').click();
|
||||||
|
}
|
||||||
|
@ -52,7 +52,7 @@ class UserMetadataEditor:
|
|||||||
|
|
||||||
def create_default_buttons(self):
|
def create_default_buttons(self):
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row(elem_classes="edit-user-metadata-buttons"):
|
||||||
self.button_cancel = gr.Button('Cancel')
|
self.button_cancel = gr.Button('Cancel')
|
||||||
self.button_replace_preview = gr.Button('Replace preview', variant='primary')
|
self.button_replace_preview = gr.Button('Replace preview', variant='primary')
|
||||||
self.button_save = gr.Button('Save', variant='primary')
|
self.button_save = gr.Button('Save', variant='primary')
|
||||||
@ -88,8 +88,7 @@ class UserMetadataEditor:
|
|||||||
stats = os.stat(filename)
|
stats = os.stat(filename)
|
||||||
params = [
|
params = [
|
||||||
('File size: ', sysinfo.pretty_bytes(stats.st_size)),
|
('File size: ', sysinfo.pretty_bytes(stats.st_size)),
|
||||||
('Created: ', datetime.datetime.fromtimestamp(stats.st_ctime).strftime('%Y-%m-%d %H:%M')),
|
('Modified: ', datetime.datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M')),
|
||||||
('Last modified: ', datetime.datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M')),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return params
|
return params
|
||||||
@ -100,7 +99,12 @@ class UserMetadataEditor:
|
|||||||
def put_values_into_components(self, name):
|
def put_values_into_components(self, name):
|
||||||
user_metadata = self.get_user_metadata(name)
|
user_metadata = self.get_user_metadata(name)
|
||||||
|
|
||||||
|
try:
|
||||||
params = self.get_metadata_table(name)
|
params = self.get_metadata_table(name)
|
||||||
|
except Exception as e:
|
||||||
|
errors.display(e, f"reading metadata info for {name}")
|
||||||
|
params = []
|
||||||
|
|
||||||
table = '<table class="file-metadata">' + "".join(f"<tr><th>{name}</th><td>{value}</td></tr>" for name, value in params) + '</table>'
|
table = '<table class="file-metadata">' + "".join(f"<tr><th>{name}</th><td>{value}</td></tr>" for name, value in params) + '</table>'
|
||||||
|
|
||||||
return html.escape(name), user_metadata.get('description', ''), table, self.get_card_html(name)
|
return html.escape(name), user_metadata.get('description', ''), table, self.get_card_html(name)
|
||||||
@ -128,7 +132,9 @@ class UserMetadataEditor:
|
|||||||
.click(fn=self.put_values_into_components, inputs=[self.edit_name_input], outputs=[self.edit_name, self.edit_description, self.html_filedata, self.html_preview])\
|
.click(fn=self.put_values_into_components, inputs=[self.edit_name_input], outputs=[self.edit_name, self.edit_description, self.html_filedata, self.html_preview])\
|
||||||
.then(fn=lambda: gr.update(visible=True), inputs=[], outputs=[self.box])
|
.then(fn=lambda: gr.update(visible=True), inputs=[], outputs=[self.box])
|
||||||
|
|
||||||
self.button_save.click(fn=self.save_user_metadata, inputs=[self.edit_name_input, self.edit_description], outputs=[]).then(fn=None, _js="closePopup")
|
self.button_save\
|
||||||
|
.click(fn=self.save_user_metadata, inputs=[self.edit_name_input, self.edit_description], outputs=[])\
|
||||||
|
.then(fn=None, _js="extraNetworksReloadAll")
|
||||||
|
|
||||||
def create_ui(self):
|
def create_ui(self):
|
||||||
with gr.Box(visible=False, elem_id=self.id_part, elem_classes="edit-user-metadata") as box:
|
with gr.Box(visible=False, elem_id=self.id_part, elem_classes="edit-user-metadata") as box:
|
||||||
@ -142,7 +148,7 @@ class UserMetadataEditor:
|
|||||||
def save_preview(self, index, gallery, name):
|
def save_preview(self, index, gallery, name):
|
||||||
if len(gallery) == 0:
|
if len(gallery) == 0:
|
||||||
print("There is no image in gallery to save as a preview.")
|
print("There is no image in gallery to save as a preview.")
|
||||||
return [self.get_card_html(name)] + [page.create_html(self.ui.tabname) for page in self.ui.stored_extra_pages]
|
return [self.get_card_html(name)] + self.regenerate_ui_pages()
|
||||||
|
|
||||||
item = self.page.items.get(name, {})
|
item = self.page.items.get(name, {})
|
||||||
|
|
||||||
@ -156,7 +162,10 @@ class UserMetadataEditor:
|
|||||||
|
|
||||||
images.save_image_with_geninfo(image, geninfo, item["local_preview"])
|
images.save_image_with_geninfo(image, geninfo, item["local_preview"])
|
||||||
|
|
||||||
return [self.get_card_html(name)] + [page.create_html(self.tabname) for page in self.ui.stored_extra_pages]
|
return [self.get_card_html(name)] + self.regenerate_ui_pages()
|
||||||
|
|
||||||
|
def regenerate_ui_pages(self):
|
||||||
|
return [page.create_html(self.tabname) for page in self.ui.stored_extra_pages]
|
||||||
|
|
||||||
def setup_ui(self, gallery):
|
def setup_ui(self, gallery):
|
||||||
self.button_replace_preview.click(
|
self.button_replace_preview.click(
|
||||||
|
@ -1004,7 +1004,7 @@ footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.block.gradio-box.edit-user-metadata {
|
div.block.gradio-box.edit-user-metadata {
|
||||||
min-width: 56em;
|
width: 56em;
|
||||||
background: var(--body-background-fill);
|
background: var(--body-background-fill);
|
||||||
padding: 2em !important;
|
padding: 2em !important;
|
||||||
}
|
}
|
||||||
@ -1021,3 +1021,10 @@ div.block.gradio-box.edit-user-metadata {
|
|||||||
.edit-user-metadata .wrap.translucent{
|
.edit-user-metadata .wrap.translucent{
|
||||||
background: var(--body-background-fill);
|
background: var(--body-background-fill);
|
||||||
}
|
}
|
||||||
|
.edit-user-metadata .gradio-highlightedtext span{
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-user-metadata-buttons{
|
||||||
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user