Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6187e7a
Implement subgraph publishing
AustinMroz Aug 21, 2025
e152668
Add missing null check
AustinMroz Aug 21, 2025
68a848b
Fix subgraph blueprint display in workflows tab
AustinMroz Aug 22, 2025
a73560d
Fix demotion of subgraph blueprints on reload
AustinMroz Aug 22, 2025
3b389a6
Update locales [skip ci]
invalid-email-address Aug 22, 2025
7ef287c
Update blueprint def on save, cleanup
AustinMroz Aug 22, 2025
7eb732a
Fix skipped tracking on subgraph publish
AustinMroz Aug 22, 2025
44ff3f9
Fix failing vite tests
AustinMroz Aug 22, 2025
2ac34da
Make blueprint breadcrumb badge clickable
AustinMroz Aug 22, 2025
bae864f
Add confirmation for overwrite on publish
AustinMroz Aug 22, 2025
b6a1db8
Simplify blueprint badge naming
AustinMroz Aug 22, 2025
f57c0e5
Swap to promise.allSettled when fetching subgraphs
AustinMroz Aug 22, 2025
a8f2fda
Navigate into subgraph on blueprint edit
AustinMroz Aug 24, 2025
5cf5a30
Revert mission of value in blueprint breadcrumb
AustinMroz Aug 24, 2025
4ca6fcc
Misc code quality fixes
AustinMroz Aug 24, 2025
03222eb
Set subgraphNode title on blueprint add.
AustinMroz Aug 25, 2025
55cb9f2
Add "Delete Blueprint" option to breadcrumb
AustinMroz Aug 25, 2025
bd9797a
Extract subgraph load code as function
AustinMroz Aug 25, 2025
b4eabe9
Fix subgraphs appearing in library after refresh
AustinMroz Aug 25, 2025
48cdf7b
Add delete button and confirmation for deletion
AustinMroz Aug 26, 2025
a943e8b
Use more specific warning for blueprint deletion
AustinMroz Aug 26, 2025
91730ea
At success toast on subgraph publish
AustinMroz Aug 26, 2025
2fec269
Don't apply subgraph context menu to normal nodes
AustinMroz Aug 26, 2025
8c1b167
Remove hardcoded subgraphs path
AustinMroz Aug 26, 2025
aff0940
Fix nodeDef update on save
AustinMroz Aug 26, 2025
559a7eb
Fix SaveAs with subgraph blueprints
AustinMroz Aug 26, 2025
879b342
Remove ugly serialize/deserialize
AustinMroz Aug 26, 2025
cecad6c
Improve error specificity
AustinMroz Aug 26, 2025
a8ce2c9
Framework for user defined blueprint descriptions
AustinMroz Aug 26, 2025
383a65e
Cleanup breadcrumb dropdown options
AustinMroz Aug 26, 2025
7c567d8
Move blueprint renaming into blueprint load
AustinMroz Aug 26, 2025
a2bf4e2
Move saveAs prompt into workflow class
AustinMroz Aug 26, 2025
1fa9ab6
Fix tests by making subgraphBlueprint internal
AustinMroz Aug 27, 2025
035529f
Add tests for subgraph blueprints
AustinMroz Aug 27, 2025
12c1a56
Rewrite confirmation dialog
AustinMroz Aug 27, 2025
9bb69c0
Fix overwrite on publish new subgraph
AustinMroz Aug 30, 2025
1c3bbd1
When editing blueprint, tint background blue
AustinMroz Aug 30, 2025
3349b25
Fix blueprint tint at low LOD
AustinMroz Aug 30, 2025
23dccb8
Set node source for blueprints to Blueprint
AustinMroz Aug 30, 2025
540a104
Fix publish test
AustinMroz Aug 30, 2025
2d21b1f
Fix multiple nits
AustinMroz Sep 2, 2025
744219f
Further cleanup: error handling, and comments
AustinMroz Sep 2, 2025
3fdb599
Fixing failing test cases
AustinMroz Sep 2, 2025
4b7ca90
Fix temporary marking on publish.
AustinMroz Sep 4, 2025
5475c5a
Block unloading subgraph blueprints
AustinMroz Sep 4, 2025
415775b
Merge main into austin/subgraph-publishing
AustinMroz Sep 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/components/breadcrumb/SubgraphBreadcrumb.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import SubgraphBreadcrumbItem from '@/components/breadcrumb/SubgraphBreadcrumbIt
import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
import { useCanvasStore } from '@/stores/graphStore'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
import { useSubgraphStore } from '@/stores/subgraphStore'
import { useWorkflowStore } from '@/stores/workflowStore'
import { forEachSubgraphNode } from '@/utils/graphTraversalUtil'

