feat(other-models): announce the feature only when folders are available

Other Models management is opt-in and its folders come from
folder_paths.get_folder_paths(). In plugin mode ComfyUI registers vae,
upscale_models, text_encoders, clip_vision and controlnet out of the box, so
enabling the feature works immediately. Standalone only knows the keys present
in settings.json.folder_paths, and that file is edited by hand - there is no UI
for those keys - so a standalone user who followed the announcement banner
reached "Enable Other Models" and then an empty page.

Gate the announcement on the capability instead of on how the process was
started:

- Config.get_other_models_availability() probes every canonical other key
  (legacy clip collapses into text_encoders where the host exposes
  map_legacy) and reports which sub_types resolve to a folder that exists on
  disk. It deliberately ignores enable_other_models: the question is "could
  this work here at all?". An empty folder counts, because CivitAI downloads
  can target it.
- /api/lm/settings exposes it as the derived, non-persisted
  other_models_paths_available flag; a probe failure yields null and the
  banner fails open.
- BannerService only registers the announcement when the flag is not false.
  `=== false` (not falsy) keeps a cached/older payload working, and nothing is
  written to dismissed_banners, so the banner can return once folders exist.
- The Other page grows an "enabled but nothing to scan" empty state driven by
  config.other_roots, showing the settings.json snippet for standalone and a
  pointer to ComfyUI model paths otherwise, plus an Open Settings action. It
  also covers the corner where only a non-default sub_type has a folder.

Translate the six other.noPaths.* keys into all nine locales and record the
new "folder key" / "on disk" terminology in the i18n guidelines.

Backend tests and pytest tests/i18n could not run in this environment (no
pytest/platformdirs); the probe was exercised against a stubbed folder_paths.
Frontend: 120 files / 1101 JS tests passed.
This commit is contained in:
Will Miao
2026-09-13 21:47:16 +08:00
parent adeb40bfff
commit 84146b62fd
22 changed files with 357 additions and 7 deletions
+7
View File
@@ -553,6 +553,13 @@ class BannerService {
if (state.global.settings.enable_other_models) {
return;
}
// Only announce when the host can actually resolve other-model folders.
// Standalone installs only know the folder_paths keys present in
// settings.json, so announcing there would land the user on an empty
// page. `=== false` (not falsy) keeps older payloads working.
if (state.global.settings.other_models_paths_available === false) {
return;
}
if (this.isBannerDismissed(OTHER_MODELS_BANNER_ID)) {
return;
}
+18 -1
View File
@@ -1,11 +1,14 @@
import { appCore } from './core.js';
import { showToast } from './utils/uiHelpers.js';
import { enableOtherModels } from './utils/otherModels.js';
import { enableOtherModels, openOtherModelsSettings } from './utils/otherModels.js';
/**
* Other Models is an opt-in feature. While it is disabled this page renders an
* empty state whose button turns the feature on; the backend then rebuilds the
* other-model roots and starts scanning, so a reload lands on the real page.
*
* The same module backs the "enabled but no folders found" state, where the
* only useful action is jumping to Settings instead of enabling anything.
*/
async function handleEnableClick() {
const button = document.getElementById('enableOtherModelsBtn');
@@ -20,6 +23,15 @@ async function handleEnableClick() {
}
}
/**
* Open Settings on the Library section for the "no folders found" state, so a
* misconfigured install can be fixed without hand-editing unknown keys.
*/
function handleOpenSettingsClick(event) {
event.preventDefault();
openOtherModelsSettings();
}
async function initializeOtherDisabledPage() {
// appCore.initialize() wires the shared header (theme, settings modal,
// language) so this page is not a dead end.
@@ -29,6 +41,11 @@ async function initializeOtherDisabledPage() {
if (button) {
button.addEventListener('click', handleEnableClick);
}
const settingsButton = document.getElementById('openOtherModelsSettingsBtn');
if (settingsButton) {
settingsButton.addEventListener('click', handleOpenSettingsClick);
}
}
document.addEventListener('DOMContentLoaded', initializeOtherDisabledPage);