219 lines
6.9 KiB
JavaScript
219 lines
6.9 KiB
JavaScript
function normalizeIdentity(value) {
|
|
return String(value ?? '').trim().replaceAll('\\', '/').toLowerCase();
|
|
}
|
|
|
|
function getResolvedModelPath(model = {}) {
|
|
return String(model.full_path || model.path || model.resolved_path || '').trim();
|
|
}
|
|
|
|
function getResolvedModelFilename(model = {}) {
|
|
const source = String(
|
|
model.name
|
|
|| model.filename
|
|
|| model.original_path
|
|
|| model.relative_path
|
|
|| getResolvedModelPath(model)
|
|
|| 'Resolved model'
|
|
);
|
|
return source.split(/[\\/]/).pop() || source;
|
|
}
|
|
|
|
function getResolvedModelCategoryLabel(model = {}, formatCategory = null) {
|
|
const category = String(model.category || 'unknown').trim() || 'unknown';
|
|
const formatted = typeof formatCategory === 'function'
|
|
? formatCategory(category)
|
|
: category.replaceAll('_', ' ');
|
|
return String(formatted || category).toUpperCase();
|
|
}
|
|
|
|
function getResolvedModelMenuLabel(model = {}, formatCategory = null) {
|
|
const category = getResolvedModelCategoryLabel(model, formatCategory);
|
|
const widgetName = String(model.widget_name || '').trim();
|
|
const filename = getResolvedModelFilename(model);
|
|
const slot = widgetName ? `${category} · ${widgetName}` : category;
|
|
return `${slot} — ${filename}`;
|
|
}
|
|
|
|
function getWidgetChoiceValue(choice) {
|
|
if (choice && typeof choice === 'object') {
|
|
return choice.value ?? choice.name ?? choice.label ?? '';
|
|
}
|
|
return choice;
|
|
}
|
|
|
|
function isCurrentWidgetSelection(widget, value) {
|
|
const normalizedValue = normalizeIdentity(value);
|
|
if (!normalizedValue || ['none', 'null', 'undefined'].includes(normalizedValue)) {
|
|
return false;
|
|
}
|
|
|
|
const choices = widget?.options?.values;
|
|
if (!Array.isArray(choices) || !choices.length) {
|
|
return true;
|
|
}
|
|
return choices.some(choice => (
|
|
normalizeIdentity(getWidgetChoiceValue(choice)) === normalizedValue
|
|
));
|
|
}
|
|
|
|
function createModelActions(model, handlers = {}) {
|
|
return [
|
|
{
|
|
content: 'Show in Model Resolver',
|
|
callback: () => handlers.showInResolver?.(model),
|
|
},
|
|
{
|
|
content: 'Show info',
|
|
callback: () => handlers.showInfo?.(model),
|
|
},
|
|
{
|
|
content: 'Open containing folder',
|
|
callback: () => handlers.openContainingFolder?.(model),
|
|
},
|
|
];
|
|
}
|
|
|
|
export function isExistingResolvedModel(model = {}) {
|
|
return model.exists === true && Boolean(getResolvedModelPath(model));
|
|
}
|
|
|
|
export function matchesWorkflowModelReference(model = {}, reference = {}) {
|
|
if (String(model.node_id ?? '') !== String(reference.node_id ?? '')) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
reference.widget_index !== undefined
|
|
&& reference.widget_index !== null
|
|
&& String(model.widget_index ?? '') !== String(reference.widget_index)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
reference.is_top_level !== undefined
|
|
&& reference.is_top_level !== null
|
|
&& Boolean(model.is_top_level) !== Boolean(reference.is_top_level)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
reference.subgraph_id
|
|
&& String(model.subgraph_id || '') !== String(reference.subgraph_id)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (reference.original_path) {
|
|
return normalizeIdentity(model.original_path) === normalizeIdentity(reference.original_path);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
export function getResolvedModelsForNode(data = {}, nodeId, scope = {}) {
|
|
const nodeReference = {
|
|
node_id: nodeId,
|
|
is_top_level: scope.is_top_level,
|
|
subgraph_id: scope.subgraph_id,
|
|
};
|
|
|
|
return (data.resolved_models || [])
|
|
.filter(isExistingResolvedModel)
|
|
.filter(model => matchesWorkflowModelReference(model, nodeReference))
|
|
.sort((left, right) => {
|
|
const widgetOrder = Number(left.widget_index ?? 0) - Number(right.widget_index ?? 0);
|
|
if (widgetOrder) return widgetOrder;
|
|
return getResolvedModelMenuLabel(left).localeCompare(getResolvedModelMenuLabel(right));
|
|
});
|
|
}
|
|
|
|
export function getImmediateModelsForNode(data = {}, node = {}, scope = {}) {
|
|
const references = [
|
|
...(data.resolved_models || []),
|
|
...(data.missing_models || []),
|
|
].filter(model => (
|
|
(!model.node_type || !node?.type || model.node_type === node.type)
|
|
&& matchesWorkflowModelReference(model, {
|
|
node_id: node?.id,
|
|
is_top_level: scope.is_top_level,
|
|
subgraph_id: scope.subgraph_id,
|
|
})
|
|
));
|
|
const modelsByWidget = new Map();
|
|
|
|
for (const reference of references) {
|
|
const widgetIndex = Number(reference.widget_index);
|
|
if (!Number.isInteger(widgetIndex) || modelsByWidget.has(widgetIndex)) continue;
|
|
|
|
const widget = node?.widgets?.[widgetIndex];
|
|
const currentValue = widget?.value;
|
|
if (typeof currentValue !== 'string') continue;
|
|
if (!isCurrentWidgetSelection(widget, currentValue)) continue;
|
|
|
|
if (normalizeIdentity(currentValue) === normalizeIdentity(reference.original_path)) {
|
|
if (isExistingResolvedModel(reference)) {
|
|
modelsByWidget.set(widgetIndex, reference);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
modelsByWidget.set(widgetIndex, {
|
|
...reference,
|
|
name: String(currentValue),
|
|
filename: getResolvedModelFilename({ original_path: currentValue }),
|
|
original_path: String(currentValue),
|
|
full_path: '',
|
|
path: '',
|
|
resolved_path: '',
|
|
exists: false,
|
|
resolution_pending: true,
|
|
});
|
|
}
|
|
|
|
return [...modelsByWidget.values()].sort((left, right) => (
|
|
Number(left.widget_index ?? 0) - Number(right.widget_index ?? 0)
|
|
));
|
|
}
|
|
|
|
export function buildModelResolverNodeMenu(models = [], handlers = {}, options = {}) {
|
|
const resolvedModels = models.filter(model => (
|
|
isExistingResolvedModel(model) || model.resolution_pending === true
|
|
));
|
|
if (!resolvedModels.length) return null;
|
|
|
|
const submenuOptions = resolvedModels.length === 1
|
|
? createModelActions(resolvedModels[0], handlers)
|
|
: resolvedModels.map(model => ({
|
|
title: getResolvedModelMenuLabel(model, options.formatCategory),
|
|
has_submenu: true,
|
|
submenu: {
|
|
options: createModelActions(model, handlers),
|
|
},
|
|
}));
|
|
|
|
return {
|
|
title: 'Model Resolver',
|
|
className: 'mdi mdi-link-variant mr-model-resolver-node-menu',
|
|
has_submenu: true,
|
|
submenu: {
|
|
options: submenuOptions,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function toResolverContextModel(model = {}) {
|
|
const path = getResolvedModelPath(model);
|
|
const filename = getResolvedModelFilename(model);
|
|
return {
|
|
...model,
|
|
name: model.name || filename,
|
|
filename,
|
|
path,
|
|
resolved_path: path,
|
|
relative_path: model.relative_path || model.original_path || filename,
|
|
context_scope: 'local_model',
|
|
};
|
|
}
|