Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
afdb198
feat: add model browser filtering composable with fuzzy search and so…
jay-comfy Jan 27, 2026
3ba78e9
feat: add ModelCard component and dialog composable with test models
jay-comfy Jan 28, 2026
ca2144f
feat: add model loader composable with caching and race-condition han…
jay-comfy Jan 28, 2026
52ad350
feat: integrate real model data with proper states
jay-comfy Jan 28, 2026
448df73
feat: add search functionality
jay-comfy Jan 28, 2026
6ac3b28
feat: add clear filters button to empty state
jay-comfy Jan 28, 2026
2022789
feat: add left panel with model type navigation
jay-comfy Jan 28, 2026
e70634d
feat: add right panel with model info details
jay-comfy Jan 28, 2026
c1c265c
feat: add file format and model type filters with sort options
jay-comfy Jan 28, 2026
87e5289
fix: remove duplicate close button in dialog
jay-comfy Jan 28, 2026
710fcf9
fix: remove gap between title and all models in left panel
jay-comfy Jan 28, 2026
51a4df2
feat: add list view mode
jay-comfy Jan 28, 2026
c4e0d5e
refactor: extend sort options with size and date
jay-comfy Jan 28, 2026
26154eb
feat: add keyboard navigation
jay-comfy Jan 28, 2026
208f9dc
feat: add virtualization with virtual grid component
jay-comfy Jan 28, 2026
dfe780f
feat: add telemetry events and search validation
jay-comfy Jan 28, 2026
8388e96
fix: use list icon for all models type
jay-comfy Jan 28, 2026
334edbe
style: improve dialog style and mobile responsiveness
jay-comfy Jan 28, 2026
5ccc298
fix: implement close button behavior
jay-comfy Jan 28, 2026
5ab71fa
fix: use model image metadata for previews and add type icon fallbacks
jay-comfy Jan 28, 2026
3b62818
feat: implement use model in workflow functionality
jay-comfy Jan 28, 2026
272faa3
refactor: extract shared node creation logic
jay-comfy Jan 28, 2026
e0579dc
refactor: reorganize model browser into platform structure
jay-comfy Jan 28, 2026
8ba095f
test: add keyboard navigation and dialog integration tests
jay-comfy Jan 28, 2026
20879f2
feat: update model list view to table structure
jay-comfy Jan 28, 2026
10a29c9
refactor: use design tokens for table row colors
jay-comfy Jan 28, 2026
eea9c7c
refactor: simplify model browser sort options and improve mobile layout
jay-comfy Jan 28, 2026
1f57a9f
feat: add bookmark functionality with localStorage for models
jay-comfy Jan 28, 2026
a258b58
fix: correct i18n translation keys for bookmark feature
jay-comfy Jan 28, 2026
e170f88
refactor: update bookmark UI with proper icons and improve sort labels
jay-comfy Jan 28, 2026
5e57129
fix: remove unused exports flagged by knip
jay-comfy Jan 28, 2026
89a6c55
refactor: remove base model column from list view header
jay-comfy Jan 29, 2026
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
18 changes: 18 additions & 0 deletions packages/design-system/src/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,12 @@
--modal-card-tag-background: var(--color-smoke-200);
--modal-card-tag-foreground: var(--base-foreground);
--modal-panel-background: var(--color-white);

/* Table tokens */
--table-row-even-background: #f9fafb;
--table-row-odd-background: #ffffff;
--table-row-hover-background: #f3f4f6;
--table-header-background: #f5f5f5;
Comment on lines +320 to +325
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider reusing existing palette tokens for table backgrounds.

Hard-coded hex values duplicate palette intent and make global theming harder. Consider mapping these to existing palette variables for consistency.

🤖 Prompt for AI Agents
In `@packages/design-system/src/css/style.css` around lines 320 - 325, The table
CSS currently hard-codes hex values for --table-row-even-background,
--table-row-odd-background, --table-row-hover-background, and
--table-header-background; replace those literal colors by mapping them to the
project's palette tokens (e.g., use the existing gray/neutral palette variables)
so global theming is respected — update the declarations for the four variables
to reference the appropriate palette variables (use the closest matching tokens
for even/odd/hover/header states) instead of hex values.

}

