mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-11 13:19:24 -03:00
feat(embedding): send embedding to workflow + fix copy button format
- Fix copy button on embedding cards to copy 'embedding:folder/name' format - Add send-embedding-to-workflow for Prompt (LoraManager), Text (LoraManager), and CLIPTextEncode nodes, appending embedding code to text content - Extend workflow registry to register text-capable nodes by comfyClass (not generic widget name 'text') to avoid false matches - Add mode parameter to update_node_widget API/event for append support - Fix single/bulk context menus: single shows plain 'Send to Workflow', bulk collapses submenu into direct action for embeddings (append-only)
This commit is contained in:
@@ -3086,6 +3086,7 @@ class NodeRegistryHandler:
|
|||||||
data = await request.json()
|
data = await request.json()
|
||||||
widget_name = data.get("widget_name")
|
widget_name = data.get("widget_name")
|
||||||
value = data.get("value")
|
value = data.get("value")
|
||||||
|
mode = data.get("mode", "replace")
|
||||||
node_ids = data.get("node_ids")
|
node_ids = data.get("node_ids")
|
||||||
|
|
||||||
if not isinstance(widget_name, str) or not widget_name:
|
if not isinstance(widget_name, str) or not widget_name:
|
||||||
@@ -3133,6 +3134,7 @@ class NodeRegistryHandler:
|
|||||||
"id": parsed_node_id,
|
"id": parsed_node_id,
|
||||||
"widget_name": widget_name,
|
"widget_name": widget_name,
|
||||||
"value": value,
|
"value": value,
|
||||||
|
"mode": mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
if graph_identifier is not None:
|
if graph_identifier is not None:
|
||||||
|
|||||||
@@ -51,22 +51,34 @@ export class BulkContextMenu extends BaseContextMenu {
|
|||||||
reimportMetadataItem.style.display = config.reimportMetadata ? 'flex' : 'none';
|
reimportMetadataItem.style.display = config.reimportMetadata ? 'flex' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isEmbeddings = currentModelType === 'embeddings';
|
||||||
if (sendToWorkflowAppendItem) {
|
if (sendToWorkflowAppendItem) {
|
||||||
sendToWorkflowAppendItem.style.display = config.sendToWorkflow ? 'flex' : 'none';
|
sendToWorkflowAppendItem.style.display = config.sendToWorkflow ? 'flex' : 'none';
|
||||||
}
|
}
|
||||||
if (sendToWorkflowReplaceItem) {
|
if (sendToWorkflowReplaceItem) {
|
||||||
sendToWorkflowReplaceItem.style.display = config.sendToWorkflow ? 'flex' : 'none';
|
sendToWorkflowReplaceItem.style.display = (config.sendToWorkflow && !isEmbeddings) ? 'flex' : 'none';
|
||||||
}
|
}
|
||||||
if (copyAllItem) {
|
if (copyAllItem) {
|
||||||
copyAllItem.style.display = config.copyAll ? 'flex' : 'none';
|
copyAllItem.style.display = config.copyAll ? 'flex' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submenu parent visibility
|
// Submenu parent - for embeddings, collapse into a direct item (no replace choice)
|
||||||
const sendToWorkflowSubmenu = this.menu.querySelector('[data-has-submenu="send-to-workflow"]');
|
const sendToWorkflowSubmenu = this.menu.querySelector('[data-has-submenu="send-to-workflow"]');
|
||||||
if (sendToWorkflowSubmenu) {
|
if (sendToWorkflowSubmenu) {
|
||||||
const hasWorkflowActions = config.sendToWorkflow || config.copyAll;
|
const hasWorkflowActions = config.sendToWorkflow || config.copyAll;
|
||||||
|
if (isEmbeddings && config.sendToWorkflow && !config.copyAll) {
|
||||||
|
sendToWorkflowSubmenu.classList.remove('has-submenu');
|
||||||
|
sendToWorkflowSubmenu.removeAttribute('data-has-submenu');
|
||||||
|
sendToWorkflowSubmenu.dataset.action = 'send-to-workflow-append';
|
||||||
|
const arrow = sendToWorkflowSubmenu.querySelector('.submenu-arrow');
|
||||||
|
if (arrow) arrow.style.display = 'none';
|
||||||
|
const submenu = sendToWorkflowSubmenu.querySelector('.context-submenu');
|
||||||
|
if (submenu) submenu.style.display = 'none';
|
||||||
|
sendToWorkflowSubmenu.style.display = 'flex';
|
||||||
|
} else {
|
||||||
sendToWorkflowSubmenu.style.display = hasWorkflowActions ? 'flex' : 'none';
|
sendToWorkflowSubmenu.style.display = hasWorkflowActions ? 'flex' : 'none';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (refreshAllItem) {
|
if (refreshAllItem) {
|
||||||
refreshAllItem.style.display = config.refreshAll ? 'flex' : 'none';
|
refreshAllItem.style.display = config.refreshAll ? 'flex' : 'none';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ModelContextMenuMixin } from './ModelContextMenuMixin.js';
|
|||||||
import { getModelApiClient, resetAndReload } from '../../api/modelApiFactory.js';
|
import { getModelApiClient, resetAndReload } from '../../api/modelApiFactory.js';
|
||||||
import { moveManager } from '../../managers/MoveManager.js';
|
import { moveManager } from '../../managers/MoveManager.js';
|
||||||
import { showDeleteModal, showExcludeModal } from '../../utils/modalUtils.js';
|
import { showDeleteModal, showExcludeModal } from '../../utils/modalUtils.js';
|
||||||
|
import { sendEmbeddingToWorkflow } from '../../utils/uiHelpers.js';
|
||||||
|
|
||||||
export class EmbeddingContextMenu extends BaseContextMenu {
|
export class EmbeddingContextMenu extends BaseContextMenu {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -51,6 +52,13 @@ export class EmbeddingContextMenu extends BaseContextMenu {
|
|||||||
this.currentCard.querySelector('.fa-copy').click();
|
this.currentCard.querySelector('.fa-copy').click();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'sendtoworkflow': {
|
||||||
|
const folder = this.currentCard.dataset.folder || '';
|
||||||
|
const name = this.currentCard.dataset.file_name || '';
|
||||||
|
const embeddingCode = folder ? `embedding:${folder}/${name}` : `embedding:${name}`;
|
||||||
|
sendEmbeddingToWorkflow(embeddingCode, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'refresh-metadata':
|
case 'refresh-metadata':
|
||||||
// Refresh metadata from CivitAI
|
// Refresh metadata from CivitAI
|
||||||
apiClient.refreshSingleModelMetadata(this.currentCard.dataset.filepath);
|
apiClient.refreshSingleModelMetadata(this.currentCard.dataset.filepath);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { showToast, openCivitai, copyToClipboard, copyLoraSyntax, sendLoraToWorkflow, openExampleImagesFolder, buildLoraSyntax, sendModelPathToWorkflow } from '../../utils/uiHelpers.js';
|
import { showToast, openCivitai, copyToClipboard, copyLoraSyntax, sendLoraToWorkflow, sendEmbeddingToWorkflow, openExampleImagesFolder, buildLoraSyntax, sendModelPathToWorkflow } from '../../utils/uiHelpers.js';
|
||||||
import { state, getCurrentPageState } from '../../state/index.js';
|
import { state, getCurrentPageState } from '../../state/index.js';
|
||||||
import { showModelModal } from './ModelModal.js';
|
import { showModelModal } from './ModelModal.js';
|
||||||
import { toggleShowcase } from './showcase/ShowcaseView.js';
|
import { toggleShowcase } from './showcase/ShowcaseView.js';
|
||||||
@@ -216,6 +216,11 @@ function handleSendToWorkflow(card, replaceMode, modelType) {
|
|||||||
missingNodesMessage,
|
missingNodesMessage,
|
||||||
missingTargetMessage,
|
missingTargetMessage,
|
||||||
});
|
});
|
||||||
|
} else if (modelType === MODEL_TYPES.EMBEDDING) {
|
||||||
|
const folder = card.dataset.folder || '';
|
||||||
|
const name = card.dataset.file_name || '';
|
||||||
|
const embeddingCode = folder ? `embedding:${folder}/${name}` : `embedding:${name}`;
|
||||||
|
sendEmbeddingToWorkflow(embeddingCode, false);
|
||||||
} else {
|
} else {
|
||||||
showToast('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'info');
|
showToast('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'info');
|
||||||
}
|
}
|
||||||
@@ -230,8 +235,11 @@ function handleCopyAction(card, modelType) {
|
|||||||
const message = translate('modelCard.actions.checkpointNameCopied', {}, 'Checkpoint name copied');
|
const message = translate('modelCard.actions.checkpointNameCopied', {}, 'Checkpoint name copied');
|
||||||
copyToClipboard(checkpointName, message);
|
copyToClipboard(checkpointName, message);
|
||||||
} else if (modelType === MODEL_TYPES.EMBEDDING) {
|
} else if (modelType === MODEL_TYPES.EMBEDDING) {
|
||||||
const embeddingName = card.dataset.file_name;
|
const folder = card.dataset.folder || '';
|
||||||
copyToClipboard(embeddingName, 'Embedding name copied');
|
const name = card.dataset.file_name || '';
|
||||||
|
const embeddingCode = folder ? `embedding:${folder}/${name}` : `embedding:${name}`;
|
||||||
|
const message = translate('modelCard.actions.embeddingNameCopied', {}, 'Embedding syntax copied');
|
||||||
|
copyToClipboard(embeddingCode, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { showToast, openCivitai, sendLoraToWorkflow, sendModelPathToWorkflow, buildLoraSyntax } from '../../utils/uiHelpers.js';
|
import { showToast, openCivitai, sendLoraToWorkflow, sendEmbeddingToWorkflow, sendModelPathToWorkflow, buildLoraSyntax } from '../../utils/uiHelpers.js';
|
||||||
import { modalManager } from '../../managers/ModalManager.js';
|
import { modalManager } from '../../managers/ModalManager.js';
|
||||||
import { MODEL_TYPES } from '../../api/apiConfig.js';
|
import { MODEL_TYPES } from '../../api/apiConfig.js';
|
||||||
import {
|
import {
|
||||||
@@ -648,6 +648,10 @@ export async function showModelModal(model, modelType) {
|
|||||||
if (modelType === 'checkpoints' && modelWithFullData.sub_type) {
|
if (modelType === 'checkpoints' && modelWithFullData.sub_type) {
|
||||||
activeModalElement.dataset.subType = modelWithFullData.sub_type;
|
activeModalElement.dataset.subType = modelWithFullData.sub_type;
|
||||||
}
|
}
|
||||||
|
// Store folder for embedding models
|
||||||
|
if (modelType === 'embeddings' && modelWithFullData.folder) {
|
||||||
|
activeModalElement.dataset.folder = modelWithFullData.folder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateVersionsTabBadge(updateAvailabilityState.hasUpdateAvailable);
|
updateVersionsTabBadge(updateAvailabilityState.hasUpdateAvailable);
|
||||||
const versionsTabController = initVersionsTab({
|
const versionsTabController = initVersionsTab({
|
||||||
@@ -1188,9 +1192,10 @@ async function handleSendToWorkflow(target, modelType) {
|
|||||||
missingTargetMessage,
|
missingTargetMessage,
|
||||||
});
|
});
|
||||||
} else if (modelType === 'embeddings') {
|
} else if (modelType === 'embeddings') {
|
||||||
// For Embedding: Send as LoRA syntax (embedding name only)
|
const folder = modalElement?.dataset?.folder || '';
|
||||||
const embeddingSyntax = `<embed:${currentFileName}:1>`;
|
const name = currentFileName.replace(/\.[^.]+$/, '');
|
||||||
await sendLoraToWorkflow(embeddingSyntax, false, 'embedding');
|
const embeddingCode = folder ? `embedding:${folder}/${name}` : `embedding:${name}`;
|
||||||
|
await sendEmbeddingToWorkflow(embeddingCode, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { state, getCurrentPageState } from '../state/index.js';
|
import { state, getCurrentPageState } from '../state/index.js';
|
||||||
import { showToast, copyToClipboard, sendLoraToWorkflow, buildLoraSyntax, getNSFWLevelName } from '../utils/uiHelpers.js';
|
import { showToast, copyToClipboard, sendLoraToWorkflow, sendEmbeddingToWorkflow, buildLoraSyntax, getNSFWLevelName } from '../utils/uiHelpers.js';
|
||||||
import { updateCardsForBulkMode } from '../components/shared/ModelCard.js';
|
import { updateCardsForBulkMode } from '../components/shared/ModelCard.js';
|
||||||
import { modalManager } from './ModalManager.js';
|
import { modalManager } from './ModalManager.js';
|
||||||
import { getModelApiClient, resetAndReload } from '../api/modelApiFactory.js';
|
import { getModelApiClient, resetAndReload } from '../api/modelApiFactory.js';
|
||||||
@@ -47,7 +47,7 @@ export class BulkManager {
|
|||||||
},
|
},
|
||||||
[MODEL_TYPES.EMBEDDING]: {
|
[MODEL_TYPES.EMBEDDING]: {
|
||||||
addTags: true,
|
addTags: true,
|
||||||
sendToWorkflow: false,
|
sendToWorkflow: true,
|
||||||
copyAll: false,
|
copyAll: false,
|
||||||
refreshAll: true,
|
refreshAll: true,
|
||||||
checkUpdates: true,
|
checkUpdates: true,
|
||||||
@@ -504,13 +504,17 @@ export class BulkManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sendAllModelsToWorkflow(replaceMode = false) {
|
async sendAllModelsToWorkflow(replaceMode = false) {
|
||||||
if (state.currentPageType !== MODEL_TYPES.LORA) {
|
if (state.selectedModels.size === 0) {
|
||||||
showToast('toast.loras.sendOnlyForLoras', {}, 'warning');
|
showToast('toast.models.noModelsSelected', {}, 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.selectedModels.size === 0) {
|
if (state.currentPageType === MODEL_TYPES.EMBEDDING) {
|
||||||
showToast('toast.loras.noLorasSelected', {}, 'warning');
|
return this._sendAllEmbeddingsToWorkflow();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.currentPageType !== MODEL_TYPES.LORA) {
|
||||||
|
showToast('toast.loras.sendOnlyForLoras', {}, 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,6 +546,28 @@ export class BulkManager {
|
|||||||
await sendLoraToWorkflow(loraSyntaxes.join(', '), replaceMode, 'lora');
|
await sendLoraToWorkflow(loraSyntaxes.join(', '), replaceMode, 'lora');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _sendAllEmbeddingsToWorkflow() {
|
||||||
|
const embeddingCodes = [];
|
||||||
|
for (const filepath of state.selectedModels) {
|
||||||
|
const escapedPath = CSS.escape(filepath);
|
||||||
|
const card = document.querySelector(`.model-card[data-filepath="${escapedPath}"]`);
|
||||||
|
if (card) {
|
||||||
|
const folder = card.dataset.folder || '';
|
||||||
|
const name = card.dataset.file_name || '';
|
||||||
|
const code = folder ? `embedding:${folder}/${name}` : `embedding:${name}`;
|
||||||
|
embeddingCodes.push(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embeddingCodes.length === 0) {
|
||||||
|
showToast('No valid embedding data found', {}, 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const joinedCode = embeddingCodes.join(', ');
|
||||||
|
await sendEmbeddingToWorkflow(joinedCode, false);
|
||||||
|
}
|
||||||
|
|
||||||
showBulkDeleteModal() {
|
showBulkDeleteModal() {
|
||||||
if (state.selectedModels.size === 0) {
|
if (state.selectedModels.size === 0) {
|
||||||
showToast('toast.models.noModelsSelected', {}, 'warning');
|
showToast('toast.models.noModelsSelected', {}, 'warning');
|
||||||
|
|||||||
@@ -866,6 +866,108 @@ async function sendWidgetValueToNodes(nodeIds, nodesMap, widgetName, value, mess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendTextToNodes(nodeIds, nodesMap, text, mode, messages = {}) {
|
||||||
|
const {
|
||||||
|
successMessage = 'Updated workflow node',
|
||||||
|
failureMessage = 'Failed to update workflow node',
|
||||||
|
missingTargetMessage = 'No target node selected',
|
||||||
|
} = messages;
|
||||||
|
|
||||||
|
const targetIds = Array.isArray(nodeIds) ? nodeIds : [];
|
||||||
|
if (targetIds.length === 0) {
|
||||||
|
showToast(missingTargetMessage, {}, 'warning');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const references = targetIds
|
||||||
|
.map((nodeKey) => resolveNodeReference(nodeKey, nodesMap))
|
||||||
|
.filter((reference) => reference && reference.node_id !== undefined);
|
||||||
|
|
||||||
|
if (references.length === 0) {
|
||||||
|
showToast(missingTargetMessage, {}, 'warning');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/lm/update-node-widget', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
widget_name: 'text',
|
||||||
|
value: text,
|
||||||
|
mode: mode || 'append',
|
||||||
|
node_ids: references,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
showToast(successMessage, {}, 'success');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMessage = result?.error || failureMessage;
|
||||||
|
showToast(errorMessage, {}, 'error');
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to send text to workflow:', error);
|
||||||
|
showToast(failureMessage, {}, 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendEmbeddingToWorkflow(embeddingCode, replaceMode = false) {
|
||||||
|
const registry = await fetchWorkflowRegistry();
|
||||||
|
if (!registry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textNodes = filterRegistryNodes(registry.nodes, (node) => {
|
||||||
|
if (!isNodeEnabled(node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return node.capabilities?.has_text_widget === true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeKeys = Object.keys(textNodes);
|
||||||
|
if (nodeKeys.length === 0) {
|
||||||
|
showToast('uiHelpers.workflow.noMatchingNodes', {}, 'warning');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = replaceMode ? 'replace' : 'append';
|
||||||
|
const messages = {
|
||||||
|
successMessage: translate(
|
||||||
|
replaceMode ? 'uiHelpers.workflow.embeddingReplaced' : 'uiHelpers.workflow.embeddingAdded',
|
||||||
|
{},
|
||||||
|
replaceMode ? 'Embedding replaced in workflow' : 'Embedding added to workflow'
|
||||||
|
),
|
||||||
|
failureMessage: translate('uiHelpers.workflow.embeddingFailed', {}, 'Failed to add embedding'),
|
||||||
|
missingTargetMessage: translate('uiHelpers.workflow.noTargetNodeSelected', {}, 'No target node selected'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSend = (selectedNodeIds) =>
|
||||||
|
sendTextToNodes(selectedNodeIds, textNodes, embeddingCode, mode, messages);
|
||||||
|
|
||||||
|
if (nodeKeys.length === 1) {
|
||||||
|
return await handleSend([nodeKeys[0]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionType = translate('uiHelpers.nodeSelector.embedding', {}, 'Embedding');
|
||||||
|
const actionMode = replaceMode
|
||||||
|
? translate('uiHelpers.nodeSelector.replace', {}, 'Replace')
|
||||||
|
: translate('uiHelpers.nodeSelector.append', {}, 'Append');
|
||||||
|
|
||||||
|
showNodeSelector(textNodes, {
|
||||||
|
actionType,
|
||||||
|
actionMode,
|
||||||
|
onSend: handleSend,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Global variable to track active node selector state
|
// Global variable to track active node selector state
|
||||||
let nodeSelectorState = {
|
let nodeSelectorState = {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<div class="context-menu-separator menu-section-break"></div>
|
<div class="context-menu-separator menu-section-break"></div>
|
||||||
<!-- Workflow -->
|
<!-- Workflow -->
|
||||||
<div class="context-menu-item" data-action="copyname"><i class="fas fa-copy"></i> {{ t('loras.contextMenu.copyFilename') }}</div>
|
<div class="context-menu-item" data-action="copyname"><i class="fas fa-copy"></i> {{ t('loras.contextMenu.copyFilename') }}</div>
|
||||||
|
<div class="context-menu-item" data-action="sendtoworkflow"><i class="fas fa-paper-plane"></i> {{ t('checkpoints.contextMenu.sendToWorkflow') }}</div>
|
||||||
<div class="context-menu-separator menu-section-break"></div>
|
<div class="context-menu-separator menu-section-break"></div>
|
||||||
<!-- Media / Preview -->
|
<!-- Media / Preview -->
|
||||||
<div class="context-menu-item" data-action="preview"><i class="fas fa-folder-open"></i> {{ t('loras.contextMenu.openExamples') }}</div>
|
<div class="context-menu-item" data-action="preview"><i class="fas fa-folder-open"></i> {{ t('loras.contextMenu.openExamples') }}</div>
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ const LORA_NODE_CLASSES = new Set([
|
|||||||
|
|
||||||
const TARGET_WIDGET_NAMES = new Set(["ckpt_name", "unet_name"]);
|
const TARGET_WIDGET_NAMES = new Set(["ckpt_name", "unet_name"]);
|
||||||
|
|
||||||
|
// Node classes whose "text" widget is a prompt text input (not LoRA syntax, notes, etc.)
|
||||||
|
const TEXT_CAPABLE_CLASSES = new Set([
|
||||||
|
"Prompt (LoraManager)",
|
||||||
|
"Text (LoraManager)",
|
||||||
|
"CLIPTextEncode",
|
||||||
|
]);
|
||||||
|
|
||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "LoraManager.WorkflowRegistry",
|
name: "LoraManager.WorkflowRegistry",
|
||||||
|
|
||||||
@@ -41,8 +48,9 @@ app.registerExtension({
|
|||||||
|
|
||||||
const supportsLora = LORA_NODE_CLASSES.has(node.comfyClass);
|
const supportsLora = LORA_NODE_CLASSES.has(node.comfyClass);
|
||||||
const hasTargetWidget = widgetNames.some((name) => TARGET_WIDGET_NAMES.has(name));
|
const hasTargetWidget = widgetNames.some((name) => TARGET_WIDGET_NAMES.has(name));
|
||||||
|
const hasTextWidget = TEXT_CAPABLE_CLASSES.has(node.comfyClass);
|
||||||
|
|
||||||
if (!supportsLora && !hasTargetWidget) {
|
if (!supportsLora && !hasTargetWidget && !hasTextWidget) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +73,7 @@ app.registerExtension({
|
|||||||
mode: node.mode,
|
mode: node.mode,
|
||||||
capabilities: {
|
capabilities: {
|
||||||
supports_lora: supportsLora,
|
supports_lora: supportsLora,
|
||||||
|
has_text_widget: hasTextWidget,
|
||||||
widget_names: widgetNames,
|
widget_names: widgetNames,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -95,6 +104,7 @@ app.registerExtension({
|
|||||||
const graphId = message?.graph_id;
|
const graphId = message?.graph_id;
|
||||||
const widgetName = message?.widget_name;
|
const widgetName = message?.widget_name;
|
||||||
const value = message?.value;
|
const value = message?.value;
|
||||||
|
const mode = message?.mode ?? "replace";
|
||||||
|
|
||||||
if (nodeId == null || !widgetName) {
|
if (nodeId == null || !widgetName) {
|
||||||
console.warn("LoRA Manager: invalid widget update payload", message);
|
console.warn("LoRA Manager: invalid widget update payload", message);
|
||||||
@@ -127,15 +137,22 @@ app.registerExtension({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const widget = node.widgets[widgetIndex];
|
const widget = node.widgets[widgetIndex];
|
||||||
widget.value = value;
|
let newValue = value;
|
||||||
|
|
||||||
|
if (mode === "append") {
|
||||||
|
const separator = widget.value && widget.value.length > 0 ? " " : "";
|
||||||
|
newValue = widget.value + separator + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.value = newValue;
|
||||||
|
|
||||||
if (Array.isArray(node.widgets_values) && node.widgets_values.length > widgetIndex) {
|
if (Array.isArray(node.widgets_values) && node.widgets_values.length > widgetIndex) {
|
||||||
node.widgets_values[widgetIndex] = value;
|
node.widgets_values[widgetIndex] = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof widget.callback === "function") {
|
if (typeof widget.callback === "function") {
|
||||||
try {
|
try {
|
||||||
widget.callback(value);
|
widget.callback(newValue);
|
||||||
} catch (callbackError) {
|
} catch (callbackError) {
|
||||||
console.error("LoRA Manager: widget callback failed", callbackError);
|
console.error("LoRA Manager: widget callback failed", callbackError);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user