Skip to content

Commit abf2b3b

Browse files
authored
Full Asset Selection Experience (Assets API) (#5900)
## Summary Full Integration of Asset Browsing and Selection when Assets API is enabled. ## Changes 1. Replace Model Left Side Tab with experience 2. Configurable titles for the Asset Browser Modal 3. Refactors to simplify callback code 4. Refactor to make modal filters reactive (they change their values based on assets displayed) 5. Add `browse()` mode with ability to create node directly from the Asset Browser Modal (in `browse()` mode) ## Screenshots Demo of many different types of Nodes getting configured by the Modal https://github.com/user-attachments/assets/34f9c964-cdf2-4c5d-86a9-a8e7126a7de9 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5900-Feat-asset-selection-cloud-integration-2816d73d365081ccb4aeecdc14b0e5d3) by [Unito](https://www.unito.io)
1 parent 661885f commit abf2b3b

File tree

22 files changed

+1450
-552
lines changed

22 files changed

+1450
-552
lines changed

src/components/sidebar/SideToolbar.vue

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
3838
import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'
3939
import SidebarShortcutsToggleButton from '@/components/sidebar/SidebarShortcutsToggleButton.vue'
4040
import { useSettingStore } from '@/platform/settings/settingStore'
41+
import { useCommandStore } from '@/stores/commandStore'
4142
import { useKeybindingStore } from '@/stores/keybindingStore'
4243
import { useUserStore } from '@/stores/userStore'
4344
import { useWorkspaceStore } from '@/stores/workspaceStore'
@@ -51,6 +52,7 @@ import SidebarTemplatesButton from './SidebarTemplatesButton.vue'
5152
const workspaceStore = useWorkspaceStore()
5253
const settingStore = useSettingStore()
5354
const userStore = useUserStore()
55+
const commandStore = useCommandStore()
5456
5557
const teleportTarget = computed(() =>
5658
settingStore.get('Comfy.Sidebar.Location') === 'left'
@@ -64,9 +66,12 @@ const isSmall = computed(
6466
6567
const tabs = computed(() => workspaceStore.getSidebarTabs())
6668
const selectedTab = computed(() => workspaceStore.sidebarTab.activeSidebarTab)
67-
const onTabClick = (item: SidebarTabExtension) => {
68-
workspaceStore.sidebarTab.toggleSidebarTab(item.id)
69-
}
69+
70+
const onTabClick = async (item: SidebarTabExtension) =>
71+
await commandStore.commands
72+
.find((cmd) => cmd.id === `Workspace.ToggleSidebarTab.${item.id}`)
73+
?.function?.()
74+
7075
const keybindingStore = useKeybindingStore()
7176
const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
7277
const keybinding = keybindingStore.getKeybindingByCommandId(

src/components/widget/layout/BaseModalLayout.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@
4848
<main class="flex flex-col flex-1 min-h-0">
4949
<!-- Fallback title bar when no leftPanel is provided -->
5050
<slot name="contentFilter"></slot>
51-
<h2 v-if="!$slots.leftPanel" class="text-xxl px-6 pt-2 pb-6 m-0">
51+
<h2
52+
v-if="!$slots.leftPanel"
53+
class="text-xxl px-6 pt-2 pb-6 m-0 capitalize"
54+
>
5255
{{ contentTitle }}
5356
</h2>
5457
<div :class="contentContainerClasses">

src/composables/useCoreCommands.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
SubgraphNode
1616
} from '@/lib/litegraph/src/litegraph'
1717
import type { Point } from '@/lib/litegraph/src/litegraph'
18+
import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog'
19+
import { createModelNodeFromAsset } from '@/platform/assets/utils/createModelNodeFromAsset'
1820
import { useSettingStore } from '@/platform/settings/settingStore'
1921
import { useToastStore } from '@/platform/updates/common/toastStore'
2022
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
@@ -1062,6 +1064,30 @@ export function useCoreCommands(): ComfyCommand[] {
10621064
}
10631065
await api.freeMemory({ freeExecutionCache: true })
10641066
}
1067+
},
1068+
{
1069+
id: 'Comfy.BrowseModelAssets',
1070+
icon: 'pi pi-folder-open',
1071+
label: 'Browse Model Assets',
1072+
versionAdded: '1.28.3',
1073+
function: async () => {
1074+
const assetBrowserDialog = useAssetBrowserDialog()
1075+
await assetBrowserDialog.browse({
1076+
assetType: 'models',
1077+
title: t('sideToolbar.modelLibrary'),
1078+
onAssetSelected: (asset) => {
1079+
const result = createModelNodeFromAsset(asset)
1080+
if (!result.success) {
1081+
toastStore.add({
1082+
severity: 'error',
1083+
summary: t('g.error'),
1084+
detail: t('assetBrowser.failedToCreateNode')
1085+
})
1086+
console.error('Node creation failed:', result.error)
1087+
}
1088+
}
1089+
})
1090+
}
10651091
}
10661092
]
10671093

