Unify metadata workflow cache and category handling
This commit is contained in:
@@ -14,6 +14,7 @@ from ..request_utils import (
|
||||
read_text_field,
|
||||
)
|
||||
from ..routes.context import RouteContext
|
||||
from ..type_utils import CATEGORY_MAP, normalize_category_token
|
||||
from ..workflow.formats import normalize_workflow_payload
|
||||
|
||||
MAX_IMAGE_BYTES = 64 * 1024 * 1024
|
||||
@@ -25,22 +26,6 @@ _CIVITAI_JSON_LABELS = (
|
||||
"Civitai metadata",
|
||||
"Civitai resources",
|
||||
)
|
||||
_CIVITAI_CATEGORY_ALIASES = {
|
||||
"checkpoint": "checkpoints",
|
||||
"checkpoints": "checkpoints",
|
||||
"lora": "loras",
|
||||
"loras": "loras",
|
||||
"vae": "vae",
|
||||
"controlnet": "controlnet",
|
||||
"embedding": "embeddings",
|
||||
"embeddings": "embeddings",
|
||||
"textualinversion": "embeddings",
|
||||
"upscale": "upscale_models",
|
||||
"upscaler": "upscale_models",
|
||||
"upscalemodel": "upscale_models",
|
||||
}
|
||||
|
||||
|
||||
def _decode_exif_user_comment(value: bytes) -> str:
|
||||
"""Decode the standard EXIF UserComment prefix and payload."""
|
||||
if value.startswith(b"UNICODE"):
|
||||
@@ -268,8 +253,8 @@ def _normalize_civitai_resource(value: Any, index: int) -> Optional[dict[str, An
|
||||
resource_type = str(
|
||||
_first_mapping_value(value, ("type", "model_type", "modelType")) or ""
|
||||
).strip()
|
||||
category_key = re.sub(r"[^a-z0-9]+", "", resource_type.lower())
|
||||
category = _CIVITAI_CATEGORY_ALIASES.get(category_key, category_key or "unknown")
|
||||
category_key = normalize_category_token(resource_type)
|
||||
category = CATEGORY_MAP.get(category_key, category_key or "unknown")
|
||||
model_version_id = _coerce_identifier(
|
||||
_first_mapping_value(
|
||||
value,
|
||||
|
||||
@@ -242,6 +242,7 @@ URN_TYPE_MAP = {
|
||||
CATEGORY_MAP = {
|
||||
"checkpoints": "checkpoints",
|
||||
"checkpoint": "checkpoints",
|
||||
"ckpt": "checkpoints",
|
||||
"loras": "loras",
|
||||
"lora": "loras",
|
||||
"embeddings": "embeddings",
|
||||
@@ -254,7 +255,9 @@ CATEGORY_MAP = {
|
||||
"control_net": "controlnet",
|
||||
"vae": "vae",
|
||||
"upscaler": "upscale_models",
|
||||
"upscale": "upscale_models",
|
||||
"upscale_model": "upscale_models",
|
||||
"upscalemodel": "upscale_models",
|
||||
"upscale_models": "upscale_models",
|
||||
"latent_upscale_model": "latent_upscale_models",
|
||||
"latent_upscale_models": "latent_upscale_models",
|
||||
|
||||
@@ -11,33 +11,11 @@ from typing import Any, Dict, List, Optional
|
||||
from .contracts import ModelReference, Resolution, ResolvedModel
|
||||
from .custom_nodes import get_custom_node_resolution_metadata
|
||||
from .path_utils import get_filename_from_path
|
||||
from .type_utils import CATEGORY_MAP, normalize_category_token
|
||||
from .workflow import dynamic_widgets
|
||||
from .workflow.widgets import get_widget_name_candidates, normalize_widget_name
|
||||
from .workflow_updater import update_model_path
|
||||
|
||||
_CATEGORY_ALIASES = {
|
||||
"checkpoint": "checkpoints",
|
||||
"checkpoints": "checkpoints",
|
||||
"ckpt": "checkpoints",
|
||||
"lora": "loras",
|
||||
"loras": "loras",
|
||||
"text_encoder": "text_encoders",
|
||||
"text_encoders": "text_encoders",
|
||||
"clip": "text_encoders",
|
||||
"vae": "vae",
|
||||
"controlnet": "controlnet",
|
||||
"control_net": "controlnet",
|
||||
"upscale": "upscale_models",
|
||||
"upscaler": "upscale_models",
|
||||
"upscale_model": "upscale_models",
|
||||
"upscale_models": "upscale_models",
|
||||
"diffusion_model": "diffusion_models",
|
||||
"diffusion_models": "diffusion_models",
|
||||
"unet": "diffusion_models",
|
||||
"embedding": "embeddings",
|
||||
"embeddings": "embeddings",
|
||||
}
|
||||
|
||||
_LORA_MANAGER_NODE_TYPES = {
|
||||
"LoraLoaderV2",
|
||||
"Lora Loader (LoraManager)",
|
||||
@@ -64,10 +42,10 @@ _TRANSFER_ACTIVITY_SCOPES = {"all", "active", "inactive"}
|
||||
|
||||
def normalize_transfer_category(value: Any) -> str:
|
||||
"""Normalize the category names used by workflow and metadata payloads."""
|
||||
token = str(value or "").strip().lower().replace("-", "_")
|
||||
token = normalize_category_token(value)
|
||||
if not token or token in {"unknown", "none", "null"}:
|
||||
return ""
|
||||
return _CATEGORY_ALIASES.get(token, token)
|
||||
return CATEGORY_MAP.get(token, token)
|
||||
|
||||
|
||||
def _normalize_identity(value: Any) -> str:
|
||||
@@ -289,17 +267,6 @@ def _resolution_payload(resolution: Resolution) -> Dict[str, Any]:
|
||||
return payload
|
||||
|
||||
|
||||
def _assign_sources(
|
||||
sources: Sequence[Mapping[str, Any]],
|
||||
references: Sequence[ModelReference],
|
||||
) -> List[tuple[ModelReference, Mapping[str, Any]]]:
|
||||
if not sources or not references:
|
||||
return []
|
||||
if len(sources) == 1:
|
||||
return [(reference, sources[0]) for reference in references]
|
||||
return list(zip(references, sources, strict=False))
|
||||
|
||||
|
||||
def _select_source_for_reference(
|
||||
category_sources: Sequence[Mapping[str, Any]],
|
||||
selector: Mapping[str, Any],
|
||||
|
||||
@@ -2096,6 +2096,40 @@ test('background Loaded Models refresh keeps the current view until new data is
|
||||
assert.equal(contentElement.scrollTop, 48);
|
||||
});
|
||||
|
||||
test('Loaded Models renders the shared cache without starting another scan', async () => {
|
||||
const loadLoadedModels = eval(`(${extractMethod(tabsLoadedMethodsSource, 'loadLoadedModels')})`);
|
||||
const getCachedLoadedModelsForSignature = eval(`(${extractMethod(
|
||||
workflowStateMethodsSource,
|
||||
'getCachedLoadedModelsForSignature'
|
||||
)})`);
|
||||
const cachedData = { loaded_models: [{ strength: 0.5 }], total: 1 };
|
||||
const contentElement = { innerHTML: '' };
|
||||
let fetchCount = 0;
|
||||
let renderCount = 0;
|
||||
const dialog = {
|
||||
activeTab: 'loaded',
|
||||
contentElement,
|
||||
cachedLoadedModelsSignature: 'workflow-a',
|
||||
cachedLoadedModelsData: cachedData,
|
||||
syncWorkflowScopedQueue() {},
|
||||
getWorkflowSignature: () => 'workflow-a',
|
||||
getCachedLoadedModelsForSignature,
|
||||
fetchJson: async () => {
|
||||
fetchCount += 1;
|
||||
throw new Error('The shared cache was not used');
|
||||
},
|
||||
displayLoadedModels: (_container, data) => {
|
||||
renderCount += 1;
|
||||
assert.equal(data, cachedData);
|
||||
},
|
||||
};
|
||||
|
||||
await loadLoadedModels.call(dialog, { nodes: [] });
|
||||
|
||||
assert.equal(fetchCount, 0);
|
||||
assert.equal(renderCount, 1);
|
||||
});
|
||||
|
||||
test('Loaded Models progress patches the stable progress container', () => {
|
||||
const pollWorkflowProgress = extractMethod(renderFormatMethodsSource, 'pollWorkflowProgress');
|
||||
const patchLoadedModelsProgress = extractMethod(
|
||||
@@ -2730,6 +2764,31 @@ test('workflow analysis and loaded model caches stay independent and cloned', ()
|
||||
assert.equal(dialog.workflowAnalysisCaches.get(dialog.workflowKey).data.missing_models.length, 1);
|
||||
});
|
||||
|
||||
test('shared loaded model cache requires a matching workflow signature unless forced', () => {
|
||||
const getCachedLoadedModelsForSignature = eval(`(${extractMethod(
|
||||
workflowStateMethodsSource,
|
||||
'getCachedLoadedModelsForSignature'
|
||||
)})`);
|
||||
const cachedData = { loaded_models: [{ filename: 'loaded.safetensors' }] };
|
||||
const dialog = {
|
||||
cachedLoadedModelsSignature: 'workflow-a',
|
||||
cachedLoadedModelsData: cachedData,
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
getCachedLoadedModelsForSignature.call(dialog, 'workflow-a'),
|
||||
cachedData
|
||||
);
|
||||
assert.equal(
|
||||
getCachedLoadedModelsForSignature.call(dialog, 'workflow-b'),
|
||||
null
|
||||
);
|
||||
assert.equal(
|
||||
getCachedLoadedModelsForSignature.call(dialog, 'workflow-a', { force: true }),
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
test('Missing Models filter toggles persist state and rerender once per change', () => {
|
||||
const bindMissingFilterToggle = eval(
|
||||
`(${extractMethod(missingBrowserMethodsSource, 'bindMissingFilterToggle')})`
|
||||
|
||||
@@ -147,6 +147,123 @@ function createLoraTransferState(sourceModels, activityScope = 'all') {
|
||||
};
|
||||
}
|
||||
|
||||
test('metadata transfer persists only mode and activity scope preferences', () => {
|
||||
const context = createTransferContext();
|
||||
|
||||
assert.deepEqual(context.getMetadataTransferPreferences(), {
|
||||
mode: 'replace',
|
||||
activityScope: 'all',
|
||||
});
|
||||
|
||||
assert.equal(context.setMetadataTransferMode('merge'), 'merge');
|
||||
assert.equal(context.setMetadataTransferActivityScope('inactive'), 'inactive');
|
||||
assert.deepEqual(context.getMetadataTransferPreferences(), {
|
||||
mode: 'merge',
|
||||
activityScope: 'inactive',
|
||||
});
|
||||
});
|
||||
|
||||
test('metadata tab reuses transfer state for the unchanged workflow', () => {
|
||||
const context = createTransferContext();
|
||||
context.getWorkflowSignature = workflow => workflow.signature;
|
||||
const state = { transfer: { workflowSignature: 'workflow-a' } };
|
||||
|
||||
assert.equal(
|
||||
context.isMetadataTransferCurrentWorkflow(state, { signature: 'workflow-a' }),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
context.isMetadataTransferCurrentWorkflow(state, { signature: 'workflow-b' }),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
context.isMetadataTransferCurrentWorkflow({ transfer: { workflowSignature: '' } }, null),
|
||||
true
|
||||
);
|
||||
assert.equal(context.isMetadataTransferCurrentWorkflow(null, null), false);
|
||||
});
|
||||
|
||||
test('metadata transfer reuses the shared Loaded Models cache for the same workflow', async () => {
|
||||
const cachedData = {
|
||||
loaded_models: [{
|
||||
category: 'text_encoders',
|
||||
node_id: 1,
|
||||
node_type: 'CLIPLoader',
|
||||
widget_index: 0,
|
||||
original_path: 'current.safetensors',
|
||||
active: true,
|
||||
connected: true,
|
||||
is_top_level: true,
|
||||
}],
|
||||
total: 1,
|
||||
};
|
||||
let fetchCount = 0;
|
||||
const context = {
|
||||
...imageMetadataMethods,
|
||||
getCurrentWorkflow: () => ({ signature: 'workflow-a' }),
|
||||
getWorkflowSignature: workflow => workflow.signature,
|
||||
cachedLoadedModelsSignature: 'workflow-a',
|
||||
cachedLoadedModelsData: cachedData,
|
||||
getCachedLoadedModelsForSignature(signature, { force = false } = {}) {
|
||||
return !force && signature === this.cachedLoadedModelsSignature
|
||||
? this.cachedLoadedModelsData
|
||||
: null;
|
||||
},
|
||||
fetchJson: async () => {
|
||||
fetchCount += 1;
|
||||
throw new Error('The shared cache was not used');
|
||||
},
|
||||
renderImageMetadataResult() {},
|
||||
};
|
||||
|
||||
const result = await context.openMetadataTransfer();
|
||||
|
||||
assert.equal(fetchCount, 0);
|
||||
assert.equal(result, cachedData);
|
||||
assert.equal(context.imageInspectorState.transfer.targetModels, cachedData);
|
||||
});
|
||||
|
||||
test('metadata transfer stores a fresh target scan in the shared Loaded Models cache', async () => {
|
||||
const freshData = { loaded_models: [], total: 0 };
|
||||
let saveCount = 0;
|
||||
const context = {
|
||||
...imageMetadataMethods,
|
||||
getCurrentWorkflow: () => ({ signature: 'workflow-b' }),
|
||||
getWorkflowSignature: workflow => workflow.signature,
|
||||
cachedLoadedModelsSignature: null,
|
||||
cachedLoadedModelsData: null,
|
||||
getCachedLoadedModelsForSignature() {
|
||||
return null;
|
||||
},
|
||||
fetchJson: async () => freshData,
|
||||
saveLoadedModelsCacheForActiveWorkflow() {
|
||||
saveCount += 1;
|
||||
},
|
||||
renderImageMetadataResult() {},
|
||||
};
|
||||
|
||||
const result = await context.openMetadataTransfer();
|
||||
|
||||
assert.equal(result, freshData);
|
||||
assert.equal(context.cachedLoadedModelsSignature, 'workflow-b');
|
||||
assert.equal(context.cachedLoadedModelsData, freshData);
|
||||
assert.equal(saveCount, 1);
|
||||
});
|
||||
|
||||
test('metadata transfer uses the shared canonical category aliases', () => {
|
||||
const context = createTransferContext();
|
||||
const state = createTransferState([
|
||||
{ category: 'clip_gguf', original_path: 'clip.safetensors' },
|
||||
]);
|
||||
state.transfer.targetGroups[0].category = 'clips';
|
||||
state.transfer.targetGroups[0].nodes[0].refs[0].category = 'clips';
|
||||
|
||||
const [row] = context.getMetadataTransferPreviewRows(state);
|
||||
|
||||
assert.equal(row.sourceUnavailable, false);
|
||||
assert.equal(row.nextValue, 'clip.safetensors');
|
||||
});
|
||||
|
||||
test('metadata transfer hides imported models with an unsupported file extension', () => {
|
||||
const context = createTransferContext();
|
||||
const state = createTransferState([
|
||||
|
||||
@@ -4,6 +4,7 @@ from types import SimpleNamespace
|
||||
import pytest
|
||||
|
||||
from core.contracts import ModelReference
|
||||
from core.type_utils import CATEGORY_MAP
|
||||
from core.workflow_transfer import (
|
||||
normalize_transfer_category,
|
||||
transfer_models_to_workflow,
|
||||
@@ -48,9 +49,15 @@ def _selector(reference):
|
||||
def test_normalize_transfer_category_covers_workflow_aliases():
|
||||
assert normalize_transfer_category("UPSCALE-MODEL") == "upscale_models"
|
||||
assert normalize_transfer_category("checkpoint") == "checkpoints"
|
||||
assert normalize_transfer_category("CLIP / GGUF") == "text_encoders"
|
||||
assert normalize_transfer_category("unknown") == ""
|
||||
|
||||
|
||||
def test_normalize_transfer_category_matches_the_canonical_category_map():
|
||||
for raw_category, canonical_category in CATEGORY_MAP.items():
|
||||
assert normalize_transfer_category(raw_category) == canonical_category
|
||||
|
||||
|
||||
def test_replace_fans_one_source_to_selected_slots_and_syncs_named_values():
|
||||
workflow = {
|
||||
"nodes": [
|
||||
|
||||
@@ -486,6 +486,18 @@ export const workflowStateMethods = {
|
||||
};
|
||||
},
|
||||
|
||||
getCachedLoadedModelsForSignature(workflowSignature, { force = false } = {}) {
|
||||
if (
|
||||
force
|
||||
|| !workflowSignature
|
||||
|| this.cachedLoadedModelsSignature !== workflowSignature
|
||||
|| !this.cachedLoadedModelsData
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return this.cachedLoadedModelsData;
|
||||
},
|
||||
|
||||
saveAnalysisCacheForActiveWorkflow() {
|
||||
this.saveWorkflowModelCache(
|
||||
this.workflowAnalysisCaches,
|
||||
|
||||
@@ -16,6 +16,7 @@ export const CATEGORY_ALIASES = Object.freeze(
|
||||
"background_removal_model": "background_removal",
|
||||
"checkpoint": "checkpoints",
|
||||
"checkpoints": "checkpoints",
|
||||
"ckpt": "checkpoints",
|
||||
"clip": "text_encoders",
|
||||
"clip_gguf": "text_encoders",
|
||||
"clip_vision": "clip_vision",
|
||||
@@ -67,8 +68,10 @@ export const CATEGORY_ALIASES = Object.freeze(
|
||||
"ultralytics_segm": "ultralytics",
|
||||
"unet": "diffusion_models",
|
||||
"unet_gguf": "diffusion_models",
|
||||
"upscale": "upscale_models",
|
||||
"upscale_model": "upscale_models",
|
||||
"upscale_models": "upscale_models",
|
||||
"upscalemodel": "upscale_models",
|
||||
"upscaler": "upscale_models",
|
||||
"vae": "vae",
|
||||
"vae_approx": "vae_approx",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
import { CATEGORY_ALIASES, normalizeCategoryToken } from './category_aliases.generated.js';
|
||||
|
||||
export function normalizeCategoryValue(
|
||||
category = '',
|
||||
{
|
||||
aliases = CATEGORY_ALIASES,
|
||||
defaultValue = '',
|
||||
emptyValues = ['unknown', 'none', 'null'],
|
||||
} = {},
|
||||
) {
|
||||
const token = normalizeCategoryToken(category);
|
||||
const categoryAliases = aliases && typeof aliases === 'object'
|
||||
? aliases
|
||||
: CATEGORY_ALIASES;
|
||||
const emptyTokens = new Set(emptyValues);
|
||||
if (!token || emptyTokens.has(token)) return defaultValue;
|
||||
return categoryAliases[token] || token;
|
||||
}
|
||||
|
||||
export function normalizeDownloadCategoryValue(
|
||||
category = '',
|
||||
aliases = CATEGORY_ALIASES,
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
import { safeStorage } from "../utils/html_utils.js";
|
||||
import {
|
||||
isExistingResolvedModel,
|
||||
toResolverContextModel,
|
||||
} from "../node_context_menu.js";
|
||||
import { normalizeCategoryValue } from "../utils/category_utils.js";
|
||||
|
||||
const MAX_IMAGE_FILE_SIZE = 64 * 1024 * 1024;
|
||||
const MAX_WORKFLOW_FILE_SIZE = 16 * 1024 * 1024;
|
||||
|
||||
const TRANSFER_CATEGORY_ALIASES = Object.freeze({
|
||||
checkpoint: 'checkpoints',
|
||||
checkpoints: 'checkpoints',
|
||||
ckpt: 'checkpoints',
|
||||
lora: 'loras',
|
||||
loras: 'loras',
|
||||
clip: 'text_encoders',
|
||||
text_encoder: 'text_encoders',
|
||||
text_encoders: 'text_encoders',
|
||||
vae: 'vae',
|
||||
control_net: 'controlnet',
|
||||
controlnet: 'controlnet',
|
||||
upscale: 'upscale_models',
|
||||
upscaler: 'upscale_models',
|
||||
upscale_model: 'upscale_models',
|
||||
upscale_models: 'upscale_models',
|
||||
unet: 'diffusion_models',
|
||||
diffusion_model: 'diffusion_models',
|
||||
diffusion_models: 'diffusion_models',
|
||||
embedding: 'embeddings',
|
||||
embeddings: 'embeddings',
|
||||
});
|
||||
const METADATA_TRANSFER_MODE_STORAGE_KEY = 'model_resolver_metadata_transfer_mode';
|
||||
const METADATA_TRANSFER_ACTIVITY_SCOPE_STORAGE_KEY = 'model_resolver_metadata_transfer_activity_scope';
|
||||
|
||||
const MERGE_LORA_NODE_TYPES = new Set([
|
||||
'LoraLoaderV2',
|
||||
@@ -63,8 +49,7 @@ function isAppendableLoraNodeType(nodeType) {
|
||||
}
|
||||
|
||||
function normalizeTransferCategory(value) {
|
||||
const token = String(value || '').trim().toLowerCase().replaceAll('-', '_');
|
||||
return TRANSFER_CATEGORY_ALIASES[token] || token;
|
||||
return normalizeCategoryValue(value);
|
||||
}
|
||||
|
||||
function getTransferNodeKey(model = {}) {
|
||||
@@ -161,11 +146,23 @@ function buildCivitaiModelData(resources = []) {
|
||||
}
|
||||
|
||||
export const imageMetadataMethods = {
|
||||
isMetadataTransferCurrentWorkflow(state = null, workflow = null) {
|
||||
const transfer = state?.transfer;
|
||||
if (!transfer) return false;
|
||||
if (!workflow) return !transfer.workflowSignature;
|
||||
|
||||
const workflowSignature = this.getWorkflowSignature?.(workflow) || '';
|
||||
return Boolean(workflowSignature) && transfer.workflowSignature === workflowSignature;
|
||||
},
|
||||
|
||||
loadImageMetadata() {
|
||||
if (!this.contentElement) return null;
|
||||
this.contentElement.style.overflowY = 'auto';
|
||||
const state = getImageInspectorState(this);
|
||||
const workflow = this.getCurrentWorkflow?.() || null;
|
||||
const shouldOpenTransfer = !this.isMetadataTransferCurrentWorkflow(state, workflow);
|
||||
this.renderImageMetadataShell();
|
||||
void this.openMetadataTransfer();
|
||||
if (shouldOpenTransfer) void this.openMetadataTransfer();
|
||||
return null;
|
||||
},
|
||||
|
||||
@@ -244,7 +241,7 @@ export const imageMetadataMethods = {
|
||||
if (action === 'choose-file') fileInput?.click();
|
||||
if (action === 'clear') this.clearImageMetadata();
|
||||
if (action === 'clear-transfer-targets') this.clearMetadataTransferTargets();
|
||||
if (action === 'retry-transfer-targets') void this.openMetadataTransfer();
|
||||
if (action === 'retry-transfer-targets') void this.openMetadataTransfer({ force: true });
|
||||
if (action === 'apply-transfer') void this.applyMetadataTransfer();
|
||||
if (action === 'analyze-paste') {
|
||||
const textarea = root.querySelector('.mr-image-inspector-textarea');
|
||||
@@ -275,14 +272,14 @@ export const imageMetadataMethods = {
|
||||
}
|
||||
if (input.matches?.('[data-image-transfer-mode]')) {
|
||||
const state = getImageInspectorState(this);
|
||||
if (state.transfer) state.transfer.mode = normalizeTransferMode(input.value);
|
||||
if (state.transfer) state.transfer.mode = this.setMetadataTransferMode(input.value);
|
||||
this.renderImageMetadataResult();
|
||||
return;
|
||||
}
|
||||
if (input.matches?.('[data-image-transfer-activity-scope]')) {
|
||||
const state = getImageInspectorState(this);
|
||||
if (state.transfer) {
|
||||
state.transfer.activityScope = normalizeTransferActivityScope(input.value);
|
||||
state.transfer.activityScope = this.setMetadataTransferActivityScope(input.value);
|
||||
}
|
||||
this.renderImageMetadataResult();
|
||||
return;
|
||||
@@ -681,18 +678,22 @@ export const imageMetadataMethods = {
|
||||
},
|
||||
|
||||
getMetadataTransferModelInteractionAttrs(model = {}, contextScope = 'loaded_model', label = '') {
|
||||
const modelPath = String(
|
||||
model?.resolved_path
|
||||
|| model?.path
|
||||
|| model?.full_path
|
||||
|| ''
|
||||
).trim();
|
||||
if (model?.exists !== true || !modelPath) return '';
|
||||
if (!isExistingResolvedModel(model)) return '';
|
||||
|
||||
const contextModel = {
|
||||
...model,
|
||||
context_scope: contextScope,
|
||||
};
|
||||
let contextModel;
|
||||
if (contextScope === 'local_model') {
|
||||
contextModel = toResolverContextModel(model);
|
||||
} else if (
|
||||
contextScope === 'loaded_model'
|
||||
&& typeof this.getLoadedModelContext === 'function'
|
||||
) {
|
||||
contextModel = this.getLoadedModelContext(model);
|
||||
} else {
|
||||
contextModel = {
|
||||
...model,
|
||||
context_scope: contextScope,
|
||||
};
|
||||
}
|
||||
const contextMenuAttrs = typeof this.getContextMenuAttrs === 'function'
|
||||
? this.getContextMenuAttrs(contextModel)
|
||||
: '';
|
||||
@@ -702,6 +703,27 @@ export const imageMetadataMethods = {
|
||||
return `${contextMenuAttrs}${previewTooltipAttrs}`;
|
||||
},
|
||||
|
||||
getMetadataTransferPreferences() {
|
||||
return {
|
||||
mode: normalizeTransferMode(safeStorage.getItem(METADATA_TRANSFER_MODE_STORAGE_KEY)),
|
||||
activityScope: normalizeTransferActivityScope(
|
||||
safeStorage.getItem(METADATA_TRANSFER_ACTIVITY_SCOPE_STORAGE_KEY)
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
setMetadataTransferMode(value) {
|
||||
const mode = normalizeTransferMode(value);
|
||||
safeStorage.setItem(METADATA_TRANSFER_MODE_STORAGE_KEY, mode);
|
||||
return mode;
|
||||
},
|
||||
|
||||
setMetadataTransferActivityScope(value) {
|
||||
const activityScope = normalizeTransferActivityScope(value);
|
||||
safeStorage.setItem(METADATA_TRANSFER_ACTIVITY_SCOPE_STORAGE_KEY, activityScope);
|
||||
return activityScope;
|
||||
},
|
||||
|
||||
getMetadataTransferSourceSelectionKey(ref = {}) {
|
||||
return String(
|
||||
ref.transferKey
|
||||
@@ -1378,13 +1400,14 @@ export const imageMetadataMethods = {
|
||||
`;
|
||||
},
|
||||
|
||||
async openMetadataTransfer() {
|
||||
async openMetadataTransfer({ force = false } = {}) {
|
||||
const state = getImageInspectorState(this);
|
||||
const imageRequestToken = state.requestToken;
|
||||
const workflow = this.getCurrentWorkflow?.();
|
||||
const workflowSignature = workflow
|
||||
? (this.getWorkflowSignature?.(workflow) || '')
|
||||
: '';
|
||||
if (workflow) this.syncWorkflowScopedQueue?.(workflow);
|
||||
const previousTransfer = state.transfer;
|
||||
const preserveSelection = Boolean(
|
||||
workflowSignature
|
||||
@@ -1392,6 +1415,7 @@ export const imageMetadataMethods = {
|
||||
&& previousTransfer.workflowSignature === workflowSignature
|
||||
);
|
||||
const previousSourceSelections = previousTransfer?.sourceSelections;
|
||||
const savedPreferences = this.getMetadataTransferPreferences();
|
||||
const sourceSelections = preserveSelection
|
||||
? previousSourceSelections instanceof Map
|
||||
? Object.fromEntries(previousSourceSelections)
|
||||
@@ -1405,8 +1429,10 @@ export const imageMetadataMethods = {
|
||||
loading: Boolean(workflow),
|
||||
applying: false,
|
||||
error: '',
|
||||
mode: normalizeTransferMode(previousTransfer?.mode),
|
||||
activityScope: normalizeTransferActivityScope(previousTransfer?.activityScope),
|
||||
mode: normalizeTransferMode(previousTransfer?.mode ?? savedPreferences.mode),
|
||||
activityScope: normalizeTransferActivityScope(
|
||||
previousTransfer?.activityScope ?? savedPreferences.activityScope
|
||||
),
|
||||
requestToken: token,
|
||||
workflowSignature,
|
||||
targetGroups: [],
|
||||
@@ -1428,18 +1454,30 @@ export const imageMetadataMethods = {
|
||||
}
|
||||
|
||||
try {
|
||||
const targetModels = await this.fetchJson(
|
||||
'/model_resolver/loaded',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ workflow }),
|
||||
},
|
||||
'Scan current workflow for transfer'
|
||||
const cachedTargetModels = this.getCachedLoadedModelsForSignature?.(
|
||||
workflowSignature,
|
||||
{ force },
|
||||
);
|
||||
let targetModels = cachedTargetModels;
|
||||
if (!targetModels) {
|
||||
targetModels = await this.fetchJson(
|
||||
'/model_resolver/loaded',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ workflow }),
|
||||
},
|
||||
'Scan current workflow for transfer'
|
||||
);
|
||||
}
|
||||
if (
|
||||
state.requestToken !== imageRequestToken
|
||||
|| state.transfer?.requestToken !== token
|
||||
) return null;
|
||||
if (!cachedTargetModels && workflowSignature) {
|
||||
this.cachedLoadedModelsSignature = workflowSignature;
|
||||
this.cachedLoadedModelsData = targetModels;
|
||||
this.saveLoadedModelsCacheForActiveWorkflow?.();
|
||||
}
|
||||
state.transfer.loading = false;
|
||||
state.transfer.targetModels = targetModels;
|
||||
state.transfer.targetGroups = this.getMetadataTransferTargetGroups(targetModels);
|
||||
|
||||
@@ -120,14 +120,13 @@ export const tabsLoadedMethods = {
|
||||
this._loadedModelsLoadToken = loadToken;
|
||||
|
||||
const workflowSignature = this.getWorkflowSignature(workflow);
|
||||
if (
|
||||
!force &&
|
||||
workflowSignature &&
|
||||
this.cachedLoadedModelsSignature === workflowSignature &&
|
||||
this.cachedLoadedModelsData
|
||||
) {
|
||||
const cachedLoadedModels = this.getCachedLoadedModelsForSignature?.(
|
||||
workflowSignature,
|
||||
{ force },
|
||||
);
|
||||
if (cachedLoadedModels) {
|
||||
if (shouldRenderLoadedModels()) {
|
||||
this.displayLoadedModels(this.contentElement, this.cachedLoadedModelsData);
|
||||
this.displayLoadedModels(this.contentElement, cachedLoadedModels);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user