.dark-theme {
Expand Down Expand Up @@ -454,6 +460,12 @@
--modal-card-tag-background: var(--color-ash-800);
--modal-card-tag-foreground: var(--base-foreground);
--modal-panel-background: var(--color-charcoal-600);

/* Table tokens */
--table-row-even-background: #1e1e1e;
--table-row-odd-background: #252525;
--table-row-hover-background: #2a2a2a;
--table-header-background: #1a1a1a;
}

@theme inline {
Expand Down Expand Up @@ -590,6 +602,12 @@
--color-accent-background: var(--accent-background);
--color-brand-yellow: var(--brand-yellow);
--color-brand-blue: var(--brand-blue);

/* Table tokens */
--color-table-row-even-background: var(--table-row-even-background);
--color-table-row-odd-background: var(--table-row-odd-background);
--color-table-row-hover-background: var(--table-row-hover-background);
--color-table-header-background: var(--table-header-background);
}

@custom-variant dark-theme {
Expand Down
11 changes: 8 additions & 3 deletions src/components/sidebar/SideToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPa
import SidebarSettingsButton from '@/components/sidebar/SidebarSettingsButton.vue'
import SidebarShortcutsToggleButton from '@/components/sidebar/SidebarShortcutsToggleButton.vue'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { useModelBrowserDialog } from '@/platform/models/browser/composables/useModelBrowserDialog'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
Expand Down Expand Up @@ -115,7 +116,8 @@ const selectedTab = computed(() => workspaceStore.sidebarTab.activeSidebarTab)
/**
* Handle sidebar tab icon click.
* - Emits UI button telemetry for known tabs
* - Delegates to the corresponding toggle command
* - For model-library tab, opens model browser dialog instead
* - Delegates to the corresponding toggle command for other tabs
*/
const onTabClick = async (item: SidebarTabExtension) => {
const telemetry = useTelemetry()
Expand All @@ -129,11 +131,14 @@ const onTabClick = async (item: SidebarTabExtension) => {
telemetry?.trackUiButtonClicked({
button_id: 'sidebar_tab_node_library_selected'
})
else if (isModelLibraryTab)
else if (isModelLibraryTab) {
telemetry?.trackUiButtonClicked({
button_id: 'sidebar_tab_model_library_selected'
})
else if (isWorkflowsTab)
const { show } = useModelBrowserDialog()
show()
return
} else if (isWorkflowsTab)
telemetry?.trackUiButtonClicked({
button_id: 'sidebar_tab_workflows_selected'
})
Expand Down
1 change: 1 addition & 0 deletions src/components/widget/panel/LeftSidePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
>
<div v-if="'items' in item" class="flex flex-col gap-2">
<NavTitle
v-if="item.title"
v-model="collapsedGroups[item.title]"
:title="item.title"
:collapsible="item.collapsible"
Expand Down
48 changes: 48 additions & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"searchSettings": "Search Settings",
"searchNodes": "Search Nodes",
"searchModels": "Search Models",
"browseModels": "Browse Models",
"searchKeybindings": "Search Keybindings",
"searchExtensions": "Search Extensions",
"search": "Search",
Expand Down Expand Up @@ -2824,5 +2825,52 @@
"label": "Preview Version",
"tooltip": "You are using a nightly version of ComfyUI. Please use the feedback button to share your thoughts about these features."
}
},
"modelBrowser": {
"title": "Model Library",
"use": "Use",
"showInfo": "Show Info",
"viewDetails": "View details",
"modelInfo": "Model Info",
"selectModelToViewInfo": "Select a model to view details",
"searchPlaceholder": "Search models...",
"allModels": "All Models",
"filterByType": "BY TYPE",
"noModels": "No models found",
"noModelsForFilter": "No models match your filters",
"noFavorites": "No favorite models",
"loading": "Loading models...",
"error": "Failed to load models",
"errorLoading": "Failed to load models",
"retry": "Retry",
"clearFilters": "Clear Filters",
"columns": {
"modelName": "Model name",
"baseModel": "Base model",
"modelType": "Model type",
"fileSize": "File size",
"dateModified": "Date modified"
},
"basicInfo": "Basic Info",
"displayName": "Display Name",
"fileName": "File Name",
"filePath": "File Path",
"fileSize": "File Size",
"lastModified": "Last Modified",
"modelTagging": "Model Tagging",
"modelType": "Model Type",
"additionalTags": "Additional Tags",
"modelDescription": "Model Description",
"description": "Description",
"sortBy": "Sort by",
"sortAZ": "A-Z",
"sortZA": "Z-A",
"sortBySize": "Size",
"sortRecent": "Date",
"fileFormats": "File Formats",
"modelTypes": "Model Types",
"viewGrid": "Grid view",
"viewList": "List view",
"bookmarked": "Bookmarked"
}
}
84 changes: 9 additions & 75 deletions src/platform/assets/utils/createModelNodeFromAsset.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import type { LGraphNode, Point } from '@/lib/litegraph/src/litegraph'
import { assetItemSchema } from '@/platform/assets/schemas/assetSchema'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
Expand All @@ -7,10 +6,7 @@ import {
MODELS_TAG
} from '@/platform/assets/services/assetService'
import { getAssetFilename } from '@/platform/assets/utils/assetMetadataUtils'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { app } from '@/scripts/app'
import { useLitegraphService } from '@/services/litegraphService'
import { useModelToNodeStore } from '@/stores/modelToNodeStore'
import { createNodeFromModel } from '@/utils/nodeCreation/createNodeFromModel'