src/locales/en/main.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,9 +1942,11 @@
19421942
"tryAdjustingFilters": "Try adjusting your search or filters",
19431943
"loadingModels": "Loading {type}...",
19441944
"connectionError": "Please check your connection and try again",
1945+
"failedToCreateNode": "Failed to create node. Please try again or check console for details.",
19451946
"noModelsInFolder": "No {type} available in this folder",
19461947
"searchAssetsPlaceholder": "Search assets...",
19471948
"allModels": "All Models",
1949+
"allCategory": "All {category}",
19481950
"unknown": "Unknown",
19491951
"fileFormats": "File formats",
19501952
"baseModels": "Base models",

src/platform/assets/components/AssetBrowserModal.vue

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<BaseModalLayout
33
data-component-id="AssetBrowserModal"
44
class="size-full max-h-full max-w-full min-w-0"
5-
:content-title="contentTitle"
5+
:content-title="displayTitle"
66
@close="handleClose"
77
>
88
<template v-if="shouldShowLeftPanel" #leftPanel>
@@ -14,7 +14,9 @@
1414
<template #header-icon>
1515
<div class="icon-[lucide--folder] size-4" />
1616
</template>
17-
<template #header-title>{{ $t('assetBrowser.browseAssets') }}</template>
17+
<template #header-title>
18+
<span class="capitalize">{{ displayTitle }}</span>
19+
</template>
1820
</LeftSidePanel>
1921
</template>
2022

@@ -28,7 +30,10 @@
2830
</template>
2931

3032
<template #contentFilter>
31-
<AssetFilterBar :assets="assets" @filter-change="updateFilters" />
33+
<AssetFilterBar
34+
:assets="categoryFilteredAssets"
35+
@filter-change="updateFilters"
36+
/>
3237
</template>
3338

3439
<template #content>
@@ -56,10 +61,11 @@ import { OnCloseKey } from '@/types/widgetTypes'
5661
const props = defineProps<{
5762
nodeType?: string
5863
inputName?: string
59-
onSelect?: (assetPath: string) => void
64+
onSelect?: (asset: AssetItem) => void
6065
onClose?: () => void
6166
showLeftPanel?: boolean
6267
assets?: AssetItem[]
68+
title?: string
6369
}>()
6470
6571
const emit = defineEmits<{
@@ -74,11 +80,15 @@ const {
7480
selectedCategory,
7581
availableCategories,
7682
contentTitle,
83+
categoryFilteredAssets,
7784
filteredAssets,
78-
selectAssetWithCallback,
7985
updateFilters
8086
} = useAssetBrowser(props.assets)
8187
88+
const displayTitle = computed(() => {
89+
return props.title ?? contentTitle.value
90+
})
91+
8292
const shouldShowLeftPanel = computed(() => {
8393
return props.showLeftPanel ?? true
8494
})
@@ -88,8 +98,10 @@ function handleClose() {
8898
emit('close')
8999
}
90100
91-
async function handleAssetSelectAndEmit(asset: AssetDisplayItem) {
101+
function handleAssetSelectAndEmit(asset: AssetDisplayItem) {
92102
emit('asset-select', asset)
93-
await selectAssetWithCallback(asset.id, props.onSelect)
103+
// onSelect callback is provided by dialog composable layer
104+
// It handles the appropriate transformation (filename extraction or full asset)
105+
props.onSelect?.(asset)
94106
}
95107
</script>

0 commit comments

Comments
 (0)