mirror of
https://github.com/vladmandic/automatic
synced 2026-09-20 01:31:13 +02:00
feat(autocomplete): apply keep-underscores setting to insertion
Rename `autocomplete_replace_underscores` to `autocomplete_keep_underscores` (default false) so both autocomplete toggles read as "keep" semantics. `insertTag` now honors the setting for both tag and artist modes, matching the dropdown display. Embedding insertion always preserves underscores because the names are filesystem identifiers. - ui_definitions: rename option, flip default, update label - scripts/autocomplete: rename handler, checkbox, and config-bridge keys - javascript/autocomplete: invert render polarity; insertTag takes a `kind` parameter; drop the artist-mode hardcoded underscore replacement so the setting controls tags and artists uniformly Closes #4827
This commit is contained in:
+13
-11
@@ -358,7 +358,7 @@ function insertExtraNetwork(textarea, item, kind) {
|
||||
}
|
||||
|
||||
/** Insert a tag at the current word position, replacing the typed prefix. */
|
||||
function insertTag(textarea, tagName) {
|
||||
function insertTag(textarea, tagName, kind = 'tag') {
|
||||
const info = getCurrentWord(textarea);
|
||||
if (!info || (info.mode !== 'tag' && info.mode !== 'artist')) return;
|
||||
const { value } = textarea;
|
||||
@@ -371,13 +371,13 @@ function insertTag(textarea, tagName) {
|
||||
const prefix = needsSepBefore ? `${sep} ` : '';
|
||||
let suffix = `${sep} `;
|
||||
if (after.length > 0 && after.trimStart().startsWith(',')) suffix = ' ';
|
||||
// Artist mode: optionally keep the `@` prefix (Anima syntax); always convert underscores to spaces
|
||||
// since Anima requires space-separated artist names. The `@` is consumed for non-Anima models.
|
||||
// Embedding names are file-system identifiers, so underscores must be preserved regardless of the user setting.
|
||||
// Tags and artists honor `autocomplete_keep_underscores`; default is to swap `_` for space.
|
||||
const keepUnderscores = window.opts?.autocomplete_keep_underscores ?? false;
|
||||
let body = tagName;
|
||||
if (info.mode === 'artist') {
|
||||
body = body.replace(/_/g, ' ');
|
||||
if (window.opts?.autocomplete_at_prefix_artist) body = `@${body}`;
|
||||
}
|
||||
if (kind !== 'embed' && !keepUnderscores) body = body.replace(/_/g, ' ');
|
||||
// Artist mode optionally keeps the `@` prefix (Anima syntax). The `@` is consumed for non-Anima models.
|
||||
if (info.mode === 'artist' && window.opts?.autocomplete_at_prefix_artist) body = `@${body}`;
|
||||
const insertion = `${prefix}${escapeParensForPrompt(body)}${suffix}`;
|
||||
textarea.value = before.trimEnd() + (before.trimEnd().length > 0 ? ' ' : '') + insertion + after.trimStart();
|
||||
// Position cursor after the inserted tag + separator
|
||||
@@ -447,7 +447,7 @@ const dropdown = {
|
||||
},
|
||||
|
||||
render() {
|
||||
const replaceUnderscores = window.opts?.autocomplete_replace_underscores ?? true;
|
||||
const keepUnderscores = window.opts?.autocomplete_keep_underscores ?? false;
|
||||
const queryNorm = this.query.toLowerCase().replace(/ /g, '_');
|
||||
this.listEl.replaceChildren();
|
||||
this.results.forEach((tag, i) => {
|
||||
@@ -462,7 +462,9 @@ const dropdown = {
|
||||
dot.title = kind === 'tag' ? (engine.categoryNames[tag.category] || '') : kind;
|
||||
const name = document.createElement('span');
|
||||
name.className = 'autocomplete-tag';
|
||||
const tagText = replaceUnderscores ? tag.display.replace(/_/g, ' ') : tag.display;
|
||||
// Embeddings are file-name identifiers, so they always render as-is to match how they get inserted.
|
||||
const swapForKind = kind !== 'embed';
|
||||
const tagText = (swapForKind && !keepUnderscores) ? tag.display.replace(/_/g, ' ') : tag.display;
|
||||
const canonicalMatch = tag.name.indexOf(queryNorm);
|
||||
if (canonicalMatch >= 0 && queryNorm.length > 0) {
|
||||
const mark = document.createElement('mark');
|
||||
@@ -480,7 +482,7 @@ const dropdown = {
|
||||
if (tag.matchedVia === 'alias') annotationTerm = tag.matchedAlias;
|
||||
else if (tag.matchedVia === 'translation') annotationTerm = tag.matchedTerm;
|
||||
if (annotationTerm) {
|
||||
const annotationDisplay = replaceUnderscores ? annotationTerm.replace(/_/g, ' ') : annotationTerm;
|
||||
const annotationDisplay = (swapForKind && !keepUnderscores) ? annotationTerm.replace(/_/g, ' ') : annotationTerm;
|
||||
const annotationLower = annotationTerm.toLowerCase();
|
||||
const annotationMatch = annotationLower.indexOf(queryNorm);
|
||||
const prefix = tag.matchedVia === 'translation' ? ' \u{1F310} ' : ' (';
|
||||
@@ -561,7 +563,7 @@ const dropdown = {
|
||||
insertExtraNetwork(this.textarea, result, result.kind);
|
||||
} else {
|
||||
// 'embed' kind and untagged tag results both go through insertTag (comma-aware, paren-escaped).
|
||||
insertTag(this.textarea, result.display ?? result.name);
|
||||
insertTag(this.textarea, result.display ?? result.name, result.kind);
|
||||
}
|
||||
}
|
||||
this.hide();
|
||||
|
||||
@@ -684,7 +684,7 @@ def create_settings(cmd_opts):
|
||||
"autocomplete_active": OptionInfo(False, "Enable Autocomplete", gr.Checkbox, {"visible": False}),
|
||||
"autocomplete_enabled": OptionInfo([], "Enabled tag autocomplete files", gr.Dropdown, {"multiselect": True, "choices": [], "visible": False}),
|
||||
"autocomplete_min_chars": OptionInfo(3, "Min autocomplete chars", gr.Slider, {"minimum": 2, "maximum": 6, "step": 1, "visible": False}),
|
||||
"autocomplete_replace_underscores": OptionInfo(True, "Replace underscores in autocomplete", gr.Checkbox, {"visible": False}),
|
||||
"autocomplete_keep_underscores": OptionInfo(False, "Keep underscores in autocomplete", gr.Checkbox, {"visible": False}),
|
||||
"autocomplete_append_comma": OptionInfo(True, "Append comma after autocomplete", gr.Checkbox, {"visible": False}),
|
||||
|
||||
# Caption settings (controlled via Caption Tab UI)
|
||||
|
||||
+10
-10
@@ -27,7 +27,7 @@ def get_config_json():
|
||||
"autocomplete_active": bool(shared.opts.data.get('autocomplete_active', False)),
|
||||
"autocomplete_enabled": enabled,
|
||||
"autocomplete_min_chars": shared.opts.data.get('autocomplete_min_chars', 3),
|
||||
"autocomplete_replace_underscores": shared.opts.data.get('autocomplete_replace_underscores', True),
|
||||
"autocomplete_keep_underscores": shared.opts.data.get('autocomplete_keep_underscores', False),
|
||||
"autocomplete_append_comma": shared.opts.data.get('autocomplete_append_comma', True),
|
||||
"autocomplete_at_prefix_artist": shared.opts.data.get('autocomplete_at_prefix_artist', False),
|
||||
"autocomplete_translations": bool(shared.opts.data.get('autocomplete_translations', False)),
|
||||
@@ -52,8 +52,8 @@ def on_min_chars_change(value):
|
||||
return get_config_json()
|
||||
|
||||
|
||||
def on_replace_underscores_change(value):
|
||||
shared.opts.data['autocomplete_replace_underscores'] = bool(value)
|
||||
def on_keep_underscores_change(value):
|
||||
shared.opts.data['autocomplete_keep_underscores'] = bool(value)
|
||||
shared.opts.save(silent=True)
|
||||
return get_config_json()
|
||||
|
||||
@@ -180,10 +180,10 @@ class AutocompleteScript(scripts_manager.Script):
|
||||
refresh_btn = ToolButton(value=symbols.refresh, elem_id=self.elem_id("refresh"))
|
||||
update_btn = ToolButton(value=symbols.save, elem_id=self.elem_id("update"))
|
||||
with gr.Row():
|
||||
replace_underscores = gr.Checkbox(
|
||||
label="Replace underscores",
|
||||
value=shared.opts.data.get('autocomplete_replace_underscores', True),
|
||||
elem_id=self.elem_id("replace_underscores"),
|
||||
keep_underscores = gr.Checkbox(
|
||||
label="Keep underscores",
|
||||
value=shared.opts.data.get('autocomplete_keep_underscores', False),
|
||||
elem_id=self.elem_id("keep_underscores"),
|
||||
)
|
||||
append_comma = gr.Checkbox(
|
||||
label="Comma separator",
|
||||
@@ -217,14 +217,14 @@ class AutocompleteScript(scripts_manager.Script):
|
||||
active_cb.change(fn=on_active_change, inputs=[active_cb], outputs=[config_json, status])
|
||||
enabled_dd.change(fn=on_enabled_change, inputs=[enabled_dd], outputs=[config_json, status])
|
||||
min_chars.change(fn=on_min_chars_change, inputs=[min_chars], outputs=[config_json])
|
||||
replace_underscores.change(fn=on_replace_underscores_change, inputs=[replace_underscores], outputs=[config_json])
|
||||
keep_underscores.change(fn=on_keep_underscores_change, inputs=[keep_underscores], outputs=[config_json])
|
||||
append_comma.change(fn=on_append_comma_change, inputs=[append_comma], outputs=[config_json])
|
||||
at_prefix_artist.change(fn=on_at_prefix_artist_change, inputs=[at_prefix_artist], outputs=[config_json])
|
||||
translations_cb.change(fn=on_translations_change, inputs=[translations_cb], outputs=[config_json])
|
||||
refresh_btn.click(fn=on_refresh, inputs=[], outputs=[enabled_dd, status])
|
||||
update_btn.click(fn=on_update, inputs=[enabled_dd], outputs=[status])
|
||||
|
||||
for comp in [enabled_dd, min_chars, replace_underscores, append_comma, at_prefix_artist, translations_cb, config_json, status]:
|
||||
for comp in [enabled_dd, min_chars, keep_underscores, append_comma, at_prefix_artist, translations_cb, config_json, status]:
|
||||
comp.do_not_save_to_config = True
|
||||
|
||||
return [active_cb, enabled_dd, min_chars, replace_underscores, append_comma, at_prefix_artist, translations_cb, config_json]
|
||||
return [active_cb, enabled_dd, min_chars, keep_underscores, append_comma, at_prefix_artist, translations_cb, config_json]
|
||||
|
||||
Reference in New Issue
Block a user