interface CreateNodeOptions {
position?: Point
Expand Down Expand Up @@ -117,81 +113,19 @@ export function createModelNodeFromAsset(
}
}

const modelToNodeStore = useModelToNodeStore()
const provider = modelToNodeStore.getNodeProvider(category)
if (!provider) {
console.error(`No node provider registered for category: ${category}`)
return {
success: false,
error: {
code: 'NO_PROVIDER',
message: `No node provider registered for category: ${category}`,
assetId: validAsset.id,
details: { category }
}
}
}
// Use shared core function to create node
const result = createNodeFromModel(category, filename, validAsset.id, options)

const litegraphService = useLitegraphService()
const pos = options?.position ?? litegraphService.getCanvasCenter()

const node = LiteGraph.createNode(
provider.nodeDef.name,
provider.nodeDef.display_name,
{ pos }
)

if (!node) {
console.error(`Failed to create node for type: ${provider.nodeDef.name}`)
if (!result.success) {
// Map generic error to asset-specific error format
return {
success: false,
error: {
code: 'NODE_CREATION_FAILED',
message: `Failed to create node for type: ${provider.nodeDef.name}`,
assetId: validAsset.id,
details: { nodeType: provider.nodeDef.name }
}
...result.error,
assetId: result.error.itemId
} as NodeCreationError
Comment on lines +116 to +126
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Avoid type assertion by remapping itemId explicitly.

as NodeCreationError hides the extra itemId field. Remap and drop it so the returned error matches the declared shape.

♻️ Proposed fix
   if (!result.success) {
-    // Map generic error to asset-specific error format
-    return {
-      success: false,
-      error: {
-        ...result.error,
-        assetId: result.error.itemId
-      } as NodeCreationError
-    }
+    const { itemId, ...rest } = result.error
+    return {
+      success: false,
+      error: {
+        ...rest,
+        assetId: itemId
+      }
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Use shared core function to create node
const result = createNodeFromModel(category, filename, validAsset.id, options)
const litegraphService = useLitegraphService()
const pos = options?.position ?? litegraphService.getCanvasCenter()
const node = LiteGraph.createNode(
provider.nodeDef.name,
provider.nodeDef.display_name,
{ pos }
)
if (!node) {
console.error(`Failed to create node for type: ${provider.nodeDef.name}`)
if (!result.success) {
// Map generic error to asset-specific error format
return {
success: false,
error: {
code: 'NODE_CREATION_FAILED',
message: `Failed to create node for type: ${provider.nodeDef.name}`,
assetId: validAsset.id,
details: { nodeType: provider.nodeDef.name }
}
...result.error,
assetId: result.error.itemId
} as NodeCreationError
// Use shared core function to create node
const result = createNodeFromModel(category, filename, validAsset.id, options)
if (!result.success) {
const { itemId, ...rest } = result.error
return {
success: false,
error: {
...rest,
assetId: itemId
}
}
}
🤖 Prompt for AI Agents
In `@src/platform/assets/utils/createModelNodeFromAsset.ts` around lines 116 -
126, The function calling createNodeFromModel returns result.error with a type
assertion to NodeCreationError; instead explicitly build and return a
NodeCreationError object by mapping result.error.message, result.error.code (and
any other expected NodeCreationError fields) and set assetId =
result.error.itemId, omitting itemId, instead of using "as NodeCreationError";
update the error return in createModelNodeFromAsset to construct the exact shape
required (refer to createNodeFromModel's result and the NodeCreationError type)
so no type assertion is needed.

}
}
Comment on lines +119 to 128
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Same type assertion pattern as createModelNode.ts.

Similar to createModelNode.ts, the as NodeCreationError assertion could be avoided by explicitly constructing the error object. This would also prevent the extra itemId property from being included.

♻️ Proposed refactor
   if (!result.success) {
-    // Map generic error to asset-specific error format
     return {
       success: false,
       error: {
-        ...result.error,
-        assetId: result.error.itemId
-      } as NodeCreationError
+        code: result.error.code,
+        message: result.error.message,
+        assetId: result.error.itemId,
+        details: result.error.details
+      }
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!result.success) {
// Map generic error to asset-specific error format
return {
success: false,
error: {
code: 'NODE_CREATION_FAILED',
message: `Failed to create node for type: ${provider.nodeDef.name}`,
assetId: validAsset.id,
details: { nodeType: provider.nodeDef.name }
}
...result.error,
assetId: result.error.itemId
} as NodeCreationError
}
}
if (!result.success) {
return {
success: false,
error: {
code: result.error.code,
message: result.error.message,
assetId: result.error.itemId,
details: result.error.details
}
}
}
🤖 Prompt for AI Agents
In `@src/platform/assets/utils/createModelNodeFromAsset.ts` around lines 119 -
128, The returned error construction in createModelNodeFromAsset.ts currently
spreads result.error and casts to NodeCreationError, which leaks itemId and
relies on a type assertion; instead explicitly build and return a
NodeCreationError object using known fields from result.error (e.g., code,
message, details) and set assetId from result.error.itemId (or the correct
source) without including itemId, and remove the "as NodeCreationError" cast so
the shape is concrete and typesafe.


const workflowStore = useWorkflowStore()
const targetGraph = workflowStore.isSubgraphActive
? workflowStore.activeSubgraph
: app.canvas.graph

if (!targetGraph) {
console.error('No active graph available')
return {
success: false,
error: {
code: 'NO_GRAPH',
message: 'No active graph available',
assetId: validAsset.id
}
}
}

const widget = node.widgets?.find((w) => w.name === provider.key)
if (!widget) {
console.error(
`Widget ${provider.key} not found on node ${provider.nodeDef.name}`
)
return {
success: false,
error: {
code: 'MISSING_WIDGET',
message: `Widget ${provider.key} not found on node ${provider.nodeDef.name}`,
assetId: validAsset.id,
details: { widgetName: provider.key, nodeType: provider.nodeDef.name }
}
}
}

// Set widget value BEFORE adding to graph so the node is created with correct value
widget.value = filename

// Now add the node to the graph with the correct widget value already set
targetGraph.add(node)

return { success: true, value: node }
return result
}
Loading