Persist ACE-Step XL-SFT quality defaults
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* User preferences persistence – SAVE side only.
|
||||
*
|
||||
* Listens for user changes on Gradio UI controls and persists the current
|
||||
* values to browser localStorage. Restoration is handled on the Python side
|
||||
* via ``gr.Blocks.load()`` so Gradio's own Svelte reactivity updates every
|
||||
* component correctly.
|
||||
*
|
||||
* Storage schema:
|
||||
* key = "acestep.ui.user_preferences"
|
||||
* value = JSON { _version: 2, audio_format: "flac", … }
|
||||
*/
|
||||
(() => {
|
||||
const STORAGE_KEY = "acestep.ui.user_preferences";
|
||||
const SCHEMA_VERSION = 2;
|
||||
const DEBOUNCE_MS = 500;
|
||||
|
||||
/**
|
||||
* Map of preference key → { elemId, type }.
|
||||
* elemId : the HTML elem_id set in Gradio
|
||||
* type : "dropdown" | "slider" | "checkbox" | "number"
|
||||
*/
|
||||
const PREFS = {
|
||||
audio_format: { elemId: "acestep-audio-format", type: "dropdown" },
|
||||
mp3_bitrate: { elemId: "acestep-mp3-bitrate", type: "dropdown" },
|
||||
mp3_sample_rate: { elemId: "acestep-mp3-sample-rate", type: "dropdown" },
|
||||
score_scale: { elemId: "acestep-score-scale", type: "slider" },
|
||||
enable_normalization:{ elemId: "acestep-enable-normalization", type: "checkbox" },
|
||||
normalization_db: { elemId: "acestep-normalization-db", type: "slider" },
|
||||
fade_in_duration: { elemId: "acestep-fade-in-duration", type: "slider" },
|
||||
fade_out_duration: { elemId: "acestep-fade-out-duration", type: "slider" },
|
||||
latent_shift: { elemId: "acestep-latent-shift", type: "slider" },
|
||||
latent_rescale: { elemId: "acestep-latent-rescale", type: "slider" },
|
||||
lm_batch_chunk_size: { elemId: "acestep-lm-batch-chunk-size", type: "number" },
|
||||
};
|
||||
|
||||
let saveTimer = null;
|
||||
const wiredElements = new WeakSet();
|
||||
|
||||
// ── Storage helpers ──────────────────────────────────────────────
|
||||
|
||||
const saveAll = (prefs) => {
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
|
||||
} catch (_e) {
|
||||
// Private browsing or quota exceeded – silently ignore.
|
||||
}
|
||||
};
|
||||
|
||||
// ── DOM helpers ──────────────────────────────────────────────────
|
||||
|
||||
const findInput = (elemId, type) => {
|
||||
const wrapper = document.getElementById(elemId);
|
||||
if (!wrapper) return null;
|
||||
|
||||
if (type === "dropdown") {
|
||||
return wrapper.querySelector("input");
|
||||
}
|
||||
if (type === "slider") {
|
||||
return wrapper.querySelector("input[type='range']")
|
||||
|| wrapper.querySelector("input[type='number']");
|
||||
}
|
||||
if (type === "checkbox") {
|
||||
return wrapper.querySelector("input[type='checkbox']");
|
||||
}
|
||||
if (type === "number") {
|
||||
return wrapper.querySelector("input[type='number']");
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const readValue = (key) => {
|
||||
const spec = PREFS[key];
|
||||
if (!spec) return undefined;
|
||||
const el = findInput(spec.elemId, spec.type);
|
||||
if (!el) return undefined;
|
||||
|
||||
if (spec.type === "checkbox") return el.checked;
|
||||
if (spec.type === "slider" || spec.type === "number") {
|
||||
const v = Number(el.value);
|
||||
return Number.isFinite(v) ? v : undefined;
|
||||
}
|
||||
return el.value || undefined;
|
||||
};
|
||||
|
||||
// ── Save (debounced) ─────────────────────────────────────────────
|
||||
|
||||
const scheduleSave = () => {
|
||||
if (saveTimer !== null) {
|
||||
clearTimeout(saveTimer);
|
||||
}
|
||||
saveTimer = setTimeout(() => {
|
||||
saveTimer = null;
|
||||
const prefs = { _version: SCHEMA_VERSION };
|
||||
for (const key of Object.keys(PREFS)) {
|
||||
const v = readValue(key);
|
||||
if (v !== undefined) {
|
||||
prefs[key] = v;
|
||||
}
|
||||
}
|
||||
saveAll(prefs);
|
||||
}, DEBOUNCE_MS);
|
||||
};
|
||||
|
||||
// ── Wire listeners (re-entrant – safe to call on re-renders) ─────
|
||||
|
||||
const wireListeners = () => {
|
||||
for (const key of Object.keys(PREFS)) {
|
||||
const spec = PREFS[key];
|
||||
const el = findInput(spec.elemId, spec.type);
|
||||
if (!el || wiredElements.has(el)) continue;
|
||||
wiredElements.add(el);
|
||||
el.addEventListener("input", scheduleSave, { passive: true });
|
||||
el.addEventListener("change", scheduleSave, { passive: true });
|
||||
}
|
||||
};
|
||||
|
||||
// ── MutationObserver – re-wire after Gradio re-renders ───────────
|
||||
|
||||
const startObserver = () => {
|
||||
const target = document.getElementById("acestep-audio-format")
|
||||
|| document.body;
|
||||
const root = target.closest(".gradio-container") || document.body;
|
||||
|
||||
let rafPending = false;
|
||||
new MutationObserver(() => {
|
||||
if (rafPending) return;
|
||||
rafPending = true;
|
||||
requestAnimationFrame(() => {
|
||||
rafPending = false;
|
||||
wireListeners();
|
||||
});
|
||||
}).observe(root, { childList: true, subtree: true });
|
||||
};
|
||||
|
||||
// ── Boot ─────────────────────────────────────────────────────────
|
||||
|
||||
const BOOT_POLL_MS = 200;
|
||||
const BOOT_TIMEOUT_MS = 10000;
|
||||
|
||||
const boot = () => {
|
||||
const started = Date.now();
|
||||
const poll = () => {
|
||||
const probe = document.getElementById(
|
||||
PREFS.audio_format.elemId
|
||||
);
|
||||
if (!probe) {
|
||||
if (Date.now() - started < BOOT_TIMEOUT_MS) {
|
||||
setTimeout(poll, BOOT_POLL_MS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
wireListeners();
|
||||
startObserver();
|
||||
};
|
||||
poll();
|
||||
};
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", boot, { once: true });
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user