From 75892eb461b7e6968a98d092f791dca820e3da57 Mon Sep 17 00:00:00 2001 From: qrtal <90406181+qrtal@users.noreply.github.com> Date: Mon, 8 Jan 2024 01:27:02 -0500 Subject: [PATCH 1/3] added autocomplete support for loras --- py/autocomplete.py | 7 +++++++ py/model_info.py | 4 ++-- web/js/autocompleter.js | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/py/autocomplete.py b/py/autocomplete.py index abf838d..d3a879e 100644 --- a/py/autocomplete.py +++ b/py/autocomplete.py @@ -1,6 +1,7 @@ from server import PromptServer from aiohttp import web import os +import folder_paths dir = os.path.abspath(os.path.join(__file__, "../../user")) if not os.path.exists(dir): @@ -20,3 +21,9 @@ async def update_autocomplete(request): with open(file, "w", encoding="utf-8") as f: f.write(await request.text()) return web.Response(status=200) + + +@PromptServer.instance.routes.get("/pysssss/loras") +async def get_loras(request): + loras = folder_paths.get_filename_list("loras") + return web.json_response(list(map(lambda a: os.path.splitext(a)[0], loras))) \ No newline at end of file diff --git a/py/model_info.py b/py/model_info.py index b9b4daf..b360baf 100644 --- a/py/model_info.py +++ b/py/model_info.py @@ -31,7 +31,7 @@ async def save_notes(request): name = name[pos+1:] file_path = None - if type == "embeddings": + if type == "embeddings" or type == "loras": name = name.lower() files = folder_paths.get_filename_list(type) for f in files: @@ -67,7 +67,7 @@ async def load_metadata(request): name = name[pos+1:] file_path = None - if type == "embeddings": + if type == "embeddings" or type == "loras": name = name.lower() files = folder_paths.get_filename_list(type) for f in files: diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index 99af50f..47ab948 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -143,6 +143,23 @@ class EmbeddingInfoDialog extends ModelInfoDialog { } } +class LoraInfoDialog extends ModelInfoDialog { + async addInfo() { + super.addInfo(); + const info = await this.addCivitaiInfo(); + if (info) { + $el("div", { + parent: this.content, + innerHTML: info.description, + style: { + maxHeight: "250px", + overflow: "auto", + }, + }); + } + } +} + class CustomWordsDialog extends ComfyDialog { async show() { const text = await getCustomWords(); @@ -268,7 +285,25 @@ app.registerExtension({ TextAreaAutoComplete.updateWords("pysssss.embeddings", words); } - Promise.all([addEmbeddings(), addCustomWords()]); + async function addLoras() { + const loras = await api + .fetchApi("/pysssss/loras", { cache: "no-store" }) + .then(res => res.json()); + const words = {}; + words["lora:"] = { text: "lora:" }; + + for (const lora of loras) { + const v = ``; + words[v] = { + text: v, + info: () => new LoraInfoDialog(lora).show("loras", lora), + }; + } + + TextAreaAutoComplete.updateWords("pysssss.loras", words); + } + + Promise.all([addEmbeddings(), addLoras(), addCustomWords()]); const STRING = ComfyWidgets.STRING; const SKIP_WIDGETS = new Set(["ttN xyPlot.x_values", "ttN xyPlot.y_values"]); From 256aa16adee2bc73aaee42e18032aa2c830cbfae Mon Sep 17 00:00:00 2001 From: qrtal <90406181+qrtal@users.noreply.github.com> Date: Mon, 8 Jan 2024 03:52:41 -0500 Subject: [PATCH 2/3] export and use already defined LoraInfoDialog --- web/js/autocompleter.js | 18 +----------------- web/js/modelInfo.js | 2 +- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index 47ab948..5a54dc8 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -4,6 +4,7 @@ import { api } from "../../../../scripts/api.js"; import { $el, ComfyDialog } from "../../../../scripts/ui.js"; import { TextAreaAutoComplete } from "./common/autocomplete.js"; import { ModelInfoDialog } from "./common/modelInfoDialog.js"; +import { LoraInfoDialog } from "./modelInfo.js"; function parseCSV(csvText) { const rows = []; @@ -143,23 +144,6 @@ class EmbeddingInfoDialog extends ModelInfoDialog { } } -class LoraInfoDialog extends ModelInfoDialog { - async addInfo() { - super.addInfo(); - const info = await this.addCivitaiInfo(); - if (info) { - $el("div", { - parent: this.content, - innerHTML: info.description, - style: { - maxHeight: "250px", - overflow: "auto", - }, - }); - } - } -} - class CustomWordsDialog extends ComfyDialog { async show() { const text = await getCustomWords(); diff --git a/web/js/modelInfo.js b/web/js/modelInfo.js index d23f024..d4c39ce 100644 --- a/web/js/modelInfo.js +++ b/web/js/modelInfo.js @@ -4,7 +4,7 @@ import { ModelInfoDialog } from "./common/modelInfoDialog.js"; const MAX_TAGS = 500; -class LoraInfoDialog extends ModelInfoDialog { +export class LoraInfoDialog extends ModelInfoDialog { getTagFrequency() { if (!this.metadata.ss_tag_frequency) return []; From b40b6bc15362dc196ec8d994981fb957434c1e68 Mon Sep 17 00:00:00 2001 From: qrtal <90406181+qrtal@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:02:23 -0500 Subject: [PATCH 3/3] make lora autocompletion opt-in --- py/autocomplete.py | 2 +- web/js/autocompleter.js | 42 ++++++++++++++++++++++++++++++++++- web/js/common/autocomplete.js | 3 +++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/py/autocomplete.py b/py/autocomplete.py index d3a879e..8ac6a05 100644 --- a/py/autocomplete.py +++ b/py/autocomplete.py @@ -26,4 +26,4 @@ async def update_autocomplete(request): @PromptServer.instance.routes.get("/pysssss/loras") async def get_loras(request): loras = folder_paths.get_filename_list("loras") - return web.json_response(list(map(lambda a: os.path.splitext(a)[0], loras))) \ No newline at end of file + return web.json_response(list(map(lambda a: os.path.splitext(a)[0], loras))) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index 5a54dc8..8f473e8 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -127,6 +127,13 @@ async function addCustomWords(text) { } } +function toggleLoras() { + [TextAreaAutoComplete.globalWords, TextAreaAutoComplete.globalWordsExclLoras] = [ + TextAreaAutoComplete.globalWordsExclLoras, + TextAreaAutoComplete.globalWords, + ]; +} + class EmbeddingInfoDialog extends ModelInfoDialog { async addInfo() { super.addInfo(); @@ -287,7 +294,20 @@ app.registerExtension({ TextAreaAutoComplete.updateWords("pysssss.loras", words); } - Promise.all([addEmbeddings(), addLoras(), addCustomWords()]); + // store global words with/without loras + Promise.all([addEmbeddings(), addCustomWords()]) + .then(() => { + TextAreaAutoComplete.globalWordsExclLoras = Object.assign( + {}, + TextAreaAutoComplete.globalWords + ); + }) + .then(addLoras) + .then(() => { + if (!TextAreaAutoComplete.lorasEnabled) { + toggleLoras(); // off by default + } + }); const STRING = ComfyWidgets.STRING; const SKIP_WIDGETS = new Set(["ttN xyPlot.x_values", "ttN xyPlot.y_values"]); @@ -365,6 +385,26 @@ app.registerExtension({ }), ] ), + $el( + "label", + { + textContent: "Loras enabled ", + style: { + display: "block", + }, + }, + [ + $el("input", { + type: "checkbox", + checked: !!TextAreaAutoComplete.lorasEnabled, + onchange: (event) => { + const checked = !!event.target.checked; + TextAreaAutoComplete.lorasEnabled = checked; + toggleLoras(); + }, + }), + ] + ), $el( "label", { diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index 862133b..a347509 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -315,6 +315,7 @@ export class TextAreaAutoComplete { static globalSeparator = ""; static enabled = true; static replacer = undefined; + static lorasEnabled = false; /** @type {Record>} */ static groups = {}; @@ -322,6 +323,8 @@ export class TextAreaAutoComplete { static globalGroups = new Set(); /** @type {Record} */ static globalWords = {}; + /** @type {Record} */ + static globalWordsExclLoras = {}; /** @type {HTMLTextAreaElement} */ el;