From f38a827372718373b502b61bc4690d04aa088c30 Mon Sep 17 00:00:00 2001 From: Immac Date: Tue, 23 Sep 2025 22:02:28 -0600 Subject: [PATCH 1/6] Implement debouncing for update calls in TextAreaAutoComplete --- web/js/common/autocomplete.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index ac5fb1d..f70c38b 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -390,10 +390,19 @@ export class TextAreaAutoComplete { this.dropdown = $el("div.pysssss-autocomplete"); this.overrideWords = words; this.overrideSeparator = separator; + this.debouncedUpdate = this.#debounce(this.#update.bind(this), 150); this.#setup(); } + #debounce(func, delay) { + let timeout; + return (...args) => { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), delay); + }; + } + #setup() { this.el.addEventListener("keydown", this.#keyDown.bind(this)); this.el.addEventListener("keypress", this.#keyPress.bind(this)); @@ -457,7 +466,7 @@ export class TextAreaAutoComplete { } if (!e.defaultPrevented) { - this.#update(); + this.debouncedUpdate(); } } @@ -475,7 +484,7 @@ export class TextAreaAutoComplete { return; } if (!e.defaultPrevented) { - this.#update(); + this.debouncedUpdate(); } } From b2b658a4b11d1f877ddf98eeb11ed914daf1f3bc Mon Sep 17 00:00:00 2001 From: Immac Date: Thu, 25 Sep 2025 13:43:09 -0600 Subject: [PATCH 2/6] Add debounce configuration for TextAreaAutoComplete and adjust default value --- web/js/autocompleter.js | 38 +++++++++++++++++++++++++++++------ web/js/common/autocomplete.js | 2 +- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index d0fb2ec..fef9277 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -489,6 +489,31 @@ app.registerExtension({ }), ] ), + $el( + "label", + { + textContent: "Debounce in ms: ", + style: { + display: "block", + }, + }, + [ + $el("input", { + type: "number", + value: +TextAreaAutoComplete.debounceMs || 150, + min: 0, + step: 10, + style: { + width: "80px" + }, + onchange: (event) => { + const value = Math.max(0, +event.target.value || 0); + TextAreaAutoComplete.debounceMs = value; + localStorage.setItem(id + ".DebounceMs", value); + }, + }), + ] + ), $el("button", { textContent: "Manage Custom Words", onclick: () => { @@ -520,12 +545,13 @@ app.registerExtension({ }, }); - TextAreaAutoComplete.enabled = enabledSetting.value; - TextAreaAutoComplete.replacer = localStorage.getItem(id + ".ReplaceUnderscore") === "true" ? (v) => v.replaceAll("_", " ") : undefined; - TextAreaAutoComplete.insertOnTab = localStorage.getItem(id + ".InsertOnTab") !== "false"; - TextAreaAutoComplete.insertOnEnter = localStorage.getItem(id + ".InsertOnEnter") !== "false"; - TextAreaAutoComplete.lorasEnabled = localStorage.getItem(id + ".ShowLoras") === "true"; - TextAreaAutoComplete.suggestionCount = +localStorage.getItem(id + ".SuggestionCount") || 20; + TextAreaAutoComplete.enabled = enabledSetting.value; + TextAreaAutoComplete.replacer = localStorage.getItem(id + ".ReplaceUnderscore") === "true" ? (v) => v.replaceAll("_", " ") : undefined; + TextAreaAutoComplete.insertOnTab = localStorage.getItem(id + ".InsertOnTab") !== "false"; + TextAreaAutoComplete.insertOnEnter = localStorage.getItem(id + ".InsertOnEnter") !== "false"; + TextAreaAutoComplete.lorasEnabled = localStorage.getItem(id + ".ShowLoras") === "true"; + TextAreaAutoComplete.suggestionCount = +localStorage.getItem(id + ".SuggestionCount") || 20; + TextAreaAutoComplete.debounceMs = +localStorage.getItem(id + ".DebounceMs") || 75; }, setup() { async function addEmbeddings() { diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index f70c38b..6dd8b2b 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -390,7 +390,7 @@ export class TextAreaAutoComplete { this.dropdown = $el("div.pysssss-autocomplete"); this.overrideWords = words; this.overrideSeparator = separator; - this.debouncedUpdate = this.#debounce(this.#update.bind(this), 150); + this.debouncedUpdate = this.#debounce(this.#update.bind(this), 75); this.#setup(); } From 509cb0dae8992ed3c849d0a9f05da28e30cac724 Mon Sep 17 00:00:00 2001 From: Miguel C Date: Thu, 25 Sep 2025 13:51:27 -0600 Subject: [PATCH 3/6] Change default debounceMs value from 150 to 75 --- web/js/autocompleter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index fef9277..45bc2c2 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -500,7 +500,7 @@ app.registerExtension({ [ $el("input", { type: "number", - value: +TextAreaAutoComplete.debounceMs || 150, + value: +TextAreaAutoComplete.debounceMs || 75, min: 0, step: 10, style: { From 9c3a184f7a907ca354eb68a8f4f80d8f3f464a97 Mon Sep 17 00:00:00 2001 From: Immac Date: Wed, 1 Oct 2025 16:09:27 -0600 Subject: [PATCH 4/6] Refactor localStorage keys for AutoCompleter to improve consistency and maintainability --- web/js/autocompleter.js | 43 ++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index 45bc2c2..17085e5 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -270,10 +270,13 @@ class CustomWordsDialog extends ComfyDialog { } } -const id = "pysssss.AutoCompleter"; +// Base key used for all localStorage persisted options for backward compatibility +const baseKey = "pysssss.AutoCompleter"; +// Distinct setting id for the enabled toggle to avoid clashes with other dynamic settings systems +const settingId = baseKey + ".Enabled"; app.registerExtension({ - name: id, + name: baseKey, init() { const STRING = ComfyWidgets.STRING; const SKIP_WIDGETS = new Set(["ttN xyPlot.x_values", "ttN xyPlot.y_values"]); @@ -316,16 +319,16 @@ app.registerExtension({ return r; }; - TextAreaAutoComplete.globalSeparator = localStorage.getItem(id + ".AutoSeparate") ?? ", "; + TextAreaAutoComplete.globalSeparator = localStorage.getItem(baseKey + ".AutoSeparate") ?? ", "; const enabledSetting = app.ui.settings.addSetting({ - id, + id: settingId, name: "🐍 Text Autocomplete", defaultValue: true, type: (name, setter, value) => { return $el("tr", [ $el("td", [ $el("label", { - for: id.replaceAll(".", "-"), + for: settingId.replaceAll(".", "-"), textContent: name, }), ]), @@ -340,7 +343,7 @@ app.registerExtension({ }, [ $el("input", { - id: id.replaceAll(".", "-"), + id: settingId.replaceAll(".", "-"), type: "checkbox", checked: value, onchange: (event) => { @@ -368,7 +371,7 @@ app.registerExtension({ const checked = !!event.target.checked; TextAreaAutoComplete.lorasEnabled = checked; toggleLoras(); - localStorage.setItem(id + ".ShowLoras", TextAreaAutoComplete.lorasEnabled); + localStorage.setItem(baseKey + ".ShowLoras", TextAreaAutoComplete.lorasEnabled); }, }), ] @@ -388,7 +391,7 @@ app.registerExtension({ onchange: (event) => { const checked = !!event.target.checked; TextAreaAutoComplete.globalSeparator = checked ? ", " : ""; - localStorage.setItem(id + ".AutoSeparate", TextAreaAutoComplete.globalSeparator); + localStorage.setItem(baseKey + ".AutoSeparate", TextAreaAutoComplete.globalSeparator); }, }), ] @@ -408,7 +411,7 @@ app.registerExtension({ onchange: (event) => { const checked = !!event.target.checked; TextAreaAutoComplete.replacer = checked ? (v) => v.replaceAll("_", " ") : undefined; - localStorage.setItem(id + ".ReplaceUnderscore", checked); + localStorage.setItem(baseKey + ".ReplaceUnderscore", checked); }, }), ] @@ -438,7 +441,7 @@ app.registerExtension({ onchange: (event) => { const checked = !!event.target.checked; TextAreaAutoComplete.insertOnTab = checked; - localStorage.setItem(id + ".InsertOnTab", checked); + localStorage.setItem(baseKey + ".InsertOnTab", checked); }, }), ] @@ -459,7 +462,7 @@ app.registerExtension({ onchange: (event) => { const checked = !!event.target.checked; TextAreaAutoComplete.insertOnEnter = checked; - localStorage.setItem(id + ".InsertOnEnter", checked); + localStorage.setItem(baseKey + ".InsertOnEnter", checked); }, }), ] @@ -484,7 +487,7 @@ app.registerExtension({ onchange: (event) => { const value = +event.target.value; TextAreaAutoComplete.suggestionCount = value;; - localStorage.setItem(id + ".SuggestionCount", TextAreaAutoComplete.suggestionCount); + localStorage.setItem(baseKey + ".SuggestionCount", TextAreaAutoComplete.suggestionCount); }, }), ] @@ -509,7 +512,7 @@ app.registerExtension({ onchange: (event) => { const value = Math.max(0, +event.target.value || 0); TextAreaAutoComplete.debounceMs = value; - localStorage.setItem(id + ".DebounceMs", value); + localStorage.setItem(baseKey + ".DebounceMs", value); }, }), ] @@ -545,13 +548,13 @@ app.registerExtension({ }, }); - TextAreaAutoComplete.enabled = enabledSetting.value; - TextAreaAutoComplete.replacer = localStorage.getItem(id + ".ReplaceUnderscore") === "true" ? (v) => v.replaceAll("_", " ") : undefined; - TextAreaAutoComplete.insertOnTab = localStorage.getItem(id + ".InsertOnTab") !== "false"; - TextAreaAutoComplete.insertOnEnter = localStorage.getItem(id + ".InsertOnEnter") !== "false"; - TextAreaAutoComplete.lorasEnabled = localStorage.getItem(id + ".ShowLoras") === "true"; - TextAreaAutoComplete.suggestionCount = +localStorage.getItem(id + ".SuggestionCount") || 20; - TextAreaAutoComplete.debounceMs = +localStorage.getItem(id + ".DebounceMs") || 75; + TextAreaAutoComplete.enabled = enabledSetting?.value ?? true; + TextAreaAutoComplete.replacer = localStorage.getItem(baseKey + ".ReplaceUnderscore") === "true" ? (v) => v.replaceAll("_", " ") : undefined; + TextAreaAutoComplete.insertOnTab = localStorage.getItem(baseKey + ".InsertOnTab") !== "false"; + TextAreaAutoComplete.insertOnEnter = localStorage.getItem(baseKey + ".InsertOnEnter") !== "false"; + TextAreaAutoComplete.lorasEnabled = localStorage.getItem(baseKey + ".ShowLoras") === "true"; + TextAreaAutoComplete.suggestionCount = +localStorage.getItem(baseKey + ".SuggestionCount") || 20; + TextAreaAutoComplete.debounceMs = +localStorage.getItem(baseKey + ".DebounceMs") || 75; }, setup() { async function addEmbeddings() { From 14d9de932d96e8d9e629ae804390c10b1131ff83 Mon Sep 17 00:00:00 2001 From: Immac Date: Mon, 9 Mar 2026 10:50:14 -0600 Subject: [PATCH 5/6] Fix TextAreaAutoComplete initialization to handle undefined input element --- .gitignore | 3 ++- web/js/autocompleter.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8d303a4..7514dab 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ __pycache__ pysssss.json user/autocomplete.txt web/js/assets/favicon.user.ico -web/js/assets/favicon-active.user.ico \ No newline at end of file +web/js/assets/favicon-active.user.ico +.directory diff --git a/web/js/autocompleter.js b/web/js/autocompleter.js index 17085e5..6644343 100644 --- a/web/js/autocompleter.js +++ b/web/js/autocompleter.js @@ -313,7 +313,7 @@ app.registerExtension({ } } - new TextAreaAutoComplete(r.widget.inputEl, words, separator); + new TextAreaAutoComplete(r.widget.inputEl ?? r.widget.element, words, separator); } return r; From cebfa3cbc8ad7fe352b13fdc5a5c36082ba188e3 Mon Sep 17 00:00:00 2001 From: Immac Date: Mon, 9 Mar 2026 11:03:54 -0600 Subject: [PATCH 6/6] Fix input element handling in draw function for better compatibility --- web/js/workflowImage.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/js/workflowImage.js b/web/js/workflowImage.js index 37c3ad0..ff19c6a 100644 --- a/web/js/workflowImage.js +++ b/web/js/workflowImage.js @@ -544,7 +544,8 @@ app.registerExtension({ const draw = w.widget.draw; w.widget.draw = function (ctx) { draw.apply(this, arguments); - if (this.inputEl.hidden) return; + const inputEl = this.inputEl ?? this.element; + if (!inputEl || inputEl.hidden) return; if (getDrawTextConfig) { const config = getDrawTextConfig(ctx, this); @@ -554,10 +555,10 @@ app.registerExtension({ ctx.resetTransform(); } - const style = document.defaultView.getComputedStyle(this.inputEl, null); + const style = document.defaultView.getComputedStyle(inputEl, null); const x = config.x; const y = config.y; - const domWrapper = this.inputEl.closest(".dom-widget") ?? widget.inputEl; + const domWrapper = inputEl.closest(".dom-widget") ?? inputEl; let w = parseInt(domWrapper.style.width); if (w === 0) { w = this.node.size[0] - 20; @@ -570,7 +571,7 @@ app.registerExtension({ ctx.font = style.getPropertyValue("font"); const line = t.d * 12; - const split = this.inputEl.value.split("\n"); + const split = inputEl.value.split("\n"); let start = y; for (const l of split) { start += line;