From f38a827372718373b502b61bc4690d04aa088c30 Mon Sep 17 00:00:00 2001 From: Immac Date: Tue, 23 Sep 2025 22:02:28 -0600 Subject: [PATCH 1/4] 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/4] 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/4] 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/4] 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() {