feat: add 'Send to ComfyUI' button to ModelModal and RecipeModal

- Add send button to ModelModal header for all model types (LoRA, Checkpoint, Embedding)
- Add send button to RecipeModal header for sending entire recipes
- Style buttons to match existing modal action buttons
- Add translations for all supported languages
This commit is contained in:
Will Miao
2026-03-29 20:35:08 +08:00
parent a4cb51e96c
commit 267082c712
15 changed files with 262 additions and 14 deletions

View File

@@ -1,5 +1,6 @@
import { showToast, openCivitai } from '../../utils/uiHelpers.js';
import { showToast, openCivitai, sendLoraToWorkflow, sendModelPathToWorkflow, buildLoraSyntax } from '../../utils/uiHelpers.js';
import { modalManager } from '../../managers/ModalManager.js';
import { MODEL_TYPES } from '../../api/apiConfig.js';
import {
toggleShowcase,
setupShowcaseScroll,
@@ -294,6 +295,17 @@ export async function showModelModal(model, modelType) {
].join('\n')
: '';
const headerActionItems = [];
// Add send to ComfyUI button for all model types
const sendToWorkflowTitle = translate('modals.model.actions.sendToWorkflow', {}, 'Send to ComfyUI');
const sendToWorkflowButton = `
<button class="modal-send-btn" data-action="send-to-workflow" data-model-type="${modelType}" title="${sendToWorkflowTitle}">
<i class="fas fa-paper-plane"></i>
<span>${translate('modals.model.actions.sendToWorkflowText', {}, 'Send to ComfyUI')}</span>
</button>
`.trim();
headerActionItems.push(indentMarkup(sendToWorkflowButton, 20));
if (creatorActionsMarkup) {
headerActionItems.push(creatorActionsMarkup);
}
@@ -615,6 +627,14 @@ export async function showModelModal(model, modelType) {
const activeModalElement = document.getElementById(modalId);
if (activeModalElement) {
activeModalElement.dataset.filePath = modelWithFullData.file_path || '';
// Store usage_tips for LoRA models
if (modelType === 'loras' && modelWithFullData.usage_tips) {
activeModalElement.dataset.usageTips = modelWithFullData.usage_tips;
}
// Store sub_type for checkpoint models
if (modelType === 'checkpoints' && modelWithFullData.sub_type) {
activeModalElement.dataset.subType = modelWithFullData.sub_type;
}
}
updateVersionsTabBadge(updateAvailabilityState.hasUpdateAvailable);
const versionsTabController = initVersionsTab({
@@ -747,6 +767,9 @@ function setupEventHandlers(filePath, modelType) {
case 'nav-next':
handleDirectionalNavigation('next', modelType);
break;
case 'send-to-workflow':
handleSendToWorkflow(target, modelType);
break;
}
}
@@ -1026,6 +1049,70 @@ async function openFileLocation(filePath) {
}
}
async function handleSendToWorkflow(target, modelType) {
const filePath = getModalFilePath();
if (!filePath) {
showToast('modals.model.sendToWorkflow.noFilePath', {}, 'error');
return;
}
// Get the current model data from the modal
const modalElement = document.getElementById('modelModal');
const currentFileName = modalElement?.querySelector('#file-name')?.textContent || '';
if (modelType === 'loras') {
// For LoRA: Build syntax from usage tips and send
const usageTipsData = modalElement?.dataset?.usageTips;
const usageTips = usageTipsData ? JSON.parse(usageTipsData) : {};
const loraSyntax = buildLoraSyntax(currentFileName, usageTips);
await sendLoraToWorkflow(loraSyntax, false, 'lora');
} else if (modelType === 'checkpoints') {
// For Checkpoint: Send model path
const subtype = (modalElement?.dataset?.subType || 'checkpoint').toLowerCase();
const isDiffusionModel = subtype === 'diffusion_model';
const widgetName = isDiffusionModel ? 'unet_name' : 'ckpt_name';
const actionTypeText = translate(
isDiffusionModel ? 'uiHelpers.nodeSelector.diffusionModel' : 'uiHelpers.nodeSelector.checkpoint',
{},
isDiffusionModel ? 'Diffusion Model' : 'Checkpoint'
);
const successMessage = translate(
isDiffusionModel ? 'uiHelpers.workflow.diffusionModelUpdated' : 'uiHelpers.workflow.checkpointUpdated',
{},
isDiffusionModel ? 'Diffusion model updated in workflow' : 'Checkpoint updated in workflow'
);
const failureMessage = translate(
isDiffusionModel ? 'uiHelpers.workflow.diffusionModelFailed' : 'uiHelpers.workflow.checkpointFailed',
{},
isDiffusionModel ? 'Failed to update diffusion model node' : 'Failed to update checkpoint node'
);
const missingNodesMessage = translate(
'uiHelpers.workflow.noMatchingNodes',
{},
'No compatible nodes available in the current workflow'
);
const missingTargetMessage = translate(
'uiHelpers.workflow.noTargetNodeSelected',
{},
'No target node selected'
);
await sendModelPathToWorkflow(filePath, {
widgetName,
collectionType: MODEL_TYPES.CHECKPOINT,
actionTypeText,
successMessage,
failureMessage,
missingNodesMessage,
missingTargetMessage,
});
} else if (modelType === 'embeddings') {
// For Embedding: Send as LoRA syntax (embedding name only)
const embeddingSyntax = `<embed:${currentFileName}:1>`;
await sendLoraToWorkflow(embeddingSyntax, false, 'embedding');
}
}
// Export the model modal API
const modelModal = {
show: showModelModal,