frontend: add Model Resolver node context menu action and workflow selection support
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
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 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 buildModelResolverNodeMenu(models = [], handlers = {}, options = {}) {
|
||||
const resolvedModels = models.filter(isExistingResolvedModel);
|
||||
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',
|
||||
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',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user