feat: add active workflow context to download queue items and download data

Track the active ComfyUI workflow name, route key, tab identity, and
signature on every download-info object, then expose those details in
the download queue render. Add a contextual 'Switch to Workflow'
menu action and helpers for locating and activating the correct
workflow tab from a stored route/label/tab identity.
This commit is contained in:
Dariusz L
2026-06-19 20:54:37 +02:00
parent 66bbaedf66
commit 78697ffc49
4 changed files with 642 additions and 26 deletions
+547 -20
View File
@@ -196,28 +196,549 @@ export const queueMethods = {
const entries = Object.entries(this.activeDownloads || {});
if (!entries.length) return [];
const workflowKey = this.getWorkflowScopedQueueKey?.() || '';
const visibleMissingKeys = new Set(
(this.missingModels || [])
.filter(Boolean)
.map(missing => this.getDownloadStateKey?.(missing) || this.getMissingModelKey(missing))
);
return entries
.filter(([, info]) => {
if (!info?.missing) return false;
if (workflowKey && info.workflowKey) {
return info.workflowKey === workflowKey;
}
if (visibleMissingKeys.size) {
const missingKey = this.getDownloadStateKey?.(info.missing) || this.getMissingModelKey(info.missing);
return visibleMissingKeys.has(missingKey);
}
return true;
})
.filter(([, info]) => Boolean(info?.missing))
.map(([downloadId, info]) => ({ downloadId, info }));
},
getWorkflowLabelFromRouteKey(routeKey = '') {
const rawRoute = String(routeKey || '').split('\n')[0].trim();
if (!rawRoute) return '';
const decodeLabel = (value = '') => {
try {
return decodeURIComponent(String(value || '').replace(/\+/g, ' '));
} catch (error) {
return String(value || '');
}
};
const cleanRoutePart = (value = '') => this.cleanWorkflowLabel?.(decodeLabel(value)) || decodeLabel(value).trim();
const commonRouteParts = new Set(['', '#', '/', 'workflow', 'workflows', 'view', 'tab', 'tabs', 'graph', 'graphs']);
try {
const base = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : 'http://localhost';
const url = new URL(rawRoute, base);
const candidates = [
url.searchParams.get('workflow'),
url.searchParams.get('workflowName'),
url.searchParams.get('workflow_name'),
url.searchParams.get('name'),
url.searchParams.get('filename'),
url.searchParams.get('file'),
url.hash ? new URLSearchParams(url.hash.includes('?') ? url.hash.slice(url.hash.indexOf('?') + 1) : '').get('workflow') : '',
url.hash ? new URLSearchParams(url.hash.includes('?') ? url.hash.slice(url.hash.indexOf('?') + 1) : '').get('workflowName') : '',
url.hash ? new URLSearchParams(url.hash.includes('?') ? url.hash.slice(url.hash.indexOf('?') + 1) : '').get('name') : ''
];
const found = candidates.map(cleanRoutePart).find(Boolean);
if (found) return found;
} catch (error) { /* fall through to manual route parsing */ }
try {
const hash = rawRoute.startsWith('#') ? rawRoute.slice(1) : rawRoute;
const paramsText = hash.includes('?') ? hash.slice(hash.indexOf('?') + 1) : hash.replace(/^\?/, '');
const params = new URLSearchParams(paramsText);
const candidates = [
params.get('workflow'),
params.get('workflowName'),
params.get('workflow_name'),
params.get('name'),
params.get('filename'),
params.get('file'),
params.get('workflowId'),
params.get('workflow_id'),
params.get('tab'),
params.get('id')
];
const found = candidates.map(cleanRoutePart).find(Boolean);
if (found) return found;
} catch (error) { /* fall through to readable route */ }
const withoutQuery = rawRoute.split('?')[0].split('&')[0];
const parts = withoutQuery
.replace(/^#/, '')
.split(/[\\/]/)
.map(part => cleanRoutePart(part))
.filter(part => !commonRouteParts.has(part.toLowerCase()));
if (parts.length) return parts[parts.length - 1];
return cleanRoutePart(rawRoute
.replace(/^#\/?/, '')
.replace(/^\?/, '')
.replace(/[?&](workflow|workflowName|workflow_name|name|filename|file|workflowId|workflow_id|tab|id)=/i, ' ')
.replace(/[=&]+/g, ' '));
},
cleanWorkflowLabel(label = '') {
return String(label || '')
.replace(/[\r\n\t]+/g, ' ')
.replace(/\s+/g, ' ')
.replace(/^[*•\s]+|[×✕✖*•\s]+$/g, '')
.trim();
},
getWorkflowTabActiveSelectors() {
return [
'[data-workflow-name][aria-selected="true"]',
'[data-workflow-name].active',
'[data-workflow-name].selected',
'[data-workflow-id][aria-selected="true"]',
'[data-workflow-id][data-active="true"]',
'[data-workflow-id].active',
'[data-workflow-id].selected',
'[data-tab-id*="workflow" i][aria-selected="true"]',
'[data-tab-id*="workflow" i][data-active="true"]',
'[class*="workflow" i][class*="tab" i].active',
'[class*="workflow" i][class*="tab" i].selected',
'[class*="workflow" i][class*="tab" i].p-highlight',
'[class*="workflow" i][class*="tab" i].p-tab-active',
'[class*="tab" i][class*="workflow" i].active',
'[class*="tab" i][class*="workflow" i].selected',
'[class*="tab" i][class*="workflow" i].p-highlight',
'[class*="tab" i][class*="workflow" i].p-tab-active',
'[role="tab"][aria-selected="true"]',
'[role="tab"][data-active="true"]',
'[role="tab"].active',
'[role="tab"].selected',
'[role="tab"].p-highlight',
'[role="tab"].p-tab-active'
];
},
getWorkflowTabSearchSelectors() {
return [
'[data-workflow-name]',
'[data-workflow-id]',
'[data-tab-id*="workflow" i]',
'[aria-controls*="workflow" i]',
'[class*="workflow" i][class*="tab" i]',
'[class*="tab" i][class*="workflow" i]',
'[role="tab"]'
];
},
getWorkflowTabElementClassText(element) {
return String(element?.getAttribute?.('class') || '');
},
getWorkflowTabElementIdentity(element) {
if (!element) return null;
const workflowTabId = String(
element.getAttribute?.('data-workflow-id')
|| element.dataset?.workflowId
|| element.getAttribute?.('data-tab-id')
|| element.dataset?.tabId
|| ''
).trim();
const workflowTabName = String(
element.getAttribute?.('data-workflow-name')
|| element.dataset?.workflowName
|| element.getAttribute?.('data-tab-title')
|| element.dataset?.tabTitle
|| ''
).trim();
const workflowTabAriaControls = String(element.getAttribute?.('aria-controls') || '').trim();
const title = String(element.getAttribute?.('title') || '').trim();
const ariaLabel = String(element.getAttribute?.('aria-label') || '').trim();
const workflowTabText = this.cleanWorkflowLabel(element.textContent || '');
const workflowLabel = this.cleanWorkflowLabel(workflowTabName || title || ariaLabel || workflowTabText);
return {
element,
workflowLabel,
workflowTabId,
workflowTabName,
workflowTabAriaControls,
workflowTabText,
workflowTabTitle: title,
workflowTabAriaLabel: ariaLabel
};
},
isLikelyWorkflowTabElement(element, label = '') {
if (!element) return false;
if (element.closest?.('#model-resolver-modal, .model-resolver-backdrop')) return false;
const attrText = [
element.getAttribute?.('data-workflow-id') || '',
element.getAttribute?.('data-workflow-name') || '',
element.getAttribute?.('data-tab-id') || '',
element.getAttribute?.('aria-controls') || '',
element.getAttribute?.('title') || '',
element.getAttribute?.('aria-label') || '',
this.getWorkflowTabElementClassText(element)
].join(' ');
if (/workflow|untitled|unsaved|\.json/i.test(`${label} ${attrText}`)) return true;
if (element.hasAttribute?.('data-workflow-id') || element.hasAttribute?.('data-workflow-name')) return true;
if (element.matches?.('[role="tab"]')) {
const normalized = this.cleanWorkflowLabel(label).toLowerCase();
if (!normalized) return false;
if (/^(queued|downloads|missing|loaded|options|settings|queue|history|logs|console|manager)$/i.test(normalized)) {
return false;
}
return true;
}
return false;
},
isWorkflowTabElementActive(element) {
if (!element) return false;
const classList = element.classList;
return element.getAttribute?.('aria-selected') === 'true'
|| element.getAttribute?.('data-active') === 'true'
|| classList?.contains('active')
|| classList?.contains('selected')
|| classList?.contains('p-highlight')
|| classList?.contains('p-tab-active');
},
findActiveWorkflowTabElement() {
if (typeof document === 'undefined') return null;
for (const selector of this.getWorkflowTabActiveSelectors()) {
let elements = [];
try {
elements = Array.from(document.querySelectorAll(selector));
} catch (error) {
continue;
}
for (const element of elements) {
const identity = this.getWorkflowTabElementIdentity(element);
if (!identity?.workflowLabel && !identity?.workflowTabId && !identity?.workflowTabName) continue;
if (!this.isLikelyWorkflowTabElement(element, identity.workflowLabel)) continue;
return identity;
}
}
return null;
},
getActiveWorkflowTabContext() {
const activeTab = this.findActiveWorkflowTabElement?.();
const routeKey = this.getActiveWorkflowRouteKey?.() || this.activeWorkflowRouteKey || '';
const workflow = this.getCurrentWorkflow?.();
const workflowSignature = this.activeWorkflowSignature || (workflow ? this.getWorkflowSignature?.(workflow) : '') || '';
const fallbackLabel = this.getWorkflowLabelFromComfyState?.()
|| activeTab?.workflowLabel
|| this.getWorkflowLabelFromRouteKey?.(routeKey)
|| 'Current workflow';
return {
workflowRouteKey: routeKey,
workflowSignature,
workflowLabel: activeTab?.workflowLabel || fallbackLabel,
workflowTabId: activeTab?.workflowTabId || '',
workflowTabName: activeTab?.workflowTabName || '',
workflowTabAriaControls: activeTab?.workflowTabAriaControls || '',
workflowTabText: activeTab?.workflowTabText || ''
};
},
getWorkflowLabelFromComfyState() {
const candidates = [];
try {
const activeWorkflow = app?.workflowManager?.activeWorkflow
|| app?.workflowManager?.workflow
|| app?.ui?.workflow?.activeWorkflow
|| app?.canvas?.workflow;
candidates.push(
activeWorkflow?.name,
activeWorkflow?.title,
activeWorkflow?.filename,
activeWorkflow?.path?.split?.(/[\\/]/)?.pop?.(),
activeWorkflow?.workflow?.name,
activeWorkflow?.workflow?.title
);
} catch (error) { /* ignore unavailable Comfy internals */ }
for (const candidate of candidates) {
const label = this.cleanWorkflowLabel(candidate);
if (label) return label;
}
return '';
},
getWorkflowLabelFromActiveTabElement() {
return this.findActiveWorkflowTabElement?.()?.workflowLabel || '';
},
getActiveWorkflowDownloadLabel() {
const stateLabel = this.getWorkflowLabelFromComfyState?.();
if (stateLabel) return stateLabel;
const tabLabel = this.getWorkflowLabelFromActiveTabElement?.();
if (tabLabel) return tabLabel;
const routeKey = this.getActiveWorkflowRouteKey?.() || this.activeWorkflowRouteKey || '';
const routeLabel = this.getWorkflowLabelFromRouteKey(routeKey);
return routeLabel || 'Current workflow';
},
getDownloadWorkflowLabel(info = {}) {
return info.workflowLabel
|| this.getWorkflowLabelFromRouteKey(info.workflowRouteKey || '')
|| this.getWorkflowLabelFromRouteKey(info.workflowKey || '')
|| this.getActiveWorkflowDownloadLabel?.()
|| 'Unknown workflow';
},
getWorkflowContextRouteKey(context = {}) {
const direct = context.workflow_route_key || context.workflowRouteKey || '';
if (direct) return String(direct).split('\n')[0].trim();
const key = context.workflow_key || context.workflowKey || '';
return String(key || '').split('\n')[0].trim();
},
isUsableWorkflowRouteKey(routeKey = '') {
const normalized = String(routeKey || '').trim();
return Boolean(normalized && normalized !== '#');
},
getWorkflowContextSignature(context = {}) {
const direct = context.workflow_signature || context.workflowSignature || '';
if (direct) return String(direct).trim();
const key = context.workflow_key || context.workflowKey || '';
const parts = String(key || '').split('\n').map(part => part.trim()).filter(Boolean);
return parts.length > 1 ? parts.slice(1).join('\n') : '';
},
getWorkflowContextLabel(context = {}) {
return this.cleanWorkflowLabel(
context.workflow_label
|| context.workflowLabel
|| context.workflow_tab_name
|| context.workflowTabName
|| context.workflow_tab_text
|| context.workflowTabText
|| this.getWorkflowLabelFromRouteKey?.(this.getWorkflowContextRouteKey(context))
|| ''
);
},
getDownloadWorkflowRuntimeContext(context = {}) {
const downloadId = context.download_id || context.downloadId || '';
const info = downloadId ? this.activeDownloads?.[downloadId] : null;
if (!info) return context;
return {
...context,
workflowRouteKey: context.workflowRouteKey || context.workflow_route_key || info.workflowRouteKey || '',
workflow_route_key: context.workflow_route_key || context.workflowRouteKey || info.workflowRouteKey || '',
workflowKey: context.workflowKey || info.workflowKey || '',
workflowLabel: context.workflowLabel || context.workflow_label || info.workflowLabel || '',
workflow_label: context.workflow_label || context.workflowLabel || info.workflowLabel || '',
workflowSignature: context.workflowSignature || info.workflowSignature || '',
workflowTabId: context.workflowTabId || context.workflow_tab_id || info.workflowTabId || '',
workflow_tab_id: context.workflow_tab_id || context.workflowTabId || info.workflowTabId || '',
workflowTabName: context.workflowTabName || context.workflow_tab_name || info.workflowTabName || '',
workflow_tab_name: context.workflow_tab_name || context.workflowTabName || info.workflowTabName || '',
workflowTabAriaControls: context.workflowTabAriaControls || context.workflow_tab_aria_controls || info.workflowTabAriaControls || '',
workflow_tab_aria_controls: context.workflow_tab_aria_controls || context.workflowTabAriaControls || info.workflowTabAriaControls || '',
workflowTabText: context.workflowTabText || context.workflow_tab_text || info.workflowTabText || '',
workflow_tab_text: context.workflow_tab_text || context.workflowTabText || info.workflowTabText || ''
};
},
canSwitchToDownloadWorkflow(context = {}) {
context = this.getDownloadWorkflowRuntimeContext(context);
const label = this.getWorkflowContextLabel(context);
const hasSpecificLabel = Boolean(label && !/^(current workflow|unknown workflow|workflow)$/i.test(label));
return Boolean(
this.isUsableWorkflowRouteKey(this.getWorkflowContextRouteKey(context))
|| this.getWorkflowContextSignature(context)
|| hasSpecificLabel
|| context.workflow_tab_id
|| context.workflowTabId
|| context.workflow_tab_aria_controls
|| context.workflowTabAriaControls
);
},
isDownloadWorkflowActive(context = {}) {
context = this.getDownloadWorkflowRuntimeContext(context);
const routeKey = this.getWorkflowContextRouteKey(context);
const currentRoute = this.getActiveWorkflowRouteKey?.() || this.activeWorkflowRouteKey || '';
if (this.isUsableWorkflowRouteKey(routeKey) && currentRoute && routeKey === currentRoute) return true;
const signature = this.getWorkflowContextSignature(context);
const currentSignature = this.activeWorkflowSignature || this.getWorkflowSignature?.(this.getCurrentWorkflow?.()) || '';
if (signature && currentSignature && signature === currentSignature) return true;
const activeTab = this.findActiveWorkflowTabElement?.();
if (activeTab) {
const targetTabId = String(context.workflow_tab_id || context.workflowTabId || '').trim();
const targetTabName = String(context.workflow_tab_name || context.workflowTabName || '').trim();
const targetAriaControls = String(context.workflow_tab_aria_controls || context.workflowTabAriaControls || '').trim();
if (targetTabId && targetTabId === activeTab.workflowTabId) return true;
if (targetTabName && targetTabName === activeTab.workflowTabName) return true;
if (targetAriaControls && targetAriaControls === activeTab.workflowTabAriaControls) return true;
}
const label = this.cleanWorkflowLabel(this.getWorkflowContextLabel(context)).toLowerCase();
const activeLabel = this.cleanWorkflowLabel(this.getActiveWorkflowDownloadLabel?.()).toLowerCase();
return Boolean(label && activeLabel && label === activeLabel);
},
getDownloadQueueContext(progress = {}, info = {}, workflowLabel = '', downloadId = '') {
const folderContext = this.getDownloadFolderContext?.(progress, info) || null;
const missing = info?.missing || {};
const label = workflowLabel || this.getDownloadWorkflowLabel?.(info) || '';
const context = {
...(folderContext || {}),
context_scope: 'download_queue',
download_id: downloadId,
open_folder_label: folderContext ? 'Open Download Folder' : '',
name: folderContext?.name || progress?.filename || info?.filename || missing?.original_path?.split(/[\/\\]/).pop() || 'Download',
workflow_label: label,
workflow_route_key: info.workflowRouteKey || this.getWorkflowContextRouteKey(info) || '',
workflow_tab_id: info.workflowTabId || '',
workflow_tab_name: info.workflowTabName || '',
workflow_tab_aria_controls: info.workflowTabAriaControls || '',
workflow_tab_text: info.workflowTabText || '',
node_id: missing.locate_node_id ?? missing.node_id ?? '',
widget_index: missing.widget_index ?? '',
subgraph_id: missing.locate_subgraph_id || missing.subgraph_id || '',
is_top_level: missing.locate_is_top_level ?? missing.is_top_level ?? true
};
return (folderContext || this.canSwitchToDownloadWorkflow(context)) ? context : null;
},
getWorkflowTabComparisonValues(identity = {}, element = null) {
if (!identity) return [];
return [
identity.workflowLabel,
identity.workflowTabId,
identity.workflowTabName,
identity.workflowTabAriaControls,
identity.workflowTabText,
identity.workflowTabTitle,
identity.workflowTabAriaLabel,
element?.getAttribute?.('href') || '',
element?.getAttribute?.('data-route') || '',
element?.getAttribute?.('data-path') || ''
].map(value => String(value || '').trim()).filter(Boolean);
},
findWorkflowTabForDownload(context = {}) {
if (typeof document === 'undefined') return null;
context = this.getDownloadWorkflowRuntimeContext(context);
const routeKey = this.getWorkflowContextRouteKey(context);
const routeLabel = this.getWorkflowLabelFromRouteKey?.(routeKey) || '';
const labelTargets = new Set([
this.getWorkflowContextLabel(context),
routeLabel,
context.workflow_tab_text,
context.workflowTabText,
context.workflow_tab_name,
context.workflowTabName
].map(value => this.cleanWorkflowLabel(value).toLowerCase()).filter(Boolean));
const idTargets = new Set([
context.workflow_tab_id,
context.workflowTabId,
context.workflow_tab_aria_controls,
context.workflowTabAriaControls
].map(value => String(value || '').trim().toLowerCase()).filter(Boolean));
const routeTarget = String(routeKey || '').trim().toLowerCase();
const seen = new Set();
let bestMatch = null;
for (const selector of this.getWorkflowTabSearchSelectors()) {
let elements = [];
try {
elements = Array.from(document.querySelectorAll(selector));
} catch (error) {
continue;
}
for (const element of elements) {
if (!element || seen.has(element)) continue;
seen.add(element);
const identity = this.getWorkflowTabElementIdentity(element);
if (!identity || !this.isLikelyWorkflowTabElement(element, identity.workflowLabel)) continue;
const values = this.getWorkflowTabComparisonValues(identity, element);
const normalizedValues = values.map(value => this.cleanWorkflowLabel(value).toLowerCase()).filter(Boolean);
const rawValues = values.map(value => String(value || '').trim().toLowerCase()).filter(Boolean);
let score = 0;
for (const value of rawValues) {
if (idTargets.has(value)) score += 100;
if (routeTarget && value.includes(routeTarget)) score += 70;
}
for (const value of normalizedValues) {
if (labelTargets.has(value)) score += 80;
for (const target of labelTargets) {
if (target && value !== target && (value.includes(target) || target.includes(value))) score += 25;
}
}
if (this.isWorkflowTabElementActive(element)) score += 5;
if (score > (bestMatch?.score || 0)) {
bestMatch = { element, score };
}
}
}
return bestMatch?.score > 0 ? bestMatch.element : null;
},
switchToDownloadWorkflow(context = {}) {
context = this.getDownloadWorkflowRuntimeContext(context);
if (!this.canSwitchToDownloadWorkflow(context)) {
this.showNotification('No workflow information available for this download', 'error');
return false;
}
const label = this.getWorkflowContextLabel(context) || 'workflow';
if (this.isDownloadWorkflowActive(context)) {
this.showNotification(`Already on workflow: ${label}`, 'info');
return true;
}
const tab = this.findWorkflowTabForDownload(context);
if (tab) {
try {
tab.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
tab.click();
setTimeout(() => {
window.dispatchEvent?.(new Event('model-resolver-active-workflowchange'));
}, 30);
this.showNotification(`Switched to workflow: ${label}`, 'success');
return true;
} catch (error) {
console.warn('Model Resolver: failed to switch workflow tab', error);
}
}
const routeKey = this.getWorkflowContextRouteKey(context);
const currentRoute = this.getActiveWorkflowRouteKey?.() || '';
if (this.isUsableWorkflowRouteKey(routeKey) && routeKey !== currentRoute && typeof window !== 'undefined') {
try {
window.location.hash = routeKey.startsWith('#') ? routeKey : `#${routeKey.replace(/^#/, '')}`;
setTimeout(() => {
window.dispatchEvent?.(new Event('model-resolver-active-workflowchange'));
}, 30);
this.showNotification(`Switched to workflow: ${label}`, 'success');
return true;
} catch (error) {
console.warn('Model Resolver: failed to switch workflow route', error);
}
}
this.showNotification(`Could not find workflow tab: ${label}. It may be closed.`, 'error');
return false;
},
getActiveQueuePanelDownloadIds() {
return this.getActiveQueuePanelDownloads().map(({ downloadId }) => downloadId);
},
@@ -249,9 +770,14 @@ export const queueMethods = {
const sizeText = total ? `${downloaded} / ${total}` : downloaded;
const targetPath = progress.directory || info.downloadDirectory || info.downloadPath || '';
const targetLabel = targetPath ? targetPath.split(/[\/\\]/).filter(Boolean).pop() || targetPath : '';
const contextModel = this.getDownloadFolderContext?.(progress, info);
const workflowLabel = this.getDownloadWorkflowLabel?.(info) || 'Unknown workflow';
const contextModel = this.getDownloadQueueContext?.(progress, info, workflowLabel, downloadId);
const hasFolderAction = Boolean(contextModel?.folder_path || contextModel?.download_directory || contextModel?.directory || contextModel?.path || contextModel?.resolved_path);
const contextTooltip = hasFolderAction
? 'Right-click for workflow and download folder actions'
: 'Right-click to switch to workflow';
const contextData = contextModel
? ` data-model="${this.escapeHtml(encodeURIComponent(JSON.stringify(contextModel)))}" oncontextmenu="window.MLOpenContextMenu(event, this)" data-tooltip="Right-click to open download folder"`
? ` data-model="${this.escapeHtml(encodeURIComponent(JSON.stringify(contextModel)))}" oncontextmenu="window.MLOpenContextMenu(event, this)" data-tooltip="${this.escapeHtml(contextTooltip)}"`
: '';
html += `<div class="mr-queue-item mr-download-queue-item"${contextData}>`;
@@ -260,6 +786,7 @@ export const queueMethods = {
html += `<span class="mr-download-queue-status">${this.escapeHtml(statusLabel)}</span>`;
html += `</div>`;
html += `<div class="mr-queue-item-meta"><span>Model</span><code>${this.escapeHtml(nodeLabel)} #${this.escapeHtml(String(info.missing?.node_id ?? ''))}</code></div>`;
html += `<div class="mr-queue-item-meta"><span>Workflow</span><code data-tooltip="${this.escapeHtml(workflowLabel)}">${this.escapeHtml(workflowLabel)}</code></div>`;
if (category) {
html += `<div class="mr-queue-item-meta"><span>Type</span><code>${this.escapeHtml(category)}</code></div>`;
}
@@ -672,6 +672,14 @@ export const resolveDownloadMethods = {
downloadDirectory: progress.directory || info.downloadDirectory || '',
baseDirectory: info.baseDirectory || '',
sourceUrl: info.sourceUrl || '',
workflowKey: info.workflowKey || '',
workflowRouteKey: info.workflowRouteKey || '',
workflowLabel: info.workflowLabel || this.getWorkflowLabelFromRouteKey?.(info.workflowRouteKey || '') || '',
workflowSignature: info.workflowSignature || '',
workflowTabId: info.workflowTabId || '',
workflowTabName: info.workflowTabName || '',
workflowTabAriaControls: info.workflowTabAriaControls || '',
workflowTabText: info.workflowTabText || '',
progress: {
...progress,
status,
@@ -1076,7 +1084,15 @@ export const resolveDownloadMethods = {
const progressDiv = this.contentElement?.querySelector(`#${progressId}`);
const downloadBtn = this.contentElement?.querySelector(`#${this.getDownloadButtonElementId(missing)}`);
const tokens = this.getStoredTokens();
const workflowContext = this.getActiveWorkflowTabContext?.() || {};
const workflowKey = this.getWorkflowScopedQueueKey?.() || '';
const workflowRouteKey = workflowContext.workflowRouteKey || this.getActiveWorkflowRouteKey?.() || this.activeWorkflowRouteKey || '';
const workflowLabel = workflowContext.workflowLabel || this.getActiveWorkflowDownloadLabel?.() || 'Current workflow';
const workflowSignature = workflowContext.workflowSignature || this.activeWorkflowSignature || '';
const workflowTabId = workflowContext.workflowTabId || '';
const workflowTabName = workflowContext.workflowTabName || '';
const workflowTabAriaControls = workflowContext.workflowTabAriaControls || '';
const workflowTabText = workflowContext.workflowTabText || '';
try {
this.rememberDownloadSnapshotForMissing(missing, {
@@ -1087,6 +1103,14 @@ export const resolveDownloadMethods = {
downloadDirectory: '',
baseDirectory,
sourceUrl: source.url,
workflowKey,
workflowRouteKey,
workflowLabel,
workflowSignature,
workflowTabId,
workflowTabName,
workflowTabAriaControls,
workflowTabText,
progress: { status: 'starting', progress: 0, filename },
status: 'starting',
isActive: true
@@ -1157,7 +1181,14 @@ export const resolveDownloadMethods = {
downloadDirectory: data.directory || '',
baseDirectory,
sourceUrl: source.url,
workflowKey
workflowKey,
workflowRouteKey,
workflowLabel,
workflowSignature,
workflowTabId,
workflowTabName,
workflowTabAriaControls,
workflowTabText
};
const snapshot = this.rememberDownloadUiState(
downloadId,
@@ -1189,6 +1220,14 @@ export const resolveDownloadMethods = {
downloadDirectory: '',
baseDirectory,
sourceUrl: source.url,
workflowKey,
workflowRouteKey,
workflowLabel,
workflowSignature,
workflowTabId,
workflowTabName,
workflowTabAriaControls,
workflowTabText,
progress: { status: 'error', filename, error: error.message },
status: 'error',
message: error.message,
@@ -2164,7 +2203,15 @@ export const resolveDownloadMethods = {
const progressDiv = this.contentElement?.querySelector(`#${progressId}`);
const tokens = this.getStoredTokens();
const targetSelection = this.getDownloadTargetSelection(missing, category || missing.category || 'checkpoints');
const workflowContext = this.getActiveWorkflowTabContext?.() || {};
const workflowKey = this.getWorkflowScopedQueueKey?.() || '';
const workflowRouteKey = workflowContext.workflowRouteKey || this.getActiveWorkflowRouteKey?.() || this.activeWorkflowRouteKey || '';
const workflowLabel = workflowContext.workflowLabel || this.getActiveWorkflowDownloadLabel?.() || 'Current workflow';
const workflowSignature = workflowContext.workflowSignature || this.activeWorkflowSignature || '';
const workflowTabId = workflowContext.workflowTabId || '';
const workflowTabName = workflowContext.workflowTabName || '';
const workflowTabAriaControls = workflowContext.workflowTabAriaControls || '';
const workflowTabText = workflowContext.workflowTabText || '';
const resolvedPathMetadata = pathMetadata || this.getDownloadPathMetadata(missing, {
filename,
category: targetSelection.category
@@ -2197,6 +2244,14 @@ export const resolveDownloadMethods = {
downloadDirectory: '',
baseDirectory: targetSelection.baseDirectory || '',
sourceUrl: url,
workflowKey,
workflowRouteKey,
workflowLabel,
workflowSignature,
workflowTabId,
workflowTabName,
workflowTabAriaControls,
workflowTabText,
progress: { status: 'starting', progress: 0, filename },
status: 'starting',
isActive: true
@@ -2262,7 +2317,14 @@ export const resolveDownloadMethods = {
downloadDirectory: data.directory || '',
baseDirectory: targetSelection.baseDirectory || '',
sourceUrl: url,
workflowKey
workflowKey,
workflowRouteKey,
workflowLabel,
workflowSignature,
workflowTabId,
workflowTabName,
workflowTabAriaControls,
workflowTabText
};
const snapshot = this.rememberDownloadUiState(
downloadId,
@@ -2294,6 +2356,14 @@ export const resolveDownloadMethods = {
downloadDirectory: '',
baseDirectory: targetSelection.baseDirectory || '',
sourceUrl: url,
workflowKey,
workflowRouteKey,
workflowLabel,
workflowSignature,
workflowTabId,
workflowTabName,
workflowTabAriaControls,
workflowTabText,
progress: { status: 'error', filename, error: error.message },
status: 'error',
message: error.message,
+12
View File
@@ -122,6 +122,18 @@ export class ResolverManagerDialog extends ComfyDialog {
}),
$el("span", { textContent: "Open in CivitAI" })
]),
$el("div.mr-context-menu-divider.mr-context-menu-divider-workflow", {
"data-menu-divider": "workflow"
}),
$el("div.mr-context-menu-item.mr-context-menu-action-switch-workflow", {
"data-menu-action": "switchWorkflow",
onclick: () => this.handleContextMenuAction('switchWorkflow')
}, [
$el("span.mr-context-menu-item-icon", {
innerHTML: getSvgIcon('internalLink', 'currentColor', 'mr-context-menu-item-svg')
}),
$el("span", { textContent: "Switch to Workflow" })
]),
$el("div.mr-context-menu-divider.mr-context-menu-divider-folder", {
"data-menu-divider": "folder"
}),
+11 -4
View File
@@ -27,15 +27,20 @@ export const modelInfoMethods = {
const isDownloadTableContext = model?.context_scope === 'download_table';
const isDownloadFolderContext = model?.context_scope === 'download_folder';
const isDownloadRootContext = model?.context_scope === 'download_root';
const isDownloadQueueContext = model?.context_scope === 'download_queue';
const isFolderOnlyContext = isDownloadFolderContext || isDownloadRootContext;
const isSourceModelContext = !isDownloadTableContext && !isFolderOnlyContext && !isDownloadQueueContext;
const hasLocalPath = Boolean(model?.folder_path || model?.download_directory || model?.directory || model?.path || model?.resolved_path);
const showOpenFolder = !isDownloadTableContext && hasLocalPath;
this.setContextMenuItemVisible('showInfo', !isDownloadTableContext && !isFolderOnlyContext);
const showSwitchWorkflow = isDownloadQueueContext && Boolean(this.canSwitchToDownloadWorkflow?.(model));
this.setContextMenuItemVisible('showInfo', isSourceModelContext);
this.setContextMenuItemVisible('showMore', canShowMore);
this.setContextMenuItemVisible('civitai', !isDownloadTableContext && !isFolderOnlyContext);
this.setContextMenuItemVisible('civitai', isSourceModelContext);
this.setContextMenuItemVisible('switchWorkflow', showSwitchWorkflow);
this.setContextMenuItemVisible('openFolder', showOpenFolder);
this.setContextMenuDividerVisible('source', (!isDownloadTableContext && !isFolderOnlyContext) || canShowMore);
this.setContextMenuDividerVisible('folder', showOpenFolder && !isFolderOnlyContext);
this.setContextMenuDividerVisible('source', isSourceModelContext || canShowMore);
this.setContextMenuDividerVisible('workflow', showSwitchWorkflow && (isSourceModelContext || canShowMore));
this.setContextMenuDividerVisible('folder', showOpenFolder && (isSourceModelContext || canShowMore || showSwitchWorkflow));
const openFolderLabel = this.contextMenu.querySelector('.mr-context-menu-action-open-folder span:last-child');
if (openFolderLabel) {
@@ -80,6 +85,8 @@ export const modelInfoMethods = {
this.openInCivitAI(model);
} else if (action === 'openFolder') {
this.openContainingFolder(model);
} else if (action === 'switchWorkflow') {
this.switchToDownloadWorkflow(model);
} else if (action === 'showInfo') {
this.showModelInfo(model);
} else if (action === 'showMore') {