Files
willmiao-ComfyUI-Lora-Manager/static/js/components/ContextMenu/WorkflowContextMenu.js
T
Will Miao 720440507c feat: add opt-in workflow library management
Add a Workflows page that manages ComfyUI workflow JSON files as a
first-class library alongside loras/checkpoints/embeddings:

- opt-in via the new 'Workflow Management' settings toggle (default off)
- dedicated workflow folder roots with sidecar/preview support, separate
  from ComfyUI's own workflow storage; 'Send to ComfyUI' loads a workflow
  straight into the graph
- workflow scanner parses the graph document (UI/API format sniffing,
  node classes, model/civitai refs) and persists the analysis in the
  SQLite model cache
- settings UI: workflow folder paths editor in the Library tab and the
  standalone Model Paths page

Fixes from sandbox E2E verification:

- include the workflow scanner in startup initialization so the page no
  longer hangs on 'initializing' when enabled without folders
- add the missing workflows entry to the frontend page-state map (page
  init crashed and the sidebar fell back to the loras tree without it)
- project workflow analysis fields through the scanner cache entry and
  persist them in the model cache (new columns with auto-migration)
- keep symlinked paths intact in the directory picker (abspath instead
  of resolve), so stored library paths match what the user sees on disk

Also rebuilds the vue-widgets bundle to sync it with web/comfyui/utils.js.
2026-10-28 11:45:01 +08:00

87 lines
3.2 KiB
JavaScript

import { BaseContextMenu } from './BaseContextMenu.js';
import { ModelContextMenuMixin } from './ModelContextMenuMixin.js';
import { getModelApiClient, resetAndReload } from '../../api/modelApiFactory.js';
import { moveManager } from '../../managers/MoveManager.js';
import { showDeleteModal, showExcludeModal } from '../../utils/modalUtils.js';
import { showToast } from '../../utils/uiHelpers.js';
export class WorkflowContextMenu extends BaseContextMenu {
constructor() {
super('workflowContextMenu', '.model-card');
this.nsfwSelector = document.getElementById('nsfwLevelSelector');
this.modelType = 'workflows';
this.resetAndReload = resetAndReload;
this.initNSFWSelector();
}
// Implementation needed by the mixin
async saveModelMetadata(filePath, data) {
return getModelApiClient().saveModelMetadata(filePath, data);
}
showMenu(x, y, card) {
super.showMenu(x, y, card);
this.updateExcludeMenuItem();
}
handleMenuAction(action) {
// First try to handle with common actions
if (ModelContextMenuMixin.handleCommonMenuActions.call(this, action)) {
return;
}
const apiClient = getModelApiClient();
// Otherwise handle workflow-specific actions
switch(action) {
case 'details':
// Show workflow details
this.currentCard.click();
break;
case 'send-to-comfy':
this.sendToComfy(this.currentCard.dataset.filepath);
break;
case 'replace-preview':
// Add new action for replacing preview images
apiClient.replaceModelPreview(this.currentCard.dataset.filepath);
break;
case 'delete':
showDeleteModal(this.currentCard.dataset.filepath);
break;
case 'copyname':
// Copy workflow name
if (this.currentCard.querySelector('.fa-copy')) {
this.currentCard.querySelector('.fa-copy').click();
}
break;
case 'refresh-metadata':
// Refresh metadata from CivitAI
apiClient.refreshSingleModelMetadata(this.currentCard.dataset.filepath);
break;
case 'move':
moveManager.showMoveModal(this.currentCard.dataset.filepath);
break;
case 'exclude':
showExcludeModal(this.currentCard.dataset.filepath);
break;
case 'restore':
this.restoreExcludedModel(this.currentCard.dataset.filepath);
break;
}
}
async sendToComfy(filePath) {
try {
await getModelApiClient().sendToComfy(filePath);
showToast('workflows.sendToComfy.success', {}, 'success');
} catch (error) {
console.error('Failed to send workflow to ComfyUI:', error);
showToast('workflows.sendToComfy.failed', { message: error.message }, 'error');
}
}
}
// Mix in shared methods
Object.assign(WorkflowContextMenu.prototype, ModelContextMenuMixin);