feat(frontend): opt-in Other Models toggles, hidden nav and announcement

- The Other nav entry is hidden while the feature is off
  (nav-item--hidden, toggled client-side after enabling) and now uses the
  fa-shapes icon.
- Shared utils/otherModels.js helpers (enable through the settings API,
  open the settings Library section) are reused by the disabled page, the
  announcement banner and the download modal.
- BannerService registers a one-time dismissible "other-models-announcement"
  banner while the feature is off; SettingsManager drops the banner and
  updates the nav when the master switch flips.
- A disabled download routing answer now surfaces a showActionToast with an
  "Enable Other Models" action.
- Settings UI: master toggle + five sub_type checkboxes whose default-root
  selects are disabled when unchecked; i18n keys added to en.json and synced
  (other locales keep TODO placeholders).
This commit is contained in:
Will Miao
2026-09-13 07:59:28 +08:00
parent 28fbb86dce
commit 69a62d739c
25 changed files with 1006 additions and 19 deletions
+2 -2
View File
@@ -38,9 +38,9 @@
id="embeddingsNavItem">
<i class="fas fa-code"></i> <span>{{ t('header.navigation.embeddings') }}</span>
</a>
<a href="/other" class="nav-item{% if current_path.startswith('/other') %} active{% endif %}"
<a href="/other" class="nav-item{% if current_path.startswith('/other') %} active{% endif %}{% if not settings.get('enable_other_models') %} nav-item--hidden{% endif %}"
id="otherNavItem">
<i class="fas fa-cubes"></i> <span>{{ t('header.navigation.other') }}</span>
<i class="fas fa-shapes"></i> <span>{{ t('header.navigation.other') }}</span>
</a>
<a href="/statistics" class="nav-item{% if current_path.startswith('/statistics') %} active{% endif %}"
id="statisticsNavItem">
@@ -39,17 +39,44 @@
{{ sm.setting_select('defaultEmbeddingRoot', 'default_embedding_root', 'settings.folderSettings.defaultEmbeddingRoot', [], 'settings.folderSettings.defaultEmbeddingRootHelp') }}
{{ sm.setting_toggle('enableOtherModels', 'enable_other_models', 'settings.folderSettings.enableOtherModels', 'settings.folderSettings.enableOtherModelsHelp') }}
{% set enabled_other_sub_types = settings.get('enabled_other_sub_types') or ['vae', 'upscaler', 'text_encoder', 'clip_vision'] %}
<div class="setting-item other-subtype-toggles" id="otherSubTypeToggles">
<div class="setting-row">
<div class="setting-info">
<label>
{{ t('settings.folderSettings.otherSubTypes') }}
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.folderSettings.otherSubTypesHelp') }}"></i>
</label>
</div>
<div class="setting-control other-subtype-checkboxes">
{% for sub_type, label_key in [
('vae', 'settings.folderSettings.subTypeVae'),
('upscaler', 'settings.folderSettings.subTypeUpscaler'),
('text_encoder', 'settings.folderSettings.subTypeTextEncoder'),
('clip_vision', 'settings.folderSettings.subTypeClipVision'),
('controlnet', 'settings.folderSettings.subTypeControlnet'),
] %}
<label class="other-subtype-checkbox">
<input type="checkbox" value="{{ sub_type }}"
data-other-subtype-toggle="{{ sub_type }}"
{% if sub_type in enabled_other_sub_types %}checked{% endif %}
onchange="settingsManager.saveEnabledOtherSubTypes()">
<span>{{ t(label_key) }}</span>
</label>
{% endfor %}
</div>
</div>
</div>
{% set other_root_selects = [
('vae', 'defaultOtherRootVae', 'settings.folderSettings.defaultVaeRoot', 'settings.folderSettings.defaultVaeRootHelp'),
('upscaler', 'defaultOtherRootUpscaler', 'settings.folderSettings.defaultUpscalerRoot', 'settings.folderSettings.defaultUpscalerRootHelp'),
('text_encoder', 'defaultOtherRootTextEncoder', 'settings.folderSettings.defaultTextEncoderRoot', 'settings.folderSettings.defaultTextEncoderRootHelp'),
('clip_vision', 'defaultOtherRootClipVision', 'settings.folderSettings.defaultClipVisionRoot', 'settings.folderSettings.defaultClipVisionRootHelp'),
('controlnet', 'defaultOtherRootControlnet', 'settings.folderSettings.defaultControlnetRoot', 'settings.folderSettings.defaultControlnetRootHelp'),
] %}
{% if 'controlnet' in (settings.get('enabled_other_folders') or []) %}
{% set other_root_selects = other_root_selects + [
('controlnet', 'defaultOtherRootControlnet', 'settings.folderSettings.defaultControlnetRoot', 'settings.folderSettings.defaultControlnetRootHelp'),
] %}
{% endif %}
{% for sub_type, select_id, label_key, help_key in other_root_selects %}
<div class="setting-item">
<div class="setting-row">
@@ -60,7 +87,7 @@
</label>
</div>
<div class="setting-control select-control">
<select id="{{ select_id }}" data-other-root-subtype="{{ sub_type }}" onchange="settingsManager.saveOtherRootSetting('{{ sub_type }}', this.value)">
<select id="{{ select_id }}" data-other-root-subtype="{{ sub_type }}" {% if sub_type not in enabled_other_sub_types %}disabled{% endif %} onchange="settingsManager.saveOtherRootSetting('{{ sub_type }}', this.value)">
</select>
</div>
</div>
+63
View File
@@ -7,6 +7,53 @@
{% block init_message %}{{ t('initialization.other.message') }}{% endblock %}
{% block init_check_url %}/api/other/list?page=1&page_size=1{% endblock %}
{% block page_css %}
<style>
.other-disabled {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
text-align: center;
padding: 64px 24px;
color: var(--text-color, #e0e0e0);
}
.other-disabled > i {
font-size: 48px;
opacity: 0.6;
}
.other-disabled h2 {
margin: 0;
font-size: 20px;
}
.other-disabled p {
margin: 0;
max-width: 520px;
line-height: 1.6;
opacity: 0.8;
}
.other-disabled .other-disabled-hint {
font-size: 13px;
opacity: 0.6;
}
.other-disabled button {
margin-top: 8px;
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
color: #fff;
background: var(--primary-color, #4a7dff);
}
.other-disabled button:disabled {
opacity: 0.6;
cursor: default;
}
</style>
{% endblock %}
{% block additional_components %}
<div id="otherContextMenu" class="context-menu" style="display: none;">
@@ -52,6 +99,17 @@
{% endblock %}
{% block content %}
{% if other_disabled %}
<div class="other-disabled">
<i class="fas fa-shapes"></i>
<h2>{{ t('other.disabled.title') }}</h2>
<p>{{ t('other.disabled.description') }}</p>
<button id="enableOtherModelsBtn" type="button">
<i class="fas fa-toggle-on"></i> {{ t('other.disabled.enableButton') }}
</button>
<p class="other-disabled-hint">{{ t('other.disabled.hint') }}</p>
</div>
{% else %}
<div class="sticky-topbar">
{% include 'components/controls.html' %}
{% include 'components/breadcrumb.html' %}
@@ -63,6 +121,7 @@
<div class="card-grid" id="modelGrid">
<!-- Cards will be dynamically inserted here -->
</div>
{% endif %}
{% endblock %}
{% block overlay %}
@@ -70,5 +129,9 @@
{% endblock %}
{% block main_script %}
{% if other_disabled %}
<script type="module" src="/loras_static/js/other_disabled.js?v={{ version }}"></script>
{% else %}
<script type="module" src="/loras_static/js/other.js?v={{ version }}"></script>
{% endif %}
{% endblock %}