Expand All @@ -52,6 +53,9 @@ const workflowStore = useWorkflowStore()
const navigationStore = useSubgraphNavigationStore()
const breadcrumbRef = ref<InstanceType<typeof Breadcrumb>>()
const workflowName = computed(() => workflowStore.activeWorkflow?.filename)
const isBlueprint = computed(() =>
useSubgraphStore().isSubgraphBlueprint(workflowStore.activeWorkflow)
)
const collapseTabs = ref(false)
const overflowingTabs = ref(false)

Expand Down Expand Up @@ -89,6 +93,7 @@ const home = computed(() => ({
label: workflowName.value,
icon: 'pi pi-home',
key: 'root',
isBlueprint: isBlueprint.value,
command: () => {
const canvas = useCanvasStore().getCanvas()
if (!canvas.graph) throw new TypeError('Canvas has no graph')
Expand Down
20 changes: 18 additions & 2 deletions src/components/breadcrumb/SubgraphBreadcrumbItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
@click="handleClick"
>
<span class="p-breadcrumb-item-label">{{ item.label }}</span>
<Tag v-if="item.isBlueprint" :value="'Blueprint'" severity="primary" />
<i v-if="isActive" class="pi pi-angle-down text-[10px]"></i>
</a>
<Menu
Expand Down Expand Up @@ -48,6 +49,7 @@
import InputText from 'primevue/inputtext'
import Menu, { MenuState } from 'primevue/menu'
import type { MenuItem } from 'primevue/menuitem'
import Tag from 'primevue/tag'
import { computed, nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n'

Expand Down Expand Up @@ -121,7 +123,7 @@ const menuItems = computed<MenuItem[]>(() => {
command: async () => {
await workflowService.duplicateWorkflow(workflowStore.activeWorkflow!)
},
visible: isRoot
visible: isRoot && !props.item.isBlueprint
},
{
separator: true,
Expand Down Expand Up @@ -153,12 +155,26 @@ const menuItems = computed<MenuItem[]>(() => {
await useCommandStore().execute('Comfy.ClearWorkflow')
}
},
{
separator: true,
visible: props.item.key === 'root' && props.item.isBlueprint
},
{
label: t('subgraphStore.publish'),
icon: 'pi pi-copy',
command: async () => {
await workflowService.saveWorkflowAs(workflowStore.activeWorkflow!)
},
visible: props.item.key === 'root' && props.item.isBlueprint
},
{
separator: true,
visible: isRoot
},
{
label: t('breadcrumbsMenu.deleteWorkflow'),
label: props.item.isBlueprint
? t('breadcrumbsMenu.deleteBlueprint')
: t('breadcrumbsMenu.deleteWorkflow'),
icon: 'pi pi-times',
command: async () => {
await workflowService.deleteWorkflow(workflowStore.activeWorkflow!)
Expand Down
27 changes: 26 additions & 1 deletion src/components/dialog/content/ConfirmationDialogContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@
{{ hint }}
</Message>
<div class="flex gap-4 justify-end">
<div
v-if="type === 'overwriteBlueprint'"
class="flex gap-4 justify-start"
>
<Checkbox
v-model="doNotAskAgain"
class="flex gap-4 justify-start"
input-id="doNotAskAgain"
binary
/>
<label for="doNotAskAgain" severity="secondary">{{
t('missingModelsDialog.doNotAskAgain')
}}</label>
</div>

<Button
:label="$t('g.cancel')"
icon="pi pi-undo"
Expand All @@ -38,7 +53,7 @@
@click="onConfirm"
/>
<Button
v-else-if="type === 'overwrite'"
v-else-if="type === 'overwrite' || type === 'overwriteBlueprint'"
:label="$t('g.overwrite')"
severity="warn"
icon="pi pi-save"
Expand Down Expand Up @@ -74,10 +89,14 @@

<script setup lang="ts">
import Button from 'primevue/button'
import Checkbox from 'primevue/checkbox'
import Message from 'primevue/message'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'

import type { ConfirmationDialogType } from '@/services/dialogService'
import { useDialogStore } from '@/stores/dialogStore'
import { useSettingStore } from '@/stores/settingStore'

const props = defineProps<{
message: string
Expand All @@ -87,14 +106,20 @@ const props = defineProps<{
hint?: string
}>()

const { t } = useI18n()

const onCancel = () => useDialogStore().closeDialog()

const doNotAskAgain = ref(false)

const onDeny = () => {
props.onConfirm(false)
useDialogStore().closeDialog()
}

const onConfirm = () => {
if (props.type === 'overwriteBlueprint' && doNotAskAgain.value)
void useSettingStore().set('Comfy.Workflow.WarnBlueprintOverwrite', false)
props.onConfirm(true)
useDialogStore().closeDialog()
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/graph/SelectionToolbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<Load3DViewerButton />
<MaskEditorButton />
<ConvertToSubgraphButton />
<PublishSubgraphButton />
<DeleteButton />
<RefreshSelectionButton />
<ExtensionCommandButton
Expand Down Expand Up @@ -49,6 +50,7 @@ import Load3DViewerButton from '@/components/graph/selectionToolbox/Load3DViewer
import MaskEditorButton from '@/components/graph/selectionToolbox/MaskEditorButton.vue'
import PinButton from '@/components/graph/selectionToolbox/PinButton.vue'
import RefreshSelectionButton from '@/components/graph/selectionToolbox/RefreshSelectionButton.vue'
import PublishSubgraphButton from '@/components/graph/selectionToolbox/SaveToSubgraphLibrary.vue'
import { useSelectionToolboxPosition } from '@/composables/canvas/useSelectionToolboxPosition'
import { useCanvasInteractions } from '@/composables/graph/useCanvasInteractions'
import { useExtensionService } from '@/services/extensionService'
Expand Down
37 changes: 37 additions & 0 deletions src/components/graph/selectionToolbox/SaveToSubgraphLibrary.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<template>
<Button
v-show="isVisible"
v-tooltip.top="{
value: t('commands.Comfy_PublishSubgraph.label'),
showDelay: 1000
}"
severity="secondary"
text
@click="() => commandStore.execute('Comfy.PublishSubgraph')"
>
<template #icon>
<i-lucide:book-open />
</template>
</Button>
</template>

<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'

import { SubgraphNode } from '@/lib/litegraph/src/litegraph'
import { useCommandStore } from '@/stores/commandStore'
import { useCanvasStore } from '@/stores/graphStore'

const { t } = useI18n()
const commandStore = useCommandStore()
const canvasStore = useCanvasStore()

const isVisible = computed(() => {
return (
canvasStore.selectedItems?.length === 1 &&
canvasStore.selectedItems[0] instanceof SubgraphNode
)
})
</script>
61 changes: 59 additions & 2 deletions src/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div ref="container" class="node-lib-node-container">
<TreeExplorerTreeNode :node="node">
<TreeExplorerTreeNode :node="node" @contextmenu="handleContextMenu">
<template #before-label>
<Tag
v-if="nodeDef.experimental"
Expand All @@ -13,7 +13,30 @@
severity="danger"
/>
</template>
<template #actions>
<template
v-if="nodeDef.name.startsWith(useSubgraphStore().typePrefix)"
#actions
>
<Button
size="small"
icon="pi pi-trash"
text
severity="danger"
@click.stop="deleteBlueprint"
>
</Button>
<Button
size="small"
text
severity="secondary"
@click.stop="editBlueprint"
>
<template #icon>
<i-lucide:square-pen />
</template>
</Button>
</template>
<template v-else #actions>
<Button
class="bookmark-button"
size="small"
Expand All @@ -40,10 +63,13 @@
</div>
</teleport>
</div>
<ContextMenu ref="menu" :model="menuItems" />
</template>

<script setup lang="ts">
import Button from 'primevue/button'
import ContextMenu from 'primevue/contextmenu'
import type { MenuItem } from 'primevue/menuitem'
import Tag from 'primevue/tag'
import {
CSSProperties,
Expand All @@ -53,14 +79,18 @@ import {
onUnmounted,
ref
} from 'vue'
import { useI18n } from 'vue-i18n'

import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
import NodePreview from '@/components/node/NodePreview.vue'
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
import { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import { useSettingStore } from '@/stores/settingStore'
import { useSubgraphStore } from '@/stores/subgraphStore'
import { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'

const { t } = useI18n()

const props = defineProps<{
node: RenderedTreeExplorerNode<ComfyNodeDefImpl>
openNodeHelp: (nodeDef: ComfyNodeDefImpl) => void
Expand All @@ -80,6 +110,33 @@ const sidebarLocation = computed<'left' | 'right'>(() =>
const toggleBookmark = async () => {
await nodeBookmarkStore.toggleBookmark(nodeDef.value)
}
const editBlueprint = async () => {
if (!props.node.data)
throw new Error(
'Failed to edit subgraph blueprint lacking backing node data'
)
await useSubgraphStore().editBlueprint(props.node.data.name)
}
const menu = ref<InstanceType<typeof ContextMenu> | null>(null)
const menuItems = computed<MenuItem[]>(() => {
const items: MenuItem[] = [
{
label: t('g.delete'),
icon: 'pi pi-trash',
severity: 'error',
command: deleteBlueprint
}
]
return items
})
function handleContextMenu(event: Event) {
if (!nodeDef.value.name.startsWith(useSubgraphStore().typePrefix)) return
menu.value?.show(event)
}
function deleteBlueprint() {
if (!props.node.data) return
void useSubgraphStore().deleteBlueprint(props.node.data.name)
}

const previewRef = ref<InstanceType<typeof NodePreview> | null>(null)
const nodePreviewStyle = ref<CSSProperties>({
Expand Down
10 changes: 10 additions & 0 deletions src/composables/useCoreCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore'
import { useSettingStore } from '@/stores/settingStore'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
import { useSubgraphStore } from '@/stores/subgraphStore'
import { useToastStore } from '@/stores/toastStore'
import { type ComfyWorkflow, useWorkflowStore } from '@/stores/workflowStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
Expand Down Expand Up @@ -115,6 +116,15 @@ export function useCoreCommands(): ComfyCommand[] {
await workflowService.saveWorkflow(workflow)
}
},
{
id: 'Comfy.PublishSubgraph',
icon: 'pi pi-save',
label: 'Publish Subgraph',
menubarLabel: 'Publish',
function: async () => {
await useSubgraphStore().publishSubgraph()
}
},
{
id: 'Comfy.SaveWorkflowAs',
icon: 'pi pi-save',
Expand Down
6 changes: 6 additions & 0 deletions src/constants/coreSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ export const CORE_SETTINGS: SettingParams[] = [
defaultValue: true,
experimental: true
},
{
id: 'Comfy.Workflow.WarnBlueprintOverwrite',
name: 'Require confirmation to overwrite an existing subgraph blueprint',
type: 'boolean',
defaultValue: true
},
{
id: 'Comfy.Graph.ZoomSpeed',
category: ['LiteGraph', 'Canvas', 'ZoomSpeed'],
Expand Down
Loading
Loading