mirror of
https://github.com/vladmandic/automatic
synced 2026-09-18 16:54:33 +02:00
feat(autocomplete): move settings to always-on script UI
- create scripts/autocomplete.py as always-on script with dictionary management UI (dropdown, refresh, update, settings controls) - move autocomplete settings from Settings > Extra Networks to hidden options, controlled via script UI with JS config bridge - remove early exit in autocomplete.js when no dictionaries enabled, allowing dictionaries enabled via script UI to work without reload - add id-based suffix matching to setHints.js for unique button hints - add locale_en.json entries with tooltips for all autocomplete controls
This commit is contained in:
+33
-10
@@ -457,15 +457,39 @@ const PROMPT_IDS = [
|
||||
'video_prompt', 'video_neg_prompt',
|
||||
];
|
||||
|
||||
// -- Config bridge --
|
||||
|
||||
/** Monkey-patch script config bridge textboxes to push autocomplete config changes to window.opts immediately. */
|
||||
function patchConfigBridge() {
|
||||
const elements = gradioApp().querySelectorAll('[id$="_tag_autocomplete_config_json"]');
|
||||
for (const el of elements) {
|
||||
const textarea = el.querySelector('textarea');
|
||||
if (!textarea || textarea.acBridgePatched) continue;
|
||||
textarea.acBridgePatched = true;
|
||||
const proto = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
|
||||
Object.defineProperty(textarea, 'value', {
|
||||
set(newValue) {
|
||||
const oldValue = proto.get.call(textarea);
|
||||
proto.set.call(textarea, newValue);
|
||||
if (oldValue !== newValue && newValue) {
|
||||
try {
|
||||
const cfg = JSON.parse(newValue);
|
||||
for (const [key, val] of Object.entries(cfg)) window.opts[key] = val;
|
||||
log('autocomplete', 'config updated via bridge');
|
||||
executeCallbacks(optionsChangedCallbacks);
|
||||
} catch { /* ignore parse errors */ }
|
||||
}
|
||||
},
|
||||
get() { return proto.get.call(textarea); },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// -- Initialization --
|
||||
|
||||
async function initAutocomplete() {
|
||||
const enabled = window.opts?.autocomplete_enabled || [];
|
||||
if (!enabled.length) {
|
||||
log('autocomplete', 'no dictionaries enabled');
|
||||
return;
|
||||
}
|
||||
log('autocomplete', `init: ${enabled.join(', ')}`);
|
||||
log('autocomplete', enabled.length ? `init: ${enabled.join(', ')}` : 'init: no dictionaries enabled yet');
|
||||
// Inject styles (CSS files in javascript/ are not auto-loaded)
|
||||
const style = document.createElement('style');
|
||||
style.textContent = [
|
||||
@@ -489,11 +513,7 @@ async function initAutocomplete() {
|
||||
document.head.appendChild(style);
|
||||
dropdown.init();
|
||||
await engine.loadEnabled();
|
||||
if (engine.indices.size === 0) {
|
||||
log('autocomplete', 'no dictionaries loaded');
|
||||
return;
|
||||
}
|
||||
// Attach to all prompt textareas
|
||||
// Attach to all prompt textareas; even if no dictionaries loaded yet, they may be enabled later via script UI
|
||||
let attached = 0;
|
||||
PROMPT_IDS.forEach((id) => {
|
||||
const textarea = gradioApp().querySelector(`#${id} > label > textarea`);
|
||||
@@ -513,4 +533,7 @@ async function initAutocomplete() {
|
||||
await engine.loadEnabled();
|
||||
}
|
||||
});
|
||||
// Watch for config updates from the script UI bridge
|
||||
patchConfigBridge();
|
||||
onAfterUiUpdate(() => patchConfigBridge());
|
||||
}
|
||||
|
||||
+11
-5
@@ -324,8 +324,11 @@ async function setHints() {
|
||||
for (const el of elements) {
|
||||
// localize elements text
|
||||
let found;
|
||||
if (el.dataset.original) found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.dataset.original.toLowerCase().trim());
|
||||
else found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.textContent.toLowerCase().trim());
|
||||
if (el.id) found = localeData.data.find((l) => l.id && (l.id === el.id || el.id.endsWith(l.id))); // prefer id match for disambiguation
|
||||
if (!found) {
|
||||
if (el.dataset.original) found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.dataset.original.toLowerCase().trim());
|
||||
else found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.textContent.toLowerCase().trim());
|
||||
}
|
||||
if (found?.localized?.length > 0) {
|
||||
if (!el.dataset.original) el.dataset.original = el.textContent;
|
||||
replaceTextContent(el, found.localized);
|
||||
@@ -359,9 +362,12 @@ async function applyHintToElement(el) {
|
||||
|| (el.tagName === 'SPAN' && (el.parentElement?.tagName === 'LABEL' || el.parentElement?.classList.contains('label-wrap')));
|
||||
if (!isValidElement) return;
|
||||
|
||||
let found; // find matching hint data
|
||||
if (el.dataset.original) found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.dataset.original.toLowerCase().trim());
|
||||
else found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.textContent.toLowerCase().trim());
|
||||
let found; // find matching hint data - prefer id match for disambiguation
|
||||
if (el.id) found = localeData.data.find((l) => l.id && (l.id === el.id || el.id.endsWith(l.id)));
|
||||
if (!found) {
|
||||
if (el.dataset.original) found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.dataset.original.toLowerCase().trim());
|
||||
else found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.textContent.toLowerCase().trim());
|
||||
}
|
||||
|
||||
if (found?.localized?.length > 0) { // apply localization if found
|
||||
if (!el.dataset.original) el.dataset.original = el.textContent;
|
||||
|
||||
Reference in New Issue
Block a user