refactor: unify settings schema and dynamic node rules exposure across backend and frontend

This commit is contained in:
Dariusz L
2026-07-18 10:21:49 +02:00
parent aff97c5e29
commit 2987c036e2
6 changed files with 83 additions and 13 deletions
+8 -2
View File
@@ -4724,12 +4724,14 @@ class ModelResolverExtension:
@json_api_endpoint("capabilities")
async def get_capabilities(request):
"""Get optional source capabilities available in this install."""
from .core.workflow_analyzer import NODE_TYPE_MODEL_WIDGET_CATEGORIES
return web.json_response(
{
"sources": {
"civarchive": is_civarchive_available(),
"lora_manager_archive": is_lora_manager_archive_available()
}
},
"node_rules": NODE_TYPE_MODEL_WIDGET_CATEGORIES
}
)
@@ -5011,7 +5013,11 @@ class ModelResolverExtension:
async def get_settings_route(request):
"""Return persisted settings (API keys, preferences)."""
data = await asyncio.to_thread(load_resolver_settings)
return web.json_response(data)
from .core.settings import get_settings_schema
return web.json_response({
"settings": data,
"schema": get_settings_schema()
})
@routes.post("/model_resolver/settings")
@json_api_endpoint("settings POST")
+7 -4
View File
@@ -73,10 +73,13 @@ def _extract_size_from_object(data: Any, prefix: str = "") -> Optional[Tuple[int
if not isinstance(data, dict):
return None
for field_path, value, multiplier in _iter_size_fields(data, prefix):
size = _coerce_size_bytes(value, multiplier=multiplier)
if size is not None:
return size, field_path
from .type_utils import extract_file_size
size = extract_file_size(data)
if size is not None:
for field_path, value, multiplier in _iter_size_fields(data, prefix):
if _coerce_size_bytes(value, multiplier=multiplier) == size:
return size, field_path
return size, f"{prefix}size"
return None
+34
View File
@@ -16,6 +16,35 @@ from .type_utils import normalize_download_category, to_bool
DOWNLOAD_PATH_MODES = {"suggested", "template", "manual"}
DOWNLOAD_BACKENDS = {"python", "aria2"}
SETTINGS_SCHEMA: List[Dict[str, Any]] = [
{ "serverKey": "civitai_key", "localKey": "ModelResolver.civitaiApiKey", "type": "string", "default": "" },
{ "serverKey": "civitai_session_token", "localKey": "ModelResolver.civitaiSessionToken", "type": "string", "default": "" },
{ "serverKey": "civitai_use_trpc_search", "localKey": "ModelResolver.civitaiUseTrpcSearch", "type": "boolean", "default": True },
{ "serverKey": "civitai_use_api_search", "localKey": "ModelResolver.civitaiUseApiSearch", "type": "boolean", "default": True },
{ "serverKey": "civitai_use_html_fallback", "localKey": "ModelResolver.civitaiUseHtmlFallback", "type": "boolean", "default": True },
{ "serverKey": "hf_token", "localKey": "ModelResolver.huggingFaceToken", "type": "string", "default": "" },
{ "serverKey": "brave_search_api_key", "localKey": "ModelResolver.braveSearchApiKey", "type": "string", "default": "" },
{ "serverKey": "hf_use_api_search", "localKey": "ModelResolver.hfUseApiSearch", "type": "boolean", "default": True },
{ "serverKey": "hf_use_comfy_org_fallback", "localKey": "ModelResolver.hfUseComfyOrgFallback", "type": "boolean", "default": True },
{ "serverKey": "hf_use_brave_fallback", "localKey": "ModelResolver.hfUseBraveFallback", "type": "boolean", "default": True },
{ "serverKey": "auto_fill_base_model", "localKey": "ModelResolver.autoFillBaseModel", "type": "boolean", "default": True },
{ "serverKey": "auto_fill_subfolder", "localKey": "ModelResolver.autoFillSubfolder", "type": "boolean", "default": True },
{ "serverKey": "auto_refresh_comfy_models_after_apply", "localKey": "ModelResolver.autoRefreshComfyModelsAfterApply", "type": "boolean", "default": True },
{ "serverKey": "workflow_hash_metadata_enabled", "localKey": "ModelResolver.workflowHashMetadataEnabled", "type": "boolean", "default": True },
{ "serverKey": "workflow_dependency_marker_enabled", "localKey": "ModelResolver.workflowDependencyMarkerEnabled", "type": "boolean", "default": False },
{ "serverKey": "download_backend", "localKey": "ModelResolver.downloadBackend", "type": "backend", "default": "python" },
{ "serverKey": "aria2c_path", "localKey": "ModelResolver.aria2cPath", "type": "string", "default": "" },
{ "serverKey": "aria2_auto_stop_daemon", "localKey": "ModelResolver.aria2AutoStopDaemon", "type": "boolean", "default": True },
{ "serverKey": "download_path_mode", "localKey": "ModelResolver.downloadPathMode", "type": "pathMode", "default": "suggested" },
{ "serverKey": "download_path_templates", "localKey": "ModelResolver.downloadPathTemplates", "type": "json", "default": {} },
{ "serverKey": "base_model_path_mappings", "localKey": "ModelResolver.baseModelPathMappings", "type": "json", "default": {} },
{ "serverKey": "frontend_logs_enabled", "localKey": "ModelResolver.frontendLogsEnabled", "type": "frontendLogsEnabled", "default": True },
{ "serverKey": "backend_logs_enabled", "localKey": "ModelResolver.backendLogsEnabled", "type": "backendLogsEnabled", "default": True },
{ "serverKey": "frontend_log_level", "localKey": "ModelResolver.frontendLogLevel", "type": "string", "default": "ERROR" },
{ "serverKey": "backend_log_level", "localKey": "ModelResolver.backendLogLevel", "type": "string", "default": "ERROR" },
{ "serverKey": "civitai_candidate_limit", "localKey": "ModelResolver.civitaiCandidateLimit", "type": "candidateLimit", "default": 5 },
]
DEFAULT_DOWNLOAD_PATH_TEMPLATES: Dict[str, str] = {
"loras": "{base_model}/{first_tag}",
"checkpoints": "{base_model}",
@@ -358,3 +387,8 @@ def get_default_root_for_category(
if not key:
return ""
return str(active_settings.get(key) or "").strip()
def get_settings_schema() -> List[Dict[str, Any]]:
"""Return the schema dictionary for frontend setting options synchronization."""
return SETTINGS_SCHEMA
@@ -281,6 +281,16 @@ export const downloadTargetMethods = {
getExactNodeTypeDownloadCategory(nodeType = '') {
const normalizedNodeType = String(nodeType || '').trim();
const dynamicRules = this.capabilities?.node_rules;
if (dynamicRules && typeof dynamicRules === 'object' && dynamicRules[normalizedNodeType]) {
const widgetsMap = dynamicRules[normalizedNodeType];
if (widgetsMap && typeof widgetsMap === 'object') {
const indices = Object.keys(widgetsMap).map(k => parseInt(k, 10)).sort((a, b) => a - b);
if (indices.length > 0) {
return widgetsMap[indices[0]] || '';
}
}
}
const exactMap = {
CheckpointLoaderSimple: 'checkpoints',
CheckpointLoader: 'checkpoints',
+12 -2
View File
@@ -1,4 +1,4 @@
import { app } from "../../../../../scripts/app.js";
import { app } from "../../../../../scripts/app.js";
import { api } from "../../../../../scripts/api.js";
import { $el } from "../../../../../scripts/ui.js";
import { createModuleLogger } from "../../log_system/log_funcs.js";
@@ -67,7 +67,7 @@ export const workflowStateMethods = {
'on'
]);
const loraStrengthNodeTypes = new Set(['LoraLoader', 'LoraLoaderModelOnly']);
const modelWidgetIndicesByNodeType = {
const staticFallback = {
CheckpointLoaderSimple: [0],
CheckpointLoader: [1],
DiffusersLoader: [0],
@@ -131,6 +131,16 @@ export const workflowStateMethods = {
LTXAVTextEncoderLoader: [0, 1]
};
const modelWidgetIndicesByNodeType = { ...staticFallback };
const dynamicRules = this.capabilities?.node_rules;
if (dynamicRules && typeof dynamicRules === 'object') {
for (const [nodeType, widgetsMap] of Object.entries(dynamicRules)) {
if (widgetsMap && typeof widgetsMap === 'object') {
modelWidgetIndicesByNodeType[nodeType] = Object.keys(widgetsMap).map(k => parseInt(k, 10));
}
}
}
const normalizeKey = (key = '') => String(key).replace(/[-\s]/g, '_').toLowerCase();
const isModelWidgetIndex = (nodeType = '', index = -1) => (
modelWidgetIndicesByNodeType[nodeType] || []
+12 -5
View File
@@ -3572,8 +3572,15 @@ export const optionsMethods = {
return;
}
let settingsObj = data;
if (data.schema && Array.isArray(data.schema)) {
SETTINGS_MAP.length = 0;
SETTINGS_MAP.push(...data.schema);
settingsObj = data.settings || {};
}
for (const opt of SETTINGS_MAP) {
const serverValue = data[opt.serverKey];
const serverValue = settingsObj[opt.serverKey];
if (serverValue === undefined) continue;
if (opt.type === 'boolean' || opt.type === 'frontendLogsEnabled' || opt.type === 'backendLogsEnabled') {
@@ -3591,14 +3598,14 @@ export const optionsMethods = {
}
}
this.getDefaultRootCategoryDefinitions().forEach((item) => {
if (data[item.settingKey] !== undefined) {
safeStorage.setItem(item.storageKey, String(data[item.settingKey] || ''));
if (settingsObj[item.settingKey] !== undefined) {
safeStorage.setItem(item.storageKey, String(settingsObj[item.settingKey] || ''));
}
});
// Source-enabled flags stored as a nested object
if (data.search_source_enabled && typeof data.search_source_enabled === 'object') {
Object.entries(data.search_source_enabled).forEach(([key, val]) => {
if (settingsObj.search_source_enabled && typeof settingsObj.search_source_enabled === 'object') {
Object.entries(settingsObj.search_source_enabled).forEach(([key, val]) => {
if (key) safeStorage.setItem(key, val ? 'true' : 'false');
});
}