Skip to content

Commit a12bf07

Browse files
feat(ui): add node publish denylist
1 parent a5bc21c commit a12bf07

File tree

3 files changed

+43
-14
lines changed

3 files changed

+43
-14
lines changed

invokeai/frontend/web/public/locales/en.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1809,7 +1809,7 @@
18091809
"cannotPublish": "Cannot publish workflow",
18101810
"publishWarnings": "Warnings",
18111811
"errorWorkflowHasUnsavedChanges": "Workflow has unsaved changes",
1812-
"errorWorkflowHasBatchOrGeneratorNodes": "Workflow has batch and/or generator nodes",
1812+
"errorWorkflowHasUnpublishableNodes": "Workflow has batch, generator, or metadata extraction nodes",
18131813
"errorWorkflowHasInvalidGraph": "Workflow graph invalid (hover Invoke button for details)",
18141814
"errorWorkflowHasNoOutputNode": "No output node selected",
18151815
"warningWorkflowHasNoPublishableInputFields": "No publishable input fields selected - published workflow will run with only default values",

invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/PublishWorkflowPanelContent.tsx

+12-12
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
$isSelectingOutputNode,
2727
$outputNodeId,
2828
$validationRunData,
29+
selectHasUnpublishableNodes,
2930
usePublishInputs,
3031
} from 'features/nodes/components/sidePanel/workflow/publish';
3132
import { useInputFieldTemplateTitleOrThrow } from 'features/nodes/hooks/useInputFieldTemplateTitleOrThrow';
@@ -36,7 +37,6 @@ import { useNodeUserTitleOrThrow } from 'features/nodes/hooks/useNodeUserTitleOr
3637
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
3738
import { useOutputFieldTemplate } from 'features/nodes/hooks/useOutputFieldTemplate';
3839
import { useZoomToNode } from 'features/nodes/hooks/useZoomToNode';
39-
import { selectHasBatchOrGeneratorNodes } from 'features/nodes/store/selectors';
4040
import { useEnqueueWorkflows } from 'features/queue/hooks/useEnqueueWorkflows';
4141
import { $isReadyToEnqueue } from 'features/queue/store/readiness';
4242
import { selectAllowPublishWorkflows } from 'features/system/store/configSlice';
@@ -201,7 +201,7 @@ const PublishWorkflowButton = memo(() => {
201201
const isReadyToDoValidationRun = useStore($isReadyToDoValidationRun);
202202
const isReadyToEnqueue = useStore($isReadyToEnqueue);
203203
const doesWorkflowHaveUnsavedChanges = useDoesWorkflowHaveUnsavedChanges();
204-
const hasBatchOrGeneratorNodes = useAppSelector(selectHasBatchOrGeneratorNodes);
204+
const hasUnpublishableNodes = useAppSelector(selectHasUnpublishableNodes);
205205
const outputNodeId = useStore($outputNodeId);
206206
const isSelectingOutputNode = useStore($isSelectingOutputNode);
207207
const inputs = usePublishInputs();
@@ -249,7 +249,7 @@ const PublishWorkflowButton = memo(() => {
249249
return (
250250
<PublishTooltip
251251
isWorkflowSaved={!doesWorkflowHaveUnsavedChanges}
252-
hasBatchOrGeneratorNodes={hasBatchOrGeneratorNodes}
252+
hasUnpublishableNodes={hasUnpublishableNodes}
253253
isReadyToEnqueue={isReadyToEnqueue}
254254
hasOutputNode={outputNodeId !== null && !isSelectingOutputNode}
255255
hasPublishableInputs={inputs.publishable.length > 0}
@@ -261,7 +261,7 @@ const PublishWorkflowButton = memo(() => {
261261
!allowPublishWorkflows ||
262262
!isReadyToEnqueue ||
263263
doesWorkflowHaveUnsavedChanges ||
264-
hasBatchOrGeneratorNodes ||
264+
hasUnpublishableNodes ||
265265
!isReadyToDoValidationRun ||
266266
!(outputNodeId !== null && !isSelectingOutputNode)
267267
}
@@ -330,7 +330,7 @@ export const StartPublishFlowButton = memo(() => {
330330
const allowPublishWorkflows = useAppSelector(selectAllowPublishWorkflows);
331331
const isReadyToEnqueue = useStore($isReadyToEnqueue);
332332
const doesWorkflowHaveUnsavedChanges = useDoesWorkflowHaveUnsavedChanges();
333-
const hasBatchOrGeneratorNodes = useAppSelector(selectHasBatchOrGeneratorNodes);
333+
const hasUnpublishableNodes = useAppSelector(selectHasUnpublishableNodes);
334334
const inputs = usePublishInputs();
335335

336336
const onClick = useCallback(() => {
@@ -340,7 +340,7 @@ export const StartPublishFlowButton = memo(() => {
340340
return (
341341
<PublishTooltip
342342
isWorkflowSaved={!doesWorkflowHaveUnsavedChanges}
343-
hasBatchOrGeneratorNodes={hasBatchOrGeneratorNodes}
343+
hasUnpublishableNodes={hasUnpublishableNodes}
344344
isReadyToEnqueue={isReadyToEnqueue}
345345
hasOutputNode={true}
346346
hasPublishableInputs={inputs.publishable.length > 0}
@@ -352,7 +352,7 @@ export const StartPublishFlowButton = memo(() => {
352352
variant="ghost"
353353
size="sm"
354354
isDisabled={
355-
!allowPublishWorkflows || !isReadyToEnqueue || doesWorkflowHaveUnsavedChanges || hasBatchOrGeneratorNodes
355+
!allowPublishWorkflows || !isReadyToEnqueue || doesWorkflowHaveUnsavedChanges || hasUnpublishableNodes
356356
}
357357
>
358358
{t('workflows.builder.publish')}
@@ -366,15 +366,15 @@ StartPublishFlowButton.displayName = 'StartPublishFlowButton';
366366
const PublishTooltip = memo(
367367
({
368368
isWorkflowSaved,
369-
hasBatchOrGeneratorNodes,
369+
hasUnpublishableNodes,
370370
isReadyToEnqueue,
371371
hasOutputNode,
372372
hasPublishableInputs,
373373
hasUnpublishableInputs,
374374
children,
375375
}: PropsWithChildren<{
376376
isWorkflowSaved: boolean;
377-
hasBatchOrGeneratorNodes: boolean;
377+
hasUnpublishableNodes: boolean;
378378
isReadyToEnqueue: boolean;
379379
hasOutputNode: boolean;
380380
hasPublishableInputs: boolean;
@@ -396,8 +396,8 @@ const PublishTooltip = memo(
396396
if (!isWorkflowSaved) {
397397
_errors.push(t('workflows.builder.errorWorkflowHasUnsavedChanges'));
398398
}
399-
if (hasBatchOrGeneratorNodes) {
400-
_errors.push(t('workflows.builder.errorWorkflowHasBatchOrGeneratorNodes'));
399+
if (hasUnpublishableNodes) {
400+
_errors.push(t('workflows.builder.errorWorkflowHasUnpublishableNodes'));
401401
}
402402
if (!isReadyToEnqueue) {
403403
_errors.push(t('workflows.builder.errorWorkflowHasInvalidGraph'));
@@ -406,7 +406,7 @@ const PublishTooltip = memo(
406406
_errors.push(t('workflows.builder.errorWorkflowHasNoOutputNode'));
407407
}
408408
return _errors;
409-
}, [hasBatchOrGeneratorNodes, hasOutputNode, isReadyToEnqueue, isWorkflowSaved, t]);
409+
}, [hasUnpublishableNodes, hasOutputNode, isReadyToEnqueue, isWorkflowSaved, t]);
410410

411411
if (errors.length === 0 && warnings.length === 0) {
412412
return children;

invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/publish.ts

+30-1
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { skipToken } from '@reduxjs/toolkit/query';
44
import { useAppSelector } from 'app/store/storeHooks';
55
import { $templates } from 'features/nodes/store/nodesSlice';
66
import {
7+
selectNodes,
78
selectNodesSlice,
89
selectWorkflowFormNodeFieldFieldIdentifiersDeduped,
910
selectWorkflowId,
1011
} from 'features/nodes/store/selectors';
1112
import type { Templates } from 'features/nodes/store/types';
1213
import type { FieldIdentifier } from 'features/nodes/types/field';
1314
import { isBoardFieldType } from 'features/nodes/types/field';
14-
import { isInvocationNode } from 'features/nodes/types/invocation';
15+
import { isBatchNode, isGeneratorNode, isInvocationNode } from 'features/nodes/types/invocation';
1516
import { atom, computed } from 'nanostores';
1617
import { useMemo } from 'react';
1718
import { useGetBatchStatusQuery } from 'services/api/endpoints/queue';
@@ -108,3 +109,31 @@ export const useIsWorkflowPublished = () => {
108109

109110
return isPublished;
110111
};
112+
113+
// These nodes are not allowed to be in published workflows because they dynamically generate model identifiers
114+
const NODE_TYPE_PUBLISH_DENYLIST = [
115+
'metadata_to_model',
116+
'metadata_to_sdxl_model',
117+
'metadata_to_vae',
118+
'metadata_to_lora_collection',
119+
'metadata_to_loras',
120+
'metadata_to_sdlx_loras',
121+
'metadata_to_controlnets',
122+
'metadata_to_ip_adapters',
123+
'metadata_to_t2i_adapters',
124+
];
125+
126+
export const selectHasUnpublishableNodes = createSelector(selectNodes, (nodes) => {
127+
for (const node of nodes) {
128+
if (!isInvocationNode(node)) {
129+
return true;
130+
}
131+
if (isBatchNode(node) || isGeneratorNode(node)) {
132+
return true;
133+
}
134+
if (NODE_TYPE_PUBLISH_DENYLIST.includes(node.data.type)) {
135+
return true;
136+
}
137+
}
138+
return false;
139+
});

0 commit comments

Comments
 (0)